From a7108dc0256df4e3f10de709c1f9678a4dab7a31 Mon Sep 17 00:00:00 2001 From: Hanno Spreeuw Date: Wed, 21 Jun 2017 16:19:52 +0200 Subject: [PATCH] Major cleanup - in particular of files that should not be tracked, i.e. should not be included in the repo. --- .gitignore | 25 + src/MS/Makefile | 3 +- src/MS/Makefile.gpu | 3 +- src/MS/lib | 1 - src/MS/main.cpp | 40 +- src/lib/Dirac/.gitignore | 2 - src/lib/Dirac/Makefile.gpu | 3 +- src/lib/Makefile | 66 - src/lib/Makefile.MIC | 66 - src/lib/Makefile.gpu | 108 - src/lib/Radio/Makefile.gpu | 4 +- src/lib/Radio/libsagecal-gpu.a | Bin 1231450 -> 0 bytes src/lib/Radio/predict_model.cu | 5 +- src/lib/Radio/reserve/radio-reserve.h | 470 --- src/lib/Solvers/.Common.h.swp | Bin 57344 -> 0 bytes src/lib/Solvers/.Dirac.h.swp | Bin 102402 -> 0 bytes src/lib/Solvers/.gitignore | 2 - src/lib/Solvers/Common.h | 1162 ------- src/lib/Solvers/Dirac.h | 1479 -------- src/lib/Solvers/Makefile | 54 - src/lib/Solvers/Makefile.gpu | 88 - src/lib/Solvers/Solvers.h | 1479 -------- src/lib/Solvers/admm_solve.c | 1482 -------- src/lib/Solvers/admm_solve.o | Bin 14944 -> 0 bytes src/lib/Solvers/barrier.c | 121 - src/lib/Solvers/barrier.o | Bin 11496 -> 0 bytes src/lib/Solvers/clmfit.c | 1978 ----------- src/lib/Solvers/clmfit.o | Bin 101512 -> 0 bytes src/lib/Solvers/clmfit_fl.c | 1116 ------ src/lib/Solvers/clmfit_fl.o | Bin 68120 -> 0 bytes src/lib/Solvers/clmfit_nocuda.c | 1666 --------- src/lib/Solvers/clmfit_nocuda.o | Bin 27368 -> 0 bytes src/lib/Solvers/consensus_poly.c | 349 -- src/lib/Solvers/consensus_poly.o | Bin 9136 -> 0 bytes src/lib/Solvers/diag_fl.cu | 270 -- src/lib/Solvers/diag_fl.o | Bin 26376 -> 0 bytes src/lib/Solvers/diagnostics.c | 550 --- src/lib/Solvers/diagnostics.o | Bin 50208 -> 0 bytes src/lib/Solvers/lbfgs.c | 1106 ------ src/lib/Solvers/lbfgs.o | Bin 80704 -> 0 bytes src/lib/Solvers/lbfgs_nocuda.c | 926 ----- src/lib/Solvers/lbfgs_nocuda.o | Bin 20680 -> 0 bytes src/lib/Solvers/libdirac-gpu.a | Bin 1519392 -> 0 bytes src/lib/Solvers/libdirac.a | Bin 392388 -> 0 bytes src/lib/Solvers/libsolvers-gpu.a | Bin 2206404 -> 0 bytes src/lib/Solvers/lmfit.c | 2171 ------------ src/lib/Solvers/lmfit.o | Bin 141256 -> 0 bytes src/lib/Solvers/lmfit_nocuda.c | 1283 ------- src/lib/Solvers/lmfit_nocuda.o | Bin 29808 -> 0 bytes src/lib/Solvers/load_balance.c | 161 - src/lib/Solvers/load_balance.o | Bin 18616 -> 0 bytes src/lib/Solvers/manifold_average.c | 627 ---- src/lib/Solvers/manifold_average.o | Bin 20680 -> 0 bytes src/lib/Solvers/manifold_fl.cu | 2493 -------------- src/lib/Solvers/manifold_fl.o | Bin 166944 -> 0 bytes src/lib/Solvers/mderiv.cu | 1625 --------- src/lib/Solvers/mderiv.o | Bin 107592 -> 0 bytes src/lib/Solvers/mderiv_fl.cu | 380 -- src/lib/Solvers/mderiv_fl.o | Bin 26832 -> 0 bytes src/lib/Solvers/myblas.c | 462 --- src/lib/Solvers/myblas.o | Bin 9264 -> 0 bytes src/lib/Solvers/oslmfit.c | 705 ---- src/lib/Solvers/oslmfit.o | Bin 47200 -> 0 bytes src/lib/Solvers/residual.c | 1711 --------- src/lib/Solvers/residual.o | Bin 63112 -> 0 bytes src/lib/Solvers/robust.cu | 721 ---- src/lib/Solvers/robust.o | Bin 63008 -> 0 bytes src/lib/Solvers/robust_fl.cu | 536 --- src/lib/Solvers/robust_fl.o | Bin 51456 -> 0 bytes src/lib/Solvers/robust_lbfgs_nocuda.c | 1063 ------ src/lib/Solvers/robust_lbfgs_nocuda.o | Bin 20344 -> 0 bytes src/lib/Solvers/robustlm.c | 3248 ------------------ src/lib/Solvers/robustlm.o | Bin 21048 -> 0 bytes src/lib/Solvers/rtr_solve.c | 1604 --------- src/lib/Solvers/rtr_solve.o | Bin 40520 -> 0 bytes src/lib/Solvers/rtr_solve_cuda.c | 894 ----- src/lib/Solvers/rtr_solve_cuda.o | Bin 89704 -> 0 bytes src/lib/Solvers/rtr_solve_robust.c | 2246 ------------ src/lib/Solvers/rtr_solve_robust.o | Bin 53904 -> 0 bytes src/lib/Solvers/rtr_solve_robust_admm.c | 2009 ----------- src/lib/Solvers/rtr_solve_robust_admm.o | Bin 48136 -> 0 bytes src/lib/Solvers/rtr_solve_robust_cuda.c | 1262 ------- src/lib/Solvers/rtr_solve_robust_cuda.o | Bin 102784 -> 0 bytes src/lib/Solvers/rtr_solve_robust_cuda_admm.c | 1272 ------- src/lib/Solvers/rtr_solve_robust_cuda_admm.o | Bin 109064 -> 0 bytes src/lib/Solvers/updatenu.c | 443 --- src/lib/Solvers/updatenu.o | Bin 11096 -> 0 bytes src/lib/admm_solve.c | 1482 -------- src/lib/barrier.c | 121 - src/lib/clmfit.c | 1978 ----------- src/lib/clmfit_fl.c | 1116 ------ src/lib/clmfit_nocuda.c | 1666 --------- src/lib/consensus_poly.c | 349 -- src/lib/dataio.c | 82 - src/lib/diag_fl.cu | 270 -- src/lib/diagnostics.c | 550 --- src/lib/lbfgs.c | 1106 ------ src/lib/lbfgs_nocuda.c | 926 ----- src/lib/lmfit.c | 2171 ------------ src/lib/lmfit_nocuda.c | 1283 ------- src/lib/load_balance.c | 161 - src/lib/manifold_average.c | 627 ---- src/lib/manifold_fl.cu | 2493 -------------- src/lib/mderiv.cu | 1625 --------- src/lib/mderiv_fl.cu | 380 -- src/lib/myblas.c | 462 --- src/lib/oslmfit.c | 705 ---- src/lib/predict.c | 1693 --------- src/lib/predict_model.cu | 832 ----- src/lib/predict_withbeam.c | 1358 -------- src/lib/predict_withbeam_gpu.c | 1061 ------ src/lib/readsky.c | 809 ----- src/lib/residual.c | 1711 --------- src/lib/robust.cu | 721 ---- src/lib/robust_fl.cu | 536 --- src/lib/robust_lbfgs_nocuda.c | 1063 ------ src/lib/robustlm.c | 3248 ------------------ src/lib/rtr_solve.c | 1604 --------- src/lib/rtr_solve_cuda.c | 894 ----- src/lib/rtr_solve_robust.c | 2246 ------------ src/lib/rtr_solve_robust_admm.c | 2009 ----------- src/lib/rtr_solve_robust_cuda.c | 1262 ------- src/lib/rtr_solve_robust_cuda_admm.c | 1272 ------- src/lib/sagecal.h | 2641 -------------- src/lib/stationbeam.c | 112 - src/lib/transforms.c | 289 -- src/lib/updatenu.c | 443 --- test/Generate_sources.py | 1 + test/dosage.sh | 2 +- 129 files changed, 57 insertions(+), 86843 deletions(-) create mode 100644 .gitignore delete mode 120000 src/MS/lib delete mode 100644 src/lib/Dirac/.gitignore delete mode 100644 src/lib/Makefile delete mode 100644 src/lib/Makefile.MIC delete mode 100644 src/lib/Makefile.gpu delete mode 100644 src/lib/Radio/libsagecal-gpu.a delete mode 100644 src/lib/Radio/reserve/radio-reserve.h delete mode 100644 src/lib/Solvers/.Common.h.swp delete mode 100644 src/lib/Solvers/.Dirac.h.swp delete mode 100644 src/lib/Solvers/.gitignore delete mode 100644 src/lib/Solvers/Common.h delete mode 100644 src/lib/Solvers/Dirac.h delete mode 100644 src/lib/Solvers/Makefile delete mode 100644 src/lib/Solvers/Makefile.gpu delete mode 100644 src/lib/Solvers/Solvers.h delete mode 100644 src/lib/Solvers/admm_solve.c delete mode 100644 src/lib/Solvers/admm_solve.o delete mode 100644 src/lib/Solvers/barrier.c delete mode 100644 src/lib/Solvers/barrier.o delete mode 100644 src/lib/Solvers/clmfit.c delete mode 100644 src/lib/Solvers/clmfit.o delete mode 100644 src/lib/Solvers/clmfit_fl.c delete mode 100644 src/lib/Solvers/clmfit_fl.o delete mode 100644 src/lib/Solvers/clmfit_nocuda.c delete mode 100644 src/lib/Solvers/clmfit_nocuda.o delete mode 100644 src/lib/Solvers/consensus_poly.c delete mode 100644 src/lib/Solvers/consensus_poly.o delete mode 100644 src/lib/Solvers/diag_fl.cu delete mode 100644 src/lib/Solvers/diag_fl.o delete mode 100644 src/lib/Solvers/diagnostics.c delete mode 100644 src/lib/Solvers/diagnostics.o delete mode 100644 src/lib/Solvers/lbfgs.c delete mode 100644 src/lib/Solvers/lbfgs.o delete mode 100644 src/lib/Solvers/lbfgs_nocuda.c delete mode 100644 src/lib/Solvers/lbfgs_nocuda.o delete mode 100644 src/lib/Solvers/libdirac-gpu.a delete mode 100644 src/lib/Solvers/libdirac.a delete mode 100644 src/lib/Solvers/libsolvers-gpu.a delete mode 100644 src/lib/Solvers/lmfit.c delete mode 100644 src/lib/Solvers/lmfit.o delete mode 100644 src/lib/Solvers/lmfit_nocuda.c delete mode 100644 src/lib/Solvers/lmfit_nocuda.o delete mode 100644 src/lib/Solvers/load_balance.c delete mode 100644 src/lib/Solvers/load_balance.o delete mode 100644 src/lib/Solvers/manifold_average.c delete mode 100644 src/lib/Solvers/manifold_average.o delete mode 100644 src/lib/Solvers/manifold_fl.cu delete mode 100644 src/lib/Solvers/manifold_fl.o delete mode 100644 src/lib/Solvers/mderiv.cu delete mode 100644 src/lib/Solvers/mderiv.o delete mode 100644 src/lib/Solvers/mderiv_fl.cu delete mode 100644 src/lib/Solvers/mderiv_fl.o delete mode 100644 src/lib/Solvers/myblas.c delete mode 100644 src/lib/Solvers/myblas.o delete mode 100644 src/lib/Solvers/oslmfit.c delete mode 100644 src/lib/Solvers/oslmfit.o delete mode 100644 src/lib/Solvers/residual.c delete mode 100644 src/lib/Solvers/residual.o delete mode 100644 src/lib/Solvers/robust.cu delete mode 100644 src/lib/Solvers/robust.o delete mode 100644 src/lib/Solvers/robust_fl.cu delete mode 100644 src/lib/Solvers/robust_fl.o delete mode 100644 src/lib/Solvers/robust_lbfgs_nocuda.c delete mode 100644 src/lib/Solvers/robust_lbfgs_nocuda.o delete mode 100644 src/lib/Solvers/robustlm.c delete mode 100644 src/lib/Solvers/robustlm.o delete mode 100644 src/lib/Solvers/rtr_solve.c delete mode 100644 src/lib/Solvers/rtr_solve.o delete mode 100644 src/lib/Solvers/rtr_solve_cuda.c delete mode 100644 src/lib/Solvers/rtr_solve_cuda.o delete mode 100644 src/lib/Solvers/rtr_solve_robust.c delete mode 100644 src/lib/Solvers/rtr_solve_robust.o delete mode 100644 src/lib/Solvers/rtr_solve_robust_admm.c delete mode 100644 src/lib/Solvers/rtr_solve_robust_admm.o delete mode 100644 src/lib/Solvers/rtr_solve_robust_cuda.c delete mode 100644 src/lib/Solvers/rtr_solve_robust_cuda.o delete mode 100644 src/lib/Solvers/rtr_solve_robust_cuda_admm.c delete mode 100644 src/lib/Solvers/rtr_solve_robust_cuda_admm.o delete mode 100644 src/lib/Solvers/updatenu.c delete mode 100644 src/lib/Solvers/updatenu.o delete mode 100644 src/lib/admm_solve.c delete mode 100644 src/lib/barrier.c delete mode 100644 src/lib/clmfit.c delete mode 100644 src/lib/clmfit_fl.c delete mode 100644 src/lib/clmfit_nocuda.c delete mode 100644 src/lib/consensus_poly.c delete mode 100644 src/lib/dataio.c delete mode 100644 src/lib/diag_fl.cu delete mode 100644 src/lib/diagnostics.c delete mode 100644 src/lib/lbfgs.c delete mode 100644 src/lib/lbfgs_nocuda.c delete mode 100644 src/lib/lmfit.c delete mode 100644 src/lib/lmfit_nocuda.c delete mode 100644 src/lib/load_balance.c delete mode 100644 src/lib/manifold_average.c delete mode 100644 src/lib/manifold_fl.cu delete mode 100644 src/lib/mderiv.cu delete mode 100644 src/lib/mderiv_fl.cu delete mode 100644 src/lib/myblas.c delete mode 100644 src/lib/oslmfit.c delete mode 100644 src/lib/predict.c delete mode 100644 src/lib/predict_model.cu delete mode 100644 src/lib/predict_withbeam.c delete mode 100644 src/lib/predict_withbeam_gpu.c delete mode 100644 src/lib/readsky.c delete mode 100644 src/lib/residual.c delete mode 100644 src/lib/robust.cu delete mode 100644 src/lib/robust_fl.cu delete mode 100644 src/lib/robust_lbfgs_nocuda.c delete mode 100644 src/lib/robustlm.c delete mode 100644 src/lib/rtr_solve.c delete mode 100644 src/lib/rtr_solve_cuda.c delete mode 100644 src/lib/rtr_solve_robust.c delete mode 100644 src/lib/rtr_solve_robust_admm.c delete mode 100644 src/lib/rtr_solve_robust_cuda.c delete mode 100644 src/lib/rtr_solve_robust_cuda_admm.c delete mode 100644 src/lib/sagecal.h delete mode 100644 src/lib/stationbeam.c delete mode 100644 src/lib/transforms.c delete mode 100644 src/lib/updatenu.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8d2dcda --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +*/*.png +*/*.out +*/*.output +*/*.solutions +*/*.dot +test/sm.ms/ +test/nvprof-resultaten/ +src/MS/sagecal +test/analysis-*.txt +test/extended_*.* + +*/*/*.a +*/*/*.o +*/*/*.swp +*/*/*.swo +*/*/*.out +*/*/*.output + +*/*/*/*.o +*/*/*/*.a +*/*/*/*.swp +*/*/*/*.swo +*/*/*/*.out +*/*/*/*.output + diff --git a/src/MS/Makefile b/src/MS/Makefile index 1fafa54..28c842e 100644 --- a/src/MS/Makefile +++ b/src/MS/Makefile @@ -32,4 +32,5 @@ data.o:data.cpp data.h sagecal:$(OBJECTS) ../lib/Radio/libsagecal.a ../lib/Dirac/libdirac.a $(CXX) $(CXXFLAGS) $(LDFLAGS) $(INCLUDES) $(GLIBI) $(LIBPATH) -o $@ $(OBJECTS) $(MY_LIBS) $(LAPACK) $(CASA_LIBS) $(GLIBL) clean: - rm *.o *.tmp *.fits + rm *.o *.tmp *.fits *.swp *.swo *.o *.output + diff --git a/src/MS/Makefile.gpu b/src/MS/Makefile.gpu index aea5e14..bbaddf9 100644 --- a/src/MS/Makefile.gpu +++ b/src/MS/Makefile.gpu @@ -1,11 +1,10 @@ OUTPUT= CXX=g++ CXXFLAGS=-O3 -Wall -g -DHAVE_CUDA -# CXXFLAGS=-O3 -Wall -g -DHAVE_CUDA -DONE_GPU CASA_LIBDIR=-L/cm/shared/package/casacore/v2.1.0-gcc-4.9.3/lib -L/cm/shared/package/cfitsio/3380-gcc-4.9.3/lib -L/cm/shared/package/lapack/3.6.0-gcc-4.9.3/lib64 CASA_INCDIR=-I/cm/shared/package/casacore/v2.1.0-g++-4.9.3/include -I/cm/shared/package/casacore/v2.1.0-g++-4.9.3/include/casacore CASA_LIBS=-lcasa_casa -lcasa_tables -lcasa_measures -lcasa_ms -lcfitsio -#LAPACK=-llapack -lblas +# LAPACK=-llapack -lblas LAPACK=-lopenblas -lgfortran -lpthread LAPACK_DIR=/cm/shared/apps/openblas/0.2.8/lib #LAPACK_DIR=/usr/lib/atlas/sse/ diff --git a/src/MS/lib b/src/MS/lib deleted file mode 120000 index dc598c5..0000000 --- a/src/MS/lib +++ /dev/null @@ -1 +0,0 @@ -../lib \ No newline at end of file diff --git a/src/MS/main.cpp b/src/MS/main.cpp index 05bb0a0..f2e0c4f 100644 --- a/src/MS/main.cpp +++ b/src/MS/main.cpp @@ -236,11 +236,7 @@ main(int argc, char **argv) { Data::readMSlist(Data::MSlist,&msnames); } if (Data::TableName) { - if (!doBeam) { - Data::readAuxData(Data::TableName,&iodata); - } else { Data::readAuxData(Data::TableName,&iodata,&beam); - } cout<<"Only one MS"<more()) { start_time = time(0); if (iodata.Nms==1) { - if (!doBeam) { - Data::loadData(msitr[0]->table(),iodata,&iodata.fratio); - } else { Data::loadData(msitr[0]->table(),iodata,beam,&iodata.fratio); - } } else { Data::loadDataList(msitr,iodata,&iodata.fratio); } @@ -449,15 +442,9 @@ main(int argc, char **argv) { preset_flags_and_data(iodata.Nbase*iodata.tilesz,iodata.flag,barr,iodata.x,Data::Nt); /* if data is being whitened, whiten x here, no need for a copy because we use xo for residual calculation */ - if (Data::whiten) { - whiten_data(iodata.Nbase*iodata.tilesz,iodata.x,iodata.u,iodata.v,iodata.freq0,Data::Nt); - } /* precess source locations (also beam pointing) from J2000 to JAPP if we do any beam predictions, using first time slot as epoch */ - if (doBeam && !sources_precessed) { - precess_source_locations(beam.time_utc[iodata.tilesz/2],carr,M,&beam.p_ra0,&beam.p_dec0,Data::Nt); - sources_precessed=1; - } + // sources_precessed=1; @@ -467,6 +454,9 @@ main(int argc, char **argv) { #ifdef HAVE_CUDA precalculate_coherencies_withbeam_gpu(iodata.u,iodata.v,iodata.w,coh,iodata.N,iodata.Nbase*iodata.tilesz,barr,carr,M,iodata.freq0,iodata.deltaf,iodata.deltat,iodata.dec0,Data::min_uvcut,Data::max_uvcut, beam.p_ra0,beam.p_dec0,iodata.freq0,beam.sx,beam.sy,beam.time_utc,iodata.tilesz,beam.Nelem,beam.xx,beam.yy,beam.zz,doBeam,Data::Nt); +#endif +#ifndef HAVE_CUDA + precalculate_coherencies(iodata.u,iodata.v,iodata.w,coh,iodata.N,iodata.Nbase*iodata.tilesz,barr,carr,M,iodata.freq0,iodata.deltaf,iodata.deltat,iodata.dec0,Data::min_uvcut,Data::max_uvcut,Data::Nt); #endif /****************** end calibration **************************/ /****************** begin diagnostics ************************/ @@ -475,7 +465,17 @@ main(int argc, char **argv) { predict_visibilities_multifreq_withbeam_gpu(iodata.u,iodata.v,iodata.w,iodata.xo,iodata.N,iodata.Nbase,iodata.tilesz,barr,carr,M,iodata.freqs,iodata.Nchan,iodata.deltaf,iodata.deltat,iodata.dec0, beam.p_ra0,beam.p_dec0,iodata.freq0,beam.sx,beam.sy,beam.time_utc,beam.Nelem,beam.xx,beam.yy,beam.zz,doBeam,Data::Nt,(Data::DoSim>1?1:0)); #endif - } +#ifndef HAVE_CUDA + precalculate_coherencies_withbeam(iodata.u,iodata.v,iodata.w,coh,iodata.N,iodata.Nbase*iodata.tilesz,barr,carr,M,iodata.freq0,iodata.deltaf,iodata.deltat,iodata.dec0,Data::min_uvcut,Data::max_uvcut, + beam.p_ra0,beam.p_dec0,iodata.freq0,beam.sx,beam.sy,beam.time_utc,iodata.tilesz,beam.Nelem,beam.xx,beam.yy,beam.zz,Data::Nt); +#endif +} + +#ifdef HAVE_CUDA + cudaDeviceSynchronize(); + cudaProfilerStop(); + exit(0); +#endif tilex+=iodata.tilesz; /* print solutions to file */ @@ -495,10 +495,6 @@ main(int argc, char **argv) { for(int cm=0; cmI zjD{6mcGm@5^|$OU9_zX;cm`1pab3I+bzSjXA|8mVvR?e(t9q}yrjlX(?QcK-&;Rpp znC@5KdiCC`SFft8t2^BT#}(BE%Pvb8=8fLcGcrev8Zl<{=nStnO=PKiuXp&!krMDO zPB08(f?*^)8vXm{X2bX|{LC{Gj81>ew;1L#&qe*ZRBkr#*ERf{_<5>sG5%lp)rN|S z19eMR27(p!b>UF0A%W_O`fz!5Rh>~^Q(P1d1g(#ti>hp(w!Es;P865dEwvNj^2$)4t};|qYt+<+ zipzuHz_RkX@+IXJ<>B&B9fHB4ieP;Onh~rn3)P0If|vn@*47qPmHr5HxWc@os1E8; zB_-E^%_S8@rFDU#s^S3Dw8$u~u3u6S3WTA071c#yL+mjrGmO$uRj3wiaRddc>#M>_ z8l)}{m6w)<>x>{+3B1)+p=(36)yqObqof=()zw$l)K(YQ2gAmSmDdihs3t*#2x6x9}0N(V{AK!>bc5-O^U>9xS}@^Be* zbZ~Z{vc4i*UQ!#n#^H0EUmL0`FRm}DsOyZ4>< z8ht0KTv=Tlst5#YYSIJc<$>YBCB?zCk!i+hVoT$!G^G*nxKR#aCl3)O}LSf;$77_-LMS$43O z<5o~ID=;fike<$w7YIYz`BzY;&;{isCBR<}0*7aG6d0(hC|VX0V#N-arFbc#Q&A`@ zeKg4up%%bMQcNo@C@%m3!^MBFOqeh$0OdxcbtorI2-KBV1*_}OS~ft~+))ql|F4)q zxGp(;wAQTxXr|PT6&!g5j4PqKa6$F*Q0?@R;iA@~^Z`w{7ukoaBb$S&LmZ!;wu~GT z;K;2D*Dnb`xWsh5?oHjb{~K9)GwQOS+h}(EuR^GUxJ+H#e-z0-D%Qtz(Dh&If&cps zo0-v};{Vn0MOgHoj!YS89gK@HE{dTsP*N0LQeL&Zwy5U+Hqslz^eJQX1hPYF)EI^q zC-7N>&!nlFmn#*t?T#Bq`ZlP(~oT%NI_p7gZQqn&-)s8KMB9ho^MeUxBb zoUkT0|8lp>1rt}fxs1yUC&j2?Y+CQoS%S9%3pSqY8QJaG_;lmR1kc7c>uJxrmk|B- zl&5Jw5P~N!vd7Tezo5{#YIu!G6JVLXXD!SfUv zB`9H+zwsGs2SQgZ%wCYaFndvUV7F&LmXZEROZKVE!(lJhYEN9UfsB!(3fB{&cCQOF zcDb`7J;035qs-X1b0r8STVk3~BK-rk$=)018vrGq#GNY1v+fT+oI1603Bbo56z4O$ zeVz9F=bTxhme`^eP2s8kbjK;!phBLed2v|73ZK6@?3=yrE6=)Hy8!^bg#7!qzop5S z1r9Fwu|yg(V_z1fPD2zhk47|a^d&=9dRu@kJkc#?!O{nleA~QGVnHe`K%VdJ1Dg>cD1t z*3%T}jm`bpS0_A4O}>MW3&bE)T;fSR4)o2w!vb9Nyyqvqx?^_XE`ZNP7Lk2K5d+97vC(%N2&r$;E1sXMglm3~@U+M}DR>sWEP?s^ zKop6E9^m!2JnU-#4u8wzzV(7LDTjIllzv!X%mctZ9htq*iNd}yurKzd3C{o@7^#%n zsFhlfgIzCaJ86VIm<7cvd?{S~PPRqlB<1^3PI%_O0l(R(Xnwvg4T^_-sCpsP6x9JN z)c{sc9_p2wbi%XXIbhi6!&n0k9fIs>mglDN7W9C1qhSxZ5BnaJRp0L0A&Gl@ZIWp6 z?Gr?=dQ?^kcJvB?@?N=E;anI|=D!I7ntWqGJS6rb6V$C&ZjFE~0r#pe0BBtUi%gPe zXfdMeCCbe^X9bwN8-CE{Gme7Ap8!*ZFWGZbQj#zh>UtE4fHnvPFe)h*$fRjjiDzD% zPvrQU9~Og#;7IX265n{_?QxSmO|9{UF)qi`^qK|7nVzN>Axy^2@-+2wq|M*bx53{suE4+RA90c$dtWY$#OWR|xEaf?d(+eObRwGH-v`5N zGFBxW)IZ(oZ;tcjw~RYGzuB7&WM1?2J}WyiIWD_7#}^00Vj(g+(lsygcD}g^&eIb> zk;#hkB9kpo(6O#vCwrP6J6*ol)AZ}p<)^?nbGrOAPgBk5^2fk{9+j`u zt-#YXF)B~1d!eUk$m#NHJWWZb%U|JX`WgZvmhXPj0PW;ydh>KF>jleGr(2xex1 z3FBT;R+^n0<8RJ`aV;rpjD$Ro)MQOupD_fj_cz!0F$=8BKdW#`B%|FQdEIYf#x+^C z1i_MQCvnKkDzK9<5GG|6a`F!iVC}T#HBh&tti?8pkpccgu*?QA77(ei5o~%yR@lf^ zi8M6e5uN%eMwe=WB$)%jLge zH=dOI%~_V15XQl^!bY5ef!g+qzddyd<^Nx%V>nrgF?YlBlgK&^v6y)@0w!oFkR>N* zn1>TIF%hQ;o2+Kz#|`bZU!6Jy=N;EmSx`|46MiPsT`g{WWyO4dj|l{dGQ3IUV(arIsv3eKx0dT3PdU~K*fm}Iu107 z=Vxu%k=!xuZ=O7b5eL^2Fqx9)ejyLK+HrBx#rX%Ex91-RxoNiCeYV^M;6u+%=L+64B`?lori}rar2 z{V233j4TI*DJMZZ_y^`Pg-;9bf*qC0z5-9q9(*3 zY!ArNCdKU^{!@gL`>8Z27Zpw~cSS)QP8bE4j0zS@0$LQvm{Nj8#uPLS&$VPsrME%x z4uO(_5!8M^_(VpV2PU#0VE)ZwG74^i8UW-Kken$Nq9?#GfSP=Anpn7tOLI$z$tTR7 zF)o0;!(5eSyO720QUGpdDgP%MdgZ3IKmPrHO;}3%r2n2UZ~JrKi3l(|M8LWqK+VoS zL6o<@Ev{?Bz7#)>x-v}QgasXSA71LF;5hAVe+zmz3{7!Q6@ribkuY7^i0i^K7MHIURmC2<2_CHzy&AvQOJ1)(?Li>IXQaewd7j<$k(W5 zj2x2Cwk&B|%ZeU(bnPk513A7l)DIK7UZ6ie@(o{^qTy-&M^j76!*!{Z)#3z`=h_R| zd66@-_fEEo;fA1;iF4;16!8T*1IA~(fEESBsJ|WRT#yfw6TckA@jQg1cmZ(XWj5yf zTQ-Vo48D4X^SrMh(&W1bdGY$;l#oYfwOm-(dD4O`;>h(pa4Ih-LsIxOH|fgxo~B#C zoZNM%!WG$#pSk>z1-`8ONU?9ll$O5VLs=s?{ggklYg**Hya>#IDtu%7k(ZBkhw~t; zj3wp4(i9rC5e;y?L#JeAxy{p*3F7lI|5kqxuNSiNn?ZDbD>8Vpn@ebpJ929VzX!8BLaYD#x z|Hr@n+Y-zFs}fVNO7hwo4DR7;zvaIw9!BT(B#;P7#94&qUH-@kiVJMGdhY%5YPr1v zMh_@(E{2*@K!h>$*Z_g_6wmDhEmeB=1btpav{vt-V7Jh^>OpXeu#Y8 z>3C_W`6^_oe-GA7Es?{EPSZdNOda2^{}YB}66MK@aQ_X@;2bKwO<8+QKXvRW!35U9 zKddjN2(O5DM&3u>I$T0AvF`JJfxbz_V#3??m{XNsr^XBA$7acdX59MnvsL@am3vD zODGVNbLTe5ZT~$?_a*ZKQOqQ#gL|h%j_f=f#d++NoqtD3OTa{@0b$paS08tWpHs04e>|bIGXhjT%d)U3#*~ZPbC2=S!w2c8=x!&mOj)$IB##R1?+GS z;YTZb`9m<5)=BeP=D=FXGpfYw#_wG9ecRDAv7VjX_yZXB^Z%WWYS5A2e2=JLc4SX> z!#JN2{?k)vs#CMQo+flXEaXe9WrfSh?CNP+FET&mb0PLEShN)^XcXKKu{`TAwnW#; zk9;ai!&%T?a}ulGS06?1CWJr2rTm`92082ahk0!Z`077x&j!Za^iSF!fY5Lnw0#8L zqA)QSH^aEF^AwoXUWwGFNCm}M5$D_4A36SxbCQ8b#R1pRLA-iSr z57{kqPl1JT{@~M*m;5a~8sO*m4>>5-`*yt(=WmHC$ZN^(vDm-sNF3h7v8J^Y$35r| z&V~hMG{^2KVO((r0Q*S)_eCEG_Y*Asmf61kYS`wk`wC|_mgkl`z*8`P$ZkFp+H&1X zYkD7Bj_a1$ExE3|#%Eob2UnE}5e*ZJ@JB*vr~0WENRV)9M8?%v%0O>@^`^n=e5+hGGDAenz!qSd(z&VE(WsVvedC(iTDiP z!SlGB$cG{xX~=~*OmgCIcW!#yv8N~$aSVPVDjGWs(-7oMpa@h!|C$!ri(M-_@+I9u z%#OSO)rWZMORMU1zo!L^V{@=|l>2JOik$HR?yK=lWEd~98$WTSziI2sd@Fn|cbN;| zQo!jlTrr+4G(MB!R1j{<`Xir$p&(6$SMXVAjr2FO8$X5la*of+Z-M^rZ^`%d#kK+8 zhz4a-*0rr(2~p8ECvrSz$Pp2f$^K32_Ty|QZtv=acwu4rgS%a?`-i~2!{h$u3?KBs zJ(0g3^N6_H?eaGswK5Nchi86K-zO(>B4^0E*iQc7OG3tL*_m%aY#VD{WtHfmWQdC{9xWpSQ3k8>YSHe@AUU)#oD;``K zm_RZo!Q;KeI>UU)XuyS%mv8^X3d-kYo#zB&LfMZ`~;b z2s!Z)9tT-1~4B5K#gSO zrGJFFE`wG;b`Q)=?lSndK#RLQU531akfbj{*pl?cNGE}Pm2eai;s-qnB;40Ss~-T; zo#a%tYuamo2=RxluL_}n5MzBk%@%Kchm$KQ-ufQtB+2sw(#euOg|rv$-~y-N!bFNp zxI`jNCfp)1MkY*=$dZXJBH@>bc#$ZOiLN41C==aWtzhC}ndt7i0}^F2(bI(%*T_UK zkqFB~AJ=loSs@c=x-c3VWFpx$8xm_};w;ylAgxg*&UI~v#Cn;y(6tuWT4iFG3u9xG zOr(j#R+&f_iET15TqGWpi4m?#q0A1M7%6OPlZnx;hd|FW&|t7xzm&~v-G!Bue*HC) zMBk0orFQrY))t#N09d?*jo1rA9?kCE0tBMioC1BC(LP2W%(BM0utdCtQiRg1ZsuhW z6@5d6Mz^@3$kR78pGd23sDQ|%zQGmcMz?t6NbXx_)83v~MB*t5ohIq7(;#<@499NP zD`1UKn$|7hSFE)AP?R9W_e9!~;sq@OuA87`z)70aCZhNh67R_aktDv?cPzfQBEGL8 zUeF|7&?H{aB>oIle7|ihzOY;JM(D16tI~{aXCaGhrfz2o@jVgfb`EkR39t4?I9YgA zkY3?cL8i!RpNAZ2vf6?kBZUY$OQHRe9xx5~3!oK;!V!E5`n+91pQwCNw~Jmy>&lCb zZWkjXS$7H2aXk^}HdKroR5bNoE^2j4`xnrXCms7TiywZ3d0M-Tyc34oz7-Wlw^2ip zqi z3f-X4>y2K>x*bZ2_&2FqW8(l39(l0a`2BD;>3fCI?XqdWDt$8n$v9DuENm=G)I*R- zeXFXBZi1Ay3)0)SZU&(#ee32B3C%W_$T6b&6H!=}tiGVB(I%k{1${#+jBYu91+q}q z;c_%%ao-xF+vFFZDa)iIrl61-Pzat7^a{|(bOWeldcAO#U}=>C1-(h3w<`2D2paT# zyk(#0x=sBvI4a(H6>M@RiD-gcP=7{TV%l^#HTMI4qroi?aq?ivLy|mr<)MvO8(cC? zWn@a0m&Gju9$thwL>AWF6$Ty2@CQebM)Itn;hKmUVAJz3R-KKcYas4%1H;7Ty7M_; zJ_l*nz;`5O^slQkT<2p3Zekf2iSK=eah_|A`Gso^O2tr-Qu|{rZjRL6aD!e-?axv> zmoos%xeA1(NW%6sn2Ve1&J8fHlu`JKik6JE@AlC~o} zgAC&?FcvE2US=5QBO7jm0FPqY<1pFyC}4VD;Ipkd!}9tp?}bjO8Iao#%s$UGyBnE{ zi5Y5QrrWA^N>oCLp`yebSGr>hs1x`416L#fer!sfK@CqVO-vd zGGNFjB3Mq8WNRG?xgE$2SVp3!A}A# zzA~yFy<1{2lE{eLV6gnr=m2~aZN_bIZ4tP_PLf>Cr6k+YUs(IN9lYS0Cpa$!rOBkZ zp<#7@bdsIfKX|p0lB`c+Lzo*jt@etNZFp|N&5JwNt}n=VnSAeQ>+PY>4oH&_t+ZySxz1)O9Cb=(~*-rKk zttd5I&z)l5hgK9Dt^-|YV1(RS!}WZ3CkdnQUwUy8&ZVxuvf%zOb5v3_WolBI_@Y?w zfyR@`moPkV^Wx6+GLP;T{S5lMy1CwW!`%Af-vE3G;zJ)IhJnnr2D%nL&rU92uXzNGuL>ySh+Z0!97epbLiE8;${m(mr{*8bKP;*{V43;v{eJ}bmq2A z4sI)qZYw+&sGI9`*L1i58ul^a!`@-yH71@XLN?>qlp5}a6Nb5=Y4yAf z6IlfM@Hv9mxO(VfAcUNyMA(h<2>7hNm#A{w zhHb0jozvm-kkcY_+-rI`_`87riA!u<6%WBGO2WC?y+#S}Q^5DPXg=p$?ua`ZPB#PK zZy~_vBqsX&Z*Ew>`aIW6bHmMWC^R=bxaK_9d~-wV>Sz(OPb0#;3Z&1B+wh=Ty|JKo zxh!O@q1gzY=Ep?Ky<4)tYzHzO`=PSfKn^cX_nu%Iz$;D;PC?p5PIP%kn8S-x5WHvx zXTX%kZX^^jR=}}DK>gg-FTp`^8&J6&p(s!a(^@7hwYaa3u9u@zG{0mr6JVLeHf9bu z?p`Wgr%63P1(q8a7M;~lOacYh>}au9gV?0LXl?YAuQMSSGgAaFj;T?aM5wgMP}v6u z2$h3MGhw(dp^|E_Vr{P_e3eou4ED{03s*siW1y=%SoRU(v05ER-2zq~5qMGjhJ&lV zvW7xSm>W*60)x#B$5%yjCY*tF2hP@i$9bvb?73k*a&Fj%`~I;+-v>s_W6`33GgE#V z^6X~)8zj~X#yRfQ=u;6IGjN)3^I+3&B_3#cd-{*TZ!>)hOwT$@QTSU9ZWl)P*$FIc zC!%uxf1j`&l{6TxG~oIHniN{XaV(Akg9P4b&%*5adNy>2?hsKM;~-cz#zB-6LbEy| zu_N6^s{VH!37g~hRMN_FBwhr@?-e+N@+J&Tr#&Y?o?T01JkE?F=A#+yD44C55>Wut z1N4oR7A@^Ef&U1#rOf#6)Y>+bgAH~94YCveX^!m&tkFN68rgzZ>(xH%EUI>4bhSqc z{6|!K5i|aOq1qU&Cxk&UgZV62FVV*K8S?aIJ1J`5!Ng^vO!VT!ZK@}cDM(D}AdXC{ zBp$b+K|O!p139--3FwZt?x+UB>bxMBlGUsw%x9%c z$6Ea~iHiT$xneh5jrwrQxI&ed0qZr6>`_<1GRv=QRihxa*-nkd(v&tgY+Ex5mtCCfQ9)Q=bdqC& za7`|g=7#NSWZ^Atyhz7c{g@^7C5CIOTQ1D(Skv*+_jm59^ZxzyTNd!(*#YJ!cKhIk zVWETD?GSza*5O)vulwyEq3{om;3$Rr9lUdeHaEu7*Tii#IA&i6VbX7asE*UEoDhr} z{IKiK6}-_M#3_g~;{=;@`QKv!Y=5LM`7BnA+7j6-&OVt9UyJJ?)z{s}5BL;sNbZVB3H zUZ2F^cgiz2ca%}0TfJs?D-rL9ZFSf=oRo}jnRai;ABEBH1c;?FiK`(IJzilD-6fb#8$@roX&z~}35F8X0K$kyr*Cbq zpr=1@Nq@WU{2QEprCYzX8zEy_*l;-oIGcwnY?IizvQ55M+Qe}EU@I3bq$Xh{*h$JJ zl4*>f==8&DMwKn;$bO1OtjjGyq-_TFi>svID92;hy4}X3^ugFBmz!Ym>0ub@VDV{( zGKirO`~JNV@OYu^T&0_2qIbG(!-p&+`CEtl9g<^_A>myY-f~OWh>1&a*R_ubZoJ`^ zAWrY&5KSyd10deIA85gr@V0m)1Z@sZW2cTiC7<2OLY^*b{|b9t4oDF9oCFwO_zr-0^(@PL3@KwG zyfn_}IRPc(MYdEv5#CB?^vp&OFIfq!s4g{nRw9U(t^|fw)EGUB5yYz+31&gc==m!I z@#08=lMw7O0v-vv?yGPP zh;!rwBxH+c0cCW#4E=!9BB5NmDhG3V;7*9d6P+xQi%{3AMf_4+7_MHfL5Y33n~CQn z_Hy;^nsg>Sf8aB^cfSlEn1}W=%*3(XFYTTOE0l4BLsWI~01JdZ9x$uxg$5ohXgH(W z8Lq)+<@IqvI-!ezyCZ_>9(`P9V)yPS5R*N7+S12>w8TD;90!-c9>Tli^+6n-Z;(d7 z<5Vt}l-zqDNFD~6eJB%n;gJ$3Xi0Oi;2Ci)M_E63LMgF-Pq+h7o;lH&4!L6ktb;klAs|v+6V7rf8Kq~iQ&%)K z#VO|o`-UhLf#gN!g3_6;IcFAjpD#RhaTf!vxTHH^a2F;6QYa-=;p>7;I}G_SCk=DZ z0FWSZ(z}CadUrL9;i51o8DU%71)C(w+6=N5nir*wq$*Q#6j@8j(Nq^qg4{7mE+jK4 z7Xo&|Sr}$xRlVaxvtFt~d3_`}F4a)IiABeQJmWISj5cS0K&b#(ra>vsV3Nnf35!_tHLMC$vlCn4zau}JsI*O4~?>dZ9w6pgT$;w^E zY$nIsr4}&Bn$eEgTR0VT7@1_vl95x69Sa-_B-PQ58EnU;w)nYBvSt)N!?t6J!^k9S zmW-ULb{Ls_h#<+xDaYWsu?&VwNU^z%v@J~EW@G7FnEoYUE+fF?J2o_n$%}2v0!(sU zQ32C)Y_?fUE+)t{>Rem6qCa)`^eVZH^bXOC=Um*lvM!^5$qh0Gf^rs4{c!tE9snv} zTgN*3*wkq@RKQkU<3PGqAfBY!hbhEeWS5%DBnt!!Sf*2Bw%itUoCNf7O9;9glha^! ziksV5ZZO?|;}>ig5p%Y1>RDKW#SJ-`{wraUQ|3E+R_nIa&Svs%8(P35>p@wwZLRCc z7MHPr$&HG6v$t^S4-O-fs&%tFG&x&0!Zu|Flk5nWF_+0La8AO_G&by5#fcB(JW4of z#uiR_;qJA~%8APfl)a2oHT@J;PJBe5WaZQ;m6a23!C4PC$;v5%Tp?`bgjey}j4hlh zQ&~CD5XH(VZgX%7lk8xrNSb2rJNkDf`&TJqooIw^PLN0eLANx6MLqFk_( zatD7zxx!A$J=38a{%t;Pv+cMmVDcFon#JUsHdMeQha`*`+&0_^U^rm%PU@P##pLg7 zsDMds$xcR}Uyx?*pQh36)D{;_4l?Kn3V#i=X54;HM}W3b03D$t4&Y3mAPm?R&;%q% zM}U~Dv!Mk{uC<|p$BBI2ftWnUuH&LoBCBnvU^S6HwV_4QcxQi{I=TlgWN<^^7$huV3{cCAfbJ&daGkb59naw8Po+!)#B`^G|3Y)5GDy z1~(WknS6;L)3|=k7EY`qv&31 z+!_b$DlRM_hdYj6*i5zlEgfNCeVGhAVjEh(q!ZWyj^g!p6bG1O<2o@)4{Xsm9N8Sq zcyec&0~=)NF@kIy)%CXd1xz~axS(S@F3{T%h3VUN@|P|cDO(c1jt)Y}jMMLW< z*6@I%p>-7}{z#z9C}mQyq;(Zr5>HoPU`gvLPIw4}!b~a_w|3~KZ1L~OVi2~e1Je;Y z4}{_shZIwZwNA#E<9 zTviH&9m$e{=dnvA(Y%nOyA#0y zV!4Bkz{2Fc6nbDGx8+#7J1%6h(1r?_tK>27R=xiujBo>?EP=-n48O_ z(;x*M8)RU+^DRt6oRsw0V<;6Nb4H^*SE& z0?08@QU|HGePZ{zIZT$FBUzwFGg(Uzu4vBL!l?_+wHafH?`q4M&1AU^&1I4WqWIaJ zh@boK#m`{z6K(Mem{i5ju*JXQFfz#%k_)&0Ug=C$surBtAt>0_%f~x$CAbhSIw@zwzfOLH9=;A*w=f4N;q*NODe}>)H<}g`g zLkpPvo*>yzIW_n^Hfn(+oIQ)5V9T1#IIUH3#3f5W}I!T;RK8CY$dg${FyAD>jon|lPpn2E~mb< ztGS3tu9<19c5UGV+tJCXqoWyxY{nb5^Z=8r7|kfOz3qq(=q-E|$tbdA6)^dd4b5e- zY%g>mCgZ(QB=kHc9d#W#&`(JwyG`61YbP+7A^^J!(-#od$+)Y_!eT7y8xn~a(`<1k zmqGuzs1AK?6dByAR7Q7tIazlz9ieh9M6zQ5Vy7Q(FTG0SQ3ujXHB;8@ z4$H5I{Ivt=r7i(LUKrDTC}QB&*7-~5Dw^+s@g+3h>(cp|ZyS}Y81^tqvjP7OLgcWWN4IlMln@Y4C2Z!6_*qtM`Pr1O6W_Zi&qZ3~!LxxP*8 z340elJ%j0C!OCIq?p65s`>>j#;L@Vf(6G{Aa9BoaX6lGx73E7rYQ>mQfl(Pl>#LSl zRWGj^S^=*)T`{z@s-D>3BjpwK#UW!@eO>J^N(@^99}|g2N_V3YOCSI0B=uYYPnzd;E4O91)yC)ut~9T0UDVjT@J936wTsLipudkbVNJQ&?~vJdE&jh??bLPV z>(^Vo=Qcqd4qW-xT=Oy0@-{waoj*3sJt*1wpuoI#%EbJ+Q#UkON2C5dXtiS+4A=ex zdJMd`_Wp*~rsmDhD|L_PW1cb2yn4#i4?g%{YTkV}HqP5%_4&sKADE%AdCrvk%;K=w zf82f6K=?P+Tx?AlHt{j@oK|z8Rbmc)^fB}M)$YMA^G4Sc^PAO=n)k1^9yR}NxOcl; zQ>_J1pMi&AHy?i)V5+%ejn!(6pISC~>ft@`vLJX{Yy64$E3MN}<|6kPmwBx_-DSRK zxbt08&19?aLzZe)n48zMx|>`*-P7>DdBku}1Ksn@`(4&J^L=Qvsn)T{8-6-@{*^a= z^;7e)Ra4B}?zLq=6uN!=?)zZg_)G7zGTfJ+F=x4T!PMEyr&>$h$_Bp{bD7^#k0E z$K3|pt@+D*bH!RSVe^(IvmWgH1k8GJ%?b0w>O!mD{HuGj`G!#lm3U;$<`uN9#N)4V zGk31uZ1&r1rffE!U$xo1*@c|{TD{r&!0fU4Uh@-pFX(ILV{0~>&lqdXiI1AE0{g=* zE7dBqmi-i3rF5NDYSr%?|2T9dAZ{4H48SMvGix6;6Q`K9Q_Mak=3T2TuXP57`yXAF z*WAB)t$EP({x8hx&1UN6H_e}}-n_;9-W4`Ka&OOXc-mZ1xOIA?dDC_8AGmVYT=VuW zR_gq_wwg1o-q0m)xo)$0GH$awt1mcn;FZ=QaDdC|1ABM86^@^}$6|2WrL0tKv8^J447U2|Ty?y=Buu7&flh_!RRHGabtE7BT2G_QHz3>QMJy;kbi zv%=HO`>wTmi!Qp->TRCrvU$Gs_>JaG0Ge-fu?lato|~5U+R##zVs>+%k4@$_mxrfL zE%V4I`paTNEf0ML|IzR8@>XCGP4B_=}U0YLJG4m)DkuLn6N_v?6Sj)>MOOp<1K9 zrnm?)#Ksy&o3zth}wx#rP8!;(UJOm!j&v ztf-=oRF3LaqTu1dW<{IM>&eUmJpU zM{NQ5iuyn}fEBC*e}d;%6qlElOD70H!i4fFC>y{Rsn;1r#g&x-P>b#YZ2=K8_>O%8 zqO8Q2oR5K06)b~kh*2k^2Ba2Mz`M(#1p42YoO?}uQH5x+fMll^oQp~XVVVOq5$2#q zyBCPol;I*sVO~vGgb75g3@Zqr8jN&^+#+@<^bs*xD6a|#7Yr+_t_%$;s{`Ip{qkXT zFq?v?8X5#;!|G~-Vm>vq2qq@NmGBDr)SyvPi|xC17hnj0E#htV zm{A84!H@wRq!{W~Q&gNT4=`3>YJ@yw$iqnP8aRJ4N<^0^sRlo2rQGKNP!03 z0V0fxaBl)h<#pA;%*;TYG)~kFdW>k&E9Q77dBt?pJG3%fUlkf(8mbD_mIonOR2wWC z50lfOqcXfhOT9y}Z^C;7omPY(g-X{zH&=oaTbdeP zL?CjYj}0-bicW_;#Hg_FLM<;l9E@QwMuV-TwM8{$Mi~y6(E|kz zvpBv&T;QlLI;|KVI}R#=>WX3>A#gCiJTE_227ag(&LXI$sG_E<$nLjd{4l_=Rl~W7 zF)CnGT2ftI5h|*3Y>{0N#~BE`0CiG0XW{wEIdM=I#PbP`)Hs%;$w7Ix=|X;5Q*aCG;2vk5so8r5Eb@-_+o0`D5v4*gJ%^3`U;NbWG=W+ zoFJqv)Z)eUm6a<4Gp898$^np!V_Dv;`kERz@dzi%bK7vNPXNLU!%B{T2voA~xUUPg zI-_jml3MgS)?icy)}g)%qOVw)M%yYekaloE}}RR=BoGf=eB`XvJ$Iu_2a5G-Hb)okQ5ucnvu*G3i$K#Kh!V%!!HK zmbi(DDK~e?Nld#jK09$tW7jE(S?=!>631Mgn3kQG0vx~zB_<|Xx!vagKB)tq-Q9Q} z#BL)U;;pl%*D*b@)ih_g5;vJM61SRpiQC}!ApCZ~uMK|t;CB#yhv9bwen;VV9DXO^ z*AVBPgvI}A{fEW#64%G&CAP*bMJcy;0V#Q4g!>vGvJtw#j8#i``8d=OeUIBLyp(k6 zIi(X{&P=RvSH+T+1;zhHvc3iyW!-}9(qiT&dT)*c@88%3TA?w18Z^n)gv7K-i7DAi zdw}239xq+eiq|zb_JE8IZJVFC*u6HEJw|HX%1Rjf;TW!!$1CYzC&$>yU|l#WkvBWrI;hDh&0NA?UfO@DZL+Ds-`-FbRPXRf-Lj z!Q=@i-v6b|3v#e|>A5vqY8FxYN+0%Jb;E@#SPJQ|`8P?Zjqar5Guo(!*AWR8FN8*% z9z2nSU|;C9$tXhCg)5XO^FJ-04rM#5e=PmTt8Q74Ky0`Q0?p;yG@Q%xaXRF8Rz7*4 zELdbO1<5q~kIOHj^0YuHnPA^=fh5P+h$Of?AD04*vh?z8R9r#Mw!}#1MkG$7;w?T%EA@5gklEf`d9_+o&dpG2CR{tGGWf3lq^~Z;`_4@xAFkL2= zys#ek@ESxdQ%f4p0gsMhxqi!WZx|o(30fvQ@IDK8XZ9Z@`?;OaetaNbxBn1esGsWz zq}*1?Ln0R-m~G|bJAijqK5?BapvN_r!t>mFVix# z>KNZZ+0OiDv`Y=!G2p{2 zcr!rP53ly?RDaqXvizA^{dN74tK%N|;*8JHy&X1FSls~zJ38|O+SjRv0oQ^XMB;O{ zZbd$GoJV*Psbw6KW{0yUytRS&y~qHYnA1uU_~|wjrTJjm-ep7{;f856Kq*s zoNzry4)Y!TGn4rB8Wa-F{f9N+c@(4DD~`^tSdbQclqq_;i2_r>*jZJ|uAu+mKfTrZRBu5wAR6c;-KAzTTEB}}C8 zorzQ6l&*1}#+E2Gar?7>sLj+9JL5n0=>0EY5el=|nyj%+dEd>@_Z&7J1PJ`jG4Z$S_?o&R4F7Wnx4W z;K%X@EBGJGu4BaNV9Q8jkt6-J{4+?YUpURePROaMhk4H2Gttcq0uB zgbfda;m7rzq2M?NVEk$YAEMwJV&K~q{33<_PlU4{*q%ofe$}4C3VyyK=Tk)vk&tT` zUuZa=D^4o-#e!4rzE^NuTj9-3Q)|o~3P1KkPX$-~kRAhHL^#%$+ZJ_vukfcT^3!SR zj(TxU&3eZxxY{oB6g*AgFH&%of31S!^%cv%SHaIz@JAFJ=a9_*oPy)E8smo)9Iv4m zA4nGvST9^dV|=25rz`k%G4MxY;D4g)Hk6O+?=1fv1s|^9-zxYB1<#{P6_k^q;PVw+ zZTE^8_=Xtx-3qSuiw6}PuRGcPClq|7fTY^1J8+p7stSxV&M10!1u<$KZ=3(q)v{0R_#xVfzOJ8 zFN=ZS5d;6Lf~)aA7%%W))B8nX4+kFwB>TBS!7otoI>J#6Hr+b-;kH8I_d%NF+^OKI zoO={GLKdB${;cq;az4@c{X#2zNQf@&v0m!<`6mTe{r|m!k5=sLLvMsYIb#&ur{F68 zSOr)4uTXG&IFE@G%8f$Kg0SA);Q@ zKNl&uTHh=MSL-`N!BzfJ1y}i(E4b>PpDMWOpMwgHa|HHhKU@HW4eeL?XDhhMA5?Ia z|1Jer`5#npmH$%(SNY>>^;&u+Kj#{}T$X@_(z~Dt}L$6vBq~sOOQs3a*al=Mj$Wh`)pI!)=Jd zua4)JDYz;>SHV^J(=_>;qxAX6kL_FN5NJ87ZiMg zBIlTbtK$H^k{CDivs&M-gmb?TOt9;r;pbCEA4QJphjSHt5b$!nhA6nIH&v6*dPitD z>m8%WQT0w#a8<9FEG6powFuYi+f&0aAGh9$993_B1y}W6px|nKhimesbi){};W%dD zHcpYF>b+dSRlNaCj+9~;#Ts5k8A}y8s-3k8uIjyB!BxExO+M?rMZ>vX_b77IaqJld z&jKMFKc6YMdOqrYmej=USU`$<6OQekt;k7N@GJ#i7(-66!mrA?UBOj3`xRWZ^DPBe z&$AyYxXS;%f=^WJ>~=OJV8eP%f*f~)QFXbe0KCy}tBJ*xfv z6kOGt83Vt9aBN5XZKC=4EQLQ8aIWt{1y{$(dIeYGd5t390y(@jX*lk;5NvwfoyUz91hTole^=z7?!y!( zA1M5j6@CM6D8q*KtMSm2aJFAC!LGN4kE4vU6gg@p+jJMQjw$9Ymb6o1bpoO4-|a7g7?5q2pj8tL?(^CgrhpO zeSf0hYJGnm1K%D4e=!FBfr6`cey-rE-@YZB?I-i)%>$)g_fkezuOzTus^5AkxEc=> zrXmhe75kGe?>gYLOep-BC{>9)ElUU-FL7RslbI|B?MHl@DRG)^+x)8ur_&4EQImZ` za>M+Y2$VyYdICvPaFoBYs{}@CI3*44H*C*!6mNMNeRc}Yxvug#{1;Bv3!1S z_(~1uzcgQ`;Y&#V1`XdN#TviT@UtlWpoYIoewN>za_p(3cES7GxUrqjlK=mr;aiEH z&p*sRkL16v@$Z*n?dLt1Kbz|HoyI?b;@P71Vg6rJy03=sCplgX-%a)m)$p6hp0OJK zPpa2s4d;HvZ-r-jejvU4{4C?=Qh9y_AmiLlJRUHfK=og*>1`!@_`QM5KaTj=AB^8i z{10eyrjj2X*YJ195BoLzR%-V{8s3fg-_!7mNd9pRXTPiW`*8f6PVy7Te{BC;!q3$3 zg@pSwyqIvl56E)(wU?J^{DUbyRl|>w-{xz0I@NcvhL56jg@zxX`mfONC&)id8h#VC z`)&>Ae)Ot_AExx#>_4y%{dqard7*~;N&avRuc7`mLBpRRKTOeZz8}9*!&&b-4QIU@ zG@SKr*6A+j+evhwWUi;avWg8qW8L|3|}zQu+xEe~035zlI+r{E&vn(?#Js8qVYFCmN11 zhub$AUP|%Vh5W>R_=^0Qr{Vm($Q%v7kmRn=@S!BXNyE>T<`}ISKA7f@nfckyLmK}ri!2*)Hm<7azLDmdDcN&Meya`?Ka51r6CP7=wWa|vfZcu7u%#?SME%M={-))N0D z1;=`E|Cy%X$o~!T&s1>a=kw441xJ2-E)=(N4d<`@EYt9=G;Um{;3)rns_!}lNBMUW z|4oFmAN;5kwqI*F&krBa@Vh0u!Ot_WobL($K;s`s_4-D^vFXky{;t?iupt{YsYsux z;25|33A9uVZzB1ZYWOz73ltpXQ_@(d;X*0wN);USK16a>YxoOGvU4)6=e z8Gne{f8h9IStH4dse{@w~I9V6&jaDYIvz+H?lOG zudAnO_ZBmV-D^JfJ|{!`RmyA&Mxzajn?H2g}k|78V7IonCjTMCYHeoFiwYxr}7f3Dyt z=Pkm&RdAH!CO`9^u5i0AhCJL7Xn_fFG~s(flh5l@ zuWC52H+`bv+bB@FqEOh_PJY9thj46nv@;EvU>l&|NcN)VGkglJ@()#TBqtO9aKc$H zf8&DVh;jbV#S)Ew<=JSBVO+1_yzbgWINM(%*^P|~jwC&(Cu*|9!;2Lg7cf{CSMq6kL^akAfpVzj60T4L_6Ogr5sV zy(s5=_~CX$;YXaej}#o`^Ba8e)AP8o{aF+b{S+MK@O{J!G+Y#h-K7eS^7%t11sa}# znXoNYaFoyORj=U4I*0gg(Qv+Q`X3E{lK7uia8>^E3XZIQBmNIGJf7w)-)J~>AEPVv zd-fZM5?e0~chP+H5(URT#@kc{M^i=;e^A4DU5lSnWxZDs{}zouMEIXI{Bk<)zoX&2 zj@O;e&n*8*;vc8sAJBZGP{U6V9@6k>G_FQ8{5OQ(rQr_~e!qtAB7BdAA0+(m8vY*P z$25Etb&iu7o=_cn0D7G<+W6uWIau`GeQ> z3Xc4KWQJ{{f+PP`*S|wXKVOG8ka|Dcp>4FG`yT}p0Ba| zHH24a{7(|TQp3L^{6-B=qVe%I4Zni$ts1_B@cT4;E8&l7_%6ctYWOL_U(xWP)X&=$ z9Q)Tn>|C&Yq2SoR781Wj^HB5;@^6A4Zf7ev^7D8%Si|omITJLT$GdzDe}wppHT*fk z`3n!+UM~@ThsMw2+T9vHj{5Ts1;=she5@2~{6z?qPp1}vJ74IO{gFRn`k^8R{m_r% zn7``9a?p3US@b+D;}V5=u7aZ+{vyN>4PQp`M`?Hi;nOsHAK}FseuVIwH2e#~`O6aQ z=YA9)k0|`;p8~4D9tB7HM-u;A8a|D1lb+XQ`DKJBE4V5@MZr=2&xwD6hW~-^kcR(- z@ar}F9l|$i_(z1_rQt4W@0}XnlkgWcJcaONT6n}bz^U;|yomUH8s0*9rh;SBqAc7l*Kmm% z#ykZ_`TQlEDh)qD^4DoNf5-6m8qOatY*TP`{Nd-c*`G@&Zkur4(=O^JPBTgK};lz9bEQhww8L9ObYdyb6wT$|#+t;d?24 znTC(L01Cl2O~cm`zCgqOLU@saqdkksKi6pZ8?=7Xtl(&8vJbVw{Rag{Is9DjJ%qae zbHk6n%(Y$N$MZ!iGQjpk41AA*quz0(_WBG|cv zWBa0=ZDeO^41BbLqn+!>&T*Q2{+iuv4d+kVRcLrSjVCv1cq8F=$G~?G&hhye+4F?P z&)>;=Rm1sXdB-$-4*9Js$wRlO<4-RIcSGLO#DAfJqfPug{Rjm|IlSd4ILhJk{Q?c= zZzwKTaFp{13A#hUQBHTS%=o>6Bmcb;Hg;$@!Nz_CM>%JBWzJiKW1TSmpT$hrj%xTF z6hB{R_{LdQB&sG0L!~abD?`b&S?-MUb2NSryO;q1=E|hjLK9KP9HT=#llJ8Otzm)XO z)$n%WU#j6R5PqwMpNN<8Z`bev#DBkr^WV?x*6_6?=PeDthV1-K!@rYijb4MJ{m`A- zca4VMME=>R;rV3ey&8Us^gg2D{64amH9XQ)+VO^l|B3W|q2afZ{GKV%_Rpet8?52g zgb&s5Wz;XSH2iDgC%20Dp`}%!kd_w>G=oG4d_LKw<6e;~c~$-l4QIK24d?RPHJqP| z+ems*Hvaf}`G|(|JlRJ^Fh9?8;0sB`<-u@j^)Rp4(latgj2ba!^r%d)H$83iXlI`` z3O?@RO&^gtW;h(Z-o*)Pa`P{DyGS9};4&@~{vng?!@*LACrhAN@NST^jOOW9^YR{< zpRc*vAKc|{>6_(mo}A=wUXucJYfD@=|0aJ++~i|E&wwUTs(EbVE|&Ktf8xwU!b7`&0OC$m|`s2Uf^)m*hkYE|U5A>ci=6yGv|&)8j6kZh3Bf z7^H&*C7#dr`&%-y@>}AbMSU%Cf58J-vFk&(f7cQ7*ws)p2cCh*8E(1!&GVA|d+~Lq zz_K^TZ80HQ6cMq4goKx~M9d+Q#>f~Xz|_3PHp}0*2Wys;C5DR(|kcJ~%eJCC`-| z**&FY+@T@6{E=_`yWSn=-}TXi%;!9lUu^tWoagcv*S!?3124SP_>Jp?=gKo%R=B>H zJ?qe! zkoyVdwqfoiBKMLVnJ;>pwnAp)K;+dSFSlIx-LB8vEz?~&k-z2)d3(rfIW4{x$Tb>I zxtER3bA6QlQu?VYTKXTF4$)DP?YS~;R^wh568W=|JpFcoIdQ$AsgpAQwfsnq>qt)K zd*NKrd&(a?2ujBtLW9S(wiLK>GM`&>G30#J*yi>JU&_wh1^F2wKcn^fUYO(GgwP%= zlF@KgdfUS61-n}#C)@KNvTXmezbAvg+dQ|T0`#4g1BE?J9_jJ-rv>+e$1&S?D+EL@-wTirKK~3n17$xE`Ei(k ziO9bM^TF$XmHA&`ew)b85cwG|pxb{7xzg=9EmObC-qr5L@W_qq${q50_K+9Z_1TU8 zaxc3~_&zs%KREwqSG4p!lw0Cv33$!Q({jnG^jH!C~ynCI5JpjQ6i@$q?{kbjKr>3;T^~-K- zcl%w39)(6Rj`i^jXn>>9_%w`0Z9V*%$HV94WIi3SA_Z~Rf0UiMfAt{5TV}X%yvjUY z|892UZr3!|yI^GfFJx`97w&Eq($e=Idz9ogcU{-!x#b{8_6O(1L7#=Ld)VKyJP9m+ z6&2=1Ud(sBk=N4q;MA5LN&d`3{+4;kS)SYW_@Pa2dpgTH6uz$UWb(3Q{>XE)?07rq zx%^;m`kTZ6E$DE0W`ON{Z=Yju+y*B}`s!00A&q;2Q%c3_xl%U8&yfP61 z6G0`4l1KtK8Z=0P$r#oI1Vu#%iwXfvaJ ze%anbzJryF3>|u=r16-NcxFlCPr@bfiZHI}xTfQpi7Vl=aLvLs8`o@Hb8yXxHC{A0 z7B3y#*d3O-1Kv`M6}h;6T_|sE=SWj2=BFBMvS;In>_4M?U=4jSC3GzdCz0Y|!*ndF zcRssRO}+8TAccAFL~eZ*U4=pBz%Gs5$tt5^7MO%A6Zug-?DA49`9ka%Mr1F3Ir66- z5ZW^w`BNpCr7D~h`D1YyP)RscH#?W?_^-4}VdMDDs>kb6$v)JdB_-%4%@(mw!I*;b zqTF4GV8!uoM>n3acwb}*1!nR0g2uvVsJO8t8ZNY3ZOo2X6&4 zQW8z>8`0D+TC{kdYOEmAcW)5(4_~pjixrh z1Y02%rl{OV=GfVB%F@E7OTPyW8`Rwg>|I5R-&Y2S^!+ZikwBf7sJh^fkEy z8R^pmcktP$O4+EE`X1a`ciozzgLTM8K`o;ta+5tYPEAGMYGY8_t&xUW@_yy;AA1}l zrL940N1+?>3K}S(2d!O&*46I{*Y8Y)O55V^PAQCJuZ2EuM;hpzA@$$bwPomYVKj@( z4MUmgY z2d;a;_aflG#_HGST!Otda4!{%Ef@#<)6V$}0@M+F=c{^OLH-5YQ81dd<1UN?@0$vn zs=o#2r#o(i|02#8HqHGGWlVfMMR!y{U*JXFj@g*rx~ya|{nI;gG#$wU?P;nVniJfP z;m_9m1s8L~qj8*w|`&ndMHLAwVf)%^eM00?@Bpa>3r!X)y7`4&CDk!4{{IU2}0Axo%2m{+!Fg69`ow$CDzgO_riZbR@70gue0Vt4Z2Bt?# zr$jE!h@xt0I9B~kEWRlgFO24^Dkx0TP_G<|*T&)-A`Jr&4?%n)brvHb?ysfj}S9(8%cwjlQ3!||}Lmb8|X`CO;R}>}j z1FF8Hu{Ihjj&B&bc-P4IfJnoWph_Spj;|lN`2CUbDd9-NFZFoTC_h!f8%@`c@hi#U z6~1!4lDl{t5QWjq$P#){1$w0%u_$+6QSO1AIoyw;#Rnn{CqrlfB5kDMNG-lFnqCO) zcJHT?V)o|Z`i)_9_~q5qr^5JGue6}gTX2%AEISQ?M?Hy!k)EGn_eWj%OYZJ#VR~UXvsRh{=WXhq}ojmBiug(_xEtme!3VVG}3+^$Kt}gZz8u+G>J7rTiRm9Ls9LR@!fp= zNL{HyaC?c`B_ZO_jHM8xm(0#uw;1-7nV8N> z9Y|4?aIh@EovFir1E7Nrq6}1khgD#T>Pdi@J@1mS$}Ew}=L)I{2*&)We4$`m!FXJ; zAE9JF<2Q)tAkj71PEGcX zp?V!JiI$>W2?i<>Q#7hvr)YPas;RKFK?;_viWhfmgUB@_l$)MSF8OErdXm0!*D9u` zDA-9;a0_r!SN^i&({I#DtwPC;_fc3wcX4krOS#nHFUXC`@6H= z#IFB%Gg>KZOmmb^pF0(LkC=g_2X^>3&%F;>P`77bs4MwXn*Xa{Y|m_+_md=B!3=z z2=pW)d3Y$+I47)9xcNxA+^A2lf5QEj7)VHq#?qX{FGrRWJPl#K>`4vykDvNlBbP!quK$Cc(& z)8o-aAjw_3CQLPr539yi>_SgOPa=zvq~xjDCDoG=xBiZVQgTUryRsXt^z_s=k(GiB zc)bF_g2ZNse#b-=2}GL75N&3n2?<0GM~TT0-A+WXA`wxuQ(eT=PYR-jcnlL05fRVu z5U+I;_h4coBI0=-;>~X2eoRb6i?>ZFqWn2p-vS!=8y*~wHQvHbbcgb{lE%qMGmOQF zy&xTqJA?p4r}Ul#Q1rrm;k1>_ZML8P{DSn=%IB`aW(@8}#c?m>#qIv><7RYK(}z~;%%$Z0Zi&IBNsk{-3J_U_t3mekrDgr_a{Yeorb6yS6hZ6x=&G{ zh{-dKB<09*X=I}#=__saiBwY*E?A{J?gDZ!MKdBy^(fST=R-nz3_7|+d#fpDL zIohX@x>m{A#oG!Z7pyC6$~Zl5f9-j(b?+tTu0?vcxM|3dBb!Q&Ko;mLHQF{Monf9( z8isvc;2~{uwj2ixV(a#$gMB$r6ZoU0%&Q2 zYong7F_g(l`2@5824A-yU3<6~Qk>R53R>63r$qOk)U5ijMllqS70VADD zIhq$$M1qxY*q{j+IwjV)pn?(wo#Aj8-fY5V48X+NcwXZWN{3FTBU|O`YLL)NRm4DK z(`>-soA_IgzrXM(Qow`nt%Q=mw%`xRI1Cl;Y+Xk4Djmu+j7%n~;;ZQK z3PD?eA4c}Ce3>TVa}-uy22MqhEkptLr_>ep8<@q&TUBlG?kvb=`H(#xk2E7HNVNsd5wks2>!#kTZjlYoQGes+Gz3 z8y&_M5Ktd61}9pzC4!Wvr5U|PqJe=H7#Ok!4a{HYl#lCrGcfsq1C!$n%wQUruaKjm za|?|D?B7}J{~{6yaHE-ej0Ui8rBnWmM`{2?lfTiB_H-x^Rh^B zD$!t2x2T`U@lzB+{U%gDM>QCRimj-@PCI=tDv&6vd=@xtn@3@{MY*h%IybbR21m^= zb*z>{HvBtFj}Jt?A!AelsL~kD7z`vZm@*cO<}w(%8{o-Vj={3YV4J_h@!vcjvxXAg z%6?~K3bc1{*XQjuH2+=C+pMA8>P~c$pGR6l4=?WgPp3?K-kra6$`l`VE_2F5sC>OC z|GSUl=};G=@-H4o6^F9bKvHnZqH(nLVH}qt-MV-g0&7w3+8yuWN+<6eCfgSgL z1_EcZ#L+Mzb z(jtdRpa}Kkris2I2`MAE>JKcn7JG<<{Le}Z4xV+%DQt{D3GA2ELT7S7&&D^#8={@n z2>O#jJt!Hpt|gTcLMy>Zc^6tL=|e|&T5aQd>RWB9*rV}S>5#K;%2*U@3YCI6M1*7( zEQCZtI}4JLyN_09C80qt(jZWcB+tuNW-J7PG^3j&OHStWj}n2_mP>2&m;zd8QKGea zRNjIz=XohXF#&R1`5WSP=WkTIAfuVwPX6XL%4C3yCa^nyLu^WAaY!_g>N#;jMAU4|MNEBMg_KL|p12UhO9Cz{EsE#1$UmwQk~` zOiV{LM;~Xh2(V{U7IV=q+nb{szaa_VPEWv?5?-!SW>jt&YoGB8QT` znGy#3cCaItquDMzc=~1vEsnyZ(l;XxIeqg}7zp$J@X|NmBYk7mCt7QNi2Thdhn2s{ zKs-hI2K!Rj8HW*cgLYZz>_M5h;^0^r()TeG4b=hANmx{J`liI>S6um3Z>mi4CM`I3Q)FSsqmws9 zmd@Uc@Mmv|T-loYRN??9*0J0C{!rVz=SexBq_Xp_#}w8Ag} zxtsYqcSBQK!=J41wEmpZE{I8i!(Tn2V`(ajS`3= z`RvS;!C8wU?Mq1*Z!&1Z!8;Hqo8x3~-cuVAUeBmqPyVK4ugU|s5;%kK{FBb#R2?*b zGu_GG)F79E<7TZ&l^l8+Cm)S*8i&$0?lcZ%PdJT3hsQ33l)%A(D~6bhGZbJtNl6-q za5!)Pmq}snjaHb5Y=lgL$<=^(3!$JSwY3G#OX5_%Mhywh8`y3^C$;s$>HRaXaK;AV z9-FWnG_Ddh=sFH5Dq%xiSBW0TAe&LzhSX7;M4iZ}*o9UqKB6z&ZG@8Pkz22%tc_|G zk3b@AgQlLQG2cpxlY^*^yo3zROOccAP#Y2I+3P@TA;b&>NkVMFSH^3ES;UxE3G)hJ zmIA|B9F?EK4pe0>IWxxTt{P=cWagm`vOW5#3rXlmf`>fY>>Ca_%fmY}q&Bwjct8)z zf-Fao#HpSRjy2}zlPyRu8ZwL|U_7Xs5hk?O6Xwq%4Jko<9_q@%WMP97O}JRHZ!ugGfn}NQCbNsD#I|Iy3JVU z+-Cf#y3N>dJbuPQlzWIDowGn1v-OR?_#XVw0?#z+TH4dH$h_3ZtvQtFxqkgh(ErnU zo1rFe1AjGnn{zOe{%3g`G6tNm1%JVLo1c60HW)X~+Ypov4RqcHC0E|&{K0te;9rop zaqlSp>AX$i*5ltJZ{r&nZ{8;9tHivG*gr9Eb520s<{XM3|EjzVy(slO&61|`47(5?BaZlg+vYVHIGuh2Cn@q1$^AGrESosYA-}z?@`q{g5KQm z_Ww$f<|rg-B<|A!p%;qb2$AWb&9TPeSl}oJn%;Rc07`En9Xtw$l>yI2ZoW?~DthHa zZ44WY^nBcb!cX)%;X5`Va^oUQcoFYjsm3@f)eRf-!$~_X1|j1451cIe61`I!EQsE` zgMN~s&Uf^fY73sk!jr05jUy;qUkd#%%!dARX+?Zx3HrEdJF#yfWE78? zA=91?ep<`)Q>ye389kwjL%(W)641Dts2(?P67FEZ%|qIGI++0^-}M7qs89z%Jfw!s z?0yzMO>)kk=(p-7eJz9*$bO^eqys(of!|w3Drs^31b^sIC7yIpo&N`}$WB+i2mEG@ zqX%Xyz+L%1@VLK`-ko{K5J))aO!&3EK!MIwz>~CEJScchrha`U9&}dEF5qsb7zg0a z=bilU|7H4;Jz(R(xbBjB#CT%00#6fHz^z7ofZ6drI=vxs^HX{+1LLR-lB8SV2f&n= zt-07mW3ch0QQQ1kk)E82TIXMBj$;3rP3H!Tk=%)RK>1vyzO5a>4eLndH&B4?HNbs! z&HzfO`!sX#gGBY9AO->T%8j^vT>yis8uv6tZ!o3=a>UvARu4|%%n+=)zOmE-X8nMr zqQfjudt-EH$6lN>*b~$pB1lbFQjJ$AH2MiNgoJnWRTDbjO3VkNtA&wAZ&62i#2mHd zMPi#|;kI-vJ_L`R&&Q+Z^!O2QxF-|CBMRs$RL@Zj4#uI}K2Q|@>)l^It`oS0(K2NM z#hJTL!__Oa@TMVeqfMQt#525fEYFWL{ED{PO`%I-O+)JOyi7>*=xRL1{Sd0@Ipihh z17I~N4NSL2=$MsH55LCfiI!bO@o({5M;RT%D|XP&SqtOai{c-h^fX0Q5*uqAqJEDQ z?}p!(#)pOQo4sU}HJ*jT1G*M#>f9YB!?A?ULn_3_&nF||yn?!;W63ZNWbKD6U~#y? zM(l z8?2rPja~l?9cxtg=^5~%yl?9U6y`mwY*!a8jPJ)UAhV0|)<>2^F`h;7P4R#1=ncpf zuaFeUFK8;lQZhYS5o@a26z2QK3nbS!74;gBoO%?bVbNQ{*e3;-lKj5HX4tpu11r*`q6&h*THwrb;d^jT=^-w@I08Un;y98&R;c`hLfzSC|vQ+=ttSy(Uh%{^kr*g@809apg zN%|MK?4jhDc>EqN`7!xt1;(RhT}u+V=G2k#y-;dGQT%({mLexJ@w+hoSyBAUlb(ia zDi@7B3OaILz1zTqgu^Pw%P+vQOv>5tq;#V9(%gmjl8-9yBrDE>Gr)c69t^uM&305= zHzAg+Pv@B4kf%(Em_aT~-a7*{!=@@9ha01CK1Sg>1cG7tD2#?b!-sbR{P`lZ=l0`b z@wTG8@9X*$<~^Xsq^_s&T^8a{7y}>grVLb+x1lIR z!dY4fz*E@6S_#MmuwM;WQQqE#Hy6+xq33^~AFU`DeLe7Pj=K}Bvdy^XXz2R&Gy;L+ zPXBS&8B#^=k6TA35c$VF@%D8!iMsK^aqkXDhxcRPcZ6=^s|58lrapp!L^-p(R+(-TE%@33AEUJeZ&EmgE* z$ifbm1}|G}XA5DKK_&6Mv}qW;^9ba%3iGB%bLz$;I%E6>(*~BJb7B01py5;TIGf|K z9zGR@)v)>E@gAjTQQj8|@1cRrl3gTz!-Ij8=O@}2PbVH{>Fz>vj?C@E6VD4CDR zG@S4@nu>ZJ=B>xlDpN62k0nnD+8utyXSy^Vm@AxFfMA;{fp_< z2}HiZTl)XA@xtMqLM9K~zjnMHOc;tb#_OR3BL8@C{7mKe`K5`UG{+D$?#3_KLlin` z-J|#!E{ul`YuP)(S@yisth+=`ni}wZ3Y?E};KZt@0;gK_c2VHOs;2^H_Reljd??K8 z7p=Qk!e`slO@&ULZX0#z{1^*uC_e04T5r`D>cNyj{KBk%`tbdOuYcbq3{4y3NB>LI ziKuOkpR9jhV8ikqt$^2myc~w4g%G2dqgKFDUIDT1FO2rYALj)yvZd`TepunY#mNaT zfWg|Aq89_b_6?3SJb{{#)gPbVH2-(>yX=Gk4o4d9LN#7{QP+=8fN&q&_+6Az9Dgy^ z)N>uyzubLDm>@Tz*OubEcV=T}vs?W(T$;j9`y&l~AZhWg$dY%-Q=mS%$}7rMur8GV zGdTW9VZ0_smqAb!lIG^HV z?nLMUx-(q=3=VhtXoI(zZJnl#-1x>>bsGS_`1oBC*l33I9by$OO9y*KCIM0N2Z}D=lUDzos#)%Hkms0i`?~zie z$;gs7(cPs5jb)21>}8i#vm#NBTbfd1TyHJi3mzONFkdXEY1))-QZjoeI?p z#_;A8R3YIQi@&2A!mHIN0g3Oj`fnoxUYgSbpiY}(*Mbcs7{=*mvFQ*O&c(Cpl;6QR zhM$zdBznLK^4?mg@=m2ntUQnqb9M`WxNm?4>&CBNPtNJt6ygMXsJbpOy2jseYr115 zat>=|pc|aCYG>xGnF6w%Z>$*~M6J|x7QZzWMEfS?Eht_#6?W%v`5mU)D{4DZ7VXSu z1N=%7$yOYQYhiA+BjNs21Z`)YVUu|PR^co_!c4*x?4Uv=CIH1pIBsW}0QUn{n@qLC z#NmKEfA7vR+E>Q^fkc(^bL4=K%hcOL+Kt4!QPKylK)Fg~AwC$;A$||#DOLB2@;<0L zo{krt>y9amx6m(bDNBIdK;3=u=ZfO%ciyl37kLnML};p77p8pA!OjP+{Umbec#VMk zFlGmOBLlbQIau5oJS3{6g;W!{Fe?fI8mE8dntm zgwF?9f9o##XZMr5r0KzTpY0x`4JFn?_t~z!yd2vAbC^$cfOsU^I^RX{0M0>&FgtD?ZU`pUMFW8eH$wecm59lRWh44haI;A7_kBt9iXNG&fLg#gy_gd6>U2RzM zHx0`q-`_M0FD4{4-Ym@9fKaN8SBOL$&ri%V%EYawJZyXzmI!rPQ295tKq)sZ%=>`O zdlZ4_^j?C+CWqD@Xo!SCNz;s;H0TAJil`~8U{fecS3VNvWB$qfMIg`vj2ED$hobnd z82%Q$3{}DDMV7*};LZaf@oemRmG9JZ1>TWvAoiGmbE8*{osY}Zo|)hmN|Y0ig=kI! zk^QO z_1QN*TefcH1OFKM+Pocoa>JhvJ#w^VVc&=k-R!_m5b4YPsj6^FNnu5b6bfDt{VM5@ z6vo52xY%KJNz$bdBVkU|B_XP!kxjSy)w*z+(rv9SoMGRMcGg=RXDx*}|JqVaT7W zamh^iC3RZc`91;}zMs0!o|HO-oDsh+v8=(VLwAEPLU3w<1D;C`rllSbsb^^COD%c<7}baL)bme)zo|Z? zjwF!b3p6@4Ot%P>N2Xp#pyJ9-t@vmIAiTCh>LkK<*7zyPA{w4b zaE6Ab(Z4y*w9>OvfATya-Jx0e2CQ|kvz@+eL%p)&k6Ft(q*;0>bzTbQt`>4FO&<+j zr<4T+j5Ys5TzLwnhK~bzm#(4k@dE+Sf05vC+e!+lD3BT^J0_*8f-*yXTJnFQlrg!& zN(=QTj*Q8b42Cl%mok{1Q9W;(m6l2rnHjSToTd7$@!2joN5g5P>R{cgwD1Ttq#Dgh z>qzGe)o3T(s1kn!!C@_4!8#bEDOd*s4Rfsn-{mS1-_<2P!zEt9EMCDZ zUcoH>D7W~dkA+C3eMMSk8iHKLl{r>g4>cCLv&Sg$8a|dN(p3k05;{{^Re@Q`stU~3 zR_#R;Il9{l9;}5ZINt@wH2foKv=mc{_>>={WnC-Utx7w)FVxJqz)CwuDVH&=#!4GX zU?^kybSo{NR10TZaRr0v8CPD(AV#*B!K{o~qZrK2m{raolpMq0;Eb6Qt+aDVSbj#$ zP zDPxL&OSM47y4(fd>w;IpM8nZwYM8>RHq`l&9RSiwi9gJ| zqYFTguhqg=g`ou)Gz`C zem|h$>xh1}hUXK0t%es8eltdih9fn6y83QKT~fLlGd*xOEKqo&RYQ=S_8+4=$nZ6j zI7^GWmtY>gUlJVF`1@!)(#f{>laNe}eSqLB9^hYDkf@~Rr2Xbuz%(YmB^fOHA%a=< z!vwSJ-w~|WhO|d?J8I+}rIF*7R;iYBtN%&Dv(ec};M4v4uW>-BwlmZI&>cU9)s%lE z$qfIAU~YXiN#oYn5IL>S6!x{WKmW~<_QX|2+LJD6Pr0P6)xHY9T1VJ)-S*Q2XRyR) zvLQw3mzB2hX~4?)H@O6Eb_slrI>_q1K>olY|4N*hP?gp^t?Cx~m)zX(S3BXScuMeV zF2S!W!5NrqTUD}-iZig0Q~%Y` zMbWbs8{}lFq0e!M4EFM5l4V~6qyH7!Xqg`+$b3dolgyujQ~jqcTT|HO$3bFiGT{M} zyni_IXaZ_^MIpD2@_%0J6>l3I9SYKMc!+A~)01SzLaM)1#x4efsij_+AeA1=qK~~| z;1SYuhk~S*CQ7}?k*ekWH9_99S{&p(40*cOnnL$_ONj8?>z9=*`;wm3msz$h5c`kl ztDQ2sL$gme|8R=iNa_hS;$HE+Xce_>=h?skE=D;6uO;vlE|e2le4^+MD5sRYAAGpc#o$qdwUXv z2)lxtIWxh}cM=Ic_KI6fGnzs-bC#Mp+{`)J&vi4pK)kw{p~(r&^i3Ft7l;lYOwn|4 zREL5b_2d+)p^qBf{$?z+(b_Ke)?D~eZK?A=x~+@c1*$ng$fzc2mnY2vA~n1ju6W`` zjDdW1@z~fa&dIQ>KCg3CpLZGfv74Bf>=P#7AI_V4!;wXmeslo;jrcE!e!^_xr_U;V z2_QMit;^X=W}i?HD9;$+>~u1~#FV+#zHooCL>)N$x2|2SQKBv-`y_A}xPcB(u8TCPxsLQvJa;h}a zU6^Ruw@YKWlUxNY(bRd0xwEl6n9AHa2_@u=+;#4f7IUYx=qdh~!n^xpKXNTP*k>W5 z`^*;Laseg?aIpXvFpx!p2Hwo2fp-dUuK*7T@J9jG3Gh4vgI)tLutm^21=z=cKa5;_ zGB(ftdFAo*iK)w4>NwIbgqnUiV`u#{a!#W@(`PCc)Y~8dOG#+i(uJu}yUZSJB~(A6 ztL;+*>YmqiNedys$Jp9dWjz}0j@v_6$a?7hebJwtzU8T28q>>Bg2;y-IyrTzT9KCr z>$NDMa*)%|a&Ru^$~jgdeih>vc<`GUkEm>{Msl`md}!J7g{hX==KK~l2?fJLoy>y| z!zMpocJD%r`Lg{q^1TvFhi8bY{~QA$4rvr(kOnBiFqJ?$wko6xgjSRr z)>`dSTD>M(?GmkaiB=kbRtivBeM00)tKCK`4M8gnKr02HElLUNw%$=~72t5IEbxxB z^^UZq0kow6)z-U2uG(rbZD|N?X#j000Buoz7`nmE5p^?#K=tXWFZPORxE&-WP|(&r z-KHA)sI7Zfofc5VD=gbx#4+AgERD^y>~6L!9X(59SUX(h5ecQ@;)LR;Q3=KJ@`U1; zG4A4V!z}wKdm5BXUbbkVK2RKOlTGNO%iT@+3_>pHDHRu(3Qc&SDI3S_I!aBt)MYo# z$8@tSC1DuIHOE9-*5ko%!tLE3d$K+CI0W$&WqAPKGC`lu1=ztr7V!-{k_iW93ea1C zegd2(z&QfM1h`0mu?!?kU-~sDCLktlou0IHPNbpWg7oWE%tr+PCHf#t#y6>@Lt)TP`xM0YES+lE^Nr8; zEH0j&P#!fZv0PrBP|h1O#*{r;RHrUmx=>?x&+=yHV3o zD#7SW`1={Z(1U-B@!qY_YtkP2mndns)SzdHH61V!RiDw9#F`VN2HHC{w9~mz)g0DN zCpEkqn~;ta%C7;px;vfBE9MsoE9O~d#r*q8vSK9?Ns)K$iM>MGHC>MGKD>MGKD>MGKD>J(``{m!$VesA>9dL1xj zwHm&IP@pSLOG;dLPd8-~EzEkV;wP`CxAzeVv81-w(~_jsQN;g$kM;C7P~~vf(+iT$ zIr0Dd^|Ul;J!$c8vYvhkUDO)3*{qan{p)EzIEbQJZKzfqbUmGTh)l*e&3bx}R`hxr z`7y7jHO6Q4dg>~x_0&~X>#0-L>!~h#w7`10s{QqJ71o3S(18}x94r{2W%uH2WkNJ9 zr5CB{_Lti^LetK2dymkxv)n!@H0><6+l8i`kR@aCpG9xV$E$*1MQs}T5g|IHEk}p#vduM@S2u_$d?gPt% zM)y8QJok}5J>6DJNK!wCTzU>3%Cb#;&K1Zan9`X<*k+Lu+J=-8+D-wf z5mWF8={X7QS!To1vu2iMCppELNX;jkqTa=%n6j}q?OKc{{{@9d(G@I6U6#dg=D~?h z9$N0@jFB&yXPu?`cc0niQSs&^`bM``?1SxwppW;NUEUCM(p5q)T_rp};VNA~rD<9e zCvxsLWgJG)fG#I3;~UQR+w>Gn(z3AB(qmak%hJ!rMpQ^MmEWN#<9WE{voFAoSfpk3 zys{R@&j*cO+T2cArI?(euKtaO+$rPKfNuTPriVP8=w0u;M%Dp8}n$C6NrLf`a||x*~+M zxhrr}&19*el&rA@h4kgR4JSS9Q>!ChpJZ4lvOlVYA+j4ZN!osJNqEPS+4{JW&#B>c z7*;)Fn$#l@dQh?8ni+G_Ks{rc)Kd^tPcz0+5 zk|aGvc*ZPA(o-Z%Q$*8Zz6fjL6j@?UKL68tHHDG)Nq{Jz-JSlMs-JK*!U^BQhWqy_0#*}%+sR)=dZ|mBCDPy0i1hzY625Fk41@jJh z%ABuQd{d@IOY%>dI$aSkWtK~gZ^}Hc+h}jf;EhejeB|icwJ`6LISNjVuC*r#uXPHV zGJ3LnZ3dg%{4=xoK8wb+FFF)bCO4^mx;kloTMn8utJHA>3h6r)htEGl=!S1aqQKotSA>=&cp=pcmYO}@ zkbb$UY1gapTM-cH5BcW}TA0^A|8Q_gc>Pq{{#mXbG?RuL|C}~h{L`V({#Krx=%3}u z62zrG(Lc+R-TvuFQ~s%o#y>AM+Gq!K%E~{TqOPtmt{`r6wWTpW+$uFb z|Lla(KSLucXUMh7>9TgYM?5a~sK@121-sn1JmXo8?a1ZkI~4&g z_cL7^;BtS^HEp@vN4h#`&LM}y?-hs7<+@`Gd~<~YU#_kQShCNTnuK6Uj#8&;+6{?# zUzhPM3W;}WVP2Qpd~iv4saM+}G3-3K4VQb_xZK#Y#pN6d?PB3jV#o`J#07_7m+RCH zAqRArwF7oA&1nyF%E|$qqOR^}$h8CNvUb2up~RTiIg}XljtJ4C^z<=yq&4~7vjWDE z9Pm4*BESK=4^eCZ4tTn*fdi^@misJ5=d7u^x;+Q1a5SVfd7gsz8IR*jbx~v`U=N^Y!;c>WM1UuZi z{D5&IhkMtl2ynQsbZvmcWfmxbZ8_X|y8192ZldDwIo!2ckUzrRp(_F$?h&c+Iox}? zjrJlOUg?(LaQn3|ufv@T$AG=slZ5xawe4`9m?6dhTk5?UxY{e$7K+0;6x!jO@(`qo zoRZX<1(%jsr&>GUZquChFsG~>&?)NbJ%(I6pe`Rzs@k7=9Pl%b1AeZ4w?(AJjui3E z9oB{ePH-v$9PnCQ8{mL<>zcM4@OfQ*7!J5yarhkY8!gE1fJb4d5DEhv@D!=>Ip8E! z)2`d#opi=<6!EUn!n_Xn@WCbF#dRmpIM9bA>4lnen8VGFo~U|yGJTT+-V_rDbSTK- zT%~D9V-8PATz`kBB%CoEYV9;5QWE!NF}#pOx2PSi*!&_(m(NR~U#ikamrGLQCjiP{ zFEGmMYNxE--6`w(3qADS@?9zNGdW{=^4WbwZTM`)a8nWBvjcQ(fX~KE&1q3*lqqk2 zsjlg;02{^yR&V$E{R>n#=<1}p3Qv3V^!P8iasfpo+x37rZ8cxv%685=6C71&<^PA` zNSFuD0DZ&=)n_~&hgR^5Hss*lKKXZ$^mmhZ=EVWj7jgX@*XEXP7L3z){}h|U3=3Sr zgf<&XH9oX#t2?hmHNTab)!v%*Qj;(#sb?Q?EA8|w1+xOv$8_GY7@gd5Rc%|d+M%UM z7}Jv6rXvxx?TZMS$3tm)*pH>qg+344`f9J(dmd7!n6r-=w;zIsuOCa1GujY5ll@qV z+#pap7Kc<%*vCB+_6g5~eNz2AmKszOa;<4ePdKNnC!|x>_3J$JPB~!$&Mh|c%&{Wv zTJCfzJQIQ@)1$gJU;@6VYuZ{gEEtZ4xvuTRfM=WBU2!B#7V_vEEy(ZD=jn>jGRipR z>jA?JE_gtV)a-YAF8Rh?x{dbs8hBTqF&uf>BU+f(%icSf?^tv^*SD(uQ{qbSeV8 zbEK{f@XqPFrY-OM4_$p2Tbe&84xe|vs0I1GvsG6FWJ;4T!fLbQ%aop`YT62ZbkDdW zUPEZCOx{_dg?YVm;lU;0U4?CX=Op6~G_FI;ZP{bdB!xq#o|00N9ACV~UDAH*)OyRx za~)CIb8Ae~+BKcB@?58=t0x-e{QMhp%d3UO!BOG?=PANqFsHkRKM%u!L69%iA>g8wdO?f#(uI_)~RP zkzNikOl@;DR^NN14V??y;T83`H$B-k47cUIuSuxTU%n-e^R zPG1&1d87mlquqr1vI=KSyVB}dK<&^ISh}6U%W|!b>c?gDaG2n7td5@$9X;BraZ1Tf z7CmN7kBDm7MB+f&Y#sI~o#`3JVf0cfmp!nm@^+ zbYnNzHeUsHQDi?m95JbJ0-Xty!!ObFw#>#MM|%TQQtAH~q&B{ftuLzHUT{NYw= zY$PFR1sbNH3M#50N)3+&d;~34so`+|&m%N#!(Brd{)%MKzG4_Xgra)S>Kw1sp(BD@ z$@i6mttKov*f4@1|4CeqpqJVyeba7MRJ3tdy%8L;yl9x=>YGH#_;rPMwI-MlpPZVH*sx4qQwr?ncFqwq4wf$T}Tw6?Z!Yn9kA+ z`q&3Jyxg#Rg20F)(6Z9I+fGwm`k+f)LoQ4=yw>i>o=4zqNMH(U<*C`DjE*VP_|aU; zN_A%yP{e~#18mLt;~vG`vQ(FPq!?lN{UhqhEGbk^X3NjK>)akB6|cI}HO1YMV9K6@ zG#8{&@iFXx7tdqINog9nwW9^j# z^yi+b!T?rT6;2so5AC6y$Y-q_$EakUTt^5MPj$plao`~C#UKK(`IGLG-A_+!MUUAT zWI?JO?COjf{5)<+jrN&NN2r~%j41!`4q=6PJkNF&Vfk}dVI_5_TTH@mRe>laP+)Bd~3i3pkf6L)PbZd9$Hppw!mDHGBo5fPX@S zq)#y87-qQNFqR6q*PzM;Jd0)7R%tbZ^Bk0b^bfn~V_T!>g{$-tC7_o^INxiTWV_)i z7jQ5wGx!9nzL6P@P1aOUO2Eqvs#3t!231NgDW%WX4obkIxkadc3WK7AZB=SE=X)uW ztl?{tmE#+kAR3|HW?H*Qzz-OeY|XTrrSK1j?f`>noaV5tiv;Yzs3dEKqas6oEpnSh z3rG4Wk)C3tj}p)&omFrep`Xgrr_N}Q)BFd9HyTlw&(+2~l0q6U6u6l;AN!Dyj;O&eH19wMghSl5Gz!s+Xn6I`q(Gjho3lZb# zQ_fc!eu39B4&$SVobPE^>*;d^Ux`iN7RCjzSsgT+eup3RxLm+ZTxDBT0=~eg;Hl%4 zMJkA}bw=1&-JtV5gsVpLwS#rF70!uQb&shZE*TXIj1QNMZ^{jD|$mA|q^; z2=kI|VUjFt$moNy5OBCbO&0JXMkQHuErDOeUWGoX*5}E4yYz3C@>a$}qJVv|MRSu% zxq$H?6|mexD&?sTseo4))MNn{c(|p!)R0aT@J@r8EZ`F!ZmqQQEjByD7^qypx!A|i z$F@cb_!*;;ty!tfQs~Z2XdjZ&Z;URZ1r%Krx0daEZ|A|=$=76S_QGZ2`J`J zbW(8HCdC(I#5Us1|Ie|Jw>XGnf6sm-Qy2pfFm?gMdse9=QDJ%&?2@g>`u zEUL-m297p6z$0*WZi)A_zBDZ+#pLm}WFSQITR-p`v{ z=Zu6;O(BVzm^WF#A&d%=yo)8TH*7x1LL%azvw(*p`D<=L*+U8<0NV@!*RuwJc9Vui zIz-nsOW_Wqd%1uvVb}QvL4*l0Iwas~78PVFdT%j((mU%b_Cww^0VoOoC1xS3l9{o> zL1_ujx5z%v$et|VBu0@+{`MwIdonZ88EH=z380*oH1w6fFCysh6c<6FE!q;$t+)vP z=d}EuwS{XeXqJLBgM*oXvYsnCDYzE*WV5)_+lJ|*7WaWYoBjHgN{EyAriA)UTPjw5 zf=3s0$v&4mf(KDFbt(NmS1I{gE9cwK7ieCGK|{jU(~&*DSqjo1oYl)GLpIalyRKOZ zs~L$oE1-yi?*3^e!h{&EA>dmqDrlPR<2o54sOcRFpuAQO=!^&|HXG8(0$#x=YTqy1 zH(OGfTwLkU+7~Wq6#W;_-M)7WgsmGls%$F-5r8oeQ2bocNkKFP(r)(JN+G??ib^5@ zMA!N@$k@|+g#p!;sK*_{`?-8ZUd!|6fQgdj0*;C3CeW6E)yQ$tC)vWieJQ-nNPVP` z(#Q-=3Y!Vo_b820+!}PgMUlNmk%teoUCpjD%+Q(B+ zrbtKYKRBa^rxDrle_|%U|B$`f+xpY2tyn(Q`b?3Con)R1{(p=AIMc(?0*Xp7vFx{v!V_~xtG8hTE zTm~=ZE2Xdnt~I#aW`Sh_Ct0U93%thA5x6g z;F}KWEuNY0^OZOS{@CDhn*|n!C4IgAM7-i#GD>7l@un}>s|Y8s3>-Qk&L(~al-Ik& zYJIr_)S|$SX1UE?_gD7d)`K(?Er`ym25BonQ6dH5yt;}1I(nRH^l?)5N8 z`B5)}SBrEe5N*gW1YXT`a8CgrXH>Ga(0@x%g4aD}d?yQd0lTp>p%liL$ypB0a0nXD>C&MNaQ&DG zjM5rplvdy3A{}u>ZxJQ|u#I5oHLsT~a16XAQeltX$+Zl4J4q^d$2cJPdZjBBys`rI z%Hh4`3clh%Env=%bx(tD2w(1Dqz(z^V_b(8AFkVwrNz9;ExRM)aDoXd72}R z(Y#0$3D})c$<}@&s&l(>=?DJ$xmd1@5jMsiuD9Hb2Jj zTLzcYEb!2tMrQ#pFsM=iM;xcAs0A_Sl&InSlEDFe3@V?&2EG!fz%md4**kGI@f~9O zkjMNBqt`eA^M9lp#E|Zv?^kTxJ*=41;yBIVdr|r+}L+Qg< z?!!dBVwMRgHiN9y3%piSNa@2SR$L*a7cx=`c_^yf%qlE#R1wgn%Gw}R)-DiL{^Y2# zc0qtDYw_c_#81%3{)tt1%uz)^mnxfsRM{NV>gGdgRU+4SPOAb+V1hCC*Ga*p%9bEi zwk#0$kvRVnL zRJv)tSM+G+tKn-W3n?f4k3EiU7UXoU3}`o<=i~ssrdpG44^O#0EUsO0ylzqOQm$uw zN32ToZx7=iM%*42+zP`p@%Hef+rv_x(>qa?lzX0_@sPEgWG%&);7;SZR7=Z}@>>S4 z^&QDBE@4~fWw0MOF|qBVI7~>`5PFFjtGHhJE#<#4-mR|-ar0I}(zDNnz_@5Cs$i9%)^jm5QJIQc9Cp9I+Y_;h(Z_B;N#diO4(@5q*pn zQw4O1NIw)2IXrX$DWv0A`YC*c6_B1iwtw$t!3=cTpTgjDzG8l?^vw?;n&}YT+a{6p zB*~iPHK8SfTr?(4D8!F*BOCaNp%UCJX7t~iiM(@<{yk^WzqIrZ*E*(s)`+PV zP{bUC0~5V09>OSt-FsFzP*R*F2%Bxyh^}%Kl3$y~CJFeEK}{1-1i{v;eJ6?wcwl92 zdfn1Hx4iYDl3WAiCHQE7yo7p@BUh0ZHx4*SK$pr1J{-_QLcNHVt280D!rGW zkD1^PC`a>ZBdd$0;;=HoG37iR!^3nP;zREsJnv&~tTtBRr#-v(PZx3T@ zCuKPbV-K~N_K2(TCA@Nd(I?}w+ryUz%2-P>Rt3mN=k}%DRBM{r4@=pl$5xWDB|wH4 zWE1ZPkx~-yu2Z$Lu+`=TQh11wI?p7f%kx~M(nYz>W6>g)5(ytMZntT+RKNzO*`;+- z_^E?jStkY233D?*k)^&he?K>tm#tMJMMO!t{`@2j<@I%F1isqfa+?MIwZWa*+<3C) zd4aD;stEW7qg3+gLoS@!Pt$`=z|$F}=+5Fog+nJ`m7DHHEX*Q;8C-7j-2S?~ z<$NVNf$afC7Xi}^s#L%Zr)VnT6qEPD3v}s&UOdR)J7*Zw9tKYutWm@%@IuCcGmRZ+ zEMI}r^v?Gb9=#hJml04r%eKk{ba~{%K1bNh4NI$7Qk-@)B&~__lHR{&zxksv%s2s4 zb2S_0%JKz%%ZxjZE#q`~oHXuqdEB8W@jNThi)RFKO#b%9UhHdctiQdnhtl32Ztqrh z8YJKZ9Kv=$r&rGRDxr{~#!ropjK7OP@fFAuP~1kbO5uR^Pq3 z$MY3YL_o1EWNq<{rb0@`=5lWoQo1ICtdIfv`SUVC-gX}Ab%CRwfG+*E2I;qTf#`Rs zqum*LT}qv*%Hm0GU1gRIW_q%4bwn@3A` zzHelkJ-}BdZ6}x~xCS02pcqh{`WCjGwiDsMF(Rr3bcx7JJp>W&7!gwibcskm6cMdF zqycHW*I4>Te09=xg4yY`-;3o$`HK0m*_V+MqP`B%+BS)#CqZdD5#*u?JHw};T5ept4sT*mA7Kkf+X=}y)7T^ds|{+JfFcOC z_NVPqc}~jQ^tz>YZh7my<_wUR;G+TZ66)KMSHOBA#tP_CIl+emnnKC=OcGE!!ubbBX-+|^w4K-1?Gk$%zm^-7mXMk7$^%MzFAD)?fRudu*ZtoQlFcVH?h0ZaG)d+YV zqYif_2%jkO1GufXBR_yhulY{r-kxg=F-bt_NW0GMRk2-SZ{ufjqtX)G-YX9%@Af8e zCd}ca#2UWJKFizWGOo9Bbd99#OqtK{*h96nl`gx}Cqt+00%WL^8|DAJdc5J2q0@E& zGWL)jtpPG-@ILTulP@V3u;xtNAoe~2PG(f_!(k%pej{t5fSU|zvVbD1t=~|Iw3`g; zBmomx1AZ7KtUoub)dD842K?ITDQ5XAU$Lt@ntn3VyIC-=7@?DQG5D#25^y|Qihdd; zu(X|IE%QAL=NC0eM7h{1ye{Bn3)&3^(D+=bKdXDX8S{w(Uc{&jYwuD0mrYRj(2j2N zHtv~BeU1?{Nk9>V8}Z(59KqDb8?I^rg$qWT&=%WqhOI_G7u!@X+YBb(ZrI8N)Gv8A zZ<0DovmVJ;j+Z}1<*(3q07=;5% zDH^6l-lzT)rn%9u`{FR7xIw?HoBJ|oqf_tb2FW=`+$g76;27g@CtScCj7qi^ENqrS340dkJmYK);HrUqrMvxJn)Xz<*r-q{ z;QL0?Ss{U24Q^&gv^ro|#|wD0QM6P*NB8kwy?(`}5j!H~C7^5Qzo_%_4d#YN8U;oP zDEiW|RA86xUP;y~tis!Tg$e@7w1)S!m^r@O>7alvRa<!fhAi?A*EL-KVE9*+@F*dS}U&)lNd@rG}dfG)m!+vJl*5H1CD z@%h8Ev?s*2RUx481&2#NW2G>@VRRoaU}rWJUNI=3SWyR6DM_R444E{8I4huV!KD6` z#INb306zap;^(Ul;Pc0IKVMA%pFghq`6dVO`Qy5uZ%P24Kdv|OFv%EE;1xJhAZ}YF z;2#);$Kw_@OF;%R;F+=AO#cO6gW`I7D(p30R4U-ZM%1j3z`r-RnIX|?6XW1S0={EV zr2;y-`=Y~OZd>e#pemrt)%x)_L&mH*!ib%+Bt*d6a*E#Y9bjkO}^`%Vzyx}VsaO^NkeGm);3>6A$ ztbnI8isD0gvy^TygyS@a^DQlIGcA@0C|u}~f8Gh-3x;o$fG$4&yc0fY1R+~e9B_Er#tcm6p@g>r|uG#JIj$TpiBN9pZ-gk z{&~Y!F5uWA-9?(O42FgaYOH{#Gm7Ucmu@hGK1)lB+f0jP0t#2~d}aC<4BsdLU3>xa zmFcAsny-vH&1gAZKzDaAUwz&2&sU%L3K8#8D`39*_$mYV0_H0Zg$#+BuZ(LpBQ{3B zml&nzD;NIm&oQqxjw4?{mwf+h%xC(`3}3l`pBvP80q-c* zc0&vla3iDiY>`se^Mug197u}|Mtqro!Uc=@cca2L+3<}L(8cFp-GomX!RjWUi_brM z{CpJweE!+9iS_%Aue2Ble2TH(SOF(8N@WG5AjS>I3f40HHGBQQoglj5%*OLguS7FE*tx2&##$pHDAFepm>s+JyK|KG!@V#-#>fSvD{Z3 z`2vba$ZxIN$#Rp8+m;IGlJB2ASxi67@RbWV=X^Z_D4+{m&p5QXrA`WebrE`_lE}Z< z$S)I6*dWXAyTX@a_(lo1(V)f&SjHM)RtqS~(X1ZTEG20(;QwJri=rN8nSjCtP5raw z9#&@yU&);X{>G>}UceKK1+mshL6ix&z3t_?t_ke0bTN2 z>sGSdwT^rNUGn|2Wed}PZurUt>@(7tEdmc@oSH3Cp#M)zpP+q`$WJTLG)OHAC~SeV zMfh5bUZVt@V?>P;FvJ>Qwg@Q7>DeMBX)|E9NQ9eN4|hA`Tp6m zi0OZ4_{s$wdZ8W<$bZ$BuMoZ`4PTjn{VtG+FD(w(&8^o zi=zY-u0U@VK2Z;|KtLCte|7WoRS4gQth+jIF)TfR4mfWyQW^+~BR12VZiMAD3w#;l zurm{IIiu7LP6}eofF0aJOuxz{-kD!KLR=@!(nJ@ynClQ%1e|G5r2^h%q|dbcO8Ith zf9A0hLt>T*D5iq_{Ijfu`93paUn-!O8hkkausTPu>q5SOF8Tgh_6^HD!s(%aA`!cvU;9y?WpI{NMIHCkxeAA#x1^mRI#@^3U=eK+% zJp}%vnO36(e2P){A4ChArLf+i8_8A>tB^Ra3+(6CzvC;>h)GXzsUh%aBfeC?i;WsH z?B=;#xX+;rvl=Jxm6{N^r0s!fZ%}?_Y&K3nF?zC9x3F0X(vyH4=K>yonR9SN0cFO( zrS|)#-#l*mHlxBQ0mT>S#;w3EzwnygTI{n`buQb(u}ig=Z55NeWK35sU|N|b#`F*{ zol!dUN~y{rY)kwOBfd;P;R+0cqLvWjnF|43eE#)D_+BvWjS}!ZgBmTMOVnGwq3JYQ zcjh?0Vn_v)jzjf5byDbStWYYTOTPbT-iPJpIr0S*k;-LGW4R+8`2xD+`(3La%Pr?C zO%#EzblMcK$&k$o34FJ~%?#bc@NW!jm4HteRH=aP8_n_g^G7$u;XjApCAF7tWkg*AakbG8}Pasgi` zmw8tu;46&M^G-@X8e<5(@X5K$j^0)>2duV(erD zbn*EQSRZpc$8n#@KLkGCm}b0ywTx0@FNFsjI&_vSqub+6i~X3C@i4UyKw@Fqja(6)1mc_Q19HH>UwOmREgJgrRQ2a zG*Tx66L740o>hF?xIw9a!VQmVt&_qo$0!1dbVcVKmk(L)SB`uEMI`w4)a_@v>Bjj> z1$4>xhubWsFExDS0={KX;{?3wVwqZF1pJ;+dJamdiht)BuqTifYmE3Z0Y7C~bUYSV zB%+sV>!cv!gU-<+|9m5Vlz_qpS(|+eoA8M;n3w{7Xhc;B_yVh{j4uUIk8E*Kvy`OO zfLr+fHY=pf_f4B)1(ascCSIJ*3P>x|W<@}ozDeb8vr^g=P0@fqaQ&JU5(`j-5cnik z25A}rM;QYlXD$U%8g!y}I(te`!1*c5xP`Aob0pKZ$Jd^!UmLqt37BiNFBP!Vv^die z@m zzP9ppCtr8-b!#V0H|R)xmH#DO#pM}%UBp-UkJ#S!zGr!_vb=wmU;Y>S;qs5`s5O#* z^L{y(|G-zTzJFo-7QV_qW0!wJ?xio}ehugAd3+to*D-vRe@E`+e~0Ve#d?Ug5fv5NcQ)#r-N8YBOj`dTi_|CRRA@8tSV z_$vPzTK-M4*Ds~rw)B_((JcH2qpv+eYb5_%`8qBy;OmWi{eZ6rlPCYBbz(%zm48b5 zM=n3j*L_`ez5HJ{`HyX0`>ajZm>qnTe^Vv@{LM>$aTkpl&(|4zy_>Hix!nrBdjGwY z_uo``<>wruF_nD%Kh(W(Dr`XHchn0xA!L9 z{*Lf{|43f9oinI?YFf3p0-y}d)!RhI@YF$yu}@M-ajr?#4D6rXzi-d#vh&Nz7nk)c zDK4w7=~-A-O=iVqc_r2PekZBAsxpZZNx8*=s(QppOiY?qUb4tvS>-qtP+)OhATbY} ztP14k`wJ3_(4oMR3V#*s!sQ;|;&Rw^9S0oY-HJd_r9UTM*o`VLEiEqt!zQ3>kJ?-y z02%FmX-=RhFQ=r0w1?(a7ncNj7MBgTt<>CJUP8jXwwb4$H@VHZ4~)NH{K9)uzjj+> zxVxPl?Yslq%-&gR7RTGf>sD{{4XaCcTNV85?s1M!@}|b#H_Z24tuMW{YM6I$4A{6k zBfQ;PNBBCgNgp<3*nLU9wio?^cD~MazFGJ`7^WD*y%FBT))6PxR2ANu^y5YDAI|aK z15*YjfQH9Ax=n4xt(|vbk=VxT?j~ zzi>4ISI_lxS1;Mv!9U)&*B4tjWX`$JF=XWHiMzva<(-0+@aDLYzM;$A#RuHx0eA6X z_bq2fhpO@JQeWoL?LF4iZ9&tBM6=Gqbwh<4-Il(g?uYJApyxc-29&K|d8pu)Y+v&U`jYdvIDR@UI!T5qqm z-a&1k?LFT3*a+X|2ya=N&z~FNO={=uYX0xp&P)HF<}Hc!_0012j~V9e4gXeqOn84E z;Z1KH;Y~J)8ti-TxESAMzG1#keP_@9*4 z#n&3QzCzy%zN{fo6L-~$4EH-{k9*aMq3*>VU&h|TTYX7IYuBzT1VgWT!C|*uhBqPB zjq+A{+zY(jJZ@CLz2G=>AZ#`p(dIrAh&~hVn9Z%k)plG};i?!{?$Xt`@m1p*qt7*g zJ2RM^72KPMduh0ujjMcIO~BP-xH8#HF(!vOx(^Td%&0TaXGWo03gplKViIgN*niEC)2H6 zu>-al?h`A#>7I*y)!(_%$G`dJ8(6zNkGsEJgen(%3Se(DY`4KytE}{7x+_6tr6)s=|(`7BonI|ac_$Dnn8sl+xsZ4Owz1;>6k_@yzcrH2i#ws17?5q3hy+Jd#UHRn@U6Aw`FDs ztaD)q{J!iYL*TgA4uLzDWAMJ`EpYE#z8SXLV5@`ofH%{*4Mfh1l-n#-7-AM zZ=0U-MAqZpE_iL^@x346jfYFP`PdUU$m~q#R^x>0b(@<_GVTw;)d*ZoHiwoD#f|BA zoZh`taAoGj6}WdEuH2<&RyOmdi3l^f8lxU~d=joqY|O#E99)?q7T}&+m5CcX^Vroi z+%x4|YK||mbAAi!Yk@Dr*X4US;JRl(qvOseb8oA=-Oca~_0`=57Xq(9h`}YCTN!X; z4!h-!- zCHO7^9$sQnp*EU|)YS4~dmK;Fwmqlgo~hdy+%v7U2=`3$m?REbuUV1sXapvsyTmwR z8k$<(0Mo#x0gZwmGZ=5(qEKs{?lY6+Cv&$zV@>47dd)cw*GL8K;s9)hPMjfP=B@~H z4u`Q9@x*x}q3rajgCSzvQ!-%2@8>Scgll=XWnj#bVYOG+x)tb&>$dmdXooJrQf6=| z&pR-`{_hvF%M_CV*AZ^iHur*|8E*5;E;HQ4iEi`N(BZz@!8MQXE*uNkM82UBw@pcz zHW}J^6jaY$n&GxsJ+@}pnpzu^uG_|EE=6o@GJUOWfbDg*O@){+5j8Zh07|GC`c-54 z1)!hmF3lVXGw*23!6m@$7lfo~u(qu!Q zH+6(tow-T6Zlcob+Y?bcxsSWVdR|>!3udNyT8DbzHg^uYz8kK7T4sO^oVYXGilJ`H z%=npZHEcIefPTKqcZ2Ea_fW!TE+5~Viv(hDb%eJo%!GKzYX2C8HcAr^=jbm zV%q>yKalXaE#Mx;cOc>=NZ6W~X$x;{?YuGr_j=;W)XojqI<+u$T8kWF=~LLcP?2sl zlh)N`z3FCU`nKEwj*NhN;63i*OgCnuTRs#9lZnL4WNi4B8EM1k)P=^B31hD>BjPp_ zfjBobUky>xeCr z`_8Pch9L(dYW0}QFwn1o@d|Y~%hbV#z3Q4va@Smviw6ODeQ{cX_IC4qab_@kTfk$a zg5t9L?5gV0ipui*>b!tcx+J@*3LdjKxj9vScueHa4mj0S{%knD(4QA5E-&LJG;qLQ zQ(RV1o?Tg7R+#Nn6&IE*f{Y;6+sUpfDz6OWRu?!`75=>J;{2Lk!96&dJ~_LjJnwvv zfX7HsT3&TYPQag?S6<|=^q1uo`>UL)qKZWz7RdKkR+U`5Nz{7e0n<=IxQpv)<)$) z%vlojZDDzFnUf6y=i?(Yda{+B2T#8OeyAEeD09G{;(8828-PeQw{3oL)%n>DKKClm zgBC0*4$7E{XIGbDL$WnI2dway_ygHa^&-d#Ekbp*#>O-k#W|UL9}> z^8F=&9MdILIg4zMWJA|im%*`;@|=KEbwOpIUYkIWpufY6o zvFUIes|Za64^aJ;PGL@URaJ3L8F*e6s4UKPvdaq!Al_^pvIFJWP}|%RKSnrU+u+$b zFxoIO>o*%@s(?WhD1d%}RxK-c;Bjn~lUGqv1L3|D#~_02s`Bc}JV<3*SJ64>oD!%n zG=aae)VPYzR>+O`5=fg0~gJ{Je-wWa#fHNsjl$(rWv;ushp)Z~lPqN__9FD=H@wx)q z7v_`zI3b$I#;yr1+oagICQ?E}uU?1c=VOaxXIH^|P=&Ew>0jg|6_uCzlZvV!;jdnt zR0Yq;VN~_Z1E-U!D)Y>f@~JuSyqcz@kal+V`7lW~9(vG%roEs-c9`=>42A~=C(ML) zl$k*l3Nz|J8eJT!rVWGhFU&*26)49H8lwkQ!AJ0+!=ah9dXedl>_sqVmsdjMz#-_U zQhzCiD9oSGJ)C_}aTPqAFDVY-v@5%urXo86a^c-Vbp-`hX+aLmMm*VYNKv0~>^Rf_PEv98Vh5a~CcwS|A=6tll(E(AIWgPGwQ~tL+1r7+!t|_cJRUFL zN_J^=Nq$}*m@!NJ*;S=5orVS~o{#cyTKCsfn6syGseWK^u`t@oODjtJHF4xmy-5Mb z@&(DzB8YdA#*D7lbr6YWeut(igNqG3SMrR{^5%L0a5-K8EcKgnXCqT} zc}YHZx%Av#2h=N5Rw6hX+o=Y-%$zENkzr=J>?*vT30+FiY=u`q*nDOx4u!D20O77P zqZnscn1;<9*vrZH=OsJ#S|FQ(1xBlrlb>%+9ykNaDxeayeXbw4xi-NICYHxp&b$vR zF3YCtqkMa2hk;uEtjSr>Bd63}SHL-~pa8^ec?F<>*G<0;8;GiW?m7D zVYnWHODTvwoS$Hl#fwd>8@S1NY;0glLrwelh30a`jT$vLt~*>jo^7uk&6~(J_I;$| zSvuA6)I@tuZ`I7V5#EbN1E)_R1bj z8Mmg1JHyi=vcssz=#i1Wk?`>}xH+aWHcDMrdFK#kA zviq8*-uqfa#*K>X0LQ2prvsS2NBQ2zt#?vn^ct`C{AVM5SdU-m*iE!W)(ZWBWue`e zHi#n@MO)PEOZ!LBF4xFD2eJ-+T44$udkK$$KUaE3Mt0cXj){!BqDg9G_luj3j!a$? z;r(Z1_fe5?aLBssI4NL$1?Bx3UM!2eBZGNIVctoR3*1gkT0|y;95exFj5XDs)N%r3 zdxbO>;ztib{3_mjxzF_Av(t3!1B9y~reGR}K>i%4H;OA{Y|oa9&s_!IyxCD@*6ktyC! zZ9c3+UpjUf=KrfUIplHUyp6UAcwyUkE$Qv3uLm*HpwGrd9`m>p|3dE#(mRg6hx`la zK@3t1jEmgmaaY5TYoyPD{v}{M!t#GhFF<;KukYX3V0}aWrDKc^{R{tgliq9f^~N=# zHxcVQ`!Dp`G`00L{=wj)F%)XQk-_#Gi~bG&3%&gR%XnV4Hu zF1_m?bj65$#VE}k%Vuf$%#WL_2gX;?l6iC+C>Y9J&~JKvvMU6_qewV(vE zpOc%5w~z8LTpQL)=a{D>;E2t^DIthA4SX2P~LG*-+5O|Jb zi}4p_7gXky`t6|kAFksdKZXr;QBW;~_Vh>a@w_b;{A4Ok2NFA*E<{zwIdZne_z74f z`~iPFSKx9M{6kJtC?d1mlF57Zx4Nuz;V^3cpXTocc^j*LxbkhSLO!KXe)@!E@Brr@ z93*y!^YeBx<;T;rTnxIaY7Owc`v@ z;@b9Ru+a?j?c9!Q$9d-xyXCdEy&evzB8=&!o$STV0?LT~p9251|MvmYnwaIfftDs? zLM+<$ZaCIh`A4XH&X48er|Nb2JAi3TK+bbiel&fR1sN{dc7G!b?cPkamRTw=b^bkI z+nE0yI@|W+en$UMO8fsdFx2FF!me2or`wW-;z>;`Tt06P!?DKlFU+mr;JrIUjQ`0A#kdOdnY!(A;U%c-?EJ_Mgkg@M^65M;F`tZ>xj4e8}GDf_6=u z?@ly!&}~o)YvnfT3HsMq3nQlsLs1BZn^)-L{Dw;x_=kKYCSZBiZXCSdM$Y%K=AItd zMB=+BFwDI>uma*K>W@)&%POF9xh-+r@hKF1;!#2k0dqwJy+u*Ei4P1wx z64lS(I_>HMO1Wla*FV(W43viL(9!UQ?GR7)(e-7V?qsi(5{YYj`>dw9x!;#kgGoP5 zLfB6suIKNuq_6EK5bv&ZrV`gWnZySw`}xFE6wfD~ruag`!^Im1Z75SY*z2~zqpQR4 z8-TZlINud%HO%uQ*xee&9`BVK(|;rk-w}r6JzirvyTkAo!|>O`@PlFa=VAEKFdUzS zH0I~;Vfde6xCiFb#&mqZvHkh4Xq)F-(Eg_~A8h;O9=mbQA|5xSKJP=E|2n04o&_eu zfj3s}NV4a@U1y$gf&Hv7_QhfNx-k5TF#P5)d}|o~1aNF0{!>Nf*%|nGfH?on67$Ro z_*cwFLjo4&E}YL%hkq*GJoAC$?TPb`S-%GThf7c5{Ik7mKa4p4Kq>Qy#QCQeF}`tG z!1{Q@1&ew10^17UjrGf#FnmK8etj6e4LH`9e@@9fqk(e&MVx=zh5g)1oPTn`JTpOu z>3uAGqbl@?3w#F=dXL)Rt(bZ@8nO)gDwbc^)O)SWFKg(%Pw>?xzkHML%Gg#oc@hL4 z6Cn}#0gSY4e_{o^6;Dj&N{2j$I>Ni^P&#qKs6h^_dco5hSi@5rSR2wi8TWhlHtRlS zZESk?GyBG}kKug`hwm7IeILUIs24=$bzl8AJ@m;Mepwv+oCm#M11Ir05WKdvAEGcH zTA`0C@uxY=XBpJXEXdBU(1I_97xr=%CiBaAew`I8u-?Zu>bb21;Mtg_d=g5 z75aQmJ)&NM(F;K~p^qwY(tMtY6ZPIJ+g@wrV_cz7mY>RLl=c=O#bIjR1cQ(I<1;Go z8+_=dkvF9Jg(cTRe7q#|QJLV!;OblPdqnw86Y2tQV#OO=94+!AbvU+R;b*QeZ8~Sd zzdugcC7&+UkmLOm^Yw!F7W{g_rTsR+rF}=b^hN!6p;I1)KOKgDABNAS`){q^hAyo& zzbFiEg%3|)(e?>+y`=fTFg!mD4}{@s!|*L(_&>w&BZ7B
8R;X@f%wEykH@KnL^ z-jQ{l6}+F|ZwVeRcxO84VY$+NL>Rsx48KV51fhR};P~xo_Va$hr9T7c776{4I@1J~ zI&%b*dn@q^|El!3PLG4~5}J!tmo^cpw%;VZm~x{;DwiA;G&s9&Weeg3I{H zq+3PQ$9o*MuMER47hJaUX2E4UKO^`cq5lGL9QIUo*mInh6sKvyd0pt>r8N8bzTkK% z&HNX^W&gHni={LiFKL3~I|bHxhHja$eTEADc^KZRJs82F?R$mc^TY7V!|+GL@Dh3; zhULnBUw*1_$N19?cDY_p3NG!x6TGjmpNtOxVL|<2@Q-zh1jl=7<{Jf%7yKIh5CANw zgWs)Z`>kR46JhvkVfZy?pnAjpO`)ICz;Z{xKQ4DcXSBBV17OR%C=9O%!}3+l^q zj|tubjjjtd($#{?dFdv><-D|0a9QqFyqJRp%a!B$MZr_x z7{|$1f=iuq&PW=?J4_rjNr0=XA3U-cS#t28xBHPg8fU^ zD-ROa{p*gj_Q+-bwiaCWZ%4sp|DF|wrwT6Hc|395zf%;Cx9&MJgpO?IY{6wa7YHuP ztst(;tyVmq%3UUOWVsg$F3UYAxa{ARdCtAZAOLqMe4{{0{f|4DG!&Q6;3 zO!se7#krkZ5J&%IJGT*Bw)5$N%W}IaeQuu~igUTWgpMqCh~To^Rf5a@-5|K^-x`L+$?-d0aM{1J1(*GsFSzXAvM~H&!DTydB#wT{>*wnf=XSnH=*V`y zQ*hbN_X#e`eOT$6MuF81#kt(4g^n!uMZsmcO|Wrc!FZ7U8!b3?5%=#If}-;Xb?B5ocfCaP5{%s?;?B7mdcz?lVJEsuW{X0f+Zs!c4Binhp z;If_P2rkR@D}7T*SS?hX%e_G8$Z{_fT$a02aM`~v2rm2g4Z&sqejA3*!Onn%`OlJGUT?{>yf5Be-nm z(*>91c2)Yu30U<|oXhPcbY!_h1efJrB)II~%LJGGdxPMze;*9PUkSroOtCe=_Q`~Q z++Q69$489JuO!ZK;xxDU9~Sm9PFkF2kE6bfldgg%3H>U;vjo3VaA{vB_-tXnQ}8*0 z?-N|c=WBw?_`7x{{Fe4a0K zWPDZ&F5~l3!DYD{l)kAXtgcp^%e_J9$Z~HJT$cNp;Bs6X7hH~u6N1ZeaoRNN8MeRV ztB7+v^Y;p_7xprq-xXZO^Ou6lxcWhG8CS8>L;lG2JcGFI$9ToLJ-Z1V*`B=wm+d)F za9Qq1rEk51A9qlk%bg^2WVxAw%W`iKd?bkTd~&zovVR{ET=wr%g3EqdyKB}h6z6hdh-16Sa!(grmYXBE?B7zsW&bV}T=ws^VfY!dLjI#q{5_wE z#F5GVtrJ}K?`wj~{(VpI0ieZk`&k$siziW7uw2>BXAsx@8?Si0$?Z7ZgpO?IUV_VZ z9w@jhccjuMd1svBT<#>HBg@ScT$bBPj62yr5mXS?_f#$vwygxmkGe#7EQnFu-^7Bp zihn_zH8DH3H7{q=4wiccL)az@j^$RO0WAIO##~P#zsT zzt3VfLH-|V01A^Je}Vj;uk88rseZ*bHnX5gar|68E;V)&)Ne}lTBCUHmKJPO{2x@W zI>q_=|4zj(CH=oE-i7?xsrb*-zb`1hmfGQU#TQe3KUDl>(m$$rHR=4UcrWUgCR9H6 z*JorGqj)T}PbbCiCi`;~f1s&7KwlpYwtrXZ_c6+T2?gm?#d9f5UxN+mtfF@2@71vX zS5Z4GQ92vQ{$j;nrvAM~@$F>4Me&2=|NV+{9OL(^aAAM8lKu;dXON$7DDEfyj}$K< z{*B^Q#PR!2vfQP_@%v1YuOi-7@eGQSc*R>#zo#f(ZDpNtijSp!pQiY)RNrjH&!IRx zUvc`{x`W?0lKxkb{xgdAA%FHOUPI-+t@xePzlRlHLL9!_S+88yX{mSw<;U+W;llm? zF|{X;d*kE8a-?=wmLcGPZJieF6r_!aL(=?fIUlHzl@;$5hn*DL-G z`G1|_C1iiE;*&`KVa2)qcPY;8|DNIzRNrqEXPw^^XPpT0m)rj_+l)@E;;kruH^qOT z_Ux&64%Mr_;#ZRWD8{$C#5GT{v5^sY{mKaD+&~EL*uAQalU@NNbw)Y&+8OVr#Qb& zasIyPi;AaHxh-kS?REpTe|yDSQhRn$yp-Z6NpUW3h~kyhKI0TWmD1A`UrPE56(3AG zixe*>KUXWhpW2P*d#-OOwcAa~ehjtO-HO+eKMyHBhtf|f{yD|#KE+?6a*rzh0F`@8 z@mr}}4{h21C1iJs;@l3WDxOQ@v#a9xJ#k$4x|sDje#R+#j@#*q-$?zEt@t|9uU4G> zT%|bsxj}LE^G3zl&j%HMgW}-{#owkh|1JmDH=6qEU1k4ws@La=-%R%3E54HIdqVLT z@~0*BKbMmX9{8Q5Hr#SnQsyO>IQE_fJ{u9}F81GxTUn-RSG%EK(#ebl< zy;^aemo_WT?SHT0gGlEQ#qIB1IL=)5<2LEtrulO0% zUkQqHeD+eD<8!#;T;ExWPo(Ypx zzMuJ#IEFV33Da(^=)n!Q=k-)>d%;gLmUh)O3?D8y_R9^_4(Y_X+?AxYK-qJ-S1SGh z`E#Sv;m;v#SN5#4Q*iWu5S6<(3_qxNCE4?zNayyMK<)f3*>k;iQKRyoCTDy0C*~9@ zh#dXl>w(T;c%tGxX!o`HNBb;lpMMCB_NQQj!15$<_MhL^ys7Nj|6c`1{eO{u(?}y1yq_IH zent~#eSTliP1&>lc)?M>2VGyy5*+>SOL3m7^!a)95@pZ&w+fE>e4l)~;2mvM%=#X| zvD|NI{&`4n)VZAed`fV%Uqx}Uk2tpjf1dUuWzT-LjxxoY_C);$_&QT?)aU!*vlO31 z{o9K;m&>2?OjGt;?h?VV+)t_8iv*YDZczLX#lt4zTrR)Y{=2g0a(4)h<#wd`XHOW; z-%sapT}1Zp348RD$I<74qkbLPe@m)hjn|HIvEW$WH%R9y#g9?^ zUn@B3Y$2Un1xK9(D)(;1`9A3H#JS!6B>R5}d(=Nf`p*lF`lnNz|66g^e?#fFCHpUw zJwN|BuJ|ov|9cpncJ!i!`{hrv@2oiMBoOENjwAcAiq9uLN$6lZ#9>BQvINI==ug)f zxr(nLUa0i>`towc_fxso2pue!&-0d|s ze#Ji^UMh63++Eb~HG*TgeEqRX@tdjKiv>rWWSXC^5*&3}(s^i;;?s%WMx5K{SMu|3 z!rrtG=|3ts>OV+2Pb<#)&nx{?s2|@|_7~Fpdsy**5dS)i4*V5uvtU1>KVOo4jN+`* zjyTtMJoz(F@lxU`LdWzMwZnM9vD|o?N2V*z^L3Wee~k2(D}I#tI-!H*9;0$M3XbLS z{ClI~-;zI@1xK9}I`7{jIO+_d@v~j=a^jB>=XT@k@;8+I-DLm1;;i$r(#fEBZqf#v zfQ9t~#9I;Paf-jshD&?F(f>0jKH?NFBHmT$Kf&Wy*yDA=emei=D9-2cm5Sd&{@kYc zeKa3#Q~XAB9+rP8eu&PG2Na({*TWwv-X9wZmai1gu-N%o@%40mZ%!W+K=-h|L+E-S zmN>UR|L$ZTVUO*+nc6c&@ovj^!r|b+P4)P{TWYj*jaGYNhNzOpX29Jil0Pfzmn_+5a+m> zOY`#xVQ=PD8b^}^$8w({omq;XAihBH5%j{KSnG=M(=z@tcWvpt$CC;QEeJ zoUf1b6>moK^D4#PBYvaee7*Ic;$M;dUd4YR{)ysqX}&$C_#)!}QT%q|gK1oHecvKJ zS#T^8;{}(widzc5gDyB;D)IU9LdC~WKi;Ky4V|Z+P@J#B-XYHO-XNN2LpJ9oAG1I3pS|61{sv#k9eioZy_CC$6oZdl)$-K_m- zioZm>r{Fks@_XBXf}_r=?p9}*;Ar2Q=Djq*(f&@dpC~x)ZXx@bf}>79I-lkUjyf-p zPJ!TP&vA8u;Anq>>=zN|IN^A`O4#G>E9B26!BKw?#o---qyC7qVFk-J;;g@u`s1I% z9(PkHZg&fA>ea)@IQtb(ILGn>iWd_9Q1N?-9~K;Ux!kV>$8y>JC&l-Z&I!dkpKJZ` z(RrBTYBcc}#fylaruY@ayD0uT@pBbFqo*ympW-RR2NUP^Urgh1w&EWUFI9XljnitO zk5%7D{k2YTY`4p)+$$A-m-r2epOa|)-=g@H#P1SZmivIzuryjrdJ#51kV zYQ@WlZ&kb*#pnBq52XHTP1m#Rk3;=6nmD#Uw$BurS7!;1?NdkfDpdRh;)@k;+1L7i ziQ3Z>8#rqMTqIfRxfZ|sX-=z5C#2;7u zN4oBLPw^YDQDONb&TN9uZTNhUK>f-7KTY-%6zBOppm;vD%N2^35WiFL0P)8aUq$?Y z;#U$srufaoThM&zf$DkT-&W!Yif^Pi93(hiuTs((tGI0~h)2P_0B<6lrNl9AF?3>i z9#;1J_it`d_I$l}kKh>R*HC;u9ER^!{3o)1UGWwDKnxcC9Z{a&?jU|l*$*3F?SB*; z>xF*@#4I$vS?5x+k5>FX;&F;65419gifRd!Rj|h(T zeBJo8;AsB{**{O5<3E*;A?ZXbzIK$=KUHzh zXv@10=XU!ywL`MvGim-ArT82DtwIJ5gglRCe`a-ieE(h1;w8s{+8kqX|{ci z5NAIHd1W;#qVbcfR6f#II3&4e_0dZzBGQ;=|~E=Xb@&5RWB)+5d^e&ry6f@iB@Q z6383dACo4Xi><20S8u5{eH_x#8;}!2oe7fT25uc~{ zO~eZne}#Ct;?2g}as$Mo;yex>5Wrda!Hh;#eg zL+!9d@u!GCAawA0yQG=bc~sf+-yJ+C?6F_?_lZ7J{Pgpz&QZZppTB?fqu{20$^JLN z(VoA@)P(LEI3A*=TKyQs*Awrg_-^9oD9*oMlPoxv%lBV{1xG&zPqXEwEB;U7nTq$F zZtV*duOeO|IF|c>&)P2*9Lv3)?3WSe{_Qr@+OJpq9^%^-|Csm_igz7mbzUTn?Tr5L z_YV&!d;YtUKM8yE^AP#jq^H&6{z{o))2$V+As(mrcH)VOA0ggHaP*VEUo%{A^ykc( zw%k#IqdkA$ZK~q@{hb`e`TKp#73Vm)RB$YJHkG?Ua4eU(YUf$UEc9PQ6_t$h#0U!{4hpWvu-kaPwMjyn9kfH8uj z{lF}%pCLHfS2eeMhTv$wiR@<+=W%*3jjt-jA0b|=_>08vQ~XQf|5p5q;ntt;6zBK3 zUV4wg{*0sN2B#`MlX!2%7Z9JI_(I|Zir+~5Lc!(r=cS5U@8Nqhg5&k)9i+2e@m<89 z6I|-Qra0?=A~@>rBc1;c=lEYq8GLmA#O=m^r?xwBj_0V^HbZ~K%ZZOx{2t;{75|X< z0>#_UvHE_&F%Eg&s}TG&;8V!HT5z=I`E9M@Jil#GoaeXu1V{a6NPoNFsL%7;PQlUs zH?rR)INJ04_M+fu-+L~sV0lGwwCDNlUBS_QA=w`i9PRmg)<*?L``gL>nBZv7-wXI% zaI}Ai>`$fpE$;W$^WY#Xodic6o?M{M-xM2RSe8=(!iS+hwHFgE+_GJ}exT ze#(9$*-udRJTJ`>9OI#a;$g1f7(YBO6$*~_ACvuJ#ZOxR2Vq$zIO_2C(=QPmb*3(W z6)aZ>j`sXL;u{1<`v(`m3YNPR|6+l`&a;Z2nPd4L!BL;*f!75`{cFhnW5xeP{Byxk zhv$Lgf}>9NTv);Kv*2jY^MKpe91ec(lE-_j;ym6v36A;;NWY8VsL$g)QE;^1MfUK= zug$`7^&ZEG;zx)tRQx~0*DBtDo~z%X_^ZU9Q=H!`y{Y&Kvj0l)NP6zwv|oMyI}q=z zcs%hzil0Y(lHhVY&QaW!2KV2BdjZZNoi&Qr5Z@%Y)W1t{)_+8B)L%_H`-t;+?}7(m zc~jYMB>S(FJ&*UF1;==Z%QG_0AA)22@OW?5-yX+u(f(?(Z?E`1;vEG?9Ukx91VTJb{Smk5qJJl?Ms z9CZ$o{SAVnJ&*T06zB2&55;-BKPfosM;E{fmS+V=eID;G3y$^`WdAzx;CLU6D)7A$ z#g7ngF~A;Y`~MJ6P`tw!Yd1>qSBd)-=kG69D}I9PuTVU4tku6u@eahFQaqmc+lrq@ z{2RgLc>G;)TPplMC%xa}c9=stT@u zu#xPqQ1(3DZx9^g;gteg@-2d6{P1|cM{u+sS!nII36AzWPX8r1+TTw0&nx~8@dJvt zF0%UX3Xb|bPCpYI^)Dd%9~Hlj_-}%v4v*8O^qijOv4~<@ZVSQDp2ulB#d(~br8tk% zUV@{a!%4rt;Hb~zG*xi4-%R#niE}*cq5jQQ{0-vE75|9%4T}Fk{1L@Bjf28rIjFd$ zj&nrur^x<<;x7~LFxci~{|^%Ht@y{pCnz47X7lF=F2_-s;_T-N!Es!4Af4+K???PT z!KMDAinIP+!BKw%>AX*z$7!x>a~x6jDP(^_+4DGUF~sKM`23UNf!~v`KK~BYY0AE0 zq0Qe}aEvz|*F6M34fs~FAE@{t;^P$WaK6=_rT7%$a|Oq8dHfa%j^+NH>=!BiI`Iny zM;#u&7YmL$qf1}~%jJTjJ&)fT6zB2F?_0P%dHg;k>`{Li>HkY`)aUX0jNoYh9@)P@ zoa28T_4^^kYl;7<_-5iAhJq+8tn(Q00gC6OTe~d9S6b{8D}EK(uT*?9@f#K2O8g&+ zKSX@L;vW&`_ZaBE9ADoGd*s1|o||%gkCIM%#eEqz$GL(_{XvSeeum&Uep{2yeBvAr zw@26<#mYX8>{lv#{vET+1;=<8T55GR3Xbu^zlV0S;Anpf+5b)PH;6x~_}DV5zen+l zi0>C1%jNNSP;e~weX{>taZkC`|4MMw;qmyh;HXnc_J0VD_Bt~-Gmpm(it~8v zA~@>bOZwdf?*yF3V;{lMzD0#CZy<4wt4!+0^Ayh~Uat5B#Mdi+1@WzlcNuTX-KRMJ z{>D3sPbT~C6rWGL#R#j%{udEXP`r}(aK$$hpC-5*FZ|ws$Eht1uCs(aj+d>ZbE)Fb z5x-S%seiBHtiMxm)c-f>{F`_SV4dLKR63u&qwHTH`|p%JkFP%j$9Pys@!&~;1T3u2 z?8Zr6!%_W?ao#_Kzxwm+lUVr9P7p7FtP?Ly4c7>X?Pc%j?94D837W4wmtXpCJ2O#Um$L`wJECK>Rwz zoO3Xbu^@BNnxj`pvR{pE^}48TEH_&GQG z$=AI%EBmEnf3M;X5#OPB>uRh2l;G$mkKcWQqn{U${o9IPNBjf9QHRIx7lNZs#3ES1 z@{QnV&*S$u#d-X;7-g~r=Qkd|Z3RdD;iP|>;Hb~zx2xc2znScN5a)h>iu!$&;x7`< zQT!nB3l;yK_)Us$nq=$qxZ){IEPqMyr^x;j#a|}=hvEl`x1;AJUnRKI=g))lxMuzPg+1zzAf2a)^SBs{H18 z6J^ii_Zz`69{!|w_(SnEi;cX~lAh;bacKWD@o2#@-uOAv>4KwuQVpzN>7jTb@g%`f zhvR>Q;vD}Of}?&N>G0>@xqUePXDj=hjC zJ1v71EH4X=_B|=y-WD9~i^={2;ymtFQyl)R_?5)l(sL}fznOTF;tvx~SNsj){P)3G zCvpmU?>Os}eH`)I6@QF4{~d7F;or&R=X1;>&$IeRmCj<~k@P%_?e8F-sQ6RF$146Z z@odEp5MQkLA>y@)e@Xm4#eX3FoZ?-k+Ik&Pd?IoF-UHXGocQVVJOjC$-?}Sq)#39d zg5&(QgmlssznS=a!KMEBinD&T;HZBW>8vNt^FuDh^G0RAo$R*@dyMlsit{HG|C9Jj ziuYe`J$hI1GUA5>$MMDU-Z8~_-uqo}^ye|sF@F>p(p)c|_gd0(7vyMv>P4`EBa1 z6+eggQo&n;X}mReE>WDYvq^9q7yU@*?}|?)zFTmq|El7w|FPhxKbLfVBHjWR_V0@3 zHiu_CZDE7<3&_3$akl5{7JeRs9OI|`N?5_tUD@+>NFTw`e$h&_hTla~{DGAQJ5v<@ zmG}b1d#$qee!;O`JYFgU$8tB4{R+kZNqnu~a=ct6IO=p=ZOgq@a5-N1`z73NJfCb= z_B@~L6dd*Ukp3>gQJ?1%{`>6Q{@vGDfB5+g$Nv`U$N!KH^LvT&_ga`gLcA~CuQPv^ z_$0+YAYQ6?V21VQD#iKtJ?a#%Bl|}be}MSE6@QfY7m7ba+@<>-_Wv8={9FOK97kP+ zJ#t?7@4vIoFQhY3*yDIVdkBvDT`#u!gA|`fe7NAK!{@euv^05Pw?ni-^Cg_$J~%Dt_K9^b+QE zx~}JP+0Sl@-$C}n6+b|Hmg0wqU!eGx#4lI;oJ{M_7QyBCx?gc?4S&Z$a2zlFNasz( zrxO2CaH;>Z;;iqZ`xS1VxunB?*Ph4kd#=sVjqI88-)roxcpVkM-*03453wLvrYZY} zh!-n*8T^@_t164Z;C%;&7CQ9-^2CFYi17w z6u%A)V7XTD-ozhN{F|oM{u9Mhsb0S-{tfB0p^1zA;osZuq4@dKo@I*9AkKfko^{@# zdTmzrH&eUu_w(5PV`}Ha%0Ajw3p(EJu>C>Yf#tOhc8f{eEop1hUxwkWPpxl%A=&o` z!y~M@xvwEfL)b5@J~gr73Dh2%$C;znR@(Pfd?lsnHp^(EJ-_d(QTDq?Y^&n@JoAv^ ze82FA;(UD)P5z@S{^NO%>&2WuXUl(|mpOkPZ@SXq=NKCm=f7vcf5(<}_;;ie5WvEm zzuy$lyqmS#tvG)_o&QcN>+tuc9f~{V{C(k$iu3nj2P)3ri@a2E{@&0*#reHLcNzy= zE`QFpM)R?@UV9Yh&;N9wanCyZxq>3a`TF=##rZnSM}f{d{QNdkaegkbRdK#=`bBZR zP774#lvNd!SC&>KmM6u<#r5hts89bs0|)dU1pCPY287nh{rmTgi|ajTK>vYY7Z(=%Mzl~)&~+gn7=%jwthlF+yq#4pRoS(BdC$KZdcm+XQS4h11$s0SWQ^} zDIZH;zdLGee>k@8>!>U51g}PKC_RzBVT@-?udmbAk52GKb#~G&ADs|=d}LH~t0TDE zp&+XJ+n8)}1&?z zOc+v<;H$m|%82Tm;vA2%qZ+oSpv%WcMP)Vl7Ob;c9X%d3|7oKDFs8{@Ci5W7yw7A_ zN%qlAzDnB=NJviGuqI^!8gxmFsb$RE1fS@hGst2Bc9Z=S z5?JOZWO7u1;Lq?6yXj~6XA~iQ0QNtCe{Vwky#QNtK7{>eq10yzqNeWwn`5B(9qWM2 z%T(j}W-{Re+zg};U1Gw=Mr2ghm&W@kkQ;g=^Et>=f_;TNYd@3)fn@`VWuST(8VgK) zQ!p@#z!h){DxSth%MuolQFI`o$QXgnXl#R+W!OG3frMx_o{#`CM$wKJHlLn=ZlMGD zrnvls4j8#6T?7*O32`g|0~+uI7|u~yFp^;n^97LU31~PyA;oa;In$0hQy!HQD=aGV_Z+Af+ zXe3xe|HB}8ZV~8a(x`|jb)Ldlc@(eEz+}4VrarJ|LI)bJyU|Kpv%3Zd#j_iscetau zZ%vU9roV$nbPQ*(xi|rU$C=#g!CnPMwJ;HN5c0hZMY7$CR2LQ#q0kFz6XVW#w&D2r z#f}fGXFnVLJo2+3s$*;~{Rw*kGb>P8F&z`YNeK5W7z&vmgVUcvv+d@_?3jRkmJGRI z#>Ji+h*4*cj>gc6rsL7eXu5>rA=C>}qq?J~LqR1<9C${E!E&?Zlps zwTw2c@g5XuLk@%+gxrf<@cdvanYJ`lgm8m7IB|~&F$#s4F%CqJnNpvH^B=|u%wto+ z#Syko3b{N~kNSZnqjE?Hw2I38C9F;{rLLa4I{U;!V z?i~mT5J-lmfZjvJeb`&9Wt!Z!pb1Zy;;ftr6*CP$elu4=eH1*(n8M;CcmJJp_C|?qC$9f(@8wy$?nhLvvq3okO^SNdb;U#k7P& zRH-Z&XIa03<`1xiQ%NSAf}+hS2sJ(j_&M%;0>xsTc0 zS>JOS*fU@`>p0}eOEw;W$@B2kt zARI>D@tU9tnkfrHBl8GagInhEZ+r?9tQy)*1GShEjGN4Z@DC>6%=h5mtKiapatj6n z*a)|bIfN$`@ijD049;>{??N_c^Yh>eEOFP;(_!Sp8J9xc&MFYLc%c@$L_7M9xVOl2 zx>gO2>zY3}uB<$+pwjP;EA^L_SHfTO>snF}7v>*5O2AV{%H7j5Q(w8EW1HP0GE<+~ z{pHVRA3dBhu-~f}_j~(r%CT$z{BGm<7mh$f_{DDAt~~djBh6jMmN!32aop3!-FnUE zsihm!pFdJ|Eahk*?&#m%{MU#xhj0Jnsg!7&ufvZinA=&o$Znl7yIon3vS!cT(&X$n zMs(;odiw8YHczqJQ&R#RHy$2%$&x28`E^~v6UAqa*tzi6_wxTfb;Q+|4u0a6QQwVt z>AM!AFMQx1DXU-qqSIIT*AKto?Ta2gF|%&?=MT>Re%zfaMoewneC{bD?j3RLm!BR; z&Aw?wmv?`?=)jFXq^$jU)PZZG%bwi##Cx8V^UoUbk@L}^`R`5}k@MxPyLz1BrhK+< z%R7fUc1^kUsuK_0HRFxpi{cNjcISRQ;*vgdFYQ_VHnju%%>cWsz-ozQCGW3|TTnmX!Hj!)+TWq42No=)=-X^i#Ci-~rk=LU((a-p{ z%O(bR)CL7eIyAivD{0&HcHE`?JFvRc5BC%Uj&>q^cpo0&!%l38eT#0l z+5kI9u{#aDk!dW@I!K&i;=slX$66B;gx+YgiSYFV32(Fsl1P;EBw&%e+zukRcy1|% zz>BJZ!@CGYEs=?(B4HjNh73=hjG075dTX85IgaNnbO;;8%vY@o98Whxx_|Wj+S!ZG zAJID3@pLzY=d#xDgBG5%4dQ96wZ8&b55r!2?}m>)yy?Obu@#Qz9Cp5qsZ=l-n^)|3 z`f#tosgF5c*;0wjVt~alCgnO2=40o$~9qgtH+DsT=-r6allF)7h+ih8KQoAMA zt^>>$wlDZ%sZ(tVfYa-h5FD9vpe{kbvA8>pjBqAsiX%E`if!3$oDQs{jKi zGBhqtT{qb}&w<6;qdbu2;7vngE#dAwHxDo|H*yTT$!$}@!c6eM-KWeYy zWIex|gBW7M$^_l8N< z>*;^MRp(i3R19}_@1H@9BkXuc9xkA>K*K;J0qx%gXJBo-*mBa(xK^v zE;8Y7hGxBiSWj+@Rerr=GF7jScAtWM39VaXChG;EDG>wWV|_H( z+;|jXpmlJ1=6lCZ_0-ItR&qS{J_Xwkmz3aOp8zUnROTe31-Bx8SV*SNz&<&}!<_== z^^)e&CUm&7$sOfEWn6T+Ugh^e1VAO4!jNfwl2g`qWM+m_ z*S~5EwjW*vLeX|?m>ivqxgE0ca0`R&F&C^_ZGauG69;Rj$yU4@vb%o^?cR&KXQF|9 zDT~*q@t8jzTg|MTE_k@vV5C^KIZV*ZrFdyHd3>~o3gXue(Vmguz5Opap=aIF>0NB92N0QF7)k@ zfp`Q@#n#hqkaC(VG)2T)Wycfa=^7c^(v3VlGRD)o8U9%;Z-UdZ<#0goe4w4gPRxREVeqNN9tzEgtP27+00N;XZ8#bgT>g{?~xMAd}XdBhkaedY)FSrYDf!Ie(#)zYE z@zT0)(6}R<_~GZ?WZpKAr21jrwk@GKS~tT&+O1#hfkZFkUHi*cdmL|v+L0jG0S-8L z0Pe(EdSwWAXsS!xjCyt#qlnFA_NzYGq+)Jb8n`jA?+#FJhb-H z?sD3Co#Z;+f0>xana8`Y5ptd7p1MPnXvrr{XCCiH5i)jhl(;lx$GcZZyG@kv1uHd= zcUuuMdJStjk30!(hLF^}V3E1JyDC^DJoX7@pq+F&SRKrl$GajQJSS4iKgj;=V7^@5 z{e%z%_@gUkQ$6y7^=ZV3{gi`qdYn9+4-iXmOo9HT4g6w>KS?s$!P_Rd&cSyKaAk3N z9UqAaHhO`oo3ot`eH=8&;oTANfCv{Tyy2w6`666vN#JhV7@%w75N$t zOV0OY(9e9{{f(r+&tER$1dByDaFx}jXPxyjv}V1TK|lPw%UQh+|LD=lvd!RZ!>PGE z&P?8AC7iTo{1+}vXRTpDt!ca~wWc@Fill0=fX6A|UC!7Essr~wHbxpgUTPpYxPfF2 zOP0`~!zY)-qW<51VpYery*3ysIiY~Cd)lOnW1q*^#%_4C3{Oeo>sasZ@VdL{(EgAK z@0P$LV_YEW8%|mLsZb|mv~q>D53SjVhm09Np~H5sDJhH!DK)$@VWU1FqlVKJJG_*P zV4CAy4$(&IqVvJ9Y;dMyn?Opny&?d?R2-Y@i>LSW3pLwts&jbvc|vBIifK4aan`NDtTTA`xgeUxyR6pu ze3MTZmIX8B@GfWUgtKM%CwL9!h$~2neS$XRU6#BI&r1B1`K0P#_rLpmuz~xpko#Tj z{VQB)3xitI zcvosQJUg-$w`iPmk_Vai7_*Kox#saWFv36lGS$i5jbk&IUFOP62H5B}t!2r(n1S$NAnoTeeRPJJr$|fKAzjM$Q2C!yxcyM=9hQ zI|H0X$JoZ>_;m}eB$JK7$j#y1E%0;$mv9peXMHG`bq4R&QC7IENE#~;>V#tL}s!FkP2x}c$?;&5-lMXZ-?{fD4zh{!UBvlfW%;R15Y>Hz~ zP5f~se8a#WS&D~4nC6czb)+&Pd)T@FkBY$F6%TlNi@us*GRSo*LW?dx1(t5 zaq@Y0GmSxwH6lMVBy9JhNTV%nn>vNp_^yfO0zJwi_r<++XCmhQ@BFS~DnTHEo+Z1y~_Yvakch5U|!yaJ^yC=YkjZ(NcY{ID5!RyT{Kn`QhaPdM?FJ?6mz6IPP^01NL}+ zk30p|<_m}L=+2XrSDI8+lvC-?PpZhtJ3pt;pH!Ham((|LP-35?lHy#Gsu|cnyMN!F z)n(_Gl`k&qSyEh9UDLC$teVV<%koO9^ZiazbyZ~&C6aQB16B2is7ca z#Fckc6mFiumCqdW4GA9f&Bon(akUm#X}Fqzt5@BYnQIJlbA7FmxUt?u4%PJl3jI6x`SL?Dqhjm!yX|(k%~!iER{wwGy$yVn#kn`Un_a`Ik=>M3 zgGE{G+6If5AZpTLO<)7Nu+b<{6GchL2SEd*NznKO5dyLomIu}Z zN^iVq;f%`zCtT(`vdgz%T$}Hj!0LJ5tNKRW_on}8dd`>;@xRwy6j-?P;)|x=azWiK zjdc&ir^j!*p)P*Og^Bbfv5Tiqxn%l9)8loQToL$O&a}_p=j)y}ecs={H+}j=@!wrI zea*#<)8o?5W% zn-(mb-PY`x6+d@YM{`5dtoE)BPuqg|bLMuj@DqloS+g5Dn?3Cv*s9%vaOPw0U{`B% zS3_b(jWSHFf{B~=#c!Al!ra&0Id{(d=B98X>Z0YcaK6S#0(ro0R?FPB=J^XUax~9x zvSRCLl<*V}`VzrVwHLN4*=Du0HO%RBym$zf?8!l%sZS~$P?tU1l|n>*$T;bK52h30oy(W^-e6 zXXo4n^F0tRliNxdrXK?!Kb9#Nr^FMLC|pQ50CSqVW@R|cYH#RhnAhoPX32EC^k5=QYo| zyvftpiqyQkX}na6Q(G6zYd*EL6CyS*yz10WbO2~qPiaJYoZ8vZC|$t$4QP->7SF8t z&D{u>qMR`Su|vSDIE=N_2`T+fdP~|M%1uabz7Q3@PE)T5>UFwj*24JqDjNUpoO1F!vhuU{fqE##H;H}biM7#`U%X!EJ} zt@CEsc*3CBgMU7<=L@t;bEXcj_Z{uiL3y~b#gF*%Dd!t9ucTbdebM3JrT%Hdt9{3L zj~E`FJiHLzO>{h-ah+GsLpj8|t3$n8jj5x#tzldjOUAe+6j{%>*`1x9aUBb2A$iHT z5|6l#TR6XM?iI~#S39naon582rQ{6Q$gZh*_QE-{=FV?f0H$JYm_56r`ATDB(Xc5| zujbEbMzD(5*eqTcwlo`O2;~`vhJ9W$D#p0^3%Z)ep`4u3*@cEuvu$pj)q=V@M4cij z_t}5K@WSNQ8X|{5xi-1Sdf4zY=;2O0t?eI#4`^jntDZ%$EHNAu9eHTy^CkQf*NZuX zLRs34f9e!c1B|=3TDM?(~~|2>Fda^5AnW{)v5# z7VBx%{)=56O!}EO=U3VMZ#YUZ3tK#CH2G)V3xK(T5FdTof3G1FbSUlRz}aPLBbA*eziK}^YgH1O77i$HpyIvT7=KYlR&#>&L zZHR(axf*|lb^r!qu4MAGYyYJ#4<`P`|9%);Q52`mI{j8_LZ%---!lG9y5<9Ow_WtI zv7Em+Ls7k)Uza#pWhb#YgSk|zrMbzDS>g_#buwG{^=z2%q+9{ItcobhFX{ZA*AHfK zLU`G^__9hu`Ld-jalJ6;3dA?0H&X;Yk4}ss^d?mXJNTr<8jR8A^JqPa%gN`--sts;*d3;VQJ=oDVq?b_cK4Gk|`lG~VaJ*Jymeg@0S)8(jGJG~VyRf28pPF8n7N-{`{c()cbH{%;z0 zHy!`3@l7uJts39$!gp$1eHEW^d{ESmS2$3*~s>GhX)uIb9?zEpOU>1^RRklW)w)CX*+c3+JTZT!D1diFhr{&@@M*wx@K zTR3a4!DYn*c81F(1cS5O^C5mV{tbS%g%?)!tL_e zW8rprUSZ*OeOYhecDcR7!tM0=xrK+V`2L%P+vV!F7H;!--oov2`?7`G<<>sC*XIAF zTnK@O`DOEuTDV=_t~><3*1{v;W%6;0h1-1gTew|rKelk2{&YP~p`0gK{1X;#m)jZ( z|B^*tXW>&U{0a-V=~r5~T@Ke;_?Im{>n*&McSh!8k6Lt77J#G5y zExZ(NMxI+OoMSwL?_k4;$Ia*6L*T2l%7^rJz1V8u_V|kDH}D~S1^!LAx;lCoj1CGW ze55SD3ZqtNmRAA!F8mdZbBxD_JV<}4?ibmX@*x?+HV^9vAJQuwkXRkPi{azb{SeCl zAA_?E;xkFT9Q-~lPuzw7QU_k`!r#~Q7rF4GwEUO3@XgxZ?83jQ`E)A4tKQ+k8AqdTzEj!-|oVn)AUMzkx9438drLX41R)^f2WI2gSJ2F!uM(Wvo3t0 zmS?{Ue?{Ze3-B@dXui@kYpn)v(QG zUKjqMwm;y)&)0k&a^V{G?AAEN)<$u9*P_==zTkcxZ>A6NQbBQqgbQ!ixS8LX^qjA8 zqen7){;2VjEIw@iFV%9Ge!$QV)9F0HMgM}ff8D|_AY zx(;`Y3tyt~Gc`{BMrQz{r`*DAIj31T!`-CAJ=elXZ{)1AaMFLI>04ZQOxv%}ZzI2H zk5VqYS*P2rE}UZ*KEHP1bqe-8>ca2W_#PL2m`S&$8({Jk2w>Dq2S9X0%q)#b$Kp^1}znWlfn#phma zH}hFTZ|1SDTlAF2*n<{tgQ#{Zk~ybC-pa{vl0&kH$?sx=5GLU+DZaxH%Qzk1pJdHwIj|`5<2% z^PHL2n9u9WbvhaTrk%XTg!>idNj zPWo<5e~E>Y-t_Oy7Eb!#Yx>JIZsPS59j_y_yTSic>zbxMneuaurhnE&Z{{Z-SUAJI zPt%(Lr-_#{Z`bwA@c+D4)CDb^@_eY{HPXV#|2vxgcnc@}iJHF1!b$(i=kSAPrVEd9 zF&WS0F8o@Jcf0VrHNM=1f2{GfF8mBGGUNH73vbi-k2P+}+bZ2~Z+GE;*7)CCcu4mX z1-ibPbUt5~pV1mO{5OyQPlZL#^!cHdXNrY0J$XGKpYtr7^sj6BdKW%cD^?e{@RY_E zyYQDZ-fQ6u_hudL%@)pZ<45BM&sqy7{aQ_bn}w79ST45Xxn1KX9e$_N;rA~51C77o z!fzonJV!GyJcf_yAHV3rpV9P}YTV?@1TAN)3pew)P8WW!F3*cx_*aw|o?aI|U(w0hE`wJc4Vi*3R<}=BK|60?}bm81P z!si<^n7-U}zW83HNP1EHYNXVtjU-WNGV?$p6~SGry^LsCyT5SfWj^kiML72uM!1#BFp=vn z4J}f|CCPD!D@%-Anu!!taYtH(c)^xof|9WeS7;&sit&4Iq(?l}{Q(wG;#b12djfuV z-QP+H1qO8$=KwC?;suUTxg^kp!D|v{J_H}QmyLH6A~z94-3vf>K_J{^2h+kK;>s zdKFeDscfPR(rDQS2tp!RyI!YG?OKW7;-&gZOzH052%d~<(k|;q6mSunT>FL_`sCs< zh<+ZfJjKOhlUa7StOPmydt>^W`K^nx{MJQTe(RzvzkN}b-@YizZ(o$uV=uEe6)a-KXTKA9d$jS|{OPA{A9ZR3) zF@@H?zuDIKQ2@taARp<0?ZW%N18%Ic~#Sa8p3WhP$$yK(#kB!pLql z5@=V(o6m4ux)-9*QXb@O8BqP*ix97eR``lpQ5CeBThACA?x(aK-PMkwSIROO z&<*t0Wv+8AG!rTY7Y=58(O_$%u@*A_`5XscPoTPtraLm82iy0Q47>~Dm{TO!byh6e zK=H7m$X&C6H$08>;?|>$$^;oUsF>qi!!Jm(;D>$?tK2o49Qw@;{U$}vr+xj9tNQYi6gQjTUZmLE@;&co#T3PwSkJtq(hT<2$HqzD;Lo)g7sg*Yi# z7`lEk{QtjqnS^tT!I495Ul?xP6M-_#OC_wEAVQWa@|~AT7*|+%sf1`WA2dPy%Nra6CH;`mIIGuJ|l zR@Xv=Gj5q{Au{(!Sl2?F=um2qB|$$Y>o*QhG)B{X@!+^chR*$NbNtOVM$6yEPvNgI zi@!~u!e3Jsf15vrKlC>0vE%u0FkHtpTgf9w?8>l@0g)y?G6?7e79Ewx%X29S)0Y*z^Cw6 zm&Ma(AMep-m>Ame)Z82#GIumX49zd(i=RN%<`_r|z zxPE83w4~9iyqREeHsBjW0-K>|GQWpwUkmhGZd@>3Cr%t4b(?W9TkX7_Ruc)sf2gNS z0LnB@vo}l*<8j5KZ!^{-bW{Vl?|{AaXeXUa5ZZ&AuhC~3MuR0e8sZxlb0x(H9^P#t zsK*9FLL17)9kAKJYS=@*0xLo$?+~HAhl~(CxcPPt$;Fu_2{J`yrrX!jA-*mh>g!Us zFQeqnl$gtg`nt^R>o2b2iEA=UYz-sS(gWAKSqbQ7X1=d77hmA?WMj0L^(@N5tY=8Q zbXOeQ7?DM?71@}r7{Y9qPSV}_jc6L9CE4n1%vOI2X3bfct;@!2-4JG`By(8D2We)E zCb~%Lf$NoP=69&S7UR#P9Q+MU3A5*Tqz(^6L(|?6nVOt6w;NqWhrwakI{3*#H1whe zBe01uk9Ia}Oj~Kz9FRW7c&%;_5%9qE#&W&WrZ#?48^4!<3|I61Y#}$9kUKIV@4enw zG9fqJs@;F!gxq98H1CVGlkvrT7?OHfh=wMynXYfnlptd?33?<`2rcp3g*Xh=NMPQ# zXn%J({u_+(cN&8K)tsW>^!MX$GoZy>IUWN?1DaYjT>D(JoBof#&0Y1~?nWr{UZa!A ze1eFIYgoMRB<^fOZQf&HP@+5KNSr>>UJRXim+NN1Y>=qe_AB(;^qSXd`(phz3x(g& z_Fw6@*~npeaK_ZZ=-OrtM~x$gk7=c>ShIiqBd4U8&!u5x7j-&EUi&(V^n| z1j5Vl41nYR&cj~EjXQP5g0?H0J34uwYi4Pxkx|7L=h+T^wN85l#iu&>bux}{TQDC7 zzlu89I{5V~-oUuIi!YgqBVK)ZwSf&eXOGA^ePrdDwNuxunlq<$&W*l>Z>%`WclDC< zru({=oIm|u-<7q#BU^4*<6Fp+P<>YhrumMXra(eRF1f*XC)6&1k5!9>ACpBb}mtl4^ z;QUiL9+YX6IVhF_(-Eh1$|;|)L)^g!cQ#6nFfVXyCrhJsu!midS2Rm#Y zqwKA^Yo|J$Ml&EWO6D(V+&wZW&&CwAZkR(I^89E0m+0_^#NQnd==kX9C<}heHKw=3 zWpM4^#M3yK_?vgtA^g{C|L*iQcJr>+{tbWgZrA?52UWNIxaGeDj@hCb)|;Ywj#9pv zf0WtCZQ?~8lba`f9F@mkkLPKZqs!{K1~hJr{$A4I-p@@)*5jtV32ttTT}ky_*dq9`mg@!{c^Mcld_`%NBmUZhy~%yZyMsx7}Hg0@GtuHGc)xDRy%=U_Q~#Y7bv)F3K-&$?W{Z!Wjixd_96`FmCZQH)u@AqYA$k}@?io$ zUJSWe&&A7jhUXVSZ=c;~`u|gahYVBeeflBr%0u9ZL*T9%Fi(t=qvH4@$GJ_ghNNcD z?w*QZ(2Jq2I{seE$mL!>Y#-Q$o3k(2Zt)?`x@GVK1o61>nY!W-XYLyM)fUb?Ft~ko z8QVaE|LG9)`z_q&e=GyU!+p@ z`FFeU=QY06g`2(Qt6lh&+J2i0pRW1b?!vpZ{F_|(_qF|A7rsN=`(5}JZGX&#oAR;8 zh0oC8zU;zHeBN{6=G%OpCCbO7+a1d48R5b;?3{IK=!U(>B{(T~^mZ@Takbi97(!p->nE*Ji`j@P{| ze6hwKaN!?l{Baj<&gl4?3%^#!cff@ot?`ds_>CGjOEM-Mf;wKu7D_`JYtmJ$Jb1AJw=ygT&C^pu^qb zqTi(L+gx}|$M+$P)4MiG2(Mc748uGhx$si$kf-^PmjCS<&$n=<|3)q67c89g8#R5A z3*V#hGhBF~<}=xaS8LqFkGvVKSyDLPMZaFtn{UAl{p%WUaM7D3ixvwfe{&X)Ia`u^ z_@AfKYoUuit@)dIl%b!h=~r6xbTel(nI$ws{~Jwj&Zs0#d2Yl%pUoB@;^r)sT^3G1 zuW3HdxbSnd-9Gb#^ky7zvYsTFaNpN_CRjM#{$2Ai_t6vM|B0H;d=qc@By{?m@8V-_ zeZR=U8Lv^Ae}jdS*PGhDz=cmg432oNci}f^+?-Wn?prZ_ZjFPWu1S{ITy)9z*Zb6WIa_C%rk-?05^O zpCV2FMGGhW=XH5F#llHnrRmKXq(+`4TKykGSYR)ObLbYetjlV9rV&;lf*WxW~J2)89l~_z!fw zILn1UtJC3p7hbIKc`kgm=A*@zwpJHSi9txm!^BrxJs%wc4{Myk=^zC;wKzQJG2c#} z3B08E%ri5;#b@AZLtv$6l$}8@pZ?p)0ds&KLM)e1%dx&tXni}0FDKU_CaE)1Q~80L zlp(xlwc-n|sqM?&!RK1*BP-w)2KDh@aMe023&k3DZQYZzvo~_Pu=RNG`6Oo+v30Sn z&*jFp?(xTxYa^Wd$3V146U9Kq`j2nH=r5N17>;HFL&AQC@Lu`yP=D*@o8m`+v2@<~ zvDAfuSi0b*)Dg)|Yk6`Ght%bsF<``{d0^*6>LwNmmf zmeQZYrfMQZu~bDUk-9vPNX^E(gWqgUECxu!C>p{r6ci_tdyxwhV_Wy0UA8+o_5P(F z_<}dTgTRuz`E6{KXXyd2bbz$^BPgVQ#6LbitnDX~3(I|Caf)&szDvxn@^oE6_vXOT z1HSU#f`BZX8#hCjm($hbFc;C{k-ioCkc+>vFQKZ6M*Wz==ck#a;|Ifv?zTj&K7Q9oH zt~uQU!z)$kya{+eUzPTsQPLOlzFP7~aI~kTB3P7{v-CbM3?aX_C0K{gNb9z@;Hyp| zdGK2I2eAd!_ni3HG8dmKhsu7}aeoS-dUr<4c6W`9H6BE4>aN3Qp?Hn@kiP{VBjV5E zA0KVjiP8vhsrd*W_ud)lL1ZdRo^J_`?uALdJ*QcQT=D5?e%_1mrQ5vGvcE0*Sl*cTyGA(bNxCBfMBD z*^gH=c-NlbU8v<%jn7X@Pxw1Ui6`Geo8#S;NKe=rYmDN<;W4}7$({SZ5gc94kk>}m zfeKNKCYC#4nuiK`Wg2g)M&JwIx_1ZDW-epXsv1 zpnOaMt5-a6e9Iaw+rQ{E*hA4*EZe7TvC~ zCig(RWl#MU>EsEX`)DGC$U9YFS8(cmOW)54-u$Bgs=zL$6v@G-5;Y-5)r4T^a1jT2 zI1I5l9GRIvY~hI{3b3pPLvfaCR*<@>Fy+GvlBt2RUBTX;gWZ_>qUq@eD}D>3C*ARJ za_hsdF5Tf>_Ecr?oZm%J{wm70cGVbBV_W~~t1Njck*+z)lStL~pS766Dv!oM7+I`1S>Rmts`F;v0E^ zzhhslNJk@HmgKVC*L?>KVG6?Wh=HQustB?ymg*?NkpVF@^&2508opvQf~AN@S0t23 zN4A4T&M63hGHkk+7!DHvaBqa|a|TqW8o-`NzOxrD;C`<(y8^w6w9u&eXdmS^6ice( z0Fvt?8-&|OOgl2JO1>CNt(Wr&qN&!JMCx{7CbAW_M1w^r=n%7r#%IJBh=Liz5L=ij zzY#VPyo#nF3I|ky1hP*TYo=_|M`)DrZG1GC-xld6G7$|8(iuQgr2UPl!O(;c>2EAQ zH$M*i$feeQfCM;ML4BbPL5dkpAAff7ryuTh+xYVY*!>m%@3HLdBJv>p%FzjR`)I7e z=?@jb+DBLekiiJIoW_S>L~SEQB2o`aTcj4*FQ2@_51WU_U)2p$HGttPXBa+-LTK0~#KD>tFsxWpE}N771$RizH>T;KjsXKw(-CalC5L z)PWXphf7nW2%c*ZQ^7j^ZqcF}*T6LNMqaAb!DiLiK2l7?*8C?kCc&kv{ZF+X(akU#E>boG}P_Ib#e{v^AM3(I~jP7#KxGb|Cpfu~cs$@K`#LgVvt0hlkp^ zbj6VfU|tA&KJEEl5`|;wXbv(0l#M*B1w}yi z;C~-{2m_SmqSUebw!Dun5&bh|qBKgbM5>B)&X!9_gkS#{C08sp@vDcFYhNR~+W!H* zM{2%XCLzT!4vVGEg25v_=6}*u(kJnj4p~g9J=`({Zu=izy@g+K?SH@-KW#am_AMFv zIkf*IW1mL*tr`2pv?m?=C#D+{TJjL-#Jp9hr&1DmIVc7H8~KTxRJn@$+;>2wJ10gL zy?gLr$-#X;#>%Zzq{Fc`TmtT+d*ld+`My9eZ*-;+I*;fNXwxzqkOP$<;#!h<4{Ydbc5$QYsCdLpu zDje+s6hh%Qyzh4?`6%OCrI74vMY%F^0eyxY@%~@5tgg7I;zD({QEchUg{i5S{eHU# z;U#xq>flceOTO;iTJ}&deJbA9>gmco1}xM~|5aKua^`8Gx;4{EZCzi4Ev!oJOZcXDqa~}2rDmW#=|syCx}rRI7tbHMq_iqk7pqFe%VC*Oj6r)J{vkK| zT-4(VQgO2Km8iF`B6*-Pc-Ib?5~-!ko!Gi~+TT}wu(KUmiZ*LHj#ipn&-Q7;0FY{$ z^btRZOhx)ajJ|ew>n_7s_U6lwnv^h}d{u@}aE!r`=Rn~!oBM(*KM#V`41dapbXf|4 zyyy`yz6fBvNJcCZ)bgZ{|}>qPM7L&Zi66(P(FQ*cE_ z0TMR~knyobXg@06J%E94VA&(^z-fNGaY{{a)xAftTtKUVddE;0S|54ip_%(Lw8Kzs54g<U`-z2NmuMY1sc#$P6j%lAzub!$)-nt1}cPLgbvFEx58_*2gpTKZshe!dz6bpzt0$dF#!(U6wT>qm>2@f8 zMoLh$S`-`~(}zQ}NE2}=W!P}oO-~Nd8XYEy$g$fZW+H4vyd2^hO-v#Z_qd3AY~o`y zF^NdL!bQBoCO%dZlSnCeHo-Z#>Jenu1oi3bV26Ll_2y{G~w942MS1z$T%f zl)XDuX!z+zIdW-sy1)&Z9n4^3sp}$zQ0}m07*??vp^9n(ZMfF%0EQVuXSVA`Pbrhy z{JaRXJHb!`l{*!iM7eV=r?_={;6o{Qf}uHRfgLps=fe4@X3*}yU#Mb}D0j{c4e`V= z55_@I4+ZPC(i6tgW%mbrpM!kKXOcU|>_`rvc_~`@YK}~r5rcHW8JGZugS~0ca14C_ zE&H4Je<=nH3ds#YQ4`kOtpuJ&gYGeoz(eMc>BJFF>6;zRb!us@Wzy2oqsFF z1$Co#a->kuhnUqO8!XiiX14{~&=YXFU6@E>)_7f{+vzWmYW@gMXe-xcDTX%_&oAfj zp>9F-LpK;!iYAe1=YNgAP?_-fud@A%w^XF2R9B?F#Wpkw{^-ReFr;UK!3OWb$nv#! zf_JH=jiQ8EwORksqr4xy3zM{}#)H$+c^@(9Ag*^;JoT-9CZja6KGkT%lYc~OcwapE zr~R|lJe6Xt0~s0tOp=kg{Yd|$?SkMi;#Zt6!#G0ee-Hy`AsJP~qsse{lnW%5 z|1I`I(XuxeeL)6XZ&3qu&9@hR6D*)R!02tej2qEt*vcNnl99$-8j*JDhokn1LY+#r zPXbd!cAgO^eJVs%1h$yND5Xv%qakXaO3%Uw3DLz@+>ObOrTM8uWVGg|GJwvo_YMd( z=Dzf`2P+u3yv3r#mQlqCnN(xWDzv=%3z zkW^Y9A5M^C@pLJDa#13;B3VTH7%kh`@kpij{fe?DSPCDJ2u(}-Ww0X2^_bH5?1jif zG(L%B6Er@_H`r9}hR{{XR})CHZe%yeCq^V)D@DIk3jNMGWH|O?>USncv7lXPAuI*Y z#aQ*Bg6Dm{q2M_`pcFjgv04WY`S8Hk*pDmHQ!&KYNd?cX%bu#jz$gWA;>pK@tMZ|j zmV!zN!_bMLY;J^r)aj|{`x(kO7JY<-`V(9}8--tjf-6d;9cm`t2p9>`?RX9@zX0Bpux`&L`LyWpBVtRE z-Q6RyX%T|Ut3Hv-3J_=ILI+Eh*ZhZ)Mf)C<9ZI0o5gbp>#dy-RH)>?UQ6bLdT!;K( z87@Q|(eANGh2a2I5*&byVos?E^ZLjp!QjYV5jbWW;);R-R(-~`p9b{87$O)t@C27a z=pu74MSUufgl_$InKZ?dPbZQ}ox&ozkx5}nZAvZp867JITpa4EcwqF+_L~g?t5xJ> zX*OY+_$oDd5u9wPRC_obl|%Khm=kHG9*bTnuv-6Ks?0@)$DiH&5w+bPAbuTIATruK zs?zHIgdY^9S~!ThYbMS_MSt}uexLy49GBWX9?6NwQvZ{$j(BaOPH&Lr&S>)rn7joM z$ez*V!B4d8PKl~i2c#s!6t!V6L|#!I&j}dev%y1e5n^~+VF+Ca#h7_NNf`Sew0kU^ zREI$d*(=>2qO$pL~*3kM#UfE$6^9=@aF4`LJvhoWh8TH z7qTawd<6XZsDPBA>&qv^(Tmp z7$9scQ6P~vLyn5n_xteTBqa<|%&pLmB%ur7tjhblSW=Od-5jdx(*UtSkIN$@6~EFWCPy<8~0JES$Wx)rv_ z!5e=@t=mnTgp#}76s75n(1JR8x!_Gl2o>)+Bh|hY@9A*H3r>Bd@y$#}tl@jB=r+{6 z>>9S{Vc<|+$5N9p>@C7dEy{8cmq!ZOHu%2`wPY<#1z*B%T^`9X7D7GW3H2j7chv;i zN=G=NqDhAZjzg7>7)`0EA6)m~e;?@4j>deKqyC{X7N;)t>>CAbm6<W@Qi_=a4mHf0pvI7a zeGj8Wz`4!4Q4j{H%RVEUo4^M8PC3V&y3S~7PG0IFDE9mX6=mCk=?%!Oc;mKM>dbiZ zBd9uiFusjNlYfmtt(lo0Lyt8sRl4lpqJ~&17UQB&$y3zZ#i^M*^y;E3n=hyJ0&! zxM4I`}$Jj~b zU!5wBHOBnZ8=_RK$SZ{Vp#4zWsYQ#yE4#E*rJqzQ@hxUFRap=2l#s<|Qab8klMP)f zm6FMw$=$cU3+46&t?7wxi&TvV6PQaO@Wz5A2t0q5vqS+P~i2dJz51L=vPm&V=!vZKebeUfCQA_+ObV_189n>1vpc{vqvsd-mu$Wrre29#0r_S1&C zunTWz{|2m-9R-fXkm^hB-;6h&D#}=-_XO&9!`3FhByLr>H*$c?w*DRZ>z9x=rtxO` z*AJaMCzC<}N^Z^sRrU>hACMSCym;3Tap+A?(wvav4owsqM0BTka){RGV1)=A3oT;S zvOYx5A+FKHLImQli+F`iJVz4?5r~Ui#H(%MX_}ZsO269==isWX_>_?Y)@r0%9Mil| zRcdM=o?4(3yf8^23yl&JDp{xirm3Rhh0?CiQDHfo_Zq-ZZQg@=F9A&{biCZYWOngE zg_Tk9!YR9o7aREIVs;g(Hyik%g$r=jw+B9qinjq3$pY7UykhH69w<#7{V3@dsCp{r0M`_rla7+ z*pv!hvz>1gs;ww^S$$7}2W*r~_^t)zHAN9M%-knhImUflMZmK9o zRh7`gqN%By2^|)+y9ba0F6}Oo20Qz>)efVuc=BbAntLFJ8E+tmIJ<&^cMt0i3e#S^ zF;GwKaIx z-r!vW0Q(qd<14qu(i2~SR1oytd!@gOr`Brq#cP}ZvxSHyKMLN}pY{(&#{zY)G#;Ys zeSCkNt*>4wT`JNeqvDTN?F%xE5oP3|wKy zvWicD9T`HwgAQ(Gqz5r) z;gzj^L$$uSXwO9Ho4fFfLN-L@JB1yK+Uz$)Hv9b;9HH{<)GFWfHQej>8X_h7-q+J( z&o0~1Rc(akPQMcDNX4aOYNOxlj>>mOHkB_IA+}=3B>VizKJIw5!uwL$OI@dn!dFFS znx*rN{)qz(Z1WpHqGeI~LOTZi8g;5{icU-_Z0_V_RR5sC|)CV_%)< zkEJI_p#dGoa6lutOQjBdGB@@ufzFnC-(#U!r{4E+zOk`y+EJ3&`JhGR82f6v64Aga z#qR6Ay9|n52op~}7F_ifPK*HK(;*3Thq6%dK2^ZQ)c)QhjdyU&J&_G`hUDz4s8?g# z3UAPe?w`eRDd)yaX13qJGkRb@cG@W`MkHDd%y!$LrGknriyHV2)`uZ#;Jjz9!6t{- zo2lY=(SAC!Fhe& zTkIO|r?Kg6b{(M!?@CiTB^as!z;XMV5D%kN#EEL*0j{8-;2m&O!+r4ZA$W7ArYMD9 zhw~EzP@9o7t$;-(jE$HmI@kh<5djp5P%}|;%k>E)Q8hJyR9G|G?I@GfKS^N(HO~5Foq~iaOMlbwHPq_ zWN3!pgBwab@(PMyWJN9KIv7ZRF_poh<`!q{>ZHKTctyzltJDUUiw9;o%)y(N8*MN) zU303;Mr0ZEJ| zs$OV|5ib~4M)p`7shZb$QA>5-Oi8Q(p&J?s@&k!Srd^TROo(BS8}&QbA;fkdNV{wy zUMC@{{kjK0wq9?F=H6UnHVPMtKnTBUW%>wqQdE(d^(Msivei>a!V&~x3LiS$gj8je z1x1b`)dW|(Solzg_Q0bK6gEa0nTua_$(#-qg}w>0;DyTmN#VjL!bNI>3(6e0gblgc z(1Nn3+_A3~nlQcLS;mxzVxdZ+C>LnJhou%9-B75pk^^4VuwNHxcVcpno-M;?2tHBQ zFH-#_=p+`7EUn_do&^=7E7^m7Po&4n`P*f4jQA^uTxz8iRwM)H{{qwPdXZnrf%rj2 z1ROxXjzB2mDh6xiV-*TyV(1h*_6;zYkr z(J=HXI^sprLPb&*j}VD&@xqdcR5f0l7^zt3c=k%VKpu0W;n1+04us8xefW+pE&#ctctAv^dx6^b(tL4oQ~n5z}jZX6;80CB^Z^ z`$Pr)8rBHMZ4chGP?m4#=?XE?lNpS1A!oa&uA}5^yxix6vss6;@7lMipzeW$q>~9y z@TNaIbh{jR%)VQ){v`!j&GiDkKGcTNrPhb|IYtu-6+fTh@W6j+JhfP@4~>Hzo6Y#qyYN4BWeD>vm7H+hgMYS~oQnCb!DMvwJ`cASJNvDy z^^AWgAJ;JSrujkgVLe03x9<{J5kh+YFXrnre7Eo%`OmTrG#B*yA9m))TbgOdCMW%U z-xeO;vHzDD`=zvFo@?Ca(Y_{Q{|45>%_OQDzCzF29k)Rv-3tfS~5U(HrTBh3BTPD-55T zZYwOFp6)6POiw2Yu}H6$N2jN23PaQ3hDH^i{||_lXdz`${T4m|zz#TASIU?vp4=Nx zJ_t=5zV?bIG0j7IaEygnPbe~)T5a~-Ts9-pbsVMLM&rce5#2mcVERCMHL<%>Z+b?i z0Zd@3*)EP~`~%?42^Z1~T(EIyk0c`oxwq>kQ z{XGzl6469Zm^@POD>@m(hGYPS%|~zr1N&eUme>{})5_D>3%j+lTU z<_y`)ylM}?qO{3K7d3|}1tb}W)Fxw0w@eh)%V1c%u=2ujrJnhqtcO&p$z%tE=~%{- zN4c!k^Igz}1%dA(8@QVpD^Z17bWE8_zn%;&-i1D@dl|pI=Z&PyUho%`1%g*2}m*`ZY}0 zz#zD60Gp!_M6c8%>WVi4Hrf_PMT7qCXlSjCk_H|yHxsOon4_mD77SfAHUMCp&HZ3t z2bHzj5KxX(A@DWSD7>y8i_XJr9IFNpK$Fb<2m79W_Ta&als^Im5+Ks<{}pz1bc3gB zS4Zd{k0?-mP-V*Hw^)8z5ZmxEyjF?t`zZz|=6fo92E}b3@#NbV%!nl)jiLj&;pKi5 zPRL#lLAdJFBV|>UWvI$xF)YQ^MzPR63XCxg#pm~4q^h)$3@LOgEKv2m3+*acz#{Vn zROh0KrLMwV=Dmd(4 zp$zzeU$A9BQZf-7JhBtf%B?*>25WEfgwq>sbe~njg6*nCA_E8STJt5Laozc;@%|9v70JGet$Rn~(-iI{j#WH-fVqnODAm>bgDtC} za%KJnS3M3tQ_>SpNbXI(Hl{xc+EK}A_e9I^-9`RvoJVkRc}4KtZTM#?xIC1cvf|*8`+aE(rkD@lGxY!Ggu&_tZERto#XX3tbW>5h9md(gjo5 zN5PwqBPH~YDb?sLdjtpPpvD>gF{EK}$ty9OhcYVGc#k9?%V7XqYLR~UxCevgymI`n zKw;ZSH>hZKVZqKH!`GBCA2eVD>sepg^>-%$wX>nI6eQ4KnsN|NZVUGQQbK140YryM z&x&>HyQ&)XToT=`BDPfw6z!qkV z;^Gf5Yfv8)H;H|*fxO1?V{l3wv2ZHIppje?HO#XS_ZkRIq2%cV$ zLYgAELyJz5@==kl#0IFgodLC;__dOEuv1NcUs1My;VUdFP9DX$;@rNh4>{Cz3Cn{{ z_KM`ZnyTa-{g5YtZ+esON@B)u*v{I5GGu&3rSK8WJ8?K})es9OWpsG6$#6^V^X6g#;sK|*wzp4gSsJnyIc#*v+q94h_{@K zM9qAHufNG3@+JP3gYb|c+yBHCo~%3M3w{YR^ZovocR{rONnLKapRY3ckm|4g!2aq3 zbXJ=|lDbo-N4kK-(nw}D_2q2N&{(r=kHV4p4;#kp{2DoiCITZ)^i*ic(MX4+@q-b% z#B}s9Hc*(Qv-+{rRV25o1R-;)+oD^h+bW2-daa^ZsvyD^ldZQ*7TA|0Sd7@F$AY~- z(47@SuR)m7k5S?T>MxGsLc`A_02w#~Ofh8HkBVz%UM}s9lW$(ROby`dS)oI04rxgpX z>XS|@*t;3;mLL_I(MWj-Hn+0djH0J2gr_LRIx0s_R*eZ3!1u84!3GD+O!ps!{)fzi zt6oGFqyH&N?nD2xH96@Xic*dKC$@7E2h#YOdQyyUbU_cY8`?Rk8jbtGXS;6{s@wzW~r-7VmE{?sGPkI+D=TT z(M#bAZjPkTOQHLhUJCYFUB6-}e`HYiq?YC|Ad>DW0_`G6q<3P*nBIwI^iDBPa%Zsj zJFu{Kx&?1pS7(z|Z+a&bIrL8N=-#Q6z0(KKRqQ(k)T;;eOHArmx=Yq_y4b+0P6@kF zUeDSeVYN$|7u6l17P9X_x53^>W)hc`e^O6`9ATn@BxZ8Y^h>3{VU~WWT$$@hqx&f7 zmvkQ`{SxyE^z4@cR=>o8syigzJxP^gzr+MwRKm1%cT1zJZs}>KTY3^E^dGuEDn$YZ z*dKMF%U1o-j!)>1aK;n}nJZ|4(9Em;XomDhNbXPH8SNsEVj0(D?T66DSpAS+c7CB7 zV$lvRTP@uXno`y7K#(HGRPLDc_~?dhY~%;^L7Xu#!+44$i%Q8m2<;PEIUa|JFPYg@4n+H( z)ScQE9<86D9oi%E>Vy41Ysri+2JKg8sf(iO7O=To4GS78wO<&WT{~kAjob!^16(cS z2k%l-ZX9|MgDP*ea#IR`7<7+)0Al#YX`OO_)+~V*xL?_tv_85Bq3uL4b~6bB8pzobw0*OeU+4Mz8HIDEr>o4~GYVByP^%bpLe*o*Zuem2lJ zGg3hPG)@Psj&FUzpYYuy4C9T9>ZuiF?j|R)dt)Yjpt7G`oLUY`xp{GgQW+Wi=OFTTwC#rZpC9#DS{5We0<+mLu2bdjm+Yi5pQ0(Mlh&^r#|^1KH!D z84Ar0!1M^!sp!uhByuHp0;maO9Tm5273T^NFy zN0veV#xE)XVT%_#axw?w1F&I|A%O<#9s>b3-_Rq~skKwkdCQQ3{y@D;sM}%MDankI zFVj;F7sLtU6ddIM7_%8wEeu_gjA6#YZRQ^Q?*or*AO;N6gD{Pi^hbd_uOBO%oPF{O zT5t{oZ<>Qu3z#v7fzx{(erS)LfXEME0A7wEZaLgAuFzw3@Ou*c62h4h^P6c5L|XBS zb$KI@8uCa=DD^uidm}yIBA&ZJtvy0QrH0RVWr&C~97J;uA7;g$p&Z&D(?^nqqbQN5 z9=}kC;I}|00P=XHk^?5paNvZN4!>Ozg*K#YWibETKK@SaM>gdz-o@XmcJp_^9#A$( z?&nV(ATnn!f6qOTDElC|?E7drxpB81ZM`?;zHo|+ z)Yp5Zq2$PxV_IUFF|Bgb!`cL9yL6tG9wjR+vE;LoZdx;fJ=Ysi_ok=)SH{y5m%9h0 z7$Po#bHe*3TrYDEEn9=V9dJb>!Vs3dLuE&;8aLJkS3Mh`vLo301l}yrqLvUnhWfr1 ztvuSNKEx`4LDB}i-w0yxxB;v%c*I&$bv2h>`r&{VTgfOwQqIVd-Qq#3bWzB5?9XR? z#W<6~1Xm5f9plU~=yXx_3&!*(UsHOHMDl#7MrK!IY`GnSNUA$mgBFSo*8LuM=BP7y zaTLQ#S;w1$wIPGYo1zVo@#e8$F?hV$%^0TD zkXq_%p&D{RAz}?VH-n0GaS^M2Q>4fla$bq)nmyz!AXSSqyGUQ~*$TH;Y0epxdC)02dwha#*a*niyoPTzP zoR7m~=|A*%v-netH&G)|6gXDQrvG4-`}9N3e?tG^j5}Fhg3DH*fyBJKdf6kv-Ww4% z>UlNGDrAF{(R4kQPT$5PC5~lMv~W%F^w?g0!Idz(0>%8Rf;;6*wd197or1%xxh ztfU@W9jHitIB0ma2}Gh`mH}0IW^OI?lG`~RT-Bo+myICd87}GWLVLV))c{9FZPeKC zRHdSaI4qhI<~Rp&f~FRADs(n}YNzf6_&ms-fCCbX4zsv)i_r+W?>cqxUpYxHI=}(d zUufLUpE*p4z3}FYnK+h$i*<@c?bJe{2jwoI@%ukKUP>ZY5egb-jF(=Q z@zUd&@zQom@PFHQsY=F6l`>wsUdBrcWxO_gy`>ev{{Ago7TUjcOWR=Qe^KXK zVn0ysL26VhlJ8tFBbvNF*7y{Zs*F1|sKvL&@S#`;pAu84ii(M9gU^tuW<ZL>1@_x(pi%dU&$ExIV0+%qK|fr9U)%CZ+0V|nV0%H&&P z?xPAU#3p<-*d%8Jl9Tgr9I_;SEZGSc)U^tHg%wLe;fD{-w+B=Vmfl}TK0l=YLYzYd z?H&S*rDtx1lBEZ{kTcgq|B&%f_VmJ+CAky>w4PzdRH#kZpiAuwhDc~g7&gXGIt9>{ z4MT}OQYc}d>a-8-Q9e;oS>!`QxsA@3Tv{=+;?j!CDrVikWhB~ze^^G21JQoo&DP%0 z+%&hbYt~hByIN;AH_RK?=sBtLtKpNHz8XGfLAa%(xj8(qdESDKtB-K-`7WGM$1`xY zdha>A@RVrH2PcdyS9aV#s_o{@J`<0*YR{1M7{e(O&7H*oln5-O8wc~gy2+SuiAwF+y^B85wQFq$pcms6E$-(duFTZ+uz}&G>0)8 z^5E7fFD*sN;uTA=vgC-RR9Spt;m2F>bC_6S%91OVYGoNFmRe;w+&c#%)+@`A-g&UJ zD$7ycx!}{TEcs&TQkD_ktKibDEXR1OVChkoLhmK8aE1p7MtVEJZK<*x=UocR3T63{ zcL@kqE6b@~ZhKg#EX88kpe!X~*{Cez#j;6RPV-KHpUuj0y2#e2EN6O?2xpsI3FJBK zyOIxuhy8$&RO$Ld8nyXHjIPdyAI}9G^5h220G1nICLYPWrPz6C7>F`E1-;dvBY@rp z%Tc7w4Umala&jlK@Zq3NkEb;EY~FJucO2(dWQ>zopcGf$Qzv1asR~jtWhDOYvuZ@KxI^)_H9?^N8HWrQOyg(a^M|57OjinKbPhDFbXu~xxTC`SE=cF(Y>w?q0?gjehtHjPZ$5uh=5 zBSd5FL6FA0VnmBEu2u}i>pIK4!7^_|Ch^4Y+yF}*!ax#DKND|BsJvn4tbt^T{#;2a z%j{R?YWfdozccDVFMf9oyXb8+86(b_?HN{Ud99OVR$ebAMPS52&#+4vztD&)wW)B# zRoWC*lruj7WzmQ#&#+7Bzjy>v?K1i)9nrX;)iZ1so1Ai{S_9D-^L92VtCjyonroD~ zi3LHYb_>n*3U8&kRhj3~+^)=*)7-7hSJ2!uq8({Gp8>5<@B*^e>D^BAT7`FzMyGuz z@eK;Ul3BY^nY)Q^Qs!$(zgd~DBi^UXi;4FmmzW*7feYk!7^0jLf`iGpCF@P%uN$^J zh+G(PPN!$sjpXA|d{)q`^DRkpK;bu$e~5{eB3h`>l|;jqxd;UsF<-?*C|0%L!id+7=tlPa3ni={arM>O*gE2xYqYUl5r31J-aVp`wewqK z*rV`ov)JgAUq|MvktTfO52f$ckw};V8VkLz`RftN;k0-M{N(5PJQBtGx}W?4%Ha*M z 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%0D5oVv@EAg)~44r&GB>zK_ce;{afaEbC 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

f&zd@!dTi+o5J$RIhydd&HJ}w+} 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,T,K,F,freqs,longitude,latitude,time_utc,Nelem,xx,yy,zz,ra,dec,ph_ra0,ph_dec0,ph_freq0,beam,buffer); @@ -786,6 +788,7 @@ cudakernel_coherencies(int B, int N, int T, int K, int F, float *u, float *v, fl /* spawn threads to handle baselines, these threads will spawn threads for sources */ int ThreadsPerBlock=DEFAULT_TH_PER_BK; + // int ThreadsPerBlock=16; /* note: make sure we do not exceed max no of blocks available, otherwise (too many baselines, loop over source id) */ int BlocksPerGrid= 2*(B+ThreadsPerBlock-1)/ThreadsPerBlock; diff --git a/src/lib/Radio/reserve/radio-reserve.h b/src/lib/Radio/reserve/radio-reserve.h deleted file mode 100644 index 2ae4ce7..0000000 --- a/src/lib/Radio/reserve/radio-reserve.h +++ /dev/null @@ -1,470 +0,0 @@ -/* - * - Copyright (C) 2006-2008 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ -#ifndef SAGECAL_H -#define SAGECAL_H -#ifdef __cplusplus - extern "C" { -#endif - -#include - -/* structures to store extra source info for extended sources */ -typedef struct exinfo_gaussian_ { - double eX,eY,eP; /* major,minor,PA */ - - double cxi,sxi,cphi,sphi; /* projection of [0,0,1] to [l,m,n] */ - int use_projection; -} exinfo_gaussian; - -typedef struct exinfo_disk_ { - double eX; /* diameter */ - - double cxi,sxi,cphi,sphi; /* projection of [0,0,1] to [l,m,n] */ - int use_projection; -} exinfo_disk; - -typedef struct exinfo_ring_ { - double eX; /* diameter */ - - double cxi,sxi,cphi,sphi; /* projection of [0,0,1] to [l,m,n] */ - int use_projection; -} exinfo_ring; - -typedef struct exinfo_shapelet_ { - int n0; /* model order, no of modes=n0*n0 */ - double beta; /* scale*/ - double *modes; /* array of n0*n0 x 1 values */ - double eX,eY,eP; /* linear transform parameters */ - - double cxi,sxi,cphi,sphi; /* projection of [0,0,1] to [l,m,n] */ - int use_projection; -} exinfo_shapelet; - - -/* when to project l,m coordinates */ -#ifndef PROJ_CUT -#define PROJ_CUT 0.998 -#endif - - -/* struct for a cluster GList item */ -typedef struct clust_t_{ - int id; /* cluster id */ - int nchunk; /* no of chunks the data is divided for solving */ - GList *slist; /* list of sources in this cluster (string)*/ -} clust_t; - -typedef struct clust_n_{ - char *name; /* source name (string)*/ -} clust_n; - -/* struct to store source info in hash table */ -typedef struct sinfo_t_ { - double ll,mm,ra,dec,sI[4]; /* sI:4x1 for I,Q,U,V, note sI is updated for central freq (ra,dec) for Az,El */ - unsigned char stype; /* source type */ - void *exdata; /* pointer to carry additional data, if needed */ - double sI0[4],f0,spec_idx,spec_idx1,spec_idx2; /* for multi channel data, original sI,Q,U,V, f0 and spectral index */ -} sinfo_t; - -/* struct for array of the sky model, with clusters */ -typedef struct clus_source_t_ { - int N; /* no of source in this cluster */ - int id; /* cluster id */ - double *ll,*mm,*nn,*sI,*sQ,*sU,*sV; /* arrays Nx1 of source info, note: sI is at reference freq of data */ - /* nn=sqrt(1-ll^2-mm^2)-1 */ - double *ra,*dec; /* arrays Nx1 for Az,El calculation */ - unsigned char *stype; /* source type array Nx1 */ - void **ex; /* array for extra source information Nx1 */ - - int nchunk; /* no of chunks the data is divided for solving */ - int *p; /* array nchunkx1 points to parameter array indices */ - - - double *sI0,*sQ0,*sU0,*sV0,*f0,*spec_idx,*spec_idx1,*spec_idx2; /* for multi channel data, original sI, f0 and spectral index */ -} clus_source_t; - -/* strutct to store baseline to station mapping */ -typedef struct baseline_t_ { - int sta1,sta2; - unsigned char flag; /* if this baseline is flagged, set to 1, otherwise 0: - special case: 2 if baseline is not used in solution, but will be - subtracted */ -} baseline_t; - - -/****************************** readsky.c ****************************/ -/* read sky/cluster files, - carr: return array size Mx1 of clusters - M : no of clusters - freq0: obs frequency Hz - ra0,dec0 : ra,dec of phase center (radians) - format: 0: LSM, 1: LSM with 3 order spec index - each element has source infor for that cluster */ -extern int -read_sky_cluster(const char *skymodel, const char *clusterfile, clus_source_t **carr, int *M, double freq0, double ra0, double dec0,int format); - -/* read solution file, only a set of solutions and load to p - sfp: solution file pointer - p: solutions vector Mt x 1 - carr: for getting correct offset in p - N : stations - M : clusters -*/ -extern int -read_solutions(FILE *sfp,double *p,clus_source_t *carr,int N,int M); - -/* set ignlist[ci]=1 if - cluster id 'cid' is mentioned in ignfile and carr[ci].id==cid -*/ -extern int -update_ignorelist(const char *ignfile, int *ignlist, int M, clus_source_t *carr); - -/* read ADMM regularization factor per cluster from text file, format: - cluster_id hybrid_parameter admm_rho - ... - ... - (M values) - and store it in array arho : size Mtx1, taking into account the hybrid parameter - also in array arhoslave : size Mx1, without taking hybrid params into account - - admm_rho : can be 0 to ignore consensus, just normal calibration -*/ - -extern int -read_arho_fromfile(const char *admm_rho_file,int Mt,double *arho, int M, double *arhoslave); - -/****************************** predict.c ****************************/ -/************* extended source contributions ************/ -extern complex double -shapelet_contrib(void*dd, double u, double v, double w); - -extern complex double -gaussian_contrib(void*dd, double u, double v, double w); - -extern complex double -ring_contrib(void*dd, double u, double v, double w); - -extern complex double -disk_contrib(void*dd, double u, double v, double w); - - -/* time smearing TMS eq. 6.80 for EW-array formula - note u,v,w: meter/c so multiply by freq. to get wavelength - ll,mm: source - dec0: phase center declination - tdelta: integration time */ -extern double -time_smear(double ll,double mm,double dec0,double tdelta,double u,double v,double w,double freq0); - -/* predict visibilities - u,v,w: u,v,w coordinates (wavelengths) size Nbase*tilesz x 1 - u,v,w are ordered with baselines, timeslots - x: data to write size Nbase*8*tileze x 1 - ordered by XX(re,im),XY(re,im),YX(re,im), YY(re,im), baseline, timeslots - N: no of stations - Nbase: no of baselines - tilesz: tile size - barr: baseline to station map, size Nbase*tilesz x 1 - carr: sky model/cluster info size Mx1 of clusters - M: no of clusters - freq0: frequency - fdelta: bandwidth for freq smearing - tdelta: integration time for time smearing - dec0: declination for time smearing - Nt: no of threads -*/ -extern int -predict_visibilities(double *u, double *v, double *w, double *x, int N, - int Nbase, int tilesz, baseline_t *barr, clus_source_t *carr, int M, double freq0, double fdelta, double tdelta, double dec0, int Nt); - - -/* precalculate cluster coherencies - u,v,w: u,v,w coordinates (wavelengths) size Nbase*tilesz x 1 - u,v,w are ordered with baselines, timeslots - x: coherencies size Nbase*4*Mx 1 - ordered by XX(re,im),XY(re,im),YX(re,im), YY(re,im), baseline, timeslots - N: no of stations - Nbase: no of baselines (including more than one tile) - barr: baseline to station map, size Nbase*tilesz x 1 - carr: sky model/cluster info size Mx1 of clusters - M: no of clusters - freq0: frequency - fdelta: bandwidth for freq smearing - tdelta: integration time for time smearing - dec0: declination for time smearing - uvmin: baseline length sqrt(u^2+v^2) below which not to include in solution - uvmax: baseline length higher than this not included in solution - Nt: no of threads - - NOTE: prediction is done for all baselines, even flagged ones - and flags are set to 2 for baselines lower than uvcut -*/ -extern int -precalculate_coherencies(double *u, double *v, double *w, complex double *x, int N, - int Nbase, baseline_t *barr, clus_source_t *carr, int M, double freq0, double fdelta, double tdelta, double dec0, double uvmin, double uvmax, int Nt); - - - -/* rearranges coherencies for GPU use later */ -/* barr: 2*Nbase x 1 - coh: M*Nbase*4 x 1 complex - ddcoh: M*Nbase*8 x 1 - ddbase: 2*Nbase x 1 (sta1,sta2) = -1 if flagged -*/ -extern int -rearrange_coherencies(int Nbase, baseline_t *barr, complex double *coh, double *ddcoh, short *ddbase, int M, int Nt); -/* ddbase: 3*Nbase x 1 (sta1,sta2,flag) */ -extern int -rearrange_coherencies2(int Nbase, baseline_t *barr, complex double *coh, double *ddcoh, short *ddbase, int M, int Nt); - -/* rearranges baselines for GPU use later */ -/* barr: 2*Nbase x 1 - ddbase: 2*Nbase x 1 -*/ -extern int -rearrange_baselines(int Nbase, baseline_t *barr, short *ddbase, int Nt); - -/* cont how many baselines contribute to each station */ -extern int -count_baselines(int Nbase, int N, float *iw, short *ddbase, int Nt); - -/* initialize array b (size Nx1) to given value a */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern void -setweights(int N, double *b, double a, int Nt); - -/* update baseline flags, also make data zero if flagged - this is needed for solving (calculate error) ignore flagged data */ -/* Nbase: total actual data points = Nbasextilesz - flag: flag array Nbasex1 - barr: baseline array Nbasex1 - x: data Nbase*8 x 1 ( 8 value per baseline ) - Nt: no of threads -*/ -extern int -preset_flags_and_data(int Nbase, double *flag, baseline_t *barr, double *x, int Nt); - -/* generte baselines -> sta1,sta2 pairs for later use */ -/* barr: Nbasextilesz - N : stations - Nt : threads -*/ -extern int -generate_baselines(int Nbase, int tilesz, int N, baseline_t *barr,int Nt); - -/* convert types */ -/* both arrays size nx1 - Nt: no of threads -*/ -extern int -double_to_float(float *farr, double *darr,int n, int Nt); -extern int -float_to_double(double *darr, float *farr,int n, int Nt); - -/* create a vector with 1's at flagged data points */ -/* - ddbase: 3*Nbase x 1 (sta1,sta2,flag) - x: 8*Nbase (set to 0's and 1's) -*/ -extern int -create_onezerovec(int Nbase, short *ddbase, float *x, int Nt); - -/* - find sum1=sum(|x|), and sum2=y^T |x| - x,y: nx1 arrays -*/ -extern int -find_sumproduct(int N, float *x, float *y, float *sum1, float *sum2, int Nt); - -/****************************** transforms.c ****************************/ -#ifndef ASEC2RAD -#define ASEC2RAD 4.848136811095359935899141e-6 -#endif - -/* - convert xyz ITRF 2000 coords (m) to - long,lat, (rad) height (m) - References: -*/ -extern int -xyz2llh(double *x, double *y, double *z, double *longitude, double *latitude, double *height, int N); - -/* convert ra,dec to az,el - ra,dec: radians - longitude,latitude: rad,rad - time_jd: JD days - - az,el: output rad,rad - -References: Darin C. Koblick MATLAB code, based on - % Fundamentals of Astrodynamics and Applications - % D. Vallado, Second Edition - % Example 3-5. Finding Local Siderial Time (pg. 192) - % Algorithm 28: AzElToRaDec (pg. 259) -*/ -extern int -radec2azel(double ra, double dec, double longitude, double latitude, double time_jd, double *az, double *el); - -/* convert time to Greenwitch Mean Sideral Angle (deg) - time_jd : JD days - thetaGMST : GMST angle (deg) -*/ -extern int -jd2gmst(double time_jd, double *thetaGMST); - - -/* convert ra,dec to az,el - ra,dec: radians - longitude,latitude: rad,rad - thetaGMST : GMST angle (deg) - - az,el: output rad,rad - -*/ -extern int -radec2azel_gmst(double ra, double dec, double longitude, double latitude, double thetaGMST, double *az, double *el); - - - -/* given the epoch jd_tdb2, - calculate rotation matrix params needed to precess from J2000 - NOVAS (Naval Observatory Vector Astronomy Software) - PURPOSE: - Precesses equatorial rectangular coordinates from one epoch to - another. One of the two epochs must be J2000.0. The coordinates - are referred to the mean dynamical equator and equinox of the two - respective epochs. - - REFERENCES: - Explanatory Supplement To The Astronomical Almanac, pp. 103-104. - Capitaine, N. et al. (2003), Astronomy And Astrophysics 412, - pp. 567-586. - Hilton, J. L. et al. (2006), IAU WG report, Celest. Mech., 94, - pp. 351-367. - -*/ -extern int -get_precession_params(double jd_tdb2, double Tr[9]); -/* precess ra0,dec0 at J2000 - to ra,dec at epoch given by transform Tr - using NOVAS library */ -extern int -precession(double ra0, double dec0, double Tr[9], double *ra, double *dec); - -/****************************** stationbeam.c ****************************/ -/* - ra,dec: source direction (rad) - ra0,dec0: beam center (rad) - f: frequency (Hz) - f0: beam forming frequency (Hz) - - longitude,latitude : Nx1 array of station positions (rad,rad) - time_jd: JD (day) time - Nelem : Nx1 array, no. of elements used in each station - x,y,z: Nx1 pointer arrays to station positions, each station has Nelem[]x1 arrays - - beamgain: Nx1 array of station beam gain along the source direction -*/ -extern int -arraybeam(double ra, double dec, double ra0, double dec0, double f, double f0, int N, double *longitude, double *latitude, double time_jd, int *Nelem, double **x, double **y, double **z, double *beamgain); - - -/****************************** predict_withbeam.c ****************************/ -/* precalculate cluster coherencies - u,v,w: u,v,w coordinates (wavelengths) size Nbase*tilesz x 1 - u,v,w are ordered with baselines, timeslots - x: coherencies size Nbase*4*Mx 1 - ordered by XX(re,im),XY(re,im),YX(re,im), YY(re,im), baseline, timeslots - N: no of stations - Nbase: total no of baselines (including more than one tile or timeslot) - barr: baseline to station map, size Nbase*tilesz x 1 - carr: sky model/cluster info size Mx1 of clusters - M: no of clusters - freq0: frequency - fdelta: bandwidth for freq smearing - tdelta: integration time for time smearing - dec0: declination for time smearing - uvmin: baseline length sqrt(u^2+v^2) below which not to include in solution - uvmax: baseline length higher than this not included in solution - - Station beam specific parameters - ph_ra0,ph_dec0: beam pointing rad,rad - ph_freq0: beam reference freq - longitude,latitude: Nx1 arrays (rad,rad) station locations - time_utc: JD (day) : tilesz x 1 - tilesz: how many tiles: == unique time_utc - Nelem: Nx1 array, size of stations (elements) - xx,yy,zz: Nx1 arrays of station element locations arrays xx[],yy[],zz[] - Nt: no of threads - - NOTE: prediction is done for all baselines, even flagged ones - and flags are set to 2 for baselines lower than uvcut -*/ - -extern int -precalculate_coherencies_withbeam(double *u, double *v, double *w, complex double *x, int N, - int Nbase, baseline_t *barr, clus_source_t *carr, int M, double freq0, double fdelta, double tdelta, double dec0, double uvmin, double uvmax, - double ph_ra0, double ph_dec0, double ph_freq0, double *longitude, double *latitude, double *time_utc, int tileze, int *Nelem, double **xx, double **yy, double **zz, int Nt); - - -extern int -predict_visibilities_multifreq_withbeam(double *u,double *v,double *w,double *x,int N,int Nbase,int tilesz,baseline_t *barr, clus_source_t *carr, int M,double *freqs,int Nchan, double fdelta,double tdelta, double dec0, - double ph_ra0, double ph_dec0, double ph_freq0, double *longitude, double *latitude, double *time_utc,int *Nelem, double **xx, double **yy, double **zz, int Nt, int add_to_data); - -extern int -calculate_residuals_multifreq_withbeam(double *u,double *v,double *w,double *p,double *x,int N,int Nbase,int tilesz,baseline_t *barr, clus_source_t *carr, int M,double *freqs,int Nchan, double fdelta,double tdelta,double dec0, -double ph_ra0, double ph_dec0, double ph_freq0, double *longitude, double *latitude, double *time_utc,int *Nelem, double **xx, double **yy, double **zz, int Nt, int ccid, double rho, int phase_only); - - -/* change epoch of soure ra,dec from J2000 to JAPP */ -/* also the beam pointing ra_beam,dec_beam */ -extern int -precess_source_locations(double jd_tdb, clus_source_t *carr, int M, double *ra_beam, double *dec_beam, int Nt); - -/****************************** predict_withbeam_gpu.c ****************************/ -/* if dobeam==0, beam calculation is off */ -extern int -precalculate_coherencies_withbeam_gpu(double *u, double *v, double *w, complex double *x, int N, - int Nbase, baseline_t *barr, clus_source_t *carr, int M, double freq0, double fdelta, double tdelta, double dec0, double uvmin, double uvmax, - double ph_ra0, double ph_dec0, double ph_freq0, double *longitude, double *latitude, double *time_utc, int tileze, int *Nelem, double **xx, double **yy, double **zz, int dobeam, int Nt); - -extern int -predict_visibilities_multifreq_withbeam_gpu(double *u,double *v,double *w,double *x,int N,int Nbase,int tilesz,baseline_t *barr, clus_source_t *carr, int M,double *freqs,int Nchan, double fdelta,double tdelta, double dec0, - double ph_ra0, double ph_dec0, double ph_freq0, double *longitude, double *latitude, double *time_utc,int *Nelem, double **xx, double **yy, double **zz, int dobeam, int Nt, int add_to_data); - - - -/****************************** predict_model.cu ****************************/ -extern void -cudakernel_array_beam(int N, int T, int K, int F, float *freqs, float *longitude, float *latitude, - double *time_utc, int *Nelem, float **xx, float **yy, float **zz, float *ra, float *dec, float ph_ra0, float ph_dec0, float ph_freq0, float *beam); - - -extern void -cudakernel_coherencies(int B, int N, int T, int K, int F, float *u, float *v, float *w,baseline_t *barr, float *freqs, float *beam, float *ll, float *mm, float *nn, float *sI, - unsigned char *stype, float *sI0, float *f0, float *spec_idx, float *spec_idx1, float *spec_idx2, int **exs, float deltaf, float deltat, float dec0, float *coh,int dobeam); - - -extern void -cudakernel_convert_time(int T, double *time_utc); -#ifdef __cplusplus - } /* extern "C" */ -#endif diff --git a/src/lib/Solvers/.Common.h.swp b/src/lib/Solvers/.Common.h.swp deleted file mode 100644 index 3a5af645c7795bac12826e5580415fb5694386b5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57344 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 N9)_D#_}en@{{cVSV`TsU diff --git a/src/lib/Solvers/.Dirac.h.swp b/src/lib/Solvers/.Dirac.h.swp deleted file mode 100644 index f49edf1cf8939eb9d0baae37765a60a94e49ff5a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 102402 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 RzFsl2m_BFwP0K0e{|EO6>}3D| diff --git a/src/lib/Solvers/.gitignore b/src/lib/Solvers/.gitignore deleted file mode 100644 index 3819313..0000000 --- a/src/lib/Solvers/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.swp -*.swo diff --git a/src/lib/Solvers/Common.h b/src/lib/Solvers/Common.h deleted file mode 100644 index 2ad3114..0000000 --- a/src/lib/Solvers/Common.h +++ /dev/null @@ -1,1162 +0,0 @@ - -/* structures to store extra source info for extended sources */ -typedef struct exinfo_gaussian_ { - double eX,eY,eP; /* major,minor,PA */ - - double cxi,sxi,cphi,sphi; /* projection of [0,0,1] to [l,m,n] */ - int use_projection; -} exinfo_gaussian; - -typedef struct exinfo_disk_ { - double eX; /* diameter */ - - double cxi,sxi,cphi,sphi; /* projection of [0,0,1] to [l,m,n] */ - int use_projection; -} exinfo_disk; - -typedef struct exinfo_ring_ { - double eX; /* diameter */ - - double cxi,sxi,cphi,sphi; /* projection of [0,0,1] to [l,m,n] */ - int use_projection; -} exinfo_ring; - -typedef struct exinfo_shapelet_ { - int n0; /* model order, no of modes=n0*n0 */ - double beta; /* scale*/ - double *modes; /* array of n0*n0 x 1 values */ - double eX,eY,eP; /* linear transform parameters */ - - double cxi,sxi,cphi,sphi; /* projection of [0,0,1] to [l,m,n] */ - int use_projection; -} exinfo_shapelet; - - -/* when to project l,m coordinates */ -#ifndef PROJ_CUT -#define PROJ_CUT 0.998 -#endif - - -/* struct for a cluster GList item */ -typedef struct clust_t_{ - int id; /* cluster id */ - int nchunk; /* no of chunks the data is divided for solving */ - GList *slist; /* list of sources in this cluster (string)*/ -} clust_t; - -typedef struct clust_n_{ - char *name; /* source name (string)*/ -} clust_n; - -/* struct to store source info in hash table */ -typedef struct sinfo_t_ { - double ll,mm,ra,dec,sI[4]; /* sI:4x1 for I,Q,U,V, note sI is updated for central freq (ra,dec) for Az,El */ - unsigned char stype; /* source type */ - void *exdata; /* pointer to carry additional data, if needed */ - double sI0[4],f0,spec_idx,spec_idx1,spec_idx2; /* for multi channel data, original sI,Q,U,V, f0 and spectral index */ -} sinfo_t; - -/* struct for array of the sky model, with clusters */ -typedef struct clus_source_t_ { - int N; /* no of source in this cluster */ - int id; /* cluster id */ - double *ll,*mm,*nn,*sI,*sQ,*sU,*sV; /* arrays Nx1 of source info, note: sI is at reference freq of data */ - /* nn=sqrt(1-ll^2-mm^2)-1 */ - double *ra,*dec; /* arrays Nx1 for Az,El calculation */ - unsigned char *stype; /* source type array Nx1 */ - void **ex; /* array for extra source information Nx1 */ - - int nchunk; /* no of chunks the data is divided for solving */ - int *p; /* array nchunkx1 points to parameter array indices */ - - - double *sI0,*sQ0,*sU0,*sV0,*f0,*spec_idx,*spec_idx1,*spec_idx2; /* for multi channel data, original sI, f0 and spectral index */ -} clus_source_t; - -/* strutct to store baseline to station mapping */ -typedef struct baseline_t_ { - int sta1,sta2; - unsigned char flag; /* if this baseline is flagged, set to 1, otherwise 0: - special case: 2 if baseline is not used in solution, but will be - subtracted */ -} baseline_t; - - -/* structure for worker threads for various function calculations */ -typedef struct thread_data_base_ { - int Nb; /* no of baselines this handle */ - int boff; /* baseline offset per thread */ - baseline_t *barr; /* pointer to baseline-> stations mapping array */ - double *u,*v,*w; /* pointers to uwv arrays,size Nbx1 */ - clus_source_t *carr; /* sky model, with clusters Mx1 */ - int M; /* no of clusters */ - double *x; /* output vector Nbx8 array re,im,re,im .... */ - complex double *coh; /* output vector in complex form, (not used always) size 4*M*Nb */ - /* following are only used while predict with gain */ - double *p; /* parameter array, size could be 8*N*Mx1 (full) or 8*Nx1 (single)*/ - int N; /* no of stations */ - int clus; /* which cluster to process, 0,1,...,M-1 if -1 all clusters */ - double uvmin; /* baseline length sqrt(u^2+v^2) lower limit, below this is not - included in calibration, but will be subtracted */ - double uvmax; - /* following used for freq/time smearing calculation */ - double freq0; - double fdelta; - double tdelta; /* integration time for time smearing */ - double dec0; /* declination for time smearing */ - - /* following used for interpolation */ - double *p0; /* old parameters, same as p */ - int tilesz; /* tile size */ - int Nbase; /* total no of baselines */ - /* following for correction of data */ - double *pinv; /* inverted solution array, if null no correction */ - int ccid; /* which cluster id (not user specified id) for correction, >=0 */ - - /* following for ignoring clusters in simulation */ - int *ignlist; /* Mx1 array, if any value 1, that cluster will not be simulated */ - /* flag for adding model to data */ - int add_to_data; - - /* following used for multifrequency (channel) data */ - double *freqs; - int Nchan; - - /* following used for calculating beam */ - double *arrayfactor; /* storage for precomputed beam */ - /* if clus==0, reset memory before adding */ - -} thread_data_base_t; - -/* structure for worker threads for - precalculating beam array factor */ -typedef struct thread_data_arrayfac_ { - int Ns; /* total no of sources per thread */ - int soff; /* starting source */ - int Ntime; /* total timeslots */ - double *time_utc; /* Ntimex1 array */ - int N; /* no. of stations */ - double *longitude, *latitude; - - double ra0,dec0,freq0; /* reference pointing and freq */ - int Nf; /* no. of frequencies to calculate */ - double *freqs; /* Nfx1 array */ - - int *Nelem; /* Nx1 array of element counts */ - double **xx,**yy,**zz; /* Nx1 arrays to element coords of each station, size Nelem[]x1 */ - - clus_source_t *carr; /* sky model, with clusters Mx1 */ - int cid; /* cluster id to calculate beam */ - baseline_t *barr; /* pointer to baseline-> stations mapping array */ - double *beamgain; /* output */ -} thread_data_arrayfac_t; - - -/* structure for worker threads for presetting - flagged data before solving */ -typedef struct thread_data_preflag_ { - int Nbase; /* total no of baselines */ - int startbase; /* starting baseline */ - int endbase; /* ending baseline */ - baseline_t *barr; /* pointer to baseline-> stations mapping array */ - double *x; /* data */ - double *flag; /* flag array 0 or 1 */ -} thread_data_preflag_t; - - -/* structure for worker threads for arranging coherencies for GPU use */ -typedef struct thread_data_coharr_ { - int M; /* no of clusters */ - int Nbase; /* no of baselines */ - int startbase; /* starting baseline */ - int endbase; /* ending baseline */ - baseline_t *barr; /* pointer to baseline-> stations mapping array */ - complex double *coh; /* output vector in complex form, (not used always) size 4*M*Nb */ - double *ddcoh; /* coherencies, rearranged for easy copying to GPU, also real,imag instead of complex */ - short *ddbase; /* baseline to station maps, same as barr, assume no of stations < 32k, if flagged set to -1 OR (sta1,sta2,flag) 3 values for each baseline */ -} thread_data_coharr_t; - -/* structure for worker threads for type conversion */ -typedef struct thread_data_typeconv_{ - int starti; /* starting baseline */ - int endi; /* ending baseline */ - double *darr; /* double array */ - float *farr; /* float array */ -} thread_data_typeconv_t; - -/* structure for worker threads for baseline generation */ -typedef struct thread_data_baselinegen_{ - int starti; /* starting tile */ - int endi; /* ending tile */ - baseline_t *barr; /* baseline array */ - int N; /* stations */ - int Nbase; /* baselines */ -} thread_data_baselinegen_t; - -/* structure for counting baselines for each station (RTR)*/ -typedef struct thread_data_count_ { - int Nb; /* no of baselines this handle */ - int boff; /* baseline offset per thread */ - - short *ddbase; - - int *bcount; - - /* mutexs: N x 1, one for each station */ - pthread_mutex_t *mx_array; -} thread_data_count_t; - - -/* structure for initializing an array */ -typedef struct thread_data_setwt_ { - int Nb; /* no of baselines this handle */ - int boff; /* baseline offset per thread */ - - double *b; - double a; - -} thread_data_setwt_t; - -/* structure for weight calculation for baselines */ -typedef struct thread_data_baselinewt_ { - int Nb; /* no of baselines this handle */ - int boff; /* baseline offset per thread */ - - double *wt; /* 8 values per baseline */ - double *u,*v; - double freq0; - -} thread_data_baselinewt_t; - - - -/* structure for worker threads for jacobian calculation */ -typedef struct thread_data_jac_ { - int Nb; /* no of baselines this handle */ - int n; /* function dimension n=8*Nb is implied */ - int m; /* no of parameters */ - baseline_t *barr; /* pointer to baseline-> stations mapping array */ - double *u,*v,*w; /* pointers to uwv arrays,size Nbx1 */ - clus_source_t *carr; /* sky model, with clusters Mx1 */ - int M; /* no of clusters */ - double *jac; /* output jacobian Nbx8 rows, re,im,re,im .... */ - complex double *coh; /* output vector in complex form, (not used always) size 4*M*Nb */ - /* following are only used while predict with gain */ - double *p; /* parameter array, size could be 8*N*Mx1 (full) or 8*Nx1 (single)*/ - int N; /* no of stations */ - int clus; /* which cluster to process, 0,1,...,M-1 if -1 all clusters */ - int start_col; - int end_col; /* which column of jacobian we calculate */ -} thread_data_jac_t; - - -/* structure for levmar */ -typedef struct me_data_t_ { - int clus; /* which cluster 0,1,...,M-1 if -1 all clusters */ - double *u,*v,*w; /* uvw coords size Nbase*tilesz x 1 */ - int Nbase; /* no of baselines */ - int tilesz; /* tile size */ - int N; /* no of stations */ - baseline_t *barr; /* baseline->station mapping, size Nbase*tilesz x 1 */ - clus_source_t *carr; /* sky model, with clusters size Mx1 */ - int M; /* no of clusters */ - int Mt; /* apparent no of clusters, due to hybrid solving, Mt>=M */ - double *freq0; /* frequency */ - int Nt; /* no of threads */ - - complex double *coh; /* pre calculated cluster coherencies, per cluster 4xNbase values, total size 4*M*Nbase*tilesz x 1 */ - /* following only used by CPU LM */ - int tileoff; /* tile offset for hybrid solution */ - - /* following only used by GPU LM version */ - double *ddcoh; /* coherencies, rearranged for easy copying to GPU, also real,imag instead of complex */ - short *ddbase; /* baseline to station maps, same as barr, size 2*Nbase*tilesz x 1, assume no of stations < 32k, if flagged set to -1 */ - /* following used only by LBFGS */ - short *hbb; /* baseline to station maps, same as ddbase size 2*Nbase*tilesz x 1, assume no of stations < 32k, if flagged set to -1 */ - int *ptoclus; /* param no -> cluster mapping, size 2*M x 1 - for each cluster : chunk size, start param index */ - - /* following used only by mixed precision solver */ - float *ddcohf; /* float version of ddcoh */ - - /* following used only by robust T cost/grad functions */ - double robust_nu; - - /* following used only by RTR */ -} me_data_t; - - -/* structure for gpu driver threads for LBFGS */ -typedef struct thread_gpu_data_t { - int ThreadsPerBlock; - int BlocksPerGrid; - int card; /* which gpu ? */ - - int Nbase; /* no of baselines */ - int tilesz; /* tile size */ - baseline_t *barr; /* baseline->station mapping, size Nbase*tilesz x 1 */ - int M; /* no of clusters */ - int N; /* no of stations */ - complex double *coh; /* pre calculated cluster coherencies, per cluster 4xNbase values, total size 4*M*Nbase*tilesz x 1 */ - int m; /* no of parameters */ - int n; /* no of observations */ - double *xo; /* observed data size n x 1 */ - double *p;/* parameter vectors size m x 1 */ - double *g; /* gradient vector (output) size m x 1*/ - int g_start; /* at which point in g do we start calculation */ - int g_end; /* at which point in g do we end calculation */ - - short *hbb; /* baseline to station maps, same as ddbase size 2*Nbase*tilesz x 1, assume no of stations < 32k, if flagged set to -1 */ - int *ptoclus; /* param no -> cluster mapping, size 2*M x 1 - for each cluster : chunk size, start param index */ - - /* only used in robust LBFGS */ - double robust_nu; -} thread_gpu_data; - - -/* structure for driver threads to evaluate gradient */ -typedef struct thread_data_grad_ { - int Nbase; /* no of baselines */ - int tilesz; /* tile size */ - baseline_t *barr; /* baseline->station mapping, size Nbase*tilesz x 1 */ - clus_source_t *carr; /* sky model, with clusters Mx1 */ - int M; /* no of clusters */ - int N; /* no of stations */ - complex double *coh; /* pre calculated cluster coherencies, per cluster 4xNbase values, total size 4*M*Nbase*tilesz x 1 */ - int m; /* no of parameters */ - int n; /* no of observations */ - double *x; /* residual data size n x 1 x=observed-func*/ - double *p;/* parameter vectors size m x 1 */ - double *g; /* gradient vector (output) size m x 1*/ - int g_start; /* at which point in g do we start calculation */ - int g_end; /* at which point in g do we end calculation */ - - /* only used in robust version */ - double robust_nu; -} thread_data_grad_t; - -/* structure for weight product calculation in robust LM */ -typedef struct thread_data_vec_{ - int starti,endi; - double *ed; - double *wtd; -} thread_data_vec_t; - -/* structure for weight calculation + nu update in robust LM */ -typedef struct thread_data_vecnu_{ - int starti,endi; - double *ed; - double *wtd; - double *q; - double nu0; - double sumq; - double nulow,nuhigh; -} thread_data_vecnu_t; - - -/* structure for worker threads for setting 1/0 */ -typedef struct thread_data_onezero_ { - int startbase; /* starting baseline */ - int endbase; /* ending baseline */ - short *ddbase; /* baseline to station maps, (sta1,sta2,flag) */ - float *x; /* data vector */ -} thread_data_onezero_t; - - -/* structure for worker threads for finding sum(|x|) and y^T |x| */ -typedef struct thread_data_findsumprod_ { - int startbase; /* starting baseline */ - int endbase; /* ending baseline */ - float *x; /* can be -ve*/ - float *y; - float sum1; /* sum(|x|) */ - float sum2; /* y^T |x| */ -} thread_data_findsumprod_t; - - -/****************************** readsky.c ****************************/ -/* read sky/cluster files, - carr: return array size Mx1 of clusters - M : no of clusters - freq0: obs frequency Hz - ra0,dec0 : ra,dec of phase center (radians) - format: 0: LSM, 1: LSM with 3 order spec index - each element has source infor for that cluster */ -extern int -read_sky_cluster(const char *skymodel, const char *clusterfile, clus_source_t **carr, int *M, double freq0, double ra0, double dec0,int format); - -/* read solution file, only a set of solutions and load to p - sfp: solution file pointer - p: solutions vector Mt x 1 - carr: for getting correct offset in p - N : stations - M : clusters -*/ -extern int -read_solutions(FILE *sfp,double *p,clus_source_t *carr,int N,int M); - -/* set ignlist[ci]=1 if - cluster id 'cid' is mentioned in ignfile and carr[ci].id==cid -*/ -extern int -update_ignorelist(const char *ignfile, int *ignlist, int M, clus_source_t *carr); - -/* read ADMM regularization factor per cluster from text file, format: - cluster_id hybrid_parameter admm_rho - ... - ... - (M values) - and store it in array arho : size Mtx1, taking into account the hybrid parameter - also in array arhoslave : size Mx1, without taking hybrid params into account - - admm_rho : can be 0 to ignore consensus, just normal calibration -*/ - -extern int -read_arho_fromfile(const char *admm_rho_file,int Mt,double *arho, int M, double *arhoslave); - -/****************************** dataio.c ****************************/ -/* open binary file for input/output - datfile: data file descriptor id - d: array of input/output stream, size (count-(header length))x1 - N: no of stations - freq0: frequency Hz - ra0,dec0: ra,dec of phase center (radians) -*/ -extern int -open_data_stream(int file, double **d, int *count, int *N, double *freq0, double *ra0, double *dec0); - -/* close the data stream */ -extern int -close_data_stream(double *d, int count); - - -/****************************** predict.c ****************************/ -/************* extended source contributions ************/ -extern complex double -shapelet_contrib(void*dd, double u, double v, double w); - -extern complex double -gaussian_contrib(void*dd, double u, double v, double w); - -extern complex double -ring_contrib(void*dd, double u, double v, double w); - -extern complex double -disk_contrib(void*dd, double u, double v, double w); - - -/* time smearing TMS eq. 6.80 for EW-array formula - note u,v,w: meter/c so multiply by freq. to get wavelength - ll,mm: source - dec0: phase center declination - tdelta: integration time */ -extern double -time_smear(double ll,double mm,double dec0,double tdelta,double u,double v,double w,double freq0); - -/* predict visibilities - u,v,w: u,v,w coordinates (wavelengths) size Nbase*tilesz x 1 - u,v,w are ordered with baselines, timeslots - x: data to write size Nbase*8*tileze x 1 - ordered by XX(re,im),XY(re,im),YX(re,im), YY(re,im), baseline, timeslots - N: no of stations - Nbase: no of baselines - tilesz: tile size - barr: baseline to station map, size Nbase*tilesz x 1 - carr: sky model/cluster info size Mx1 of clusters - M: no of clusters - freq0: frequency - fdelta: bandwidth for freq smearing - tdelta: integration time for time smearing - dec0: declination for time smearing - Nt: no of threads -*/ -extern int -predict_visibilities(double *u, double *v, double *w, double *x, int N, - int Nbase, int tilesz, baseline_t *barr, clus_source_t *carr, int M, double freq0, double fdelta, double tdelta, double dec0, int Nt); - - -/* precalculate cluster coherencies - u,v,w: u,v,w coordinates (wavelengths) size Nbase*tilesz x 1 - u,v,w are ordered with baselines, timeslots - x: coherencies size Nbase*4*Mx 1 - ordered by XX(re,im),XY(re,im),YX(re,im), YY(re,im), baseline, timeslots - N: no of stations - Nbase: no of baselines (including more than one tile) - barr: baseline to station map, size Nbase*tilesz x 1 - carr: sky model/cluster info size Mx1 of clusters - M: no of clusters - freq0: frequency - fdelta: bandwidth for freq smearing - tdelta: integration time for time smearing - dec0: declination for time smearing - uvmin: baseline length sqrt(u^2+v^2) below which not to include in solution - uvmax: baseline length higher than this not included in solution - Nt: no of threads - - NOTE: prediction is done for all baselines, even flagged ones - and flags are set to 2 for baselines lower than uvcut -*/ -extern int -precalculate_coherencies(double *u, double *v, double *w, complex double *x, int N, - int Nbase, baseline_t *barr, clus_source_t *carr, int M, double freq0, double fdelta, double tdelta, double dec0, double uvmin, double uvmax, int Nt); - - - -/* rearranges coherencies for GPU use later */ -/* barr: 2*Nbase x 1 - coh: M*Nbase*4 x 1 complex - ddcoh: M*Nbase*8 x 1 - ddbase: 2*Nbase x 1 (sta1,sta2) = -1 if flagged -*/ -extern int -rearrange_coherencies(int Nbase, baseline_t *barr, complex double *coh, double *ddcoh, short *ddbase, int M, int Nt); -/* ddbase: 3*Nbase x 1 (sta1,sta2,flag) */ -extern int -rearrange_coherencies2(int Nbase, baseline_t *barr, complex double *coh, double *ddcoh, short *ddbase, int M, int Nt); - -/* rearranges baselines for GPU use later */ -/* barr: 2*Nbase x 1 - ddbase: 2*Nbase x 1 -*/ -extern int -rearrange_baselines(int Nbase, baseline_t *barr, short *ddbase, int Nt); - -/* cont how many baselines contribute to each station */ -extern int -count_baselines(int Nbase, int N, float *iw, short *ddbase, int Nt); - -/* initialize array b (size Nx1) to given value a */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern void -setweights(int N, double *b, double a, int Nt); - -/* update baseline flags, also make data zero if flagged - this is needed for solving (calculate error) ignore flagged data */ -/* Nbase: total actual data points = Nbasextilesz - flag: flag array Nbasex1 - barr: baseline array Nbasex1 - x: data Nbase*8 x 1 ( 8 value per baseline ) - Nt: no of threads -*/ -extern int -preset_flags_and_data(int Nbase, double *flag, baseline_t *barr, double *x, int Nt); - -/* generte baselines -> sta1,sta2 pairs for later use */ -/* barr: Nbasextilesz - N : stations - Nt : threads -*/ -extern int -generate_baselines(int Nbase, int tilesz, int N, baseline_t *barr,int Nt); - -/****************************** myblas.c ****************************/ -/* BLAS wrappers */ -/* machine precision */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern double -dlamch(char CMACH); - -/* blas dcopy */ -/* y = x */ -/* read x values spaced by Nx (so x size> N*Nx) */ -/* write to y values spaced by Ny (so y size > N*Ny) */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern void -my_dcopy(int N, double *x, int Nx, double *y, int Ny); - -/* blas scale */ -/* x = a. x */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern void -my_dscal(int N, double a, double *x); -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern void -my_sscal(int N, float a, float *x); - -/* x^T*y */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern double -my_ddot(int N, double *x, double *y); - -/* ||x||_2 */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern double -my_dnrm2(int N, double *x); -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern float -my_fnrm2(int N, float *x); - -/* sum||x||_1 */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern double -my_dasum(int N, double *x); -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern float -my_fasum(int N, float *x); - -/* BLAS y = a.x + y */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern void -my_daxpy(int N, double *x, double a, double *y); - -/* BLAS y = a.x + y */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern void -my_daxpys(int N, double *x, int incx, double a, double *y, int incy); - -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern void -my_saxpy(int N, float *x, float a, float *y); - -/* max |x| index (start from 1...)*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -my_idamax(int N, double *x, int incx); -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -my_isamax(int N, float *x, int incx); - -/* min |x| index (start from 1...)*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -int -my_idamin(int N, double *x, int incx); - -/* BLAS DGEMM C = alpha*op(A)*op(B)+ beta*C */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern void -my_dgemm(char transa, char transb, int M, int N, int K, double alpha, double *A, int lda, double *B, int ldb, double beta, double *C, int ldc); - -/* BLAS DGEMV y = alpha*op(A)*x+ beta*y : op 'T' or 'N' */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern void -my_dgemv(char trans, int M, int N, double alpha, double *A, int lda, double *x, int incx, double beta, double *y, int incy); - -/* following routines used in LAPACK solvers */ -/* cholesky factorization: real symmetric */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -my_dpotrf(char uplo, int N, double *A, int lda); - -/* solve Ax=b using cholesky factorization */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -my_dpotrs(char uplo, int N, int nrhs, double *A, int lda, double *b, int ldb); - -/* solve Ax=b using QR factorization */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -my_dgels(char TRANS, int M, int N, int NRHS, double *A, int LDA, double *B, int LDB, double *WORK, int LWORK); - -/* A=U S VT, so V needs NOT to be transposed */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -my_dgesvd(char JOBU, char JOBVT, int M, int N, double *A, int LDA, double *S, - double *U, int LDU, double *VT, int LDVT, double *WORK, int LWORK); -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -my_zgesvd(char JOBU, char JOBVT, int M, int N, complex double *A, int LDA, double *S, - complex double *U, int LDU, complex double *VT, int LDVT, complex double *WORK, int LWORK, double *RWORK); - -/* QR factorization QR=A, only TAU is used for Q, R stored in A*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -my_dgeqrf(int M, int N, double *A, int LDA, double *TAU, double *WORK, int LWORK); - -/* calculate Q using elementary reflections */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -my_dorgqr(int M,int N,int K,double *A,int LDA,double *TAU,double *WORK,int LWORK); - -/* solves a triangular system of equations Ax=b, A triangular */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -my_dtrtrs(char UPLO, char TRANS, char DIAG,int N,int NRHS,double *A,int LDA,double *B,int LDB); - - -/* blas ccopy */ -/* y = x */ -/* read x values spaced by Nx (so x size> N*Nx) */ -/* write to y values spaced by Ny (so y size > N*Ny) */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern void -my_ccopy(int N, complex double *x, int Nx, complex double *y, int Ny); - -/* blas scale */ -/* x = a. x */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern void -my_cscal(int N, complex double a, complex double *x); - - -/* BLAS y = a.x + y */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern void -my_caxpy(int N, complex double *x, complex double a, complex double *y); - - -/* BLAS x^H*y */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern complex double -my_cdot(int N, complex double *x, complex double *y); - -/* solve Ax=b using QR factorization */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -my_zgels(char TRANS, int M, int N, int NRHS, complex double *A, int LDA, complex double *B, int LDB, complex double *WORK, int LWORK); -extern int -my_cgels(char TRANS, int M, int N, int NRHS, complex float *A, int LDA, complex float *B, int LDB, complex float *WORK, int LWORK); - -/* BLAS ZGEMM C = alpha*op(A)*op(B)+ beta*C */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern void -my_zgemm(char transa, char transb, int M, int N, int K, complex double alpha, complex double *A, int lda, complex double *B, int ldb, complex double beta, complex double *C, int ldc); - -/* ||x||_2 */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern double -my_cnrm2(int N, complex double *x); - - -/* blas fcopy */ -/* y = x */ -/* read x values spaced by Nx (so x size> N*Nx) */ -/* write to y values spaced by Ny (so y size > N*Ny) */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern void -my_fcopy(int N, float *x, int Nx, float *y, int Ny); - - -/* LAPACK eigen value expert routine, real symmetric matrix */ -extern int -my_dsyevx(char jobz, char range, char uplo, int N, double *A, int lda, - double vl, double vu, int il, int iu, double abstol, int M, double *W, - double *Z, int ldz, double *WORK, int lwork, int *iwork, int *ifail); - -/* BLAS vector outer product - A= alpha x x^H + A -*/ -extern void -my_zher(char uplo, int N, double alpha, complex double *x, int incx, complex double *A, int lda); - -/****************************** barrier.c ****************************/ -typedef struct t_barrier_ { - int tcount; /* current no. of threads inside barrier */ - int nthreads; /* the no. of threads the barrier works - with. This is a constant */ - pthread_mutex_t enter_mutex; - pthread_mutex_t exit_mutex; - pthread_cond_t lastthread_cond; - pthread_cond_t exit_cond; -} th_barrier; - - -/* initialize barrier */ -/* N - no. of accomodated threads */ -extern void -init_th_barrier(th_barrier *barrier, int N); - -/* destroy barrier */ -extern void -destroy_th_barrier(th_barrier *barrier); - -/* the main operation of the barrier */ -extern void -sync_barrier(th_barrier *barrier); - -/********* solver modes *********/ -#define SM_LM_LBFGS 1 -#define SM_OSLM_LBFGS 0 -#define SM_OSLM_OSRLM_RLBFGS 3 -#define SM_RLM_RLBFGS 2 -#define SM_RTR_OSLM_LBFGS 4 -#define SM_RTR_OSRLM_RLBFGS 5 -#define SM_NSD_RLBFGS 6 -/* fit visibilities - u,v,w: u,v,w coordinates (wavelengths) size Nbase*tilesz x 1 - u,v,w are ordered with baselines, timeslots - x: data to write size Nbase*8*tileze x 1 - ordered by XX(re,im),XY(re,im),YX(re,im), YY(re,im), baseline, timeslots - N: no of stations - Nbase: no of baselines - tilesz: tile size - barr: baseline to station map, size Nbase*tilesz x 1 - carr: sky model/cluster info size Mx1 of clusters - coh: coherencies size Nbase*tilesz*4*M x 1 - M: no of clusters - Mt: actual no of cluster/parameters (for hybrid solutions) Mt>=M - freq0: frequency - fdelta: bandwidth for freq smearing - pp: parameter array 8*N*M x1 double values (re,img) for each station/direction - uvmin: baseline length sqrt(u^2+v^2) below which not to include in solution - Nt: no. of threads - max_emiter: EM iterations - max_iter: iterations within a single EM - max_lbfgs: LBFGS iterations (if>0 outside minimization will be LBFGS) - lbfgs_m: memory size for LBFGS - gpu_threads: GPU threads per block (LBFGS) - linsolv: (GPU/CPU versions) 0: Cholesky, 1: QR, 2: SVD - solver_mode: 0: OS-LM, 1: LM , 2: OS-Robust LM, 3: Robust LM, 4: OS-LM + RTR, 5: OS-LM, RTR, OS-Robust LM - nulow,nuhigh: robust nu search range - randomize: if >0, randomize cluster selection in SAGE and OS subset selection - - mean_nu: output mean value of nu - res_0,res_1: initial and final residuals (output) - return val=0 if final residual< initial residual - return val=-1 if final residual>initial residual -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -sagefit_visibilities(double *u, double *v, double *w, double *x, int N, - int Nbase, int tilesz, baseline_t *barr, clus_source_t *carr, complex double *coh, int M, int Mt, double freq0, double fdelta, double *pp, double uvmin, int Nt,int max_emiter, int max_iter, int max_lbfgs, int lbfgs_m, int gpu_threads, int linsolv, int solver_mode, double nulow, double nuhigh, int randomize, double *mean_nu, double *res_0, double *res_1); - -/* same as above, but uses 2 GPUS in the LM stage */ -extern int -sagefit_visibilities_dual(double *u, double *v, double *w, double *x, int N, - int Nbase, int tilesz, baseline_t *barr, clus_source_t *carr, complex double *coh, int M, int Mt, double freq0, double fdelta, double *pp, double uvmin, int Nt,int max_emiter, int max_iter, int max_lbfgs, int lbfgs_m, int gpu_threads, int linsolv, double nulow, double nuhigh, int randomize, double *mean_nu, double *res_0, double *res_1); - - - -#ifdef USE_MIC -/* wrapper function with bitwise copyable carr[] for MIC */ -/* nchunks: Mx1 array of chunk sizes for each cluster */ -/* pindex: Mt x 1 array of index of solutions for each cluster in pp */ -__attribute__ ((target(MIC))) -extern int -sagefit_visibilities_mic(double *u, double *v, double *w, double *x, int N, - int Nbase, int tilesz, baseline_t *barr, int *nchunks, int *pindex, complex double *coh, int M, int Mt, double freq0, double fdelta, double *pp, double uvmin, int Nt, int max_emiter, int max_iter, int max_lbfgs, int lbfgs_m, int gpu_threads, int linsolv,int solver_mode,double nulow, double nuhigh,int randomize, double *mean_nu, double *res_0, double *res_1); - -__attribute__ ((target(MIC))) -extern int -bfgsfit_visibilities_mic(double *u, double *v, double *w, double *x, int N, - int Nbase, int tilesz, baseline_t *barr, int *nchunks, int *pindex, complex double *coh, int M, int Mt, double freq0, double fdelta, double *pp, double uvmin, int Nt, int max_lbfgs, int lbfgs_m, int gpu_threads, int solver_mode,double nu_mean, double *res_0, double *res_1); -#endif - - -/* BFGS only fit for multi channel data, interface same as sagefit_visibilities_xxx - NO EM iterations are taken */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -bfgsfit_visibilities(double *u, double *v, double *w, double *x, int N, - int Nbase, int tilesz, baseline_t *barr, clus_source_t *carr, complex double *coh, int M, int Mt, double freq0, double fdelta, double *pp, double uvmin, int Nt, int max_lbfgs, int lbfgs_m, int gpu_threads, int solver_mode, double mean_nu, double *res_0, double *res_1); - - -extern int -bfgsfit_visibilities_gpu(double *u, double *v, double *w, double *x, int N, - int Nbase, int tilesz, baseline_t *barr, clus_source_t *carr, complex double *coh, int M, int Mt, double freq0, double fdelta, double *pp, double uvmin, int Nt, int max_lbfgs, int lbfgs_m, int gpu_threads, int solver_mode, double mean_nu, double *res_0, double *res_1); - - - -/* struct to keep histoty of last used GPU */ -typedef struct taskhist_{ - int prev; /* last used GPU (by any thread) */ - pthread_mutex_t prev_mutex; /* mutex to lock changing prev value */ - unsigned int rseed; /* random seed used in rand_r() */ -} taskhist; - -/* structs for thread pool (reusable), using a barrier */ -/* slave thread data struct */ -typedef struct slave_tdata_ { - struct pipeline_ *pline; /* forward declaration */ - int tid; /* 0,1 for 2 GPUs */ -} slave_tdata; - -/* pipeline struct */ -typedef struct pipeline_ { - void *data; /* all data needed by two threads */ - int terminate; /* 1: terminate, default 0*/ - pthread_t slave0; - pthread_t slave1; - slave_tdata *sd0; /* note recursive types */ - slave_tdata *sd1; - th_barrier gate1; - th_barrier gate2; - pthread_attr_t attr; - taskhist *thst; -} th_pipeline; - -/* pipeline state values */ -#ifndef PT_DO_NOTHING -#define PT_DO_NOTHING 0 -#endif -#ifndef PT_DO_AGPU -#define PT_DO_AGPU 1 /* allocate GPU memory, attach GPU */ -#endif -#ifndef PT_DO_DGPU -#define PT_DO_DGPU 2 /* free GPU memory, detach GPU */ -#endif -#ifndef PT_DO_WORK_LM /* plain LM */ -#define PT_DO_WORK_LM 3 -#endif -#ifndef PT_DO_WORK_OSLM /* OS accel LM */ -#define PT_DO_WORK_OSLM 4 -#endif -#ifndef PT_DO_WORK_RLM /* robust LM */ -#define PT_DO_WORK_RLM 5 -#endif -#ifndef PT_DO_WORK_OSRLM /* robust LM, OS accel */ -#define PT_DO_WORK_OSRLM 6 -#endif -#ifndef PT_DO_WORK_RTR /* RTR */ -#define PT_DO_WORK_RTR 7 -#endif -#ifndef PT_DO_WORK_RRTR /* Robust RTR */ -#define PT_DO_WORK_RRTR 8 -#endif -#ifndef PT_DO_WORK_NSD /* Nesterov's SD */ -#define PT_DO_WORK_NSD 9 -#endif -#ifndef PT_DO_MEMRESET -#define PT_DO_MEMRESET 99 -#endif -/* for BFGS pipeline */ -#ifndef PT_DO_CDERIV -#define PT_DO_CDERIV 20 -#endif -#ifndef PT_DO_CCOST -#define PT_DO_CCOST 21 -#endif - - - -#ifdef HAVE_CUDA -/* data struct shared by all threads */ -typedef struct gb_data_ { - int status[2]; /* 0: do nothing, - 1: allocate GPU memory, attach GPU - 2: free GPU memory, detach GPU - 3,4..: do work on GPU - 99: reset GPU memory (memest all memory) */ - double *p[2]; /* pointer to parameters being solved by each thread */ - double *x[2]; /* pointer to data being fit by each thread */ - int M[2]; - int N[2]; - int itermax[2]; - double *opts[2]; - double *info[2]; - int linsolv; - me_data_t *lmdata[2]; /* two for each thread */ - - /* GPU related info */ - cublasHandle_t cbhandle[2]; /* CUBLAS handles */ - cusolverDnHandle_t solver_handle[2]; /* solver handle */ - double *gWORK[2]; /* GPU buffers */ - int64_t data_size; /* size of buffer (bytes) */ - - double nulow,nuhigh; /* used only in robust version */ - int randomize; /* >0 for randomization */ -} gbdata; - -/* same as above, but using floats */ -typedef struct gb_data_fl_ { - int status[2]; /* 0: do nothing, - 1: allocate GPU memory, attach GPU - 3: free GPU memory, detach GPU - 3,4..: do work on GPU - 99: reset GPU memory (memest all memory) */ - float *p[2]; /* pointer to parameters being solved by each thread */ - float *x[2]; /* pointer to data being fit by each thread */ - int M[2]; - int N[2]; - int itermax[2]; - double *opts[2]; - double *info[2]; - int linsolv; - me_data_t *lmdata[2]; /* two for each thread */ - - /* GPU related info */ - cublasHandle_t cbhandle[2]; /* CUBLAS handles */ - cusolverDnHandle_t solver_handle[2]; /* solver handle */ - float *gWORK[2]; /* GPU buffers */ - int64_t data_size; /* size of buffer (bytes) */ - - double nulow,nuhigh; /* used only in robust version */ - int randomize; /* >0 for randomization */ -} gbdatafl; - -/* for ADMM solver */ -typedef struct gb_data_admm_fl_ { - int status[2]; /* 0: do nothing, - 1: allocate GPU memory, attach GPU - 3: free GPU memory, detach GPU - 3,4..: do work on GPU - 99: reset GPU memory (memest all memory) */ - float *p[2]; /* pointer to parameters being solved by each thread */ - float *Y[2]; /* pointer to Lagrange multiplier */ - float *Z[2]; /* pointer to consensus term */ - float admm_rho[2]; - float *x[2]; /* pointer to data being fit by each thread */ - int M[2]; - int N[2]; - int itermax[2]; - double *opts[2]; - double *info[2]; - int linsolv; - me_data_t *lmdata[2]; /* two for each thread */ - - /* GPU related info */ - cublasHandle_t cbhandle[2]; /* CUBLAS handles */ - cusolverDnHandle_t solver_handle[2]; /* solver handle */ - float *gWORK[2]; /* GPU buffers */ - int64_t data_size; /* size of buffer (bytes) */ - - double nulow,nuhigh; /* used only in robust version */ - int randomize; /* >0 for randomization */ -} gbdatafl_admm; - - -#endif /* !HAVE_CUDA */ - -/* with 2 GPUs */ -extern int -sagefit_visibilities_dual_pt(double *u, double *v, double *w, double *x, int N, - int Nbase, int tilesz, baseline_t *barr, clus_source_t *carr, complex double *coh, int M, int Mt, double freq0, double fdelta, double *pp, double uvmin, int Nt,int max_emiter, int max_iter, int max_lbfgs, int lbfgs_m, int gpu_threads, int linsolv, int solver_mode, double nulow, double nuhigh, int randomize, double *mean_nu, double *res_0, double *res_1); - -/* with 1 GPU and 1 CPU thread */ -extern int -sagefit_visibilities_dual_pt_one_gpu(double *u, double *v, double *w, double *x, int N, - int Nbase, int tilesz, baseline_t *barr, clus_source_t *carr, complex double *coh, int M, int Mt, double freq0, double fdelta, double *pp, double uvmin, int Nt, int max_emiter, int max_iter, int max_lbfgs, int lbfgs_m, int gpu_threads, int linsolv,int solver_mode, double nulow, double nuhigh, int randomize, double *mean_nu, double *res_0, double *res_1); - -/* with mixed precision */ -extern int -sagefit_visibilities_dual_pt_flt(double *u, double *v, double *w, double *x, int N, - int Nbase, int tilesz, baseline_t *barr, clus_source_t *carr, complex double *coh, int M, int Mt, double freq0, double fdelta, double *pp, double uvmin, int Nt, int max_emiter, int max_iter, int max_lbfgs, int lbfgs_m, int gpu_threads, int linsolv,int solver_mode, double nulow, double nuhigh, int randomize, double *mean_nu, double *res_0, double *res_1); - -/****************************** load_balance.c ****************************/ -/* select a GPU from 0,1..,max_gpu - in such a way to allow load balancing */ -/* also keep a global variableto ensure same GPU is - not assigned to one process */ -#ifdef HAVE_CUDA -extern void -init_task_hist(taskhist *th); -extern void -destroy_task_hist(taskhist *th); - -extern int -select_work_gpu(int max_gpu, taskhist *th); -#endif - -/****************************** transforms.c ****************************/ -#ifndef ASEC2RAD -#define ASEC2RAD 4.848136811095359935899141e-6 -#endif - -/* - convert xyz ITRF 2000 coords (m) to - long,lat, (rad) height (m) - References: -*/ -extern int -xyz2llh(double *x, double *y, double *z, double *longitude, double *latitude, double *height, int N); - -/* convert ra,dec to az,el - ra,dec: radians - longitude,latitude: rad,rad - time_jd: JD days - - az,el: output rad,rad - -References: Darin C. Koblick MATLAB code, based on - % Fundamentals of Astrodynamics and Applications - % D. Vallado, Second Edition - % Example 3-5. Finding Local Siderial Time (pg. 192) - % Algorithm 28: AzElToRaDec (pg. 259) -*/ -extern int -radec2azel(double ra, double dec, double longitude, double latitude, double time_jd, double *az, double *el); - -/* convert time to Greenwitch Mean Sideral Angle (deg) - time_jd : JD days - thetaGMST : GMST angle (deg) -*/ -extern int -jd2gmst(double time_jd, double *thetaGMST); - - -/* convert ra,dec to az,el - ra,dec: radians - longitude,latitude: rad,rad - thetaGMST : GMST angle (deg) - - az,el: output rad,rad - -*/ -extern int -radec2azel_gmst(double ra, double dec, double longitude, double latitude, double thetaGMST, double *az, double *el); - - diff --git a/src/lib/Solvers/Dirac.h b/src/lib/Solvers/Dirac.h deleted file mode 100644 index b7dbe57..0000000 --- a/src/lib/Solvers/Dirac.h +++ /dev/null @@ -1,1479 +0,0 @@ -/* - * - Copyright (C) 2006-2008 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - -#ifndef SAGECAL_H -#define SAGECAL_H -#ifdef __cplusplus - extern "C" { -#endif - -#include -#include - -#include -#include -#include -#include -#include -#include - -#include - -/* for gcc 4.8 and above */ -#ifndef complex -#define complex _Complex -#endif - -#ifdef HAVE_CUDA -#include -#include -#include -#endif /* HAVE_CUDA */ - -#ifndef MAX_GPU_ID -#define MAX_GPU_ID 3 /* use 0 (1 GPU), 1 (2 GPUs), ... */ -#endif -/* default value for threads per block */ -#ifndef DEFAULT_TH_PER_BK -#define DEFAULT_TH_PER_BK 64 -#endif -#ifndef DEFAULT_TH_PER_BK_2 -#define DEFAULT_TH_PER_BK_2 32 -#endif - -/* speed of light */ -#ifndef CONST_C -#define CONST_C 299792458.0 -#endif - -#ifndef MIN -#define MIN(x,y) \ - ((x)<=(y)? (x): (y)) -#endif - -#ifndef MAX -#define MAX(x,y) \ - ((x)>=(y)? (x): (y)) -#endif - -/* soure types */ -#define STYPE_POINT 0 -#define STYPE_GAUSSIAN 1 -#define STYPE_DISK 2 -#define STYPE_RING 3 -#define STYPE_SHAPELET 4 - -/* max source name length, increase it if names get longer */ -#define MAX_SNAME 2048 - -/********* constants - from levmar ******************/ -#define CLM_INIT_MU 1E-03 -#define CLM_STOP_THRESH 1E-17 -#define CLM_DIFF_DELTA 1E-06 -#define CLM_EPSILON 1E-12 -#define CLM_ONE_THIRD 0.3333333334 /* 1.0/3.0 */ -#define CLM_OPTS_SZ 5 /* max(4, 5) */ -#define CLM_INFO_SZ 10 -#define CLM_DBL_MAX 1E12 /* max double value */ - -#include - -/* given the epoch jd_tdb2, - calculate rotation matrix params needed to precess from J2000 - NOVAS (Naval Observatory Vector Astronomy Software) - PURPOSE: - Precesses equatorial rectangular coordinates from one epoch to - another. One of the two epochs must be J2000.0. The coordinates - are referred to the mean dynamical equator and equinox of the two - respective epochs. - - REFERENCES: - Explanatory Supplement To The Astronomical Almanac, pp. 103-104. - Capitaine, N. et al. (2003), Astronomy And Astrophysics 412, - pp. 567-586. - Hilton, J. L. et al. (2006), IAU WG report, Celest. Mech., 94, - pp. 351-367. - -*/ - -/* convert types */ -/* both arrays size nx1 - Nt: no of threads -*/ -extern int -double_to_float(float *farr, double *darr,int n, int Nt); -extern int -float_to_double(double *darr, float *farr,int n, int Nt); - -/* create a vector with 1's at flagged data points */ -/* - ddbase: 3*Nbase x 1 (sta1,sta2,flag) - x: 8*Nbase (set to 0's and 1's) -*/ -extern int -create_onezerovec(int Nbase, short *ddbase, float *x, int Nt); - -/* - find sum1=sum(|x|), and sum2=y^T |x| - x,y: nx1 arrays -*/ -extern int -find_sumproduct(int N, float *x, float *y, float *sum1, float *sum2, int Nt); - -/****************************** lbfgs.c ****************************/ -/****************************** lbfgs_nocuda.c ****************************/ -/* LBFGS routines */ -/* func: vector function to minimize, actual cost minimized is ||func-x||^2 - NOTE: gradient function given seperately - p: parameters m x 1 (used as initial value, output final value) - x: data n x 1 - itmax: max iterations - lbfgs_m: memory size - gpu_threads: GPU threads per block - adata: additional data -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -lbfgs_fit( - void (*func)(double *p, double *hx, int m, int n, void *adata), - double *p, double *x, int m, int n, int itmax, int lbfgs_m, int gpu_threads, void *adata); - -/****************************** robust_lbfgs_nocuda.c ****************************/ -typedef struct thread_data_logf_t_ { - double *f; - double *x; - double nu; - int start,end; - double sum; -} thread_data_logf_t; - -/* robust_nu: nu in T distribution */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -lbfgs_fit_robust( - void (*func)(double *p, double *hx, int m, int n, void *adata), - double *p, double *x, int m, int n, int itmax, int lbfgs_m, int gpu_threads, - void *adata); -#ifdef HAVE_CUDA -extern int -lbfgs_fit_robust_cuda( - void (*func)(double *p, double *hx, int m, int n, void *adata), - double *p, double *x, int m, int n, int itmax, int lbfgs_m, int gpu_threads, void *adata); -#endif - -/****************************** residual.c ****************************/ -/* residual calculation, with/without linear interpolation */ -/* - u,v,w: u,v,w coordinates (wavelengths) size Nbase*tilesz x 1 - u,v,w are ordered with baselines, timeslots - p0,p: parameter arrays 8*N*M x1 double values (re,img) for each station/direction - p0: old value, p new one, interpolate between the two - x: data to write size Nbase*8*tilesz x 1 - ordered by XX(re,im),XY(re,im),YX(re,im), YY(re,im), baseline, timeslots - input: x is actual data, output: x is the residual - N: no of stations - Nbase: no of baselines - tilesz: tile size - barr: baseline to station map, size Nbase*tilesz x 1 - carr: sky model/cluster info size Mx1 of clusters - coh: coherencies size Nbase*tilesz*4*M x 1 - M: no of clusters - freq0: frequency - fdelta: bandwidth for freq smearing - tdelta: integration time for time smearing - dec0: declination for time smearing - Nt: no. of threads - ccid: which cluster to use as correction - rho: MMSE robust parameter J+rho I inverted - - phase_only: if >0, and if there is any correction done, use only phase of diagonal elements for correction -*/ -extern int -calculate_residuals(double *u,double *v,double *w,double *p,double *x,int N,int Nbase,int tilesz,baseline_t *barr, clus_source_t *carr, int M,double freq0,double fdelta,double tdelta,double dec0, int Nt, int ccid, double rho); - -/* - residuals for multiple channels - data to write size Nbase*8*tilesz*Nchan x 1 - ordered by XX(re,im),XY(re,im),YX(re,im), YY(re,im), baseline, timeslots, channels - input: x is actual data, output: x is the residual - freqs: Nchanx1 of frequency values - fdelta: total bandwidth, so divide by Nchan to get each channel bandwith - tdelta: integration time for time smearing - dec0: declination for time smearing -*/ -extern int -calculate_residuals_multifreq(double *u,double *v,double *w,double *p,double *x,int N,int Nbase,int tilesz,baseline_t *barr, clus_source_t *carr, int M,double *freqs,int Nchan, double fdelta,double tdelta,double dec0, int Nt, int ccid, double rho, int phase_only); - -/* - calculate visibilities for multiple channels, no solutions are used - note: output column x is set to 0 if add_to_data ==0, else model is added to data -*/ -extern int -predict_visibilities_multifreq(double *u,double *v,double *w,double *x,int N,int Nbase,int tilesz,baseline_t *barr, clus_source_t *carr, int M,double *freqs,int Nchan, double fdelta,double tdelta,double dec0,int Nt,int add_to_data); - - -/* predict with solutions in p , ignore clusters flagged in ignorelist (Mx1) array - also correct final data with solutions for cluster ccid, if valid -*/ -extern int -predict_visibilities_multifreq_withsol(double *u,double *v,double *w,double *p,double *x,int *ignorelist,int N,int Nbase,int tilesz,baseline_t *barr, clus_source_t *carr, int M,double *freqs,int Nchan, double fdelta,double tdelta,double dec0,int Nt,int add_to_data, int ccid, double rho,int phase_only); -/****************************** mderiv.cu ****************************/ -/* cuda driver for kernel */ -/* ThreadsPerBlock: keep <= 128 - BlocksPerGrid: depends on the threads/baselines> Threads*Blocks approx baselines - N: no of baselines (total, including tilesz >1) - tilesz: tile size - M: no of clusters - Ns: no of stations - Nparam: no of actual parameters <=total - goff: starting point of gradient calculation 0..Nparams - x: N*8 x 1 residual - coh: N*8*M x 1 - p: M*Ns*8 x 1 - bb: 2*N x 1 - ptoclus: 2*M x 1 - grad: Nparamsx1 gradient values -*/ -extern void -cudakernel_lbfgs(int ThreadsPerBlock, int BlocksPerGrid, int N, int tilesz, int M, int Ns, int Nparam, int goff, double *x, double *coh, double *p, short *bb, int *ptoclus, double *grad); -/* x: data vector, not residual */ -extern void -cudakernel_lbfgs_r(int ThreadsPerBlock, int BlocksPerGrid, int N, int tilesz, int M, int Ns, int Nparam, int goff, double *x, double *coh, double *p, short *bb, int *ptoclus, double *grad); -extern void -cudakernel_lbfgs_r_robust(int ThreadsPerBlock, int BlocksPerGrid, int N, int tilesz, int M, int Ns, int Nparam, int goff, double *x, double *coh, double *p, short *bb, int *ptoclus, double *grad, double robust_nu); - - -/* cost function calculation, each GPU works with Nbase baselines out of Nbasetotal baselines - */ -extern double -cudakernel_lbfgs_cost(int ThreadsPerBlock, int BlocksPerGrid, int Nbase, int boff, int M, int Ns, int Nbasetotal, double *x, double *coh, double *p, short *bb, int *ptoclus); -extern double -cudakernel_lbfgs_cost_robust(int ThreadsPerBlock, int BlocksPerGrid, int Nbase, int boff, int M, int Ns, int Nbasetotal, double *x, double *coh, double *p, short *bb, int *ptoclus, double robust_nu); - - -/* divide by singular values Dpd[]/Sd[] for Sd[]> eps */ -extern void -cudakernel_diagdiv(int ThreadsPerBlock, int BlocksPerGrid, int M, double eps, double *Dpd, double *Sd); - -/* cuda driver for calculating - A<= A+mu I, adding mu to diagonal entries of A - A: size MxM - ThreadsPerBlock, BlocksPerGrid calculated to meet M -*/ -extern void -cudakernel_diagmu(int ThreadsPerBlock, int BlocksPerGrid, int M, double *A, double mu); - -/* cuda driver for calculating f() */ -/* p: params (Mx1): for all chunks, x: data (Nx1), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -extern void -cudakernel_func(int ThreadsPerBlock, int BlocksPerGrid, double *p, double *x, int M, int N, double *coh, short *bbh, int Nbase, int Mclus, int Nstations); - -/* cuda driver for calculating jacf() */ -/* p: params (Mx1): for all chunks, jac: jacobian (NxM), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -extern void -cudakernel_jacf(int ThreadsPerBlock_row, int ThreadsPerBlock_col, double *p, double *jac, int M, int N, double *coh, short *bbh, int Nbase, int Mclus, int Nstations); - - -/****************************** mderiv_fl.cu ****************************/ -/* divide by singular values Dpd[]/Sd[] for Sd[]> eps */ -extern void -cudakernel_diagdiv_fl(int ThreadsPerBlock, int BlocksPerGrid, int M, float eps, float *Dpd, float *Sd); -/* cuda driver for calculating - A<= A+mu I, adding mu to diagonal entries of A - A: size MxM - ThreadsPerBlock, BlocksPerGrid calculated to meet M -*/ -extern void -cudakernel_diagmu_fl(int ThreadsPerBlock, int BlocksPerGrid, int M, float *A, float mu); -/* cuda driver for calculating f() */ -/* p: params (Mx1), x: data (Nx1), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -extern void -cudakernel_func_fl(int ThreadsPerBlock, int BlocksPerGrid, float *p, float *x, int M, int N, float *coh, short *bbh, int Nbase, int Mclus, int Nstations); -/* cuda driver for calculating jacf() */ -/* p: params (Mx1), jac: jacobian (NxM), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -extern void -cudakernel_jacf_fl(int ThreadsPerBlock_row, int ThreadsPerBlock_col, float *p, float *jac, int M, int N, float *coh, short *bbh, int Nbase, int Mclus, int Nstations); -/****************************** robust.cu ****************************/ -/* cuda driver for calculating wt \odot f() */ -/* p: params (Mx1): for all chunks, x: data (Nx1), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -extern void -cudakernel_func_wt(int ThreadsPerBlock, int BlocksPerGrid, double *p, double *x, int M, int N, double *coh, short *bbh, double *wt, int Nbase, int Mclus, int Nstations); - -/* cuda driver for calculating wt \odot jacf() */ -/* p: params (Mx1): for all chunks, jac: jacobian (NxM), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -extern void -cudakernel_jacf_wt(int ThreadsPerBlock_row, int ThreadsPerBlock_col, double *p, double *jac, int M, int N, double *coh, short *bbh, double *wt, int Nbase, int Mclus, int Nstations); - - -/* set initial weights to 1 by a cuda kernel */ -extern void -cudakernel_setweights(int ThreadsPerBlock, int BlocksPerGrid, int N, double *wtd, double alpha); - -/* hadamard product by a cuda kernel x<= x*wt */ -extern void -cudakernel_hadamard(int ThreadsPerBlock, int BlocksPerGrid, int N, double *wt, double *x); - -/* update weights by a cuda kernel */ -extern void -cudakernel_updateweights(int ThreadsPerBlock, int BlocksPerGrid, int N, double *wt, double *x, double *q, double robust_nu); - -/* make sqrt() weights */ -extern void -cudakernel_sqrtweights(int ThreadsPerBlock, int BlocksPerGrid, int N, double *wt); - -/* evaluate expression for finding optimum nu for - a range of nu values */ -extern void -cudakernel_evaluatenu(int ThreadsPerBlock, int BlocksPerGrid, int Nd, double qsum, double *q, double deltanu,double nulow); - -/* ThreadsPerBlock: keep <= 128 - BlocksPerGrid: depends on the threads/baselines> Threads*Blocks approx baselines - N: no of baselines (total, including tilesz >1) - tilesz: tile size - M: no of clusters - Ns: no of stations - Nparam: no of actual parameters <=total - goff: starting point of gradient calculation 0..Nparams - x: N*8 x 1 residual - coh: N*8*M x 1 - p: M*Ns*8 x 1 - bb: 2*N x 1 - ptoclus: 2*M x 1 - grad: Nparamsx1 gradient values -*/ -extern void -cudakernel_lbfgs_robust(int ThreadsPerBlock, int BlocksPerGrid, int N, int tilesz, int M, int Ns, int Nparam, int goff, double robust_nu, double *x, double *coh, double *p, short *bb, int *ptoclus, double *grad); - -/****************************** robust_fl.cu ****************************/ -/* cuda driver for calculating wt \odot f() */ -/* p: params (Mx1): for all chunks, x: data (Nx1), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -extern void -cudakernel_func_wt_fl(int ThreadsPerBlock, int BlocksPerGrid, float *p, float *x, int M, int N, float *coh, short *bbh, float *wt, int Nbase, int Mclus, int Nstations); - -/* cuda driver for calculating wt \odot jacf() */ -/* p: params (Mx1): for all chunks, jac: jacobian (NxM), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -extern void -cudakernel_jacf_wt_fl(int ThreadsPerBlock_row, int ThreadsPerBlock_col, float *p, float *jac, int M, int N, float *coh, short *bbh, float *wt, int Nbase, int Mclus, int Nstations); - - -/* set initial weights to 1 by a cuda kernel */ -extern void -cudakernel_setweights_fl(int ThreadsPerBlock, int BlocksPerGrid, int N, float *wtd, float alpha); - -/* hadamard product by a cuda kernel x<= x*wt */ -extern void -cudakernel_hadamard_fl(int ThreadsPerBlock, int BlocksPerGrid, int N, float *wt, float *x); - -/* update weights by a cuda kernel */ -extern void -cudakernel_updateweights_fl(int ThreadsPerBlock, int BlocksPerGrid, int N, float *wt, float *x, float *q, float robust_nu); - -/* make sqrt() weights */ -extern void -cudakernel_sqrtweights_fl(int ThreadsPerBlock, int BlocksPerGrid, int N, float *wt); - -/* evaluate expression for finding optimum nu for - a range of nu values */ -extern void -cudakernel_evaluatenu_fl(int ThreadsPerBlock, int BlocksPerGrid, int Nd, float qsum, float *q, float deltanu,float nulow); - -/* evaluate expression for finding optimum nu for - a range of nu values , 8 variate T distrubution - using AECM */ -extern void -cudakernel_evaluatenu_fl_eight(int ThreadsPerBlock, int BlocksPerGrid, int Nd, float qsum, float *q, float deltanu,float nulow, float nu0); - -/****************************** clmfit.c ****************************/ -#ifdef HAVE_CUDA -/* LM with GPU */ -extern int -clevmar_der_single( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - * info[5]= # iterations, - * info[6]=reason for terminating: 1 - stopped by small gradient J^T e - * 2 - stopped by small Dp - * 3 - stopped by itmax - * 4 - singular matrix. Restart from current p with increased mu - * 5 - no further error reduction is possible. Restart with increased mu - * 6 - stopped by small ||e||_2 - * 7 - stopped by invalid (i.e. NaN or Inf) "func" values. This is a user error - * info[7]= # function evaluations - * info[8]= # Jacobian evaluations - * info[9]= # linear systems solved, i.e. # attempts for reducing error - */ - - int card, /* GPU to use */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - void *adata); /* pointer to possibly additional data */ - -/* function to set up a GPU, should be called only once */ -extern void -attach_gpu_to_thread(int card, cublasHandle_t *cbhandle, cusolverDnHandle_t *solver_handle); -extern void -attach_gpu_to_thread1(int card, cublasHandle_t *cbhandle, cusolverDnHandle_t *solver_handle, double **WORK, int64_t work_size); -extern void -attach_gpu_to_thread2(int card, cublasHandle_t *cbhandle, cusolverDnHandle_t *solver_handle, float **WORK, int64_t work_size, int usecula); - - -/* function to detach a GPU from a thread */ -extern void -detach_gpu_from_thread(cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle); -extern void -detach_gpu_from_thread1(cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle, double *WORK); -extern void -detach_gpu_from_thread2(cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle, float *WORK, int usecula); -/* function to set memory to zero */ -extern void -reset_gpu_memory(double *WORK, int64_t work_size); - - -/* same as above, but f() and jac() calculations are done - entirely in the GPU */ -extern int -clevmar_der_single_cuda( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - * info[5]= # iterations, - * info[6]=reason for terminating: 1 - stopped by small gradient J^T e - * 2 - stopped by small Dp - * 3 - stopped by itmax - * 4 - singular matrix. Restart from current p with increased mu - * 5 - no further error reduction is possible. Restart with increased mu - * 6 - stopped by small ||e||_2 - * 7 - stopped by invalid (i.e. NaN or Inf) "func" values. This is a user error - * info[7]= # function evaluations - * info[8]= # Jacobian evaluations - * info[9]= # linear systems solved, i.e. # attempts for reducing error - */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - double *gWORK, /* GPU allocated memory */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - void *adata); /* pointer to possibly additional data */ - -/** keep interface almost the same as in levmar **/ -extern int -mlm_der_single_cuda( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[6], /* I: minim. options [\mu, \m, \p0, \p1, \p2, \delta]. - delta: 1 or 2 - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - */ - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - double *gWORK, /* GPU allocated memory */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - - void *adata); /* pointer to possibly additional data, passed uninterpreted to func & jacf. - * Set to NULL if not needed - */ - -#endif /* HAVE_CUDA */ -/****************************** robustlm.c ****************************/ -/* robust, iteratively weighted non linear least squares using LM - entirely in the GPU */ -#ifdef HAVE_CUDA -extern int -rlevmar_der_single_cuda( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - double *gWORK, /* GPU allocated memory */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - void *adata); - -/* robust, iteratively weighted non linear least squares using LM - entirely in the GPU, using float data */ -int -rlevmar_der_single_cuda_fl( - float *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - float *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - float *gWORK, /* GPU allocated memory */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - void *adata); - -/* robust, iteratively weighted non linear least squares using LM - entirely in the GPU, using float data, OS acceleration */ -extern int -osrlevmar_der_single_cuda_fl( - float *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - float *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - float *gWORK, /* GPU allocated memory */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - int randomize, /* if >0 randomize */ - void *adata); -#endif /* HAVE_CUDA */ - -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -rlevmar_der_single_nocuda( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - */ - - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int Nt, /* no of threads */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - void *adata); - -/* robust LM, OS acceleration */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -osrlevmar_der_single_nocuda( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - * info[5]= # iterations, - * info[6]=reason for terminating: 1 - stopped by small gradient J^T e - * 2 - stopped by small Dp - * 3 - stopped by itmax - * 4 - singular matrix. Restart from current p with increased mu - * 5 - no further error reduction is possible. Restart with increased mu - * 6 - stopped by small ||e||_2 - * 7 - stopped by invalid (i.e. NaN or Inf) "func" values. This is a user error - * info[7]= # function evaluations - * info[8]= # Jacobian evaluations - * info[9]= # linear systems solved, i.e. # attempts for reducing error - */ - - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int Nt, /* no of threads */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - int randomize, /* if >0 randomize */ - void *adata); - -/****************************** updatenu.c ****************************/ -/* update nu (degrees of freedom) - - nu0: current value of nu (need for AECM update) - sumlogw = 1/N sum(log(w_i)-w_i) - use Nd values in [nulow,nuhigh] to find nu - p: 1 or 8 depending on scalar or 2x2 matrix formulation -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern double -update_nu(double sumlogw, int Nd, int Nt, double nulow, double nuhigh, int p, double nu0); - -/* update w and nu together - nu0: current value of nu - w: Nx1 weight vector - ed: Nx1 error vector - Nt: no of threads - - return new nu, w is also updated, search range [nulow,nuhigh] -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern double -update_w_and_nu(double nu0, double *w, double *ed, int N, int Nt, double nulow, double nuhigh); - -/* - taper data by weighting based on uv distance (for short baselines) - for example: use weights as the inverse density function - 1/( 1+f(u,v) ) - as u,v->inf, f(u,v) -> 0 so long baselines are not affected - x: Nbase*8 x 1 (input,output) data - u,v : Nbase x 1 - note: u = u/c, v=v/c here, so need freq to convert to wavelengths */ -extern void -whiten_data(int Nbase, double *x, double *u, double *v, double freq0, int Nt); -/****************************** clmfit_nocuda.c ****************************/ -/* LM with LAPACK */ -/** keep interface almost the same as in levmar **/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -clevmar_der_single_nocuda( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - * info[5]= # iterations, - * info[6]=reason for terminating: 1 - stopped by small gradient J^T e - * 2 - stopped by small Dp - * 3 - stopped by itmax - * 4 - singular matrix. Restart from current p with increased mu - * 5 - no further error reduction is possible. Restart with increased mu - * 6 - stopped by small ||e||_2 - * 7 - stopped by invalid (i.e. NaN or Inf) "func" values. This is a user error - * info[7]= # function evaluations - * info[8]= # Jacobian evaluations - * info[9]= # linear systems solved, i.e. # attempts for reducing error - */ - - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - void *adata); /* pointer to possibly additional data, passed uninterpreted to func & jacf. - * Set to NULL if not needed - */ - -extern int -mlm_der_single( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[6], /* I: minim. options [\mu, \m, \p0, \p1, \p2, \delta]. - delta: 1 or 2 - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - */ - - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - void *adata); /* pointer to possibly additional data, passed uninterpreted to func & jacf. - * Set to NULL if not needed - */ - -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -oslevmar_der_single_nocuda( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - */ - - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int randomize, /* if >0 randomize */ - void *adata); -/****************************** oslmfit.c ****************************/ -#ifdef HAVE_CUDA -/* OS-LM, but f() and jac() calculations are done - entirely in the GPU */ -extern int -oslevmar_der_single_cuda( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - * info[5]= # iterations, - * info[6]=reason for terminating: 1 - stopped by small gradient J^T e - * 2 - stopped by small Dp - * 3 - stopped by itmax - * 4 - singular matrix. Restart from current p with increased mu - * 5 - no further error reduction is possible. Restart with increased mu - * 6 - stopped by small ||e||_2 - * 7 - stopped by invalid (i.e. NaN or Inf) "func" values. This is a user error - * info[7]= # function evaluations - * info[8]= # Jacobian evaluations - * info[9]= # linear systems solved, i.e. # attempts for reducing error - */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - double *gWORK, /* GPU allocated memory */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - int randomize, /* if >0 randomize */ - void *adata); /* pointer to possibly additional data, passed uninterpreted to func & jacf. - * Set to NULL if not needed - */ - -#endif /* !HAVE_CUDA */ - -/****************************** clmfit_fl.c ****************************/ -#ifdef HAVE_CUDA -extern int -clevmar_der_single_cuda_fl( - float *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - float *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - * info[5]= # iterations, - * info[6]=reason for terminating: 1 - stopped by small gradient J^T e - * 2 - stopped by small Dp - * 3 - stopped by itmax - * 4 - singular matrix. Restart from current p with increased mu - * 5 - no further error reduction is possible. Restart with increased mu - * 6 - stopped by small ||e||_2 - * 7 - stopped by invalid (i.e. NaN or Inf) "func" values. This is a user error - * info[7]= # function evaluations - * info[8]= # Jacobian evaluations - * info[9]= # linear systems solved, i.e. # attempts for reducing error - */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - float *gWORK, /* GPU allocated memory */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - void *adata); /* pointer to possibly additional data */ - -extern int -oslevmar_der_single_cuda_fl( - float *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - float *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - * info[5]= # iterations, - * info[6]=reason for terminating: 1 - stopped by small gradient J^T e - * 2 - stopped by small Dp - * 3 - stopped by itmax - * 4 - singular matrix. Restart from current p with increased mu - * 5 - no further error reduction is possible. Restart with increased mu - * 6 - stopped by small ||e||_2 - * 7 - stopped by invalid (i.e. NaN or Inf) "func" values. This is a user error - * info[7]= # function evaluations - * info[8]= # Jacobian evaluations - * info[9]= # linear systems solved, i.e. # attempts for reducing error - */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - float *gWORK, /* GPU allocated memory */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - int randomize, /* if >0 randomize */ - void *adata); /* pointer to possibly additional data, passed uninterpreted to func & jacf. - * Set to NULL if not needed - */ - -#endif /* !HAVE_CUDA */ -/****************************** rtr_solve.c ****************************/ -/* structure for worker threads for function calculation */ -typedef struct thread_data_rtr_ { - int Nb; /* no of baselines this handle */ - int boff; /* baseline offset per thread */ - baseline_t *barr; /* pointer to baseline-> stations mapping array */ - clus_source_t *carr; /* sky model, with clusters Mx1 */ - int M; /* no of clusters */ - double *y; /* data vector Nbx8 array re,im,re,im .... */ - complex double *coh; /* output vector in complex form, (not used always) size 4*M*Nb */ - /* following are only used while predict with gain */ - complex double *x; /* parameter array, */ - /* general format of element in manifold x - x: size 4N x 1 vector - x[0:2N-1] : first column, x[2N:4N-1] : second column - x=[J_1(1,1) J_1(1,2); - J_1(2,1) J_1(2,2); - ... .... - J_N(1,1) J_N(1,2); - J_N(2,1) J_N(2,2)]; - */ - int N; /* no of stations */ - int clus; /* which cluster to process, 0,1,...,M-1 if -1 all clusters */ - - /* output of cost function */ - double fcost; - /* gradient */ - complex double *grad; - /* Hessian */ - complex double *hess; - /* Eta (used in Hessian) */ - complex double *eta; - - /* normalization factors for grad,hess calculation */ - /* size Nx1 */ - int *bcount; - double *iw; /* 1/bcount */ - - /* for robust solver */ - double *wtd; /* weights for baseline */ - double nu0; - - /* mutexs: N x 1, one for each station */ - pthread_mutex_t *mx_array; -} thread_data_rtr_t; - -/* structure for common data */ -typedef struct global_data_rtr_ { - me_data_t *medata; /* passed from caller */ - - /* normalization factors for grad,hess calculation */ - /* size Nx1 */ - double *iw; /* 1/bcount */ - - /* for robust solver */ - double *wtd; /* weights for baseline */ - double nulow,nuhigh; - - /* for ADMM cost */ - complex double *Y; /* size 2Nx2 */ - complex double *BZ; /* size 2Nx2 */ - double admm_rho; - - /* thread stuff Nt x 1 threads */ - pthread_t *th_array; - /* mutexs: N x 1, one for each station */ - pthread_mutex_t *mx_array; - pthread_attr_t attr; -} global_data_rtr_t; - - -/* RTR (ICASSP 2013) */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -rtr_solve_nocuda( - double *x, /* initial values and updated solution at output (size 8*N double) */ - double *y, /* data vector (size 8*M double) */ - int N, /* no. of stations */ - int M, /* no. of constraints */ - int itmax_sd, /* maximum number of iterations RSD */ - int itmax_rtr, /* maximum number of iterations RTR */ - double Delta_bar, double Delta0, /* Trust region radius and initial value */ - double *info, /* initial and final residuals */ - me_data_t *adata); /* pointer to additional data - */ - -/****************************** rtr_solve_robust.c ****************************/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -rtr_solve_nocuda_robust( - double *x0, /* initial values and updated solution at output (size 8*N double) */ - double *y, /* data vector (size 8*M double) */ - int N, /* no. of stations */ - int M, /* no. of constraints */ - int itmax_rsd, /* maximum number of iterations RSD */ - int itmax_rtr, /* maximum number of iterations RTR */ - double Delta_bar, double Delta0, /* Trust region radius and initial value */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - double *info, /* initial and final residuals */ - me_data_t *adata); - -/* Nesterov's SD */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -nsd_solve_nocuda_robust( - double *x, /* initial values and updated solution at output (size 8*N double) */ - double *y, /* data vector (size 8*M double) */ - int N, /* no. of stations */ - int M, /* no. of constraints */ - int itmax, /* maximum number of iterations */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - double *info, /* initial and final residuals */ - me_data_t *adata); /* pointer to additional data - */ - -/****************************** rtr_solve_robust_admm.c ****************************/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -rtr_solve_nocuda_robust_admm( - double *x0, /* initial values and updated solution at output (size 8*N double) */ - double *Y, /* Lagrange multiplier (size 8*N double) */ - double *BZ, /* consensus B Z (size 8*N double) */ - double *y, /* data vector (size 8*M double) */ - int N, /* no. of stations */ - int M, /* no. of constraints */ - int itmax_rsd, /* maximum number of iterations RSD */ - int itmax_rtr, /* maximum number of iterations RTR */ - double Delta_bar, double Delta0, /* Trust region radius and initial value */ - double admm_rho, /* ADMM regularization value */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - double *info, /* initial and final residuals */ - me_data_t *adata); -#ifdef HAVE_CUDA -/****************************** manifold_fl.cu ****************************/ -extern float -cudakernel_fns_f(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, float *y, float *coh, short *bbh); -extern void -cudakernel_fns_fgradflat(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, float *y, float *coh, short *bbh); -extern void -cudakernel_fns_fhessflat(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, cuFloatComplex *fhess, float *y, float *coh, short *bbh); -extern void -cudakernel_fns_fscale(int N, cuFloatComplex *eta, float *iw); -extern float -cudakernel_fns_f_robust(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, float *y, float *coh, short *bbh, float *wtd); -extern void -cudakernel_fns_fgradflat_robust(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, float *y, float *coh, short *bbh, float *wtd, cuFloatComplex *Ai, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle); -extern void -cudakernel_fns_fgradflat_robust1(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, float *y, float *coh, short *bbh, float *wtd); -extern void -cudakernel_fns_fgradflat_robust_admm(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, float *y, float *coh, short *bbh, float *wtd, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle); -extern void -cudakernel_fns_fhessflat_robust1(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, cuFloatComplex *fhess, float *y, float *coh, short *bbh, float *wtd); -extern void -cudakernel_fns_fhessflat_robust(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, cuFloatComplex *fhess, float *y, float *coh, short *bbh, float *wtd, cuFloatComplex *Ai, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle); -extern void -cudakernel_fns_fhessflat_robust_admm(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, cuFloatComplex *fhess, float *y, float *coh, short *bbh, float *wtd, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle); -extern void -cudakernel_fns_fupdate_weights(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, float *y, float *coh, short *bbh, float *wtd, float nu0); -extern void -cudakernel_fns_fupdate_weights_q(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, float *y, float *coh, short *bbh, float *wtd, float *qd, float nu0); -/****************************** rtr_solve_cuda.c ****************************/ -extern int -rtr_solve_cuda_fl( - float *x, /* initial values and updated solution at output (size 8*N float) */ - float *y, /* data vector (size 8*M float) */ - int N, /* no of stations */ - int M, /* no of constraints */ - int itmax_sd, /* maximum number of iterations RSD */ - int itmax_rtr, /* maximum number of iterations RTR */ - float Delta_bar, float Delta0, /* Trust region radius and initial value */ - double *info, /* initial and final residuals */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - me_data_t *adata); /* pointer to possibly additional data */ - - -extern void -cudakernel_fns_R(int N, cuFloatComplex *x, cuFloatComplex *r, cuFloatComplex *rnew, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle); -extern float -cudakernel_fns_g(int N,cuFloatComplex *x,cuFloatComplex *eta, cuFloatComplex *gamma,cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle); -extern void -cudakernel_fns_proj(int N, cuFloatComplex *x, cuFloatComplex *z, cuFloatComplex *rnew, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle); -/****************************** rtr_solve_robust_cuda.c ****************************/ -extern int -rtr_solve_cuda_robust_fl( - float *x0, /* initial values and updated solution at output (size 8*N float) */ - float *y, /* data vector (size 8*M float) */ - int N, /* no of stations */ - int M, /* no of constraints */ - int itmax_sd, /* maximum number of iterations RSD */ - int itmax_rtr, /* maximum number of iterations RTR */ - float Delta_bar, float Delta0, /* Trust region radius and initial value */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - double *info, /* initial and final residuals */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - me_data_t *adata); - - -/* Nesterov's steepest descent */ -extern int -nsd_solve_cuda_robust_fl( - float *x0, /* initial values and updated solution at output (size 8*N float) */ - float *y, /* data vector (size 8*M float) */ - int N, /* no of stations */ - int M, /* no of constraints */ - int itmax, /* maximum number of iterations */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - double *info, /* initial and final residuals */ - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - me_data_t *adata); - -/****************************** rtr_solve_robust_cuda_admm.c ****************************/ -/* ADMM solver */ -extern int -rtr_solve_cuda_robust_admm_fl( - float *x0, /* initial values and updated solution at output (size 8*N float) */ - float *Y, /* Lagrange multiplier size 8N */ - float *Z, /* consensus term B Z size 8N */ - float *y, /* data vector (size 8*M float) */ - int N, /* no of stations */ - int M, /* no of constraints */ - int itmax_rtr, /* maximum number of iterations */ - float Delta_bar, float Delta0, /* Trust region radius and initial value */ - float admm_rho, /* ADMM regularization */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - double *info, /* initial and final residuals */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - me_data_t *adata); - - -/* Nesterov's SD */ -extern int -nsd_solve_cuda_robust_admm_fl( - float *x0, /* initial values and updated solution at output (size 8*N float) */ - float *Y, /* Lagrange multiplier size 8N */ - float *Z, /* consensus term B Z size 8N */ - float *y, /* data vector (size 8*M float) */ - int N, /* no of stations */ - int M, /* no of constraints */ - int itmax, /* maximum number of iterations */ - float admm_rho, /* ADMM regularization */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - double *info, /* initial and final residuals */ - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - me_data_t *adata); -#endif /* HAVE_CUDA */ -/****************************** lmfit.c ****************************/ -/****************************** lmfit_nocuda.c ****************************/ -/* struct for calling parallel LM jobs */ -typedef struct thread_clm_data_t { - double *p; /* parameters */ - double *x; /* data */ - int M; - int N; - int itermax; - double *opts; - double *info; - int card; - int linsolv; - me_data_t *lmdata; -} thread_clm_data; - - -/* generate a random permutation of given integers */ -/* note: free returned value after use */ -/* n: no of entries, - weighter_iter: if 1, take weight into account - if 0, only generate a random permutation - w: weights (size nx1): sort them in descending order and - give permutation accordingly -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int* -random_permutation(int n, int weighted_iter, double *w); - -/****************************** diagnostics.c ****************************/ -#ifdef HAVE_CUDA -/* Calculate St.Laurent-Cook Jacobian leverage -x: input: residual, output: levarage - flags: 2 for flags based on uvcut, 1 for normal flags - coh: coherencies are calculated for all baselines, regardless of flag - diagmode: 1: replaces residual with Jacobian Leverage, 2: calculates (prints) fraction of leverage/noise - */ -extern int -calculate_diagnostics(double *u,double *v,double *w,double *p,double *x,int N,int Nbase,int tilesz,baseline_t *barr, clus_source_t *carr, complex double *coh, int M,int Mt,int diagmode,int Nt); -#endif - - -/****************************** diag_fl.cu ****************************/ -#ifdef HAVE_CUDA -/* cuda driver for calculating Jacobian for leverage */ -/* p: params (Mx1), jac: jacobian (NxM), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -/* flags are always ignored */ -extern void -cudakernel_jacf_fl2(float *p, float *jac, int M, int N, float *coh, short *bbh, int Nbase, int Mclus, int Nstations); - -/* invert sqrt(singular values) Sd[]=1/sqrt(Sd[]) for Sd[]> eps */ -extern void -cudakernel_sqrtdiv_fl(int ThreadsPerBlock, int BlocksPerGrid, int M, float eps, float *Sd); - -/* U <= U D, - U : MxM - D : Mx1, diagonal matrix -*/ -extern void -cudakernel_diagmult_fl(int ThreadsPerBlock, int BlocksPerGrid, int M, float * U, float *D); - -/* diag(J^T J) - d[i] = J[i,:] * J[i,:] - J: NxM (in row major order, so J[i,:] is actually J[:,i] - d: Nx1 -*/ -extern void -cudakernel_jnorm_fl(int ThreadsPerBlock, int BlocksPerGrid, float *J, int N, int M, float *d); -#endif - - -/****************************** manifold_average.c ****************************/ -/* calculate manifold average of 2Nx2 solution blocks, - then project each solution to this average - Y: 2Nx2 x M x Nf values (average calculated for each 2Nx2 x Nf blocks) - N: no of stations - M: no of directions - Nf: no of frequencies - Niter: everaging iterations - Nt: threads -*/ -extern int -calculate_manifold_average(int N,int M,int Nf,double *Y,int Niter,int Nt); - - -/* find U to minimize - ||J-J1 U|| solving Procrustes problem - J,J1 : 8N x 1 vectors, in standard format - will be reshaped to 2Nx2 format and J1 will be modified as J1 U -*/ -extern int -project_procrustes(int N,double *J,double *J1); - -/* same as above, but J,J1 are in right 2Nx2 matrix format */ -/* J1 is modified */ -extern int -project_procrustes_block(int N,complex double *J,complex double *J1); - - -/* Extract only the phase of diagonal entries from solutions - p: 8Nx1 solutions, orders as [(real,imag)vec(J1),(real,imag)vec(J2),...] - pout: 8Nx1 phases (exp(j*phase)) of solutions, after joint diagonalization of p - N: no. of 2x2 Jones matrices in p, having common unitary ambiguity - niter: no of iterations for Jacobi rotation */ -extern int -extract_phases(double *p, double *pout, int N, int niter); -/****************************** consensus_poly.c ****************************/ -/* build matrix with polynomial terms - B : Npoly x Nf, each row is one basis function - Npoly : total basis functions - Nf: frequencies - freqs: Nfx1 array freqs - freq0: reference freq - type : 0 for [1 ((f-fo)/fo) ((f-fo)/fo)^2 ...] basis functions - 1 : same as type 0, normalize each row such that norm is 1 - 2 : Bernstein poly \sum N_C_r x^r (1-x)^r where x in [0,1] : use min,max values of freq to normalize -*/ -extern int -setup_polynomials(double *B, int Npoly, int Nf, double *freqs, double freq0, int type); - -/* build matrix with polynomial terms - B : Npoly x Nf, each row is one basis function - Bi: Npoly x Npoly pseudo inverse of sum( B(:,col) x B(:,col)' ) - Npoly : total basis functions - Nf: frequencies - fratio: Nfx1 array of weighing factors depending on the flagged data of each freq - Sum taken is a weighted sum, using weights in fratio -*/ -extern int -find_prod_inverse(double *B, double *Bi, int Npoly, int Nf, double *fratio); - -/* update Z - Z: 8NxNpoly x M double array (real and complex need to be updated separate) - N : stations - M : clusters - Npoly: no of basis functions - z : right hand side 8NMxNpoly (note the different ordering from Z) - Bi : NpolyxNpoly matrix, Bi^T=Bi assumed -*/ -extern int -update_global_z(double *Z,int N,int M,int Npoly,double *z,double *Bi); - - -/****************************** admm_solve.c ****************************/ -/* ADMM cost function = normal_cost + ||Y^H(J-BZ)|| + rho/2 ||J-BZ||^2 */ -/* extra params - Y : Lagrange multiplier - BZ : consensus term - Y,BZ : size same as pp : 8*N*Mt x1 double values (re,img) for each station/direction - admm_rho : regularization factor array size Mx1 -*/ -extern int -sagefit_visibilities_admm(double *u, double *v, double *w, double *x, int N, - int Nbase, int tilesz, baseline_t *barr, clus_source_t *carr, complex double *coh, int M, int Mt, double freq0, double fdelta, double *pp, double *Y, double *BZ, double uvmin, int Nt, int max_emiter, int max_iter, int max_lbfgs, int lbfgs_m, int gpu_threads, int linsolv,int solver_mode,double nulow, double nuhigh,int randomize, double *admm_rho, double *mean_nu, double *res_0, double *res_1); - -/* ADMM cost function = normal_cost + ||Y^H(J-BZ)|| + rho/2 ||J-BZ||^2 */ -/* extra params - Y : Lagrange multiplier - BZ : consensus term - Y,BZ : size same as pp : 8*N*Mt x1 double values (re,img) for each station/direction - admm_rho : regularization factor array size Mx1 -*/ -#ifdef HAVE_CUDA -extern int -sagefit_visibilities_admm_dual_pt_flt(double *u, double *v, double *w, double *x, int N, - int Nbase, int tilesz, baseline_t *barr, clus_source_t *carr, complex double *coh, int M, int Mt, double freq0, double fdelta, double *pp, double *Y, double *BZ, double uvmin, int Nt, int max_emiter, int max_iter, int max_lbfgs, int lbfgs_m, int gpu_threads, int linsolv,int solver_mode, double nulow, double nuhigh, int randomize, double *admm_rho, double *mean_nu, double *res_0, double *res_1); -#endif - -/****************************** OpenBLAS ************************************/ -/* prototype declaration */ -extern void -openblas_set_num_threads(int num_threads); - -extern int -get_precession_params(double jd_tdb2, double Tr[9]); -/* precess ra0,dec0 at J2000 - to ra,dec at epoch given by transform Tr - using NOVAS library */ -extern int -precession(double ra0, double dec0, double Tr[9], double *ra, double *dec); - -/****************************** stationbeam.c ****************************/ -/* - ra,dec: source direction (rad) - ra0,dec0: beam center (rad) - f: frequency (Hz) - f0: beam forming frequency (Hz) - - longitude,latitude : Nx1 array of station positions (rad,rad) - time_jd: JD (day) time - Nelem : Nx1 array, no. of elements used in each station - x,y,z: Nx1 pointer arrays to station positions, each station has Nelem[]x1 arrays - - beamgain: Nx1 array of station beam gain along the source direction -*/ -extern int -arraybeam(double ra, double dec, double ra0, double dec0, double f, double f0, int N, double *longitude, double *latitude, double time_jd, int *Nelem, double **x, double **y, double **z, double *beamgain); - - -/****************************** predict_withbeam.c ****************************/ -/* precalculate cluster coherencies - u,v,w: u,v,w coordinates (wavelengths) size Nbase*tilesz x 1 - u,v,w are ordered with baselines, timeslots - x: coherencies size Nbase*4*Mx 1 - ordered by XX(re,im),XY(re,im),YX(re,im), YY(re,im), baseline, timeslots - N: no of stations - Nbase: total no of baselines (including more than one tile or timeslot) - barr: baseline to station map, size Nbase*tilesz x 1 - carr: sky model/cluster info size Mx1 of clusters - M: no of clusters - freq0: frequency - fdelta: bandwidth for freq smearing - tdelta: integration time for time smearing - dec0: declination for time smearing - uvmin: baseline length sqrt(u^2+v^2) below which not to include in solution - uvmax: baseline length higher than this not included in solution - - Station beam specific parameters - ph_ra0,ph_dec0: beam pointing rad,rad - ph_freq0: beam reference freq - longitude,latitude: Nx1 arrays (rad,rad) station locations - time_utc: JD (day) : tilesz x 1 - tilesz: how many tiles: == unique time_utc - Nelem: Nx1 array, size of stations (elements) - xx,yy,zz: Nx1 arrays of station element locations arrays xx[],yy[],zz[] - Nt: no of threads - - NOTE: prediction is done for all baselines, even flagged ones - and flags are set to 2 for baselines lower than uvcut -*/ - -extern int -precalculate_coherencies_withbeam(double *u, double *v, double *w, complex double *x, int N, - int Nbase, baseline_t *barr, clus_source_t *carr, int M, double freq0, double fdelta, double tdelta, double dec0, double uvmin, double uvmax, - double ph_ra0, double ph_dec0, double ph_freq0, double *longitude, double *latitude, double *time_utc, int tileze, int *Nelem, double **xx, double **yy, double **zz, int Nt); - - -extern int -predict_visibilities_multifreq_withbeam(double *u,double *v,double *w,double *x,int N,int Nbase,int tilesz,baseline_t *barr, clus_source_t *carr, int M,double *freqs,int Nchan, double fdelta,double tdelta, double dec0, - double ph_ra0, double ph_dec0, double ph_freq0, double *longitude, double *latitude, double *time_utc,int *Nelem, double **xx, double **yy, double **zz, int Nt, int add_to_data); - -extern int -calculate_residuals_multifreq_withbeam(double *u,double *v,double *w,double *p,double *x,int N,int Nbase,int tilesz,baseline_t *barr, clus_source_t *carr, int M,double *freqs,int Nchan, double fdelta,double tdelta,double dec0, -double ph_ra0, double ph_dec0, double ph_freq0, double *longitude, double *latitude, double *time_utc,int *Nelem, double **xx, double **yy, double **zz, int Nt, int ccid, double rho, int phase_only); - - -/* change epoch of soure ra,dec from J2000 to JAPP */ -/* also the beam pointing ra_beam,dec_beam */ -extern int -precess_source_locations(double jd_tdb, clus_source_t *carr, int M, double *ra_beam, double *dec_beam, int Nt); - -/****************************** predict_withbeam_gpu.c ****************************/ -/* if dobeam==0, beam calculation is off */ -extern int -precalculate_coherencies_withbeam_gpu(double *u, double *v, double *w, complex double *x, int N, - int Nbase, baseline_t *barr, clus_source_t *carr, int M, double freq0, double fdelta, double tdelta, double dec0, double uvmin, double uvmax, - double ph_ra0, double ph_dec0, double ph_freq0, double *longitude, double *latitude, double *time_utc, int tileze, int *Nelem, double **xx, double **yy, double **zz, int dobeam, int Nt); - -extern int -predict_visibilities_multifreq_withbeam_gpu(double *u,double *v,double *w,double *x,int N,int Nbase,int tilesz,baseline_t *barr, clus_source_t *carr, int M,double *freqs,int Nchan, double fdelta,double tdelta, double dec0, - double ph_ra0, double ph_dec0, double ph_freq0, double *longitude, double *latitude, double *time_utc,int *Nelem, double **xx, double **yy, double **zz, int dobeam, int Nt, int add_to_data); - - - -/****************************** predict_model.cu ****************************/ -extern void -cudakernel_array_beam(int N, int T, int K, int F, float *freqs, float *longitude, float *latitude, - double *time_utc, int *Nelem, float **xx, float **yy, float **zz, float *ra, float *dec, float ph_ra0, float ph_dec0, float ph_freq0, float *beam); - - -extern void -cudakernel_coherencies(int B, int N, int T, int K, int F, float *u, float *v, float *w,baseline_t *barr, float *freqs, float *beam, float *ll, float *mm, float *nn, float *sI, - unsigned char *stype, float *sI0, float *f0, float *spec_idx, float *spec_idx1, float *spec_idx2, int **exs, float deltaf, float deltat, float dec0, float *coh,int dobeam); - - -extern void -cudakernel_convert_time(int T, double *time_utc); -#ifdef __cplusplus - } /* extern "C" */ -#endif -#endif /* SAGECAL_H */ diff --git a/src/lib/Solvers/Makefile b/src/lib/Solvers/Makefile deleted file mode 100644 index a3a873d..0000000 --- a/src/lib/Solvers/Makefile +++ /dev/null @@ -1,54 +0,0 @@ -CC=gcc -CXX=g++ -#CFLAGS= -Wall -O3 -g #-pg -CFLAGS= -Wall -O3 -fopt-info-optimized -CLIBS= -lm -lpthread -#LAPACK=-L/usr/lib/atlas/sse -llapack -lblas -#LAPACK=-L/usr/local/GotoBLAS2/lib -lgoto2 -lpthread -lgfortran -LAPACK=-L/usr/local/OpenBLAS/lib/ -lopenblas -lgfortran -lpthread - - -INCLUDES= -I. -LIBPATH= - -#### glib -GLIBI=-I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -I/usr/lib/x86_64-linux-gnu/glib-2.0/include/ -I/usr/lib64/glib-2.0/include -GLIBL=-lglib-2.0 - -OBJECTS= lmfit_nocuda.o clmfit_nocuda.o lbfgs_nocuda.o myblas.o residual.o robustlm.o updatenu.o robust_lbfgs_nocuda.o rtr_solve.o rtr_solve_robust.o manifold_average.o consensus_poly.o rtr_solve_robust_admm.o admm_solve.o - -default:libsolvers.a -lmfit_nocuda.o:lmfit_nocuda.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -clmfit_nocuda.o:clmfit_nocuda.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -lbfgs_nocuda.o:lbfgs_nocuda.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -myblas.o:myblas.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -residual.o:residual.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -robustlm.o:robustlm.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -updatenu.o:updatenu.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -robust_lbfgs_nocuda.o:robust_lbfgs_nocuda.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -rtr_solve.o:rtr_solve.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -rtr_solve_robust.o:rtr_solve_robust.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -manifold_average.o:manifold_average.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -consensus_poly.o:consensus_poly.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -rtr_solve_robust_admm.o:rtr_solve_robust_admm.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -admm_solve.o:admm_solve.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< - - -RANLIB=ranlib -libsolvers.a:$(OBJECTS) Solvers.h - ar rv $@ $(OBJECTS); \ - $(RANLIB) $@; diff --git a/src/lib/Solvers/Makefile.gpu b/src/lib/Solvers/Makefile.gpu deleted file mode 100644 index 98ccba1..0000000 --- a/src/lib/Solvers/Makefile.gpu +++ /dev/null @@ -1,88 +0,0 @@ -CC=gcc -CXX=g++ -NVCC=nvcc -CFLAGS= -Wall -O3 -g -DHAVE_CUDA -DHYBRID_CODE -CLIBS= -lm -lpthread -LAPACK=-L/usr/local/OpenBLAS/lib/ -lopenblas -lgfortran -lpthread - -CUDAINC=/usr/local/cuda/include -CUDALIB=-L/usr/local/cuda/lib64 -lcuda -lcudart -#NVCC=/usr/local/cuda/bin/nvcc -#NVCFLAGS=-arch=sm_35 -g -G --ptxas-options=-v -O3 -NVCFLAGS=-arch=sm_35 --ptxas-options=-v -O3 - -#### glib -GLIBI=-I/usr/include/glib-2.0 -I/usr/lib64/glib-2.0/include/ -GLIBL=-lglib-2.0 -L/usr/lib64 - -# NVML -NVML_INC=/usr/include/nvidia/gdk/ -NVML_LIB=-lnvidia-ml -L/usr/lib64/nvidia/ - -INCLUDES= -I. -I$(CUDAINC) -I$(NVML_INC) -LIBPATH= $(CUDALIB) - - -OBJECTS=lmfit.o lbfgs.o myblas.o mderiv.o clmfit.o clmfit_nocuda.o residual.o barrier.o robust.o robustlm.o oslmfit.o mderiv_fl.o clmfit_fl.o updatenu.o robust_lbfgs_nocuda.o robust_fl.o manifold_fl.o rtr_solve_cuda.o rtr_solve_robust_cuda.o diagnostics.o diag_fl.o manifold_average.o consensus_poly.o rtr_solve_robust_cuda_admm.o rtr_solve_robust_admm.o admm_solve.o load_balance.o - - -default:libsolvers-gpu.a -lmfit.o:lmfit.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -lbfgs.o:lbfgs.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -myblas.o:myblas.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -mderiv.o:mderiv.cu - $(NVCC) $(NVCFLAGS) $(INCLUDES) $(GLIBI) -c $< -clmfit.o:clmfit.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -clmfit_nocuda.o:clmfit_nocuda.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -residual.o:residual.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -barrier.o:barrier.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -robustlm.o:robustlm.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -robust.o:robust.cu - $(NVCC) $(NVCFLAGS) $(INCLUDES) $(GLIBI) -c $< -robust_fl.o:robust_fl.cu - $(NVCC) $(NVCFLAGS) $(INCLUDES) $(GLIBI) -c $< -oslmfit.o:oslmfit.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -robust_lbfgs_nocuda.o:robust_lbfgs_nocuda.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -clmfit_fl.o:clmfit_fl.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -updatenu.o:updatenu.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -mderiv_fl.o:mderiv_fl.cu - $(NVCC) $(NVCFLAGS) $(INCLUDES) $(GLIBI) -c $< -manifold_fl.o:manifold_fl.cu - $(NVCC) $(NVCFLAGS) $(INCLUDES) $(GLIBI) -c $< -rtr_solve_cuda.o:rtr_solve_cuda.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -rtr_solve_robust_cuda.o:rtr_solve_robust_cuda.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -diagnostics.o:diagnostics.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -diag_fl.o:diag_fl.cu - $(NVCC) $(NVCFLAGS) $(INCLUDES) $(GLIBI) -c $< -manifold_average.o:manifold_average.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -consensus_poly.o:consensus_poly.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -rtr_solve_robust_cuda_admm.o:rtr_solve_robust_cuda_admm.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -rtr_solve_robust_admm.o:rtr_solve_robust_admm.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -admm_solve.o:admm_solve.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -load_balance.o:load_balance.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< - -RANLIB=ranlib -libsolvers-gpu.a:$(OBJECTS) Solvers.h - ar rv $@ $(OBJECTS); \ - $(RANLIB) $@; diff --git a/src/lib/Solvers/Solvers.h b/src/lib/Solvers/Solvers.h deleted file mode 100644 index b7dbe57..0000000 --- a/src/lib/Solvers/Solvers.h +++ /dev/null @@ -1,1479 +0,0 @@ -/* - * - Copyright (C) 2006-2008 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - -#ifndef SAGECAL_H -#define SAGECAL_H -#ifdef __cplusplus - extern "C" { -#endif - -#include -#include - -#include -#include -#include -#include -#include -#include - -#include - -/* for gcc 4.8 and above */ -#ifndef complex -#define complex _Complex -#endif - -#ifdef HAVE_CUDA -#include -#include -#include -#endif /* HAVE_CUDA */ - -#ifndef MAX_GPU_ID -#define MAX_GPU_ID 3 /* use 0 (1 GPU), 1 (2 GPUs), ... */ -#endif -/* default value for threads per block */ -#ifndef DEFAULT_TH_PER_BK -#define DEFAULT_TH_PER_BK 64 -#endif -#ifndef DEFAULT_TH_PER_BK_2 -#define DEFAULT_TH_PER_BK_2 32 -#endif - -/* speed of light */ -#ifndef CONST_C -#define CONST_C 299792458.0 -#endif - -#ifndef MIN -#define MIN(x,y) \ - ((x)<=(y)? (x): (y)) -#endif - -#ifndef MAX -#define MAX(x,y) \ - ((x)>=(y)? (x): (y)) -#endif - -/* soure types */ -#define STYPE_POINT 0 -#define STYPE_GAUSSIAN 1 -#define STYPE_DISK 2 -#define STYPE_RING 3 -#define STYPE_SHAPELET 4 - -/* max source name length, increase it if names get longer */ -#define MAX_SNAME 2048 - -/********* constants - from levmar ******************/ -#define CLM_INIT_MU 1E-03 -#define CLM_STOP_THRESH 1E-17 -#define CLM_DIFF_DELTA 1E-06 -#define CLM_EPSILON 1E-12 -#define CLM_ONE_THIRD 0.3333333334 /* 1.0/3.0 */ -#define CLM_OPTS_SZ 5 /* max(4, 5) */ -#define CLM_INFO_SZ 10 -#define CLM_DBL_MAX 1E12 /* max double value */ - -#include - -/* given the epoch jd_tdb2, - calculate rotation matrix params needed to precess from J2000 - NOVAS (Naval Observatory Vector Astronomy Software) - PURPOSE: - Precesses equatorial rectangular coordinates from one epoch to - another. One of the two epochs must be J2000.0. The coordinates - are referred to the mean dynamical equator and equinox of the two - respective epochs. - - REFERENCES: - Explanatory Supplement To The Astronomical Almanac, pp. 103-104. - Capitaine, N. et al. (2003), Astronomy And Astrophysics 412, - pp. 567-586. - Hilton, J. L. et al. (2006), IAU WG report, Celest. Mech., 94, - pp. 351-367. - -*/ - -/* convert types */ -/* both arrays size nx1 - Nt: no of threads -*/ -extern int -double_to_float(float *farr, double *darr,int n, int Nt); -extern int -float_to_double(double *darr, float *farr,int n, int Nt); - -/* create a vector with 1's at flagged data points */ -/* - ddbase: 3*Nbase x 1 (sta1,sta2,flag) - x: 8*Nbase (set to 0's and 1's) -*/ -extern int -create_onezerovec(int Nbase, short *ddbase, float *x, int Nt); - -/* - find sum1=sum(|x|), and sum2=y^T |x| - x,y: nx1 arrays -*/ -extern int -find_sumproduct(int N, float *x, float *y, float *sum1, float *sum2, int Nt); - -/****************************** lbfgs.c ****************************/ -/****************************** lbfgs_nocuda.c ****************************/ -/* LBFGS routines */ -/* func: vector function to minimize, actual cost minimized is ||func-x||^2 - NOTE: gradient function given seperately - p: parameters m x 1 (used as initial value, output final value) - x: data n x 1 - itmax: max iterations - lbfgs_m: memory size - gpu_threads: GPU threads per block - adata: additional data -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -lbfgs_fit( - void (*func)(double *p, double *hx, int m, int n, void *adata), - double *p, double *x, int m, int n, int itmax, int lbfgs_m, int gpu_threads, void *adata); - -/****************************** robust_lbfgs_nocuda.c ****************************/ -typedef struct thread_data_logf_t_ { - double *f; - double *x; - double nu; - int start,end; - double sum; -} thread_data_logf_t; - -/* robust_nu: nu in T distribution */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -lbfgs_fit_robust( - void (*func)(double *p, double *hx, int m, int n, void *adata), - double *p, double *x, int m, int n, int itmax, int lbfgs_m, int gpu_threads, - void *adata); -#ifdef HAVE_CUDA -extern int -lbfgs_fit_robust_cuda( - void (*func)(double *p, double *hx, int m, int n, void *adata), - double *p, double *x, int m, int n, int itmax, int lbfgs_m, int gpu_threads, void *adata); -#endif - -/****************************** residual.c ****************************/ -/* residual calculation, with/without linear interpolation */ -/* - u,v,w: u,v,w coordinates (wavelengths) size Nbase*tilesz x 1 - u,v,w are ordered with baselines, timeslots - p0,p: parameter arrays 8*N*M x1 double values (re,img) for each station/direction - p0: old value, p new one, interpolate between the two - x: data to write size Nbase*8*tilesz x 1 - ordered by XX(re,im),XY(re,im),YX(re,im), YY(re,im), baseline, timeslots - input: x is actual data, output: x is the residual - N: no of stations - Nbase: no of baselines - tilesz: tile size - barr: baseline to station map, size Nbase*tilesz x 1 - carr: sky model/cluster info size Mx1 of clusters - coh: coherencies size Nbase*tilesz*4*M x 1 - M: no of clusters - freq0: frequency - fdelta: bandwidth for freq smearing - tdelta: integration time for time smearing - dec0: declination for time smearing - Nt: no. of threads - ccid: which cluster to use as correction - rho: MMSE robust parameter J+rho I inverted - - phase_only: if >0, and if there is any correction done, use only phase of diagonal elements for correction -*/ -extern int -calculate_residuals(double *u,double *v,double *w,double *p,double *x,int N,int Nbase,int tilesz,baseline_t *barr, clus_source_t *carr, int M,double freq0,double fdelta,double tdelta,double dec0, int Nt, int ccid, double rho); - -/* - residuals for multiple channels - data to write size Nbase*8*tilesz*Nchan x 1 - ordered by XX(re,im),XY(re,im),YX(re,im), YY(re,im), baseline, timeslots, channels - input: x is actual data, output: x is the residual - freqs: Nchanx1 of frequency values - fdelta: total bandwidth, so divide by Nchan to get each channel bandwith - tdelta: integration time for time smearing - dec0: declination for time smearing -*/ -extern int -calculate_residuals_multifreq(double *u,double *v,double *w,double *p,double *x,int N,int Nbase,int tilesz,baseline_t *barr, clus_source_t *carr, int M,double *freqs,int Nchan, double fdelta,double tdelta,double dec0, int Nt, int ccid, double rho, int phase_only); - -/* - calculate visibilities for multiple channels, no solutions are used - note: output column x is set to 0 if add_to_data ==0, else model is added to data -*/ -extern int -predict_visibilities_multifreq(double *u,double *v,double *w,double *x,int N,int Nbase,int tilesz,baseline_t *barr, clus_source_t *carr, int M,double *freqs,int Nchan, double fdelta,double tdelta,double dec0,int Nt,int add_to_data); - - -/* predict with solutions in p , ignore clusters flagged in ignorelist (Mx1) array - also correct final data with solutions for cluster ccid, if valid -*/ -extern int -predict_visibilities_multifreq_withsol(double *u,double *v,double *w,double *p,double *x,int *ignorelist,int N,int Nbase,int tilesz,baseline_t *barr, clus_source_t *carr, int M,double *freqs,int Nchan, double fdelta,double tdelta,double dec0,int Nt,int add_to_data, int ccid, double rho,int phase_only); -/****************************** mderiv.cu ****************************/ -/* cuda driver for kernel */ -/* ThreadsPerBlock: keep <= 128 - BlocksPerGrid: depends on the threads/baselines> Threads*Blocks approx baselines - N: no of baselines (total, including tilesz >1) - tilesz: tile size - M: no of clusters - Ns: no of stations - Nparam: no of actual parameters <=total - goff: starting point of gradient calculation 0..Nparams - x: N*8 x 1 residual - coh: N*8*M x 1 - p: M*Ns*8 x 1 - bb: 2*N x 1 - ptoclus: 2*M x 1 - grad: Nparamsx1 gradient values -*/ -extern void -cudakernel_lbfgs(int ThreadsPerBlock, int BlocksPerGrid, int N, int tilesz, int M, int Ns, int Nparam, int goff, double *x, double *coh, double *p, short *bb, int *ptoclus, double *grad); -/* x: data vector, not residual */ -extern void -cudakernel_lbfgs_r(int ThreadsPerBlock, int BlocksPerGrid, int N, int tilesz, int M, int Ns, int Nparam, int goff, double *x, double *coh, double *p, short *bb, int *ptoclus, double *grad); -extern void -cudakernel_lbfgs_r_robust(int ThreadsPerBlock, int BlocksPerGrid, int N, int tilesz, int M, int Ns, int Nparam, int goff, double *x, double *coh, double *p, short *bb, int *ptoclus, double *grad, double robust_nu); - - -/* cost function calculation, each GPU works with Nbase baselines out of Nbasetotal baselines - */ -extern double -cudakernel_lbfgs_cost(int ThreadsPerBlock, int BlocksPerGrid, int Nbase, int boff, int M, int Ns, int Nbasetotal, double *x, double *coh, double *p, short *bb, int *ptoclus); -extern double -cudakernel_lbfgs_cost_robust(int ThreadsPerBlock, int BlocksPerGrid, int Nbase, int boff, int M, int Ns, int Nbasetotal, double *x, double *coh, double *p, short *bb, int *ptoclus, double robust_nu); - - -/* divide by singular values Dpd[]/Sd[] for Sd[]> eps */ -extern void -cudakernel_diagdiv(int ThreadsPerBlock, int BlocksPerGrid, int M, double eps, double *Dpd, double *Sd); - -/* cuda driver for calculating - A<= A+mu I, adding mu to diagonal entries of A - A: size MxM - ThreadsPerBlock, BlocksPerGrid calculated to meet M -*/ -extern void -cudakernel_diagmu(int ThreadsPerBlock, int BlocksPerGrid, int M, double *A, double mu); - -/* cuda driver for calculating f() */ -/* p: params (Mx1): for all chunks, x: data (Nx1), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -extern void -cudakernel_func(int ThreadsPerBlock, int BlocksPerGrid, double *p, double *x, int M, int N, double *coh, short *bbh, int Nbase, int Mclus, int Nstations); - -/* cuda driver for calculating jacf() */ -/* p: params (Mx1): for all chunks, jac: jacobian (NxM), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -extern void -cudakernel_jacf(int ThreadsPerBlock_row, int ThreadsPerBlock_col, double *p, double *jac, int M, int N, double *coh, short *bbh, int Nbase, int Mclus, int Nstations); - - -/****************************** mderiv_fl.cu ****************************/ -/* divide by singular values Dpd[]/Sd[] for Sd[]> eps */ -extern void -cudakernel_diagdiv_fl(int ThreadsPerBlock, int BlocksPerGrid, int M, float eps, float *Dpd, float *Sd); -/* cuda driver for calculating - A<= A+mu I, adding mu to diagonal entries of A - A: size MxM - ThreadsPerBlock, BlocksPerGrid calculated to meet M -*/ -extern void -cudakernel_diagmu_fl(int ThreadsPerBlock, int BlocksPerGrid, int M, float *A, float mu); -/* cuda driver for calculating f() */ -/* p: params (Mx1), x: data (Nx1), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -extern void -cudakernel_func_fl(int ThreadsPerBlock, int BlocksPerGrid, float *p, float *x, int M, int N, float *coh, short *bbh, int Nbase, int Mclus, int Nstations); -/* cuda driver for calculating jacf() */ -/* p: params (Mx1), jac: jacobian (NxM), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -extern void -cudakernel_jacf_fl(int ThreadsPerBlock_row, int ThreadsPerBlock_col, float *p, float *jac, int M, int N, float *coh, short *bbh, int Nbase, int Mclus, int Nstations); -/****************************** robust.cu ****************************/ -/* cuda driver for calculating wt \odot f() */ -/* p: params (Mx1): for all chunks, x: data (Nx1), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -extern void -cudakernel_func_wt(int ThreadsPerBlock, int BlocksPerGrid, double *p, double *x, int M, int N, double *coh, short *bbh, double *wt, int Nbase, int Mclus, int Nstations); - -/* cuda driver for calculating wt \odot jacf() */ -/* p: params (Mx1): for all chunks, jac: jacobian (NxM), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -extern void -cudakernel_jacf_wt(int ThreadsPerBlock_row, int ThreadsPerBlock_col, double *p, double *jac, int M, int N, double *coh, short *bbh, double *wt, int Nbase, int Mclus, int Nstations); - - -/* set initial weights to 1 by a cuda kernel */ -extern void -cudakernel_setweights(int ThreadsPerBlock, int BlocksPerGrid, int N, double *wtd, double alpha); - -/* hadamard product by a cuda kernel x<= x*wt */ -extern void -cudakernel_hadamard(int ThreadsPerBlock, int BlocksPerGrid, int N, double *wt, double *x); - -/* update weights by a cuda kernel */ -extern void -cudakernel_updateweights(int ThreadsPerBlock, int BlocksPerGrid, int N, double *wt, double *x, double *q, double robust_nu); - -/* make sqrt() weights */ -extern void -cudakernel_sqrtweights(int ThreadsPerBlock, int BlocksPerGrid, int N, double *wt); - -/* evaluate expression for finding optimum nu for - a range of nu values */ -extern void -cudakernel_evaluatenu(int ThreadsPerBlock, int BlocksPerGrid, int Nd, double qsum, double *q, double deltanu,double nulow); - -/* ThreadsPerBlock: keep <= 128 - BlocksPerGrid: depends on the threads/baselines> Threads*Blocks approx baselines - N: no of baselines (total, including tilesz >1) - tilesz: tile size - M: no of clusters - Ns: no of stations - Nparam: no of actual parameters <=total - goff: starting point of gradient calculation 0..Nparams - x: N*8 x 1 residual - coh: N*8*M x 1 - p: M*Ns*8 x 1 - bb: 2*N x 1 - ptoclus: 2*M x 1 - grad: Nparamsx1 gradient values -*/ -extern void -cudakernel_lbfgs_robust(int ThreadsPerBlock, int BlocksPerGrid, int N, int tilesz, int M, int Ns, int Nparam, int goff, double robust_nu, double *x, double *coh, double *p, short *bb, int *ptoclus, double *grad); - -/****************************** robust_fl.cu ****************************/ -/* cuda driver for calculating wt \odot f() */ -/* p: params (Mx1): for all chunks, x: data (Nx1), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -extern void -cudakernel_func_wt_fl(int ThreadsPerBlock, int BlocksPerGrid, float *p, float *x, int M, int N, float *coh, short *bbh, float *wt, int Nbase, int Mclus, int Nstations); - -/* cuda driver for calculating wt \odot jacf() */ -/* p: params (Mx1): for all chunks, jac: jacobian (NxM), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -extern void -cudakernel_jacf_wt_fl(int ThreadsPerBlock_row, int ThreadsPerBlock_col, float *p, float *jac, int M, int N, float *coh, short *bbh, float *wt, int Nbase, int Mclus, int Nstations); - - -/* set initial weights to 1 by a cuda kernel */ -extern void -cudakernel_setweights_fl(int ThreadsPerBlock, int BlocksPerGrid, int N, float *wtd, float alpha); - -/* hadamard product by a cuda kernel x<= x*wt */ -extern void -cudakernel_hadamard_fl(int ThreadsPerBlock, int BlocksPerGrid, int N, float *wt, float *x); - -/* update weights by a cuda kernel */ -extern void -cudakernel_updateweights_fl(int ThreadsPerBlock, int BlocksPerGrid, int N, float *wt, float *x, float *q, float robust_nu); - -/* make sqrt() weights */ -extern void -cudakernel_sqrtweights_fl(int ThreadsPerBlock, int BlocksPerGrid, int N, float *wt); - -/* evaluate expression for finding optimum nu for - a range of nu values */ -extern void -cudakernel_evaluatenu_fl(int ThreadsPerBlock, int BlocksPerGrid, int Nd, float qsum, float *q, float deltanu,float nulow); - -/* evaluate expression for finding optimum nu for - a range of nu values , 8 variate T distrubution - using AECM */ -extern void -cudakernel_evaluatenu_fl_eight(int ThreadsPerBlock, int BlocksPerGrid, int Nd, float qsum, float *q, float deltanu,float nulow, float nu0); - -/****************************** clmfit.c ****************************/ -#ifdef HAVE_CUDA -/* LM with GPU */ -extern int -clevmar_der_single( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - * info[5]= # iterations, - * info[6]=reason for terminating: 1 - stopped by small gradient J^T e - * 2 - stopped by small Dp - * 3 - stopped by itmax - * 4 - singular matrix. Restart from current p with increased mu - * 5 - no further error reduction is possible. Restart with increased mu - * 6 - stopped by small ||e||_2 - * 7 - stopped by invalid (i.e. NaN or Inf) "func" values. This is a user error - * info[7]= # function evaluations - * info[8]= # Jacobian evaluations - * info[9]= # linear systems solved, i.e. # attempts for reducing error - */ - - int card, /* GPU to use */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - void *adata); /* pointer to possibly additional data */ - -/* function to set up a GPU, should be called only once */ -extern void -attach_gpu_to_thread(int card, cublasHandle_t *cbhandle, cusolverDnHandle_t *solver_handle); -extern void -attach_gpu_to_thread1(int card, cublasHandle_t *cbhandle, cusolverDnHandle_t *solver_handle, double **WORK, int64_t work_size); -extern void -attach_gpu_to_thread2(int card, cublasHandle_t *cbhandle, cusolverDnHandle_t *solver_handle, float **WORK, int64_t work_size, int usecula); - - -/* function to detach a GPU from a thread */ -extern void -detach_gpu_from_thread(cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle); -extern void -detach_gpu_from_thread1(cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle, double *WORK); -extern void -detach_gpu_from_thread2(cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle, float *WORK, int usecula); -/* function to set memory to zero */ -extern void -reset_gpu_memory(double *WORK, int64_t work_size); - - -/* same as above, but f() and jac() calculations are done - entirely in the GPU */ -extern int -clevmar_der_single_cuda( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - * info[5]= # iterations, - * info[6]=reason for terminating: 1 - stopped by small gradient J^T e - * 2 - stopped by small Dp - * 3 - stopped by itmax - * 4 - singular matrix. Restart from current p with increased mu - * 5 - no further error reduction is possible. Restart with increased mu - * 6 - stopped by small ||e||_2 - * 7 - stopped by invalid (i.e. NaN or Inf) "func" values. This is a user error - * info[7]= # function evaluations - * info[8]= # Jacobian evaluations - * info[9]= # linear systems solved, i.e. # attempts for reducing error - */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - double *gWORK, /* GPU allocated memory */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - void *adata); /* pointer to possibly additional data */ - -/** keep interface almost the same as in levmar **/ -extern int -mlm_der_single_cuda( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[6], /* I: minim. options [\mu, \m, \p0, \p1, \p2, \delta]. - delta: 1 or 2 - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - */ - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - double *gWORK, /* GPU allocated memory */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - - void *adata); /* pointer to possibly additional data, passed uninterpreted to func & jacf. - * Set to NULL if not needed - */ - -#endif /* HAVE_CUDA */ -/****************************** robustlm.c ****************************/ -/* robust, iteratively weighted non linear least squares using LM - entirely in the GPU */ -#ifdef HAVE_CUDA -extern int -rlevmar_der_single_cuda( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - double *gWORK, /* GPU allocated memory */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - void *adata); - -/* robust, iteratively weighted non linear least squares using LM - entirely in the GPU, using float data */ -int -rlevmar_der_single_cuda_fl( - float *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - float *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - float *gWORK, /* GPU allocated memory */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - void *adata); - -/* robust, iteratively weighted non linear least squares using LM - entirely in the GPU, using float data, OS acceleration */ -extern int -osrlevmar_der_single_cuda_fl( - float *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - float *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - float *gWORK, /* GPU allocated memory */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - int randomize, /* if >0 randomize */ - void *adata); -#endif /* HAVE_CUDA */ - -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -rlevmar_der_single_nocuda( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - */ - - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int Nt, /* no of threads */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - void *adata); - -/* robust LM, OS acceleration */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -osrlevmar_der_single_nocuda( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - * info[5]= # iterations, - * info[6]=reason for terminating: 1 - stopped by small gradient J^T e - * 2 - stopped by small Dp - * 3 - stopped by itmax - * 4 - singular matrix. Restart from current p with increased mu - * 5 - no further error reduction is possible. Restart with increased mu - * 6 - stopped by small ||e||_2 - * 7 - stopped by invalid (i.e. NaN or Inf) "func" values. This is a user error - * info[7]= # function evaluations - * info[8]= # Jacobian evaluations - * info[9]= # linear systems solved, i.e. # attempts for reducing error - */ - - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int Nt, /* no of threads */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - int randomize, /* if >0 randomize */ - void *adata); - -/****************************** updatenu.c ****************************/ -/* update nu (degrees of freedom) - - nu0: current value of nu (need for AECM update) - sumlogw = 1/N sum(log(w_i)-w_i) - use Nd values in [nulow,nuhigh] to find nu - p: 1 or 8 depending on scalar or 2x2 matrix formulation -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern double -update_nu(double sumlogw, int Nd, int Nt, double nulow, double nuhigh, int p, double nu0); - -/* update w and nu together - nu0: current value of nu - w: Nx1 weight vector - ed: Nx1 error vector - Nt: no of threads - - return new nu, w is also updated, search range [nulow,nuhigh] -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern double -update_w_and_nu(double nu0, double *w, double *ed, int N, int Nt, double nulow, double nuhigh); - -/* - taper data by weighting based on uv distance (for short baselines) - for example: use weights as the inverse density function - 1/( 1+f(u,v) ) - as u,v->inf, f(u,v) -> 0 so long baselines are not affected - x: Nbase*8 x 1 (input,output) data - u,v : Nbase x 1 - note: u = u/c, v=v/c here, so need freq to convert to wavelengths */ -extern void -whiten_data(int Nbase, double *x, double *u, double *v, double freq0, int Nt); -/****************************** clmfit_nocuda.c ****************************/ -/* LM with LAPACK */ -/** keep interface almost the same as in levmar **/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -clevmar_der_single_nocuda( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - * info[5]= # iterations, - * info[6]=reason for terminating: 1 - stopped by small gradient J^T e - * 2 - stopped by small Dp - * 3 - stopped by itmax - * 4 - singular matrix. Restart from current p with increased mu - * 5 - no further error reduction is possible. Restart with increased mu - * 6 - stopped by small ||e||_2 - * 7 - stopped by invalid (i.e. NaN or Inf) "func" values. This is a user error - * info[7]= # function evaluations - * info[8]= # Jacobian evaluations - * info[9]= # linear systems solved, i.e. # attempts for reducing error - */ - - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - void *adata); /* pointer to possibly additional data, passed uninterpreted to func & jacf. - * Set to NULL if not needed - */ - -extern int -mlm_der_single( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[6], /* I: minim. options [\mu, \m, \p0, \p1, \p2, \delta]. - delta: 1 or 2 - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - */ - - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - void *adata); /* pointer to possibly additional data, passed uninterpreted to func & jacf. - * Set to NULL if not needed - */ - -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -oslevmar_der_single_nocuda( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - */ - - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int randomize, /* if >0 randomize */ - void *adata); -/****************************** oslmfit.c ****************************/ -#ifdef HAVE_CUDA -/* OS-LM, but f() and jac() calculations are done - entirely in the GPU */ -extern int -oslevmar_der_single_cuda( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - * info[5]= # iterations, - * info[6]=reason for terminating: 1 - stopped by small gradient J^T e - * 2 - stopped by small Dp - * 3 - stopped by itmax - * 4 - singular matrix. Restart from current p with increased mu - * 5 - no further error reduction is possible. Restart with increased mu - * 6 - stopped by small ||e||_2 - * 7 - stopped by invalid (i.e. NaN or Inf) "func" values. This is a user error - * info[7]= # function evaluations - * info[8]= # Jacobian evaluations - * info[9]= # linear systems solved, i.e. # attempts for reducing error - */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - double *gWORK, /* GPU allocated memory */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - int randomize, /* if >0 randomize */ - void *adata); /* pointer to possibly additional data, passed uninterpreted to func & jacf. - * Set to NULL if not needed - */ - -#endif /* !HAVE_CUDA */ - -/****************************** clmfit_fl.c ****************************/ -#ifdef HAVE_CUDA -extern int -clevmar_der_single_cuda_fl( - float *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - float *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - * info[5]= # iterations, - * info[6]=reason for terminating: 1 - stopped by small gradient J^T e - * 2 - stopped by small Dp - * 3 - stopped by itmax - * 4 - singular matrix. Restart from current p with increased mu - * 5 - no further error reduction is possible. Restart with increased mu - * 6 - stopped by small ||e||_2 - * 7 - stopped by invalid (i.e. NaN or Inf) "func" values. This is a user error - * info[7]= # function evaluations - * info[8]= # Jacobian evaluations - * info[9]= # linear systems solved, i.e. # attempts for reducing error - */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - float *gWORK, /* GPU allocated memory */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - void *adata); /* pointer to possibly additional data */ - -extern int -oslevmar_der_single_cuda_fl( - float *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - float *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - * info[5]= # iterations, - * info[6]=reason for terminating: 1 - stopped by small gradient J^T e - * 2 - stopped by small Dp - * 3 - stopped by itmax - * 4 - singular matrix. Restart from current p with increased mu - * 5 - no further error reduction is possible. Restart with increased mu - * 6 - stopped by small ||e||_2 - * 7 - stopped by invalid (i.e. NaN or Inf) "func" values. This is a user error - * info[7]= # function evaluations - * info[8]= # Jacobian evaluations - * info[9]= # linear systems solved, i.e. # attempts for reducing error - */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - float *gWORK, /* GPU allocated memory */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - int randomize, /* if >0 randomize */ - void *adata); /* pointer to possibly additional data, passed uninterpreted to func & jacf. - * Set to NULL if not needed - */ - -#endif /* !HAVE_CUDA */ -/****************************** rtr_solve.c ****************************/ -/* structure for worker threads for function calculation */ -typedef struct thread_data_rtr_ { - int Nb; /* no of baselines this handle */ - int boff; /* baseline offset per thread */ - baseline_t *barr; /* pointer to baseline-> stations mapping array */ - clus_source_t *carr; /* sky model, with clusters Mx1 */ - int M; /* no of clusters */ - double *y; /* data vector Nbx8 array re,im,re,im .... */ - complex double *coh; /* output vector in complex form, (not used always) size 4*M*Nb */ - /* following are only used while predict with gain */ - complex double *x; /* parameter array, */ - /* general format of element in manifold x - x: size 4N x 1 vector - x[0:2N-1] : first column, x[2N:4N-1] : second column - x=[J_1(1,1) J_1(1,2); - J_1(2,1) J_1(2,2); - ... .... - J_N(1,1) J_N(1,2); - J_N(2,1) J_N(2,2)]; - */ - int N; /* no of stations */ - int clus; /* which cluster to process, 0,1,...,M-1 if -1 all clusters */ - - /* output of cost function */ - double fcost; - /* gradient */ - complex double *grad; - /* Hessian */ - complex double *hess; - /* Eta (used in Hessian) */ - complex double *eta; - - /* normalization factors for grad,hess calculation */ - /* size Nx1 */ - int *bcount; - double *iw; /* 1/bcount */ - - /* for robust solver */ - double *wtd; /* weights for baseline */ - double nu0; - - /* mutexs: N x 1, one for each station */ - pthread_mutex_t *mx_array; -} thread_data_rtr_t; - -/* structure for common data */ -typedef struct global_data_rtr_ { - me_data_t *medata; /* passed from caller */ - - /* normalization factors for grad,hess calculation */ - /* size Nx1 */ - double *iw; /* 1/bcount */ - - /* for robust solver */ - double *wtd; /* weights for baseline */ - double nulow,nuhigh; - - /* for ADMM cost */ - complex double *Y; /* size 2Nx2 */ - complex double *BZ; /* size 2Nx2 */ - double admm_rho; - - /* thread stuff Nt x 1 threads */ - pthread_t *th_array; - /* mutexs: N x 1, one for each station */ - pthread_mutex_t *mx_array; - pthread_attr_t attr; -} global_data_rtr_t; - - -/* RTR (ICASSP 2013) */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -rtr_solve_nocuda( - double *x, /* initial values and updated solution at output (size 8*N double) */ - double *y, /* data vector (size 8*M double) */ - int N, /* no. of stations */ - int M, /* no. of constraints */ - int itmax_sd, /* maximum number of iterations RSD */ - int itmax_rtr, /* maximum number of iterations RTR */ - double Delta_bar, double Delta0, /* Trust region radius and initial value */ - double *info, /* initial and final residuals */ - me_data_t *adata); /* pointer to additional data - */ - -/****************************** rtr_solve_robust.c ****************************/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -rtr_solve_nocuda_robust( - double *x0, /* initial values and updated solution at output (size 8*N double) */ - double *y, /* data vector (size 8*M double) */ - int N, /* no. of stations */ - int M, /* no. of constraints */ - int itmax_rsd, /* maximum number of iterations RSD */ - int itmax_rtr, /* maximum number of iterations RTR */ - double Delta_bar, double Delta0, /* Trust region radius and initial value */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - double *info, /* initial and final residuals */ - me_data_t *adata); - -/* Nesterov's SD */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -nsd_solve_nocuda_robust( - double *x, /* initial values and updated solution at output (size 8*N double) */ - double *y, /* data vector (size 8*M double) */ - int N, /* no. of stations */ - int M, /* no. of constraints */ - int itmax, /* maximum number of iterations */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - double *info, /* initial and final residuals */ - me_data_t *adata); /* pointer to additional data - */ - -/****************************** rtr_solve_robust_admm.c ****************************/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -rtr_solve_nocuda_robust_admm( - double *x0, /* initial values and updated solution at output (size 8*N double) */ - double *Y, /* Lagrange multiplier (size 8*N double) */ - double *BZ, /* consensus B Z (size 8*N double) */ - double *y, /* data vector (size 8*M double) */ - int N, /* no. of stations */ - int M, /* no. of constraints */ - int itmax_rsd, /* maximum number of iterations RSD */ - int itmax_rtr, /* maximum number of iterations RTR */ - double Delta_bar, double Delta0, /* Trust region radius and initial value */ - double admm_rho, /* ADMM regularization value */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - double *info, /* initial and final residuals */ - me_data_t *adata); -#ifdef HAVE_CUDA -/****************************** manifold_fl.cu ****************************/ -extern float -cudakernel_fns_f(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, float *y, float *coh, short *bbh); -extern void -cudakernel_fns_fgradflat(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, float *y, float *coh, short *bbh); -extern void -cudakernel_fns_fhessflat(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, cuFloatComplex *fhess, float *y, float *coh, short *bbh); -extern void -cudakernel_fns_fscale(int N, cuFloatComplex *eta, float *iw); -extern float -cudakernel_fns_f_robust(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, float *y, float *coh, short *bbh, float *wtd); -extern void -cudakernel_fns_fgradflat_robust(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, float *y, float *coh, short *bbh, float *wtd, cuFloatComplex *Ai, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle); -extern void -cudakernel_fns_fgradflat_robust1(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, float *y, float *coh, short *bbh, float *wtd); -extern void -cudakernel_fns_fgradflat_robust_admm(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, float *y, float *coh, short *bbh, float *wtd, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle); -extern void -cudakernel_fns_fhessflat_robust1(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, cuFloatComplex *fhess, float *y, float *coh, short *bbh, float *wtd); -extern void -cudakernel_fns_fhessflat_robust(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, cuFloatComplex *fhess, float *y, float *coh, short *bbh, float *wtd, cuFloatComplex *Ai, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle); -extern void -cudakernel_fns_fhessflat_robust_admm(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, cuFloatComplex *fhess, float *y, float *coh, short *bbh, float *wtd, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle); -extern void -cudakernel_fns_fupdate_weights(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, float *y, float *coh, short *bbh, float *wtd, float nu0); -extern void -cudakernel_fns_fupdate_weights_q(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, float *y, float *coh, short *bbh, float *wtd, float *qd, float nu0); -/****************************** rtr_solve_cuda.c ****************************/ -extern int -rtr_solve_cuda_fl( - float *x, /* initial values and updated solution at output (size 8*N float) */ - float *y, /* data vector (size 8*M float) */ - int N, /* no of stations */ - int M, /* no of constraints */ - int itmax_sd, /* maximum number of iterations RSD */ - int itmax_rtr, /* maximum number of iterations RTR */ - float Delta_bar, float Delta0, /* Trust region radius and initial value */ - double *info, /* initial and final residuals */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - me_data_t *adata); /* pointer to possibly additional data */ - - -extern void -cudakernel_fns_R(int N, cuFloatComplex *x, cuFloatComplex *r, cuFloatComplex *rnew, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle); -extern float -cudakernel_fns_g(int N,cuFloatComplex *x,cuFloatComplex *eta, cuFloatComplex *gamma,cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle); -extern void -cudakernel_fns_proj(int N, cuFloatComplex *x, cuFloatComplex *z, cuFloatComplex *rnew, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle); -/****************************** rtr_solve_robust_cuda.c ****************************/ -extern int -rtr_solve_cuda_robust_fl( - float *x0, /* initial values and updated solution at output (size 8*N float) */ - float *y, /* data vector (size 8*M float) */ - int N, /* no of stations */ - int M, /* no of constraints */ - int itmax_sd, /* maximum number of iterations RSD */ - int itmax_rtr, /* maximum number of iterations RTR */ - float Delta_bar, float Delta0, /* Trust region radius and initial value */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - double *info, /* initial and final residuals */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - me_data_t *adata); - - -/* Nesterov's steepest descent */ -extern int -nsd_solve_cuda_robust_fl( - float *x0, /* initial values and updated solution at output (size 8*N float) */ - float *y, /* data vector (size 8*M float) */ - int N, /* no of stations */ - int M, /* no of constraints */ - int itmax, /* maximum number of iterations */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - double *info, /* initial and final residuals */ - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - me_data_t *adata); - -/****************************** rtr_solve_robust_cuda_admm.c ****************************/ -/* ADMM solver */ -extern int -rtr_solve_cuda_robust_admm_fl( - float *x0, /* initial values and updated solution at output (size 8*N float) */ - float *Y, /* Lagrange multiplier size 8N */ - float *Z, /* consensus term B Z size 8N */ - float *y, /* data vector (size 8*M float) */ - int N, /* no of stations */ - int M, /* no of constraints */ - int itmax_rtr, /* maximum number of iterations */ - float Delta_bar, float Delta0, /* Trust region radius and initial value */ - float admm_rho, /* ADMM regularization */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - double *info, /* initial and final residuals */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - me_data_t *adata); - - -/* Nesterov's SD */ -extern int -nsd_solve_cuda_robust_admm_fl( - float *x0, /* initial values and updated solution at output (size 8*N float) */ - float *Y, /* Lagrange multiplier size 8N */ - float *Z, /* consensus term B Z size 8N */ - float *y, /* data vector (size 8*M float) */ - int N, /* no of stations */ - int M, /* no of constraints */ - int itmax, /* maximum number of iterations */ - float admm_rho, /* ADMM regularization */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - double *info, /* initial and final residuals */ - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - me_data_t *adata); -#endif /* HAVE_CUDA */ -/****************************** lmfit.c ****************************/ -/****************************** lmfit_nocuda.c ****************************/ -/* struct for calling parallel LM jobs */ -typedef struct thread_clm_data_t { - double *p; /* parameters */ - double *x; /* data */ - int M; - int N; - int itermax; - double *opts; - double *info; - int card; - int linsolv; - me_data_t *lmdata; -} thread_clm_data; - - -/* generate a random permutation of given integers */ -/* note: free returned value after use */ -/* n: no of entries, - weighter_iter: if 1, take weight into account - if 0, only generate a random permutation - w: weights (size nx1): sort them in descending order and - give permutation accordingly -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int* -random_permutation(int n, int weighted_iter, double *w); - -/****************************** diagnostics.c ****************************/ -#ifdef HAVE_CUDA -/* Calculate St.Laurent-Cook Jacobian leverage -x: input: residual, output: levarage - flags: 2 for flags based on uvcut, 1 for normal flags - coh: coherencies are calculated for all baselines, regardless of flag - diagmode: 1: replaces residual with Jacobian Leverage, 2: calculates (prints) fraction of leverage/noise - */ -extern int -calculate_diagnostics(double *u,double *v,double *w,double *p,double *x,int N,int Nbase,int tilesz,baseline_t *barr, clus_source_t *carr, complex double *coh, int M,int Mt,int diagmode,int Nt); -#endif - - -/****************************** diag_fl.cu ****************************/ -#ifdef HAVE_CUDA -/* cuda driver for calculating Jacobian for leverage */ -/* p: params (Mx1), jac: jacobian (NxM), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -/* flags are always ignored */ -extern void -cudakernel_jacf_fl2(float *p, float *jac, int M, int N, float *coh, short *bbh, int Nbase, int Mclus, int Nstations); - -/* invert sqrt(singular values) Sd[]=1/sqrt(Sd[]) for Sd[]> eps */ -extern void -cudakernel_sqrtdiv_fl(int ThreadsPerBlock, int BlocksPerGrid, int M, float eps, float *Sd); - -/* U <= U D, - U : MxM - D : Mx1, diagonal matrix -*/ -extern void -cudakernel_diagmult_fl(int ThreadsPerBlock, int BlocksPerGrid, int M, float * U, float *D); - -/* diag(J^T J) - d[i] = J[i,:] * J[i,:] - J: NxM (in row major order, so J[i,:] is actually J[:,i] - d: Nx1 -*/ -extern void -cudakernel_jnorm_fl(int ThreadsPerBlock, int BlocksPerGrid, float *J, int N, int M, float *d); -#endif - - -/****************************** manifold_average.c ****************************/ -/* calculate manifold average of 2Nx2 solution blocks, - then project each solution to this average - Y: 2Nx2 x M x Nf values (average calculated for each 2Nx2 x Nf blocks) - N: no of stations - M: no of directions - Nf: no of frequencies - Niter: everaging iterations - Nt: threads -*/ -extern int -calculate_manifold_average(int N,int M,int Nf,double *Y,int Niter,int Nt); - - -/* find U to minimize - ||J-J1 U|| solving Procrustes problem - J,J1 : 8N x 1 vectors, in standard format - will be reshaped to 2Nx2 format and J1 will be modified as J1 U -*/ -extern int -project_procrustes(int N,double *J,double *J1); - -/* same as above, but J,J1 are in right 2Nx2 matrix format */ -/* J1 is modified */ -extern int -project_procrustes_block(int N,complex double *J,complex double *J1); - - -/* Extract only the phase of diagonal entries from solutions - p: 8Nx1 solutions, orders as [(real,imag)vec(J1),(real,imag)vec(J2),...] - pout: 8Nx1 phases (exp(j*phase)) of solutions, after joint diagonalization of p - N: no. of 2x2 Jones matrices in p, having common unitary ambiguity - niter: no of iterations for Jacobi rotation */ -extern int -extract_phases(double *p, double *pout, int N, int niter); -/****************************** consensus_poly.c ****************************/ -/* build matrix with polynomial terms - B : Npoly x Nf, each row is one basis function - Npoly : total basis functions - Nf: frequencies - freqs: Nfx1 array freqs - freq0: reference freq - type : 0 for [1 ((f-fo)/fo) ((f-fo)/fo)^2 ...] basis functions - 1 : same as type 0, normalize each row such that norm is 1 - 2 : Bernstein poly \sum N_C_r x^r (1-x)^r where x in [0,1] : use min,max values of freq to normalize -*/ -extern int -setup_polynomials(double *B, int Npoly, int Nf, double *freqs, double freq0, int type); - -/* build matrix with polynomial terms - B : Npoly x Nf, each row is one basis function - Bi: Npoly x Npoly pseudo inverse of sum( B(:,col) x B(:,col)' ) - Npoly : total basis functions - Nf: frequencies - fratio: Nfx1 array of weighing factors depending on the flagged data of each freq - Sum taken is a weighted sum, using weights in fratio -*/ -extern int -find_prod_inverse(double *B, double *Bi, int Npoly, int Nf, double *fratio); - -/* update Z - Z: 8NxNpoly x M double array (real and complex need to be updated separate) - N : stations - M : clusters - Npoly: no of basis functions - z : right hand side 8NMxNpoly (note the different ordering from Z) - Bi : NpolyxNpoly matrix, Bi^T=Bi assumed -*/ -extern int -update_global_z(double *Z,int N,int M,int Npoly,double *z,double *Bi); - - -/****************************** admm_solve.c ****************************/ -/* ADMM cost function = normal_cost + ||Y^H(J-BZ)|| + rho/2 ||J-BZ||^2 */ -/* extra params - Y : Lagrange multiplier - BZ : consensus term - Y,BZ : size same as pp : 8*N*Mt x1 double values (re,img) for each station/direction - admm_rho : regularization factor array size Mx1 -*/ -extern int -sagefit_visibilities_admm(double *u, double *v, double *w, double *x, int N, - int Nbase, int tilesz, baseline_t *barr, clus_source_t *carr, complex double *coh, int M, int Mt, double freq0, double fdelta, double *pp, double *Y, double *BZ, double uvmin, int Nt, int max_emiter, int max_iter, int max_lbfgs, int lbfgs_m, int gpu_threads, int linsolv,int solver_mode,double nulow, double nuhigh,int randomize, double *admm_rho, double *mean_nu, double *res_0, double *res_1); - -/* ADMM cost function = normal_cost + ||Y^H(J-BZ)|| + rho/2 ||J-BZ||^2 */ -/* extra params - Y : Lagrange multiplier - BZ : consensus term - Y,BZ : size same as pp : 8*N*Mt x1 double values (re,img) for each station/direction - admm_rho : regularization factor array size Mx1 -*/ -#ifdef HAVE_CUDA -extern int -sagefit_visibilities_admm_dual_pt_flt(double *u, double *v, double *w, double *x, int N, - int Nbase, int tilesz, baseline_t *barr, clus_source_t *carr, complex double *coh, int M, int Mt, double freq0, double fdelta, double *pp, double *Y, double *BZ, double uvmin, int Nt, int max_emiter, int max_iter, int max_lbfgs, int lbfgs_m, int gpu_threads, int linsolv,int solver_mode, double nulow, double nuhigh, int randomize, double *admm_rho, double *mean_nu, double *res_0, double *res_1); -#endif - -/****************************** OpenBLAS ************************************/ -/* prototype declaration */ -extern void -openblas_set_num_threads(int num_threads); - -extern int -get_precession_params(double jd_tdb2, double Tr[9]); -/* precess ra0,dec0 at J2000 - to ra,dec at epoch given by transform Tr - using NOVAS library */ -extern int -precession(double ra0, double dec0, double Tr[9], double *ra, double *dec); - -/****************************** stationbeam.c ****************************/ -/* - ra,dec: source direction (rad) - ra0,dec0: beam center (rad) - f: frequency (Hz) - f0: beam forming frequency (Hz) - - longitude,latitude : Nx1 array of station positions (rad,rad) - time_jd: JD (day) time - Nelem : Nx1 array, no. of elements used in each station - x,y,z: Nx1 pointer arrays to station positions, each station has Nelem[]x1 arrays - - beamgain: Nx1 array of station beam gain along the source direction -*/ -extern int -arraybeam(double ra, double dec, double ra0, double dec0, double f, double f0, int N, double *longitude, double *latitude, double time_jd, int *Nelem, double **x, double **y, double **z, double *beamgain); - - -/****************************** predict_withbeam.c ****************************/ -/* precalculate cluster coherencies - u,v,w: u,v,w coordinates (wavelengths) size Nbase*tilesz x 1 - u,v,w are ordered with baselines, timeslots - x: coherencies size Nbase*4*Mx 1 - ordered by XX(re,im),XY(re,im),YX(re,im), YY(re,im), baseline, timeslots - N: no of stations - Nbase: total no of baselines (including more than one tile or timeslot) - barr: baseline to station map, size Nbase*tilesz x 1 - carr: sky model/cluster info size Mx1 of clusters - M: no of clusters - freq0: frequency - fdelta: bandwidth for freq smearing - tdelta: integration time for time smearing - dec0: declination for time smearing - uvmin: baseline length sqrt(u^2+v^2) below which not to include in solution - uvmax: baseline length higher than this not included in solution - - Station beam specific parameters - ph_ra0,ph_dec0: beam pointing rad,rad - ph_freq0: beam reference freq - longitude,latitude: Nx1 arrays (rad,rad) station locations - time_utc: JD (day) : tilesz x 1 - tilesz: how many tiles: == unique time_utc - Nelem: Nx1 array, size of stations (elements) - xx,yy,zz: Nx1 arrays of station element locations arrays xx[],yy[],zz[] - Nt: no of threads - - NOTE: prediction is done for all baselines, even flagged ones - and flags are set to 2 for baselines lower than uvcut -*/ - -extern int -precalculate_coherencies_withbeam(double *u, double *v, double *w, complex double *x, int N, - int Nbase, baseline_t *barr, clus_source_t *carr, int M, double freq0, double fdelta, double tdelta, double dec0, double uvmin, double uvmax, - double ph_ra0, double ph_dec0, double ph_freq0, double *longitude, double *latitude, double *time_utc, int tileze, int *Nelem, double **xx, double **yy, double **zz, int Nt); - - -extern int -predict_visibilities_multifreq_withbeam(double *u,double *v,double *w,double *x,int N,int Nbase,int tilesz,baseline_t *barr, clus_source_t *carr, int M,double *freqs,int Nchan, double fdelta,double tdelta, double dec0, - double ph_ra0, double ph_dec0, double ph_freq0, double *longitude, double *latitude, double *time_utc,int *Nelem, double **xx, double **yy, double **zz, int Nt, int add_to_data); - -extern int -calculate_residuals_multifreq_withbeam(double *u,double *v,double *w,double *p,double *x,int N,int Nbase,int tilesz,baseline_t *barr, clus_source_t *carr, int M,double *freqs,int Nchan, double fdelta,double tdelta,double dec0, -double ph_ra0, double ph_dec0, double ph_freq0, double *longitude, double *latitude, double *time_utc,int *Nelem, double **xx, double **yy, double **zz, int Nt, int ccid, double rho, int phase_only); - - -/* change epoch of soure ra,dec from J2000 to JAPP */ -/* also the beam pointing ra_beam,dec_beam */ -extern int -precess_source_locations(double jd_tdb, clus_source_t *carr, int M, double *ra_beam, double *dec_beam, int Nt); - -/****************************** predict_withbeam_gpu.c ****************************/ -/* if dobeam==0, beam calculation is off */ -extern int -precalculate_coherencies_withbeam_gpu(double *u, double *v, double *w, complex double *x, int N, - int Nbase, baseline_t *barr, clus_source_t *carr, int M, double freq0, double fdelta, double tdelta, double dec0, double uvmin, double uvmax, - double ph_ra0, double ph_dec0, double ph_freq0, double *longitude, double *latitude, double *time_utc, int tileze, int *Nelem, double **xx, double **yy, double **zz, int dobeam, int Nt); - -extern int -predict_visibilities_multifreq_withbeam_gpu(double *u,double *v,double *w,double *x,int N,int Nbase,int tilesz,baseline_t *barr, clus_source_t *carr, int M,double *freqs,int Nchan, double fdelta,double tdelta, double dec0, - double ph_ra0, double ph_dec0, double ph_freq0, double *longitude, double *latitude, double *time_utc,int *Nelem, double **xx, double **yy, double **zz, int dobeam, int Nt, int add_to_data); - - - -/****************************** predict_model.cu ****************************/ -extern void -cudakernel_array_beam(int N, int T, int K, int F, float *freqs, float *longitude, float *latitude, - double *time_utc, int *Nelem, float **xx, float **yy, float **zz, float *ra, float *dec, float ph_ra0, float ph_dec0, float ph_freq0, float *beam); - - -extern void -cudakernel_coherencies(int B, int N, int T, int K, int F, float *u, float *v, float *w,baseline_t *barr, float *freqs, float *beam, float *ll, float *mm, float *nn, float *sI, - unsigned char *stype, float *sI0, float *f0, float *spec_idx, float *spec_idx1, float *spec_idx2, int **exs, float deltaf, float deltat, float dec0, float *coh,int dobeam); - - -extern void -cudakernel_convert_time(int T, double *time_utc); -#ifdef __cplusplus - } /* extern "C" */ -#endif -#endif /* SAGECAL_H */ diff --git a/src/lib/Solvers/admm_solve.c b/src/lib/Solvers/admm_solve.c deleted file mode 100644 index aed03a5..0000000 --- a/src/lib/Solvers/admm_solve.c +++ /dev/null @@ -1,1482 +0,0 @@ -/* - * - Copyright (C) 2006-2015 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#include -#include -#include -#include -#include -#include "Solvers.h" - - -//#define DEBUG - -/* Jones matrix multiplication - C=A*B -*/ -static void -amb(complex double * __restrict a, complex double * __restrict b, complex double * __restrict c) { - c[0]=a[0]*b[0]+a[1]*b[2]; - c[1]=a[0]*b[1]+a[1]*b[3]; - c[2]=a[2]*b[0]+a[3]*b[2]; - c[3]=a[2]*b[1]+a[3]*b[3]; -} - -/* Jones matrix multiplication - C=A*B^H -*/ -static void -ambt(complex double * __restrict a, complex double * __restrict b, complex double * __restrict c) { - c[0]=a[0]*conj(b[0])+a[1]*conj(b[1]); - c[1]=a[0]*conj(b[2])+a[1]*conj(b[3]); - c[2]=a[2]*conj(b[0])+a[3]*conj(b[1]); - c[3]=a[2]*conj(b[2])+a[3]*conj(b[3]); -} - -/********************** sage minimization ***************************/ -/* worker thread function for prediction */ -static void * -predict_threadfn_withgain(void *data) { - thread_data_base_t *t=(thread_data_base_t*)data; - - int ci,cm,sta1,sta2; - complex double C[4],G1[4],G2[4],T1[4],T2[4]; - int M=(t->M); - cm=(t->clus); - int Ntilebase=(t->Nbase)*(t->tilesz); - int px; - double *pm; - - for (ci=0; ciNb; ci++) { - /* iterate over the sky model and calculate contribution */ - /* for this x[8*ci:8*(ci+1)-1] */ - memset(&(t->x[8*ci]),0,sizeof(double)*8); - - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - - px=(ci+t->boff)/((Ntilebase+t->carr[cm].nchunk-1)/t->carr[cm].nchunk); - pm=&(t->p[t->carr[cm].p[px]]); - /* gains for this cluster, for sta1,sta2 */ - G1[0]=(pm[sta1*8])+_Complex_I*(pm[sta1*8+1]); - G1[1]=(pm[sta1*8+2])+_Complex_I*(pm[sta1*8+3]); - G1[2]=(pm[sta1*8+4])+_Complex_I*(pm[sta1*8+5]); - G1[3]=(pm[sta1*8+6])+_Complex_I*(pm[sta1*8+7]); - G2[0]=(pm[sta2*8])+_Complex_I*(pm[sta2*8+1]); - G2[1]=(pm[sta2*8+2])+_Complex_I*(pm[sta2*8+3]); - G2[2]=(pm[sta2*8+4])+_Complex_I*(pm[sta2*8+5]); - G2[3]=(pm[sta2*8+6])+_Complex_I*(pm[sta2*8+7]); - - - /* use pre calculated values */ - C[0]=t->coh[4*M*ci+4*cm]; - C[1]=t->coh[4*M*ci+4*cm+1]; - C[2]=t->coh[4*M*ci+4*cm+2]; - C[3]=t->coh[4*M*ci+4*cm+3]; - - - /* form G1*C*G2' */ - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - - /* add to baseline visibilities */ - t->x[8*ci]+=creal(T2[0]); - t->x[8*ci+1]+=cimag(T2[0]); - t->x[8*ci+2]+=creal(T2[1]); - t->x[8*ci+3]+=cimag(T2[1]); - t->x[8*ci+4]+=creal(T2[2]); - t->x[8*ci+5]+=cimag(T2[2]); - t->x[8*ci+6]+=creal(T2[3]); - t->x[8*ci+7]+=cimag(T2[3]); - } - } - - return NULL; -} - - -/* minimization function (multithreaded) */ -/* p: size ??x1 parameters, not all belong to - this cluster - x: size nx1 data calculated - data: extra info needed */ -static void -mylm_fit_single_pth(double *p, double *x, int m, int n, void *data) { - - me_data_t *dp=(me_data_t*)data; - /* u,v,w : size Nbase*tilesz x 1 x: size Nbase*8*tilesz x 1 */ - /* barr: size Nbase*tilesz x 1 carr: size Mx1 */ - /* pp: size 8*N*M x 1 */ - /* pm: size Mx1 of double */ - - int nth,nth1,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthb0,Nthb; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_base_t *threaddata; - - int Nbase=(dp->Nbase); - int tilesz=(dp->tilesz); - - int Nbase1=Nbase*tilesz; - - /* calculate min baselines a thread can handle */ - //Nthb0=ceil((double)Nbase1/(double)Nt); - Nthb0=(Nbase1+Nt-1)/Nt; - - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((threaddata=(thread_data_base_t*)malloc((size_t)Nt*sizeof(thread_data_base_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr; - threaddata[nth].u=&(dp->u[ci]); - threaddata[nth].v=&(dp->v[ci]); - threaddata[nth].w=&(dp->w[ci]); - threaddata[nth].carr=dp->carr; - threaddata[nth].M=dp->M; - threaddata[nth].x=&(x[8*ci]); - threaddata[nth].N=dp->N; - threaddata[nth].Nbase=Nbase; - threaddata[nth].tilesz=tilesz; - threaddata[nth].p=p; - threaddata[nth].clus=(dp->clus); - threaddata[nth].coh=&(dp->coh[4*(dp->M)*ci]); - - - //printf("thread %d predict data from %d baselines %d\n",nth,8*ci,Nthb); - pthread_create(&th_array[nth],&attr,predict_threadfn_withgain,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - /* now wait for threads to finish */ - for(nth1=0; nth1M); - int Ntilebase=(t->Nbase)*(t->tilesz); - int px; - - for (ci=0; ciNb; ci++) { - /* iterate over the sky model and calculate contribution */ - /* for this x[8*ci:8*(ci+1)-1] */ - memset(&(t->x[8*ci]),0,sizeof(double)*8); - - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - for (cm=0; cm x[0.....Nbase*tilesz/nchunk-1] - p[1] -> x[Nbase*tilesz/nchunk......2*Nbase*tilesz-1] - .... - p[last] -> x[(nchunk-1)*Nbase*tilesz/nchunk......Nbase*tilesz] - - so given bindex, right p[] is bindex/((Nbase*tilesz+nchunk-1)/nchunk) - */ - - px=(ci+t->boff)/((Ntilebase+t->carr[cm].nchunk-1)/t->carr[cm].nchunk); - //pm=&(t->p[cm*8*N]); - pm=&(t->p[t->carr[cm].p[px]]); - G1[0]=(pm[sta1*8])+_Complex_I*(pm[sta1*8+1]); - G1[1]=(pm[sta1*8+2])+_Complex_I*(pm[sta1*8+3]); - G1[2]=(pm[sta1*8+4])+_Complex_I*(pm[sta1*8+5]); - G1[3]=(pm[sta1*8+6])+_Complex_I*(pm[sta1*8+7]); - G2[0]=(pm[sta2*8])+_Complex_I*(pm[sta2*8+1]); - G2[1]=(pm[sta2*8+2])+_Complex_I*(pm[sta2*8+3]); - G2[2]=(pm[sta2*8+4])+_Complex_I*(pm[sta2*8+5]); - G2[3]=(pm[sta2*8+6])+_Complex_I*(pm[sta2*8+7]); - - - /* use pre calculated values */ - C[0]=t->coh[4*M*ci+4*cm]; - C[1]=t->coh[4*M*ci+4*cm+1]; - C[2]=t->coh[4*M*ci+4*cm+2]; - C[3]=t->coh[4*M*ci+4*cm+3]; - - /* form G1*C*G2' */ - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - - /* add to baseline visibilities */ - t->x[8*ci]+=creal(T2[0]); - t->x[8*ci+1]+=cimag(T2[0]); - t->x[8*ci+2]+=creal(T2[1]); - t->x[8*ci+3]+=cimag(T2[1]); - t->x[8*ci+4]+=creal(T2[2]); - t->x[8*ci+5]+=cimag(T2[2]); - t->x[8*ci+6]+=creal(T2[3]); - t->x[8*ci+7]+=cimag(T2[3]); - } - } - } - - return NULL; -} - - -/* minimization function (multithreaded) */ -/* p: size mx1 parameters - x: size nx1 data calculated - data: extra info needed */ -static void -minimize_viz_full_pth(double *p, double *x, int m, int n, void *data) { - - me_data_t *dp=(me_data_t*)data; - /* u,v,w : size Nbase*tilesz x 1 x: size Nbase*8*tilesz x 1 */ - /* barr: size Nbase*tilesz x 1 carr: size Mx1 */ - /* pp: size 8*N*M x 1 */ - /* pm: size Mx1 of double */ - - int nth,nth1,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthb0,Nthb; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_base_t *threaddata; - - int Nbase1=(dp->Nbase)*(dp->tilesz); - - /* calculate min baselines a thread can handle */ - //Nthb0=ceil((double)Nbase1/(double)Nt); - Nthb0=(Nbase1+Nt-1)/Nt; - - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((threaddata=(thread_data_base_t*)malloc((size_t)Nt*sizeof(thread_data_base_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr; - threaddata[nth].u=&(dp->u[ci]); - threaddata[nth].v=&(dp->v[ci]); - threaddata[nth].w=&(dp->w[ci]); - threaddata[nth].carr=dp->carr; - threaddata[nth].M=dp->M; - threaddata[nth].x=&(x[8*ci]); - threaddata[nth].p=p; - threaddata[nth].N=dp->N; - threaddata[nth].Nbase=dp->Nbase; - threaddata[nth].tilesz=dp->tilesz; - threaddata[nth].coh=&(dp->coh[4*(dp->M)*ci]); - - //printf("thread %d predict data from %d baselines %d\n",nth,8*ci,Nthb); - pthread_create(&th_array[nth],&attr,predict_threadfn_withgain_full,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - /* now wait for threads to finish */ - for(nth1=0; nth10) { - /* calculate contribution from hidden data, subtract from x - actually, add the current model for this cluster to residual */ - lmdata.clus=cj; - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata); - my_daxpy(n, xsub, 1.0, xdummy); - - tilechunk=(tilesz+carr[cj].nchunk-1)/carr[cj].nchunk; - tcj=0; - init_res=final_res=0.0; - /* loop through hybrid parameter space */ - for (ck=0; ck0.0) { - nerr[cj]=(init_res-final_res)/init_res; - if (nerr[cj]<0.0) { nerr[cj]=0.0; } - } else { - nerr[cj]=0.0; - } - /* subtract current model */ - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata); - my_daxpy(n, xsub, -1.0, xdummy); - robust_nuM[cj]/=(double)carr[cj].nchunk; - } - } - - /* normalize nerr array so that the sum is 1 */ - total_err=my_dasum(M,nerr); - if (total_err>0.0) { - my_dscal(M, 1.0/total_err, nerr); - } - - /* flip weighting flag */ - if (randomize) { - weighted_iter=!weighted_iter; - } - } - free(nerr); - free(xdummy); - /* calculate mean robust_nu over all clusters */ - robust_nu0=my_dasum(M,robust_nuM)/(double)M; -#ifdef DEBUG - for (ci=0; cinuhigh) { - robust_nu0=nuhigh; - } - - /* final residual calculation */ - minimize_viz_full_pth(p, xsub, m, n, (void*)&lmdata); - my_daxpy(n, xsub, -1.0, x); - - *mean_nu=robust_nu0; - *res_1=my_dnrm2(n,x)/(double)n; - - free(xsub); - /* if final residual > initial residual, - return -1, else 0 - */ - if (*res_1>*res_0) { - return -1; - } - return 0; -} - - - - -/****************************************************************************/ -#ifdef HYBRID_CODE -/* slave thread 2GPU function */ -static void * -pipeline_slave_code_admm_flt(void *data) -{ - slave_tdata *td=(slave_tdata*)data; - gbdatafl_admm *gd=(gbdatafl_admm*)(td->pline->data); - int tid=td->tid; - - while(1) { - sync_barrier(&(td->pline->gate1)); /* stop at gate 1*/ - if(td->pline->terminate) break; /* if flag is set, break loop */ - sync_barrier(&(td->pline->gate2)); /* stop at gate 2 */ - /* do work : only one solver */ - //printf("state=%d, thread %d\n",gd->status[tid],tid); - if (gd->status[tid]==PT_DO_WORK_RRTR || gd->status[tid]==PT_DO_WORK_NSD) { -/************************* work *********************/ - me_data_t *t=(me_data_t *)gd->lmdata[tid]; - /* divide the tiles into chunks tilesz/nchunk */ - int tilechunk=(t->tilesz+t->carr[t->clus].nchunk-1)/t->carr[t->clus].nchunk; - - - int ci; - - int cj=0; - int ntiles; - double init_res,final_res; - init_res=final_res=0.0; - if (tid<2) { - /* for GPU, the cost func and jacobian are not used */ - /* loop over each chunk, with right parameter set and data set */ - for (ci=0; cicarr[t->clus].nchunk; ci++) { - /* divide the tiles into chunks tilesz/nchunk */ - if (cj+tilechunktilesz) { - ntiles=tilechunk; - } else { - ntiles=t->tilesz-cj; - } - - if (gd->status[tid]==PT_DO_WORK_NSD) { - nsd_solve_cuda_robust_admm_fl(&gd->p[tid][ci*(gd->M[tid])], &gd->Y[tid][ci*(gd->M[tid])], &gd->Z[tid][ci*(gd->M[tid])], &gd->x[tid][8*cj*t->Nbase], gd->M[tid]/8, ntiles*t->Nbase, gd->itermax[tid]+15, gd->admm_rho[tid], gd->nulow, gd->nuhigh, gd->info[tid], gd->cbhandle[tid], gd->solver_handle[tid], cj, ntiles, (void*)gd->lmdata[tid]); - } else { - /* max trust region radius: keep reasonable */ - float Delta0=2.0f; - rtr_solve_cuda_robust_admm_fl(&gd->p[tid][ci*(gd->M[tid])], &gd->Y[tid][ci*(gd->M[tid])], &gd->Z[tid][ci*(gd->M[tid])], &gd->x[tid][8*cj*t->Nbase], gd->M[tid]/8, ntiles*t->Nbase, gd->itermax[tid]+10, Delta0, Delta0*0.125f, gd->admm_rho[tid], gd->nulow, gd->nuhigh, gd->info[tid], gd->cbhandle[tid], gd->solver_handle[tid], cj, ntiles, (void*)gd->lmdata[tid]); - } - - init_res+=gd->info[tid][0]; - final_res+=gd->info[tid][1]; - cj=cj+tilechunk; - } - - } - - gd->info[tid][0]=init_res; - gd->info[tid][1]=final_res; - -/************************* work *********************/ - } else if (gd->status[tid]==PT_DO_AGPU) { - /* no cula needed: 0 at end */ - attach_gpu_to_thread2(select_work_gpu(MAX_GPU_ID,td->pline->thst),&gd->cbhandle[tid],&gd->solver_handle[tid],&gd->gWORK[tid],gd->data_size,0); - } else if (gd->status[tid]==PT_DO_DGPU) { - detach_gpu_from_thread2(gd->cbhandle[tid],gd->solver_handle[tid],gd->gWORK[tid],0); - } else if (gd->status[tid]==PT_DO_MEMRESET) { - reset_gpu_memory((double*)gd->gWORK[tid],gd->data_size); - } else if (gd->status[tid]!=PT_DO_NOTHING) { /* catch error */ - fprintf(stderr,"%s: %d: invalid mode for slave tid=%d status=%d\n",__FILE__,__LINE__,tid,gd->status[tid]); - exit(1); - } - } - return NULL; -} - -/* initialize the pipeline - and start the slaves rolling */ -static void -init_pipeline_admm_flt(th_pipeline *pline, - void *data) -{ - slave_tdata *t0,*t1; - pthread_attr_init(&(pline->attr)); - pthread_attr_setdetachstate(&(pline->attr),PTHREAD_CREATE_JOINABLE); - - init_th_barrier(&(pline->gate1),3); /* 3 threads, including master */ - init_th_barrier(&(pline->gate2),3); /* 3 threads, including master */ - pline->terminate=0; - pline->data=data; /* data should have pointers to t1 and t2 */ - - if ((t0=(slave_tdata*)malloc(sizeof(slave_tdata)))==0) { - fprintf(stderr,"no free memory\n"); - exit(1); - } - if ((t1=(slave_tdata*)malloc(sizeof(slave_tdata)))==0) { - fprintf(stderr,"no free memory\n"); - exit(1); - } - if ((pline->thst=(taskhist*)malloc(sizeof(taskhist)))==0) { - fprintf(stderr,"no free memory\n"); - exit(1); - } - - init_task_hist(pline->thst); - t0->pline=t1->pline=pline; - t0->tid=0; - t1->tid=1; /* link back t1, t2 to data so they could be freed */ - pline->sd0=t0; - pline->sd1=t1; - pthread_create(&(pline->slave0),&(pline->attr),pipeline_slave_code_admm_flt,(void*)t0); - pthread_create(&(pline->slave1),&(pline->attr),pipeline_slave_code_admm_flt,(void*)t1); - -} - -/* destroy the pipeline */ -/* need to kill the slaves first */ -static void -destroy_pipeline_admm_flt(th_pipeline *pline) -{ - - pline->terminate=1; - sync_barrier(&(pline->gate1)); - pthread_join(pline->slave0,NULL); - pthread_join(pline->slave1,NULL); - destroy_th_barrier(&(pline->gate1)); - destroy_th_barrier(&(pline->gate2)); - pthread_attr_destroy(&(pline->attr)); - destroy_task_hist(pline->thst); - free(pline->thst); - free(pline->sd0); - free(pline->sd1); - pline->data=NULL; -} - -//#define DEBUG -int -sagefit_visibilities_admm_dual_pt_flt(double *u, double *v, double *w, double *x, int N, - int Nbase, int tilesz, baseline_t *barr, clus_source_t *carr, complex double *coh, int M, int Mt, double freq0, double fdelta, double *pp, double *Y, double *BZ, double uvmin, int Nt, int max_emiter, int max_iter, int max_lbfgs, int lbfgs_m, int gpu_threads, int linsolv,int solver_mode, double nulow, double nuhigh, int randomize,double *admm_rho, double *mean_nu, double *res_0, double *res_1) { - - - int ci,cj; - double *p; // parameters: m x 1 - int m, n; - double opts[CLM_OPTS_SZ], info0[CLM_INFO_SZ], info1[CLM_INFO_SZ]; - me_data_t lmdata0,lmdata1; - int Nbase1; - - double *xdummy0,*xdummy1,*xsub,*xo; - double *nerr; /* array to store cost reduction per cluster */ - int weighted_iter,this_itermax0,this_itermax1,total_iter; - double total_err; - - /* rearraged memory for GPU use */ - double *ddcoh; - short *ddbase; - - int *cr=0; /* array for random permutation of clusters */ - int c0,c1; - - //opts[0]=LM_INIT_MU; opts[1]=1E-15; opts[2]=1E-15; opts[3]=1E-20; - opts[0]=CLM_INIT_MU; opts[1]=1E-9; opts[2]=1E-9; opts[3]=1E-9; - opts[4]=-CLM_DIFF_DELTA; - - /* robust */ - double robust_nu0; - double *robust_nuM; - - /* no. of parameters >= than the no of clusters*8N */ - m=N*Mt*8; - /* no of data */ - n=Nbase*tilesz*8; - - /* true no of baselines */ - Nbase1=Nbase*tilesz; - - float *ddcohf, *pf, *Yf, *Zf, *xdummy0f, *xdummy1f; -/********* thread data ******************/ - /* barrier */ - th_pipeline tp; - gbdatafl_admm tpg; -/****************************************/ - - /* use full parameter space */ - p=pp; - lmdata0.clus=lmdata1.clus=-1; - /* setup data for lmfit */ - lmdata0.u=lmdata1.u=u; - lmdata0.v=lmdata1.v=v; - lmdata0.w=lmdata1.w=w; - lmdata0.Nbase=lmdata1.Nbase=Nbase; - lmdata0.tilesz=lmdata1.tilesz=tilesz; - lmdata0.N=lmdata1.N=N; - lmdata0.barr=lmdata1.barr=barr; - lmdata0.carr=lmdata1.carr=carr; - lmdata0.M=lmdata1.M=M; - lmdata0.Mt=lmdata1.Mt=Mt; - lmdata0.freq0=lmdata1.freq0=&freq0; - lmdata0.Nt=lmdata1.Nt=Nt; - lmdata0.coh=lmdata1.coh=coh; - /* rearrange coh for GPU use */ - if ((ddcoh=(double*)calloc((size_t)(M*Nbase1*8),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((ddcohf=(float*)calloc((size_t)(M*Nbase1*8),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((ddbase=(short*)calloc((size_t)(Nbase1*2),sizeof(short)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - rearrange_coherencies(Nbase1, barr, coh, ddcoh, ddbase, M, Nt); - lmdata0.ddcoh=lmdata1.ddcoh=ddcoh; - lmdata0.ddbase=lmdata1.ddbase=ddbase; - - /* ddcohf (float) << ddcoh (double) */ - double_to_float(ddcohf,ddcoh,M*Nbase1*8,Nt); - lmdata0.ddcohf=lmdata1.ddcohf=ddcohf; - - if ((xsub=(double*)calloc((size_t)(n),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xo=(double*)calloc((size_t)(n),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xdummy0=(double*)calloc((size_t)(n),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xdummy1=(double*)calloc((size_t)(n),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((nerr=(double*)calloc((size_t)(M),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xdummy0f=(float*)calloc((size_t)(n),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xdummy1f=(float*)calloc((size_t)(n),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((pf=(float*)calloc((size_t)(Mt*8*N),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((Yf=(float*)calloc((size_t)(Mt*8*N),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((Zf=(float*)calloc((size_t)(Mt*8*N),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((robust_nuM=(double*)calloc((size_t)(M),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - /* starting guess of robust nu */ - robust_nu0=nulow; - - double_to_float(pf,p,Mt*8*N,Nt); - double_to_float(Yf,Y,Mt*8*N,Nt); - double_to_float(Zf,BZ,Mt*8*N,Nt); - /* remember for each partition how much the cost function decreases - in the next EM iteration, we allocate more LM iters to partitions - where const function significantly decreases. So two stages - 1) equal LM iters (find the decrease) 2) weighted LM iters */ - weighted_iter=0; - total_iter=M*max_iter; /* total iterations per EM */ -/********** setup threads *******************************/ - init_pipeline_admm_flt(&tp,&tpg); - sync_barrier(&(tp.gate1)); /* sync at gate 1*/ - tpg.status[0]=tpg.status[1]=PT_DO_AGPU; - /* also calculate the total storage needed to be allocated on a GPU */ - /* determine total size for memory allocation */ - int64_t data_sz=0; - /* size for RTR/NSD (float), 128 is the ThreadsPerBlock - NSD is a bit lower - Use dummy data size - */ - if (solver_mode==SM_NSD_RLBFGS) { - //data_sz=(8*N*(7+(Nbase1+128-1)/128)+N+8*Nbase1*2+3*Nbase1)*sizeof(float); - data_sz=8*sizeof(float); - } else { /* default is RTR */ - //data_sz=(8*N*(11+(Nbase1+128-1)/128)+N+8*Nbase1*2+3*Nbase1)*sizeof(float); - data_sz=8*sizeof(float); - } - - tpg.data_size=data_sz; - tpg.nulow=nulow; - tpg.nuhigh=nuhigh; - tpg.randomize=randomize; - sync_barrier(&(tp.gate2)); /* sync at gate 2*/ - - sync_barrier(&(tp.gate1)); /* sync at gate 1*/ - tpg.status[0]=tpg.status[1]=PT_DO_NOTHING; - sync_barrier(&(tp.gate2)); /* sync at gate 2*/ - -/********** done setup threads *******************************/ - - /* initial residual calculation - subtract full model from data */ - minimize_viz_full_pth(p, xsub, m, n, (void*)&lmdata0); - memcpy(xo,x,(size_t)(n)*sizeof(double)); - my_daxpy(n, xsub, -1.0, xo); - *res_0=my_dnrm2(n,xo)/(double)n; - - int iter_bar=(int)ceil((0.80/(double)M)*((double)total_iter)); - for (ci=0; ci1) { - /* find a random permutation of clusters */ - cr=random_permutation(M,weighted_iter,nerr); - } else { - cr=NULL; - } - - for (cj=0; cj0 || this_itermax1>0) { - /* calculate contribution from hidden data, subtract from x */ - /* since x has already subtracted this model, just add - the ones we are solving for */ - - sync_barrier(&(tp.gate1)); /* sync at gate 1 */ - memcpy(xdummy0,xo,(size_t)(n)*sizeof(double)); - memcpy(xdummy1,xo,(size_t)(n)*sizeof(double)); - lmdata0.clus=c0; - lmdata1.clus=c1; - - /* NOTE: conditional mean x^i = s^i + 0.5 * residual^i */ - /* so xdummy=0.5 ( 2*model + residual ) */ - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata0); - my_daxpy(n, xsub, 2.0, xdummy0); - my_dscal(n, 0.5, xdummy0); - my_daxpy(n, xsub, 1.0, xo); - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata1); - my_daxpy(n, xsub, 2.0, xdummy1); - my_dscal(n, 0.5, xdummy1); - my_daxpy(n, xsub, 1.0, xo); -/**************************************************************************/ - /* xdummy*f (float) << xdummy* (double) */ - double_to_float(xdummy0f,xdummy0,n,Nt); - double_to_float(xdummy1f,xdummy1,n,Nt); - /* run this from a separate thread */ - tpg.p[0]=&pf[carr[c0].p[0]]; /* length carr[c0].nchunk times */ - tpg.Y[0]=&Yf[carr[c0].p[0]]; /* length carr[c0].nchunk times */ - tpg.Z[0]=&Zf[carr[c0].p[0]]; /* length carr[c0].nchunk times */ - tpg.admm_rho[0]=(float)admm_rho[c0]; - tpg.x[0]=xdummy0f; - tpg.M[0]=8*N; /* even though size of p is > M, dont change this */ - tpg.N[0]=n; /* Nbase*tilesz*8 */ - tpg.itermax[0]=this_itermax0; - tpg.opts[0]=opts; - tpg.info[0]=info0; - tpg.linsolv=linsolv; - tpg.lmdata[0]=&lmdata0; - - tpg.p[1]=&pf[carr[c1].p[0]]; /* length carr[c1].nchunk times */ - tpg.Y[1]=&Yf[carr[c1].p[0]]; /* length carr[c1].nchunk times */ - tpg.Z[1]=&Zf[carr[c1].p[0]]; /* length carr[c1].nchunk times */ - tpg.admm_rho[1]=(float)admm_rho[c1]; - tpg.x[1]=xdummy1f; - tpg.M[1]=8*N; /* even though size of p is > M, dont change this */ - tpg.N[1]=n; /* Nbase*tilesz*8 */ - tpg.itermax[1]=this_itermax1; - tpg.opts[1]=opts; - tpg.info[1]=info1; - tpg.linsolv=linsolv; - tpg.lmdata[1]=&lmdata1; -/**************************************************************************/ - - /* both threads do work */ - if (solver_mode==SM_NSD_RLBFGS) { - tpg.status[0]=tpg.status[1]=PT_DO_WORK_NSD; - } else { - tpg.status[0]=tpg.status[1]=PT_DO_WORK_RRTR; - } - sync_barrier(&(tp.gate2)); /* sync at gate 2 */ - sync_barrier(&(tp.gate1)); /* sync at gate 1 */ - tpg.status[0]=tpg.status[1]=PT_DO_NOTHING; - sync_barrier(&(tp.gate2)); /* sync at gate 2 */ -#ifdef DEBUG -printf("1: %lf -> %lf 2: %lf -> %lf\n\n\n",info0[0],info0[1],info1[0],info1[1]); -#endif - /* catch -ve value here */ - if (info0[0]>0.0) { - nerr[c0]=(info0[0]-info0[1])/info0[0]; - if (nerr[c0]<0.0) { nerr[c0]=0.0; } - } else { - nerr[c0]=0.0; - } - if (info1[0]>0.0) { - nerr[c1]=(info1[0]-info1[1])/info1[0]; - if (nerr[c1]<0.0) { nerr[c1]=0.0; } - } else { - nerr[c1]=0.0; - } - /* update robust_nu */ - robust_nuM[c0]+=lmdata0.robust_nu; - robust_nuM[c1]+=lmdata1.robust_nu; - /* p (double) << pf (float) */ - float_to_double(&p[carr[c0].p[0]],&pf[carr[c0].p[0]],carr[c0].nchunk*8*N,Nt); - float_to_double(&p[carr[c1].p[0]],&pf[carr[c1].p[0]],carr[c1].nchunk*8*N,Nt); - /* once again subtract solved model from data */ - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata0); - my_daxpy(n, xsub, -1.0, xo); - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata1); - my_daxpy(n, xsub, -1.0, xo); - - } - } - /* odd cluster out, if M is odd */ - if (M%2) { - if (randomize && M>1) { - c0=cr[M-1]; - } else { - c0=M-1; - } - /* calculate max LM iter for this cluster */ - if (weighted_iter) { - this_itermax0=(int)((0.20*nerr[c0])*((double)total_iter))+iter_bar; - } else { - this_itermax0=max_iter; - } -#ifdef DEBUG - printf("Cluster %d(iter=%d, wt=%lf)\n",c0,this_itermax0,nerr[c0]); -#endif - if (this_itermax0>0) { -/**************************************************************************/ - sync_barrier(&(tp.gate1)); /* sync at gate 1 */ - /* calculate contribution from hidden data, subtract from x */ - memcpy(xdummy0,xo,(size_t)(n)*sizeof(double)); - lmdata0.clus=c0; - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata0); - my_daxpy(n, xsub, 1.0, xdummy0); - my_daxpy(n, xsub, 1.0, xo); - - double_to_float(xdummy0f,xdummy0,n,Nt); - /* run this from a separate thread */ - tpg.p[0]=&pf[carr[c0].p[0]]; - tpg.Y[0]=&Yf[carr[c0].p[0]]; - tpg.Z[0]=&Zf[carr[c0].p[0]]; - tpg.admm_rho[0]=(float)admm_rho[c0]; - tpg.x[0]=xdummy0f; - tpg.M[0]=8*N; - tpg.N[0]=n; - tpg.itermax[0]=this_itermax0; - tpg.opts[0]=opts; - tpg.info[0]=info0; - tpg.linsolv=linsolv; - tpg.lmdata[0]=&lmdata0; - - if (solver_mode==SM_NSD_RLBFGS) { - tpg.status[0]=PT_DO_WORK_NSD; - } else { - tpg.status[0]=PT_DO_WORK_RRTR; - } - - tpg.status[1]=PT_DO_NOTHING; - sync_barrier(&(tp.gate2)); /* sync at gate 2 */ -/**************************************************************************/ - sync_barrier(&(tp.gate1)); /* sync at gate 1 */ - tpg.status[0]=tpg.status[1]=PT_DO_NOTHING; - sync_barrier(&(tp.gate2)); /* sync at gate 2 */ - -#ifdef DEBUG -printf("1: %lf -> %lf\n\n\n",info0[0],info0[1]); -#endif - /* catch -ve value here */ - if (info0[0]>0.0) { - nerr[c0]=(info0[0]-info0[1])/info0[0]; - if (nerr[c0]<0.0) { nerr[c0]=0.0; } - } else { - nerr[c0]=0.0; - } - /* update robust_nu */ - robust_nuM[c0]+=lmdata0.robust_nu; - /* once again subtract solved model from data */ - float_to_double(&p[carr[c0].p[0]],&pf[carr[c0].p[0]],carr[c0].nchunk*8*N,Nt); - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata0); - my_daxpy(n, xsub, -1.0, xo); - } - } - /* normalize nerr array so that the sum is 1 */ - total_err=my_dasum(M,nerr); - if (total_err>0.0) { - my_dscal(M, 1.0/total_err, nerr); - } - if (randomize && M>1) { /* nothing to randomize if only 1 direction */ - /* flip weighting flag */ - weighted_iter=!weighted_iter; - free(cr); - } - /**************** End EM iteration ***********************/ - } - free(nerr); - free(xo); - free(xdummy0); - free(xdummy1); - free(ddcoh); - free(ddbase); - free(xdummy0f); - free(xdummy1f); - free(pf); - free(Yf); - free(Zf); - free(ddcohf); - /* calculate mean robust_nu over all clusters */ - robust_nu0=my_dasum(M,robust_nuM)/(double)M; -#ifdef DEBUG - for (ci=0; cinuhigh) { - robust_nu0=nuhigh; - } - - /******** free threads ***************/ - sync_barrier(&(tp.gate1)); /* sync at gate 1*/ - tpg.status[0]=tpg.status[1]=PT_DO_DGPU; - sync_barrier(&(tp.gate2)); /* sync at gate 2*/ - - - destroy_pipeline_admm_flt(&tp); - /******** done free threads ***************/ - - /* final residual calculation */ - minimize_viz_full_pth(p, xsub, m, n, (void*)&lmdata0); - my_daxpy(n, xsub, -1.0, x); - - *mean_nu=robust_nu0; - *res_1=my_dnrm2(n,x)/(double)n; - - free(xsub); - - /* if final residual > initial residual, - return -1, else 0 - */ - if (*res_1>*res_0) { - return -1; - } - return 0; -} - - -int -sagefit_visibilities_admm_dual_pt_flt_one(double *u, double *v, double *w, double *x, int N, - int Nbase, int tilesz, baseline_t *barr, clus_source_t *carr, complex double *coh, int M, int Mt, double freq0, double fdelta, double *pp, double *Y, double *BZ, double uvmin, int Nt, int max_emiter, int max_iter, int max_lbfgs, int lbfgs_m, int gpu_threads, int linsolv,int solver_mode, double nulow, double nuhigh, int randomize,double *admm_rho, double *mean_nu, double *res_0, double *res_1) { - - - int ci,cj; - double *p; // parameters: m x 1 - int m, n; - double opts[CLM_OPTS_SZ], info0[CLM_INFO_SZ]; - me_data_t lmdata0,lmdata1; - int Nbase1; - - double *xdummy0,*xdummy1,*xsub,*xo; - double *nerr; /* array to store cost reduction per cluster */ - int weighted_iter,this_itermax0,total_iter; - double total_err; - - /* rearraged memory for GPU use */ - double *ddcoh; - short *ddbase; - - int *cr=0; /* array for random permutation of clusters */ - int c0; - - //opts[0]=LM_INIT_MU; opts[1]=1E-15; opts[2]=1E-15; opts[3]=1E-20; - opts[0]=CLM_INIT_MU; opts[1]=1E-9; opts[2]=1E-9; opts[3]=1E-9; - opts[4]=-CLM_DIFF_DELTA; - - /* robust */ - double robust_nu0; - double *robust_nuM; - - /* no. of parameters >= than the no of clusters*8N */ - m=N*Mt*8; - /* no of data */ - n=Nbase*tilesz*8; - - /* true no of baselines */ - Nbase1=Nbase*tilesz; - - float *ddcohf, *pf, *Yf, *Zf, *xdummy0f, *xdummy1f; -/********* thread data ******************/ - /* barrier */ - th_pipeline tp; - gbdatafl_admm tpg; -/****************************************/ - - /* use full parameter space */ - p=pp; - lmdata0.clus=lmdata1.clus=-1; - /* setup data for lmfit */ - lmdata0.u=lmdata1.u=u; - lmdata0.v=lmdata1.v=v; - lmdata0.w=lmdata1.w=w; - lmdata0.Nbase=lmdata1.Nbase=Nbase; - lmdata0.tilesz=lmdata1.tilesz=tilesz; - lmdata0.N=lmdata1.N=N; - lmdata0.barr=lmdata1.barr=barr; - lmdata0.carr=lmdata1.carr=carr; - lmdata0.M=lmdata1.M=M; - lmdata0.Mt=lmdata1.Mt=Mt; - lmdata0.freq0=lmdata1.freq0=&freq0; - lmdata0.Nt=lmdata1.Nt=Nt; - lmdata0.coh=lmdata1.coh=coh; - /* rearrange coh for GPU use */ - if ((ddcoh=(double*)calloc((size_t)(M*Nbase1*8),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((ddcohf=(float*)calloc((size_t)(M*Nbase1*8),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((ddbase=(short*)calloc((size_t)(Nbase1*2),sizeof(short)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - rearrange_coherencies(Nbase1, barr, coh, ddcoh, ddbase, M, Nt); - lmdata0.ddcoh=lmdata1.ddcoh=ddcoh; - lmdata0.ddbase=lmdata1.ddbase=ddbase; - - /* ddcohf (float) << ddcoh (double) */ - double_to_float(ddcohf,ddcoh,M*Nbase1*8,Nt); - lmdata0.ddcohf=lmdata1.ddcohf=ddcohf; - - if ((xsub=(double*)calloc((size_t)(n),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xo=(double*)calloc((size_t)(n),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xdummy0=(double*)calloc((size_t)(n),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xdummy1=(double*)calloc((size_t)(n),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((nerr=(double*)calloc((size_t)(M),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xdummy0f=(float*)calloc((size_t)(n),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xdummy1f=(float*)calloc((size_t)(n),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((pf=(float*)calloc((size_t)(Mt*8*N),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((Yf=(float*)calloc((size_t)(Mt*8*N),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((Zf=(float*)calloc((size_t)(Mt*8*N),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((robust_nuM=(double*)calloc((size_t)(M),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - /* starting guess of robust nu */ - robust_nu0=nulow; - - double_to_float(pf,p,Mt*8*N,Nt); - double_to_float(Yf,Y,Mt*8*N,Nt); - double_to_float(Zf,BZ,Mt*8*N,Nt); - /* remember for each partition how much the cost function decreases - in the next EM iteration, we allocate more LM iters to partitions - where const function significantly decreases. So two stages - 1) equal LM iters (find the decrease) 2) weighted LM iters */ - weighted_iter=0; - total_iter=M*max_iter; /* total iterations per EM */ -/********** setup threads *******************************/ - init_pipeline_admm_flt(&tp,&tpg); - sync_barrier(&(tp.gate1)); /* sync at gate 1*/ - tpg.status[0]=tpg.status[1]=PT_DO_AGPU; - /* also calculate the total storage needed to be allocated on a GPU */ - /* determine total size for memory allocation */ - int64_t data_sz=0; - /* size for RTR/NSD (float), 128 is the ThreadsPerBlock - NSD is a bit lower, but use the same - */ - //data_sz=(8*N*(11+(Nbase1+128-1)/128)+N+8*Nbase1*2+3*Nbase1)*sizeof(float); - data_sz=8*sizeof(float); - - tpg.data_size=data_sz; - tpg.nulow=nulow; - tpg.nuhigh=nuhigh; - tpg.randomize=randomize; - sync_barrier(&(tp.gate2)); /* sync at gate 2*/ - - sync_barrier(&(tp.gate1)); /* sync at gate 1*/ - tpg.status[0]=tpg.status[1]=PT_DO_NOTHING; - sync_barrier(&(tp.gate2)); /* sync at gate 2*/ - -/********** done setup threads *******************************/ - - /* initial residual calculation - subtract full model from data */ - minimize_viz_full_pth(p, xsub, m, n, (void*)&lmdata0); - memcpy(xo,x,(size_t)(n)*sizeof(double)); - my_daxpy(n, xsub, -1.0, xo); - *res_0=my_dnrm2(n,xo)/(double)n; - - int iter_bar=(int)ceil((0.80/(double)M)*((double)total_iter)); - for (ci=0; ci1) { - /* find a random permutation of clusters */ - cr=random_permutation(M,weighted_iter,nerr); - } else { - cr=NULL; - } - - /* only one cluster at a time */ - for (cj=0; cj1) { - c0=cr[cj]; - } else { - c0=cj; - } - /* calculate max LM iter for this cluster */ - if (weighted_iter) { - this_itermax0=(int)((0.20*nerr[c0])*((double)total_iter))+iter_bar; - } else { - this_itermax0=max_iter; - } -#ifdef DEBUG - printf("Cluster %d(iter=%d, wt=%lf)\n",c0,this_itermax0,nerr[c0]); -#endif - if (this_itermax0>0) { -/**************************************************************************/ - sync_barrier(&(tp.gate1)); /* sync at gate 1 */ - /* calculate contribution from hidden data, subtract from x */ - memcpy(xdummy0,xo,(size_t)(n)*sizeof(double)); - lmdata0.clus=c0; - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata0); - my_daxpy(n, xsub, 1.0, xdummy0); - my_daxpy(n, xsub, 1.0, xo); - - double_to_float(xdummy0f,xdummy0,n,Nt); - /* run this from a separate thread */ - tpg.p[0]=&pf[carr[c0].p[0]]; - tpg.Y[0]=&Yf[carr[c0].p[0]]; - tpg.Z[0]=&Zf[carr[c0].p[0]]; - tpg.admm_rho[0]=(float)admm_rho[c0]; - tpg.x[0]=xdummy0f; - tpg.M[0]=8*N; - tpg.N[0]=n; - tpg.itermax[0]=this_itermax0; - tpg.opts[0]=opts; - tpg.info[0]=info0; - tpg.linsolv=linsolv; - tpg.lmdata[0]=&lmdata0; - - tpg.status[0]=PT_DO_WORK_RRTR; - - tpg.status[1]=PT_DO_NOTHING; - sync_barrier(&(tp.gate2)); /* sync at gate 2 */ -/**************************************************************************/ - sync_barrier(&(tp.gate1)); /* sync at gate 1 */ - tpg.status[0]=tpg.status[1]=PT_DO_NOTHING; - sync_barrier(&(tp.gate2)); /* sync at gate 2 */ - -#ifdef DEBUG -printf("1: %lf -> %lf\n\n\n",info0[0],info0[1]); -#endif - /* catch -ve value here */ - if (info0[0]>0.0) { - nerr[c0]=(info0[0]-info0[1])/info0[0]; - if (nerr[c0]<0.0) { nerr[c0]=0.0; } - } else { - nerr[c0]=0.0; - } - /* update robust_nu */ - robust_nuM[c0]+=lmdata0.robust_nu; - /* once again subtract solved model from data */ - float_to_double(&p[carr[c0].p[0]],&pf[carr[c0].p[0]],carr[c0].nchunk*8*N,Nt); - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata0); - my_daxpy(n, xsub, -1.0, xo); - } - } - /* normalize nerr array so that the sum is 1 */ - total_err=my_dasum(M,nerr); - if (total_err>0.0) { - my_dscal(M, 1.0/total_err, nerr); - } - if (randomize && M>1) { /* nothing to randomize if only 1 direction */ - /* flip weighting flag */ - weighted_iter=!weighted_iter; - free(cr); - } - /**************** End EM iteration ***********************/ - } - free(nerr); - free(xo); - free(xdummy0); - free(xdummy1); - free(ddcoh); - free(ddbase); - free(xdummy0f); - free(xdummy1f); - free(pf); - free(Yf); - free(Zf); - free(ddcohf); - /* calculate mean robust_nu over all clusters */ - robust_nu0=my_dasum(M,robust_nuM)/(double)M; -#ifdef DEBUG - for (ci=0; cinuhigh) { - robust_nu0=nuhigh; - } - - - /******** free threads ***************/ - sync_barrier(&(tp.gate1)); /* sync at gate 1*/ - tpg.status[0]=tpg.status[1]=PT_DO_DGPU; - sync_barrier(&(tp.gate2)); /* sync at gate 2*/ - - - destroy_pipeline_admm_flt(&tp); - /******** done free threads ***************/ - - /* final residual calculation */ - minimize_viz_full_pth(p, xsub, m, n, (void*)&lmdata0); - my_daxpy(n, xsub, -1.0, x); - - *mean_nu=robust_nu0; - *res_1=my_dnrm2(n,x)/(double)n; - - free(xsub); - - /* if final residual > initial residual, - return -1, else 0 - */ - if (*res_1>*res_0) { - return -1; - } - return 0; -} -#endif /* HYBRID_CODE */ diff --git a/src/lib/Solvers/admm_solve.o b/src/lib/Solvers/admm_solve.o deleted file mode 100644 index 05a852c0f8c0d34c236acc9ed0f10a80b7056b83..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14944 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⪚0-s^@oxuNINTmhJmb}se&t_+7fj8qSE%0r+B2?gi{K`;4pljNqg1$WG&!!aw z$_o6U0v|{~3YO9WH;yD=Z!ly%h&ALgvKANgIhO<|-I=mKTgTBL!pl zb-LCR09}=b2!sp#WfQ|0x;pPe3waC3^L}9gNk-nEEFe*mm$-`B*ES_0b)#-LI&R2o~((l1{LY4YpAMkt7)p^R{fvvUPz$uqq3QB1&z1zSrcH1 z9m)mhMznDZopel^qHZibn4;K__V2!u=Za(j~;U6;i{T8`q z{LSY>2EUa~navrkcA0jY&vt{~qDGo+@LO?{&%`$x{Io2Im(Sp*ie0=+yG^VM4QozN z-*5@1=->~Qf0T+QznOp2fmw5hSiCCTCF!5|BYU@)!^X~PI}so7aTd2>oQ&5S@U!a2 zU)QG8-$#j!>&F&_mRC+R#hvI}(g(DiPP&}T>Pax{m?{!v-&7GMoSi-<2%>qOC&*`e zp+VuCG|ME3!bJ3^XwSsO_B%Yw@FP4(djc-DZ{b;uAK|<5(V(``Pi?1U2%#6@a%Q70 zfM_};fm!HsO2(okV2`o(WcewEo1>az&<)($v&F#8`D=1KVc=$7n)p8(xYq=X=T8ir z;%B{_5@prI>)-Hd)kzflZSX@j_^USfTQ>NIHaPvO?Ns($Z-d`xgU`0X=i1=60lx0mis(wu( zk?Sm$Y$Y;EsVG{8Lrt`-gth$a&Vb_ES^ExlpD$HaHLXe1#_y1zmrSdwNhaH>pg1`Z zX|GGx*5TBHGgeKqPHL)2BwFHGk~mm_nlc`0fn(+i6Ife^b5_e*i7E!E9irOWr25vj z#^z+bRM!Dz?KKb9Q4}6-Y;RoEm}pEk*0onr-66%fDwG-V*0l&%Rc%d&(P(aKx?PIb zH72As7|jZls^*sXn%bJGww6_E+LK%Y<7r>h#F*P5kMhi8EUmOx&^~~om|cr(@TY9> z7i{o=u|!+t*Vy1cvB96V!QU2e+RIG)Z!@+4tKO)9i}Vi*xEP0wfQ$71E#M;ka{?~X zyNrXJ)t(X?JYj=BX@fswgU>P+deTd4xEY^Y1^h1r{7wP?nt-pc!EZB`blR~Q zIAL@aH}(Efz^N`Z@k0WBlYswT!2eRf-xu(i0zPKoWRJKjUb5iqy98*=eVXj_;%C~E zZ*0MY&l2!!1zgm7lYv|H&bHvD-Z_FCQSTiBF6vDQxHyhm1za4*9}BoRj=eVc4~(M) zb(7ELIp$XeZXL&K&2tWD#c`Z1;Nm#W6L4`Hmk78R&*cVA_K4&7_ZHlYXO$qw3m$VE z8wFg9XRCmVde>RxLlnCnv*4!Q#|1fFP?~zT3b?5E=X9{gMe!8ZjaO{&ZyAM-mA=Ua z|D6qf!3MwUT4v59dsf@vTLoNP-%bg*=+|ih7wHR)4^~#czA4}${e1#1%5M{Jk^V^m z7wMm~!KYm}bsX-r!5^~0dr28C^6TsPnfdU74SvxEzs@*tTjjI}xGKp1o`BQ&)ztd~ z0l!(mpAhhG2>43^PAO^1`9Q#L5%3?Mb=sj*^@?f}BbL7wumy;By7~-2yJIZ$G!e zj|#Y`_ay-rf?!AUn=Zwqp~ zcsIvoM8L)Mu$GL#WgYi~fQ#ckDB$9_FCwE2IU@Zs0T=V?4+1XAS?gtDm_4GL4FWF8 zc~8JaIj4>D2Kgn%IV|i8B5sIbdWpxp5=MhC`kCJ&dM&v5JtE)1J4||WAE$pPq07Y0 HI`e-42}*XZ diff --git a/src/lib/Solvers/barrier.c b/src/lib/Solvers/barrier.c deleted file mode 100644 index d450cec..0000000 --- a/src/lib/Solvers/barrier.c +++ /dev/null @@ -1,121 +0,0 @@ -/* - * - Copyright (C) 2006-2008 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - -#include -#include -#include -#include - -#include "Solvers.h" -/* implementation of a barrier to sync threads. - The barrier has two doors (enter and exit). Only one door - can be open at a time. Initially the enter door is open. - All threads that enter the barrier are sleeping (wait). - The last thread to enter the barrier will - 1)close the enter door - 2)wakeup all sleeping threads. - 3)open the exit door. - So the woken up threads will leave the barrier one by - one, as they are awoken. The last thread to leave the barrier - will - 1)open the enter door - 2)close the exit door, - So finally the barrier reaches its initial state -*/ - -/* initialize barrier */ -/* N - no. of accomodated threads */ -void -init_th_barrier(th_barrier *barrier, int N) -{ - barrier->tcount=0; /* initially empty */ - barrier->nthreads=N; - pthread_mutex_init(&barrier->enter_mutex,NULL); - pthread_mutex_init(&barrier->exit_mutex,NULL); - pthread_cond_init(&barrier->lastthread_cond,NULL); - pthread_cond_init(&barrier->exit_cond,NULL); -} -/* destroy barrier */ -void -destroy_th_barrier(th_barrier *barrier) -{ - pthread_mutex_destroy(&barrier->enter_mutex); - pthread_mutex_destroy(&barrier->exit_mutex); - pthread_cond_destroy(&barrier->lastthread_cond); - pthread_cond_destroy(&barrier->exit_cond); - barrier->tcount=barrier->nthreads=0; -} -/* the main operation of the barrier */ -void -sync_barrier(th_barrier *barrier) -{ - /* trivial case */ - if(barrier->nthreads <2) return; - /* else */ - /* new threads enters the barrier. Now close the entry door - so that other threads cannot enter the barrier until we are done */ - pthread_mutex_lock(&barrier->enter_mutex); - /* next lock the exit mutex - no threads can leave either */ - pthread_mutex_lock(&barrier->exit_mutex); - /* now check to see if this is the last expected thread */ - if( ++(barrier->tcount) < barrier->nthreads) { - /* no. this is not the last thread. so open the entry door */ - pthread_mutex_unlock(&barrier->enter_mutex); - /* go to sleep */ - pthread_cond_wait(&barrier->exit_cond,&barrier->exit_mutex); - } else { - /* this is the last thread */ - /* wakeup sleeping threads */ - pthread_cond_broadcast(&barrier->exit_cond); - /* go to sleep until all threads are woken up - and leave the barrier */ - pthread_cond_wait(&barrier->lastthread_cond,&barrier->exit_mutex); -/* now all threads have left the barrier. so open the entry door again */ - pthread_mutex_unlock(&barrier->enter_mutex); - } - /* next to the last thread leaving the barrier */ - if(--(barrier->tcount)==1) { - /* wakeup the last sleeping thread */ - pthread_cond_broadcast(&barrier->lastthread_cond); - } - pthread_mutex_unlock(&barrier->exit_mutex); -} - - - -/* master and two slaves */ -//int -//main(int argc, char *argv[]) { -// th_pipeline p; -// -// gbdata g; -// -// init_pipeline(&p,&g); -//sync_barrier(&(p.gate1)); /* stop at gate 1 */ -// g.status=0; /* master work */ -//sync_barrier(&(p.gate2)); /* stop at gate 2 */ -// //exec_pipeline(&p); -//sync_barrier(&(p.gate1)); /* stop at gate 1 */ -// g.status=10; /* master work */ -//sync_barrier(&(p.gate2)); /* stop at gate 2 */ -// //exec_pipeline(&p); -// destroy_pipeline(&p); -// /* still need to free slave_data structs, from data */ -// return 0; -//} diff --git a/src/lib/Solvers/barrier.o b/src/lib/Solvers/barrier.o deleted file mode 100644 index 1cbd92de9233af514b0c78aa8860c9979fdab547..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11496 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 b|GrA&NIXe6>W20C-p6<+>1`JJJBj} - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#include -#include -#include -#include -#include -#include - -#include "Solvers.h" -#include - -//#define DEBUG -/* helper functions for diagnostics */ -static void -checkCudaError(cudaError_t err, char *file, int line) -{ -#ifdef CUDA_DEBUG - if(!err) - return; - fprintf(stderr,"GPU (CUDA): %s %s %d\n", cudaGetErrorString(err),file,line); - exit(EXIT_FAILURE); -#endif -} - - -static void -checkCublasError(cublasStatus_t cbstatus, char *file, int line) -{ -#ifdef CUDA_DEBUG - if (cbstatus!=CUBLAS_STATUS_SUCCESS) { - fprintf(stderr,"%s: %d: CUBLAS failure\n",file,line); - exit(EXIT_FAILURE); - } -#endif -} - - - -/** keep interface almost the same as in levmar **/ -int -clevmar_der_single( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - * info[5]= # iterations, - * info[6]=reason for terminating: 1 - stopped by small gradient J^T e - * 2 - stopped by small Dp - * 3 - stopped by itmax - * 4 - singular matrix. Restart from current p with increased mu - * 5 - no further error reduction is possible. Restart with increased mu - * 6 - stopped by small ||e||_2 - * 7 - stopped by invalid (i.e. NaN or Inf) "func" values. This is a user error - * info[7]= # function evaluations - * info[8]= # Jacobian evaluations - * info[9]= # linear systems solved, i.e. # attempts for reducing error - */ - - int card, /* device 0, 1 */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - void *adata) /* pointer to possibly additional data, passed uninterpreted to func & jacf. - * Set to NULL if not needed - */ -{ - - /* general note: all device variables end with a 'd' */ - int stop=0; - cudaError_t err; - cublasStatus_t cbstatus; - cublasHandle_t cbhandle; - cusolverDnHandle_t solver_handle; - - int nu=2,nu2; - double p_L2, Dp_L2=DBL_MAX, dF, dL, p_eL2, jacTe_inf=0.0, pDp_eL2, init_p_eL2; - double tmp,mu=0.0; - double tau, eps1, eps2, eps2_sq, eps3; - int k,ci,issolved; - - double *hx; - double *hxd; - - double *ed; - double *xd; - - double *jac,*jacd; - - double *jacTjacd,*jacTjacd0; - - double *pnew,*Dpd,*bd; - double *pd,*pnewd; - double *jacTed; - - /* used in QR solver */ - double *taud; - - /* used in SVD solver */ - double *Ud; - double *VTd; - double *Sd; - - int solve_axb=linsolv; - - /* setup default settings */ - if(opts){ - tau=opts[0]; - eps1=opts[1]; - eps2=opts[2]; - eps2_sq=opts[2]*opts[2]; - eps3=opts[3]; - } else { - tau=CLM_INIT_MU; - eps1=CLM_STOP_THRESH; - eps2=CLM_STOP_THRESH; - eps2_sq=CLM_STOP_THRESH*CLM_STOP_THRESH; - eps3=CLM_STOP_THRESH; - } - - /* calculate no of cuda threads and blocks */ - int ThreadsPerBlock=DEFAULT_TH_PER_BK; - int BlocksPerGrid= 2*(M+ThreadsPerBlock-1)/ThreadsPerBlock; - - err=cudaSetDevice(card); - checkCudaError(err,__FILE__,__LINE__); - cusolverDnCreate(&solver_handle); - - cbstatus=cublasCreate(&cbhandle); - if (cbstatus!=CUBLAS_STATUS_SUCCESS) { - fprintf(stderr,"%s: %d: CUBLAS create fail\n",__FILE__,__LINE__); - exit(1); - } - - err=cudaMalloc((void**)&xd, N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - /* xd <=x */ - err=cudaMemcpy(xd, x, N*sizeof(double), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - - - if ((hx=(double*)calloc((size_t)N,sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((jac=(double*)calloc((size_t)N*M,sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((pnew=(double*)calloc((size_t)M,sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - - - err=cudaMalloc((void**)&jacd, M*N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&jacTjacd, M*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&jacTed, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&jacTjacd0, M*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&Dpd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&bd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&pd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&pnewd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMemcpy(pd, p, M*sizeof(double), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - - - /* memory allocation: different solvers */ - int work_size=0; - int *devInfo; - int devInfo_h=0; - err=cudaMalloc((void**)&devInfo, sizeof(int)); - checkCudaError(err,__FILE__,__LINE__); - double *work; - double *rwork; - if (solve_axb==0) { - cusolverDnDpotrf_bufferSize(solver_handle, CUBLAS_FILL_MODE_UPPER, M, jacTjacd, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } else if (solve_axb==1) { - err=cudaMalloc((void**)&taud, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - cusolverDnDgeqrf_bufferSize(solver_handle, M, M, jacTjacd, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } else { - err=cudaMalloc((void**)&Ud, M*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&VTd, M*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&Sd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - cusolverDnDgesvd_bufferSize(solver_handle, M, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&rwork, 5*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } - - - /* ### compute e=x - f(p) and its L2 norm */ - /* ### e=x-hx, p_eL2=||e|| */ - (*func)(p, hx, M, N, adata); - /* copy to device */ - err=cudaMalloc((void**)&hxd, N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - /* hxd<=hx */ - err=cudaMemcpy(hxd, hx, N*sizeof(double), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - - err=cudaMalloc((void**)&ed, N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - /* e=x */ - cbstatus=cublasDcopy(cbhandle, N, xd, 1, ed, 1); - /* e=x-hx */ - double alpha=-1.0; - cbstatus=cublasDaxpy(cbhandle, N, &alpha, hxd, 1, ed, 1); - - /* norm ||e|| */ - cbstatus=cublasDnrm2(cbhandle, N, ed, 1, &p_eL2); - /* square */ - p_eL2=p_eL2*p_eL2; - - init_p_eL2=p_eL2; - if(!finite(p_eL2)) stop=7; - - - /**** iteration loop ***********/ - for(k=0; k A+ mu*I, increment diagonal entries */ - /* copy jacTjacd<=jacTjacd0 */ - cbstatus=cublasDcopy(cbhandle, M*M, jacTjacd0, 1, jacTjacd, 1); - cudakernel_diagmu(ThreadsPerBlock, BlocksPerGrid, M, jacTjacd, mu); - -#ifdef DEBUG - printf("mu=%lf\n",mu); -#endif -/*************************************************************************/ - /* solve augmented equations A x = b */ - /* A==jacTjacd, b==Dpd, after solving, x==Dpd */ - /* b=jacTed : intially right hand side, at exit the solution */ - if (solve_axb==0) { - /* Cholesky solver **********************/ - /* lower triangle of Ad is destroyed */ - //status=culaDeviceDpotrf('U',M,jacTjacd,M); - cusolverDnDpotrf(solver_handle, CUBLAS_FILL_MODE_UPPER, M, jacTjacd, M, work, work_size, devInfo); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (!devInfo_h) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - fprintf(stderr,"Singular matrix\n"); -#endif - } - if (issolved) { - /* copy Dpd<=jacTed */ - cbstatus=cublasDcopy(cbhandle, M, jacTed, 1, Dpd, 1); -#ifdef DEBUG - checkCublasError(cbstatus); -#endif - //status=culaDeviceDpotrs('U',M,1,jacTjacd,M,Dpd,M); - cusolverDnDpotrs(solver_handle, CUBLAS_FILL_MODE_UPPER,M,1,jacTjacd,M,Dpd,M,devInfo); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (devInfo_h) { - issolved=0; -#ifdef DEBUG - fprintf(stderr,"Singular matrix\n"); -#endif - } - } - } else if (solve_axb==1) { - /* QR solver ********************************/ - //status=culaDeviceDgeqrf(M,M,jacTjacd,M,taud); - cusolverDnDgeqrf(solver_handle, M, M, jacTjacd, M, taud, work, work_size, devInfo); - cudaDeviceSynchronize(); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (!devInfo_h) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - fprintf(stderr,"Singular matrix\n"); -#endif - } - - if (issolved) { - /* copy Dpd<=jacTed */ - cbstatus=cublasDcopy(cbhandle, M, jacTed, 1, Dpd, 1); - //status=culaDeviceDgeqrs(M,M,1,jacTjacd,M,taud,Dpd,M); - cusolverDnDormqr(solver_handle, CUBLAS_SIDE_LEFT, CUBLAS_OP_T, M, 1, M, jacTjacd, M, taud, Dpd, M, work, work_size, devInfo); - cudaDeviceSynchronize(); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (devInfo_h) { - issolved=0; -#ifdef DEBUG - fprintf(stderr,"Singular matrix\n"); -#endif - } else { - cone=1.0; - cbstatus=cublasDtrsm(cbhandle,CUBLAS_SIDE_LEFT,CUBLAS_FILL_MODE_UPPER,CUBLAS_OP_N,CUBLAS_DIAG_NON_UNIT,M,1,&cone,jacTjacd,M,Dpd,M); - } - } - } else { - /* SVD solver *********************************/ - /* U S VT = A */ - //status=culaDeviceDgesvd('A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M); - //checkStatus(status,__FILE__,__LINE__); - cusolverDnDgesvd(solver_handle,'A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M,work,work_size,rwork,devInfo); - cudaDeviceSynchronize(); - /* copy Dpd<=jacTed */ - cbstatus=cublasDcopy(cbhandle, M, jacTed, 1, Dpd, 1); - /* b<=U^T * b */ - //status=culaDeviceDgemv('T',M,M,1.0,Ud,M,Dpd,1,0.0,Dpd,1); - //checkStatus(status,__FILE__,__LINE__); - cone=1.0; czero=0.0; - cbstatus=cublasDgemv(cbhandle,CUBLAS_OP_T,M,M,&cone,Ud,M,Dpd,1,&czero,Dpd,1); - /* get back the values jTjdiag<=Sd, pnew<=Dpd*/ -// err=cudaMemcpy(pnew,Dpd,M*sizeof(double),cudaMemcpyDeviceToHost); -// checkCudaError(err); -// err=cudaMemcpy(jTjdiag,Sd,M*sizeof(double),cudaMemcpyDeviceToHost); -// checkCudaError(err); - - /* robust correction */ -// for (ci=0; ci eps1 */ - cudakernel_diagdiv(ThreadsPerBlock, BlocksPerGrid, M, eps1, Dpd, Sd); - - /* b<=VT^T * b */ - //status=culaDeviceDgemv('T',M,M,1.0,VTd,M,Dpd,1,0.0,Dpd,1); - //checkStatus(status,__FILE__,__LINE__); - cbstatus=cublasDgemv(cbhandle,CUBLAS_OP_T,M,M,&cone,VTd,M,Dpd,1,&czero,Dpd,1); - - issolved=1; - } -/*************************************************************************/ - - /* compute p's new estimate and ||Dp||^2 */ - if (issolved) { - /* compute p's new estimate and ||Dp||^2 */ - /* pnew=p+Dp */ - /* pnew=p */ - cbstatus=cublasDcopy(cbhandle, M, pd, 1, pnewd, 1); - /* pnew=pnew+Dp */ - alpha=1.0; - cbstatus=cublasDaxpy(cbhandle, M, &alpha, Dpd, 1, pnewd, 1); - - /* copy back the solution to host */ - err=cudaMemcpy(pnew,pnewd,M*sizeof(double),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - - /* norm ||Dp|| */ - cbstatus=cublasDnrm2(cbhandle, M, Dpd, 1, &Dp_L2); - Dp_L2=Dp_L2*Dp_L2; - -#ifdef DEBUG -printf("norm ||dp|| =%lf, norm ||p||=%lf\n",Dp_L2,p_L2); -#endif - if(Dp_L2<=eps2_sq*p_L2){ /* relative change in p is small, stop */ - stop=2; - break; - } - - if(Dp_L2>=(p_L2+eps2)/(CLM_EPSILON*CLM_EPSILON)){ /* almost singular */ - stop=4; - break; - } - - /* new function value */ - (*func)(pnew, hx, M, N, adata); /* evaluate function at p + Dp */ - - /* compute ||e(pDp)||_2 */ - /* ### hx=x-hx, pDp_eL2=||hx|| */ - /* copy to device */ - /* hxd<=hx */ - err=cudaMemcpy(hxd, hx, N*sizeof(double), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - - /* e=x */ - cbstatus=cublasDcopy(cbhandle, N, xd, 1, ed, 1); - /* e=x-hx */ - alpha=-1.0; - cbstatus=cublasDaxpy(cbhandle, N, &alpha, hxd, 1, ed, 1); - /* note: e is updated */ - - /* norm ||e|| */ - cbstatus=cublasDnrm2(cbhandle, N, ed, 1, &pDp_eL2); - pDp_eL2=pDp_eL2*pDp_eL2; - - - if(!finite(pDp_eL2)){ /* sum of squares is not finite, most probably due to a user error. - */ - stop=7; - break; - } - - /* dL=Dp'*(mu*Dp+jacTe) */ - /* bd=jacTe+mu*Dp */ - cbstatus=cublasDcopy(cbhandle, M, jacTed, 1, bd, 1); - cbstatus=cublasDaxpy(cbhandle, M, &mu, Dpd, 1, bd, 1); - cbstatus=cublasDdot(cbhandle, M, Dpd, 1, bd, 1, &dL); - - dF=p_eL2-pDp_eL2; - -#ifdef DEBUG - printf("dF=%lf, dL=%lf\n",dF,dL); -#endif - if(dL>0.0 && dF>0.0){ /* reduction in error, increment is accepted */ - tmp=(2.0*dF/dL-1.0); - tmp=1.0-tmp*tmp*tmp; - mu=mu*((tmp>=CLM_ONE_THIRD)? tmp : CLM_ONE_THIRD); - nu=2; - - /* update p's estimate */ - cbstatus=cublasDcopy(cbhandle, M, pnewd, 1, pd, 1); - - /* update ||e||_2 */ - p_eL2=pDp_eL2; - break; - } - - } - /* if this point is reached, either the linear system could not be solved or - * the error did not reduce; in any case, the increment must be rejected - */ - - mu*=(double)nu; - nu2=nu<<1; // 2*nu; - if(nu2<=nu){ /* nu has wrapped around (overflown). */ - stop=5; - break; - } - - nu=nu2; - - } /* inner loop */ - - /* copy back solution, need for jacobian calculation */ - err=cudaMemcpy(p,pd,M*sizeof(double),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - } - /**** end iteration loop ***********/ - - - if(k>=itmax) stop=3; - - /* copy back current solution */ - err=cudaMemcpy(p,pd,M*sizeof(double),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - - - - cudaFree(xd); - cudaFree(jacd); - cudaFree(jacTjacd); - cudaFree(jacTjacd0); - cudaFree(jacTed); - cudaFree(Dpd); - cudaFree(bd); - cudaFree(pd); - cudaFree(pnewd); - cudaFree(hxd); - cudaFree(ed); - cudaFree(devInfo); - cudaFree(work); - if (solve_axb==0) { - } else if (solve_axb==1) { - cudaFree(taud); - } else { - cudaFree(Ud); - cudaFree(VTd); - cudaFree(Sd); - cudaFree(rwork); - } - cublasDestroy(cbhandle); - cusolverDnDestroy(solver_handle); - free(hx); - free(jac); - free(pnew); - -#ifdef DEBUG - printf("stop=%d\n",stop); -#endif - if(info){ - info[0]=init_p_eL2; - info[1]=p_eL2; - info[2]=jacTe_inf; - info[3]=Dp_L2; - info[4]=mu; - info[5]=(double)k; - info[6]=(double)stop; - info[7]=(double)0; - info[8]=(double)0; - info[9]=(double)0; - } - return 0; -} - - -/* same as above, but f() and jac() calculations are done - entirely in the GPU */ -int -clevmar_der_single_cuda( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - * info[5]= # iterations, - * info[6]=reason for terminating: 1 - stopped by small gradient J^T e - * 2 - stopped by small Dp - * 3 - stopped by itmax - * 4 - singular matrix. Restart from current p with increased mu - * 5 - no further error reduction is possible. Restart with increased mu - * 6 - stopped by small ||e||_2 - * 7 - stopped by invalid (i.e. NaN or Inf) "func" values. This is a user error - * info[7]= # function evaluations - * info[8]= # Jacobian evaluations - * info[9]= # linear systems solved, i.e. # attempts for reducing error - */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - double *gWORK, /* GPU allocated memory */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - void *adata) /* pointer to possibly additional data, passed uninterpreted to func & jacf. - * Set to NULL if not needed - */ -{ - - /* general note: all device variables end with a 'd' */ - int stop=0; - cudaError_t err; - cublasStatus_t cbstatus; - - int nu=2,nu2; - double p_L2, Dp_L2=DBL_MAX, dF, dL, p_eL2, jacTe_inf=0.0, pDp_eL2, init_p_eL2; - double tmp,mu=0.0; - double tau, eps1, eps2, eps2_sq, eps3; - int k,ci,issolved; - - double *hxd; - - double *ed; - double *xd; - - double *jacd; - - double *jacTjacd,*jacTjacd0; - - double *Dpd,*bd; - double *pd,*pnewd; - double *jacTed; - - /* used in QR solver */ - double *taud; - - /* used in SVD solver */ - double *Ud; - double *VTd; - double *Sd; - - /* ME data */ - me_data_t *dp=(me_data_t*)adata; - int Nbase=(dp->Nbase)*(ntiles); /* note: we do not use the total tile size */ - /* coherency on device */ - double *cohd; - /* baseline-station map on device/host */ - short *bbd; - - int solve_axb=linsolv; - - /* setup default settings */ - if(opts){ - tau=opts[0]; - eps1=opts[1]; - eps2=opts[2]; - eps2_sq=opts[2]*opts[2]; - eps3=opts[3]; - } else { - tau=CLM_INIT_MU; - eps1=CLM_STOP_THRESH; - eps2=CLM_STOP_THRESH; - eps2_sq=CLM_STOP_THRESH*CLM_STOP_THRESH; - eps3=CLM_STOP_THRESH; - } - - /* calculate no of cuda threads and blocks */ - int ThreadsPerBlock=DEFAULT_TH_PER_BK; - int BlocksPerGrid= 2*(M+ThreadsPerBlock-1)/ThreadsPerBlock; - - - unsigned long int moff; /* make sure offsets are multiples of 4 */ - if (!gWORK) { - err=cudaMalloc((void**)&xd, N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&jacd, M*N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&jacTjacd, M*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&jacTed, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&jacTjacd0, M*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&Dpd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&bd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&pd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&pnewd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - /* needed for calculating f() and jac() */ - err=cudaMalloc((void**) &bbd, Nbase*2*sizeof(short)); - checkCudaError(err,__FILE__,__LINE__); - /* we need coherencies for only this cluster */ - err=cudaMalloc((void**) &cohd, Nbase*8*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&hxd, N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&ed, N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - /* memory allocation: different solvers */ - if (solve_axb==1) { - err=cudaMalloc((void**)&taud, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } else if (solve_axb==2) { - err=cudaMalloc((void**)&Ud, M*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&VTd, M*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&Sd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } - } else { - moff=0; - xd=&gWORK[moff]; - moff+=N; - jacd=&gWORK[moff]; - moff+=M*N; - jacTjacd=&gWORK[moff]; - moff+=M*M; - jacTed=&gWORK[moff]; - moff+=M; - jacTjacd0=&gWORK[moff]; - moff+=M*M; - Dpd=&gWORK[moff]; - moff+=M; - bd=&gWORK[moff]; - moff+=M; - pd=&gWORK[moff]; - moff+=M; - pnewd=&gWORK[moff]; - moff+=M; - cohd=&gWORK[moff]; - moff+=Nbase*8; - hxd=&gWORK[moff]; - moff+=N; - ed=&gWORK[moff]; - moff+=N; - if (solve_axb==1) { - taud=&gWORK[moff]; - moff+=M; - } else if (solve_axb==2) { - Ud=&gWORK[moff]; - moff+=M*M; - VTd=&gWORK[moff]; - moff+=M*M; - Sd=&gWORK[moff]; - moff+=M; - } - bbd=(short*)&gWORK[moff]; - moff+=(Nbase*2*sizeof(short))/sizeof(double); - } - - /* extra storage for cusolver */ - int work_size=0; - int *devInfo; - int devInfo_h=0; - err=cudaMalloc((void**)&devInfo, sizeof(int)); - checkCudaError(err,__FILE__,__LINE__); - double *work; - double *rwork; - if (solve_axb==0) { - cusolverDnDpotrf_bufferSize(solver_handle, CUBLAS_FILL_MODE_UPPER, M, jacTjacd, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } else if (solve_axb==1) { - cusolverDnDgeqrf_bufferSize(solver_handle, M, M, jacTjacd, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } else { - cusolverDnDgesvd_bufferSize(solver_handle, M, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&rwork, 5*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } - - - err=cudaMemcpyAsync(pd, p, M*sizeof(double), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - /* need to give right offset for coherencies */ - /* offset: cluster offset+time offset */ - err=cudaMemcpyAsync(cohd, &(dp->ddcoh[(dp->Nbase)*(dp->tilesz)*(dp->clus)*8+(dp->Nbase)*tileoff*8]), Nbase*8*sizeof(double), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - /* correct offset for baselines */ - err=cudaMemcpyAsync(bbd, &(dp->ddbase[2*(dp->Nbase)*(tileoff)]), Nbase*2*sizeof(short), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - cudaDeviceSynchronize(); - /* xd <=x */ - err=cudaMemcpyAsync(xd, x, N*sizeof(double), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - - /* ### compute e=x - f(p) and its L2 norm */ - /* ### e=x-hx, p_eL2=||e|| */ - /* p: params (Mx1), x: data (Nx1), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations*/ - cudakernel_func(ThreadsPerBlock, (Nbase+ThreadsPerBlock-1)/ThreadsPerBlock, pd,hxd,M,N, cohd, bbd, Nbase, dp->M, dp->N); - - /* e=x */ - cbstatus=cublasDcopy(cbhandle, N, xd, 1, ed, 1); - /* e=x-hx */ - double alpha=-1.0; - cbstatus=cublasDaxpy(cbhandle, N, &alpha, hxd, 1, ed, 1); - - /* norm ||e|| */ - cbstatus=cublasDnrm2(cbhandle, N, ed, 1, &p_eL2); - /* square */ - p_eL2=p_eL2*p_eL2; - - init_p_eL2=p_eL2; - if(!finite(p_eL2)) stop=7; - - - /**** iteration loop ***********/ - for(k=0; kstat mapping, Nbase, Mclusters, Nstations*/ - /* FIXME thread/block sizes 16x16=256, so 16 is chosen */ - cudakernel_jacf(ThreadsPerBlock, ThreadsPerBlock/4, pd, jacd, M, N, cohd, bbd, Nbase, dp->M, dp->N); - - /* Compute J^T J and J^T e */ - /* Cache efficient computation of J^T J based on blocking - */ - /* since J is in ROW major order, assume it is transposed, - so actually calculate A=J*J^T, where J is size MxN */ - //status=culaDeviceDgemm('N','T',M,M,N,1.0,jacd,M,jacd,M,0.0,jacTjacd,M); - //checkStatus(status,__FILE__,__LINE__); - double cone=1.0; double czero=0.0; - cbstatus=cublasDgemm(cbhandle,CUBLAS_OP_N,CUBLAS_OP_T,M,M,N,&cone,jacd,M,jacd,M,&czero,jacTjacd,M); - /* create backup */ - /* copy jacTjacd0<=jacTjacd */ - cbstatus=cublasDcopy(cbhandle, M*M, jacTjacd, 1, jacTjacd0, 1); - /* J^T e */ - /* calculate b=J^T*e (actually compute b=J*e, where J in row major (size MxN) */ - //status=culaDeviceDgemv('N',M,N,1.0,jacd,M,ed,1,0.0,jacTed,1); - //checkStatus(status,__FILE__,__LINE__); - cbstatus=cublasDgemv(cbhandle,CUBLAS_OP_N,M,N,&cone,jacd,M,ed,1,&czero,jacTed,1); - - - /* Compute ||J^T e||_inf and ||p||^2 */ - /* find infinity norm of J^T e, 1 based indexing*/ - cbstatus=cublasIdamax(cbhandle, M, jacTed, 1, &ci); - err=cudaMemcpy(&jacTe_inf,&(jacTed[ci-1]),sizeof(double),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - /* L2 norm of current parameter values */ - /* norm ||Dp|| */ - cbstatus=cublasDnrm2(cbhandle, M, pd, 1, &p_L2); - p_L2=p_L2*p_L2; - if(jacTe_inf<0.0) {jacTe_inf=-jacTe_inf;} -#ifdef DEBUG - printf("Inf norm=%lf\n",jacTe_inf); -#endif - - /* check for convergence */ - if((jacTe_inf <= eps1)){ - Dp_L2=0.0; /* no increment for p in this case */ - stop=1; - break; - } - - /* compute initial (k=0) damping factor */ - if (k==0) { - /* find max diagonal element (stride is M+1) */ - /* should be MAX not MAX(ABS) */ - cbstatus=cublasIdamax(cbhandle, M, jacTjacd, M+1, &ci); /* 1 based index */ - ci=(ci-1)*(M+1); /* right value of the diagonal */ - - err=cudaMemcpy(&tmp,&(jacTjacd[ci]),sizeof(double),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - mu=tau*tmp; - } - - - /* determine increment using adaptive damping */ - while(1){ - /* augment normal equations */ - /* increment A => A+ mu*I, increment diagonal entries */ - /* copy jacTjacd<=jacTjacd0 */ - cbstatus=cublasDcopy(cbhandle, M*M, jacTjacd0, 1, jacTjacd, 1); - cudakernel_diagmu(ThreadsPerBlock, BlocksPerGrid, M, jacTjacd, mu); - -#ifdef DEBUG - printf("mu=%lf\n",mu); -#endif -/*************************************************************************/ - issolved=0; - /* solve augmented equations A x = b */ - /* A==jacTjacd, b==Dpd, after solving, x==Dpd */ - /* b=jacTed : intially right hand side, at exit the solution */ - if (solve_axb==0) { - /* Cholesky solver **********************/ - /* lower triangle of Ad is destroyed */ - //status=culaDeviceDpotrf('U',M,jacTjacd,M); - cusolverDnDpotrf(solver_handle, CUBLAS_FILL_MODE_UPPER, M, jacTjacd, M, work, work_size, devInfo); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (!devInfo_h) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - fprintf(stderr,"Singular matrix\n"); -#endif - } - if (issolved) { - /* copy Dpd<=jacTed */ - cbstatus=cublasDcopy(cbhandle, M, jacTed, 1, Dpd, 1); -#ifdef DEBUG - checkCublasError(cbstatus); -#endif - //status=culaDeviceDpotrs('U',M,1,jacTjacd,M,Dpd,M); - cusolverDnDpotrs(solver_handle, CUBLAS_FILL_MODE_UPPER,M,1,jacTjacd,M,Dpd,M,devInfo); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (devInfo_h) { - issolved=0; -#ifdef DEBUG - fprintf(stderr,"Singular matrix\n"); -#endif - } - } - } else if (solve_axb==1) { - /* QR solver ********************************/ - //status=culaDeviceDgeqrf(M,M,jacTjacd,M,taud); - cusolverDnDgeqrf(solver_handle, M, M, jacTjacd, M, taud, work, work_size, devInfo); - cudaDeviceSynchronize(); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (!devInfo_h) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - fprintf(stderr,"Singular matrix\n"); -#endif - } - - if (issolved) { - /* copy Dpd<=jacTed */ - cbstatus=cublasDcopy(cbhandle, M, jacTed, 1, Dpd, 1); - //status=culaDeviceDgeqrs(M,M,1,jacTjacd,M,taud,Dpd,M); - cusolverDnDormqr(solver_handle, CUBLAS_SIDE_LEFT, CUBLAS_OP_T, M, 1, M, jacTjacd, M, taud, Dpd, M, work, work_size, devInfo); - cudaDeviceSynchronize(); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (devInfo_h) { - issolved=0; -#ifdef DEBUG - fprintf(stderr,"Singular matrix\n"); -#endif - } else { - cone=1.0; - cbstatus=cublasDtrsm(cbhandle,CUBLAS_SIDE_LEFT,CUBLAS_FILL_MODE_UPPER,CUBLAS_OP_N,CUBLAS_DIAG_NON_UNIT,M,1,&cone,jacTjacd,M,Dpd,M); - } - } - } else { - /* SVD solver *********************************/ - /* U S VT = A */ - //status=culaDeviceDgesvd('A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M); - //checkStatus(status,__FILE__,__LINE__); - cusolverDnDgesvd(solver_handle,'A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M,work,work_size,rwork,devInfo); - cudaDeviceSynchronize(); - /* copy Dpd<=jacTed */ - cbstatus=cublasDcopy(cbhandle, M, jacTed, 1, Dpd, 1); - /* b<=U^T * b */ - //status=culaDeviceDgemv('T',M,M,1.0,Ud,M,Dpd,1,0.0,Dpd,1); - //checkStatus(status,__FILE__,__LINE__); - cone=1.0; czero=0.0; - cbstatus=cublasDgemv(cbhandle,CUBLAS_OP_T,M,M,&cone,Ud,M,Dpd,1,&czero,Dpd,1); - - /* divide by singular values Dpd[]/Sd[] for Sd[]> eps1 */ - cudakernel_diagdiv(ThreadsPerBlock, BlocksPerGrid, M, eps1, Dpd, Sd); - - /* b<=VT^T * b */ - //status=culaDeviceDgemv('T',M,M,1.0,VTd,M,Dpd,1,0.0,Dpd,1); - //checkStatus(status,__FILE__,__LINE__); - cbstatus=cublasDgemv(cbhandle,CUBLAS_OP_T,M,M,&cone,VTd,M,Dpd,1,&czero,Dpd,1); - - issolved=1; - } -/*************************************************************************/ - - /* compute p's new estimate and ||Dp||^2 */ - if (issolved) { - /* compute p's new estimate and ||Dp||^2 */ - /* pnew=p+Dp */ - /* pnew=p */ - cbstatus=cublasDcopy(cbhandle, M, pd, 1, pnewd, 1); - /* pnew=pnew+Dp */ - alpha=1.0; - cbstatus=cublasDaxpy(cbhandle, M, &alpha, Dpd, 1, pnewd, 1); - - /* norm ||Dp|| */ - cbstatus=cublasDnrm2(cbhandle, M, Dpd, 1, &Dp_L2); - Dp_L2=Dp_L2*Dp_L2; - -#ifdef DEBUG -printf("norm ||dp|| =%lf, norm ||p||=%lf\n",Dp_L2,p_L2); -#endif - if(Dp_L2<=eps2_sq*p_L2){ /* relative change in p is small, stop */ - stop=2; - break; - } - - if(Dp_L2>=(p_L2+eps2)/(CLM_EPSILON*CLM_EPSILON)){ /* almost singular */ - stop=4; - break; - } - - /* new function value */ - /* compute ||e(pDp)||_2 */ - /* ### hx=x-hx, pDp_eL2=||hx|| */ - /* copy to device */ - /* hxd<=hx */ - cudakernel_func(ThreadsPerBlock, (Nbase+ThreadsPerBlock-1)/ThreadsPerBlock, pnewd, hxd, M, N, cohd, bbd, Nbase, dp->M, dp->N); - - /* e=x */ - cbstatus=cublasDcopy(cbhandle, N, xd, 1, ed, 1); - /* e=x-hx */ - alpha=-1.0; - cbstatus=cublasDaxpy(cbhandle, N, &alpha, hxd, 1, ed, 1); - /* note: e is updated */ - - /* norm ||e|| */ - cbstatus=cublasDnrm2(cbhandle, N, ed, 1, &pDp_eL2); - pDp_eL2=pDp_eL2*pDp_eL2; - - - if(!finite(pDp_eL2)){ /* sum of squares is not finite, most probably due to a user error. - */ - stop=7; - break; - } - - /* dL=Dp'*(mu*Dp+jacTe) */ - /* bd=jacTe+mu*Dp */ - cbstatus=cublasDcopy(cbhandle, M, jacTed, 1, bd, 1); - cbstatus=cublasDaxpy(cbhandle, M, &mu, Dpd, 1, bd, 1); - cbstatus=cublasDdot(cbhandle, M, Dpd, 1, bd, 1, &dL); - - dF=p_eL2-pDp_eL2; - -#ifdef DEBUG - printf("dF=%lf, dL=%lf\n",dF,dL); -#endif - if(dL>0.0 && dF>0.0){ /* reduction in error, increment is accepted */ - tmp=(2.0*dF/dL-1.0); - tmp=1.0-tmp*tmp*tmp; - mu=mu*((tmp>=CLM_ONE_THIRD)? tmp : CLM_ONE_THIRD); - nu=2; - - /* update p's estimate */ - cbstatus=cublasDcopy(cbhandle, M, pnewd, 1, pd, 1); - - /* update ||e||_2 */ - p_eL2=pDp_eL2; - break; - } - - } - /* if this point is reached, either the linear system could not be solved or - * the error did not reduce; in any case, the increment must be rejected - */ - - mu*=(double)nu; - nu2=nu<<1; // 2*nu; - if(nu2<=nu){ /* nu has wrapped around (overflown). */ - stop=5; - break; - } - - nu=nu2; - - } /* inner loop */ - - } - /**** end iteration loop ***********/ - - - if(k>=itmax) stop=3; - - /* copy back current solution */ - err=cudaMemcpyAsync(p,pd,M*sizeof(double),cudaMemcpyDeviceToHost,0); - checkCudaError(err,__FILE__,__LINE__); - - /* check once CUBLAS error */ - checkCublasError(cbstatus,__FILE__,__LINE__); - - /* synchronize async operations */ - cudaDeviceSynchronize(); - - if (!gWORK) { - cudaFree(xd); - cudaFree(jacd); - cudaFree(jacTjacd); - cudaFree(jacTjacd0); - cudaFree(jacTed); - cudaFree(Dpd); - cudaFree(bd); - cudaFree(pd); - cudaFree(pnewd); - cudaFree(hxd); - cudaFree(ed); - if (solve_axb==1) { - cudaFree(taud); - } else if (solve_axb==2) { - cudaFree(Ud); - cudaFree(VTd); - cudaFree(Sd); - } - cudaFree(cohd); - cudaFree(bbd); - } - - cudaFree(devInfo); - cudaFree(work); - if (solve_axb==2) { - cudaFree(rwork); - } - -#ifdef DEBUG - printf("stop=%d\n",stop); -#endif - if(info){ - info[0]=init_p_eL2; - info[1]=p_eL2; - info[2]=jacTe_inf; - info[3]=Dp_L2; - info[4]=mu; - info[5]=(double)k; - info[6]=(double)stop; - info[7]=(double)0; - info[8]=(double)0; - info[9]=(double)0; - } - return 0; -} - - -/* function to set up a GPU, should be called only once */ -void -attach_gpu_to_thread(int card, cublasHandle_t *cbhandle, cusolverDnHandle_t *solver_handle) { - - cudaError_t err; - cublasStatus_t cbstatus; - cusolverStatus_t status; - err=cudaSetDevice(card); - checkCudaError(err,__FILE__,__LINE__); - status=cusolverDnCreate(solver_handle); - if (status != CUSOLVER_STATUS_SUCCESS) { - fprintf(stderr,"%s: %d: CUSOLV create fail %d\n",__FILE__,__LINE__,status); - exit(1); - } - - cbstatus=cublasCreate(cbhandle); - if (cbstatus!=CUBLAS_STATUS_SUCCESS) { - fprintf(stderr,"%s: %d: CUBLAS create fail\n",__FILE__,__LINE__); - exit(1); - } - -} -void -attach_gpu_to_thread1(int card, cublasHandle_t *cbhandle, cusolverDnHandle_t *solver_handle, double **WORK, int64_t work_size) { - - cudaError_t err; - cublasStatus_t cbstatus; - cusolverStatus_t status; - err=cudaSetDevice(card); - checkCudaError(err,__FILE__,__LINE__); - status=cusolverDnCreate(solver_handle); - if (status != CUSOLVER_STATUS_SUCCESS) { - fprintf(stderr,"%s: %d: CUSOLV create fail %d\n",__FILE__,__LINE__,status); - sleep(10); - status=cusolverDnCreate(solver_handle); - if (status != CUSOLVER_STATUS_SUCCESS) { - fprintf(stderr,"%s: %d: CUSOLV create fail %d\n",__FILE__,__LINE__,status); - exit(1); - } - } - - cbstatus=cublasCreate(cbhandle); - if (cbstatus!=CUBLAS_STATUS_SUCCESS) { - /* retry once more before exiting */ - fprintf(stderr,"%s: %d: CUBLAS create failure, retrying\n",__FILE__,__LINE__); - sleep(10); - cbstatus=cublasCreate(cbhandle); - if (cbstatus!=CUBLAS_STATUS_SUCCESS) { - fprintf(stderr,"%s: %d: CUBLAS create fail\n",__FILE__,__LINE__); - exit(1); - } - } - - err=cudaMalloc((void**)WORK, (size_t)work_size); - checkCudaError(err,__FILE__,__LINE__); - -} -void -attach_gpu_to_thread2(int card, cublasHandle_t *cbhandle, cusolverDnHandle_t *solver_handle, float **WORK, int64_t work_size, int usecula) { - - cudaError_t err; - cublasStatus_t cbstatus; - cusolverStatus_t status; - err=cudaSetDevice(card); /* we need this */ - checkCudaError(err,__FILE__,__LINE__); - if (usecula) { - status=cusolverDnCreate(solver_handle); - if (status != CUSOLVER_STATUS_SUCCESS) { - fprintf(stderr,"%s: %d: CUSOLV create fail %d\n",__FILE__,__LINE__,status); - sleep(10); - status=cusolverDnCreate(solver_handle); - if (status != CUSOLVER_STATUS_SUCCESS) { - fprintf(stderr,"%s: %d: CUSOLV create fail %d\n",__FILE__,__LINE__,status); - exit(1); - } - } - } - - cbstatus=cublasCreate(cbhandle); - if (cbstatus!=CUBLAS_STATUS_SUCCESS) { - /* retry once more before exiting */ - fprintf(stderr,"%s: %d: CUBLAS create failure, retrying\n",__FILE__,__LINE__); - sleep(10); - cbstatus=cublasCreate(cbhandle); - if (cbstatus!=CUBLAS_STATUS_SUCCESS) { - fprintf(stderr,"%s: %d: CUBLAS create fail\n",__FILE__,__LINE__); - exit(1); - } - } - - err=cudaMalloc((void**)WORK, (size_t)work_size); - checkCudaError(err,__FILE__,__LINE__); - -} -void -detach_gpu_from_thread(cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle) { - cublasDestroy(cbhandle); - cusolverDnDestroy(solver_handle); -} -void -detach_gpu_from_thread1(cublasHandle_t cbhandle,cusolverDnHandle_t solver_handle,double *WORK) { - - cublasDestroy(cbhandle); - cusolverDnDestroy(solver_handle); - cudaFree(WORK); -} -void -detach_gpu_from_thread2(cublasHandle_t cbhandle,cusolverDnHandle_t solver_handle,float *WORK, int usecula) { - - cublasDestroy(cbhandle); - if (usecula) { - cusolverDnDestroy(solver_handle); - } - cudaFree(WORK); -} -void -reset_gpu_memory(double *WORK, int64_t work_size) { - - cudaError_t err; - - err=cudaMemset((void*)WORK, 0, (size_t)work_size); - checkCudaError(err,__FILE__,__LINE__); -} - - -/** keep interface almost the same as in levmar **/ -int -mlm_der_single_cuda( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[6], /* I: minim. options [\mu, \m, \p0, \p1, \p2, \delta]. - delta: 1 or 2 - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - double *gWORK, /* GPU allocated memory */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - void *adata) /* pointer to possibly additional data, passed uninterpreted to func & jacf. - * Set to NULL if not needed - */ -{ - cudaError_t err; - cublasStatus_t cbstatus; - - /* NOTE F()=func()-data */ - double *xd,*Jkd,*Fxkd,*Fykd,*Jkdkd,*JkTed,*JkTed0,*JkTJkd,*JkTJkd0,*dkd,*dhatkd, *ykd, *skd, *pd; - - double lambda; - double mu,m,p0,p1,p2; - int delta; - double Fxknrm,Fyknrm,Fykdhatknrm,Fxksknrm,FJkdknrm; - int niter=0; - int p_update=1; - double Fxknrm2,Fxksknrm2; - - double Ak,Pk,rk; - - /* use cudaHostAlloc and cudaFreeHost */ - /* used in QR solver */ - double *taud=0; - /* used in SVD solver */ - double *Ud=0; - double *VTd=0; - double *Sd=0; - - int issolved; - int solve_axb=linsolv; - - /* ME data */ - me_data_t *dp=(me_data_t*)adata; - int Nbase=(dp->Nbase)*(ntiles); /* note: we do not use the total tile size */ - /* coherency on device */ - double *cohd; - /* baseline-station map on device/host */ - short *bbd; - - - /* calculate no of cuda threads and blocks */ - int ThreadsPerBlock=DEFAULT_TH_PER_BK; - int BlocksPerGrid= 2*(M+ThreadsPerBlock-1)/ThreadsPerBlock; - - - if (opts) { - mu=opts[0]; - m=opts[1]; - p0=opts[2]; - p1=opts[3]; - p2=opts[4]; - delta=(int)opts[5]; - } else { - mu=1e-3;//1e-5; - m=1e-2;//1e-3; - p0=0.0001; - p1=0.25; - p2=0.75; - delta=1; /* 1 or 2 */ - } - - double epsilon=CLM_EPSILON; - - unsigned long int moff; - if (!gWORK) { - err=cudaMalloc((void**)&xd, N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&Jkd, M*N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&Fxkd, N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&Fykd, N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&Jkdkd, N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&JkTed, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&JkTed0, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&JkTJkd, M*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&JkTJkd0, M*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&dkd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&dhatkd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&ykd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&skd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&pd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - /* needed for calculating f() and jac() */ - err=cudaMalloc((void**) &bbd, Nbase*2*sizeof(short)); - checkCudaError(err,__FILE__,__LINE__); - /* we need coherencies for only this cluster */ - err=cudaMalloc((void**) &cohd, Nbase*8*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - /* memory allocation: different solvers */ - if (solve_axb==1) { - /* QR solver ********************************/ - err=cudaMalloc((void**)&taud, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } else if (solve_axb==2) { - err=cudaMalloc((void**)&Ud, M*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&VTd, M*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&Sd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } - } else { /* use pre allocated memory */ - moff=0; - xd=&gWORK[moff]; - moff+=N; - Jkd=&gWORK[moff]; - moff+=M*N; - Fxkd=&gWORK[moff]; - moff+=N; - Fykd=&gWORK[moff]; - moff+=N; - Jkdkd=&gWORK[moff]; - moff+=N; - JkTed=&gWORK[moff]; - moff+=M; - JkTed0=&gWORK[moff]; - moff+=M; - JkTJkd=&gWORK[moff]; - moff+=M*M; - JkTJkd0=&gWORK[moff]; - moff+=M*M; - dkd=&gWORK[moff]; - moff+=M; - dhatkd=&gWORK[moff]; - moff+=M; - ykd=&gWORK[moff]; - moff+=M; - skd=&gWORK[moff]; - moff+=M; - pd=&gWORK[moff]; - moff+=M; - cohd=&gWORK[moff]; - moff+=Nbase*8; - if (solve_axb==1) { - taud=&gWORK[moff]; - moff+=M; - } else if (solve_axb==2) { - Ud=&gWORK[moff]; - moff+=M*M; - VTd=&gWORK[moff]; - moff+=M*M; - Sd=&gWORK[moff]; - moff+=M; - } - bbd=(short*)&gWORK[moff]; - moff+=(Nbase*2*sizeof(short))/sizeof(double); - } - - /* extra storage for cusolver */ - int work_size=0; - int *devInfo; - int devInfo_h=0; - err=cudaMalloc((void**)&devInfo, sizeof(int)); - checkCudaError(err,__FILE__,__LINE__); - double *work; - double *rwork; - if (solve_axb==0) { - cusolverDnDpotrf_bufferSize(solver_handle, CUBLAS_FILL_MODE_UPPER, M, JkTJkd, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } else if (solve_axb==1) { - cusolverDnDgeqrf_bufferSize(solver_handle, M, M, JkTJkd, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } else { - cusolverDnDgesvd_bufferSize(solver_handle, M, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&rwork, 5*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } - - - err=cudaMemcpyAsync(pd, p, M*sizeof(double), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - /* need to give right offset for coherencies */ - /* offset: cluster offset+time offset */ - err=cudaMemcpyAsync(cohd, &(dp->ddcoh[(dp->Nbase)*(dp->tilesz)*(dp->clus)*8+(dp->Nbase)*tileoff*8]), Nbase*8*sizeof(double), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - /* correct offset for baselines */ - err=cudaMemcpyAsync(bbd, &(dp->ddbase[2*(dp->Nbase)*(tileoff)]), Nbase*2*sizeof(short), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - cudaDeviceSynchronize(); - /* xd <=x */ - err=cudaMemcpyAsync(xd, x, N*sizeof(double), cudaMemcpyHostToDevice, 0); - checkCudaError(err,__FILE__,__LINE__); - - /* F(x_k) = func()-data */ - /* func() */ - //(*func)(p, Fxk, M, N, adata); - /* p: params (Mx1), x: data (Nx1), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations*/ - cudakernel_func(ThreadsPerBlock, (Nbase+ThreadsPerBlock-1)/ThreadsPerBlock, pd,Fxkd,M,N, cohd, bbd, Nbase, dp->M, dp->N); - - /* func() - data */ - double alpha=-1.0; - cbstatus=cublasDaxpy(cbhandle, N, &alpha, xd, 1, Fxkd, 1); - //my_daxpy(N, x, -1.0, Fxk); - - /* find ||Fxk|| */ - //Fxknrm=my_dnrm2(N,Fxk); - cbstatus=cublasDnrm2(cbhandle, N, Fxkd, 1, &Fxknrm); - - double init_Fxknrm=Fxknrm; -#ifdef DEBUG - printf("init norm=%lf\n",Fxknrm); -#endif - - - double cone=1.0; double czero=0.0; - while (niter1) { - lambda=mu*Fxknrm*Fxknrm; - } else { - lambda=mu*Fxknrm; - } - Fxknrm2=Fxknrm*Fxknrm; - - if ( p_update==1 ) { - /* J_k */ - /* p: params (Mx1), jacd: jacobian (NxM), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations*/ - /* FIXME thread/block sizes 16x16=256, so 16 is chosen */ - cudakernel_jacf(ThreadsPerBlock, ThreadsPerBlock/4, pd, Jkd, M, N, cohd, bbd, Nbase, dp->M, dp->N); - - /* Compute J_k^T J_k and -J_k^T F(x_k) */ - //my_dgemm('N','T',M,M,N,1.0,Jk,M,Jk,M,0.0,JkTJk0,M); - //my_dgemv('N',M,N,-1.0,Jk,M,Fxk,1,0.0,JkTe0,1); - - //status=culaDeviceDgemm('N','T',M,M,N,1.0,Jkd,M,Jkd,M,0.0,JkTJkd0,M); - //checkStatus(status,__FILE__,__LINE__); - double cone=1.0; double czero=0.0; - cbstatus=cublasDgemm(cbhandle,CUBLAS_OP_N,CUBLAS_OP_T,M,M,N,&cone,Jkd,M,Jkd,M,&czero,JkTJkd0,M); - //status=culaDeviceDgemv('N',M,N,-1.0,Jkd,M,Fxkd,1,0.0,JkTed0,1); - //checkStatus(status,__FILE__,__LINE__); - cone=-1.0; - cbstatus=cublasDgemv(cbhandle,CUBLAS_OP_N,M,N,&cone,Jkd,M,Fxkd,1,&czero,JkTed0,1); - } - /* if || J_k^T F(x_k) || < epsilon, stop */ - //Fyknrm=my_dnrm2(M,JkTe0); - cbstatus=cublasDnrm2(cbhandle, M, JkTed0, 1, &Fyknrm); - - if (Fyknrm epsilon */ - /*for (ci=0; ciepsilon) { - dk[ci]=dk[ci]/Sd[ci]; - } else { - dk[ci]=0.0; - } - } */ - - /* dk <= VT^T dk */ - //memcpy(yk,dk,M*sizeof(double)); - //my_dgemv('T',M,M,1.0,VTd,M,yk,1,0.0,dk,1); - /* U S VT = A */ - //status=culaDeviceDgesvd('A','A',M,M,JkTJkd,M,Sd,Ud,M,VTd,M); - //checkStatus(status,__FILE__,__LINE__); - cusolverDnDgesvd(solver_handle,'A','A',M,M,JkTJkd,M,Sd,Ud,M,VTd,M,work,work_size,rwork,devInfo); - cudaDeviceSynchronize(); - /* b<=U^T * b */ - //status=culaDeviceDgemv('T',M,M,1.0,Ud,M,JkTed,1,0.0,dkd,1); - //checkStatus(status,__FILE__,__LINE__); - cone=1.0; czero=0.0; - cbstatus=cublasDgemv(cbhandle,CUBLAS_OP_T,M,M,&cone,Ud,M,JkTed,1,&czero,dkd,1); - - /* divide by singular values Dpd[]/Sd[] for Sd[]> eps1 */ - cudakernel_diagdiv(ThreadsPerBlock, BlocksPerGrid, M, epsilon, dkd, Sd); - - /* b<=VT^T * b */ - cbstatus=cublasDcopy(cbhandle, M, dkd, 1, ykd, 1); - //status=culaDeviceDgemv('T',M,M,1.0,VTd,M,ykd,1,0.0,dkd,1); - //checkStatus(status,__FILE__,__LINE__); - cbstatus=cublasDgemv(cbhandle,CUBLAS_OP_T,M,M,&cone,VTd,M,ykd,1,&czero,dkd,1); - - issolved=1; - } -/********************************************************************/ - - /* y_k<= x_k+ d_k */ - //my_dcopy(M,p,1,yk,1); - //my_daxpy(M,dk,1.0,yk); - - cbstatus=cublasDcopy(cbhandle, M, pd, 1, ykd, 1); - alpha=1.0; - cbstatus=cublasDaxpy(cbhandle, M, &alpha, dkd, 1, ykd, 1); - - /* compute F(y_k) */ - /* func() */ - //(*func)(yk, Fyk, M, N, adata); - cudakernel_func(ThreadsPerBlock, (Nbase+ThreadsPerBlock-1)/ThreadsPerBlock, ykd,Fykd,M,N, cohd, bbd, Nbase, dp->M, dp->N); - - /* func() - data */ - //my_daxpy(N, x, -1.0, Fyk); - /* copy to device */ - - /* func() - data */ - alpha=-1.0; - cbstatus=cublasDaxpy(cbhandle, N, &alpha, xd, 1, Fykd, 1); - - - /* Compute -J_k^T F(y_k) */ - //my_dgemv('N',M,N,-1.0,Jk,M,Fyk,1,0.0,JkTe,1); - //status=culaDeviceDgemv('N',M,N,1.0,Jkd,M,Fykd,1,0.0,JkTed,1); - //checkStatus(status,__FILE__,__LINE__); - cone=1.0; czero=0.0; - cbstatus=cublasDgemv(cbhandle,CUBLAS_OP_N,M,N,&cone,Jkd,M,Fykd,1,&czero,JkTed,1); - - -/********************************************************************/ - if (solve_axb==0) { - /* Cholesky solver **********************/ - /* copy dk<=JkTe */ - // memcpy(dhatk,JkTe,M*sizeof(double)); - // status=my_dpotrs('U',M,1,JkTJk,M,dhatk,M); - cbstatus=cublasDcopy(cbhandle, M, JkTed, 1, dhatkd, 1); - //status=culaDeviceDpotrs('U',M,1,JkTJkd,M,dhatkd,M); - cusolverDnDpotrs(solver_handle, CUBLAS_FILL_MODE_UPPER,M,1,JkTJkd,M,dhatkd,M,devInfo); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (devInfo_h) { - issolved=0; -#ifdef DEBUG - fprintf(stderr,"Singular matrix info=%d\n",status); -#endif - } - } else if (solve_axb==1) { - /* QR solver ********************************/ - /* dhatk <= Q^T jacTed */ - //my_dgemv('T',M,M,1.0,JkTJk,M,JkTe,1,0.0,dhatk,1); - /* solve R x = b */ - //status=my_dtrtrs('U','N','N',M,1,R,M,dhatk,M); - cbstatus=cublasDcopy(cbhandle, M, JkTed, 1, dhatkd, 1); - //status=culaDeviceDgeqrs(M,M,1,JkTJkd,M,taud,dhatkd,M); - cusolverDnDormqr(solver_handle, CUBLAS_SIDE_LEFT, CUBLAS_OP_T, M, 1, M, JkTJkd, M, taud, dhatkd, M, work, work_size, devInfo); - cudaDeviceSynchronize(); - cbstatus=cublasDtrsm(cbhandle,CUBLAS_SIDE_LEFT,CUBLAS_FILL_MODE_UPPER,CUBLAS_OP_N,CUBLAS_DIAG_NON_UNIT,M,1,&cone,JkTJkd,M,dhatkd,M); - - issolved=1; - } else { - /* SVD solver *********************************/ - /* dhatk <= U^T jacTed */ - //my_dgemv('T',M,M,1.0,Ud,M,JkTe,1,0.0,dhatk,1); - /* robust correction */ - /* divide by singular values dk[]/Sd[] for Sd[]> epsilon */ - /*for (ci=0; ciepsilon) { - dhatk[ci]=dhatk[ci]/Sd[ci]; - } else { - dhatk[ci]=0.0; - } - }*/ - /* dk <= VT^T dk */ - //memcpy(yk,dhatk,M*sizeof(double)); - //my_dgemv('T',M,M,1.0,VTd,M,yk,1,0.0,dhatk,1); - //status=culaDeviceDgemv('T',M,M,1.0,Ud,M,JkTed,1,0.0,dhatkd,1); - //checkStatus(status,__FILE__,__LINE__); - cone=1.0; czero=0.0; - cbstatus=cublasDgemv(cbhandle,CUBLAS_OP_T,M,M,&cone,Ud,M,JkTed,1,&czero,dhatkd,1); - - /* divide by singular values Dpd[]/Sd[] for Sd[]> eps1 */ - cudakernel_diagdiv(ThreadsPerBlock, BlocksPerGrid, M, epsilon, dhatkd, Sd); - - /* b<=VT^T * b */ - cbstatus=cublasDcopy(cbhandle, M, dhatkd, 1, ykd, 1); - //status=culaDeviceDgemv('T',M,M,1.0,VTd,M,ykd,1,0.0,dhatkd,1); - //checkStatus(status,__FILE__,__LINE__); - cbstatus=cublasDgemv(cbhandle,CUBLAS_OP_T,M,M,&cone,VTd,M,ykd,1,&czero,dhatkd,1); - - issolved=1; - - } -/********************************************************************/ - - - - /* s_k<= d_k+ dhat_k */ - //my_dcopy(M,dk,1,sk,1); - //my_daxpy(M,dhatk,1.0,sk); - cbstatus=cublasDcopy(cbhandle, M, dkd, 1, skd, 1); - alpha=1.0; - cbstatus=cublasDaxpy(cbhandle, M, &alpha, dhatkd, 1, skd, 1); - - - /* find norms */ - /* || F(y_k) || */ -// Fyknrm=my_dnrm2(N,Fyk); - cbstatus=cublasDnrm2(cbhandle, N, Fykd, 1, &Fyknrm); - Fyknrm=Fyknrm*Fyknrm; - - /* || F(y_k) + J_k dhat_k || */ - //my_dgemv('T',M,N,1.0,Jk,M,dhatk,1,0.0,Jkdk,1); - //status=culaDeviceDgemv('T',M,N,1.0,Jkd,M,dhatkd,1,0.0,Jkdkd,1); - cone=1.0; czero=0.0; - cbstatus=cublasDgemv(cbhandle,CUBLAS_OP_T,M,N,&cone,Jkd,M,dhatkd,1,&czero,Jkdkd,1); - - - /* Fyk <= Fyk+ J_k dhat_k */ -// my_daxpy(N,Jkdk,1.0,Fyk); -// Fykdhatknrm=my_dnrm2(N,Fyk); - cbstatus=cublasDaxpy(cbhandle, N, &alpha, Jkdkd, 1, Fykd, 1); - cbstatus=cublasDnrm2(cbhandle, N, Fykd, 1, &Fykdhatknrm); - Fykdhatknrm=Fykdhatknrm*Fykdhatknrm; - - /* ||F(x_k+d_k+dhat_k)|| == ||F(x_k+s_k)|| */ - /* y_k<= x_k+ s_k */ - //my_dcopy(M,p,1,yk,1); - //my_daxpy(M,sk,1.0,yk); - cbstatus=cublasDcopy(cbhandle, M, pd, 1, ykd, 1); - cbstatus=cublasDaxpy(cbhandle, M, &alpha, skd, 1, ykd, 1); - - //(*func)(yk, Fyk, M, N, adata); - cudakernel_func(ThreadsPerBlock, (Nbase+ThreadsPerBlock-1)/ThreadsPerBlock, ykd,Fykd,M,N, cohd, bbd, Nbase, dp->M, dp->N); - - /* func() - data */ - //my_daxpy(N, x, -1.0, Fyk); - alpha=-1.0; - cbstatus=cublasDaxpy(cbhandle, N, &alpha, xd, 1, Fykd, 1); - - //Fxksknrm=my_dnrm2(N,Fyk); - cbstatus=cublasDnrm2(cbhandle, N, Fykd, 1, &Fxksknrm); - - Fxksknrm2=Fxksknrm*Fxksknrm; - - /* || Fxk + J_k d_k || */ - /* J d_k : since J is row major, transpose */ -// my_dgemv('T',M,N,1.0,Jk,M,dk,1,0.0,Jkdk,1); - //status=culaDeviceDgemv('T',M,N,1.0,Jkd,M,dkd,1,0.0,Jkdkd,1); - cone=1.0; czero=0.0; - cbstatus=cublasDgemv(cbhandle,CUBLAS_OP_T,M,N,&cone,Jkd,M,dkd,1,&czero,Jkdkd,1); - - - /* Fxk <= Fxk+ J_k d_k or, J_k d_k <= Fxk+ J_k d_k */ - //my_daxpy(N,Fxk,1.0,Jkdk); - //FJkdknrm=my_dnrm2(N,Jkdk); - alpha=1.0; - cbstatus=cublasDaxpy(cbhandle, N, &alpha, Fxkd, 1, Jkdkd, 1); - cbstatus=cublasDnrm2(cbhandle, N, Jkdkd, 1, &FJkdknrm); - - - FJkdknrm=FJkdknrm*FJkdknrm; - - /* find ratio */ - Ak=Fxknrm2-Fxksknrm2; - Pk=Fxknrm2-FJkdknrm+Fyknrm-Fykdhatknrm; - /* if Pk=p0) { - p_update=1; - /* update p<= p+sk */ - //my_daxpy(M,sk,1.0,p); - alpha=1.0; - cbstatus=cublasDaxpy(cbhandle, M, &alpha, skd, 1, pd, 1); - /* also update auxiliary info */ - /* Fxk <= Fyk */ - //my_dcopy(N,Fyk,1,Fxk,1); - cbstatus=cublasDcopy(cbhandle, N, Fykd, 1, Fxkd, 1); - - Fxknrm=Fxksknrm; - /* new Jk needed */ - } else { /* else no p update */ - p_update=0; - /* use previous Jk, Fxk, JkTJk, JkTe */ - } - if (rk0.25*mu) { - mu=m; - } else { - mu=0.25*mu; - } - } - -#ifdef DEBUG - printf("Ak=%lf Pk=%lf rk=%lf mu=%lf ||Fxk||=%lf\n",Ak,Pk,rk,mu,Fxknrm); -#endif - niter++; - } - - /* copy back solution */ - //err=cudaMemcpy(p,pd,M*sizeof(double),cudaMemcpyDeviceToHost); - err=cudaMemcpyAsync(p,pd,M*sizeof(double),cudaMemcpyDeviceToHost,0); - checkCudaError(err,__FILE__,__LINE__); - - /* check once CUBLAS error */ - checkCublasError(cbstatus,__FILE__,__LINE__); - - - if (!gWORK) { - if (solve_axb==1) { - cudaFree(taud); - } else if (solve_axb==2) { - cudaFree(Ud); - cudaFree(VTd); - cudaFree(Sd); - } - cudaFree(xd); - cudaFree(Jkd); - cudaFree(Fxkd); - cudaFree(Fykd); - cudaFree(Jkdkd); - cudaFree(JkTed); - cudaFree(JkTed0); - cudaFree(JkTJkd); - cudaFree(JkTJkd0); - cudaFree(dkd); - cudaFree(dhatkd); - cudaFree(ykd); - cudaFree(skd); - cudaFree(pd); - - cudaFree(bbd); - cudaFree(cohd); - } - - cudaFree(devInfo); - cudaFree(work); - if (solve_axb==2) { - cudaFree(rwork); - } - - if(info){ - info[0]=init_Fxknrm; - info[1]=Fxknrm; - } - /* synchronize async operations */ - cudaDeviceSynchronize(); - return 0; -} diff --git a/src/lib/Solvers/clmfit.o b/src/lib/Solvers/clmfit.o deleted file mode 100644 index 27250373aaeb4eda64ec259058a75e2b0fc9587d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 101512 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

@n6!X{9lkqtf?BadhufMuQ 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 NGV?|Y=I+<|{}0S$vBdxY diff --git a/src/lib/Solvers/clmfit_fl.c b/src/lib/Solvers/clmfit_fl.c deleted file mode 100644 index 19709c4..0000000 --- a/src/lib/Solvers/clmfit_fl.c +++ /dev/null @@ -1,1116 +0,0 @@ -/* - * - Copyright (C) 2006-2008 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#include -#include -#include -#include -#include - -#include "Solvers.h" -#include - -//#define DEBUG -/* helper functions for diagnostics */ -static void -checkCudaError(cudaError_t err, const char *file, int line) -{ -#ifdef CUDA_DEBUG - if(!err) - return; - fprintf(stderr,"GPU (CUDA): %s %s %d\n", cudaGetErrorString(err),file,line); - exit(EXIT_FAILURE); -#endif -} - - -static void -checkCublasError(cublasStatus_t cbstatus, char *file, int line) -{ -#ifdef CUDA_DEBUG - if (cbstatus!=CUBLAS_STATUS_SUCCESS) { - fprintf(stderr,"%s: %d: CUBLAS failure\n",file,line); - exit(EXIT_FAILURE); - } -#endif -} - - - - -/* OS-LM, but f() and jac() calculations are done - entirely in the GPU */ -int -oslevmar_der_single_cuda_fl( - float *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - float *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - float *gWORK, /* GPU allocated memory */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - int randomize, /* if >0 randomize */ - void *adata) /* pointer to possibly additional data, passed uninterpreted to func & jacf. - * Set to NULL if not needed - */ -{ - - /* general note: all device variables end with a 'd' */ - int stop=0; - cudaError_t err; - cublasStatus_t cbstatus; - - int nu=2,nu2; - float p_L2, Dp_L2=(float)DBL_MAX, dF, dL, p_eL2, jacTe_inf=0.0f, pDp_eL2, init_p_eL2; - float tmp,mu=0.0f; - float tau, eps1, eps2, eps2_sq, eps3; - int k,ci,issolved; - - float *hxd; - - float *ed; - float *xd; - - float *jacd; - - float *jacTjacd,*jacTjacd0; - - float *Dpd,*bd; - float *pd,*pnewd; - float *jacTed; - - /* used in QR solver */ - float *taud=0; - - /* used in SVD solver */ - float *Ud=0; - float *VTd=0; - float *Sd=0; - - /* ME data */ - me_data_t *dp=(me_data_t*)adata; - int Nbase=(dp->Nbase)*(ntiles); /* note: we do not use the total tile size */ - /* coherency on device */ - float *cohd; - /* baseline-station map on device/host */ - short *bbd; - - int solve_axb=linsolv; - - /* setup default settings */ - if(opts){ - tau=(float)opts[0]; - eps1=(float)opts[1]; - eps2=(float)opts[2]; - eps2_sq=(float)opts[2]*opts[2]; - eps3=(float)opts[3]; - } else { - tau=(float)CLM_INIT_MU; - eps1=(float)CLM_STOP_THRESH; - eps2=(float)CLM_STOP_THRESH; - eps2_sq=(float)CLM_STOP_THRESH*CLM_STOP_THRESH; - eps3=(float)CLM_STOP_THRESH; - } - - /* calculate no of cuda threads and blocks */ - int ThreadsPerBlock=DEFAULT_TH_PER_BK; - int BlocksPerGrid= 2*(M+ThreadsPerBlock-1)/ThreadsPerBlock; - - - unsigned long int moff; - moff=0; - xd=&gWORK[moff]; - moff+=N; - jacd=&gWORK[moff]; - moff+=M*N; - jacTjacd=&gWORK[moff]; - moff+=M*M; - jacTed=&gWORK[moff]; - moff+=M; - jacTjacd0=&gWORK[moff]; - moff+=M*M; - Dpd=&gWORK[moff]; - moff+=M; - bd=&gWORK[moff]; - moff+=M; - pd=&gWORK[moff]; - moff+=M; - pnewd=&gWORK[moff]; - moff+=M; - cohd=&gWORK[moff]; - moff+=Nbase*8; - hxd=&gWORK[moff]; - moff+=N; - ed=&gWORK[moff]; - moff+=N; - if (solve_axb==1) { - taud=&gWORK[moff]; - moff+=M; - } else if (solve_axb==2) { - Ud=&gWORK[moff]; - moff+=M*M; - VTd=&gWORK[moff]; - moff+=M*M; - Sd=&gWORK[moff]; - moff+=M; - } - bbd=(short*)&gWORK[moff]; - moff+=(Nbase*2*sizeof(short))/sizeof(float); - - /* extra storage for cusolver */ - int work_size=0; - int *devInfo; - int devInfo_h=0; - err=cudaMalloc((void**)&devInfo, sizeof(int)); - checkCudaError(err,__FILE__,__LINE__); - float *work; - float *rwork; - if (solve_axb==0) { - cusolverDnSpotrf_bufferSize(solver_handle, CUBLAS_FILL_MODE_UPPER, M, jacTjacd, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(float)); - checkCudaError(err,__FILE__,__LINE__); - } else if (solve_axb==1) { - cusolverDnSgeqrf_bufferSize(solver_handle, M, M, jacTjacd, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(float)); - checkCudaError(err,__FILE__,__LINE__); - } else { - cusolverDnSgesvd_bufferSize(solver_handle, M, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(float)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&rwork, 5*M*sizeof(float)); - checkCudaError(err,__FILE__,__LINE__); - } - - - err=cudaMemcpyAsync(pd, p, M*sizeof(float), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - /* need to give right offset for coherencies */ - /* offset: cluster offset+time offset */ - err=cudaMemcpyAsync(cohd, &(dp->ddcohf[(dp->Nbase)*(dp->tilesz)*(dp->clus)*8+(dp->Nbase)*tileoff*8]), Nbase*8*sizeof(float), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - /* correct offset for baselines */ - err=cudaMemcpyAsync(bbd, &(dp->ddbase[2*(dp->Nbase)*(tileoff)]), Nbase*2*sizeof(short), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - cudaDeviceSynchronize(); - /* xd <=x */ - err=cudaMemcpyAsync(xd, x, N*sizeof(float), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - - /* ### compute e=x - f(p) and its L2 norm */ - /* ### e=x-hx, p_eL2=||e|| */ - /* p: params (Mx1), x: data (Nx1), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations*/ - cudakernel_func_fl(ThreadsPerBlock, (Nbase+ThreadsPerBlock-1)/ThreadsPerBlock, pd,hxd,M,N, cohd, bbd, Nbase, dp->M, dp->N); - - /* e=x */ - cbstatus=cublasScopy(cbhandle, N, xd, 1, ed, 1); - /* e=x-hx */ - float alpha=-1.0f; - cbstatus=cublasSaxpy(cbhandle, N, &alpha, hxd, 1, ed, 1); - - /* norm ||e|| */ - cbstatus=cublasSnrm2(cbhandle, N, ed, 1, &p_eL2); - /* square */ - p_eL2=p_eL2*p_eL2; - - init_p_eL2=p_eL2; - if(!finitef(p_eL2)) stop=7; - - /* setup OS subsets and stating offsets */ - /* ed : N, cohd : Nbase*8, bbd : Nbase*2 full size */ - /* if ntiles= no. of OS iterations, so select - a random set of subsets */ - /* N, Nbase changes with subset, cohd,bbd,ed gets offsets */ - /* ed : N, cohd : Nbase*8, bbd : Nbase*2 full size */ - /* p: params (Mx1), jacd: jacobian (NxM), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations*/ - /* FIXME thread/block sizes 16x16=256, so 16 is chosen */ - cudakernel_jacf_fl(ThreadsPerBlock, ThreadsPerBlock/4, pd, jacd, M, Nos[l], &cohd[8*NbI[l]], &bbd[2*NbI[l]], Nbaseos[l], dp->M, dp->N); - - /* Compute J^T J and J^T e */ - /* Cache efficient computation of J^T J based on blocking - */ - /* since J is in ROW major order, assume it is transposed, - so actually calculate A=J*J^T, where J is size MxN */ - //status=culaDeviceSgemm('N','T',M,M,Nos[l],1.0f,jacd,M,jacd,M,0.0f,jacTjacd,M); - //checkStatus(status,__FILE__,__LINE__); - float cone=1.0f; float czero=0.0f; - cbstatus=cublasSgemm(cbhandle,CUBLAS_OP_N,CUBLAS_OP_T,M,M,Nos[l],&cone,jacd,M,jacd,M,&czero,jacTjacd,M); - - /* create backup */ - /* copy jacTjacd0<=jacTjacd */ - cbstatus=cublasScopy(cbhandle, M*M, jacTjacd, 1, jacTjacd0, 1); - /* J^T e */ - /* calculate b=J^T*e (actually compute b=J*e, where J in row major (size MxN) */ - //status=culaDeviceSgemv('N',M,Nos[l],1.0f,jacd,M,&ed[edI[l]],1,0.0f,jacTed,1); - //checkStatus(status,__FILE__,__LINE__); - cbstatus=cublasSgemv(cbhandle,CUBLAS_OP_N,M,Nos[l],&cone,jacd,M,&ed[edI[l]],1,&czero,jacTed,1); - - - /* Compute ||J^T e||_inf and ||p||^2 */ - /* find infinity norm of J^T e, 1 based indexing*/ - cbstatus=cublasIsamax(cbhandle, M, jacTed, 1, &ci); - err=cudaMemcpy(&jacTe_inf,&(jacTed[ci-1]),sizeof(float),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - /* L2 norm of current parameter values */ - /* norm ||Dp|| */ - cbstatus=cublasSnrm2(cbhandle, M, pd, 1, &p_L2); - p_L2=p_L2*p_L2; - if(jacTe_inf<0.0f) {jacTe_inf=-jacTe_inf;} -#ifdef DEBUG - printf("Inf norm=%f\n",jacTe_inf); -#endif - - /* check for convergence */ - if((jacTe_inf <= eps1)){ - Dp_L2=0.0f; /* no increment for p in this case */ - stop=1; - break; - } - - /* compute initial (k=0) damping factor */ - if (k==0) { - /* find max diagonal element (stride is M+1) */ - /* should be MAX not MAX(ABS) */ - cbstatus=cublasIsamax(cbhandle, M, jacTjacd, M+1, &ci); /* 1 based index */ - ci=(ci-1)*(M+1); /* right value of the diagonal */ - - err=cudaMemcpy(&tmp,&(jacTjacd[ci]),sizeof(float),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - mu=tau*tmp; - } - - - /* determine increment using adaptive damping */ - while(1){ - /* augment normal equations */ - /* increment A => A+ mu*I, increment diagonal entries */ - /* copy jacTjacd<=jacTjacd0 */ - cbstatus=cublasScopy(cbhandle, M*M, jacTjacd0, 1, jacTjacd, 1); - cudakernel_diagmu_fl(ThreadsPerBlock, BlocksPerGrid, M, jacTjacd, mu); - -#ifdef DEBUG - printf("mu=%f\n",mu); -#endif -/*************************************************************************/ - issolved=0; - /* solve augmented equations A x = b */ - /* A==jacTjacd, b==Dpd, after solving, x==Dpd */ - /* b=jacTed : intially right hand side, at exit the solution */ - if (solve_axb==0) { - /* Cholesky solver **********************/ - /* lower triangle of Ad is destroyed */ - //status=culaDeviceSpotrf('U',M,jacTjacd,M); - cusolverDnSpotrf(solver_handle, CUBLAS_FILL_MODE_UPPER, M, jacTjacd, M, work, work_size, devInfo); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (!devInfo_h) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - fprintf(stderr,"Singular matrix\n"); -#endif - } - if (issolved) { - /* copy Dpd<=jacTed */ - cbstatus=cublasScopy(cbhandle, M, jacTed, 1, Dpd, 1); -#ifdef DEBUG - checkCublasError(cbstatus,__FILE__,__LINE__); -#endif - //status=culaDeviceSpotrs('U',M,1,jacTjacd,M,Dpd,M); - cusolverDnSpotrs(solver_handle, CUBLAS_FILL_MODE_UPPER,M,1,jacTjacd,M,Dpd,M,devInfo); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (devInfo_h) { - issolved=0; -#ifdef DEBUG - fprintf(stderr,"Singular matrix\n"); -#endif - } - } - } else if (solve_axb==1) { - /* QR solver ********************************/ - //status=culaDeviceSgeqrf(M,M,jacTjacd,M,taud); - cusolverDnSgeqrf(solver_handle, M, M, jacTjacd, M, taud, work, work_size, devInfo); - cudaDeviceSynchronize(); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (!devInfo_h) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - fprintf(stderr,"Singular matrix\n"); -#endif - } - - if (issolved) { - /* copy Dpd<=jacTed */ - cbstatus=cublasScopy(cbhandle, M, jacTed, 1, Dpd, 1); - //status=culaDeviceSgeqrs(M,M,1,jacTjacd,M,taud,Dpd,M); - cusolverDnSormqr(solver_handle, CUBLAS_SIDE_LEFT, CUBLAS_OP_T, M, 1, M, jacTjacd, M, taud, Dpd, M, work, work_size, devInfo); - cudaDeviceSynchronize(); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (devInfo_h) { - issolved=0; -#ifdef DEBUG - fprintf(stderr,"Singular matrix\n"); -#endif - } else { - cone=1.0f; - cbstatus=cublasStrsm(cbhandle,CUBLAS_SIDE_LEFT,CUBLAS_FILL_MODE_UPPER,CUBLAS_OP_N,CUBLAS_DIAG_NON_UNIT,M,1,&cone,jacTjacd,M,Dpd,M); - } - } - } else { - /* SVD solver *********************************/ - /* U S VT = A */ - //status=culaDeviceSgesvd('A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M); - //checkStatus(status,__FILE__,__LINE__); - cusolverDnSgesvd(solver_handle,'A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M,work,work_size,rwork,devInfo); - cudaDeviceSynchronize(); - /* copy Dpd<=jacTed */ - cbstatus=cublasScopy(cbhandle, M, jacTed, 1, Dpd, 1); - /* b<=U^T * b */ - //status=culaDeviceSgemv('T',M,M,1.0f,Ud,M,Dpd,1,0.0f,Dpd,1); - //checkStatus(status,__FILE__,__LINE__); - cone=1.0f; czero=0.0f; - cbstatus=cublasSgemv(cbhandle,CUBLAS_OP_T,M,M,&cone,Ud,M,Dpd,1,&czero,Dpd,1); - /* divide by singular values Dpd[]/Sd[] for Sd[]> eps1 */ - cudakernel_diagdiv_fl(ThreadsPerBlock, BlocksPerGrid, M, eps1, Dpd, Sd); - - /* b<=VT^T * b */ - //status=culaDeviceSgemv('T',M,M,1.0f,VTd,M,Dpd,1,0.0f,Dpd,1); - //checkStatus(status,__FILE__,__LINE__); - cbstatus=cublasSgemv(cbhandle,CUBLAS_OP_T,M,M,&cone,VTd,M,Dpd,1,&czero,Dpd,1); - - issolved=1; - } -/*************************************************************************/ - - /* compute p's new estimate and ||Dp||^2 */ - if (issolved) { - /* compute p's new estimate and ||Dp||^2 */ - /* pnew=p+Dp */ - /* pnew=p */ - cbstatus=cublasScopy(cbhandle, M, pd, 1, pnewd, 1); - /* pnew=pnew+Dp */ - alpha=1.0f; - cbstatus=cublasSaxpy(cbhandle, M, &alpha, Dpd, 1, pnewd, 1); - - /* norm ||Dp|| */ - cbstatus=cublasSnrm2(cbhandle, M, Dpd, 1, &Dp_L2); - Dp_L2=Dp_L2*Dp_L2; - -#ifdef DEBUG -printf("norm ||dp|| =%f, norm ||p||=%f\n",Dp_L2,p_L2); -#endif - if(Dp_L2<=eps2_sq*p_L2){ /* relative change in p is small, stop */ - stop=2; - break; - } - - if(Dp_L2>=(p_L2+eps2)/(float)(CLM_EPSILON*CLM_EPSILON)){ /* almost singular */ - stop=4; - break; - } - - /* new function value */ - /* compute ||e(pDp)||_2 */ - /* ### hx=x-hx, pDp_eL2=||hx|| */ - /* copy to device */ - /* hxd<=hx */ - cudakernel_func_fl(ThreadsPerBlock, (Nbase+ThreadsPerBlock-1)/ThreadsPerBlock, pnewd, hxd, M, N, cohd, bbd, Nbase, dp->M, dp->N); - - /* e=x */ - cbstatus=cublasScopy(cbhandle, N, xd, 1, ed, 1); - /* e=x-hx */ - alpha=-1.0f; - cbstatus=cublasSaxpy(cbhandle, N, &alpha, hxd, 1, ed, 1); - /* note: e is updated */ - - /* norm ||e|| */ - cbstatus=cublasSnrm2(cbhandle, N, ed, 1, &pDp_eL2); - pDp_eL2=pDp_eL2*pDp_eL2; - - - if(!finitef(pDp_eL2)){ /* sum of squares is not finite, most probably due to a user error. - */ - stop=7; - break; - } - - /* dL=Dp'*(mu*Dp+jacTe) */ - /* bd=jacTe+mu*Dp */ - cbstatus=cublasScopy(cbhandle, M, jacTed, 1, bd, 1); - cbstatus=cublasSaxpy(cbhandle, M, &mu, Dpd, 1, bd, 1); - cbstatus=cublasSdot(cbhandle, M, Dpd, 1, bd, 1, &dL); - - dF=p_eL2-pDp_eL2; - -#ifdef DEBUG - printf("dF=%f, dL=%f\n",dF,dL); -#endif - if(dL>0.0f && dF>0.0f){ /* reduction in error, increment is accepted */ - tmp=(2.0f*dF/dL-1.0f); - tmp=1.0f-tmp*tmp*tmp; - mu=mu*((tmp>=(float)CLM_ONE_THIRD)? tmp : (float)CLM_ONE_THIRD); - nu=2; - - /* update p's estimate */ - cbstatus=cublasScopy(cbhandle, M, pnewd, 1, pd, 1); - - /* update ||e||_2 */ - p_eL2=pDp_eL2; - break; - } - - } - /* if this point is reached, either the linear system could not be solved or - * the error did not reduce; in any case, the increment must be rejected - */ - - mu*=(float)nu; - nu2=nu<<1; // 2*nu; - if(nu2<=nu){ /* nu has wrapped around (overflown). */ - stop=5; - break; - } - - nu=nu2; - - } /* inner loop */ - - } - if (randomize) { - free(subI); - } -/**************** end OS loop ***************************/ - - } - /**** end iteration loop ***********/ - free(Nos); - free(Nbaseos); - free(edI); - free(NbI); - - if(k>=itmax) stop=3; - - /* copy back current solution */ - err=cudaMemcpyAsync(p,pd,M*sizeof(float),cudaMemcpyDeviceToHost,0); - checkCudaError(err,__FILE__,__LINE__); - checkCublasError(cbstatus,__FILE__,__LINE__); - /* synchronize async operations */ - cudaDeviceSynchronize(); - - cudaFree(devInfo); - cudaFree(work); - if (solve_axb==2) { - cudaFree(rwork); - } - -#ifdef DEBUG - printf("stop=%d\n",stop); -#endif - if(info){ - info[0]=(double)init_p_eL2; - info[1]=(double)p_eL2; - info[2]=(double)jacTe_inf; - info[3]=(double)Dp_L2; - info[4]=(double)mu; - info[5]=(double)k; - info[6]=(double)stop; - info[7]=(double)0; - info[8]=(double)0; - info[9]=(double)0; - } - return 0; -} - - -int -clevmar_der_single_cuda_fl( - float *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - float *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - float *gWORK, /* GPU allocated memory */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - void *adata) /* pointer to possibly additional data, passed uninterpreted to func & jacf. - * Set to NULL if not needed - */ -{ - - /* general note: all device variables end with a 'd' */ - int stop=0; - cudaError_t err; - cublasStatus_t cbstatus; - - int nu=2,nu2; - float p_L2, Dp_L2=(float)DBL_MAX, dF, dL, p_eL2, jacTe_inf=0.0f, pDp_eL2, init_p_eL2; - float tmp,mu=0.0f; - float tau, eps1, eps2, eps2_sq, eps3; - int k,ci,issolved; - - float *hxd; - - float *ed; - float *xd; - - float *jacd; - - float *jacTjacd,*jacTjacd0; - - float *Dpd,*bd; - float *pd,*pnewd; - float *jacTed; - - /* used in QR solver */ - float *taud=0; - - /* used in SVD solver */ - float *Ud=0; - float *VTd=0; - float *Sd=0; - - /* ME data */ - me_data_t *dp=(me_data_t*)adata; - int Nbase=(dp->Nbase)*(ntiles); /* note: we do not use the total tile size */ - /* coherency on device */ - float *cohd; - /* baseline-station map on device/host */ - short *bbd; - - int solve_axb=linsolv; - - /* setup default settings */ - if(opts){ - tau=(float)opts[0]; - eps1=(float)opts[1]; - eps2=(float)opts[2]; - eps2_sq=(float)opts[2]*opts[2]; - eps3=(float)opts[3]; - } else { - tau=(float)CLM_INIT_MU; - eps1=(float)CLM_STOP_THRESH; - eps2=(float)CLM_STOP_THRESH; - eps2_sq=(float)CLM_STOP_THRESH*CLM_STOP_THRESH; - eps3=(float)CLM_STOP_THRESH; - } - - /* calculate no of cuda threads and blocks */ - int ThreadsPerBlock=DEFAULT_TH_PER_BK; - int BlocksPerGrid= 2*(M+ThreadsPerBlock-1)/ThreadsPerBlock; - - - unsigned long int moff; - moff=0; - xd=&gWORK[moff]; - moff+=N; - jacd=&gWORK[moff]; - moff+=M*N; - jacTjacd=&gWORK[moff]; - moff+=M*M; - jacTed=&gWORK[moff]; - moff+=M; - jacTjacd0=&gWORK[moff]; - moff+=M*M; - Dpd=&gWORK[moff]; - moff+=M; - bd=&gWORK[moff]; - moff+=M; - pd=&gWORK[moff]; - moff+=M; - pnewd=&gWORK[moff]; - moff+=M; - cohd=&gWORK[moff]; - moff+=Nbase*8; - hxd=&gWORK[moff]; - moff+=N; - ed=&gWORK[moff]; - moff+=N; - if (solve_axb==1) { - taud=&gWORK[moff]; - moff+=M; - } else if (solve_axb==2) { - Ud=&gWORK[moff]; - moff+=M*M; - VTd=&gWORK[moff]; - moff+=M*M; - Sd=&gWORK[moff]; - moff+=M; - } - bbd=(short*)&gWORK[moff]; - moff+=(Nbase*2*sizeof(short))/sizeof(float); - - /* extra storage for cusolver */ - int work_size=0; - int *devInfo; - int devInfo_h=0; - err=cudaMalloc((void**)&devInfo, sizeof(int)); - checkCudaError(err,__FILE__,__LINE__); - float *work; - float *rwork; - if (solve_axb==0) { - cusolverDnSpotrf_bufferSize(solver_handle, CUBLAS_FILL_MODE_UPPER, M, jacTjacd, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(float)); - checkCudaError(err,__FILE__,__LINE__); - } else if (solve_axb==1) { - cusolverDnSgeqrf_bufferSize(solver_handle, M, M, jacTjacd, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(float)); - checkCudaError(err,__FILE__,__LINE__); - } else { - cusolverDnSgesvd_bufferSize(solver_handle, M, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(float)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&rwork, 5*M*sizeof(float)); - checkCudaError(err,__FILE__,__LINE__); - } - - - - err=cudaMemcpyAsync(pd, p, M*sizeof(float), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - /* need to give right offset for coherencies */ - /* offset: cluster offset+time offset */ - err=cudaMemcpyAsync(cohd, &(dp->ddcohf[(dp->Nbase)*(dp->tilesz)*(dp->clus)*8+(dp->Nbase)*tileoff*8]), Nbase*8*sizeof(float), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - /* correct offset for baselines */ - err=cudaMemcpyAsync(bbd, &(dp->ddbase[2*(dp->Nbase)*(tileoff)]), Nbase*2*sizeof(short), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - cudaDeviceSynchronize(); - /* xd <=x */ - err=cudaMemcpyAsync(xd, x, N*sizeof(float), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - - /* ### compute e=x - f(p) and its L2 norm */ - /* ### e=x-hx, p_eL2=||e|| */ - /* p: params (Mx1), x: data (Nx1), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations*/ - cudakernel_func_fl(ThreadsPerBlock, (Nbase+ThreadsPerBlock-1)/ThreadsPerBlock, pd,hxd,M,N, cohd, bbd, Nbase, dp->M, dp->N); - - /* e=x */ - cbstatus=cublasScopy(cbhandle, N, xd, 1, ed, 1); - /* e=x-hx */ - float alpha=-1.0f; - cbstatus=cublasSaxpy(cbhandle, N, &alpha, hxd, 1, ed, 1); - - /* norm ||e|| */ - cbstatus=cublasSnrm2(cbhandle, N, ed, 1, &p_eL2); - /* square */ - p_eL2=p_eL2*p_eL2; - - init_p_eL2=p_eL2; - if(!finitef(p_eL2)) stop=7; - - - /**** iteration loop ***********/ - for(k=0; kstat mapping, Nbase, Mclusters, Nstations*/ - /* FIXME thread/block sizes 16x16=256, so 16 is chosen */ - cudakernel_jacf_fl(ThreadsPerBlock, ThreadsPerBlock/4, pd, jacd, M, N, cohd, bbd, Nbase, dp->M, dp->N); - - /* Compute J^T J and J^T e */ - /* Cache efficient computation of J^T J based on blocking - */ - /* since J is in ROW major order, assume it is transposed, - so actually calculate A=J*J^T, where J is size MxN */ - //status=culaDeviceSgemm('N','T',M,M,N,1.0f,jacd,M,jacd,M,0.0f,jacTjacd,M); - //checkStatus(status,__FILE__,__LINE__); - float cone=1.0f; float czero=0.0f; - cbstatus=cublasSgemm(cbhandle,CUBLAS_OP_N,CUBLAS_OP_T,M,M,N,&cone,jacd,M,jacd,M,&czero,jacTjacd,M); - - - /* create backup */ - /* copy jacTjacd0<=jacTjacd */ - cbstatus=cublasScopy(cbhandle, M*M, jacTjacd, 1, jacTjacd0, 1); - /* J^T e */ - /* calculate b=J^T*e (actually compute b=J*e, where J in row major (size MxN) */ - //status=culaDeviceSgemv('N',M,N,1.0f,jacd,M,ed,1,0.0f,jacTed,1); - //checkStatus(status,__FILE__,__LINE__); - cbstatus=cublasSgemv(cbhandle,CUBLAS_OP_N,M,N,&cone,jacd,M,ed,1,&czero,jacTed,1); - - - /* Compute ||J^T e||_inf and ||p||^2 */ - /* find infinity norm of J^T e, 1 based indexing*/ - cbstatus=cublasIsamax(cbhandle, M, jacTed, 1, &ci); - err=cudaMemcpy(&jacTe_inf,&(jacTed[ci-1]),sizeof(float),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - /* L2 norm of current parameter values */ - /* norm ||Dp|| */ - cbstatus=cublasSnrm2(cbhandle, M, pd, 1, &p_L2); - p_L2=p_L2*p_L2; - if(jacTe_inf<0.0f) {jacTe_inf=-jacTe_inf;} -#ifdef DEBUG - printf("Inf norm=%f\n",jacTe_inf); -#endif - - /* check for convergence */ - if((jacTe_inf <= eps1)){ - Dp_L2=0.0f; /* no increment for p in this case */ - stop=1; - break; - } - - /* compute initial (k=0) damping factor */ - if (k==0) { - /* find max diagonal element (stride is M+1) */ - /* should be MAX not MAX(ABS) */ - cbstatus=cublasIsamax(cbhandle, M, jacTjacd, M+1, &ci); /* 1 based index */ - ci=(ci-1)*(M+1); /* right value of the diagonal */ - - err=cudaMemcpy(&tmp,&(jacTjacd[ci]),sizeof(float),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - mu=tau*tmp; - } - - - /* determine increment using adaptive damping */ - while(1){ - /* augment normal equations */ - /* increment A => A+ mu*I, increment diagonal entries */ - /* copy jacTjacd<=jacTjacd0 */ - cbstatus=cublasScopy(cbhandle, M*M, jacTjacd0, 1, jacTjacd, 1); - cudakernel_diagmu_fl(ThreadsPerBlock, BlocksPerGrid, M, jacTjacd, mu); - -#ifdef DEBUG - printf("mu=%f\n",mu); -#endif -/*************************************************************************/ - issolved=0; - /* solve augmented equations A x = b */ - /* A==jacTjacd, b==Dpd, after solving, x==Dpd */ - /* b=jacTed : intially right hand side, at exit the solution */ - if (solve_axb==0) { - /* Cholesky solver **********************/ - /* lower triangle of Ad is destroyed */ - //status=culaDeviceSpotrf('U',M,jacTjacd,M); - cusolverDnSpotrf(solver_handle, CUBLAS_FILL_MODE_UPPER, M, jacTjacd, M, work, work_size, devInfo); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (!devInfo_h) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - fprintf(stderr,"Singular matrix\n"); -#endif - } - if (issolved) { - /* copy Dpd<=jacTed */ - cbstatus=cublasScopy(cbhandle, M, jacTed, 1, Dpd, 1); -#ifdef DEBUG - checkCublasError(cbstatus,__FILE__,__LINE__); -#endif - //status=culaDeviceSpotrs('U',M,1,jacTjacd,M,Dpd,M); - cusolverDnSpotrs(solver_handle, CUBLAS_FILL_MODE_UPPER,M,1,jacTjacd,M,Dpd,M,devInfo); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (devInfo_h) { - issolved=0; -#ifdef DEBUG - fprintf(stderr,"Singular matrix\n"); -#endif - } - } - } else if (solve_axb==1) { - /* QR solver ********************************/ - //status=culaDeviceSgeqrf(M,M,jacTjacd,M,taud); - cusolverDnSgeqrf(solver_handle, M, M, jacTjacd, M, taud, work, work_size, devInfo); - cudaDeviceSynchronize(); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (!devInfo_h) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - fprintf(stderr,"Singular matrix\n"); -#endif - } - - if (issolved) { - /* copy Dpd<=jacTed */ - cbstatus=cublasScopy(cbhandle, M, jacTed, 1, Dpd, 1); - //status=culaDeviceSgeqrs(M,M,1,jacTjacd,M,taud,Dpd,M); - cusolverDnSormqr(solver_handle, CUBLAS_SIDE_LEFT, CUBLAS_OP_T, M, 1, M, jacTjacd, M, taud, Dpd, M, work, work_size, devInfo); - cudaDeviceSynchronize(); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (devInfo_h) { - issolved=0; -#ifdef DEBUG - fprintf(stderr,"Singular matrix\n"); -#endif - } else { - cone=1.0f; - cbstatus=cublasStrsm(cbhandle,CUBLAS_SIDE_LEFT,CUBLAS_FILL_MODE_UPPER,CUBLAS_OP_N,CUBLAS_DIAG_NON_UNIT,M,1,&cone,jacTjacd,M,Dpd,M); - } - } - } else { - /* SVD solver *********************************/ - /* U S VT = A */ - //status=culaDeviceSgesvd('A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M); - //checkStatus(status,__FILE__,__LINE__); - cusolverDnSgesvd(solver_handle,'A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M,work,work_size,rwork,devInfo); - cudaDeviceSynchronize(); - /* copy Dpd<=jacTed */ - cbstatus=cublasScopy(cbhandle, M, jacTed, 1, Dpd, 1); - /* b<=U^T * b */ - //status=culaDeviceSgemv('T',M,M,1.0f,Ud,M,Dpd,1,0.0f,Dpd,1); - //checkStatus(status,__FILE__,__LINE__); - cone=1.0f; czero=0.0f; - cbstatus=cublasSgemv(cbhandle,CUBLAS_OP_T,M,M,&cone,Ud,M,Dpd,1,&czero,Dpd,1); - - /* divide by singular values Dpd[]/Sd[] for Sd[]> eps1 */ - cudakernel_diagdiv_fl(ThreadsPerBlock, BlocksPerGrid, M, eps1, Dpd, Sd); - - /* b<=VT^T * b */ - //status=culaDeviceSgemv('T',M,M,1.0f,VTd,M,Dpd,1,0.0f,Dpd,1); - //checkStatus(status,__FILE__,__LINE__); - cbstatus=cublasSgemv(cbhandle,CUBLAS_OP_T,M,M,&cone,VTd,M,Dpd,1,&czero,Dpd,1); - - issolved=1; - } -/*************************************************************************/ - - /* compute p's new estimate and ||Dp||^2 */ - if (issolved) { - /* compute p's new estimate and ||Dp||^2 */ - /* pnew=p+Dp */ - /* pnew=p */ - cbstatus=cublasScopy(cbhandle, M, pd, 1, pnewd, 1); - /* pnew=pnew+Dp */ - alpha=1.0f; - cbstatus=cublasSaxpy(cbhandle, M, &alpha, Dpd, 1, pnewd, 1); - - /* norm ||Dp|| */ - cbstatus=cublasSnrm2(cbhandle, M, Dpd, 1, &Dp_L2); - Dp_L2=Dp_L2*Dp_L2; - -#ifdef DEBUG -printf("norm ||dp|| =%f, norm ||p||=%f\n",Dp_L2,p_L2); -#endif - if(Dp_L2<=eps2_sq*p_L2){ /* relative change in p is small, stop */ - stop=2; - break; - } - - if(Dp_L2>=(p_L2+eps2)/(float)(CLM_EPSILON*CLM_EPSILON)){ /* almost singular */ - stop=4; - break; - } - - /* new function value */ - /* compute ||e(pDp)||_2 */ - /* ### hx=x-hx, pDp_eL2=||hx|| */ - /* copy to device */ - /* hxd<=hx */ - cudakernel_func_fl(ThreadsPerBlock, (Nbase+ThreadsPerBlock-1)/ThreadsPerBlock, pnewd, hxd, M, N, cohd, bbd, Nbase, dp->M, dp->N); - - /* e=x */ - cbstatus=cublasScopy(cbhandle, N, xd, 1, ed, 1); - /* e=x-hx */ - alpha=-1.0f; - cbstatus=cublasSaxpy(cbhandle, N, &alpha, hxd, 1, ed, 1); - /* note: e is updated */ - - /* norm ||e|| */ - cbstatus=cublasSnrm2(cbhandle, N, ed, 1, &pDp_eL2); - pDp_eL2=pDp_eL2*pDp_eL2; - - - if(!finitef(pDp_eL2)){ /* sum of squares is not finite, most probably due to a user error. - */ - stop=7; - break; - } - - /* dL=Dp'*(mu*Dp+jacTe) */ - /* bd=jacTe+mu*Dp */ - cbstatus=cublasScopy(cbhandle, M, jacTed, 1, bd, 1); - cbstatus=cublasSaxpy(cbhandle, M, &mu, Dpd, 1, bd, 1); - cbstatus=cublasSdot(cbhandle, M, Dpd, 1, bd, 1, &dL); - - dF=p_eL2-pDp_eL2; - -#ifdef DEBUG - printf("dF=%f, dL=%f\n",dF,dL); -#endif - if(dL>0.0f && dF>0.0f){ /* reduction in error, increment is accepted */ - tmp=(2.0f*dF/dL-1.0f); - tmp=1.0f-tmp*tmp*tmp; - mu=mu*((tmp>=(float)CLM_ONE_THIRD)? tmp : (float)CLM_ONE_THIRD); - nu=2; - - /* update p's estimate */ - cbstatus=cublasScopy(cbhandle, M, pnewd, 1, pd, 1); - - /* update ||e||_2 */ - p_eL2=pDp_eL2; - break; - } - - } - /* if this point is reached, either the linear system could not be solved or - * the error did not reduce; in any case, the increment must be rejected - */ - - mu*=(float)nu; - nu2=nu<<1; // 2*nu; - if(nu2<=nu){ /* nu has wrapped around (overflown). */ - stop=5; - break; - } - - nu=nu2; - - } /* inner loop */ - - } - /**** end iteration loop ***********/ - - - if(k>=itmax) stop=3; - - /* copy back current solution */ - err=cudaMemcpyAsync(p,pd,M*sizeof(float),cudaMemcpyDeviceToHost,0); - checkCudaError(err,__FILE__,__LINE__); - checkCublasError(cbstatus,__FILE__,__LINE__); - /* synchronize async operations */ - cudaDeviceSynchronize(); - - cudaFree(devInfo); - cudaFree(work); - if (solve_axb==2) { - cudaFree(rwork); - } - -#ifdef DEBUG - printf("stop=%d\n",stop); -#endif - if(info){ - info[0]=(double)init_p_eL2; - info[1]=(double)p_eL2; - info[2]=(double)jacTe_inf; - info[3]=(double)Dp_L2; - info[4]=(double)mu; - info[5]=(double)k; - info[6]=(double)stop; - info[7]=(double)0; - info[8]=(double)0; - info[9]=(double)0; - } - return 0; -} diff --git a/src/lib/Solvers/clmfit_fl.o b/src/lib/Solvers/clmfit_fl.o deleted file mode 100644 index d05e9ac08e3e08f0d0444e7df06f13f43af3fb2c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 68120 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 - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#include "Solvers.h" - -//#define DEBUG - - - -/** keep interface almost the same as in levmar **/ -int -clevmar_der_single_nocuda( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - * info[5]= # iterations, - * info[6]=reason for terminating: 1 - stopped by small gradient J^T e - * 2 - stopped by small Dp - * 3 - stopped by itmax - * 4 - singular matrix. Restart from current p with increased mu - * 5 - no further error reduction is possible. Restart with increased mu - * 6 - stopped by small ||e||_2 - * 7 - stopped by invalid (i.e. NaN or Inf) "func" values. This is a user error - * info[7]= # function evaluations - * info[8]= # Jacobian evaluations - * info[9]= # linear systems solved, i.e. # attempts for reducing error - */ - - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - void *adata) /* pointer to possibly additional data, passed uninterpreted to func & jacf. - * Set to NULL if not needed - */ -{ - - /* general note: all device variables end with a 'd' */ - int stop=0; - int nu=2,nu2; - double p_L2, Dp_L2=DBL_MAX, dF, dL, p_eL2, jacTe_inf=0.0, pDp_eL2, init_p_eL2; - double tmp,mu=0.0; - double tau, eps1, eps2, eps2_sq, eps3; - int k,ci,issolved; - - double *hxd,*hxm=0; - double *ed; - double *jac; - - double *jacTjacd,*jacTjacd0; - - double *pnew,*Dpd,*bd; - double *aones; - double *jacTed; - - /* used in QR solver */ - double *WORK; - int lwork=0; - double w[1]; - - int status; - - /* used in SVD solver */ - double *Ud; - double *VTd; - double *Sd; - - /* for Jacobian evaluation */ - int jac_given; - double delta,tempp,ddiff; - if (!jacf) { - jac_given=0; - /* need more memory for jacobian calculation */ - if ((hxm=(double*)calloc((size_t)N,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - delta=CLM_DIFF_DELTA; - } else { - jac_given=1; - } - - int solve_axb=linsolv; - - /* setup default settings */ - if(opts){ - tau=opts[0]; - eps1=opts[1]; - eps2=opts[2]; - eps2_sq=opts[2]*opts[2]; - eps3=opts[3]; - } else { - tau=CLM_INIT_MU; - eps1=CLM_STOP_THRESH; - eps2=CLM_STOP_THRESH; - eps2_sq=CLM_STOP_THRESH*CLM_STOP_THRESH; - eps3=CLM_STOP_THRESH; - } - - if ((hxd=(double*)calloc((size_t)N,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((ed=(double*)calloc((size_t)N,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((jac=(double*)calloc((size_t)N*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((jacTjacd=(double*)calloc((size_t)M*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((jacTjacd0=(double*)calloc((size_t)M*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((jacTed=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((Dpd=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((bd=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((pnew=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((aones=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - WORK=Ud=Sd=VTd=0; - me_data_t *dt=(me_data_t*)adata; - setweights(M,aones,1.0,dt->Nt); - /* memory allocation: different solvers */ - if (solve_axb==0) { - - } else if (solve_axb==1) { - /* workspace query */ - status=my_dgels('N',M,M,1,jacTjacd,M,Dpd,M,w,-1); - if (!status) { - lwork=(int)w[0]; - if ((WORK=(double*)calloc((size_t)lwork,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - } - } else { - if ((Ud=(double*)calloc((size_t)M*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((VTd=(double*)calloc((size_t)M*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((Sd=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - status=my_dgesvd('A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M,w,-1); - if (!status) { - lwork=(int)w[0]; - if ((WORK=(double*)calloc((size_t)lwork,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - } - } - - - /* ### compute e=x - f(p) and its L2 norm */ - /* ### e=x-hx, p_eL2=||e|| */ - (*func)(p, hxd, M, N, adata); - - - /* e=x */ - my_dcopy(N, x, 1, ed, 1); - /* e=x-hx */ - my_daxpy(N, hxd, -1.0, ed); - - /* norm ||e|| */ - p_eL2=my_dnrm2(N, ed); - /* square */ - p_eL2=p_eL2*p_eL2; - - init_p_eL2=p_eL2; - if(!finite(p_eL2)) stop=7; - - - /**** iteration loop ***********/ - for(k=0; k A+ mu*I, increment diagonal entries */ - /* copy jacTjacd<=jacTjacd0 */ - //cbstatus=cublasDcopy(cbhandle, M*M, jacTjacd0, 1, jacTjacd, 1); - memcpy(jacTjacd,jacTjacd0,M*M*sizeof(double)); - //cudakernel_diagmu(ThreadsPerBlock, BlocksPerGrid, M, jacTjacd, mu); - my_daxpys(M,aones,1,mu,jacTjacd,M+1); - -#ifdef DEBUG - printf("mu=%lf\n",mu); -#endif -/*************************************************************************/ - /* solve augmented equations A x = b */ - /* A==jacTjacd, b==Dpd, after solving, x==Dpd */ - /* b=jacTed : intially right hand side, at exit the solution */ - if (solve_axb==0) { - /* Cholesky solver **********************/ - /* lower triangle of Ad is destroyed */ - status=my_dpotrf('U',M,jacTjacd,M); - if (!status) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - printf("Singular matrix info=%d\n",status); -#endif - } - if (issolved) { - /* copy Dpd<=jacTed */ - //cbstatus=cublasDcopy(cbhandle, M, jacTed, 1, Dpd, 1); - memcpy(Dpd,jacTed,M*sizeof(double)); - status=my_dpotrs('U',M,1,jacTjacd,M,Dpd,M); - if (status) { - issolved=0; -#ifdef DEBUG - printf("Singular matrix info=%d\n",status); -#endif - } - } - } else if (solve_axb==1) { - /* QR solver ********************************/ - /* copy Dpd<=jacTed */ - memcpy(Dpd,jacTed,M*sizeof(double)); - status=my_dgels('N',M,M,1,jacTjacd,M,Dpd,M,WORK,lwork); - if (!status) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - printf("Singular matrix info=%d\n",status); -#endif - } - - } else { - /* SVD solver *********************************/ - /* U S VT = A */ - status=my_dgesvd('A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M,WORK,lwork); - /* copy Dpd<=jacTed */ - //memcpy(Dpd,jacTed,M*sizeof(double)); - memcpy(bd,jacTed,M*sizeof(double)); - /* b<=U^T * b */ - my_dgemv('T',M,M,1.0,Ud,M,bd,1,0.0,Dpd,1); - /* robust correction */ - /* divide by singular values Dpd[]/Sd[] for Sd[]> eps1 */ - for (ci=0; cieps1) { - Dpd[ci]=Dpd[ci]/Sd[ci]; - } else { - Dpd[ci]=0.0; - } - } - - /* b<=VT^T * b */ - memcpy(bd,Dpd,M*sizeof(double)); - my_dgemv('T',M,M,1.0,VTd,M,bd,1,0.0,Dpd,1); - - issolved=1; - } -/*************************************************************************/ - - /* compute p's new estimate and ||Dp||^2 */ - if (issolved) { - /* compute p's new estimate and ||Dp||^2 */ - /* pnew=p+Dp */ - /* pnew=p */ - // cbstatus=cublasDcopy(cbhandle, M, pd, 1, pnewd, 1); - memcpy(pnew,p,M*sizeof(double)); - /* pnew=pnew+Dp */ - //cbstatus=cublasDaxpy(cbhandle, M, &alpha, Dpd, 1, pnewd, 1); - my_daxpy(M,Dpd,1.0,pnew); - - /* norm ||Dp|| */ - //cbstatus=cublasDnrm2(cbhandle, M, Dpd, 1, &Dp_L2); - Dp_L2=my_dnrm2(M,Dpd); - Dp_L2=Dp_L2*Dp_L2; - -#ifdef DEBUG -printf("norm ||dp|| =%lf, norm ||p||=%lf\n",Dp_L2,p_L2); -#endif - if(Dp_L2<=eps2_sq*p_L2){ /* relative change in p is small, stop */ - stop=2; - break; - } - - if(Dp_L2>=(p_L2+eps2)/(CLM_EPSILON*CLM_EPSILON)){ /* almost singular */ - stop=4; - break; - } - - /* new function value */ - (*func)(pnew, hxd, M, N, adata); /* evaluate function at p + Dp */ - - /* compute ||e(pDp)||_2 */ - /* ### hx=x-hx, pDp_eL2=||hx|| */ - /* copy to device */ - /* hxd<=hx */ - //err=cudaMemcpy(hxd, hx, N*sizeof(double), cudaMemcpyHostToDevice); - - /* e=x */ - //cbstatus=cublasDcopy(cbhandle, N, xd, 1, ed, 1); - memcpy(ed,x,N*sizeof(double)); - /* e=x-hx */ - //cbstatus=cublasDaxpy(cbhandle, N, &alpha, hxd, 1, ed, 1); - my_daxpy(N,hxd,-1.0,ed); - /* note: e is updated */ - - /* norm ||e|| */ - //cbstatus=cublasDnrm2(cbhandle, N, ed, 1, &pDp_eL2); - pDp_eL2=my_dnrm2(N,ed); - pDp_eL2=pDp_eL2*pDp_eL2; - - - if(!finite(pDp_eL2)){ /* sum of squares is not finite, most probably due to a user error. - */ - stop=7; - break; - } - - /* dL=Dp'*(mu*Dp+jacTe) */ - /* bd=jacTe+mu*Dp */ - //cbstatus=cublasDcopy(cbhandle, M, jacTed, 1, bd, 1); - memcpy(bd,jacTed,M*sizeof(double)); - //cbstatus=cublasDaxpy(cbhandle, M, &mu, Dpd, 1, bd, 1); - my_daxpy(M,Dpd,mu,bd); - //cbstatus=cublasDdot(cbhandle, M, Dpd, 1, bd, 1, &dL); - dL=my_ddot(M,Dpd,bd); - - dF=p_eL2-pDp_eL2; - -#ifdef DEBUG - printf("dF=%lf, dL=%lf\n",dF,dL); -#endif - if(dL>0.0 && dF>0.0){ /* reduction in error, increment is accepted */ - tmp=(2.0*dF/dL-1.0); - tmp=1.0-tmp*tmp*tmp; - mu=mu*((tmp>=CLM_ONE_THIRD)? tmp : CLM_ONE_THIRD); - nu=2; - - /* update p's estimate */ - //cbstatus=cublasDcopy(cbhandle, M, pnewd, 1, pd, 1); - memcpy(p,pnew,M*sizeof(double)); - - /* update ||e||_2 */ - p_eL2=pDp_eL2; - break; - } - - } - /* if this point is reached, either the linear system could not be solved or - * the error did not reduce; in any case, the increment must be rejected - */ - - mu*=(double)nu; - nu2=nu<<1; // 2*nu; - if(nu2<=nu){ /* nu has wrapped around (overflown). */ - stop=5; - break; - } - - nu=nu2; - - } /* inner loop */ - - } - /**** end iteration loop ***********/ - - - if(k>=itmax) stop=3; - - - free(jac); - free(jacTjacd); - free(jacTjacd0); - free(jacTed); - free(Dpd); - free(bd); - free(hxd); - if (!jac_given) { free(hxm); } - free(ed); - free(aones); - free(pnew); - - if (solve_axb==0) { - } else if (solve_axb==1) { - free(WORK); - } else { - free(Ud); - free(VTd); - free(Sd); - free(WORK); - } -#ifdef DEBUG - printf("stop=%d\n",stop); -#endif - if(info){ - info[0]=init_p_eL2; - info[1]=p_eL2; - info[2]=jacTe_inf; - info[3]=Dp_L2; - info[4]=mu; - info[5]=(double)k; - info[6]=(double)stop; - info[7]=(double)0; - info[8]=(double)0; - info[9]=(double)0; - } - return 0; -} - - -int -mlm_der_single( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[6], /* I: minim. options [\mu, \m, \p0, \p1, \p2, \delta]. - delta: 1 or 2 - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - */ - - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - void *adata) /* pointer to possibly additional data, passed uninterpreted to func & jacf. - * Set to NULL if not needed - */ -{ - /* NOTE F()=func()-data */ - double *Fxk, *Fyk; - double *Jk, *JkTJk, *JkTJk0, *JkTe, *JkTe0; - double *dk,*yk,*dhatk,*sk; - double *Jkdk; - - double lambda; - double *aones; - double mu,m,p0,p1,p2; int delta; - double Fxknrm,Fyknrm,Fykdhatknrm,Fxksknrm,FJkdknrm; - int niter=0; - int p_update=1; - double Fxknrm2,Fxksknrm2; - - double Ak,Pk,rk; - - int ci; - - /* used in QR solver */ - double *WORK=0,*TAU=0,*R=0; - /* used in SVD solver */ - double *Ud=0; - double *VTd=0; - double *Sd=0; - - int lwork=0; - double w[1]; - int status,issolved; - int solve_axb=linsolv; - - - - if (opts) { - mu=opts[0]; - m=opts[1]; - p0=opts[2]; - p1=opts[3]; - p2=opts[4]; - delta=(int)opts[5]; - } else { - mu=1e-5; - m=1e-3; - p0=0.0001; - p1=0.25; - p2=0.75; - delta=1; /* 1 or 2 */ - } - - double epsilon=CLM_EPSILON; - - if ((aones=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } -// for (ci=0;ciNt); - - if ((dk=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((sk=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((dhatk=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((yk=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((Jkdk=(double*)calloc((size_t)N,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((Fxk=(double*)calloc((size_t)N,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((Fyk=(double*)calloc((size_t)N,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((Jk=(double*)calloc((size_t)N*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((JkTJk=(double*)calloc((size_t)M*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((JkTJk0=(double*)calloc((size_t)M*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((JkTe=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((JkTe0=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - /* memory allocation: different solvers */ - if (solve_axb==1) { - /* QR solver ********************************/ - /* workspace query */ - status=my_dgeqrf(M,M,JkTJk,M,TAU,w,-1); - if (!status) { - lwork=(int)w[0]; - if ((WORK=(double*)calloc((size_t)lwork,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - } - if ((R=(double*)calloc((size_t)M*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((TAU=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - } else if (solve_axb==2) { - if ((Ud=(double*)calloc((size_t)M*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((VTd=(double*)calloc((size_t)M*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((Sd=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - status=my_dgesvd('A','A',M,M,JkTJk,M,Sd,Ud,M,VTd,M,w,-1); - if (!status) { - lwork=(int)w[0]; - if ((WORK=(double*)calloc((size_t)lwork,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - } - } - - /* F(x_k) = func()-data */ - /* func() */ - (*func)(p, Fxk, M, N, adata); - /* func() - data */ - my_daxpy(N, x, -1.0, Fxk); - /* find ||Fxk|| */ - Fxknrm=my_dnrm2(N,Fxk); - - double init_Fxknrm=Fxknrm; - - while (niter1) { - lambda=mu*Fxknrm*Fxknrm; - } else { - lambda=mu*Fxknrm; - } - Fxknrm2=Fxknrm*Fxknrm; - - if ( p_update==1 ) { - /* J_k */ - (*jacf)(p, Jk, M, N, adata); - /* Compute J_k^T J_k and -J_k^T F(x_k) */ - my_dgemm('N','T',M,M,N,1.0,Jk,M,Jk,M,0.0,JkTJk0,M); - my_dgemv('N',M,N,-1.0,Jk,M,Fxk,1,0.0,JkTe0,1); - } - /* if || J_k^T F(x_k) || < epsilon, stop */ - Fyknrm=my_dnrm2(M,JkTe0); - if (Fyknrm epsilon */ - for (ci=0; ciepsilon) { - dk[ci]=dk[ci]/Sd[ci]; - } else { - dk[ci]=0.0; - } - } - - /* dk <= VT^T dk */ - memcpy(yk,dk,M*sizeof(double)); - my_dgemv('T',M,M,1.0,VTd,M,yk,1,0.0,dk,1); - - } -/********************************************************************/ - - /* y_k<= x_k+ d_k */ - my_dcopy(M,p,1,yk,1); - my_daxpy(M,dk,1.0,yk); - - /* compute F(y_k) */ - /* func() */ - (*func)(yk, Fyk, M, N, adata); - /* func() - data */ - my_daxpy(N, x, -1.0, Fyk); - - /* Compute -J_k^T F(y_k) */ - my_dgemv('N',M,N,-1.0,Jk,M,Fyk,1,0.0,JkTe,1); - -/********************************************************************/ - if (solve_axb==0) { - /* Cholesky solver **********************/ - /* copy dk<=JkTe */ - memcpy(dhatk,JkTe,M*sizeof(double)); - status=my_dpotrs('U',M,1,JkTJk,M,dhatk,M); - if (status) { - issolved=0; -#ifdef DEBUG - printf("Singular matrix info=%d\n",status); -#endif - } - } else if (solve_axb==1) { - /* QR solver ********************************/ - /* dhatk <= Q^T jacTed */ - my_dgemv('T',M,M,1.0,JkTJk,M,JkTe,1,0.0,dhatk,1); - /* solve R x = b */ - status=my_dtrtrs('U','N','N',M,1,R,M,dhatk,M); - if (!status) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - printf("Singular matrix info=%d\n",status); -#endif - } - } else { - /* SVD solver *********************************/ - /* dhatk <= U^T jacTed */ - my_dgemv('T',M,M,1.0,Ud,M,JkTe,1,0.0,dhatk,1); - /* robust correction */ - /* divide by singular values dk[]/Sd[] for Sd[]> epsilon */ - for (ci=0; ciepsilon) { - dhatk[ci]=dhatk[ci]/Sd[ci]; - } else { - dhatk[ci]=0.0; - } - } - /* dk <= VT^T dk */ - memcpy(yk,dhatk,M*sizeof(double)); - my_dgemv('T',M,M,1.0,VTd,M,yk,1,0.0,dhatk,1); - } -/********************************************************************/ - - - - /* s_k<= d_k+ dhat_k */ - my_dcopy(M,dk,1,sk,1); - my_daxpy(M,dhatk,1.0,sk); - - /* find norms */ - /* || F(y_k) || */ - Fyknrm=my_dnrm2(N,Fyk); - Fyknrm=Fyknrm*Fyknrm; - - /* || F(y_k) + J_k dhat_k || */ - my_dgemv('T',M,N,1.0,Jk,M,dhatk,1,0.0,Jkdk,1); - /* Fyk <= Fyk+ J_k dhat_k */ - my_daxpy(N,Jkdk,1.0,Fyk); - Fykdhatknrm=my_dnrm2(N,Fyk); - Fykdhatknrm=Fykdhatknrm*Fykdhatknrm; - - /* ||F(x_k+d_k+dhat_k)|| == ||F(x_k+s_k)|| */ - /* y_k<= x_k+ s_k */ - my_dcopy(M,p,1,yk,1); - my_daxpy(M,sk,1.0,yk); - (*func)(yk, Fyk, M, N, adata); - /* func() - data */ - my_daxpy(N, x, -1.0, Fyk); - Fxksknrm=my_dnrm2(N,Fyk); - Fxksknrm2=Fxksknrm*Fxksknrm; - - /* || Fxk + J_k d_k || */ - /* J d_k : since J is row major, transpose */ - my_dgemv('T',M,N,1.0,Jk,M,dk,1,0.0,Jkdk,1); - /* Fxk <= Fxk+ J_k d_k or, J_k d_k <= Fxk+ J_k d_k */ - my_daxpy(N,Fxk,1.0,Jkdk); - FJkdknrm=my_dnrm2(N,Jkdk); - FJkdknrm=FJkdknrm*FJkdknrm; - - /* find ratio */ - Ak=Fxknrm2-Fxksknrm2; - Pk=Fxknrm2-FJkdknrm+Fyknrm-Fykdhatknrm; - /* if Pk=p0) { - p_update=1; - /* update p<= p+sk */ - my_daxpy(M,sk,1.0,p); - /* also update auxiliary info */ - /* Fxk <= Fyk */ - my_dcopy(N,Fyk,1,Fxk,1); - Fxknrm=Fxksknrm; - /* new Jk needed */ - } else { /* else no p update */ - p_update=0; - /* use previous Jk, Fxk, JkTJk, JkTe */ - } - if (rk0.25*mu) { - mu=m; - } else { - mu=0.25*mu; - } - } - niter++; - } - - free(aones); - if (solve_axb==1) { - free(WORK); - free(TAU); - free(R); - } else if (solve_axb==2) { - free(WORK); - free(Ud); - free(VTd); - free(Sd); - } - free(Jkdk); - free(dk); - free(dhatk); - free(sk); - free(yk); - free(Fxk); - free(Fyk); - free(Jk); - free(JkTJk0); - free(JkTJk); - free(JkTe); - free(JkTe0); - - if(info){ - info[0]=init_Fxknrm; - info[1]=Fxknrm; - } - return 0; -} - - -/** keep interface almost the same as in levmar **/ -/* OS accel */ -int -oslevmar_der_single_nocuda( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - * info[5]= # iterations, - * info[6]=reason for terminating: 1 - stopped by small gradient J^T e - * 2 - stopped by small Dp - * 3 - stopped by itmax - * 4 - singular matrix. Restart from current p with increased mu - * 5 - no further error reduction is possible. Restart with increased mu - * 6 - stopped by small ||e||_2 - * 7 - stopped by invalid (i.e. NaN or Inf) "func" values. This is a user error - * info[7]= # function evaluations - * info[8]= # Jacobian evaluations - * info[9]= # linear systems solved, i.e. # attempts for reducing error - */ - - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int randomize, /* if >0 randomize */ - void *adata) /* pointer to possibly additional data, passed uninterpreted to func & jacf. - * Set to NULL if not needed - */ -{ - - /* general note: all device variables end with a 'd' */ - int stop=0; - int nu=2,nu2; - double p_L2, Dp_L2=DBL_MAX, dF, dL, p_eL2, jacTe_inf=0.0, pDp_eL2, init_p_eL2; - double tmp,mu=0.0; - double tau, eps1, eps2, eps2_sq, eps3; - int k,ci,issolved; - - double *hxd; - double *ed; - double *jac; - - double *jacTjacd,*jacTjacd0; - - double *pnew,*Dpd,*bd; - double *aones; - double *jacTed; - - /* used in QR solver */ - double *WORK; - int lwork=0; - double w[1]; - - int status; - - /* used in SVD solver */ - double *Ud; - double *VTd; - double *Sd; - - int solve_axb=linsolv; - - /* setup default settings */ - if(opts){ - tau=opts[0]; - eps1=opts[1]; - eps2=opts[2]; - eps2_sq=opts[2]*opts[2]; - eps3=opts[3]; - } else { - tau=CLM_INIT_MU; - eps1=CLM_STOP_THRESH; - eps2=CLM_STOP_THRESH; - eps2_sq=CLM_STOP_THRESH*CLM_STOP_THRESH; - eps3=CLM_STOP_THRESH; - } - - if ((hxd=(double*)calloc((size_t)N,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((ed=(double*)calloc((size_t)N,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((jac=(double*)calloc((size_t)N*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((jacTjacd=(double*)calloc((size_t)M*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((jacTjacd0=(double*)calloc((size_t)M*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((jacTed=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((Dpd=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((bd=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((pnew=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((aones=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - WORK=Ud=Sd=VTd=0; -// for (ci=0;ciNt); - - /* memory allocation: different solvers */ - if (solve_axb==0) { - - } else if (solve_axb==1) { - /* workspace query */ - status=my_dgels('N',M,M,1,jacTjacd,M,Dpd,M,w,-1); - if (!status) { - lwork=(int)w[0]; - if ((WORK=(double*)calloc((size_t)lwork,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - } - } else { - if ((Ud=(double*)calloc((size_t)M*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((VTd=(double*)calloc((size_t)M*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((Sd=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - status=my_dgesvd('A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M,w,-1); - if (!status) { - lwork=(int)w[0]; - lwork=(int)w[0]; - if ((WORK=(double*)calloc((size_t)lwork,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - } - } - - - /* ### compute e=x - f(p) and its L2 norm */ - /* ### e=x-hx, p_eL2=||e|| */ - (*func)(p, hxd, M, N, adata); - - - /* e=x */ - my_dcopy(N, x, 1, ed, 1); - /* e=x-hx */ - my_daxpy(N, hxd, -1.0, ed); - - /* norm ||e|| */ - p_eL2=my_dnrm2(N, ed); - /* square */ - p_eL2=p_eL2*p_eL2; - - init_p_eL2=p_eL2; - if(!finite(p_eL2)) stop=7; - - /* setup OS subsets and stating offsets */ - /* ME data for Jacobian calculation (need a new one) */ - me_data_t lmdata; - me_data_t *lmdata0=(me_data_t*)adata; - lmdata.clus=lmdata0->clus; - lmdata.u=lmdata.v=lmdata.w=0; /* not needed */ - lmdata.Nbase=lmdata0->Nbase; - lmdata.tilesz=lmdata0->tilesz; - lmdata.N=lmdata0->N; - lmdata.carr=lmdata0->carr; - lmdata.M=lmdata0->M; - lmdata.Mt=lmdata0->Mt; - lmdata.freq0=lmdata0->freq0; - lmdata.Nt=lmdata0->Nt; - lmdata.barr=lmdata0->barr; - lmdata.coh=lmdata0->coh; - lmdata.tileoff=lmdata0->tileoff; - /* we work with lmdata0->tilesz tiles, and offset from 0 is lmdata0->tileoff, - so, OS needs to divide this many tiles with the right offset per subset */ - /* barr and coh offsets will be calculated internally */ - /* ed : N, cohd : Nbase*8, bbd : Nbase*2 full size */ - /* if ntilestilesztilesz; } - /* FIXME: is 0.1 of subsets enough ? */ - int max_os_iter=(int)ceil(0.1*(double)Nsubsets); - int Npersubset=(N+Nsubsets-1)/Nsubsets; - int Ntpersubset=(lmdata0->tilesz+Nsubsets-1)/Nsubsets; - int *Nos,*edI,*subI=0,*tileI,*tileoff; - if ((Nos=(int*)calloc((size_t)Nsubsets,sizeof(int)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((edI=(int*)calloc((size_t)Nsubsets,sizeof(int)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((tileI=(int*)calloc((size_t)Nsubsets,sizeof(int)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((tileoff=(int*)calloc((size_t)Nsubsets,sizeof(int)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - int l,ositer;; - k=l=0; - for (ci=0; citileoff+l; - if (l+Ntpersubsettilesz) { - Nos[ci]=Npersubset; - tileI[ci]=Ntpersubset; - } else { - Nos[ci]=N-k; - tileI[ci]=lmdata0->tilesz-l; - } - k=k+Npersubset; - l=l+Ntpersubset; - } - -#ifdef DEBUG - for (ci=0; ci= no. of OS iterations, so select - a random set of subsets */ - /* N, Nbase changes with subset, cohd,bbd,ed gets offsets */ - /* ed : N, cohd : Nbase*8, bbd : Nbase*2 full size */ - /* Compute the Jacobian J at p, J^T J, J^T e, ||J^T e||_inf and ||p||^2. - * Since J^T J is symmetric, its computation can be sped up by computing - * only its upper triangular part and copying it to the lower part - */ - /* note: adata has to advance */ - lmdata.tileoff=tileoff[l]; - lmdata.tilesz=tileI[l]; - (*jacf)(p, jac, M, Nos[l], (void*)&lmdata); - /* Compute J^T J and J^T e */ - /* Cache efficient computation of J^T J based on blocking - */ - /* since J is in ROW major order, assume it is transposed, - so actually calculate A=J*J^T, where J is size MxN */ - my_dgemm('N','T',M,M,Nos[l],1.0,jac,M,jac,M,0.0,jacTjacd,M); - - /* create backup */ - /* copy jacTjacd0<=jacTjacd */ - my_dcopy(M*M,jacTjacd,1,jacTjacd0,1); - /* J^T e */ - /* calculate b=J^T*e (actually compute b=J*e, where J in row major (size MxN) */ - my_dgemv('N',M,Nos[l],1.0,jac,M,&ed[edI[l]],1,0.0,jacTed,1); - - - /* Compute ||J^T e||_inf and ||p||^2 */ - /* find infinity norm of J^T e, 1 based indexing*/ - ci=my_idamax(M,jacTed,1); - memcpy(&jacTe_inf,&(jacTed[ci-1]),sizeof(double)); - /* L2 norm of current parameter values */ - /* norm ||Dp|| */ - p_L2=my_dnrm2(M,p); - p_L2=p_L2*p_L2; - if(jacTe_inf<0.0) {jacTe_inf=-jacTe_inf;} -#ifdef DEBUG - printf("Inf norm=%lf\n",jacTe_inf); -#endif - - /* check for convergence */ - if((jacTe_inf <= eps1)){ - Dp_L2=0.0; /* no increment for p in this case */ - stop=1; - break; - } - - /* compute initial (k=0) damping factor */ - if (k==0) { - /* find max diagonal element (stride is M+1) */ - /* should be MAX not MAX(ABS) */ - ci=my_idamax(M,jacTjacd,M+1); - ci=(ci-1)*(M+1); /* right value of the diagonal */ - - memcpy(&tmp,&(jacTjacd[ci]),sizeof(double)); - mu=tau*tmp; - } - - - /* determine increment using adaptive damping */ - while(1){ - /* augment normal equations */ - /* increment A => A+ mu*I, increment diagonal entries */ - /* copy jacTjacd<=jacTjacd0 */ - memcpy(jacTjacd,jacTjacd0,M*M*sizeof(double)); - my_daxpys(M,aones,1,mu,jacTjacd,M+1); - -#ifdef DEBUG - printf("mu=%lf\n",mu); -#endif -/*************************************************************************/ - /* solve augmented equations A x = b */ - /* A==jacTjacd, b==Dpd, after solving, x==Dpd */ - /* b=jacTed : intially right hand side, at exit the solution */ - if (solve_axb==0) { - /* Cholesky solver **********************/ - /* lower triangle of Ad is destroyed */ - status=my_dpotrf('U',M,jacTjacd,M); - if (!status) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - printf("Singular matrix info=%d\n",status); -#endif - } - if (issolved) { - /* copy Dpd<=jacTed */ - memcpy(Dpd,jacTed,M*sizeof(double)); - status=my_dpotrs('U',M,1,jacTjacd,M,Dpd,M); - if (status) { - issolved=0; -#ifdef DEBUG - printf("Singular matrix info=%d\n",status); -#endif - } - } - } else if (solve_axb==1) { - /* QR solver ********************************/ - /* copy Dpd<=jacTed */ - memcpy(Dpd,jacTed,M*sizeof(double)); - status=my_dgels('N',M,M,1,jacTjacd,M,Dpd,M,WORK,lwork); - if (!status) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - printf("Singular matrix info=%d\n",status); -#endif - } - - } else { - /* SVD solver *********************************/ - /* U S VT = A */ - status=my_dgesvd('A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M,WORK,lwork); - /* copy Dpd<=jacTed */ - memcpy(bd,jacTed,M*sizeof(double)); - /* b<=U^T * b */ - my_dgemv('T',M,M,1.0,Ud,M,bd,1,0.0,Dpd,1); - /* robust correction */ - /* divide by singular values Dpd[]/Sd[] for Sd[]> eps1 */ - for (ci=0; cieps1) { - Dpd[ci]=Dpd[ci]/Sd[ci]; - } else { - Dpd[ci]=0.0; - } - } - - /* b<=VT^T * b */ - memcpy(bd,Dpd,M*sizeof(double)); - my_dgemv('T',M,M,1.0,VTd,M,bd,1,0.0,Dpd,1); - - issolved=1; - } -/*************************************************************************/ - - /* compute p's new estimate and ||Dp||^2 */ - if (issolved) { - /* compute p's new estimate and ||Dp||^2 */ - /* pnew=p+Dp */ - /* pnew=p */ - memcpy(pnew,p,M*sizeof(double)); - /* pnew=pnew+Dp */ - my_daxpy(M,Dpd,1.0,pnew); - - /* norm ||Dp|| */ - Dp_L2=my_dnrm2(M,Dpd); - Dp_L2=Dp_L2*Dp_L2; - -#ifdef DEBUG -printf("norm ||dp|| =%lf, norm ||p||=%lf\n",Dp_L2,p_L2); -#endif - if(Dp_L2<=eps2_sq*p_L2){ /* relative change in p is small, stop */ - stop=2; - break; - } - - if(Dp_L2>=(p_L2+eps2)/(CLM_EPSILON*CLM_EPSILON)){ /* almost singular */ - stop=4; - break; - } - - /* new function value */ - (*func)(pnew, hxd, M, N, adata); /* evaluate function at p + Dp */ - - /* compute ||e(pDp)||_2 */ - /* ### hx=x-hx, pDp_eL2=||hx|| */ - memcpy(ed,x,N*sizeof(double)); - /* e=x-hx */ - my_daxpy(N,hxd,-1.0,ed); - /* note: e is updated */ - - /* norm ||e|| */ - pDp_eL2=my_dnrm2(N,ed); - pDp_eL2=pDp_eL2*pDp_eL2; - - - if(!finite(pDp_eL2)){ /* sum of squares is not finite, most probably due to a user error. - */ - stop=7; - break; - } - - /* dL=Dp'*(mu*Dp+jacTe) */ - /* bd=jacTe+mu*Dp */ - memcpy(bd,jacTed,M*sizeof(double)); - my_daxpy(M,Dpd,mu,bd); - dL=my_ddot(M,Dpd,bd); - - dF=p_eL2-pDp_eL2; - -#ifdef DEBUG - printf("dF=%lf, dL=%lf\n",dF,dL); -#endif - if(dL>0.0 && dF>0.0){ /* reduction in error, increment is accepted */ - tmp=(2.0*dF/dL-1.0); - tmp=1.0-tmp*tmp*tmp; - mu=mu*((tmp>=CLM_ONE_THIRD)? tmp : CLM_ONE_THIRD); - nu=2; - - /* update p's estimate */ - memcpy(p,pnew,M*sizeof(double)); - - /* update ||e||_2 */ - p_eL2=pDp_eL2; - break; - } - - } - /* if this point is reached, either the linear system could not be solved or - * the error did not reduce; in any case, the increment must be rejected - */ - - mu*=(double)nu; - nu2=nu<<1; // 2*nu; - if(nu2<=nu){ /* nu has wrapped around (overflown). */ - stop=5; - break; - } - - nu=nu2; - - } /* inner loop */ - - } - if (randomize) { - free(subI); - } -/**************** end OS loop ***************************/ - - } - /**** end iteration loop ***********/ - free(Nos); - free(edI); - free(tileI); - free(tileoff); - - if(k>=itmax) stop=3; - - - free(jac); - free(jacTjacd); - free(jacTjacd0); - free(jacTed); - free(Dpd); - free(bd); - free(hxd); - free(ed); - free(aones); - free(pnew); - - if (solve_axb==0) { - } else if (solve_axb==1) { - free(WORK); - } else { - free(Ud); - free(VTd); - free(Sd); - free(WORK); - } -#ifdef DEBUG - printf("stop=%d\n",stop); -#endif - if(info){ - info[0]=init_p_eL2; - info[1]=p_eL2; - info[2]=jacTe_inf; - info[3]=Dp_L2; - info[4]=mu; - info[5]=(double)k; - info[6]=(double)stop; - info[7]=(double)0; - info[8]=(double)0; - info[9]=(double)0; - } - return 0; -} diff --git a/src/lib/Solvers/clmfit_nocuda.o b/src/lib/Solvers/clmfit_nocuda.o deleted file mode 100644 index 2c9a0578483b023f6977ef8669f2ba650e983886..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27368 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 - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - -#include "Solvers.h" -#include -#include - -//#define DEBUG -/* build matrix with polynomial terms - B : Npoly x Nf, each row is one basis function - Npoly : total basis functions - Nf: frequencies - freqs: Nfx1 array freqs - freq0: reference freq - type : - 0 :[1 ((f-fo)/fo) ((f-fo)/fo)^2 ...] basis functions - 1 : normalize each row such that norm is 1 - 2 : Bernstein poly \sum N_C_r x^r (1-x)^r where x in [0,1] : use min,max values of freq to normalize - Note: freqs might not be in sorted order, so need to search array to find min,max values - 3: [1 ((f-fo)/fo) (fo/f-1) ((f-fo)/fo)^2 (fo/f-1)^2 ... ] basis, for this case odd Npoly preferred -*/ -int -setup_polynomials(double *B, int Npoly, int Nf, double *freqs, double freq0, int type) { - - if (type==0 || type==1) { - double frat,dsum; - double invf=1.0/freq0; - int ci,cm; - for (ci=0; ci0.0) { - invf=1.0/sqrt(dsum); - } else { - invf=0.0; - } - for (ci=0; ciCLM_EPSILON) { - S[ci]=1.0/S[ci]; - } else { - S[ci]=0.0; - } - my_dscal(Npoly,S[ci],&U[ci*Npoly]); - } - - /* find product U 1/S V^T */ - my_dgemm('N','N',Npoly,Npoly,Npoly,1.0,U,Npoly,VT,Npoly,0.0,Bi,Npoly); - -#ifdef DEBUG - printf("Bii=[\n"); - for (ci=0; ci Q - Bi : NpolyxNpoly matrix = B^T - - for each direction (M values) - select 2N,2N,... : 2Nx Npoly complex values from z (ordered by M) - select real,imag: size 2NxNpoly, 2NxNpoly vectors - reshape to 2NxNpoly => R - reshape to 2NxNpoly => I (imag) - - then Q=([R I] Bi^T) for each column - Q=[R_1^T I_1^T R_2^T I_2^T]^T Bi^T for 2 columns - R_1,I_1,R_2,I_2 : size 2NxNpoly - R : (2N 4) x Npoly - so find Q - */ - double *R,*Q; - if ((R=(double*)calloc((size_t)2*N*Npoly*4,sizeof(double)))==0) { - printf("%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((Q=(double*)calloc((size_t)2*N*Npoly*4,sizeof(double)))==0) { - printf("%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - - int ci,np; - for (ci=0; ciMgfT7bdXuc-h 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 D(vR?g diff --git a/src/lib/Solvers/diag_fl.cu b/src/lib/Solvers/diag_fl.cu deleted file mode 100644 index a222a4e..0000000 --- a/src/lib/Solvers/diag_fl.cu +++ /dev/null @@ -1,270 +0,0 @@ -/* - * - Copyright (C) 2006-2008 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - -#include "cuda.h" -#include -#include - -/* enable this for checking for kernel failure */ -#define CUDA_DBG - -__global__ void -kernel_sqrtdiv_fl(int M, float eps, float *__restrict__ x){ - unsigned int tid = blockIdx.x*blockDim.x + threadIdx.x; - /* make sure to use only M threads */ - if (tideps) { - x[tid]=1.0f/sqrtf(x[tid]); - } else { - x[tid]=0.0f; - } - } -} - -__global__ void -kernel_diagmult_fl(int M, float *__restrict__ U, const float *__restrict__ D) { - - unsigned int tid = blockIdx.x*blockDim.x + threadIdx.x; - /* which column this tid operates on */ - unsigned int col = tid/M; - if (tid>3; /* 0...Ns-1 (because M=total par= 8 * Nstations */ - /* flags are not taken into account */ - if (((stc==sta2)||(stc==sta1))) { - - cuFloatComplex C[4]; - C[0].x=coh[8*n]; - C[0].y=coh[8*n+1]; - C[1].x=coh[8*n+2]; - C[1].y=coh[8*n+3]; - C[2].x=coh[8*n+4]; - C[2].y=coh[8*n+5]; - C[3].x=coh[8*n+6]; - C[3].y=coh[8*n+7]; - - /* which parameter exactly 0..7 */ - int stoff=m-stc*8; - float pp1[8]; - float pp2[8]; - if (stc==sta1) { - for (int cn=0; cn<8; cn++) { - pp1[cn]=0.0f; - pp2[cn]=p[sta2*8+cn]; - } - pp1[stoff]=1.0f; - } else if (stc==sta2) { - for (int cn=0; cn<8; cn++) { - pp2[cn]=0.0f; - pp1[cn]=p[sta1*8+cn]; - } - pp2[stoff]=1.0f; - } - - - cuFloatComplex G1[4]; - G1[0].x=pp1[0]; - G1[0].y=pp1[1]; - G1[1].x=pp1[2]; - G1[1].y=pp1[3]; - G1[2].x=pp1[4]; - G1[2].y=pp1[5]; - G1[3].x=pp1[6]; - G1[3].y=pp1[7]; - - cuFloatComplex T1[4]; - /* T=G1*C */ - T1[0]=cuCaddf(cuCmulf(G1[0],C[0]),cuCmulf(G1[1],C[2])); - T1[1]=cuCaddf(cuCmulf(G1[0],C[1]),cuCmulf(G1[1],C[3])); - T1[2]=cuCaddf(cuCmulf(G1[2],C[0]),cuCmulf(G1[3],C[2])); - T1[3]=cuCaddf(cuCmulf(G1[2],C[1]),cuCmulf(G1[3],C[3])); - - cuFloatComplex G2[4]; - /* conjugate this */ - G2[0].x=pp2[0]; - G2[0].y=-pp2[1]; - G2[2].x=pp2[2]; - G2[2].y=-pp2[3]; - G2[1].x=pp2[4]; - G2[1].y=-pp2[5]; - G2[3].x=pp2[6]; - G2[3].y=-pp2[7]; - - cuFloatComplex T2[4]; - T2[0]=cuCaddf(cuCmulf(T1[0],G2[0]),cuCmulf(T1[1],G2[2])); - T2[1]=cuCaddf(cuCmulf(T1[0],G2[1]),cuCmulf(T1[1],G2[3])); - T2[2]=cuCaddf(cuCmulf(T1[2],G2[0]),cuCmulf(T1[3],G2[2])); - T2[3]=cuCaddf(cuCmulf(T1[2],G2[1]),cuCmulf(T1[3],G2[3])); - /* update jacobian */ - /* NOTE: row major order */ - jac[m+M*8*n]=T2[0].x; - jac[m+M*(8*n+1)]=T2[0].y; - jac[m+M*(8*n+2)]=T2[1].x; - jac[m+M*(8*n+3)]=T2[1].y; - jac[m+M*(8*n+4)]=T2[2].x; - jac[m+M*(8*n+5)]=T2[2].y; - jac[m+M*(8*n+6)]=T2[3].x; - jac[m+M*(8*n+7)]=T2[3].y; - - } - } - -} - - -/* only use extern if calling code is C */ -extern "C" -{ - - -/* cuda driver for calculating jacf() */ -/* p: params (Mx1), jac: jacobian (NxM), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -void -cudakernel_jacf_fl2(float *p, float *jac, int M, int N, float *coh, short *bbh, int Nbase, int Mclus, int Nstations) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - /* NOTE: use small value for ThreadsPerBlock here, like 8 */ - dim3 threadsPerBlock(16, 8); - /* jacobian: Nbase x Nstations (proportional to N), so */ - dim3 numBlocks((Nbase+threadsPerBlock.x-1)/threadsPerBlock.x, - (M+threadsPerBlock.y-1)/threadsPerBlock.y); - /* set memory of jac to zero */ - cudaMemset(jac, 0, N*M*sizeof(float)); - // printf("Kernel Jax data size=%d, params=%d, block=%d,%d, thread=%d,%d, baselines=%d\n",N, M, numBlocks.x,numBlocks.y, threadsPerBlock.x, threadsPerBlock.y, Nbase); - kernel_jacf_fl2<<< numBlocks, threadsPerBlock>>>(Nbase, M, jac, coh, p, bbh, Nstations); - - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - -} - -/* invert sqrt(singular values) 1/Sd[] for Sd[]> eps */ -void -cudakernel_sqrtdiv_fl(int ThreadsPerBlock, int BlocksPerGrid, int M, float eps, float *Sd) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - kernel_sqrtdiv_fl<<< BlocksPerGrid, ThreadsPerBlock >>>(M, eps, Sd); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - -} - - -/* U <= U D, - U : MxM - D : Mx1, diagonal matrix -*/ -void -cudakernel_diagmult_fl(int ThreadsPerBlock, int BlocksPerGrid, int M, float *U, float *D) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - kernel_diagmult_fl<<< BlocksPerGrid, ThreadsPerBlock >>>(M, U, D); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - -} - - -/* diag(J^T J) - d[i] = J[i,:] * J[i,:] - J: NxM (in row major order, so J[i,:] is actually J[:,i] - d: Nx1 -*/ -void -cudakernel_jnorm_fl(int ThreadsPerBlock, int BlocksPerGrid, float *J, int N, int M, float *d) { -#ifdef CUDA_DBG - cudaError_t error; -#endif - kernel_jnorm_fl<<< BlocksPerGrid, ThreadsPerBlock >>>(N,M,J,d); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif -} - -} diff --git a/src/lib/Solvers/diag_fl.o b/src/lib/Solvers/diag_fl.o deleted file mode 100644 index 040faf088c648db401c9d70da4cfc438d73cb0a2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26376 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 diff --git a/src/lib/Solvers/diagnostics.c b/src/lib/Solvers/diagnostics.c deleted file mode 100644 index 3b7f480..0000000 --- a/src/lib/Solvers/diagnostics.c +++ /dev/null @@ -1,550 +0,0 @@ -/* - * - Copyright (C) 2014 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - -#include "Solvers.h" -#include -#include -#include -#include -#include - - -static void -checkCudaError(cudaError_t err, const char *file, int line) -{ -#ifdef CUDA_DEBUG - if(!err) - return; - fprintf(stderr,"GPU (CUDA): %s %s %d\n", cudaGetErrorString(err),file,line); - exit(EXIT_FAILURE); -#endif -} - -static void -checkCublasError(cublasStatus_t cbstatus, char *file, int line) -{ -#ifdef CUDA_DEBUG - if (cbstatus!=CUBLAS_STATUS_SUCCESS) { - fprintf(stderr,"%s: %d: CUBLAS failure\n",file,line); - exit(EXIT_FAILURE); - } -#endif -} - - -/* find for one cluster J (J^T W J+ eW)^-1 J^T and extract diagonal as output - p: parameters M x 1 - rd: residual vector N x 1 (on the device, invarient) - x: (output) diagonal of leverage matrix - - cbhandle,gWORK: BLAS/storage pointers - - tileoff: need for hybrid parameters - - adata: has all additional info: coherency,baselines,flags -*/ -static int -calculate_leverage(float *p, float *rd, float *x, int M, int N, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle, float *gWORK, int tileoff, int ntiles, me_data_t *dp) { - - /* p needs to be copied to device and x needs to be copied back from device - rd always remains in the device (select part with the right offset) - N will change in hybrid mode, so copy back to x with right offset */ - - int Nbase=(dp->Nbase)*(ntiles); /* note: we do not use the total tile size */ - float *jacd,*xd,*jacTjacd,*pd,*cohd,*Ud,*VTd,*Sd; - unsigned long int moff=0; - short *bbd; - - cudaError_t err; - - /* total storage N+M*N+M*M+M+Nbase*8+M*M+M*M+M+M+Nbase*3(short)/(float) */ - xd=&gWORK[moff]; - moff+=N; - jacd=&gWORK[moff]; - moff+=M*N; - jacTjacd=&gWORK[moff]; - moff+=M*M; - pd=&gWORK[moff]; - moff+=M; - cohd=&gWORK[moff]; - moff+=Nbase*8; - Ud=&gWORK[moff]; - moff+=M*M; - VTd=&gWORK[moff]; - moff+=M*M; - Sd=&gWORK[moff]; - moff+=M; - - bbd=(short*)&gWORK[moff]; - moff+=(Nbase*3*sizeof(short))/sizeof(float); - - err=cudaMemcpyAsync(pd, p, M*sizeof(float), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - /* need to give right offset for coherencies */ - /* offset: cluster offset+time offset */ - err=cudaMemcpyAsync(cohd, &(dp->ddcohf[(dp->Nbase)*(dp->tilesz)*(dp->clus)*8+(dp->Nbase)*tileoff*8]), Nbase*8*sizeof(float), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - /* correct offset for baselines */ - err=cudaMemcpyAsync(bbd, &(dp->ddbase[3*(dp->Nbase)*(tileoff)]), Nbase*3*sizeof(short), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - cudaDeviceSynchronize(); - - int ThreadsPerBlock=DEFAULT_TH_PER_BK; - int ci,Mi; - - /* extra storage for cusolver */ - int work_size=0; - int *devInfo; - err=cudaMalloc((void**)&devInfo, sizeof(int)); - checkCudaError(err,__FILE__,__LINE__); - float *work; - float *rwork; - cusolverDnSgesvd_bufferSize(solver_handle, M, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(float)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&rwork, 5*M*sizeof(float)); - checkCudaError(err,__FILE__,__LINE__); - - - /* set mem to 0 */ - cudaMemset(xd, 0, N*sizeof(float)); - - /* calculate J^T, not taking flags into account */ - cudakernel_jacf_fl2(pd, jacd, M, N, cohd, bbd, Nbase, dp->M, dp->N); - - /* calculate JTJ=(J^T J - [e] [W]) */ - //status=culaDeviceSgemm('N','T',M,M,N,1.0f,jacd,M,jacd,M,0.0f,jacTjacd,M); - //checkStatus(status,__FILE__,__LINE__); - cublasStatus_t cbstatus=CUBLAS_STATUS_SUCCESS; - float cone=1.0f; float czero=0.0f; - cbstatus=cublasSgemm(cbhandle,CUBLAS_OP_N,CUBLAS_OP_T,M,M,N,&cone,jacd,M,jacd,M,&czero,jacTjacd,M); - - - /* add mu * I to JTJ */ - cudakernel_diagmu_fl(ThreadsPerBlock, (M+ThreadsPerBlock-1)/ThreadsPerBlock, M, jacTjacd, 1e-9f); - - /* calculate inv(JTJ) using SVD */ - /* inv(JTJ) = Ud x Sid x VTd : we take into account that JTJ is symmetric */ - //status=culaDeviceSgesvd('A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M); - //checkStatus(status,__FILE__,__LINE__); - cusolverDnSgesvd(solver_handle,'A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M,work,work_size,rwork,devInfo); - cudaDeviceSynchronize(); - - - /* find Sd= 1/sqrt(Sd) of the singular values (positive singular values) */ - cudakernel_sqrtdiv_fl(ThreadsPerBlock, (M+ThreadsPerBlock-1)/ThreadsPerBlock, M, 1e-9f, Sd); - - /* multiply Ud with Sid (diagonal) Ud <= Ud Sid (columns modified) */ - cudakernel_diagmult_fl(ThreadsPerBlock, (M*M+ThreadsPerBlock-1)/ThreadsPerBlock, M, Ud, Sd); - /* now multiply Ud VTd to get the square root */ - //status=culaDeviceSgemm('N','N',M,M,M,1.0f,Ud,M,VTd,M,0.0f,jacTjacd,M); - //checkStatus(status,__FILE__,__LINE__); - cbstatus=cublasSgemm(cbhandle,CUBLAS_OP_N,CUBLAS_OP_N,M,M,M,&cone,Ud,M,VTd,M,&czero,jacTjacd,M); - - /* calculate J^T, without taking flags into account (use same storage as previous J^T) */ - cudakernel_jacf_fl2(pd, jacd, M, N, cohd, bbd, Nbase, dp->M, dp->N); - - /* multiply (J^T)^T sqrt(B) == sqrt(B)^T J^T, taking M columns at a time */ - for (ci=0; ci<(N+M-1)/M;ci++) { - if (ci*M+Mpline->data); - int tid=td->tid; - - while(1) { - sync_barrier(&(td->pline->gate1)); /* stop at gate 1*/ - if(td->pline->terminate) break; /* if flag is set, break loop */ - sync_barrier(&(td->pline->gate2)); /* stop at gate 2 */ - /* do work */ - if (gd->status[tid]==PT_DO_CDERIV) { - me_data_t *t=(me_data_t *)gd->lmdata[tid]; - /* divide the tiles into chunks tilesz/nchunk */ - int tilechunk=(t->tilesz+t->carr[t->clus].nchunk-1)/t->carr[t->clus].nchunk; - - int ci; - int cj=0; - int ntiles; - - /* loop over chunk, righ set of parameters and residual vector */ - for (ci=0; cicarr[t->clus].nchunk; ci++) { - /* divide the tiles into chunks tilesz/nchunk */ - if (cj+tilechunktilesz) { - ntiles=tilechunk; - } else { - ntiles=t->tilesz-cj; - } - - /* right offset for rd[] and x[] needed and since no overlap, - can wait for all chunks to complete */ - calculate_leverage(&gd->p[tid][ci*(gd->M[tid])],&gd->rd[tid][8*cj*t->Nbase],&gd->x[tid][8*cj*t->Nbase], gd->M[tid], 8*ntiles*t->Nbase, gd->cbhandle[tid], gd->solver_handle[tid], gd->gWORK[tid], cj, ntiles, gd->lmdata[tid]); - - cj=cj+tilechunk; - } - - } else if (gd->status[tid]==PT_DO_AGPU) { - attach_gpu_to_thread2(tid,&gd->cbhandle[tid],&gd->solver_handle[tid],&gd->gWORK[tid],gd->data_size,1); - - /* copy residual vector to device */ - cudaError_t err; - me_data_t *t=(me_data_t *)gd->lmdata[tid]; - err=cudaMalloc((void**)&gd->rd[tid], (size_t)8*t->tilesz*t->Nbase*sizeof(float)); - checkCudaError(err,__FILE__,__LINE__); - - err=cudaMemcpy(gd->rd[tid], gd->xo, 8*t->tilesz*t->Nbase*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - } else if (gd->status[tid]==PT_DO_DGPU) { - cudaFree(gd->rd[tid]); - detach_gpu_from_thread2(gd->cbhandle[tid],gd->solver_handle[tid],gd->gWORK[tid],1); - } else if (gd->status[tid]!=PT_DO_NOTHING) { /* catch error */ - fprintf(stderr,"%s: %d: invalid mode for slave tid=%d status=%d\n",__FILE__,__LINE__,tid,gd->status[tid]); - exit(1); - } - } - return NULL; -} - -/* initialize the pipeline - and start the slaves rolling */ -static void -init_pipeline_dg(th_pipeline *pline, - void *data) -{ - slave_tdata *t0,*t1; - pthread_attr_init(&(pline->attr)); - pthread_attr_setdetachstate(&(pline->attr),PTHREAD_CREATE_JOINABLE); - - init_th_barrier(&(pline->gate1),3); /* 3 threads, including master */ - init_th_barrier(&(pline->gate2),3); /* 3 threads, including master */ - pline->terminate=0; - pline->data=data; /* data should have pointers to t1 and t2 */ - - if ((t0=(slave_tdata*)malloc(sizeof(slave_tdata)))==0) { - fprintf(stderr,"no free memory\n"); - exit(1); - } - if ((t1=(slave_tdata*)malloc(sizeof(slave_tdata)))==0) { - fprintf(stderr,"no free memory\n"); - exit(1); - } - if ((pline->thst=(taskhist*)malloc(sizeof(taskhist)))==0) { - fprintf(stderr,"no free memory\n"); - exit(1); - } - - init_task_hist(pline->thst); - t0->pline=t1->pline=pline; - t0->tid=0; - t1->tid=1; /* link back t1, t2 to data so they could be freed */ - pline->sd0=t0; - pline->sd1=t1; - pthread_create(&(pline->slave0),&(pline->attr),pipeline_slave_code_dg,(void*)t0); - pthread_create(&(pline->slave1),&(pline->attr),pipeline_slave_code_dg,(void*)t1); -} - - - -/* destroy the pipeline */ -/* need to kill the slaves first */ -static void -destroy_pipeline_dg(th_pipeline *pline) -{ - - pline->terminate=1; - sync_barrier(&(pline->gate1)); - pthread_join(pline->slave0,NULL); - pthread_join(pline->slave1,NULL); - destroy_th_barrier(&(pline->gate1)); - destroy_th_barrier(&(pline->gate2)); - pthread_attr_destroy(&(pline->attr)); - destroy_task_hist(pline->thst); - free(pline->thst); - free(pline->sd0); - free(pline->sd1); - pline->data=NULL; -} -/******************** end pipeline functions **************************/ - - - -/* Calculate St.Laurent-Cook Jacobian leverage - xo: residual (modified) - flags: 2 for flags based on uvcut, 1 for normal flags - coh: coherencies are calculated for all baselines, regardless of flag - diagmode: 1: replace residual, 2: calc noise/leverage ratio - */ -int -calculate_diagnostics(double *u,double *v,double *w,double *p,double *xo,int N,int Nbase,int tilesz,baseline_t *barr, clus_source_t *carr, complex double *coh, int M,int Mt,int diagmode, int Nt) { - - - int cj; - int n; - me_data_t lmdata0,lmdata1; - int Nbase1; - - /* no of data */ - n=Nbase*tilesz*8; - - /* true no of baselines */ - Nbase1=Nbase*tilesz; - - double *ddcoh; - short *ddbase; - - int c0,c1; - - float *ddcohf, *pf, *xdummy0f, *xdummy1f, *res0, *dgf; -/********* thread data ******************/ - /* barrier */ - th_pipeline tp; - gbdatadg tpg; -/****************************************/ - - lmdata0.clus=lmdata1.clus=-1; - /* setup data for lmfit */ - lmdata0.u=lmdata1.u=u; - lmdata0.v=lmdata1.v=v; - lmdata0.w=lmdata1.w=w; - lmdata0.Nbase=lmdata1.Nbase=Nbase; - lmdata0.tilesz=lmdata1.tilesz=tilesz; - lmdata0.N=lmdata1.N=N; - lmdata0.barr=lmdata1.barr=barr; - lmdata0.carr=lmdata1.carr=carr; - lmdata0.M=lmdata1.M=M; - lmdata0.Mt=lmdata1.Mt=Mt; - lmdata0.freq0=lmdata1.freq0=NULL; /* not used */ - lmdata0.Nt=lmdata1.Nt=Nt; - lmdata0.coh=lmdata1.coh=coh; - /* rearrange coh for GPU use */ - if ((ddcoh=(double*)calloc((size_t)(M*Nbase1*8),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((ddcohf=(float*)calloc((size_t)(M*Nbase1*8),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((ddbase=(short*)calloc((size_t)(Nbase1*3),sizeof(short)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - rearrange_coherencies2(Nbase1, barr, coh, ddcoh, ddbase, M, Nt); - lmdata0.ddcoh=lmdata1.ddcoh=ddcoh; - lmdata0.ddbase=lmdata1.ddbase=ddbase; - /* ddcohf (float) << ddcoh (double) */ - double_to_float(ddcohf,ddcoh,M*Nbase1*8,Nt); - lmdata0.ddcohf=lmdata1.ddcohf=ddcohf; - - if ((pf=(float*)calloc((size_t)(Mt*8*N),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - double_to_float(pf,p,Mt*8*N,Nt); - /* residual */ - if ((res0=(float*)calloc((size_t)(n),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - double_to_float(res0,xo,n,Nt); - - /* sum of diagonal values of leverage */ - if ((dgf=(float*)calloc((size_t)(n),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xdummy0f=(float*)calloc((size_t)(n),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xdummy1f=(float*)calloc((size_t)(n),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } -/********** setup threads *******************************/ - /* also calculate the total storage needed to be allocated on a GPU */ - /* determine total size for memory allocation - residual = n (separately allocated) - diagonal = n - For one cluster, - Jacobian = nxm, J^T J = mxm, (also inverse) - */ - int Mm=8*N; /* no of parameters */ - int64_t data_sz=0; - data_sz=(int64_t)(n+Mm*n+3*Mm*Mm+3*Mm+Nbase1*8)*sizeof(float)+(int64_t)Nbase1*3*sizeof(short); - tpg.data_size=data_sz; - tpg.lmdata[0]=&lmdata0; - tpg.lmdata[1]=&lmdata1; - tpg.xo=res0; /* residual */ - - init_pipeline_dg(&tp,&tpg); - sync_barrier(&(tp.gate1)); /* sync at gate 1*/ - tpg.status[0]=tpg.status[1]=PT_DO_AGPU; - sync_barrier(&(tp.gate2)); /* sync at gate 2*/ - - sync_barrier(&(tp.gate1)); /* sync at gate 1*/ - tpg.status[0]=tpg.status[1]=PT_DO_NOTHING; - sync_barrier(&(tp.gate2)); /* sync at gate 2*/ - -/********** done setup threads *******************************/ - tpg.x[0]=xdummy0f; - tpg.M[0]=8*N; /* even though size of p is > M, dont change this */ - tpg.N[0]=n; /* Nbase*tilesz*8 */ - tpg.x[1]=xdummy1f; - tpg.M[1]=8*N; /* even though size of p is > M, dont change this */ - tpg.N[1]=n; /* Nbase*tilesz*8 */ - - for (cj=0; cj1e-6f) { /* can be solved */ - alpha=(r00*a11-r01*a01)/denom; - } else { - alpha=0.0f; - } - beta=(r00-a00*alpha)/a01; - printf("Error Noise/Model %e/%e\n",beta,alpha); - } - free(dgf); - return 0; -} diff --git a/src/lib/Solvers/diagnostics.o b/src/lib/Solvers/diagnostics.o deleted file mode 100644 index 97ed3acb527ebb2cda162029db1040862a52d99f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 50208 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⋘`>q=j6}l?tBjH|-~7V70)IZFWrhBW=N8S$ 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 XmE#ZNX++>K(NGB_i})Gm=QIBYtEURc diff --git a/src/lib/Solvers/lbfgs.c b/src/lib/Solvers/lbfgs.c deleted file mode 100644 index 69bff56..0000000 --- a/src/lib/Solvers/lbfgs.c +++ /dev/null @@ -1,1106 +0,0 @@ -/* - * - Copyright (C) 2006-2008 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#include "Solvers.h" -#include -#include -#include -#include -#include - - -static void -checkCudaError(cudaError_t err, char *file, int line) -{ -#ifdef CUDA_DEBUG - if(!err) - return; - fprintf(stderr,"GPU (CUDA): %s %s %d\n", cudaGetErrorString(err),file,line); - exit(EXIT_FAILURE); -#endif -} -/************************ pipeline **************************/ -/* data struct shared by all threads */ -typedef struct gb_data_b_ { - int status[2]; /* 0: do nothing, - 1: allocate GPU memory, attach GPU - 2: free GPU memory, detach GPU - 3,4..: do work on GPU - 99: reset GPU memory (memest all memory) */ - thread_gpu_data *lmdata[2]; /* two for each thread */ - - /* GPU related info */ - cublasHandle_t cbhandle[2]; /* CUBLAS handles */ - cusolverDnHandle_t solver_handle[2]; /* solver handles */ - double *gWORK[2]; /* GPU buffers */ - int64_t data_size[2]; /* size of buffer (bytes), size gradient vector has different lengths, will be different for each thread */ - /* different pointers to GPU data */ - double *cxo[2]; /* data vector */ - double *ccoh[2]; /* coherency vector */ - double *cpp[2]; /* parameter vector */ - double *cgrad[2]; /* gradient vector */ - short *cbb[2]; /* baseline map */ - int *cptoclus[2]; /* param to cluster map */ - - /* for cost calculation */ - int Nbase[2]; - int boff[2]; - double fcost[2]; - - /* for robust LBFGS */ - int do_robust; -} gbdata_b; - -/* slave thread 2GPU function */ -static void * -pipeline_slave_code_b(void *data) -{ - cudaError_t err; - - slave_tdata *td=(slave_tdata*)data; - gbdata_b *dp=(gbdata_b*)(td->pline->data); - int tid=td->tid; - int Nbase=(dp->lmdata[tid]->Nbase)*(dp->lmdata[tid]->tilesz); - int M=dp->lmdata[tid]->M; - int N=dp->lmdata[tid]->N; - int Nparam=(dp->lmdata[tid]->g_end-dp->lmdata[tid]->g_start+1); - int m=dp->lmdata[tid]->m; - - while(1) { - sync_barrier(&(td->pline->gate1)); /* stop at gate 1*/ - if(td->pline->terminate) break; /* if flag is set, break loop */ - sync_barrier(&(td->pline->gate2)); /* stop at gate 2 */ - /* do work */ - if (dp->status[tid]==PT_DO_CDERIV) { - /* copy the current solution to device */ - err=cudaMemcpy(dp->cpp[tid], dp->lmdata[tid]->p, m*sizeof(double), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - if (!dp->do_robust) { - cudakernel_lbfgs_r(dp->lmdata[tid]->ThreadsPerBlock, dp->lmdata[tid]->BlocksPerGrid, Nbase, dp->lmdata[tid]->tilesz, M, N, Nparam, dp->lmdata[tid]->g_start, dp->cxo[tid], dp->ccoh[tid], dp->cpp[tid], dp->cbb[tid], dp->cptoclus[tid], dp->cgrad[tid]); - } else { - cudakernel_lbfgs_r_robust(dp->lmdata[tid]->ThreadsPerBlock, dp->lmdata[tid]->BlocksPerGrid, Nbase, dp->lmdata[tid]->tilesz, M, N, Nparam, dp->lmdata[tid]->g_start, dp->cxo[tid], dp->ccoh[tid], dp->cpp[tid], dp->cbb[tid], dp->cptoclus[tid], dp->cgrad[tid],dp->lmdata[tid]->robust_nu); - } - /* read back the result */ - err=cudaMemcpy(&(dp->lmdata[tid]->g[dp->lmdata[tid]->g_start]), dp->cgrad[tid], Nparam*sizeof(double), cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - } else if (dp->status[tid]==PT_DO_CCOST) { - /* divide total baselines by 2 */ - int BlocksPerGrid= 2*(dp->Nbase[tid]+dp->lmdata[tid]->ThreadsPerBlock-1)/dp->lmdata[tid]->ThreadsPerBlock; - int boff=dp->boff[tid]; - /* copy the current solution to device */ - err=cudaMemcpy(dp->cpp[tid], dp->lmdata[tid]->p, m*sizeof(double), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - if (!dp->do_robust) { - dp->fcost[tid]=cudakernel_lbfgs_cost(dp->lmdata[tid]->ThreadsPerBlock, BlocksPerGrid, dp->Nbase[tid], boff, M, N, Nbase, &dp->cxo[tid][8*boff], &dp->ccoh[tid][boff*8*M], dp->cpp[tid], &dp->cbb[tid][boff*2], dp->cptoclus[tid]); - } else { - dp->fcost[tid]=cudakernel_lbfgs_cost_robust(dp->lmdata[tid]->ThreadsPerBlock, BlocksPerGrid, dp->Nbase[tid], boff, M, N, Nbase, &dp->cxo[tid][8*boff], &dp->ccoh[tid][boff*8*M], dp->cpp[tid], &dp->cbb[tid][boff*2], dp->cptoclus[tid], dp->lmdata[tid]->robust_nu); - } - } else if (dp->status[tid]==PT_DO_AGPU) { - attach_gpu_to_thread1(select_work_gpu(MAX_GPU_ID,td->pline->thst),&dp->cbhandle[tid],&dp->solver_handle[tid],&dp->gWORK[tid],dp->data_size[tid]); - err=cudaMalloc((void**)&(dp->cxo[tid]),dp->lmdata[tid]->n*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&(dp->ccoh[tid]),Nbase*8*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&(dp->cpp[tid]),m*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&(dp->cgrad[tid]),Nparam*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&(dp->cptoclus[tid]),M*2*sizeof(int)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&(dp->cbb[tid]),Nbase*2*sizeof(short)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMemcpy(dp->cxo[tid], dp->lmdata[tid]->xo, dp->lmdata[tid]->n*sizeof(double), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMemcpy(dp->ccoh[tid], dp->lmdata[tid]->coh, Nbase*8*M*sizeof(double), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMemcpy(dp->cptoclus[tid], dp->lmdata[tid]->ptoclus, M*2*sizeof(int), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMemcpy(dp->cbb[tid], dp->lmdata[tid]->hbb, Nbase*2*sizeof(short), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - - } else if (dp->status[tid]==PT_DO_DGPU) { - cudaFree(dp->cxo[tid]); - cudaFree(dp->ccoh[tid]); - cudaFree(dp->cptoclus[tid]); - cudaFree(dp->cbb[tid]); - cudaFree(dp->cpp[tid]); - cudaFree(dp->cgrad[tid]); - - detach_gpu_from_thread1(dp->cbhandle[tid],dp->solver_handle[tid],dp->gWORK[tid]); - } - - } - return NULL; -} - -/* initialize the pipeline - and start the slaves rolling */ -static void -init_pipeline_b(th_pipeline *pline, - void *data) -{ - slave_tdata *t0,*t1; - pthread_attr_init(&(pline->attr)); - pthread_attr_setdetachstate(&(pline->attr),PTHREAD_CREATE_JOINABLE); - - init_th_barrier(&(pline->gate1),3); /* 3 threads, including master */ - init_th_barrier(&(pline->gate2),3); /* 3 threads, including master */ - pline->terminate=0; - pline->data=data; /* data should have pointers to t1 and t2 */ - - if ((t0=(slave_tdata*)malloc(sizeof(slave_tdata)))==0) { - fprintf(stderr,"no free memory\n"); - exit(1); - } - if ((t1=(slave_tdata*)malloc(sizeof(slave_tdata)))==0) { - fprintf(stderr,"no free memory\n"); - exit(1); - } - if ((pline->thst=(taskhist*)malloc(sizeof(taskhist)))==0) { - fprintf(stderr,"no free memory\n"); - exit(1); - } - - init_task_hist(pline->thst); - t0->pline=t1->pline=pline; - t0->tid=0; - t1->tid=1; /* link back t1, t2 to data so they could be freed */ - pline->sd0=t0; - pline->sd1=t1; - pthread_create(&(pline->slave0),&(pline->attr),pipeline_slave_code_b,(void*)t0); - pthread_create(&(pline->slave1),&(pline->attr),pipeline_slave_code_b,(void*)t1); -} - - -/* destroy the pipeline */ -/* need to kill the slaves first */ -static void -destroy_pipeline_b(th_pipeline *pline) -{ - pline->terminate=1; - sync_barrier(&(pline->gate1)); - pthread_join(pline->slave0,NULL); - pthread_join(pline->slave1,NULL); - destroy_th_barrier(&(pline->gate1)); - destroy_th_barrier(&(pline->gate2)); - pthread_attr_destroy(&(pline->attr)); - destroy_task_hist(pline->thst); - free(pline->thst); - free(pline->sd0); - free(pline->sd1); - pline->data=NULL; -} -/************************ end pipeline **************************/ - -/* use algorithm 9.1 to compute pk=Hk gk */ -/* pk,gk: size m x 1 - s, y: size mM x 1 - rho: size M x 1 - ii: true location of the k th values in s,y */ -static void -mult_hessian(int m, double *pk, double *gk, double *s, double *y, double *rho, int M, int ii) { - int ci; - double *alphai; - int *idx; /* store sorted locations of s, y here */ - double gamma,beta; - - if ((alphai=(double*)calloc((size_t)M,sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((idx=(int*)calloc((size_t)M,sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if (M>0) { - /* find the location of k-1 th value */ - if (ii>0) { - ii=ii-1; - } else { - ii=M-1; - } - /* s,y will have 0,1,...,ii,ii+1,...M-1 */ - /* map this to ii+1,ii+2,...,M-1,0,1,..,ii */ - for (ci=0; ci%d ",ci,idx[ci]); - } - printf("\n"); -#endif - /* q = grad(f)k : pk<=gk */ - my_dcopy(m,gk,1,pk,1); - /* this should be done in the right order */ - for (ci=0; ci0) { - gamma=my_ddot(m,&s[m*idx[M-1]],&y[m*idx[M-1]]); - gamma/=my_ddot(m,&y[m*idx[M-1]],&y[m*idx[M-1]]); - /* Hk(0)=gamma I, so scale q by gamma */ - /* r= Hk(0) q */ - my_dscal(m,gamma,pk); - } - - for (ci=0; cib is possible) - to find step that minimizes cost function */ -/* func: vector function - xk: parameter values size m x 1 (at which step is calculated) - pk: step direction size m x 1 (x(k+1)=x(k)+alphak * pk) - a/b: interval for interpolation - x: size n x 1 (storage) - xp: size m x 1 (storage) - xo: observed data size n x 1 - n: size of vector function - step: step size for differencing - adata: additional data passed to the function -*/ -static double -cubic_interp( - void (*func)(double *p, double *hx, int m, int n, void *adata), - double *xk, double *pk, double a, double b, double *x, double *xp, double *xo, int m, int n, double step, void *adata, th_pipeline *tp, gbdata_b *tpg) { - - double f0,f1,f0d,f1d; /* function values and derivatives at a,b */ - double p01,p02,z0,fz0; - double aa,cc; - - my_dcopy(m,xk,1,xp,1); /* xp<=xk */ - my_daxpy(m,pk,a,xp); /* xp<=xp+(a)*pk */ -// func(xp,x,m,n,adata); -// my_daxpy(n,xo,-1.0,x); -// f0=my_dnrm2(n,x); -// f0*=f0; - sync_barrier(&(tp->gate1)); - tpg->lmdata[0]->p=tpg->lmdata[1]->p=xp; - tpg->status[0]=tpg->status[1]=PT_DO_CCOST; - sync_barrier(&(tp->gate2)); - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_NOTHING; - f0=tpg->fcost[0]+tpg->fcost[1]; - sync_barrier(&(tp->gate2)); - - /* grad(phi_0): evaluate at -step and +step */ - my_daxpy(m,pk,step,xp); /* xp<=xp+(a+step)*pk */ -// func(xp,x,m,n,adata); -// my_daxpy(n,xo,-1.0,x); -// p01=my_dnrm2(n,x); - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_CCOST; - sync_barrier(&(tp->gate2)); - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_NOTHING; - p01=tpg->fcost[0]+tpg->fcost[1]; - sync_barrier(&(tp->gate2)); - - - my_daxpy(m,pk,-2.0*step,xp); /* xp<=xp+(a-step)*pk */ -// func(xp,x,m,n,adata); -// my_daxpy(n,xo,-1.0,x); -// p02=my_dnrm2(n,x); - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_CCOST; - sync_barrier(&(tp->gate2)); - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_NOTHING; - p02=tpg->fcost[0]+tpg->fcost[1]; - sync_barrier(&(tp->gate2)); - - -// f0d=(p01*p01-p02*p02)/(2.0*step); - f0d=(p01-p02)/(2.0*step); - - my_dcopy(m,xk,1,xp,1); /* xp<=xk */ - my_daxpy(m,pk,b,xp); /* xp<=xp+(b)*pk */ -// func(xp,x,m,n,adata); -// my_daxpy(n,xo,-1.0,x); -// f1=my_dnrm2(n,x); -// f1*=f1; - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_CCOST; - sync_barrier(&(tp->gate2)); - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_NOTHING; - f1=tpg->fcost[0]+tpg->fcost[1]; - sync_barrier(&(tp->gate2)); - - - /* grad(phi_1): evaluate at -step and +step */ - my_daxpy(m,pk,step,xp); /* xp<=xp+(b+step)*pk */ -// func(xp,x,m,n,adata); -// my_daxpy(n,xo,-1.0,x); -// p01=my_dnrm2(n,x); - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_CCOST; - sync_barrier(&(tp->gate2)); - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_NOTHING; - p01=tpg->fcost[0]+tpg->fcost[1]; - sync_barrier(&(tp->gate2)); - - - my_daxpy(m,pk,-2.0*step,xp); /* xp<=xp+(b-step)*pk */ -// func(xp,x,m,n,adata); -// my_daxpy(n,xo,-1.0,x); -// p02=my_dnrm2(n,x); - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_CCOST; - sync_barrier(&(tp->gate2)); - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_NOTHING; - p02=tpg->fcost[0]+tpg->fcost[1]; - sync_barrier(&(tp->gate2)); - - -// f1d=(p01*p01-p02*p02)/(2.0*step); - f1d=(p01-p02)/(2.0*step); - - - //printf("Interp a,f(a),f'(a): (%lf,%lf,%lf) (%lf,%lf,%lf)\n",a,f0,f0d,b,f1,f1d); - /* cubic poly in [0,1] is f0+f0d z+eta z^2+xi z^3 - where eta=3(f1-f0)-2f0d-f1d, xi=f0d+f1d-2(f1-f0) - derivative f0d+2 eta z+3 xi z^2 => cc+bb z+aa z^2 */ - aa=3.0*(f0-f1)/(b-a)+(f1d-f0d); - p01=aa*aa-f0d*f1d; - /* root exist? */ - if (p01>0.0) { - /* root */ - cc=sqrt(p01); - z0=b-(f1d+cc-aa)*(b-a)/(f1d-f0d+2.0*cc); - /* FIXME: check if this is within boundary */ - aa=MAX(a,b); - cc=MIN(a,b); - //printf("Root=%lf, in [%lf,%lf]\n",z0,cc,aa); - if (z0>aa || z0gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_CCOST; - sync_barrier(&(tp->gate2)); - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_NOTHING; - fz0=tpg->fcost[0]+tpg->fcost[1]; - sync_barrier(&(tp->gate2)); - - } - //printf("Val=%lf, [%lf,%lf]\n",fz0,f0,f1); - - /* now choose between f0,f1,fz0,fz1 */ - if (f0b) is possible - x: size n x 1 (storage) - xp: size m x 1 (storage) - phi_0: phi(0) - gphi_0: grad(phi(0)) - xo: observed data size n x 1 - n: size of vector function - step: step size for differencing - adata: additional data passed to the function -*/ -static double -linesearch_zoom( - void (*func)(double *p, double *hx, int m, int n, void *adata), - double *xk, double *pk, double a, double b, double *x, double *xp, double phi_0, double gphi_0, double sigma, double rho, double t1, double t2, double t3, double *xo, int m, int n, double step, void *adata, th_pipeline *tp, gbdata_b *tpg) { - - double alphaj,phi_j,phi_aj; - double gphi_j,p01,p02,aj,bj; - double alphak=1.0; - int ci,found_step=0; - - aj=a; - bj=b; - ci=0; - while(ci<10) { - /* choose alphaj from [a+t2(b-a),b-t3(b-a)] */ - p01=aj+t2*(bj-aj); - p02=bj-t3*(bj-aj); - alphaj=cubic_interp(func,xk,pk,p01,p02,x,xp,xo,m,n,step,adata,tp,tpg); - //printf("cubic intep [%lf,%lf]->%lf\n",p01,p02,alphaj); - - /* evaluate phi(alphaj) */ - my_dcopy(m,xk,1,xp,1); /* xp<=xk */ - my_daxpy(m,pk,alphaj,xp); /* xp<=xp+(alphaj)*pk */ -// func(xp,x,m,n,adata); - /* calculate x<=x-xo */ -// my_daxpy(n,xo,-1.0,x); -// phi_j=my_dnrm2(n,x); -// phi_j*=phi_j; - sync_barrier(&(tp->gate1)); - tpg->lmdata[0]->p=tpg->lmdata[1]->p=xp; - tpg->status[0]=tpg->status[1]=PT_DO_CCOST; - sync_barrier(&(tp->gate2)); - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_NOTHING; - phi_j=tpg->fcost[0]+tpg->fcost[1]; - sync_barrier(&(tp->gate2)); - - - /* evaluate phi(aj) */ - my_dcopy(m,xk,1,xp,1); /* xp<=xk */ - my_daxpy(m,pk,aj,xp); /* xp<=xp+(alphaj)*pk */ -// func(xp,x,m,n,adata); - /* calculate x<=x-xo */ -// my_daxpy(n,xo,-1.0,x); -// phi_aj=my_dnrm2(n,x); -// phi_aj*=phi_aj; - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_CCOST; - sync_barrier(&(tp->gate2)); - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_NOTHING; - phi_aj=tpg->fcost[0]+tpg->fcost[1]; - sync_barrier(&(tp->gate2)); - - - if ((phi_j>phi_0+rho*alphaj*gphi_0) || phi_j>=phi_aj) { - bj=alphaj; /* aj unchanged */ - } else { - /* evaluate grad(alphaj) */ - my_dcopy(m,xk,1,xp,1); /* xp<=xk */ - my_daxpy(m,pk,alphaj+step,xp); /* xp<=xp+(alphaj+step)*pk */ -// func(xp,x,m,n,adata); - /* calculate x<=x-xo */ -// my_daxpy(n,xo,-1.0,x); -// p01=my_dnrm2(n,x); - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_CCOST; - sync_barrier(&(tp->gate2)); - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_NOTHING; - p01=tpg->fcost[0]+tpg->fcost[1]; - sync_barrier(&(tp->gate2)); - - - my_daxpy(m,pk,-2.0*step,xp); /* xp<=xp+(alphaj-step)*pk */ -// func(xp,x,m,n,adata); - /* calculate x<=x-xo */ -// my_daxpy(n,xo,-1.0,x); -// p02=my_dnrm2(n,x); - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_CCOST; - sync_barrier(&(tp->gate2)); - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_NOTHING; - p02=tpg->fcost[0]+tpg->fcost[1]; - sync_barrier(&(tp->gate2)); - - -// gphi_j=(p01*p01-p02*p02)/(2.0*step); - gphi_j=(p01-p02)/(2.0*step); - - /* termination due to roundoff/other errors pp. 38, Fletcher */ - if ((aj-alphaj)*gphi_j<=step) { - alphak=alphaj; - found_step=1; - break; - } - - if (fabs(gphi_j)<=-sigma*gphi_0) { - alphak=alphaj; - found_step=1; - break; - } - - if (gphi_j*(bj-aj)>=0) { - bj=aj; - } /* else bj unchanged */ - aj=alphaj; - } - ci++; - } - - if (!found_step) { - /* use bound to find possible step */ - alphak=alphaj; - } - -#ifdef DEBUG - printf("Found %lf Interval [%lf,%lf]\n",alphak,a,b); -#endif - return alphak; -} - - - -/* line search */ -/* func: vector function - xk: parameter values size m x 1 (at which step is calculated) - pk: step direction size m x 1 (x(k+1)=x(k)+alphak * pk) - alpha1: initial value for step - sigma,rho,t1,t2,t3: line search parameters (from Fletcher) - xo: observed data size n x 1 - n: size of vector function - step: step size for differencing - adata: additional data passed to the function -*/ -static double -linesearch( - void (*func)(double *p, double *hx, int m, int n, void *adata), - double *xk, double *pk, double alpha1, double sigma, double rho, double t1, double t2, double t3, double *xo, int m, int n, double step, void *adata, th_pipeline *tp, gbdata_b *tpg) { - /* phi(alpha)=f(xk+alpha pk) - for vector function func - f(xk) =||func(xk)||^2 */ - - double *x,*xp; - double alphai,alphai1; - double phi_0,phi_alphai,phi_alphai1; - double p01,p02; - double gphi_0,gphi_i; - double alphak; - - double mu; - double tol; /* lower limit for minimization, need to be just about min value of cost function */ - - int ci; - - if ((x=(double*)calloc((size_t)n,sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xp=(double*)calloc((size_t)m,sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - - alphak=1.0; - /* evaluate phi_0 and grad(phi_0) */ -//func(xk,x,m,n,adata); -//my_daxpy(n,xo,-1.0,x); -//phi_0=my_dnrm2(n,x); -//phi_0*=phi_0; -//printf("CPU cost=%lf\n",phi_0); - sync_barrier(&(tp->gate1)); - tpg->lmdata[0]->p=tpg->lmdata[1]->p=xk; - tpg->status[0]=tpg->status[1]=PT_DO_CCOST; - sync_barrier(&(tp->gate2)); - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_NOTHING; - phi_0=tpg->fcost[0]+tpg->fcost[1]; - sync_barrier(&(tp->gate2)); -//printf("GPU cost=%lf\n",phi_0); - /* select tolarance 1/100 of current function value */ - tol=MIN(0.01*phi_0,1e-6); - - /* grad(phi_0): evaluate at -step and +step */ - my_dcopy(m,xk,1,xp,1); /* xp<=xk */ - my_daxpy(m,pk,step,xp); /* xp<=xp+(0.0+step)*pk */ - -//func(xp,x,m,n,adata); - /* calculate x<=x-xo */ -//my_daxpy(n,xo,-1.0,x); -//p01=my_dnrm2(n,x); - sync_barrier(&(tp->gate1)); - tpg->lmdata[0]->p=tpg->lmdata[1]->p=xp; - tpg->status[0]=tpg->status[1]=PT_DO_CCOST; - sync_barrier(&(tp->gate2)); - sync_barrier(&(tp->gate1)); - p01=tpg->fcost[0]+tpg->fcost[1]; - tpg->status[0]=tpg->status[1]=PT_DO_NOTHING; - sync_barrier(&(tp->gate2)); - - my_daxpy(m,pk,-2.0*step,xp); /* xp<=xp+(0.0-step)*pk */ -//func(xp,x,m,n,adata); - /* calculate x<=x-xo */ -//my_daxpy(n,xo,-1.0,x); -//p02=my_dnrm2(n,x); - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_CCOST; - sync_barrier(&(tp->gate2)); - sync_barrier(&(tp->gate1)); - p02=tpg->fcost[0]+tpg->fcost[1]; - tpg->status[0]=tpg->status[1]=PT_DO_NOTHING; - sync_barrier(&(tp->gate2)); - - -// gphi_0=(p01*p01-p02*p02)/(2.0*step); - gphi_0=(p01-p02)/(2.0*step); - - - /* estimate for mu */ - /* mu = (tol-phi_0)/(rho gphi_0) */ - mu=(tol-phi_0)/(rho*gphi_0); -#ifdef DEBUG - printf("cost=%lf grad=%lf mu=%lf, alpha1=%lf\n",phi_0,gphi_0,mu,alpha1); -#endif - - ci=1; - alphai=alpha1; /* initial value for alpha(i) : check if 0gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_CCOST; - sync_barrier(&(tp->gate2)); - sync_barrier(&(tp->gate1)); - phi_alphai=tpg->fcost[0]+tpg->fcost[1]; - tpg->status[0]=tpg->status[1]=PT_DO_NOTHING; - sync_barrier(&(tp->gate2)); - - - if (phi_alphaiphi_0+alphai*gphi_0) || (ci>1 && phi_alphai>=phi_alphai1)) { - /* ai=alphai1, bi=alphai bracket */ - alphak=linesearch_zoom(func,xk,pk,alphai1,alphai,x,xp,phi_0,gphi_0,sigma,rho,t1,t2,t3,xo,m,n,step,adata,tp,tpg); -#ifdef DEBUG - printf("Linesearch : Condition 1 met\n"); -#endif - break; - } - - /* evaluate grad(phi(alpha(i))) */ - my_dcopy(m,xk,1,xp,1); /* NOT NEEDED here?? xp<=xk */ - my_daxpy(m,pk,alphai+step,xp); /* xp<=xp+(alphai+step)*pk */ -// func(xp,x,m,n,adata); - /* calculate x<=x-xo */ -// my_daxpy(n,xo,-1.0,x); -// p01=my_dnrm2(n,x); - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_CCOST; - sync_barrier(&(tp->gate2)); - sync_barrier(&(tp->gate1)); - p01=tpg->fcost[0]+tpg->fcost[1]; - tpg->status[0]=tpg->status[1]=PT_DO_NOTHING; - sync_barrier(&(tp->gate2)); - - my_daxpy(m,pk,-2.0*step,xp); /* xp<=xp+(alphai-step)*pk */ -// func(xp,x,m,n,adata); - /* calculate x<=x-xo */ -// my_daxpy(n,xo,-1.0,x); -// p02=my_dnrm2(n,x); - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_CCOST; - sync_barrier(&(tp->gate2)); - sync_barrier(&(tp->gate1)); - p02=tpg->fcost[0]+tpg->fcost[1]; - tpg->status[0]=tpg->status[1]=PT_DO_NOTHING; - sync_barrier(&(tp->gate2)); - - -// gphi_i=(p01*p01-p02*p02)/(2.0*step); - gphi_i=(p01-p02)/(2.0*step); - - if (fabs(gphi_i)<=-sigma*gphi_0) { - alphak=alphai; -#ifdef DEBUG - printf("Linesearch : Condition 2 met\n"); -#endif - break; - } - - if (gphi_i>=0) { - /* ai=alphai, bi=alphai1 bracket */ - alphak=linesearch_zoom(func,xk,pk,alphai,alphai1,x,xp,phi_0,gphi_0,sigma,rho,t1,t2,t3,xo,m,n,step,adata,tp,tpg); -#ifdef DEBUG - printf("Linesearch : Condition 3 met\n"); -#endif - break; - } - - /* else preserve old values */ - if (mu<=(2.0*alphai-alphai1)) { - /* next step */ - alphai1=alphai; - alphai=mu; - } else { - /* choose by interpolation in [2*alphai-alphai1,min(mu,alphai+t1*(alphai-alphai1)] */ - p01=2.0*alphai-alphai1; - p02=MIN(mu,alphai+t1*(alphai-alphai1)); - alphai=cubic_interp(func,xk,pk,p01,p02,x,xp,xo,m,n,step,adata,tp,tpg); - //printf("cubic interp [%lf,%lf]->%lf\n",p01,p02,alphai); - } - phi_alphai1=phi_alphai; - - ci++; - } - - - - free(x); - free(xp); -#ifdef DEBUG - printf("Step size=%lf\n",alphak); -#endif - return alphak; -} -/*************** END Fletcher line search **********************************/ - - -/* note M here is LBFGS memory size */ -static int -lbfgs_fit_common( - void (*func)(double *p, double *hx, int m, int n, void *adata), - double *p, double *x, int m, int n, int itmax, int M, int gpu_threads, int do_robust, void *adata) { - - double *gk; /* gradients at both k+1 and k iter */ - double *xk1,*xk; /* parameters at k+1 and k iter */ - double *pk; /* step direction H_k * grad(f) */ - - double step; /* FIXME tune for GPU, use larger if far away from convergence */ - double *y, *s; /* storage for delta(grad) and delta(p) */ - double *rho; /* storage for 1/yk^T*sk */ - int ci,ck,cm; - double alphak=1.0; - - - me_data_t *dp=(me_data_t*)adata; - short *hbb; - int *ptoclus; - int Nbase1=dp->Nbase*dp->tilesz; - - thread_gpu_data threaddata[2]; /* 2 for 2 threads/cards */ - - if ((gk=(double*)calloc((size_t)m,sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xk1=(double*)calloc((size_t)m,sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xk=(double*)calloc((size_t)m,sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - - if ((pk=(double*)calloc((size_t)m,sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - - - /* storage size mM x 1*/ - if ((s=(double*)calloc((size_t)m*M,sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((y=(double*)calloc((size_t)m*M,sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((rho=(double*)calloc((size_t)M,sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - -/*********** following are not part of LBFGS, but done here only for GPU use */ - /* auxilliary arrays for GPU */ - if ((hbb=(short*)calloc((size_t)(Nbase1*2),sizeof(short)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - /* baseline->station mapping */ - rearrange_baselines(Nbase1, dp->barr, hbb, dp->Nt); - - /* parameter->cluster mapping */ - /* for each cluster: chunk size, start param index */ - if ((ptoclus=(int*)calloc((size_t)(2*dp->M),sizeof(int)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - for(ci=0; ciM; ci++) { - ptoclus[2*ci]=dp->carr[ci].nchunk; - ptoclus[2*ci+1]=dp->carr[ci].p[0]; /* so end at p[0]+nchunk*8*N-1 */ - } - dp->hbb=hbb; - dp->ptoclus=ptoclus; -/*****************************************************************************/ - /* choose 256 threads per block for high occupancy */ - int ThreadsPerBlock = gpu_threads; - - /* partition parameters, per each parameter, one thread */ - /* also account for the no of GPUs using */ - /* parameters per thread (GPU) */ - int Nparm=(m+2-1)/2; - /* find number of blocks */ - int BlocksPerGrid = 2* (Nparm+ThreadsPerBlock-1)/ThreadsPerBlock; - ci=0; - int nth; - for (nth=0; nth<2; nth++) { - threaddata[nth].ThreadsPerBlock=ThreadsPerBlock; - threaddata[nth].BlocksPerGrid= 2*BlocksPerGrid; - threaddata[nth].card=nth; - threaddata[nth].Nbase=dp->Nbase; - threaddata[nth].tilesz=dp->tilesz; - threaddata[nth].barr=dp->barr; - threaddata[nth].M=dp->M; - threaddata[nth].N=dp->N; - threaddata[nth].coh=dp->coh; - threaddata[nth].xo=x; - threaddata[nth].p=p; - threaddata[nth].g=gk; - threaddata[nth].m=m; - threaddata[nth].n=n; - threaddata[nth].hbb=dp->hbb; - threaddata[nth].ptoclus=dp->ptoclus; - threaddata[nth].g_start=ci; - threaddata[nth].g_end=ci+Nparm-1; - if (threaddata[nth].g_end>=m) { - threaddata[nth].g_end=m-1; - } - /* for robust mode */ - if (do_robust) { - threaddata[nth].robust_nu=dp->robust_nu; - } - ci=ci+Nparm; - } - - /* pipeline data */ - th_pipeline tp; - gbdata_b tpg; - - tpg.do_robust=do_robust; - /* divide no of baselines */ - int Nthb0=(Nbase1+2-1)/2; - tpg.Nbase[0]=Nthb0; - tpg.Nbase[1]=Nbase1-Nthb0; - tpg.boff[0]=0; - tpg.boff[1]=Nthb0; - - tpg.lmdata[0]=&threaddata[0]; - tpg.lmdata[1]=&threaddata[1]; - /* calculate total size of memory need to be allocated in GPU, in bytes +2 added to align memory */ - /* note: we do not allocate memory here, use pinned memory for transfer */ - //tpg.data_size[0]=(n+(dp->Nbase*dp->tilesz)*8*dp->M+m+(tpg.lmdata[0]->g_end-tpg.lmdata[0]->g_start+1)+2)*sizeof(double)+(2*dp->M*sizeof(int))+(2*dp->Nbase*dp->tilesz*sizeof(char)); - //tpg.data_size[1]=(n+(dp->Nbase*dp->tilesz)*8*dp->M+m+(tpg.lmdata[1]->g_end-tpg.lmdata[1]->g_start+1)+2)*sizeof(double)+(2*dp->M*sizeof(int))+(2*dp->Nbase*dp->tilesz*sizeof(char)); - tpg.data_size[0]=tpg.data_size[1]=sizeof(float); - - tpg.status[0]=tpg.status[1]=PT_DO_NOTHING; - init_pipeline_b(&tp,&tpg); - sync_barrier(&(tp.gate1)); - tpg.status[0]=tpg.status[1]=PT_DO_AGPU; - sync_barrier(&(tp.gate2)); - sync_barrier(&(tp.gate1)); - tpg.status[0]=tpg.status[1]=PT_DO_NOTHING; - sync_barrier(&(tp.gate2)); - -/*****************************************************************************/ - /* initial value for params xk=p */ - my_dcopy(m,p,1,xk,1); - sync_barrier(&(tp.gate1)); - threaddata[0].p=threaddata[1].p=xk; - tpg.status[0]=tpg.status[1]=PT_DO_CDERIV; - sync_barrier(&(tp.gate2)); - sync_barrier(&(tp.gate1)); - tpg.status[0]=tpg.status[1]=PT_DO_NOTHING; - sync_barrier(&(tp.gate2)); -// for (ci=0; ci<20; ci++) { -// printf("GPU %d %lf\n",ci,gk[ci]); -// } - /* gradient gk=grad(f)_k */ -// func_grad(func,xk,gk,x,m,n,step,gpu_threads,adata); -// for (ci=0; ci<20; ci++) { -// printf("CPU %d %lf\n",ci,gk[ci]); -// } - - double gradnrm=my_dnrm2(m,gk); - /* if gradient is too small, no need to solve, so stop */ - if (gradnrmp=tpg.lmdata[1]->p=xk1; - tpg.status[0]=tpg.status[1]=PT_DO_CDERIV; - sync_barrier(&(tp.gate2)); - sync_barrier(&(tp.gate1)); - tpg.status[0]=tpg.status[1]=PT_DO_NOTHING; - sync_barrier(&(tp.gate2)); - -// func_grad(func,xk1,gk,x,m,n,step,gpu_threads,adata); - /* yk=yk+gk1 */ - my_daxpy(m,gk,1.0,&y[cm]); - - /* calculate 1/yk^T*sk */ - rho[ci]=1.0/my_ddot(m,&y[cm],&s[cm]); - - /* update xk=xk1 */ - my_dcopy(m,xk1,1,xk,1); - - //printf("iter %d store %d\n",ck,cm); - ck++; - /* increment storage appropriately */ - if (cm<(M-1)*m) { - /* offset of m */ - cm=cm+m; - ci++; - } else { - cm=ci=0; - } - } - - - /* copy back solution to p */ - my_dcopy(m,xk,1,p,1); - - /* for (ci=0; cihbb=NULL; - dp->ptoclus=NULL; - - /******** free threads ***************/ - sync_barrier(&(tp.gate1)); /* sync at gate 1*/ - tpg.status[0]=tpg.status[1]=PT_DO_DGPU; - sync_barrier(&(tp.gate2)); /* sync at gate 2*/ - - destroy_pipeline_b(&tp); - - return 0; -} - - - -int -lbfgs_fit( - void (*func)(double *p, double *hx, int m, int n, void *adata), - double *p, double *x, int m, int n, int itmax, int M, int gpu_threads, void *adata) { - return lbfgs_fit_common(func, p, x, m, n, itmax, M, gpu_threads, 0, adata); -} - -int -lbfgs_fit_robust_cuda( - void (*func)(double *p, double *hx, int m, int n, void *adata), - double *p, double *x, int m, int n, int itmax, int M, int gpu_threads, void *adata) { - return lbfgs_fit_common(func, p, x, m, n, itmax, M, gpu_threads, 1, adata); -} diff --git a/src/lib/Solvers/lbfgs.o b/src/lib/Solvers/lbfgs.o deleted file mode 100644 index 4f9c5befa82f52cd4351a4d68727969ee9897190..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 80704 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 Kcgw|XeEuJ)>2HGo diff --git a/src/lib/Solvers/lbfgs_nocuda.c b/src/lib/Solvers/lbfgs_nocuda.c deleted file mode 100644 index 11a5542..0000000 --- a/src/lib/Solvers/lbfgs_nocuda.c +++ /dev/null @@ -1,926 +0,0 @@ -/* - * - Copyright (C) 2006-2008 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#include "Solvers.h" -#include - - -/**** repeated code here ********************/ -/* Jones matrix multiplication - C=A*B -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -amb(complex double * __restrict a, complex double * __restrict b, complex double * __restrict c) { - c[0]=a[0]*b[0]+a[1]*b[2]; - c[1]=a[0]*b[1]+a[1]*b[3]; - c[2]=a[2]*b[0]+a[3]*b[2]; - c[3]=a[2]*b[1]+a[3]*b[3]; -} - -/* Jones matrix multiplication - C=A*B^H -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -ambt(complex double * __restrict a, complex double * __restrict b, complex double * __restrict c) { - c[0]=a[0]*conj(b[0])+a[1]*conj(b[1]); - c[1]=a[0]*conj(b[2])+a[1]*conj(b[3]); - c[2]=a[2]*conj(b[0])+a[3]*conj(b[1]); - c[3]=a[2]*conj(b[2])+a[3]*conj(b[3]); -} - -/**** end repeated code ********************/ - -/* worker thread for a cpu */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -cpu_calc_deriv(void *adata) { - thread_data_grad_t *t=(thread_data_grad_t*)adata; - - int ci,nb; - int stc,stoff,stm,sta1,sta2; - int N=t->N; /* stations */ - int M=t->M; /* clusters */ - int Nbase=(t->Nbase)*(t->tilesz); - - - complex double xr[4]; /* residuals */ - complex double G1[4],G2[4],C[4],T1[4],T2[4]; - double pp[8]; - complex double csum; - int cli,tpchunk,pstart,nchunk,tilesperchunk,stci,ttile,tptile,poff; - - /* iterate over each paramter */ - for (ci=t->g_start; ci<=t->g_end; ++ci) { - t->g[ci]=0.0; - /* find station and parameter corresponding to this value of ci */ - /* this parameter should correspond to the right baseline (x tilesz) - to contribute to the derivative */ - cli=0; - while((clicarr[cli].p[0] || ci>t->carr[cli].p[0]+8*N*t->carr[cli].nchunk-1)) { - cli++; - } - /* now either cli>=M: cluster not found - or cli=t->carr[cli-1].p[0] && ci<=t->carr[cli-1].p[0]+8*N*t->carr[cli-1].nchunk-1) { - cli--; - } - - if (clicarr[cli].p[0]; - - stc=(stci%(8*N))/8; /* 0..N-1 */ - /* make sure this baseline contribute to this parameter */ - tpchunk=stci/(8*N); - nchunk=t->carr[cli].nchunk; - pstart=t->carr[cli].p[0]; - tilesperchunk=(t->tilesz+nchunk-1)/nchunk; - - - /* iterate over all baselines and accumulate sum */ - for (nb=0; nbNbase; - /* which chunk this tile belongs to */ - tptile=ttile/tilesperchunk; - /* now tptile has to match tpchunk, otherwise ignore calculation */ - if (tptile==tpchunk) { - - sta1=t->barr[nb].sta1; - sta2=t->barr[nb].sta2; - - if (((stc==sta1)||(stc==sta2))&& !t->barr[nb].flag) { - /* this baseline has a contribution */ - /* which paramter of this station */ - stoff=(stci%(8*N))%8; /* 0..7 */ - /* which cluster */ - stm=cli; /* 0..M-1 */ - - /* exact expression for derivative - 2 real( vec^H(residual_this_baseline) - * vec(-J_{pm}C_{pqm} J_{qm}^H) - where m: chosen cluster - J_{pm},J_{qm} Jones matrices for baseline p-q - depending on the parameter, J ==> E - E: zero matrix, except 1 at location of m - - residual : in x[8*nb:8*nb+7] - C coh: in coh[8*M*nb+m*8:8*M*nb+m*8+7] (double storage) - coh[4*M*nb+4*m:4*M*nb+4*m+3] (complex storage) - J_p,J_q: in p[sta1*8+m*8*N: sta1*8+m*8*N+7] - and p[sta2*8+m*8*N: sta2*8+m*8*N+ 7] - */ - /* read in residual vector, conjugated */ - xr[0]=(t->x[nb*8])-_Complex_I*(t->x[nb*8+1]); - xr[1]=(t->x[nb*8+2])-_Complex_I*(t->x[nb*8+3]); - xr[2]=(t->x[nb*8+4])-_Complex_I*(t->x[nb*8+5]); - xr[3]=(t->x[nb*8+6])-_Complex_I*(t->x[nb*8+7]); - - /* read in coherency */ - C[0]=t->coh[4*M*nb+4*stm]; - C[1]=t->coh[4*M*nb+4*stm+1]; - C[2]=t->coh[4*M*nb+4*stm+2]; - C[3]=t->coh[4*M*nb+4*stm+3]; - - memset(pp,0,sizeof(double)*8); - if (stc==sta1) { - /* this station parameter gradient */ - pp[stoff]=1.0; - memset(G1,0,sizeof(complex double)*4); - G1[0]=pp[0]+_Complex_I*pp[1]; - G1[1]=pp[2]+_Complex_I*pp[3]; - G1[2]=pp[4]+_Complex_I*pp[5]; - G1[3]=pp[6]+_Complex_I*pp[7]; - poff=pstart+tpchunk*8*N+sta2*8; - G2[0]=(t->p[poff])+_Complex_I*(t->p[poff+1]); - G2[1]=(t->p[poff+2])+_Complex_I*(t->p[poff+3]); - G2[2]=(t->p[poff+4])+_Complex_I*(t->p[poff+4]); - G2[3]=(t->p[poff+6])+_Complex_I*(t->p[poff+7]); - - } else if (stc==sta2) { - memset(G2,0,sizeof(complex double)*4); - pp[stoff]=1.0; - G2[0]=pp[0]+_Complex_I*pp[1]; - G2[1]=pp[2]+_Complex_I*pp[3]; - G2[2]=pp[4]+_Complex_I*pp[5]; - G2[3]=pp[6]+_Complex_I*pp[7]; - poff=pstart+tpchunk*8*N+sta1*8; - G1[0]=(t->p[poff])+_Complex_I*(t->p[poff+1]); - G1[1]=(t->p[poff+2])+_Complex_I*(t->p[poff+3]); - G1[2]=(t->p[poff+4])+_Complex_I*(t->p[poff+5]); - G1[3]=(t->p[poff+6])+_Complex_I*(t->p[poff+7]); - } - - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - - /* calculate product xr*vec(J_p C J_q^H ) */ - csum=xr[0]*T2[0]; - csum+=xr[1]*T2[1]; - csum+=xr[2]*T2[2]; - csum+=xr[3]*T2[3]; - - /* accumulate sum */ - t->g[ci]+=-2.0*creal(csum); - } - } - } - } - } - - - return NULL; -} - -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static int -func_grad( - void (*func)(double *p, double *hx, int m, int n, void *adata), - double *p, double *g, double *xo, int m, int n, double step, void *adata) { - /* gradient for each parameter is - (||func(p+step*e_i)-x||^2-||func(p-step*e_i)-x||^2)/2*step - i=0,...,m-1 for all parameters - e_i: unit vector, 1 only at i-th location - */ - - double *x; /* array to store residual */ - int ci; - me_data_t *dp=(me_data_t*)adata; - - int Nt=dp->Nt; - - pthread_attr_t attr; - pthread_t *th_array; - thread_data_grad_t *threaddata; - - - if ((x=(double*)calloc((size_t)n,sizeof(double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - /* evaluate func once, store in x, and create threads */ - /* and calculate the residual x=xo-func */ - func(p,x,m,n,adata); - /* calculate x<=x-xo */ - my_daxpy(n,xo,-1.0,x); - - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((threaddata=(thread_data_grad_t*)malloc((size_t)Nt*sizeof(thread_data_grad_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - int nth,nth1,Nparm; - - /* parameters per thread */ - Nparm=(m+Nt-1)/Nt; - - /* each thread will calculate derivative of part of - parameters */ - ci=0; - for (nth=0; nthNbase; - threaddata[nth].tilesz=dp->tilesz; - threaddata[nth].barr=dp->barr; - threaddata[nth].carr=dp->carr; - threaddata[nth].M=dp->M; - threaddata[nth].N=dp->N; - threaddata[nth].coh=dp->coh; - threaddata[nth].m=m; - threaddata[nth].n=n; - threaddata[nth].x=x; - threaddata[nth].p=p; - threaddata[nth].g=g; - threaddata[nth].g_start=ci; - threaddata[nth].g_end=ci+Nparm-1; - if (threaddata[nth].g_end>=m) { - threaddata[nth].g_end=m-1; - } - ci=ci+Nparm; - pthread_create(&th_array[nth],&attr,cpu_calc_deriv,(void*)(&threaddata[nth])); - } - - /* now wait for threads to finish */ - for(nth1=0; nth10) { - /* find the location of k-1 th value */ - if (ii>0) { - ii=ii-1; - } else { - ii=M-1; - } - /* s,y will have 0,1,...,ii,ii+1,...M-1 */ - /* map this to ii+1,ii+2,...,M-1,0,1,..,ii */ - for (ci=0; ci%d ",ci,idx[ci]); - } - printf("\n"); -#endif - /* q = grad(f)k : pk<=gk */ - my_dcopy(m,gk,1,pk,1); - /* this should be done in the right order */ - for (ci=0; ci0) { - gamma=my_ddot(m,&s[m*idx[M-1]],&y[m*idx[M-1]]); - gamma/=my_ddot(m,&y[m*idx[M-1]],&y[m*idx[M-1]]); - /* Hk(0)=gamma I, so scale q by gamma */ - /* r= Hk(0) q */ - my_dscal(m,gamma,pk); - } - - for (ci=0; cib is possible) - to find step that minimizes cost function */ -/* func: vector function - xk: parameter values size m x 1 (at which step is calculated) - pk: step direction size m x 1 (x(k+1)=x(k)+alphak * pk) - a/b: interval for interpolation - x: size n x 1 (storage) - xp: size m x 1 (storage) - xo: observed data size n x 1 - n: size of vector function - step: step size for differencing - adata: additional data passed to the function -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static double -cubic_interp( - void (*func)(double *p, double *hx, int m, int n, void *adata), - double *xk, double *pk, double a, double b, double *x, double *xp, double *xo, int m, int n, double step, void *adata) { - - double f0,f1,f0d,f1d; /* function values and derivatives at a,b */ - double p01,p02,z0,fz0; - double aa,cc; - - my_dcopy(m,xk,1,xp,1); /* xp<=xk */ - my_daxpy(m,pk,a,xp); /* xp<=xp+(a)*pk */ - func(xp,x,m,n,adata); - my_daxpy(n,xo,-1.0,x); - f0=my_dnrm2(n,x); - f0*=f0; - /* grad(phi_0): evaluate at -step and +step */ - my_daxpy(m,pk,step,xp); /* xp<=xp+(a+step)*pk */ - func(xp,x,m,n,adata); - my_daxpy(n,xo,-1.0,x); - p01=my_dnrm2(n,x); - my_daxpy(m,pk,-2.0*step,xp); /* xp<=xp+(a-step)*pk */ - func(xp,x,m,n,adata); - my_daxpy(n,xo,-1.0,x); - p02=my_dnrm2(n,x); - f0d=(p01*p01-p02*p02)/(2.0*step); - - my_dcopy(m,xk,1,xp,1); /* xp<=xk */ - my_daxpy(m,pk,b,xp); /* xp<=xp+(b)*pk */ - func(xp,x,m,n,adata); - my_daxpy(n,xo,-1.0,x); - f1=my_dnrm2(n,x); - f1*=f1; - /* grad(phi_1): evaluate at -step and +step */ - my_daxpy(m,pk,step,xp); /* xp<=xp+(b+step)*pk */ - func(xp,x,m,n,adata); - my_daxpy(n,xo,-1.0,x); - p01=my_dnrm2(n,x); - my_daxpy(m,pk,-2.0*step,xp); /* xp<=xp+(b-step)*pk */ - func(xp,x,m,n,adata); - my_daxpy(n,xo,-1.0,x); - p02=my_dnrm2(n,x); - f1d=(p01*p01-p02*p02)/(2.0*step); - - - //printf("Interp a,f(a),f'(a): (%lf,%lf,%lf) (%lf,%lf,%lf)\n",a,f0,f0d,b,f1,f1d); - /* cubic poly in [0,1] is f0+f0d z+eta z^2+xi z^3 - where eta=3(f1-f0)-2f0d-f1d, xi=f0d+f1d-2(f1-f0) - derivative f0d+2 eta z+3 xi z^2 => cc+bb z+aa z^2 */ - aa=3.0*(f0-f1)/(b-a)+(f1d-f0d); - p01=aa*aa-f0d*f1d; - /* root exist? */ - if (p01>0.0) { - /* root */ - cc=sqrt(p01); - z0=b-(f1d+cc-aa)*(b-a)/(f1d-f0d+2.0*cc); - /* FIXME: check if this is within boundary */ - aa=MAX(a,b); - cc=MIN(a,b); - //printf("Root=%lf, in [%lf,%lf]\n",z0,cc,aa); - if (z0>aa || z0b) is possible - x: size n x 1 (storage) - xp: size m x 1 (storage) - phi_0: phi(0) - gphi_0: grad(phi(0)) - xo: observed data size n x 1 - n: size of vector function - step: step size for differencing - adata: additional data passed to the function -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static double -linesearch_zoom( - void (*func)(double *p, double *hx, int m, int n, void *adata), - double *xk, double *pk, double a, double b, double *x, double *xp, double phi_0, double gphi_0, double sigma, double rho, double t1, double t2, double t3, double *xo, int m, int n, double step, void *adata) { - - double alphaj,phi_j,phi_aj; - double gphi_j,p01,p02,aj,bj; - double alphak=1.0; - int ci,found_step=0; - - aj=a; - bj=b; - ci=0; - while(ci<10) { - /* choose alphaj from [a+t2(b-a),b-t3(b-a)] */ - p01=aj+t2*(bj-aj); - p02=bj-t3*(bj-aj); - alphaj=cubic_interp(func,xk,pk,p01,p02,x,xp,xo,m,n,step,adata); - //printf("cubic intep [%lf,%lf]->%lf\n",p01,p02,alphaj); - - /* evaluate phi(alphaj) */ - my_dcopy(m,xk,1,xp,1); /* xp<=xk */ - my_daxpy(m,pk,alphaj,xp); /* xp<=xp+(alphaj)*pk */ - func(xp,x,m,n,adata); - /* calculate x<=x-xo */ - my_daxpy(n,xo,-1.0,x); - phi_j=my_dnrm2(n,x); - phi_j*=phi_j; - - /* evaluate phi(aj) */ - my_dcopy(m,xk,1,xp,1); /* xp<=xk */ - my_daxpy(m,pk,aj,xp); /* xp<=xp+(alphaj)*pk */ - func(xp,x,m,n,adata); - /* calculate x<=x-xo */ - my_daxpy(n,xo,-1.0,x); - phi_aj=my_dnrm2(n,x); - phi_aj*=phi_aj; - - - if ((phi_j>phi_0+rho*alphaj*gphi_0) || phi_j>=phi_aj) { - bj=alphaj; /* aj unchanged */ - } else { - /* evaluate grad(alphaj) */ - my_dcopy(m,xk,1,xp,1); /* xp<=xk */ - my_daxpy(m,pk,alphaj+step,xp); /* xp<=xp+(alphaj+step)*pk */ - func(xp,x,m,n,adata); - /* calculate x<=x-xo */ - my_daxpy(n,xo,-1.0,x); - p01=my_dnrm2(n,x); - my_daxpy(m,pk,-2.0*step,xp); /* xp<=xp+(alphaj-step)*pk */ - func(xp,x,m,n,adata); - /* calculate x<=x-xo */ - my_daxpy(n,xo,-1.0,x); - p02=my_dnrm2(n,x); - gphi_j=(p01*p01-p02*p02)/(2.0*step); - - /* termination due to roundoff/other errors pp. 38, Fletcher */ - if ((aj-alphaj)*gphi_j<=step) { - alphak=alphaj; - found_step=1; - break; - } - - if (fabs(gphi_j)<=-sigma*gphi_0) { - alphak=alphaj; - found_step=1; - break; - } - - if (gphi_j*(bj-aj)>=0) { - bj=aj; - } /* else bj unchanged */ - aj=alphaj; - } - ci++; - } - - if (!found_step) { - /* use bound to find possible step */ - alphak=alphaj; - } - -#ifdef DEBUG - printf("Found %g Interval [%lf,%lf]\n",alphak,a,b); -#endif - return alphak; -} - - - -/* line search */ -/* func: vector function - xk: parameter values size m x 1 (at which step is calculated) - pk: step direction size m x 1 (x(k+1)=x(k)+alphak * pk) - alpha1: initial value for step - sigma,rho,t1,t2,t3: line search parameters (from Fletcher) - xo: observed data size n x 1 - n: size of vector function - step: step size for differencing - adata: additional data passed to the function -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static double -linesearch( - void (*func)(double *p, double *hx, int m, int n, void *adata), - double *xk, double *pk, double alpha1, double sigma, double rho, double t1, double t2, double t3, double *xo, int m, int n, double step, void *adata) { - - /* phi(alpha)=f(xk+alpha pk) - for vector function func - f(xk) =||func(xk)||^2 */ - - double *x,*xp; - double alphai,alphai1; - double phi_0,phi_alphai,phi_alphai1; - double p01,p02; - double gphi_0,gphi_i; - double alphak; - - double mu; - double tol; /* lower limit for minimization */ - - int ci; - - if ((x=(double*)calloc((size_t)n,sizeof(double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((xp=(double*)calloc((size_t)m,sizeof(double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - alphak=1.0; - /* evaluate phi_0 and grad(phi_0) */ - func(xk,x,m,n,adata); - my_daxpy(n,xo,-1.0,x); - phi_0=my_dnrm2(n,x); - phi_0*=phi_0; - /* select tolarance 1/100 of current function value */ - tol=MIN(0.01*phi_0,1e-6); - - /* grad(phi_0): evaluate at -step and +step */ - my_dcopy(m,xk,1,xp,1); /* xp<=xk */ - my_daxpy(m,pk,step,xp); /* xp<=xp+(0.0+step)*pk */ - func(xp,x,m,n,adata); - /* calculate x<=x-xo */ - my_daxpy(n,xo,-1.0,x); - p01=my_dnrm2(n,x); - my_daxpy(m,pk,-2.0*step,xp); /* xp<=xp+(0.0-step)*pk */ - func(xp,x,m,n,adata); - /* calculate x<=x-xo */ - my_daxpy(n,xo,-1.0,x); - p02=my_dnrm2(n,x); - gphi_0=(p01*p01-p02*p02)/(2.0*step); - - /* estimate for mu */ - /* mu = (tol-phi_0)/(rho gphi_0) */ - mu=(tol-phi_0)/(rho*gphi_0); -#ifdef DEBUG - printf("mu=%lf, alpha1=%lf\n",mu,alpha1); -#endif - - ci=1; - alphai=alpha1; /* initial value for alpha(i) : check if 0phi_0+alphai*gphi_0) || (ci>1 && phi_alphai>=phi_alphai1)) { - /* ai=alphai1, bi=alphai bracket */ - alphak=linesearch_zoom(func,xk,pk,alphai1,alphai,x,xp,phi_0,gphi_0,sigma,rho,t1,t2,t3,xo,m,n,step,adata); -#ifdef DEBUG - printf("Linesearch : Condition 1 met\n"); -#endif - break; - } - - /* evaluate grad(phi(alpha(i))) */ - my_dcopy(m,xk,1,xp,1); /* NOT NEEDED here?? xp<=xk */ - my_daxpy(m,pk,alphai+step,xp); /* xp<=xp+(alphai+step)*pk */ - func(xp,x,m,n,adata); - /* calculate x<=x-xo */ - my_daxpy(n,xo,-1.0,x); - p01=my_dnrm2(n,x); - my_daxpy(m,pk,-2.0*step,xp); /* xp<=xp+(alphai-step)*pk */ - func(xp,x,m,n,adata); - /* calculate x<=x-xo */ - my_daxpy(n,xo,-1.0,x); - p02=my_dnrm2(n,x); - gphi_i=(p01*p01-p02*p02)/(2.0*step); - - if (fabs(gphi_i)<=-sigma*gphi_0) { - alphak=alphai; -#ifdef DEBUG - printf("Linesearch : Condition 2 met\n"); -#endif - break; - } - - if (gphi_i>=0) { - /* ai=alphai, bi=alphai1 bracket */ - alphak=linesearch_zoom(func,xk,pk,alphai,alphai1,x,xp,phi_0,gphi_0,sigma,rho,t1,t2,t3,xo,m,n,step,adata); -#ifdef DEBUG - printf("Linesearch : Condition 3 met\n"); -#endif - break; - } - - /* else preserve old values */ - if (mu<=(2.0*alphai-alphai1)) { - /* next step */ - alphai1=alphai; - alphai=mu; - } else { - /* choose by interpolation in [2*alphai-alphai1,min(mu,alphai+t1*(alphai-alphai1)] */ - p01=2.0*alphai-alphai1; - p02=MIN(mu,alphai+t1*(alphai-alphai1)); - alphai=cubic_interp(func,xk,pk,p01,p02,x,xp,xo,m,n,step,adata); - //printf("cubic interp [%lf,%lf]->%lf\n",p01,p02,alphai); - } - phi_alphai1=phi_alphai; - - ci++; - } - - - - free(x); - free(xp); -#ifdef DEBUG - printf("Step size=%g\n",alphak); -#endif - return alphak; -} -/*************** END Fletcher line search **********************************/ - -int -lbfgs_fit( - void (*func)(double *p, double *hx, int m, int n, void *adata), - double *p, double *x, int m, int n, int itmax, int M, int gpu_threads, void *adata) { - - double *gk; /* gradients at both k+1 and k iter */ - double *xk1,*xk; /* parameters at k+1 and k iter */ - double *pk; /* step direction H_k * grad(f) */ - - double step=1e-6; /* step for interpolation */ - double *y, *s; /* storage for delta(grad) and delta(p) */ - double *rho; /* storage for 1/yk^T*sk */ - int ci,ck,cm; - double alphak=1.0; - - - if ((gk=(double*)calloc((size_t)m,sizeof(double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((xk1=(double*)calloc((size_t)m,sizeof(double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((xk=(double*)calloc((size_t)m,sizeof(double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - if ((pk=(double*)calloc((size_t)m,sizeof(double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - - /* storage size mM x 1*/ - if ((s=(double*)calloc((size_t)m*M,sizeof(double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((y=(double*)calloc((size_t)m*M,sizeof(double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((rho=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - /* initial value for params xk=p */ - my_dcopy(m,p,1,xk,1); - /* gradient gk=grad(f)_k */ - func_grad(func,xk,gk,x,m,n,step,adata); - double gradnrm=my_dnrm2(m,gk); - /* if gradient is too small, no need to solve, so stop */ - if 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 diff --git a/src/lib/Solvers/libdirac-gpu.a b/src/lib/Solvers/libdirac-gpu.a deleted file mode 100644 index 24d5aa5f844702b6799aa64829ecc3fa97bd0c46..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1519392 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 z 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

ALE*w<#oe336(}OQ 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� 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⁣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

<>Ze==)yxSobH9Vb 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

&&1n5nCB*tIeW{z2YYzveU=X?dFm%9-#qh~ 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≪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�j0| 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%0At#R0>?~-Gx8*g5E+*Y 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 diff --git a/src/lib/Solvers/libsolvers-gpu.a b/src/lib/Solvers/libsolvers-gpu.a deleted file mode 100644 index 24e6424164f6297123c0cd72310e6e3583e11aba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2206404 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 ZPcTW)MpH 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

_P}#2-;>29PqTG?gFz8p!b7Xh6-_50H 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

4fCfp26DLC0 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 diff --git a/src/lib/Solvers/lmfit.c b/src/lib/Solvers/lmfit.c deleted file mode 100644 index 8518d18..0000000 --- a/src/lib/Solvers/lmfit.c +++ /dev/null @@ -1,2171 +0,0 @@ -/* - * - Copyright (C) 2006-2008 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#include -#include -#include -#include -#include -#include "Solvers.h" - - -//#define DEBUG - -/* Jones matrix multiplication - C=A*B -*/ -static void -amb(complex double * __restrict a, complex double * __restrict b, complex double * __restrict c) { - c[0]=a[0]*b[0]+a[1]*b[2]; - c[1]=a[0]*b[1]+a[1]*b[3]; - c[2]=a[2]*b[0]+a[3]*b[2]; - c[3]=a[2]*b[1]+a[3]*b[3]; -} - -/* Jones matrix multiplication - C=A*B^H -*/ -static void -ambt(complex double * __restrict a, complex double * __restrict b, complex double * __restrict c) { - c[0]=a[0]*conj(b[0])+a[1]*conj(b[1]); - c[1]=a[0]*conj(b[2])+a[1]*conj(b[3]); - c[2]=a[2]*conj(b[0])+a[3]*conj(b[1]); - c[3]=a[2]*conj(b[2])+a[3]*conj(b[3]); -} - -/********************** sage minimization ***************************/ -/* worker thread function for prediction */ -static void * -predict_threadfn_withgain(void *data) { - thread_data_base_t *t=(thread_data_base_t*)data; - - int ci,cm,sta1,sta2; - complex double C[4],G1[4],G2[4],T1[4],T2[4]; - int M=(t->M); - cm=(t->clus); - int Ntilebase=(t->Nbase)*(t->tilesz); - int px; - double *pm; - - for (ci=0; ciNb; ci++) { - /* iterate over the sky model and calculate contribution */ - /* for this x[8*ci:8*(ci+1)-1] */ - memset(&(t->x[8*ci]),0,sizeof(double)*8); - - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - - px=(ci+t->boff)/((Ntilebase+t->carr[cm].nchunk-1)/t->carr[cm].nchunk); - pm=&(t->p[t->carr[cm].p[px]]); - /* gains for this cluster, for sta1,sta2 */ - G1[0]=(pm[sta1*8])+_Complex_I*(pm[sta1*8+1]); - G1[1]=(pm[sta1*8+2])+_Complex_I*(pm[sta1*8+3]); - G1[2]=(pm[sta1*8+4])+_Complex_I*(pm[sta1*8+5]); - G1[3]=(pm[sta1*8+6])+_Complex_I*(pm[sta1*8+7]); - G2[0]=(pm[sta2*8])+_Complex_I*(pm[sta2*8+1]); - G2[1]=(pm[sta2*8+2])+_Complex_I*(pm[sta2*8+3]); - G2[2]=(pm[sta2*8+4])+_Complex_I*(pm[sta2*8+5]); - G2[3]=(pm[sta2*8+6])+_Complex_I*(pm[sta2*8+7]); - - - /* use pre calculated values */ - C[0]=t->coh[4*M*ci+4*cm]; - C[1]=t->coh[4*M*ci+4*cm+1]; - C[2]=t->coh[4*M*ci+4*cm+2]; - C[3]=t->coh[4*M*ci+4*cm+3]; - - - /* form G1*C*G2' */ - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - - /* add to baseline visibilities */ - t->x[8*ci]+=creal(T2[0]); - t->x[8*ci+1]+=cimag(T2[0]); - t->x[8*ci+2]+=creal(T2[1]); - t->x[8*ci+3]+=cimag(T2[1]); - t->x[8*ci+4]+=creal(T2[2]); - t->x[8*ci+5]+=cimag(T2[2]); - t->x[8*ci+6]+=creal(T2[3]); - t->x[8*ci+7]+=cimag(T2[3]); - } - } - - return NULL; -} - - -/* minimization function (multithreaded) */ -/* p: size ??x1 parameters, not all belong to - this cluster - x: size nx1 data calculated - data: extra info needed */ -static void -mylm_fit_single_pth(double *p, double *x, int m, int n, void *data) { - - me_data_t *dp=(me_data_t*)data; - /* u,v,w : size Nbase*tilesz x 1 x: size Nbase*8*tilesz x 1 */ - /* barr: size Nbase*tilesz x 1 carr: size Mx1 */ - /* pp: size 8*N*M x 1 */ - /* pm: size Mx1 of double */ - - int nth,nth1,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthb0,Nthb; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_base_t *threaddata; - - int Nbase=(dp->Nbase); - int tilesz=(dp->tilesz); - - int Nbase1=Nbase*tilesz; - - /* calculate min baselines a thread can handle */ - //Nthb0=ceil((double)Nbase1/(double)Nt); - Nthb0=(Nbase1+Nt-1)/Nt; - - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((threaddata=(thread_data_base_t*)malloc((size_t)Nt*sizeof(thread_data_base_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr; - threaddata[nth].u=&(dp->u[ci]); - threaddata[nth].v=&(dp->v[ci]); - threaddata[nth].w=&(dp->w[ci]); - threaddata[nth].carr=dp->carr; - threaddata[nth].M=dp->M; - threaddata[nth].x=&(x[8*ci]); - threaddata[nth].N=dp->N; - threaddata[nth].Nbase=Nbase; - threaddata[nth].tilesz=tilesz; - threaddata[nth].p=p; - threaddata[nth].clus=(dp->clus); - threaddata[nth].coh=&(dp->coh[4*(dp->M)*ci]); - - - //printf("thread %d predict data from %d baselines %d\n",nth,8*ci,Nthb); - pthread_create(&th_array[nth],&attr,predict_threadfn_withgain,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - /* now wait for threads to finish */ - for(nth1=0; nth1M); - int Ntilebase=(t->Nbase)*(t->tilesz); - int px; - - for (ci=0; ciNb; ci++) { - /* iterate over the sky model and calculate contribution */ - /* for this x[8*ci:8*(ci+1)-1] */ - memset(&(t->x[8*ci]),0,sizeof(double)*8); - - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - for (cm=0; cm x[0.....Nbase*tilesz/nchunk-1] - p[1] -> x[Nbase*tilesz/nchunk......2*Nbase*tilesz-1] - .... - p[last] -> x[(nchunk-1)*Nbase*tilesz/nchunk......Nbase*tilesz] - - so given bindex, right p[] is bindex/((Nbase*tilesz+nchunk-1)/nchunk) - */ - - px=(ci+t->boff)/((Ntilebase+t->carr[cm].nchunk-1)/t->carr[cm].nchunk); - //pm=&(t->p[cm*8*N]); - pm=&(t->p[t->carr[cm].p[px]]); - G1[0]=(pm[sta1*8])+_Complex_I*(pm[sta1*8+1]); - G1[1]=(pm[sta1*8+2])+_Complex_I*(pm[sta1*8+3]); - G1[2]=(pm[sta1*8+4])+_Complex_I*(pm[sta1*8+5]); - G1[3]=(pm[sta1*8+6])+_Complex_I*(pm[sta1*8+7]); - G2[0]=(pm[sta2*8])+_Complex_I*(pm[sta2*8+1]); - G2[1]=(pm[sta2*8+2])+_Complex_I*(pm[sta2*8+3]); - G2[2]=(pm[sta2*8+4])+_Complex_I*(pm[sta2*8+5]); - G2[3]=(pm[sta2*8+6])+_Complex_I*(pm[sta2*8+7]); - - - /* use pre calculated values */ - C[0]=t->coh[4*M*ci+4*cm]; - C[1]=t->coh[4*M*ci+4*cm+1]; - C[2]=t->coh[4*M*ci+4*cm+2]; - C[3]=t->coh[4*M*ci+4*cm+3]; - - /* form G1*C*G2' */ - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - - /* add to baseline visibilities */ - t->x[8*ci]+=creal(T2[0]); - t->x[8*ci+1]+=cimag(T2[0]); - t->x[8*ci+2]+=creal(T2[1]); - t->x[8*ci+3]+=cimag(T2[1]); - t->x[8*ci+4]+=creal(T2[2]); - t->x[8*ci+5]+=cimag(T2[2]); - t->x[8*ci+6]+=creal(T2[3]); - t->x[8*ci+7]+=cimag(T2[3]); - } - } - } - - return NULL; -} - - -/* minimization function (multithreaded) */ -/* p: size mx1 parameters - x: size nx1 data calculated - data: extra info needed */ -static void -minimize_viz_full_pth(double *p, double *x, int m, int n, void *data) { - - me_data_t *dp=(me_data_t*)data; - /* u,v,w : size Nbase*tilesz x 1 x: size Nbase*8*tilesz x 1 */ - /* barr: size Nbase*tilesz x 1 carr: size Mx1 */ - /* pp: size 8*N*M x 1 */ - /* pm: size Mx1 of double */ - - int nth,nth1,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthb0,Nthb; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_base_t *threaddata; - - int Nbase1=(dp->Nbase)*(dp->tilesz); - - /* calculate min baselines a thread can handle */ - //Nthb0=ceil((double)Nbase1/(double)Nt); - Nthb0=(Nbase1+Nt-1)/Nt; - - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((threaddata=(thread_data_base_t*)malloc((size_t)Nt*sizeof(thread_data_base_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr; - threaddata[nth].u=&(dp->u[ci]); - threaddata[nth].v=&(dp->v[ci]); - threaddata[nth].w=&(dp->w[ci]); - threaddata[nth].carr=dp->carr; - threaddata[nth].M=dp->M; - threaddata[nth].x=&(x[8*ci]); - threaddata[nth].p=p; - threaddata[nth].N=dp->N; - threaddata[nth].Nbase=dp->Nbase; - threaddata[nth].tilesz=dp->tilesz; - threaddata[nth].coh=&(dp->coh[4*(dp->M)*ci]); - - //printf("thread %d predict data from %d baselines %d\n",nth,8*ci,Nthb); - pthread_create(&th_array[nth],&attr,predict_threadfn_withgain_full,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - /* now wait for threads to finish */ - for(nth1=0; nth1w>bw->w) return -1; - if (aw->w==bw->w) return 0; - - return 1; -} - -/* generate a random permutation of given integers */ -/* note: free returned value after use */ -/* n: no of entries, - weighter_iter: if 1, take weight into account - if 0, only generate a random permutation - w: weights (size nx1): sort them in descending order and - give permutation accordingly -*/ -int* -random_permutation(int n, int weighted_iter, double *w) { - int *p; - int i; - if ((p=(int*)malloc((size_t)(n)*sizeof(int)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if (!weighted_iter) { - for (i = 0; i < n; ++i) { - int j = rand() % (i + 1); - p[i] = p[j]; - p[j] = i; - } - } else { - /* we take weight into account */ - w_n *wn_arr; - if ((wn_arr=(w_n*)malloc((size_t)(n)*sizeof(w_n)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - for (i=0; ipline->data); - int tid=td->tid; - - while(1) { - sync_barrier(&(td->pline->gate1)); /* stop at gate 1*/ - if(td->pline->terminate) break; /* if flag is set, break loop */ - sync_barrier(&(td->pline->gate2)); /* stop at gate 2 */ - /* do work */ - //printf("state=%d, thread %d\n",gd->status[tid],tid); - if (gd->status[tid]==PT_DO_WORK_LM || gd->status[tid]==PT_DO_WORK_OSLM - || gd->status[tid]==PT_DO_WORK_RLM) { -/************************* work *********************/ - me_data_t *t=(me_data_t *)gd->lmdata[tid]; - /* divide the tiles into chunks tilesz/nchunk */ - int tilechunk=(t->tilesz+t->carr[t->clus].nchunk-1)/t->carr[t->clus].nchunk; - - - int ci; - - int cj=0; - int ntiles; - double init_res,final_res; - init_res=final_res=0.0; - if (tid<2) { - /* for GPU, the cost func and jacobian are not used */ - /* loop over each chunk, with right parameter set and data set */ - for (ci=0; cicarr[t->clus].nchunk; ci++) { - /* divide the tiles into chunks tilesz/nchunk */ - if (cj+tilechunktilesz) { - ntiles=tilechunk; - } else { - ntiles=t->tilesz-cj; - } - if (gd->status[tid]==PT_DO_WORK_LM) { - clevmar_der_single_cuda(NULL, NULL, &gd->p[tid][ci*(gd->M[tid])], &gd->x[tid][8*cj*t->Nbase], gd->M[tid], 8*ntiles*t->Nbase, gd->itermax[tid], gd->opts[tid], gd->info[tid], gd->cbhandle[tid], gd->solver_handle[tid], gd->gWORK[tid], gd->linsolv, cj, ntiles, (void*)gd->lmdata[tid]); - } else if (gd->status[tid]==PT_DO_WORK_OSLM) { - oslevmar_der_single_cuda(NULL, NULL, &gd->p[tid][ci*(gd->M[tid])], &gd->x[tid][8*cj*t->Nbase], gd->M[tid], 8*ntiles*t->Nbase, gd->itermax[tid], gd->opts[tid], gd->info[tid], gd->cbhandle[tid], gd->solver_handle[tid], gd->gWORK[tid], gd->linsolv, cj, ntiles, gd->randomize, (void*)gd->lmdata[tid]); - } else if (gd->status[tid]==PT_DO_WORK_RLM) { - rlevmar_der_single_cuda(NULL, NULL, &gd->p[tid][ci*(gd->M[tid])], &gd->x[tid][8*cj*t->Nbase], gd->M[tid], 8*ntiles*t->Nbase, gd->itermax[tid], gd->opts[tid], gd->info[tid], gd->cbhandle[tid], gd->solver_handle[tid], gd->gWORK[tid], gd->linsolv, cj, ntiles, gd->nulow,gd->nuhigh, (void*)gd->lmdata[tid]); - } - init_res+=gd->info[tid][0]; - final_res+=gd->info[tid][1]; - cj=cj+tilechunk; - } - - } - - gd->info[tid][0]=init_res; - gd->info[tid][1]=final_res; - -/************************* work *********************/ - } else if (gd->status[tid]==PT_DO_AGPU) { - attach_gpu_to_thread1(select_work_gpu(MAX_GPU_ID,td->pline->thst),&gd->cbhandle[tid],&gd->solver_handle[tid],&gd->gWORK[tid],gd->data_size); - } else if (gd->status[tid]==PT_DO_DGPU) { - detach_gpu_from_thread1(gd->cbhandle[tid],gd->solver_handle[tid],gd->gWORK[tid]); - } else if (gd->status[tid]==PT_DO_MEMRESET) { - reset_gpu_memory(gd->gWORK[tid],gd->data_size); - } - } - return NULL; -} - -/* initialize the pipeline - and start the slaves rolling */ -static void -init_pipeline(th_pipeline *pline, - void *data) -{ - slave_tdata *t0,*t1; - pthread_attr_init(&(pline->attr)); - pthread_attr_setdetachstate(&(pline->attr),PTHREAD_CREATE_JOINABLE); - - init_th_barrier(&(pline->gate1),3); /* 3 threads, including master */ - init_th_barrier(&(pline->gate2),3); /* 3 threads, including master */ - pline->terminate=0; - pline->data=data; /* data should have pointers to t1 and t2 */ - - if ((t0=(slave_tdata*)malloc(sizeof(slave_tdata)))==0) { - fprintf(stderr,"no free memory\n"); - exit(1); - } - if ((t1=(slave_tdata*)malloc(sizeof(slave_tdata)))==0) { - fprintf(stderr,"no free memory\n"); - exit(1); - } - if ((pline->thst=(taskhist*)malloc(sizeof(taskhist)))==0) { - fprintf(stderr,"no free memory\n"); - exit(1); - } - - init_task_hist(pline->thst); - t0->pline=t1->pline=pline; - t0->tid=0; - t1->tid=1; /* link back t1, t2 to data so they could be freed */ - pline->sd0=t0; - pline->sd1=t1; - pthread_create(&(pline->slave0),&(pline->attr),pipeline_slave_code,(void*)t0); - pthread_create(&(pline->slave1),&(pline->attr),pipeline_slave_code,(void*)t1); -} - -/* destroy the pipeline */ -/* need to kill the slaves first */ -static void -destroy_pipeline(th_pipeline *pline) -{ - - pline->terminate=1; - sync_barrier(&(pline->gate1)); - pthread_join(pline->slave0,NULL); - pthread_join(pline->slave1,NULL); - destroy_th_barrier(&(pline->gate1)); - destroy_th_barrier(&(pline->gate2)); - pthread_attr_destroy(&(pline->attr)); - destroy_task_hist(pline->thst); - free(pline->thst); - free(pline->sd0); - free(pline->sd1); - pline->data=NULL; -} - -int -sagefit_visibilities_dual_pt(double *u, double *v, double *w, double *x, int N, - int Nbase, int tilesz, baseline_t *barr, clus_source_t *carr, complex double *coh, int M, int Mt, double freq0, double fdelta, double *pp, double uvmin, int Nt, int max_emiter, int max_iter, int max_lbfgs, int lbfgs_m, int gpu_threads, int linsolv,int solver_mode, double nulow, double nuhigh, int randomize, double *mean_nu, double *res_0, double *res_1) { - /* u,v,w : size Nbase*tilesz x 1 x: size Nbase*8*tilesz x 1 */ - /* barr: size Nbase*tilesz x 1 carr: size Mx1 */ - /* pp: size 8*N*M x 1 */ - /* pm: size Mx1 of double */ - - - int ci,cj; - double *p; // parameters: m x 1 - int m, n; - double opts[CLM_OPTS_SZ], info0[CLM_INFO_SZ], info1[CLM_INFO_SZ]; - me_data_t lmdata0,lmdata1; - int Nbase1; - - double *xdummy0,*xdummy1,*xsub,*xo; - double *nerr; /* array to store cost reduction per cluster */ - double *robust_nuM; - int weighted_iter,this_itermax0,this_itermax1,total_iter; - double total_err; - - /* rearraged memory for GPU use */ - double *ddcoh; - short *ddbase; - - int *cr=0; /* array for random permutation of clusters */ - int c0,c1; - - //opts[0]=LM_INIT_MU; opts[1]=1E-15; opts[2]=1E-15; opts[3]=1E-20; - opts[0]=CLM_INIT_MU; opts[1]=1E-9; opts[2]=1E-9; opts[3]=1E-9; - opts[4]=-CLM_DIFF_DELTA; - - /* no. of parameters >= than the no of clusters*8N */ - m=N*Mt*8; - /* no of data */ - n=Nbase*tilesz*8; - - /* true no of baselines */ - Nbase1=Nbase*tilesz; - -/********* thread data ******************/ - /* barrier */ - th_pipeline tp; - gbdata tpg; -/****************************************/ - - /* use full parameter space */ - p=pp; - lmdata0.clus=lmdata1.clus=-1; - /* setup data for lmfit */ - lmdata0.u=lmdata1.u=u; - lmdata0.v=lmdata1.v=v; - lmdata0.w=lmdata1.w=w; - lmdata0.Nbase=lmdata1.Nbase=Nbase; - lmdata0.tilesz=lmdata1.tilesz=tilesz; - lmdata0.N=lmdata1.N=N; - lmdata0.barr=lmdata1.barr=barr; - lmdata0.carr=lmdata1.carr=carr; - lmdata0.M=lmdata1.M=M; - lmdata0.Mt=lmdata1.Mt=Mt; - lmdata0.freq0=lmdata1.freq0=&freq0; - lmdata0.Nt=lmdata1.Nt=Nt; - lmdata0.coh=lmdata1.coh=coh; - /* rearrange coh for GPU use */ - if ((ddcoh=(double*)calloc((size_t)(M*Nbase1*8),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((ddbase=(short*)calloc((size_t)(Nbase1*2),sizeof(short)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - rearrange_coherencies(Nbase1, barr, coh, ddcoh, ddbase, M, Nt); - lmdata0.ddcoh=lmdata1.ddcoh=ddcoh; - lmdata0.ddbase=lmdata1.ddbase=ddbase; - - if ((xsub=(double*)calloc((size_t)(n),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xo=(double*)calloc((size_t)(n),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xdummy0=(double*)calloc((size_t)(n),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xdummy1=(double*)calloc((size_t)(n),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((nerr=(double*)calloc((size_t)(M),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if (solver_mode==2) { - if ((robust_nuM=(double*)calloc((size_t)(M),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - } else { - robust_nuM=0; - } - /* starting guess of robust nu */ - double robust_nu0=nulow; - - /* remember for each partition how much the cost function decreases - in the next EM iteration, we allocate more LM iters to partitions - where const function significantly decreases. So two stages - 1) equal LM iters (find the decrease) 2) weighted LM iters */ - weighted_iter=0; - total_iter=M*max_iter; /* total iterations per EM */ -/********** setup threads *******************************/ - init_pipeline(&tp,&tpg); - sync_barrier(&(tp.gate1)); /* sync at gate 1*/ - tpg.status[0]=tpg.status[1]=PT_DO_AGPU; - /* also calculate the total storage needed to be allocated on a GPU */ - /* determine total size for memory allocation */ - int Mm=8*N; - int64_t data_sz=0; - if (solver_mode==0 || solver_mode==1) { - /* size for LM */ - data_sz=(int64_t)(n+Mm*n+Mm*Mm+Mm+Mm*Mm+Mm+Mm+Mm+Mm+Nbase1*8+n+n)*sizeof(double)+(int64_t)Nbase1*2*sizeof(short); - } else if (solver_mode==2) { - /* size for ROBUSTLM */ - data_sz=(int64_t)(n+Mm*n+Mm*Mm+Mm+Mm*Mm+Mm+Mm+Mm+Mm+Nbase1*8+n+n+n+n)*sizeof(double)+(int64_t)Nbase1*2*sizeof(short); - } else { - fprintf(stderr,"%s: %d: invalid mode for solver\n",__FILE__,__LINE__); - exit(1); - } - if (linsolv==1) { - data_sz+=(int64_t)Mm*sizeof(double); - } else if (linsolv==2) { - data_sz+=(int64_t)(Mm*Mm+Mm*Mm+Mm)*sizeof(double); - } - tpg.data_size=data_sz; - tpg.nulow=nulow; - tpg.nuhigh=nuhigh; - tpg.randomize=randomize; - sync_barrier(&(tp.gate2)); /* sync at gate 2*/ - - sync_barrier(&(tp.gate1)); /* sync at gate 1*/ - tpg.status[0]=tpg.status[1]=PT_DO_NOTHING; - sync_barrier(&(tp.gate2)); /* sync at gate 2*/ - -/********** done setup threads *******************************/ - - /* initial residual calculation - subtract full model from data */ - minimize_viz_full_pth(p, xsub, m, n, (void*)&lmdata0); - memcpy(xo,x,(size_t)(n)*sizeof(double)); - my_daxpy(n, xsub, -1.0, xo); - *res_0=my_dnrm2(n,xo)/(double)n; - - for (ci=0; ci0 || this_itermax1>0) { - /* calculate contribution from hidden data, subtract from x */ - /* since x has already subtracted this model, just add - the ones we are solving for */ - - sync_barrier(&(tp.gate1)); /* sync at gate 1 */ - memcpy(xdummy0,xo,(size_t)(n)*sizeof(double)); - memcpy(xdummy1,xo,(size_t)(n)*sizeof(double)); - lmdata0.clus=c0; - lmdata1.clus=c1; - - /* NOTE: conditional mean x^i = s^i + 0.5 * residual^i */ - /* so xdummy=0.5 ( 2*model + residual ) */ - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata0); - my_daxpy(n, xsub, 2.0, xdummy0); - my_dscal(n, 0.5, xdummy0); - my_daxpy(n, xsub, 1.0, xo); - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata1); - my_daxpy(n, xsub, 2.0, xdummy1); - my_dscal(n, 0.5, xdummy1); - my_daxpy(n, xsub, 1.0, xo); -/**************************************************************************/ - /* run this from a separate thread */ - tpg.p[0]=&p[carr[c0].p[0]]; - tpg.x[0]=xdummy0; - tpg.M[0]=8*N; /* even though size of p is > M, dont change this */ - tpg.N[0]=n; /* Nbase*tilesz*8 */ - tpg.itermax[0]=this_itermax0; - tpg.opts[0]=opts; - tpg.info[0]=info0; - tpg.linsolv=linsolv; - tpg.lmdata[0]=&lmdata0; - - tpg.p[1]=&p[carr[c1].p[0]]; - tpg.x[1]=xdummy1; - tpg.M[1]=8*N; /* even though size of p is > M, dont change this */ - tpg.N[1]=n; /* Nbase*tilesz*8 */ - tpg.itermax[1]=this_itermax1; - tpg.opts[1]=opts; - tpg.info[1]=info1; - tpg.linsolv=linsolv; - tpg.lmdata[1]=&lmdata1; -/**************************************************************************/ - - /* both threads do work */ - /* if solver_mode>0 last EM iteration full LM, the rest OS LM */ - if (!solver_mode) { - if (ci==max_emiter-1) { - tpg.status[0]=tpg.status[1]=PT_DO_WORK_LM; - } else { - tpg.status[0]=tpg.status[1]=PT_DO_WORK_OSLM; - } - } else if (solver_mode==1) { - /* normal LM */ - tpg.status[0]=tpg.status[1]=PT_DO_WORK_LM; - } else if (solver_mode==2) { - /* last EM iteration robust LM, the rest OS LM */ - if (ci==max_emiter-1) { - lmdata0.robust_nu=lmdata1.robust_nu=robust_nu0; /* initial robust nu */ - tpg.status[0]=tpg.status[1]=PT_DO_WORK_RLM; - } else { - tpg.status[0]=tpg.status[1]=PT_DO_WORK_OSLM; - } - } - sync_barrier(&(tp.gate2)); /* sync at gate 2 */ - sync_barrier(&(tp.gate1)); /* sync at gate 1 */ - tpg.status[0]=tpg.status[1]=PT_DO_NOTHING; - sync_barrier(&(tp.gate2)); /* sync at gate 2 */ - nerr[c0]=(info0[0]-info0[1])/info0[0]; - nerr[c1]=(info1[0]-info1[1])/info1[0]; - /* update robust_nu */ - if (solver_mode==2 && (ci==max_emiter-1)) { - robust_nuM[c0]+=lmdata0.robust_nu; - robust_nuM[c1]+=lmdata1.robust_nu; - } - - /* once again subtract solved model from data */ - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata0); - my_daxpy(n, xsub, -1.0, xo); - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata1); - my_daxpy(n, xsub, -1.0, xo); - - } - } - /* odd cluster out, if M is odd */ - if (M%2) { - if (randomize) { - c0=cr[M-1]; - } else { - c0=M-1; - } - /* calculate max LM iter for this cluster */ - if (weighted_iter) { - this_itermax0=(int)floor((0.33*nerr[c0]+0.66/(double)M)*((double)total_iter)); - } else { - this_itermax0=max_iter; - } - //printf("Cluster %d(iter=%d, wt=%lf)\n",c0,this_itermax0,nerr[c0]); - if (this_itermax0>0) { - /* calculate contribution from hidden data, subtract from x */ - memcpy(xdummy0,xo,(size_t)(n)*sizeof(double)); - lmdata0.clus=c0; - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata0); - my_daxpy(n, xsub, 1.0, xdummy0); - my_daxpy(n, xsub, 1.0, xo); - -/**************************************************************************/ - sync_barrier(&(tp.gate1)); /* sync at gate 1 */ - /* run this from a separate thread */ - tpg.p[0]=&p[carr[c0].p[0]]; - tpg.x[0]=xdummy0; - tpg.M[0]=8*N; - tpg.N[0]=n; - tpg.itermax[0]=this_itermax0; - tpg.opts[0]=opts; - tpg.info[0]=info0; - tpg.linsolv=linsolv; - tpg.lmdata[0]=&lmdata0; - - if (!solver_mode) { - if (ci==max_emiter-1) { - tpg.status[0]=PT_DO_WORK_LM; - } else { - tpg.status[0]=PT_DO_WORK_OSLM; - } - } else if (solver_mode==1) { - tpg.status[0]=PT_DO_WORK_LM; - } else if (solver_mode==2) { - if (ci==max_emiter-1) { - lmdata0.robust_nu=robust_nu0; /* initial robust nu */ - tpg.status[0]=PT_DO_WORK_RLM; - } else { - tpg.status[0]=PT_DO_WORK_OSLM; - } - } - tpg.status[1]=PT_DO_NOTHING; - sync_barrier(&(tp.gate2)); /* sync at gate 2 */ -/**************************************************************************/ - sync_barrier(&(tp.gate1)); /* sync at gate 1 */ - tpg.status[0]=tpg.status[1]=PT_DO_NOTHING; - sync_barrier(&(tp.gate2)); /* sync at gate 2 */ - - nerr[c0]=(info0[0]-info0[1])/info0[0]; - /* update robust_nu */ - if (solver_mode==2 && (ci==max_emiter-1)) { - robust_nuM[c0]+=lmdata0.robust_nu; - } - /* once again subtract solved model from data */ - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata0); - my_daxpy(n, xsub, -1.0, xo); - } - } - /* normalize nerr array so that the sum is 1 */ - total_err=my_dasum(M,nerr); - my_dscal(M, 1.0/total_err, nerr); - if (randomize && M>1) { - /* flip weighting flag */ - weighted_iter=!weighted_iter; - free(cr); - } - /**************** End EM iteration ***********************/ - } - free(nerr); - free(xo); - free(xdummy0); - free(xdummy1); - free(ddcoh); - free(ddbase); - - if (solver_mode==2) { - /* calculate mean robust_nu over all clusters */ - robust_nu0=my_dasum(M,robust_nuM)/(double)M; -#ifdef DEBUG - for (ci=0; cinuhigh) { - robust_nu0=nuhigh; - } - } - - /******** free threads ***************/ - sync_barrier(&(tp.gate1)); /* sync at gate 1*/ - tpg.status[0]=tpg.status[1]=PT_DO_DGPU; - sync_barrier(&(tp.gate2)); /* sync at gate 2*/ - - - destroy_pipeline(&tp); - /******** done free threads ***************/ - - if (max_lbfgs>0) { - /* use LBFGS */ - if (solver_mode==2) { - lmdata0.robust_nu=robust_nu0; - lbfgs_fit_robust_cuda(minimize_viz_full_pth, p, x, m, n, max_lbfgs, lbfgs_m, gpu_threads, (void*)&lmdata0); - /* also print robust nu to output */ - } else { - lbfgs_fit(minimize_viz_full_pth, p, x, m, n, max_lbfgs, lbfgs_m, gpu_threads, (void*)&lmdata0); - } - } - - - /* final residual calculation */ - minimize_viz_full_pth(p, xsub, m, n, (void*)&lmdata0); - my_daxpy(n, xsub, -1.0, x); - - *mean_nu=robust_nu0; - *res_1=my_dnrm2(n,x)/(double)n; - - free(xsub); - - /* if final residual > initial residual, - return -1, else 0 - */ - if (*res_1>*res_0) { - return -1; - } - return 0; -} - - - - -/*************************** 1 GPU version *********************************/ -/* slave thread 1GPU function */ -static void * -pipeline_slave_code_one_gpu(void *data) -{ - slave_tdata *td=(slave_tdata*)data; - gbdata *gd=(gbdata*)(td->pline->data); - int tid=td->tid; - - while(1) { - sync_barrier(&(td->pline->gate1)); /* stop at gate 1*/ - if(td->pline->terminate) break; /* if flag is set, break loop */ - sync_barrier(&(td->pline->gate2)); /* stop at gate 2 */ - /* do work */ - //printf("state=%d, thread %d\n",gd->status[tid],tid); - if (gd->status[tid]==PT_DO_WORK_LM || gd->status[tid]==PT_DO_WORK_OSLM - || gd->status[tid]==PT_DO_WORK_RLM || gd->status[tid]==PT_DO_WORK_OSRLM) { -/************************* work *********************/ - me_data_t *t=(me_data_t *)gd->lmdata[tid]; - /* divide the tiles into chunks tilesz/nchunk */ - int tilechunk=(t->tilesz+t->carr[t->clus].nchunk-1)/t->carr[t->clus].nchunk; - - - int ci; - - int cj=0; - int ntiles; - double init_res,final_res; - init_res=final_res=0.0; - if (tid<2) { - /* for GPU, the cost func and jacobian are not used */ - /* loop over each chunk, with right parameter set and data set */ - for (ci=0; cicarr[t->clus].nchunk; ci++) { - /* divide the tiles into chunks tilesz/nchunk */ - if (cj+tilechunktilesz) { - ntiles=tilechunk; - } else { - ntiles=t->tilesz-cj; - } - - if (gd->status[tid]==PT_DO_WORK_LM) { - clevmar_der_single_cuda(NULL, NULL, &gd->p[tid][ci*(gd->M[tid])], &gd->x[tid][8*cj*t->Nbase], gd->M[tid], 8*ntiles*t->Nbase, gd->itermax[tid], gd->opts[tid], gd->info[tid], gd->cbhandle[tid], gd->solver_handle[tid], gd->gWORK[tid], gd->linsolv, cj, ntiles, (void*)gd->lmdata[tid]); - } else if (gd->status[tid]==PT_DO_WORK_OSLM) { - oslevmar_der_single_cuda(NULL, NULL, &gd->p[tid][ci*(gd->M[tid])], &gd->x[tid][8*cj*t->Nbase], gd->M[tid], 8*ntiles*t->Nbase, gd->itermax[tid], gd->opts[tid], gd->info[tid], gd->cbhandle[tid], gd->solver_handle[tid], gd->gWORK[tid], gd->linsolv, cj, ntiles, gd->randomize, (void*)gd->lmdata[tid]); - } else if (gd->status[tid]==PT_DO_WORK_RLM || gd->status[tid]==PT_DO_WORK_OSRLM) { - rlevmar_der_single_cuda(NULL, NULL, &gd->p[tid][ci*(gd->M[tid])], &gd->x[tid][8*cj*t->Nbase], gd->M[tid], 8*ntiles*t->Nbase, gd->itermax[tid], gd->opts[tid], gd->info[tid], gd->cbhandle[tid], gd->solver_handle[tid], gd->gWORK[tid], gd->linsolv, cj, ntiles, gd->nulow, gd->nuhigh, (void*)gd->lmdata[tid]); - } - init_res+=gd->info[tid][0]; - final_res+=gd->info[tid][1]; - cj=cj+tilechunk; - } - - } - - gd->info[tid][0]=init_res; - gd->info[tid][1]=final_res; - -/************************* work *********************/ - } else if (gd->status[tid]==PT_DO_AGPU) { - attach_gpu_to_thread1(select_work_gpu(MAX_GPU_ID,td->pline->thst),&gd->cbhandle[tid],&gd->solver_handle[tid],&gd->gWORK[tid],gd->data_size); - } else if (gd->status[tid]==PT_DO_DGPU) { - detach_gpu_from_thread1(gd->cbhandle[tid],gd->solver_handle[tid],gd->gWORK[tid]); - } else if (gd->status[tid]==PT_DO_MEMRESET) { - reset_gpu_memory(gd->gWORK[tid],gd->data_size); - } - } - return NULL; -} - -/* initialize the pipeline - and start the slaves rolling */ -static void -init_pipeline_one_gpu(th_pipeline *pline, - void *data) -{ - slave_tdata *t0; - pthread_attr_init(&(pline->attr)); - pthread_attr_setdetachstate(&(pline->attr),PTHREAD_CREATE_JOINABLE); - - init_th_barrier(&(pline->gate1),2); /* 2 threads, including master */ - init_th_barrier(&(pline->gate2),2); /* 2 threads, including master */ - pline->terminate=0; - pline->data=data; /* data should have pointers to t1 */ - - if ((t0=(slave_tdata*)malloc(sizeof(slave_tdata)))==0) { - fprintf(stderr,"no free memory\n"); - exit(1); - } - if ((pline->thst=(taskhist*)malloc(sizeof(taskhist)))==0) { - fprintf(stderr,"no free memory\n"); - exit(1); - } - - init_task_hist(pline->thst); - t0->pline=pline; - t0->tid=0; - pline->sd0=t0; - pthread_create(&(pline->slave0),&(pline->attr),pipeline_slave_code_one_gpu,(void*)t0); -} - -/* destroy the pipeline */ -/* need to kill the slaves first */ -static void -destroy_pipeline_one_gpu(th_pipeline *pline) -{ - - pline->terminate=1; - sync_barrier(&(pline->gate1)); - pthread_join(pline->slave0,NULL); - destroy_th_barrier(&(pline->gate1)); - destroy_th_barrier(&(pline->gate2)); - pthread_attr_destroy(&(pline->attr)); - destroy_task_hist(pline->thst); - free(pline->thst); - free(pline->sd0); - pline->data=NULL; -} - -int -sagefit_visibilities_dual_pt_one_gpu(double *u, double *v, double *w, double *x, int N, - int Nbase, int tilesz, baseline_t *barr, clus_source_t *carr, complex double *coh, int M, int Mt, double freq0, double fdelta, double *pp, double uvmin, int Nt, int max_emiter, int max_iter, int max_lbfgs, int lbfgs_m, int gpu_threads, int linsolv, int solver_mode, double nulow, double nuhigh, int randomize, double *mean_nu, double *res_0, double *res_1) { - int ci,cj; - double *p; // parameters: m x 1 - int m, n; - double opts[CLM_OPTS_SZ], info[CLM_INFO_SZ]; - me_data_t lmdata; - - double *xdummy,*xsub; - double *nerr; /* array to store cost reduction per cluster */ - double *robust_nuM; - int weighted_iter,this_itermax,total_iter; - double total_err; - - double init_res,final_res; - - opts[0]=CLM_INIT_MU; opts[1]=1E-15; opts[2]=1E-15; opts[3]=1E-20; - opts[4]=-CLM_DIFF_DELTA; - - /* no. of true parameters */ - m=N*Mt*8; - /* no of data */ - n=Nbase*tilesz*8; - - /* rearraged memory for GPU use */ - double *ddcoh; - short *ddbase; - -/********* thread data ******************/ - /* barrier */ - th_pipeline tp; - gbdata tpg; -/****************************************/ - - /* use full parameter space */ - p=pp; - lmdata.clus=-1; - /* setup data for lmfit */ - lmdata.u=u; - lmdata.v=v; - lmdata.w=w; - lmdata.Nbase=Nbase; - lmdata.tilesz=tilesz; - lmdata.N=N; - lmdata.barr=barr; - lmdata.carr=carr; - lmdata.M=M; - lmdata.Mt=Mt; - lmdata.freq0=&freq0; - lmdata.Nt=Nt; - lmdata.coh=coh; - - if ((xsub=(double*)calloc((size_t)(n),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xdummy=(double*)calloc((size_t)(n),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((nerr=(double*)calloc((size_t)(M),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if (solver_mode==2||solver_mode==3) { - if ((robust_nuM=(double*)calloc((size_t)(M),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - } else { - robust_nuM=0; - } - /* starting guess of robust nu */ - double robust_nu0=nulow; - - int Nbase1=Nbase*tilesz; - /* rearrange coh for GPU use */ - if ((ddcoh=(double*)calloc((size_t)(M*Nbase1*8),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((ddbase=(short*)calloc((size_t)(Nbase1*2),sizeof(short)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - rearrange_coherencies(Nbase1, barr, coh, ddcoh, ddbase, M, Nt); - lmdata.ddcoh=ddcoh; - lmdata.ddbase=ddbase; - - init_pipeline_one_gpu(&tp,&tpg); - sync_barrier(&(tp.gate1)); /* sync at gate 1*/ - tpg.status[0]=PT_DO_AGPU; - -/************ setup GPU *********************/ - int64_t data_sz=0; - int Mm=8*N; - if (solver_mode==0 || solver_mode==1) { - /* size for LM */ - data_sz=(int64_t)(n+Mm*n+Mm*Mm+Mm+Mm*Mm+Mm+Mm+Mm+Mm+Nbase1*8+n+n)*sizeof(double)+(int64_t)Nbase1*2*sizeof(short); - } else if (solver_mode==2||solver_mode==3) { - /* size for ROBUSTLM */ - data_sz=(int64_t)(n+Mm*n+Mm*Mm+Mm+Mm*Mm+Mm+Mm+Mm+Mm+Nbase1*8+n+n+n+n)*sizeof(double)+(int64_t)Nbase1*2*sizeof(short); - } else { - fprintf(stderr,"%s: %d: invalid mode for solver\n",__FILE__,__LINE__); - exit(1); - } - - if (linsolv==1) { - data_sz+=(int64_t)Mm*sizeof(double); - } else if (linsolv==2) { - data_sz+=(int64_t)(Mm*Mm+Mm*Mm+Mm)*sizeof(double); - } - - - tpg.data_size=data_sz; - tpg.nulow=nulow; - tpg.nuhigh=nuhigh; - tpg.randomize=randomize; - sync_barrier(&(tp.gate2)); /* sync at gate 2*/ - - sync_barrier(&(tp.gate1)); /* sync at gate 1*/ - tpg.status[0]=PT_DO_NOTHING; - sync_barrier(&(tp.gate2)); /* sync at gate 2*/ - -/************ done setup GPU *********************/ - - /* remember for each partition how much the cost function decreases - in the next EM iteration, we allocate more LM iters to partitions - where const function significantly decreases. So two stages - 1) equal LM iters (find the decrease) 2) weighted LM iters */ - weighted_iter=0; - total_iter=M*max_iter; /* total iterations per EM */ - /* calculate current model and subtract from data */ - minimize_viz_full_pth(p, xsub, m, n, (void*)&lmdata); - memcpy(xdummy,x,(size_t)(n)*sizeof(double)); - my_daxpy(n, xsub, -1.0, xdummy); - *res_0=my_dnrm2(n,xdummy)/(double)n; - - for (ci=0; ci0) { - sync_barrier(&(tp.gate1)); /* sync at gate 1 */ - /* calculate contribution from hidden data, subtract from x - actually, add the current model for this cluster to residual */ - lmdata.clus=cj; - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata); - my_daxpy(n, xsub, 1.0, xdummy); - - /* run this from a separate thread */ - tpg.p[0]=&p[carr[cj].p[0]]; - tpg.x[0]=xdummy; - tpg.M[0]=8*N; /* even though size of p is > M, dont change this */ - tpg.N[0]=n; /* Nbase*tilesz*8 */ - tpg.itermax[0]=this_itermax; - tpg.opts[0]=opts; - tpg.info[0]=info; - tpg.linsolv=linsolv; - tpg.lmdata[0]=&lmdata; - - /* if solver_mode>0 last EM iteration full LM, the rest OS LM */ - if (!solver_mode) { - if (ci==max_emiter-1) { - tpg.status[0]=PT_DO_WORK_LM; - } else { - tpg.status[0]=PT_DO_WORK_OSLM; - } - } else if (solver_mode==1) { - tpg.status[0]=PT_DO_WORK_LM; - } else if (solver_mode==2) { - if (ci==max_emiter-1) { - lmdata.robust_nu=robust_nu0; /* initial robust nu */ - tpg.status[0]=PT_DO_WORK_RLM; - } else { - tpg.status[0]=PT_DO_WORK_OSLM; - } - } - sync_barrier(&(tp.gate2)); /* sync at gate 2 */ - sync_barrier(&(tp.gate1)); /* sync at gate 1 */ - tpg.status[0]=tpg.status[1]=PT_DO_NOTHING; - sync_barrier(&(tp.gate2)); /* sync at gate 2 */ - - init_res=info[0]; - final_res=info[1]; - - nerr[cj]=(init_res-final_res)/init_res; - /* update robust_nu */ - if ((solver_mode==2 || solver_mode==3) && (ci==max_emiter-1)) { - robust_nuM[cj]+=lmdata.robust_nu; - } - - /* subtract current model */ - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata); - my_daxpy(n, xsub, -1.0, xdummy); - } - } - - /* normalize nerr array so that the sum is 1 */ - total_err=my_dasum(M,nerr); - my_dscal(M, 1.0/total_err, nerr); - - /* flip weighting flag */ - if (M>1) { - weighted_iter=!weighted_iter; - } - } - free(nerr); - free(xdummy); - free(ddcoh); - free(ddbase); - if (solver_mode==2 ||solver_mode==3) { - /* calculate mean robust_nu over all clusters */ - robust_nu0=my_dasum(M,robust_nuM)/(double)M; -#ifdef DEBUG - for (ci=0; cinuhigh) { - robust_nu0=nuhigh; - } - - } - - /******** free threads ***************/ - sync_barrier(&(tp.gate1)); /* sync at gate 1*/ - tpg.status[0]=PT_DO_DGPU; - sync_barrier(&(tp.gate2)); /* sync at gate 2*/ - - destroy_pipeline_one_gpu(&tp); - /******** done free threads ***************/ - - - if (max_lbfgs>0) { - /* use LBFGS */ - if (solver_mode==2 || solver_mode==3) { - lmdata.robust_nu=robust_nu0; - lbfgs_fit_robust_cuda(minimize_viz_full_pth, p, x, m, n, max_lbfgs, lbfgs_m, gpu_threads, (void*)&lmdata); - } else { - lbfgs_fit(minimize_viz_full_pth, p, x, m, n, max_lbfgs, lbfgs_m, gpu_threads, (void*)&lmdata); - } - } - /* final residual calculation */ - minimize_viz_full_pth(p, xsub, m, n, (void*)&lmdata); - my_daxpy(n, xsub, -1.0, x); - - *mean_nu=robust_nu0; - *res_1=my_dnrm2(n,x)/(double)n; - - free(xsub); - - /* if final residual > initial residual, - return -1, else 0 - */ - if (*res_1>*res_0) { - return -1; - } - - return 0; -} - - -int -bfgsfit_visibilities_gpu(double *u, double *v, double *w, double *x, int N, - int Nbase, int tilesz, baseline_t *barr, clus_source_t *carr, complex double *coh, int M, int Mt, double freq0, double fdelta, double *pp, double uvmin, int Nt, int max_lbfgs, int lbfgs_m, int gpu_threads, int solver_mode, double mean_nu, double *res_0, double *res_1) { - double *p; // parameters: m x 1 - int m, n; - me_data_t lmdata; - - double *xdummy,*xsub; - - /* no. of true parameters */ - m=N*Mt*8; - /* no of data */ - n=Nbase*tilesz*8; - - /* use full parameter space */ - p=pp; - lmdata.clus=-1; - /* setup data for lmfit */ - lmdata.u=u; - lmdata.v=v; - lmdata.w=w; - lmdata.Nbase=Nbase; - lmdata.tilesz=tilesz; - lmdata.N=N; - lmdata.barr=barr; - lmdata.carr=carr; - lmdata.M=M; - lmdata.Mt=Mt; - lmdata.freq0=&freq0; - lmdata.Nt=Nt; - lmdata.coh=coh; - - if ((xsub=(double*)calloc((size_t)(n),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xdummy=(double*)calloc((size_t)(n),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - - /* calculate current model and subtract from data */ - minimize_viz_full_pth(p, xsub, m, n, (void*)&lmdata); - memcpy(xdummy,x,(size_t)(n)*sizeof(double)); - my_daxpy(n, xsub, -1.0, xdummy); - *res_0=my_dnrm2(n,xdummy)/(double)n; - - - if (max_lbfgs>0) { - /* use LBFGS */ - if (solver_mode==2 || solver_mode==3) { - lmdata.robust_nu=mean_nu; - lbfgs_fit_robust_cuda(minimize_viz_full_pth, p, x, m, n, max_lbfgs, lbfgs_m, gpu_threads, (void*)&lmdata); - } else { - lbfgs_fit(minimize_viz_full_pth, p, x, m, n, max_lbfgs, lbfgs_m, gpu_threads, (void*)&lmdata); - } - } - /* final residual calculation */ - minimize_viz_full_pth(p, xsub, m, n, (void*)&lmdata); - my_daxpy(n, xsub, -1.0, x); - - *res_1=my_dnrm2(n,x)/(double)n; - - free(xsub); - free(xdummy); - - /* if final residual > initial residual, - return -1, else 0 - */ - if (*res_1>*res_0) { - return -1; - } - - return 0; -} - - - - -#ifdef HYBRID_CODE -/****************************************************************************/ -/*************************** hybrid implementation **************************/ -/* slave thread 2GPU function */ -static void * -pipeline_slave_code_flt(void *data) -{ - slave_tdata *td=(slave_tdata*)data; - gbdatafl *gd=(gbdatafl*)(td->pline->data); - int tid=td->tid; - - while(1) { - sync_barrier(&(td->pline->gate1)); /* stop at gate 1*/ - if(td->pline->terminate) break; /* if flag is set, break loop */ - sync_barrier(&(td->pline->gate2)); /* stop at gate 2 */ - /* do work */ - //printf("state=%d, thread %d\n",gd->status[tid],tid); - if (gd->status[tid]==PT_DO_WORK_LM || gd->status[tid]==PT_DO_WORK_OSLM - || gd->status[tid]==PT_DO_WORK_RLM || gd->status[tid]==PT_DO_WORK_OSRLM - || gd->status[tid]==PT_DO_WORK_RTR || gd->status[tid]==PT_DO_WORK_RRTR || gd->status[tid]==PT_DO_WORK_NSD) { -/************************* work *********************/ - me_data_t *t=(me_data_t *)gd->lmdata[tid]; - /* divide the tiles into chunks tilesz/nchunk */ - int tilechunk=(t->tilesz+t->carr[t->clus].nchunk-1)/t->carr[t->clus].nchunk; - - - int ci; - - int cj=0; - int ntiles; - double init_res,final_res; - init_res=final_res=0.0; - if (tid<2) { - /* for GPU, the cost func and jacobian are not used */ - /* loop over each chunk, with right parameter set and data set */ - for (ci=0; cicarr[t->clus].nchunk; ci++) { - /* divide the tiles into chunks tilesz/nchunk */ - if (cj+tilechunktilesz) { - ntiles=tilechunk; - } else { - ntiles=t->tilesz-cj; - } - - if (gd->status[tid]==PT_DO_WORK_LM) { - clevmar_der_single_cuda_fl(&gd->p[tid][ci*(gd->M[tid])], &gd->x[tid][8*cj*t->Nbase], gd->M[tid], 8*ntiles*t->Nbase, gd->itermax[tid], gd->opts[tid], gd->info[tid], gd->cbhandle[tid], gd->solver_handle[tid], gd->gWORK[tid], gd->linsolv, cj, ntiles, (void*)gd->lmdata[tid]); - } else if (gd->status[tid]==PT_DO_WORK_OSLM) { - oslevmar_der_single_cuda_fl(&gd->p[tid][ci*(gd->M[tid])], &gd->x[tid][8*cj*t->Nbase], gd->M[tid], 8*ntiles*t->Nbase, gd->itermax[tid], gd->opts[tid], gd->info[tid], gd->cbhandle[tid], gd->solver_handle[tid], gd->gWORK[tid], gd->linsolv, cj, ntiles, gd->randomize, (void*)gd->lmdata[tid]); - } else if (gd->status[tid]==PT_DO_WORK_RLM) { - rlevmar_der_single_cuda_fl(&gd->p[tid][ci*(gd->M[tid])], &gd->x[tid][8*cj*t->Nbase], gd->M[tid], 8*ntiles*t->Nbase, gd->itermax[tid], gd->opts[tid], gd->info[tid], gd->cbhandle[tid], gd->solver_handle[tid], gd->gWORK[tid], gd->linsolv, cj, ntiles, gd->nulow,gd->nuhigh,(void*)gd->lmdata[tid]); - } else if (gd->status[tid]==PT_DO_WORK_OSRLM) { - osrlevmar_der_single_cuda_fl(&gd->p[tid][ci*(gd->M[tid])], &gd->x[tid][8*cj*t->Nbase], gd->M[tid], 8*ntiles*t->Nbase, gd->itermax[tid], gd->opts[tid], gd->info[tid], gd->cbhandle[tid], gd->solver_handle[tid], gd->gWORK[tid], gd->linsolv, cj, ntiles, gd->nulow,gd->nuhigh,gd->randomize,(void*)gd->lmdata[tid]); - } else if (gd->status[tid]==PT_DO_WORK_RTR) { - /* note stations: M/8, baselines ntiles*Nbase RSD+RTR */ - float Delta0=0.01f; /* use very small value because previous LM has already made the solution close to true value */ - /* storage: see function header - */ - rtr_solve_cuda_fl(&gd->p[tid][ci*(gd->M[tid])], &gd->x[tid][8*cj*t->Nbase], gd->M[tid]/8, ntiles*t->Nbase, gd->itermax[tid]+5, gd->itermax[tid]+10, Delta0, Delta0*0.125f, gd->info[tid], gd->cbhandle[tid], gd->solver_handle[tid], cj, ntiles, (void*)gd->lmdata[tid]); - } else if (gd->status[tid]==PT_DO_WORK_RRTR) { - float Delta0=0.01f; - rtr_solve_cuda_robust_fl(&gd->p[tid][ci*(gd->M[tid])], &gd->x[tid][8*cj*t->Nbase], gd->M[tid]/8, ntiles*t->Nbase, gd->itermax[tid]+5, gd->itermax[tid]+10, Delta0, Delta0*0.125f, gd->nulow, gd->nuhigh, gd->info[tid], gd->cbhandle[tid], gd->solver_handle[tid], cj, ntiles, (void*)gd->lmdata[tid]); - } else if (gd->status[tid]==PT_DO_WORK_NSD) { - nsd_solve_cuda_robust_fl(&gd->p[tid][ci*(gd->M[tid])], &gd->x[tid][8*cj*t->Nbase], gd->M[tid]/8, ntiles*t->Nbase, gd->itermax[tid]+15, gd->nulow, gd->nuhigh, gd->info[tid], gd->cbhandle[tid], gd->solver_handle[tid], cj, ntiles, (void*)gd->lmdata[tid]); - } - init_res+=gd->info[tid][0]; - final_res+=gd->info[tid][1]; - cj=cj+tilechunk; - } - - } - - gd->info[tid][0]=init_res; - gd->info[tid][1]=final_res; - -/************************* work *********************/ - } else if (gd->status[tid]==PT_DO_AGPU) { - /* also enable cula : 1 at end */ - attach_gpu_to_thread2(select_work_gpu(MAX_GPU_ID,td->pline->thst),&gd->cbhandle[tid],&gd->solver_handle[tid],&gd->gWORK[tid],gd->data_size,1); - } else if (gd->status[tid]==PT_DO_DGPU) { - detach_gpu_from_thread2(gd->cbhandle[tid],gd->solver_handle[tid],gd->gWORK[tid],1); - } else if (gd->status[tid]==PT_DO_MEMRESET) { - reset_gpu_memory((double*)gd->gWORK[tid],gd->data_size); - } else if (gd->status[tid]!=PT_DO_NOTHING) { /* catch error */ - fprintf(stderr,"%s: %d: invalid mode for slave tid=%d status=%d\n",__FILE__,__LINE__,tid,gd->status[tid]); - exit(1); - } - } - return NULL; -} - -/* initialize the pipeline - and start the slaves rolling */ -static void -init_pipeline_flt(th_pipeline *pline, - void *data) -{ - slave_tdata *t0,*t1; - pthread_attr_init(&(pline->attr)); - pthread_attr_setdetachstate(&(pline->attr),PTHREAD_CREATE_JOINABLE); - - init_th_barrier(&(pline->gate1),3); /* 3 threads, including master */ - init_th_barrier(&(pline->gate2),3); /* 3 threads, including master */ - pline->terminate=0; - pline->data=data; /* data should have pointers to t1 and t2 */ - - if ((t0=(slave_tdata*)malloc(sizeof(slave_tdata)))==0) { - fprintf(stderr,"no free memory\n"); - exit(1); - } - if ((t1=(slave_tdata*)malloc(sizeof(slave_tdata)))==0) { - fprintf(stderr,"no free memory\n"); - exit(1); - } - if ((pline->thst=(taskhist*)malloc(sizeof(taskhist)))==0) { - fprintf(stderr,"no free memory\n"); - exit(1); - } - - init_task_hist(pline->thst); - t0->pline=t1->pline=pline; - t0->tid=0; - t1->tid=1; /* link back t1, t2 to data so they could be freed */ - pline->sd0=t0; - pline->sd1=t1; - pthread_create(&(pline->slave0),&(pline->attr),pipeline_slave_code_flt,(void*)t0); - pthread_create(&(pline->slave1),&(pline->attr),pipeline_slave_code_flt,(void*)t1); -} - -/* destroy the pipeline */ -/* need to kill the slaves first */ -static void -destroy_pipeline_flt(th_pipeline *pline) -{ - - pline->terminate=1; - sync_barrier(&(pline->gate1)); - pthread_join(pline->slave0,NULL); - pthread_join(pline->slave1,NULL); - destroy_th_barrier(&(pline->gate1)); - destroy_th_barrier(&(pline->gate2)); - pthread_attr_destroy(&(pline->attr)); - destroy_task_hist(pline->thst); - free(pline->thst); - free(pline->sd0); - free(pline->sd1); - pline->data=NULL; -} - - -int -sagefit_visibilities_dual_pt_flt(double *u, double *v, double *w, double *x, int N, - int Nbase, int tilesz, baseline_t *barr, clus_source_t *carr, complex double *coh, int M, int Mt, double freq0, double fdelta, double *pp, double uvmin, int Nt, int max_emiter, int max_iter, int max_lbfgs, int lbfgs_m, int gpu_threads, int linsolv,int solver_mode, double nulow, double nuhigh, int randomize, double *mean_nu, double *res_0, double *res_1) { - - - int ci,cj; - double *p; // parameters: m x 1 - int m, n; - double opts[CLM_OPTS_SZ], info0[CLM_INFO_SZ], info1[CLM_INFO_SZ]; - me_data_t lmdata0,lmdata1; - int Nbase1; - - double *xdummy0,*xdummy1,*xsub,*xo; - double *nerr; /* array to store cost reduction per cluster */ - int weighted_iter,this_itermax0,this_itermax1,total_iter; - double total_err; - - /* rearraged memory for GPU use */ - double *ddcoh; - short *ddbase; - - int *cr=0; /* array for random permutation of clusters */ - int c0,c1; - - opts[0]=CLM_INIT_MU; opts[1]=1E-9; opts[2]=1E-9; opts[3]=1E-9; - opts[4]=-CLM_DIFF_DELTA; - - /* robust */ - double robust_nu0; - double *robust_nuM; - - /* no. of parameters >= than the no of clusters*8N */ - m=N*Mt*8; - /* no of data */ - n=Nbase*tilesz*8; - - /* true no of baselines */ - Nbase1=Nbase*tilesz; - - float *ddcohf, *pf, *xdummy0f, *xdummy1f; -/********* thread data ******************/ - /* barrier */ - th_pipeline tp; - gbdatafl tpg; -/****************************************/ - - /* use full parameter space */ - p=pp; - lmdata0.clus=lmdata1.clus=-1; - /* setup data for lmfit */ - lmdata0.u=lmdata1.u=u; - lmdata0.v=lmdata1.v=v; - lmdata0.w=lmdata1.w=w; - lmdata0.Nbase=lmdata1.Nbase=Nbase; - lmdata0.tilesz=lmdata1.tilesz=tilesz; - lmdata0.N=lmdata1.N=N; - lmdata0.barr=lmdata1.barr=barr; - lmdata0.carr=lmdata1.carr=carr; - lmdata0.M=lmdata1.M=M; - lmdata0.Mt=lmdata1.Mt=Mt; - lmdata0.freq0=lmdata1.freq0=&freq0; - lmdata0.Nt=lmdata1.Nt=Nt; - lmdata0.coh=lmdata1.coh=coh; - /* rearrange coh for GPU use */ - if ((ddcoh=(double*)calloc((size_t)(M*Nbase1*8),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((ddcohf=(float*)calloc((size_t)(M*Nbase1*8),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((ddbase=(short*)calloc((size_t)(Nbase1*2),sizeof(short)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - rearrange_coherencies(Nbase1, barr, coh, ddcoh, ddbase, M, Nt); - lmdata0.ddcoh=lmdata1.ddcoh=ddcoh; - lmdata0.ddbase=lmdata1.ddbase=ddbase; - - /* ddcohf (float) << ddcoh (double) */ - double_to_float(ddcohf,ddcoh,M*Nbase1*8,Nt); - lmdata0.ddcohf=lmdata1.ddcohf=ddcohf; - - if ((xsub=(double*)calloc((size_t)(n),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xo=(double*)calloc((size_t)(n),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xdummy0=(double*)calloc((size_t)(n),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xdummy1=(double*)calloc((size_t)(n),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((nerr=(double*)calloc((size_t)(M),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xdummy0f=(float*)calloc((size_t)(n),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xdummy1f=(float*)calloc((size_t)(n),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((pf=(float*)calloc((size_t)(Mt*8*N),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if (solver_mode==SM_OSLM_OSRLM_RLBFGS || solver_mode==SM_RLM_RLBFGS || solver_mode==SM_RTR_OSRLM_RLBFGS || solver_mode==SM_NSD_RLBFGS) { - if ((robust_nuM=(double*)calloc((size_t)(M),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - } else { - robust_nuM=0; - } - /* starting guess of robust nu */ - robust_nu0=nulow; - - double_to_float(pf,p,Mt*8*N,Nt); - /* remember for each partition how much the cost function decreases - in the next EM iteration, we allocate more LM iters to partitions - where const function significantly decreases. So two stages - 1) equal LM iters (find the decrease) 2) weighted LM iters */ - weighted_iter=0; - total_iter=M*max_iter; /* total iterations per EM */ -/********** setup threads *******************************/ - init_pipeline_flt(&tp,&tpg); - sync_barrier(&(tp.gate1)); /* sync at gate 1*/ - tpg.status[0]=tpg.status[1]=PT_DO_AGPU; - /* also calculate the total storage needed to be allocated on a GPU */ - /* determine total size for memory allocation */ - int Mm=8*N; - int64_t data_sz=0; - /* Do NOT use fixed buffer for for RTR/NSD - */ - if (solver_mode==SM_RTR_OSLM_LBFGS) { - /* use dummy data size */ - data_sz=8*sizeof(float); - } else if (solver_mode==SM_RTR_OSRLM_RLBFGS) { - data_sz=8*sizeof(float); - } else if (solver_mode==SM_NSD_RLBFGS) { - data_sz=8*sizeof(float); - } else if (solver_mode==SM_LM_LBFGS || solver_mode==SM_OSLM_LBFGS) { - /* size for LM */ - data_sz=(int64_t)(n+Mm*n+Mm*Mm+Mm+Mm*Mm+Mm+Mm+Mm+Mm+Nbase1*8+n+n)*sizeof(float)+(int64_t)Nbase1*2*sizeof(short); - if (linsolv==1) { - data_sz+=(int64_t)Mm*sizeof(float); - } else if (linsolv==2) { - data_sz+=(int64_t)(Mm*Mm+Mm*Mm+Mm)*sizeof(float); - } - } else if (solver_mode==SM_RLM_RLBFGS || solver_mode==SM_OSLM_OSRLM_RLBFGS) { - /* size for ROBUSTLM */ - data_sz=(int64_t)(n+Mm*n+Mm*Mm+Mm+Mm*Mm+Mm+Mm+Mm+Mm+Nbase1*8+n+n+n+n)*sizeof(float)+(int64_t)Nbase1*2*sizeof(short); - if (linsolv==1) { - data_sz+=(int64_t)Mm*sizeof(float); - } else if (linsolv==2) { - data_sz+=(int64_t)(Mm*Mm+Mm*Mm+Mm)*sizeof(float); - } - } else { - fprintf(stderr,"%s: %d: invalid mode for solver\n",__FILE__,__LINE__); - exit(1); - } - - tpg.data_size=data_sz; - tpg.nulow=nulow; - tpg.nuhigh=nuhigh; - tpg.randomize=randomize; - sync_barrier(&(tp.gate2)); /* sync at gate 2*/ - - sync_barrier(&(tp.gate1)); /* sync at gate 1*/ - tpg.status[0]=tpg.status[1]=PT_DO_NOTHING; - sync_barrier(&(tp.gate2)); /* sync at gate 2*/ - -/********** done setup threads *******************************/ - - /* initial residual calculation - subtract full model from data */ - minimize_viz_full_pth(p, xsub, m, n, (void*)&lmdata0); - memcpy(xo,x,(size_t)(n)*sizeof(double)); - my_daxpy(n, xsub, -1.0, xo); - *res_0=my_dnrm2(n,xo)/(double)n; - - int iter_bar=(int)ceil((0.80/(double)M)*((double)total_iter)); - for (ci=0; ci1) { - /* find a random permutation of clusters */ - cr=random_permutation(M,weighted_iter,nerr); - } else { - cr=NULL; - } - - for (cj=0; cj0 || this_itermax1>0) { - /* calculate contribution from hidden data, subtract from x */ - /* since x has already subtracted this model, just add - the ones we are solving for */ - - sync_barrier(&(tp.gate1)); /* sync at gate 1 */ - memcpy(xdummy0,xo,(size_t)(n)*sizeof(double)); - memcpy(xdummy1,xo,(size_t)(n)*sizeof(double)); - lmdata0.clus=c0; - lmdata1.clus=c1; - - /* NOTE: conditional mean x^i = s^i + 0.5 * residual^i */ - /* so xdummy=0.5 ( 2*model + residual ) */ - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata0); - my_daxpy(n, xsub, 2.0, xdummy0); - my_dscal(n, 0.5, xdummy0); - my_daxpy(n, xsub, 1.0, xo); - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata1); - my_daxpy(n, xsub, 2.0, xdummy1); - my_dscal(n, 0.5, xdummy1); - my_daxpy(n, xsub, 1.0, xo); -/**************************************************************************/ - /* xdummy*f (float) << xdummy* (double) */ - double_to_float(xdummy0f,xdummy0,n,Nt); - double_to_float(xdummy1f,xdummy1,n,Nt); - /* run this from a separate thread */ - tpg.p[0]=&pf[carr[c0].p[0]]; /* length carr[c0].nchunk times */ - tpg.x[0]=xdummy0f; - tpg.M[0]=8*N; /* even though size of p is > M, dont change this */ - tpg.N[0]=n; /* Nbase*tilesz*8 */ - tpg.itermax[0]=this_itermax0; - tpg.opts[0]=opts; - tpg.info[0]=info0; - tpg.linsolv=linsolv; - tpg.lmdata[0]=&lmdata0; - - tpg.p[1]=&pf[carr[c1].p[0]]; /* length carr[c1].nchunk times */ - tpg.x[1]=xdummy1f; - tpg.M[1]=8*N; /* even though size of p is > M, dont change this */ - tpg.N[1]=n; /* Nbase*tilesz*8 */ - tpg.itermax[1]=this_itermax1; - tpg.opts[1]=opts; - tpg.info[1]=info1; - tpg.linsolv=linsolv; - tpg.lmdata[1]=&lmdata1; -/**************************************************************************/ - - /* both threads do work */ - /* if solver_mode>0 last EM iteration full LM, the rest OS LM */ - if (solver_mode==SM_OSLM_LBFGS) { - if (ci==max_emiter-1) { - tpg.status[0]=tpg.status[1]=PT_DO_WORK_LM; - } else { - tpg.status[0]=tpg.status[1]=PT_DO_WORK_OSLM; - } - } else if (solver_mode==SM_LM_LBFGS) { - tpg.status[0]=tpg.status[1]=PT_DO_WORK_LM; - } else if (solver_mode==SM_OSLM_OSRLM_RLBFGS) { - /* last EM iteration robust OS-LM, the one before LM, the rest OS LM */ - if (ci==max_emiter-2) { - tpg.status[0]=tpg.status[1]=PT_DO_WORK_LM; - } else if (ci==max_emiter-1) { - lmdata0.robust_nu=lmdata1.robust_nu=robust_nu0; /* initial robust nu */ - //tpg.status[0]=tpg.status[1]=PT_DO_WORK_RLM; - tpg.status[0]=tpg.status[1]=PT_DO_WORK_OSRLM; - } else { - tpg.status[0]=tpg.status[1]=PT_DO_WORK_OSLM; - } - } else if (solver_mode==SM_RLM_RLBFGS) { - /* last EM iteration robust LM, the rest OS LM */ - if (ci==max_emiter-1) { - lmdata0.robust_nu=lmdata1.robust_nu=robust_nu0; /* initial robust nu */ - tpg.status[0]=tpg.status[1]=PT_DO_WORK_OSRLM; - } else { - tpg.status[0]=tpg.status[1]=PT_DO_WORK_OSLM; - } - } else if (solver_mode==SM_RTR_OSLM_LBFGS) { - tpg.status[0]=tpg.status[1]=PT_DO_WORK_RTR; - } else if (solver_mode==SM_RTR_OSRLM_RLBFGS) { - if (!ci) { - lmdata0.robust_nu=lmdata1.robust_nu=robust_nu0; /* initial robust nu */ - } - tpg.status[0]=tpg.status[1]=PT_DO_WORK_RRTR; - } else if (solver_mode==SM_NSD_RLBFGS) { - if (!ci) { - lmdata0.robust_nu=lmdata1.robust_nu=robust_nu0; /* initial robust nu */ - } - tpg.status[0]=tpg.status[1]=PT_DO_WORK_NSD; - } else { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: undefined solver mode\n",__FILE__,__LINE__); -#endif - exit(1); - } - sync_barrier(&(tp.gate2)); /* sync at gate 2 */ - sync_barrier(&(tp.gate1)); /* sync at gate 1 */ - tpg.status[0]=tpg.status[1]=PT_DO_NOTHING; - sync_barrier(&(tp.gate2)); /* sync at gate 2 */ -#ifdef DEBUG -printf("1: %lf -> %lf 2: %lf -> %lf\n\n\n",info0[0],info0[1],info1[0],info1[1]); -#endif - /* catch -ve value here */ - if (info0[0]>0.0) { - nerr[c0]=(info0[0]-info0[1])/info0[0]; - if (nerr[c0]<0.0) { nerr[c0]=0.0; } - } else { - nerr[c0]=0.0; - } - if (info1[0]>0.0) { - nerr[c1]=(info1[0]-info1[1])/info1[0]; - if (nerr[c1]<0.0) { nerr[c1]=0.0; } - } else { - nerr[c1]=0.0; - } - /* update robust_nu */ - if ((solver_mode==SM_RLM_RLBFGS || solver_mode==SM_OSLM_OSRLM_RLBFGS || solver_mode==SM_RTR_OSRLM_RLBFGS || solver_mode==SM_NSD_RLBFGS) && (ci==max_emiter-1)) { - robust_nuM[c0]+=lmdata0.robust_nu; - robust_nuM[c1]+=lmdata1.robust_nu; - } - /* p (double) << pf (float) */ - float_to_double(&p[carr[c0].p[0]],&pf[carr[c0].p[0]],carr[c0].nchunk*8*N,Nt); - float_to_double(&p[carr[c1].p[0]],&pf[carr[c1].p[0]],carr[c1].nchunk*8*N,Nt); - /* once again subtract solved model from data */ - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata0); - my_daxpy(n, xsub, -1.0, xo); - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata1); - my_daxpy(n, xsub, -1.0, xo); - - } - } - /* odd cluster out, if M is odd */ - if (M%2) { - if (randomize && M>1) { - c0=cr[M-1]; - } else { - c0=M-1; - } - /* calculate max LM iter for this cluster */ - if (weighted_iter) { - this_itermax0=(int)((0.20*nerr[c0])*((double)total_iter))+iter_bar; - } else { - this_itermax0=max_iter; - } -#ifdef DEBUG - printf("Cluster %d(iter=%d, wt=%lf)\n",c0,this_itermax0,nerr[c0]); -#endif - if (this_itermax0>0) { -/**************************************************************************/ - sync_barrier(&(tp.gate1)); /* sync at gate 1 */ - /* calculate contribution from hidden data, subtract from x */ - memcpy(xdummy0,xo,(size_t)(n)*sizeof(double)); - lmdata0.clus=c0; - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata0); - my_daxpy(n, xsub, 1.0, xdummy0); - my_daxpy(n, xsub, 1.0, xo); - - double_to_float(xdummy0f,xdummy0,n,Nt); - /* run this from a separate thread */ - tpg.p[0]=&pf[carr[c0].p[0]]; - tpg.x[0]=xdummy0f; - tpg.M[0]=8*N; - tpg.N[0]=n; - tpg.itermax[0]=this_itermax0; - tpg.opts[0]=opts; - tpg.info[0]=info0; - tpg.linsolv=linsolv; - tpg.lmdata[0]=&lmdata0; - - if (solver_mode==SM_OSLM_LBFGS) { - if (ci==max_emiter-1) { - tpg.status[0]=PT_DO_WORK_LM; - } else { - tpg.status[0]=PT_DO_WORK_OSLM; - } - } else if (solver_mode==SM_LM_LBFGS) { - tpg.status[0]=PT_DO_WORK_LM; - } else if (solver_mode==SM_OSLM_OSRLM_RLBFGS) { - /* last EM iteration robust OS-LM, the one before LM, the rest OS LM */ - if (ci==max_emiter-2) { - tpg.status[0]=PT_DO_WORK_LM; - } else if (ci==max_emiter-1) { - lmdata0.robust_nu=robust_nu0; /* initial robust nu */ - //tpg.status[0]=PT_DO_WORK_RLM; - tpg.status[0]=PT_DO_WORK_OSRLM; - } else { - tpg.status[0]=PT_DO_WORK_OSLM; - } - } else if (solver_mode==SM_RLM_RLBFGS) { - if (ci==max_emiter-1) { - lmdata0.robust_nu=robust_nu0; /* initial robust nu */ - tpg.status[0]=PT_DO_WORK_OSRLM; - } else { - tpg.status[0]=PT_DO_WORK_OSLM; - } - } else if (solver_mode==SM_RTR_OSLM_LBFGS) { - tpg.status[0]=PT_DO_WORK_RTR; - } else if (solver_mode==SM_RTR_OSRLM_RLBFGS) { - if (!ci) { - lmdata0.robust_nu=robust_nu0; /* initial robust nu */ - } - tpg.status[0]=PT_DO_WORK_RRTR; - } else if (solver_mode==SM_NSD_RLBFGS) { - if (!ci) { - lmdata0.robust_nu=robust_nu0; /* initial robust nu */ - } - tpg.status[0]=PT_DO_WORK_NSD; - } else { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: undefined solver mode\n",__FILE__,__LINE__); -#endif - exit(1); - } - - tpg.status[1]=PT_DO_NOTHING; - sync_barrier(&(tp.gate2)); /* sync at gate 2 */ -/**************************************************************************/ - sync_barrier(&(tp.gate1)); /* sync at gate 1 */ - tpg.status[0]=tpg.status[1]=PT_DO_NOTHING; - sync_barrier(&(tp.gate2)); /* sync at gate 2 */ - -#ifdef DEBUG -printf("1: %lf -> %lf\n\n\n",info0[0],info0[1]); -#endif - /* catch -ve value here */ - if (info0[0]>0.0) { - nerr[c0]=(info0[0]-info0[1])/info0[0]; - if (nerr[c0]<0.0) { nerr[c0]=0.0; } - } else { - nerr[c0]=0.0; - } - /* update robust_nu */ - if ((solver_mode==SM_RLM_RLBFGS || solver_mode==SM_OSLM_OSRLM_RLBFGS || solver_mode==SM_RTR_OSRLM_RLBFGS || solver_mode==SM_NSD_RLBFGS) && (ci==max_emiter-1)) { - robust_nuM[c0]+=lmdata0.robust_nu; - } - /* once again subtract solved model from data */ - float_to_double(&p[carr[c0].p[0]],&pf[carr[c0].p[0]],carr[c0].nchunk*8*N,Nt); - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata0); - my_daxpy(n, xsub, -1.0, xo); - } - } - /* normalize nerr array so that the sum is 1 */ - total_err=my_dasum(M,nerr); - if (total_err>0.0) { - my_dscal(M, 1.0/total_err, nerr); - } - if (randomize && M>1) { /* nothing to randomize if only 1 direction */ - /* flip weighting flag */ - weighted_iter=!weighted_iter; - free(cr); - } - /**************** End EM iteration ***********************/ - } - free(nerr); - free(xo); - free(xdummy0); - free(xdummy1); - free(ddcoh); - free(ddbase); - free(xdummy0f); - free(xdummy1f); - free(pf); - free(ddcohf); - if (solver_mode==SM_RLM_RLBFGS || solver_mode==SM_OSLM_OSRLM_RLBFGS || solver_mode==SM_RTR_OSRLM_RLBFGS || solver_mode==SM_NSD_RLBFGS) { - /* calculate mean robust_nu over all clusters */ - robust_nu0=my_dasum(M,robust_nuM)/(double)M; -#ifdef DEBUG - for (ci=0; cinuhigh) { - robust_nu0=nuhigh; - } - - } - /******** free threads ***************/ - sync_barrier(&(tp.gate1)); /* sync at gate 1*/ - tpg.status[0]=tpg.status[1]=PT_DO_DGPU; - sync_barrier(&(tp.gate2)); /* sync at gate 2*/ - - - destroy_pipeline_flt(&tp); - /******** done free threads ***************/ - - if (max_lbfgs>0) { - /* use LBFGS */ - if (solver_mode==SM_RLM_RLBFGS || solver_mode==SM_OSLM_OSRLM_RLBFGS || solver_mode==SM_RTR_OSRLM_RLBFGS || solver_mode==SM_NSD_RLBFGS) { - lmdata0.robust_nu=robust_nu0; - lbfgs_fit_robust_cuda(minimize_viz_full_pth, p, x, m, n, max_lbfgs, lbfgs_m, gpu_threads, (void*)&lmdata0); - } else { - lbfgs_fit(minimize_viz_full_pth, p, x, m, n, max_lbfgs, lbfgs_m, gpu_threads, (void*)&lmdata0); - } - } - - /* final residual calculation */ - minimize_viz_full_pth(p, xsub, m, n, (void*)&lmdata0); - my_daxpy(n, xsub, -1.0, x); - - *mean_nu=robust_nu0; - *res_1=my_dnrm2(n,x)/(double)n; - - free(xsub); - - /* if final residual > initial residual, - return -1, else 0 - */ - if (*res_1>*res_0) { - return -1; - } - return 0; -} -#endif /* HYBRID_CODE */ diff --git a/src/lib/Solvers/lmfit.o b/src/lib/Solvers/lmfit.o deleted file mode 100644 index 32bd1a010f672e5b9f045ff9fef09dc9c0bce245..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 141256 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πAB|{4K>VCH^nP7gL=5uK4Z5&!_%p|Li3`RB?{Wm5Ps~{`;rm zxy0X6oUfNpDt<4?H>3D+?IGS-@lS}4R{S^OIf`?9R4G23{8Fd*G2;6bzku@pR@_JX z#$ObVBi@S6+^#N^=QHzB#OEpb63VYrd^P3wD88QZ?nI&-~u zQ+}}G|Db%a;{9lUc&*|ih~KMtKJmSZkEH$K5ydNrpH%#M;+<*#%l6k1pQHFr;tn@ph&QHoaJ?&u4^#X$;uVTNMEoJepC$gT;*Ze&_It&jBF_79w&!2O zub}oZ&!>HFisIK0U#j>9;twlcNBlL#Un2gE;y)8_K<#6D8qoY7ulPB{`zhXz_!Px2 zCtj#{58`VTA4L2f#YYm~qxdA^Z!12R__vBz6K_cU!0qLA{9?s#r1h((;(UKBTk-8A ze~01+iNCD)7sS6%ya`-C{$xGLv=7{?cnZlMR(u6rH+`k}N5mUY z|FHh8rc-ZU#UG%0rz&1V?VYdq1=Oz9ivL0KcPgHV9RbHjiYF8QS@9`k|2gyQV)pNt zVZU5Nc6Lyl|L)FE#s5q4(-fa=8TPSQ@pp+IQv5LatsBKJ`|~rp9$cq*T?0q|CyMWB z=m{4PEh;}vL{FJRN@7Sms7m0Q2Yqle~aRD z`^czM`~=zajN+Blu49Vx-)Z<#@r4v8Cl!B-{NIAcG5d2P>AzI*o2b3=?|5Qd9U}dc zl>Eu2j(=AxK9~5tiZ}K<^3N$=;$Y)L#ix^kwjd=T+36`xDI#pRAZwtp0H`8yJ58rMticH8TehWkU5 zz%Zi_vB^yocbdnOJjzP>o{A??zB%b(d44|aYsGne?5f$ee%I zF20^)0CWDGt^&pR_k6Z0&cBbqfB%d1@b5NM*FRf7KUd$J9L@6lJi|K0`TMy41440g AfB*mh diff --git a/src/lib/Solvers/lmfit_nocuda.c b/src/lib/Solvers/lmfit_nocuda.c deleted file mode 100644 index afd9adb..0000000 --- a/src/lib/Solvers/lmfit_nocuda.c +++ /dev/null @@ -1,1283 +0,0 @@ -/* - * - Copyright (C) 2006-2008 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#include -#include -#include -#include -#include -#include "Solvers.h" - -//#define DEBUG - -/* Jones matrix multiplication - C=A*B -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -amb(complex double * __restrict a, complex double * __restrict b, complex double * __restrict c) { - c[0]=a[0]*b[0]+a[1]*b[2]; - c[1]=a[0]*b[1]+a[1]*b[3]; - c[2]=a[2]*b[0]+a[3]*b[2]; - c[3]=a[2]*b[1]+a[3]*b[3]; -} - -/* Jones matrix multiplication - C=A*B^H -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -ambt(complex double * __restrict a, complex double * __restrict b, complex double * __restrict c) { - c[0]=a[0]*conj(b[0])+a[1]*conj(b[1]); - c[1]=a[0]*conj(b[2])+a[1]*conj(b[3]); - c[2]=a[2]*conj(b[0])+a[3]*conj(b[1]); - c[3]=a[2]*conj(b[2])+a[3]*conj(b[3]); -} - -/********************** sage minimization ***************************/ -/* worker thread function for prediction */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -predict_threadfn_withgain(void *data) { - thread_data_base_t *t=(thread_data_base_t*)data; - - int ci,cm,sta1,sta2; - complex double C[4],G1[4],G2[4],T1[4],T2[4]; - int M=(t->M); - cm=(t->clus); - int Ntilebase=(t->Nbase)*(t->tilesz); - int px; - double *pm; - - for (ci=0; ciNb; ci++) { - /* iterate over the sky model and calculate contribution */ - /* for this x[8*ci:8*(ci+1)-1] */ - memset(&(t->x[8*ci]),0,sizeof(double)*8); - - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - px=(ci+t->boff)/((Ntilebase+t->carr[cm].nchunk-1)/t->carr[cm].nchunk); - pm=&(t->p[t->carr[cm].p[px]]); - - /* gains for this cluster, for sta1,sta2 */ - G1[0]=(pm[sta1*8])+_Complex_I*(pm[sta1*8+1]); - G1[1]=(pm[sta1*8+2])+_Complex_I*(pm[sta1*8+3]); - G1[2]=(pm[sta1*8+4])+_Complex_I*(pm[sta1*8+5]); - G1[3]=(pm[sta1*8+6])+_Complex_I*(pm[sta1*8+7]); - G2[0]=(pm[sta2*8])+_Complex_I*(pm[sta2*8+1]); - G2[1]=(pm[sta2*8+2])+_Complex_I*(pm[sta2*8+3]); - G2[2]=(pm[sta2*8+4])+_Complex_I*(pm[sta2*8+5]); - G2[3]=(pm[sta2*8+6])+_Complex_I*(pm[sta2*8+7]); - - - /* use pre calculated values */ - C[0]=t->coh[4*M*ci+4*cm]; - C[1]=t->coh[4*M*ci+4*cm+1]; - C[2]=t->coh[4*M*ci+4*cm+2]; - C[3]=t->coh[4*M*ci+4*cm+3]; - - - /* form G1*C*G2' */ - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - - /* add to baseline visibilities */ - t->x[8*ci]+=creal(T2[0]); - t->x[8*ci+1]+=cimag(T2[0]); - t->x[8*ci+2]+=creal(T2[1]); - t->x[8*ci+3]+=cimag(T2[1]); - t->x[8*ci+4]+=creal(T2[2]); - t->x[8*ci+5]+=cimag(T2[2]); - t->x[8*ci+6]+=creal(T2[3]); - t->x[8*ci+7]+=cimag(T2[3]); - } - } - - return NULL; -} - - -/* minimization function (multithreaded) */ -/* p: size mx1 parameters - x: size nx1 data calculated - data: extra info needed */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -mylm_fit_single_pth(double *p, double *x, int m, int n, void *data) { - - me_data_t *dp=(me_data_t*)data; - /* u,v,w : size Nbase*tilesz x 1 x: size Nbase*8*tilesz x 1 */ - /* barr: size Nbase*tilesz x 1 carr: size Mx1 */ - /* pp: size 8*N*M x 1 */ - /* pm: size Mx1 of double */ - - int nth,nth1,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthb0,Nthb; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_base_t *threaddata; - - int Nbase=(dp->Nbase); - int tilesz=(dp->tilesz); - - int Nbase1=Nbase*tilesz; - - /* calculate min baselines a thread can handle */ - //Nthb0=ceil((double)Nbase1/(double)Nt); - Nthb0=(Nbase1+Nt-1)/Nt; - - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((threaddata=(thread_data_base_t*)malloc((size_t)Nt*sizeof(thread_data_base_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr; - threaddata[nth].u=&(dp->u[ci]); - threaddata[nth].v=&(dp->v[ci]); - threaddata[nth].w=&(dp->w[ci]); - threaddata[nth].carr=dp->carr; - threaddata[nth].M=dp->M; - threaddata[nth].x=&(x[8*ci]); - threaddata[nth].N=dp->N; - threaddata[nth].Nbase=Nbase; - threaddata[nth].tilesz=tilesz; - threaddata[nth].p=p; - threaddata[nth].clus=(dp->clus); - threaddata[nth].coh=&(dp->coh[4*(dp->M)*ci]); - - //printf("thread %d predict data from %d baselines %d\n",nth,8*ci,Nthb); - pthread_create(&th_array[nth],&attr,predict_threadfn_withgain,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - /* now wait for threads to finish */ - for(nth1=0; nth1M); - cm=(t->clus); - for (ci=0; ciNb; ci++) { - /* iterate over the sky model and calculate contribution */ - /* for this x[8*ci:8*(ci+1)-1] */ - memset(&(t->x[8*ci]),0,sizeof(double)*8); - - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - /* gains for this cluster, for sta1,sta2 */ - G1[0]=(t->p[sta1*8])+_Complex_I*(t->p[sta1*8+1]); - G1[1]=(t->p[sta1*8+2])+_Complex_I*(t->p[sta1*8+3]); - G1[2]=(t->p[sta1*8+4])+_Complex_I*(t->p[sta1*8+5]); - G1[3]=(t->p[sta1*8+6])+_Complex_I*(t->p[sta1*8+7]); - G2[0]=(t->p[sta2*8])+_Complex_I*(t->p[sta2*8+1]); - G2[1]=(t->p[sta2*8+2])+_Complex_I*(t->p[sta2*8+3]); - G2[2]=(t->p[sta2*8+4])+_Complex_I*(t->p[sta2*8+5]); - G2[3]=(t->p[sta2*8+6])+_Complex_I*(t->p[sta2*8+7]); - - - /* use pre calculated values */ - C[0]=t->coh[4*M*ci+4*cm]; - C[1]=t->coh[4*M*ci+4*cm+1]; - C[2]=t->coh[4*M*ci+4*cm+2]; - C[3]=t->coh[4*M*ci+4*cm+3]; - - - /* form G1*C*G2' */ - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - - /* add to baseline visibilities */ - t->x[8*ci]+=creal(T2[0]); - t->x[8*ci+1]+=cimag(T2[0]); - t->x[8*ci+2]+=creal(T2[1]); - t->x[8*ci+3]+=cimag(T2[1]); - t->x[8*ci+4]+=creal(T2[2]); - t->x[8*ci+5]+=cimag(T2[2]); - t->x[8*ci+6]+=creal(T2[3]); - t->x[8*ci+7]+=cimag(T2[3]); - } - } - - return NULL; -} - - -/* minimization function (multithreaded) : not considering - hybrid parameter space */ -/* p: size mx1 parameters - x: size nx1 data calculated - data: extra info needed */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -mylm_fit_single_pth0(double *p, double *x, int m, int n, void *data) { - - me_data_t *dp=(me_data_t*)data; - /* u,v,w : size Nbase*tilesz x 1 x: size Nbase*8*tilesz x 1 */ - /* barr: size Nbase*tilesz x 1 carr: size Mx1 */ - /* pp: size 8*N*M x 1 */ - /* pm: size Mx1 of double */ - - int nth,nth1,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthb0,Nthb; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_base_t *threaddata; - - int Nbase1=(dp->Nbase)*(dp->tilesz); - int boff=(dp->Nbase)*(dp->tileoff); - - /* calculate min baselines a thread can handle */ - //Nthb0=ceil((double)Nbase1/(double)Nt); - Nthb0=(Nbase1+Nt-1)/Nt; - - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((threaddata=(thread_data_base_t*)malloc((size_t)Nt*sizeof(thread_data_base_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr; - threaddata[nth].u=&(dp->u[ci]); - threaddata[nth].v=&(dp->v[ci]); - threaddata[nth].w=&(dp->w[ci]); - threaddata[nth].carr=dp->carr; - threaddata[nth].M=dp->M; - threaddata[nth].x=&(x[8*ci]); - threaddata[nth].N=dp->N; - threaddata[nth].p=p; /* note the difference: here p assumes no hybrid */ - threaddata[nth].clus=(dp->clus); - threaddata[nth].coh=&(dp->coh[4*(dp->M)*(ci+boff)]); - - //printf("thread %d predict data from %d baselines %d\n",nth,8*ci,Nthb); - pthread_create(&th_array[nth],&attr,predict_threadfn_withgain0,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - /* now wait for threads to finish */ - for(nth1=0; nth1M); - cm=(t->clus); - int stc,stoff; - - /* Loop order to minimize cache misses */ - /* we calculate the jacobian (nxm) columns [startc...endc] */ - for (col=t->start_col; col<=t->end_col; col++) { - /* iterate over row */ - for (ci=0; ciNb; ci++) { - - /* if this baseline is flagged, - or if this parameter does not belong to sta1 or sta2 - we do not compute */ - stc=col/8; /* 0..N-1 */ - /* stations for this baseline */ - sta1=t->barr[ci].sta1; - sta2=t->barr[ci].sta2; - - /* change order for checking condition to minimize cache misses - since sta2 will appear more, first check that ??? */ - if ( ((stc==sta2)||(stc==sta1)) && (!t->barr[ci].flag) ) { - - /* use pre calculated values */ - C[0]=t->coh[4*M*ci+4*cm]; - C[1]=t->coh[4*M*ci+4*cm+1]; - C[2]=t->coh[4*M*ci+4*cm+2]; - C[3]=t->coh[4*M*ci+4*cm+3]; - - /* which parameter exactly 0..7 */ - stoff=col%8; - //printf("sta1=%d,sta2=%d,stc=%d,off=%d,col=%d,param=%d\n",sta1,sta2,stc,col%8,col,stc*8+stoff); - if (stc==sta1) { - for (cn=0; cn<8; cn++) { - pp1[cn]=0.0; - pp2[cn]=t->p[sta2*8+cn]; - } - pp1[stoff]=1.0; - } else if (stc==sta2) { - for (cn=0; cn<8; cn++) { - pp2[cn]=0.0; - pp1[cn]=t->p[sta1*8+cn]; - } - pp2[stoff]=1.0; - } - /* gains for this cluster, for sta1,sta2 */ - G1[0]=pp1[0]+_Complex_I*pp1[1]; - G1[1]=pp1[2]+_Complex_I*pp1[3]; - G1[2]=pp1[4]+_Complex_I*pp1[5]; - G1[3]=pp1[6]+_Complex_I*pp1[7]; - G2[0]=pp2[0]+_Complex_I*pp2[1]; - G2[1]=pp2[2]+_Complex_I*pp2[3]; - G2[2]=pp2[4]+_Complex_I*pp2[5]; - G2[3]=pp2[6]+_Complex_I*pp2[7]; - - /* form G1*C*G2' */ - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - - /* add to baseline visibilities */ - /* NOTE: row major order */ - t->jac[col+(t->m)*8*ci]=creal(T2[0]); - t->jac[col+(t->m)*(8*ci+1)]=cimag(T2[0]); - t->jac[col+(t->m)*(8*ci+2)]=creal(T2[1]); - t->jac[col+(t->m)*(8*ci+3)]=cimag(T2[1]); - t->jac[col+(t->m)*(8*ci+4)]=creal(T2[2]); - t->jac[col+(t->m)*(8*ci+5)]=cimag(T2[2]); - t->jac[col+(t->m)*(8*ci+6)]=creal(T2[3]); - t->jac[col+(t->m)*(8*ci+7)]=cimag(T2[3]); - - } - } - } - - return NULL; -} - -/* jacobian function (multithreaded) */ -/* p: size mx1 parameters - jac: size nxm jacobian to be calculated (row major) - data: extra info needed */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -mylm_jac_single_pth(double *p, double *jac, int m, int n, void *data) { - - me_data_t *dp=(me_data_t*)data; - /* u,v,w : size Nbase*tilesz x 1 x: size Nbase*8*tilesz x 1 */ - /* barr: size Nbase*tilesz x 1 carr: size Mx1 */ - /* pp: size 8*N*M x 1 */ - /* pm: size Mx1 of double */ - - int nth,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthcol; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_jac_t *threaddata; - - int Nbase=(dp->Nbase)*(dp->tilesz); - int boff=(dp->Nbase)*(dp->tileoff); - - /* calculate min columns of the jacobian one thread can handle */ - Nthcol=(m+Nt-1)/Nt; - - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((threaddata=(thread_data_jac_t*)malloc((size_t)Nt*sizeof(thread_data_jac_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - /* set jacobian to all zeros */ - memset(jac,0,sizeof(double)*n*m); - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr[boff]); - threaddata[nth].u=dp->u; - threaddata[nth].v=dp->v; - threaddata[nth].w=dp->w; - threaddata[nth].carr=dp->carr; - threaddata[nth].M=dp->M; - threaddata[nth].jac=jac; /* NOTE: jacobian is in row major order */ - threaddata[nth].N=dp->N; - threaddata[nth].p=p; - threaddata[nth].clus=(dp->clus); - threaddata[nth].coh=&(dp->coh[4*(dp->M)*(boff)]); - threaddata[nth].start_col=ci; - threaddata[nth].end_col=ci+Nthcol-1; - if (threaddata[nth].end_col>=m) { - threaddata[nth].end_col=m-1; - } - - //printf("thread %d calculate cols %d to %d\n",nth,threaddata[nth].start_col, threaddata[nth].end_col); - pthread_create(&th_array[nth],&attr,jacobian_threadfn,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthcol; - } - - /* now wait for threads to finish */ - for(nth=0; nthM); - int Ntilebase=(t->Nbase)*(t->tilesz); - int px; - - for (ci=0; ciNb; ci++) { - /* iterate over the sky model and calculate contribution */ - /* for this x[8*ci:8*(ci+1)-1] */ - memset(&(t->x[8*ci]),0,sizeof(double)*8); - - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - for (cm=0; cm x[0.....Nbase*tilesz/nchunk-1] - p[1] -> x[Nbase*tilesz/nchunk......2*Nbase*tilesz-1] - .... - p[last] -> x[(nchunk-1)*Nbase*tilesz/nchunk......Nbase*tilesz] - - so given bindex, right p[] is bindex/((Nbase*tilesz+nchunk-1)/nchunk) - */ - - px=(ci+t->boff)/((Ntilebase+t->carr[cm].nchunk-1)/t->carr[cm].nchunk); - pm=&(t->p[t->carr[cm].p[px]]); - G1[0]=(pm[sta1*8])+_Complex_I*(pm[sta1*8+1]); - G1[1]=(pm[sta1*8+2])+_Complex_I*(pm[sta1*8+3]); - G1[2]=(pm[sta1*8+4])+_Complex_I*(pm[sta1*8+5]); - G1[3]=(pm[sta1*8+6])+_Complex_I*(pm[sta1*8+7]); - G2[0]=(pm[sta2*8])+_Complex_I*(pm[sta2*8+1]); - G2[1]=(pm[sta2*8+2])+_Complex_I*(pm[sta2*8+3]); - G2[2]=(pm[sta2*8+4])+_Complex_I*(pm[sta2*8+5]); - G2[3]=(pm[sta2*8+6])+_Complex_I*(pm[sta2*8+7]); - - - /* use pre calculated values */ - C[0]=t->coh[4*M*ci+4*cm]; - C[1]=t->coh[4*M*ci+4*cm+1]; - C[2]=t->coh[4*M*ci+4*cm+2]; - C[3]=t->coh[4*M*ci+4*cm+3]; - - /* form G1*C*G2' */ - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - - /* add to baseline visibilities */ - t->x[8*ci]+=creal(T2[0]); - t->x[8*ci+1]+=cimag(T2[0]); - t->x[8*ci+2]+=creal(T2[1]); - t->x[8*ci+3]+=cimag(T2[1]); - t->x[8*ci+4]+=creal(T2[2]); - t->x[8*ci+5]+=cimag(T2[2]); - t->x[8*ci+6]+=creal(T2[3]); - t->x[8*ci+7]+=cimag(T2[3]); - } - } - } - - return NULL; -} - -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -minimize_viz_full_pth(double *p, double *x, int m, int n, void *data) { - - me_data_t *dp=(me_data_t*)data; - /* u,v,w : size Nbase*tilesz x 1 x: size Nbase*8*tilesz x 1 */ - /* barr: size Nbase*tilesz x 1 carr: size Mx1 */ - /* pp: size 8*N*M x 1 */ - /* pm: size Mx1 of double */ - - int nth,nth1,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthb0,Nthb; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_base_t *threaddata; - - int Nbase1=(dp->Nbase)*(dp->tilesz); - - /* calculate min baselines a thread can handle */ - Nthb0=(Nbase1+Nt-1)/Nt; - - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((threaddata=(thread_data_base_t*)malloc((size_t)Nt*sizeof(thread_data_base_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr; - threaddata[nth].u=&(dp->u[ci]); - threaddata[nth].v=&(dp->v[ci]); - threaddata[nth].w=&(dp->w[ci]); - threaddata[nth].carr=dp->carr; - threaddata[nth].M=dp->M; - threaddata[nth].x=&(x[8*ci]); - threaddata[nth].p=p; - threaddata[nth].N=dp->N; - threaddata[nth].Nbase=dp->Nbase; - threaddata[nth].tilesz=dp->tilesz; - threaddata[nth].coh=&(dp->coh[4*(dp->M)*ci]); - - //printf("thread %d predict data from %d baselines %d\n",nth,8*ci,Nthb); - pthread_create(&th_array[nth],&attr,predict_threadfn_withgain_full,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - /* now wait for threads to finish */ - for(nth1=0; nth10) { - /* calculate contribution from hidden data, subtract from x - actually, add the current model for this cluster to residual */ - lmdata.clus=cj; - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata); - my_daxpy(n, xsub, 1.0, xdummy); - - tilechunk=(tilesz+carr[cj].nchunk-1)/carr[cj].nchunk; - tcj=0; - init_res=final_res=0.0; - /* loop through hybrid parameter space */ - for (ck=0; ck0.0) { - nerr[cj]=(init_res-final_res)/init_res; - if (nerr[cj]<0.0) { nerr[cj]=0.0; } - } else { - nerr[cj]=0.0; - } - /* subtract current model */ - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata); - my_daxpy(n, xsub, -1.0, xdummy); - /* if robust LM, calculate average nu over hybrid clusters */ - if ((solver_mode==SM_OSLM_OSRLM_RLBFGS || solver_mode==SM_RLM_RLBFGS || solver_mode==SM_RTR_OSRLM_RLBFGS || solver_mode==SM_NSD_RLBFGS) && (ci==max_emiter-1)) { - robust_nuM[cj]/=(double)carr[cj].nchunk; - } - } - } - - /* normalize nerr array so that the sum is 1 */ - total_err=my_dasum(M,nerr); - if (total_err>0.0) { - my_dscal(M, 1.0/total_err, nerr); - } - - /* flip weighting flag */ - if (randomize) { - weighted_iter=!weighted_iter; - } - } - free(nerr); - free(xdummy); - if (solver_mode==SM_OSLM_OSRLM_RLBFGS || solver_mode==SM_RLM_RLBFGS || solver_mode==SM_RTR_OSRLM_RLBFGS || solver_mode==SM_NSD_RLBFGS) { - /* calculate mean robust_nu over all clusters */ - robust_nu0=my_dasum(M,robust_nuM)/(double)M; -#ifdef DEBUG - for (ci=0; cinuhigh) { - robust_nu0=nuhigh; - } - } - - if (max_lbfgs>0) { -#ifdef USE_MIC - lmdata.Nt=32; /* FIXME increase threads for MIC */ -#endif - /* use LBFGS */ - if (solver_mode==SM_OSLM_OSRLM_RLBFGS || solver_mode==SM_RLM_RLBFGS || solver_mode==SM_RTR_OSRLM_RLBFGS || solver_mode==SM_NSD_RLBFGS) { - lmdata.robust_nu=robust_nu0; - lbfgs_fit_robust(minimize_viz_full_pth, p, x, m, n, max_lbfgs, lbfgs_m, gpu_threads, (void*)&lmdata); - } else { - lbfgs_fit(minimize_viz_full_pth, p, x, m, n, max_lbfgs, lbfgs_m, gpu_threads, (void*)&lmdata); - } -#ifdef USE_MIC - lmdata.Nt=Nt; /* reset threads for MIC */ -#endif - } - /* final residual calculation */ - minimize_viz_full_pth(p, xsub, m, n, (void*)&lmdata); - my_daxpy(n, xsub, -1.0, x); - - *mean_nu=robust_nu0; - *res_1=my_dnrm2(n,x)/(double)n; - - free(xsub); - /* if final residual > initial residual, - return -1, else 0 - */ - if (*res_1>*res_0) { - return -1; - } - return 0; -} - - - -/* struct and function for qsort */ -typedef struct w_n_ { - int i; - double w; -} w_n; -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static int -weight_compare(const void *a, const void *b) { - w_n *aw,*bw; - aw=(w_n*)a; - bw=(w_n*)b; - if (aw->w>bw->w) return -1; - if (aw->w==bw->w) return 0; - - return 1; -} - -/* generate a random permutation of given integers */ -/* note: free returned value after use */ -/* n: no of entries, - weighter_iter: if 1, take weight into account - if 0, only generate a random permutation - w: weights (size nx1): sort them in descending order and - give permutation accordingly -*/ -int* -random_permutation(int n, int weighted_iter, double *w) { - int *p; - int i; - if ((p=(int*)malloc((size_t)(n)*sizeof(int)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if (!weighted_iter) { - for (i = 0; i < n; ++i) { - int j = rand() % (i + 1); - p[i] = p[j]; - p[j] = i; - } - } else { - /* we take weight into account */ - w_n *wn_arr; - if ((wn_arr=(w_n*)malloc((size_t)(n)*sizeof(w_n)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - for (i=0; i0) { -#ifdef USE_MIC - lmdata.Nt=64; /* increase threads for MIC */ -#endif - /* use LBFGS */ - if (solver_mode==2 || solver_mode==3) { - lmdata.robust_nu=mean_nu; - lbfgs_fit_robust(minimize_viz_full_pth, p, x, m, n, max_lbfgs, lbfgs_m, gpu_threads, (void*)&lmdata); - } else { - lbfgs_fit(minimize_viz_full_pth, p, x, m, n, max_lbfgs, lbfgs_m, gpu_threads, (void*)&lmdata); - } -#ifdef USE_MIC - lmdata.Nt=Nt; /* reset threads for MIC */ -#endif - } - /* final residual calculation */ - minimize_viz_full_pth(p, xsub, m, n, (void*)&lmdata); - my_daxpy(n, xsub, -1.0, x); - - *res_1=my_dnrm2(n,x)/(double)n; - - free(xsub); - free(xdummy); - /* if final residual > initial residual, - return -1, else 0 - */ - if (*res_1>*res_0) { - return -1; - } - return 0; -} - - -#ifdef USE_MIC -/* wrapper function with bitwise copyable carr[] for MIC */ -/* nchunks: Mx1 array of chunk sizes for each cluster */ -/* pindex: Mt x 1 array of index of solutions for each cluster in pp */ -int -sagefit_visibilities_mic(double *u, double *v, double *w, double *x, int N, - int Nbase, int tilesz, baseline_t *barr, int *nchunks, int *pindex, complex double *coh, int M, int Mt, double freq0, double fdelta, double *pp, double uvmin, int Nt, int max_emiter, int max_iter, int max_lbfgs, int lbfgs_m, int gpu_threads, int linsolv,int solver_mode,double nulow, double nuhigh,int randomize, double *mean_nu, double *res_0, double *res_1) { - - clus_source_t *carr; - /* create a dummy carr[] structure to pass on */ - if ((carr=(clus_source_t*)calloc((size_t)M,sizeof(clus_source_t)))==0) { - exit(1); - } - int ci,cj,retval; - /* only need two fields in carr[] */ - cj=0; - for (ci=0; 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 - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#include -#include -#include -#include -#include -#include "Solvers.h" - - -#include - -//#define MPI_BUILD -#ifdef MPI_BUILD -#include -#endif - -//#define DEBUG - -/* return random value in 0,1,..,maxval */ -#ifndef MPI_BUILD -static int -random_pick(int maxval, taskhist *th) { - double rat=(double)random()/(double)RAND_MAX; - double y=rat*(double)(maxval+1); - int x=(int)floor(y); - return x; -} -#endif - -void -init_task_hist(taskhist *th) { - th->prev=-1; - th->rseed=0; - pthread_mutex_init(&th->prev_mutex,NULL); -} - -void -destroy_task_hist(taskhist *th) { - th->prev=-1; - th->rseed=0; - pthread_mutex_destroy(&th->prev_mutex); -} - -/* select a GPU from 0,1..,max_gpu - in such a way to allow load balancing */ -int -select_work_gpu(int max_gpu, taskhist *th) { -#ifdef MPI_BUILD - int rank; - MPI_Comm_rank(MPI_COMM_WORLD, &rank); - /* check if max_gpu > no. of actual devices */ - int actual_devcount; - cudaGetDeviceCount(&actual_devcount); - if (max_gpu+1>actual_devcount) { - return rank%(actual_devcount); - } - return rank%(max_gpu+1); /* modulo value */ -#endif - -#ifndef MPI_BUILD - /* sequentially query the devices to find - one with the min load/memory usage */ - nvmlReturn_t result; - result = nvmlInit(); - int retval; - int minid=-1; - int maxid=-1; - - - if (result!=NVML_SUCCESS) { - fprintf(stderr,"%s: %d: cannot access NVML\n",__FILE__,__LINE__); - /* return random pick */ - retval=random_pick(max_gpu, th); - /* if this matches the previous value, select again */ - pthread_mutex_lock(&th->prev_mutex); - while (retval==th->prev) { - retval=random_pick(max_gpu, th); - } - - th->prev=retval; - pthread_mutex_unlock(&th->prev_mutex); - return retval; - } else { - /* iterate */ - nvmlDevice_t device; - nvmlUtilization_t nvmlUtilization; - nvmlMemory_t nvmlMemory; - unsigned int min_util=101; /* GPU utilization */ - unsigned int max_util=0; /* GPU utilization */ - unsigned long long int max_free=0; /* max free memory */ - unsigned long long int min_free=ULLONG_MAX; /* max free memory */ - int ci; - for (ci=0; ci<=max_gpu; ci++) { - result=nvmlDeviceGetHandleByIndex(ci, &device); - result=nvmlDeviceGetUtilizationRates(device, &nvmlUtilization); - result=nvmlDeviceGetMemoryInfo(device, &nvmlMemory); - if (min_util>nvmlUtilization.gpu) { - min_util=nvmlUtilization.gpu; - minid=ci; - } - if (max_utilnvmlMemory.free) { - min_free=nvmlMemory.free; - } - } - result = nvmlShutdown(); - /* give priority for selection a GPU with max free memory, - if there is a tie, use min utilization as second criterion */ - /* if all have 0 usage, again use random */ - if (max_free==min_free && max_util==min_util) { - retval=random_pick(max_gpu,th); - /* if this value matches previous one, select again */ - pthread_mutex_lock(&th->prev_mutex); - while(retval==th->prev) { - retval=random_pick(max_gpu,th); - } - th->prev=retval; - pthread_mutex_unlock(&th->prev_mutex); - return retval; - } else { - if (max_free==min_free) { /* all cards have equal free mem */ - retval=(int)minid; - } else { - retval=(int)maxid; - } - } - } - - /* update last pick */ - pthread_mutex_lock(&th->prev_mutex); - th->prev=retval; - pthread_mutex_unlock(&th->prev_mutex); - - return retval; -#endif -} diff --git a/src/lib/Solvers/load_balance.o b/src/lib/Solvers/load_balance.o deleted file mode 100644 index 242b896fa5b0b9ada89f20b306c71217cf7d52af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18616 zcmbtc3w)H-mA~Ii9s`j)2tuL)qXvv%W|9yv0jZh1$S5Q@Nq9)$b29moOqtB2^MF9L z_=DOX5g(z}?Mnsh{<>;yt$nV2EnQK%t$o?<7TfyRYS-G0I08Fbu)P5FZd3icujZ7H9gcvfC=E#awZ`_zaZeYw8he zIMu!Hxa%eCmM#1!HreCp(Gd@i`!@PE_@_?!27LVkeN%6A@B8znsq1T8e?Zxrj+Wi@ zcWi%Cw!gt4MECJW(O750%bg7`bT*v%t_6crgEi&w?}D<0Hv%QPZ%x+Jc1*n@6?7lC zzNQutcQ#os$palV?v_6!il-jyn2OX~;A=VFQP%c)H^9wnTb|qD@=qPZf%5K_N4v|` zobc^?%;;!|)VOZ=tQ23{@_gARj&`)Xm0I;taI^%X=D#)S>L%LE8tfK>F0ssE=9iGR5)i|9;u7&Db*!vz<4M)+0R4A*F zvR96F8~~qAmX$9@1^`|@*nvE<3@_ z`c^GS)s-#m5F)KsY5}nhRK6KpOAGm079>x=yjZw9cWSK}J?vf`x`*C_i)ZXEqW9q9 zTVU%kaPfNS;_V{#lI~zAWF->ro(=2P&P0E)90l2DealOPD3G*EZjYQj@(?5_7l%u- zI$VW_I138H(WH`g{Bj*24p%V_I1HC;9EvI+utEG^9>M(SStnXS5Pf%*0tTFI4_Xqvb_c zKL@xpTC@(k)bCMnk?Y%l$rLK;#jcd9EgB3snR8nvg^J7}P*9+>U5B`GyJ!gUa}+)z zr=a>{*q@{N;r{~lRid=E=-TT+c^Qm|qQt8Js%|oarZC&p6mJ1mIq^%#%N^)hX+hEC zH_}RWW4_~`1|Oq>VF0MD&(%OV!xbZzcvBkdZ?oFV5Tp=v|+ z9kO=Hxrz>l$+}$D6|$Z!>q=R_SgZo%dcg4@ zI5rnuF)qcJ8vDVwc^SGYIR+y&z*HR%CWWyCuNT$3gt1h1CWUdCT#ECgNF&x*s#GJ4 zyM6}x&POGUs07Oes(}T7vwXfWQ1~}v;9}Q&qu+p=6{V+zS)^w`7k(oB3L9X|&)96yx69XTLAi|J0jvJuv$M}l$@H7lBl*2GEZ5wzP2AX-`@Se=1 zo`iw4_%lFkCG9(G_}>9fW$;?|28BgdKsss27p_MYFF0^h)PbP)X)xl&9wg2(XlCJsNpZa-xiHBk7hE@> zl=uw1yft8d<|AkXlg=xxNio;)LOdRBMijTH7ki)_x4UBaehD&s0T~eVVhEXX4QFXd2@V@Tx@M($n}KZxk%j(BBxf3izY^I>E)@PsFCgczZdjGYfWeK!JR_s7 zETaxCrD;=zsH1{JK{s4UD#7j*)o=+1372vfEzT)m_oU3ZwZL}4EV$r#07teH56FwCiyB0)o`Q)vn1#oaZsFtY%pZTx-=Q+p5u_^D{CI2<8P-dku*3oo&?(FgX9_ zy@qg99c+UO;{v-1e`S`Eg&8kUi5pB)?2){%#2A=|wl1>apz-32hsahl-)N`|0PCib z&2nyw3PnRDjx0t6LR`{BbS*e3Du5eKF%&{ySZGNJxIY_C^`&Yi4RYx&V*)l;#)&Jd zG5DVJ#0oL4SIDq<3+1}jdj!Re#$bb`+2t}Q_C~&Qz#J9emn?=D;4Zi?i&LZ~4>7?) z1p@9R4iQJXaZ^w3Xy}7B?k=Yxsf~#h1m@S)N~2q-1#R3-q@h0UUPBM0PI2({k1+8) z0u9l}-8rx~u_&8NbrLG2nYcTPl_`9>zydl|CsVglm)$f(KX+H8J=w>PyW#yyV{_)1z; zA9w#okW0K)QFnUPX>o{=*E7svvUhvhZXb8?;SNhio)LC7Pt^6n4Lhj_khSAwjB zLnlFHF;fpymxJdNF6$5zkI|qZHvEvf93y}@Ol%w=UaiLTWH;n`0R}SmRZsWAA?njk zWGtGw7EQLuW(;9+m*&r>(!!(grtgSkEoz8@dt`f%to7W;B7_OZoa zI?jE%#do#Z`QyAb*Wy;TxHxTbt8Ou0oLP){4Uw~bnT5}xcQ};vRh&=G3{qPuCdBEc z({+EZgBuL>BJcd1rSI2-=H(H3t*UN8jvxn{w=Zl??()8UPdj;n9C?S>=q&j!9Vt?D z+j|LvE_7a=Fu?J5%9SS)Jl%}vITwdrbOQ2;lxN|Yg|Ah%%N9%JpYKOV_aoH4t7U&! z6Uw{nbMs7=-Q!aq+~h7F_nBp9geQ>a0RP$l9T}Y^e;EFq6#eDBgh3a2FU$TeCWp^f zDhpu;+6te=Hb{2);OFD6mFBWZ3zCykyO`P;)K*b@HvK&AAwIq^@m|*bc^>RK#A>Jw zy1Vh_{qn39x6!JECT0G0Vk?@eDCYx87E1K$3(e2UL z&S+gE98FEujYd<%7LJA@sbNcaQ;E2j2=7ohnaD!m`OIc4I5Cm%hEl`9=6Y{37K>~T zC%w&{dQVdmYY_U+95Aaya(LJp@xTi*A(Fc$tORr+1^|a+(1k};aGARk$+5T<9JcYb z$HvEFQDB58$LNmn2=w5&k@%GScrZB@3PvIs&7e^Y!6O>Q8y@)@jLOlJs}ZXvm&aLg z)i&XP=cjpli}n^dYAXNj_rG_U&XjAfv+}_~=Ny;k+gGnzJ=k6Ed}NPfiQ$^;a<1QW zaN|DzX8-8Ms}DIFr}sIpzQ?tC)VXZh>D{#2IeV`&c9pZju(GA0-MdA-hQ*Icc7;OmNPo)mQ^a604JpFV}`b9h#9gdBg6XDSIY;iLZ z11RGR`atf4U?a%HSZ9sL;=44tVJnf0$982o9Jt2x_w@~cqa1T9IxNhQaKwtnlo8Z# zjzof^3A^M#kH5#?@As{RS$3pDjKY}AQJWVq)wq?!^}v)3Sjkj83fOo!YRb9O!r@?t zwIdwD)rJK$@%_9Riwt8@BrNcOTSyDJG0aFfkpyOx^hd)D=llRWH*E`Vzuq^ zM~AIR9Hj`D$zWo;IR?_0R-{ZWjhUdhi#0t1?sm64M%;Dd$yC%@J!(a*csK-PFdiCP z4TnnIiY9m6sJpIrnHX0Q$1uY1b}KHjl4N8e4sjf@M^HoPv2n{gmVljY zrFMD~aKys_R~G^oyoq>79`YUGcrauSZr4Chdp}Ms6J=Z)6-La`>}3x_`pB^1-hz~} z1$m(7>Ym<>JtCEmE5dTHnpQj-jL0O!P{Hpdm{yoorZ6XB35b$RgoGX_mEYlEyA|Ks zK&P)`Gn`48O(l;wdBm8ov1w1)0&YV7;u@&Uj+D708H7WScZ$pb+r9!0+r-waAAr4Y zuI=sXPkRn)Bu|%&5Nso*-ya>J1A#WIJZx|m%QerEusInBCatW4qrEo}7+Bxm*#UFy z>J9X+v-{-l*#IX>hv^Hf8CVC$GM*9;D0PV9B4uq|I1pAe(E&i`iWNxILV+AyQG92bn!1 z!-p4Fbq#|fJ&}q)#wG9?EY`HQuXNYKnR%%?GU*0R{ssztV|PFpljX+w=M=l9;nt@d zF#chX-{v7E--biWKLp}^Xb1em62p6!w$k$39c`smw>djY-M1FBmDb);=qs)NSdp)^ zd0%l?>9pZ^YG!G3duhF|w6?9(4Xhy2R_f|5aU9P(&o`F`cm&3yAen|xVYkF0bD>V7g9} z48o65SojTwqH=jZ3i&KDRi9b|?}HeO8zw;1qP2X(LYcZJdUpUCfHy z;!OW8nmy)JUzlZq<&x4iA8|Nyt`_QpGq<7m-leckHNou{CKYC!#{l%{F`UNe|i@Dw`aj$ISXC@p~>eDerBEzuRIHW-dS+{ zN!hcuy-8?~IoS^(@;8!)!J+lF6g z!w=YSJUy8I8#eqR8~(30+&-_L+wco*{C~3Hc0FfoxIGTl7%(W<*!3*2;dVba+whA( zhW)o}xZTeW*>Jo512){Q=N=nwUtfMM#bNBTUA`Yj9M3k!`8fyI(SB=*_jXti$J726 zvd1Sm<3FH_t^6F<#y^)XahStcFs!6P(wE9w>4y)Q(xu*pquo=q9(X=uVY>$jZ&z>{ zXFt|!`0HeRqlWLL0PwHGtUpFLKUFgR6VemW^sFR*5*l7cm-I;uCsORwa1Ys?*6>*r zPyUgZ{rM8<|3{5~J>d^&INv{gTf;v?{vX$HzLWTchF?$o&uVx-`Tr*k|2SQ`Piy!* z${)NQVc~ggqxn{7IIrIV4gVI=OEvr&(%-D%SCBuQ8a_n+2Q>Vr?<4fm1VFKYNw6-L4FV7qsc-Qybn0Klyo)hX0lLTQz(St;-q>f0XDx4X-9WriOo& z@?pD%|B&>5Q^PN#d45O3?!g|BB8a_?ycv!<( z&lfbD^?Xyqe@*(2X*i#=Khp4T(fU5A;rCMfpVDyt7V`xSKT3LD)$k7T=WPu?Lh&pi zIbL60?`jR-P5vy^@V6*$uh4M*iwd8HyGhSx4d=Wc(eM*Qe@MfZk)9hg{IfLADGh&( z;&7*i-%T{{GmgU(G_Nme{LPdnyl%|TkKnuxj2|IAk865PlmAa>I6s0vso^iuyf_}L z|F6XNjK=>w?dKOX{2LU9KWg|e;jEAKAEx=9()j;}_}|p?$|67TFxyC<}CU>O`M_qRj|2hrtp*RO@IL_-qTCYJHj(Yxs z_=7ea`JWcUZ%>QeN%UaQ={% zpL;kyoKNacwbkCmv4_d-;j`%ZI^nG6SH%B44L^tI;~L&g^b^$by!fl!r!@R_n%5sR z{B9Xd_=72WFl0Rs67JS;e$H&saK0XOX*hp(xlP0QuNrn}_`T%kehug6uFnz9aobMw zc~HY|BmCjB=y_b@=lK3q!`~!5Cp3IH(N9y$apO;m|ES^o{PvcH^Ow3+^sf%X(I6{68W74h?q_y-vfgBsxGX>*vpYO%3NSnWGv$Ncr<&4X;LpQ0~z1 zTEf4e;k@qZ|4UHk4{jBH5BXyoKW!nY|7jbJDaC(Ar*4SO$>enuZN diff --git a/src/lib/Solvers/manifold_average.c b/src/lib/Solvers/manifold_average.c deleted file mode 100644 index da27c5e..0000000 --- a/src/lib/Solvers/manifold_average.c +++ /dev/null @@ -1,627 +0,0 @@ -/* - * - Copyright (C) 2014 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - -#include "Solvers.h" -#include - -//#define DEBUG -typedef struct thread_data_manavg_ { - double *Y; - int startM; - int endM; - int Niter; - int N; - int M; - int Nf; -} thread_data_manavg_t; - -/* worker thread function for manifold average+projection */ -static void* -manifold_average_threadfn(void *data) { - thread_data_manavg_t *t=(thread_data_manavg_t*)data; - int ci,cj,iter; - double *Yl; - complex double *J3,*Jp; - /* local storage 2Nx2 x Nf complex values */ - if ((Yl=(double*)malloc((size_t)t->N*8*t->Nf*sizeof(double)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((J3=(complex double*)malloc((size_t)t->N*4*sizeof(complex double)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((Jp=(complex double*)malloc((size_t)t->N*4*sizeof(complex double)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } -#ifdef DEBUG - complex double *Jerr; - if ((Jerr=(complex double*)malloc((size_t)t->N*4*sizeof(complex double)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } -#endif - - complex double *Yc=(complex double*)Yl; - complex double a=1.0/(double)t->Nf+0.0*_Complex_I; - - /* work for SVD */ - complex double *WORK=0; - complex double w[1]; - double RWORK[32]; /* size > 5*max_matrix_dimension */ - complex double JTJ[4],U[4],VT[4]; - double S[2]; - - int status=my_zgesvd('A','A',2,2,JTJ,2,S,U,2,VT,2,w,-1,RWORK); - if (status!=0) { - fprintf(stderr,"%s: %d: LAPACK error %d\n",__FILE__,__LINE__,status); - exit(1); - } - int lwork=(int)w[0]; - if ((WORK=(complex double*)malloc((size_t)(int)lwork*sizeof(complex double)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - for (ci=t->startM; ci<=t->endM; ci++) { - /* copy to local storage */ - for (cj=0; cjNf; cj++) { - my_dcopy(t->N, &t->Y[cj*8*t->N*t->M+ci*8*t->N], 8, &Yl[cj*8*t->N], 4); - my_dcopy(t->N, &t->Y[cj*8*t->N*t->M+ci*8*t->N+1], 8, &Yl[cj*8*t->N+1], 4); - my_dcopy(t->N, &t->Y[cj*8*t->N*t->M+ci*8*t->N+4], 8, &Yl[cj*8*t->N+2], 4); - my_dcopy(t->N, &t->Y[cj*8*t->N*t->M+ci*8*t->N+5], 8, &Yl[cj*8*t->N+3], 4); - my_dcopy(t->N, &t->Y[cj*8*t->N*t->M+ci*8*t->N+2], 8, &Yl[cj*8*t->N+4*t->N], 4); - my_dcopy(t->N, &t->Y[cj*8*t->N*t->M+ci*8*t->N+3], 8, &Yl[cj*8*t->N+4*t->N+1], 4); - my_dcopy(t->N, &t->Y[cj*8*t->N*t->M+ci*8*t->N+6], 8, &Yl[cj*8*t->N+4*t->N+2], 4); - my_dcopy(t->N, &t->Y[cj*8*t->N*t->M+ci*8*t->N+7], 8, &Yl[cj*8*t->N+4*t->N+3], 4); - - } - /* first averaging, select random block in [0,Nf-1] to project to */ - int cr=rand()%(t->Nf); /* remainder always in [0,Nf-1] */ - /* J3 <= cr th block */ - my_ccopy(t->N*4,&Yc[cr*t->N*4],1,J3,1); - /* project the remainder */ - for (cj=0; cjN,J3,&Yc[cj*t->N*4]); - } - for (cj=cr+1; cjNf; cj++) { - project_procrustes_block(t->N,J3,&Yc[cj*t->N*4]); - } - - - /* now each 2, 2N complex vales is one J block */ - /* average values and project to common average */ - for (iter=0; iterNiter; iter++) { - /* J3 <= 1st block */ - my_ccopy(t->N*4,Yc,1,J3,1); - /* add the remainder */ - for (cj=1; cjNf; cj++) { - my_caxpy(t->N*4,&Yc[cj*t->N*4],1.0+_Complex_I*0.0,J3); - } - my_cscal(t->N*4,a,J3); - /* now find unitary matrix using Procrustes problem */ - for (cj=0; cjNf; cj++) { - /* find product JTJ = J^H J3 */ - my_zgemm('C','N',2,2,2*t->N,1.0+_Complex_I*0.0,&Yc[cj*t->N*4],2*t->N,J3,2*t->N,0.0+_Complex_I*0.0,JTJ,2); - status=my_zgesvd('A','A',2,2,JTJ,2,S,U,2,VT,2,WORK,lwork,RWORK); - //printf("%d %d %lf %lf\n",ci,cj,S[0],S[1]); - /* find JTJ= U V^H */ - my_zgemm('N','N',2,2,2,1.0+_Complex_I*0.0,U,2,VT,2,0.0+_Complex_I*0.0,JTJ,2); - /* find J*(JTJ) : projected matrix */ - my_zgemm('N','N',2*t->N,2,2,1.0+_Complex_I*0.0,&Yc[cj*t->N*4],2*t->N,JTJ,2,0.0+_Complex_I*0.0,Jp,2*t->N); - /* copy back */ - my_ccopy(t->N*4,Jp,1,&Yc[cj*t->N*4],1); -#ifdef DEBUG - /* calculate error between projected value and global mean */ - my_ccopy(t->N*4,J3,1,Jerr,1); - my_caxpy(t->N*4,&Yc[cj*t->N*4],-1.0+_Complex_I*0.0,Jerr); - printf("Error freq=%d dir=%d iter=%d %lf\n",cj,ci,iter,my_cnrm2(t->N*4,Jerr)); -#endif - } - } - - /* now get a fresh copy, because we should modify Y only by - one unitary matrix */ - my_ccopy(t->N*4,Yc,1,J3,1); - /* add the remainder */ - for (cj=1; cjNf; cj++) { - my_caxpy(t->N*4,&Yc[cj*t->N*4],1.0+_Complex_I*0.0,J3); - } - my_cscal(t->N*4,a,J3); - for (cj=0; cjNf; cj++) { - my_dcopy(t->N, &t->Y[cj*8*t->N*t->M+ci*8*t->N], 8, &Yl[cj*8*t->N], 4); - my_dcopy(t->N, &t->Y[cj*8*t->N*t->M+ci*8*t->N+1], 8, &Yl[cj*8*t->N+1], 4); - my_dcopy(t->N, &t->Y[cj*8*t->N*t->M+ci*8*t->N+4], 8, &Yl[cj*8*t->N+2], 4); - my_dcopy(t->N, &t->Y[cj*8*t->N*t->M+ci*8*t->N+5], 8, &Yl[cj*8*t->N+3], 4); - my_dcopy(t->N, &t->Y[cj*8*t->N*t->M+ci*8*t->N+2], 8, &Yl[cj*8*t->N+4*t->N], 4); - my_dcopy(t->N, &t->Y[cj*8*t->N*t->M+ci*8*t->N+3], 8, &Yl[cj*8*t->N+4*t->N+1], 4); - my_dcopy(t->N, &t->Y[cj*8*t->N*t->M+ci*8*t->N+6], 8, &Yl[cj*8*t->N+4*t->N+2], 4); - my_dcopy(t->N, &t->Y[cj*8*t->N*t->M+ci*8*t->N+7], 8, &Yl[cj*8*t->N+4*t->N+3], 4); - } - - for (cj=0; cjNf; cj++) { - /* find product JTJ = J^H J3 */ - my_zgemm('C','N',2,2,2*t->N,1.0+_Complex_I*0.0,&Yc[cj*t->N*4],2*t->N,J3,2*t->N,0.0+_Complex_I*0.0,JTJ,2); - - status=my_zgesvd('A','A',2,2,JTJ,2,S,U,2,VT,2,WORK,lwork,RWORK); - /* find JTJ= U V^H */ - my_zgemm('N','N',2,2,2,1.0+_Complex_I*0.0,U,2,VT,2,0.0+_Complex_I*0.0,JTJ,2); - /* find J*(JTJ) : projected matrix */ - my_zgemm('N','N',2*t->N,2,2,1.0+_Complex_I*0.0,&Yc[cj*t->N*4],2*t->N,JTJ,2,0.0+_Complex_I*0.0,Jp,2*t->N); - /* copy back */ - my_ccopy(t->N*4,Jp,1,&Yc[cj*t->N*4],1); - } - - /* copy back from local storage */ - for (cj=0; cjNf; cj++) { - my_dcopy(t->N, &Yl[cj*8*t->N], 4, &t->Y[cj*8*t->N*t->M+ci*8*t->N], 8); - my_dcopy(t->N, &Yl[cj*8*t->N+1], 4, &t->Y[cj*8*t->N*t->M+ci*8*t->N+1], 8); - my_dcopy(t->N, &Yl[cj*8*t->N+2], 4, &t->Y[cj*8*t->N*t->M+ci*8*t->N+4], 8); - my_dcopy(t->N, &Yl[cj*8*t->N+3], 4, &t->Y[cj*8*t->N*t->M+ci*8*t->N+5], 8); - my_dcopy(t->N, &Yl[cj*8*t->N+4*t->N], 4, &t->Y[cj*8*t->N*t->M+ci*8*t->N+2], 8); - my_dcopy(t->N, &Yl[cj*8*t->N+4*t->N+1], 4, &t->Y[cj*8*t->N*t->M+ci*8*t->N+3], 8); - my_dcopy(t->N, &Yl[cj*8*t->N+4*t->N+2], 4, &t->Y[cj*8*t->N*t->M+ci*8*t->N+6], 8); - my_dcopy(t->N, &Yl[cj*8*t->N+4*t->N+3], 4, &t->Y[cj*8*t->N*t->M+ci*8*t->N+7], 8); - - } - } - -#ifdef DEBUG - free(Jerr); -#endif - free(Yl); - free(J3); - free(Jp); - free(WORK); - return NULL; -} - -int -calculate_manifold_average(int N,int M,int Nf,double *Y,int Niter,int Nt) { - /* Y : each 2Nx2xM blocks belong to one freq, - select one 2Nx2 from this, reorder to J format : Nf blocks - and average */ - pthread_attr_t attr; - pthread_t *th_array; - thread_data_manavg_t *threaddata; - - int ci,Nthb0,Nthb,nth,nth1; - /* clusters per thread */ - Nthb0=(M+Nt-1)/Nt; - - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((threaddata=(thread_data_manavg_t*)malloc((size_t)Nt*sizeof(thread_data_manavg_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - ci=0; - for (nth=0; nth 5*max_matrix_dimension */ - complex double JTJ[4],U[4],VT[4]; - double S[2]; - - int status=my_zgesvd('A','A',2,2,JTJ,2,S,U,2,VT,2,w,-1,RWORK); - if (status!=0) { - fprintf(stderr,"%s: %d: LAPACK error %d\n",__FILE__,__LINE__,status); - exit(1); - } - int lwork=(int)w[0]; - if ((WORK=(complex double*)malloc((size_t)(int)lwork*sizeof(complex double)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - /* find product JTJ = Y^H X */ - my_zgemm('C','N',2,2,2*N,1.0+_Complex_I*0.0,Y,2*N,X,2*N,0.0+_Complex_I*0.0,JTJ,2); - /* JTJ = U S V^H */ - status=my_zgesvd('A','A',2,2,JTJ,2,S,U,2,VT,2,WORK,lwork,RWORK); - /* find JTJ= U V^H */ - my_zgemm('N','N',2,2,2,1.0+_Complex_I*0.0,U,2,VT,2,0.0+_Complex_I*0.0,JTJ,2); - /* find Y*(JTJ) : projected matrix -> store in X */ - my_zgemm('N','N',2*N,2,2,1.0+_Complex_I*0.0,Y,2*N,JTJ,2,0.0+_Complex_I*0.0,X,2*N); - - my_dcopy(N, &Jx[0], 4, &J1[0], 8); - my_dcopy(N, &Jx[1], 4, &J1[0+1], 8); - my_dcopy(N, &Jx[2], 4, &J1[0+4], 8); - my_dcopy(N, &Jx[3], 4, &J1[0+5], 8); - my_dcopy(N, &Jx[4*N], 4, &J1[0+2], 8); - my_dcopy(N, &Jx[4*N+1], 4, &J1[0+3], 8); - my_dcopy(N, &Jx[4*N+2], 4, &J1[0+6], 8); - my_dcopy(N, &Jx[4*N+3], 4, &J1[0+7], 8); - - - free(WORK); - free(X); - free(Y); - return 0; -} - - - -int -project_procrustes_block(int N,complex double *X,complex double *Y) { - /* min ||X - Y U || find U */ - complex double *Jlocal; - /* local storage */ - if ((Jlocal=(complex double*)malloc((size_t)N*4*sizeof(complex double)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - /* work for SVD */ - complex double *WORK=0; - complex double w[1]; - double RWORK[32]; /* size > 5*max_matrix_dimension */ - complex double JTJ[4],U[4],VT[4]; - double S[2]; - - int status=my_zgesvd('A','A',2,2,JTJ,2,S,U,2,VT,2,w,-1,RWORK); - if (status!=0) { - fprintf(stderr,"%s: %d: LAPACK error %d\n",__FILE__,__LINE__,status); - exit(1); - } - int lwork=(int)w[0]; - if ((WORK=(complex double*)malloc((size_t)(int)lwork*sizeof(complex double)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - /* find product JTJ = Y^H X */ - my_zgemm('C','N',2,2,2*N,1.0+_Complex_I*0.0,Y,2*N,X,2*N,0.0+_Complex_I*0.0,JTJ,2); - /* JTJ = U S V^H */ - status=my_zgesvd('A','A',2,2,JTJ,2,S,U,2,VT,2,WORK,lwork,RWORK); - /* find JTJ= U V^H */ - my_zgemm('N','N',2,2,2,1.0+_Complex_I*0.0,U,2,VT,2,0.0+_Complex_I*0.0,JTJ,2); - /* find Y*(JTJ) : projected matrix -> store in Jlocal */ - my_zgemm('N','N',2*N,2,2,1.0+_Complex_I*0.0,Y,2*N,JTJ,2,0.0+_Complex_I*0.0,Jlocal,2*N); - - /* copy Jlocal -> Y */ - my_dcopy(8*N, (double*)Jlocal, 1, (double*)Y, 1); - - free(WORK); - free(Jlocal); - return 0; -} - - - - -//#define DEBUG -/* Extract only the phase of diagonal entries from solutions - p: 8Nx1 solutions, orders as [(real,imag)vec(J1),(real,imag)vec(J2),...] - pout: 8Nx1 phases (exp(j*phase)) of solutions, after joint diagonalization of p - N: no. of 2x2 Jones matrices in p, having common unitary ambiguity - niter: no of iterations for Jacobi rotation */ -int -extract_phases(double *p, double *pout, int N, int niter) { - - /* local storage */ - complex double *J,*Jcopy; - /* local storage, change ordering of solutions [J_1^T,J_2^T,...]^T */ - if ((J=(complex double*)malloc((size_t)N*4*sizeof(complex double)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((Jcopy=(complex double*)malloc((size_t)N*4*sizeof(complex double)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - double *Jx=(double *)J; - /* copy to get correct format */ - my_dcopy(N, &p[0], 8, &Jx[0], 4); - my_dcopy(N, &p[0+1], 8, &Jx[1], 4); - my_dcopy(N, &p[0+4], 8, &Jx[2], 4); - my_dcopy(N, &p[0+5], 8, &Jx[3], 4); - my_dcopy(N, &p[0+2], 8, &Jx[4*N], 4); - my_dcopy(N, &p[0+3], 8, &Jx[4*N+1], 4); - my_dcopy(N, &p[0+6], 8, &Jx[4*N+2], 4); - my_dcopy(N, &p[0+7], 8, &Jx[4*N+3], 4); - - complex double h[3],Hc[9]; - double H[9]; - double W[3],Z[3]; - double w[1],*WORK; - int IWORK[15],IFAIL[3],info; - int ni,ci; - complex double c,s,G[4]; - -#ifdef DEBUG - printf("J=[\n"); - for (ci=0; ci=0.0) { - c=sqrt(0.5+Z[0]*0.5)+_Complex_I*0.0; - s=0.5*(Z[1]-_Complex_I*Z[2])/c; - } else { - /* flip sign of eigenvector */ - c=sqrt(0.5-Z[0]*0.5)+_Complex_I*0.0; - s=0.5*(-Z[1]+_Complex_I*Z[2])/c; - } - /* form Givens rotation matrix */ - G[0]=c; - G[1]=-s; - G[2]=conj(s); - G[3]=conj(c); -#ifdef DEBUG - printf("G=[\n"); - printf("%lf+j*(%lf), %lf+j*(%lf)\n",creal(G[0]),cimag(G[0]),creal(G[2]),cimag(G[2])); - printf("%lf+j*(%lf), %lf+j*(%lf)\n",creal(G[1]),cimag(G[1]),creal(G[3]),cimag(G[3])); - printf("];\n"); -#endif - /* rotate J <= J * G^H: Jcopy = 1 x J x G^H + 0 x Jcopy */ - my_zgemm('N','C',2*N,2,2,1.0+_Complex_I*0.0,J,2*N,G,2,0.0+_Complex_I*0.0,Jcopy,2*N); - memcpy(J,Jcopy,(size_t)4*N*sizeof(complex double)); -#ifdef DEBUG - printf("JGH=[\n"); - for (ci=0; ci=0.0) { - c=sqrt(0.5+Z[0]*0.5)+_Complex_I*0.0; - s=0.5*(Z[1]-_Complex_I*Z[2])/c; - } else { - /* flip sign of eigenvector */ - c=sqrt(0.5-Z[0]*0.5)+_Complex_I*0.0; - s=0.5*(-Z[1]+_Complex_I*Z[2])/c; - } - /* form Givens rotation matrix */ - G[0]=c; - G[1]=-s; - G[2]=conj(s); - G[3]=conj(c); -#ifdef DEBUG - printf("G=[\n"); - printf("%lf+j*(%lf), %lf+j*(%lf)\n",creal(G[0]),cimag(G[0]),creal(G[2]),cimag(G[2])); - printf("%lf+j*(%lf), %lf+j*(%lf)\n",creal(G[1]),cimag(G[1]),creal(G[3]),cimag(G[3])); - printf("];\n"); -#endif - /* rotate J <= J * G^H: Jcopy = 1 x J x G^H + 0 x Jcopy */ - my_zgemm('N','C',2*N,2,2,1.0+_Complex_I*0.0,J,2*N,G,2,0.0+_Complex_I*0.0,Jcopy,2*N); - /* before copying updated result, find residual norm */ - /* J = -Jcopy + J */ - my_caxpy(4*N,Jcopy,-1.0+_Complex_I*0.0,J); -#ifdef DEBUG - printf("Iter %d residual=%lf\n",ni,my_cnrm2(4*N,J)); -#endif - memcpy(J,Jcopy,(size_t)4*N*sizeof(complex double)); -#ifdef DEBUG - printf("JGH=[\n"); - for (ci=0; cihFu|n0I`%Ug{r7UgGB+ALTOQ`KeFF*-@P+u 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 - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - -#include "cuda.h" -#include -#include -#include -#include "Solvers.h" - -/* enable this for checking for kernel failure */ -//#define CUDA_DBG - -/* matrix multiplications */ -/* C=A*B */ -__device__ void -amb(const cuFloatComplex *__restrict__ a, const cuFloatComplex *__restrict__ b, cuFloatComplex *__restrict__ c) { - c[0]=cuCaddf(cuCmulf(a[0],b[0]),cuCmulf(a[1],b[2])); - c[1]=cuCaddf(cuCmulf(a[0],b[1]),cuCmulf(a[1],b[3])); - c[2]=cuCaddf(cuCmulf(a[2],b[0]),cuCmulf(a[3],b[2])); - c[3]=cuCaddf(cuCmulf(a[2],b[1]),cuCmulf(a[3],b[3])); -} -/* C=A*B^H */ -__device__ void -ambt(const cuFloatComplex *__restrict__ a, const cuFloatComplex *__restrict__ b, cuFloatComplex *__restrict__ c) { - c[0]=cuCaddf(cuCmulf(a[0],cuConjf(b[0])),cuCmulf(a[1],cuConjf(b[1]))); - c[1]=cuCaddf(cuCmulf(a[0],cuConjf(b[2])),cuCmulf(a[1],cuConjf(b[3]))); - c[2]=cuCaddf(cuCmulf(a[2],cuConjf(b[0])),cuCmulf(a[3],cuConjf(b[1]))); - c[3]=cuCaddf(cuCmulf(a[2],cuConjf(b[2])),cuCmulf(a[3],cuConjf(b[3]))); -} - -/* C=A^H * B */ -__device__ void -atmb(const cuFloatComplex *__restrict__ a, const cuFloatComplex *__restrict__ b, cuFloatComplex *__restrict__ c) { - c[0]=cuCaddf(cuCmulf(cuConjf(a[0]),b[0]),cuCmulf(cuConjf(a[2]),b[2])); - c[1]=cuCaddf(cuCmulf(cuConjf(a[0]),b[1]),cuCmulf(cuConjf(a[2]),b[3])); - c[2]=cuCaddf(cuCmulf(cuConjf(a[1]),b[0]),cuCmulf(cuConjf(a[3]),b[2])); - c[3]=cuCaddf(cuCmulf(cuConjf(a[1]),b[1]),cuCmulf(cuConjf(a[3]),b[3])); -} - - -__global__ void -kernel_fns_fhess(int N, int Nbase, const cuFloatComplex *__restrict__ x, const cuFloatComplex *__restrict__ eta, cuFloatComplex *__restrict__ hess0, const float *__restrict__ y, const float *__restrict__ coh, const short *__restrict__ bbh) { - - /* eta0: each block will store result in its own block */ - /* global thread index : equal to the baseline */ - unsigned int n = threadIdx.x + blockDim.x*blockIdx.x; - int bid=blockIdx.x; - int tid=threadIdx.x; - /* 4x2xblockDim.x cuFloatComplex values and 2xblockDim.x int values */ - extern __shared__ cuFloatComplex hs[]; - int *stm= (int*)&hs[8*blockDim.x]; - stm[2*tid]=-1; - stm[2*tid+1]=-1; - /* x,eta: 2Nx2 matrix */ - if(n=0 - */ - if (sta1>=0 && sta2>=0) { - stm[2*tid]=sta1; - stm[2*tid+1]=sta2; - - cuFloatComplex G1[4]; - cuFloatComplex G2[4]; - cuFloatComplex E1[4]; - cuFloatComplex E2[4]; - /* J1 */ - G1[0]=x[sta1*2]; - G1[1]=x[sta1*2+N*2]; - G1[2]=x[sta1*2+1]; - G1[3]=x[sta1*2+N*2+1]; - /* conjugate this to get J2^H */ - G2[0]=x[sta2*2]; - G2[1]=x[sta2*2+N*2]; - G2[2]=x[sta2*2+1]; - G2[3]=x[sta2*2+N*2+1]; - E1[0]=eta[2*sta1]; - E1[1]=eta[2*sta1+2*N]; - E1[2]=eta[2*sta1+1]; - E1[3]=eta[2*sta1+2*N+1]; - E2[0]=eta[2*sta2]; - E2[1]=eta[2*sta2+2*N]; - E2[2]=eta[2*sta2+1]; - E2[3]=eta[2*sta2+2*N+1]; - - - cuFloatComplex C[4]; - C[0].x=coh[8*n]; - C[0].y=coh[8*n+1]; - C[1].x=coh[8*n+2]; - C[1].y=coh[8*n+3]; - C[2].x=coh[8*n+4]; - C[2].y=coh[8*n+5]; - C[3].x=coh[8*n+6]; - C[3].y=coh[8*n+7]; - - /* G1*C*G2' */ - cuFloatComplex T1[4]; - cuFloatComplex T2[4]; - /* T=G1*C */ - amb(G1,C,T1); - ambt(T1,G2,T2); - - /* res=V(2*ck-1:2*ck,:)-x(2*p-1:2*p,:)*C*x(2*q-1:2*q,:)'; */ - /* V->U */ - cuFloatComplex res[4],res1[4]; - res[0]=cuCsubf(make_cuFloatComplex(y[8*n],y[8*n+1]),T2[0]); - res[1]=cuCsubf(make_cuFloatComplex(y[8*n+2],y[8*n+3]),T2[1]); - res[2]=cuCsubf(make_cuFloatComplex(y[8*n+4],y[8*n+5]),T2[2]); - res[3]=cuCsubf(make_cuFloatComplex(y[8*n+6],y[8*n+7]),T2[3]); - - /* - res1=x(2*p-1:2*p,:)*C*eta(2*q-1:2*q,:)'+eta(2*p-1:2*p,:)*C*x(2*q-1:2*q,:)'; - */ - /* G1*C*E2' */ - amb(G1,C,T1); - ambt(T1,E2,T2); - res1[0]=T2[0]; - res1[1]=T2[1]; - res1[2]=T2[2]; - res1[3]=T2[3]; - /* E1*C*G2' */ - amb(E1,C,T1); - ambt(T1,G2,T2); - res1[0]=cuCaddf(res1[0],T2[0]); - res1[1]=cuCaddf(res1[1],T2[1]); - res1[2]=cuCaddf(res1[2],T2[2]); - res1[3]=cuCaddf(res1[3],T2[3]); - - - /* - hess(2*p-1:2*p,:)=hess(2*p-1:2*p,:)+(res*eta(2*q-1:2*q,:)-res1*x(2*q-1:2*q,:))*C'; - */ - - /* (res*E2-res1*G2)*C' */ - amb(res,E2,T1); - amb(res1,G2,T2); - T1[0]=cuCsubf(T1[0],T2[0]); - T1[1]=cuCsubf(T1[1],T2[1]); - T1[2]=cuCsubf(T1[2],T2[2]); - T1[3]=cuCsubf(T1[3],T2[3]); - ambt(T1,C,T2); - - hs[8*tid]=T2[0]; - hs[8*tid+1]=T2[1]; - hs[8*tid+2]=T2[2]; - hs[8*tid+3]=T2[3]; - - - /* - hess(2*q-1:2*q,:)=hess(2*q-1:2*q,:)+(res'*eta(2*p-1:2*p,:)-res1'*x(2*p-1:2*p,:))*C; - */ - - /* (res'*E1-res1'*G1)*C */ - atmb(res,E1,T1); - atmb(res1,G1,T2); - T1[0]=cuCsubf(T1[0],T2[0]); - T1[1]=cuCsubf(T1[1],T2[1]); - T1[2]=cuCsubf(T1[2],T2[2]); - T1[3]=cuCsubf(T1[3],T2[3]); - amb(T1,C,T2); - - - hs[8*tid+4]=T2[0]; - hs[8*tid+5]=T2[1]; - hs[8*tid+6]=T2[2]; - hs[8*tid+7]=T2[3]; - - } - } - - __syncthreads(); - /* copy back to global memory */ - if (tid==0) { - for(int ci=0; ci=0 && sta2>=0) { - hess0[2*sta1+bid*4*N]=cuCaddf(hess0[2*sta1+bid*4*N],hs[8*ci]); - hess0[2*sta1+2*N+bid*4*N]=cuCaddf(hess0[2*sta1+2*N+bid*4*N],hs[8*ci+1]); - hess0[2*sta1+1+bid*4*N]=cuCaddf(hess0[2*sta1+1+bid*4*N],hs[8*ci+2]); - hess0[2*sta1+2*N+1+bid*4*N]=cuCaddf(hess0[2*sta1+2*N+1+bid*4*N],hs[8*ci+3]); - hess0[2*sta2+bid*4*N]=cuCaddf(hess0[2*sta2+bid*4*N],hs[8*ci+4]); - hess0[2*sta2+2*N+bid*4*N]=cuCaddf(hess0[2*sta2+2*N+bid*4*N],hs[8*ci+5]); - hess0[2*sta2+1+bid*4*N]=cuCaddf(hess0[2*sta2+1+bid*4*N],hs[8*ci+6]); - hess0[2*sta2+2*N+1+bid*4*N]=cuCaddf(hess0[2*sta2+2*N+1+bid*4*N],hs[8*ci+7]); - } - } - } - __syncthreads(); -} - -__global__ void -kernel_fns_fhess_robust1(int N, int Nbase, const cuFloatComplex *__restrict__ x, const cuFloatComplex *__restrict__ eta, cuFloatComplex *__restrict__ hess0, const float *__restrict__ y, const float *__restrict__ coh, const short *__restrict__ bbh, const float *__restrict__ wtd) { - - /* eta0: each block will store result in its own block */ - /* global thread index : equal to the baseline */ - unsigned int n = threadIdx.x + blockDim.x*blockIdx.x; - int bid=blockIdx.x; - int tid=threadIdx.x; - /* 4x2xblockDim.x cuFloatComplex values and 2xblockDim.x int values */ - extern __shared__ cuFloatComplex hs[]; - int *stm= (int*)&hs[8*blockDim.x]; - stm[2*tid]=-1; - stm[2*tid+1]=-1; - /* x,eta: 2Nx2 matrix */ - if(n=0 - */ - if (sta1>=0 && sta2>=0) { - stm[2*tid]=sta1; - stm[2*tid+1]=sta2; - - cuFloatComplex G1[4]; - cuFloatComplex G2[4]; - cuFloatComplex E1[4]; - cuFloatComplex E2[4]; - /* J1 */ - G1[0]=x[sta1*2]; - G1[1]=x[sta1*2+N*2]; - G1[2]=x[sta1*2+1]; - G1[3]=x[sta1*2+N*2+1]; - /* conjugate this to get J2^H */ - G2[0]=x[sta2*2]; - G2[1]=x[sta2*2+N*2]; - G2[2]=x[sta2*2+1]; - G2[3]=x[sta2*2+N*2+1]; - E1[0]=eta[2*sta1]; - E1[1]=eta[2*sta1+2*N]; - E1[2]=eta[2*sta1+1]; - E1[3]=eta[2*sta1+2*N+1]; - E2[0]=eta[2*sta2]; - E2[1]=eta[2*sta2+2*N]; - E2[2]=eta[2*sta2+1]; - E2[3]=eta[2*sta2+2*N+1]; - - - cuFloatComplex C[4]; - C[0].x=coh[8*n]; - C[0].y=coh[8*n+1]; - C[1].x=coh[8*n+2]; - C[1].y=coh[8*n+3]; - C[2].x=coh[8*n+4]; - C[2].y=coh[8*n+5]; - C[3].x=coh[8*n+6]; - C[3].y=coh[8*n+7]; - - /* G1*C*G2' */ - cuFloatComplex T1[4]; - cuFloatComplex T2[4]; - /* T=G1*C */ - amb(G1,C,T1); - ambt(T1,G2,T2); - - /* res=V(2*ck-1:2*ck,:)-x(2*p-1:2*p,:)*C*x(2*q-1:2*q,:)'; */ - /* V->U */ - cuFloatComplex res[4],res1[4]; - res[0]=cuCsubf(make_cuFloatComplex(y[8*n],y[8*n+1]),T2[0]); - res[1]=cuCsubf(make_cuFloatComplex(y[8*n+2],y[8*n+3]),T2[1]); - res[2]=cuCsubf(make_cuFloatComplex(y[8*n+4],y[8*n+5]),T2[2]); - res[3]=cuCsubf(make_cuFloatComplex(y[8*n+6],y[8*n+7]),T2[3]); - - /* - res1=x(2*p-1:2*p,:)*C*eta(2*q-1:2*q,:)'+eta(2*p-1:2*p,:)*C*x(2*q-1:2*q,:)'; - */ - /* G1*C*E2' */ - amb(G1,C,T1); - ambt(T1,E2,T2); - res1[0]=T2[0]; - res1[1]=T2[1]; - res1[2]=T2[2]; - res1[3]=T2[3]; - /* E1*C*G2' */ - amb(E1,C,T1); - ambt(T1,G2,T2); - res1[0]=cuCaddf(res1[0],T2[0]); - res1[1]=cuCaddf(res1[1],T2[1]); - res1[2]=cuCaddf(res1[2],T2[2]); - res1[3]=cuCaddf(res1[3],T2[3]); - - - /* - hess(2*p-1:2*p,:)=hess(2*p-1:2*p,:)+(res*eta(2*q-1:2*q,:)-res1*x(2*q-1:2*q,:))*C'; - */ - - /* (res*E2-res1*G2)*C' */ - amb(res,E2,T1); - amb(res1,G2,T2); - T1[0]=cuCsubf(T1[0],T2[0]); - T1[1]=cuCsubf(T1[1],T2[1]); - T1[2]=cuCsubf(T1[2],T2[2]); - T1[3]=cuCsubf(T1[3],T2[3]); - ambt(T1,C,T2); - - float wtdn=wtd[n]; - /* mult with weights */ - T2[0].x=wtdn*T2[0].x; - T2[0].y=wtdn*T2[0].y; - T2[1].x=wtdn*T2[1].x; - T2[1].y=wtdn*T2[1].y; - T2[2].x=wtdn*T2[2].x; - T2[2].y=wtdn*T2[2].y; - T2[3].x=wtdn*T2[3].x; - T2[3].y=wtdn*T2[3].y; - - - hs[8*tid]=T2[0]; - hs[8*tid+1]=T2[1]; - hs[8*tid+2]=T2[2]; - hs[8*tid+3]=T2[3]; - - - /* - hess(2*q-1:2*q,:)=hess(2*q-1:2*q,:)+(res'*eta(2*p-1:2*p,:)-res1'*x(2*p-1:2*p,:))*C; - */ - - /* (res'*E1-res1'*G1)*C */ - atmb(res,E1,T1); - atmb(res1,G1,T2); - T1[0]=cuCsubf(T1[0],T2[0]); - T1[1]=cuCsubf(T1[1],T2[1]); - T1[2]=cuCsubf(T1[2],T2[2]); - T1[3]=cuCsubf(T1[3],T2[3]); - amb(T1,C,T2); - - /* mult with weights */ - T2[0].x=wtdn*T2[0].x; - T2[0].y=wtdn*T2[0].y; - T2[1].x=wtdn*T2[1].x; - T2[1].y=wtdn*T2[1].y; - T2[2].x=wtdn*T2[2].x; - T2[2].y=wtdn*T2[2].y; - T2[3].x=wtdn*T2[3].x; - T2[3].y=wtdn*T2[3].y; - - - hs[8*tid+4]=T2[0]; - hs[8*tid+5]=T2[1]; - hs[8*tid+6]=T2[2]; - hs[8*tid+7]=T2[3]; - - } - } - - __syncthreads(); - /* copy back to global memory */ - if (tid==0) { - for(int ci=0; ci=0 && sta2>=0) { - hess0[2*sta1+bid*4*N]=cuCaddf(hess0[2*sta1+bid*4*N],hs[8*ci]); - hess0[2*sta1+2*N+bid*4*N]=cuCaddf(hess0[2*sta1+2*N+bid*4*N],hs[8*ci+1]); - hess0[2*sta1+1+bid*4*N]=cuCaddf(hess0[2*sta1+1+bid*4*N],hs[8*ci+2]); - hess0[2*sta1+2*N+1+bid*4*N]=cuCaddf(hess0[2*sta1+2*N+1+bid*4*N],hs[8*ci+3]); - hess0[2*sta2+bid*4*N]=cuCaddf(hess0[2*sta2+bid*4*N],hs[8*ci+4]); - hess0[2*sta2+2*N+bid*4*N]=cuCaddf(hess0[2*sta2+2*N+bid*4*N],hs[8*ci+5]); - hess0[2*sta2+1+bid*4*N]=cuCaddf(hess0[2*sta2+1+bid*4*N],hs[8*ci+6]); - hess0[2*sta2+2*N+1+bid*4*N]=cuCaddf(hess0[2*sta2+2*N+1+bid*4*N],hs[8*ci+7]); - } - } - } - __syncthreads(); -} - - -__global__ void -kernel_fns_fhess_robust(int N, int Nbase, const cuFloatComplex *__restrict__ x, const cuFloatComplex *__restrict__ eta, cuFloatComplex *__restrict__ hess0, const float *__restrict__ y, const float *__restrict__ coh, const short *__restrict__ bbh, const float *__restrict__ wtd) { - - /* hess0: each block will store result in its own block */ - int bid=blockIdx.x; - int tid=threadIdx.x; - /* baselines */ - int nbase=N*(N-1)/2; - /* blocks per timeslot */ - int Bt=(nbase+blockDim.x-1)/blockDim.x; - - /* which timeslot */ - int ntime=bid/Bt; - /* which offset */ - int noff=bid%Bt; - /* local index within one timeslot, 0...N(N-1)/2-1 */ - unsigned int m = noff*blockDim.x+threadIdx.x; - /* global thread index : less than the total baselines */ - unsigned int n = ntime*nbase+m; - - /* 4x2xblockDim.x cuFloatComplex values and 2xblockDim.x int values */ - extern __shared__ cuFloatComplex hs[]; - int *stm= (int*)&hs[8*blockDim.x]; - stm[2*tid]=-1; - stm[2*tid+1]=-1; - /* x,eta: 2Nx2 matrix */ - if(m=0 - */ - if (sta1>=0 && sta2>=0) { - stm[2*tid]=sta1; - stm[2*tid+1]=sta2; - - cuFloatComplex G1[4]; - cuFloatComplex G2[4]; - cuFloatComplex E1[4]; - cuFloatComplex E2[4]; - /* J1 */ - G1[0]=x[sta1*2]; - G1[1]=x[sta1*2+N*2]; - G1[2]=x[sta1*2+1]; - G1[3]=x[sta1*2+N*2+1]; - /* conjugate this to get J2^H */ - G2[0]=x[sta2*2]; - G2[1]=x[sta2*2+N*2]; - G2[2]=x[sta2*2+1]; - G2[3]=x[sta2*2+N*2+1]; - E1[0]=eta[2*sta1]; - E1[1]=eta[2*sta1+2*N]; - E1[2]=eta[2*sta1+1]; - E1[3]=eta[2*sta1+2*N+1]; - E2[0]=eta[2*sta2]; - E2[1]=eta[2*sta2+2*N]; - E2[2]=eta[2*sta2+1]; - E2[3]=eta[2*sta2+2*N+1]; - - - cuFloatComplex C[4]; - C[0].x=coh[8*n]; - C[0].y=coh[8*n+1]; - C[1].x=coh[8*n+2]; - C[1].y=coh[8*n+3]; - C[2].x=coh[8*n+4]; - C[2].y=coh[8*n+5]; - C[3].x=coh[8*n+6]; - C[3].y=coh[8*n+7]; - - /* G1*C*G2' */ - cuFloatComplex T1[4]; - cuFloatComplex T2[4]; - /* T=G1*C */ - amb(G1,C,T1); - ambt(T1,G2,T2); - - /* res=V(2*ck-1:2*ck,:)-x(2*p-1:2*p,:)*C*x(2*q-1:2*q,:)'; */ - /* V->U */ - cuFloatComplex res[4],res1[4]; - res[0]=cuCsubf(make_cuFloatComplex(y[8*n],y[8*n+1]),T2[0]); - res[1]=cuCsubf(make_cuFloatComplex(y[8*n+2],y[8*n+3]),T2[1]); - res[2]=cuCsubf(make_cuFloatComplex(y[8*n+4],y[8*n+5]),T2[2]); - res[3]=cuCsubf(make_cuFloatComplex(y[8*n+6],y[8*n+7]),T2[3]); - - /* - res1=x(2*p-1:2*p,:)*C*eta(2*q-1:2*q,:)'+eta(2*p-1:2*p,:)*C*x(2*q-1:2*q,:)'; - */ - /* G1*C*E2' */ - amb(G1,C,T1); - ambt(T1,E2,T2); - res1[0]=T2[0]; - res1[1]=T2[1]; - res1[2]=T2[2]; - res1[3]=T2[3]; - /* E1*C*G2' */ - amb(E1,C,T1); - ambt(T1,G2,T2); - res1[0]=cuCaddf(res1[0],T2[0]); - res1[1]=cuCaddf(res1[1],T2[1]); - res1[2]=cuCaddf(res1[2],T2[2]); - res1[3]=cuCaddf(res1[3],T2[3]); - - - /* - hess(2*p-1:2*p,:)=hess(2*p-1:2*p,:)+(res*eta(2*q-1:2*q,:)-res1*x(2*q-1:2*q,:))*C'; - */ - - /* (res*E2-res1*G2)*C' */ - amb(res,E2,T1); - amb(res1,G2,T2); - T1[0]=cuCsubf(T1[0],T2[0]); - T1[1]=cuCsubf(T1[1],T2[1]); - T1[2]=cuCsubf(T1[2],T2[2]); - T1[3]=cuCsubf(T1[3],T2[3]); - ambt(T1,C,T2); - - float wtdn=wtd[n]; - /* mult with weights */ - T2[0].x=wtdn*T2[0].x; - T2[0].y=wtdn*T2[0].y; - T2[1].x=wtdn*T2[1].x; - T2[1].y=wtdn*T2[1].y; - T2[2].x=wtdn*T2[2].x; - T2[2].y=wtdn*T2[2].y; - T2[3].x=wtdn*T2[3].x; - T2[3].y=wtdn*T2[3].y; - - - hs[8*tid]=T2[0]; - hs[8*tid+1]=T2[1]; - hs[8*tid+2]=T2[2]; - hs[8*tid+3]=T2[3]; - - - /* - hess(2*q-1:2*q,:)=hess(2*q-1:2*q,:)+(res'*eta(2*p-1:2*p,:)-res1'*x(2*p-1:2*p,:))*C; - */ - - /* (res'*E1-res1'*G1)*C */ - atmb(res,E1,T1); - atmb(res1,G1,T2); - T1[0]=cuCsubf(T1[0],T2[0]); - T1[1]=cuCsubf(T1[1],T2[1]); - T1[2]=cuCsubf(T1[2],T2[2]); - T1[3]=cuCsubf(T1[3],T2[3]); - amb(T1,C,T2); - - /* mult with weights */ - T2[0].x=wtdn*T2[0].x; - T2[0].y=wtdn*T2[0].y; - T2[1].x=wtdn*T2[1].x; - T2[1].y=wtdn*T2[1].y; - T2[2].x=wtdn*T2[2].x; - T2[2].y=wtdn*T2[2].y; - T2[3].x=wtdn*T2[3].x; - T2[3].y=wtdn*T2[3].y; - - - hs[8*tid+4]=T2[0]; - hs[8*tid+5]=T2[1]; - hs[8*tid+6]=T2[2]; - hs[8*tid+7]=T2[3]; - - } - } - __syncthreads(); - - /* copy back to global memory */ - if (tid=0 always */ - hess0[2*tid+bid*4*N]=cuCaddf(hess0[2*tid+bid*4*N],hs[8*ci]); - hess0[2*tid+2*N+bid*4*N]=cuCaddf(hess0[2*tid+2*N+bid*4*N],hs[8*ci+1]); - hess0[2*tid+1+bid*4*N]=cuCaddf(hess0[2*tid+1+bid*4*N],hs[8*ci+2]); - hess0[2*tid+2*N+1+bid*4*N]=cuCaddf(hess0[2*tid+2*N+1+bid*4*N],hs[8*ci+3]); - } - if (sta2==tid) { /* note, tid >=0 always */ - hess0[2*tid+bid*4*N]=cuCaddf(hess0[2*tid+bid*4*N],hs[8*ci+4]); - hess0[2*tid+2*N+bid*4*N]=cuCaddf(hess0[2*tid+2*N+bid*4*N],hs[8*ci+5]); - hess0[2*tid+1+bid*4*N]=cuCaddf(hess0[2*tid+1+bid*4*N],hs[8*ci+6]); - hess0[2*tid+2*N+1+bid*4*N]=cuCaddf(hess0[2*tid+2*N+1+bid*4*N],hs[8*ci+7]); - } - } - } - __syncthreads(); -} - - -__global__ void -kernel_fns_fgrad(int N, int Nbase, const cuFloatComplex *__restrict__ x, cuFloatComplex *__restrict__ eta0, const float *__restrict__ y, const float *__restrict__ coh, const short *__restrict__ bbh) { - - /* eta0: each block will store result in its own block */ - /* global thread index : equal to the baseline */ - unsigned int n = threadIdx.x + blockDim.x*blockIdx.x; - int bid=blockIdx.x; - int tid=threadIdx.x; - /* 4x2xblockDim.x cuFloatComplex values and 2xblockDim.x int values */ - extern __shared__ cuFloatComplex eta[]; - int *stm= (int*)&eta[8*blockDim.x]; - stm[2*tid]=-1; - stm[2*tid+1]=-1; - /* x,eta: 2Nx2 matrix */ - if(n=0 - */ - if (sta1>=0 && sta2>=0) { - stm[2*tid]=sta1; - stm[2*tid+1]=sta2; - - cuFloatComplex G1[4]; - cuFloatComplex G2[4]; - /* J1 */ - G1[0]=x[sta1*2]; - G1[1]=x[sta1*2+N*2]; - G1[2]=x[sta1*2+1]; - G1[3]=x[sta1*2+N*2+1]; - /* conjugate this to get J2^H */ - G2[0]=x[sta2*2]; - G2[1]=x[sta2*2+N*2]; - G2[2]=x[sta2*2+1]; - G2[3]=x[sta2*2+N*2+1]; - - cuFloatComplex C[4]; - C[0].x=coh[8*n]; - C[0].y=coh[8*n+1]; - C[1].x=coh[8*n+2]; - C[1].y=coh[8*n+3]; - C[2].x=coh[8*n+4]; - C[2].y=coh[8*n+5]; - C[3].x=coh[8*n+6]; - C[3].y=coh[8*n+7]; - - /* G1*C*G2' */ - cuFloatComplex T1[4]; - cuFloatComplex T2[4]; - /* T=G1*C */ - amb(G1,C,T1); - ambt(T1,G2,T2); - - /* res=V(2*ck-1:2*ck,:)-x(2*p-1:2*p,:)*C*x(2*q-1:2*q,:)'; */ - /* V->U */ - cuFloatComplex res[4]; - res[0]=cuCsubf(make_cuFloatComplex(y[8*n],y[8*n+1]),T2[0]); - res[1]=cuCsubf(make_cuFloatComplex(y[8*n+2],y[8*n+3]),T2[1]); - res[2]=cuCsubf(make_cuFloatComplex(y[8*n+4],y[8*n+5]),T2[2]); - res[3]=cuCsubf(make_cuFloatComplex(y[8*n+6],y[8*n+7]),T2[3]); - - /* - grad(2*p-1:2*p,:)=grad(2*p-1:2*p,:)+res*x(2*q-1:2*q,:)*C'; - grad(2*q-1:2*q,:)=grad(2*q-1:2*q,:)+res'*x(2*p-1:2*p,:)*C; - */ - /* res*G2*C' */ - amb(res,G2,T1); - ambt(T1,C,T2); - - eta[8*tid]=T2[0]; - eta[8*tid+1]=T2[1]; - eta[8*tid+2]=T2[2]; - eta[8*tid+3]=T2[3]; - - - /* res'*G1*C */ - atmb(res,G1,T1); - amb(T1,C,T2); - - eta[8*tid+4]=T2[0]; - eta[8*tid+5]=T2[1]; - eta[8*tid+6]=T2[2]; - eta[8*tid+7]=T2[3]; - - } - } - - __syncthreads(); - /* copy back to global memory */ - if (tid==0) { - for(int ci=0; ci=0 && sta2>=0) { - eta0[2*sta1+bid*4*N]=cuCaddf(eta0[2*sta1+bid*4*N],eta[8*ci]); - eta0[2*sta1+2*N+bid*4*N]=cuCaddf(eta0[2*sta1+2*N+bid*4*N],eta[8*ci+1]); - eta0[2*sta1+1+bid*4*N]=cuCaddf(eta0[2*sta1+1+bid*4*N],eta[8*ci+2]); - eta0[2*sta1+2*N+1+bid*4*N]=cuCaddf(eta0[2*sta1+2*N+1+bid*4*N],eta[8*ci+3]); - eta0[2*sta2+bid*4*N]=cuCaddf(eta0[2*sta2+bid*4*N],eta[8*ci+4]); - eta0[2*sta2+2*N+bid*4*N]=cuCaddf(eta0[2*sta2+2*N+bid*4*N],eta[8*ci+5]); - eta0[2*sta2+1+bid*4*N]=cuCaddf(eta0[2*sta2+1+bid*4*N],eta[8*ci+6]); - eta0[2*sta2+2*N+1+bid*4*N]=cuCaddf(eta0[2*sta2+2*N+1+bid*4*N],eta[8*ci+7]); - - } - } - } - __syncthreads(); -} - -__global__ void -kernel_fns_fgrad_robust(int N, int Nbase, const cuFloatComplex *__restrict__ x, cuFloatComplex *__restrict__ eta0, const float *__restrict__ y, const float *__restrict__ coh, const short *__restrict__ bbh, const float *__restrict__ wtd) { - - /* eta0: each block will store result in its own block */ - int bid=blockIdx.x; - int tid=threadIdx.x; - /* baselines */ - int nbase=N*(N-1)/2; - /* blocks per timeslot */ - int Bt=(nbase+blockDim.x-1)/blockDim.x; - - /* which timeslot */ - int ntime=bid/Bt; - /* which offset */ - int noff=bid%Bt; - /* local index within one timeslot, 0...N(N-1)/2-1 */ - unsigned int m = noff*blockDim.x+threadIdx.x; - /* global thread index : less than the total baselines */ - unsigned int n = ntime*nbase+m; - /* 4x2xblockDim.x cuFloatComplex values and 2xblockDim.x int values */ - extern __shared__ cuFloatComplex eta[]; - int *stm= (int*)&eta[8*blockDim.x]; - stm[2*tid]=-1; - stm[2*tid+1]=-1; - /* x,eta: 2Nx2 matrix */ - if(m=0 - */ - if (sta1>=0 && sta2>=0) { - stm[2*tid]=sta1; - stm[2*tid+1]=sta2; - - cuFloatComplex G1[4]; - cuFloatComplex G2[4]; - /* J1 */ - G1[0]=x[sta1*2]; - G1[1]=x[sta1*2+N*2]; - G1[2]=x[sta1*2+1]; - G1[3]=x[sta1*2+N*2+1]; - /* conjugate this to get J2^H */ - G2[0]=x[sta2*2]; - G2[1]=x[sta2*2+N*2]; - G2[2]=x[sta2*2+1]; - G2[3]=x[sta2*2+N*2+1]; - - cuFloatComplex C[4]; - C[0].x=coh[8*n]; - C[0].y=coh[8*n+1]; - C[1].x=coh[8*n+2]; - C[1].y=coh[8*n+3]; - C[2].x=coh[8*n+4]; - C[2].y=coh[8*n+5]; - C[3].x=coh[8*n+6]; - C[3].y=coh[8*n+7]; - - /* G1*C*G2' */ - cuFloatComplex T1[4]; - cuFloatComplex T2[4]; - /* T=G1*C */ - amb(G1,C,T1); - ambt(T1,G2,T2); - - /* res=V(2*ck-1:2*ck,:)-x(2*p-1:2*p,:)*C*x(2*q-1:2*q,:)'; */ - /* V->U */ - cuFloatComplex res[4]; - res[0]=cuCsubf(make_cuFloatComplex(y[8*n],y[8*n+1]),T2[0]); - res[1]=cuCsubf(make_cuFloatComplex(y[8*n+2],y[8*n+3]),T2[1]); - res[2]=cuCsubf(make_cuFloatComplex(y[8*n+4],y[8*n+5]),T2[2]); - res[3]=cuCsubf(make_cuFloatComplex(y[8*n+6],y[8*n+7]),T2[3]); - - /* - grad(2*p-1:2*p,:)=grad(2*p-1:2*p,:)+res*x(2*q-1:2*q,:)*C'; - grad(2*q-1:2*q,:)=grad(2*q-1:2*q,:)+res'*x(2*p-1:2*p,:)*C; - */ - /* res*G2*C' */ - amb(res,G2,T1); - ambt(T1,C,T2); - - float wtdn=wtd[n]; - /* mult with weights */ - T2[0].x=wtdn*T2[0].x; - T2[0].y=wtdn*T2[0].y; - T2[1].x=wtdn*T2[1].x; - T2[1].y=wtdn*T2[1].y; - T2[2].x=wtdn*T2[2].x; - T2[2].y=wtdn*T2[2].y; - T2[3].x=wtdn*T2[3].x; - T2[3].y=wtdn*T2[3].y; - - eta[8*tid]=T2[0]; - eta[8*tid+1]=T2[1]; - eta[8*tid+2]=T2[2]; - eta[8*tid+3]=T2[3]; - - - /* res'*G1*C */ - atmb(res,G1,T1); - amb(T1,C,T2); - - /* mult with weights */ - T2[0].x=wtdn*T2[0].x; - T2[0].y=wtdn*T2[0].y; - T2[1].x=wtdn*T2[1].x; - T2[1].y=wtdn*T2[1].y; - T2[2].x=wtdn*T2[2].x; - T2[2].y=wtdn*T2[2].y; - T2[3].x=wtdn*T2[3].x; - T2[3].y=wtdn*T2[3].y; - - - eta[8*tid+4]=T2[0]; - eta[8*tid+5]=T2[1]; - eta[8*tid+6]=T2[2]; - eta[8*tid+7]=T2[3]; - - } - } - - __syncthreads(); - /* copy back to global memory */ - if (tid=0 always */ - eta0[2*tid+bid*4*N]=cuCaddf(eta0[2*tid+bid*4*N],eta[8*ci]); - eta0[2*tid+2*N+bid*4*N]=cuCaddf(eta0[2*tid+2*N+bid*4*N],eta[8*ci+1]); - eta0[2*tid+1+bid*4*N]=cuCaddf(eta0[2*tid+1+bid*4*N],eta[8*ci+2]); - eta0[2*tid+2*N+1+bid*4*N]=cuCaddf(eta0[2*tid+2*N+1+bid*4*N],eta[8*ci+3]); - } - if (sta2==tid) { /* note, tid >=0 always */ - eta0[2*tid+bid*4*N]=cuCaddf(eta0[2*tid+bid*4*N],eta[8*ci+4]); - eta0[2*tid+2*N+bid*4*N]=cuCaddf(eta0[2*tid+2*N+bid*4*N],eta[8*ci+5]); - eta0[2*tid+1+bid*4*N]=cuCaddf(eta0[2*tid+1+bid*4*N],eta[8*ci+6]); - eta0[2*tid+2*N+1+bid*4*N]=cuCaddf(eta0[2*tid+2*N+1+bid*4*N],eta[8*ci+7]); - } - } - } - __syncthreads(); -} - -__global__ void -kernel_fns_sumblocks_pertime(int N, int Nblocks, int offset, cuFloatComplex *__restrict__ eta0) { - /* offset: values in 0...4N - each block will sum Nblocks in eta0 and store it in first value */ - extern __shared__ cuFloatComplex etas[]; - int bid=blockIdx.x; - int tid=threadIdx.x; - int gtid=tid+offset; - /* this block will work on blocks bid*Nblocks,bid*Nblocks+1,...,(bid+1)Nblocks-1 */ - /* each thread will work with Nblocks values */ - /* load global data */ - if (gtid < 4*N) { - for (int ci=0; ci0; s=s/2) { - if(tid < s) { etas[tid] = cuCaddf(etas[tid],etas[tid + s]); } - __syncthreads(); - } - - - /* add to proper location in eta */ - if(tid==0 && bid<4*N) { - eta[bid]=cuCaddf(etas[tid],eta[bid]); - } - __syncthreads(); -} - - -__global__ void -kernel_fns_sumelements_alltime(int Ntime,int offset, const cuFloatComplex *__restrict__ eta0, cuFloatComplex *__restrict__ C) { - /* C: 2x2, eta0: 2x2Ntime, sum eta0 and store it in C (C initialized to 0) */ - /* 4 blocks, blockDim.x threads */ - extern __shared__ cuFloatComplex etas[]; - int bid=blockIdx.x; /* 0..3 add to C[bid] */ - int tid=threadIdx.x; /* 0...Ntime-1 */ - int gtid=4*(tid+offset)+bid; - etas[tid]=make_cuFloatComplex(0.0f,0.0f); - if (tid+offset0; s=s/2) { - if(tid < s) { etas[tid] = cuCaddf(etas[tid],etas[tid + s]); } - __syncthreads(); - } - - /* add to proper location in C */ - if(tid==0 && bid<4) { - C[bid]=cuCaddf(etas[tid],C[bid]); - } - __syncthreads(); - -} - - -__global__ void -kernel_fns_rhs_alltime(cuFloatComplex *__restrict__ C) { - /* C: 2 x 2 Nblocks , each block (4) threads will work on 2x2 matrix */ - extern __shared__ cuFloatComplex etas[]; - int bid=blockIdx.x; /* 0..ntime-1 */ - int tid=threadIdx.x; /* 0..3 */ - - /* load data to shared mem, X^H Z */ - if (tid<4) { - etas[tid]=C[bid*4+tid]; - } - __syncthreads(); - - /* now find X^H-Z^H X */ - cuFloatComplex a,b; - if (tid==0) { - a=etas[0]; b=etas[0]; - } else if (tid==1) { - a=etas[2]; b=etas[1]; - } else if (tid==2) { - a=etas[1]; b=etas[2]; - } else { - a=etas[3]; b=etas[3]; - } - etas[tid]=cuCsubf(a,cuConjf(b)); - __syncthreads(); - - /* write back to C */ - if (tid<4) { - C[bid*4+tid]=etas[tid]; - } - __syncthreads(); - -} - -__global__ void -kernel_fns_fgrad_robust1(int N, int Nbase, const cuFloatComplex *__restrict__ x, cuFloatComplex *__restrict__ eta0, const float *__restrict__ y, const float *__restrict__ coh, const short *__restrict__ bbh, const float *__restrict__ wtd) { - - /* eta0: each block will store result in its own block */ - /* global thread index : equal to the baseline */ - unsigned int n = threadIdx.x + blockDim.x*blockIdx.x; - int bid=blockIdx.x; - int tid=threadIdx.x; - /* 4x2xblockDim.x cuFloatComplex values and 2xblockDim.x int values */ - extern __shared__ cuFloatComplex eta[]; - int *stm= (int*)&eta[8*blockDim.x]; - stm[2*tid]=-1; - stm[2*tid+1]=-1; - /* x,eta: 2Nx2 matrix */ - if(n=0 - */ - if (sta1>=0 && sta2>=0) { - stm[2*tid]=sta1; - stm[2*tid+1]=sta2; - - cuFloatComplex G1[4]; - cuFloatComplex G2[4]; - /* J1 */ - G1[0]=x[sta1*2]; - G1[1]=x[sta1*2+N*2]; - G1[2]=x[sta1*2+1]; - G1[3]=x[sta1*2+N*2+1]; - /* conjugate this to get J2^H */ - G2[0]=x[sta2*2]; - G2[1]=x[sta2*2+N*2]; - G2[2]=x[sta2*2+1]; - G2[3]=x[sta2*2+N*2+1]; - - cuFloatComplex C[4]; - C[0].x=coh[8*n]; - C[0].y=coh[8*n+1]; - C[1].x=coh[8*n+2]; - C[1].y=coh[8*n+3]; - C[2].x=coh[8*n+4]; - C[2].y=coh[8*n+5]; - C[3].x=coh[8*n+6]; - C[3].y=coh[8*n+7]; - - /* G1*C*G2' */ - cuFloatComplex T1[4]; - cuFloatComplex T2[4]; - /* T=G1*C */ - amb(G1,C,T1); - ambt(T1,G2,T2); - - /* res=V(2*ck-1:2*ck,:)-x(2*p-1:2*p,:)*C*x(2*q-1:2*q,:)'; */ - /* V->U */ - cuFloatComplex res[4]; - res[0]=cuCsubf(make_cuFloatComplex(y[8*n],y[8*n+1]),T2[0]); - res[1]=cuCsubf(make_cuFloatComplex(y[8*n+2],y[8*n+3]),T2[1]); - res[2]=cuCsubf(make_cuFloatComplex(y[8*n+4],y[8*n+5]),T2[2]); - res[3]=cuCsubf(make_cuFloatComplex(y[8*n+6],y[8*n+7]),T2[3]); - - /* - grad(2*p-1:2*p,:)=grad(2*p-1:2*p,:)+res*x(2*q-1:2*q,:)*C'; - grad(2*q-1:2*q,:)=grad(2*q-1:2*q,:)+res'*x(2*p-1:2*p,:)*C; - */ - /* res*G2*C' */ - amb(res,G2,T1); - ambt(T1,C,T2); - - float wtdn=wtd[n]; - /* mult with weights */ - T2[0].x=wtdn*T2[0].x; - T2[0].y=wtdn*T2[0].y; - T2[1].x=wtdn*T2[1].x; - T2[1].y=wtdn*T2[1].y; - T2[2].x=wtdn*T2[2].x; - T2[2].y=wtdn*T2[2].y; - T2[3].x=wtdn*T2[3].x; - T2[3].y=wtdn*T2[3].y; - - eta[8*tid]=T2[0]; - eta[8*tid+1]=T2[1]; - eta[8*tid+2]=T2[2]; - eta[8*tid+3]=T2[3]; - - - /* res'*G1*C */ - atmb(res,G1,T1); - amb(T1,C,T2); - - /* mult with weights */ - T2[0].x=wtdn*T2[0].x; - T2[0].y=wtdn*T2[0].y; - T2[1].x=wtdn*T2[1].x; - T2[1].y=wtdn*T2[1].y; - T2[2].x=wtdn*T2[2].x; - T2[2].y=wtdn*T2[2].y; - T2[3].x=wtdn*T2[3].x; - T2[3].y=wtdn*T2[3].y; - - - eta[8*tid+4]=T2[0]; - eta[8*tid+5]=T2[1]; - eta[8*tid+6]=T2[2]; - eta[8*tid+7]=T2[3]; - - } - } - - __syncthreads(); - /* copy back to global memory */ - if (tid==0) { - for(int ci=0; ci=0 && sta2>=0) { - eta0[2*sta1+bid*4*N]=cuCaddf(eta0[2*sta1+bid*4*N],eta[8*ci]); - eta0[2*sta1+2*N+bid*4*N]=cuCaddf(eta0[2*sta1+2*N+bid*4*N],eta[8*ci+1]); - eta0[2*sta1+1+bid*4*N]=cuCaddf(eta0[2*sta1+1+bid*4*N],eta[8*ci+2]); - eta0[2*sta1+2*N+1+bid*4*N]=cuCaddf(eta0[2*sta1+2*N+1+bid*4*N],eta[8*ci+3]); - eta0[2*sta2+bid*4*N]=cuCaddf(eta0[2*sta2+bid*4*N],eta[8*ci+4]); - eta0[2*sta2+2*N+bid*4*N]=cuCaddf(eta0[2*sta2+2*N+bid*4*N],eta[8*ci+5]); - eta0[2*sta2+1+bid*4*N]=cuCaddf(eta0[2*sta2+1+bid*4*N],eta[8*ci+6]); - eta0[2*sta2+2*N+1+bid*4*N]=cuCaddf(eta0[2*sta2+2*N+1+bid*4*N],eta[8*ci+7]); - - } - } - } - __syncthreads(); -} - -__global__ void -kernel_fns_fgradsum(int N, int B, int blockDim_2, const cuFloatComplex *__restrict__ etaloc, cuFloatComplex *__restrict__ eta) { - int bid=blockIdx.x; - int tid=threadIdx.x; - /* B x cuFloatComplex values */ - extern __shared__ cuFloatComplex etas[]; - etas[tid]=make_cuFloatComplex(0.0f,0.0f); - if (tid 1) { - int halfPoint = (nTotalThreads >> 1); // divide by two - if (tid < halfPoint) { - int thread2 = tid + halfPoint; - if (thread2 < blockDim.x) { // Skipping the fictitious threads blockDim.x ... blockDim_2-1 - etas[tid] = cuCaddf(etas[tid],etas[thread2]); - } - } - __syncthreads(); - nTotalThreads = halfPoint; // Reducing the binary tree size by two - } - - /* add back the sum to proper location in eta */ - if(tid==0) { - eta[bid]=cuCaddf(eta[bid],etas[0]); - } -} - -__global__ void -kernel_fns_f(int N, int Nbase, const cuFloatComplex *__restrict__ x, const float *__restrict__ y, const float *__restrict__ coh, const short *__restrict__ bbh, float *__restrict__ ed) { - - // Each block saves error into shared memory - extern __shared__ float ek[]; - /* global thread index : equal to the baseline */ - unsigned int n = threadIdx.x + blockDim.x*blockIdx.x; - int tid = threadIdx.x; - - /* this thread works on - coh[8*M*n:8*M*n+8*M-1] - bb[2*n:2*n+1] (sta1,sta2) - organization of p (N stations and M clusters) - sta 0 sta 1 sta 2 .... sta N-1 - clus 0 0...7 8...15 16...23 ... 8N-8 8N-1 - clus 1 8N..8N+7 8N+8..8N+15 8N+16..8N+23 .... 8N+8N-8...8N+8N-1 - ...... - clus M-1 (M-1)N..(M-1)N+7 (M-1)N+8..(M-1)N+15.... ...(M-1)N+8N-8 (M-1)N+8N-1 - - organization of coherencies (coh) - [0, 8*M-1] : baseline 0 - [8*M, 8*M+8*M-1]: baseline 1 - [n*8*M, n*8*M+8*M-1]: baseline n - ...... - [n*8*M+cm*8, n*8*M+cm*8+7] cluster cm, baseline n - - x: 2Nx2 matrix - */ - ek[tid]=0.0f; - if(n=0 - */ - float sumn=0.0f; - float temp1,temp2,tt,yy,c=0.0f; - if (sta1>=0 && sta2>=0) { - cuFloatComplex G1[4]; - cuFloatComplex G2[4]; - G1[0]=x[sta1*2]; - G1[1]=x[sta1*2+N*2]; - G1[2]=x[sta1*2+1]; - G1[3]=x[sta1*2+N*2+1]; - G2[0]=x[sta2*2]; - G2[1]=x[sta2*2+N*2]; - G2[2]=x[sta2*2+1]; - G2[3]=x[sta2*2+N*2+1]; - - cuFloatComplex C[4]; - C[0].x=coh[8*n]; - C[0].y=coh[8*n+1]; - C[1].x=coh[8*n+2]; - C[1].y=coh[8*n+3]; - C[2].x=coh[8*n+4]; - C[2].y=coh[8*n+5]; - C[3].x=coh[8*n+6]; - C[3].y=coh[8*n+7]; - - cuFloatComplex T1[4]; - cuFloatComplex T2[4]; - /* T=G1*C */ - amb(G1,C,T1); - /* T=T*G2' */ - ambt(T1,G2,T2); - - /* error using Kahan summation */ - /* V->U */ - temp1=y[8*n]-T2[0].x; - temp2=temp1*temp1; yy=temp2-c; tt=sumn+yy; c=(tt-sumn)-yy; sumn=tt; - temp1=y[8*n+1]-T2[0].y; - temp2=temp1*temp1; yy=temp2-c; tt=sumn+yy; c=(tt-sumn)-yy; sumn=tt; - temp1=y[8*n+2]-T2[1].x; - temp2=temp1*temp1; yy=temp2-c; tt=sumn+yy; c=(tt-sumn)-yy; sumn=tt; - temp1=y[8*n+3]-T2[1].y; - temp2=temp1*temp1; yy=temp2-c; tt=sumn+yy; c=(tt-sumn)-yy; sumn=tt; - temp1=y[8*n+4]-T2[2].x; - temp2=temp1*temp1; yy=temp2-c; tt=sumn+yy; c=(tt-sumn)-yy; sumn=tt; - temp1=y[8*n+5]-T2[2].y; - temp2=temp1*temp1; yy=temp2-c; tt=sumn+yy; c=(tt-sumn)-yy; sumn=tt; - temp1=y[8*n+6]-T2[3].x; - temp2=temp1*temp1; yy=temp2-c; tt=sumn+yy; c=(tt-sumn)-yy; sumn=tt; - temp1=y[8*n+7]-T2[3].y; - temp2=temp1*temp1; yy=temp2-c; tt=sumn+yy; c=(tt-sumn)-yy; sumn=tt; - ek[tid]=sumn; - } - } - - __syncthreads(); - // Build summation tree over elements, assuming blockDim.x is power of 2. - for(int s=blockDim.x/2; s>0; s=s/2) { - if(tid < s) ek[tid] += ek[tid + s]; - __syncthreads(); - } - - /* copy back the sum to proper location in ed */ - if(tid==0) { - ed[blockIdx.x]=ek[0]; - } -} - -__global__ void -kernel_fns_f_robust(int N, int Nbase, const cuFloatComplex *__restrict__ x, const float *__restrict__ y, const float *__restrict__ coh, const short *__restrict__ bbh, const float *__restrict__ wtd, float *__restrict__ ed) { - - // Each block saves error into shared memory - extern __shared__ float ek[]; - /* global thread index : equal to the baseline */ - unsigned int n = threadIdx.x + blockDim.x*blockIdx.x; - int tid = threadIdx.x; - - /* this thread works on - coh[8*M*n:8*M*n+8*M-1] - bb[2*n:2*n+1] (sta1,sta2) - organization of p (N stations and M clusters) - sta 0 sta 1 sta 2 .... sta N-1 - clus 0 0...7 8...15 16...23 ... 8N-8 8N-1 - clus 1 8N..8N+7 8N+8..8N+15 8N+16..8N+23 .... 8N+8N-8...8N+8N-1 - ...... - clus M-1 (M-1)N..(M-1)N+7 (M-1)N+8..(M-1)N+15.... ...(M-1)N+8N-8 (M-1)N+8N-1 - - organization of coherencies (coh) - [0, 8*M-1] : baseline 0 - [8*M, 8*M+8*M-1]: baseline 1 - [n*8*M, n*8*M+8*M-1]: baseline n - ...... - [n*8*M+cm*8, n*8*M+cm*8+7] cluster cm, baseline n - - x: 2Nx2 matrix - */ - ek[tid]=0.0f; - if(n=0 - */ - float sumn=0.0f; - float temp1,temp2,tt,yy,c=0.0f; - if (sta1>=0 && sta2>=0) { - cuFloatComplex G1[4]; - cuFloatComplex G2[4]; - G1[0]=x[sta1*2]; - G1[1]=x[sta1*2+N*2]; - G1[2]=x[sta1*2+1]; - G1[3]=x[sta1*2+N*2+1]; - G2[0]=x[sta2*2]; - G2[1]=x[sta2*2+N*2]; - G2[2]=x[sta2*2+1]; - G2[3]=x[sta2*2+N*2+1]; - - cuFloatComplex C[4]; - C[0].x=coh[8*n]; - C[0].y=coh[8*n+1]; - C[1].x=coh[8*n+2]; - C[1].y=coh[8*n+3]; - C[2].x=coh[8*n+4]; - C[2].y=coh[8*n+5]; - C[3].x=coh[8*n+6]; - C[3].y=coh[8*n+7]; - - cuFloatComplex T1[4]; - cuFloatComplex T2[4]; - /* T=G1*C */ - amb(G1,C,T1); - /* T=T*G2' */ - ambt(T1,G2,T2); - - /* error using Kahan summation */ - /* V->U */ - temp1=y[8*n]-T2[0].x; - temp2=temp1*temp1; yy=temp2-c; tt=sumn+yy; c=(tt-sumn)-yy; sumn=tt; - temp1=y[8*n+1]-T2[0].y; - temp2=temp1*temp1; yy=temp2-c; tt=sumn+yy; c=(tt-sumn)-yy; sumn=tt; - temp1=y[8*n+2]-T2[1].x; - temp2=temp1*temp1; yy=temp2-c; tt=sumn+yy; c=(tt-sumn)-yy; sumn=tt; - temp1=y[8*n+3]-T2[1].y; - temp2=temp1*temp1; yy=temp2-c; tt=sumn+yy; c=(tt-sumn)-yy; sumn=tt; - temp1=y[8*n+4]-T2[2].x; - temp2=temp1*temp1; yy=temp2-c; tt=sumn+yy; c=(tt-sumn)-yy; sumn=tt; - temp1=y[8*n+5]-T2[2].y; - temp2=temp1*temp1; yy=temp2-c; tt=sumn+yy; c=(tt-sumn)-yy; sumn=tt; - temp1=y[8*n+6]-T2[3].x; - temp2=temp1*temp1; yy=temp2-c; tt=sumn+yy; c=(tt-sumn)-yy; sumn=tt; - temp1=y[8*n+7]-T2[3].y; - temp2=temp1*temp1; yy=temp2-c; tt=sumn+yy; c=(tt-sumn)-yy; sumn=tt; - ek[tid]=wtd[n]*sumn; - } - } - - __syncthreads(); - // Build summation tree over elements, assuming blockDim.x is power of 2. - for(int s=blockDim.x/2; s>0; s=s/2) { - if(tid < s) { ek[tid] += ek[tid + s]; } - __syncthreads(); - } - - /* copy back the sum to proper location in ed */ - if(tid==0) { - ed[blockIdx.x]=ek[0]; - } -} - - -/* update weights */ -__global__ void -kernel_fns_fupdate_weights(int N, int Nbase, const cuFloatComplex *__restrict__ x, const float *__restrict__ y, const float *__restrict__ coh, const short *__restrict__ bbh, float *__restrict__ wtd, float nu0) { - - /* global thread index : equal to the baseline */ - unsigned int n = threadIdx.x + blockDim.x*blockIdx.x; - - if(n=0 - */ - float sumn=0.0f; - float temp1,temp2,tt; - if (sta1>=0 && sta2>=0) { - cuFloatComplex G1[4]; - cuFloatComplex G2[4]; - G1[0]=x[sta1*2]; - G1[1]=x[sta1*2+N*2]; - G1[2]=x[sta1*2+1]; - G1[3]=x[sta1*2+N*2+1]; - G2[0]=x[sta2*2]; - G2[1]=x[sta2*2+N*2]; - G2[2]=x[sta2*2+1]; - G2[3]=x[sta2*2+N*2+1]; - - cuFloatComplex C[4]; - C[0].x=coh[8*n]; - C[0].y=coh[8*n+1]; - C[1].x=coh[8*n+2]; - C[1].y=coh[8*n+3]; - C[2].x=coh[8*n+4]; - C[2].y=coh[8*n+5]; - C[3].x=coh[8*n+6]; - C[3].y=coh[8*n+7]; - - cuFloatComplex T1[4]; - cuFloatComplex T2[4]; - /* T=G1*C */ - amb(G1,C,T1); - /* T=T*G2' */ - ambt(T1,G2,T2); - - /* use p=2, find MAX value of residual error out of XX,XY,YX,YY - instead of the sum */ - /* V->U */ - temp1=y[8*n]-T2[0].x; - temp2=y[8*n+1]-T2[0].y; - sumn=temp1*temp1+temp2*temp2; - temp1=y[8*n+2]-T2[1].x; - temp2=y[8*n+3]-T2[1].y; - tt=temp1*temp1+temp2*temp2; - if (sumn=0 - */ - float sumn=0.0f; - float temp1,temp2,tt; - if (sta1>=0 && sta2>=0) { - cuFloatComplex G1[4]; - cuFloatComplex G2[4]; - G1[0]=x[sta1*2]; - G1[1]=x[sta1*2+N*2]; - G1[2]=x[sta1*2+1]; - G1[3]=x[sta1*2+N*2+1]; - G2[0]=x[sta2*2]; - G2[1]=x[sta2*2+N*2]; - G2[2]=x[sta2*2+1]; - G2[3]=x[sta2*2+N*2+1]; - - cuFloatComplex C[4]; - C[0].x=coh[8*n]; - C[0].y=coh[8*n+1]; - C[1].x=coh[8*n+2]; - C[1].y=coh[8*n+3]; - C[2].x=coh[8*n+4]; - C[2].y=coh[8*n+5]; - C[3].x=coh[8*n+6]; - C[3].y=coh[8*n+7]; - - cuFloatComplex T1[4]; - cuFloatComplex T2[4]; - /* T=G1*C */ - amb(G1,C,T1); - /* T=T*G2' */ - ambt(T1,G2,T2); - - /* use p=2, find MAX value of residual error out of XX,XY,YX,YY - instead of the sum */ - /* V->U */ - temp1=y[8*n]-T2[0].x; - temp2=y[8*n+1]-T2[0].y; - sumn=temp1*temp1+temp2*temp2; - temp1=y[8*n+2]-T2[1].x; - temp2=y[8*n+3]-T2[1].y; - tt=temp1*temp1+temp2*temp2; - if (sumn number of blocks) */ -__global__ void -plus_reduce_multi(const float *__restrict__ input, int N, int blockDim_2, float *__restrict__ output) { - // Each block loads its elements into shared memory - extern __shared__ float x[]; - int tid = threadIdx.x; - int i = blockIdx.x*blockDim.x + threadIdx.x; - x[tid] = (i 1) { - int halfPoint = (nTotalThreads >> 1); // divide by two - if (tid < halfPoint) { - int thread2 = tid + halfPoint; - if (thread2 < blockDim.x) { // Skipping the fictitious threads blockDim.x ... blockDim_2-1 - x[tid] = x[tid]+x[thread2]; - } - } - __syncthreads(); - nTotalThreads = halfPoint; // Reducing the binary tree size by two - } - - /* add back to total */ - if( tid == 0 ) { - output[blockIdx.x]=x[tid]; - } -} - - -/* sum up all N elements of vector input - NOTE: only 1 block should be used */ -__global__ void -plus_reduce(const float *__restrict__ input, int N, int blockDim_2, float *total) { - // Each block loads its elements into shared memory - extern __shared__ float x[]; - int tid = threadIdx.x; - int i = blockIdx.x*blockDim.x + threadIdx.x; - x[tid] = (i 1) { - int halfPoint = (nTotalThreads >> 1); // divide by two - if (tid < halfPoint) { - int thread2 = tid + halfPoint; - if (thread2 < blockDim.x) { // Skipping the fictitious threads blockDim.x ... blockDim_2-1 - x[tid] = x[tid]+x[thread2]; - } - } - __syncthreads(); - nTotalThreads = halfPoint; // Reducing the binary tree size by two - } - - /* add back to total */ - if( tid == 0 ) { - *total=*total+x[tid]; - } -} - - -__global__ void -kernel_fns_fscale(int N, cuFloatComplex *__restrict__ eta, const float *__restrict__ iw) { - unsigned int n = threadIdx.x + blockDim.x*blockIdx.x; - if (nstation mapping - - return ed: error vector, BlocksPerGridx1 -*/ -/* need BlocksPerGrid+1+L float storage */ -float -cudakernel_fns_f(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, float *y, float *coh, short *bbh) { -#ifdef CUDA_DBG - cudaError_t error; -#endif - float *ed,*eo; - cudaMalloc((void**)&ed, sizeof(float)*BlocksPerGrid); - cudaMemset(ed, 0, sizeof(float)*BlocksPerGrid); - kernel_fns_f<<< BlocksPerGrid, ThreadsPerBlock, sizeof(float)*ThreadsPerBlock >>>(N, M, x, y, coh, bbh,ed); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - float total; - float *totald; - cudaMalloc((void**)&totald, sizeof(float)); - cudaMemset(totald, 0, sizeof(float)); - int T=DEFAULT_TH_PER_BK; /* max possible threads, use a smaller no to have large no. of blocks, but not too large to exceed no. of. SMs in the card*/ - /* we use 1 block, so need to launch BlocksPerGrid number of threads */ - if (BlocksPerGrid>>(ed, BlocksPerGrid, NearestPowerOf2(BlocksPerGrid), totald); - } else { - /* multiple kernel launches */ - int L=(BlocksPerGrid+T-1)/T; - cudaMalloc((void**)&eo, sizeof(float)*L); - plus_reduce_multi<<< L, T, sizeof(float)*T>>>(ed, BlocksPerGrid, NearestPowerOf2(T), eo); - plus_reduce<<< 1, L, sizeof(float)*L>>>(eo, L, NearestPowerOf2(L), totald); - cudaFree(eo); - } -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - cudaMemcpy(&total,totald,sizeof(float),cudaMemcpyDeviceToHost); - cudaFree(ed); - cudaFree(totald); - return total; -} - -/* - robust cost function: - N: no of stations - M: no of constraints (baselines) - x: solution 2Nx2 complex float - y: data 8M float (8 for each baseline) - coh: coherency - bbh: baseline->station mapping - wtd: weight Mx1 - - return ed: error vector, BlocksPerGridx1 -*/ -/* need BlocksPerGrid+4+L float storage <= (2 BlocksPerGrid + 4) */ -float -cudakernel_fns_f_robust(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, float *y, float *coh, short *bbh, float *wtd) { -#ifdef CUDA_DBG - cudaError_t error; -#endif - float *ed,*eo; - cudaMalloc((void**)&ed, sizeof(float)*BlocksPerGrid); - cudaMemset(ed, 0, sizeof(float)*BlocksPerGrid); - kernel_fns_f_robust<<< BlocksPerGrid, ThreadsPerBlock, sizeof(float)*ThreadsPerBlock >>>(N, M, x, y, coh, bbh, wtd, ed); - -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - float total; - float *totald; - cudaMalloc((void**)&totald, sizeof(float)); - cudaMemset(totald, 0, sizeof(float)); - int T=DEFAULT_TH_PER_BK; /* max possible threads, use a smaller no to have large no. of blocks, but not too large to exceed no. of. SMs in the card*/ - /* we use 1 block, so need to launch BlocksPerGrid number of threads */ - if (BlocksPerGrid>>(ed, BlocksPerGrid, NearestPowerOf2(BlocksPerGrid), totald); - } else { - /* multiple kernel launches */ - int L=(BlocksPerGrid+T-1)/T; - cudaMalloc((void**)&eo, sizeof(float)*L); - plus_reduce_multi<<< L, T, sizeof(float)*T>>>(ed, BlocksPerGrid, NearestPowerOf2(T), eo); - plus_reduce<<< 1, L, sizeof(float)*L>>>(eo, L, NearestPowerOf2(L), totald); - cudaFree(eo); - } -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - cudaMemcpy(&total,totald,sizeof(float),cudaMemcpyDeviceToHost); - cudaFree(ed); - cudaFree(totald); - - return total; -} - -/* gradient, output eta: reset to 0 initially */ -/* need 8N*BlocksPerGrid float storage */ -void -cudakernel_fns_fgradflat(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, float *y, float *coh, short *bbh) { -#ifdef CUDA_DBG - cudaError_t error; -#endif - cuFloatComplex *etaloc; - /* each block stores result in 2Nx2 block, so need BlocksPerGrid x 2Nx 2 storage */ - cudaMalloc((void**)&etaloc, sizeof(cuFloatComplex)*BlocksPerGrid*4*N); - cudaMemset(etaloc, 0, sizeof(cuFloatComplex)*BlocksPerGrid*4*N); - cudaMemset(eta, 0, sizeof(cuFloatComplex)*4*N); - /* eachekernel require 2xThreadsPerBlocx2 x 2 complex float for storing eta values - 2*ThreadsPerBloc x1 int array for station numbers - and */ - kernel_fns_fgrad<<< BlocksPerGrid, ThreadsPerBlock, sizeof(cuFloatComplex)*8*ThreadsPerBlock + sizeof(int)*2*ThreadsPerBlock >>>(N, M, x, etaloc, y, coh, bbh); - -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - int T=256; /* max possible threads */ - /* now create 4N blocks, threads in each block will read BlocksPerGrid values from etalocal and find average, so no of threads>= BlocksPerGrid */ - /* each block need BlocksPerGrid float complex values */ - if (T>BlocksPerGrid) { - int B=((BlocksPerGrid+1)/2)*2; /* even no of threads */ - kernel_fns_fgradsum<<< 4*N, B, sizeof(cuFloatComplex)*B>>>(N, BlocksPerGrid, NearestPowerOf2(B), etaloc, eta); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - } else { - /* iterate over T values */ - int L=(BlocksPerGrid+T-1)/T; - int ct=0; - int myT; - for (int ci=0; ci>>(N, myT, NearestPowerOf2(myT), &etaloc[ct*4*N], eta); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - ct=ct+T; - } - } - - cudaFree(etaloc); -} - -/* Robust gradient, output eta: reset to 0 initially */ -/* need 8N*BlocksPerGrid float storage */ -void -cudakernel_fns_fgradflat_robust1(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, float *y, float *coh, short *bbh, float *wtd) { -#ifdef CUDA_DBG - cudaError_t error; -#endif - cuFloatComplex *etaloc; - /* each block stores result in 2Nx2 block, so need BlocksPerGrid x 2Nx 2 storage */ - cudaMalloc((void**)&etaloc, sizeof(cuFloatComplex)*BlocksPerGrid*4*N); - cudaMemset(etaloc, 0, sizeof(cuFloatComplex)*BlocksPerGrid*4*N); - cudaMemset(eta, 0, sizeof(cuFloatComplex)*4*N); - /* eachekernel require 2xThreadsPerBlocx2 x 2 complex float for storing eta values - 2*ThreadsPerBloc x1 int array for station numbers - and */ - kernel_fns_fgrad_robust1<<< BlocksPerGrid, ThreadsPerBlock, sizeof(cuFloatComplex)*8*ThreadsPerBlock + sizeof(int)*2*ThreadsPerBlock >>>(N, M, x, etaloc, y, coh, bbh, wtd); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - int T=256; /* max possible threads */ - /* now create 4N blocks, threads in each block will read BlocksPerGrid values from etalocal and find average, so no of threads>= BlocksPerGrid */ - /* each block need BlocksPerGrid float complex values */ - if (T>BlocksPerGrid) { - int B=((BlocksPerGrid+1)/2)*2; /* even no of threads */ - kernel_fns_fgradsum<<< 4*N, B, sizeof(cuFloatComplex)*B>>>(N, BlocksPerGrid, NearestPowerOf2(B), etaloc, eta); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - } else { - /* iterate over T values */ - int L=(BlocksPerGrid+T-1)/T; - int ct=0; - int myT; - for (int ci=0; ci>>(N, myT, NearestPowerOf2(myT), &etaloc[ct*4*N], eta); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - ct=ct+T; - } - } - cudaFree(etaloc); -} - - -/* Robust gradient, output eta: reset to 0 initially */ -/* Ai: inverse of A matrix for projection */ -/* need 8N*BlocksPerGrid float storage */ -void -cudakernel_fns_fgradflat_robust(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, float *y, float *coh, short *bbh, float *wtd, cuFloatComplex *Ai, cublasHandle_t cbhandle) { -#ifdef CUDA_DBG - cudaError_t error; -#endif - cuFloatComplex *etaloc; - /* each block stores result in 2Nx2 block, so need BlocksPerGrid x 2Nx 2 storage */ - cudaMalloc((void**)&etaloc, sizeof(cuFloatComplex)*BlocksPerGrid*4*N); - cudaMemset(etaloc, 0, sizeof(cuFloatComplex)*BlocksPerGrid*4*N); - cudaMemset(eta, 0, sizeof(cuFloatComplex)*4*N); - /* each block requires 2xThreadsPerBloc x2 x 2 complex float for storing eta values - and 2*ThreadsPerBloc x1 int array for station numbers - each block requires Nx2x2 complex float to store calculated value - - also ThreadsPerBlock>= N - */ - kernel_fns_fgrad_robust<<< BlocksPerGrid, ThreadsPerBlock, sizeof(cuFloatComplex)*8*ThreadsPerBlock + sizeof(int)*2*ThreadsPerBlock >>>(N, M, x, etaloc, y, coh, bbh, wtd); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - /* now etaloc has [Z_1,Z_2,....,Z_K] where K: total blocks, - need to add it to - [Z_1, Z_2,....Z_t] where t: total timeslots. - so each blocks_per_timeslot blocks will be added to just one block - - project [P_1,P_2,...,P_t]=[Z_1,Z_2,..,Z_t]-J[U_1,U_2,...,U_t] - where U_i is the projection matrix obtained by solving Sylvester equation, - for that we need J^H [Z_1,Z_2,...,Z_t] - */ - /* baselines */ - int nbase=N*(N-1)/2; - /* blocks per timeslot */ - int Bt=(nbase+ThreadsPerBlock-1)/ThreadsPerBlock; - /* timeslots */ - int ntime=(M+nbase-1)/nbase; - /* threads to use (need to use small value to enable enough shared memory) */ - int T=DEFAULT_TH_PER_BK_2; - /* sum Bt values each to the first value */ - for (int ci=0; ci<(4*N+T-1)/T; ci++) { - /* create blocks equal to timeslots, each will need Bt*T complex float storage, ci*T is the offset of 0...4N-1 values */ - /* each thread will sum Bt values */ - kernel_fns_sumblocks_pertime<<< ntime, T, sizeof(cuFloatComplex)*Bt*T >>>(N, Bt, ci*T, etaloc); -#ifdef DEBUG - printf("sum blocks %d, threads %d thread offset %d, numblocks/time %d\n",ntime,T,ci*T,Bt); -#endif - } - -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - /* now create 4N blocks, each block will sum elements in 0...4N-1 of ntime values, separated by Bt blocks */ - T=DEFAULT_TH_PER_BK_2; /* even number */ - for (int ci=0; ci<(ntime+T-1)/T; ci++) { - kernel_fns_sumblocks_alltime<<< 4*N, T, sizeof(cuFloatComplex)*T >>>(N, Bt, ntime, ci*T, etaloc, eta); -#ifdef DEBUG - printf("sum all blocks %d, timeslots %d, threads %d block offset %d, spacing %d\n",4*N,ntime,T,ci*T,Bt); -#endif - } - -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - /* now etaloc : 4N x ntime (== 2N x 2ntime) blocks correspond to eta for each timeslot */ - /* find the product x^H etaloc == x^H Z, - reuse tail end of etaloc to store result, since BlocksPerGrid >> ntime */ - cuFloatComplex *C; - C=&etaloc[8*N*ntime]; /* size 2 x 2ntime */ - //cudaMemset(C, 0, sizeof(cuFloatComplex)*4*ntime); Not needed because a2=0 - cublasStatus_t cbstatus; - cuFloatComplex a1,a2; - a1.x=1.0f; a1.y=0.0f; - a2.x=0.0f; a2.y=0.0f; - cbstatus=cublasCgemm(cbhandle,CUBLAS_OP_C,CUBLAS_OP_N,2,2*ntime,2*N,&a1,x,2*N,etaloc,2*N,&a2,C,2); - checkCublasError(cbstatus,__FILE__,__LINE__); - - /* setup RHS matrices x^H Z - Z^H x */ - /* 2x2 matrix: 4 threads per block, ntime blocks */ - T=4; - kernel_fns_rhs_alltime<<< ntime, T, sizeof(cuFloatComplex)*T >>>(C); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - - /* now consider C as 4xntime matrix and multiply it with Ai */ - /* reuse etaloc first block, size needed 4 x ntime << 4N x ntime */ - cbstatus=cublasCgemm(cbhandle,CUBLAS_OP_N,CUBLAS_OP_N,4,ntime,4,&a1,Ai,4,C,4,&a2,etaloc,4); - checkCublasError(cbstatus,__FILE__,__LINE__); - - /* now average 2x 2ntime matrix etaloc to one 2x2 matrix, stoared at C */ - cudaMemset(C, 0, sizeof(cuFloatComplex)*4); - T=DEFAULT_TH_PER_BK_2; /* even number */ - for (int ci=0; ci<(ntime+T-1)/T; ci++) { - kernel_fns_sumelements_alltime<<< 4, T, sizeof(cuFloatComplex)*T >>>(ntime,ci*T, etaloc, C); - } -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - /* now find eta = -1 x C + eta => C = A B + C */ - a1.x=-1.0f; a1.y=0.0f; - a2.x=1.0f; a2.y=0.0f; - cbstatus=cublasCgemm(cbhandle,CUBLAS_OP_N,CUBLAS_OP_N,2*N,2,2,&a1,x,2*N,C,2,&a2,eta,2*N); - checkCublasError(cbstatus,__FILE__,__LINE__); - - cudaFree(etaloc); -} - - -/* Robust gradient (Euclidean), output eta: reset to 0 initially */ -/* need 8N*BlocksPerGrid float storage */ -void -cudakernel_fns_fgradflat_robust_admm(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, float *y, float *coh, short *bbh, float *wtd, cublasHandle_t cbhandle) { -#ifdef CUDA_DBG - cudaError_t error; -#endif - cuFloatComplex *etaloc; - /* each block stores result in 2Nx2 block, so need BlocksPerGrid x 2Nx 2 storage */ - cudaMalloc((void**)&etaloc, sizeof(cuFloatComplex)*BlocksPerGrid*4*N); - cudaMemset(etaloc, 0, sizeof(cuFloatComplex)*BlocksPerGrid*4*N); - cudaMemset(eta, 0, sizeof(cuFloatComplex)*4*N); - /* each block requires 2xThreadsPerBloc x2 x 2 complex float for storing eta values - and 2*ThreadsPerBloc x1 int array for station numbers - each block requires Nx2x2 complex float to store calculated value - - also ThreadsPerBlock>= N - */ - kernel_fns_fgrad_robust<<< BlocksPerGrid, ThreadsPerBlock, sizeof(cuFloatComplex)*8*ThreadsPerBlock + sizeof(int)*2*ThreadsPerBlock >>>(N, M, x, etaloc, y, coh, bbh, wtd); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - /* now etaloc has [Z_1,Z_2,....,Z_K] where K: total blocks, - need to add it to - [Z_1, Z_2,....Z_t] where t: total timeslots. - so each blocks_per_timeslot blocks will be added to just one block - - */ - /* baselines */ - int nbase=N*(N-1)/2; - /* blocks per timeslot */ - int Bt=(nbase+ThreadsPerBlock-1)/ThreadsPerBlock; - /* timeslots */ - int ntime=(M+nbase-1)/nbase; - /* threads to use (need to use small value to enable enough shared memory) */ - int T=DEFAULT_TH_PER_BK_2; - /* sum Bt values each to the first value */ - for (int ci=0; ci<(4*N+T-1)/T; ci++) { - /* create blocks equal to timeslots, each will need Bt*T complex float storage, ci*T is the offset of 0...4N-1 values */ - /* each thread will sum Bt values */ - kernel_fns_sumblocks_pertime<<< ntime, T, sizeof(cuFloatComplex)*Bt*T >>>(N, Bt, ci*T, etaloc); -#ifdef DEBUG - printf("sum blocks %d, threads %d thread offset %d, numblocks/time %d\n",ntime,T,ci*T,Bt); -#endif - } - -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - /* now create 4N blocks, each block will sum elements in 0...4N-1 of ntime values, separated by Bt blocks */ - T=DEFAULT_TH_PER_BK_2; /* even number */ - for (int ci=0; ci<(ntime+T-1)/T; ci++) { - kernel_fns_sumblocks_alltime<<< 4*N, T, sizeof(cuFloatComplex)*T >>>(N, Bt, ntime, ci*T, etaloc, eta); -#ifdef DEBUG - printf("sum all blocks %d, timeslots %d, threads %d block offset %d, spacing %d\n",4*N,ntime,T,ci*T,Bt); -#endif - } - -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - /* now etaloc : 4N x ntime (== 2N x 2ntime) blocks correspond to eta for each timeslot */ - /* now average 2x 2ntime matrix etaloc to one 2x2 matrix, stoared at eta */ - T=DEFAULT_TH_PER_BK_2; /* even number */ - for (int ci=0; ci<(ntime+T-1)/T; ci++) { - kernel_fns_sumelements_alltime<<< 4, T, sizeof(cuFloatComplex)*T >>>(ntime,ci*T, etaloc, eta); - } -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - cudaFree(etaloc); -} - - - -/* Hessian - output fhess: reset to 0 initially */ -/* need 8N*BlocksPerGrid float storage */ -void -cudakernel_fns_fhessflat(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, cuFloatComplex *fhess, float *y, float *coh, short *bbh) { -#ifdef CUDA_DBG - cudaError_t error; -#endif - cuFloatComplex *etaloc; - /* each block stores result in 2Nx2 block, so need BlocksPerGrid x 2Nx 2 storage */ - cudaMalloc((void**)&etaloc, sizeof(cuFloatComplex)*BlocksPerGrid*4*N); - cudaMemset(etaloc, 0, sizeof(cuFloatComplex)*BlocksPerGrid*4*N); - cudaMemset(fhess, 0, sizeof(cuFloatComplex)*4*N); - /* eachekernel require 2xThreadsPerBlocx2 x 2 complex float for storing eta values - 2*ThreadsPerBloc x1 int array for station numbers - and */ - kernel_fns_fhess<<< BlocksPerGrid, ThreadsPerBlock, sizeof(cuFloatComplex)*8*ThreadsPerBlock + sizeof(int)*2*ThreadsPerBlock >>>(N, M, x, eta, etaloc, y, coh, bbh); - -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - int T=256; - /* now create 4N blocks, threads in each block will read BlocksPerGrid values from etalocal and find average, so no of threads>= BlocksPerGrid */ - /* each block need BlocksPerGrid float complex values */ - if (T>BlocksPerGrid) { - int B=((BlocksPerGrid+1)/2)*2; /* even no of threads */ - kernel_fns_fgradsum<<< 4*N, B, sizeof(cuFloatComplex)*B>>>(N, BlocksPerGrid, NearestPowerOf2(B), etaloc, fhess); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - } else { - /* iterate over T values */ - int L=(BlocksPerGrid+T-1)/T; - int ct=0; - int myT; - for (int ci=0; ci>>(N, myT, NearestPowerOf2(myT), &etaloc[ct*4*N], fhess); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - ct=ct+T; - } - } - - cudaFree(etaloc); -} - - -/* Robust Hessian - output fhess: reset to 0 initially */ -/* need 8N*BlocksPerGrid float storage */ -void -cudakernel_fns_fhessflat_robust1(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, cuFloatComplex *fhess, float *y, float *coh, short *bbh, float *wtd) { -#ifdef CUDA_DBG - cudaError_t error; -#endif - cuFloatComplex *etaloc; - /* each block stores result in 2Nx2 block, so need BlocksPerGrid x 2Nx 2 storage */ - cudaMalloc((void**)&etaloc, sizeof(cuFloatComplex)*BlocksPerGrid*4*N); - cudaMemset(etaloc, 0, sizeof(cuFloatComplex)*BlocksPerGrid*4*N); - cudaMemset(fhess, 0, sizeof(cuFloatComplex)*4*N); - /* eachekernel require 2xThreadsPerBlocx2 x 2 complex float for storing eta values - 2*ThreadsPerBloc x1 int array for station numbers - and */ - kernel_fns_fhess_robust1<<< BlocksPerGrid, ThreadsPerBlock, sizeof(cuFloatComplex)*8*ThreadsPerBlock + sizeof(int)*2*ThreadsPerBlock >>>(N, M, x, eta, etaloc, y, coh, bbh, wtd); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - int T=256; - /* now create 4N blocks, threads in each block will read BlocksPerGrid values from etalocal and find average, so no of threads>= BlocksPerGrid */ - /* each block need BlocksPerGrid float complex values */ - if (T>BlocksPerGrid) { - int B=((BlocksPerGrid+1)/2)*2; /* even no of threads */ - kernel_fns_fgradsum<<< 4*N, B, sizeof(cuFloatComplex)*B>>>(N, BlocksPerGrid, NearestPowerOf2(B), etaloc, fhess); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - } else { - /* iterate over T values */ - int L=(BlocksPerGrid+T-1)/T; - int ct=0; - int myT; - for (int ci=0; ci>>(N, myT, NearestPowerOf2(myT), &etaloc[ct*4*N], fhess); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - ct=ct+T; - } - } - - cudaFree(etaloc); -} - - -/* Robust Hessian - output fhess: reset to 0 initially */ -/* need 8N*BlocksPerGrid float storage */ -void -cudakernel_fns_fhessflat_robust(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, cuFloatComplex *fhess, float *y, float *coh, short *bbh, float *wtd, cuFloatComplex *Ai, cublasHandle_t cbhandle) { -#ifdef CUDA_DBG - cudaError_t error; -#endif - cuFloatComplex *etaloc; - /* each block stores result in 2Nx2 block, so need BlocksPerGrid x 2Nx 2 storage */ - cudaMalloc((void**)&etaloc, sizeof(cuFloatComplex)*BlocksPerGrid*4*N); - cudaMemset(etaloc, 0, sizeof(cuFloatComplex)*BlocksPerGrid*4*N); - cudaMemset(fhess, 0, sizeof(cuFloatComplex)*4*N); - /* eachekernel require 2xThreadsPerBlocx2 x 2 complex float for storing eta values - 2*ThreadsPerBloc x1 int array for station numbers - */ - kernel_fns_fhess_robust<<< BlocksPerGrid, ThreadsPerBlock, sizeof(cuFloatComplex)*8*ThreadsPerBlock + sizeof(int)*2*ThreadsPerBlock >>>(N, M, x, eta, etaloc, y, coh, bbh, wtd); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - /* baselines */ - int nbase=N*(N-1)/2; - /* blocks per timeslot */ - int Bt=(nbase+ThreadsPerBlock-1)/ThreadsPerBlock; - /* timeslots */ - int ntime=(M+nbase-1)/nbase; - /* threads to use (need to use small value to enable enough shared memory) */ - int T=DEFAULT_TH_PER_BK_2; - /* sum Bt values each to the first value */ - for (int ci=0; ci<(4*N+T-1)/T; ci++) { - /* create blocks equal to timeslots, each will need Bt*T complex float storage, ci*T is the offset of 0...4N-1 values */ - /* each thread will sum Bt values */ - kernel_fns_sumblocks_pertime<<< ntime, T, sizeof(cuFloatComplex)*Bt*T >>>(N, Bt, ci*T, etaloc); -#ifdef DEBUG - printf("sum blocks %d, threads %d thread offset %d, numblocks/time %d\n",ntime,T,ci*T,Bt); -#endif - } -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - /* now create 4N blocks, each block will sum elements in 0...4N-1 of ntime values, separated by Bt blocks */ - T=DEFAULT_TH_PER_BK_2; /* even number */ - for (int ci=0; ci<(ntime+T-1)/T; ci++) { - kernel_fns_sumblocks_alltime<<< 4*N, T, sizeof(cuFloatComplex)*T >>>(N, Bt, ntime, ci*T, etaloc, fhess); -#ifdef DEBUG - printf("sum all blocks %d, timeslots %d, threads %d block offset %d, spacing %d\n",4*N,ntime,T,ci*T,Bt); -#endif - } - -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - /* now etaloc : 4N x ntime (== 2N x 2ntime) blocks correspond to eta for each timeslot */ - /* find the product x^H etaloc == x^H Z, - reuse tail end of etaloc to store result, since BlocksPerGrid >> ntime */ - cuFloatComplex *C; - C=&etaloc[8*N*ntime]; /* size 2 x 2ntime */ - //cudaMemset(C, 0, sizeof(cuFloatComplex)*4*ntime); Not needed because a2=0 - cublasStatus_t cbstatus; - cuFloatComplex a1,a2; - a1.x=1.0f; a1.y=0.0f; - a2.x=0.0f; a2.y=0.0f; - cbstatus=cublasCgemm(cbhandle,CUBLAS_OP_C,CUBLAS_OP_N,2,2*ntime,2*N,&a1,x,2*N,etaloc,2*N,&a2,C,2); - checkCublasError(cbstatus,__FILE__,__LINE__); - - /* setup RHS matrices x^H Z - Z^H x */ - /* 2x2 matrix: 4 threads per block, ntime blocks */ - T=4; - kernel_fns_rhs_alltime<<< ntime, T, sizeof(cuFloatComplex)*T >>>(C); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - - /* now consider C as 4xntime matrix and multiply it with Ai */ - /* reuse etaloc first block, size needed 4 x ntime << 4N x ntime */ - cbstatus=cublasCgemm(cbhandle,CUBLAS_OP_N,CUBLAS_OP_N,4,ntime,4,&a1,Ai,4,C,4,&a2,etaloc,4); - checkCublasError(cbstatus,__FILE__,__LINE__); - - /* now average 2x 2ntime matrix etaloc to one 2x2 matrix, stoared at C */ - cudaMemset(C, 0, sizeof(cuFloatComplex)*4); - T=DEFAULT_TH_PER_BK_2; /* even number */ - for (int ci=0; ci<(ntime+T-1)/T; ci++) { - kernel_fns_sumelements_alltime<<< 4, T, sizeof(cuFloatComplex)*T >>>(ntime,ci*T, etaloc, C); - } -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - /* now find fhess = -1 x C + fhess => C = A B + C */ - a1.x=-1.0f; a1.y=0.0f; - a2.x=1.0f; a2.y=0.0f; - cbstatus=cublasCgemm(cbhandle,CUBLAS_OP_N,CUBLAS_OP_N,2*N,2,2,&a1,x,2*N,C,2,&a2,fhess,2*N); - checkCublasError(cbstatus,__FILE__,__LINE__); - - cudaFree(etaloc); -} - - -/* Robust Hessian (Euclidean) - output fhess: reset to 0 initially */ -/* need 8N*BlocksPerGrid float storage */ -void -cudakernel_fns_fhessflat_robust_admm(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, cuFloatComplex *fhess, float *y, float *coh, short *bbh, float *wtd, cublasHandle_t cbhandle) { -#ifdef CUDA_DBG - cudaError_t error; -#endif - cuFloatComplex *etaloc; - /* each block stores result in 2Nx2 block, so need BlocksPerGrid x 2Nx 2 storage */ - cudaMalloc((void**)&etaloc, sizeof(cuFloatComplex)*BlocksPerGrid*4*N); - cudaMemset(etaloc, 0, sizeof(cuFloatComplex)*BlocksPerGrid*4*N); - cudaMemset(fhess, 0, sizeof(cuFloatComplex)*4*N); - /* eachekernel require 2xThreadsPerBlocx2 x 2 complex float for storing eta values - 2*ThreadsPerBloc x1 int array for station numbers - */ - kernel_fns_fhess_robust<<< BlocksPerGrid, ThreadsPerBlock, sizeof(cuFloatComplex)*8*ThreadsPerBlock + sizeof(int)*2*ThreadsPerBlock >>>(N, M, x, eta, etaloc, y, coh, bbh, wtd); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - /* baselines */ - int nbase=N*(N-1)/2; - /* blocks per timeslot */ - int Bt=(nbase+ThreadsPerBlock-1)/ThreadsPerBlock; - /* timeslots */ - int ntime=(M+nbase-1)/nbase; - /* threads to use (need to use small value to enable enough shared memory) */ - int T=DEFAULT_TH_PER_BK_2; - /* sum Bt values each to the first value */ - for (int ci=0; ci<(4*N+T-1)/T; ci++) { - /* create blocks equal to timeslots, each will need Bt*T complex float storage, ci*T is the offset of 0...4N-1 values */ - /* each thread will sum Bt values */ - kernel_fns_sumblocks_pertime<<< ntime, T, sizeof(cuFloatComplex)*Bt*T >>>(N, Bt, ci*T, etaloc); -#ifdef DEBUG - printf("sum blocks %d, threads %d thread offset %d, numblocks/time %d\n",ntime,T,ci*T,Bt); -#endif - } - -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - /* now create 4N blocks, each block will sum elements in 0...4N-1 of ntime values, separated by Bt blocks */ - T=DEFAULT_TH_PER_BK_2; /* even number */ - for (int ci=0; ci<(ntime+T-1)/T; ci++) { - kernel_fns_sumblocks_alltime<<< 4*N, T, sizeof(cuFloatComplex)*T >>>(N, Bt, ntime, ci*T, etaloc, fhess); -#ifdef DEBUG - printf("sum all blocks %d, timeslots %d, threads %d block offset %d, spacing %d\n",4*N,ntime,T,ci*T,Bt); -#endif - } - -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - /* now etaloc : 4N x ntime (== 2N x 2ntime) blocks correspond to eta for each timeslot */ - T=DEFAULT_TH_PER_BK_2; /* even number */ - for (int ci=0; ci<(ntime+T-1)/T; ci++) { - kernel_fns_sumelements_alltime<<< 4, T, sizeof(cuFloatComplex)*T >>>(ntime,ci*T, etaloc, fhess); - } -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - cudaFree(etaloc); -} - - - -/* scale eta with weights wt - N stations - eta: 4Nx2 complex float - iw: N x 1 weights, per station -*/ -void -cudakernel_fns_fscale(int N, cuFloatComplex *eta, float *iw) { -#ifdef CUDA_DBG - cudaError_t error; -#endif - /* since N is small ~60, use small no. of threads per block */ - int T=32; - int B=(N+T-1)/T; - kernel_fns_fscale<<< T, B>>>(N, eta, iw); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif -} - - -/* - update weight vector (nu+1)/(nu+error^2): - N: no of stations - M: no of constraints (baselines) - x: solution 2Nx2 complex float - y: data 8M float (8 for each baseline) - coh: coherency - bbh: baseline->station mapping - wtd: weight Mx1 - -*/ -void -cudakernel_fns_fupdate_weights(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, float *y, float *coh, short *bbh, float *wtd, float nu0) { -#ifdef CUDA_DBG - cudaError_t error; -#endif - kernel_fns_fupdate_weights<<< BlocksPerGrid, ThreadsPerBlock >>>(N, M, x, y, coh, bbh, wtd, nu0); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif -} - -/* - update weight vector (nu+1)/(nu+error^2) and log(weight) : - N: no of stations - M: no of constraints (baselines) - x: solution 2Nx2 complex float - y: data 8M float (8 for each baseline) - coh: coherency - bbh: baseline->station mapping - wtd: weight Mx1 - qd: weight Mx1 -*/ -void -cudakernel_fns_fupdate_weights_q(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, float *y, float *coh, short *bbh, float *wtd, float *qd, float nu0) { -#ifdef CUDA_DBG - cudaError_t error; -#endif - kernel_fns_fupdate_weights_q<<< BlocksPerGrid, ThreadsPerBlock >>>(N, M, x, y, coh, bbh, wtd, qd, nu0); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif -} -} diff --git a/src/lib/Solvers/manifold_fl.o b/src/lib/Solvers/manifold_fl.o deleted file mode 100644 index 91da634861138dd019d5c45a6a188fd5d55105d3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 166944 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

8*01>~Vg6Rk=&;V2Blw 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 diff --git a/src/lib/Solvers/mderiv.cu b/src/lib/Solvers/mderiv.cu deleted file mode 100644 index da91f26..0000000 --- a/src/lib/Solvers/mderiv.cu +++ /dev/null @@ -1,1625 +0,0 @@ -/* - * - Copyright (C) 2006-2008 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - -#include "cuda.h" -#include -#include -#include "Solvers.h" - -/* enable this for checking for kernel failure */ -//#define CUDA_DBG - - -__global__ void -kernel_deriv(int Nbase, int tilesz, int M, int Ns, int Nparam, int goff, const double *__restrict__ x, const double *__restrict__ coh, const double *__restrict__ p, const short *__restrict__ bb, const int *__restrict__ ptoclus, double *__restrict__ grad){ - /* global thread index */ - unsigned int n = threadIdx.x + blockDim.x*blockIdx.x; - /* parameter number of this thread */ - unsigned int np=n+goff; - - - /* this thread works on - x[8*n:8*n+7], coh[8*M*n:8*M*n+8*M-1] - bb[2*n:2*n+1] (sta1,sta2) - organization of p (N stations and M clusters) - sta 0 sta 1 sta 2 .... sta N-1 - clus 0 0...7 8...15 16...23 ... 8N-8 8N-1 - clus 1 8N..8N+7 8N+8..8N+15 8N+16..8N+23 .... 8N+8N-8...8N+8N-1 - ...... - clus M-1 (M-1)N..(M-1)N+7 (M-1)N+8..(M-1)N+15.... ...(M-1)N+8N-8 (M-1)N+8N-1 - - organization of coherencies (coh) - [0, 8*M-1] : baseline 0 - [8*M, 8*M+8*M-1]: baseline 1 - [n*8*M, n*8*M+8*M-1]: baseline n - ...... - [n*8*M+cm*8, n*8*M+cm*8+7] cluster cm, baseline n - - residual error stored at sum[n] - */ - - if (nptoclus[2*cli+1]+ptoclus[2*cli]*8*Ns-1)) { cli++; } - /* now either ci>=M: cluster not found - or ci=ptoclus[2*cli-1] && np<=ptoclus[2*cli-1]+ptoclus[2*cli-2]*8*Ns-1) { - cli--; - } - - if (cli=0 && sta2>=0) { - /* which parameter 0..7 */ - unsigned int stoff=np_s-stc*8; - /* which cluster 0..M-1 */ - unsigned int stm=cli; - - /* read residual vector, conjugated */ - cuDoubleComplex xr[4]; - xr[0].x=x[nb*8]; - xr[0].y=-x[nb*8+1]; - xr[1].x=x[nb*8+2]; - xr[1].y=-x[nb*8+3]; - xr[2].x=x[nb*8+4]; - xr[2].y=-x[nb*8+5]; - xr[3].x=x[nb*8+6]; - xr[3].y=-x[nb*8+7]; - - /* read in coherency */ - cuDoubleComplex C[4]; - C[0].x=coh[8*nb*M+8*stm]; - C[0].y=coh[8*nb*M+8*stm+1]; - C[1].x=coh[8*nb*M+8*stm+2]; - C[1].y=coh[8*nb*M+8*stm+3]; - C[2].x=coh[8*nb*M+8*stm+4]; - C[2].y=coh[8*nb*M+8*stm+5]; - C[3].x=coh[8*nb*M+8*stm+6]; - C[3].y=coh[8*nb*M+8*stm+7]; - - cuDoubleComplex G1[4]; - cuDoubleComplex G2[4]; - double pp[8]; - pp[0]=0.0; - pp[1]=0.0; - pp[2]=0.0; - pp[3]=0.0; - pp[4]=0.0; - pp[5]=0.0; - pp[6]=0.0; - pp[7]=0.0; - - if(stc==sta1) { - pp[stoff]=1.0; - G1[0].x=pp[0]; - G1[0].y=pp[1]; - G1[1].x=pp[2]; - G1[1].y=pp[3]; - G1[2].x=pp[4]; - G1[2].y=pp[5]; - G1[3].x=pp[6]; - G1[3].y=pp[7]; - - /* conjugate and transpose G2 */ - G2[0].x=p[pstart+tpchunk*8*Ns+sta2*8]; - G2[0].y=-p[pstart+tpchunk*8*Ns+sta2*8+1]; - G2[2].x=p[pstart+tpchunk*8*Ns+sta2*8+2]; - G2[2].y=-p[pstart+tpchunk*8*Ns+sta2*8+3]; - G2[1].x=p[pstart+tpchunk*8*Ns+sta2*8+4]; - G2[1].y=-p[pstart+tpchunk*8*Ns+sta2*8+5]; - G2[3].x=p[pstart+tpchunk*8*Ns+sta2*8+6]; - G2[3].y=-p[pstart+tpchunk*8*Ns+sta2*8+7]; - } else if (stc==sta2) { - pp[stoff]=1.0; - /* conjugate and transpose G2 */ - G2[0].x=pp[0]; - G2[0].y=-pp[1]; - G2[2].x=pp[2]; - G2[2].y=-pp[3]; - G2[1].x=pp[4]; - G2[1].y=-pp[5]; - G2[3].x=pp[6]; - G2[3].y=-pp[7]; - - /* conjugate and transpose G2 */ - G1[0].x=p[pstart+tpchunk*8*Ns+sta1*8]; - G1[0].y=p[pstart+tpchunk*8*Ns+sta1*8+1]; - G1[1].x=p[pstart+tpchunk*8*Ns+sta1*8+2]; - G1[1].y=p[pstart+tpchunk*8*Ns+sta1*8+3]; - G1[2].x=p[pstart+tpchunk*8*Ns+sta1*8+4]; - G1[2].y=p[pstart+tpchunk*8*Ns+sta1*8+5]; - G1[3].x=p[pstart+tpchunk*8*Ns+sta1*8+6]; - G1[3].y=p[pstart+tpchunk*8*Ns+sta1*8+7]; - } - cuDoubleComplex T1[4]; - /* T1=G1*C */ - T1[0]=cuCadd(cuCmul(G1[0],C[0]),cuCmul(G1[1],C[2])); - T1[1]=cuCadd(cuCmul(G1[0],C[1]),cuCmul(G1[1],C[3])); - T1[2]=cuCadd(cuCmul(G1[2],C[0]),cuCmul(G1[3],C[2])); - T1[3]=cuCadd(cuCmul(G1[2],C[1]),cuCmul(G1[3],C[3])); - - cuDoubleComplex T2[4]; - /* T2=T1*G2 , G2 conjugate transposed */ - T2[0]=cuCadd(cuCmul(T1[0],G2[0]),cuCmul(T1[1],G2[2])); - T2[1]=cuCadd(cuCmul(T1[0],G2[1]),cuCmul(T1[1],G2[3])); - T2[2]=cuCadd(cuCmul(T1[2],G2[0]),cuCmul(T1[3],G2[2])); - T2[3]=cuCadd(cuCmul(T1[2],G2[1]),cuCmul(T1[3],G2[3])); - - /* calculate product xr*vec(J_p C J_q^H ) */ - cuDoubleComplex csum; - csum=cuCmul(xr[0],T2[0]); - csum=cuCadd(csum,cuCmul(xr[1],T2[1])); - csum=cuCadd(csum,cuCmul(xr[2],T2[2])); - csum=cuCadd(csum,cuCmul(xr[3],T2[3])); - - - - gsum+=-2.0*csum.x; - } - - } - - } - } - - - grad[n]=gsum; - } - -} - - -/* note x is residual, not data */ -__global__ void -kernel_deriv_r_robust(int Nbase, int tilesz, int M, int Ns, int Nparam, int goff, const double *__restrict__ x, const double *__restrict__ coh, const double *__restrict__ p, const short *__restrict__ bb, const int *__restrict__ ptoclus, double *__restrict__ grad, double robust_nu){ - /* global thread index */ - unsigned int n = threadIdx.x + blockDim.x*blockIdx.x; - /* parameter number of this thread */ - unsigned int np=n+goff; - - - /* this thread works on - x[8*n:8*n+7], coh[8*M*n:8*M*n+8*M-1] - bb[2*n:2*n+1] (sta1,sta2) - organization of p (N stations and M clusters) - sta 0 sta 1 sta 2 .... sta N-1 - clus 0 0...7 8...15 16...23 ... 8N-8 8N-1 - clus 1 8N..8N+7 8N+8..8N+15 8N+16..8N+23 .... 8N+8N-8...8N+8N-1 - ...... - clus M-1 (M-1)N..(M-1)N+7 (M-1)N+8..(M-1)N+15.... ...(M-1)N+8N-8 (M-1)N+8N-1 - - organization of coherencies (coh) - [0, 8*M-1] : baseline 0 - [8*M, 8*M+8*M-1]: baseline 1 - [n*8*M, n*8*M+8*M-1]: baseline n - ...... - [n*8*M+cm*8, n*8*M+cm*8+7] cluster cm, baseline n - - residual error stored at sum[n] - */ - - if (nptoclus[2*cli+1]+ptoclus[2*cli]*8*Ns-1)) { cli++; } - /* now either ci>=M: cluster not found - or ci=ptoclus[2*cli-1] && np<=ptoclus[2*cli-1]+ptoclus[2*cli-2]*8*Ns-1) { - cli--; - } - - if (cli=0 && sta2>=0) { - /* which parameter 0..7 */ - unsigned int stoff=np_s-stc*8; - /* which cluster 0..M-1 */ - unsigned int stm=cli; - - /* read residual vector */ - double xr[8]; - xr[0]=x[nb*8]; - xr[1]=x[nb*8+1]; - xr[2]=x[nb*8+2]; - xr[3]=x[nb*8+3]; - xr[4]=x[nb*8+4]; - xr[5]=x[nb*8+5]; - xr[6]=x[nb*8+6]; - xr[7]=x[nb*8+7]; - - /* read in coherency */ - cuDoubleComplex C[4]; - C[0].x=coh[8*nb*M+8*stm]; - C[0].y=coh[8*nb*M+8*stm+1]; - C[1].x=coh[8*nb*M+8*stm+2]; - C[1].y=coh[8*nb*M+8*stm+3]; - C[2].x=coh[8*nb*M+8*stm+4]; - C[2].y=coh[8*nb*M+8*stm+5]; - C[3].x=coh[8*nb*M+8*stm+6]; - C[3].y=coh[8*nb*M+8*stm+7]; - - cuDoubleComplex G1[4]; - cuDoubleComplex G2[4]; - cuDoubleComplex T1[4]; - cuDoubleComplex T2[4]; - - G1[0].x=p[pstart+tpchunk*8*Ns+sta1*8]; - G1[0].y=p[pstart+tpchunk*8*Ns+sta1*8+1]; - G1[1].x=p[pstart+tpchunk*8*Ns+sta1*8+2]; - G1[1].y=p[pstart+tpchunk*8*Ns+sta1*8+3]; - G1[2].x=p[pstart+tpchunk*8*Ns+sta1*8+4]; - G1[2].y=p[pstart+tpchunk*8*Ns+sta1*8+5]; - G1[3].x=p[pstart+tpchunk*8*Ns+sta1*8+6]; - G1[3].y=p[pstart+tpchunk*8*Ns+sta1*8+7]; - /* conjugate and transpose G2 */ - G2[0].x=p[pstart+tpchunk*8*Ns+sta2*8]; - G2[0].y=-p[pstart+tpchunk*8*Ns+sta2*8+1]; - G2[2].x=p[pstart+tpchunk*8*Ns+sta2*8+2]; - G2[2].y=-p[pstart+tpchunk*8*Ns+sta2*8+3]; - G2[1].x=p[pstart+tpchunk*8*Ns+sta2*8+4]; - G2[1].y=-p[pstart+tpchunk*8*Ns+sta2*8+5]; - G2[3].x=p[pstart+tpchunk*8*Ns+sta2*8+6]; - G2[3].y=-p[pstart+tpchunk*8*Ns+sta2*8+7]; - - double pp[8]; - pp[0]=0.0; - pp[1]=0.0; - pp[2]=0.0; - pp[3]=0.0; - pp[4]=0.0; - pp[5]=0.0; - pp[6]=0.0; - pp[7]=0.0; - - pp[stoff]=1.0; - if(stc==sta1) { - G1[0].x=pp[0]; - G1[0].y=pp[1]; - G1[1].x=pp[2]; - G1[1].y=pp[3]; - G1[2].x=pp[4]; - G1[2].y=pp[5]; - G1[3].x=pp[6]; - G1[3].y=pp[7]; - } else if (stc==sta2) { - /* conjugate and transpose G2 */ - G2[0].x=pp[0]; - G2[0].y=-pp[1]; - G2[2].x=pp[2]; - G2[2].y=-pp[3]; - G2[1].x=pp[4]; - G2[1].y=-pp[5]; - G2[3].x=pp[6]; - G2[3].y=-pp[7]; - } - /* T1=G1*C */ - T1[0]=cuCadd(cuCmul(G1[0],C[0]),cuCmul(G1[1],C[2])); - T1[1]=cuCadd(cuCmul(G1[0],C[1]),cuCmul(G1[1],C[3])); - T1[2]=cuCadd(cuCmul(G1[2],C[0]),cuCmul(G1[3],C[2])); - T1[3]=cuCadd(cuCmul(G1[2],C[1]),cuCmul(G1[3],C[3])); - - /* T2=T1*G2 , G2 conjugate transposed */ - T2[0]=cuCadd(cuCmul(T1[0],G2[0]),cuCmul(T1[1],G2[2])); - T2[1]=cuCadd(cuCmul(T1[0],G2[1]),cuCmul(T1[1],G2[3])); - T2[2]=cuCadd(cuCmul(T1[2],G2[0]),cuCmul(T1[3],G2[2])); - T2[3]=cuCadd(cuCmul(T1[2],G2[1]),cuCmul(T1[3],G2[3])); - - - /* calculate product xr*vec(J_p C J_q^H )/(nu+residual^2) */ - double dsum; - dsum=xr[0]*T2[0].x/(robust_nu+xr[0]*xr[0]); - dsum+=xr[1]*T2[0].y/(robust_nu+xr[1]*xr[1]); - dsum+=xr[2]*T2[1].x/(robust_nu+xr[2]*xr[2]); - dsum+=xr[3]*T2[1].y/(robust_nu+xr[3]*xr[3]); - dsum+=xr[4]*T2[2].x/(robust_nu+xr[4]*xr[4]); - dsum+=xr[5]*T2[2].y/(robust_nu+xr[5]*xr[5]); - dsum+=xr[6]*T2[3].x/(robust_nu+xr[6]*xr[6]); - dsum+=xr[7]*T2[3].y/(robust_nu+xr[7]*xr[7]); - /* accumulate sum NOTE - its important to get the sign right, - depending on res=data-model or res=model-data */ - gsum+=-2.0*dsum; - - } - - } - - } - } - - - grad[n]=gsum; - } - -} - - -/* note x is residual, not data */ -__global__ void -kernel_deriv_r(int Nbase, int tilesz, int M, int Ns, int Nparam, int goff, const double *__restrict__ x, const double *__restrict__ coh, const double *__restrict__ p, const short *__restrict__ bb, const int *__restrict__ ptoclus, double *__restrict__ grad){ - /* global thread index */ - unsigned int n = threadIdx.x + blockDim.x*blockIdx.x; - /* parameter number of this thread */ - unsigned int np=n+goff; - - - /* this thread works on - x[8*n:8*n+7], coh[8*M*n:8*M*n+8*M-1] - bb[2*n:2*n+1] (sta1,sta2) - organization of p (N stations and M clusters) - sta 0 sta 1 sta 2 .... sta N-1 - clus 0 0...7 8...15 16...23 ... 8N-8 8N-1 - clus 1 8N..8N+7 8N+8..8N+15 8N+16..8N+23 .... 8N+8N-8...8N+8N-1 - ...... - clus M-1 (M-1)N..(M-1)N+7 (M-1)N+8..(M-1)N+15.... ...(M-1)N+8N-8 (M-1)N+8N-1 - - organization of coherencies (coh) - [0, 8*M-1] : baseline 0 - [8*M, 8*M+8*M-1]: baseline 1 - [n*8*M, n*8*M+8*M-1]: baseline n - ...... - [n*8*M+cm*8, n*8*M+cm*8+7] cluster cm, baseline n - - residual error stored at sum[n] - */ - - if (nptoclus[2*cli+1]+ptoclus[2*cli]*8*Ns-1)) { cli++; } - /* now either ci>=M: cluster not found - or ci=ptoclus[2*cli-1] && np<=ptoclus[2*cli-1]+ptoclus[2*cli-2]*8*Ns-1) { - cli--; - } - - if (cli=0 && sta2>=0) { - /* which parameter 0..7 */ - unsigned int stoff=np_s-stc*8; - /* which cluster 0..M-1 */ - unsigned int stm=cli; - - /* read residual vector, conjugated */ - cuDoubleComplex xr[4]; - xr[0].x=x[nb*8]; - xr[0].y=-x[nb*8+1]; - xr[1].x=x[nb*8+2]; - xr[1].y=-x[nb*8+3]; - xr[2].x=x[nb*8+4]; - xr[2].y=-x[nb*8+5]; - xr[3].x=x[nb*8+6]; - xr[3].y=-x[nb*8+7]; - - /* read in coherency */ - cuDoubleComplex C[4]; - C[0].x=coh[8*nb*M+8*stm]; - C[0].y=coh[8*nb*M+8*stm+1]; - C[1].x=coh[8*nb*M+8*stm+2]; - C[1].y=coh[8*nb*M+8*stm+3]; - C[2].x=coh[8*nb*M+8*stm+4]; - C[2].y=coh[8*nb*M+8*stm+5]; - C[3].x=coh[8*nb*M+8*stm+6]; - C[3].y=coh[8*nb*M+8*stm+7]; - - cuDoubleComplex G1[4]; - cuDoubleComplex G2[4]; - cuDoubleComplex T1[4]; - cuDoubleComplex T2[4]; - - G1[0].x=p[pstart+tpchunk*8*Ns+sta1*8]; - G1[0].y=p[pstart+tpchunk*8*Ns+sta1*8+1]; - G1[1].x=p[pstart+tpchunk*8*Ns+sta1*8+2]; - G1[1].y=p[pstart+tpchunk*8*Ns+sta1*8+3]; - G1[2].x=p[pstart+tpchunk*8*Ns+sta1*8+4]; - G1[2].y=p[pstart+tpchunk*8*Ns+sta1*8+5]; - G1[3].x=p[pstart+tpchunk*8*Ns+sta1*8+6]; - G1[3].y=p[pstart+tpchunk*8*Ns+sta1*8+7]; - /* conjugate and transpose G2 */ - G2[0].x=p[pstart+tpchunk*8*Ns+sta2*8]; - G2[0].y=-p[pstart+tpchunk*8*Ns+sta2*8+1]; - G2[2].x=p[pstart+tpchunk*8*Ns+sta2*8+2]; - G2[2].y=-p[pstart+tpchunk*8*Ns+sta2*8+3]; - G2[1].x=p[pstart+tpchunk*8*Ns+sta2*8+4]; - G2[1].y=-p[pstart+tpchunk*8*Ns+sta2*8+5]; - G2[3].x=p[pstart+tpchunk*8*Ns+sta2*8+6]; - G2[3].y=-p[pstart+tpchunk*8*Ns+sta2*8+7]; - - double pp[8]; - pp[0]=0.0; - pp[1]=0.0; - pp[2]=0.0; - pp[3]=0.0; - pp[4]=0.0; - pp[5]=0.0; - pp[6]=0.0; - pp[7]=0.0; - - pp[stoff]=1.0; - if(stc==sta1) { - G1[0].x=pp[0]; - G1[0].y=pp[1]; - G1[1].x=pp[2]; - G1[1].y=pp[3]; - G1[2].x=pp[4]; - G1[2].y=pp[5]; - G1[3].x=pp[6]; - G1[3].y=pp[7]; - } else if (stc==sta2) { - /* conjugate and transpose G2 */ - G2[0].x=pp[0]; - G2[0].y=-pp[1]; - G2[2].x=pp[2]; - G2[2].y=-pp[3]; - G2[1].x=pp[4]; - G2[1].y=-pp[5]; - G2[3].x=pp[6]; - G2[3].y=-pp[7]; - } - /* T1=G1*C */ - T1[0]=cuCadd(cuCmul(G1[0],C[0]),cuCmul(G1[1],C[2])); - T1[1]=cuCadd(cuCmul(G1[0],C[1]),cuCmul(G1[1],C[3])); - T1[2]=cuCadd(cuCmul(G1[2],C[0]),cuCmul(G1[3],C[2])); - T1[3]=cuCadd(cuCmul(G1[2],C[1]),cuCmul(G1[3],C[3])); - - /* T2=T1*G2 , G2 conjugate transposed */ - T2[0]=cuCadd(cuCmul(T1[0],G2[0]),cuCmul(T1[1],G2[2])); - T2[1]=cuCadd(cuCmul(T1[0],G2[1]),cuCmul(T1[1],G2[3])); - T2[2]=cuCadd(cuCmul(T1[2],G2[0]),cuCmul(T1[3],G2[2])); - T2[3]=cuCadd(cuCmul(T1[2],G2[1]),cuCmul(T1[3],G2[3])); - - - /* calculate product xr*vec(J_p C J_q^H ) */ - cuDoubleComplex csum; - csum=cuCmul(xr[0],T2[0]); - csum=cuCadd(csum,cuCmul(xr[1],T2[1])); - csum=cuCadd(csum,cuCmul(xr[2],T2[2])); - csum=cuCadd(csum,cuCmul(xr[3],T2[3])); - - - /* notice no -ve sign */ - gsum+=2.0*csum.x; - } - - } - - } - } - - - grad[n]=gsum; - } - -} - - -__global__ void -kernel_residual(int Nbase, int M, int Ns, const double *__restrict__ x, const double *__restrict__ coh, const double *__restrict__ p, const short *__restrict__ bb, const int *__restrict__ ptoclus, double *__restrict__ ed){ - - /* global thread index */ - unsigned int n = threadIdx.x + blockDim.x*blockIdx.x; - - if (n=0 && sta2>=0) { - /* read data vector */ - cuDoubleComplex xr[4]; - xr[0].x=x[n*8]; - xr[0].y=x[n*8+1]; - xr[1].x=x[n*8+2]; - xr[1].y=x[n*8+3]; - xr[2].x=x[n*8+4]; - xr[2].y=x[n*8+5]; - xr[3].x=x[n*8+6]; - xr[3].y=x[n*8+7]; - - for (int cm=0; cm=0 && sta2>=0) { - /* read data vector */ - cuDoubleComplex xr[4]; - xr[0].x=x[n*8]; - xr[0].y=x[n*8+1]; - xr[1].x=x[n*8+2]; - xr[1].y=x[n*8+3]; - xr[2].x=x[n*8+4]; - xr[2].y=x[n*8+5]; - xr[3].x=x[n*8+6]; - xr[3].y=x[n*8+7]; - - - for (int cm=0; cm0; s=s/2) { - if(tid < s) ek[tid] += ek[tid + s]; - __syncthreads(); - } - - /* copy back to global array */ - if(tid==0) { - ed[blockIdx.x]=ek[0]; - } - -} - - -__global__ void -kernel_fcost(int Nbase, int boff, int M, int Ns, int Nbasetotal, const double *__restrict__ x, const double *__restrict__ coh, const double *__restrict__ p, const short *__restrict__ bb, const int *__restrict__ ptoclus, double *__restrict__ ed){ - /* shared memory */ - extern __shared__ double ek[]; - - /* global thread index */ - unsigned int n = threadIdx.x + blockDim.x*blockIdx.x; - int tid=threadIdx.x; - ek[tid]=0.0; - - if (n=0 && sta2>=0) { - /* read data vector */ - cuDoubleComplex xr[4]; - xr[0].x=x[n*8]; - xr[0].y=x[n*8+1]; - xr[1].x=x[n*8+2]; - xr[1].y=x[n*8+3]; - xr[2].x=x[n*8+4]; - xr[2].y=x[n*8+5]; - xr[3].x=x[n*8+6]; - xr[3].y=x[n*8+7]; - - - for (int cm=0; cm0; s=s/2) { - if(tid < s) ek[tid] += ek[tid + s]; - __syncthreads(); - } - - /* copy back to global array */ - if(tid==0) { - ed[blockIdx.x]=ek[0]; - } - -} - - -__global__ void -kernel_diagdiv(int M, double eps, double *__restrict__ y,const double *__restrict__ x){ - unsigned int tid = blockIdx.x*blockDim.x + threadIdx.x; - /* make sure to use only M threads */ - if (tideps) { - y[tid]=y[tid]/x[tid]; - } else { - y[tid]=0.0; - } - } -} - -__global__ void -kernel_diagmu(int M, double *__restrict__ A,double mu){ - unsigned int tid = blockIdx.x*blockDim.x + threadIdx.x; - /* make sure to use only M threads */ - if (tid=0 - */ - if (sta1>=0 && sta2>=0) { - cuDoubleComplex G1[4]; - double pp[8]; - pp[0]=p[sta1*8]; - pp[1]=p[sta1*8+1]; - pp[2]=p[sta1*8+2]; - pp[3]=p[sta1*8+3]; - pp[4]=p[sta1*8+4]; - pp[5]=p[sta1*8+5]; - pp[6]=p[sta1*8+6]; - pp[7]=p[sta1*8+7]; - G1[0].x=pp[0]; - G1[0].y=pp[1]; - G1[1].x=pp[2]; - G1[1].y=pp[3]; - G1[2].x=pp[4]; - G1[2].y=pp[5]; - G1[3].x=pp[6]; - G1[3].y=pp[7]; - - - cuDoubleComplex C[4]; - C[0].x=coh[8*n]; - C[0].y=coh[8*n+1]; - C[1].x=coh[8*n+2]; - C[1].y=coh[8*n+3]; - C[2].x=coh[8*n+4]; - C[2].y=coh[8*n+5]; - C[3].x=coh[8*n+6]; - C[3].y=coh[8*n+7]; - - cuDoubleComplex T1[4]; - /* T=G1*C */ - T1[0]=cuCadd(cuCmul(G1[0],C[0]),cuCmul(G1[1],C[2])); - T1[1]=cuCadd(cuCmul(G1[0],C[1]),cuCmul(G1[1],C[3])); - T1[2]=cuCadd(cuCmul(G1[2],C[0]),cuCmul(G1[3],C[2])); - T1[3]=cuCadd(cuCmul(G1[2],C[1]),cuCmul(G1[3],C[3])); - - cuDoubleComplex G2[4]; - /* conjugate this */ - pp[0]=p[sta2*8]; - pp[1]=-p[sta2*8+1]; - pp[2]=p[sta2*8+2]; - pp[3]=-p[sta2*8+3]; - pp[4]=p[sta2*8+4]; - pp[5]=-p[sta2*8+5]; - pp[6]=p[sta2*8+6]; - pp[7]=-p[sta2*8+7]; - G2[0].x=pp[0]; - G2[0].y=pp[1]; - G2[2].x=pp[2]; - G2[2].y=pp[3]; - G2[1].x=pp[4]; - G2[1].y=pp[5]; - G2[3].x=pp[6]; - G2[3].y=pp[7]; - - cuDoubleComplex T2[4]; - T2[0]=cuCadd(cuCmul(T1[0],G2[0]),cuCmul(T1[1],G2[2])); - T2[1]=cuCadd(cuCmul(T1[0],G2[1]),cuCmul(T1[1],G2[3])); - T2[2]=cuCadd(cuCmul(T1[2],G2[0]),cuCmul(T1[3],G2[2])); - T2[3]=cuCadd(cuCmul(T1[2],G2[1]),cuCmul(T1[3],G2[3])); - /* update model vector */ - x[8*n]=T2[0].x; - x[8*n+1]=T2[0].y; - x[8*n+2]=T2[1].x; - x[8*n+3]=T2[1].y; - x[8*n+4]=T2[2].x; - x[8*n+5]=T2[2].y; - x[8*n+6]=T2[3].x; - x[8*n+7]=T2[3].y; - - } - } - -} - -__global__ void -kernel_jacf(int Nbase, int M, double *__restrict__ jac, const double *__restrict__ coh, const double *__restrict__ p, const short *__restrict__ bb, int N){ - /* global thread index : equal to the baseline */ - unsigned int n = threadIdx.x + blockDim.x*blockIdx.x; - /* which parameter:0...M */ - unsigned int m = threadIdx.y + blockDim.y*blockIdx.y; - - /* this thread works on - x[8*n:8*n+7], coh[8*M*n:8*M*n+8*M-1] - bb[2*n:2*n+1] (sta1,sta2) - organization of p (N stations and M clusters) - sta 0 sta 1 sta 2 .... sta N-1 - clus 0 0...7 8...15 16...23 ... 8N-8 8N-1 - clus 1 8N..8N+7 8N+8..8N+15 8N+16..8N+23 .... 8N+8N-8...8N+8N-1 - ...... - clus M-1 (M-1)N..(M-1)N+7 (M-1)N+8..(M-1)N+15.... ...(M-1)N+8N-8 (M-1)N+8N-1 - - organization of coherencies (coh) - [0, 8*M-1] : baseline 0 - [8*M, 8*M+8*M-1]: baseline 1 - [n*8*M, n*8*M+8*M-1]: baseline n - ...... - [n*8*M+cm*8, n*8*M+cm*8+7] cluster cm, baseline n - - residual error stored at sum[n] - */ - - if(n>3; /* 0...Ns-1 (because M=total par= 8 * Nstations */ - - if (((stc==sta2)||(stc==sta1)) && sta1>=0 && sta2>=0 ) { - - cuDoubleComplex C[4]; - C[0].x=coh[8*n]; - C[0].y=coh[8*n+1]; - C[1].x=coh[8*n+2]; - C[1].y=coh[8*n+3]; - C[2].x=coh[8*n+4]; - C[2].y=coh[8*n+5]; - C[3].x=coh[8*n+6]; - C[3].y=coh[8*n+7]; - - /* which parameter exactly 0..7 */ - //int stoff=m%8; - int stoff=m-stc*8; - double pp1[8]; - double pp2[8]; - if (stc==sta1) { - for (int cn=0; cn<8; cn++) { - pp1[cn]=0.0; - pp2[cn]=p[sta2*8+cn]; - } - pp1[stoff]=1.0; - } else if (stc==sta2) { - for (int cn=0; cn<8; cn++) { - pp2[cn]=0.0; - pp1[cn]=p[sta1*8+cn]; - } - pp2[stoff]=1.0; - } - - - cuDoubleComplex G1[4]; - G1[0].x=pp1[0]; - G1[0].y=pp1[1]; - G1[1].x=pp1[2]; - G1[1].y=pp1[3]; - G1[2].x=pp1[4]; - G1[2].y=pp1[5]; - G1[3].x=pp1[6]; - G1[3].y=pp1[7]; - - cuDoubleComplex T1[4]; - /* T=G1*C */ - T1[0]=cuCadd(cuCmul(G1[0],C[0]),cuCmul(G1[1],C[2])); - T1[1]=cuCadd(cuCmul(G1[0],C[1]),cuCmul(G1[1],C[3])); - T1[2]=cuCadd(cuCmul(G1[2],C[0]),cuCmul(G1[3],C[2])); - T1[3]=cuCadd(cuCmul(G1[2],C[1]),cuCmul(G1[3],C[3])); - - cuDoubleComplex G2[4]; - /* conjugate this */ - G2[0].x=pp2[0]; - G2[0].y=-pp2[1]; - G2[2].x=pp2[2]; - G2[2].y=-pp2[3]; - G2[1].x=pp2[4]; - G2[1].y=-pp2[5]; - G2[3].x=pp2[6]; - G2[3].y=-pp2[7]; - - cuDoubleComplex T2[4]; - T2[0]=cuCadd(cuCmul(T1[0],G2[0]),cuCmul(T1[1],G2[2])); - T2[1]=cuCadd(cuCmul(T1[0],G2[1]),cuCmul(T1[1],G2[3])); - T2[2]=cuCadd(cuCmul(T1[2],G2[0]),cuCmul(T1[3],G2[2])); - T2[3]=cuCadd(cuCmul(T1[2],G2[1]),cuCmul(T1[3],G2[3])); - /* update jacobian */ - /* NOTE: row major order */ - jac[m+M*8*n]=T2[0].x; - jac[m+M*(8*n+1)]=T2[0].y; - jac[m+M*(8*n+2)]=T2[1].x; - jac[m+M*(8*n+3)]=T2[1].y; - jac[m+M*(8*n+4)]=T2[2].x; - jac[m+M*(8*n+5)]=T2[2].y; - jac[m+M*(8*n+6)]=T2[3].x; - jac[m+M*(8*n+7)]=T2[3].y; - - } - } - -} - - -/* sum up all N elements of vector input - and save (per block) in output (size > number of blocks) */ -__global__ void -plus_reduce_multi(const double *__restrict__ input, int N, int blockDim_2, double *__restrict__ output) { - // Each block loads its elements into shared memory - extern __shared__ double x[]; - int tid = threadIdx.x; - int i = blockIdx.x*blockDim.x + threadIdx.x; - x[tid] = (i 1) { - int halfPoint = (nTotalThreads >> 1); // divide by two - if (tid < halfPoint) { - int thread2 = tid + halfPoint; - if (thread2 < blockDim.x) { // Skipping the fictitious threads blockDim.x ... blockDim_2-1 - x[tid] = x[tid]+x[thread2]; - } - } - __syncthreads(); - nTotalThreads = halfPoint; // Reducing the binary tree size by two - } - - /* add back to total */ - if( tid == 0 ) { - output[blockIdx.x]=x[tid]; - } -} - -/* sum up all N elements of vector input - NOTE: only 1 block should be used */ -__global__ void -plus_reduce(const double *__restrict__ input, int N, int blockDim_2, double *total) { - // Each block loads its elements into shared memory - extern __shared__ double x[]; - int tid = threadIdx.x; - int i = blockIdx.x*blockDim.x + threadIdx.x; - x[tid] = (i 1) { - int halfPoint = (nTotalThreads >> 1); // divide by two - if (tid < halfPoint) { - int thread2 = tid + halfPoint; - if (thread2 < blockDim.x) { // Skipping the fictitious threads blockDim.x ... blockDim_2-1 - x[tid] = x[tid]+x[thread2]; - } - } - __syncthreads(); - nTotalThreads = halfPoint; // Reducing the binary tree size by two - } - - /* add back to total */ - if( tid == 0 ) { - *total=*total+x[tid]; - } -} - - -/* only use extern if calling code is C */ -extern "C" -{ - - -static void -checkCudaError(cudaError_t err, const char *file, int line) -{ - -#ifdef CUDA_DEBUG - if(!err) - return; - fprintf(stderr,"GPU (CUDA): %s %s %d\n", cudaGetErrorString(err),file,line); - exit(EXIT_FAILURE); -#endif -} - -/* need power of 2 for tree reduction to work */ -static int -NearestPowerOf2 (int n){ - if (!n) return n; //(0 == 2^0) - - int x = 1; - while(x < n) { - x <<= 1; - } - return x; -} - - -/* cuda driver for kernel */ -/* ThreadsPerBlock: keep <= 128 ??? - BlocksPerGrid: depends on the threads/baselines> Threads*Blocks approx baselines - Nbase: no of baselines (total, including tilesz >1) - tilesz: tile size - M: no of clusters - Ns: no of stations - Nparam: no of actual parameters <=total - goff: starting point of gradient calculation 0..Nparams - x: N*8 x 1 residual - coh: N*8*M x 1 - p: M*Ns*8 x 1 - bb: 2*N x 1 - ptoclus: 2*M x 1 - - grad: Nparamsx1 gradient values -*/ -void -cudakernel_lbfgs(int ThreadsPerBlock, int BlocksPerGrid, int Nbase, int tilesz, int M, int Ns, int Nparam, int goff, double *x, double *coh, double *p, short *bb, int *ptoclus, double *grad){ - -#ifdef CUDA_DBG - cudaError_t error; -#endif - /* invoke device on this block/thread grid (last argument is buffer size in bytes) */ - kernel_deriv<<< BlocksPerGrid, ThreadsPerBlock, ThreadsPerBlock*sizeof(double) >>> (Nbase, tilesz, M, Ns, Nparam, goff, x, coh, p, bb, ptoclus, grad); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - -} - -void -cudakernel_lbfgs_r_robust(int ThreadsPerBlock, int BlocksPerGrid, int Nbase, int tilesz, int M, int Ns, int Nparam, int goff, double *x, double *coh, double *p, short *bb, int *ptoclus, double *grad, double robust_nu){ - - cudaError_t error; - /* invoke kernel to calculate residuals first */ - double *eo; - if((error=cudaMalloc((void**)&eo, Nbase*8*sizeof(double)))!=cudaSuccess) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - cudaMemset(eo, 0, sizeof(double)*Nbase*8); - checkCudaError(error,__FILE__,__LINE__); - - int L=(Nbase+ThreadsPerBlock-1)/ThreadsPerBlock; -#ifdef CUDA_DBG - error = cudaGetLastError(); /* reset all previous errors */ -#endif - - kernel_residual<<< L, ThreadsPerBlock >>> (Nbase, M, Ns, x, coh, p, bb, ptoclus, eo); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - /* invoke device on this block/thread grid (last argument is buffer size in bytes) */ - kernel_deriv_r_robust<<< BlocksPerGrid, ThreadsPerBlock >>> (Nbase, tilesz, M, Ns, Nparam, goff, eo, coh, p, bb, ptoclus, grad, robust_nu); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - cudaFree(eo); -} - -void -cudakernel_lbfgs_r(int ThreadsPerBlock, int BlocksPerGrid, int Nbase, int tilesz, int M, int Ns, int Nparam, int goff, double *x, double *coh, double *p, short *bb, int *ptoclus, double *grad){ - - cudaError_t error; - /* invoke kernel to calculate residuals first */ - double *eo; - if((error=cudaMalloc((void**)&eo, Nbase*8*sizeof(double)))!=cudaSuccess) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - cudaMemset(eo, 0, sizeof(double)*Nbase*8); - checkCudaError(error,__FILE__,__LINE__); - int L=(Nbase+ThreadsPerBlock-1)/ThreadsPerBlock; - -#ifdef CUDA_DBG - error = cudaGetLastError(); /* reset all previous errors */ -#endif - - kernel_residual<<< L, ThreadsPerBlock >>> (Nbase, M, Ns, x, coh, p, bb, ptoclus, eo); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - /* invoke device on this block/thread grid (last argument is buffer size in bytes) */ - kernel_deriv_r<<< BlocksPerGrid, ThreadsPerBlock >>> (Nbase, tilesz, M, Ns, Nparam, goff, eo, coh, p, bb, ptoclus, grad); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - cudaFree(eo); -} - -/* note x,coh and bb are with the right offset */ -double -cudakernel_lbfgs_cost_robust(int ThreadsPerBlock, int BlocksPerGrid, int Nbase, int boff, int M, int Ns, int Nbasetotal, double *x, double *coh, double *p, short *bb, int *ptoclus, double robust_nu){ - - double *ed; - cudaError_t error; - if((error=cudaMalloc((void**)&ed, sizeof(double)*BlocksPerGrid))!=cudaSuccess) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - - cudaMemset(ed, 0, sizeof(double)*BlocksPerGrid); - kernel_fcost_robust<<< BlocksPerGrid, ThreadsPerBlock, ThreadsPerBlock*sizeof(double) >>> (Nbase, boff, M, Ns, Nbasetotal, x, coh, p, bb, ptoclus, ed, 1.0/robust_nu); - -#ifdef CUDA_DBG - error = cudaGetLastError(); - checkCudaError(error,__FILE__,__LINE__); -#endif - - int T=DEFAULT_TH_PER_BK; - double *totald,total; - if((error=cudaMalloc((void**)&totald, sizeof(double)))!=cudaSuccess) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - cudaMemset(totald, 0, sizeof(double)); - checkCudaError(error,__FILE__,__LINE__); - - - if (T>BlocksPerGrid) { - /* one kernel launch is enough */ - plus_reduce<<< 1, BlocksPerGrid, sizeof(double)*BlocksPerGrid>>>(ed, BlocksPerGrid, NearestPowerOf2(BlocksPerGrid), totald); -#ifdef CUDA_DBG - error = cudaGetLastError(); - checkCudaError(error,__FILE__,__LINE__); -#endif - } else { - /* multiple kernel launches */ - int L=(BlocksPerGrid+T-1)/T; - double *eo; - if((error=cudaMalloc((void**)&eo, L*sizeof(double)))!=cudaSuccess) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - plus_reduce_multi<<< L, T, sizeof(double)*T>>>(ed, BlocksPerGrid, NearestPowerOf2(T), eo); - -#ifdef CUDA_DBG - error = cudaGetLastError(); - checkCudaError(error,__FILE__,__LINE__); -#endif - plus_reduce<<< 1, L, sizeof(double)*L>>>(eo, L, NearestPowerOf2(L), totald); -#ifdef CUDA_DBG - error = cudaGetLastError(); - checkCudaError(error,__FILE__,__LINE__); -#endif - cudaFree(eo); - } - cudaMemcpy(&total,totald,sizeof(double),cudaMemcpyDeviceToHost); - cudaFree(totald); - cudaFree(ed); - - return total; -} - -double -cudakernel_lbfgs_cost(int ThreadsPerBlock, int BlocksPerGrid, int Nbase, int boff, int M, int Ns, int Nbasetotal, double *x, double *coh, double *p, short *bb, int *ptoclus){ - double *ed; - cudaError_t error; - if((error=cudaMalloc((void**)&ed, sizeof(double)*BlocksPerGrid))!=cudaSuccess) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - cudaMemset(ed, 0, sizeof(double)*BlocksPerGrid); - kernel_fcost<<< BlocksPerGrid, ThreadsPerBlock, ThreadsPerBlock*sizeof(double) >>> (Nbase, boff, M, Ns, Nbasetotal, x, coh, p, bb, ptoclus, ed); -#ifdef CUDA_DBG - error = cudaGetLastError(); - checkCudaError(error,__FILE__,__LINE__); -#endif - - int T=DEFAULT_TH_PER_BK; - double *totald,total; - if((error=cudaMalloc((void**)&totald, sizeof(double)))!=cudaSuccess) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - cudaMemset(totald, 0, sizeof(double)); - checkCudaError(error,__FILE__,__LINE__); - - - if (T>BlocksPerGrid) { - /* one kernel launch is enough */ - plus_reduce<<< 1, BlocksPerGrid, sizeof(double)*BlocksPerGrid>>>(ed, BlocksPerGrid, NearestPowerOf2(BlocksPerGrid), totald); - -#ifdef CUDA_DBG - error = cudaGetLastError(); - checkCudaError(error,__FILE__,__LINE__); -#endif - } else { - /* multiple kernel launches */ - int L=(BlocksPerGrid+T-1)/T; - double *eo; - if((error=cudaMalloc((void**)&eo, L*sizeof(double)))!=cudaSuccess) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - plus_reduce_multi<<< L, T, sizeof(double)*T>>>(ed, BlocksPerGrid, NearestPowerOf2(T), eo); -#ifdef CUDA_DBG - error = cudaGetLastError(); - checkCudaError(error,__FILE__,__LINE__); -#endif - plus_reduce<<< 1, L, sizeof(double)*L>>>(eo, L, NearestPowerOf2(L), totald); -#ifdef CUDA_DBG - error = cudaGetLastError(); - checkCudaError(error,__FILE__,__LINE__); -#endif - cudaFree(eo); - } - cudaMemcpy(&total,totald,sizeof(double),cudaMemcpyDeviceToHost); - cudaFree(totald); - cudaFree(ed); - - return total; -} - - - -/* divide by singular values Dpd[]/Sd[] for Sd[]> eps */ -void -cudakernel_diagdiv(int ThreadsPerBlock, int BlocksPerGrid, int M, double eps, double *Dpd, double *Sd) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - kernel_diagdiv<<< BlocksPerGrid, ThreadsPerBlock >>>(M, eps, Dpd, Sd); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - -} - -/* cuda driver for calculating - A<= A+mu I, adding mu to diagonal entries of A - A: size MxM - ThreadsPerBlock, BlocksPerGrid calculated to meet M -*/ -void -cudakernel_diagmu(int ThreadsPerBlock, int BlocksPerGrid, int M, double *A, double mu) { -#ifdef CUDA_DBG - cudaError_t error; -#endif - kernel_diagmu<<< BlocksPerGrid, ThreadsPerBlock >>>(M, A, mu); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif -} - - -/* cuda driver for calculating f() */ -/* p: params (Mx1), x: data (Nx1), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -void -cudakernel_func(int ThreadsPerBlock, int BlocksPerGrid, double *p, double *x, int M, int N, double *coh, short *bbh, int Nbase, int Mclus, int Nstations) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - cudaMemset(x, 0, N*sizeof(double)); -// printf("Kernel data size=%d, block=%d, thread=%d, baselines=%d\n",N,BlocksPerGrid, ThreadsPerBlock,Nbase); - kernel_func<<< BlocksPerGrid, ThreadsPerBlock >>>(Nbase, x, coh, p, bbh, Nstations); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - -} - -/* cuda driver for calculating jacf() */ -/* p: params (Mx1), jac: jacobian (NxM), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -void -cudakernel_jacf(int ThreadsPerBlock_row, int ThreadsPerBlock_col, double *p, double *jac, int M, int N, double *coh, short *bbh, int Nbase, int Mclus, int Nstations) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - /* NOTE: use small value for ThreadsPerBlock here, like 8 */ - dim3 threadsPerBlock(16, 8); - /* jacobian: Nbase x Nstations (proportional to N), so */ - dim3 numBlocks((Nbase+threadsPerBlock.x-1)/threadsPerBlock.x, - (M+threadsPerBlock.y-1)/threadsPerBlock.y); - /* set memory of jac to zero */ - cudaMemset(jac, 0, N*M*sizeof(double)); - // printf("Kernel Jax data size=%d, params=%d, block=%d,%d, thread=%d,%d, baselines=%d\n",N, M, numBlocks.x,numBlocks.y, threadsPerBlock.x, threadsPerBlock.y, Nbase); - kernel_jacf<<< numBlocks, threadsPerBlock>>>(Nbase, M, jac, coh, p, bbh, Nstations); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - -} - -} diff --git a/src/lib/Solvers/mderiv.o b/src/lib/Solvers/mderiv.o deleted file mode 100644 index 5b07d3c7ffa9098819cc4a5f397c275b5e97676b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 107592 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 - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - -#include "cuda.h" -#include -#include - -/* enable this for checking for kernel failure */ -//#define CUDA_DBG - -__global__ void kernel_diagdiv_fl(int M, float eps, float *y, float *x){ - unsigned int tid = blockIdx.x*blockDim.x + threadIdx.x; - /* make sure to use only M threads */ - if (tideps) { - y[tid]=y[tid]/x[tid]; - } else { - y[tid]=0.0f; - } - } -} - -__global__ void kernel_diagmu_fl(int M, float *A,float mu){ - unsigned int tid = blockIdx.x*blockDim.x + threadIdx.x; - /* make sure to use only M threads */ - if (tid=0 - */ - if (sta1>=0 && sta2>=0) { - cuFloatComplex G1[4]; - float pp[8]; - pp[0]=p[sta1*8]; - pp[1]=p[sta1*8+1]; - pp[2]=p[sta1*8+2]; - pp[3]=p[sta1*8+3]; - pp[4]=p[sta1*8+4]; - pp[5]=p[sta1*8+5]; - pp[6]=p[sta1*8+6]; - pp[7]=p[sta1*8+7]; - G1[0].x=pp[0]; - G1[0].y=pp[1]; - G1[1].x=pp[2]; - G1[1].y=pp[3]; - G1[2].x=pp[4]; - G1[2].y=pp[5]; - G1[3].x=pp[6]; - G1[3].y=pp[7]; - - - cuFloatComplex C[4]; - C[0].x=coh[8*n]; - C[0].y=coh[8*n+1]; - C[1].x=coh[8*n+2]; - C[1].y=coh[8*n+3]; - C[2].x=coh[8*n+4]; - C[2].y=coh[8*n+5]; - C[3].x=coh[8*n+6]; - C[3].y=coh[8*n+7]; - - cuFloatComplex T1[4]; - /* T=G1*C */ - T1[0]=cuCaddf(cuCmulf(G1[0],C[0]),cuCmulf(G1[1],C[2])); - T1[1]=cuCaddf(cuCmulf(G1[0],C[1]),cuCmulf(G1[1],C[3])); - T1[2]=cuCaddf(cuCmulf(G1[2],C[0]),cuCmulf(G1[3],C[2])); - T1[3]=cuCaddf(cuCmulf(G1[2],C[1]),cuCmulf(G1[3],C[3])); - - cuFloatComplex G2[4]; - /* conjugate this */ - pp[0]=p[sta2*8]; - pp[1]=-p[sta2*8+1]; - pp[2]=p[sta2*8+2]; - pp[3]=-p[sta2*8+3]; - pp[4]=p[sta2*8+4]; - pp[5]=-p[sta2*8+5]; - pp[6]=p[sta2*8+6]; - pp[7]=-p[sta2*8+7]; - G2[0].x=pp[0]; - G2[0].y=pp[1]; - G2[2].x=pp[2]; - G2[2].y=pp[3]; - G2[1].x=pp[4]; - G2[1].y=pp[5]; - G2[3].x=pp[6]; - G2[3].y=pp[7]; - - cuFloatComplex T2[4]; - T2[0]=cuCaddf(cuCmulf(T1[0],G2[0]),cuCmulf(T1[1],G2[2])); - T2[1]=cuCaddf(cuCmulf(T1[0],G2[1]),cuCmulf(T1[1],G2[3])); - T2[2]=cuCaddf(cuCmulf(T1[2],G2[0]),cuCmulf(T1[3],G2[2])); - T2[3]=cuCaddf(cuCmulf(T1[2],G2[1]),cuCmulf(T1[3],G2[3])); - /* update model vector */ - x[8*n]=T2[0].x; - x[8*n+1]=T2[0].y; - x[8*n+2]=T2[1].x; - x[8*n+3]=T2[1].y; - x[8*n+4]=T2[2].x; - x[8*n+5]=T2[2].y; - x[8*n+6]=T2[3].x; - x[8*n+7]=T2[3].y; - - } - } - -} - -__global__ void kernel_jacf_fl(int Nbase, int M, float *jac, float *coh, float *p, short *bb, int N){ - /* global thread index : equal to the baseline */ - unsigned int n = threadIdx.x + blockDim.x*blockIdx.x; - /* which parameter:0...M */ - unsigned int m = threadIdx.y + blockDim.y*blockIdx.y; - - /* this thread works on - x[8*n:8*n+7], coh[8*M*n:8*M*n+8*M-1] - bb[2*n:2*n+1] (sta1,sta2) - organization of p (N stations and M clusters) - sta 0 sta 1 sta 2 .... sta N-1 - clus 0 0...7 8...15 16...23 ... 8N-8 8N-1 - clus 1 8N..8N+7 8N+8..8N+15 8N+16..8N+23 .... 8N+8N-8...8N+8N-1 - ...... - clus M-1 (M-1)N..(M-1)N+7 (M-1)N+8..(M-1)N+15.... ...(M-1)N+8N-8 (M-1)N+8N-1 - - organization of coherencies (coh) - [0, 8*M-1] : baseline 0 - [8*M, 8*M+8*M-1]: baseline 1 - [n*8*M, n*8*M+8*M-1]: baseline n - ...... - [n*8*M+cm*8, n*8*M+cm*8+7] cluster cm, baseline n - - residual error stored at sum[n] - */ - - if(n>3; /* 0...Ns-1 (because M=total par= 8 * Nstations */ - - if (((stc==sta2)||(stc==sta1)) && sta1>=0 && sta2>=0 ) { - - cuFloatComplex C[4]; - C[0].x=coh[8*n]; - C[0].y=coh[8*n+1]; - C[1].x=coh[8*n+2]; - C[1].y=coh[8*n+3]; - C[2].x=coh[8*n+4]; - C[2].y=coh[8*n+5]; - C[3].x=coh[8*n+6]; - C[3].y=coh[8*n+7]; - - /* which parameter exactly 0..7 */ - //int stoff=m%8; - int stoff=m-stc*8; - float pp1[8]; - float pp2[8]; - if (stc==sta1) { - for (int cn=0; cn<8; cn++) { - pp1[cn]=0.0f; - pp2[cn]=p[sta2*8+cn]; - } - pp1[stoff]=1.0f; - } else if (stc==sta2) { - for (int cn=0; cn<8; cn++) { - pp2[cn]=0.0f; - pp1[cn]=p[sta1*8+cn]; - } - pp2[stoff]=1.0f; - } - - - cuFloatComplex G1[4]; - G1[0].x=pp1[0]; - G1[0].y=pp1[1]; - G1[1].x=pp1[2]; - G1[1].y=pp1[3]; - G1[2].x=pp1[4]; - G1[2].y=pp1[5]; - G1[3].x=pp1[6]; - G1[3].y=pp1[7]; - - cuFloatComplex T1[4]; - /* T=G1*C */ - T1[0]=cuCaddf(cuCmulf(G1[0],C[0]),cuCmulf(G1[1],C[2])); - T1[1]=cuCaddf(cuCmulf(G1[0],C[1]),cuCmulf(G1[1],C[3])); - T1[2]=cuCaddf(cuCmulf(G1[2],C[0]),cuCmulf(G1[3],C[2])); - T1[3]=cuCaddf(cuCmulf(G1[2],C[1]),cuCmulf(G1[3],C[3])); - - cuFloatComplex G2[4]; - /* conjugate this */ - G2[0].x=pp2[0]; - G2[0].y=-pp2[1]; - G2[2].x=pp2[2]; - G2[2].y=-pp2[3]; - G2[1].x=pp2[4]; - G2[1].y=-pp2[5]; - G2[3].x=pp2[6]; - G2[3].y=-pp2[7]; - - cuFloatComplex T2[4]; - T2[0]=cuCaddf(cuCmulf(T1[0],G2[0]),cuCmulf(T1[1],G2[2])); - T2[1]=cuCaddf(cuCmulf(T1[0],G2[1]),cuCmulf(T1[1],G2[3])); - T2[2]=cuCaddf(cuCmulf(T1[2],G2[0]),cuCmulf(T1[3],G2[2])); - T2[3]=cuCaddf(cuCmulf(T1[2],G2[1]),cuCmulf(T1[3],G2[3])); - /* update jacobian */ - /* NOTE: row major order */ - jac[m+M*8*n]=T2[0].x; - jac[m+M*(8*n+1)]=T2[0].y; - jac[m+M*(8*n+2)]=T2[1].x; - jac[m+M*(8*n+3)]=T2[1].y; - jac[m+M*(8*n+4)]=T2[2].x; - jac[m+M*(8*n+5)]=T2[2].y; - jac[m+M*(8*n+6)]=T2[3].x; - jac[m+M*(8*n+7)]=T2[3].y; - - } - } - -} - - -/* only use extern if calling code is C */ -extern "C" -{ - - -/* divide by singular values Dpd[]/Sd[] for Sd[]> eps */ -void -cudakernel_diagdiv_fl(int ThreadsPerBlock, int BlocksPerGrid, int M, float eps, float *Dpd, float *Sd) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - kernel_diagdiv_fl<<< BlocksPerGrid, ThreadsPerBlock >>>(M, eps, Dpd, Sd); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - -} - -/* cuda driver for calculating - A<= A+mu I, adding mu to diagonal entries of A - A: size MxM - ThreadsPerBlock, BlocksPerGrid calculated to meet M -*/ -void -cudakernel_diagmu_fl(int ThreadsPerBlock, int BlocksPerGrid, int M, float *A, float mu) { -#ifdef CUDA_DBG - cudaError_t error; -#endif - kernel_diagmu_fl<<< BlocksPerGrid, ThreadsPerBlock >>>(M, A, mu); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif -} - - -/* cuda driver for calculating f() */ -/* p: params (Mx1), x: data (Nx1), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -void -cudakernel_func_fl(int ThreadsPerBlock, int BlocksPerGrid, float *p, float *x, int M, int N, float *coh, short *bbh, int Nbase, int Mclus, int Nstations) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - cudaMemset(x, 0, N*sizeof(float)); -// printf("Kernel data size=%d, block=%d, thread=%d, baselines=%d\n",N,BlocksPerGrid, ThreadsPerBlock,Nbase); - kernel_func_fl<<< BlocksPerGrid, ThreadsPerBlock >>>(Nbase, x, coh, p, bbh, Nstations); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - -} - -/* cuda driver for calculating jacf() */ -/* p: params (Mx1), jac: jacobian (NxM), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -void -cudakernel_jacf_fl(int ThreadsPerBlock_row, int ThreadsPerBlock_col, float *p, float *jac, int M, int N, float *coh, short *bbh, int Nbase, int Mclus, int Nstations) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - /* NOTE: use small value for ThreadsPerBlock here, like 8 */ - dim3 threadsPerBlock(16, 8); - /* jacobian: Nbase x Nstations (proportional to N), so */ - dim3 numBlocks((Nbase+threadsPerBlock.x-1)/threadsPerBlock.x, - (M+threadsPerBlock.y-1)/threadsPerBlock.y); - /* set memory of jac to zero */ - cudaMemset(jac, 0, N*M*sizeof(float)); - // printf("Kernel Jax data size=%d, params=%d, block=%d,%d, thread=%d,%d, baselines=%d\n",N, M, numBlocks.x,numBlocks.y, threadsPerBlock.x, threadsPerBlock.y, Nbase); - kernel_jacf_fl<<< numBlocks, threadsPerBlock>>>(Nbase, M, jac, coh, p, bbh, Nstations); - - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - -} - -} diff --git a/src/lib/Solvers/mderiv_fl.o b/src/lib/Solvers/mderiv_fl.o deleted file mode 100644 index 9b30acb6bf559f63d0f3ddfb189f5554270a7ccd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26832 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�^Hx3mB&T+7wcl_-!E=0w-txsQq1~21QfA!~Q)A3o-co*p}JpIIiELhQ%e>-8Mmd_H9}st diff --git a/src/lib/Solvers/myblas.c b/src/lib/Solvers/myblas.c deleted file mode 100644 index 7426b24..0000000 --- a/src/lib/Solvers/myblas.c +++ /dev/null @@ -1,462 +0,0 @@ -/* - * - Copyright (C) 2006-2008 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#include "Solvers.h" -#include /* for memcpy */ - -/* machine precision */ -double -dlamch(char CMACH) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern double dlamch_(char *CMACH); - return(dlamch_(&CMACH)); -} - - -/* blas dcopy */ -/* y = x */ -/* read x values spaced by Nx (so x size> N*Nx) */ -/* write to y values spaced by Ny (so y size > N*Ny) */ -void -my_dcopy(int N, double *x, int Nx, double *y, int Ny) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void dcopy_(int *N, double *x, int *incx, double *y, int *incy); - /* use memcpy if Nx=Ny=1 */ - if (Nx==1&&Ny==1) { - memcpy((void*)y,(void*)x,sizeof(double)*(size_t)N); - } else { - dcopy_(&N,x,&Nx,y,&Ny); - } -} -/* blas scale */ -/* x = a. x */ -void -my_dscal(int N, double a, double *x) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void dscal_(int *N, double *alpha, double *x, int *incx); - int i=1; - dscal_(&N,&a,x,&i); -} -void -my_sscal(int N, float a, float *x) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void sscal_(int *N, float *alpha, float *x, int *incx); - int i=1; - sscal_(&N,&a,x,&i); -} - -/* x^T*y */ -double -my_ddot(int N, double *x, double *y) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern double ddot_(int *N, double *x, int *incx, double *y, int *incy); - int i=1; - return(ddot_(&N,x,&i,y,&i)); -} - -/* ||x||_2 */ -double -my_dnrm2(int N, double *x) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern double dnrm2_(int *N, double *x, int *incx); - int i=1; - return(dnrm2_(&N,x,&i)); -} -float -my_fnrm2(int N, float *x) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern float snrm2_(int *N, float *x, int *incx); - int i=1; - return(snrm2_(&N,x,&i)); -} - - - -/* sum||x||_1 */ -double -my_dasum(int N, double *x) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern double dasum_(int *N, double *x, int *incx); - int i=1; - return(dasum_(&N,x,&i)); -} -float -my_fasum(int N, float *x) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern float sasum_(int *N, float *x, int *incx); - int i=1; - return(sasum_(&N,x,&i)); -} - -/* BLAS y = a.x + y */ -void -my_daxpy(int N, double *x, double a, double *y) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void daxpy_(int *N, double *alpha, double *x, int *incx, double *y, int *incy); - int i=1; /* strides */ - daxpy_(&N,&a,x,&i,y,&i); -} - -/* BLAS y = a.x + y */ -void -my_daxpys(int N, double *x, int incx, double a, double *y, int incy) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void daxpy_(int *N, double *alpha, double *x, int *incx, double *y, int *incy); - daxpy_(&N,&a,x,&incx,y,&incy); -} - -void -my_saxpy(int N, float *x, float a, float *y) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void saxpy_(int *N, float *alpha, float *x, int *incx, float *y, int *incy); - int i=1; /* strides */ - saxpy_(&N,&a,x,&i,y,&i); -} - - - -/* max |x| index (start from 1...)*/ -int -my_idamax(int N, double *x, int incx) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern int idamax_(int *N, double *x, int *incx); - return idamax_(&N,x,&incx); -} - -int -my_isamax(int N, float *x, int incx) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern int isamax_(int *N, float *x, int *incx); - return isamax_(&N,x,&incx); -} - -/* min |x| index (start from 1...)*/ -int -my_idamin(int N, double *x, int incx) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern int idamin_(int *N, double *x, int *incx); - return idamin_(&N,x,&incx); -} - -/* BLAS DGEMM C = alpha*op(A)*op(B)+ beta*C */ -void -my_dgemm(char transa, char transb, int M, int N, int K, double alpha, double *A, int lda, double *B, int ldb, double beta, double *C, int ldc) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void dgemm_(char *TRANSA, char *TRANSB, int *M, int *N, int *K, double *ALPHA, double *A, int *LDA, double *B, int * LDB, double *BETA, double *C, int *LDC); - dgemm_(&transa, &transb, &M, &N, &K, &alpha, A, &lda, B, &ldb, &beta, C, &ldc); -} - -/* BLAS DGEMV y = alpha*op(A)*x+ beta*y : op 'T' or 'N' */ -void -my_dgemv(char trans, int M, int N, double alpha, double *A, int lda, double *x, int incx, double beta, double *y, int incy) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void dgemv_(char *TRANS, int *M, int *N, double *ALPHA, double *A, int *LDA, double *X, int *INCX, double *BETA, double *Y, int *INCY); - dgemv_(&trans, &M, &N, &alpha, A, &lda, x, &incx, &beta, y, &incy); -} - - -/* following routines used in LAPACK solvers */ -/* cholesky factorization: real symmetric */ -int -my_dpotrf(char uplo, int N, double *A, int lda) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void dpotrf_(char *uplo, int *N, double *A, int *lda, int *info); - int info; - dpotrf_(&uplo,&N,A,&lda,&info); - return info; -} - -/* solve Ax=b using cholesky factorization */ -int -my_dpotrs(char uplo, int N, int nrhs, double *A, int lda, double *b, int ldb){ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void dpotrs_(char *uplo, int *N, int *nrhs, double *A, int *lda, double *b, int *ldb, int *info); - int info; - dpotrs_(&uplo,&N,&nrhs,A,&lda,b,&ldb,&info); - return info; -} - -/* solve Ax=b using QR factorization */ -int -my_dgels(char TRANS, int M, int N, int NRHS, double *A, int LDA, double *B, int LDB, double *WORK, int LWORK) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void dgels_(char *TRANS, int *M, int *N, int *NRHS, double *A, int *LDA, double *B, int *LDB, double *WORK, int *LWORK, int *INFO); - int info; - dgels_(&TRANS,&M,&N,&NRHS,A,&LDA,B,&LDB,WORK,&LWORK,&info); - return info; -} - - -/* A=U S VT, so V needs NOT to be transposed */ -int -my_dgesvd(char JOBU, char JOBVT, int M, int N, double *A, int LDA, double *S, - double *U, int LDU, double *VT, int LDVT, double *WORK, int LWORK) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void dgesvd_(char *JOBU, char *JOBVT, int *M, int *N, double *A, - int *LDA, double *S, double *U, int *LDU, double *VT, int *LDVT, - double *WORK, int *LWORK, int *info); - int info; - dgesvd_(&JOBU,&JOBVT,&M,&N,A,&LDA,S,U,&LDU,VT,&LDVT,WORK,&LWORK,&info); - return info; -} - -/* QR factorization QR=A, only TAU is used for Q, R stored in A*/ -int -my_dgeqrf(int M, int N, double *A, int LDA, double *TAU, double *WORK, int LWORK) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void dgeqrf_(int *M, int *N, double *A, int *LDA, double *TAU, double *WORK, int *LWORK, int *INFO); - int info; - dgeqrf_(&M,&N,A,&LDA,TAU,WORK,&LWORK,&info); - return info; -} - -/* calculate Q using elementary reflections */ -int -my_dorgqr(int M,int N,int K,double *A,int LDA,double *TAU,double *WORK,int LWORK) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void dorgqr_(int *M, int *N, int *K, double *A, int *LDA, double *TAU, double *WORK, int *LWORK, int *INFO); - int info; - dorgqr_(&M, &N, &K, A, &LDA, TAU, WORK, &LWORK, &info); - - return info; -} - -/* solves a triangular system of equations Ax=b, A triangular */ -int -my_dtrtrs(char UPLO, char TRANS, char DIAG,int N,int NRHS,double *A,int LDA,double *B,int LDB) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void dtrtrs_(char *UPLO,char *TRANS,char *DIAG,int *N,int *NRHS,double *A,int *LDA,double *B,int *LDB,int *INFO); - int info; - dtrtrs_(&UPLO,&TRANS,&DIAG,&N,&NRHS,A,&LDA,B,&LDB,&info); - - return info; -} - - -/* blas ccopy */ -/* y = x */ -/* read x values spaced by Nx (so x size> N*Nx) */ -/* write to y values spaced by Ny (so y size > N*Ny) */ -void -my_ccopy(int N, complex double *x, int Nx, complex double *y, int Ny) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void zcopy_(int *N, complex double *x, int *incx, complex double *y, int *incy); - /* use memcpy if Nx=Ny=1 */ - if (Nx==1&&Ny==1) { - memcpy((void*)y,(void*)x,sizeof(complex double)*(size_t)N); - } else { - zcopy_(&N,x,&Nx,y,&Ny); - } -} - -/* blas scale */ -/* x = a. x */ -void -my_cscal(int N, complex double a, complex double *x) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void zscal_(int *N, complex double *alpha, complex double *x, int *incx); - int i=1; - zscal_(&N,&a,x,&i); -} - -/* BLAS y = a.x + y */ -void -my_caxpy(int N, complex double *x, complex double a, complex double *y) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void zaxpy_(int *N, complex double *alpha, complex double *x, int *incx, complex double *y, int *incy); - int i=1; /* strides */ - zaxpy_(&N,&a,x,&i,y,&i); -} - - -/* BLAS x^H*y */ -complex double -my_cdot(int N, complex double *x, complex double *y) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern complex double zdotc_(int *N, complex double *x, int *incx, complex double *y, int *incy); - int i=1; - return(zdotc_(&N,x,&i,y,&i)); -} - -/* A=U S VT, so V needs NOT to be transposed */ -int -my_zgesvd(char JOBU, char JOBVT, int M, int N, complex double *A, int LDA, double *S, - complex double *U, int LDU, complex double *VT, int LDVT, complex double *WORK, int LWORK, double *RWORK) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void zgesvd_(char *JOBU, char *JOBVT, int *M, int *N, complex double *A, - int *LDA, double *S, complex double *U, int *LDU, complex double *VT, int *LDVT, - complex double *WORK, int *LWORK, double *RWORK, int *info); - int info; - zgesvd_(&JOBU,&JOBVT,&M,&N,A,&LDA,S,U,&LDU,VT,&LDVT,WORK,&LWORK,RWORK,&info); - return info; -} - -/* solve Ax=b using QR factorization */ -int -my_zgels(char TRANS, int M, int N, int NRHS, complex double *A, int LDA, complex double *B, int LDB, complex double *WORK, int LWORK) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void zgels_(char *TRANS, int *M, int *N, int *NRHS, complex double *A, int *LDA, complex double *B, int *LDB, complex double *WORK, int *LWORK, int *INFO); - int info; - zgels_(&TRANS,&M,&N,&NRHS,A,&LDA,B,&LDB,WORK,&LWORK,&info); - return info; -} - - -/* solve Ax=b using QR factorization */ -int -my_cgels(char TRANS, int M, int N, int NRHS, complex float *A, int LDA, complex float *B, int LDB, complex float *WORK, int LWORK) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void cgels_(char *TRANS, int *M, int *N, int *NRHS, complex float *A, int *LDA, complex float *B, int *LDB, complex float *WORK, int *LWORK, int *INFO); - int info; - cgels_(&TRANS,&M,&N,&NRHS,A,&LDA,B,&LDB,WORK,&LWORK,&info); - return info; -} - - - - -/* BLAS ZGEMM C = alpha*op(A)*op(B)+ beta*C */ -void -my_zgemm(char transa, char transb, int M, int N, int K, complex double alpha, complex double *A, int lda, complex double *B, int ldb, complex double beta, complex double *C, int ldc) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void zgemm_(char *TRANSA, char *TRANSB, int *M, int *N, int *K, complex double *ALPHA, complex double *A, int *LDA, complex double *B, int * LDB, complex double *BETA, complex double *C, int *LDC); - zgemm_(&transa, &transb, &M, &N, &K, &alpha, A, &lda, B, &ldb, &beta, C, &ldc); -} - -/* ||x||_2 */ -double -my_cnrm2(int N, complex double *x) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern double dznrm2_(int *N, complex double *x, int *incx); - int i=1; - return(dznrm2_(&N,x,&i)); -} - -/* blas fcopy */ -/* y = x */ -/* read x values spaced by Nx (so x size> N*Nx) */ -/* write to y values spaced by Ny (so y size > N*Ny) */ -void -my_fcopy(int N, float *x, int Nx, float *y, int Ny) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void scopy_(int *N, float *x, int *incx, float *y, int *incy); - /* use memcpy if Nx=Ny=1 */ - if (Nx==1&&Ny==1) { - memcpy((void*)y,(void*)x,sizeof(float)*(size_t)N); - } else { - scopy_(&N,x,&Nx,y,&Ny); - } -} - - -/* LAPACK eigen value expert routine, real symmetric matrix */ -int -my_dsyevx(char jobz, char range, char uplo, int N, double *A, int lda, - double vl, double vu, int il, int iu, double abstol, int M, double *W, - double *Z, int ldz, double *WORK, int lwork, int *iwork, int *ifail) { - - extern void dsyevx_(char *JOBZ, char *RANGE, char *UPLO, int *N, double *A, int *LDA, - double *VL, double *VU, int *IL, int *IU, double *ABSTOL, int *M, double *W, double *Z, - int *LDZ, double *WORK, int *LWORK, int *IWORK, int *IFAIL, int *INFO); - int info; - dsyevx_(&jobz,&range,&uplo,&N,A,&lda,&vl,&vu,&il,&iu,&abstol,&M,W,Z,&ldz,WORK,&lwork,iwork,ifail,&info); - return info; -} - - - -/* BLAS vector outer product - A= alpha x x^H + A -*/ -void -my_zher(char uplo, int N, double alpha, complex double *x, int incx, complex double *A, int lda) { - - extern void zher_(char *UPLO, int *N, double *ALPHA, complex double *X, int *INCX, complex double *A, int *LDA); - - zher_(&uplo,&N,&alpha,x,&incx,A,&lda); -} diff --git a/src/lib/Solvers/myblas.o b/src/lib/Solvers/myblas.o deleted file mode 100644 index 37c7e21166a0d7ec663032fdf226767b9562924a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9264 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 diff --git a/src/lib/Solvers/oslmfit.c b/src/lib/Solvers/oslmfit.c deleted file mode 100644 index 1be087f..0000000 --- a/src/lib/Solvers/oslmfit.c +++ /dev/null @@ -1,705 +0,0 @@ -/* - * - Copyright (C) 2006-2008 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#include -#include -#include -#include -#include - -#include "Solvers.h" -#include - -//#define DEBUG -/* helper functions for diagnostics */ -static void -checkCudaError(cudaError_t err, char *file, int line) -{ -#ifdef CUDA_DEBUG - if(!err) - return; - fprintf(stderr,"GPU (CUDA): %s %s %d\n", cudaGetErrorString(err),file,line); - exit(EXIT_FAILURE); -#endif -} - - -static void -checkCublasError(cublasStatus_t cbstatus, char *file, int line) -{ -#ifdef CUDA_DEBUG - if (cbstatus!=CUBLAS_STATUS_SUCCESS) { - fprintf(stderr,"%s: %d: CUBLAS failure\n",file,line); - exit(EXIT_FAILURE); - } -#endif -} - - - - -/* OS-LM, but f() and jac() calculations are done - entirely in the GPU */ -int -oslevmar_der_single_cuda( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - * info[5]= # iterations, - * info[6]=reason for terminating: 1 - stopped by small gradient J^T e - * 2 - stopped by small Dp - * 3 - stopped by itmax - * 4 - singular matrix. Restart from current p with increased mu - * 5 - no further error reduction is possible. Restart with increased mu - * 6 - stopped by small ||e||_2 - * 7 - stopped by invalid (i.e. NaN or Inf) "func" values. This is a user error - * info[7]= # function evaluations - * info[8]= # Jacobian evaluations - * info[9]= # linear systems solved, i.e. # attempts for reducing error - */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - double *gWORK, /* GPU allocated memory */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - int randomize, /* if >0 randomize */ - void *adata) /* pointer to possibly additional data, passed uninterpreted to func & jacf. - * Set to NULL if not needed - */ -{ - - /* general note: all device variables end with a 'd' */ - int stop=0; - cudaError_t err; - cublasStatus_t cbstatus; - - int nu=2,nu2; - double p_L2, Dp_L2=DBL_MAX, dF, dL, p_eL2, jacTe_inf=0.0, pDp_eL2, init_p_eL2; - double tmp,mu=0.0; - double tau, eps1, eps2, eps2_sq, eps3; - int k,ci,issolved; - - double *hxd; - - double *ed; - double *xd; - - double *jacd; - - double *jacTjacd,*jacTjacd0; - - double *Dpd,*bd; - double *pd,*pnewd; - double *jacTed; - - /* used in QR solver */ - double *taud; - - /* used in SVD solver */ - double *Ud; - double *VTd; - double *Sd; - - /* ME data */ - me_data_t *dp=(me_data_t*)adata; - int Nbase=(dp->Nbase)*(ntiles); /* note: we do not use the total tile size */ - /* coherency on device */ - double *cohd; - /* baseline-station map on device/host */ - short *bbd; - - int solve_axb=linsolv; - - /* setup default settings */ - if(opts){ - tau=opts[0]; - eps1=opts[1]; - eps2=opts[2]; - eps2_sq=opts[2]*opts[2]; - eps3=opts[3]; - } else { - tau=CLM_INIT_MU; - eps1=CLM_STOP_THRESH; - eps2=CLM_STOP_THRESH; - eps2_sq=CLM_STOP_THRESH*CLM_STOP_THRESH; - eps3=CLM_STOP_THRESH; - } - - /* calculate no of cuda threads and blocks */ - int ThreadsPerBlock=DEFAULT_TH_PER_BK; - int BlocksPerGrid= 2*(M+ThreadsPerBlock-1)/ThreadsPerBlock; - - - unsigned long int moff; - if (!gWORK) { - err=cudaMalloc((void**)&xd, N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&jacd, M*N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&jacTjacd, M*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&jacTed, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&jacTjacd0, M*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&Dpd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&bd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&pd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&pnewd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - /* needed for calculating f() and jac() */ - err=cudaMalloc((void**) &bbd, Nbase*2*sizeof(short)); - checkCudaError(err,__FILE__,__LINE__); - /* we need coherencies for only this cluster */ - err=cudaMalloc((void**) &cohd, Nbase*8*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&hxd, N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&ed, N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - /* memory allocation: different solvers */ - if (solve_axb==1) { - err=cudaMalloc((void**)&taud, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } else if (solve_axb==2) { - err=cudaMalloc((void**)&Ud, M*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&VTd, M*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&Sd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } - } else { - moff=0; - xd=&gWORK[moff]; - moff+=N; - jacd=&gWORK[moff]; - moff+=M*N; - jacTjacd=&gWORK[moff]; - moff+=M*M; - jacTed=&gWORK[moff]; - moff+=M; - jacTjacd0=&gWORK[moff]; - moff+=M*M; - Dpd=&gWORK[moff]; - moff+=M; - bd=&gWORK[moff]; - moff+=M; - pd=&gWORK[moff]; - moff+=M; - pnewd=&gWORK[moff]; - moff+=M; - cohd=&gWORK[moff]; - moff+=Nbase*8; - hxd=&gWORK[moff]; - moff+=N; - ed=&gWORK[moff]; - moff+=N; - if (solve_axb==1) { - taud=&gWORK[moff]; - moff+=M; - } else if (solve_axb==2) { - Ud=&gWORK[moff]; - moff+=M*M; - VTd=&gWORK[moff]; - moff+=M*M; - Sd=&gWORK[moff]; - moff+=M; - } - bbd=(short*)&gWORK[moff]; - moff+=(Nbase*2*sizeof(short))/sizeof(double); - } - - /* extra storage for cusolver */ - int work_size=0; - int *devInfo; - int devInfo_h=0; - err=cudaMalloc((void**)&devInfo, sizeof(int)); - checkCudaError(err,__FILE__,__LINE__); - double *work; - double *rwork; - if (solve_axb==0) { - cusolverDnDpotrf_bufferSize(solver_handle, CUBLAS_FILL_MODE_UPPER, M, jacTjacd, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } else if (solve_axb==1) { - cusolverDnDgeqrf_bufferSize(solver_handle, M, M, jacTjacd, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } else { - cusolverDnDgesvd_bufferSize(solver_handle, M, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&rwork, 5*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } - - - err=cudaMemcpyAsync(pd, p, M*sizeof(double), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - /* need to give right offset for coherencies */ - /* offset: cluster offset+time offset */ - err=cudaMemcpyAsync(cohd, &(dp->ddcoh[(dp->Nbase)*(dp->tilesz)*(dp->clus)*8+(dp->Nbase)*tileoff*8]), Nbase*8*sizeof(double), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - /* correct offset for baselines */ - err=cudaMemcpyAsync(bbd, &(dp->ddbase[2*(dp->Nbase)*(tileoff)]), Nbase*2*sizeof(short), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - cudaDeviceSynchronize(); - /* xd <=x */ - err=cudaMemcpyAsync(xd, x, N*sizeof(double), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - - /* ### compute e=x - f(p) and its L2 norm */ - /* ### e=x-hx, p_eL2=||e|| */ - /* p: params (Mx1), x: data (Nx1), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations*/ - cudakernel_func(ThreadsPerBlock, (Nbase+ThreadsPerBlock-1)/ThreadsPerBlock, pd,hxd,M,N, cohd, bbd, Nbase, dp->M, dp->N); - - /* e=x */ - cbstatus=cublasDcopy(cbhandle, N, xd, 1, ed, 1); - /* e=x-hx */ - double alpha=-1.0; - cbstatus=cublasDaxpy(cbhandle, N, &alpha, hxd, 1, ed, 1); - - /* norm ||e|| */ - cbstatus=cublasDnrm2(cbhandle, N, ed, 1, &p_eL2); - /* square */ - p_eL2=p_eL2*p_eL2; - - init_p_eL2=p_eL2; - if(!finite(p_eL2)) stop=7; - - /* setup OS subsets and stating offsets */ - /* ed : N, cohd : Nbase*8, bbd : Nbase*2 full size */ - /* if ntiles= no. of OS iterations, so select - a random set of subsets */ - /* N, Nbase changes with subset, cohd,bbd,ed gets offsets */ - /* ed : N, cohd : Nbase*8, bbd : Nbase*2 full size */ - /* p: params (Mx1), jacd: jacobian (NxM), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations*/ - /* FIXME thread/block sizes 16x16=256, so 16 is chosen */ - //cudakernel_jacf(ThreadsPerBlock, ThreadsPerBlock/4, pd, jacd, M, N, cohd, bbd, Nbase, dp->M, dp->N); - cudakernel_jacf(ThreadsPerBlock, ThreadsPerBlock/4, pd, jacd, M, Nos[l], &cohd[8*NbI[l]], &bbd[2*NbI[l]], Nbaseos[l], dp->M, dp->N); - - /* Compute J^T J and J^T e */ - /* Cache efficient computation of J^T J based on blocking - */ - /* since J is in ROW major order, assume it is transposed, - so actually calculate A=J*J^T, where J is size MxN */ - //status=culaDeviceDgemm('N','T',M,M,Nos[l],1.0,jacd,M,jacd,M,0.0,jacTjacd,M); - //checkStatus(status,__FILE__,__LINE__); - double cone=1.0; double czero=0.0; - cbstatus=cublasDgemm(cbhandle,CUBLAS_OP_N,CUBLAS_OP_T,M,M,Nos[l],&cone,jacd,M,jacd,M,&czero,jacTjacd,M); - - /* create backup */ - /* copy jacTjacd0<=jacTjacd */ - cbstatus=cublasDcopy(cbhandle, M*M, jacTjacd, 1, jacTjacd0, 1); - /* J^T e */ - /* calculate b=J^T*e (actually compute b=J*e, where J in row major (size MxN) */ - //status=culaDeviceDgemv('N',M,Nos[l],1.0,jacd,M,&ed[edI[l]],1,0.0,jacTed,1); - //checkStatus(status,__FILE__,__LINE__); - cbstatus=cublasDgemv(cbhandle,CUBLAS_OP_N,M,Nos[l],&cone,jacd,M,&ed[edI[l]],1,&czero,jacTed,1); - - - - /* Compute ||J^T e||_inf and ||p||^2 */ - /* find infinity norm of J^T e, 1 based indexing*/ - cbstatus=cublasIdamax(cbhandle, M, jacTed, 1, &ci); - err=cudaMemcpy(&jacTe_inf,&(jacTed[ci-1]),sizeof(double),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - /* L2 norm of current parameter values */ - /* norm ||Dp|| */ - cbstatus=cublasDnrm2(cbhandle, M, pd, 1, &p_L2); - p_L2=p_L2*p_L2; - if(jacTe_inf<0.0) {jacTe_inf=-jacTe_inf;} -#ifdef DEBUG - printf("Inf norm=%lf\n",jacTe_inf); -#endif - - /* check for convergence */ - if((jacTe_inf <= eps1)){ - Dp_L2=0.0; /* no increment for p in this case */ - stop=1; - break; - } - - /* compute initial (k=0) damping factor */ - if (k==0) { - /* find max diagonal element (stride is M+1) */ - /* should be MAX not MAX(ABS) */ - cbstatus=cublasIdamax(cbhandle, M, jacTjacd, M+1, &ci); /* 1 based index */ - ci=(ci-1)*(M+1); /* right value of the diagonal */ - - err=cudaMemcpy(&tmp,&(jacTjacd[ci]),sizeof(double),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - mu=tau*tmp; - } - - - /* determine increment using adaptive damping */ - while(1){ - /* augment normal equations */ - /* increment A => A+ mu*I, increment diagonal entries */ - /* copy jacTjacd<=jacTjacd0 */ - cbstatus=cublasDcopy(cbhandle, M*M, jacTjacd0, 1, jacTjacd, 1); - cudakernel_diagmu(ThreadsPerBlock, BlocksPerGrid, M, jacTjacd, mu); - -#ifdef DEBUG - printf("mu=%lf\n",mu); -#endif -/*************************************************************************/ - issolved=0; - /* solve augmented equations A x = b */ - /* A==jacTjacd, b==Dpd, after solving, x==Dpd */ - /* b=jacTed : intially right hand side, at exit the solution */ - if (solve_axb==0) { - /* Cholesky solver **********************/ - /* lower triangle of Ad is destroyed */ - //status=culaDeviceDpotrf('U',M,jacTjacd,M); - cusolverDnDpotrf(solver_handle, CUBLAS_FILL_MODE_UPPER, M, jacTjacd, M, work, work_size, devInfo); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (!devInfo_h) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - fprintf(stderr,"Singular matrix\n"); -#endif - } - if (issolved) { - /* copy Dpd<=jacTed */ - cbstatus=cublasDcopy(cbhandle, M, jacTed, 1, Dpd, 1); -#ifdef DEBUG - checkCublasError(cbstatus,__FILE__,__LINE__); -#endif - //status=culaDeviceDpotrs('U',M,1,jacTjacd,M,Dpd,M); - cusolverDnDpotrs(solver_handle, CUBLAS_FILL_MODE_UPPER,M,1,jacTjacd,M,Dpd,M,devInfo); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (devInfo_h) { - issolved=0; -#ifdef DEBUG - fprintf(stderr,"Singular matrix\n"); -#endif - } - } - } else if (solve_axb==1) { - /* QR solver ********************************/ - //status=culaDeviceDgeqrf(M,M,jacTjacd,M,taud); - cusolverDnDgeqrf(solver_handle, M, M, jacTjacd, M, taud, work, work_size, devInfo); - cudaDeviceSynchronize(); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (!devInfo_h) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - fprintf(stderr,"Singular matrix\n"); -#endif - } - - if (issolved) { - /* copy Dpd<=jacTed */ - cbstatus=cublasDcopy(cbhandle, M, jacTed, 1, Dpd, 1); - //status=culaDeviceDgeqrs(M,M,1,jacTjacd,M,taud,Dpd,M); - cusolverDnDormqr(solver_handle, CUBLAS_SIDE_LEFT, CUBLAS_OP_T, M, 1, M, jacTjacd, M, taud, Dpd, M, work, work_size, devInfo); - cudaDeviceSynchronize(); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (devInfo_h) { - issolved=0; -#ifdef DEBUG - fprintf(stderr,"Singular matrix\n"); -#endif - } else { - cone=1.0; - cbstatus=cublasDtrsm(cbhandle,CUBLAS_SIDE_LEFT,CUBLAS_FILL_MODE_UPPER,CUBLAS_OP_N,CUBLAS_DIAG_NON_UNIT,M,1,&cone,jacTjacd,M,Dpd,M); - } - } - } else { - /* SVD solver *********************************/ - /* U S VT = A */ - //status=culaDeviceDgesvd('A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M); - //checkStatus(status,__FILE__,__LINE__); - cusolverDnDgesvd(solver_handle,'A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M,work,work_size,rwork,devInfo); - cudaDeviceSynchronize(); - /* copy Dpd<=jacTed */ - cbstatus=cublasDcopy(cbhandle, M, jacTed, 1, Dpd, 1); - /* b<=U^T * b */ - //status=culaDeviceDgemv('T',M,M,1.0,Ud,M,Dpd,1,0.0,Dpd,1); - //checkStatus(status,__FILE__,__LINE__); - cone=1.0; czero=0.0; - cbstatus=cublasDgemv(cbhandle,CUBLAS_OP_T,M,M,&cone,Ud,M,Dpd,1,&czero,Dpd,1); - - /* divide by singular values Dpd[]/Sd[] for Sd[]> eps1 */ - cudakernel_diagdiv(ThreadsPerBlock, BlocksPerGrid, M, eps1, Dpd, Sd); - - /* b<=VT^T * b */ - //status=culaDeviceDgemv('T',M,M,1.0,VTd,M,Dpd,1,0.0,Dpd,1); - //checkStatus(status,__FILE__,__LINE__); - cbstatus=cublasDgemv(cbhandle,CUBLAS_OP_T,M,M,&cone,VTd,M,Dpd,1,&czero,Dpd,1); - - - issolved=1; - } -/*************************************************************************/ - - /* compute p's new estimate and ||Dp||^2 */ - if (issolved) { - /* compute p's new estimate and ||Dp||^2 */ - /* pnew=p+Dp */ - /* pnew=p */ - cbstatus=cublasDcopy(cbhandle, M, pd, 1, pnewd, 1); - /* pnew=pnew+Dp */ - alpha=1.0; - cbstatus=cublasDaxpy(cbhandle, M, &alpha, Dpd, 1, pnewd, 1); - - /* norm ||Dp|| */ - cbstatus=cublasDnrm2(cbhandle, M, Dpd, 1, &Dp_L2); - Dp_L2=Dp_L2*Dp_L2; - -#ifdef DEBUG -printf("norm ||dp|| =%lf, norm ||p||=%lf\n",Dp_L2,p_L2); -#endif - if(Dp_L2<=eps2_sq*p_L2){ /* relative change in p is small, stop */ - stop=2; - break; - } - - if(Dp_L2>=(p_L2+eps2)/(CLM_EPSILON*CLM_EPSILON)){ /* almost singular */ - stop=4; - break; - } - - /* new function value */ - /* compute ||e(pDp)||_2 */ - /* ### hx=x-hx, pDp_eL2=||hx|| */ - /* copy to device */ - /* hxd<=hx */ - cudakernel_func(ThreadsPerBlock, (Nbase+ThreadsPerBlock-1)/ThreadsPerBlock, pnewd, hxd, M, N, cohd, bbd, Nbase, dp->M, dp->N); - - /* e=x */ - cbstatus=cublasDcopy(cbhandle, N, xd, 1, ed, 1); - /* e=x-hx */ - alpha=-1.0; - cbstatus=cublasDaxpy(cbhandle, N, &alpha, hxd, 1, ed, 1); - /* note: e is updated */ - - /* norm ||e|| */ - cbstatus=cublasDnrm2(cbhandle, N, ed, 1, &pDp_eL2); - pDp_eL2=pDp_eL2*pDp_eL2; - - - if(!finite(pDp_eL2)){ /* sum of squares is not finite, most probably due to a user error. - */ - stop=7; - break; - } - - /* dL=Dp'*(mu*Dp+jacTe) */ - /* bd=jacTe+mu*Dp */ - cbstatus=cublasDcopy(cbhandle, M, jacTed, 1, bd, 1); - cbstatus=cublasDaxpy(cbhandle, M, &mu, Dpd, 1, bd, 1); - cbstatus=cublasDdot(cbhandle, M, Dpd, 1, bd, 1, &dL); - - dF=p_eL2-pDp_eL2; - -#ifdef DEBUG - printf("dF=%lf, dL=%lf\n",dF,dL); -#endif - if(dL>0.0 && dF>0.0){ /* reduction in error, increment is accepted */ - tmp=(2.0*dF/dL-1.0); - tmp=1.0-tmp*tmp*tmp; - mu=mu*((tmp>=CLM_ONE_THIRD)? tmp : CLM_ONE_THIRD); - nu=2; - - /* update p's estimate */ - cbstatus=cublasDcopy(cbhandle, M, pnewd, 1, pd, 1); - - /* update ||e||_2 */ - p_eL2=pDp_eL2; - break; - } - - } - /* if this point is reached, either the linear system could not be solved or - * the error did not reduce; in any case, the increment must be rejected - */ - - mu*=(double)nu; - nu2=nu<<1; // 2*nu; - if(nu2<=nu){ /* nu has wrapped around (overflown). */ - stop=5; - break; - } - - nu=nu2; - - } /* inner loop */ - - } - if (randomize) { - free(subI); - } -/**************** end OS loop ***************************/ - - } - /**** end iteration loop ***********/ - free(Nos); - free(Nbaseos); - free(edI); - free(NbI); - - if(k>=itmax) stop=3; - - /* copy back current solution */ - err=cudaMemcpyAsync(p,pd,M*sizeof(double),cudaMemcpyDeviceToHost,0); - checkCudaError(err,__FILE__,__LINE__); - checkCublasError(cbstatus,__FILE__,__LINE__); - - /* synchronize async operations */ - cudaDeviceSynchronize(); - - if (!gWORK) { - cudaFree(xd); - cudaFree(jacd); - cudaFree(jacTjacd); - cudaFree(jacTjacd0); - cudaFree(jacTed); - cudaFree(Dpd); - cudaFree(bd); - cudaFree(pd); - cudaFree(pnewd); - cudaFree(hxd); - cudaFree(ed); - if (solve_axb==1) { - cudaFree(taud); - } else if (solve_axb==2) { - cudaFree(Ud); - cudaFree(VTd); - cudaFree(Sd); - } - cudaFree(cohd); - cudaFree(bbd); - } - - cudaFree(devInfo); - cudaFree(work); - if (solve_axb==2) { - cudaFree(rwork); - } - -#ifdef DEBUG - printf("stop=%d\n",stop); -#endif - if(info){ - info[0]=init_p_eL2; - info[1]=p_eL2; - info[2]=jacTe_inf; - info[3]=Dp_L2; - info[4]=mu; - info[5]=(double)k; - info[6]=(double)stop; - info[7]=(double)0; - info[8]=(double)0; - info[9]=(double)0; - } - return 0; -} diff --git a/src/lib/Solvers/oslmfit.o b/src/lib/Solvers/oslmfit.o deleted file mode 100644 index 737ba1d680bb1f5d38d29489acb54c025d75b0c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 47200 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 - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#define _GNU_SOURCE /* for sincos() */ -#include -#include -#include -#include -#include -#include "Solvers.h" - -/* Jones matrix multiplication - C=A*B -*/ -static void -amb(complex double * __restrict a, complex double * __restrict b, complex double * __restrict c) { - c[0]=(a[0]*b[0]+a[1]*b[2]); - c[1]=(a[0]*b[1]+a[1]*b[3]); - c[2]=(a[2]*b[0]+a[3]*b[2]); - c[3]=(a[2]*b[1]+a[3]*b[3]); -} - - -/* Jones matrix multiplication - C=A*B^H -*/ -static void -ambt(complex double * __restrict a, complex double * __restrict b, complex double * __restrict c) { - c[0]=a[0]*conj(b[0])+a[1]*conj(b[1]); - c[1]=a[0]*conj(b[2])+a[1]*conj(b[3]); - c[2]=a[2]*conj(b[0])+a[3]*conj(b[1]); - c[3]=a[2]*conj(b[2])+a[3]*conj(b[3]); -} - - -/* worker thread function for subtraction - also correct residual with solutions for cluster id 0 */ -static void * -residual_threadfn_nointerpolation(void *data) { - thread_data_base_t *t=(thread_data_base_t*)data; - - int ci,cm,sta1,sta2; - double *pm; - complex double C[4],G1[4],G2[4],T1[4],T2[4]; - int M=(t->M); - int Ntilebase=(t->Nbase)*(t->tilesz); - int px; - for (ci=0; ciNb; ci++) { - /* iterate over the sky model and calculate contribution */ - /* for this x[8*ci:8*(ci+1)-1] */ - /* if this baseline is flagged, we do not compute */ - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - for (cm=0; cm=0 to do a subtraction */ - if (t->carr[cm].id>=0) { - /* gains for this cluster, for sta1,sta2 */ - /* depending on the chunk size and the baseline index, - select right set of parameters - data x=[0,........,Nbase*tilesz] - divided into nchunk chunks - p[0] -> x[0.....Nbase*tilesz/nchunk-1] - p[1] -> x[Nbase*tilesz/nchunk......2*Nbase*tilesz-1] - .... - p[last] -> x[(nchunk-1)*Nbase*tilesz/nchunk......Nbase*tilesz] - - so given bindex, right p[] is bindex/((Nbase*tilesz+nchunk-1)/nchunk) - */ - px=(ci+t->boff)/((Ntilebase+t->carr[cm].nchunk-1)/t->carr[cm].nchunk); - //printf("base %d, cluster %d, parm off %d abs %d\n",t->bindex[ci],cm,px,t->carr[cm].p[px]); - //pm=&(t->p0[cm*8*N]); - pm=&(t->p0[t->carr[cm].p[px]]); - G1[0]=(pm[sta1*8])+_Complex_I*(pm[sta1*8+1]); - G1[1]=(pm[sta1*8+2])+_Complex_I*(pm[sta1*8+3]); - G1[2]=(pm[sta1*8+4])+_Complex_I*(pm[sta1*8+5]); - G1[3]=(pm[sta1*8+6])+_Complex_I*(pm[sta1*8+7]); - G2[0]=(pm[sta2*8])+_Complex_I*(pm[sta2*8+1]); - G2[1]=(pm[sta2*8+2])+_Complex_I*(pm[sta2*8+3]); - G2[2]=(pm[sta2*8+4])+_Complex_I*(pm[sta2*8+5]); - G2[3]=(pm[sta2*8+6])+_Complex_I*(pm[sta2*8+7]); - - - /* use pre calculated values */ - C[0]=t->coh[4*M*ci+4*cm]; - C[1]=t->coh[4*M*ci+4*cm+1]; - C[2]=t->coh[4*M*ci+4*cm+2]; - C[3]=t->coh[4*M*ci+4*cm+3]; - - /* form G1*C*G2' */ - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - - /* subtract from baseline visibilities */ - t->x[8*ci]-=creal(T2[0]); - t->x[8*ci+1]-=cimag(T2[0]); - t->x[8*ci+2]-=creal(T2[1]); - t->x[8*ci+3]-=cimag(T2[1]); - t->x[8*ci+4]-=creal(T2[2]); - t->x[8*ci+5]-=cimag(T2[2]); - t->x[8*ci+6]-=creal(T2[3]); - t->x[8*ci+7]-=cimag(T2[3]); - } - } - if (t->pinv) { - cm=t->ccid; - /* now do correction, if any */ - C[0]=t->x[8*ci]+_Complex_I*t->x[8*ci+1]; - C[1]=t->x[8*ci+2]+_Complex_I*t->x[8*ci+3]; - C[2]=t->x[8*ci+4]+_Complex_I*t->x[8*ci+5]; - C[3]=t->x[8*ci+6]+_Complex_I*t->x[8*ci+7]; - px=(ci+t->boff)/((Ntilebase+t->carr[cm].nchunk-1)/t->carr[cm].nchunk); - pm=&(t->pinv[8*t->N*px]); - G1[0]=(pm[sta1*8])+_Complex_I*(pm[sta1*8+1]); - G1[1]=(pm[sta1*8+2])+_Complex_I*(pm[sta1*8+3]); - G1[2]=(pm[sta1*8+4])+_Complex_I*(pm[sta1*8+5]); - G1[3]=(pm[sta1*8+6])+_Complex_I*(pm[sta1*8+7]); - G2[0]=(pm[sta2*8])+_Complex_I*(pm[sta2*8+1]); - G2[1]=(pm[sta2*8+2])+_Complex_I*(pm[sta2*8+3]); - G2[2]=(pm[sta2*8+4])+_Complex_I*(pm[sta2*8+5]); - G2[3]=(pm[sta2*8+6])+_Complex_I*(pm[sta2*8+7]); - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - t->x[8*ci]=creal(T2[0]); - t->x[8*ci+1]=cimag(T2[0]); - t->x[8*ci+2]=creal(T2[1]); - t->x[8*ci+3]=cimag(T2[1]); - t->x[8*ci+4]=creal(T2[2]); - t->x[8*ci+5]=cimag(T2[2]); - t->x[8*ci+6]=creal(T2[3]); - t->x[8*ci+7]=cimag(T2[3]); - } - } - return NULL; -} - -/* invert matrix xx - 8x1 array - * store it in yy - 8x1 array - */ -static int -mat_invert(double xx[8],double yy[8], double rho) { - complex double a[4]; - complex double det; - complex double b[4]; - - a[0]=xx[0]+xx[1]*_Complex_I+rho; - a[1]=xx[2]+xx[3]*_Complex_I; - a[2]=xx[4]+xx[5]*_Complex_I; - a[3]=xx[6]+xx[7]*_Complex_I+rho; - - - - det=a[0]*a[3]-a[1]*a[2]; - if (sqrt(cabs(det))<=rho) { - det+=rho; - } - det=1.0/det; - b[0]=a[3]*det; - b[1]=-a[1]*det; - b[2]=-a[2]*det; - b[3]=a[0]*det; - - - yy[0]=creal(b[0]); - yy[1]=cimag(b[0]); - yy[2]=creal(b[1]); - yy[3]=cimag(b[1]); - yy[4]=creal(b[2]); - yy[5]=cimag(b[2]); - yy[6]=creal(b[3]); - yy[7]=cimag(b[3]); - - return 0; -} - - - -int -calculate_residuals_interp(double *u,double *v,double *w,double *p0,double *p,double *x,int N,int Nbase,int tilesz,baseline_t *barr, clus_source_t *carr, complex double *coh, int M,double freq0,double fdelta,int Nt, int ccid, double rho) { - int nth,nth1,ci,cj; - - int Nthb0,Nthb; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_base_t *threaddata; - - int Nbase1=Nbase*tilesz; - - int cm; - double *pm,*pinv=0; - cm=-1; - /* find if any cluster is specified for correction of data */ - for (cj=0; cj=0) { /* valid cluser for correction */ - /* allocate memory for inverse J */ - if ((pinv=(double*)malloc((size_t)8*N*carr[cm].nchunk*sizeof(double)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - for (cj=0; cjfdelta*0.5; - - complex double C[4],G1[4],G2[4],T1[4],T2[4]; - int M=(t->M); - int Ntilebase=(t->Nbase)*(t->tilesz); - int px; - for (ci=0; ciNb; ci++) { - /* iterate over the sky model and calculate contribution */ - /* for this x[8*ci:8*(ci+1)-1] */ - /* even if this baseline is flagged, we do compute */ - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - for (cm=0; cm=0 to do a subtraction */ - if (t->carr[cm].id>=0) { - /* gains for this cluster, for sta1,sta2 */ - /* depending on the chunk size and the baseline index, - select right set of parameters - data x=[0,........,Nbase*tilesz] - divided into nchunk chunks - p[0] -> x[0.....Nbase*tilesz/nchunk-1] - p[1] -> x[Nbase*tilesz/nchunk......2*Nbase*tilesz-1] - .... - p[last] -> x[(nchunk-1)*Nbase*tilesz/nchunk......Nbase*tilesz] - - so given bindex, right p[] is bindex/((Nbase*tilesz+nchunk-1)/nchunk) - */ - px=(ci+t->boff)/((Ntilebase+t->carr[cm].nchunk-1)/t->carr[cm].nchunk); - //printf("base %d, cluster %d, parm off %d abs %d\n",t->bindex[ci],cm,px,t->carr[cm].p[px]); - pm=&(t->p[t->carr[cm].p[px]]); - G1[0]=(pm[sta1*8])+_Complex_I*(pm[sta1*8+1]); - G1[1]=(pm[sta1*8+2])+_Complex_I*(pm[sta1*8+3]); - G1[2]=(pm[sta1*8+4])+_Complex_I*(pm[sta1*8+5]); - G1[3]=(pm[sta1*8+6])+_Complex_I*(pm[sta1*8+7]); - G2[0]=(pm[sta2*8])+_Complex_I*(pm[sta2*8+1]); - G2[1]=(pm[sta2*8+2])+_Complex_I*(pm[sta2*8+3]); - G2[2]=(pm[sta2*8+4])+_Complex_I*(pm[sta2*8+5]); - G2[3]=(pm[sta2*8+6])+_Complex_I*(pm[sta2*8+7]); - - - /* iterate over frequencies */ - freq0=t->freq0; -/***********************************************/ - /* calculate coherencies for each freq */ - memset(C,0,sizeof(complex double)*4); - - /* setup memory */ - if (posix_memalign((void*)&PHr,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&PHi,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&G,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&II,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&QQ,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&UU,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&VV,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - /* phase (real,imag) parts */ - /* note u=u/c, v=v/c, w=w/c here */ - /* phterm is 2pi(u/c l +v/c m +w/c n) */ - for (cn=0; cncarr[cm].N; cn++) { - G[cn]=2.0*M_PI*(t->u[ci]*t->carr[cm].ll[cn]+t->v[ci]*t->carr[cm].mm[cn]+t->w[ci]*t->carr[cm].nn[cn]); - } - for (cn=0; cncarr[cm].N; cn++) { - sincos(G[cn]*freq0,&PHi[cn],&PHr[cn]); - } - - /* term due to shape of source, also multiplied by freq/time smearing */ - for (cn=0; cncarr[cm].N; cn++) { - /* freq smearing : extra term delta * sinc(delta/2 * phterm) */ - if (G[cn]!=0.0) { - double smfac=G[cn]*fdelta2; - double sinph=sin(smfac)/smfac; - G[cn]=fabs(sinph); - } else { - G[cn]=1.0; - } - } - - /* multiply (re,im) phase term with smearing/shape factor */ - for (cn=0; cncarr[cm].N; cn++) { - PHr[cn]*=G[cn]; - PHi[cn]*=G[cn]; - } - - - for (cn=0; cncarr[cm].N; cn++) { - /* time smearing TMS eq. 6.81 for EW-array formula */ - //G[cn]*=time_smear(t->carr[cm].ll[cn],t->carr[cm].mm[cn],t->dec0,t->tdelta,t->u[ci],t->v[ci],t->w[ci],t->freq0); - - /* check if source type is not a point source for additional - calculations */ - if (t->carr[cm].stype[cn]!=STYPE_POINT) { - complex double sterm=PHr[cn]+_Complex_I*PHi[cn]; - if (t->carr[cm].stype[cn]==STYPE_SHAPELET) { - sterm*=shapelet_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } else if (t->carr[cm].stype[cn]==STYPE_GAUSSIAN) { - sterm*=gaussian_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } else if (t->carr[cm].stype[cn]==STYPE_DISK) { - sterm*=disk_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } else if (t->carr[cm].stype[cn]==STYPE_RING) { - sterm*=ring_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } - PHr[cn]=creal(sterm); - PHi[cn]=cimag(sterm); - } - - } - - /* flux of each source, at each freq */ - for (cn=0; cncarr[cm].N; cn++) { - /* coherencies are NOT scaled by 1/2, with spectral index */ - if (t->carr[cm].spec_idx[cn]!=0.0) { - double fratio=log(freq0/t->carr[cm].f0[cn]); - double fratio1=fratio*fratio; - double fratio2=fratio1*fratio; - double tempfr=t->carr[cm].spec_idx[cn]*fratio+t->carr[cm].spec_idx1[cn]*fratio1+t->carr[cm].spec_idx2[cn]*fratio2; - /* catch -ve and 0 sI */ - if (t->carr[cm].sI0[cn]>0.0) { - II[cn]=exp(log(t->carr[cm].sI0[cn])+tempfr); - } else { - II[cn]=(t->carr[cm].sI0[cn]==0.0?0.0:-exp(log(-t->carr[cm].sI0[cn])+tempfr)); - } - if (t->carr[cm].sQ0[cn]>0.0) { - QQ[cn]=exp(log(t->carr[cm].sQ0[cn])+tempfr); - } else { - QQ[cn]=(t->carr[cm].sQ0[cn]==0.0?0.0:-exp(log(-t->carr[cm].sQ0[cn])+tempfr)); - } - if (t->carr[cm].sU0[cn]>0.0) { - UU[cn]=exp(log(t->carr[cm].sU0[cn])+tempfr); - } else { - UU[cn]=(t->carr[cm].sU0[cn]==0.0?0.0:-exp(log(-t->carr[cm].sU0[cn])+tempfr)); - } - if (t->carr[cm].sV0[cn]>0.0) { - VV[cn]=exp(log(t->carr[cm].sV0[cn])+tempfr); - } else { - VV[cn]=(t->carr[cm].sV0[cn]==0.0?0.0:-exp(log(-t->carr[cm].sV0[cn])+tempfr)); - } - } else { - II[cn]=t->carr[cm].sI[cn]; - QQ[cn]=t->carr[cm].sQ[cn]; - UU[cn]=t->carr[cm].sU[cn]; - VV[cn]=t->carr[cm].sV[cn]; - } - } - - - /* add up terms together */ - for (cn=0; cncarr[cm].N; cn++) { - complex double Ph,IIl,QQl,UUl,VVl; - Ph=(PHr[cn]+_Complex_I*PHi[cn]); - IIl=Ph*II[cn]; - QQl=Ph*QQ[cn]; - UUl=Ph*UU[cn]; - VVl=Ph*VV[cn]; - C[0]+=IIl+QQl; - C[1]+=UUl+_Complex_I*VVl; - C[2]+=UUl-_Complex_I*VVl; - C[3]+=IIl-QQl; - } - - free(PHr); - free(PHi); - free(G); - free(II); - free(QQ); - free(UU); - free(VV); - -/***********************************************/ - /* form G1*C*G2' */ - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - - /* subtract from baseline visibilities */ - t->x[8*ci]-=creal(T2[0]); - t->x[8*ci+1]-=cimag(T2[0]); - t->x[8*ci+2]-=creal(T2[1]); - t->x[8*ci+3]-=cimag(T2[1]); - t->x[8*ci+4]-=creal(T2[2]); - t->x[8*ci+5]-=cimag(T2[2]); - t->x[8*ci+6]-=creal(T2[3]); - t->x[8*ci+7]-=cimag(T2[3]); - } - } - if (t->pinv) { - cm=t->ccid; - px=(ci+t->boff)/((Ntilebase+t->carr[cm].nchunk-1)/t->carr[cm].nchunk); - pm=&(t->pinv[8*t->N*px]); - G1[0]=(pm[sta1*8])+_Complex_I*(pm[sta1*8+1]); - G1[1]=(pm[sta1*8+2])+_Complex_I*(pm[sta1*8+3]); - G1[2]=(pm[sta1*8+4])+_Complex_I*(pm[sta1*8+5]); - G1[3]=(pm[sta1*8+6])+_Complex_I*(pm[sta1*8+7]); - G2[0]=(pm[sta2*8])+_Complex_I*(pm[sta2*8+1]); - G2[1]=(pm[sta2*8+2])+_Complex_I*(pm[sta2*8+3]); - G2[2]=(pm[sta2*8+4])+_Complex_I*(pm[sta2*8+5]); - G2[3]=(pm[sta2*8+6])+_Complex_I*(pm[sta2*8+7]); - - /* now do correction, if any */ - C[0]=t->x[8*ci]+_Complex_I*t->x[8*ci+1]; - C[1]=t->x[8*ci+2]+_Complex_I*t->x[8*ci+3]; - C[2]=t->x[8*ci+4]+_Complex_I*t->x[8*ci+5]; - C[3]=t->x[8*ci+6]+_Complex_I*t->x[8*ci+7]; - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - t->x[8*ci]=creal(T2[0]); - t->x[8*ci+1]=cimag(T2[0]); - t->x[8*ci+2]=creal(T2[1]); - t->x[8*ci+3]=cimag(T2[1]); - t->x[8*ci+4]=creal(T2[2]); - t->x[8*ci+5]=cimag(T2[2]); - t->x[8*ci+6]=creal(T2[3]); - t->x[8*ci+7]=cimag(T2[3]); - } - } - return NULL; -} - - -int -calculate_residuals(double *u,double *v,double *w,double *p,double *x,int N,int Nbase,int tilesz,baseline_t *barr, clus_source_t *carr, int M,double freq0, double fdelta,double tdelta,double dec0,int Nt, int ccid, double rho) { - int nth,nth1,ci,cj; - - int Nthb0,Nthb; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_base_t *threaddata; - - int Nbase1=Nbase*tilesz; - - int cm; - double *pm,*pinv=0; - cm=-1; - /* find if any cluster is specified for correction of data */ - for (cj=0; cj=0) { /* valid cluser for correction */ - /* allocate memory for inverse J */ - if ((pinv=(double*)malloc((size_t)8*N*carr[cm].nchunk*sizeof(double)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - for (cj=0; cjfdelta*0.5; - - complex double C[4],G1[4],G2[4],T1[4],T2[4]; - int M=(t->M); - int Ntilebase=(t->Nbase)*(t->tilesz); - int px; - for (ci=0; ciNb; ci++) { - /* iterate over the sky model and calculate contribution */ - /* for this x[8*ci:8*(ci+1)-1] */ - /* if this baseline is flagged, we do not compute */ - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - for (cm=0; cm=0 to do a subtraction */ - if (t->carr[cm].id>=0) { - /* gains for this cluster, for sta1,sta2 */ - /* depending on the chunk size and the baseline index, - select right set of parameters - data x=[0,........,Nbase*tilesz] - divided into nchunk chunks - p[0] -> x[0.....Nbase*tilesz/nchunk-1] - p[1] -> x[Nbase*tilesz/nchunk......2*Nbase*tilesz-1] - .... - p[last] -> x[(nchunk-1)*Nbase*tilesz/nchunk......Nbase*tilesz] - - so given bindex, right p[] is bindex/((Nbase*tilesz+nchunk-1)/nchunk) - */ - px=(ci+t->boff)/((Ntilebase+t->carr[cm].nchunk-1)/t->carr[cm].nchunk); - //printf("base %d, cluster %d, parm off %d abs %d\n",t->bindex[ci],cm,px,t->carr[cm].p[px]); - pm=&(t->p[t->carr[cm].p[px]]); - G1[0]=(pm[sta1*8])+_Complex_I*(pm[sta1*8+1]); - G1[1]=(pm[sta1*8+2])+_Complex_I*(pm[sta1*8+3]); - G1[2]=(pm[sta1*8+4])+_Complex_I*(pm[sta1*8+5]); - G1[3]=(pm[sta1*8+6])+_Complex_I*(pm[sta1*8+7]); - G2[0]=(pm[sta2*8])+_Complex_I*(pm[sta2*8+1]); - G2[1]=(pm[sta2*8+2])+_Complex_I*(pm[sta2*8+3]); - G2[2]=(pm[sta2*8+4])+_Complex_I*(pm[sta2*8+5]); - G2[3]=(pm[sta2*8+6])+_Complex_I*(pm[sta2*8+7]); - - - /* iterate over frequencies */ - for (cf=0; cfNchan; cf++) { - freq0=t->freqs[cf]; -/***********************************************/ - /* calculate coherencies for each freq */ - memset(C,0,sizeof(complex double)*4); - - /* setup memory */ - if (posix_memalign((void*)&PHr,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&PHi,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&G,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&II,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&QQ,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&UU,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&VV,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - /* phase (real,imag) parts */ - /* note u=u/c, v=v/c, w=w/c here */ - /* phterm is 2pi(u/c l +v/c m +w/c n) */ - for (cn=0; cncarr[cm].N; cn++) { - G[cn]=2.0*M_PI*(t->u[ci]*t->carr[cm].ll[cn]+t->v[ci]*t->carr[cm].mm[cn]+t->w[ci]*t->carr[cm].nn[cn]); - } - for (cn=0; cncarr[cm].N; cn++) { - sincos(G[cn]*freq0,&PHi[cn],&PHr[cn]); - } - - /* term due to shape of source, also multiplied by freq/time smearing */ - for (cn=0; cncarr[cm].N; cn++) { - /* freq smearing : extra term delta * sinc(delta/2 * phterm) */ - if (G[cn]!=0.0) { - double smfac=G[cn]*fdelta2; - double sinph=sin(smfac)/smfac; - G[cn]=fabs(sinph); - } else { - G[cn]=1.0; - } - } - - /* multiply (re,im) phase term with smearing/shape factor */ - for (cn=0; cncarr[cm].N; cn++) { - PHr[cn]*=G[cn]; - PHi[cn]*=G[cn]; - } - - - for (cn=0; cncarr[cm].N; cn++) { - /* check if source type is not a point source for additional - calculations */ - if (t->carr[cm].stype[cn]!=STYPE_POINT) { - complex double sterm=PHr[cn]+_Complex_I*PHi[cn]; - if (t->carr[cm].stype[cn]==STYPE_SHAPELET) { - sterm*=shapelet_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } else if (t->carr[cm].stype[cn]==STYPE_GAUSSIAN) { - sterm*=gaussian_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } else if (t->carr[cm].stype[cn]==STYPE_DISK) { - sterm*=disk_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } else if (t->carr[cm].stype[cn]==STYPE_RING) { - sterm*=ring_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } - PHr[cn]=creal(sterm); - PHi[cn]=cimag(sterm); - } - - } - - - for (cn=0; cncarr[cm].N; cn++) { - /* coherencies are NOT scaled by 1/2, with spectral index */ - if (t->carr[cm].spec_idx[cn]!=0.0) { - double fratio=log(freq0/t->carr[cm].f0[cn]); - double fratio1=fratio*fratio; - double fratio2=fratio1*fratio; - double tempfr=t->carr[cm].spec_idx[cn]*fratio+t->carr[cm].spec_idx1[cn]*fratio1+t->carr[cm].spec_idx2[cn]*fratio2; - /* catch -ve and 0 sI */ - if (t->carr[cm].sI0[cn]>0.0) { - II[cn]=exp(log(t->carr[cm].sI0[cn])+tempfr); - } else { - II[cn]=(t->carr[cm].sI0[cn]==0.0?0.0:-exp(log(-t->carr[cm].sI0[cn])+tempfr)); - } - if (t->carr[cm].sQ0[cn]>0.0) { - QQ[cn]=exp(log(t->carr[cm].sQ0[cn])+tempfr); - } else { - QQ[cn]=(t->carr[cm].sQ0[cn]==0.0?0.0:-exp(log(-t->carr[cm].sQ0[cn])+tempfr)); - } - if (t->carr[cm].sU0[cn]>0.0) { - UU[cn]=exp(log(t->carr[cm].sU0[cn])+tempfr); - } else { - UU[cn]=(t->carr[cm].sU0[cn]==0.0?0.0:-exp(log(-t->carr[cm].sU0[cn])+tempfr)); - } - if (t->carr[cm].sV0[cn]>0.0) { - VV[cn]=exp(log(t->carr[cm].sV0[cn])+tempfr); - } else { - VV[cn]=(t->carr[cm].sV0[cn]==0.0?0.0:-exp(log(-t->carr[cm].sV0[cn])+tempfr)); - } - } else { - II[cn]=t->carr[cm].sI[cn]; - QQ[cn]=t->carr[cm].sQ[cn]; - UU[cn]=t->carr[cm].sU[cn]; - VV[cn]=t->carr[cm].sV[cn]; - } - } - - /* add up terms together */ - for (cn=0; cncarr[cm].N; cn++) { - complex double Ph,IIl,QQl,UUl,VVl; - Ph=(PHr[cn]+_Complex_I*PHi[cn]); - IIl=Ph*II[cn]; - QQl=Ph*QQ[cn]; - UUl=Ph*UU[cn]; - VVl=Ph*VV[cn]; - C[0]+=IIl+QQl; - C[1]+=UUl+_Complex_I*VVl; - C[2]+=UUl-_Complex_I*VVl; - C[3]+=IIl-QQl; - } - - free(PHr); - free(PHi); - free(G); - free(II); - free(QQ); - free(UU); - free(VV); - - -/***********************************************/ - /* form G1*C*G2' */ - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - - /* subtract from baseline visibilities */ - t->x[8*ci+cf*Ntilebase*8]-=creal(T2[0]); - t->x[8*ci+1+cf*Ntilebase*8]-=cimag(T2[0]); - t->x[8*ci+2+cf*Ntilebase*8]-=creal(T2[1]); - t->x[8*ci+3+cf*Ntilebase*8]-=cimag(T2[1]); - t->x[8*ci+4+cf*Ntilebase*8]-=creal(T2[2]); - t->x[8*ci+5+cf*Ntilebase*8]-=cimag(T2[2]); - t->x[8*ci+6+cf*Ntilebase*8]-=creal(T2[3]); - t->x[8*ci+7+cf*Ntilebase*8]-=cimag(T2[3]); - } - } - } - if (t->pinv) { - cm=t->ccid; - px=(ci+t->boff)/((Ntilebase+t->carr[cm].nchunk-1)/t->carr[cm].nchunk); - pm=&(t->pinv[8*t->N*px]); - G1[0]=(pm[sta1*8])+_Complex_I*(pm[sta1*8+1]); - G1[1]=(pm[sta1*8+2])+_Complex_I*(pm[sta1*8+3]); - G1[2]=(pm[sta1*8+4])+_Complex_I*(pm[sta1*8+5]); - G1[3]=(pm[sta1*8+6])+_Complex_I*(pm[sta1*8+7]); - G2[0]=(pm[sta2*8])+_Complex_I*(pm[sta2*8+1]); - G2[1]=(pm[sta2*8+2])+_Complex_I*(pm[sta2*8+3]); - G2[2]=(pm[sta2*8+4])+_Complex_I*(pm[sta2*8+5]); - G2[3]=(pm[sta2*8+6])+_Complex_I*(pm[sta2*8+7]); - - /* iterate over frequencies */ - for (cf=0; cfNchan; cf++) { - /* now do correction, if any */ - C[0]=t->x[8*ci+cf*Ntilebase*8]+_Complex_I*t->x[8*ci+1+cf*Ntilebase*8]; - C[1]=t->x[8*ci+2+cf*Ntilebase*8]+_Complex_I*t->x[8*ci+3+cf*Ntilebase*8]; - C[2]=t->x[8*ci+4+cf*Ntilebase*8]+_Complex_I*t->x[8*ci+5+cf*Ntilebase*8]; - C[3]=t->x[8*ci+6+cf*Ntilebase*8]+_Complex_I*t->x[8*ci+7+cf*Ntilebase*8]; - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - t->x[8*ci+cf*Ntilebase*8]=creal(T2[0]); - t->x[8*ci+1+cf*Ntilebase*8]=cimag(T2[0]); - t->x[8*ci+2+cf*Ntilebase*8]=creal(T2[1]); - t->x[8*ci+3+cf*Ntilebase*8]=cimag(T2[1]); - t->x[8*ci+4+cf*Ntilebase*8]=creal(T2[2]); - t->x[8*ci+5+cf*Ntilebase*8]=cimag(T2[2]); - t->x[8*ci+6+cf*Ntilebase*8]=creal(T2[3]); - t->x[8*ci+7+cf*Ntilebase*8]=cimag(T2[3]); - } - } - } - return NULL; -} - - -int -calculate_residuals_multifreq(double *u,double *v,double *w,double *p,double *x,int N,int Nbase,int tilesz,baseline_t *barr, clus_source_t *carr, int M,double *freqs,int Nchan, double fdelta,double tdelta,double dec0,int Nt, int ccid, double rho, int phase_only) { - int nth,nth1,ci,cj; - - int Nthb0,Nthb; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_base_t *threaddata; - - int Nbase1=Nbase*tilesz; - - int cm; - double *pm,*pinv=0,*pphase=0; - cm=-1; - /* find if any cluster is specified for correction of data */ - for (cj=0; cj=0) { /* valid cluser for correction */ - /* allocate memory for inverse J */ - if ((pinv=(double*)malloc((size_t)8*N*carr[cm].nchunk*sizeof(double)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (!phase_only) { - for (cj=0; cjfdelta*0.5; - - complex double C[4]; - int M=(t->M); - int Ntilebase=(t->Nbase)*(t->tilesz); - for (ci=0; ciNb; ci++) { - /* iterate over the sky model and calculate contribution */ - /* if this baseline is flagged, we do not compute */ - for (cm=0; cmNchan; cf++) { - freq0=t->freqs[cf]; -/***********************************************/ - /* calculate coherencies for each freq */ - memset(C,0,sizeof(complex double)*4); - /* FIXME: use arrays Nx1 to try to vectorize this part */ - - /* setup memory */ - if (posix_memalign((void*)&PHr,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&PHi,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&G,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&II,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&QQ,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&UU,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&VV,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - /* phase (real,imag) parts */ - /* note u=u/c, v=v/c, w=w/c here */ - /* phterm is 2pi(u/c l +v/c m +w/c n) */ - for (cn=0; cncarr[cm].N; cn++) { - G[cn]=2.0*M_PI*(t->u[ci]*t->carr[cm].ll[cn]+t->v[ci]*t->carr[cm].mm[cn]+t->w[ci]*t->carr[cm].nn[cn]); - } - for (cn=0; cncarr[cm].N; cn++) { - sincos(G[cn]*freq0,&PHi[cn],&PHr[cn]); - } - - /* term due to shape of source, also multiplied by freq/time smearing */ - for (cn=0; cncarr[cm].N; cn++) { - /* freq smearing : extra term delta * sinc(delta/2 * phterm) */ - if (G[cn]!=0.0) { - double smfac=G[cn]*fdelta2; - double sinph=sin(smfac)/smfac; - G[cn]=fabs(sinph); - } else { - G[cn]=1.0; - } - } - - /* multiply (re,im) phase term with smearing/shape factor */ - for (cn=0; cncarr[cm].N; cn++) { - PHr[cn]*=G[cn]; - PHi[cn]*=G[cn]; - } - - - for (cn=0; cncarr[cm].N; cn++) { - /* check if source type is not a point source for additional - calculations */ - if (t->carr[cm].stype[cn]!=STYPE_POINT) { - complex double sterm=PHr[cn]+_Complex_I*PHi[cn]; - if (t->carr[cm].stype[cn]==STYPE_SHAPELET) { - sterm*=shapelet_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } else if (t->carr[cm].stype[cn]==STYPE_GAUSSIAN) { - sterm*=gaussian_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } else if (t->carr[cm].stype[cn]==STYPE_DISK) { - sterm*=disk_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } else if (t->carr[cm].stype[cn]==STYPE_RING) { - sterm*=ring_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } - PHr[cn]=creal(sterm); - PHi[cn]=cimag(sterm); - } - - } - - - /* flux of each source, at each freq */ - for (cn=0; cncarr[cm].N; cn++) { - /* coherencies are NOT scaled by 1/2, with spectral index */ - if (t->carr[cm].spec_idx[cn]!=0.0) { - double fratio=log(freq0/t->carr[cm].f0[cn]); - double fratio1=fratio*fratio; - double fratio2=fratio1*fratio; - double tempfr=t->carr[cm].spec_idx[cn]*fratio+t->carr[cm].spec_idx1[cn]*fratio1+t->carr[cm].spec_idx2[cn]*fratio2; - /* catch -ve and 0 sI */ - if (t->carr[cm].sI0[cn]>0.0) { - II[cn]=exp(log(t->carr[cm].sI0[cn])+tempfr); - } else { - II[cn]=(t->carr[cm].sI0[cn]==0.0?0.0:-exp(log(-t->carr[cm].sI0[cn])+tempfr)); - } - if (t->carr[cm].sQ0[cn]>0.0) { - QQ[cn]=exp(log(t->carr[cm].sQ0[cn])+tempfr); - } else { - QQ[cn]=(t->carr[cm].sQ0[cn]==0.0?0.0:-exp(log(-t->carr[cm].sQ0[cn])+tempfr)); - } - if (t->carr[cm].sU0[cn]>0.0) { - UU[cn]=exp(log(t->carr[cm].sU0[cn])+tempfr); - } else { - UU[cn]=(t->carr[cm].sU0[cn]==0.0?0.0:-exp(log(-t->carr[cm].sU0[cn])+tempfr)); - } - if (t->carr[cm].sV0[cn]>0.0) { - VV[cn]=exp(log(t->carr[cm].sV0[cn])+tempfr); - } else { - VV[cn]=(t->carr[cm].sV0[cn]==0.0?0.0:-exp(log(-t->carr[cm].sV0[cn])+tempfr)); - } - } else { - II[cn]=t->carr[cm].sI[cn]; - QQ[cn]=t->carr[cm].sQ[cn]; - UU[cn]=t->carr[cm].sU[cn]; - VV[cn]=t->carr[cm].sV[cn]; - } - } - - - /* add up terms together */ - for (cn=0; cncarr[cm].N; cn++) { - complex double Ph,IIl,QQl,UUl,VVl; - Ph=(PHr[cn]+_Complex_I*PHi[cn]); - IIl=Ph*II[cn]; - QQl=Ph*QQ[cn]; - UUl=Ph*UU[cn]; - VVl=Ph*VV[cn]; - C[0]+=IIl+QQl; - C[1]+=UUl+_Complex_I*VVl; - C[2]+=UUl-_Complex_I*VVl; - C[3]+=IIl-QQl; - } - - free(PHr); - free(PHi); - free(G); - free(II); - free(QQ); - free(UU); - free(VV); - -/***********************************************/ - /* add to baseline visibilities */ - t->x[8*ci+cf*Ntilebase*8]+=creal(C[0]); - t->x[8*ci+1+cf*Ntilebase*8]+=cimag(C[0]); - t->x[8*ci+2+cf*Ntilebase*8]+=creal(C[1]); - t->x[8*ci+3+cf*Ntilebase*8]+=cimag(C[1]); - t->x[8*ci+4+cf*Ntilebase*8]+=creal(C[2]); - t->x[8*ci+5+cf*Ntilebase*8]+=cimag(C[2]); - t->x[8*ci+6+cf*Ntilebase*8]+=creal(C[3]); - t->x[8*ci+7+cf*Ntilebase*8]+=cimag(C[3]); - } - } - - } - return NULL; -} - - - - -int -predict_visibilities_multifreq(double *u,double *v,double *w,double *x,int N,int Nbase,int tilesz,baseline_t *barr, clus_source_t *carr, int M,double *freqs,int Nchan, double fdelta,double tdelta, double dec0,int Nt, int add_to_data) { - int nth,nth1,ci; - - int Nthb0,Nthb; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_base_t *threaddata; - - int Nbase1=Nbase*tilesz; - - /* calculate min baselines a thread can handle */ - Nthb0=(Nbase1+Nt-1)/Nt; - - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((threaddata=(thread_data_base_t*)malloc((size_t)Nt*sizeof(thread_data_base_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - if (!add_to_data) { - /* set output column to zero */ - memset(x,0,sizeof(double)*8*Nbase*tilesz*Nchan); - } - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthfdelta*0.5; - - complex double C[4],G1[4],G2[4],T1[4],T2[4]; - int M=(t->M); - int Ntilebase=(t->Nbase)*(t->tilesz); - int px; - for (ci=0; ciNb; ci++) { - /* iterate over the sky model and calculate contribution */ - /* for this x[8*ci:8*(ci+1)-1] */ - /* if this baseline is flagged, we do not compute */ - if (!t->add_to_data) { /* only model is written as output */ - for (cf=0; cfNchan; cf++) { - memset(&t->x[8*ci+cf*Ntilebase*8],0,sizeof(double)*8); - } - } - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - for (cm=0; cmignlist[cm]) { - /* gains for this cluster, for sta1,sta2 */ - /* depending on the chunk size and the baseline index, - select right set of parameters - data x=[0,........,Nbase*tilesz] - divided into nchunk chunks - p[0] -> x[0.....Nbase*tilesz/nchunk-1] - p[1] -> x[Nbase*tilesz/nchunk......2*Nbase*tilesz-1] - .... - p[last] -> x[(nchunk-1)*Nbase*tilesz/nchunk......Nbase*tilesz] - - so given bindex, right p[] is bindex/((Nbase*tilesz+nchunk-1)/nchunk) - */ - px=(ci+t->boff)/((Ntilebase+t->carr[cm].nchunk-1)/t->carr[cm].nchunk); - //printf("base %d, cluster %d, parm off %d abs %d\n",t->bindex[ci],cm,px,t->carr[cm].p[px]); - pm=&(t->p[t->carr[cm].p[px]]); - G1[0]=(pm[sta1*8])+_Complex_I*(pm[sta1*8+1]); - G1[1]=(pm[sta1*8+2])+_Complex_I*(pm[sta1*8+3]); - G1[2]=(pm[sta1*8+4])+_Complex_I*(pm[sta1*8+5]); - G1[3]=(pm[sta1*8+6])+_Complex_I*(pm[sta1*8+7]); - G2[0]=(pm[sta2*8])+_Complex_I*(pm[sta2*8+1]); - G2[1]=(pm[sta2*8+2])+_Complex_I*(pm[sta2*8+3]); - G2[2]=(pm[sta2*8+4])+_Complex_I*(pm[sta2*8+5]); - G2[3]=(pm[sta2*8+6])+_Complex_I*(pm[sta2*8+7]); - - - /* iterate over frequencies */ - for (cf=0; cfNchan; cf++) { - freq0=t->freqs[cf]; -/***********************************************/ - /* calculate coherencies for each freq */ - memset(C,0,sizeof(complex double)*4); - /* setup memory */ - if (posix_memalign((void*)&PHr,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&PHi,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&G,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&II,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&QQ,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&UU,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&VV,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - /* phase (real,imag) parts */ - /* note u=u/c, v=v/c, w=w/c here */ - /* phterm is 2pi(u/c l +v/c m +w/c n) */ - for (cn=0; cncarr[cm].N; cn++) { - G[cn]=2.0*M_PI*(t->u[ci]*t->carr[cm].ll[cn]+t->v[ci]*t->carr[cm].mm[cn]+t->w[ci]*t->carr[cm].nn[cn]); - } - for (cn=0; cncarr[cm].N; cn++) { - sincos(G[cn]*freq0,&PHi[cn],&PHr[cn]); - } - - /* term due to shape of source, also multiplied by freq/time smearing */ - for (cn=0; cncarr[cm].N; cn++) { - /* freq smearing : extra term delta * sinc(delta/2 * phterm) */ - if (G[cn]!=0.0) { - double smfac=G[cn]*fdelta2; - double sinph=sin(smfac)/smfac; - G[cn]=fabs(sinph); - } else { - G[cn]=1.0; - } - } - - /* multiply (re,im) phase term with smearing/shape factor */ - for (cn=0; cncarr[cm].N; cn++) { - PHr[cn]*=G[cn]; - PHi[cn]*=G[cn]; - } - - - for (cn=0; cncarr[cm].N; cn++) { - /* check if source type is not a point source for additional - calculations */ - if (t->carr[cm].stype[cn]!=STYPE_POINT) { - complex double sterm=PHr[cn]+_Complex_I*PHi[cn]; - if (t->carr[cm].stype[cn]==STYPE_SHAPELET) { - sterm*=shapelet_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } else if (t->carr[cm].stype[cn]==STYPE_GAUSSIAN) { - sterm*=gaussian_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } else if (t->carr[cm].stype[cn]==STYPE_DISK) { - sterm*=disk_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } else if (t->carr[cm].stype[cn]==STYPE_RING) { - sterm*=ring_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } - PHr[cn]=creal(sterm); - PHi[cn]=cimag(sterm); - } - - } - - - /* flux of each source, at each freq */ - for (cn=0; cncarr[cm].N; cn++) { - /* coherencies are NOT scaled by 1/2, with spectral index */ - if (t->carr[cm].spec_idx[cn]!=0.0) { - double fratio=log(freq0/t->carr[cm].f0[cn]); - double fratio1=fratio*fratio; - double fratio2=fratio1*fratio; - double tempfr=t->carr[cm].spec_idx[cn]*fratio+t->carr[cm].spec_idx1[cn]*fratio1+t->carr[cm].spec_idx2[cn]*fratio2; - /* catch -ve and 0 sI */ - if (t->carr[cm].sI0[cn]>0.0) { - II[cn]=exp(log(t->carr[cm].sI0[cn])+tempfr); - } else { - II[cn]=(t->carr[cm].sI0[cn]==0.0?0.0:-exp(log(-t->carr[cm].sI0[cn])+tempfr)); - } - if (t->carr[cm].sQ0[cn]>0.0) { - QQ[cn]=exp(log(t->carr[cm].sQ0[cn])+tempfr); - } else { - QQ[cn]=(t->carr[cm].sQ0[cn]==0.0?0.0:-exp(log(-t->carr[cm].sQ0[cn])+tempfr)); - } - if (t->carr[cm].sU0[cn]>0.0) { - UU[cn]=exp(log(t->carr[cm].sU0[cn])+tempfr); - } else { - UU[cn]=(t->carr[cm].sU0[cn]==0.0?0.0:-exp(log(-t->carr[cm].sU0[cn])+tempfr)); - } - if (t->carr[cm].sV0[cn]>0.0) { - VV[cn]=exp(log(t->carr[cm].sV0[cn])+tempfr); - } else { - VV[cn]=(t->carr[cm].sV0[cn]==0.0?0.0:-exp(log(-t->carr[cm].sV0[cn])+tempfr)); - } - } else { - II[cn]=t->carr[cm].sI[cn]; - QQ[cn]=t->carr[cm].sQ[cn]; - UU[cn]=t->carr[cm].sU[cn]; - VV[cn]=t->carr[cm].sV[cn]; - } - } - - /* add up terms together */ - for (cn=0; cncarr[cm].N; cn++) { - complex double Ph,IIl,QQl,UUl,VVl; - Ph=(PHr[cn]+_Complex_I*PHi[cn]); - IIl=Ph*II[cn]; - QQl=Ph*QQ[cn]; - UUl=Ph*UU[cn]; - VVl=Ph*VV[cn]; - C[0]+=IIl+QQl; - C[1]+=UUl+_Complex_I*VVl; - C[2]+=UUl-_Complex_I*VVl; - C[3]+=IIl-QQl; - } - - free(PHr); - free(PHi); - free(G); - free(II); - free(QQ); - free(UU); - free(VV); - - -/***********************************************/ - /* form G1*C*G2' */ - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - - /* add to baseline visibilities */ - t->x[8*ci+cf*Ntilebase*8]+=creal(T2[0]); - t->x[8*ci+1+cf*Ntilebase*8]+=cimag(T2[0]); - t->x[8*ci+2+cf*Ntilebase*8]+=creal(T2[1]); - t->x[8*ci+3+cf*Ntilebase*8]+=cimag(T2[1]); - t->x[8*ci+4+cf*Ntilebase*8]+=creal(T2[2]); - t->x[8*ci+5+cf*Ntilebase*8]+=cimag(T2[2]); - t->x[8*ci+6+cf*Ntilebase*8]+=creal(T2[3]); - t->x[8*ci+7+cf*Ntilebase*8]+=cimag(T2[3]); - } - } - } - /* if valid cluster is given, correct with its solutions */ - if (t->pinv) { - cm=t->ccid; - px=(ci+t->boff)/((Ntilebase+t->carr[cm].nchunk-1)/t->carr[cm].nchunk); - pm=&(t->pinv[8*t->N*px]); - G1[0]=(pm[sta1*8])+_Complex_I*(pm[sta1*8+1]); - G1[1]=(pm[sta1*8+2])+_Complex_I*(pm[sta1*8+3]); - G1[2]=(pm[sta1*8+4])+_Complex_I*(pm[sta1*8+5]); - G1[3]=(pm[sta1*8+6])+_Complex_I*(pm[sta1*8+7]); - G2[0]=(pm[sta2*8])+_Complex_I*(pm[sta2*8+1]); - G2[1]=(pm[sta2*8+2])+_Complex_I*(pm[sta2*8+3]); - G2[2]=(pm[sta2*8+4])+_Complex_I*(pm[sta2*8+5]); - G2[3]=(pm[sta2*8+6])+_Complex_I*(pm[sta2*8+7]); - - /* now do correction, if any */ - C[0]=t->x[8*ci]+_Complex_I*t->x[8*ci+1]; - C[1]=t->x[8*ci+2]+_Complex_I*t->x[8*ci+3]; - C[2]=t->x[8*ci+4]+_Complex_I*t->x[8*ci+5]; - C[3]=t->x[8*ci+6]+_Complex_I*t->x[8*ci+7]; - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - t->x[8*ci]=creal(T2[0]); - t->x[8*ci+1]=cimag(T2[0]); - t->x[8*ci+2]=creal(T2[1]); - t->x[8*ci+3]=cimag(T2[1]); - t->x[8*ci+4]=creal(T2[2]); - t->x[8*ci+5]=cimag(T2[2]); - t->x[8*ci+6]=creal(T2[3]); - t->x[8*ci+7]=cimag(T2[3]); - } - } - return NULL; -} - -int -predict_visibilities_multifreq_withsol(double *u,double *v,double *w,double *p,double *x,int *ignlist,int N,int Nbase,int tilesz,baseline_t *barr, clus_source_t *carr, int M,double *freqs,int Nchan, double fdelta,double tdelta,double dec0,int Nt, int add_to_data, int ccid, double rho, int phase_only) { - int nth,nth1,ci; - - int Nthb0,Nthb; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_base_t *threaddata; - - int Nbase1=Nbase*tilesz; - - - int cm,cj; - double *pm,*pinv=0,*pphase=0; - cm=-1; - /* find if any cluster is specified for correction of data */ - for (cj=0; cj=0) { /* valid cluser for correction */ - /* allocate memory for inverse J */ - if ((pinv=(double*)malloc((size_t)8*N*carr[cm].nchunk*sizeof(double)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (!phase_only) { - for (cj=0; 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

EpTuZCS6;_HJm0>3wc@Ox$$FA6Hbabdu3~uvB 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 - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - -#include "cuda.h" -#include -#include - -/* enable this for kernel failure detection */ -//#define CUDA_DBG - -__global__ void kernel_deriv_robust(int Nbase, int tilesz, int M, int Ns, int Nparam, int goff, double robust_nu, double *x, double *coh, double *p, short *bb, int *ptoclus, double *grad){ - /* global thread index */ - unsigned int n = threadIdx.x + blockDim.x*blockIdx.x; - /* parameter number of this thread */ - unsigned int np=n+goff; - - - /* this thread works on - x[8*n:8*n+7], coh[8*M*n:8*M*n+8*M-1] - bb[2*n:2*n+1] (sta1,sta2) - organization of p (N stations and M clusters) - sta 0 sta 1 sta 2 .... sta N-1 - clus 0 0...7 8...15 16...23 ... 8N-8 8N-1 - clus 1 8N..8N+7 8N+8..8N+15 8N+16..8N+23 .... 8N+8N-8...8N+8N-1 - ...... - clus M-1 (M-1)N..(M-1)N+7 (M-1)N+8..(M-1)N+15.... ...(M-1)N+8N-8 (M-1)N+8N-1 - - organization of coherencies (coh) - [0, 8*M-1] : baseline 0 - [8*M, 8*M+8*M-1]: baseline 1 - [n*8*M, n*8*M+8*M-1]: baseline n - ...... - [n*8*M+cm*8, n*8*M+cm*8+7] cluster cm, baseline n - - residual error stored at sum[n] - */ - - if (nptoclus[2*cli+1]+ptoclus[2*cli]*8*Ns-1)) { cli++; } - /* now either ci>=M: cluster not found - or ci=ptoclus[2*cli-1] && np<=ptoclus[2*cli-1]+ptoclus[2*cli-2]*8*Ns-1) { - cli--; - } - - if (cli=0 && sta2>=0) { - /* which parameter 0..7 */ - unsigned int stoff=np_s-stc*8; - /* which cluster 0..M-1 */ - unsigned int stm=cli; - - /* read residual vector, real,imag separate*/ - double xr[8]; - xr[0]=x[nb*8]; - xr[1]=x[nb*8+1]; - xr[2]=x[nb*8+2]; - xr[3]=x[nb*8+3]; - xr[4]=x[nb*8+4]; - xr[5]=x[nb*8+5]; - xr[6]=x[nb*8+6]; - xr[7]=x[nb*8+7]; - - /* read in coherency */ - cuDoubleComplex C[4]; - C[0].x=coh[8*nb*M+8*stm]; - C[0].y=coh[8*nb*M+8*stm+1]; - C[1].x=coh[8*nb*M+8*stm+2]; - C[1].y=coh[8*nb*M+8*stm+3]; - C[2].x=coh[8*nb*M+8*stm+4]; - C[2].y=coh[8*nb*M+8*stm+5]; - C[3].x=coh[8*nb*M+8*stm+6]; - C[3].y=coh[8*nb*M+8*stm+7]; - - cuDoubleComplex G1[4]; - cuDoubleComplex G2[4]; - if(stc==sta1) { - double pp[8]; - pp[0]=0.0; - pp[1]=0.0; - pp[2]=0.0; - pp[3]=0.0; - pp[4]=0.0; - pp[5]=0.0; - pp[6]=0.0; - pp[7]=0.0; - pp[stoff]=1.0; - G1[0].x=pp[0]; - G1[0].y=pp[1]; - G1[1].x=pp[2]; - G1[1].y=pp[3]; - G1[2].x=pp[4]; - G1[2].y=pp[5]; - G1[3].x=pp[6]; - G1[3].y=pp[7]; - - /* conjugate and transpose G2 */ - G2[0].x=p[pstart+tpchunk*8*Ns+sta2*8]; - G2[0].y=-p[pstart+tpchunk*8*Ns+sta2*8+1]; - G2[2].x=p[pstart+tpchunk*8*Ns+sta2*8+2]; - G2[2].y=-p[pstart+tpchunk*8*Ns+sta2*8+3]; - G2[1].x=p[pstart+tpchunk*8*Ns+sta2*8+4]; - G2[1].y=-p[pstart+tpchunk*8*Ns+sta2*8+5]; - G2[3].x=p[pstart+tpchunk*8*Ns+sta2*8+6]; - G2[3].y=-p[pstart+tpchunk*8*Ns+sta2*8+7]; - } else if (stc==sta2) { - double pp[8]; - pp[0]=0.0; - pp[1]=0.0; - pp[2]=0.0; - pp[3]=0.0; - pp[4]=0.0; - pp[5]=0.0; - pp[6]=0.0; - pp[7]=0.0; - pp[stoff]=1.0; - /* conjugate and transpose G2 */ - G2[0].x=pp[0]; - G2[0].y=-pp[1]; - G2[2].x=pp[2]; - G2[2].y=-pp[3]; - G2[1].x=pp[4]; - G2[1].y=-pp[5]; - G2[3].x=pp[6]; - G2[3].y=-pp[7]; - - /* conjugate and transpose G2 */ - G1[0].x=p[pstart+tpchunk*8*Ns+sta1*8]; - G1[0].y=p[pstart+tpchunk*8*Ns+sta1*8+1]; - G1[1].x=p[pstart+tpchunk*8*Ns+sta1*8+2]; - G1[1].y=p[pstart+tpchunk*8*Ns+sta1*8+3]; - G1[2].x=p[pstart+tpchunk*8*Ns+sta1*8+4]; - G1[2].y=p[pstart+tpchunk*8*Ns+sta1*8+5]; - G1[3].x=p[pstart+tpchunk*8*Ns+sta1*8+6]; - G1[3].y=p[pstart+tpchunk*8*Ns+sta1*8+7]; - } - cuDoubleComplex T1[4]; - /* T1=G1*C */ - T1[0]=cuCadd(cuCmul(G1[0],C[0]),cuCmul(G1[1],C[2])); - T1[1]=cuCadd(cuCmul(G1[0],C[1]),cuCmul(G1[1],C[3])); - T1[2]=cuCadd(cuCmul(G1[2],C[0]),cuCmul(G1[3],C[2])); - T1[3]=cuCadd(cuCmul(G1[2],C[1]),cuCmul(G1[3],C[3])); - - cuDoubleComplex T2[4]; - /* T2=T1*G2 , G2 conjugate transposed */ - T2[0]=cuCadd(cuCmul(T1[0],G2[0]),cuCmul(T1[1],G2[2])); - T2[1]=cuCadd(cuCmul(T1[0],G2[1]),cuCmul(T1[1],G2[3])); - T2[2]=cuCadd(cuCmul(T1[2],G2[0]),cuCmul(T1[3],G2[2])); - T2[3]=cuCadd(cuCmul(T1[2],G2[1]),cuCmul(T1[3],G2[3])); - - /* calculate product xr*vec(J_p C J_q^H )/(nu+residual^2) */ - double dsum; - dsum=xr[0]*T2[0].x/(robust_nu+xr[0]*xr[0]); - dsum+=xr[1]*T2[0].y/(robust_nu+xr[1]*xr[1]); - dsum+=xr[2]*T2[1].x/(robust_nu+xr[2]*xr[2]); - dsum+=xr[3]*T2[1].y/(robust_nu+xr[3]*xr[3]); - dsum+=xr[4]*T2[2].x/(robust_nu+xr[4]*xr[4]); - dsum+=xr[5]*T2[2].y/(robust_nu+xr[5]*xr[5]); - dsum+=xr[6]*T2[3].x/(robust_nu+xr[6]*xr[6]); - dsum+=xr[7]*T2[3].y/(robust_nu+xr[7]*xr[7]); - /* accumulate sum NOTE - its important to get the sign right, - depending on res=data-model or res=model-data */ - gsum+=2.0*dsum; - } - - } - - } - } - - - grad[n]=gsum; - } - -} - - -__global__ void kernel_func_wt(int Nbase, double *x, double *coh, double *p, short *bb, double *wt, int N){ - /* global thread index : equal to the baseline */ - unsigned int n = threadIdx.x + blockDim.x*blockIdx.x; - - /* this thread works on - x[8*n:8*n+7], coh[8*M*n:8*M*n+8*M-1] - bb[2*n:2*n+1] (sta1,sta2) - organization of p (N stations and M clusters) - sta 0 sta 1 sta 2 .... sta N-1 - clus 0 0...7 8...15 16...23 ... 8N-8 8N-1 - clus 1 8N..8N+7 8N+8..8N+15 8N+16..8N+23 .... 8N+8N-8...8N+8N-1 - ...... - clus M-1 (M-1)N..(M-1)N+7 (M-1)N+8..(M-1)N+15.... ...(M-1)N+8N-8 (M-1)N+8N-1 - - organization of coherencies (coh) - [0, 8*M-1] : baseline 0 - [8*M, 8*M+8*M-1]: baseline 1 - [n*8*M, n*8*M+8*M-1]: baseline n - ...... - [n*8*M+cm*8, n*8*M+cm*8+7] cluster cm, baseline n - - residual error stored at sum[n] - */ - - if(n=0 - */ - if (sta1>=0 && sta2>=0) { - cuDoubleComplex G1[4]; - double pp[8]; - pp[0]=p[sta1*8]; - pp[1]=p[sta1*8+1]; - pp[2]=p[sta1*8+2]; - pp[3]=p[sta1*8+3]; - pp[4]=p[sta1*8+4]; - pp[5]=p[sta1*8+5]; - pp[6]=p[sta1*8+6]; - pp[7]=p[sta1*8+7]; - G1[0].x=pp[0]; - G1[0].y=pp[1]; - G1[1].x=pp[2]; - G1[1].y=pp[3]; - G1[2].x=pp[4]; - G1[2].y=pp[5]; - G1[3].x=pp[6]; - G1[3].y=pp[7]; - - - cuDoubleComplex C[4]; - C[0].x=coh[8*n]; - C[0].y=coh[8*n+1]; - C[1].x=coh[8*n+2]; - C[1].y=coh[8*n+3]; - C[2].x=coh[8*n+4]; - C[2].y=coh[8*n+5]; - C[3].x=coh[8*n+6]; - C[3].y=coh[8*n+7]; - - cuDoubleComplex T1[4]; - /* T=G1*C */ - T1[0]=cuCadd(cuCmul(G1[0],C[0]),cuCmul(G1[1],C[2])); - T1[1]=cuCadd(cuCmul(G1[0],C[1]),cuCmul(G1[1],C[3])); - T1[2]=cuCadd(cuCmul(G1[2],C[0]),cuCmul(G1[3],C[2])); - T1[3]=cuCadd(cuCmul(G1[2],C[1]),cuCmul(G1[3],C[3])); - - cuDoubleComplex G2[4]; - /* conjugate this */ - pp[0]=p[sta2*8]; - pp[1]=-p[sta2*8+1]; - pp[2]=p[sta2*8+2]; - pp[3]=-p[sta2*8+3]; - pp[4]=p[sta2*8+4]; - pp[5]=-p[sta2*8+5]; - pp[6]=p[sta2*8+6]; - pp[7]=-p[sta2*8+7]; - G2[0].x=pp[0]; - G2[0].y=pp[1]; - G2[2].x=pp[2]; - G2[2].y=pp[3]; - G2[1].x=pp[4]; - G2[1].y=pp[5]; - G2[3].x=pp[6]; - G2[3].y=pp[7]; - - cuDoubleComplex T2[4]; - T2[0]=cuCadd(cuCmul(T1[0],G2[0]),cuCmul(T1[1],G2[2])); - T2[1]=cuCadd(cuCmul(T1[0],G2[1]),cuCmul(T1[1],G2[3])); - T2[2]=cuCadd(cuCmul(T1[2],G2[0]),cuCmul(T1[3],G2[2])); - T2[3]=cuCadd(cuCmul(T1[2],G2[1]),cuCmul(T1[3],G2[3])); - /* update model vector, with weights */ - x[8*n]=wt[8*n]*T2[0].x; - x[8*n+1]=wt[8*n+1]*T2[0].y; - x[8*n+2]=wt[8*n+2]*T2[1].x; - x[8*n+3]=wt[8*n+3]*T2[1].y; - x[8*n+4]=wt[8*n+4]*T2[2].x; - x[8*n+5]=wt[8*n+5]*T2[2].y; - x[8*n+6]=wt[8*n+6]*T2[3].x; - x[8*n+7]=wt[8*n+7]*T2[3].y; - - } - } - -} - -__global__ void kernel_jacf_wt(int Nbase, int M, double *jac, double *coh, double *p, short *bb, double *wt, int N){ - /* global thread index : equal to the baseline */ - unsigned int n = threadIdx.x + blockDim.x*blockIdx.x; - /* which parameter:0...M */ - unsigned int m = threadIdx.y + blockDim.y*blockIdx.y; - - /* this thread works on - x[8*n:8*n+7], coh[8*M*n:8*M*n+8*M-1] - bb[2*n:2*n+1] (sta1,sta2) - organization of p (N stations and M clusters) - sta 0 sta 1 sta 2 .... sta N-1 - clus 0 0...7 8...15 16...23 ... 8N-8 8N-1 - clus 1 8N..8N+7 8N+8..8N+15 8N+16..8N+23 .... 8N+8N-8...8N+8N-1 - ...... - clus M-1 (M-1)N..(M-1)N+7 (M-1)N+8..(M-1)N+15.... ...(M-1)N+8N-8 (M-1)N+8N-1 - - organization of coherencies (coh) - [0, 8*M-1] : baseline 0 - [8*M, 8*M+8*M-1]: baseline 1 - [n*8*M, n*8*M+8*M-1]: baseline n - ...... - [n*8*M+cm*8, n*8*M+cm*8+7] cluster cm, baseline n - - residual error stored at sum[n] - */ - - if(n>3; /* 0...Ns-1 (because M=total par= 8 * Nstations */ - - if (((stc==sta2)||(stc==sta1)) && sta1>=0 && sta2>=0 ) { - - cuDoubleComplex C[4]; - C[0].x=coh[8*n]; - C[0].y=coh[8*n+1]; - C[1].x=coh[8*n+2]; - C[1].y=coh[8*n+3]; - C[2].x=coh[8*n+4]; - C[2].y=coh[8*n+5]; - C[3].x=coh[8*n+6]; - C[3].y=coh[8*n+7]; - - /* which parameter exactly 0..7 */ - //int stoff=m%8; - int stoff=m-stc*8; - double pp1[8]; - double pp2[8]; - if (stc==sta1) { - for (int cn=0; cn<8; cn++) { - pp1[cn]=0.0; - pp2[cn]=p[sta2*8+cn]; - } - pp1[stoff]=1.0; - } else if (stc==sta2) { - for (int cn=0; cn<8; cn++) { - pp2[cn]=0.0; - pp1[cn]=p[sta1*8+cn]; - } - pp2[stoff]=1.0; - } - - - cuDoubleComplex G1[4]; - G1[0].x=pp1[0]; - G1[0].y=pp1[1]; - G1[1].x=pp1[2]; - G1[1].y=pp1[3]; - G1[2].x=pp1[4]; - G1[2].y=pp1[5]; - G1[3].x=pp1[6]; - G1[3].y=pp1[7]; - - cuDoubleComplex T1[4]; - /* T=G1*C */ - T1[0]=cuCadd(cuCmul(G1[0],C[0]),cuCmul(G1[1],C[2])); - T1[1]=cuCadd(cuCmul(G1[0],C[1]),cuCmul(G1[1],C[3])); - T1[2]=cuCadd(cuCmul(G1[2],C[0]),cuCmul(G1[3],C[2])); - T1[3]=cuCadd(cuCmul(G1[2],C[1]),cuCmul(G1[3],C[3])); - - cuDoubleComplex G2[4]; - /* conjugate this */ - G2[0].x=pp2[0]; - G2[0].y=-pp2[1]; - G2[2].x=pp2[2]; - G2[2].y=-pp2[3]; - G2[1].x=pp2[4]; - G2[1].y=-pp2[5]; - G2[3].x=pp2[6]; - G2[3].y=-pp2[7]; - - cuDoubleComplex T2[4]; - T2[0]=cuCadd(cuCmul(T1[0],G2[0]),cuCmul(T1[1],G2[2])); - T2[1]=cuCadd(cuCmul(T1[0],G2[1]),cuCmul(T1[1],G2[3])); - T2[2]=cuCadd(cuCmul(T1[2],G2[0]),cuCmul(T1[3],G2[2])); - T2[3]=cuCadd(cuCmul(T1[2],G2[1]),cuCmul(T1[3],G2[3])); - /* update jacobian , with row weights */ - /* NOTE: row major order */ - jac[m+M*8*n]=wt[8*n]*T2[0].x; - jac[m+M*(8*n+1)]=wt[8*n+1]*T2[0].y; - jac[m+M*(8*n+2)]=wt[8*n+2]*T2[1].x; - jac[m+M*(8*n+3)]=wt[8*n+3]*T2[1].y; - jac[m+M*(8*n+4)]=wt[8*n+4]*T2[2].x; - jac[m+M*(8*n+5)]=wt[8*n+5]*T2[2].y; - jac[m+M*(8*n+6)]=wt[8*n+6]*T2[3].x; - jac[m+M*(8*n+7)]=wt[8*n+7]*T2[3].y; - - } - } - -} - -__global__ void kernel_setweights(int N, double *wt, double alpha){ - unsigned int tid = blockIdx.x*blockDim.x + threadIdx.x; - /* make sure to use only M threads */ - if (tid>>(N, wt, alpha); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - -} - -/* hadamard product by a cuda kernel x<= x*wt */ -void -cudakernel_hadamard(int ThreadsPerBlock, int BlocksPerGrid, int N, double *wt, double *x) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - kernel_hadamard<<< BlocksPerGrid, ThreadsPerBlock >>>(N, wt, x); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - -} - -/* update weights by a cuda kernel */ -void -cudakernel_updateweights(int ThreadsPerBlock, int BlocksPerGrid, int N, double *wt, double *x, double *q, double robust_nu) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - kernel_updateweights<<< BlocksPerGrid, ThreadsPerBlock >>>(N, wt, x, q, robust_nu); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - -} - -/* update weights by a cuda kernel */ -void -cudakernel_sqrtweights(int ThreadsPerBlock, int BlocksPerGrid, int N, double *wt) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - kernel_sqrtweights<<< BlocksPerGrid, ThreadsPerBlock >>>(N, wt); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - -} - -/* evaluate expression for finding optimum nu for - a range of nu values */ -void -cudakernel_evaluatenu(int ThreadsPerBlock, int BlocksPerGrid, int Nd, double qsum, double *q, double deltanu,double nulow) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - kernel_evaluatenu<<< BlocksPerGrid, ThreadsPerBlock >>>(Nd, qsum, q, deltanu, nulow); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - -} - -/* cuda driver for calculating wt \odot f() */ -/* p: params (Mx1), x: data (Nx1), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -void -cudakernel_func_wt(int ThreadsPerBlock, int BlocksPerGrid, double *p, double *x, int M, int N, double *coh, short *bbh, double *wt, int Nbase, int Mclus, int Nstations) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - cudaMemset(x, 0, N*sizeof(double)); -// printf("Kernel data size=%d, block=%d, thread=%d, baselines=%d\n",N,BlocksPerGrid, ThreadsPerBlock,Nbase); - kernel_func_wt<<< BlocksPerGrid, ThreadsPerBlock >>>(Nbase, x, coh, p, bbh, wt, Nstations); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - -} - -/* cuda driver for calculating wt \odot jacf() */ -/* p: params (Mx1), jac: jacobian (NxM), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -void -cudakernel_jacf_wt(int ThreadsPerBlock_row, int ThreadsPerBlock_col, double *p, double *jac, int M, int N, double *coh, short *bbh, double *wt, int Nbase, int Mclus, int Nstations, int clus) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - /* NOTE: use small value for ThreadsPerBlock here, like 8 */ - dim3 threadsPerBlock(16, 8); - /* jacobian: Nbase x Nstations (proportional to N), so */ - dim3 numBlocks((Nbase+threadsPerBlock.x-1)/threadsPerBlock.x, - (M+threadsPerBlock.y-1)/threadsPerBlock.y); - /* set memory of jac to zero */ - cudaMemset(jac, 0, N*M*sizeof(double)); - // printf("Kernel Jax data size=%d, params=%d, block=%d,%d, thread=%d,%d, baselines=%d\n",N, M, numBlocks.x,numBlocks.y, threadsPerBlock.x, threadsPerBlock.y, Nbase); - kernel_jacf_wt<<< numBlocks, threadsPerBlock>>>(Nbase, M, jac, coh, p, bbh, wt, Nstations); - - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - -} - -/* cuda driver for kernel */ -/* ThreadsPerBlock: keep <= 128 ??? - BlocksPerGrid: depends on the threads/baselines> Threads*Blocks approx baselines - Nbase: no of baselines (total, including tilesz >1) - tilesz: tile size - M: no of clusters - Ns: no of stations - Nparam: no of actual parameters <=total - goff: starting point of gradient calculation 0..Nparams - x: N*8 x 1 residual - coh: N*8*M x 1 - p: M*Ns*8 x 1 - bb: 2*N x 1 - ptoclus: 2*M x 1 - - grad: Nparamsx1 gradient values -*/ -void cudakernel_lbfgs_robust(int ThreadsPerBlock, int BlocksPerGrid, int Nbase, int tilesz, int M, int Ns, int Nparam, int goff, double robust_nu, double *x, double *coh, double *p, short *bb, int *ptoclus, double *grad){ - -#ifdef CUDA_DBG - cudaError_t error; -#endif - /* invoke device on this block/thread grid (last argument is buffer size in bytes) */ - kernel_deriv_robust<<< BlocksPerGrid, ThreadsPerBlock, ThreadsPerBlock*sizeof(double) >>> (Nbase, tilesz, M, Ns, Nparam, goff, robust_nu, x, coh, p, bb, ptoclus, grad); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - -} - -} diff --git a/src/lib/Solvers/robust.o b/src/lib/Solvers/robust.o deleted file mode 100644 index c605c89e09ead3d07b0ca21b06f200b45a4b6a0e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63008 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 - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - -#include "cuda.h" -#include -#include - -/* enable this for checking for kernel failure */ -//#define CUDA_DBG - -__global__ void -kernel_func_wt_fl(int Nbase, float *x, float *coh, float *p, short *bb, float *wt, int N){ - /* global thread index : equal to the baseline */ - unsigned int n = threadIdx.x + blockDim.x*blockIdx.x; - - /* this thread works on - x[8*n:8*n+7], coh[8*M*n:8*M*n+8*M-1] - bb[2*n:2*n+1] (sta1,sta2) - organization of p (N stations and M clusters) - sta 0 sta 1 sta 2 .... sta N-1 - clus 0 0...7 8...15 16...23 ... 8N-8 8N-1 - clus 1 8N..8N+7 8N+8..8N+15 8N+16..8N+23 .... 8N+8N-8...8N+8N-1 - ...... - clus M-1 (M-1)N..(M-1)N+7 (M-1)N+8..(M-1)N+15.... ...(M-1)N+8N-8 (M-1)N+8N-1 - - organization of coherencies (coh) - [0, 8*M-1] : baseline 0 - [8*M, 8*M+8*M-1]: baseline 1 - [n*8*M, n*8*M+8*M-1]: baseline n - ...... - [n*8*M+cm*8, n*8*M+cm*8+7] cluster cm, baseline n - - residual error stored at sum[n] - */ - - if(n=0 - */ - if (sta1>=0 && sta2>=0) { - cuComplex G1[4]; - float pp[8]; - pp[0]=p[sta1*8]; - pp[1]=p[sta1*8+1]; - pp[2]=p[sta1*8+2]; - pp[3]=p[sta1*8+3]; - pp[4]=p[sta1*8+4]; - pp[5]=p[sta1*8+5]; - pp[6]=p[sta1*8+6]; - pp[7]=p[sta1*8+7]; - G1[0].x=pp[0]; - G1[0].y=pp[1]; - G1[1].x=pp[2]; - G1[1].y=pp[3]; - G1[2].x=pp[4]; - G1[2].y=pp[5]; - G1[3].x=pp[6]; - G1[3].y=pp[7]; - - - cuComplex C[4]; - C[0].x=coh[8*n]; - C[0].y=coh[8*n+1]; - C[1].x=coh[8*n+2]; - C[1].y=coh[8*n+3]; - C[2].x=coh[8*n+4]; - C[2].y=coh[8*n+5]; - C[3].x=coh[8*n+6]; - C[3].y=coh[8*n+7]; - - cuComplex T1[4]; - /* T=G1*C */ - T1[0]=cuCaddf(cuCmulf(G1[0],C[0]),cuCmulf(G1[1],C[2])); - T1[1]=cuCaddf(cuCmulf(G1[0],C[1]),cuCmulf(G1[1],C[3])); - T1[2]=cuCaddf(cuCmulf(G1[2],C[0]),cuCmulf(G1[3],C[2])); - T1[3]=cuCaddf(cuCmulf(G1[2],C[1]),cuCmulf(G1[3],C[3])); - - cuComplex G2[4]; - /* conjugate this */ - pp[0]=p[sta2*8]; - pp[1]=-p[sta2*8+1]; - pp[2]=p[sta2*8+2]; - pp[3]=-p[sta2*8+3]; - pp[4]=p[sta2*8+4]; - pp[5]=-p[sta2*8+5]; - pp[6]=p[sta2*8+6]; - pp[7]=-p[sta2*8+7]; - G2[0].x=pp[0]; - G2[0].y=pp[1]; - G2[2].x=pp[2]; - G2[2].y=pp[3]; - G2[1].x=pp[4]; - G2[1].y=pp[5]; - G2[3].x=pp[6]; - G2[3].y=pp[7]; - - cuComplex T2[4]; - T2[0]=cuCaddf(cuCmulf(T1[0],G2[0]),cuCmulf(T1[1],G2[2])); - T2[1]=cuCaddf(cuCmulf(T1[0],G2[1]),cuCmulf(T1[1],G2[3])); - T2[2]=cuCaddf(cuCmulf(T1[2],G2[0]),cuCmulf(T1[3],G2[2])); - T2[3]=cuCaddf(cuCmulf(T1[2],G2[1]),cuCmulf(T1[3],G2[3])); - /* update model vector, with weights */ - x[8*n]=wt[8*n]*T2[0].x; - x[8*n+1]=wt[8*n+1]*T2[0].y; - x[8*n+2]=wt[8*n+2]*T2[1].x; - x[8*n+3]=wt[8*n+3]*T2[1].y; - x[8*n+4]=wt[8*n+4]*T2[2].x; - x[8*n+5]=wt[8*n+5]*T2[2].y; - x[8*n+6]=wt[8*n+6]*T2[3].x; - x[8*n+7]=wt[8*n+7]*T2[3].y; - - } - } - -} - -__global__ void -kernel_jacf_wt_fl(int Nbase, int M, float *jac, float *coh, float *p, short *bb, float *wt, int N){ - /* global thread index : equal to the baseline */ - unsigned int n = threadIdx.x + blockDim.x*blockIdx.x; - /* which parameter:0...M */ - unsigned int m = threadIdx.y + blockDim.y*blockIdx.y; - - /* this thread works on - x[8*n:8*n+7], coh[8*M*n:8*M*n+8*M-1] - bb[2*n:2*n+1] (sta1,sta2) - organization of p (N stations and M clusters) - sta 0 sta 1 sta 2 .... sta N-1 - clus 0 0...7 8...15 16...23 ... 8N-8 8N-1 - clus 1 8N..8N+7 8N+8..8N+15 8N+16..8N+23 .... 8N+8N-8...8N+8N-1 - ...... - clus M-1 (M-1)N..(M-1)N+7 (M-1)N+8..(M-1)N+15.... ...(M-1)N+8N-8 (M-1)N+8N-1 - - organization of coherencies (coh) - [0, 8*M-1] : baseline 0 - [8*M, 8*M+8*M-1]: baseline 1 - [n*8*M, n*8*M+8*M-1]: baseline n - ...... - [n*8*M+cm*8, n*8*M+cm*8+7] cluster cm, baseline n - - residual error stored at sum[n] - */ - - if(n>3; /* 0...Ns-1 (because M=total par= 8 * Nstations */ - - if (((stc==sta2)||(stc==sta1)) && sta1>=0 && sta2>=0 ) { - - cuComplex C[4]; - C[0].x=coh[8*n]; - C[0].y=coh[8*n+1]; - C[1].x=coh[8*n+2]; - C[1].y=coh[8*n+3]; - C[2].x=coh[8*n+4]; - C[2].y=coh[8*n+5]; - C[3].x=coh[8*n+6]; - C[3].y=coh[8*n+7]; - - /* which parameter exactly 0..7 */ - //int stoff=m%8; - int stoff=m-stc*8; - float pp1[8]; - float pp2[8]; - if (stc==sta1) { - for (int cn=0; cn<8; cn++) { - pp1[cn]=0.0f; - pp2[cn]=p[sta2*8+cn]; - } - pp1[stoff]=1.0f; - } else if (stc==sta2) { - for (int cn=0; cn<8; cn++) { - pp2[cn]=0.0f; - pp1[cn]=p[sta1*8+cn]; - } - pp2[stoff]=1.0f; - } - - - cuComplex G1[4]; - G1[0].x=pp1[0]; - G1[0].y=pp1[1]; - G1[1].x=pp1[2]; - G1[1].y=pp1[3]; - G1[2].x=pp1[4]; - G1[2].y=pp1[5]; - G1[3].x=pp1[6]; - G1[3].y=pp1[7]; - - cuComplex T1[4]; - /* T=G1*C */ - T1[0]=cuCaddf(cuCmulf(G1[0],C[0]),cuCmulf(G1[1],C[2])); - T1[1]=cuCaddf(cuCmulf(G1[0],C[1]),cuCmulf(G1[1],C[3])); - T1[2]=cuCaddf(cuCmulf(G1[2],C[0]),cuCmulf(G1[3],C[2])); - T1[3]=cuCaddf(cuCmulf(G1[2],C[1]),cuCmulf(G1[3],C[3])); - - cuComplex G2[4]; - /* conjugate this */ - G2[0].x=pp2[0]; - G2[0].y=-pp2[1]; - G2[2].x=pp2[2]; - G2[2].y=-pp2[3]; - G2[1].x=pp2[4]; - G2[1].y=-pp2[5]; - G2[3].x=pp2[6]; - G2[3].y=-pp2[7]; - - cuComplex T2[4]; - T2[0]=cuCaddf(cuCmulf(T1[0],G2[0]),cuCmulf(T1[1],G2[2])); - T2[1]=cuCaddf(cuCmulf(T1[0],G2[1]),cuCmulf(T1[1],G2[3])); - T2[2]=cuCaddf(cuCmulf(T1[2],G2[0]),cuCmulf(T1[3],G2[2])); - T2[3]=cuCaddf(cuCmulf(T1[2],G2[1]),cuCmulf(T1[3],G2[3])); - /* update jacobian , with row weights */ - /* NOTE: row major order */ - jac[m+M*8*n]=wt[8*n]*T2[0].x; - jac[m+M*(8*n+1)]=wt[8*n+1]*T2[0].y; - jac[m+M*(8*n+2)]=wt[8*n+2]*T2[1].x; - jac[m+M*(8*n+3)]=wt[8*n+3]*T2[1].y; - jac[m+M*(8*n+4)]=wt[8*n+4]*T2[2].x; - jac[m+M*(8*n+5)]=wt[8*n+5]*T2[2].y; - jac[m+M*(8*n+6)]=wt[8*n+6]*T2[3].x; - jac[m+M*(8*n+7)]=wt[8*n+7]*T2[3].y; - - } - } - -} - -__global__ void -kernel_setweights_fl(int N, float *wt, float alpha){ - unsigned int tid = blockIdx.x*blockDim.x + threadIdx.x; - /* make sure to use only M threads */ - if (tid>>(N, wt, alpha); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - -} - -/* hadamard product by a cuda kernel x<= x*wt */ -void -cudakernel_hadamard_fl(int ThreadsPerBlock, int BlocksPerGrid, int N, float *wt, float *x) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - kernel_hadamard_fl<<< BlocksPerGrid, ThreadsPerBlock >>>(N, wt, x); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - -} - -/* update weights by a cuda kernel */ -void -cudakernel_updateweights_fl(int ThreadsPerBlock, int BlocksPerGrid, int N, float *wt, float *x, float *q, float robust_nu) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - kernel_updateweights_fl<<< BlocksPerGrid, ThreadsPerBlock >>>(N, wt, x, q, robust_nu); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - -} - -/* update weights by a cuda kernel */ -void -cudakernel_sqrtweights_fl(int ThreadsPerBlock, int BlocksPerGrid, int N, float *wt) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - kernel_sqrtweights_fl<<< BlocksPerGrid, ThreadsPerBlock >>>(N, wt); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - -} - -/* evaluate expression for finding optimum nu for - a range of nu values */ -void -cudakernel_evaluatenu_fl(int ThreadsPerBlock, int BlocksPerGrid, int Nd, float qsum, float *q, float deltanu,float nulow) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - kernel_evaluatenu_fl<<< BlocksPerGrid, ThreadsPerBlock >>>(Nd, qsum, q, deltanu,nulow); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - -} - - -/* evaluate expression for finding optimum nu for - a range of nu values, using AECM (p=8 before, but now p=2) - nu0: current value of robust_nu*/ -void -cudakernel_evaluatenu_fl_eight(int ThreadsPerBlock, int BlocksPerGrid, int Nd, float qsum, float *q, float deltanu,float nulow, float nu0) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - kernel_evaluatenu_fl_eight<<< BlocksPerGrid, ThreadsPerBlock >>>(Nd, qsum, q, deltanu,nulow, nu0); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - -} - -/* cuda driver for calculating wt \odot f() */ -/* p: params (Mx1), x: data (Nx1), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -void -cudakernel_func_wt_fl(int ThreadsPerBlock, int BlocksPerGrid, float *p, float *x, int M, int N, float *coh, short *bbh, float *wt, int Nbase, int Mclus, int Nstations) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - cudaMemset(x, 0, N*sizeof(float)); -// printf("Kernel data size=%d, block=%d, thread=%d, baselines=%d\n",N,BlocksPerGrid, ThreadsPerBlock,Nbase); - kernel_func_wt_fl<<< BlocksPerGrid, ThreadsPerBlock >>>(Nbase, x, coh, p, bbh, wt, Nstations); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - -} - -/* cuda driver for calculating wt \odot jacf() */ -/* p: params (Mx1), jac: jacobian (NxM), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -void -cudakernel_jacf_wt_fl(int ThreadsPerBlock_row, int ThreadsPerBlock_col, float *p, float *jac, int M, int N, float *coh, short *bbh, float *wt, int Nbase, int Mclus, int Nstations, int clus) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - /* NOTE: use small value for ThreadsPerBlock here, like 8 */ - dim3 threadsPerBlock(16, 8); - /* jacobian: Nbase x Nstations (proportional to N), so */ - dim3 numBlocks((Nbase+threadsPerBlock.x-1)/threadsPerBlock.x, - (M+threadsPerBlock.y-1)/threadsPerBlock.y); - /* set memory of jac to zero */ - cudaMemset(jac, 0, N*M*sizeof(float)); - // printf("Kernel Jax data size=%d, params=%d, block=%d,%d, thread=%d,%d, baselines=%d\n",N, M, numBlocks.x,numBlocks.y, threadsPerBlock.x, threadsPerBlock.y, Nbase); - kernel_jacf_wt_fl<<< numBlocks, threadsPerBlock>>>(Nbase, M, jac, coh, p, bbh, wt, Nstations); - - cudaDeviceSynchronize(); - -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - -} - -} diff --git a/src/lib/Solvers/robust_fl.o b/src/lib/Solvers/robust_fl.o deleted file mode 100644 index 597abb6007f5d74600e569440bccff046d9a6374..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 51456 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%0D{BJUscn|Tor^|!-i1r#rj9NrP}fDHGO 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 - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#include "Solvers.h" -#include -#ifdef HAVE_CUDA -#include -#endif - -/**** repeated code here ********************/ -/* Jones matrix multiplication - C=A*B -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -amb(complex double * __restrict a, complex double * __restrict b, complex double * __restrict c) { - c[0]=a[0]*b[0]+a[1]*b[2]; - c[1]=a[0]*b[1]+a[1]*b[3]; - c[2]=a[2]*b[0]+a[3]*b[2]; - c[3]=a[2]*b[1]+a[3]*b[3]; -} - -/* Jones matrix multiplication - C=A*B^H -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -ambt(complex double * __restrict a, complex double * __restrict b, complex double * __restrict c) { - c[0]=a[0]*conj(b[0])+a[1]*conj(b[1]); - c[1]=a[0]*conj(b[2])+a[1]*conj(b[3]); - c[2]=a[2]*conj(b[0])+a[3]*conj(b[1]); - c[3]=a[2]*conj(b[2])+a[3]*conj(b[3]); -} - -/**** end repeated code ********************/ -/***************************************************************/ -/* worker thread to calculate - sum ( log(1+ (y_i-f_i)^2/nu) ) -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -func_robust_th(void *data) { - thread_data_logf_t *t=(thread_data_logf_t*)data; - double inv_nu=1.0/t->nu; - t->sum=0.0; - int ci; - double err; - for (ci=t->start; ci<=t->end; ci++) { - err=t->x[ci]-t->f[ci]; - err=err*err*inv_nu; - t->sum+=log(1.0+err); - } - return NULL; -} -/* recalculate log(1+ (y_i-f_i)^2/nu) - from function() that calculates f_i - y (data) - f=function() - x=log(1+(y_i-f_i)^2/nu) output - all size n x 1 - Nt: no of threads - - return sum(log(..)) -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static double -func_robust( - double *f, double *y, int n, double robust_nu, int Nt) { - - pthread_attr_t attr; - pthread_t *th_array; - thread_data_logf_t *threaddata; - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((threaddata=(thread_data_logf_t*)malloc((size_t)Nt*sizeof(thread_data_logf_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - int ci,nth,Nparm; - Nparm=(n+Nt-1)/Nt; - - ci=0; - for (nth=0; nth=n) { - threaddata[nth].end=n-1; - } - ci=ci+Nparm; - pthread_create(&th_array[nth],&attr,func_robust_th,(void*)(&threaddata[nth])); - - } - /* now wait for threads to finish */ - double mysum=0.0; - for(nth=0; nth0) { - /* find the location of k-1 th value */ - if (ii>0) { - ii=ii-1; - } else { - ii=M-1; - } - /* s,y will have 0,1,...,ii,ii+1,...M-1 */ - /* map this to ii+1,ii+2,...,M-1,0,1,..,ii */ - for (ci=0; ci%d ",ci,idx[ci]); - } - printf("\n"); -#endif - /* q = grad(f)k : pk<=gk */ - my_dcopy(m,gk,1,pk,1); - /* this should be done in the right order */ - for (ci=0; ci0) { - gamma=my_ddot(m,&s[m*idx[M-1]],&y[m*idx[M-1]]); - gamma/=my_ddot(m,&y[m*idx[M-1]],&y[m*idx[M-1]]); - /* Hk(0)=gamma I, so scale q by gamma */ - /* r= Hk(0) q */ - my_dscal(m,gamma,pk); - } - - for (ci=0; cib is possible) - to find step that minimizes cost function */ -/* func: vector function - xk: parameter values size m x 1 (at which step is calculated) - pk: step direction size m x 1 (x(k+1)=x(k)+alphak * pk) - a/b: interval for interpolation - x: size n x 1 (storage) - xp: size m x 1 (storage) - xo: observed data size n x 1 - n: size of vector function - step: step size for differencing - adata: additional data passed to the function -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static double -cubic_interp( - void (*func)(double *p, double *hx, int m, int n, void *adata), - double *xk, double *pk, double a, double b, double *x, double *xp, double *xo, int m, int n, double step, void *adata) { - - me_data_t *dp=(me_data_t*)adata; - double f0,f1,f0d,f1d; /* function values and derivatives at a,b */ - double p01,p02,z0,fz0; - double aa,cc; - - my_dcopy(m,xk,1,xp,1); /* xp<=xk */ - my_daxpy(m,pk,a,xp); /* xp<=xp+(a)*pk */ - func(xp,x,m,n,adata); - //my_daxpy(n,xo,-1.0,x); - //f0=my_dnrm2(n,x); - //f0*=f0; - f0=func_robust(x,xo,n,dp->robust_nu,dp->Nt); - /* grad(phi_0): evaluate at -step and +step */ - my_daxpy(m,pk,step,xp); /* xp<=xp+(a+step)*pk */ - func(xp,x,m,n,adata); - //my_daxpy(n,xo,-1.0,x); - //p01=my_dnrm2(n,x); - p01=func_robust(x,xo,n,dp->robust_nu,dp->Nt); - my_daxpy(m,pk,-2.0*step,xp); /* xp<=xp+(a-step)*pk */ - func(xp,x,m,n,adata); - //my_daxpy(n,xo,-1.0,x); - //p02=my_dnrm2(n,x); - p02=func_robust(x,xo,n,dp->robust_nu,dp->Nt); - f0d=(p01-p02)/(2.0*step); - - my_dcopy(m,xk,1,xp,1); /* xp<=xk */ - my_daxpy(m,pk,b,xp); /* xp<=xp+(b)*pk */ - func(xp,x,m,n,adata); - //my_daxpy(n,xo,-1.0,x); - //f1=my_dnrm2(n,x); - //f1*=f1; - f1=func_robust(x,xo,n,dp->robust_nu,dp->Nt); - /* grad(phi_1): evaluate at -step and +step */ - my_daxpy(m,pk,step,xp); /* xp<=xp+(b+step)*pk */ - func(xp,x,m,n,adata); - //my_daxpy(n,xo,-1.0,x); - //p01=my_dnrm2(n,x); - p01=func_robust(x,xo,n,dp->robust_nu,dp->Nt); - my_daxpy(m,pk,-2.0*step,xp); /* xp<=xp+(b-step)*pk */ - func(xp,x,m,n,adata); - //my_daxpy(n,xo,-1.0,x); - //p02=my_dnrm2(n,x); - p02=func_robust(x,xo,n,dp->robust_nu,dp->Nt); - f1d=(p01-p02)/(2.0*step); - - - //printf("Interp a,f(a),f'(a): (%lf,%lf,%lf) (%lf,%lf,%lf)\n",a,f0,f0d,b,f1,f1d); - /* cubic poly in [0,1] is f0+f0d z+eta z^2+xi z^3 - where eta=3(f1-f0)-2f0d-f1d, xi=f0d+f1d-2(f1-f0) - derivative f0d+2 eta z+3 xi z^2 => cc+bb z+aa z^2 */ - aa=3.0*(f0-f1)/(b-a)+(f1d-f0d); - p01=aa*aa-f0d*f1d; - /* root exist? */ - if (p01>0.0) { - /* root */ - cc=sqrt(p01); - z0=b-(f1d+cc-aa)*(b-a)/(f1d-f0d+2.0*cc); - /* FIXME: check if this is within boundary */ - aa=MAX(a,b); - cc=MIN(a,b); - //printf("Root=%lf, in [%lf,%lf]\n",z0,cc,aa); - if (z0>aa || z0robust_nu,dp->Nt); - } - - /* now choose between f0,f1,fz0,fz1 */ - if (f0b) is possible - x: size n x 1 (storage) - xp: size m x 1 (storage) - phi_0: phi(0) - gphi_0: grad(phi(0)) - xo: observed data size n x 1 - n: size of vector function - step: step size for differencing - adata: additional data passed to the function -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static double -linesearch_zoom( - void (*func)(double *p, double *hx, int m, int n, void *adata), - double *xk, double *pk, double a, double b, double *x, double *xp, double phi_0, double gphi_0, double sigma, double rho, double t1, double t2, double t3, double *xo, int m, int n, double step, void *adata) { - - me_data_t *dp=(me_data_t*)adata; - double alphaj,phi_j,phi_aj; - double gphi_j,p01,p02,aj,bj; - double alphak=1.0; - int ci,found_step=0; - - aj=a; - bj=b; - ci=0; - while(ci<10) { - /* choose alphaj from [a+t2(b-a),b-t3(b-a)] */ - p01=aj+t2*(bj-aj); - p02=bj-t3*(bj-aj); - alphaj=cubic_interp(func,xk,pk,p01,p02,x,xp,xo,m,n,step,adata); - //printf("cubic intep [%lf,%lf]->%lf\n",p01,p02,alphaj); - - /* evaluate phi(alphaj) */ - my_dcopy(m,xk,1,xp,1); /* xp<=xk */ - my_daxpy(m,pk,alphaj,xp); /* xp<=xp+(alphaj)*pk */ - func(xp,x,m,n,adata); - /* calculate x<=x-xo */ - //my_daxpy(n,xo,-1.0,x); - //phi_j=my_dnrm2(n,x); - //phi_j*=phi_j; - phi_j=func_robust(x,xo,n,dp->robust_nu,dp->Nt); - - /* evaluate phi(aj) */ - my_dcopy(m,xk,1,xp,1); /* xp<=xk */ - my_daxpy(m,pk,aj,xp); /* xp<=xp+(alphaj)*pk */ - func(xp,x,m,n,adata); - /* calculate x<=x-xo */ - //my_daxpy(n,xo,-1.0,x); - //phi_aj=my_dnrm2(n,x); - //phi_aj*=phi_aj; - phi_aj=func_robust(x,xo,n,dp->robust_nu,dp->Nt); - -#ifdef DEBUG - printf("phi_j=%lf, phi_aj=%lf\n",phi_j,phi_aj); -#endif - if ((phi_j>phi_0+rho*alphaj*gphi_0) || phi_j>=phi_aj) { - bj=alphaj; /* aj unchanged */ - } else { - /* evaluate grad(alphaj) */ - my_dcopy(m,xk,1,xp,1); /* xp<=xk */ - my_daxpy(m,pk,alphaj+step,xp); /* xp<=xp+(alphaj+step)*pk */ - func(xp,x,m,n,adata); - /* calculate x<=x-xo */ - //my_daxpy(n,xo,-1.0,x); - //p01=my_dnrm2(n,x); - p01=func_robust(x,xo,n,dp->robust_nu,dp->Nt); - - my_daxpy(m,pk,-2.0*step,xp); /* xp<=xp+(alphaj-step)*pk */ - func(xp,x,m,n,adata); - /* calculate x<=x-xo */ - //my_daxpy(n,xo,-1.0,x); - //p02=my_dnrm2(n,x); - p02=func_robust(x,xo,n,dp->robust_nu,dp->Nt); - gphi_j=(p01-p02)/(2.0*step); -#ifdef DEBUG - printf("p01=%lf, p02=%lf\n",p01,p02); -#endif - - /* termination due to roundoff/other errors pp. 38, Fletcher */ - if ((aj-alphaj)*gphi_j<=step) { - alphak=alphaj; - found_step=1; - break; - } - - if (fabs(gphi_j)<=-sigma*gphi_0) { - alphak=alphaj; - found_step=1; - break; - } - - if (gphi_j*(bj-aj)>=0) { - bj=aj; - } /* else bj unchanged */ - aj=alphaj; - } - ci++; - } - - if (!found_step) { - /* use bound to find possible step */ - alphak=alphaj; - } - -#ifdef DEBUG - printf("Found %lf Interval [%lf,%lf]\n",alphak,a,b); -#endif - return alphak; -} - - - -/* line search */ -/* func: vector function - xk: parameter values size m x 1 (at which step is calculated) - pk: step direction size m x 1 (x(k+1)=x(k)+alphak * pk) - alpha1: initial value for step - sigma,rho,t1,t2,t3: line search parameters (from Fletcher) - xo: observed data size n x 1 - n: size of vector function - step: step size for differencing - adata: additional data passed to the function -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static double -linesearch( - void (*func)(double *p, double *hx, int m, int n, void *adata), - double *xk, double *pk, double alpha1, double sigma, double rho, double t1, double t2, double t3, double *xo, int m, int n, double step, void *adata) { - - /* phi(alpha)=f(xk+alpha pk) - for vector function func - f(xk) =||func(xk)||^2 */ - - me_data_t *dp=(me_data_t*)adata; - double *x,*xp; - double alphai,alphai1; - double phi_0,phi_alphai,phi_alphai1; - double p01,p02; - double gphi_0,gphi_i; - double alphak; - - double mu; - double tol; /* lower limit for minimization */ - - int ci; - - if ((x=(double*)calloc((size_t)n,sizeof(double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((xp=(double*)calloc((size_t)m,sizeof(double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - alphak=1.0; - /* evaluate phi_0 and grad(phi_0) */ - func(xk,x,m,n,adata); - //my_daxpy(n,xo,-1.0,x); - //phi_0=my_dnrm2(n,x); - //phi_0*=phi_0; - phi_0=func_robust(x,xo,n,dp->robust_nu,dp->Nt); - /* select tolarance 1/100 of current function value */ - tol=MIN(0.01*phi_0,1e-6); - - - /* grad(phi_0): evaluate at -step and +step */ - my_dcopy(m,xk,1,xp,1); /* xp<=xk */ - my_daxpy(m,pk,step,xp); /* xp<=xp+(0.0+step)*pk */ - func(xp,x,m,n,adata); - /* calculate x<=x-xo */ - //my_daxpy(n,xo,-1.0,x); - //p01=my_dnrm2(n,x); - p01=func_robust(x,xo,n,dp->robust_nu,dp->Nt); - my_daxpy(m,pk,-2.0*step,xp); /* xp<=xp+(0.0-step)*pk */ - func(xp,x,m,n,adata); - /* calculate x<=x-xo */ - //my_daxpy(n,xo,-1.0,x); - //p02=my_dnrm2(n,x); - p02=func_robust(x,xo,n,dp->robust_nu,dp->Nt); - gphi_0=(p01-p02)/(2.0*step); - - - /* estimate for mu */ - /* mu = (tol-phi_0)/(rho gphi_0) */ - mu=(tol-phi_0)/(rho*gphi_0); -#ifdef DEBUG - printf("cost=%lf grad=%lf mu=%lf, alpha1=%lf\n",phi_0,gphi_0,mu,alpha1); -#endif - - ci=1; - alphai=alpha1; /* initial value for alpha(i) : check if 0robust_nu,dp->Nt); - - if (phi_alphaiphi_0+alphai*gphi_0) || (ci>1 && phi_alphai>=phi_alphai1)) { - /* ai=alphai1, bi=alphai bracket */ - alphak=linesearch_zoom(func,xk,pk,alphai1,alphai,x,xp,phi_0,gphi_0,sigma,rho,t1,t2,t3,xo,m,n,step,adata); -#ifdef DEBUG - printf("Linesearch : Condition 1 met\n"); -#endif - break; - } - - /* evaluate grad(phi(alpha(i))) */ - my_dcopy(m,xk,1,xp,1); /* NOT NEEDED here?? xp<=xk */ - my_daxpy(m,pk,alphai+step,xp); /* xp<=xp+(alphai+step)*pk */ - func(xp,x,m,n,adata); - /* calculate x<=x-xo */ - //my_daxpy(n,xo,-1.0,x); - //p01=my_dnrm2(n,x); - p01=func_robust(x,xo,n,dp->robust_nu,dp->Nt); - my_daxpy(m,pk,-2.0*step,xp); /* xp<=xp+(alphai-step)*pk */ - func(xp,x,m,n,adata); - /* calculate x<=x-xo */ - //my_daxpy(n,xo,-1.0,x); - //p02=my_dnrm2(n,x); - p01=func_robust(x,xo,n,dp->robust_nu,dp->Nt); - gphi_i=(p01-p02)/(2.0*step); - - if (fabs(gphi_i)<=-sigma*gphi_0) { - alphak=alphai; -#ifdef DEBUG - printf("Linesearch : Condition 2 met\n"); -#endif - break; - } - - if (gphi_i>=0) { - /* ai=alphai, bi=alphai1 bracket */ - alphak=linesearch_zoom(func,xk,pk,alphai,alphai1,x,xp,phi_0,gphi_0,sigma,rho,t1,t2,t3,xo,m,n,step,adata); -#ifdef DEBUG - printf("Linesearch : Condition 3 met\n"); -#endif - break; - } - - /* else preserve old values */ - if (mu<=(2*alphai-alphai1)) { - /* next step */ - alphai1=alphai; - alphai=mu; - } else { - /* choose by interpolation in [2*alphai-alphai1,min(mu,alphai+t1*(alphai-alphai1)] */ - p01=2*alphai-alphai1; - p02=MIN(mu,alphai+t1*(alphai-alphai1)); - alphai=cubic_interp(func,xk,pk,p01,p02,x,xp,xo,m,n,step,adata); - //printf("cubic interp [%lf,%lf]->%lf\n",p01,p02,alphai); - } - phi_alphai1=phi_alphai; - - ci++; - } - - - - free(x); - free(xp); -#ifdef DEBUG - printf("Step size=%lf\n",alphak); -#endif - return alphak; -} -/*************** END Fletcher line search **********************************/ - -/*************************************** ROBUST ***************************/ -/* worker thread for a cpu */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -cpu_calc_deriv_robust(void *adata) { - thread_data_grad_t *t=(thread_data_grad_t*)adata; - - int ci,nb; - int stc,stoff,stm,sta1,sta2; - int N=t->N; /* stations */ - int M=t->M; /* clusters */ - int Nbase=(t->Nbase)*(t->tilesz); - - - double xr[8]; /* residuals */ - complex double G1[4],G2[4],C[4],T1[4],T2[4]; - double pp[8]; - double dsum; - int cli,tpchunk,pstart,nchunk,tilesperchunk,stci,ttile,tptile,poff; - double nu=t->robust_nu; - - /* iterate over each paramter */ - for (ci=t->g_start; ci<=t->g_end; ++ci) { - t->g[ci]=0.0; - /* find station and parameter corresponding to this value of ci */ - /* this parameter should correspond to the right baseline (x tilesz) - to contribute to the derivative */ - cli=0; - while((clicarr[cli].p[0] || ci>t->carr[cli].p[0]+8*N*t->carr[cli].nchunk-1)) { - cli++; - } - /* now either cli>=M: cluster not found - or cli=t->carr[cli-1].p[0] && ci<=t->carr[cli-1].p[0]+8*N*t->carr[cli-1].nchunk-1) { - cli--; - } - - if (clicarr[cli].p[0]; - - stc=(stci%(8*N))/8; /* 0..N-1 */ - /* make sure this baseline contribute to this parameter */ - tpchunk=stci/(8*N); - nchunk=t->carr[cli].nchunk; - pstart=t->carr[cli].p[0]; - tilesperchunk=(t->tilesz+nchunk-1)/nchunk; - - - /* iterate over all baselines and accumulate sum */ - for (nb=0; nbNbase; - /* which chunk this tile belongs to */ - tptile=ttile/tilesperchunk; - /* now tptile has to match tpchunk, otherwise ignore calculation */ - if (tptile==tpchunk) { - - sta1=t->barr[nb].sta1; - sta2=t->barr[nb].sta2; - - if (((stc==sta1)||(stc==sta2))&& !t->barr[nb].flag) { - /* this baseline has a contribution */ - /* which paramter of this station */ - stoff=(stci%(8*N))%8; /* 0..7 */ - /* which cluster */ - stm=cli; /* 0..M-1 */ - - /* exact expression for derivative - for Gaussian \sum( y_i - f_i(\theta))^2 - 2 real( vec^H(residual_this_baseline) - * vec(-J_{pm}C_{pqm} J_{qm}^H) - where m: chosen cluster - J_{pm},J_{qm} Jones matrices for baseline p-q - depending on the parameter, J ==> E - E: zero matrix, except 1 at location of m - \sum( 2 (y_i-f_i) * -\partical (f_i)/ \partial\theta - - for robust \sum( log(1+ (y_i-f_i(\theta))^2/\nu) ) - all calculations are like for the Gaussian case, except - when taking summation - \sum( 1/(\nu+(y_i-f_i)^2) 2 (y_i-f_i) * -\partical (f_i)/ \partial\theta - - so additonal multiplication by 1/(\nu+(y_i-f_i)^2) - */ - /* read in residual vector, (real,imag) separately */ - xr[0]=t->x[nb*8]; - xr[1]=t->x[nb*8+1]; - xr[2]=t->x[nb*8+2]; - xr[3]=t->x[nb*8+3]; - xr[4]=t->x[nb*8+4]; - xr[5]=t->x[nb*8+5]; - xr[6]=t->x[nb*8+6]; - xr[7]=t->x[nb*8+7]; - - /* read in coherency */ - C[0]=t->coh[4*M*nb+4*stm]; - C[1]=t->coh[4*M*nb+4*stm+1]; - C[2]=t->coh[4*M*nb+4*stm+2]; - C[3]=t->coh[4*M*nb+4*stm+3]; - - memset(pp,0,sizeof(double)*8); - if (stc==sta1) { - /* this station parameter gradient */ - pp[stoff]=1.0; - memset(G1,0,sizeof(complex double)*4); - G1[0]=pp[0]+_Complex_I*pp[1]; - G1[1]=pp[2]+_Complex_I*pp[3]; - G1[2]=pp[4]+_Complex_I*pp[5]; - G1[3]=pp[6]+_Complex_I*pp[7]; - poff=pstart+tpchunk*8*N+sta2*8; - G2[0]=(t->p[poff])+_Complex_I*(t->p[poff+1]); - G2[1]=(t->p[poff+2])+_Complex_I*(t->p[poff+3]); - G2[2]=(t->p[poff+4])+_Complex_I*(t->p[poff+4]); - G2[3]=(t->p[poff+6])+_Complex_I*(t->p[poff+7]); - - } else if (stc==sta2) { - memset(G2,0,sizeof(complex double)*4); - pp[stoff]=1.0; - G2[0]=pp[0]+_Complex_I*pp[1]; - G2[1]=pp[2]+_Complex_I*pp[3]; - G2[2]=pp[4]+_Complex_I*pp[5]; - G2[3]=pp[6]+_Complex_I*pp[7]; - poff=pstart+tpchunk*8*N+sta1*8; - G1[0]=(t->p[poff])+_Complex_I*(t->p[poff+1]); - G1[1]=(t->p[poff+2])+_Complex_I*(t->p[poff+3]); - G1[2]=(t->p[poff+4])+_Complex_I*(t->p[poff+5]); - G1[3]=(t->p[poff+6])+_Complex_I*(t->p[poff+7]); - } - - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - - /* calculate product xr*vec(J_p C J_q^H )/(nu+residual^2) */ - dsum=xr[0]*creal(T2[0])/(nu+xr[0]*xr[0]); - dsum+=xr[1]*cimag(T2[0])/(nu+xr[1]*xr[1]); - dsum+=xr[2]*creal(T2[1])/(nu+xr[2]*xr[2]); - dsum+=xr[3]*cimag(T2[1])/(nu+xr[3]*xr[3]); - dsum+=xr[4]*creal(T2[2])/(nu+xr[4]*xr[4]); - dsum+=xr[5]*cimag(T2[2])/(nu+xr[5]*xr[5]); - dsum+=xr[6]*creal(T2[3])/(nu+xr[6]*xr[6]); - dsum+=xr[7]*cimag(T2[3])/(nu+xr[7]*xr[7]); - - /* accumulate sum NOTE - its important to get the sign right, - depending on res=data-model or res=model-data */ - t->g[ci]+=2.0*(dsum); - } - } - } - } - } - - - return NULL; -} -/* calculate gradient */ -/* func: vector function - p: parameter values size m x 1 (at which grad is calculated) - g: gradient size m x 1 - xo: observed data size n x 1 - robust_nu: nu in T distribution - n: size of vector function - step: step size for differencing - adata: additional data passed to the function -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static int -func_grad_robust( - void (*func)(double *p, double *hx, int m, int n, void *adata), - double *p, double *g, double *xo, int m, int n, double step, void *adata) { - /* gradient for each parameter is - (||func(p+step*e_i)-x||^2-||func(p-step*e_i)-x||^2)/2*step - i=0,...,m-1 for all parameters - e_i: unit vector, 1 only at i-th location - */ - - double *x; /* array to store residual */ - int ci; - me_data_t *dp=(me_data_t*)adata; - - int Nt=dp->Nt; - - pthread_attr_t attr; - pthread_t *th_array; - thread_data_grad_t *threaddata; - - - if ((x=(double*)calloc((size_t)n,sizeof(double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - /* evaluate func once, store in x, and create threads */ - /* and calculate the residual x=xo-func */ - func(p,x,m,n,adata); - /* calculate x<=x-xo */ - my_daxpy(n,xo,-1.0,x); - - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((threaddata=(thread_data_grad_t*)malloc((size_t)Nt*sizeof(thread_data_grad_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - int nth,Nparm; - - /* parameters per thread */ - Nparm=(m+Nt-1)/Nt; - - /* each thread will calculate derivative of part of - parameters */ - ci=0; - for (nth=0; nthNbase; - threaddata[nth].tilesz=dp->tilesz; - threaddata[nth].barr=dp->barr; - threaddata[nth].carr=dp->carr; - threaddata[nth].M=dp->M; - threaddata[nth].N=dp->N; - threaddata[nth].coh=dp->coh; - threaddata[nth].m=m; - threaddata[nth].n=n; - threaddata[nth].x=x; - threaddata[nth].p=p; - threaddata[nth].g=g; - threaddata[nth].robust_nu=dp->robust_nu; - threaddata[nth].g_start=ci; - threaddata[nth].g_end=ci+Nparm-1; - if (threaddata[nth].g_end>=m) { - threaddata[nth].g_end=m-1; - } - ci=ci+Nparm; - pthread_create(&th_array[nth],&attr,cpu_calc_deriv_robust,(void*)(&threaddata[nth])); - } - - /* now wait for threads to finish */ - for(nth=0; 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 DpMTA9 diff --git a/src/lib/Solvers/robustlm.c b/src/lib/Solvers/robustlm.c deleted file mode 100644 index 24f84df..0000000 --- a/src/lib/Solvers/robustlm.c +++ /dev/null @@ -1,3248 +0,0 @@ -/* - * - Copyright (C) 2006-2008 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#include -#include -#include -#include -#include - -#include "Solvers.h" - -#ifdef HAVE_CUDA -#include -/* helper functions for diagnostics */ -static void -checkCudaError(cudaError_t err, char *file, int line) -{ -#ifdef CUDA_DEBUG - if(!err) - return; - printf("GPU (CUDA): %s %s %d\n", cudaGetErrorString(err),file,line); - exit(EXIT_FAILURE); -#endif -} - - -static void -checkCublasError(cublasStatus_t cbstatus, char *file, int line) -{ -#ifdef CUDA_DEBUG - if (cbstatus!=CUBLAS_STATUS_SUCCESS) { - printf("%s: %d: CUBLAS failure\n",file,line); - exit(EXIT_FAILURE); - } -#endif -} - - - -/* robust, iteratively weighted non linear least squares using LM - entirely in the GPU */ -int -rlevmar_der_single_cuda( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - double *gWORK, /* GPU allocated memory */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - void *adata) /* pointer to possibly additional data, passed uninterpreted to func & jacf. - * Set to NULL if not needed - */ -{ - - /* general note: all device variables end with a 'd' */ - int stop=0; - cudaError_t err; - cublasStatus_t cbstatus; - - int nu=2,nu2; - double p_L2, Dp_L2=DBL_MAX, dF, dL, p_eL2, jacTe_inf=0.0, pDp_eL2, init_p_eL2; - double tmp,mu=0.0; - double tau, eps1, eps2, eps2_sq, eps3; - int k,ci,issolved; - - double *hxd; - double *wtd,*qd; - - int nw,wt_itmax=3; - /* ME data */ - me_data_t *dp=(me_data_t*)adata; - double wt_sum,lambda,robust_nu=dp->robust_nu; - double q_sum,robust_nu1; - double deltanu; - int Nd=100; /* no of points where nu is sampled, note NdN) { Nd=N; } - /* only search for nu in [2,30] because 30 is almost Gaussian */ - deltanu=(robust_nuhigh-robust_nulow)/(double)Nd; - - - double *ed; - double *xd; - - double *jacd; - - double *jacTjacd,*jacTjacd0; - - double *Dpd,*bd; - double *pd,*pnewd; - double *jacTed; - - /* used in QR solver */ - double *taud; - - /* used in SVD solver */ - double *Ud; - double *VTd; - double *Sd; - - int Nbase=(dp->Nbase)*(ntiles); /* note: we do not use the total tile size */ - /* coherency on device */ - double *cohd; - /* baseline-station map on device/host */ - short *bbd; - - int solve_axb=linsolv; - double alpha; - - /* setup default settings */ - if(opts){ - tau=opts[0]; - eps1=opts[1]; - eps2=opts[2]; - eps2_sq=opts[2]*opts[2]; - eps3=opts[3]; - } else { - tau=CLM_INIT_MU; - eps1=CLM_STOP_THRESH; - eps2=CLM_STOP_THRESH; - eps2_sq=CLM_STOP_THRESH*CLM_STOP_THRESH; - eps3=CLM_STOP_THRESH; - } - - /* calculate no of cuda threads and blocks */ - int ThreadsPerBlock=DEFAULT_TH_PER_BK; - int ThreadsPerBlock1=DEFAULT_TH_PER_BK; /* DEFAULT_TH_PER_BK/8 for accessing each element of a baseline */ - int ThreadsPerBlock2=Nd/2; /* for evaluating nu */ - int BlocksPerGrid= 2*(M+ThreadsPerBlock-1)/ThreadsPerBlock; - - - unsigned long int moff; - if (!gWORK) { - err=cudaMalloc((void**)&xd, N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&jacd, M*N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&jacTjacd, M*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&jacTed, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&jacTjacd0, M*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&Dpd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&bd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&pd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&pnewd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - /* needed for calculating f() and jac() */ - err=cudaMalloc((void**) &bbd, Nbase*2*sizeof(short)); - checkCudaError(err,__FILE__,__LINE__); - /* we need coherencies for only this cluster */ - err=cudaMalloc((void**) &cohd, Nbase*8*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&hxd, N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&wtd, N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&qd, N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&ed, N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - /* memory allocation: different solvers */ - if (solve_axb==1) { - err=cudaMalloc((void**)&taud, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } else if (solve_axb==2) { - err=cudaMalloc((void**)&Ud, M*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&VTd, M*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&Sd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } - } else { - moff=0; - xd=&gWORK[moff]; - moff+=N; - jacd=&gWORK[moff]; - moff+=M*N; - jacTjacd=&gWORK[moff]; - moff+=M*M; - jacTed=&gWORK[moff]; - moff+=M; - jacTjacd0=&gWORK[moff]; - moff+=M*M; - Dpd=&gWORK[moff]; - moff+=M; - bd=&gWORK[moff]; - moff+=M; - pd=&gWORK[moff]; - moff+=M; - pnewd=&gWORK[moff]; - moff+=M; - cohd=&gWORK[moff]; - moff+=Nbase*8; - hxd=&gWORK[moff]; - moff+=N; - ed=&gWORK[moff]; - moff+=N; - wtd=&gWORK[moff]; - moff+=N; - qd=&gWORK[moff]; - moff+=N; - if (solve_axb==1) { - taud=&gWORK[moff]; - moff+=M; - } else if (solve_axb==2) { - Ud=&gWORK[moff]; - moff+=M*M; - VTd=&gWORK[moff]; - moff+=M*M; - Sd=&gWORK[moff]; - moff+=M; - } - bbd=(short*)&gWORK[moff]; - moff+=(Nbase*2*sizeof(short))/sizeof(double); - } - - /* extra storage for cusolver */ - int work_size=0; - int *devInfo; - int devInfo_h=0; - err=cudaMalloc((void**)&devInfo, sizeof(int)); - checkCudaError(err,__FILE__,__LINE__); - double *work; - double *rwork; - if (solve_axb==0) { - cusolverDnDpotrf_bufferSize(solver_handle, CUBLAS_FILL_MODE_UPPER, M, jacTjacd, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } else if (solve_axb==1) { - cusolverDnDgeqrf_bufferSize(solver_handle, M, M, jacTjacd, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } else { - cusolverDnDgesvd_bufferSize(solver_handle, M, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&rwork, 5*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } - - err=cudaMemcpy(pd, p, M*sizeof(double), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - /* need to give right offset for coherencies */ - /* offset: cluster offset+time offset */ - err=cudaMemcpy(cohd, &(dp->ddcoh[(dp->Nbase)*(dp->tilesz)*(dp->clus)*8+(dp->Nbase)*tileoff*8]), Nbase*8*sizeof(double), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - /* correct offset for baselines */ - err=cudaMemcpy(bbd, &(dp->ddbase[2*(dp->Nbase)*(tileoff)]), Nbase*2*sizeof(short), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - cudaDeviceSynchronize(); - /* xd <=x */ - err=cudaMemcpy(xd, x, N*sizeof(double), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - - /* set initial weights to 1 by a cuda kernel */ - cudakernel_setweights(ThreadsPerBlock1, (N+ThreadsPerBlock1-1)/ThreadsPerBlock1, N, wtd, 1.0); - /* weight calculation loop */ - for (nw=0; nwstat mapping, Nbase, Mclusters, Nstations*/ - cudakernel_func_wt(ThreadsPerBlock, (Nbase+ThreadsPerBlock-1)/ThreadsPerBlock, pd,hxd,M,N, cohd, bbd, wtd, Nbase, dp->M, dp->N); - - /* e=x */ - cbstatus=cublasDcopy(cbhandle, N, xd, 1, ed, 1); - /* e = e \odot wt */ - cudakernel_hadamard(ThreadsPerBlock1, (N+ThreadsPerBlock1-1)/ThreadsPerBlock1, N, wtd, ed); - /* e=x-hx */ - alpha=-1.0; - cbstatus=cublasDaxpy(cbhandle, N, &alpha, hxd, 1, ed, 1); - - /* norm ||e|| */ - cbstatus=cublasDnrm2(cbhandle, N, ed, 1, &p_eL2); - /* square */ - p_eL2=p_eL2*p_eL2; - - init_p_eL2=p_eL2; - if(!finite(p_eL2)) stop=7; - - - /**** iteration loop ***********/ - for(k=0; kstat mapping, Nbase, Mclusters, Nstations*/ - /* FIXME thread/block sizes 16x16=256, so 16 is chosen */ - cudakernel_jacf_wt(ThreadsPerBlock, ThreadsPerBlock/4, pd, jacd, M, N, cohd, bbd, wtd, Nbase, dp->M, dp->N); - - /* Compute J^T J and J^T e */ - /* Cache efficient computation of J^T J based on blocking - */ - /* since J is in ROW major order, assume it is transposed, - so actually calculate A=J*J^T, where J is size MxN */ - //status=culaDeviceDgemm('N','T',M,M,N,1.0,jacd,M,jacd,M,0.0,jacTjacd,M); - //checkStatus(status,__FILE__,__LINE__); - double cone=1.0; double czero=0.0; - cbstatus=cublasDgemm(cbhandle,CUBLAS_OP_N,CUBLAS_OP_T,M,M,N,&cone,jacd,M,jacd,M,&czero,jacTjacd,M); - - /* create backup */ - /* copy jacTjacd0<=jacTjacd */ - cbstatus=cublasDcopy(cbhandle, M*M, jacTjacd, 1, jacTjacd0, 1); - /* J^T e */ - /* calculate b=J^T*e (actually compute b=J*e, where J in row major (size MxN) */ - //status=culaDeviceDgemv('N',M,N,1.0,jacd,M,ed,1,0.0,jacTed,1); - //checkStatus(status,__FILE__,__LINE__); - cbstatus=cublasDgemv(cbhandle,CUBLAS_OP_N,M,N,&cone,jacd,M,ed,1,&czero,jacTed,1); - - - /* Compute ||J^T e||_inf and ||p||^2 */ - /* find infinity norm of J^T e, 1 based indexing*/ - cbstatus=cublasIdamax(cbhandle, M, jacTed, 1, &ci); - err=cudaMemcpy(&jacTe_inf,&(jacTed[ci-1]),sizeof(double),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - /* L2 norm of current parameter values */ - /* norm ||Dp|| */ - cbstatus=cublasDnrm2(cbhandle, M, pd, 1, &p_L2); - p_L2=p_L2*p_L2; - if(jacTe_inf<0.0) {jacTe_inf=-jacTe_inf;} -#ifdef DEBUG - printf("Inf norm=%lf\n",jacTe_inf); -#endif - - /* check for convergence */ - if((jacTe_inf <= eps1)){ - Dp_L2=0.0; /* no increment for p in this case */ - stop=1; - break; - } - - /* compute initial (k=0) damping factor */ - if (k==0) { - /* find max diagonal element (stride is M+1) */ - /* should be MAX not MAX(ABS) */ - cbstatus=cublasIdamax(cbhandle, M, jacTjacd, M+1, &ci); /* 1 based index */ - ci=(ci-1)*(M+1); /* right value of the diagonal */ - - err=cudaMemcpy(&tmp,&(jacTjacd[ci]),sizeof(double),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - mu=tau*tmp; - } - - - /* determine increment using adaptive damping */ - while(1){ - /* augment normal equations */ - /* increment A => A+ mu*I, increment diagonal entries */ - /* copy jacTjacd<=jacTjacd0 */ - cbstatus=cublasDcopy(cbhandle, M*M, jacTjacd0, 1, jacTjacd, 1); - cudakernel_diagmu(ThreadsPerBlock, BlocksPerGrid, M, jacTjacd, mu); - -#ifdef DEBUG - printf("mu=%lf\n",mu); -#endif -/*************************************************************************/ - issolved=0; - /* solve augmented equations A x = b */ - /* A==jacTjacd, b==Dpd, after solving, x==Dpd */ - /* b=jacTed : intially right hand side, at exit the solution */ - if (solve_axb==0) { - /* Cholesky solver **********************/ - /* lower triangle of Ad is destroyed */ - //status=culaDeviceDpotrf('U',M,jacTjacd,M); - cusolverDnDpotrf(solver_handle, CUBLAS_FILL_MODE_UPPER, M, jacTjacd, M, work, work_size, devInfo); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (!devInfo_h) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - printf("Singular matrix\n"); -#endif - } - if (issolved) { - /* copy Dpd<=jacTed */ - cbstatus=cublasDcopy(cbhandle, M, jacTed, 1, Dpd, 1); -#ifdef DEBUG - checkCublasError(cbstatus,__FILE__,__LINE__); -#endif - //status=culaDeviceDpotrs('U',M,1,jacTjacd,M,Dpd,M); - cusolverDnDpotrs(solver_handle, CUBLAS_FILL_MODE_UPPER,M,1,jacTjacd,M,Dpd,M,devInfo); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (devInfo_h) { - issolved=0; -#ifdef DEBUG - printf("Singular matrix\n"); -#endif - } - } - } else if (solve_axb==1) { - /* QR solver ********************************/ - //status=culaDeviceDgeqrf(M,M,jacTjacd,M,taud); - cusolverDnDgeqrf(solver_handle, M, M, jacTjacd, M, taud, work, work_size, devInfo); - cudaDeviceSynchronize(); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (!devInfo_h) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - printf("Singular matrix\n"); -#endif - } - - if (issolved) { - /* copy Dpd<=jacTed */ - cbstatus=cublasDcopy(cbhandle, M, jacTed, 1, Dpd, 1); - //status=culaDeviceDgeqrs(M,M,1,jacTjacd,M,taud,Dpd,M); - cusolverDnDormqr(solver_handle, CUBLAS_SIDE_LEFT, CUBLAS_OP_T, M, 1, M, jacTjacd, M, taud, Dpd, M, work, work_size, devInfo); - cudaDeviceSynchronize(); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (devInfo_h) { - issolved=0; -#ifdef DEBUG - printf("Singular matrix\n"); -#endif - } else { - cone=1.0; - cbstatus=cublasDtrsm(cbhandle,CUBLAS_SIDE_LEFT,CUBLAS_FILL_MODE_UPPER,CUBLAS_OP_N,CUBLAS_DIAG_NON_UNIT,M,1,&cone,jacTjacd,M,Dpd,M); - } - } - } else { - /* SVD solver *********************************/ - /* U S VT = A */ - //status=culaDeviceDgesvd('A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M); - //checkStatus(status,__FILE__,__LINE__); - cusolverDnDgesvd(solver_handle,'A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M,work,work_size,rwork,devInfo); - cudaDeviceSynchronize(); - /* copy Dpd<=jacTed */ - cbstatus=cublasDcopy(cbhandle, M, jacTed, 1, Dpd, 1); - /* b<=U^T * b */ - //status=culaDeviceDgemv('T',M,M,1.0,Ud,M,Dpd,1,0.0,Dpd,1); - cone=1.0; czero=0.0; - cbstatus=cublasDgemv(cbhandle,CUBLAS_OP_T,M,M,&cone,Ud,M,Dpd,1,&czero,Dpd,1); - /* divide by singular values Dpd[]/Sd[] for Sd[]> eps1 */ - cudakernel_diagdiv(ThreadsPerBlock, BlocksPerGrid, M, eps1, Dpd, Sd); - - /* b<=VT^T * b */ - //status=culaDeviceDgemv('T',M,M,1.0,VTd,M,Dpd,1,0.0,Dpd,1); - //checkStatus(status,__FILE__,__LINE__); - cbstatus=cublasDgemv(cbhandle,CUBLAS_OP_T,M,M,&cone,VTd,M,Dpd,1,&czero,Dpd,1); - - issolved=1; - } -/*************************************************************************/ - - /* compute p's new estimate and ||Dp||^2 */ - if (issolved) { - /* compute p's new estimate and ||Dp||^2 */ - /* pnew=p+Dp */ - /* pnew=p */ - cbstatus=cublasDcopy(cbhandle, M, pd, 1, pnewd, 1); - /* pnew=pnew+Dp */ - alpha=1.0; - cbstatus=cublasDaxpy(cbhandle, M, &alpha, Dpd, 1, pnewd, 1); - - /* norm ||Dp|| */ - cbstatus=cublasDnrm2(cbhandle, M, Dpd, 1, &Dp_L2); - Dp_L2=Dp_L2*Dp_L2; - -#ifdef DEBUG -printf("norm ||dp|| =%lf, norm ||p||=%lf\n",Dp_L2,p_L2); -#endif - if(Dp_L2<=eps2_sq*p_L2){ /* relative change in p is small, stop */ - stop=2; - break; - } - - if(Dp_L2>=(p_L2+eps2)/(CLM_EPSILON*CLM_EPSILON)){ /* almost singular */ - stop=4; - break; - } - - /* new function value */ - /* compute ||e(pDp)||_2 */ - /* ### hx=x-hx, pDp_eL2=||hx|| */ - /* copy to device */ - /* hxd<=hx */ - cudakernel_func_wt(ThreadsPerBlock, (Nbase+ThreadsPerBlock-1)/ThreadsPerBlock, pnewd, hxd, M, N, cohd, bbd, wtd, Nbase, dp->M, dp->N); - - /* e=x */ - cbstatus=cublasDcopy(cbhandle, N, xd, 1, ed, 1); - /* e = e \odot wt */ - cudakernel_hadamard(ThreadsPerBlock1, (N+ThreadsPerBlock1-1)/ThreadsPerBlock1, N, wtd, ed); - /* e=x-hx */ - alpha=-1.0; - cbstatus=cublasDaxpy(cbhandle, N, &alpha, hxd, 1, ed, 1); - /* note: e is updated */ - - /* norm ||e|| */ - cbstatus=cublasDnrm2(cbhandle, N, ed, 1, &pDp_eL2); - pDp_eL2=pDp_eL2*pDp_eL2; - - - if(!finite(pDp_eL2)){ /* sum of squares is not finite, most probably due to a user error. - */ - stop=7; - break; - } - - /* dL=Dp'*(mu*Dp+jacTe) */ - /* bd=jacTe+mu*Dp */ - cbstatus=cublasDcopy(cbhandle, M, jacTed, 1, bd, 1); - cbstatus=cublasDaxpy(cbhandle, M, &mu, Dpd, 1, bd, 1); - cbstatus=cublasDdot(cbhandle, M, Dpd, 1, bd, 1, &dL); - - dF=p_eL2-pDp_eL2; - -#ifdef DEBUG - printf("dF=%lf, dL=%lf\n",dF,dL); -#endif - if(dL>0.0 && dF>0.0){ /* reduction in error, increment is accepted */ - tmp=(2.0*dF/dL-1.0); - tmp=1.0-tmp*tmp*tmp; - mu=mu*((tmp>=CLM_ONE_THIRD)? tmp : CLM_ONE_THIRD); - nu=2; - - /* update p's estimate */ - cbstatus=cublasDcopy(cbhandle, M, pnewd, 1, pd, 1); - - /* update ||e||_2 */ - p_eL2=pDp_eL2; - break; - } - - } - /* if this point is reached, either the linear system could not be solved or - * the error did not reduce; in any case, the increment must be rejected - */ - - mu*=(double)nu; - nu2=nu<<1; // 2*nu; - if(nu2<=nu){ /* nu has wrapped around (overflown). */ - stop=5; - break; - } - - nu=nu2; - - } /* inner loop */ - - } - /**** end iteration loop ***********/ - if(k>=itmax) stop=3; - - if (nw>0 && nwM, dp->N); - /* e=x */ - cbstatus=cublasDcopy(cbhandle, N, xd, 1, ed, 1); - /* e=x-hx */ - alpha=-1.0; - cbstatus=cublasDaxpy(cbhandle, N, &alpha, hxd, 1, ed, 1); - } - - - if (nwrobust_nu=robust_nu; - - /* copy back current solution */ - err=cudaMemcpyAsync(p,pd,M*sizeof(double),cudaMemcpyDeviceToHost,0); - checkCudaError(err,__FILE__,__LINE__); - - checkCublasError(cbstatus,__FILE__,__LINE__); - - /* synchronize async operations */ - cudaDeviceSynchronize(); - - if (!gWORK) { - cudaFree(xd); - cudaFree(jacd); - cudaFree(jacTjacd); - cudaFree(jacTjacd0); - cudaFree(jacTed); - cudaFree(Dpd); - cudaFree(bd); - cudaFree(pd); - cudaFree(pnewd); - cudaFree(hxd); - cudaFree(wtd); - cudaFree(qd); - cudaFree(ed); - if (solve_axb==1) { - cudaFree(taud); - } else if (solve_axb==2) { - cudaFree(Ud); - cudaFree(VTd); - cudaFree(Sd); - } - cudaFree(cohd); - cudaFree(bbd); - } - - cudaFree(devInfo); - cudaFree(work); - if (solve_axb==2) { - cudaFree(rwork); - } - - -#ifdef DEBUG - printf("stop=%d\n",stop); -#endif - if(info){ - info[0]=init_p_eL2; - info[1]=p_eL2; - info[2]=jacTe_inf; - info[3]=Dp_L2; - info[4]=mu; - info[5]=(double)k; - info[6]=(double)stop; - info[7]=(double)0; - info[8]=(double)0; - info[9]=(double)0; - } - return 0; -} - - -/* robust, iteratively weighted non linear least squares using LM - entirely in the GPU, using float data */ -int -rlevmar_der_single_cuda_fl( - float *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - float *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - float *gWORK, /* GPU allocated memory */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - void *adata) /* pointer to possibly additional data, passed uninterpreted to func & jacf. - * Set to NULL if not needed - */ -{ - - /* general note: all device variables end with a 'd' */ - int stop=0; - cudaError_t err; - cublasStatus_t cbstatus; - - int nu=2,nu2; - float p_L2, Dp_L2=(float)DBL_MAX, dF, dL, p_eL2, jacTe_inf=0.0f, pDp_eL2, init_p_eL2; - float tmp,mu=0.0f; - float tau, eps1, eps2, eps2_sq, eps3; - int k,ci,issolved; - - float *hxd; - float *wtd,*qd; - - int nw,wt_itmax=3; - /* ME data */ - me_data_t *dp=(me_data_t*)adata; - float wt_sum,lambda,robust_nu=(float)dp->robust_nu; - float q_sum,robust_nu1; - float deltanu; - int Nd=100; /* no of points where nu is sampled, note NdN) { Nd=N; } - /* only search for nu in [2,30] because 30 is almost Gaussian */ - deltanu=(float)(robust_nuhigh-robust_nulow)/(float)Nd; - - - float *ed; - float *xd; - - float *jacd; - - float *jacTjacd,*jacTjacd0; - - float *Dpd,*bd; - float *pd,*pnewd; - float *jacTed; - - /* used in QR solver */ - float *taud=0; - - /* used in SVD solver */ - float *Ud=0; - float *VTd=0; - float *Sd=0; - - int Nbase=(dp->Nbase)*(ntiles); /* note: we do not use the total tile size */ - /* coherency on device */ - float *cohd; - /* baseline-station map on device/host */ - short *bbd; - - int solve_axb=linsolv; - float alpha; - - /* setup default settings */ - if(opts){ - tau=(float)opts[0]; - eps1=(float)opts[1]; - eps2=(float)opts[2]; - eps2_sq=(float)opts[2]*opts[2]; - eps3=(float)opts[3]; - } else { - tau=(float)CLM_INIT_MU; - eps1=(float)CLM_STOP_THRESH; - eps2=(float)CLM_STOP_THRESH; - eps2_sq=(float)CLM_STOP_THRESH*CLM_STOP_THRESH; - eps3=(float)CLM_STOP_THRESH; - } - - /* calculate no of cuda threads and blocks */ - int ThreadsPerBlock=DEFAULT_TH_PER_BK; - /* FIXME: might need a large value for large no of baselines */ - int ThreadsPerBlock1=DEFAULT_TH_PER_BK; /* for accessing each element of a baseline */ - int ThreadsPerBlock2=Nd/2; /* for evaluating nu */ - int BlocksPerGrid= 2*(M+ThreadsPerBlock-1)/ThreadsPerBlock; - - - unsigned long int moff; - moff=0; - xd=&gWORK[moff]; - moff+=N; - jacd=&gWORK[moff]; - moff+=M*N; - jacTjacd=&gWORK[moff]; - moff+=M*M; - jacTed=&gWORK[moff]; - moff+=M; - jacTjacd0=&gWORK[moff]; - moff+=M*M; - Dpd=&gWORK[moff]; - moff+=M; - bd=&gWORK[moff]; - moff+=M; - pd=&gWORK[moff]; - moff+=M; - pnewd=&gWORK[moff]; - moff+=M; - cohd=&gWORK[moff]; - moff+=Nbase*8; - hxd=&gWORK[moff]; - moff+=N; - ed=&gWORK[moff]; - moff+=N; - wtd=&gWORK[moff]; - moff+=N; - qd=&gWORK[moff]; - moff+=N; - if (solve_axb==1) { - taud=&gWORK[moff]; - moff+=M; - } else if (solve_axb==2) { - Ud=&gWORK[moff]; - moff+=M*M; - VTd=&gWORK[moff]; - moff+=M*M; - Sd=&gWORK[moff]; - moff+=M; - } - bbd=(short*)&gWORK[moff]; - moff+=(Nbase*2*sizeof(short))/sizeof(float); - - /* extra storage for cusolver */ - int work_size=0; - int *devInfo; - int devInfo_h=0; - err=cudaMalloc((void**)&devInfo, sizeof(int)); - checkCudaError(err,__FILE__,__LINE__); - float *work; - float *rwork; - if (solve_axb==0) { - cusolverDnSpotrf_bufferSize(solver_handle, CUBLAS_FILL_MODE_UPPER, M, jacTjacd, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(float)); - checkCudaError(err,__FILE__,__LINE__); - } else if (solve_axb==1) { - cusolverDnSgeqrf_bufferSize(solver_handle, M, M, jacTjacd, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(float)); - checkCudaError(err,__FILE__,__LINE__); - } else { - cusolverDnSgesvd_bufferSize(solver_handle, M, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(float)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&rwork, 5*M*sizeof(float)); - checkCudaError(err,__FILE__,__LINE__); - } - - - err=cudaMemcpyAsync(pd, p, M*sizeof(float), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - /* need to give right offset for coherencies */ - /* offset: cluster offset+time offset */ - err=cudaMemcpyAsync(cohd, &(dp->ddcohf[(dp->Nbase)*(dp->tilesz)*(dp->clus)*8+(dp->Nbase)*tileoff*8]), Nbase*8*sizeof(float), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - /* correct offset for baselines */ - err=cudaMemcpyAsync(bbd, &(dp->ddbase[2*(dp->Nbase)*(tileoff)]), Nbase*2*sizeof(short), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - cudaDeviceSynchronize(); - /* xd <=x */ - err=cudaMemcpyAsync(xd, x, N*sizeof(float), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - - /* set initial weights to 1 by a cuda kernel */ - cudakernel_setweights_fl(ThreadsPerBlock1, (N+ThreadsPerBlock1-1)/ThreadsPerBlock1, N, wtd, 1.0f); - /* weight calculation loop */ - for (nw=0; nwstat mapping, Nbase, Mclusters, Nstations*/ - cudakernel_func_wt_fl(ThreadsPerBlock, (Nbase+ThreadsPerBlock-1)/ThreadsPerBlock, pd,hxd,M,N, cohd, bbd, wtd, Nbase, dp->M, dp->N); - - /* e=x */ - cbstatus=cublasScopy(cbhandle, N, xd, 1, ed, 1); - /* e = e \odot wt */ - cudakernel_hadamard_fl(ThreadsPerBlock1, (N+ThreadsPerBlock1-1)/ThreadsPerBlock1, N, wtd, ed); - /* e=x-hx */ - alpha=-1.0f; - cbstatus=cublasSaxpy(cbhandle, N, &alpha, hxd, 1, ed, 1); - - /* norm ||e|| */ - cbstatus=cublasSnrm2(cbhandle, N, ed, 1, &p_eL2); - /* square */ - p_eL2=p_eL2*p_eL2; - - init_p_eL2=p_eL2; - if(!finitef(p_eL2)) stop=7; - - - /**** iteration loop ***********/ - for(k=0; kstat mapping, Nbase, Mclusters, Nstations*/ - /* FIXME thread/block sizes 16x16=256, so 16 is chosen */ - cudakernel_jacf_wt_fl(ThreadsPerBlock, ThreadsPerBlock/4, pd, jacd, M, N, cohd, bbd, wtd, Nbase, dp->M, dp->N); - - /* Compute J^T J and J^T e */ - /* Cache efficient computation of J^T J based on blocking - */ - /* since J is in ROW major order, assume it is transposed, - so actually calculate A=J*J^T, where J is size MxN */ - //status=culaDeviceSgemm('N','T',M,M,N,1.0f,jacd,M,jacd,M,0.0f,jacTjacd,M); - //checkStatus(status,__FILE__,__LINE__); - float cone=1.0f; float czero=0.0f; - cbstatus=cublasSgemm(cbhandle,CUBLAS_OP_N,CUBLAS_OP_T,M,M,N,&cone,jacd,M,jacd,M,&czero,jacTjacd,M); - - /* create backup */ - /* copy jacTjacd0<=jacTjacd */ - cbstatus=cublasScopy(cbhandle, M*M, jacTjacd, 1, jacTjacd0, 1); - /* J^T e */ - /* calculate b=J^T*e (actually compute b=J*e, where J in row major (size MxN) */ - //status=culaDeviceSgemv('N',M,N,1.0f,jacd,M,ed,1,0.0f,jacTed,1); - //checkStatus(status,__FILE__,__LINE__); - cbstatus=cublasSgemv(cbhandle,CUBLAS_OP_N,M,N,&cone,jacd,M,ed,1,&czero,jacTed,1); - - - /* Compute ||J^T e||_inf and ||p||^2 */ - /* find infinity norm of J^T e, 1 based indexing*/ - cbstatus=cublasIsamax(cbhandle, M, jacTed, 1, &ci); - err=cudaMemcpy(&jacTe_inf,&(jacTed[ci-1]),sizeof(float),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - /* L2 norm of current parameter values */ - /* norm ||Dp|| */ - cbstatus=cublasSnrm2(cbhandle, M, pd, 1, &p_L2); - p_L2=p_L2*p_L2; - if(jacTe_inf<0.0f) {jacTe_inf=-jacTe_inf;} -#ifdef DEBUG - printf("Inf norm=%f\n",jacTe_inf); -#endif - - /* check for convergence */ - if((jacTe_inf <= eps1)){ - Dp_L2=0.0f; /* no increment for p in this case */ - stop=1; - break; - } - - /* compute initial (k=0) damping factor */ - if (k==0) { - /* find max diagonal element (stride is M+1) */ - /* should be MAX not MAX(ABS) */ - cbstatus=cublasIsamax(cbhandle, M, jacTjacd, M+1, &ci); /* 1 based index */ - ci=(ci-1)*(M+1); /* right value of the diagonal */ - - err=cudaMemcpy(&tmp,&(jacTjacd[ci]),sizeof(float),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - mu=tau*tmp; - } - - - /* determine increment using adaptive damping */ - while(1){ - /* augment normal equations */ - /* increment A => A+ mu*I, increment diagonal entries */ - /* copy jacTjacd<=jacTjacd0 */ - cbstatus=cublasScopy(cbhandle, M*M, jacTjacd0, 1, jacTjacd, 1); - cudakernel_diagmu_fl(ThreadsPerBlock, BlocksPerGrid, M, jacTjacd, mu); - -#ifdef DEBUG - printf("mu=%lf\n",mu); -#endif -/*************************************************************************/ - issolved=0; - /* solve augmented equations A x = b */ - /* A==jacTjacd, b==Dpd, after solving, x==Dpd */ - /* b=jacTed : intially right hand side, at exit the solution */ - if (solve_axb==0) { - /* Cholesky solver **********************/ - /* lower triangle of Ad is destroyed */ - //status=culaDeviceSpotrf('U',M,jacTjacd,M); - cusolverDnSpotrf(solver_handle, CUBLAS_FILL_MODE_UPPER, M, jacTjacd, M, work, work_size, devInfo); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (!devInfo_h) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - printf("Singular matrix\n"); -#endif - } - if (issolved) { - /* copy Dpd<=jacTed */ - cbstatus=cublasScopy(cbhandle, M, jacTed, 1, Dpd, 1); -#ifdef DEBUG - checkCublasError(cbstatus,__FILE__,__LINE__); -#endif - //status=culaDeviceSpotrs('U',M,1,jacTjacd,M,Dpd,M); - cusolverDnSpotrs(solver_handle, CUBLAS_FILL_MODE_UPPER,M,1,jacTjacd,M,Dpd,M,devInfo); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (devInfo_h) { - issolved=0; -#ifdef DEBUG - printf("Singular matrix\n"); -#endif - } - } - } else if (solve_axb==1) { - /* QR solver ********************************/ - //status=culaDeviceSgeqrf(M,M,jacTjacd,M,taud); - cusolverDnSgeqrf(solver_handle, M, M, jacTjacd, M, taud, work, work_size, devInfo); - cudaDeviceSynchronize(); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (!devInfo_h) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - printf("Singular matrix\n"); -#endif - } - - if (issolved) { - /* copy Dpd<=jacTed */ - cbstatus=cublasScopy(cbhandle, M, jacTed, 1, Dpd, 1); - //status=culaDeviceSgeqrs(M,M,1,jacTjacd,M,taud,Dpd,M); - cusolverDnSormqr(solver_handle, CUBLAS_SIDE_LEFT, CUBLAS_OP_T, M, 1, M, jacTjacd, M, taud, Dpd, M, work, work_size, devInfo); - cudaDeviceSynchronize(); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (devInfo_h) { - issolved=0; -#ifdef DEBUG - printf("Singular matrix\n"); -#endif - } else { - cone=1.0f; - cbstatus=cublasStrsm(cbhandle,CUBLAS_SIDE_LEFT,CUBLAS_FILL_MODE_UPPER,CUBLAS_OP_N,CUBLAS_DIAG_NON_UNIT,M,1,&cone,jacTjacd,M,Dpd,M); - } - } - } else { - /* SVD solver *********************************/ - /* U S VT = A */ - //status=culaDeviceSgesvd('A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M); - //checkStatus(status,__FILE__,__LINE__); - cusolverDnSgesvd(solver_handle,'A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M,work,work_size,rwork,devInfo); - cudaDeviceSynchronize(); - /* copy Dpd<=jacTed */ - cbstatus=cublasScopy(cbhandle, M, jacTed, 1, Dpd, 1); - /* b<=U^T * b */ - //status=culaDeviceSgemv('T',M,M,1.0f,Ud,M,Dpd,1,0.0f,Dpd,1); - //checkStatus(status,__FILE__,__LINE__); - cone=1.0f; czero=0.0f; - cbstatus=cublasSgemv(cbhandle,CUBLAS_OP_T,M,M,&cone,Ud,M,Dpd,1,&czero,Dpd,1); - /* divide by singular values Dpd[]/Sd[] for Sd[]> eps1 */ - cudakernel_diagdiv_fl(ThreadsPerBlock, BlocksPerGrid, M, eps1, Dpd, Sd); - - /* b<=VT^T * b */ - //status=culaDeviceSgemv('T',M,M,1.0f,VTd,M,Dpd,1,0.0f,Dpd,1); - //checkStatus(status,__FILE__,__LINE__); - cbstatus=cublasSgemv(cbhandle,CUBLAS_OP_T,M,M,&cone,VTd,M,Dpd,1,&czero,Dpd,1); - - issolved=1; - } -/*************************************************************************/ - - /* compute p's new estimate and ||Dp||^2 */ - if (issolved) { - /* compute p's new estimate and ||Dp||^2 */ - /* pnew=p+Dp */ - /* pnew=p */ - cbstatus=cublasScopy(cbhandle, M, pd, 1, pnewd, 1); - /* pnew=pnew+Dp */ - alpha=1.0f; - cbstatus=cublasSaxpy(cbhandle, M, &alpha, Dpd, 1, pnewd, 1); - - /* norm ||Dp|| */ - cbstatus=cublasSnrm2(cbhandle, M, Dpd, 1, &Dp_L2); - Dp_L2=Dp_L2*Dp_L2; - -#ifdef DEBUG -printf("norm ||dp|| =%f, norm ||p||=%f\n",Dp_L2,p_L2); -#endif - if(Dp_L2<=eps2_sq*p_L2){ /* relative change in p is small, stop */ - stop=2; - break; - } - - if(Dp_L2>=(p_L2+eps2)/(float)(CLM_EPSILON*CLM_EPSILON)){ /* almost singular */ - stop=4; - break; - } - - /* new function value */ - /* compute ||e(pDp)||_2 */ - /* ### hx=x-hx, pDp_eL2=||hx|| */ - /* copy to device */ - /* hxd<=hx */ - cudakernel_func_wt_fl(ThreadsPerBlock, (Nbase+ThreadsPerBlock-1)/ThreadsPerBlock, pnewd, hxd, M, N, cohd, bbd, wtd, Nbase, dp->M, dp->N); - - /* e=x */ - cbstatus=cublasScopy(cbhandle, N, xd, 1, ed, 1); - /* e = e \odot wt */ - cudakernel_hadamard_fl(ThreadsPerBlock1, (N+ThreadsPerBlock1-1)/ThreadsPerBlock1, N, wtd, ed); - /* e=x-hx */ - alpha=-1.0f; - cbstatus=cublasSaxpy(cbhandle, N, &alpha, hxd, 1, ed, 1); - /* note: e is updated */ - - /* norm ||e|| */ - cbstatus=cublasSnrm2(cbhandle, N, ed, 1, &pDp_eL2); - pDp_eL2=pDp_eL2*pDp_eL2; - - - if(!finite(pDp_eL2)){ /* sum of squares is not finite, most probably due to a user error. - */ - stop=7; - break; - } - - /* dL=Dp'*(mu*Dp+jacTe) */ - /* bd=jacTe+mu*Dp */ - cbstatus=cublasScopy(cbhandle, M, jacTed, 1, bd, 1); - cbstatus=cublasSaxpy(cbhandle, M, &mu, Dpd, 1, bd, 1); - cbstatus=cublasSdot(cbhandle, M, Dpd, 1, bd, 1, &dL); - - dF=p_eL2-pDp_eL2; - -#ifdef DEBUG - printf("dF=%f, dL=%f\n",dF,dL); -#endif - if(dL>0.0f && dF>0.0f){ /* reduction in error, increment is accepted */ - tmp=(2.0f*dF/dL-1.0f); - tmp=1.0f-tmp*tmp*tmp; - mu=mu*((tmp>=(float)CLM_ONE_THIRD)? tmp : (float)CLM_ONE_THIRD); - nu=2; - - /* update p's estimate */ - cbstatus=cublasScopy(cbhandle, M, pnewd, 1, pd, 1); - - /* update ||e||_2 */ - p_eL2=pDp_eL2; - break; - } - - } - /* if this point is reached, either the linear system could not be solved or - * the error did not reduce; in any case, the increment must be rejected - */ - - mu*=(float)nu; - nu2=nu<<1; // 2*nu; - if(nu2<=nu){ /* nu has wrapped around (overflown). */ - stop=5; - break; - } - - nu=nu2; - - } /* inner loop */ - - } - /**** end iteration loop ***********/ - if(k>=itmax) stop=3; - - if (nw>0 && nwM, dp->N); - /* e=x */ - cbstatus=cublasScopy(cbhandle, N, xd, 1, ed, 1); - /* e=x-hx */ - alpha=-1.0f; - cbstatus=cublasSaxpy(cbhandle, N, &alpha, hxd, 1, ed, 1); - } - - - if (nwrobust_nu=(double)robust_nu; - - /* copy back current solution */ - err=cudaMemcpyAsync(p,pd,M*sizeof(float),cudaMemcpyDeviceToHost,0); - checkCudaError(err,__FILE__,__LINE__); - - checkCublasError(cbstatus,__FILE__,__LINE__); - /* synchronize async operations */ - cudaDeviceSynchronize(); - - cudaFree(devInfo); - cudaFree(work); - if (solve_axb==2) { - cudaFree(rwork); - } - -#ifdef DEBUG - printf("stop=%d\n",stop); -#endif - if(info){ - info[0]=(double)init_p_eL2; - info[1]=(double)p_eL2; - info[2]=(double)jacTe_inf; - info[3]=(double)Dp_L2; - info[4]=(double)mu; - info[5]=(double)k; - info[6]=(double)stop; - info[7]=(double)0; - info[8]=(double)0; - info[9]=(double)0; - } - return 0; -} - - -/* robust, iteratively weighted non linear least squares using LM - entirely in the GPU, using float data, OS acceleration */ -int -osrlevmar_der_single_cuda_fl( - float *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - float *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - float *gWORK, /* GPU allocated memory */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - int randomize, /* if >0 randomize */ - void *adata) /* pointer to possibly additional data, passed uninterpreted to func & jacf. - * Set to NULL if not needed - */ -{ - - /* general note: all device variables end with a 'd' */ - int stop=0; - cudaError_t err; - cublasStatus_t cbstatus; - - int nu=2,nu2; - float p_L2, Dp_L2=(float)DBL_MAX, dF, dL, p_eL2, jacTe_inf=0.0f, pDp_eL2, init_p_eL2; - float tmp,mu=0.0f; - float tau, eps1, eps2, eps2_sq, eps3; - int k,ci,issolved; - - float *hxd; - float *wtd,*qd; - - int nw,wt_itmax=3; - /* ME data */ - me_data_t *dp=(me_data_t*)adata; - float wt_sum,lambda,robust_nu=(float)dp->robust_nu; - float q_sum,robust_nu1; - float deltanu; - int Nd=100; /* no of points where nu is sampled, note NdN) { Nd=N; } - /* only search for nu in [2,30] because 30 is almost Gaussian */ - deltanu=(float)(robust_nuhigh-robust_nulow)/(float)Nd; - - - float *ed; - float *xd; - - float *jacd; - - float *jacTjacd,*jacTjacd0; - - float *Dpd,*bd; - float *pd,*pnewd; - float *jacTed; - - /* used in QR solver */ - float *taud=0; - - /* used in SVD solver */ - float *Ud=0; - float *VTd=0; - float *Sd=0; - - int Nbase=(dp->Nbase)*(ntiles); /* note: we do not use the total tile size */ - /* coherency on device */ - float *cohd; - /* baseline-station map on device/host */ - short *bbd; - - int solve_axb=linsolv; - float alpha; - - /* setup default settings */ - if(opts){ - tau=(float)opts[0]; - eps1=(float)opts[1]; - eps2=(float)opts[2]; - eps2_sq=(float)opts[2]*opts[2]; - eps3=(float)opts[3]; - } else { - tau=(float)CLM_INIT_MU; - eps1=(float)CLM_STOP_THRESH; - eps2=(float)CLM_STOP_THRESH; - eps2_sq=(float)CLM_STOP_THRESH*CLM_STOP_THRESH; - eps3=(float)CLM_STOP_THRESH; - } - - /* calculate no of cuda threads and blocks */ - int ThreadsPerBlock=DEFAULT_TH_PER_BK; - /* FIXME: might need a large value for large no of baselines */ - int ThreadsPerBlock1=DEFAULT_TH_PER_BK; /* for accessing each element of a baseline */ - int ThreadsPerBlock2=Nd/2; /* for evaluating nu */ - int BlocksPerGrid= 2*(M+ThreadsPerBlock-1)/ThreadsPerBlock; - - - unsigned long int moff; - moff=0; - xd=&gWORK[moff]; - moff+=N; - jacd=&gWORK[moff]; - moff+=M*N; - jacTjacd=&gWORK[moff]; - moff+=M*M; - jacTed=&gWORK[moff]; - moff+=M; - jacTjacd0=&gWORK[moff]; - moff+=M*M; - Dpd=&gWORK[moff]; - moff+=M; - bd=&gWORK[moff]; - moff+=M; - pd=&gWORK[moff]; - moff+=M; - pnewd=&gWORK[moff]; - moff+=M; - cohd=&gWORK[moff]; - moff+=Nbase*8; - hxd=&gWORK[moff]; - moff+=N; - ed=&gWORK[moff]; - moff+=N; - wtd=&gWORK[moff]; - moff+=N; - qd=&gWORK[moff]; - moff+=N; - if (solve_axb==1) { - taud=&gWORK[moff]; - moff+=M; - } else if (solve_axb==2) { - Ud=&gWORK[moff]; - moff+=M*M; - VTd=&gWORK[moff]; - moff+=M*M; - Sd=&gWORK[moff]; - moff+=M; - } - bbd=(short*)&gWORK[moff]; - moff+=(Nbase*2*sizeof(short))/sizeof(float); - - /* extra storage for cusolver */ - int work_size=0; - int *devInfo; - int devInfo_h=0; - err=cudaMalloc((void**)&devInfo, sizeof(int)); - checkCudaError(err,__FILE__,__LINE__); - float *work; - float *rwork; - if (solve_axb==0) { - cusolverDnSpotrf_bufferSize(solver_handle, CUBLAS_FILL_MODE_UPPER, M, jacTjacd, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(float)); - checkCudaError(err,__FILE__,__LINE__); - } else if (solve_axb==1) { - cusolverDnSgeqrf_bufferSize(solver_handle, M, M, jacTjacd, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(float)); - checkCudaError(err,__FILE__,__LINE__); - } else { - cusolverDnSgesvd_bufferSize(solver_handle, M, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(float)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&rwork, 5*M*sizeof(float)); - checkCudaError(err,__FILE__,__LINE__); - } - - - err=cudaMemcpyAsync(pd, p, M*sizeof(float), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - /* need to give right offset for coherencies */ - /* offset: cluster offset+time offset */ - err=cudaMemcpyAsync(cohd, &(dp->ddcohf[(dp->Nbase)*(dp->tilesz)*(dp->clus)*8+(dp->Nbase)*tileoff*8]), Nbase*8*sizeof(float), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - /* correct offset for baselines */ - err=cudaMemcpyAsync(bbd, &(dp->ddbase[2*(dp->Nbase)*(tileoff)]), Nbase*2*sizeof(short), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - cudaDeviceSynchronize(); - /* xd <=x */ - err=cudaMemcpyAsync(xd, x, N*sizeof(float), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - - /* set initial weights to 1 by a cuda kernel */ - cudakernel_setweights_fl(ThreadsPerBlock1, (N+ThreadsPerBlock1-1)/ThreadsPerBlock1, N, wtd, 1.0f); - - /* setup OS subsets and stating offsets */ - /* ed : N, cohd : Nbase*8, bbd : Nbase*2 full size */ - /* if ntilesstat mapping, Nbase, Mclusters, Nstations*/ - cudakernel_func_wt_fl(ThreadsPerBlock, (Nbase+ThreadsPerBlock-1)/ThreadsPerBlock, pd,hxd,M,N, cohd, bbd, wtd, Nbase, dp->M, dp->N); - - /* e=x */ - cbstatus=cublasScopy(cbhandle, N, xd, 1, ed, 1); - /* e = e \odot wt */ - cudakernel_hadamard_fl(ThreadsPerBlock1, (N+ThreadsPerBlock1-1)/ThreadsPerBlock1, N, wtd, ed); - /* e=x-hx */ - alpha=-1.0f; - cbstatus=cublasSaxpy(cbhandle, N, &alpha, hxd, 1, ed, 1); - - /* norm ||e|| */ - cbstatus=cublasSnrm2(cbhandle, N, ed, 1, &p_eL2); - /* square */ - p_eL2=p_eL2*p_eL2; - - init_p_eL2=p_eL2; - if(!finitef(p_eL2)) stop=7; - - - - /**** iteration loop ***********/ - for(k=0; kstat mapping, Nbase, Mclusters, Nstations*/ - /* FIXME thread/block sizes 16x16=256, so 16 is chosen */ - cudakernel_jacf_wt_fl(ThreadsPerBlock, ThreadsPerBlock/4, pd, jacd, M, Nos[l], &cohd[8*NbI[l]], &bbd[2*NbI[l]], &wtd[edI[l]], Nbaseos[l], dp->M, dp->N); - - /* Compute J^T J and J^T e */ - /* Cache efficient computation of J^T J based on blocking - */ - /* since J is in ROW major order, assume it is transposed, - so actually calculate A=J*J^T, where J is size MxN */ - //status=culaDeviceSgemm('N','T',M,M,Nos[l],1.0f,jacd,M,jacd,M,0.0f,jacTjacd,M); - //checkStatus(status,__FILE__,__LINE__); - float cone=1.0f; float czero=0.0f; - cbstatus=cublasSgemm(cbhandle,CUBLAS_OP_N,CUBLAS_OP_T,M,M,Nos[l],&cone,jacd,M,jacd,M,&czero,jacTjacd,M); - - /* create backup */ - /* copy jacTjacd0<=jacTjacd */ - cbstatus=cublasScopy(cbhandle, M*M, jacTjacd, 1, jacTjacd0, 1); - /* J^T e */ - /* calculate b=J^T*e (actually compute b=J*e, where J in row major (size MxN) */ - //status=culaDeviceSgemv('N',M,Nos[l],1.0f,jacd,M,&ed[edI[l]],1,0.0f,jacTed,1); - //checkStatus(status,__FILE__,__LINE__); - cbstatus=cublasSgemv(cbhandle,CUBLAS_OP_N,M,Nos[l],&cone,jacd,M,&ed[edI[l]],1,&czero,jacTed,1); - - - /* Compute ||J^T e||_inf and ||p||^2 */ - /* find infinity norm of J^T e, 1 based indexing*/ - cbstatus=cublasIsamax(cbhandle, M, jacTed, 1, &ci); - err=cudaMemcpy(&jacTe_inf,&(jacTed[ci-1]),sizeof(float),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - /* L2 norm of current parameter values */ - /* norm ||Dp|| */ - cbstatus=cublasSnrm2(cbhandle, M, pd, 1, &p_L2); - p_L2=p_L2*p_L2; - if(jacTe_inf<0.0f) {jacTe_inf=-jacTe_inf;} -#ifdef DEBUG - printf("Inf norm=%f\n",jacTe_inf); -#endif - - /* check for convergence */ - if((jacTe_inf <= eps1)){ - Dp_L2=0.0f; /* no increment for p in this case */ - stop=1; - break; - } - - /* compute initial (k=0) damping factor */ - if (k==0) { - /* find max diagonal element (stride is M+1) */ - /* should be MAX not MAX(ABS) */ - cbstatus=cublasIsamax(cbhandle, M, jacTjacd, M+1, &ci); /* 1 based index */ - ci=(ci-1)*(M+1); /* right value of the diagonal */ - - err=cudaMemcpy(&tmp,&(jacTjacd[ci]),sizeof(float),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - mu=tau*tmp; - } - - - /* determine increment using adaptive damping */ - while(1){ - /* augment normal equations */ - /* increment A => A+ mu*I, increment diagonal entries */ - /* copy jacTjacd<=jacTjacd0 */ - cbstatus=cublasScopy(cbhandle, M*M, jacTjacd0, 1, jacTjacd, 1); - cudakernel_diagmu_fl(ThreadsPerBlock, BlocksPerGrid, M, jacTjacd, mu); - -#ifdef DEBUG - printf("mu=%lf\n",mu); -#endif -/*************************************************************************/ - issolved=0; - /* solve augmented equations A x = b */ - /* A==jacTjacd, b==Dpd, after solving, x==Dpd */ - /* b=jacTed : intially right hand side, at exit the solution */ - if (solve_axb==0) { - /* Cholesky solver **********************/ - /* lower triangle of Ad is destroyed */ - //status=culaDeviceSpotrf('U',M,jacTjacd,M); - cusolverDnSpotrf(solver_handle, CUBLAS_FILL_MODE_UPPER, M, jacTjacd, M, work, work_size, devInfo); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (!devInfo_h) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - printf("Singular matrix\n"); -#endif - } - if (issolved) { - /* copy Dpd<=jacTed */ - cbstatus=cublasScopy(cbhandle, M, jacTed, 1, Dpd, 1); -#ifdef DEBUG - checkCublasError(cbstatus,__FILE__,__LINE__); -#endif - //status=culaDeviceSpotrs('U',M,1,jacTjacd,M,Dpd,M); - cusolverDnSpotrs(solver_handle, CUBLAS_FILL_MODE_UPPER,M,1,jacTjacd,M,Dpd,M,devInfo); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (devInfo_h) { - issolved=0; -#ifdef DEBUG - printf("Singular matrix\n"); -#endif - } - } - } else if (solve_axb==1) { - /* QR solver ********************************/ - //status=culaDeviceSgeqrf(M,M,jacTjacd,M,taud); - cusolverDnSgeqrf(solver_handle, M, M, jacTjacd, M, taud, work, work_size, devInfo); - cudaDeviceSynchronize(); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (!devInfo_h) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - printf("Singular matrix\n"); -#endif - } - - if (issolved) { - /* copy Dpd<=jacTed */ - cbstatus=cublasScopy(cbhandle, M, jacTed, 1, Dpd, 1); - //status=culaDeviceSgeqrs(M,M,1,jacTjacd,M,taud,Dpd,M); - cusolverDnSormqr(solver_handle, CUBLAS_SIDE_LEFT, CUBLAS_OP_T, M, 1, M, jacTjacd, M, taud, Dpd, M, work, work_size, devInfo); - cudaDeviceSynchronize(); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (devInfo_h) { - issolved=0; -#ifdef DEBUG - printf("Singular matrix\n"); -#endif - } else { - cone=1.0f; - cbstatus=cublasStrsm(cbhandle,CUBLAS_SIDE_LEFT,CUBLAS_FILL_MODE_UPPER,CUBLAS_OP_N,CUBLAS_DIAG_NON_UNIT,M,1,&cone,jacTjacd,M,Dpd,M); - } - } - } else { - /* SVD solver *********************************/ - /* U S VT = A */ - //status=culaDeviceSgesvd('A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M); - //checkStatus(status,__FILE__,__LINE__); - cusolverDnSgesvd(solver_handle,'A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M,work,work_size,rwork,devInfo); - cudaDeviceSynchronize(); - /* copy Dpd<=jacTed */ - cbstatus=cublasScopy(cbhandle, M, jacTed, 1, Dpd, 1); - /* b<=U^T * b */ - //status=culaDeviceSgemv('T',M,M,1.0f,Ud,M,Dpd,1,0.0f,Dpd,1); - //checkStatus(status,__FILE__,__LINE__); - cone=1.0f; czero=0.0f; - cbstatus=cublasSgemv(cbhandle,CUBLAS_OP_T,M,M,&cone,Ud,M,Dpd,1,&czero,Dpd,1); - - /* divide by singular values Dpd[]/Sd[] for Sd[]> eps1 */ - cudakernel_diagdiv_fl(ThreadsPerBlock, BlocksPerGrid, M, eps1, Dpd, Sd); - - /* b<=VT^T * b */ - //status=culaDeviceSgemv('T',M,M,1.0f,VTd,M,Dpd,1,0.0f,Dpd,1); - //checkStatus(status,__FILE__,__LINE__); - cbstatus=cublasSgemv(cbhandle,CUBLAS_OP_T,M,M,&cone,VTd,M,Dpd,1,&czero,Dpd,1); - - issolved=1; - } -/*************************************************************************/ - - /* compute p's new estimate and ||Dp||^2 */ - if (issolved) { - /* compute p's new estimate and ||Dp||^2 */ - /* pnew=p+Dp */ - /* pnew=p */ - cbstatus=cublasScopy(cbhandle, M, pd, 1, pnewd, 1); - /* pnew=pnew+Dp */ - alpha=1.0f; - cbstatus=cublasSaxpy(cbhandle, M, &alpha, Dpd, 1, pnewd, 1); - - /* norm ||Dp|| */ - cbstatus=cublasSnrm2(cbhandle, M, Dpd, 1, &Dp_L2); - Dp_L2=Dp_L2*Dp_L2; - -#ifdef DEBUG -printf("norm ||dp|| =%f, norm ||p||=%f\n",Dp_L2,p_L2); -#endif - if(Dp_L2<=eps2_sq*p_L2){ /* relative change in p is small, stop */ - stop=2; - break; - } - - if(Dp_L2>=(p_L2+eps2)/(float)(CLM_EPSILON*CLM_EPSILON)){ /* almost singular */ - stop=4; - break; - } - - /* new function value */ - /* compute ||e(pDp)||_2 */ - /* ### hx=x-hx, pDp_eL2=||hx|| */ - /* copy to device */ - /* hxd<=hx */ - cudakernel_func_wt_fl(ThreadsPerBlock, (Nbase+ThreadsPerBlock-1)/ThreadsPerBlock, pnewd, hxd, M, N, cohd, bbd, wtd, Nbase, dp->M, dp->N); - - /* e=x */ - cbstatus=cublasScopy(cbhandle, N, xd, 1, ed, 1); - /* e = e \odot wt */ - cudakernel_hadamard_fl(ThreadsPerBlock1, (N+ThreadsPerBlock1-1)/ThreadsPerBlock1, N, wtd, ed); - /* e=x-hx */ - alpha=-1.0f; - cbstatus=cublasSaxpy(cbhandle, N, &alpha, hxd, 1, ed, 1); - /* note: e is updated */ - - /* norm ||e|| */ - cbstatus=cublasSnrm2(cbhandle, N, ed, 1, &pDp_eL2); - pDp_eL2=pDp_eL2*pDp_eL2; - - - if(!finite(pDp_eL2)){ /* sum of squares is not finite, most probably due to a user error. - */ - stop=7; - break; - } - - /* dL=Dp'*(mu*Dp+jacTe) */ - /* bd=jacTe+mu*Dp */ - cbstatus=cublasScopy(cbhandle, M, jacTed, 1, bd, 1); - cbstatus=cublasSaxpy(cbhandle, M, &mu, Dpd, 1, bd, 1); - cbstatus=cublasSdot(cbhandle, M, Dpd, 1, bd, 1, &dL); - - dF=p_eL2-pDp_eL2; - -#ifdef DEBUG - printf("dF=%f, dL=%f\n",dF,dL); -#endif - if(dL>0.0f && dF>0.0f){ /* reduction in error, increment is accepted */ - tmp=(2.0f*dF/dL-1.0f); - tmp=1.0f-tmp*tmp*tmp; - mu=mu*((tmp>=(float)CLM_ONE_THIRD)? tmp : (float)CLM_ONE_THIRD); - nu=2; - - /* update p's estimate */ - cbstatus=cublasScopy(cbhandle, M, pnewd, 1, pd, 1); - - /* update ||e||_2 */ - p_eL2=pDp_eL2; - break; - } - - } - /* if this point is reached, either the linear system could not be solved or - * the error did not reduce; in any case, the increment must be rejected - */ - - mu*=(float)nu; - nu2=nu<<1; // 2*nu; - if(nu2<=nu){ /* nu has wrapped around (overflown). */ - stop=5; - break; - } - - nu=nu2; - - } /* inner loop */ - - } - if (randomize) { - free(subI); - } -/**************** end OS loop ***************************/ - - } - /**** end iteration loop ***********/ - if(k>=itmax) stop=3; - - if (nw>0 && nwM, dp->N); - /* e=x */ - cbstatus=cublasScopy(cbhandle, N, xd, 1, ed, 1); - /* e=x-hx */ - alpha=-1.0f; - cbstatus=cublasSaxpy(cbhandle, N, &alpha, hxd, 1, ed, 1); - } - - - if (nwrobust_nu=(double)robust_nu; - - free(Nos); - free(Nbaseos); - free(edI); - free(NbI); - - /* copy back current solution */ - err=cudaMemcpyAsync(p,pd,M*sizeof(float),cudaMemcpyDeviceToHost,0); - checkCudaError(err,__FILE__,__LINE__); - checkCublasError(cbstatus,__FILE__,__LINE__); - - /* synchronize async operations */ - cudaDeviceSynchronize(); - - cudaFree(devInfo); - cudaFree(work); - if (solve_axb==2) { - cudaFree(rwork); - } - -#ifdef DEBUG - printf("stop=%d\n",stop); -#endif - if(info){ - info[0]=(double)init_p_eL2; - info[1]=(double)p_eL2; - info[2]=(double)jacTe_inf; - info[3]=(double)Dp_L2; - info[4]=(double)mu; - info[5]=(double)k; - info[6]=(double)stop; - info[7]=(double)0; - info[8]=(double)0; - info[9]=(double)0; - } - return 0; -} -#endif /* HAVE_CUDA */ - -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -odot_threadfn(void *data) { - thread_data_vec_t *t=(thread_data_vec_t*)data; - int ci; - for (ci=t->starti; ci<=t->endi; ci++) { - t->ed[ci]*=t->wtd[ci]; - } - return NULL; -} - - -/* Hadamard product */ -/* ed <= ed*wtd , size Nx1 - Nt threads */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static int -my_odot(double *ed,double *wtd,int N,int Nt) { - int nth,nth1,ci; - int Nthb0,Nthb; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_vec_t *threaddata; - - /* calculate min values a thread can handle */ - Nthb0=(N+Nt-1)/Nt; - - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { -#ifndef USE_MIC - printf("%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((threaddata=(thread_data_vec_t*)malloc((size_t)Nt*sizeof(thread_data_vec_t)))==0) { -#ifndef USE_MIC - printf("%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - /* iterate over threads, allocating indices per thread */ - ci=0; - for (nth=0; nthrobust_nu; - double robust_nu1; - - setweights(M,aones,1.0,lmdata->Nt); - /*W set initial weights to 1 */ - setweights(N,wtd,1.0,lmdata->Nt); - /* memory allocation: different solvers */ - if (solve_axb==0) { - - } else if (solve_axb==1) { - /* workspace query */ - status=my_dgels('N',M,M,1,jacTjacd,M,Dpd,M,w,-1); - if (!status) { - lwork=(int)w[0]; - if ((WORK=(double*)calloc((size_t)lwork,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - } - } else { - if ((Ud=(double*)calloc((size_t)M*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((VTd=(double*)calloc((size_t)M*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((Sd=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - status=my_dgesvd('A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M,w,-1); - if (!status) { - lwork=(int)w[0]; - if ((WORK=(double*)calloc((size_t)lwork,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - } - } - - - /* EM iteration loop */ - /************************************************************/ - for (nw=0; nw A+ mu*I, increment diagonal entries */ - /* copy jacTjacd<=jacTjacd0 */ - memcpy(jacTjacd,jacTjacd0,M*M*sizeof(double)); - my_daxpys(M,aones,1,mu,jacTjacd,M+1); - -#ifdef DEBUG - printf("mu=%lf\n",mu); -#endif -/*************************************************************************/ - /* solve augmented equations A x = b */ - /* A==jacTjacd, b==Dpd, after solving, x==Dpd */ - /* b=jacTed : intially right hand side, at exit the solution */ - if (solve_axb==0) { - /* Cholesky solver **********************/ - /* lower triangle of Ad is destroyed */ - status=my_dpotrf('U',M,jacTjacd,M); - if (!status) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - printf("Singular matrix info=%d\n",status); -#endif - } - if (issolved) { - /* copy Dpd<=jacTed */ - memcpy(Dpd,jacTed,M*sizeof(double)); - status=my_dpotrs('U',M,1,jacTjacd,M,Dpd,M); - if (status) { - issolved=0; -#ifdef DEBUG - printf("Singular matrix info=%d\n",status); -#endif - } - } - } else if (solve_axb==1) { - /* QR solver ********************************/ - /* copy Dpd<=jacTed */ - memcpy(Dpd,jacTed,M*sizeof(double)); - status=my_dgels('N',M,M,1,jacTjacd,M,Dpd,M,WORK,lwork); - if (!status) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - printf("Singular matrix info=%d\n",status); -#endif - } - - } else { - /* SVD solver *********************************/ - /* U S VT = A */ - status=my_dgesvd('A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M,WORK,lwork); - /* copy Dpd<=jacTed */ - memcpy(bd,jacTed,M*sizeof(double)); - /* b<=U^T * b */ - my_dgemv('T',M,M,1.0,Ud,M,bd,1,0.0,Dpd,1); - /* robust correction */ - /* divide by singular values Dpd[]/Sd[] for Sd[]> eps1 */ - for (ci=0; cieps1) { - Dpd[ci]=Dpd[ci]/Sd[ci]; - } else { - Dpd[ci]=0.0; - } - } - - /* b<=VT^T * b */ - memcpy(bd,Dpd,M*sizeof(double)); - my_dgemv('T',M,M,1.0,VTd,M,bd,1,0.0,Dpd,1); - - issolved=1; - } -/*************************************************************************/ - - /* compute p's new estimate and ||Dp||^2 */ - if (issolved) { - /* compute p's new estimate and ||Dp||^2 */ - /* pnew=p+Dp */ - /* pnew=p */ - memcpy(pnew,p,M*sizeof(double)); - /* pnew=pnew+Dp */ - my_daxpy(M,Dpd,1.0,pnew); - - /* norm ||Dp|| */ - Dp_L2=my_dnrm2(M,Dpd); - Dp_L2=Dp_L2*Dp_L2; - -#ifdef DEBUG -printf("norm ||dp|| =%lf, norm ||p||=%lf\n",Dp_L2,p_L2); -#endif - if(Dp_L2<=eps2_sq*p_L2){ /* relative change in p is small, stop */ - stop=2; - break; - } - - if(Dp_L2>=(p_L2+eps2)/(CLM_EPSILON*CLM_EPSILON)){ /* almost singular */ - stop=4; - break; - } - - /* new function value */ - (*func)(pnew, hxd, M, N, adata); /* evaluate function at p + Dp */ - - /* compute ||e(pDp)||_2 */ - /* ### hx=x-hx, pDp_eL2=||hx|| */ - /* copy to device */ - /* hxd<=hx */ - - /* e=x */ - memcpy(ed,x,N*sizeof(double)); - /* e=x-hx */ - my_daxpy(N,hxd,-1.0,ed); - /* note: e is updated */ - - /*W e<= wt\odot e */ - my_odot(ed,wtd,N,Nt); - - /* norm ||e|| */ - pDp_eL2=my_dnrm2(N,ed); - pDp_eL2=pDp_eL2*pDp_eL2; - - - if(!finite(pDp_eL2)){ /* sum of squares is not finite, most probably due to a user error. - */ - stop=7; - break; - } - - /* dL=Dp'*(mu*Dp+jacTe) */ - /* bd=jacTe+mu*Dp */ - memcpy(bd,jacTed,M*sizeof(double)); - my_daxpy(M,Dpd,mu,bd); - dL=my_ddot(M,Dpd,bd); - - dF=p_eL2-pDp_eL2; - -#ifdef DEBUG - printf("dF=%lf, dL=%lf\n",dF,dL); -#endif - if(dL>0.0 && dF>0.0){ /* reduction in error, increment is accepted */ - tmp=(2.0*dF/dL-1.0); - tmp=1.0-tmp*tmp*tmp; - mu=mu*((tmp>=CLM_ONE_THIRD)? tmp : CLM_ONE_THIRD); - nu=2; - - /* update p's estimate */ - memcpy(p,pnew,M*sizeof(double)); - - /* update ||e||_2 */ - p_eL2=pDp_eL2; - break; - } - - } - /* if this point is reached, either the linear system could not be solved or - * the error did not reduce; in any case, the increment must be rejected - */ - - mu*=(double)nu; - nu2=nu<<1; // 2*nu; - if(nu2<=nu){ /* nu has wrapped around (overflown). */ - stop=5; - break; - } - - nu=nu2; - - } /* inner loop */ - - } - /**** end iteration loop ***********/ - - - if(k>=itmax) stop=3; - - /*W if not at first or last iteration, recalculate error */ - if (nw>0 && nwrobust_nu=robust_nu; - - free(jac); - free(jacTjacd); - free(jacTjacd0); - free(jacTed); - free(Dpd); - free(bd); - free(hxd); - if (!jac_given) { free(hxm); } - free(ed); - free(wtd); - free(aones); - free(pnew); - - if (solve_axb==0) { - } else if (solve_axb==1) { - free(WORK); - } else { - free(Ud); - free(VTd); - free(Sd); - free(WORK); - } -#ifdef DEBUG - printf("stop=%d\n",stop); -#endif - if(info){ - info[0]=init_p_eL2; - info[1]=p_eL2; - info[2]=jacTe_inf; - info[3]=Dp_L2; - info[4]=mu; - info[5]=(double)k; - info[6]=(double)stop; - info[7]=(double)0; - info[8]=(double)0; - info[9]=(double)0; - } - return 0; -} - - - -/* robust LM, OS acceleration */ -int -osrlevmar_der_single_nocuda( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - * info[5]= # iterations, - * info[6]=reason for terminating: 1 - stopped by small gradient J^T e - * 2 - stopped by small Dp - * 3 - stopped by itmax - * 4 - singular matrix. Restart from current p with increased mu - * 5 - no further error reduction is possible. Restart with increased mu - * 6 - stopped by small ||e||_2 - * 7 - stopped by invalid (i.e. NaN or Inf) "func" values. This is a user error - * info[7]= # function evaluations - * info[8]= # Jacobian evaluations - * info[9]= # linear systems solved, i.e. # attempts for reducing error - */ - - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int Nt, /* no of threads */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - int randomize, /* if >0 randomize */ - void *adata) /* pointer to possibly additional data, passed uninterpreted to func & jacf. - * Set to NULL if not needed - */ -{ - - /* general note: all device variables end with a 'd' */ - int stop=0; - int nu=2,nu2; - double p_L2, Dp_L2=DBL_MAX, dF, dL, p_eL2, jacTe_inf=0.0, pDp_eL2, init_p_eL2; - double tmp,mu=0.0; - double tau, eps1, eps2, eps2_sq, eps3; - int k,ci,issolved; - - double *hxd; - double *ed,*wtd; - double *jac; - - double *jacTjacd,*jacTjacd0; - - double *pnew,*Dpd,*bd; - double *aones; - double *jacTed; - - /* used in QR solver */ - double *WORK; - int lwork=0; - double w[1]; - - int status; - - /* used in SVD solver */ - double *Ud; - double *VTd; - double *Sd; - - - int solve_axb=linsolv; - - /* setup default settings */ - if(opts){ - tau=opts[0]; - eps1=opts[1]; - eps2=opts[2]; - eps2_sq=opts[2]*opts[2]; - eps3=opts[3]; - } else { - tau=CLM_INIT_MU; - eps1=CLM_STOP_THRESH; - eps2=CLM_STOP_THRESH; - eps2_sq=CLM_STOP_THRESH*CLM_STOP_THRESH; - eps3=CLM_STOP_THRESH; - } - - if ((hxd=(double*)calloc((size_t)N,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((ed=(double*)calloc((size_t)N,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((jac=(double*)calloc((size_t)N*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((jacTjacd=(double*)calloc((size_t)M*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((jacTjacd0=(double*)calloc((size_t)M*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((jacTed=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((Dpd=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((bd=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((pnew=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((aones=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((wtd=(double*)calloc((size_t)N,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - WORK=Ud=Sd=VTd=0; - me_data_t *lmdata0=(me_data_t*)adata; - int nw,wt_itmax=3; - double wt_sum,lambda,robust_nu=lmdata0->robust_nu; - double robust_nu1; - - - setweights(M,aones,1.0,lmdata0->Nt); - /*W set initial weights to 1 */ - setweights(N,wtd,1.0,lmdata0->Nt); - - /* memory allocation: different solvers */ - if (solve_axb==0) { - - } else if (solve_axb==1) { - /* workspace query */ - status=my_dgels('N',M,M,1,jacTjacd,M,Dpd,M,w,-1); - if (!status) { - lwork=(int)w[0]; - if ((WORK=(double*)calloc((size_t)lwork,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - } - } else { - if ((Ud=(double*)calloc((size_t)M*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((VTd=(double*)calloc((size_t)M*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((Sd=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - status=my_dgesvd('A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M,w,-1); - if (!status) { - lwork=(int)w[0]; - if ((WORK=(double*)calloc((size_t)lwork,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - } - } - - - /* setup OS subsets and stating offsets */ - /* ME data for Jacobian calculation (need a new one) */ - me_data_t lmdata; - lmdata.clus=lmdata0->clus; - lmdata.u=lmdata.v=lmdata.w=0; /* not needed */ - lmdata.Nbase=lmdata0->Nbase; - lmdata.tilesz=lmdata0->tilesz; - lmdata.N=lmdata0->N; - lmdata.carr=lmdata0->carr; - lmdata.M=lmdata0->M; - lmdata.Mt=lmdata0->Mt; - lmdata.freq0=lmdata0->freq0; - lmdata.Nt=lmdata0->Nt; - lmdata.barr=lmdata0->barr; - lmdata.coh=lmdata0->coh; - lmdata.tileoff=lmdata0->tileoff; - - - int Nsubsets=10; - if (lmdata0->tilesztilesz; } - /* FIXME: is 0.1 enough ? */ - int max_os_iter=(int)ceil(0.1*(double)Nsubsets); - int Npersubset=(N+Nsubsets-1)/Nsubsets; - int Ntpersubset=(lmdata0->tilesz+Nsubsets-1)/Nsubsets; - int *Nos,*edI,*subI=0,*tileI,*tileoff; - if ((Nos=(int*)calloc((size_t)Nsubsets,sizeof(int)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((edI=(int*)calloc((size_t)Nsubsets,sizeof(int)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((tileI=(int*)calloc((size_t)Nsubsets,sizeof(int)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((tileoff=(int*)calloc((size_t)Nsubsets,sizeof(int)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - int l,ositer;; - k=l=0; - for (ci=0; citileoff+l; - if (l+Ntpersubsettilesz) { - Nos[ci]=Npersubset; - tileI[ci]=Ntpersubset; - } else { - Nos[ci]=N-k; - tileI[ci]=lmdata0->tilesz-l; - } - k=k+Npersubset; - l=l+Ntpersubset; - } - - /* EM iteration loop */ - /************************************************************/ - for (nw=0; nw A+ mu*I, increment diagonal entries */ - /* copy jacTjacd<=jacTjacd0 */ - memcpy(jacTjacd,jacTjacd0,M*M*sizeof(double)); - my_daxpys(M,aones,1,mu,jacTjacd,M+1); - -#ifdef DEBUG - printf("mu=%lf\n",mu); -#endif -/*************************************************************************/ - /* solve augmented equations A x = b */ - /* A==jacTjacd, b==Dpd, after solving, x==Dpd */ - /* b=jacTed : intially right hand side, at exit the solution */ - if (solve_axb==0) { - /* Cholesky solver **********************/ - /* lower triangle of Ad is destroyed */ - status=my_dpotrf('U',M,jacTjacd,M); - if (!status) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - printf("Singular matrix info=%d\n",status); -#endif - } - if (issolved) { - /* copy Dpd<=jacTed */ - memcpy(Dpd,jacTed,M*sizeof(double)); - status=my_dpotrs('U',M,1,jacTjacd,M,Dpd,M); - if (status) { - issolved=0; -#ifdef DEBUG - printf("Singular matrix info=%d\n",status); -#endif - } - } - } else if (solve_axb==1) { - /* QR solver ********************************/ - /* copy Dpd<=jacTed */ - memcpy(Dpd,jacTed,M*sizeof(double)); - status=my_dgels('N',M,M,1,jacTjacd,M,Dpd,M,WORK,lwork); - if (!status) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - printf("Singular matrix info=%d\n",status); -#endif - } - - } else { - /* SVD solver *********************************/ - /* U S VT = A */ - status=my_dgesvd('A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M,WORK,lwork); - /* copy Dpd<=jacTed */ - memcpy(bd,jacTed,M*sizeof(double)); - /* b<=U^T * b */ - my_dgemv('T',M,M,1.0,Ud,M,bd,1,0.0,Dpd,1); - /* robust correction */ - /* divide by singular values Dpd[]/Sd[] for Sd[]> eps1 */ - for (ci=0; cieps1) { - Dpd[ci]=Dpd[ci]/Sd[ci]; - } else { - Dpd[ci]=0.0; - } - } - - /* b<=VT^T * b */ - memcpy(bd,Dpd,M*sizeof(double)); - my_dgemv('T',M,M,1.0,VTd,M,bd,1,0.0,Dpd,1); - - issolved=1; - } -/*************************************************************************/ - - /* compute p's new estimate and ||Dp||^2 */ - if (issolved) { - /* compute p's new estimate and ||Dp||^2 */ - /* pnew=p+Dp */ - /* pnew=p */ - memcpy(pnew,p,M*sizeof(double)); - /* pnew=pnew+Dp */ - my_daxpy(M,Dpd,1.0,pnew); - - /* norm ||Dp|| */ - Dp_L2=my_dnrm2(M,Dpd); - Dp_L2=Dp_L2*Dp_L2; - -#ifdef DEBUG -printf("norm ||dp|| =%lf, norm ||p||=%lf\n",Dp_L2,p_L2); -#endif - if(Dp_L2<=eps2_sq*p_L2){ /* relative change in p is small, stop */ - stop=2; - break; - } - - if(Dp_L2>=(p_L2+eps2)/(CLM_EPSILON*CLM_EPSILON)){ /* almost singular */ - stop=4; - break; - } - - /* new function value */ - (*func)(pnew, hxd, M, N, adata); /* evaluate function at p + Dp */ - - /* compute ||e(pDp)||_2 */ - /* ### hx=x-hx, pDp_eL2=||hx|| */ - /* copy to device */ - /* hxd<=hx */ - - /* e=x */ - memcpy(ed,x,N*sizeof(double)); - /* e=x-hx */ - my_daxpy(N,hxd,-1.0,ed); - /* note: e is updated */ - - /*W e<= wt\odot e */ - my_odot(ed,wtd,N,Nt); - - /* norm ||e|| */ - pDp_eL2=my_dnrm2(N,ed); - pDp_eL2=pDp_eL2*pDp_eL2; - - - if(!finite(pDp_eL2)){ /* sum of squares is not finite, most probably due to a user error. - */ - stop=7; - break; - } - - /* dL=Dp'*(mu*Dp+jacTe) */ - /* bd=jacTe+mu*Dp */ - memcpy(bd,jacTed,M*sizeof(double)); - my_daxpy(M,Dpd,mu,bd); - dL=my_ddot(M,Dpd,bd); - - dF=p_eL2-pDp_eL2; - -#ifdef DEBUG - printf("dF=%lf, dL=%lf\n",dF,dL); -#endif - if(dL>0.0 && dF>0.0){ /* reduction in error, increment is accepted */ - tmp=(2.0*dF/dL-1.0); - tmp=1.0-tmp*tmp*tmp; - mu=mu*((tmp>=CLM_ONE_THIRD)? tmp : CLM_ONE_THIRD); - nu=2; - - /* update p's estimate */ - memcpy(p,pnew,M*sizeof(double)); - - /* update ||e||_2 */ - p_eL2=pDp_eL2; - break; - } - - } - /* if this point is reached, either the linear system could not be solved or - * the error did not reduce; in any case, the increment must be rejected - */ - - mu*=(double)nu; - nu2=nu<<1; // 2*nu; - if(nu2<=nu){ /* nu has wrapped around (overflown). */ - stop=5; - break; - } - - nu=nu2; - - } /* inner loop */ - - } - if (randomize) { - free(subI); - } -/**************** end OS loop ***************************/ - } - /**** end iteration loop ***********/ - - - if(k>=itmax) stop=3; - - /*W if not at first or last iteration, recalculate error */ - if (nw>0 && nwrobust_nu=robust_nu; - - free(jac); - free(jacTjacd); - free(jacTjacd0); - free(jacTed); - free(Dpd); - free(bd); - free(hxd); - free(ed); - free(wtd); - free(aones); - free(pnew); - - if (solve_axb==0) { - } else if (solve_axb==1) { - free(WORK); - } else { - free(Ud); - free(VTd); - free(Sd); - free(WORK); - } -#ifdef DEBUG - printf("stop=%d\n",stop); -#endif - if(info){ - info[0]=init_p_eL2; - info[1]=p_eL2; - info[2]=jacTe_inf; - info[3]=Dp_L2; - info[4]=mu; - info[5]=(double)k; - info[6]=(double)stop; - info[7]=(double)0; - info[8]=(double)0; - info[9]=(double)0; - } - return 0; -} diff --git a/src/lib/Solvers/robustlm.o b/src/lib/Solvers/robustlm.o deleted file mode 100644 index 54b66b9b7235607646b89e665f0797f4239cc37f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21048 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 p8Mki{I719gA4Z*peE89&)~je-|6aQ}4 - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#include "Solvers.h" - -//#define DEBUG -/* Jones matrix multiplication - C=A*B -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -amb(complex double * __restrict a, complex double * __restrict b, complex double * __restrict c) { - c[0]=a[0]*b[0]+a[1]*b[2]; - c[1]=a[0]*b[1]+a[1]*b[3]; - c[2]=a[2]*b[0]+a[3]*b[2]; - c[3]=a[2]*b[1]+a[3]*b[3]; -} - -/* Jones matrix multiplication - C=A*B^H -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -ambt(complex double * __restrict a, complex double * __restrict b, complex double * __restrict c) { - c[0]=a[0]*conj(b[0])+a[1]*conj(b[1]); - c[1]=a[0]*conj(b[2])+a[1]*conj(b[3]); - c[2]=a[2]*conj(b[0])+a[3]*conj(b[1]); - c[3]=a[2]*conj(b[2])+a[3]*conj(b[3]); -} - -/* Jones matrix multiplication - C=A^H*B -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -atmb(complex double * __restrict a, complex double * __restrict b, complex double * __restrict c) { - c[0]=conj(a[0])*b[0]+conj(a[2])*b[2]; - c[1]=conj(a[0])*b[1]+conj(a[2])*b[3]; - c[2]=conj(a[1])*b[0]+conj(a[3])*b[2]; - c[3]=conj(a[1])*b[1]+conj(a[3])*b[3]; -} - - -/* worker thread function for counting */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -threadfn_fns_fcount(void *data) { - thread_data_rtr_t *t=(thread_data_rtr_t*)data; - - int ci,sta1,sta2; - for (ci=0; ciNb; ci++) { - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - - pthread_mutex_lock(&t->mx_array[sta1]); - t->bcount[sta1]+=1; - pthread_mutex_unlock(&t->mx_array[sta1]); - - pthread_mutex_lock(&t->mx_array[sta2]); - t->bcount[sta2]+=1; - pthread_mutex_unlock(&t->mx_array[sta2]); - } - } - - return NULL; -} - - -/* function to count how many baselines contribute to the calculation of - grad and hess, so normalization can be made */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -fns_fcount(global_data_rtr_t *gdata) { - - me_data_t *dp=(me_data_t*)gdata->medata; - - int nth,nth1,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthb0,Nthb; - thread_data_rtr_t *threaddata; - - int Nbase1=(dp->Nbase)*(dp->tilesz); - int boff=(dp->Nbase)*(dp->tileoff); - - /* calculate min baselines a thread can handle */ - Nthb0=(Nbase1+Nt-1)/Nt; - - if ((threaddata=(thread_data_rtr_t*)malloc((size_t)Nt*sizeof(thread_data_rtr_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - int *bcount; - if ((bcount=(int*)calloc((size_t)dp->N,sizeof(int)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr; - threaddata[nth].bcount=bcount; /* note this should be 0 first */ - threaddata[nth].mx_array=gdata->mx_array; - - pthread_create(&gdata->th_array[nth],&gdata->attr,threadfn_fns_fcount,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - /* now wait for threads to finish */ - for(nth1=0; nth1th_array[nth1],NULL); - } - free(threaddata); - - /* calculate inverse count */ - for (nth1=0; nth1N; nth1++) { - gdata->iw[nth1]=(bcount[nth1]>0?1.0/(double)bcount[nth1]:0.0); - } - free(bcount); - /* scale back weight such that max value is 1 */ - nth1=my_idamax(dp->N,gdata->iw,1); - double maxw=gdata->iw[nth1-1]; /* 1 based index */ - if (maxw>0.0) { /* all baselines can be flagged */ - my_dscal(dp->N,1.0/maxw,gdata->iw); - } -} - - - -/* worker thread function for cost function calculation */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -threadfn_fns_f(void *data) { - thread_data_rtr_t *t=(thread_data_rtr_t*)data; - - int ci,cm,sta1,sta2; - complex double C[4],G1[4],G2[4],T1[4],T2[4]; - int M=(t->M); - cm=(t->clus); - for (ci=0; ciNb; ci++) { - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - /* gains for this cluster, for sta1,sta2 */ - G1[0]=(t->x[sta1*2]); - G1[1]=(t->x[sta1*2+2*t->N]); - G1[2]=(t->x[sta1*2+1]); - G1[3]=(t->x[sta1*2+2*t->N+1]); - G2[0]=(t->x[sta2*2]); - G2[1]=(t->x[sta2*2+2*t->N]); - G2[2]=(t->x[sta2*2+1]); - G2[3]=(t->x[sta2*2+2*t->N+1]); - - /* use pre calculated values */ - C[0]=t->coh[4*M*ci+4*cm]; - C[1]=t->coh[4*M*ci+4*cm+1]; - C[2]=t->coh[4*M*ci+4*cm+2]; - C[3]=t->coh[4*M*ci+4*cm+3]; - - - /* form G1*C*G2' */ - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - - /* add to baseline visibilities V->U */ - double r00=t->y[8*ci]-creal(T2[0]); - double i00=t->y[8*ci+1]-cimag(T2[0]); - double r01=t->y[8*ci+2]-creal(T2[1]); - double i01=t->y[8*ci+3]-cimag(T2[1]); - double r10=t->y[8*ci+4]-creal(T2[2]); - double i10=t->y[8*ci+5]-cimag(T2[2]); - double r11=t->y[8*ci+6]-creal(T2[3]); - double i11=t->y[8*ci+7]-cimag(T2[3]); - - t->fcost+=r00*r00+i00*i00+r01*r01+i01*i01+r10*r10+i10*i10+r11*r11+i11*i11; - } - } - - return NULL; -} - - -/* cost function */ -/* x: 2Nx2 solution - y: visibilities, vectorized V(:) 8*Nbase x 1 -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static double -fns_f(complex double *x, double *y, global_data_rtr_t *gdata) { - - me_data_t *dp=(me_data_t*)gdata->medata; - - int nth,nth1,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthb0,Nthb; - thread_data_rtr_t *threaddata; - - int Nbase1=(dp->Nbase)*(dp->tilesz); - int boff=(dp->Nbase)*(dp->tileoff); - - /* calculate min baselines a thread can handle */ - Nthb0=(Nbase1+Nt-1)/Nt; - - if ((threaddata=(thread_data_rtr_t*)malloc((size_t)Nt*sizeof(thread_data_rtr_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr; - threaddata[nth].carr=dp->carr; - threaddata[nth].M=dp->M; - threaddata[nth].y=&(y[8*ci]); - threaddata[nth].N=dp->N; - threaddata[nth].x=x; /* note the difference: here x assumes no hybrid, also ordering different */ - threaddata[nth].clus=(dp->clus); - threaddata[nth].coh=&(dp->coh[4*(dp->M)*(ci+boff)]); - threaddata[nth].fcost=0.0; - - //printf("thread %d predict data from %d baselines %d\n",nth,8*ci,Nthb); - pthread_create(&gdata->th_array[nth],&gdata->attr,threadfn_fns_f,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - /* now wait for threads to finish */ - double fcost=0.0; - for(nth1=0; nth1th_array[nth1],NULL); - fcost+=threaddata[nth1].fcost; - } - - free(threaddata); - - return fcost; -} - - -/* inner product (metric) */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static double -fns_g(int N,complex double *x, complex double *eta, complex double *gamma) { - /* 2 x real( trace(eta'*gamma) ) - = 2 x real( eta(:,1)'*gamma(:,1) + eta(:,2)'*gamma(:,2) ) - no need to calculate off diagonal terms - )*/ - complex double v1=my_cdot(2*N,eta,gamma); - complex double v2=my_cdot(2*N,&eta[2*N],&gamma[2*N]); - - return 2.0*creal(v1+v2); -} - -/* Projection - rnew: new value */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -fns_proj(int N,complex double *x, complex double *z,complex double *rnew) { - /* projection = Z-X Om, where - Om X^H X+X^H X Om = X^H Z - Z^H X - is solved to find Om */ - - /* find X^H X */ - complex double xx00,xx01,xx10,xx11; - xx00=my_cdot(2*N,x,x); - xx01=my_cdot(2*N,x,&x[2*N]); - xx10=conj(xx01); - xx11=my_cdot(2*N,&x[2*N],&x[2*N]); - - /* find X^H Z (and using this just calculte Z^H X directly) */ - complex double xz00,xz01,xz10,xz11; - xz00=my_cdot(2*N,x,z); - xz01=my_cdot(2*N,x,&z[2*N]); - xz10=my_cdot(2*N,&x[2*N],z); - xz11=my_cdot(2*N,&x[2*N],&z[2*N]); - - /* find X^H Z - Z^H X */ - complex double rr00,rr01,rr10,rr11; - rr00=xz00-conj(xz00); - rr01=xz01-conj(xz10); - rr10=-conj(rr01); - rr11=xz11-conj(xz11); - - /* find I_2 kron (X^H X) + (X^H X)^T kron I_2 */ - /* A = [2*xx00 xx01 xx10 0 - xx10 xx11+xx00 0 xx10 - xx01 0 xx11+xx00 xx01 - 0 xx01 xx10 2*xx11 ] - */ - complex double A[16]; - A[0]=2.0*xx00; - A[5]=A[10]=xx11+xx00; - A[15]=2.0*xx11; - A[1]=A[8]=A[11]=A[13]=xx10; - A[2]=A[4]=A[7]=A[14]=xx01; - A[3]=A[6]=A[9]=A[12]=0.0; - complex double b[4]; - b[0]=rr00; - b[1]=rr10; - b[2]=rr01; - b[3]=rr11; - - /* solve A u = b to find u */ - complex double w,*WORK; - /* workspace query */ - int status=my_zgels('N',4,4,1,A,4,b,4,&w,-1); - int lwork=(int)w; - if ((WORK=(complex double*)calloc((size_t)lwork,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - status=my_zgels('N',4,4,1,A,4,b,4,WORK,lwork); - if (status) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: LAPACK error %d\n",__FILE__,__LINE__,status); -#endif - } - - free(WORK); - /* form Z - X * Om, where Om is given by solution b - but no need to rearrange b because it is already in col major order */ - my_ccopy(4*N,z,1,rnew,1); - my_zgemm('N','N',2*N,2,2,-1.0+0.0*_Complex_I,x,2*N,b,2,1.0+0.0*_Complex_I,rnew,2*N); - - -} - - -/* Retraction - rnew: new value */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -fns_R(int N,complex double *x, complex double *r,complex double *rnew) { - /* rnew = x + r */ - my_ccopy(4*N,x,1,rnew,1); - my_caxpy(4*N,r,1.0+_Complex_I*0.0,rnew); -} - - -/* worker thread function for gradient/hessian weighting */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -threadfn_fns_fscale(void *data) { - thread_data_rtr_t *t=(thread_data_rtr_t*)data; - - int ci,sta1; - for (ci=0; ciNb; ci++) { - sta1=ci+t->boff; - t->grad[2*sta1]*=t->iw[sta1]; - t->grad[2*sta1+2*t->N]*=t->iw[sta1]; - t->grad[2*sta1+1]*=t->iw[sta1]; - t->grad[2*sta1+2*t->N+1]*=t->iw[sta1]; - } - - return NULL; -} - - - - -/* worker thread function for gradient calculation */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -threadfn_fns_fgrad(void *data) { - thread_data_rtr_t *t=(thread_data_rtr_t*)data; - - int ci,cm,sta1,sta2; - complex double C[4],G1[4],G2[4],T1[4],T2[4],res[4]; - int M=(t->M); - cm=(t->clus); - for (ci=0; ciNb; ci++) { - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - /* gains for this cluster, for sta1,sta2 */ - G1[0]=(t->x[sta1*2]); - G1[1]=(t->x[sta1*2+2*t->N]); - G1[2]=(t->x[sta1*2+1]); - G1[3]=(t->x[sta1*2+2*t->N+1]); - G2[0]=(t->x[sta2*2]); - G2[1]=(t->x[sta2*2+2*t->N]); - G2[2]=(t->x[sta2*2+1]); - G2[3]=(t->x[sta2*2+2*t->N+1]); - - /* use pre calculated values */ - C[0]=t->coh[4*M*ci+4*cm]; - C[1]=t->coh[4*M*ci+4*cm+1]; - C[2]=t->coh[4*M*ci+4*cm+2]; - C[3]=t->coh[4*M*ci+4*cm+3]; - - - /* G1*C*G2' */ - amb(G1,C,T1); - ambt(T1,G2,T2); - - /* res=V(2*ck-1:2*ck,:)-x(2*p-1:2*p,:)*C*x(2*q-1:2*q,:)'; */ - /* V->U */ - res[0]=(t->y[8*ci]+_Complex_I*t->y[8*ci+1])-T2[0]; - res[1]=(t->y[8*ci+2]+_Complex_I*t->y[8*ci+3])-T2[1]; - res[2]=(t->y[8*ci+4]+_Complex_I*t->y[8*ci+5])-T2[2]; - res[3]=(t->y[8*ci+6]+_Complex_I*t->y[8*ci+7])-T2[3]; - - /* - grad(2*p-1:2*p,:)=grad(2*p-1:2*p,:)+res*x(2*q-1:2*q,:)*C'; - grad(2*q-1:2*q,:)=grad(2*q-1:2*q,:)+res'*x(2*p-1:2*p,:)*C; - */ - /* res*G2*C' */ - amb(res,G2,T1); - ambt(T1,C,T2); - - pthread_mutex_lock(&t->mx_array[sta1]); - t->grad[2*sta1]+=T2[0]; - t->grad[2*sta1+2*t->N]+=T2[1]; - t->grad[2*sta1+1]+=T2[2]; - t->grad[2*sta1+2*t->N+1]+=T2[3]; - pthread_mutex_unlock(&t->mx_array[sta1]); - - /* res'*G1*C */ - atmb(res,G1,T1); - amb(T1,C,T2); - - pthread_mutex_lock(&t->mx_array[sta2]); - t->grad[2*sta2]+=T2[0]; - t->grad[2*sta2+2*t->N]+=T2[1]; - t->grad[2*sta2+1]+=T2[2]; - t->grad[2*sta2+2*t->N+1]+=T2[3]; - pthread_mutex_unlock(&t->mx_array[sta2]); - - } - } - - return NULL; -} - - - -/* gradient function */ -/* x: 2Nx2 solution - fgradx: output, same shape as x - y: visibilities, vectorized V(:) 8*Nbase x 1 - if negate==1, return -grad, else just grad -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -fns_fgrad(complex double *x, complex double *fgradx, double *y, global_data_rtr_t *gdata, int negate) { - - me_data_t *dp=(me_data_t*)gdata->medata; - - int nth,nth1,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthb0,Nthb; - thread_data_rtr_t *threaddata; - - int Nbase1=(dp->Nbase)*(dp->tilesz); - int boff=(dp->Nbase)*(dp->tileoff); - - /* calculate min baselines a thread can handle */ - Nthb0=(Nbase1+Nt-1)/Nt; - - if ((threaddata=(thread_data_rtr_t*)malloc((size_t)Nt*sizeof(thread_data_rtr_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - complex double *grad; - if ((grad=(complex double*)calloc((size_t)4*dp->N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr; - threaddata[nth].carr=dp->carr; - threaddata[nth].M=dp->M; - threaddata[nth].y=&(y[8*ci]); - threaddata[nth].N=dp->N; - threaddata[nth].x=x; /* note the difference: here x assumes no hybrid, also ordering different */ - threaddata[nth].clus=(dp->clus); - threaddata[nth].coh=&(dp->coh[4*(dp->M)*(ci+boff)]); - threaddata[nth].grad=grad; - threaddata[nth].mx_array=gdata->mx_array; - - //printf("thread %d predict data from %d baselines %d\n",nth,8*ci,Nthb); - pthread_create(&gdata->th_array[nth],&gdata->attr,threadfn_fns_fgrad,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - /* now wait for threads to finish */ - for(nth1=0; nth1th_array[nth1],NULL); - } -/******************* scale *************/ - Nthb0=(dp->N+Nt-1)/Nt; - ci=0; - for (nth=0; nthN; nth++) { - if (ci+Nthb0N) { - Nthb=Nthb0; - } else { - Nthb=dp->N-ci; - } - threaddata[nth].boff=ci; - threaddata[nth].Nb=Nthb; - threaddata[nth].N=dp->N; - threaddata[nth].grad=grad; - threaddata[nth].iw=gdata->iw; - pthread_create(&gdata->th_array[nth],&gdata->attr,threadfn_fns_fscale,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - for(nth1=0; nth1th_array[nth1],NULL); - } -/******************* scale *************/ - free(threaddata); - if (negate) { - my_cscal(4*dp->N,-1.0+0.0*_Complex_I,grad); - } - fns_proj(dp->N,x,grad,fgradx); - free(grad); - -} - - -/* worker thread function for Hessian calculation */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -threadfn_fns_fhess(void *data) { - thread_data_rtr_t *t=(thread_data_rtr_t*)data; - - int ci,cm,sta1,sta2; - complex double C[4],G1[4],G2[4],T1[4],T2[4],res[4],res1[4],E1[4],E2[4]; - int M=(t->M); - cm=(t->clus); - for (ci=0; ciNb; ci++) { - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - /* gains for this cluster, for sta1,sta2 */ - G1[0]=(t->x[sta1*2]); - G1[1]=(t->x[sta1*2+2*t->N]); - G1[2]=(t->x[sta1*2+1]); - G1[3]=(t->x[sta1*2+2*t->N+1]); - G2[0]=(t->x[sta2*2]); - G2[1]=(t->x[sta2*2+2*t->N]); - G2[2]=(t->x[sta2*2+1]); - G2[3]=(t->x[sta2*2+2*t->N+1]); - E1[0]=t->eta[2*sta1]; - E1[1]=t->eta[2*sta1+2*t->N]; - E1[2]=t->eta[2*sta1+1]; - E1[3]=t->eta[2*sta1+2*t->N+1]; - E2[0]=t->eta[2*sta2]; - E2[1]=t->eta[2*sta2+2*t->N]; - E2[2]=t->eta[2*sta2+1]; - E2[3]=t->eta[2*sta2+2*t->N+1]; - - - - /* use pre calculated values */ - C[0]=t->coh[4*M*ci+4*cm]; - C[1]=t->coh[4*M*ci+4*cm+1]; - C[2]=t->coh[4*M*ci+4*cm+2]; - C[3]=t->coh[4*M*ci+4*cm+3]; - - /* G1*C*G2' */ - amb(G1,C,T1); - ambt(T1,G2,T2); - - - /* res=V(2*ck-1:2*ck,:)-x(2*p-1:2*p,:)*C*x(2*q-1:2*q,:)'; */ - /* V->U */ - res[0]=(t->y[8*ci]+_Complex_I*t->y[8*ci+1])-T2[0]; - res[1]=(t->y[8*ci+2]+_Complex_I*t->y[8*ci+3])-T2[1]; - res[2]=(t->y[8*ci+4]+_Complex_I*t->y[8*ci+5])-T2[2]; - res[3]=(t->y[8*ci+6]+_Complex_I*t->y[8*ci+7])-T2[3]; - - - /* - res1=x(2*p-1:2*p,:)*C*eta(2*q-1:2*q,:)'+eta(2*p-1:2*p,:)*C*x(2*q-1:2*q,:)'; - */ - /* G1*C*E2' */ - amb(G1,C,T1); - ambt(T1,E2,T2); - res1[0]=T2[0]; - res1[1]=T2[1]; - res1[2]=T2[2]; - res1[3]=T2[3]; - /* E1*C*G2' */ - amb(E1,C,T1); - ambt(T1,G2,T2); - res1[0]+=T2[0]; - res1[1]+=T2[1]; - res1[2]+=T2[2]; - res1[3]+=T2[3]; - - - /* - hess(2*p-1:2*p,:)=hess(2*p-1:2*p,:)+(res*eta(2*q-1:2*q,:)-res1*x(2*q-1:2*q,:))*C'; - */ - - /* (res*E2-res1*G2)*C' */ - amb(res,E2,T1); - amb(res1,G2,T2); - T1[0]-=T2[0]; - T1[1]-=T2[1]; - T1[2]-=T2[2]; - T1[3]-=T2[3]; - ambt(T1,C,T2); - - pthread_mutex_lock(&t->mx_array[sta1]); - t->hess[2*sta1]+=T2[0]; - t->hess[2*sta1+2*t->N]+=T2[1]; - t->hess[2*sta1+1]+=T2[2]; - t->hess[2*sta1+2*t->N+1]+=T2[3]; - pthread_mutex_unlock(&t->mx_array[sta1]); - - - /* - hess(2*q-1:2*q,:)=hess(2*q-1:2*q,:)+(res'*eta(2*p-1:2*p,:)-res1'*x(2*p-1:2*p,:))*C; - */ - - /* (res'*E1-res1'*G1)*C */ - atmb(res,E1,T1); - atmb(res1,G1,T2); - T1[0]-=T2[0]; - T1[1]-=T2[1]; - T1[2]-=T2[2]; - T1[3]-=T2[3]; - amb(T1,C,T2); - - pthread_mutex_lock(&t->mx_array[sta2]); - t->hess[2*sta2]+=T2[0]; - t->hess[2*sta2+2*t->N]+=T2[1]; - t->hess[2*sta2+1]+=T2[2]; - t->hess[2*sta2+2*t->N+1]+=T2[3]; - pthread_mutex_unlock(&t->mx_array[sta2]); - - } - } - - return NULL; -} - - - -/* Hessian function */ -/* x: 2Nx2 solution - eta: same shape as x - fhess: output, same shape as x - y: visibilities, vectorized V(:) 8*Nbase x 1 -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -fns_fhess(complex double *x, complex double *eta,complex double *fhess, double *y, global_data_rtr_t *gdata) { - - me_data_t *dp=(me_data_t*)gdata->medata; - - int nth,nth1,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthb0,Nthb; - thread_data_rtr_t *threaddata; - - int Nbase1=(dp->Nbase)*(dp->tilesz); - int boff=(dp->Nbase)*(dp->tileoff); - - /* calculate min baselines a thread can handle */ - Nthb0=(Nbase1+Nt-1)/Nt; - - if ((threaddata=(thread_data_rtr_t*)malloc((size_t)Nt*sizeof(thread_data_rtr_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - complex double *hess; - if ((hess=(complex double*)calloc((size_t)4*dp->N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr; - threaddata[nth].carr=dp->carr; - threaddata[nth].M=dp->M; - threaddata[nth].y=&(y[8*ci]); - threaddata[nth].N=dp->N; - threaddata[nth].x=x; /* note the difference: here x assumes no hybrid, also ordering different */ - threaddata[nth].clus=(dp->clus); - threaddata[nth].coh=&(dp->coh[4*(dp->M)*(ci+boff)]); - threaddata[nth].eta=eta; - threaddata[nth].hess=hess; - threaddata[nth].mx_array=gdata->mx_array; - - //printf("thread %d predict data from %d baselines %d\n",nth,8*ci,Nthb); - pthread_create(&gdata->th_array[nth],&gdata->attr,threadfn_fns_fhess,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - /* now wait for threads to finish */ - for(nth1=0; nth1th_array[nth1],NULL); - } - -/******************* scale *************/ - Nthb0=(dp->N+Nt-1)/Nt; - ci=0; - for (nth=0; nthN; nth++) { - if (ci+Nthb0N) { - Nthb=Nthb0; - } else { - Nthb=dp->N-ci; - } - threaddata[nth].boff=ci; - threaddata[nth].Nb=Nthb; - threaddata[nth].N=dp->N; - threaddata[nth].grad=fhess; - threaddata[nth].iw=gdata->iw; - pthread_create(&gdata->th_array[nth],&gdata->attr,threadfn_fns_fscale,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - for(nth1=0; nth1th_array[nth1],NULL); - } -/******************* scale *************/ - free(threaddata); - fns_proj(dp->N,x,hess,fhess); - free(hess); - -} - - -/* truncated conjugate gradient method - x, grad, eta, r, z, delta, Hxd : size 2N x 2 complex - so, vector size is 4N complex double - - output: eta - output: fhess (can be reused in calling func) - return value: stop_tCG code - - y: vec(V) visibilities -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static int -tcg_solve(int N, complex double *x, complex double *grad, complex double *eta, complex double *fhess, - double Delta, double theta, double kappa, int max_inner, int min_inner, double *y, global_data_rtr_t *gdata) { - - complex double *r,*z,*delta,*Hxd, *rnew; - double e_Pe, r_r, norm_r, z_r, d_Pd, d_Hd, alpha, e_Pe_new, - e_Pd, Deltasq, tau, zold_rold, beta, norm_r0; - int cj, stop_tCG; - - if ((r=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((z=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((delta=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((Hxd=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((rnew=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - /* - initial values - % eta = 0*grad; << zero matrix provided - r = grad; - e_Pe = 0; - */ - my_ccopy(4*N,grad,1,r,1); - e_Pe=0.0; - - /* - r_r = fns.g(x,r,r); - norm_r = sqrt(r_r); - norm_r0 = norm_r; - */ - - r_r=fns_g(N,x,r,r); - norm_r=sqrt(r_r); - norm_r0=norm_r; - - /* - z = r; - */ - my_ccopy(4*N,r,1,z,1); - - /* - % compute z'*r - z_r = fns.g(x,z,r); - d_Pd = z_r; - */ - z_r=fns_g(N,x,z,r); - d_Pd=z_r; - - /* - % initial search direction - delta = -z; - e_Pd = fns.g(x,eta,delta); - */ - memset(delta,0,sizeof(complex double)*N*4); - my_caxpy(4*N,z,-1.0+_Complex_I*0.0,delta); - e_Pd=fns_g(N,x,eta,delta); - - /* - % pre-assume termination b/c j == end - stop_tCG = 5; - */ - stop_tCG=5; - - /* % begin inner/tCG loop - for j = 1:max_inner, - */ - for(cj=1; cj<=max_inner; cj++) { - /**************************************************/ - /* - Hxd = fns.fhess(x,delta); - - % compute curvature - d_Hd = fns.g(x,delta,Hxd); - */ - fns_fhess(x,delta,Hxd,y,gdata); - d_Hd=fns_g(N,x,delta,Hxd); - - /* - alpha = z_r/d_Hd; - e_Pe_new = e_Pe + 2.0*alpha*e_Pd + alpha*alpha*d_Pd; - */ - alpha=z_r/d_Hd; - e_Pe_new = e_Pe + 2.0*alpha*e_Pd + alpha*alpha*d_Pd; - - /* - - % check curvature and trust-region radius - if d_Hd <= 0 || e_Pe_new >= Delta^2, - - */ - Deltasq=Delta*Delta; - if (d_Hd <= 0.0 || e_Pe_new >= Deltasq) { - /* - tau = (-e_Pd + sqrt(e_Pd*e_Pd + d_Pd*(Delta^2-e_Pe))) / d_Pd; - - */ - tau = (-e_Pd + sqrt(e_Pd*e_Pd + d_Pd*(Deltasq-e_Pe)))/d_Pd; - - /* - eta = eta + tau*delta; - - */ - my_caxpy(4*N,delta,tau+_Complex_I*0.0,eta); - - /* NEW Heta = Heta + tau*Hdelta */ - my_caxpy(4*N,fhess,tau+_Complex_I*0.0,Hxd); - - /* - if d_Hd <= 0, - stop_tCG = 1; % negative curvature - else - stop_tCG = 2; % exceeded trust region - end - */ - stop_tCG=(d_Hd<=0.0?1:2); - - /* - break (for) - */ - break; - /* - end if - */ - } - - - /* - % no negative curvature and eta_prop inside TR: accept it - e_Pe = e_Pe_new; - eta = eta + alpha*delta; - - */ - e_Pe=e_Pe_new; - my_caxpy(4*N,delta,alpha+_Complex_I*0.0,eta); - - /* NEW Heta = Heta + alpha*Hdelta */ - my_caxpy(4*N,fhess,alpha+_Complex_I*0.0,Hxd); - - - /* - % update the residual - r = r + alpha*Hxd; - - */ - my_caxpy(4*N,Hxd,alpha+_Complex_I*0.0,r); - - /* - % compute new norm of r - r_r = fns.g(x,r,r); - norm_r = sqrt(r_r); - - */ - r_r=fns_g(N,x,r,r); - norm_r=sqrt(r_r); - - - /* - % check kappa/theta stopping criterion - if j >= min_inner && norm_r <= norm_r0*min(norm_r0^theta,kappa) - */ - if (cj >= min_inner) { - double norm_r0pow=pow(norm_r0,theta); - if (norm_r <= norm_r0*MIN(norm_r0pow,kappa)) { - - /* - % residual is small enough to quit - if kappa < norm_r0^theta, - stop_tCG = 3; % linear convergence - else - stop_tCG = 4; % superlinear convergence - end - - */ - stop_tCG=(kappamedata; - - double fx=fns_f(x,y,gdata); - fns_fgrad(x,eta,y,gdata,0); - double beta0=beta; - double minfx=fx; double minbeta=beta0; - double lhs,rhs,metric; - int m,nocostred=0; - *mincost=fx; - double metric0=fns_g(dp->N,x,eta,eta); - for (m=0; m<50; m++) { - /* abeta=(beta0)*alphabar*eta; */ - my_ccopy(4*dp->N,eta,1,teta,1); - my_cscal(4*dp->N,beta0*alphabar+0.0*_Complex_I,teta); - /* Rx=R(x,teta); */ - fns_R(dp->N,x,teta,x_prop); - lhs=fns_f(x_prop,y,gdata); - if (lhsN,x,eta,teta); - rhs=fx+sigma*metric; - /* break loop also if no further cost improvement is seen */ - if (lhs<=rhs) { - minbeta=beta0; - break; - } - beta0=beta0*beta; - } - - /* if no further cost improvement is seen */ - if (lhs>fx) { - nocostred=1; - } - - my_ccopy(4*dp->N,eta,1,teta,1); - my_cscal(4*dp->N,minbeta*alphabar+0.0*_Complex_I,teta); - - return nocostred; -} - - -int -rtr_solve_nocuda( - double *x0, /* initial values and updated solution at output (size 8*N double) */ - double *y, /* data vector (size 8*M double) */ - int N, /* no. of stations */ - int M, /* no. of constraints */ - int itmax_rsd, /* maximum number of iterations RSD */ - int itmax_rtr, /* maximum number of iterations RTR */ - double Delta_bar, double Delta0, /* Trust region radius and initial value */ - double *info, /* initial and final residuals */ - me_data_t *adata) { /* pointer to additional data */ - - /* reshape x to make J: 2Nx2 complex double - */ - complex double *x; - if ((x=(complex double*)malloc((size_t)4*N*sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - /* map x: [(re,im)J_1(0,0) (re,im)J_1(0,1) (re,im)J_1(1,0) (re,im)J_1(1,1)...] - to - J: [J_1(0,0) J_1(1,0) J_2(0,0) J_2(1,0) ..... J_1(0,1) J_1(1,1) J_2(0,1) J_2(1,1)....] - */ - double *Jd=(double*)x; - /* re J(0,0) */ - my_dcopy(N, &x0[0], 8, &Jd[0], 4); - /* im J(0,0) */ - my_dcopy(N, &x0[1], 8, &Jd[1], 4); - /* re J(1,0) */ - my_dcopy(N, &x0[4], 8, &Jd[2], 4); - /* im J(1,0) */ - my_dcopy(N, &x0[5], 8, &Jd[3], 4); - /* re J(0,1) */ - my_dcopy(N, &x0[2], 8, &Jd[4*N], 4); - /* im J(0,1) */ - my_dcopy(N, &x0[3], 8, &Jd[4*N+1], 4); - /* re J(1,1) */ - my_dcopy(N, &x0[6], 8, &Jd[4*N+2], 4); - /* im J(1,1) */ - my_dcopy(N, &x0[7], 8, &Jd[4*N+3], 4); - - - - int Nt=adata->Nt; - int ci; - global_data_rtr_t gdata; - - gdata.medata=adata; - /* setup threads */ - pthread_attr_init(&gdata.attr); - pthread_attr_setdetachstate(&gdata.attr,PTHREAD_CREATE_JOINABLE); - - if ((gdata.th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - if ((gdata.mx_array=(pthread_mutex_t*)malloc((size_t)N*sizeof(pthread_mutex_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((gdata.iw=(double*)malloc((size_t)N*sizeof(double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - for (ci=0; cistation contributions - NOTE: has to be done here because the baseline offset would change */ - fns_fcount(&gdata); -/***************************************************/ - int min_inner,max_inner,min_outer,max_outer; - double epsilon,kappa,theta,rho_prime; - /* - min_inner = 0; - max_inner = inf; - min_outer = 3; - max_outer = 100; - epsilon = 1e-6; - kappa = 0.1; - theta = 1.0; - rho_prime = 0.1; - %Delta_bar = user must specify - %Delta0 = user must specify - %x0 = user must specify - */ - min_inner=1; max_inner=itmax_rtr;//8*N; - min_outer=3; max_outer=itmax_rtr; - epsilon=CLM_EPSILON; - kappa=0.1; - theta=1.0; - /* default values 0.25, 0.75, 0.25, 2.0 */ - double eta1=0.0001; double eta2=0.99; double alpha1=0.25; double alpha2=3.5; - rho_prime=eta1; /* default 0.1 should be <= 0.25 */ - double rho_regularization; /* default 1e2 use large damping (but less than GPU version) */ - double rho_reg; - int model_decreased=0; - - complex double *fgradx,*eta,*Heta,*x_prop; - if ((fgradx=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((eta=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((Heta=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((x_prop=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - - double fx,norm_grad,Delta,fx_prop,rhonum,rhoden,rho; - fx=fns_f(x,y,&gdata); - double fx0=fx; - int rsdstat=0; -/***************************************************/ - /* RSD solution */ - for (ci=0; ci0?0:1); - int stop_inner=0; - // x0 is already copied to x: my_ccopy(4*N,x0,1,x,1); - if(!stop_outer) { - fns_fgrad(x,fgradx,y,&gdata,1); - norm_grad = sqrt(fns_g(N,x,fgradx,fgradx)); - } - Delta=Delta0; - - /* initial residual */ - info[0]=fx; - - /* - % ** Start of TR loop ** - while stop_outer==0, - */ - while(!stop_outer) { - /* - % update counter - k = k+1; - */ - k++; - - - /* - ** Begin TR Subproblem ** - % determine eta0 - % without randT, 0*fgradx is the only way that we - % know how to create a tangent vector - eta = 0*fgradx; - */ - memset(eta,0,sizeof(complex double)*N*4); - - - /* - % solve TR subproblem - [eta,numit,stop_inner] = tCG(fns,x,fgradx,eta,Delta,theta,kappa,min_inner,max_inner,useRand,debug); - */ - stop_inner=tcg_solve(N, x, fgradx, eta, Heta, Delta, theta, kappa, max_inner, min_inner,y,&gdata); - - /* - norm_eta = sqrt(fns.g(x,eta,eta)); - */ - - /* - Heta = fns.fhess(x,eta); - */ - //OLD fns_fhess(x,eta,Heta,y,&gdata); - - /* - % compute the retraction of the proposal - x_prop = fns.R(x,eta); - */ - fns_R(N,x,eta,x_prop); - - /* - % compute function value of the proposal - fx_prop = fns.f(x_prop); - */ - fx_prop=fns_f(x_prop,y,&gdata); - - /* - % do we accept the proposed solution or not? - % compute the Hessian at the proposal - Heta = fns.fhess(x,eta); - FIXME: do we need to do this, because Heta is already there - or change x to x_prop ??? - */ - //Disabled fns_fhess(x,eta,Heta,y,&gdata); - - /* - % check the performance of the quadratic model - rhonum = fx-fx_prop; - rhoden = -fns.g(x,fgradx,eta) - 0.5*fns.g(x,Heta,eta); - */ - rhonum=fx-fx_prop; - rhoden=-fns_g(N,x,fgradx,eta)-0.5*fns_g(N,x,Heta,eta); - /* regularization of rho ratio */ - /* - rho_reg = max(1, abs(fx)) * eps * options.rho_regularization; - rhonum = rhonum + rho_reg; - rhoden = rhoden + rho_reg; - */ - rho_reg=MAX(1.0,fx)*rho_regularization; /* no epsilon */ - rhonum+=rho_reg; - rhoden+=rho_reg; - - - /* - rho = rhonum / rhoden; - */ - rho=rhonum/rhoden; - - - - /* - % HEURISTIC WARNING: - % if abs(model change) is relatively zero, we are probably near a critical - % point. set rho to 1. - if abs(rhonum/fx) < sqrt(eps), - small_rhonum = rhonum; - rho = 1; - else - small_rhonum = 0; - end - FIXME: use constant for sqrt(eps) - */ - /* OLD CODE if (fabs(rhonum/fx) = 0); */ - model_decreased=(rhoden>=0.0?1:0); - - /* NOTE: if too many values of rho are -ve, it means TR radius is too big - so initial TR radius should be reduced */ -#ifdef DEBUG - printf("stop_inner=%d rho_reg=%g rho =%g/%g= %g rho'= %g\n",stop_inner,rho_reg,rhonum,rhoden,rho,rho_prime); -#endif - - /* - % choose new TR radius based on performance - if rho < 1/4 - Delta = 1/4*Delta; - elseif rho > 3/4 && (stop_inner == 2 || stop_inner == 1), - Delta = min(2*Delta,Delta_bar); - end - */ - if (!model_decreased || rhoeta2 && (stop_inner==2 || stop_inner==1)) { - /* we have a good reduction, so increase TR radius */ - Delta=MIN(alpha2*Delta,Delta_bar); - } - - /* - % choose new iterate based on performance - oldgradx = fgradx; - if rho > rho_prime, - accept = true; - x = x_prop; - fx = fx_prop; - fgradx = fns.fgrad(x); - norm_grad = sqrt(fns.g(x,fgradx,fgradx)); - else - accept = false; - end - */ - if (model_decreased && rho>rho_prime) { - my_ccopy(4*N,x_prop,1,x,1); - fx=fx_prop; - fns_fgrad(x,fgradx,y,&gdata,1); - norm_grad=sqrt(fns_g(N,x,fgradx,fgradx)); - } - - - /* - % ** Testing for Stop Criteria - % min_outer is the minimum number of inner iterations - % before we can exit. this gives randomization a chance to - % escape a saddle point. - if norm_grad < epsilon && (~useRand || k > min_outer), - stop_outer = 1; - end - */ - if (norm_gradmin_outer) { - stop_outer=1; - } - - - /* - % stop after max_outer iterations - if k >= max_outer, - if (verbosity > 0), - fprintf('\n*** timed out -- k == %d***\n',k); - end - stop_outer = 1; - end - */ - if (k>=max_outer) { - stop_outer=1; - } - -#ifdef DEBUG - printf("Iter %d cost=%lf\n",k,fx); -#endif - /* end of TR loop (counter: k) */ - } - - /* final residual */ - info[1]=fx; - - free(fgradx); - free(eta); - free(Heta); - free(x_prop); -/***************************************************/ - for (ci=0; cifx) { - /* copy back solution to x0 */ - /* re J(0,0) */ - my_dcopy(N, &Jd[0], 4, &x0[0], 8); - /* im J(0,0) */ - my_dcopy(N, &Jd[1], 4, &x0[1], 8); - /* re J(1,0) */ - my_dcopy(N, &Jd[2], 4, &x0[4], 8); - /* im J(1,0) */ - my_dcopy(N, &Jd[3], 4, &x0[5], 8); - /* re J(0,1) */ - my_dcopy(N, &Jd[4*N], 4, &x0[2], 8); - /* im J(0,1) */ - my_dcopy(N, &Jd[4*N+1], 4, &x0[3], 8); - /* re J(1,1) */ - my_dcopy(N, &Jd[4*N+2], 4, &x0[6], 8); - /* im J(1,1) */ - my_dcopy(N, &Jd[4*N+3], 4, &x0[7], 8); - } - - free(x); - return 0; -} diff --git a/src/lib/Solvers/rtr_solve.o b/src/lib/Solvers/rtr_solve.o deleted file mode 100644 index 1049f820d83916578405f444c4dde8eb626849d8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40520 zcmb__4}6r>mG%sYI9i&SSgdKab!x{tTBM0mn<#2VCh`UcjS4#0D4_`y6(mAvu|#gOv$+f9`(_kid!`w`6rZ*`+=8SZBcVm9q8R2lxb@>TD^;)0s z)->kF{?zL|Q112aFVBQu$b|Ps&+Qhj$D*;(Uf0fu*VP+Gx^chP)q}*0KK!K)9g9{r z?#BmHv1zT>BN~Sck@u)2c(n{V{rdUWz+RS=pdd#dJ@aldMUAAO!aImIl zd-U9ND3rQ2bxUe`YFeZJ^XWHxU2k|^?jp}$nyB*pjzm1y(3H;2Px#yYbYeoTA)bzY z|4{V%4K28>z9ygk^x(Hnyn}SaYu)bo+q~XaPxbN>w;f_ly=6TfvTCE<%VF<^Wk;eb z??YnQOVO40;=3`QPBb;t9B-%@%=m4In3ro#)Yd$~BFT(@w84L)!GEvO-*tHG9MLmf z*O8bIUHQMkIqmo7ATymW4>#29Xz(9@Fp~E7H~7E4u+J~^at+lPe=ygPs7`h53^#V| zig^B{2;kcM*cuOby!SvE8p*%892v1(L%o+D^9(ak@$z%)Q~7HLGx>3kM^IGj*$Aron&W6Jn)RW_g=ubLmDjaBBK*+2 z$T=3xN;78a4ljPn%PmdRY6*wL>)PRc{`ORJ>h{zfshQg;^6+zB?n>(X=xJyIsnw5p zThk-uegZHgsX60qf7RYGz2?Zxxv1yAv+RintNQ;HWH!I+@o?SK(fk@fxk=^WL$FVN zY`vF*$@Ar_I+sL3i}z((lbP0*a*$r1pYU&>3{4IHzOnb$Wf}i1uk61%0f^L0*`gLe z*XPImGl@*r)bJaPy|0y}x<-eU%0D=?(A_9xp;T8xNXc>^fL<%+!tkH3=`m*5K4+Em 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 z - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#include -#include -#include -#include -#include - -#include "Solvers.h" -#include - -//#define DEBUG -/* helper functions for diagnostics */ -static void -checkCudaError(cudaError_t err, char *file, int line) -{ -#ifdef CUDA_DEBUG - if(!err) - return; - fprintf(stderr,"GPU (CUDA): %s %s %d\n", cudaGetErrorString(err),file,line); - exit(EXIT_FAILURE); -#endif -} - - -static void -checkCublasError(cublasStatus_t cbstatus, char *file, int line) -{ -#ifdef CUDA_DEBUG - if (cbstatus!=CUBLAS_STATUS_SUCCESS) { - fprintf(stderr,"%s: %d: CUBLAS failure\n",file,line); - exit(EXIT_FAILURE); - } -#endif -} - - -/* Retraction - rnew: new value */ -/* rnew = x + r */ -void -cudakernel_fns_R(int N, cuFloatComplex *x, cuFloatComplex *r, cuFloatComplex *rnew, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle) { - cublasStatus_t cbstatus; - cbstatus=cublasCcopy(cbhandle,4*N,x,1,rnew,1); - cuFloatComplex alpha; - alpha.x=1.0f; alpha.y=0.0f; - cbstatus=cublasCaxpy(cbhandle,4*N, &alpha, r, 1, rnew, 1); - checkCublasError(cbstatus,__FILE__,__LINE__); -} - - -/* inner product (metric) */ -float -cudakernel_fns_g(int N,cuFloatComplex *x,cuFloatComplex *eta, cuFloatComplex *gamma,cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle) { - /* 2 x real( trace(eta'*gamma) ) - = 2 x real( eta(:,1)'*gamma(:,1) + eta(:,2)'*gamma(:,2) ) - no need to calculate off diagonal terms - )*/ - cublasStatus_t cbstatus; - cuFloatComplex r1,r2; - //complex double v1=my_cdot(2*N,eta,gamma); - cbstatus=cublasCdotc(cbhandle,2*N,eta,1,gamma,1,&r1); - //complex double v2=my_cdot(2*N,&eta[2*N],&gamma[2*N]); - cbstatus=cublasCdotc(cbhandle,2*N,&eta[2*N],1,&gamma[2*N],1,&r2); - - checkCublasError(cbstatus,__FILE__,__LINE__); - return 2.0f*(r1.x+r2.x); -} - - -/* Projection - rnew: new value */ -void -cudakernel_fns_proj(int N, cuFloatComplex *x, cuFloatComplex *z, cuFloatComplex *rnew, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle) { - /* projection = Z-X Om, where - Om X^H X+X^H X Om = X^H Z - Z^H X - is solved to find Om */ - - cublasStatus_t cbstatus; - /* find X^H X */ - cuFloatComplex xx00,xx01,xx10,xx11,*bd; - //xx00=my_cdot(2*N,x,x); - cbstatus=cublasCdotc(cbhandle,2*N,x,1,x,1,&xx00); - //xx01=my_cdot(2*N,x,&x[2*N]); - cbstatus=cublasCdotc(cbhandle,2*N,x,1,&x[2*N],1,&xx01); - xx10=cuConjf(xx01); - //xx11=my_cdot(2*N,&x[2*N],&x[2*N]); - cbstatus=cublasCdotc(cbhandle,2*N,&x[2*N],1,&x[2*N],1,&xx11); - - /* find X^H Z (and using this just calculte Z^H X directly) */ - cuFloatComplex xz00,xz01,xz10,xz11; - //xz00=my_cdot(2*N,x,z); - cbstatus=cublasCdotc(cbhandle,2*N,x,1,z,1,&xz00); - //xz01=my_cdot(2*N,x,&z[2*N]); - cbstatus=cublasCdotc(cbhandle,2*N,x,1,&z[2*N],1,&xz01); - //xz10=my_cdot(2*N,&x[2*N],z); - cbstatus=cublasCdotc(cbhandle,2*N,&x[2*N],1,z,1,&xz10); - //xz11=my_cdot(2*N,&x[2*N],&z[2*N]); - cbstatus=cublasCdotc(cbhandle,2*N,&x[2*N],1,&z[2*N],1,&xz11); - - /* find X^H Z - Z^H X */ - cuFloatComplex rr00,rr01,rr10,rr11; - //rr00=xz00-conj(xz00); - rr00=cuCsubf(xz00,cuConjf(xz00)); - //rr01=xz01-conj(xz10); - rr01=cuCsubf(xz01,cuConjf(xz10)); - //rr10=-conj(rr01); - rr10.x=-rr01.x; rr10.y=rr01.y; - //rr11=xz11-conj(xz11); - rr11=cuCsubf(xz11,cuConjf(xz11)); - - /* find I_2 kron (X^H X) + (X^H X)^T kron I_2 */ - /* A = [2*xx00 xx01 xx10 0 - xx10 xx11+xx00 0 xx10 - xx01 0 xx11+xx00 xx01 - 0 xx01 xx10 2*xx11 ] - */ - cuFloatComplex A[16],*Ad; - A[0]=cuCmulf(make_cuFloatComplex(2.0f,0.0f),xx00); - A[5]=A[10]=cuCaddf(xx00,xx11); - A[15]=cuCmulf(make_cuFloatComplex(2.0f,0.0f),xx11); - A[1]=A[8]=A[11]=A[13]=xx10; - A[2]=A[4]=A[7]=A[14]=xx01; - A[3]=A[6]=A[9]=A[12]=make_cuFloatComplex(0.0f,0.0f); - cuFloatComplex b[4]; - b[0]=rr00; - b[1]=rr10; - b[2]=rr01; - b[3]=rr11; - -#ifdef DEBUG - printf("BEFOREA=[\n"); - printf("%f+j*(%f) %f+j*(%f) %f+j*(%f) %f+j*(%f)\n",A[0].x,A[0].y,A[4].x,A[4].y,A[8].x,A[8].y,A[12].x,A[12].y); - printf("%f+j*(%f) %f+j*(%f) %f+j*(%f) %f+j*(%f)\n",A[1].x,A[1].y,A[5].x,A[5].y,A[9].x,A[9].y,A[13].x,A[13].y); - printf("%f+j*(%f) %f+j*(%f) %f+j*(%f) %f+j*(%f)\n",A[2].x,A[2].y,A[6].x,A[6].y,A[10].x,A[10].y,A[14].x,A[14].y); - printf("%f+j*(%f) %f+j*(%f) %f+j*(%f) %f+j*(%f)\n",A[3].x,A[3].y,A[7].x,A[7].y,A[11].x,A[11].y,A[15].x,A[15].y); - printf("];\n"); - printf("BEFOREb=[\n"); - printf("%f+j*(%f)\n",b[0].x,b[0].y); - printf("%f+j*(%f)\n",b[1].x,b[1].y); - printf("%f+j*(%f)\n",b[2].x,b[2].y); - printf("%f+j*(%f)\n",b[3].x,b[3].y); - printf("];\n"); -#endif - - - /* solve A u = b to find u , using double precision */ - cudaMalloc((void **)&Ad, 16*sizeof(cuFloatComplex)); - cudaMemcpy(Ad,A,16*sizeof(cuFloatComplex),cudaMemcpyHostToDevice); - /* copy b to device */ - cudaMalloc((void **)&bd, 4*sizeof(cuFloatComplex)); - cudaMemcpy(bd,b,4*sizeof(cuFloatComplex),cudaMemcpyHostToDevice); - - //culaStatus status; - //status=culaDeviceCgels('N',4,4,1,(culaDeviceFloatComplex *)Ad,4,(culaDeviceFloatComplex *)bd,4); - //checkStatus(status,__FILE__,__LINE__); - int work_size=0; - int *devInfo; - cudaError_t err; - err=cudaMalloc((void**)&devInfo, sizeof(int)); - checkCudaError(err,__FILE__,__LINE__); - cuFloatComplex *work,*taud; - cusolverDnCgeqrf_bufferSize(solver_handle, 4, 4, (cuFloatComplex *)Ad, 4, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(cuFloatComplex)); - err=cudaMalloc((void**)&taud, 4*sizeof(cuFloatComplex)); - checkCudaError(err,__FILE__,__LINE__); - cusolverDnCgeqrf(solver_handle, 4, 4, Ad, 4, taud, work, work_size, devInfo); - cusolverDnCunmqr(solver_handle, CUBLAS_SIDE_LEFT, CUBLAS_OP_C, 4, 1, 4, Ad, 4, taud, bd, 4, work, work_size, devInfo); - cuFloatComplex cone; cone.x=1.0f; cone.y=0.0f; - cbstatus=cublasCtrsm(cbhandle,CUBLAS_SIDE_LEFT,CUBLAS_FILL_MODE_UPPER,CUBLAS_OP_N,CUBLAS_DIAG_NON_UNIT,4,1,&cone,Ad,4,bd,4); - - - cudaFree(work); - cudaFree(taud); - cudaFree(devInfo); - - -#ifdef DEBUG - cudaMemcpy(b,bd,4*sizeof(cuFloatComplex),cudaMemcpyDeviceToHost); - printf("Afterb=[\n"); - printf("%f+j*(%f)\n",b[0].x,b[0].y); - printf("%f+j*(%f)\n",b[1].x,b[1].y); - printf("%f+j*(%f)\n",b[2].x,b[2].y); - printf("%f+j*(%f)\n",b[3].x,b[3].y); - printf("];\n"); -#endif - - /* form Z - X * Om, where Om is given by solution b - but no need to rearrange b because it is already in col major order */ - //my_ccopy(4*N,z,1,rnew,1); - cbstatus=cublasCcopy(cbhandle,4*N,z,1,rnew,1); - checkCublasError(cbstatus,__FILE__,__LINE__); - //my_zgemm('N','N',2*N,2,2,-1.0+0.0*_Complex_I,z,2*N,b,2,1.0+0.0*_Complex_I,rnew,2*N); - cuFloatComplex a1,a2; - a1.x=-1.0f; a1.y=0.0f; - a2.x=1.0f; a2.y=0.0f; -#ifdef DEBUG -/* read back eta for checking */ - cuFloatComplex *etalocal; - cudaHostAlloc((void **)&etalocal, sizeof(cuFloatComplex)*4*N,cudaHostAllocDefault); - cudaMemcpy(etalocal, rnew, 4*N*sizeof(cuFloatComplex), cudaMemcpyDeviceToHost); -printf("Rnewbefore=[\n"); - int ci; - for (ci=0; ci<2*N; ci++) { - printf("%f+j*(%f) %f+j*(%f);\n",etalocal[ci].x,etalocal[ci].y,etalocal[ci+2*N].x,etalocal[ci+2*N].y); - } -printf("]\n"); -#endif - - cbstatus=cublasCgemm(cbhandle,CUBLAS_OP_N,CUBLAS_OP_N,2*N,2,2,&a1,x,2*N,bd,2,&a2,rnew,2*N); - -#ifdef DEBUG - checkCublasError(cbstatus,__FILE__,__LINE__); - cudaMemcpy(etalocal, rnew, 4*N*sizeof(cuFloatComplex), cudaMemcpyDeviceToHost); -printf("Rnewafter=[\n"); - for (ci=0; ci<2*N; ci++) { - printf("%f+j*(%f) %f+j*(%f);\n",etalocal[ci].x,etalocal[ci].y,etalocal[ci+2*N].x,etalocal[ci+2*N].y); - } -printf("]\n"); - cudaFreeHost(etalocal); -#endif - checkCublasError(cbstatus,__FILE__,__LINE__); - cudaFree(Ad); - cudaFree(bd); -} - -/* gradient, also projected to tangent space */ -/* need 8N*BlocksPerGrid+ 8N*2 float storage */ -static void -cudakernel_fns_fgrad(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, float *y, float *coh, short *bbh, float *iw, int negate, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle) { - cuFloatComplex *tempeta,*tempb; - cublasStatus_t cbstatus=CUBLAS_STATUS_SUCCESS; - cuFloatComplex alpha; - cudaMalloc((void**)&tempeta, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&tempb, sizeof(cuFloatComplex)*4*N); - /* max size of M for one kernel call, to determine optimal blocks */ - int T=DEFAULT_TH_PER_BK*ThreadsPerBlock; - if (MN,eta,1,teta,1); - cbstatus=cublasCcopy(cbhandle,4*N,eta,1,teta,1); - //my_cscal(4*dp->N,beta0*alphabar+0.0*_Complex_I,teta); - alpha.x=beta0*alphabar;alpha.y=0.0f; - cbstatus=cublasCscal(cbhandle,4*N,&alpha,teta,1); - /* Rx=R(x,teta); */ - //fns_R(dp->N,x,teta,x_prop); - cudakernel_fns_R(N,x,teta,x_prop,cbhandle,solver_handle); - //lhs=fns_f(x_prop,y,gdata); - lhs=cudakernel_fns_f(ThreadsPerBlock,BlocksPerGrid,N,M,x_prop,y,coh,bbh); - if (lhsN,x,eta,teta); - //metric=cudakernel_fns_g(N,x,eta,teta,cbhandle); - metric=beta0*alphabar*metric0; - rhs=fx+sigma*metric; -#ifdef DEBUG -printf("m=%d lhs=%e rhs=%e rat=%e norm=%e\n",m,lhs,rhs,lhs/rhs,metric); -#endif - if ((!isnan(lhs) && lhs<=rhs)) { - minbeta=beta0; - break; - } - beta0=beta0*beta; - } - - /* if no further cost improvement is seen */ - if (lhs>fx) { - nocostred=1; - } - - //my_ccopy(4*dp->N,eta,1,teta,1); - cbstatus=cublasCcopy(cbhandle,4*N,eta,1,teta,1); - alpha.x=minbeta*alphabar; alpha.y=0.0f; - //my_cscal(4*dp->N,minbeta*alphabar+0.0*_Complex_I,teta); - cbstatus=cublasCscal(cbhandle,4*N,&alpha,teta,1); - - checkCublasError(cbstatus,__FILE__,__LINE__); - cudaFree(eta); - cudaFree(x_prop); - - return nocostred; -} - - -/* truncated conjugate gradient method - x, grad, eta, r, z, delta, Hxd : size 2N x 2 complex - so, vector size is 4N complex double - - output: eta - return value: stop_tCG code - - y: vec(V) visibilities -*/ -/* need 8N*(BlocksPerGrid+2)+ 8N*6 float storage */ -static int -tcg_solve_cuda(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *grad, cuFloatComplex *eta, cuFloatComplex *fhess, float Delta, float theta, float kappa, int max_inner, int min_inner, float *y, float *coh, short *bbh, float *iw, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle) { - cuFloatComplex *r,*z,*delta,*Hxd, *rnew; - float e_Pe, r_r, norm_r, z_r, d_Pd, d_Hd, alpha, e_Pe_new, - e_Pd, Deltasq, tau, zold_rold, beta, norm_r0; - int cj, stop_tCG; - cudaMalloc((void**)&r, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&z, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&delta, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&Hxd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&rnew, sizeof(cuFloatComplex)*4*N); - - cublasStatus_t cbstatus; - cuFloatComplex a0; - - /* - initial values - */ - cbstatus=cublasCcopy(cbhandle,4*N,grad,1,r,1); - e_Pe=0.0f; - - - r_r=cudakernel_fns_g(N,x,r,r,cbhandle,solver_handle); - norm_r=sqrtf(r_r); - norm_r0=norm_r; - - cbstatus=cublasCcopy(cbhandle,4*N,r,1,z,1); - - z_r=cudakernel_fns_g(N,x,z,r,cbhandle,solver_handle); - d_Pd=z_r; - - /* - initial search direction - */ - cudaMemset(delta, 0, sizeof(cuFloatComplex)*4*N); - a0.x=-1.0f; a0.y=0.0f; - cbstatus=cublasCaxpy(cbhandle,4*N, &a0, z, 1, delta, 1); - e_Pd=cudakernel_fns_g(N,x,eta,delta,cbhandle,solver_handle); - - stop_tCG=5; - - /* % begin inner/tCG loop - for j = 1:max_inner, - */ - for(cj=1; cj<=max_inner; cj++) { - cudakernel_fns_fhess(ThreadsPerBlock,BlocksPerGrid,N,M,x,delta,Hxd,y,coh,bbh,iw, cbhandle, solver_handle); - d_Hd=cudakernel_fns_g(N,x,delta,Hxd,cbhandle,solver_handle); - - alpha=z_r/d_Hd; - e_Pe_new = e_Pe + 2.0f*alpha*e_Pd + alpha*alpha*d_Pd; - - - Deltasq=Delta*Delta; - if (d_Hd <= 0.0f || e_Pe_new >= Deltasq) { - tau = (-e_Pd + sqrtf(e_Pd*e_Pd + d_Pd*(Deltasq-e_Pe)))/d_Pd; - a0.x=tau; - cbstatus=cublasCaxpy(cbhandle,4*N, &a0, delta, 1, eta, 1); - /* Heta = Heta + tau *Hdelta */ - cbstatus=cublasCaxpy(cbhandle,4*N, &a0, Hxd, 1, fhess, 1); - stop_tCG=(d_Hd<=0.0f?1:2); - break; - } - - e_Pe=e_Pe_new; - a0.x=alpha; - cbstatus=cublasCaxpy(cbhandle,4*N, &a0, delta, 1, eta, 1); - /* Heta = Heta + alpha*Hdelta */ - cbstatus=cublasCaxpy(cbhandle,4*N, &a0, Hxd, 1, fhess, 1); - - cbstatus=cublasCaxpy(cbhandle,4*N, &a0, Hxd, 1, r, 1); - cudakernel_fns_proj(N, x, r, rnew, cbhandle,solver_handle); - cbstatus=cublasCcopy(cbhandle,4*N,rnew,1,r,1); - r_r=cudakernel_fns_g(N,x,r,r,cbhandle,solver_handle); - norm_r=sqrtf(r_r); - - /* - check kappa/theta stopping criterion - */ - if (cj >= min_inner) { - float norm_r0pow=powf(norm_r0,theta); - if (norm_r <= norm_r0*MIN(norm_r0pow,kappa)) { - stop_tCG=(kappaNbase)*(ntiles); /* note: we do not use the total tile size */ - /* coherency on device */ - float *cohd; - /* baseline-station map on device/host */ - short *bbd; - - /* calculate no of cuda threads and blocks */ - int ThreadsPerBlock=DEFAULT_TH_PER_BK; - int BlocksPerGrid= 2*(M+ThreadsPerBlock-1)/ThreadsPerBlock; - - - /* reshape x to make J: 2Nx2 complex double - */ - complex float *x; - if ((x=(complex float*)malloc((size_t)4*N*sizeof(complex float)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - /* map x: [(re,im)J_1(0,0) (re,im)J_1(0,1) (re,im)J_1(1,0) (re,im)J_1(1,1)...] - to - J: [J_1(0,0) J_1(1,0) J_2(0,0) J_2(1,0) ..... J_1(0,1) J_1(1,1) J_2(0,1) J_2(1,1)....] - */ - float *Jd=(float*)x; - /* re J(0,0) */ - my_fcopy(N, &x0[0], 8, &Jd[0], 4); - /* im J(0,0) */ - my_fcopy(N, &x0[1], 8, &Jd[1], 4); - /* re J(1,0) */ - my_fcopy(N, &x0[4], 8, &Jd[2], 4); - /* im J(1,0) */ - my_fcopy(N, &x0[5], 8, &Jd[3], 4); - /* re J(0,1) */ - my_fcopy(N, &x0[2], 8, &Jd[4*N], 4); - /* im J(0,1) */ - my_fcopy(N, &x0[3], 8, &Jd[4*N+1], 4); - /* re J(1,1) */ - my_fcopy(N, &x0[6], 8, &Jd[4*N+2], 4); - /* im J(1,1) */ - my_fcopy(N, &x0[7], 8, &Jd[4*N+3], 4); - - - int ci; - -/***************************************************/ - cuFloatComplex *xd,*fgradxd,*etad,*Hetad,*x_propd; - float *yd; - /* for counting how many baselines contribute to each station - grad/hess calculation */ - float *iwd,*iw; - if ((iw=(float*)malloc((size_t)N*sizeof(float)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - - cudaMalloc((void**)&fgradxd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&etad, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&Hetad, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&x_propd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&xd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&yd, sizeof(float)*8*M); - cudaMalloc((void**)&cohd, sizeof(float)*8*Nbase); - cudaMalloc((void**)&bbd, sizeof(short)*2*Nbase); - cudaMalloc((void**)&iwd, sizeof(float)*N); - - /* need 8N*(BlocksPerGrid+8) for tcg_solve+grad/hess storage, - so total storage needed is - 8N*(BlocksPerGrid+8) + 8N*5 + 8*M + 8*Nbase + 2*Nbase + N - */ - /* yd <=y : V */ - err=cudaMemcpy(yd, y, 8*M*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - /* need to give right offset for coherencies */ - /* offset: cluster offset+time offset */ - /* C */ - err=cudaMemcpy(cohd, &(dp->ddcohf[(dp->Nbase)*(dp->tilesz)*(dp->clus)*8+(dp->Nbase)*tileoff*8]), Nbase*8*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - /* correct offset for baselines */ - err=cudaMemcpy(bbd, &(dp->ddbase[2*(dp->Nbase)*(tileoff)]), Nbase*2*sizeof(short), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - /* xd <=x : solution */ - err=cudaMemcpy(xd, x, 8*N*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - - float fx,fx0,norm_grad,Delta,fx_prop,rhonum,rhoden,rho; - - /* count how many baselines contribute to each station, store (inverse) in iwd */ - count_baselines(Nbase,N,iw,&(dp->ddbase[2*(dp->Nbase)*(tileoff)]),dp->Nt); - err=cudaMemcpy(iwd, iw, N*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - free(iw); - - fx=cudakernel_fns_f(ThreadsPerBlock,BlocksPerGrid,N,M,xd,yd,cohd,bbd); - fx0=fx; -#ifdef DEBUG -printf("Initial Cost=%g\n",fx0); -#endif -/***************************************************/ - int rsdstat=0; - /* RSD solution */ - for (ci=0; ci locally converge, globally diverge - |\ - |\ | \___ - -|\ | \| - \ - - - right damping: locally and globally converge - -|\ - \|\ - \|\ - \____ - - */ - float rho_reg; - int model_decreased=0; - - /* RTR solution */ - int k=0; - int stop_outer=(itmax_rtr>0?0:1); - int stop_inner=0; - if (!stop_outer) { - cudakernel_fns_fgrad(ThreadsPerBlock,BlocksPerGrid,N,M,xd,fgradxd,yd,cohd,bbd,iwd,1,cbhandle,solver_handle); - norm_grad=sqrtf(cudakernel_fns_g(N,xd,fgradxd,fgradxd,cbhandle,solver_handle)); - } - Delta=Delta0; - /* initial residual */ - info[0]=fx0; - - /* - % ** Start of TR loop ** - */ - while(!stop_outer) { - /* - % update counter - */ - k++; - /* eta = 0*fgradx; */ - cudaMemset(etad, 0, sizeof(cuFloatComplex)*4*N); - - - /* solve TR subproblem, also returns Hessian */ - stop_inner=tcg_solve_cuda(ThreadsPerBlock,BlocksPerGrid, N, M, xd, fgradxd, etad, Hetad, Delta, theta, kappa, max_inner, min_inner,yd,cohd,bbd,iwd,cbhandle, solver_handle); - /* - Heta = fns.fhess(x,eta); - */ - /* - compute the retraction of the proposal - */ - cudakernel_fns_R(N,xd,etad,x_propd,cbhandle,solver_handle); - - /* - compute cost of the proposal - */ - fx_prop=cudakernel_fns_f(ThreadsPerBlock,BlocksPerGrid,N,M,x_propd,yd,cohd,bbd); - - /* - check the performance of the quadratic model - */ - rhonum=fx-fx_prop; - rhoden=-cudakernel_fns_g(N,xd,fgradxd,etad,cbhandle,solver_handle)-0.5f*cudakernel_fns_g(N,xd,Hetad,etad,cbhandle,solver_handle); - /* regularization of rho ratio */ - /* - rho_reg = max(1, abs(fx)) * eps * options.rho_regularization; - rhonum = rhonum + rho_reg; - rhoden = rhoden + rho_reg; - */ - rho_reg=MAX(1.0f,fx)*rho_regularization; /* no epsilon */ - rhonum+=rho_reg; - rhoden+=rho_reg; - - /* - rho = rhonum / rhoden; - */ - rho=rhonum/rhoden; - - /* model_decreased = (rhoden >= 0); */ - /* OLD CODE if (fabsf(rhonum/fx) =0.0f?1:0); - -#ifdef DEBUG - printf("stop_inner=%d rho_reg=%g rho =%g/%g= %g rho'= %g\n",stop_inner,rho_reg,rhonum,rhoden,rho,rho_prime); -#endif - /* - choose new TR radius based on performance - */ - if ( !model_decreased || rhoeta2 && (stop_inner==2 || stop_inner==1)) { - Delta=MIN(alpha2*Delta,Delta_bar); - } - - /* - choose new iterate based on performance - */ - if (model_decreased && rho>rho_prime) { - cbstatus=cublasCcopy(cbhandle,4*N,x_propd,1,xd,1); - fx=fx_prop; - cudakernel_fns_fgrad(ThreadsPerBlock,BlocksPerGrid,N,M,xd,fgradxd,yd,cohd,bbd,iwd,1,cbhandle,solver_handle); - norm_grad=sqrtf(cudakernel_fns_g(N,xd,fgradxd,fgradxd,cbhandle,solver_handle)); - } - - /* - Testing for Stop Criteria - */ - if (norm_gradmin_outer) { - stop_outer=1; - } - - /* - stop after max_outer iterations - */ - if (k>=max_outer) { - stop_outer=1; - } - -#ifdef DEBUG -printf("Iter %d cost=%g\n",k,fx); -#endif - - } - /* final residual */ - info[1]=fx; -#ifdef DEBUG -printf("NEW RTR cost=%g\n",fx); -#endif - -/***************************************************/ - checkCublasError(cbstatus,__FILE__,__LINE__); - cudaDeviceSynchronize(); - - if(fx0>fx) { - //printf("Cost final %g initial %g\n",fx,fx0); - /* copy back current solution */ - err=cudaMemcpy(x,xd,8*N*sizeof(float),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - - - /* copy back solution to x0 : format checked*/ - /* re J(0,0) */ - my_fcopy(N, &Jd[0], 4, &x0[0], 8); - /* im J(0,0) */ - my_fcopy(N, &Jd[1], 4, &x0[1], 8); - /* re J(1,0) */ - my_fcopy(N, &Jd[2], 4, &x0[4], 8); - /* im J(1,0) */ - my_fcopy(N, &Jd[3], 4, &x0[5], 8); - /* re J(0,1) */ - my_fcopy(N, &Jd[4*N], 4, &x0[2], 8); - /* im J(0,1) */ - my_fcopy(N, &Jd[4*N+1], 4, &x0[3], 8); - /* re J(1,1) */ - my_fcopy(N, &Jd[4*N+2], 4, &x0[6], 8); - /* im J(1,1) */ - my_fcopy(N, &Jd[4*N+3], 4, &x0[7], 8); - - } - free(x); - - cudaFree(fgradxd); - cudaFree(etad); - cudaFree(Hetad); - cudaFree(x_propd); - cudaFree(xd); - cudaFree(yd); - cudaFree(cohd); - cudaFree(bbd); - cudaFree(iwd); - - - return 0; -} diff --git a/src/lib/Solvers/rtr_solve_cuda.o b/src/lib/Solvers/rtr_solve_cuda.o deleted file mode 100644 index 39a708bfa5973acbc17ec90922ed6e23a10711bf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 89704 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 Fe*rP`f`R}5 diff --git a/src/lib/Solvers/rtr_solve_robust.c b/src/lib/Solvers/rtr_solve_robust.c deleted file mode 100644 index 4488d58..0000000 --- a/src/lib/Solvers/rtr_solve_robust.c +++ /dev/null @@ -1,2246 +0,0 @@ -/* - * - Copyright (C) 2014 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#include "Solvers.h" - -//#define DEBUG -/* Jones matrix multiplication - C=A*B -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -amb(complex double * __restrict a, complex double * __restrict b, complex double * __restrict c) { - c[0]=a[0]*b[0]+a[1]*b[2]; - c[1]=a[0]*b[1]+a[1]*b[3]; - c[2]=a[2]*b[0]+a[3]*b[2]; - c[3]=a[2]*b[1]+a[3]*b[3]; -} - -/* Jones matrix multiplication - C=A*B^H -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -ambt(complex double * __restrict a, complex double * __restrict b, complex double * __restrict c) { - c[0]=a[0]*conj(b[0])+a[1]*conj(b[1]); - c[1]=a[0]*conj(b[2])+a[1]*conj(b[3]); - c[2]=a[2]*conj(b[0])+a[3]*conj(b[1]); - c[3]=a[2]*conj(b[2])+a[3]*conj(b[3]); -} - -/* Jones matrix multiplication - C=A^H*B -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -atmb(complex double * __restrict a, complex double * __restrict b, complex double * __restrict c) { - c[0]=conj(a[0])*b[0]+conj(a[2])*b[2]; - c[1]=conj(a[0])*b[1]+conj(a[2])*b[3]; - c[2]=conj(a[1])*b[0]+conj(a[3])*b[2]; - c[3]=conj(a[1])*b[1]+conj(a[3])*b[3]; -} - - -/* worker thread function for counting */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -threadfn_fns_fcount(void *data) { - thread_data_rtr_t *t=(thread_data_rtr_t*)data; - - int ci,sta1,sta2; - for (ci=0; ciNb; ci++) { - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - - pthread_mutex_lock(&t->mx_array[sta1]); - t->bcount[sta1]+=1; - pthread_mutex_unlock(&t->mx_array[sta1]); - - pthread_mutex_lock(&t->mx_array[sta2]); - t->bcount[sta2]+=1; - pthread_mutex_unlock(&t->mx_array[sta2]); - } - } - - return NULL; -} - - -/* function to count how many baselines contribute to the calculation of - grad and hess, so normalization can be made */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -fns_fcount(global_data_rtr_t *gdata) { - - me_data_t *dp=(me_data_t*)gdata->medata; - - int nth,nth1,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthb0,Nthb; - thread_data_rtr_t *threaddata; - - int Nbase1=(dp->Nbase)*(dp->tilesz); - int boff=(dp->Nbase)*(dp->tileoff); - - /* calculate min baselines a thread can handle */ - Nthb0=(Nbase1+Nt-1)/Nt; - - if ((threaddata=(thread_data_rtr_t*)malloc((size_t)Nt*sizeof(thread_data_rtr_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - int *bcount; - if ((bcount=(int*)calloc((size_t)dp->N,sizeof(int)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr; - threaddata[nth].bcount=bcount; /* note this should be 0 first */ - threaddata[nth].mx_array=gdata->mx_array; - - pthread_create(&gdata->th_array[nth],&gdata->attr,threadfn_fns_fcount,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - /* now wait for threads to finish */ - for(nth1=0; nth1th_array[nth1],NULL); - } - free(threaddata); - - /* calculate inverse count */ - for (nth1=0; nth1N; nth1++) { - gdata->iw[nth1]=(bcount[nth1]>0?1.0/(double)bcount[nth1]:0.0); - } - free(bcount); - /* scale back weight such that max value is 1 */ - nth1=my_idamax(dp->N,gdata->iw,1); - double maxw=gdata->iw[nth1-1]; /* 1 based index */ - if (maxw>0.0) { /* all baselines can be flagged */ - my_dscal(dp->N,1.0/maxw,gdata->iw); - } -} - - - -/* worker thread function for cost function calculation */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -threadfn_fns_f(void *data) { - thread_data_rtr_t *t=(thread_data_rtr_t*)data; - - int ci,cm,sta1,sta2; - complex double C[4],G1[4],G2[4],T1[4],T2[4]; - int M=(t->M); - cm=(t->clus); - for (ci=0; ciNb; ci++) { - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - /* gains for this cluster, for sta1,sta2 */ - G1[0]=(t->x[sta1*2]); - G1[1]=(t->x[sta1*2+2*t->N]); - G1[2]=(t->x[sta1*2+1]); - G1[3]=(t->x[sta1*2+2*t->N+1]); - G2[0]=(t->x[sta2*2]); - G2[1]=(t->x[sta2*2+2*t->N]); - G2[2]=(t->x[sta2*2+1]); - G2[3]=(t->x[sta2*2+2*t->N+1]); - - /* use pre calculated values */ - C[0]=t->coh[4*M*ci+4*cm]; - C[1]=t->coh[4*M*ci+4*cm+1]; - C[2]=t->coh[4*M*ci+4*cm+2]; - C[3]=t->coh[4*M*ci+4*cm+3]; - - - /* form G1*C*G2' */ - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - - /* add to baseline visibilities V->U */ - double r00=t->y[8*ci]-creal(T2[0]); - double i00=t->y[8*ci+1]-cimag(T2[0]); - double r01=t->y[8*ci+2]-creal(T2[1]); - double i01=t->y[8*ci+3]-cimag(T2[1]); - double r10=t->y[8*ci+4]-creal(T2[2]); - double i10=t->y[8*ci+5]-cimag(T2[2]); - double r11=t->y[8*ci+6]-creal(T2[3]); - double i11=t->y[8*ci+7]-cimag(T2[3]); - - t->fcost+=t->wtd[ci]*(r00*r00+i00*i00+r01*r01+i01*i01+r10*r10+i10*i10+r11*r11+i11*i11); - } - } - - return NULL; -} - - -/* cost function */ -/* x: 2Nx2 solution - y: visibilities, vectorized V(:) 8*Nbase x 1 -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static double -fns_f(complex double *x, double *y, global_data_rtr_t *gdata) { - - me_data_t *dp=(me_data_t*)gdata->medata; - - int nth,nth1,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthb0,Nthb; - thread_data_rtr_t *threaddata; - - int Nbase1=(dp->Nbase)*(dp->tilesz); - int boff=(dp->Nbase)*(dp->tileoff); - - /* calculate min baselines a thread can handle */ - Nthb0=(Nbase1+Nt-1)/Nt; - - if ((threaddata=(thread_data_rtr_t*)malloc((size_t)Nt*sizeof(thread_data_rtr_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr; - threaddata[nth].carr=dp->carr; - threaddata[nth].M=dp->M; - threaddata[nth].y=&(y[8*ci]); - threaddata[nth].N=dp->N; - threaddata[nth].x=x; /* note the difference: here x assumes no hybrid, also ordering different */ - threaddata[nth].clus=(dp->clus); - threaddata[nth].coh=&(dp->coh[4*(dp->M)*(ci+boff)]); - threaddata[nth].fcost=0.0; - - threaddata[nth].wtd=&(gdata->wtd[ci]); /* weights for baselines */ - - //printf("thread %d predict data from %d baselines %d\n",nth,8*ci,Nthb); - pthread_create(&gdata->th_array[nth],&gdata->attr,threadfn_fns_f,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - /* now wait for threads to finish */ - double fcost=0.0; - for(nth1=0; nth1th_array[nth1],NULL); - fcost+=threaddata[nth1].fcost; - } - - free(threaddata); - - return fcost; -} - - -/* worker thread function for weight update (nu+8)/(nu+error^2) */ -/* update: error: min of XX,XY,YX,YY errors, so p=2 */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -threadfn_fns_fupdate_weights(void *data) { - thread_data_rtr_t *t=(thread_data_rtr_t*)data; - - int ci,cm,sta1,sta2; - complex double C[4],G1[4],G2[4],T1[4],T2[4]; - int M=(t->M); - cm=(t->clus); - for (ci=0; ciNb; ci++) { - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - /* gains for this cluster, for sta1,sta2 */ - G1[0]=(t->x[sta1*2]); - G1[1]=(t->x[sta1*2+2*t->N]); - G1[2]=(t->x[sta1*2+1]); - G1[3]=(t->x[sta1*2+2*t->N+1]); - G2[0]=(t->x[sta2*2]); - G2[1]=(t->x[sta2*2+2*t->N]); - G2[2]=(t->x[sta2*2+1]); - G2[3]=(t->x[sta2*2+2*t->N+1]); - - /* use pre calculated values */ - C[0]=t->coh[4*M*ci+4*cm]; - C[1]=t->coh[4*M*ci+4*cm+1]; - C[2]=t->coh[4*M*ci+4*cm+2]; - C[3]=t->coh[4*M*ci+4*cm+3]; - - - /* form G1*C*G2' */ - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - - /* add to baseline visibilities V->U */ - double r00=t->y[8*ci]-creal(T2[0]); - double i00=t->y[8*ci+1]-cimag(T2[0]); - double r01=t->y[8*ci+2]-creal(T2[1]); - double i01=t->y[8*ci+3]-cimag(T2[1]); - double r10=t->y[8*ci+4]-creal(T2[2]); - double i10=t->y[8*ci+5]-cimag(T2[2]); - double r11=t->y[8*ci+6]-creal(T2[3]); - double i11=t->y[8*ci+7]-cimag(T2[3]); - - //t->wtd[ci] = (t->nu0+8.0)/(t->nu0+(r00*r00+i00*i00+r01*r01+i01*i01+r10*r10+i10*i10+r11*r11+i11*i11)); - t->wtd[ci] = (t->nu0+2.0)/(t->nu0+MAX(r00*r00+i00*i00,MAX(r01*r01+i01*i01,MAX(r10*r10+i10*i10,r11*r11+i11*i11)))); - } - } - - return NULL; -} - - -/* calculate log(w_i) - w_i */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -threadfn_fns_flogsum_weights(void *data) { - thread_data_rtr_t *t=(thread_data_rtr_t*)data; - - int ci; - for (ci=0; ciNb; ci++) { - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - t->fcost+=log(t->wtd[ci])-t->wtd[ci]; - } - } - - return NULL; -} - - - -/* calculate weight w = (nu+1)/(nu+error^2) per baseline - then update robust_nu */ -/* x: 2Nx2 solution - y: visibilities, vectorized V(:) 8*Nbase x 1 - returns updated value for robust_nu -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static double -fns_fupdate_weights(complex double *x, double *y, global_data_rtr_t *gdata) { - - me_data_t *dp=(me_data_t*)gdata->medata; - - int nth,nth1,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthb0,Nthb; - thread_data_rtr_t *threaddata; - - int Nbase1=(dp->Nbase)*(dp->tilesz); - int boff=(dp->Nbase)*(dp->tileoff); - - /* calculate min baselines a thread can handle */ - Nthb0=(Nbase1+Nt-1)/Nt; - - if ((threaddata=(thread_data_rtr_t*)malloc((size_t)Nt*sizeof(thread_data_rtr_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr; - threaddata[nth].carr=dp->carr; - threaddata[nth].M=dp->M; - threaddata[nth].y=&(y[8*ci]); - threaddata[nth].N=dp->N; - threaddata[nth].x=x; /* note the difference: here x assumes no hybrid, also ordering different */ - threaddata[nth].clus=(dp->clus); - threaddata[nth].coh=&(dp->coh[4*(dp->M)*(ci+boff)]); - - threaddata[nth].wtd=&(gdata->wtd[ci]); /* weights for baselines */ - threaddata[nth].nu0=dp->robust_nu; - - //printf("thread %d predict data from %d baselines %d\n",nth,8*ci,Nthb); - pthread_create(&gdata->th_array[nth],&gdata->attr,threadfn_fns_fupdate_weights,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - for(nth1=0; nth1th_array[nth1],NULL); - } - - /* now calculate sum( ln(w_i)-w_i ) */ - ci=0; - for (nth1=0; nth1th_array[nth1],&gdata->attr,threadfn_fns_flogsum_weights,(void*)(&threaddata[nth1])); - /* next baseline set */ - } - - double sumlogw=0.0; - for(nth1=0; nth1th_array[nth1],NULL); - } - sumlogw/=(double)Nbase1; - free(threaddata); - - /* find new value for nu, p-variate T dist, p=8 (update p=2 because using MAX() for residual calculation, not sum) */ - /* psi((nu_old+p)/2)-ln((nu_old+p)/2)-psi(nu/2)+ln(nu/2)+1/N sum(ln(w_i)-w_i) +1 = 0, AECM */ - double nu1=update_nu(sumlogw, 30, Nt, gdata->nulow, gdata->nuhigh, 2, dp->robust_nu); - - /* make sure new value stays within bounds */ - if (nu1nulow) { return gdata->nulow; } - if (nu1>gdata->nuhigh) { return gdata->nuhigh; } - return nu1; -} - - - -/* inner product (metric) */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static double -fns_g(int N,complex double *x, complex double *eta, complex double *gamma) { - /* 2 x real( trace(eta'*gamma) ) - = 2 x real( eta(:,1)'*gamma(:,1) + eta(:,2)'*gamma(:,2) ) - no need to calculate off diagonal terms - )*/ - complex double v1=my_cdot(2*N,eta,gamma); - complex double v2=my_cdot(2*N,&eta[2*N],&gamma[2*N]); - - return 2.0*creal(v1+v2); -} - -/* Projection - rnew: new value */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -fns_proj(int N,complex double *x, complex double *z,complex double *rnew) { - /* projection = Z-X Om, where - Om X^H X+X^H X Om = X^H Z - Z^H X - is solved to find Om */ - - /* find X^H X */ - complex double xx00,xx01,xx10,xx11; - xx00=my_cdot(2*N,x,x); - xx01=my_cdot(2*N,x,&x[2*N]); - xx10=conj(xx01); - xx11=my_cdot(2*N,&x[2*N],&x[2*N]); - - /* find X^H Z (and using this just calculte Z^H X directly) */ - complex double xz00,xz01,xz10,xz11; - xz00=my_cdot(2*N,x,z); - xz01=my_cdot(2*N,x,&z[2*N]); - xz10=my_cdot(2*N,&x[2*N],z); - xz11=my_cdot(2*N,&x[2*N],&z[2*N]); - - /* find X^H Z - Z^H X */ - complex double rr00,rr01,rr10,rr11; - rr00=xz00-conj(xz00); - rr01=xz01-conj(xz10); - rr10=-conj(rr01); - rr11=xz11-conj(xz11); - - /* find I_2 kron (X^H X) + (X^H X)^T kron I_2 */ - /* A = [2*xx00 xx01 xx10 0 - xx10 xx11+xx00 0 xx10 - xx01 0 xx11+xx00 xx01 - 0 xx01 xx10 2*xx11 ] - */ - complex double A[16]; - A[0]=2.0*xx00; - A[5]=A[10]=xx11+xx00; - A[15]=2.0*xx11; - A[1]=A[8]=A[11]=A[13]=xx10; - A[2]=A[4]=A[7]=A[14]=xx01; - A[3]=A[6]=A[9]=A[12]=0.0; - complex double b[4]; - b[0]=rr00; - b[1]=rr10; - b[2]=rr01; - b[3]=rr11; - - /* solve A u = b to find u */ - complex double w,*WORK; - /* workspace query */ - int status=my_zgels('N',4,4,1,A,4,b,4,&w,-1); - int lwork=(int)w; - if ((WORK=(complex double*)calloc((size_t)lwork,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - status=my_zgels('N',4,4,1,A,4,b,4,WORK,lwork); - if (status) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: LAPACK error %d\n",__FILE__,__LINE__,status); -#endif - } - - free(WORK); - /* form Z - X * Om, where Om is given by solution b - but no need to rearrange b because it is already in col major order */ - my_ccopy(4*N,z,1,rnew,1); - my_zgemm('N','N',2*N,2,2,-1.0+0.0*_Complex_I,x,2*N,b,2,1.0+0.0*_Complex_I,rnew,2*N); - - -} - - -/* Retraction - rnew: new value */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -fns_R(int N,complex double *x, complex double *r,complex double *rnew) { - /* rnew = x + r */ - my_ccopy(4*N,x,1,rnew,1); - my_caxpy(4*N,r,1.0+_Complex_I*0.0,rnew); -} - - -/* worker thread function for gradient/hessian weighting */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -threadfn_fns_fscale(void *data) { - thread_data_rtr_t *t=(thread_data_rtr_t*)data; - - int ci,sta1; - for (ci=0; ciNb; ci++) { - sta1=ci+t->boff; - t->grad[2*sta1]*=t->iw[sta1]; - t->grad[2*sta1+2*t->N]*=t->iw[sta1]; - t->grad[2*sta1+1]*=t->iw[sta1]; - t->grad[2*sta1+2*t->N+1]*=t->iw[sta1]; - } - - return NULL; -} - - - - -/* worker thread function for gradient calculation */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -threadfn_fns_fgrad(void *data) { - thread_data_rtr_t *t=(thread_data_rtr_t*)data; - - int ci,cm,sta1,sta2; - complex double C[4],G1[4],G2[4],T1[4],T2[4],res[4]; - int M=(t->M); - cm=(t->clus); - for (ci=0; ciNb; ci++) { - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - /* gains for this cluster, for sta1,sta2 */ - G1[0]=(t->x[sta1*2]); - G1[1]=(t->x[sta1*2+2*t->N]); - G1[2]=(t->x[sta1*2+1]); - G1[3]=(t->x[sta1*2+2*t->N+1]); - G2[0]=(t->x[sta2*2]); - G2[1]=(t->x[sta2*2+2*t->N]); - G2[2]=(t->x[sta2*2+1]); - G2[3]=(t->x[sta2*2+2*t->N+1]); - - /* use pre calculated values */ - C[0]=t->coh[4*M*ci+4*cm]; - C[1]=t->coh[4*M*ci+4*cm+1]; - C[2]=t->coh[4*M*ci+4*cm+2]; - C[3]=t->coh[4*M*ci+4*cm+3]; - - - /* G1*C*G2' */ - amb(G1,C,T1); - ambt(T1,G2,T2); - - /* res=V(2*ck-1:2*ck,:)-x(2*p-1:2*p,:)*C*x(2*q-1:2*q,:)'; */ - /* V->U */ - res[0]=(t->y[8*ci]+_Complex_I*t->y[8*ci+1])-T2[0]; - res[1]=(t->y[8*ci+2]+_Complex_I*t->y[8*ci+3])-T2[1]; - res[2]=(t->y[8*ci+4]+_Complex_I*t->y[8*ci+5])-T2[2]; - res[3]=(t->y[8*ci+6]+_Complex_I*t->y[8*ci+7])-T2[3]; - - /* - grad(2*p-1:2*p,:)=grad(2*p-1:2*p,:)+res*x(2*q-1:2*q,:)*C'; - grad(2*q-1:2*q,:)=grad(2*q-1:2*q,:)+res'*x(2*p-1:2*p,:)*C; - */ - /* res*G2*C' */ - amb(res,G2,T1); - ambt(T1,C,T2); - - /* multiply by baseline weight */ - T2[0]=T2[0]*t->wtd[ci]; - T2[1]=T2[1]*t->wtd[ci]; - T2[2]=T2[2]*t->wtd[ci]; - T2[3]=T2[3]*t->wtd[ci]; - - pthread_mutex_lock(&t->mx_array[sta1]); - t->grad[2*sta1]+=T2[0]; - t->grad[2*sta1+2*t->N]+=T2[1]; - t->grad[2*sta1+1]+=T2[2]; - t->grad[2*sta1+2*t->N+1]+=T2[3]; - pthread_mutex_unlock(&t->mx_array[sta1]); - - /* res'*G1*C */ - atmb(res,G1,T1); - amb(T1,C,T2); - - /* multiply by baseline weight */ - T2[0]=T2[0]*t->wtd[ci]; - T2[1]=T2[1]*t->wtd[ci]; - T2[2]=T2[2]*t->wtd[ci]; - T2[3]=T2[3]*t->wtd[ci]; - - pthread_mutex_lock(&t->mx_array[sta2]); - t->grad[2*sta2]+=T2[0]; - t->grad[2*sta2+2*t->N]+=T2[1]; - t->grad[2*sta2+1]+=T2[2]; - t->grad[2*sta2+2*t->N+1]+=T2[3]; - pthread_mutex_unlock(&t->mx_array[sta2]); - - } - } - - return NULL; -} - - - -/* gradient function */ -/* x: 2Nx2 solution - fgradx: output, same shape as x - y: visibilities, vectorized V(:) 8*Nbase x 1 - if negate==1, return -grad, else just grad -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -fns_fgrad(complex double *x, complex double *fgradx, double *y, global_data_rtr_t *gdata, int negate) { - - me_data_t *dp=(me_data_t*)gdata->medata; - - int nth,nth1,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthb0,Nthb; - thread_data_rtr_t *threaddata; - - int Nbase1=(dp->Nbase)*(dp->tilesz); - int boff=(dp->Nbase)*(dp->tileoff); - - /* calculate min baselines a thread can handle */ - Nthb0=(Nbase1+Nt-1)/Nt; - - if ((threaddata=(thread_data_rtr_t*)malloc((size_t)Nt*sizeof(thread_data_rtr_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - complex double *grad; - if ((grad=(complex double*)calloc((size_t)4*dp->N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr; - threaddata[nth].carr=dp->carr; - threaddata[nth].M=dp->M; - threaddata[nth].y=&(y[8*ci]); - threaddata[nth].N=dp->N; - threaddata[nth].x=x; /* note the difference: here x assumes no hybrid, also ordering different */ - threaddata[nth].clus=(dp->clus); - threaddata[nth].coh=&(dp->coh[4*(dp->M)*(ci+boff)]); - threaddata[nth].grad=grad; - threaddata[nth].mx_array=gdata->mx_array; - threaddata[nth].wtd=&(gdata->wtd[ci]); /* weights for baselines */ - - //printf("thread %d predict data from %d baselines %d\n",nth,8*ci,Nthb); - pthread_create(&gdata->th_array[nth],&gdata->attr,threadfn_fns_fgrad,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - /* now wait for threads to finish */ - for(nth1=0; nth1th_array[nth1],NULL); - } -/******************* scale *************/ - Nthb0=(dp->N+Nt-1)/Nt; - ci=0; - for (nth=0; nthN; nth++) { - if (ci+Nthb0N) { - Nthb=Nthb0; - } else { - Nthb=dp->N-ci; - } - threaddata[nth].boff=ci; - threaddata[nth].Nb=Nthb; - threaddata[nth].N=dp->N; - threaddata[nth].grad=grad; - threaddata[nth].iw=gdata->iw; - pthread_create(&gdata->th_array[nth],&gdata->attr,threadfn_fns_fscale,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - for(nth1=0; nth1th_array[nth1],NULL); - } -/******************* scale *************/ - free(threaddata); - if (negate) { - my_cscal(4*dp->N,-1.0+0.0*_Complex_I,grad); - } - fns_proj(dp->N,x,grad,fgradx); - free(grad); - -} - - -/* worker thread function for Hessian calculation */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -threadfn_fns_fhess(void *data) { - thread_data_rtr_t *t=(thread_data_rtr_t*)data; - - int ci,cm,sta1,sta2; - complex double C[4],G1[4],G2[4],T1[4],T2[4],res[4],res1[4],E1[4],E2[4]; - int M=(t->M); - cm=(t->clus); - for (ci=0; ciNb; ci++) { - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - /* gains for this cluster, for sta1,sta2 */ - G1[0]=(t->x[sta1*2]); - G1[1]=(t->x[sta1*2+2*t->N]); - G1[2]=(t->x[sta1*2+1]); - G1[3]=(t->x[sta1*2+2*t->N+1]); - G2[0]=(t->x[sta2*2]); - G2[1]=(t->x[sta2*2+2*t->N]); - G2[2]=(t->x[sta2*2+1]); - G2[3]=(t->x[sta2*2+2*t->N+1]); - E1[0]=t->eta[2*sta1]; - E1[1]=t->eta[2*sta1+2*t->N]; - E1[2]=t->eta[2*sta1+1]; - E1[3]=t->eta[2*sta1+2*t->N+1]; - E2[0]=t->eta[2*sta2]; - E2[1]=t->eta[2*sta2+2*t->N]; - E2[2]=t->eta[2*sta2+1]; - E2[3]=t->eta[2*sta2+2*t->N+1]; - - - - /* use pre calculated values */ - C[0]=t->coh[4*M*ci+4*cm]; - C[1]=t->coh[4*M*ci+4*cm+1]; - C[2]=t->coh[4*M*ci+4*cm+2]; - C[3]=t->coh[4*M*ci+4*cm+3]; - - /* G1*C*G2' */ - amb(G1,C,T1); - ambt(T1,G2,T2); - - - /* res=V(2*ck-1:2*ck,:)-x(2*p-1:2*p,:)*C*x(2*q-1:2*q,:)'; */ - /* V->U */ - res[0]=(t->y[8*ci]+_Complex_I*t->y[8*ci+1])-T2[0]; - res[1]=(t->y[8*ci+2]+_Complex_I*t->y[8*ci+3])-T2[1]; - res[2]=(t->y[8*ci+4]+_Complex_I*t->y[8*ci+5])-T2[2]; - res[3]=(t->y[8*ci+6]+_Complex_I*t->y[8*ci+7])-T2[3]; - - - /* - res1=x(2*p-1:2*p,:)*C*eta(2*q-1:2*q,:)'+eta(2*p-1:2*p,:)*C*x(2*q-1:2*q,:)'; - */ - /* G1*C*E2' */ - amb(G1,C,T1); - ambt(T1,E2,T2); - res1[0]=T2[0]; - res1[1]=T2[1]; - res1[2]=T2[2]; - res1[3]=T2[3]; - /* E1*C*G2' */ - amb(E1,C,T1); - ambt(T1,G2,T2); - res1[0]+=T2[0]; - res1[1]+=T2[1]; - res1[2]+=T2[2]; - res1[3]+=T2[3]; - - - /* - hess(2*p-1:2*p,:)=hess(2*p-1:2*p,:)+(res*eta(2*q-1:2*q,:)-res1*x(2*q-1:2*q,:))*C'; - */ - - /* (res*E2-res1*G2)*C' */ - amb(res,E2,T1); - amb(res1,G2,T2); - T1[0]-=T2[0]; - T1[1]-=T2[1]; - T1[2]-=T2[2]; - T1[3]-=T2[3]; - ambt(T1,C,T2); - - /* multiply by baseline weight */ - T2[0]=T2[0]*t->wtd[ci]; - T2[1]=T2[1]*t->wtd[ci]; - T2[2]=T2[2]*t->wtd[ci]; - T2[3]=T2[3]*t->wtd[ci]; - - pthread_mutex_lock(&t->mx_array[sta1]); - t->hess[2*sta1]+=T2[0]; - t->hess[2*sta1+2*t->N]+=T2[1]; - t->hess[2*sta1+1]+=T2[2]; - t->hess[2*sta1+2*t->N+1]+=T2[3]; - pthread_mutex_unlock(&t->mx_array[sta1]); - - - /* - hess(2*q-1:2*q,:)=hess(2*q-1:2*q,:)+(res'*eta(2*p-1:2*p,:)-res1'*x(2*p-1:2*p,:))*C; - */ - - /* (res'*E1-res1'*G1)*C */ - atmb(res,E1,T1); - atmb(res1,G1,T2); - T1[0]-=T2[0]; - T1[1]-=T2[1]; - T1[2]-=T2[2]; - T1[3]-=T2[3]; - amb(T1,C,T2); - - /* multiply by baseline weight */ - T2[0]=T2[0]*t->wtd[ci]; - T2[1]=T2[1]*t->wtd[ci]; - T2[2]=T2[2]*t->wtd[ci]; - T2[3]=T2[3]*t->wtd[ci]; - - pthread_mutex_lock(&t->mx_array[sta2]); - t->hess[2*sta2]+=T2[0]; - t->hess[2*sta2+2*t->N]+=T2[1]; - t->hess[2*sta2+1]+=T2[2]; - t->hess[2*sta2+2*t->N+1]+=T2[3]; - pthread_mutex_unlock(&t->mx_array[sta2]); - - } - } - - return NULL; -} - - - -/* Hessian function */ -/* x: 2Nx2 solution - eta: same shape as x - fhess: output, same shape as x - y: visibilities, vectorized V(:) 8*Nbase x 1 -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -fns_fhess(complex double *x, complex double *eta,complex double *fhess, double *y, global_data_rtr_t *gdata) { - - me_data_t *dp=(me_data_t*)gdata->medata; - - int nth,nth1,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthb0,Nthb; - thread_data_rtr_t *threaddata; - - int Nbase1=(dp->Nbase)*(dp->tilesz); - int boff=(dp->Nbase)*(dp->tileoff); - - /* calculate min baselines a thread can handle */ - Nthb0=(Nbase1+Nt-1)/Nt; - - if ((threaddata=(thread_data_rtr_t*)malloc((size_t)Nt*sizeof(thread_data_rtr_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - complex double *hess; - if ((hess=(complex double*)calloc((size_t)4*dp->N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr; - threaddata[nth].carr=dp->carr; - threaddata[nth].M=dp->M; - threaddata[nth].y=&(y[8*ci]); - threaddata[nth].N=dp->N; - threaddata[nth].x=x; /* note the difference: here x assumes no hybrid, also ordering different */ - threaddata[nth].clus=(dp->clus); - threaddata[nth].coh=&(dp->coh[4*(dp->M)*(ci+boff)]); - threaddata[nth].eta=eta; - threaddata[nth].hess=hess; - threaddata[nth].mx_array=gdata->mx_array; - threaddata[nth].wtd=&(gdata->wtd[ci]); /* weights for baselines */ - - //printf("thread %d predict data from %d baselines %d\n",nth,8*ci,Nthb); - pthread_create(&gdata->th_array[nth],&gdata->attr,threadfn_fns_fhess,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - /* now wait for threads to finish */ - for(nth1=0; nth1th_array[nth1],NULL); - } - -/******************* scale *************/ - Nthb0=(dp->N+Nt-1)/Nt; - ci=0; - for (nth=0; nthN; nth++) { - if (ci+Nthb0N) { - Nthb=Nthb0; - } else { - Nthb=dp->N-ci; - } - threaddata[nth].boff=ci; - threaddata[nth].Nb=Nthb; - threaddata[nth].N=dp->N; - threaddata[nth].grad=fhess; - threaddata[nth].iw=gdata->iw; - pthread_create(&gdata->th_array[nth],&gdata->attr,threadfn_fns_fscale,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - for(nth1=0; nth1th_array[nth1],NULL); - } -/******************* scale *************/ - free(threaddata); - fns_proj(dp->N,x,hess,fhess); - free(hess); - -} - - -/* truncated conjugate gradient method - x, grad, eta, r, z, delta, Hxd : size 2N x 2 complex - so, vector size is 4N complex double - - output: eta - output: fhess (can be reused in calling func) - return value: stop_tCG code - - y: vec(V) visibilities -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static int -tcg_solve(int N, complex double *x, complex double *grad, complex double *eta, complex double *fhess, - double Delta, double theta, double kappa, int max_inner, int min_inner, double *y, global_data_rtr_t *gdata) { - - complex double *r,*z,*delta,*Hxd, *rnew; - double e_Pe, r_r, norm_r, z_r, d_Pd, d_Hd, alpha, e_Pe_new, - e_Pd, Deltasq, tau, zold_rold, beta, norm_r0; - int cj, stop_tCG; - - if ((r=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((z=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((delta=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((Hxd=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((rnew=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - /* - initial values - % eta = 0*grad; << zero matrix provided - r = grad; - e_Pe = 0; - */ - my_ccopy(4*N,grad,1,r,1); - e_Pe=0.0; - - /* - r_r = fns.g(x,r,r); - norm_r = sqrt(r_r); - norm_r0 = norm_r; - */ - - r_r=fns_g(N,x,r,r); - norm_r=sqrt(r_r); - norm_r0=norm_r; - - /* - z = r; - */ - my_ccopy(4*N,r,1,z,1); - - /* - % compute z'*r - z_r = fns.g(x,z,r); - d_Pd = z_r; - */ - z_r=fns_g(N,x,z,r); - d_Pd=z_r; - - /* - % initial search direction - delta = -z; - e_Pd = fns.g(x,eta,delta); - */ - memset(delta,0,sizeof(complex double)*N*4); - my_caxpy(4*N,z,-1.0+_Complex_I*0.0,delta); - e_Pd=fns_g(N,x,eta,delta); - - /* - % pre-assume termination b/c j == end - stop_tCG = 5; - */ - stop_tCG=5; - - /* % begin inner/tCG loop - for j = 1:max_inner, - */ - for(cj=1; cj<=max_inner; cj++) { - /**************************************************/ - /* - Hxd = fns.fhess(x,delta); - - % compute curvature - d_Hd = fns.g(x,delta,Hxd); - */ - fns_fhess(x,delta,Hxd,y,gdata); - d_Hd=fns_g(N,x,delta,Hxd); - - /* - alpha = z_r/d_Hd; - e_Pe_new = e_Pe + 2.0*alpha*e_Pd + alpha*alpha*d_Pd; - */ - alpha=z_r/d_Hd; - e_Pe_new = e_Pe + 2.0*alpha*e_Pd + alpha*alpha*d_Pd; - - /* - - % check curvature and trust-region radius - if d_Hd <= 0 || e_Pe_new >= Delta^2, - - */ - Deltasq=Delta*Delta; - if (d_Hd <= 0.0 || e_Pe_new >= Deltasq) { - /* - tau = (-e_Pd + sqrt(e_Pd*e_Pd + d_Pd*(Delta^2-e_Pe))) / d_Pd; - - */ - tau = (-e_Pd + sqrt(e_Pd*e_Pd + d_Pd*(Deltasq-e_Pe)))/d_Pd; - - /* - eta = eta + tau*delta; - - */ - my_caxpy(4*N,delta,tau+_Complex_I*0.0,eta); - - /* NEW Heta = Heta + tau*Hdelta */ - my_caxpy(4*N,fhess,tau+_Complex_I*0.0,Hxd); - - /* - if d_Hd <= 0, - stop_tCG = 1; % negative curvature - else - stop_tCG = 2; % exceeded trust region - end - */ - stop_tCG=(d_Hd<=0.0?1:2); - - /* - break (for) - */ - break; - /* - end if - */ - } - - - /* - % no negative curvature and eta_prop inside TR: accept it - e_Pe = e_Pe_new; - eta = eta + alpha*delta; - - */ - e_Pe=e_Pe_new; - my_caxpy(4*N,delta,alpha+_Complex_I*0.0,eta); - - /* NEW Heta = Heta + alpha*Hdelta */ - my_caxpy(4*N,fhess,alpha+_Complex_I*0.0,Hxd); - - - /* - % update the residual - r = r + alpha*Hxd; - - */ - my_caxpy(4*N,Hxd,alpha+_Complex_I*0.0,r); - - /* - % compute new norm of r - r_r = fns.g(x,r,r); - norm_r = sqrt(r_r); - - */ - r_r=fns_g(N,x,r,r); - norm_r=sqrt(r_r); - - - /* - % check kappa/theta stopping criterion - if j >= min_inner && norm_r <= norm_r0*min(norm_r0^theta,kappa) - */ - if (cj >= min_inner) { - double norm_r0pow=pow(norm_r0,theta); - if (norm_r <= norm_r0*MIN(norm_r0pow,kappa)) { - - /* - % residual is small enough to quit - if kappa < norm_r0^theta, - stop_tCG = 3; % linear convergence - else - stop_tCG = 4; % superlinear convergence - end - - */ - stop_tCG=(kappamedata; - - double fx=fns_f(x,y,gdata); - fns_fgrad(x,eta,y,gdata,0); - double beta0=beta; - double minfx=fx; double minbeta=beta0; - double lhs,rhs,metric; - int m,nocostred=0; - double metric0=fns_g(dp->N,x,eta,eta); - *mincost=minfx; - for (m=0; m<50; m++) { - /* abeta=(beta0)*alphabar*eta; */ - my_ccopy(4*dp->N,eta,1,teta,1); - my_cscal(4*dp->N,beta0*alphabar+0.0*_Complex_I,teta); - /* Rx=R(x,teta); */ - fns_R(dp->N,x,teta,x_prop); - lhs=fns_f(x_prop,y,gdata); - if (lhsN,x,eta,teta); - rhs=fx+sigma*metric; - /* break loop also if no further cost improvement is seen */ - if (lhs<=rhs) { - minbeta=beta0; - break; - } - beta0=beta0*beta; - } - - /* if no further cost improvement is seen */ - if (lhs>fx) { - nocostred=1; - } - - my_ccopy(4*dp->N,eta,1,teta,1); - my_cscal(4*dp->N,minbeta*alphabar+0.0*_Complex_I,teta); - - return nocostred; -} - - -/* Fine tune initial trust region radius, also update initial value for x - A. Sartenaer, 1995 - returns : trust region estimate, - also modifies x - eta,Heta,s,x_prop: used as storage - */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static double -itrr(int N,complex double *x,complex double *eta, complex double *Heta, double *y, global_data_rtr_t *gdata, complex double *s, complex double *x_prop) { - - double f0,fk,mk,rho,rho1,Delta0; - - /* initialize trust region radii */ - double delta_0=1.0; - double delta_m=0.0; - - double sigma=0.0; - double delta=0.0; - - // initial cost - f0=fns_f(x,y,gdata); - // gradient at x0 - fns_fgrad(x,eta,y,gdata,1); - //normalize - double eta_nrm=my_cnrm2(4*N,eta); - my_cscal(4*N, 1.0/eta_nrm+0.0*_Complex_I, eta); - - my_ccopy(4*N,eta,1,s,1); - my_cscal(4*N, delta_0+0.0*_Complex_I, s); - //Hessian at s - fns_fhess(x,s,Heta,y,gdata); - - /* constants used */ - double gamma_1=0.0625; double gamma_2=5.0; double gamma_3=0.5; double gamma_4=2.0; - double mu_0=0.5; double mu_1=0.5; double mu_2=0.35; - double teta=0.25; - - - int m,MK=4; - for (m=0; mdelta) { - delta=f0-fk; - sigma=delta_0; - } - /* radius update */ - double beta_1,beta_2,beta_i; - beta_1=0.0; - beta_2=0.0; - if (mmu_1) { - if (minbeta>1.0) { - beta_i=gamma_3; - } else if ((maxbeta=1.0)) { - beta_i=gamma_1; - } else if ((beta_1>=gamma_1 && beta_1<1.0) && (beta_2=1.0)) { - beta_i=beta_1; - } else if ((beta_2>=gamma_1 && beta_2<1.0) && (beta_1=1.0)) { - beta_i=beta_2; - } else { - beta_i=maxbeta; - } - } else if (rho1<=mu_2) { - if (maxbeta<1.0) { - beta_i=gamma_4; - } else if (maxbeta>gamma_2) { - beta_i=gamma_2; - } else if ((beta_1>=1.0 && beta_1<=gamma_2) && beta_2<1.0) { - beta_i=beta_1; - } else if ((beta_2>=1.0 && beta_2<=gamma_2) && beta_1<1.0) { - beta_i=beta_2; - } else { - beta_i=maxbeta; - } - } else { - if (maxbetagamma_4) { - beta_i=gamma_4; - } else { - beta_i=maxbeta; - } - } - /* update radius */ - delta_0=delta_0/beta_i; - } - -#ifdef DEBUG -printf("m=%d delta_0=%e delta_max=%e beta=%e rho=%e\n",m,delta_0,delta_m,beta_i,rho); -#endif - - my_ccopy(4*N,eta,1,s,1); - my_cscal(4*N,delta_0+0.0*_Complex_I, s); - } - - - // update initial value - if (delta>0.0) { - my_caxpy(4*N, eta, -sigma+0.0*_Complex_I, x); - } - - if (delta_m>0.0) { - Delta0=delta_m; - } else { - Delta0=delta_0; - } - - return Delta0; -} - - - -int -rtr_solve_nocuda_robust( - double *x0, /* initial values and updated solution at output (size 8*N double) */ - double *y, /* data vector (size 8*M double) */ - int N, /* no. of stations */ - int M, /* no. of constraints */ - int itmax_rsd, /* maximum number of iterations RSD */ - int itmax_rtr, /* maximum number of iterations RTR */ - double Delta_bar, double Delta0, /* Trust region radius and initial value */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - double *info, /* initial and final residuals */ - me_data_t *adata) { /* pointer to additional data */ - - /* reshape x to make J: 2Nx2 complex double - */ - complex double *x; - if ((x=(complex double*)malloc((size_t)4*N*sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - /* map x: [(re,im)J_1(0,0) (re,im)J_1(0,1) (re,im)J_1(1,0) (re,im)J_1(1,1)...] - to - J: [J_1(0,0) J_1(1,0) J_2(0,0) J_2(1,0) ..... J_1(0,1) J_1(1,1) J_2(0,1) J_2(1,1)....] - */ - double *Jd=(double*)x; - /* re J(0,0) */ - my_dcopy(N, &x0[0], 8, &Jd[0], 4); - /* im J(0,0) */ - my_dcopy(N, &x0[1], 8, &Jd[1], 4); - /* re J(1,0) */ - my_dcopy(N, &x0[4], 8, &Jd[2], 4); - /* im J(1,0) */ - my_dcopy(N, &x0[5], 8, &Jd[3], 4); - /* re J(0,1) */ - my_dcopy(N, &x0[2], 8, &Jd[4*N], 4); - /* im J(0,1) */ - my_dcopy(N, &x0[3], 8, &Jd[4*N+1], 4); - /* re J(1,1) */ - my_dcopy(N, &x0[6], 8, &Jd[4*N+2], 4); - /* im J(1,1) */ - my_dcopy(N, &x0[7], 8, &Jd[4*N+3], 4); - - - - int Nt=adata->Nt; - int ci; - global_data_rtr_t gdata; - - gdata.medata=adata; - /* setup threads */ - pthread_attr_init(&gdata.attr); - pthread_attr_setdetachstate(&gdata.attr,PTHREAD_CREATE_JOINABLE); - - if ((gdata.th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - if ((gdata.mx_array=(pthread_mutex_t*)malloc((size_t)N*sizeof(pthread_mutex_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((gdata.iw=(double*)malloc((size_t)N*sizeof(double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - /* weights for robust LS, length could be less than total no of baselines - therefore use relative offset boff */ - if ((gdata.wtd=(double*)malloc((size_t)M*sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - - for (ci=0; cistation contributions - NOTE: has to be done here because the baseline offset would change */ - fns_fcount(&gdata); -/***************************************************/ - int min_inner,max_inner,min_outer,max_outer; - double epsilon,kappa,theta,rho_prime; - /* - min_inner = 0; - max_inner = inf; - min_outer = 3; - max_outer = 100; - epsilon = 1e-6; - kappa = 0.1; - theta = 1.0; - rho_prime = 0.1; - %Delta_bar = user must specify - %Delta0 = user must specify - %x0 = user must specify - */ - min_inner=1; max_inner=itmax_rtr;//8*N; - min_outer=3; max_outer=itmax_rtr; - epsilon=CLM_EPSILON; - kappa=0.1; - theta=1.0; - /* default values 0.25, 0.75, 0.25, 2.0 */ - double eta1=0.0001; double eta2=0.99; double alpha1=0.25; double alpha2=3.5; - rho_prime=eta1; /* default 0.1 should be <= 0.25 */ - double rho_regularization; /* use large damping (but less than GPU version) */ - double rho_reg; - int model_decreased=0; - - complex double *fgradx,*eta,*Heta,*x_prop; - if ((fgradx=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((eta=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((Heta=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((x_prop=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - /*set initial weights to 1 */ - setweights(M,gdata.wtd,1.0,Nt); - gdata.nulow=robust_nulow; - gdata.nuhigh=robust_nuhigh; - - double fx,norm_grad,Delta,fx_prop,rhonum,rhoden,rho; - fx=fns_f(x,y,&gdata); - double fx0=fx; - int rsdstat=0; -/***************************************************/ - /* RSD solution */ - //for (ci=0; cirobust_nu,robust_nu1); - adata->robust_nu=robust_nu1; -/***************************************************/ - /* - % initialize counters/sentinals - % allocate storage for dist, counters - k = 0; % counter for outer (TR) iteration. - stop_outer = 0; % stopping criterion for TR. - - x = x0; -fx = fns.f(x); -fgradx = fns.fgrad(x); -norm_grad = sqrt(fns.g(x,fgradx,fgradx)); - -% initialize trust-region radius -Delta = Delta0; - */ - int k=0; - int stop_outer=(itmax_rtr>0?0:1); - int stop_inner=0; - // x0 is already copied to x: my_ccopy(4*N,x0,1,x,1); - if(!stop_outer) { - fns_fgrad(x,fgradx,y,&gdata,1); - norm_grad = sqrt(fns_g(N,x,fgradx,fgradx)); - } - Delta=Delta0; - - /* initial residual */ - info[0]=fx; - - /* - % ** Start of TR loop ** - while stop_outer==0, - */ - while(!stop_outer) { - /* - % update counter - k = k+1; - */ - k++; - - - /* - ** Begin TR Subproblem ** - % determine eta0 - % without randT, 0*fgradx is the only way that we - % know how to create a tangent vector - eta = 0*fgradx; - */ - memset(eta,0,sizeof(complex double)*N*4); - - - /* - % solve TR subproblem - [eta,numit,stop_inner] = tCG(fns,x,fgradx,eta,Delta,theta,kappa,min_inner,max_inner,useRand,debug); - */ - stop_inner=tcg_solve(N, x, fgradx, eta, Heta, Delta, theta, kappa, max_inner, min_inner,y,&gdata); - - /* - norm_eta = sqrt(fns.g(x,eta,eta)); - */ - - /* - Heta = fns.fhess(x,eta); - */ - //OLD fns_fhess(x,eta,Heta,y,&gdata); - - /* - % compute the retraction of the proposal - x_prop = fns.R(x,eta); - */ - fns_R(N,x,eta,x_prop); - - /* - % compute function value of the proposal - fx_prop = fns.f(x_prop); - */ - fx_prop=fns_f(x_prop,y,&gdata); - - /* - % do we accept the proposed solution or not? - % compute the Hessian at the proposal - Heta = fns.fhess(x,eta); - FIXME: do we need to do this, because Heta is already there - or change x to x_prop ??? - */ - //Disabled fns_fhess(x,eta,Heta,y,&gdata); - - /* - % check the performance of the quadratic model - rhonum = fx-fx_prop; - rhoden = -fns.g(x,fgradx,eta) - 0.5*fns.g(x,Heta,eta); - */ - rhonum=fx-fx_prop; - rhoden=-fns_g(N,x,fgradx,eta)-0.5*fns_g(N,x,Heta,eta); - /* regularization of rho ratio */ - /* - rho_reg = max(1, abs(fx)) * eps * options.rho_regularization; - rhonum = rhonum + rho_reg; - rhoden = rhoden + rho_reg; - */ - rho_reg=MAX(1.0,fx)*rho_regularization; /* no epsilon */ - rhonum+=rho_reg; - rhoden+=rho_reg; - - - /* - rho = rhonum / rhoden; - */ - rho=rhonum/rhoden; - - - - /* - % HEURISTIC WARNING: - % if abs(model change) is relatively zero, we are probably near a critical - % point. set rho to 1. - if abs(rhonum/fx) < sqrt(eps), - small_rhonum = rhonum; - rho = 1; - else - small_rhonum = 0; - end - FIXME: use constant for sqrt(eps) - */ - /* OLD CODE if (fabs(rhonum/fx) = 0); */ - model_decreased=(rhoden>=0.0?1:0); - - /* NOTE: if too many values of rho are -ve, it means TR radius is too big - so initial TR radius should be reduced */ -#ifdef DEBUG - printf("stop_inner=%d rho_reg=%g rho =%g/%g= %g rho'= %g\n",stop_inner,rho_reg,rhonum,rhoden,rho,rho_prime); -#endif - - /* - % choose new TR radius based on performance - if rho < 1/4 - Delta = 1/4*Delta; - elseif rho > 3/4 && (stop_inner == 2 || stop_inner == 1), - Delta = min(2*Delta,Delta_bar); - end - */ - if (!model_decreased || rhoeta2 && (stop_inner==2 || stop_inner==1)) { - /* we have a good reduction, so increase TR radius */ - Delta=MIN(alpha2*Delta,Delta_bar); - } - - /* - % choose new iterate based on performance - oldgradx = fgradx; - if rho > rho_prime, - accept = true; - x = x_prop; - fx = fx_prop; - fgradx = fns.fgrad(x); - norm_grad = sqrt(fns.g(x,fgradx,fgradx)); - else - accept = false; - end - */ - if (model_decreased && rho>rho_prime) { - my_ccopy(4*N,x_prop,1,x,1); - fx=fx_prop; - fns_fgrad(x,fgradx,y,&gdata,1); - norm_grad=sqrt(fns_g(N,x,fgradx,fgradx)); - } - - - /* - % ** Testing for Stop Criteria - % min_outer is the minimum number of inner iterations - % before we can exit. this gives randomization a chance to - % escape a saddle point. - if norm_grad < epsilon && (~useRand || k > min_outer), - stop_outer = 1; - end - */ - if (norm_gradmin_outer) { - stop_outer=1; - } - - - /* - % stop after max_outer iterations - if k >= max_outer, - if (verbosity > 0), - fprintf('\n*** timed out -- k == %d***\n',k); - end - stop_outer = 1; - end - */ - if (k>=max_outer) { - stop_outer=1; - } - -#ifdef DEBUG - printf("Iter %d cost=%lf\n",k,fx); -#endif - /* end of TR loop (counter: k) */ - } - - /* final residual */ - info[1]=fx; - - free(fgradx); - free(eta); - free(Heta); - free(x_prop); -/***************************************************/ - robust_nu1=fns_fupdate_weights(x,y,&gdata); - adata->robust_nu=robust_nu1; - if (fx0>fx) { - /* copy back solution to x0 */ - /* re J(0,0) */ - my_dcopy(N, &Jd[0], 4, &x0[0], 8); - /* im J(0,0) */ - my_dcopy(N, &Jd[1], 4, &x0[1], 8); - /* re J(1,0) */ - my_dcopy(N, &Jd[2], 4, &x0[4], 8); - /* im J(1,0) */ - my_dcopy(N, &Jd[3], 4, &x0[5], 8); - /* re J(0,1) */ - my_dcopy(N, &Jd[4*N], 4, &x0[2], 8); - /* im J(0,1) */ - my_dcopy(N, &Jd[4*N+1], 4, &x0[3], 8); - /* re J(1,1) */ - my_dcopy(N, &Jd[4*N+2], 4, &x0[6], 8); - /* im J(1,1) */ - my_dcopy(N, &Jd[4*N+3], 4, &x0[7], 8); - } - - for (ci=0; ciNt; - int ci; - global_data_rtr_t gdata; - - gdata.medata=adata; - /* setup threads */ - pthread_attr_init(&gdata.attr); - pthread_attr_setdetachstate(&gdata.attr,PTHREAD_CREATE_JOINABLE); - - if ((gdata.th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - if ((gdata.mx_array=(pthread_mutex_t*)malloc((size_t)N*sizeof(pthread_mutex_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((gdata.iw=(double*)malloc((size_t)N*sizeof(double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - /* weights for robust LS, length could be less than total no of baselines - therefore use relative offset boff */ - if ((gdata.wtd=(double*)malloc((size_t)M*sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - - for (ci=0; cistation contributions - NOTE: has to be done here because the baseline offset would change */ - fns_fcount(&gdata); -/***************************************************/ - complex double *fgradx,*eta,*z,*x_prop,*z_prop; - if ((fgradx=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((eta=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((z=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((x_prop=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((z_prop=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - /*set initial weights to 1 */ - setweights(M,gdata.wtd,1.0,Nt); - gdata.nulow=robust_nulow; - gdata.nuhigh=robust_nuhigh; - - double fx; - fx=fns_f(x,y,&gdata); - double fx0=fx; -/***************************************************/ - /* gradient at x0 */ - fns_fgrad(x,fgradx,y,&gdata,1); - /* Hessian at x0,x0 */ - fns_fhess(x,x,z,y,&gdata); - /* intial step ~ 1/||Hessian|| */ - double hess_nrm=my_cnrm2(4*N,z); - double t=1.0/hess_nrm; - /* if initial step too small */ - if (t<1e-6) { - t=1e-6; - } - - /* z <= x */ - my_ccopy(4*N,x,1,z,1); - double theta=1.0; - double ALPHA=1.01; /* step-size growth factor */ - double BETA=0.5; /* step-size shrinkage factor */ - - int k; - for (k=0; krobust_nu=robust_nu1; - if (fx0>fx) { - /* copy back solution to x0 */ - /* re J(0,0) */ - my_dcopy(N, &Jd[0], 4, &x0[0], 8); - /* im J(0,0) */ - my_dcopy(N, &Jd[1], 4, &x0[1], 8); - /* re J(1,0) */ - my_dcopy(N, &Jd[2], 4, &x0[4], 8); - /* im J(1,0) */ - my_dcopy(N, &Jd[3], 4, &x0[5], 8); - /* re J(0,1) */ - my_dcopy(N, &Jd[4*N], 4, &x0[2], 8); - /* im J(0,1) */ - my_dcopy(N, &Jd[4*N+1], 4, &x0[3], 8); - /* re J(1,1) */ - my_dcopy(N, &Jd[4*N+2], 4, &x0[6], 8); - /* im J(1,1) */ - my_dcopy(N, &Jd[4*N+3], 4, &x0[7], 8); - } - - for (ci=0; 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 W4h~*bmFC;=anGH|mf-HW3jYUFE7HRN diff --git a/src/lib/Solvers/rtr_solve_robust_admm.c b/src/lib/Solvers/rtr_solve_robust_admm.c deleted file mode 100644 index 3eb7c3c..0000000 --- a/src/lib/Solvers/rtr_solve_robust_admm.c +++ /dev/null @@ -1,2009 +0,0 @@ -/* - * - Copyright (C) 2014 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#include "Solvers.h" - -//#define DEBUG -/* Jones matrix multiplication - C=A*B -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -amb(complex double * __restrict a, complex double * __restrict b, complex double * __restrict c) { - c[0]=a[0]*b[0]+a[1]*b[2]; - c[1]=a[0]*b[1]+a[1]*b[3]; - c[2]=a[2]*b[0]+a[3]*b[2]; - c[3]=a[2]*b[1]+a[3]*b[3]; -} - -/* Jones matrix multiplication - C=A*B^H -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -ambt(complex double * __restrict a, complex double * __restrict b, complex double * __restrict c) { - c[0]=a[0]*conj(b[0])+a[1]*conj(b[1]); - c[1]=a[0]*conj(b[2])+a[1]*conj(b[3]); - c[2]=a[2]*conj(b[0])+a[3]*conj(b[1]); - c[3]=a[2]*conj(b[2])+a[3]*conj(b[3]); -} - -/* Jones matrix multiplication - C=A^H*B -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -atmb(complex double * __restrict a, complex double * __restrict b, complex double * __restrict c) { - c[0]=conj(a[0])*b[0]+conj(a[2])*b[2]; - c[1]=conj(a[0])*b[1]+conj(a[2])*b[3]; - c[2]=conj(a[1])*b[0]+conj(a[3])*b[2]; - c[3]=conj(a[1])*b[1]+conj(a[3])*b[3]; -} - - -/* worker thread function for counting */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -threadfn_fns_fcount(void *data) { - thread_data_rtr_t *t=(thread_data_rtr_t*)data; - - int ci,sta1,sta2; - for (ci=0; ciNb; ci++) { - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - - pthread_mutex_lock(&t->mx_array[sta1]); - t->bcount[sta1]+=1; - pthread_mutex_unlock(&t->mx_array[sta1]); - - pthread_mutex_lock(&t->mx_array[sta2]); - t->bcount[sta2]+=1; - pthread_mutex_unlock(&t->mx_array[sta2]); - } - } - - return NULL; -} - - -/* function to count how many baselines contribute to the calculation of - grad and hess, so normalization can be made */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -fns_fcount(global_data_rtr_t *gdata) { - - me_data_t *dp=(me_data_t*)gdata->medata; - - int nth,nth1,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthb0,Nthb; - thread_data_rtr_t *threaddata; - - int Nbase1=(dp->Nbase)*(dp->tilesz); - int boff=(dp->Nbase)*(dp->tileoff); - - /* calculate min baselines a thread can handle */ - Nthb0=(Nbase1+Nt-1)/Nt; - - if ((threaddata=(thread_data_rtr_t*)malloc((size_t)Nt*sizeof(thread_data_rtr_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - int *bcount; - if ((bcount=(int*)calloc((size_t)dp->N,sizeof(int)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr; - threaddata[nth].bcount=bcount; /* note this should be 0 first */ - threaddata[nth].mx_array=gdata->mx_array; - - pthread_create(&gdata->th_array[nth],&gdata->attr,threadfn_fns_fcount,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - /* now wait for threads to finish */ - for(nth1=0; nth1th_array[nth1],NULL); - } - free(threaddata); - - /* calculate inverse count */ - for (nth1=0; nth1N; nth1++) { - gdata->iw[nth1]=(bcount[nth1]>0?1.0/(double)bcount[nth1]:0.0); - } - free(bcount); - /* scale back weight such that max value is 1 */ - nth1=my_idamax(dp->N,gdata->iw,1); - double maxw=gdata->iw[nth1-1]; /* 1 based index */ - if (maxw>0.0) { /* all baselines can be flagged */ - my_dscal(dp->N,1.0/maxw,gdata->iw); - } -} - - - -/* worker thread function for cost function calculation */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -threadfn_fns_f(void *data) { - thread_data_rtr_t *t=(thread_data_rtr_t*)data; - - int ci,cm,sta1,sta2; - complex double C[4],G1[4],G2[4],T1[4],T2[4]; - int M=(t->M); - cm=(t->clus); - for (ci=0; ciNb; ci++) { - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - /* gains for this cluster, for sta1,sta2 */ - G1[0]=(t->x[sta1*2]); - G1[1]=(t->x[sta1*2+2*t->N]); - G1[2]=(t->x[sta1*2+1]); - G1[3]=(t->x[sta1*2+2*t->N+1]); - G2[0]=(t->x[sta2*2]); - G2[1]=(t->x[sta2*2+2*t->N]); - G2[2]=(t->x[sta2*2+1]); - G2[3]=(t->x[sta2*2+2*t->N+1]); - - /* use pre calculated values */ - C[0]=t->coh[4*M*ci+4*cm]; - C[1]=t->coh[4*M*ci+4*cm+1]; - C[2]=t->coh[4*M*ci+4*cm+2]; - C[3]=t->coh[4*M*ci+4*cm+3]; - - - /* form G1*C*G2' */ - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - - /* add to baseline visibilities V->U */ - double r00=t->y[8*ci]-creal(T2[0]); - double i00=t->y[8*ci+1]-cimag(T2[0]); - double r01=t->y[8*ci+2]-creal(T2[1]); - double i01=t->y[8*ci+3]-cimag(T2[1]); - double r10=t->y[8*ci+4]-creal(T2[2]); - double i10=t->y[8*ci+5]-cimag(T2[2]); - double r11=t->y[8*ci+6]-creal(T2[3]); - double i11=t->y[8*ci+7]-cimag(T2[3]); - - t->fcost+=t->wtd[ci]*(r00*r00+i00*i00+r01*r01+i01*i01+r10*r10+i10*i10+r11*r11+i11*i11); - } - } - - return NULL; -} - - -/* cost function */ -/* x: 2Nx2 solution - y: visibilities, vectorized V(:) 8*Nbase x 1 -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static double -fns_f(complex double *x, double *y, global_data_rtr_t *gdata) { - - me_data_t *dp=(me_data_t*)gdata->medata; - - int nth,nth1,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthb0,Nthb; - thread_data_rtr_t *threaddata; - - int Nbase1=(dp->Nbase)*(dp->tilesz); - int boff=(dp->Nbase)*(dp->tileoff); - - /* calculate min baselines a thread can handle */ - Nthb0=(Nbase1+Nt-1)/Nt; - - if ((threaddata=(thread_data_rtr_t*)malloc((size_t)Nt*sizeof(thread_data_rtr_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr; - threaddata[nth].carr=dp->carr; - threaddata[nth].M=dp->M; - threaddata[nth].y=&(y[8*ci]); - threaddata[nth].N=dp->N; - threaddata[nth].x=x; /* note the difference: here x assumes no hybrid, also ordering different */ - threaddata[nth].clus=(dp->clus); - threaddata[nth].coh=&(dp->coh[4*(dp->M)*(ci+boff)]); - threaddata[nth].fcost=0.0; - - threaddata[nth].wtd=&(gdata->wtd[ci]); /* weights for baselines */ - - //printf("thread %d predict data from %d baselines %d\n",nth,8*ci,Nthb); - pthread_create(&gdata->th_array[nth],&gdata->attr,threadfn_fns_f,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - /* now wait for threads to finish */ - double fcost=0.0; - for(nth1=0; nth1th_array[nth1],NULL); - fcost+=threaddata[nth1].fcost; - } - - free(threaddata); - /* add ||Y^H(J-BZ)|| + rho/2 ||J-BZ||^2 */ - complex double *Yd; - if ((Yd=(complex double*)malloc((size_t)4*dp->N*sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - /* Yd=J-BZ */ - my_ccopy(4*dp->N,x,1,Yd,1); - my_caxpy(4*dp->N,gdata->BZ,-1.0,Yd); - - /* ||Y^H Yd|| = 2 real(Y(:)^H Yd(:)) */ - fcost+=2.0*creal(my_cdot(4*dp->N,gdata->Y,Yd)); - - /* rho/2 ||J-BZ||^2 = rho/2 real(Yd(:)^H Yd(:)) */ - fcost+=0.5*gdata->admm_rho*creal(my_cdot(4*dp->N,Yd,Yd)); - - free(Yd); - - - return fcost; -} - - -/* worker thread function for weight update (nu+p)/(nu+error^2) */ -/* p=2, not p=8 because using MAX() not sum for error^2 */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -threadfn_fns_fupdate_weights(void *data) { - thread_data_rtr_t *t=(thread_data_rtr_t*)data; - - int ci,cm,sta1,sta2; - complex double C[4],G1[4],G2[4],T1[4],T2[4]; - int M=(t->M); - cm=(t->clus); - for (ci=0; ciNb; ci++) { - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - /* gains for this cluster, for sta1,sta2 */ - G1[0]=(t->x[sta1*2]); - G1[1]=(t->x[sta1*2+2*t->N]); - G1[2]=(t->x[sta1*2+1]); - G1[3]=(t->x[sta1*2+2*t->N+1]); - G2[0]=(t->x[sta2*2]); - G2[1]=(t->x[sta2*2+2*t->N]); - G2[2]=(t->x[sta2*2+1]); - G2[3]=(t->x[sta2*2+2*t->N+1]); - - /* use pre calculated values */ - C[0]=t->coh[4*M*ci+4*cm]; - C[1]=t->coh[4*M*ci+4*cm+1]; - C[2]=t->coh[4*M*ci+4*cm+2]; - C[3]=t->coh[4*M*ci+4*cm+3]; - - - /* form G1*C*G2' */ - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - - /* add to baseline visibilities V->U */ - double r00=t->y[8*ci]-creal(T2[0]); - double i00=t->y[8*ci+1]-cimag(T2[0]); - double r01=t->y[8*ci+2]-creal(T2[1]); - double i01=t->y[8*ci+3]-cimag(T2[1]); - double r10=t->y[8*ci+4]-creal(T2[2]); - double i10=t->y[8*ci+5]-cimag(T2[2]); - double r11=t->y[8*ci+6]-creal(T2[3]); - double i11=t->y[8*ci+7]-cimag(T2[3]); - - //t->wtd[ci] = (t->nu0+8.0)/(t->nu0+(r00*r00+i00*i00+r01*r01+i01*i01+r10*r10+i10*i10+r11*r11+i11*i11)); - t->wtd[ci] = (t->nu0+2.0)/(t->nu0+MAX(r00*r00+i00*i00,MAX(r01*r01+i01*i01,MAX(r10*r10+i10*i10,r11*r11+i11*i11)))); - } - } - - return NULL; -} - - -/* calculate log(w_i) - w_i */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -threadfn_fns_flogsum_weights(void *data) { - thread_data_rtr_t *t=(thread_data_rtr_t*)data; - - int ci; - for (ci=0; ciNb; ci++) { - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - t->fcost+=log(t->wtd[ci])-t->wtd[ci]; - } - } - - return NULL; -} - - - -/* calculate weight w = (nu+1)/(nu+error^2) per baseline - then update robust_nu */ -/* x: 2Nx2 solution - y: visibilities, vectorized V(:) 8*Nbase x 1 - returns updated value for robust_nu -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static double -fns_fupdate_weights(complex double *x, double *y, global_data_rtr_t *gdata) { - - me_data_t *dp=(me_data_t*)gdata->medata; - - int nth,nth1,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthb0,Nthb; - thread_data_rtr_t *threaddata; - - int Nbase1=(dp->Nbase)*(dp->tilesz); - int boff=(dp->Nbase)*(dp->tileoff); - - /* calculate min baselines a thread can handle */ - Nthb0=(Nbase1+Nt-1)/Nt; - - if ((threaddata=(thread_data_rtr_t*)malloc((size_t)Nt*sizeof(thread_data_rtr_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr; - threaddata[nth].carr=dp->carr; - threaddata[nth].M=dp->M; - threaddata[nth].y=&(y[8*ci]); - threaddata[nth].N=dp->N; - threaddata[nth].x=x; /* note the difference: here x assumes no hybrid, also ordering different */ - threaddata[nth].clus=(dp->clus); - threaddata[nth].coh=&(dp->coh[4*(dp->M)*(ci+boff)]); - - threaddata[nth].wtd=&(gdata->wtd[ci]); /* weights for baselines */ - threaddata[nth].nu0=dp->robust_nu; - - //printf("thread %d predict data from %d baselines %d\n",nth,8*ci,Nthb); - pthread_create(&gdata->th_array[nth],&gdata->attr,threadfn_fns_fupdate_weights,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - for(nth1=0; nth1th_array[nth1],NULL); - } - - /* now calculate sum( ln(w_i)-w_i ) */ - ci=0; - for (nth1=0; nth1th_array[nth1],&gdata->attr,threadfn_fns_flogsum_weights,(void*)(&threaddata[nth1])); - /* next baseline set */ - } - - double sumlogw=0.0; - for(nth1=0; nth1th_array[nth1],NULL); - } - sumlogw/=(double)Nbase1; - free(threaddata); - - /* find new value for nu, p-variate T dist, p=8 (update p=2 because using MAX() for residual calculation, not sum) */ - /* psi((nu_old+p)/2)-ln((nu_old+p)/2)-psi(nu/2)+ln(nu/2)+1/N sum(ln(w_i)-w_i) +1 = 0, AECM */ - double nu1=update_nu(sumlogw, 30, Nt, gdata->nulow, gdata->nuhigh, 2, dp->robust_nu); - /* make sure new value stays within bounds */ - if (nu1nulow) { return gdata->nulow; } - if (nu1>gdata->nuhigh) { return gdata->nuhigh; } - - return nu1; -} - - - -/* inner product (metric) */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static double -fns_g(int N,complex double *x, complex double *eta, complex double *gamma) { - /* 2 x real( trace(eta'*gamma) ) - = 2 x real( eta(:,1)'*gamma(:,1) + eta(:,2)'*gamma(:,2) ) - no need to calculate off diagonal terms - )*/ - complex double v1=my_cdot(2*N,eta,gamma); - complex double v2=my_cdot(2*N,&eta[2*N],&gamma[2*N]); - - return 2.0*creal(v1+v2); -} - -/* Projection - rnew: new value */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -fns_proj(int N,complex double *x, complex double *z,complex double *rnew) { - /* projection = Z, since Euclidean space - */ - my_ccopy(4*N,z,1,rnew,1); -} - - -/* Retraction - rnew: new value */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -fns_R(int N,complex double *x, complex double *r,complex double *rnew) { - /* rnew = x + r */ - my_ccopy(4*N,x,1,rnew,1); - my_caxpy(4*N,r,1.0+_Complex_I*0.0,rnew); -} - - -/* worker thread function for gradient/hessian weighting */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -threadfn_fns_fscale(void *data) { - thread_data_rtr_t *t=(thread_data_rtr_t*)data; - - int ci,sta1; - for (ci=0; ciNb; ci++) { - sta1=ci+t->boff; - t->grad[2*sta1]*=t->iw[sta1]; - t->grad[2*sta1+2*t->N]*=t->iw[sta1]; - t->grad[2*sta1+1]*=t->iw[sta1]; - t->grad[2*sta1+2*t->N+1]*=t->iw[sta1]; - } - - return NULL; -} - - - - -/* worker thread function for gradient calculation */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -threadfn_fns_fgrad(void *data) { - thread_data_rtr_t *t=(thread_data_rtr_t*)data; - - int ci,cm,sta1,sta2; - complex double C[4],G1[4],G2[4],T1[4],T2[4],res[4]; - int M=(t->M); - cm=(t->clus); - for (ci=0; ciNb; ci++) { - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - /* gains for this cluster, for sta1,sta2 */ - G1[0]=(t->x[sta1*2]); - G1[1]=(t->x[sta1*2+2*t->N]); - G1[2]=(t->x[sta1*2+1]); - G1[3]=(t->x[sta1*2+2*t->N+1]); - G2[0]=(t->x[sta2*2]); - G2[1]=(t->x[sta2*2+2*t->N]); - G2[2]=(t->x[sta2*2+1]); - G2[3]=(t->x[sta2*2+2*t->N+1]); - - /* use pre calculated values */ - C[0]=t->coh[4*M*ci+4*cm]; - C[1]=t->coh[4*M*ci+4*cm+1]; - C[2]=t->coh[4*M*ci+4*cm+2]; - C[3]=t->coh[4*M*ci+4*cm+3]; - - - /* G1*C*G2' */ - amb(G1,C,T1); - ambt(T1,G2,T2); - - /* res=V(2*ck-1:2*ck,:)-x(2*p-1:2*p,:)*C*x(2*q-1:2*q,:)'; */ - /* V->U */ - res[0]=(t->y[8*ci]+_Complex_I*t->y[8*ci+1])-T2[0]; - res[1]=(t->y[8*ci+2]+_Complex_I*t->y[8*ci+3])-T2[1]; - res[2]=(t->y[8*ci+4]+_Complex_I*t->y[8*ci+5])-T2[2]; - res[3]=(t->y[8*ci+6]+_Complex_I*t->y[8*ci+7])-T2[3]; - - /* - grad(2*p-1:2*p,:)=grad(2*p-1:2*p,:)+res*x(2*q-1:2*q,:)*C'; - grad(2*q-1:2*q,:)=grad(2*q-1:2*q,:)+res'*x(2*p-1:2*p,:)*C; - */ - /* res*G2*C' */ - amb(res,G2,T1); - ambt(T1,C,T2); - - /* multiply by baseline weight */ - T2[0]=T2[0]*t->wtd[ci]; - T2[1]=T2[1]*t->wtd[ci]; - T2[2]=T2[2]*t->wtd[ci]; - T2[3]=T2[3]*t->wtd[ci]; - - pthread_mutex_lock(&t->mx_array[sta1]); - t->grad[2*sta1]+=T2[0]; - t->grad[2*sta1+2*t->N]+=T2[1]; - t->grad[2*sta1+1]+=T2[2]; - t->grad[2*sta1+2*t->N+1]+=T2[3]; - pthread_mutex_unlock(&t->mx_array[sta1]); - - /* res'*G1*C */ - atmb(res,G1,T1); - amb(T1,C,T2); - - /* multiply by baseline weight */ - T2[0]=T2[0]*t->wtd[ci]; - T2[1]=T2[1]*t->wtd[ci]; - T2[2]=T2[2]*t->wtd[ci]; - T2[3]=T2[3]*t->wtd[ci]; - - pthread_mutex_lock(&t->mx_array[sta2]); - t->grad[2*sta2]+=T2[0]; - t->grad[2*sta2+2*t->N]+=T2[1]; - t->grad[2*sta2+1]+=T2[2]; - t->grad[2*sta2+2*t->N+1]+=T2[3]; - pthread_mutex_unlock(&t->mx_array[sta2]); - - } - } - - return NULL; -} - - - -/* gradient function */ -/* x: 2Nx2 solution - fgradx: output, same shape as x - y: visibilities, vectorized V(:) 8*Nbase x 1 - if negate==1, return -grad, else just grad -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -fns_fgrad(complex double *x, complex double *fgradx, double *y, global_data_rtr_t *gdata, int negate) { - - me_data_t *dp=(me_data_t*)gdata->medata; - - int nth,nth1,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthb0,Nthb; - thread_data_rtr_t *threaddata; - - int Nbase1=(dp->Nbase)*(dp->tilesz); - int boff=(dp->Nbase)*(dp->tileoff); - - /* calculate min baselines a thread can handle */ - Nthb0=(Nbase1+Nt-1)/Nt; - - if ((threaddata=(thread_data_rtr_t*)malloc((size_t)Nt*sizeof(thread_data_rtr_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - complex double *grad; - if ((grad=(complex double*)calloc((size_t)4*dp->N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr; - threaddata[nth].carr=dp->carr; - threaddata[nth].M=dp->M; - threaddata[nth].y=&(y[8*ci]); - threaddata[nth].N=dp->N; - threaddata[nth].x=x; /* note the difference: here x assumes no hybrid, also ordering different */ - threaddata[nth].clus=(dp->clus); - threaddata[nth].coh=&(dp->coh[4*(dp->M)*(ci+boff)]); - threaddata[nth].grad=grad; - threaddata[nth].mx_array=gdata->mx_array; - threaddata[nth].wtd=&(gdata->wtd[ci]); /* weights for baselines */ - - //printf("thread %d predict data from %d baselines %d\n",nth,8*ci,Nthb); - pthread_create(&gdata->th_array[nth],&gdata->attr,threadfn_fns_fgrad,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - /* now wait for threads to finish */ - for(nth1=0; nth1th_array[nth1],NULL); - } -/******************* scale *************/ - Nthb0=(dp->N+Nt-1)/Nt; - ci=0; - for (nth=0; nthN; nth++) { - if (ci+Nthb0N) { - Nthb=Nthb0; - } else { - Nthb=dp->N-ci; - } - threaddata[nth].boff=ci; - threaddata[nth].Nb=Nthb; - threaddata[nth].N=dp->N; - threaddata[nth].grad=grad; - threaddata[nth].iw=gdata->iw; - pthread_create(&gdata->th_array[nth],&gdata->attr,threadfn_fns_fscale,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - for(nth1=0; nth1th_array[nth1],NULL); - } -/******************* scale *************/ - free(threaddata); - if (negate) { - my_cscal(4*dp->N,-1.0+0.0*_Complex_I,grad); - } - - /********************/ - /* print the norms */ -// complex double *Jdiff; -// if ((Jdiff=(complex double*)calloc((size_t)4*dp->N,sizeof(complex double)))==0) { -// fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -// exit(1); -// } -// my_caxpy(4*dp->N,x,0.5*gdata->admm_rho,Jdiff); -// my_caxpy(4*dp->N,gdata->BZ,-0.5*gdata->admm_rho,Jdiff); -// printf("Norm %lf %lf %lf\n",my_cnrm2(4*dp->N,grad),0.5*my_cnrm2(4*dp->N,gdata->Y),my_cnrm2(4*dp->N,Jdiff)); -// free(Jdiff); - /********************/ - - /* extra terms 0.5*Y+0.5*rho*(J-BZ) - add to -ve grad */ - if (negate) { - my_caxpy(4*dp->N,gdata->Y,0.5,grad); - my_caxpy(4*dp->N,x,0.5*gdata->admm_rho,grad); - my_caxpy(4*dp->N,gdata->BZ,-0.5*gdata->admm_rho,grad); - } else { - my_caxpy(4*dp->N,gdata->Y,-0.5,grad); - my_caxpy(4*dp->N,x,-0.5*gdata->admm_rho,grad); - my_caxpy(4*dp->N,gdata->BZ,0.5*gdata->admm_rho,grad); - } - fns_proj(dp->N,x,grad,fgradx); - - - free(grad); - -} - - -/* worker thread function for Hessian calculation */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -threadfn_fns_fhess(void *data) { - thread_data_rtr_t *t=(thread_data_rtr_t*)data; - - int ci,cm,sta1,sta2; - complex double C[4],G1[4],G2[4],T1[4],T2[4],res[4],res1[4],E1[4],E2[4]; - int M=(t->M); - cm=(t->clus); - for (ci=0; ciNb; ci++) { - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - /* gains for this cluster, for sta1,sta2 */ - G1[0]=(t->x[sta1*2]); - G1[1]=(t->x[sta1*2+2*t->N]); - G1[2]=(t->x[sta1*2+1]); - G1[3]=(t->x[sta1*2+2*t->N+1]); - G2[0]=(t->x[sta2*2]); - G2[1]=(t->x[sta2*2+2*t->N]); - G2[2]=(t->x[sta2*2+1]); - G2[3]=(t->x[sta2*2+2*t->N+1]); - E1[0]=t->eta[2*sta1]; - E1[1]=t->eta[2*sta1+2*t->N]; - E1[2]=t->eta[2*sta1+1]; - E1[3]=t->eta[2*sta1+2*t->N+1]; - E2[0]=t->eta[2*sta2]; - E2[1]=t->eta[2*sta2+2*t->N]; - E2[2]=t->eta[2*sta2+1]; - E2[3]=t->eta[2*sta2+2*t->N+1]; - - - - /* use pre calculated values */ - C[0]=t->coh[4*M*ci+4*cm]; - C[1]=t->coh[4*M*ci+4*cm+1]; - C[2]=t->coh[4*M*ci+4*cm+2]; - C[3]=t->coh[4*M*ci+4*cm+3]; - - /* G1*C*G2' */ - amb(G1,C,T1); - ambt(T1,G2,T2); - - - /* res=V(2*ck-1:2*ck,:)-x(2*p-1:2*p,:)*C*x(2*q-1:2*q,:)'; */ - /* V->U */ - res[0]=(t->y[8*ci]+_Complex_I*t->y[8*ci+1])-T2[0]; - res[1]=(t->y[8*ci+2]+_Complex_I*t->y[8*ci+3])-T2[1]; - res[2]=(t->y[8*ci+4]+_Complex_I*t->y[8*ci+5])-T2[2]; - res[3]=(t->y[8*ci+6]+_Complex_I*t->y[8*ci+7])-T2[3]; - - - /* - res1=x(2*p-1:2*p,:)*C*eta(2*q-1:2*q,:)'+eta(2*p-1:2*p,:)*C*x(2*q-1:2*q,:)'; - */ - /* G1*C*E2' */ - amb(G1,C,T1); - ambt(T1,E2,T2); - res1[0]=T2[0]; - res1[1]=T2[1]; - res1[2]=T2[2]; - res1[3]=T2[3]; - /* E1*C*G2' */ - amb(E1,C,T1); - ambt(T1,G2,T2); - res1[0]+=T2[0]; - res1[1]+=T2[1]; - res1[2]+=T2[2]; - res1[3]+=T2[3]; - - - /* - hess(2*p-1:2*p,:)=hess(2*p-1:2*p,:)+(res*eta(2*q-1:2*q,:)-res1*x(2*q-1:2*q,:))*C'; - */ - - /* (res*E2-res1*G2)*C' */ - amb(res,E2,T1); - amb(res1,G2,T2); - T1[0]-=T2[0]; - T1[1]-=T2[1]; - T1[2]-=T2[2]; - T1[3]-=T2[3]; - ambt(T1,C,T2); - - /* multiply by baseline weight */ - T2[0]=T2[0]*t->wtd[ci]; - T2[1]=T2[1]*t->wtd[ci]; - T2[2]=T2[2]*t->wtd[ci]; - T2[3]=T2[3]*t->wtd[ci]; - - pthread_mutex_lock(&t->mx_array[sta1]); - t->hess[2*sta1]+=T2[0]; - t->hess[2*sta1+2*t->N]+=T2[1]; - t->hess[2*sta1+1]+=T2[2]; - t->hess[2*sta1+2*t->N+1]+=T2[3]; - pthread_mutex_unlock(&t->mx_array[sta1]); - - - /* - hess(2*q-1:2*q,:)=hess(2*q-1:2*q,:)+(res'*eta(2*p-1:2*p,:)-res1'*x(2*p-1:2*p,:))*C; - */ - - /* (res'*E1-res1'*G1)*C */ - atmb(res,E1,T1); - atmb(res1,G1,T2); - T1[0]-=T2[0]; - T1[1]-=T2[1]; - T1[2]-=T2[2]; - T1[3]-=T2[3]; - amb(T1,C,T2); - - /* multiply by baseline weight */ - T2[0]=T2[0]*t->wtd[ci]; - T2[1]=T2[1]*t->wtd[ci]; - T2[2]=T2[2]*t->wtd[ci]; - T2[3]=T2[3]*t->wtd[ci]; - - pthread_mutex_lock(&t->mx_array[sta2]); - t->hess[2*sta2]+=T2[0]; - t->hess[2*sta2+2*t->N]+=T2[1]; - t->hess[2*sta2+1]+=T2[2]; - t->hess[2*sta2+2*t->N+1]+=T2[3]; - pthread_mutex_unlock(&t->mx_array[sta2]); - - } - } - - return NULL; -} - - - -/* Hessian function */ -/* x: 2Nx2 solution - eta: same shape as x - fhess: output, same shape as x - y: visibilities, vectorized V(:) 8*Nbase x 1 -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -fns_fhess(complex double *x, complex double *eta,complex double *fhess, double *y, global_data_rtr_t *gdata) { - - me_data_t *dp=(me_data_t*)gdata->medata; - - int nth,nth1,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthb0,Nthb; - thread_data_rtr_t *threaddata; - - int Nbase1=(dp->Nbase)*(dp->tilesz); - int boff=(dp->Nbase)*(dp->tileoff); - - /* calculate min baselines a thread can handle */ - Nthb0=(Nbase1+Nt-1)/Nt; - - if ((threaddata=(thread_data_rtr_t*)malloc((size_t)Nt*sizeof(thread_data_rtr_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - complex double *hess; - if ((hess=(complex double*)calloc((size_t)4*dp->N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr; - threaddata[nth].carr=dp->carr; - threaddata[nth].M=dp->M; - threaddata[nth].y=&(y[8*ci]); - threaddata[nth].N=dp->N; - threaddata[nth].x=x; /* note the difference: here x assumes no hybrid, also ordering different */ - threaddata[nth].clus=(dp->clus); - threaddata[nth].coh=&(dp->coh[4*(dp->M)*(ci+boff)]); - threaddata[nth].eta=eta; - threaddata[nth].hess=hess; - threaddata[nth].mx_array=gdata->mx_array; - threaddata[nth].wtd=&(gdata->wtd[ci]); /* weights for baselines */ - - //printf("thread %d predict data from %d baselines %d\n",nth,8*ci,Nthb); - pthread_create(&gdata->th_array[nth],&gdata->attr,threadfn_fns_fhess,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - /* now wait for threads to finish */ - for(nth1=0; nth1th_array[nth1],NULL); - } - -/******************* scale *************/ - Nthb0=(dp->N+Nt-1)/Nt; - ci=0; - for (nth=0; nthN; nth++) { - if (ci+Nthb0N) { - Nthb=Nthb0; - } else { - Nthb=dp->N-ci; - } - threaddata[nth].boff=ci; - threaddata[nth].Nb=Nthb; - threaddata[nth].N=dp->N; - threaddata[nth].grad=fhess; - threaddata[nth].iw=gdata->iw; - pthread_create(&gdata->th_array[nth],&gdata->attr,threadfn_fns_fscale,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - for(nth1=0; nth1th_array[nth1],NULL); - } -/******************* scale *************/ - free(threaddata); - - /* extra terms 0.5*rho*eta*/ - my_caxpy(4*dp->N,eta,0.5*gdata->admm_rho,hess); - - - fns_proj(dp->N,x,hess,fhess); - free(hess); - -} - - -/* truncated conjugate gradient method - x, grad, eta, r, z, delta, Hxd : size 2N x 2 complex - so, vector size is 4N complex double - - output: eta - output: fhess (can be reused in calling func) - return value: stop_tCG code - - y: vec(V) visibilities -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static int -tcg_solve(int N, complex double *x, complex double *grad, complex double *eta, complex double *fhess, - double Delta, double theta, double kappa, int max_inner, int min_inner, double *y, global_data_rtr_t *gdata) { - - complex double *r,*z,*delta,*Hxd, *rnew; - double e_Pe, r_r, norm_r, z_r, d_Pd, d_Hd, alpha, e_Pe_new, - e_Pd, Deltasq, tau, zold_rold, beta, norm_r0; - int cj, stop_tCG; - - if ((r=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((z=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((delta=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((Hxd=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((rnew=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - /* - initial values - % eta = 0*grad; << zero matrix provided - r = grad; - e_Pe = 0; - */ - my_ccopy(4*N,grad,1,r,1); - e_Pe=0.0; - - /* - r_r = fns.g(x,r,r); - norm_r = sqrt(r_r); - norm_r0 = norm_r; - */ - - r_r=fns_g(N,x,r,r); - norm_r=sqrt(r_r); - norm_r0=norm_r; - - /* - z = r; - */ - my_ccopy(4*N,r,1,z,1); - - /* - % compute z'*r - z_r = fns.g(x,z,r); - d_Pd = z_r; - */ - z_r=fns_g(N,x,z,r); - d_Pd=z_r; - - /* - % initial search direction - delta = -z; - e_Pd = fns.g(x,eta,delta); - */ - memset(delta,0,sizeof(complex double)*N*4); - my_caxpy(4*N,z,-1.0+_Complex_I*0.0,delta); - e_Pd=fns_g(N,x,eta,delta); - - /* - % pre-assume termination b/c j == end - stop_tCG = 5; - */ - stop_tCG=5; - - /* % begin inner/tCG loop - for j = 1:max_inner, - */ - for(cj=1; cj<=max_inner; cj++) { - /**************************************************/ - /* - Hxd = fns.fhess(x,delta); - - % compute curvature - d_Hd = fns.g(x,delta,Hxd); - */ - fns_fhess(x,delta,Hxd,y,gdata); - d_Hd=fns_g(N,x,delta,Hxd); - - /* - alpha = z_r/d_Hd; - e_Pe_new = e_Pe + 2.0*alpha*e_Pd + alpha*alpha*d_Pd; - */ - alpha=z_r/d_Hd; - e_Pe_new = e_Pe + 2.0*alpha*e_Pd + alpha*alpha*d_Pd; - - /* - - % check curvature and trust-region radius - if d_Hd <= 0 || e_Pe_new >= Delta^2, - - */ - Deltasq=Delta*Delta; - if (d_Hd <= 0.0 || e_Pe_new >= Deltasq) { - /* - tau = (-e_Pd + sqrt(e_Pd*e_Pd + d_Pd*(Delta^2-e_Pe))) / d_Pd; - - */ - tau = (-e_Pd + sqrt(e_Pd*e_Pd + d_Pd*(Deltasq-e_Pe)))/d_Pd; - - /* - eta = eta + tau*delta; - - */ - my_caxpy(4*N,delta,tau+_Complex_I*0.0,eta); - - /* NEW Heta = Heta + tau*Hdelta */ - my_caxpy(4*N,fhess,tau+_Complex_I*0.0,Hxd); - - /* - if d_Hd <= 0, - stop_tCG = 1; % negative curvature - else - stop_tCG = 2; % exceeded trust region - end - */ - stop_tCG=(d_Hd<=0.0?1:2); - - /* - break (for) - */ - break; - /* - end if - */ - } - - - /* - % no negative curvature and eta_prop inside TR: accept it - e_Pe = e_Pe_new; - eta = eta + alpha*delta; - - */ - e_Pe=e_Pe_new; - my_caxpy(4*N,delta,alpha+_Complex_I*0.0,eta); - - /* NEW Heta = Heta + alpha*Hdelta */ - my_caxpy(4*N,fhess,alpha+_Complex_I*0.0,Hxd); - - - /* - % update the residual - r = r + alpha*Hxd; - - */ - my_caxpy(4*N,Hxd,alpha+_Complex_I*0.0,r); - - /* - % compute new norm of r - r_r = fns.g(x,r,r); - norm_r = sqrt(r_r); - - */ - r_r=fns_g(N,x,r,r); - norm_r=sqrt(r_r); - - - /* - % check kappa/theta stopping criterion - if j >= min_inner && norm_r <= norm_r0*min(norm_r0^theta,kappa) - */ - if (cj >= min_inner) { - double norm_r0pow=pow(norm_r0,theta); - if (norm_r <= norm_r0*MIN(norm_r0pow,kappa)) { - - /* - % residual is small enough to quit - if kappa < norm_r0^theta, - stop_tCG = 3; % linear convergence - else - stop_tCG = 4; % superlinear convergence - end - - */ - stop_tCG=(kappamedata; - - double fx=fns_f(x,y,gdata); - fns_fgrad(x,eta,y,gdata,0); - double beta0=beta; - double minfx=fx; double minbeta=beta0; - double lhs,rhs,metric; - int m,nocostred=0; - double metric0=fns_g(dp->N,x,eta,eta); - *mincost=minfx; - for (m=0; m<50; m++) { - /* abeta=(beta0)*alphabar*eta; */ - my_ccopy(4*dp->N,eta,1,teta,1); - my_cscal(4*dp->N,beta0*alphabar+0.0*_Complex_I,teta); - /* Rx=R(x,teta); */ - fns_R(dp->N,x,teta,x_prop); - lhs=fns_f(x_prop,y,gdata); - if (lhsN,x,eta,teta); - rhs=fx+sigma*metric; - /* break loop also if no further cost improvement is seen */ - if (lhs<=rhs) { - minbeta=beta0; - break; - } - beta0=beta0*beta; - } - - /* if no further cost improvement is seen */ - if (lhs>fx) { - nocostred=1; - } - - my_ccopy(4*dp->N,eta,1,teta,1); - my_cscal(4*dp->N,minbeta*alphabar+0.0*_Complex_I,teta); - - return nocostred; -} - -/* Fine tune initial trust region radius, also update initial value for x - A. Sartenaer, 1995 - returns : trust region estimate, - also modifies x - eta,Heta,s,x_prop: used as storage - */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static double -itrr(int N,complex double *x,complex double *eta, complex double *Heta, double *y, global_data_rtr_t *gdata, complex double *s, complex double *x_prop) { - - double f0,fk,mk,rho,rho1,Delta0; - - /* initialize trust region radii */ - double delta_0=1.0; - double delta_m=0.0; - - double sigma=0.0; - double delta=0.0; - - // initial cost - f0=fns_f(x,y,gdata); - // gradient at x0 - fns_fgrad(x,eta,y,gdata,1); - //normalize - double eta_nrm=my_cnrm2(4*N,eta); - my_cscal(4*N, 1.0/eta_nrm+0.0*_Complex_I, eta); - - my_ccopy(4*N,eta,1,s,1); - my_cscal(4*N, delta_0+0.0*_Complex_I, s); - //Hessian at s - fns_fhess(x,s,Heta,y,gdata); - - /* constants used */ - double gamma_1=0.0625; double gamma_2=5.0; double gamma_3=0.5; double gamma_4=2.0; - double mu_0=0.5; double mu_1=0.5; double mu_2=0.35; - double teta=0.25; - - - int m,MK=4; - for (m=0; mdelta) { - delta=f0-fk; - sigma=delta_0; - } - /* radius update */ - double beta_1,beta_2,beta_i; - beta_1=0.0; - beta_2=0.0; - if (mmu_1) { - if (minbeta>1.0) { - beta_i=gamma_3; - } else if ((maxbeta=1.0)) { - beta_i=gamma_1; - } else if ((beta_1>=gamma_1 && beta_1<1.0) && (beta_2=1.0)) { - beta_i=beta_1; - } else if ((beta_2>=gamma_1 && beta_2<1.0) && (beta_1=1.0)) { - beta_i=beta_2; - } else { - beta_i=maxbeta; - } - } else if (rho1<=mu_2) { - if (maxbeta<1.0) { - beta_i=gamma_4; - } else if (maxbeta>gamma_2) { - beta_i=gamma_2; - } else if ((beta_1>=1.0 && beta_1<=gamma_2) && beta_2<1.0) { - beta_i=beta_1; - } else if ((beta_2>=1.0 && beta_2<=gamma_2) && beta_1<1.0) { - beta_i=beta_2; - } else { - beta_i=maxbeta; - } - } else { - if (maxbetagamma_4) { - beta_i=gamma_4; - } else { - beta_i=maxbeta; - } - } - /* update radius */ - delta_0=delta_0/beta_i; - } - -#ifdef DEBUG -printf("m=%d delta_0=%e delta_max=%e beta=%e rho=%e\n",m,delta_0,delta_m,beta_i,rho); -#endif - - my_ccopy(4*N,eta,1,s,1); - my_cscal(4*N,delta_0+0.0*_Complex_I, s); - } - - - // update initial value - if (delta>0.0) { - my_caxpy(4*N, eta, -sigma+0.0*_Complex_I, x); - } - - if (delta_m>0.0) { - Delta0=delta_m; - } else { - Delta0=delta_0; - } - - return Delta0; -} - - - -int -rtr_solve_nocuda_robust_admm( - double *x0, /* initial values and updated solution at output (size 8*N double) */ - double *Y, /* Lagrange multiplier (size 8*N double) */ - double *BZ, /* consensus B Z (size 8*N double) */ - double *y, /* data vector (size 8*M double) */ - int N, /* no. of stations */ - int M, /* no. of constraints */ - int itmax_rsd, /* maximum number of iterations RSD */ - int itmax_rtr, /* maximum number of iterations RTR */ - double Delta_bar, double Delta0, /* Trust region radius and initial value */ - double admm_rho, /* ADMM regularization value */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - double *info, /* initial and final residuals */ - me_data_t *adata) { /* pointer to additional data */ - - /* reshape x to make J: 2Nx2 complex double - */ - complex double *x; - if ((x=(complex double*)malloc((size_t)4*N*sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - /* map x: [(re,im)J_1(0,0) (re,im)J_1(0,1) (re,im)J_1(1,0) (re,im)J_1(1,1)...] - to - J: [J_1(0,0) J_1(1,0) J_2(0,0) J_2(1,0) ..... J_1(0,1) J_1(1,1) J_2(0,1) J_2(1,1)....] - */ - double *Jd=(double*)x; - /* re J(0,0) */ - my_dcopy(N, &x0[0], 8, &Jd[0], 4); - /* im J(0,0) */ - my_dcopy(N, &x0[1], 8, &Jd[1], 4); - /* re J(1,0) */ - my_dcopy(N, &x0[4], 8, &Jd[2], 4); - /* im J(1,0) */ - my_dcopy(N, &x0[5], 8, &Jd[3], 4); - /* re J(0,1) */ - my_dcopy(N, &x0[2], 8, &Jd[4*N], 4); - /* im J(0,1) */ - my_dcopy(N, &x0[3], 8, &Jd[4*N+1], 4); - /* re J(1,1) */ - my_dcopy(N, &x0[6], 8, &Jd[4*N+2], 4); - /* im J(1,1) */ - my_dcopy(N, &x0[7], 8, &Jd[4*N+3], 4); - - /* reshape Y and BZ to form complex double */ - complex double *Yd, *Zd; - if ((Yd=(complex double*)malloc((size_t)4*N*sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((Zd=(complex double*)malloc((size_t)4*N*sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - double *YY=(double*)Yd; - double *ZZ=(double*)Zd; - my_dcopy(N, &Y[0], 8, &YY[0], 4); - my_dcopy(N, &Y[1], 8, &YY[1], 4); - my_dcopy(N, &Y[4], 8, &YY[2], 4); - my_dcopy(N, &Y[5], 8, &YY[3], 4); - my_dcopy(N, &Y[2], 8, &YY[4*N], 4); - my_dcopy(N, &Y[3], 8, &YY[4*N+1], 4); - my_dcopy(N, &Y[6], 8, &YY[4*N+2], 4); - my_dcopy(N, &Y[7], 8, &YY[4*N+3], 4); - my_dcopy(N, &BZ[0], 8, &ZZ[0], 4); - my_dcopy(N, &BZ[1], 8, &ZZ[1], 4); - my_dcopy(N, &BZ[4], 8, &ZZ[2], 4); - my_dcopy(N, &BZ[5], 8, &ZZ[3], 4); - my_dcopy(N, &BZ[2], 8, &ZZ[4*N], 4); - my_dcopy(N, &BZ[3], 8, &ZZ[4*N+1], 4); - my_dcopy(N, &BZ[6], 8, &ZZ[4*N+2], 4); - my_dcopy(N, &BZ[7], 8, &ZZ[4*N+3], 4); - - - - int Nt=adata->Nt; - int ci; - global_data_rtr_t gdata; - - gdata.Y=Yd; - gdata.BZ=Zd; - gdata.admm_rho=admm_rho; - - gdata.medata=adata; - /* setup threads */ - pthread_attr_init(&gdata.attr); - pthread_attr_setdetachstate(&gdata.attr,PTHREAD_CREATE_JOINABLE); - - if ((gdata.th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - if ((gdata.mx_array=(pthread_mutex_t*)malloc((size_t)N*sizeof(pthread_mutex_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((gdata.iw=(double*)malloc((size_t)N*sizeof(double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - /* weights for robust LS, length could be less than total no of baselines - therefore use relative offset boff */ - if ((gdata.wtd=(double*)malloc((size_t)M*sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - - for (ci=0; cistation contributions - NOTE: has to be done here because the baseline offset would change */ - fns_fcount(&gdata); -/***************************************************/ - int min_inner,max_inner,min_outer,max_outer; - double epsilon,kappa,theta,rho_prime; - /* - min_inner = 0; - max_inner = inf; - min_outer = 3; - max_outer = 100; - epsilon = 1e-6; - kappa = 0.1; - theta = 1.0; - rho_prime = 0.1; - %Delta_bar = user must specify - %Delta0 = user must specify - %x0 = user must specify - */ - min_inner=1; max_inner=itmax_rtr;//8*N; - min_outer=3; max_outer=itmax_rtr; - epsilon=CLM_EPSILON; - kappa=0.1; - theta=1.0; - /* default values 0.25, 0.75, 0.25, 2.0 */ - double eta1=0.0001; double eta2=0.99; double alpha1=0.25; double alpha2=3.5; - rho_prime=eta1; /* default 0.1 should be <= 0.25 */ - double rho_regularization; /* use large damping (but less than GPU version) */ - double rho_reg; - int model_decreased=0; - - complex double *fgradx,*eta,*Heta,*x_prop; - if ((fgradx=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((eta=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((Heta=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((x_prop=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - /*set initial weights to 1 */ - setweights(M,gdata.wtd,1.0,Nt); - gdata.nulow=robust_nulow; - gdata.nuhigh=robust_nuhigh; - - double fx,norm_grad,Delta,fx_prop,rhonum,rhoden,rho; - fx=fns_f(x,y,&gdata); - double fx0=fx; - int rsdstat=0; -/***************************************************/ - /* RSD solution */ - //for (ci=0; cirobust_nu,robust_nu1); - adata->robust_nu=robust_nu1; -/***************************************************/ - /* - % initialize counters/sentinals - % allocate storage for dist, counters - k = 0; % counter for outer (TR) iteration. - stop_outer = 0; % stopping criterion for TR. - - x = x0; -fx = fns.f(x); -fgradx = fns.fgrad(x); -norm_grad = sqrt(fns.g(x,fgradx,fgradx)); - -% initialize trust-region radius -Delta = Delta0; - */ - int k=0; - int stop_outer=(itmax_rtr>0?0:1); - int stop_inner=0; - // x0 is already copied to x: my_ccopy(4*N,x0,1,x,1); - if(!stop_outer) { - fns_fgrad(x,fgradx,y,&gdata,1); - norm_grad = sqrt(fns_g(N,x,fgradx,fgradx)); - } - Delta=Delta0; - - /* initial residual */ - info[0]=fx; - - /* - % ** Start of TR loop ** - while stop_outer==0, - */ - while(!stop_outer) { - /* - % update counter - k = k+1; - */ - k++; - - - /* - ** Begin TR Subproblem ** - % determine eta0 - % without randT, 0*fgradx is the only way that we - % know how to create a tangent vector - eta = 0*fgradx; - */ - memset(eta,0,sizeof(complex double)*N*4); - - - /* - % solve TR subproblem - [eta,numit,stop_inner] = tCG(fns,x,fgradx,eta,Delta,theta,kappa,min_inner,max_inner,useRand,debug); - */ - stop_inner=tcg_solve(N, x, fgradx, eta, Heta, Delta, theta, kappa, max_inner, min_inner,y,&gdata); - - /* - norm_eta = sqrt(fns.g(x,eta,eta)); - */ - - /* - Heta = fns.fhess(x,eta); - */ - //OLD fns_fhess(x,eta,Heta,y,&gdata); - - /* - % compute the retraction of the proposal - x_prop = fns.R(x,eta); - */ - fns_R(N,x,eta,x_prop); - - /* - % compute function value of the proposal - fx_prop = fns.f(x_prop); - */ - fx_prop=fns_f(x_prop,y,&gdata); - - /* - % do we accept the proposed solution or not? - % compute the Hessian at the proposal - Heta = fns.fhess(x,eta); - FIXME: do we need to do this, because Heta is already there - or change x to x_prop ??? - */ - //Disabled fns_fhess(x,eta,Heta,y,&gdata); - - /* - % check the performance of the quadratic model - rhonum = fx-fx_prop; - rhoden = -fns.g(x,fgradx,eta) - 0.5*fns.g(x,Heta,eta); - */ - rhonum=fx-fx_prop; - rhoden=-fns_g(N,x,fgradx,eta)-0.5*fns_g(N,x,Heta,eta); - /* regularization of rho ratio */ - /* - rho_reg = max(1, abs(fx)) * eps * options.rho_regularization; - rhonum = rhonum + rho_reg; - rhoden = rhoden + rho_reg; - */ - rho_reg=MAX(1.0,fx)*rho_regularization; /* no epsilon */ - rhonum+=rho_reg; - rhoden+=rho_reg; - - - /* - rho = rhonum / rhoden; - */ - rho=rhonum/rhoden; - - - - /* - % HEURISTIC WARNING: - % if abs(model change) is relatively zero, we are probably near a critical - % point. set rho to 1. - if abs(rhonum/fx) < sqrt(eps), - small_rhonum = rhonum; - rho = 1; - else - small_rhonum = 0; - end - FIXME: use constant for sqrt(eps) - */ - /* OLD CODE if (fabs(rhonum/fx) = 0); */ - model_decreased=(rhoden>=0.0?1:0); - - /* NOTE: if too many values of rho are -ve, it means TR radius is too big - so initial TR radius should be reduced */ -#ifdef DEBUG - printf("stop_inner=%d rho_reg=%g rho =%g/%g= %g rho'= %g\n",stop_inner,rho_reg,rhonum,rhoden,rho,rho_prime); -#endif - - /* - % choose new TR radius based on performance - if rho < 1/4 - Delta = 1/4*Delta; - elseif rho > 3/4 && (stop_inner == 2 || stop_inner == 1), - Delta = min(2*Delta,Delta_bar); - end - */ - if (!model_decreased || rhoeta2 && (stop_inner==2 || stop_inner==1)) { - /* we have a good reduction, so increase TR radius */ - Delta=MIN(alpha2*Delta,Delta_bar); - } - - /* - % choose new iterate based on performance - oldgradx = fgradx; - if rho > rho_prime, - accept = true; - x = x_prop; - fx = fx_prop; - fgradx = fns.fgrad(x); - norm_grad = sqrt(fns.g(x,fgradx,fgradx)); - else - accept = false; - end - */ - if (model_decreased && rho>rho_prime) { - my_ccopy(4*N,x_prop,1,x,1); - fx=fx_prop; - fns_fgrad(x,fgradx,y,&gdata,1); - norm_grad=sqrt(fns_g(N,x,fgradx,fgradx)); - } - - - /* - % ** Testing for Stop Criteria - % min_outer is the minimum number of inner iterations - % before we can exit. this gives randomization a chance to - % escape a saddle point. - if norm_grad < epsilon && (~useRand || k > min_outer), - stop_outer = 1; - end - */ - if (norm_gradmin_outer) { - stop_outer=1; - } - - - /* - % stop after max_outer iterations - if k >= max_outer, - if (verbosity > 0), - fprintf('\n*** timed out -- k == %d***\n',k); - end - stop_outer = 1; - end - */ - if (k>=max_outer) { - stop_outer=1; - } - -#ifdef DEBUG - printf("Iter %d cost=%lf\n",k,fx); -#endif - /* end of TR loop (counter: k) */ - } - - /* final residual */ - info[1]=fx; - - free(fgradx); - free(eta); - free(Heta); - free(x_prop); -/***************************************************/ - robust_nu1=fns_fupdate_weights(x,y,&gdata); - adata->robust_nu=robust_nu1; - if (fx0>fx) { - /* copy back solution to x0 */ - /* re J(0,0) */ - my_dcopy(N, &Jd[0], 4, &x0[0], 8); - /* im J(0,0) */ - my_dcopy(N, &Jd[1], 4, &x0[1], 8); - /* re J(1,0) */ - my_dcopy(N, &Jd[2], 4, &x0[4], 8); - /* im J(1,0) */ - my_dcopy(N, &Jd[3], 4, &x0[5], 8); - /* re J(0,1) */ - my_dcopy(N, &Jd[4*N], 4, &x0[2], 8); - /* im J(0,1) */ - my_dcopy(N, &Jd[4*N+1], 4, &x0[3], 8); - /* re J(1,1) */ - my_dcopy(N, &Jd[4*N+2], 4, &x0[6], 8); - /* im J(1,1) */ - my_dcopy(N, &Jd[4*N+3], 4, &x0[7], 8); - } - - for (ci=0; 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 CHFgC6 diff --git a/src/lib/Solvers/rtr_solve_robust_cuda.c b/src/lib/Solvers/rtr_solve_robust_cuda.c deleted file mode 100644 index 29e24b3..0000000 --- a/src/lib/Solvers/rtr_solve_robust_cuda.c +++ /dev/null @@ -1,1262 +0,0 @@ -/* - * - Copyright (C) 2006-2008 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#include -#include -#include -#include -#include - -#include "Solvers.h" -#include - -//#define DEBUG -/* helper functions for diagnostics */ -static void -checkCudaError(cudaError_t err, char *file, int line) -{ -#ifdef CUDA_DEBUG - if(!err) - return; - fprintf(stderr,"GPU (CUDA): %s %s %d\n", cudaGetErrorString(err),file,line); - exit(EXIT_FAILURE); -#endif -} - - -static void -checkCublasError(cublasStatus_t cbstatus, char *file, int line) -{ -#ifdef CUDA_DEBUG - if (cbstatus!=CUBLAS_STATUS_SUCCESS) { - fprintf(stderr,"%s: %d: CUBLAS failure\n",file,line); - exit(EXIT_FAILURE); - } -#endif -} - - - -/* gradient, also projected to tangent space */ -/* for many time samples, gradient for each time sample is projected - to tangent space before it is averaged - so calculate grad using N(N-1)/2 constraints each (total M) -*/ -/* need 8N*M/ThreadsPerBlock+ 8N complex float storage - so actual float is 2 x this value */ -static void -cudakernel_fns_fgrad_robust(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, float *y, float *coh, short *bbh, float *iw, float *wtd, int negate, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle) { - /* baselines per timeslot = N(N-1)/2 ~2400, timeslots = M/baselines ~120 - blocks per timeslot = baselines/ThreadsPerBlock ~2400/120=20 - so total blocks ~20x120=2400 - - each block needs 8*N global storage - */ - cuFloatComplex *tempeta; - cublasStatus_t cbstatus=CUBLAS_STATUS_SUCCESS; - cuFloatComplex alpha; - cudaMalloc((void**)&tempeta, sizeof(cuFloatComplex)*4*N); - - /*************************/ - /* find A=I_2 kron (X^H X) + (X^H X)^T kron I_2 - and find inv(A) by solving A x B = I_4 - use temp storage - */ - /* find X^H X */ - cuFloatComplex xx00,xx01,xx10,xx11; - cbstatus=cublasCdotc(cbhandle,2*N,x,1,x,1,&xx00); - cbstatus=cublasCdotc(cbhandle,2*N,x,1,&x[2*N],1,&xx01); - xx10=cuConjf(xx01); - cbstatus=cublasCdotc(cbhandle,2*N,&x[2*N],1,&x[2*N],1,&xx11); - - cuFloatComplex A[16],*Ad,B[16],*Bd; - A[0]=cuCmulf(make_cuFloatComplex(2.0f,0.0f),xx00); - A[5]=A[10]=cuCaddf(xx00,xx11); - A[15]=cuCmulf(make_cuFloatComplex(2.0f,0.0f),xx11); - A[1]=A[8]=A[11]=A[13]=xx10; - A[2]=A[4]=A[7]=A[14]=xx01; - A[3]=A[6]=A[9]=A[12]=make_cuFloatComplex(0.0f,0.0f); - - B[0]=B[5]=B[10]=B[15]=make_cuFloatComplex(1.0f,0.0f); - B[1]=B[2]=B[3]=B[4]=B[6]=B[7]=B[8]=B[9]=B[11]=B[12]=B[13]=B[14]=make_cuFloatComplex(0.0f,0.0f); - -#ifdef DEBUG - printf("A=[\n"); - printf("%f+j*(%f) %f+j*(%f) %f+j*(%f) %f+j*(%f)\n",A[0].x,A[0].y,A[4].x,A[4].y,A[8].x,A[8].y,A[12].x,A[12].y); - printf("%f+j*(%f) %f+j*(%f) %f+j*(%f) %f+j*(%f)\n",A[1].x,A[1].y,A[5].x,A[5].y,A[9].x,A[9].y,A[13].x,A[13].y); - printf("%f+j*(%f) %f+j*(%f) %f+j*(%f) %f+j*(%f)\n",A[2].x,A[2].y,A[6].x,A[6].y,A[10].x,A[10].y,A[14].x,A[14].y); - printf("%f+j*(%f) %f+j*(%f) %f+j*(%f) %f+j*(%f)\n",A[3].x,A[3].y,A[7].x,A[7].y,A[11].x,A[11].y,A[15].x,A[15].y); - printf("];\n"); -#endif - - - cudaMalloc((void **)&Ad, 16*sizeof(cuFloatComplex)); - cudaMalloc((void **)&Bd, 16*sizeof(cuFloatComplex)); - - cudaMemcpy(Ad,A,16*sizeof(cuFloatComplex),cudaMemcpyHostToDevice); - cudaMemcpy(Bd,B,16*sizeof(cuFloatComplex),cudaMemcpyHostToDevice); - - int work_size=0; - int *devInfo; - cudaError_t err; - err=cudaMalloc((void**)&devInfo, sizeof(int)); /* FIXME: get too many errors here */ - checkCudaError(err,__FILE__,__LINE__); - cuFloatComplex *work,*taud; - cusolverDnCgeqrf_bufferSize(solver_handle, 4, 4, (cuFloatComplex *)Ad, 4, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(cuFloatComplex)); - err=cudaMalloc((void**)&taud, 4*sizeof(cuFloatComplex)); - checkCudaError(err,__FILE__,__LINE__); - cusolverDnCgeqrf(solver_handle, 4, 4, Ad, 4, taud, work, work_size, devInfo); - cusolverDnCunmqr(solver_handle, CUBLAS_SIDE_LEFT, CUBLAS_OP_C, 4, 4, 4, Ad, 4, taud, Bd, 4, work, work_size, devInfo); - cuFloatComplex cone; cone.x=1.0f; cone.y=0.0f; - cbstatus=cublasCtrsm(cbhandle,CUBLAS_SIDE_LEFT,CUBLAS_FILL_MODE_UPPER,CUBLAS_OP_N,CUBLAS_DIAG_NON_UNIT,4,4,&cone,Ad,4,Bd,4); - - - cudaFree(work); - cudaFree(taud); - cudaFree(devInfo); - - - cudaFree(Ad); - -#ifdef DEBUG - /* copy back the result */ - cudaMemcpy(B,Bd,16*sizeof(cuFloatComplex),cudaMemcpyDeviceToHost); - printf("B=[\n"); - printf("%f+j*(%f) %f+j*(%f) %f+j*(%f) %f+j*(%f)\n",B[0].x,B[0].y,B[4].x,B[4].y,B[8].x,B[8].y,B[12].x,B[12].y); - printf("%f+j*(%f) %f+j*(%f) %f+j*(%f) %f+j*(%f)\n",B[1].x,B[1].y,B[5].x,B[5].y,B[9].x,B[9].y,B[13].x,B[13].y); - printf("%f+j*(%f) %f+j*(%f) %f+j*(%f) %f+j*(%f)\n",B[2].x,B[2].y,B[6].x,B[6].y,B[10].x,B[10].y,B[14].x,B[14].y); - printf("%f+j*(%f) %f+j*(%f) %f+j*(%f) %f+j*(%f)\n",B[3].x,B[3].y,B[7].x,B[7].y,B[11].x,B[11].y,B[15].x,B[15].y); - printf("];\n"); -#endif - - - /*************************/ - /* baselines */ - int nbase=N*(N-1)/2; - /* timeslots */ - int ntime=(M+nbase-1)/nbase; - /* blocks per timeslot */ - /* total blocks is Bt x ntime */ - int Bt=(nbase+ThreadsPerBlock-1)/ThreadsPerBlock; - - -#ifdef DEBUG -printf("N=%d Baselines=%d timeslots=%d total=%d,Threads=%d Blocks=%d\n",N,nbase,ntime,M,ThreadsPerBlock,Bt*ntime); -#endif - - /* max size of M for one kernel call, to determine optimal blocks */ - cudakernel_fns_fgradflat_robust(ThreadsPerBlock, Bt*ntime, N, M, x, tempeta, y, coh, bbh, wtd, Bd, cbhandle,solver_handle); - /* weight for missing (flagged) baselines */ - cudakernel_fns_fscale(N, tempeta, iw); - /* find -ve gradient */ - if (negate) { - alpha.x=-1.0f;alpha.y=0.0f; - cbstatus=cublasCscal(cbhandle,4*N,&alpha,tempeta,1); - } - cudaMemcpy(eta,tempeta,4*N*sizeof(cuFloatComplex),cudaMemcpyDeviceToDevice); - checkCublasError(cbstatus,__FILE__,__LINE__); - cudaFree(Bd); - cudaFree(tempeta); -} - -/* Hessian, also projected to tangent space */ -/* for many time samples, gradient for each time sample is projected - to tangent space before it is averaged - so calculate grad using N(N-1)/2 constraints each (total M) -*/ -/* need 8N*M/ThreadsPerBlock+ 8N float storage */ -static void -cudakernel_fns_fhess_robust(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, cuFloatComplex *fhess, float *y, float *coh, short *bbh, float *iw, float *wtd, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle) { - cuFloatComplex *tempeta; - cublasStatus_t cbstatus=CUBLAS_STATUS_SUCCESS; - cudaMalloc((void**)&tempeta, sizeof(cuFloatComplex)*4*N); - - /*************************/ - /* find A=I_2 kron (X^H X) + (X^H X)^T kron I_2 - and find inv(A) by solving A x B = I_4 - use temp storage - */ - /* find X^H X */ - cuFloatComplex xx00,xx01,xx10,xx11; - cbstatus=cublasCdotc(cbhandle,2*N,x,1,x,1,&xx00); - cbstatus=cublasCdotc(cbhandle,2*N,x,1,&x[2*N],1,&xx01); - xx10=cuConjf(xx01); - cbstatus=cublasCdotc(cbhandle,2*N,&x[2*N],1,&x[2*N],1,&xx11); - - cuFloatComplex A[16],*Ad,B[16],*Bd; - A[0]=cuCmulf(make_cuFloatComplex(2.0f,0.0f),xx00); - A[5]=A[10]=cuCaddf(xx00,xx11); - A[15]=cuCmulf(make_cuFloatComplex(2.0f,0.0f),xx11); - A[1]=A[8]=A[11]=A[13]=xx10; - A[2]=A[4]=A[7]=A[14]=xx01; - A[3]=A[6]=A[9]=A[12]=make_cuFloatComplex(0.0f,0.0f); - - B[0]=B[5]=B[10]=B[15]=make_cuFloatComplex(1.0f,0.0f); - B[1]=B[2]=B[3]=B[4]=B[6]=B[7]=B[8]=B[9]=B[11]=B[12]=B[13]=B[14]=make_cuFloatComplex(0.0f,0.0f); - -#ifdef DEBUG - printf("A=[\n"); - printf("%f+j*(%f) %f+j*(%f) %f+j*(%f) %f+j*(%f)\n",A[0].x,A[0].y,A[4].x,A[4].y,A[8].x,A[8].y,A[12].x,A[12].y); - printf("%f+j*(%f) %f+j*(%f) %f+j*(%f) %f+j*(%f)\n",A[1].x,A[1].y,A[5].x,A[5].y,A[9].x,A[9].y,A[13].x,A[13].y); - printf("%f+j*(%f) %f+j*(%f) %f+j*(%f) %f+j*(%f)\n",A[2].x,A[2].y,A[6].x,A[6].y,A[10].x,A[10].y,A[14].x,A[14].y); - printf("%f+j*(%f) %f+j*(%f) %f+j*(%f) %f+j*(%f)\n",A[3].x,A[3].y,A[7].x,A[7].y,A[11].x,A[11].y,A[15].x,A[15].y); - printf("];\n"); -#endif - - - cudaMalloc((void **)&Ad, 16*sizeof(cuFloatComplex)); - cudaMalloc((void **)&Bd, 16*sizeof(cuFloatComplex)); - - cudaMemcpy(Ad,A,16*sizeof(cuFloatComplex),cudaMemcpyHostToDevice); - cudaMemcpy(Bd,B,16*sizeof(cuFloatComplex),cudaMemcpyHostToDevice); - //culaStatus status; - //status=culaDeviceCgels('N',4,4,4,(culaDeviceFloatComplex *)Ad,4,(culaDeviceFloatComplex *)Bd,4); - //checkStatus(status,__FILE__,__LINE__); - int work_size=0; - int *devInfo; - cudaError_t err; - err=cudaMalloc((void**)&devInfo, sizeof(int)); - checkCudaError(err,__FILE__,__LINE__); - cuFloatComplex *work,*taud; - cusolverDnCgeqrf_bufferSize(solver_handle, 4, 4, (cuFloatComplex *)Ad, 4, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(cuFloatComplex)); - err=cudaMalloc((void**)&taud, 4*sizeof(cuFloatComplex)); - checkCudaError(err,__FILE__,__LINE__); - cusolverDnCgeqrf(solver_handle, 4, 4, Ad, 4, taud, work, work_size, devInfo); - cusolverDnCunmqr(solver_handle, CUBLAS_SIDE_LEFT, CUBLAS_OP_C, 4, 4, 4, Ad, 4, taud, Bd, 4, work, work_size, devInfo); - cuFloatComplex cone; cone.x=1.0f; cone.y=0.0f; - cbstatus=cublasCtrsm(cbhandle,CUBLAS_SIDE_LEFT,CUBLAS_FILL_MODE_UPPER,CUBLAS_OP_N,CUBLAS_DIAG_NON_UNIT,4,4,&cone,Ad,4,Bd,4); - - - cudaFree(work); - cudaFree(taud); - cudaFree(devInfo); - cudaFree(Ad); - -#ifdef DEBUG - /* copy back the result */ - cudaMemcpy(B,Bd,16*sizeof(cuFloatComplex),cudaMemcpyDeviceToHost); - printf("B=[\n"); - printf("%f+j*(%f) %f+j*(%f) %f+j*(%f) %f+j*(%f)\n",B[0].x,B[0].y,B[4].x,B[4].y,B[8].x,B[8].y,B[12].x,B[12].y); - printf("%f+j*(%f) %f+j*(%f) %f+j*(%f) %f+j*(%f)\n",B[1].x,B[1].y,B[5].x,B[5].y,B[9].x,B[9].y,B[13].x,B[13].y); - printf("%f+j*(%f) %f+j*(%f) %f+j*(%f) %f+j*(%f)\n",B[2].x,B[2].y,B[6].x,B[6].y,B[10].x,B[10].y,B[14].x,B[14].y); - printf("%f+j*(%f) %f+j*(%f) %f+j*(%f) %f+j*(%f)\n",B[3].x,B[3].y,B[7].x,B[7].y,B[11].x,B[11].y,B[15].x,B[15].y); - printf("];\n"); -#endif - /*************************/ - - /* baselines */ - int nbase=N*(N-1)/2; - /* timeslots */ - int ntime=(M+nbase-1)/nbase; - /* blocks per timeslot */ - /* total blocks is Bt x ntime */ - int Bt=(nbase+ThreadsPerBlock-1)/ThreadsPerBlock; - - -#ifdef DEBUG -printf("N=%d Baselines=%d timeslots=%d total=%d,Threads=%d Blocks=%d\n",N,nbase,ntime,M,ThreadsPerBlock,Bt*ntime); -#endif - - - cudakernel_fns_fhessflat_robust(ThreadsPerBlock, Bt*ntime, N, M, x, eta, tempeta, y, coh, bbh, wtd, Bd, cbhandle, solver_handle); - - cudakernel_fns_fscale(N, tempeta, iw); - cudaMemcpy(fhess,tempeta,4*N*sizeof(cuFloatComplex),cudaMemcpyDeviceToDevice); - checkCublasError(cbstatus,__FILE__,__LINE__); - cudaFree(Bd); - cudaFree(tempeta); -} - - -/* Fine tune initial trust region radius, also update initial value for x - A. Sartenaer, 1995 - returns : trust region estimate, - also modifies x - eta,Heta: used as storage - */ -/* need 8N*2 + MAX(2 Blocks + 4, 8N (1 + ceil(M/Threads))) float storage */ -static float -itrr(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, cuFloatComplex *Heta, float *y, float *coh, short *bbh, float *iw, float *wtd, cublasHandle_t cbhandle,cusolverDnHandle_t solver_handle) { - cuFloatComplex alpha; - cublasStatus_t cbstatus=CUBLAS_STATUS_SUCCESS; - /* temp storage, re-using global storage */ - cuFloatComplex *s, *x_prop; - cudaMalloc((void**)&s, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&x_prop, sizeof(cuFloatComplex)*4*N); - - float f0,fk,mk,rho,rho1,Delta0; - /* initialize trust region radii */ - float delta_0=1.0f; - float delta_m=0.0f; - - float sigma=0.0f; - float delta=0.0f; - - // initial cost - f0=cudakernel_fns_f_robust(ThreadsPerBlock,BlocksPerGrid,N,M,x,y,coh,bbh,wtd); - // gradient at x0; - cudakernel_fns_fgrad_robust(ThreadsPerBlock,BlocksPerGrid,N,M,x,eta,y,coh,bbh,iw,wtd,1,cbhandle, solver_handle); - // normalize - float eta_nrm; - cublasScnrm2(cbhandle,4*N,eta,1,&eta_nrm); - alpha.x=1.0f/eta_nrm;alpha.y=0.0f; - cbstatus=cublasCscal(cbhandle,4*N,&alpha,eta,1); - - cbstatus=cublasCcopy(cbhandle,4*N,eta,1,s,1); - alpha.x=delta_0;alpha.y=0.0f; - cbstatus=cublasCscal(cbhandle,4*N,&alpha,s,1); - /* Hessian at s */ - cudakernel_fns_fhess_robust(ThreadsPerBlock,BlocksPerGrid,N,M,x,s,Heta,y,coh,bbh,iw,wtd,cbhandle,solver_handle); - - /* constants used */ - float gamma_1=0.0625f; float gamma_2=5.0f; float gamma_3=0.5f; float gamma_4=2.0f; - float mu_0=0.5f; float mu_1=0.5f; float mu_2=0.35f; - float teta=0.25f; - - - int MK=4; - int m; - for (m=0; mdelta) { - delta=f0-fk; - sigma=delta_0; - } - /* radius update */ - float beta_1,beta_2,beta_i; - beta_1=0.0f; - beta_2=0.0f; - - if (mmu_1) { - if (minbeta>1.0f) { - beta_i=gamma_3; - } else if ((maxbeta=1.0f)) { - beta_i=gamma_1; - } else if ((beta_1>=gamma_1 && beta_1<1.0f) && (beta_2=1.0f)) { - beta_i=beta_1; - } else if ((beta_2>=gamma_1 && beta_2<1.0f) && (beta_1=1.0f)) { - beta_i=beta_2; - } else { - beta_i=maxbeta; - } - } else if (rho1<=mu_2) { - if (maxbeta<1.0f) { - beta_i=gamma_4; - } else if (maxbeta>gamma_2) { - beta_i=gamma_2; - } else if ((beta_1>=1.0f && beta_1<=gamma_2) && beta_2<1.0f) { - beta_i=beta_1; - } else if ((beta_2>=1.0f && beta_2<=gamma_2) && beta_1<1.0f) { - beta_i=beta_2; - } else { - beta_i=maxbeta; - } - } else { - if (maxbetagamma_4) { - beta_i=gamma_4; - } else { - beta_i=maxbeta; - } - } - /* update radius */ - delta_0=delta_0/beta_i; - } -#ifdef DEBUG -printf("m=%d delta_0=%e delta_max=%e beta=%e rho=%e\n",m,delta_0,delta_m,beta_i,rho); -#endif - - cbstatus=cublasCcopy(cbhandle,4*N,eta,1,s,1); - alpha.x=delta_0;alpha.y=0.0f; - cbstatus=cublasCscal(cbhandle,4*N,&alpha,s,1); - } - - // update initial value - if (delta>0.0f) { - alpha.x=-sigma; alpha.y=0.0f; - cbstatus=cublasCaxpy(cbhandle,4*N, &alpha, eta, 1, x, 1); - } - - if (delta_m>0.0f) { - Delta0=delta_m; - } else { - Delta0=delta_0; - } - - checkCublasError(cbstatus,__FILE__,__LINE__); - cudaFree(s); - cudaFree(x_prop); - return Delta0; -} - - -/* truncated conjugate gradient method - x, grad, eta, r, z, delta, Hxd : size 2N x 2 complex - so, vector size is 4N complex double - - output: eta - return value: stop_tCG code - - y: vec(V) visibilities -*/ -/* need 8N*(BlocksPerGrid+2)+ 8N*6 float storage */ -static int -tcg_solve_cuda(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *grad, cuFloatComplex *eta, cuFloatComplex *fhess, float Delta, float theta, float kappa, int max_inner, int min_inner, float *y, float *coh, short *bbh, float *iw, float *wtd, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle) { - cuFloatComplex *r,*z,*delta,*Hxd, *rnew; - float e_Pe, r_r, norm_r, z_r, d_Pd, d_Hd, alpha, e_Pe_new, - e_Pd, Deltasq, tau, zold_rold, beta, norm_r0; - int cj, stop_tCG; - - cudaMalloc((void**)&r, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&z, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&delta, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&Hxd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&rnew, sizeof(cuFloatComplex)*4*N); - - cublasStatus_t cbstatus=CUBLAS_STATUS_SUCCESS; - cuFloatComplex a0; - - /* - initial values - */ - cbstatus=cublasCcopy(cbhandle,4*N,grad,1,r,1); - e_Pe=0.0f; - - - r_r=cudakernel_fns_g(N,x,r,r,cbhandle,solver_handle); - norm_r=sqrtf(r_r); - norm_r0=norm_r; - - cbstatus=cublasCcopy(cbhandle,4*N,r,1,z,1); - - z_r=cudakernel_fns_g(N,x,z,r,cbhandle,solver_handle); - d_Pd=z_r; - - /* - initial search direction - */ - cudaMemset(delta, 0, sizeof(cuFloatComplex)*4*N); - a0.x=-1.0f; a0.y=0.0f; - cbstatus=cublasCaxpy(cbhandle,4*N, &a0, z, 1, delta, 1); - e_Pd=cudakernel_fns_g(N,x,eta,delta,cbhandle,solver_handle); - - stop_tCG=5; - - /* % begin inner/tCG loop - for j = 1:max_inner, - */ - for(cj=1; cj<=max_inner; cj++) { - cudakernel_fns_fhess_robust(ThreadsPerBlock,BlocksPerGrid,N,M,x,delta,Hxd,y,coh,bbh,iw,wtd,cbhandle,solver_handle); - d_Hd=cudakernel_fns_g(N,x,delta,Hxd,cbhandle,solver_handle); - - alpha=z_r/d_Hd; - e_Pe_new = e_Pe + 2.0f*alpha*e_Pd + alpha*alpha*d_Pd; - - - Deltasq=Delta*Delta; - if (d_Hd <= 0.0f || e_Pe_new >= Deltasq) { - tau = (-e_Pd + sqrtf(e_Pd*e_Pd + d_Pd*(Deltasq-e_Pe)))/d_Pd; - a0.x=tau; - cbstatus=cublasCaxpy(cbhandle,4*N, &a0, delta, 1, eta, 1); - /* Heta = Heta + tau *Hdelta */ - cbstatus=cublasCaxpy(cbhandle,4*N, &a0, Hxd, 1, fhess, 1); - stop_tCG=(d_Hd<=0.0f?1:2); - break; - } - - e_Pe=e_Pe_new; - a0.x=alpha; - cbstatus=cublasCaxpy(cbhandle,4*N, &a0, delta, 1, eta, 1); - /* Heta = Heta + alpha*Hdelta */ - cbstatus=cublasCaxpy(cbhandle,4*N, &a0, Hxd, 1, fhess, 1); - - cbstatus=cublasCaxpy(cbhandle,4*N, &a0, Hxd, 1, r, 1); - cudakernel_fns_proj(N, x, r, rnew, cbhandle, solver_handle); - cbstatus=cublasCcopy(cbhandle,4*N,rnew,1,r,1); - r_r=cudakernel_fns_g(N,x,r,r,cbhandle,solver_handle); - norm_r=sqrtf(r_r); - - /* - check kappa/theta stopping criterion - */ - if (cj >= min_inner) { - float norm_r0pow=powf(norm_r0,theta); - if (norm_r <= norm_r0*MIN(norm_r0pow,kappa)) { - stop_tCG=(kappaNbase)*(ntiles); /* note: we do not use the total tile size */ - /* coherency on device */ - float *cohd; - /* baseline-station map on device/host */ - short *bbd; - - /* calculate no of cuda threads and blocks */ - int ThreadsPerBlock=DEFAULT_TH_PER_BK; - int BlocksPerGrid= 2*(M+ThreadsPerBlock-1)/ThreadsPerBlock; - - - /* reshape x to make J: 2Nx2 complex double - */ - complex float *x; - if ((x=(complex float*)malloc((size_t)4*N*sizeof(complex float)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - /* map x: [(re,im)J_1(0,0) (re,im)J_1(0,1) (re,im)J_1(1,0) (re,im)J_1(1,1)...] - to - J: [J_1(0,0) J_1(1,0) J_2(0,0) J_2(1,0) ..... J_1(0,1) J_1(1,1) J_2(0,1) J_2(1,1)....] - */ - float *Jd=(float*)x; - /* re J(0,0) */ - my_fcopy(N, &x0[0], 8, &Jd[0], 4); - /* im J(0,0) */ - my_fcopy(N, &x0[1], 8, &Jd[1], 4); - /* re J(1,0) */ - my_fcopy(N, &x0[4], 8, &Jd[2], 4); - /* im J(1,0) */ - my_fcopy(N, &x0[5], 8, &Jd[3], 4); - /* re J(0,1) */ - my_fcopy(N, &x0[2], 8, &Jd[4*N], 4); - /* im J(0,1) */ - my_fcopy(N, &x0[3], 8, &Jd[4*N+1], 4); - /* re J(1,1) */ - my_fcopy(N, &x0[6], 8, &Jd[4*N+2], 4); - /* im J(1,1) */ - my_fcopy(N, &x0[7], 8, &Jd[4*N+3], 4); - - - int ci; - -/***************************************************/ - cuFloatComplex *xd,*fgradxd,*etad,*Hetad,*x_propd; - float *yd; - float *wtd,*qd; /* for robust weight and log(weight) */ - float robust_nu=(float)dp->robust_nu; - float q_sum,robust_nu1; - float deltanu; - int Nd=100; /* no of points where nu is sampled, note NdM) { Nd=M; } - deltanu=(float)(robust_nuhigh-robust_nulow)/(float)Nd; - - /* for counting how many baselines contribute to each station - grad/hess calculation */ - float *iwd,*iw; - if ((iw=(float*)malloc((size_t)N*sizeof(float)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - cudaMalloc((void**)&fgradxd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&etad, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&Hetad, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&x_propd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&xd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&yd, sizeof(float)*8*M); - cudaMalloc((void**)&cohd, sizeof(float)*8*Nbase); - cudaMalloc((void**)&bbd, sizeof(short)*2*Nbase); - cudaMalloc((void**)&iwd, sizeof(float)*N); - cudaMalloc((void**)&wtd, sizeof(float)*M); - cudaMalloc((void**)&qd, sizeof(float)*M); - - - /* need 8N*(BlocksPerGrid+8) for tcg_solve+grad/hess storage, - so total storage needed is - 8N*(BlocksPerGrid+8) + 8N*5 + 8*M + 8*Nbase + 2*Nbase + N + M + M - */ - - /* yd <=y : V */ - err=cudaMemcpy(yd, y, 8*M*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - /* need to give right offset for coherencies */ - /* offset: cluster offset+time offset */ - /* C */ - err=cudaMemcpy(cohd, &(dp->ddcohf[(dp->Nbase)*(dp->tilesz)*(dp->clus)*8+(dp->Nbase)*tileoff*8]), Nbase*8*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - /* correct offset for baselines */ - err=cudaMemcpy(bbd, &(dp->ddbase[2*(dp->Nbase)*(tileoff)]), Nbase*2*sizeof(short), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - /* xd <=x : solution */ - err=cudaMemcpy(xd, x, 8*N*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - - float fx,fx0,norm_grad,Delta,fx_prop,rhonum,rhoden,rho; - - /* count how many baselines contribute to each station, store (inverse) in iwd */ - count_baselines(Nbase,N,iw,&(dp->ddbase[2*(dp->Nbase)*(tileoff)]),dp->Nt); - err=cudaMemcpy(iwd, iw, N*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - free(iw); - - /* set initial weights to 1 by a cuda kernel */ - cudakernel_setweights_fl(ThreadsPerBlock, (M+ThreadsPerBlock-1)/ThreadsPerBlock, M, wtd, 1.0f); - fx=cudakernel_fns_f_robust(ThreadsPerBlock,BlocksPerGrid,N,M,xd,yd,cohd,bbd,wtd); - fx0=fx; -#ifdef DEBUG -printf("Initial Cost=%g\n",fx0); -#endif - - float Delta_new=itrr(ThreadsPerBlock, BlocksPerGrid, N, M, xd, etad, Hetad, yd, cohd, bbd, iwd, wtd, cbhandle,solver_handle); - -#ifdef DEBUG - printf("TR radius given=%f est=%f\n",Delta0,Delta_new); -#endif - - //old values - //Delta_bar=MIN(fx,0.01f); - //Delta0=Delta_bar*0.125f; - Delta0=MIN(Delta_new,0.01f); /* need to be more restrictive for EM */ - Delta_bar=Delta0*8.0f; - - cudakernel_fns_fupdate_weights(ThreadsPerBlock,BlocksPerGrid,N,M,xd,yd,cohd,bbd,wtd,robust_nu); -//printf("fx=%g Delta_bar=%g Delta0=%g\n",fx,Delta_bar,Delta0); - -#ifdef DEBUG -printf("NEW RSD cost=%g\n",fx); -#endif -/***************************************************/ - int min_inner,max_inner,min_outer,max_outer; - float epsilon,kappa,theta,rho_prime; - - min_inner=1; max_inner=itmax_rtr;//8*N; - min_outer=3;//itmax_rtr; //3; - max_outer=itmax_rtr; - epsilon=(float)CLM_EPSILON; - kappa=0.1f; - theta=1.0f; - /* default values 0.25, 0.75, 0.25, 2.0 */ - float eta1=0.0001f; float eta2=0.99f; float alpha1=0.25f; float alpha2=3.5f; - rho_prime=eta1; /* should be <= 0.25, tune for parallel solve */ - float rho_regularization; /* use large damping */ - rho_regularization=fx*1e-6f; - /* damping: too small => locally converge, globally diverge - |\ - |\ | \___ - -|\ | \| - \ - - - right damping: locally and globally converge - -|\ - \|\ - \|\ - \____ - - */ - float rho_reg; - int model_decreased=0; - - /* RTR solution */ - int k=0; - int stop_outer=(itmax_rtr>0?0:1); - int stop_inner=0; - if (!stop_outer) { - cudakernel_fns_fgrad_robust(ThreadsPerBlock,BlocksPerGrid,N,M,xd,fgradxd,yd,cohd,bbd,iwd,wtd,1,cbhandle,solver_handle); - norm_grad=sqrtf(cudakernel_fns_g(N,xd,fgradxd,fgradxd,cbhandle,solver_handle)); - } - Delta=Delta0; - /* initial residual */ - info[0]=fx0; - - /* - % ** Start of TR loop ** - */ - while(!stop_outer) { - /* - % update counter - */ - k++; - /* eta = 0*fgradx; */ - cudaMemset(etad, 0, sizeof(cuFloatComplex)*4*N); - - - /* solve TR subproblem, also returns Hessian */ - stop_inner=tcg_solve_cuda(ThreadsPerBlock,BlocksPerGrid, N, M, xd, fgradxd, etad, Hetad, Delta, theta, kappa, max_inner, min_inner,yd,cohd,bbd,iwd,wtd,cbhandle,solver_handle); - /* - Heta = fns.fhess(x,eta); - */ - /* - compute the retraction of the proposal - */ - cudakernel_fns_R(N,xd,etad,x_propd,cbhandle,solver_handle); - - /* - compute cost of the proposal - */ - fx_prop=cudakernel_fns_f_robust(ThreadsPerBlock,BlocksPerGrid,N,M,x_propd,yd,cohd,bbd,wtd); - - /* - check the performance of the quadratic model - */ - rhonum=fx-fx_prop; - rhoden=-cudakernel_fns_g(N,xd,fgradxd,etad,cbhandle,solver_handle)-0.5f*cudakernel_fns_g(N,xd,Hetad,etad,cbhandle,solver_handle); - /* regularization of rho ratio */ - /* - rho_reg = max(1, abs(fx)) * eps * options.rho_regularization; - rhonum = rhonum + rho_reg; - rhoden = rhoden + rho_reg; - */ - rho_reg=MAX(1.0f,fx)*rho_regularization; /* no epsilon */ - rhonum+=rho_reg; - rhoden+=rho_reg; - - /* - rho = rhonum / rhoden; - */ - rho=rhonum/rhoden; - - /* model_decreased = (rhoden >= 0); */ - /* OLD CODE if (fabsf(rhonum/fx) =0.0f?1:0); - -#ifdef DEBUG - printf("stop_inner=%d rho_reg=%g rho =%g/%g= %g rho'= %g\n",stop_inner,rho_reg,rhonum,rhoden,rho,rho_prime); -#endif - /* - choose new TR radius based on performance - */ - if ( !model_decreased || rhoeta2 && (stop_inner==2 || stop_inner==1)) { - Delta=MIN(alpha2*Delta,Delta_bar); - } - - /* - choose new iterate based on performance - */ - if (model_decreased && rho>rho_prime) { - cbstatus=cublasCcopy(cbhandle,4*N,x_propd,1,xd,1); - fx=fx_prop; - cudakernel_fns_fgrad_robust(ThreadsPerBlock,BlocksPerGrid,N,M,xd,fgradxd,yd,cohd,bbd,iwd,wtd,1,cbhandle,solver_handle); - norm_grad=sqrtf(cudakernel_fns_g(N,xd,fgradxd,fgradxd,cbhandle,solver_handle)); - } - - /* - Testing for Stop Criteria - */ - if (norm_gradmin_outer) { - stop_outer=1; - } - - /* - stop after max_outer iterations - */ - if (k>=max_outer) { - stop_outer=1; - } - -#ifdef DEBUG -printf("Iter %d cost=%g\n",k,fx); -#endif - - } - /* final residual */ - info[1]=fx; -#ifdef DEBUG -printf("NEW RTR cost=%g\n",fx); -#endif - -/***************************************************/ - cudaDeviceSynchronize(); - /* w <= (p+nu)/(1+error^2), q<=w-log(w) */ - /* p = 2, use MAX() residual of XX,XY,YX,YY, not the sum */ - cudakernel_fns_fupdate_weights_q(ThreadsPerBlock,BlocksPerGrid,N,M,xd,yd,cohd,bbd,wtd,qd,robust_nu); - /* sumq<=sum(w-log(w))/N */ - cbstatus=cublasSasum(cbhandle, M, qd, 1, &q_sum); - q_sum/=(float)M; -#ifdef DEBUG - printf("deltanu=%f sum(w-log(w))=%f\n",deltanu,q_sum); -#endif - /* for nu range 2~numax evaluate, p-variate T - psi((nu0+p)/2)-ln((nu0+p)/2)-psi(nu/2)+ln(nu/2)+1/N sum(ln(w_i)-w_i) +1 - note: AECM not ECME - and find min(| |) */ - int ThreadsPerBlock2=ThreadsPerBlock/4; - cudakernel_evaluatenu_fl_eight(ThreadsPerBlock2, (Nd+ThreadsPerBlock-1)/ThreadsPerBlock2, Nd, q_sum, qd, deltanu,(float)robust_nulow,robust_nu); - /* find min(abs()) value */ - cbstatus=cublasIsamin(cbhandle, Nd, qd, 1, &ci); /* 1 based index */ - robust_nu1=(float)robust_nulow+(float)(ci-1)*deltanu; -#ifdef DEBUG - printf("nu updated %d from %f [%lf,%lf] to %f\n",ci,robust_nu,robust_nulow,robust_nuhigh,robust_nu1); -#endif - /* seems pedantic, but make sure new value for robust_nu fits within bounds */ - if (robust_nu1robust_nu=robust_nulow; - } else if (robust_nu1>robust_nuhigh) { - dp->robust_nu=robust_nuhigh; - } else { - dp->robust_nu=(double)robust_nu1; - } - - if(fx0>fx) { - //printf("Cost final %g initial %g\n",fx,fx0); - /* copy back current solution */ - err=cudaMemcpy(x,xd,8*N*sizeof(float),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - - - /* copy back solution to x0 : format checked*/ - /* re J(0,0) */ - my_fcopy(N, &Jd[0], 4, &x0[0], 8); - /* im J(0,0) */ - my_fcopy(N, &Jd[1], 4, &x0[1], 8); - /* re J(1,0) */ - my_fcopy(N, &Jd[2], 4, &x0[4], 8); - /* im J(1,0) */ - my_fcopy(N, &Jd[3], 4, &x0[5], 8); - /* re J(0,1) */ - my_fcopy(N, &Jd[4*N], 4, &x0[2], 8); - /* im J(0,1) */ - my_fcopy(N, &Jd[4*N+1], 4, &x0[3], 8); - /* re J(1,1) */ - my_fcopy(N, &Jd[4*N+2], 4, &x0[6], 8); - /* im J(1,1) */ - my_fcopy(N, &Jd[4*N+3], 4, &x0[7], 8); - - } - free(x); - - checkCublasError(cbstatus,__FILE__,__LINE__); - cudaFree(fgradxd); - cudaFree(etad); - cudaFree(Hetad); - cudaFree(x_propd); - cudaFree(xd); - cudaFree(yd); - cudaFree(cohd); - cudaFree(bbd); - cudaFree(iwd); - cudaFree(wtd); - cudaFree(qd); - - - return 0; -} - - - -/* storage: - 8N * 6 + N + 8M * 2 + 2M + M (base storage) - MAX( 2 * Blocks + 4, 8N(1 + ceil(M/Threads))) for functions - Blocks = ceil(M/Threads) -*/ -int -nsd_solve_cuda_robust_fl( - float *x0, /* initial values and updated solution at output (size 8*N float) */ - float *y, /* data vector (size 8*M float) */ - int N, /* no of stations */ - int M, /* no of constraints */ - int itmax, /* maximum number of iterations */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - double *info, /* initial and final residuals */ - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - me_data_t *adata) -{ - - /* general note: all device variables end with a 'd' */ - cudaError_t err; - cublasStatus_t cbstatus=CUBLAS_STATUS_SUCCESS; - - /* ME data */ - me_data_t *dp=(me_data_t*)adata; - int Nbase=(dp->Nbase)*(ntiles); /* note: we do not use the total tile size */ - /* coherency on device */ - float *cohd; - /* baseline-station map on device/host */ - short *bbd; - - /* calculate no of cuda threads and blocks */ - int ThreadsPerBlock=DEFAULT_TH_PER_BK; - int BlocksPerGrid= 2*(M+ThreadsPerBlock-1)/ThreadsPerBlock; - - - /* reshape x to make J: 2Nx2 complex double - */ - complex float *x; - if ((x=(complex float*)malloc((size_t)4*N*sizeof(complex float)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - /* map x: [(re,im)J_1(0,0) (re,im)J_1(0,1) (re,im)J_1(1,0) (re,im)J_1(1,1)...] - to - J: [J_1(0,0) J_1(1,0) J_2(0,0) J_2(1,0) ..... J_1(0,1) J_1(1,1) J_2(0,1) J_2(1,1)....] - */ - float *Jd=(float*)x; - /* re J(0,0) */ - my_fcopy(N, &x0[0], 8, &Jd[0], 4); - /* im J(0,0) */ - my_fcopy(N, &x0[1], 8, &Jd[1], 4); - /* re J(1,0) */ - my_fcopy(N, &x0[4], 8, &Jd[2], 4); - /* im J(1,0) */ - my_fcopy(N, &x0[5], 8, &Jd[3], 4); - /* re J(0,1) */ - my_fcopy(N, &x0[2], 8, &Jd[4*N], 4); - /* im J(0,1) */ - my_fcopy(N, &x0[3], 8, &Jd[4*N+1], 4); - /* re J(1,1) */ - my_fcopy(N, &x0[6], 8, &Jd[4*N+2], 4); - /* im J(1,1) */ - my_fcopy(N, &x0[7], 8, &Jd[4*N+3], 4); - - - int ci; - -/***************************************************/ - cuFloatComplex *xd,*fgradxd,*etad,*zd,*x_propd,*z_propd; - float *yd; - float *wtd,*qd; /* for robust weight and log(weight) */ - float robust_nu=(float)dp->robust_nu; - float q_sum,robust_nu1; - float deltanu; - int Nd=100; /* no of points where nu is sampled, note NdM) { Nd=M; } - deltanu=(float)(robust_nuhigh-robust_nulow)/(float)Nd; - - /* for counting how many baselines contribute to each station - grad/hess calculation */ - float *iwd,*iw; - if ((iw=(float*)malloc((size_t)N*sizeof(float)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - cudaMalloc((void**)&fgradxd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&etad, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&zd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&x_propd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&xd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&z_propd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&yd, sizeof(float)*8*M); - cudaMalloc((void**)&cohd, sizeof(float)*8*Nbase); - cudaMalloc((void**)&bbd, sizeof(short)*2*Nbase); - cudaMalloc((void**)&iwd, sizeof(float)*N); - cudaMalloc((void**)&wtd, sizeof(float)*M); - cudaMalloc((void**)&qd, sizeof(float)*M); - - - /* yd <=y : V */ - err=cudaMemcpy(yd, y, 8*M*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - /* need to give right offset for coherencies */ - /* offset: cluster offset+time offset */ - /* C */ - err=cudaMemcpy(cohd, &(dp->ddcohf[(dp->Nbase)*(dp->tilesz)*(dp->clus)*8+(dp->Nbase)*tileoff*8]), Nbase*8*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - /* correct offset for baselines */ - err=cudaMemcpy(bbd, &(dp->ddbase[2*(dp->Nbase)*(tileoff)]), Nbase*2*sizeof(short), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - /* xd <=x : solution */ - err=cudaMemcpy(xd, x, 8*N*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - - float fx,fx0; - - /* count how many baselines contribute to each station, store (inverse) in iwd */ - count_baselines(Nbase,N,iw,&(dp->ddbase[2*(dp->Nbase)*(tileoff)]),dp->Nt); - err=cudaMemcpy(iwd, iw, N*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - free(iw); - - /* set initial weights to 1 by a cuda kernel */ - cudakernel_setweights_fl(ThreadsPerBlock, (M+ThreadsPerBlock-1)/ThreadsPerBlock, M, wtd, 1.0f); - fx=cudakernel_fns_f_robust(ThreadsPerBlock,BlocksPerGrid,N,M,xd,yd,cohd,bbd,wtd); - fx0=fx; -#ifdef DEBUG -printf("Initial Cost=%g\n",fx0); -#endif -/***************************************************/ - // gradient at x0; - cudakernel_fns_fgrad_robust(ThreadsPerBlock,BlocksPerGrid,N,M,xd,fgradxd,yd,cohd,bbd,iwd,wtd,1,cbhandle,solver_handle); - // Hessian - cudakernel_fns_fhess_robust(ThreadsPerBlock,BlocksPerGrid,N,M,xd,xd,zd,yd,cohd,bbd,iwd,wtd,cbhandle,solver_handle); - // initial step = 1/||Hess|| - float hess_nrm; - cublasScnrm2(cbhandle,4*N,zd,1,&hess_nrm); - float t=1.0f/hess_nrm; - /* if initial step too small */ - if (t<1e-6f) { - t=1e-6f; - } - - /* z <= x */ - cbstatus=cublasCcopy(cbhandle,4*N,xd,1,zd,1); - float theta=1.0f; - float ALPHA = 1.01f; // step-size growth factor - float BETA = 0.5f; // step-size shrinkage factor - int k; - cuFloatComplex alpha; - - for (k=0; krobust_nu=robust_nulow; - } else if (robust_nu1>robust_nuhigh) { - dp->robust_nu=robust_nuhigh; - } else { - dp->robust_nu=(double)robust_nu1; - } - - if(fx0>fx) { - //printf("Cost final %g initial %g\n",fx,fx0); - /* copy back current solution */ - err=cudaMemcpy(x,xd,8*N*sizeof(float),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - - - /* copy back solution to x0 : format checked*/ - /* re J(0,0) */ - my_fcopy(N, &Jd[0], 4, &x0[0], 8); - /* im J(0,0) */ - my_fcopy(N, &Jd[1], 4, &x0[1], 8); - /* re J(1,0) */ - my_fcopy(N, &Jd[2], 4, &x0[4], 8); - /* im J(1,0) */ - my_fcopy(N, &Jd[3], 4, &x0[5], 8); - /* re J(0,1) */ - my_fcopy(N, &Jd[4*N], 4, &x0[2], 8); - /* im J(0,1) */ - my_fcopy(N, &Jd[4*N+1], 4, &x0[3], 8); - /* re J(1,1) */ - my_fcopy(N, &Jd[4*N+2], 4, &x0[6], 8); - /* im J(1,1) */ - my_fcopy(N, &Jd[4*N+3], 4, &x0[7], 8); - - } - free(x); - checkCublasError(cbstatus,__FILE__,__LINE__); - cudaFree(fgradxd); - cudaFree(etad); - cudaFree(zd); - cudaFree(x_propd); - cudaFree(xd); - cudaFree(z_propd); - cudaFree(yd); - cudaFree(cohd); - cudaFree(bbd); - cudaFree(iwd); - cudaFree(wtd); - cudaFree(qd); - - return 0; -} diff --git a/src/lib/Solvers/rtr_solve_robust_cuda.o b/src/lib/Solvers/rtr_solve_robust_cuda.o deleted file mode 100644 index 5c03fddc3589e3adb27e2a299c23b21190d9c742..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 102784 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%0A7at5WE6;Rh(bFjJz*=DEUmd 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 lZ0}lL!F - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#include -#include -#include -#include -#include - -#include "Solvers.h" -#include - -//#define DEBUG -/* helper functions for diagnostics */ -static void -checkCudaError(cudaError_t err, char *file, int line) -{ -#ifdef CUDA_DEBUG - if(!err) - return; - fprintf(stderr,"GPU (CUDA): %s %s %d\n", cudaGetErrorString(err),file,line); - exit(EXIT_FAILURE); -#endif -} - - -static void -checkCublasError(cublasStatus_t cbstatus, char *file, int line) -{ -#ifdef CUDA_DEBUG - if (cbstatus!=CUBLAS_STATUS_SUCCESS) { - fprintf(stderr,"%s: %d: CUBLAS failure\n",file,line); - exit(EXIT_FAILURE); - } -#endif -} - - -/* cost function */ -/* storage <= (2 Blocks+4) + 8N */ -static float -cudakernel_fns_f_robust_admm(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *Y, cuFloatComplex *Z, float admm_rho, float *y, float *coh, short *bbh, float *wtd, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle){ - cuFloatComplex *Yd; - cublasStatus_t cbstatus=CUBLAS_STATUS_SUCCESS; - cuFloatComplex alpha,a; - cudaMalloc((void**)&Yd, sizeof(cuFloatComplex)*4*N); - /* original cost function */ - float f0=cudakernel_fns_f_robust(ThreadsPerBlock,BlocksPerGrid,N,M,x,y,coh,bbh,wtd); -#ifdef DEBUG - printf("orig cost %f ",f0); -#endif - /* extra cost from ADMM */ - /* add ||Y^H(J-BZ)|| + rho/2 ||J-BZ||^2 */ - - /* Yd=J-BZ */ - cublasCcopy(cbhandle,4*N,x,1,Yd,1); - alpha.x=-1.0f;alpha.y=0.0f; - cbstatus=cublasCaxpy(cbhandle,4*N, &alpha, Z, 1, Yd, 1); - - /* ||Y^H Yd|| = 2 real(Y(:)^H Yd(:)) */ - cbstatus=cublasCdotc(cbhandle,4*N, Y, 1, Yd, 1, &a); -#ifdef DEBUG - printf("up %f ",2.0f*a.x); -#endif - f0+=2.0f*a.x; - - /* rho/2 ||J-BZ||^2 = rho/2 real(Yd(:)^H Yd(:)) */ - cbstatus=cublasCdotc(cbhandle,4*N, Yd, 1, Yd, 1, &a); -#ifdef DEBUG - printf("up %f\n",0.5f*admm_rho*a.x); -#endif - f0+=0.5f*admm_rho*a.x; - checkCublasError(cbstatus,__FILE__,__LINE__); - cudaFree(Yd); - return f0; -} - -/* Projection - rnew: new value : Euclidean space, just old value */ -static void -cudakernel_fns_proj_admm(int N, cuFloatComplex *x, cuFloatComplex *z, cuFloatComplex *rnew, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle) { - cublasStatus_t cbstatus; - - cbstatus=cublasCcopy(cbhandle,4*N,z,1,rnew,1); - checkCublasError(cbstatus,__FILE__,__LINE__); -} - - -/* gradient, also projected to tangent space */ -/* need 8N*M/ThreadsPerBlock+ 8N float storage */ -static void -cudakernel_fns_fgrad_robust_admm(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *Y, cuFloatComplex *Z, float admm_rho, cuFloatComplex *eta, float *y, float *coh, short *bbh, float *iw, float *wtd, int negate, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle) { - - cuFloatComplex *tempeta; - cublasStatus_t cbstatus=CUBLAS_STATUS_SUCCESS; - cuFloatComplex alpha; - cudaMalloc((void**)&tempeta, sizeof(cuFloatComplex)*4*N); - - /*************************/ - /* baselines */ - int nbase=N*(N-1)/2; - /* timeslots */ - int ntime=(M+nbase-1)/nbase; - /* blocks per timeslot */ - /* total blocks is Bt x ntime */ - int Bt=(nbase+ThreadsPerBlock-1)/ThreadsPerBlock; - /* max size of M for one kernel call, to determine optimal blocks */ - cudakernel_fns_fgradflat_robust_admm(ThreadsPerBlock, Bt*ntime, N, M, x, tempeta, y, coh, bbh, wtd, cbhandle, solver_handle); - - /* weight for missing (flagged) baselines */ - cudakernel_fns_fscale(N, tempeta, iw); - /* find -ve gradient */ - if (negate) { - alpha.x=-1.0f;alpha.y=0.0f; - cbstatus=cublasCscal(cbhandle,4*N,&alpha,tempeta,1); - } - -#ifdef DEBUG - /******************************/ - /* print norms , use eta as temp storage */ - float n1,n2,n3; - cublasScnrm2(cbhandle,4*N,tempeta,1,&n1); - cublasScnrm2(cbhandle,4*N,Y,1,&n2); - cudaMemcpy(eta,x,4*N*sizeof(cuFloatComplex),cudaMemcpyDeviceToDevice); - alpha.x=-1.0f; alpha.y=0.0f; - cublasCaxpy(cbhandle,4*N, &alpha, Z, 1, eta, 1); - cublasScnrm2(cbhandle,4*N,eta,1,&n3); - printf("Norm %lf %lf %lf\n",n1,0.5f*n2,0.5f*admm_rho*n3); - /******************************/ -#endif - - /* extra terms 0.5*Y+0.5*rho*(J-BZ) - add to -ve grad */ - if (negate) { - alpha.x=0.5f; alpha.y=0.0f; - cbstatus=cublasCaxpy(cbhandle,4*N, &alpha, Y, 1, tempeta, 1); - alpha.x=0.5f*admm_rho; alpha.y=0.0f; - cbstatus=cublasCaxpy(cbhandle,4*N, &alpha, x, 1, tempeta, 1); - alpha.x=-0.5f*admm_rho; alpha.y=0.0f; - cbstatus=cublasCaxpy(cbhandle,4*N, &alpha, Z, 1, tempeta, 1); - } else { - alpha.x=-0.5f; alpha.y=0.0f; - cbstatus=cublasCaxpy(cbhandle,4*N, &alpha, Y, 1, tempeta, 1); - alpha.x=-0.5f*admm_rho; alpha.y=0.0f; - cbstatus=cublasCaxpy(cbhandle,4*N, &alpha, x, 1, tempeta, 1); - alpha.x=0.5f*admm_rho; alpha.y=0.0f; - cbstatus=cublasCaxpy(cbhandle,4*N, &alpha, Z, 1, tempeta, 1); - } - - checkCublasError(cbstatus,__FILE__,__LINE__); - cudaMemcpy(eta,tempeta,4*N*sizeof(cuFloatComplex),cudaMemcpyDeviceToDevice); - - cudaFree(tempeta); -} - -/* Hessian, also projected to tangent space */ -/* need 8N*M/ThreadsPerBlock+ 8N float storage */ -static void -cudakernel_fns_fhess_robust_admm(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *Y, cuFloatComplex *Z, float admm_rho, cuFloatComplex *eta, cuFloatComplex *fhess, float *y, float *coh, short *bbh, float *iw, float *wtd, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle) { - cuFloatComplex *tempeta; - cublasStatus_t cbstatus=CUBLAS_STATUS_SUCCESS; - cudaMalloc((void**)&tempeta, sizeof(cuFloatComplex)*4*N); - /* baselines */ - int nbase=N*(N-1)/2; - /* timeslots */ - int ntime=(M+nbase-1)/nbase; - /* blocks per timeslot */ - /* total blocks is Bt x ntime */ - int Bt=(nbase+ThreadsPerBlock-1)/ThreadsPerBlock; - - cudakernel_fns_fhessflat_robust_admm(ThreadsPerBlock, Bt*ntime, N, M, x, eta, tempeta, y, coh, bbh, wtd, cbhandle, solver_handle); - - cudakernel_fns_fscale(N, tempeta, iw); - - /* extra terms 0.5*rho*eta*/ - cuFloatComplex alpha; - alpha.x=0.5f*admm_rho;alpha.y=0.0f; - cbstatus=cublasCaxpy(cbhandle,4*N, &alpha, eta, 1, tempeta, 1); - - checkCublasError(cbstatus,__FILE__,__LINE__); - cudaMemcpy(fhess,tempeta,4*N*sizeof(cuFloatComplex),cudaMemcpyDeviceToDevice); - - cudaFree(tempeta); -} - -/* Fine tune initial trust region radius, also update initial value for x - A. Sartenaer, 1995 - returns : trust region estimate, - also modifies x - eta,Heta: used as storage - */ -/* need 8N*2 + MAX(8N+2 Blocks + 4, 8N (1 + ceil(M/Threads))) float storage */ -static float -itrr(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *Y, cuFloatComplex *Z, float admm_rho, cuFloatComplex *eta, cuFloatComplex *Heta, float *y, float *coh, short *bbh, float *iw, float *wtd, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle) { - cuFloatComplex alpha; - cublasStatus_t cbstatus=CUBLAS_STATUS_SUCCESS; - /* temp storage, re-using global storage */ - cuFloatComplex *s, *x_prop; - cudaMalloc((void**)&s, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&x_prop, sizeof(cuFloatComplex)*4*N); - - float f0,fk,mk,rho,rho1,Delta0; - /* initialize trust region radii */ - float delta_0=1.0f; - float delta_m=0.0f; - - float sigma=0.0f; - float delta=0.0f; - - // initial cost - f0=cudakernel_fns_f_robust_admm(ThreadsPerBlock,BlocksPerGrid,N,M,x,Y,Z,admm_rho,y,coh,bbh,wtd,cbhandle,solver_handle); - // gradient at x0; - cudakernel_fns_fgrad_robust_admm(ThreadsPerBlock,BlocksPerGrid,N,M,x,Y,Z,admm_rho,eta,y,coh,bbh,iw,wtd,1,cbhandle,solver_handle); - // normalize - float eta_nrm; - cublasScnrm2(cbhandle,4*N,eta,1,&eta_nrm); - alpha.x=1.0f/eta_nrm;alpha.y=0.0f; - cbstatus=cublasCscal(cbhandle,4*N,&alpha,eta,1); - - cbstatus=cublasCcopy(cbhandle,4*N,eta,1,s,1); - alpha.x=delta_0;alpha.y=0.0f; - cbstatus=cublasCscal(cbhandle,4*N,&alpha,s,1); - /* Hessian at s */ - cudakernel_fns_fhess_robust_admm(ThreadsPerBlock,BlocksPerGrid,N,M,x,Y,Z,admm_rho,s,Heta,y,coh,bbh,iw,wtd,cbhandle,solver_handle); - - /* constants used */ - float gamma_1=0.0625f; float gamma_2=5.0f; float gamma_3=0.5f; float gamma_4=2.0f; - float mu_0=0.5f; float mu_1=0.5f; float mu_2=0.35f; - float teta=0.25f; - - - int MK=4; - int m; - for (m=0; mdelta) { - delta=f0-fk; - sigma=delta_0; - } - /* radius update */ - float beta_1,beta_2,beta_i; - beta_1=0.0f; - beta_2=0.0f; - - if (mmu_1) { - if (minbeta>1.0f) { - beta_i=gamma_3; - } else if ((maxbeta=1.0f)) { - beta_i=gamma_1; - } else if ((beta_1>=gamma_1 && beta_1<1.0f) && (beta_2=1.0f)) { - beta_i=beta_1; - } else if ((beta_2>=gamma_1 && beta_2<1.0f) && (beta_1=1.0f)) { - beta_i=beta_2; - } else { - beta_i=maxbeta; - } - } else if (rho1<=mu_2) { - if (maxbeta<1.0f) { - beta_i=gamma_4; - } else if (maxbeta>gamma_2) { - beta_i=gamma_2; - } else if ((beta_1>=1.0f && beta_1<=gamma_2) && beta_2<1.0f) { - beta_i=beta_1; - } else if ((beta_2>=1.0f && beta_2<=gamma_2) && beta_1<1.0f) { - beta_i=beta_2; - } else { - beta_i=maxbeta; - } - } else { - if (maxbetagamma_4) { - beta_i=gamma_4; - } else { - beta_i=maxbeta; - } - } - /* update radius */ - delta_0=delta_0/beta_i; - } -#ifdef DEBUG -printf("m=%d delta_0=%e delta_max=%e beta=%e rho=%e\n",m,delta_0,delta_m,beta_i,rho); -#endif - - cbstatus=cublasCcopy(cbhandle,4*N,eta,1,s,1); - alpha.x=delta_0;alpha.y=0.0f; - cbstatus=cublasCscal(cbhandle,4*N,&alpha,s,1); - } - - // update initial value - if (delta>0.0f) { - alpha.x=-sigma; alpha.y=0.0f; - cbstatus=cublasCaxpy(cbhandle,4*N, &alpha, eta, 1, x, 1); - } - - if (delta_m>0.0f) { - Delta0=delta_m; - } else { - Delta0=delta_0; - } - - checkCublasError(cbstatus,__FILE__,__LINE__); - cudaFree(s); - cudaFree(x_prop); - return Delta0; -} - - - -/* truncated conjugate gradient method - x, grad, eta, r, z, delta, Hxd : size 2N x 2 complex - so, vector size is 4N complex double - - output: eta - return value: stop_tCG code - - y: vec(V) visibilities -*/ -/* need 8N*(BlocksPerGrid+2)+ 8N*6 float storage */ -static int -tcg_solve_cuda(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *Y, cuFloatComplex *Z, float admm_rho, cuFloatComplex *grad, cuFloatComplex *eta, cuFloatComplex *fhess, float Delta, float theta, float kappa, int max_inner, int min_inner, float *y, float *coh, short *bbh, float *iw, float *wtd, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle) { - cuFloatComplex *r,*z,*delta,*Hxd, *rnew; - float e_Pe, r_r, norm_r, z_r, d_Pd, d_Hd, alpha, e_Pe_new, - e_Pd, Deltasq, tau, zold_rold, beta, norm_r0; - int cj, stop_tCG; - cudaMalloc((void**)&r, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&z, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&delta, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&Hxd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&rnew, sizeof(cuFloatComplex)*4*N); - - - cublasStatus_t cbstatus=CUBLAS_STATUS_SUCCESS; - cuFloatComplex a0; - - /* - initial values - */ - cbstatus=cublasCcopy(cbhandle,4*N,grad,1,r,1); - e_Pe=0.0f; - - - r_r=cudakernel_fns_g(N,x,r,r,cbhandle,solver_handle); - norm_r=sqrtf(r_r); - norm_r0=norm_r; - - cbstatus=cublasCcopy(cbhandle,4*N,r,1,z,1); - - z_r=cudakernel_fns_g(N,x,z,r,cbhandle,solver_handle); - d_Pd=z_r; - - /* - initial search direction - */ - cudaMemset(delta, 0, sizeof(cuFloatComplex)*4*N); - a0.x=-1.0f; a0.y=0.0f; - cbstatus=cublasCaxpy(cbhandle,4*N, &a0, z, 1, delta, 1); - e_Pd=cudakernel_fns_g(N,x,eta,delta,cbhandle,solver_handle); - - stop_tCG=5; - - /* % begin inner/tCG loop - for j = 1:max_inner, - */ - for(cj=1; cj<=max_inner; cj++) { - cudakernel_fns_fhess_robust_admm(ThreadsPerBlock,BlocksPerGrid,N,M,x,Y,Z,admm_rho,delta,Hxd,y,coh,bbh,iw,wtd,cbhandle,solver_handle); - d_Hd=cudakernel_fns_g(N,x,delta,Hxd,cbhandle,solver_handle); - - alpha=z_r/d_Hd; - e_Pe_new = e_Pe + 2.0f*alpha*e_Pd + alpha*alpha*d_Pd; - - - Deltasq=Delta*Delta; - if (d_Hd <= 0.0f || e_Pe_new >= Deltasq) { - tau = (-e_Pd + sqrtf(e_Pd*e_Pd + d_Pd*(Deltasq-e_Pe)))/d_Pd; - a0.x=tau; - cbstatus=cublasCaxpy(cbhandle,4*N, &a0, delta, 1, eta, 1); - /* Heta = Heta + tau *Hdelta */ - cbstatus=cublasCaxpy(cbhandle,4*N, &a0, Hxd, 1, fhess, 1); - stop_tCG=(d_Hd<=0.0f?1:2); - break; - } - - e_Pe=e_Pe_new; - a0.x=alpha; - cbstatus=cublasCaxpy(cbhandle,4*N, &a0, delta, 1, eta, 1); - /* Heta = Heta + alpha*Hdelta */ - cbstatus=cublasCaxpy(cbhandle,4*N, &a0, Hxd, 1, fhess, 1); - - cbstatus=cublasCaxpy(cbhandle,4*N, &a0, Hxd, 1, r, 1); - cudakernel_fns_proj_admm(N, x, r, rnew, cbhandle,solver_handle); - cbstatus=cublasCcopy(cbhandle,4*N,rnew,1,r,1); - r_r=cudakernel_fns_g(N,x,r,r,cbhandle,solver_handle); - norm_r=sqrtf(r_r); - - /* - check kappa/theta stopping criterion - */ - if (cj >= min_inner) { - float norm_r0pow=powf(norm_r0,theta); - if (norm_r <= norm_r0*MIN(norm_r0pow,kappa)) { - stop_tCG=(kappaNbase)*(ntiles); /* note: we do not use the total tile size */ - /* coherency on device */ - float *cohd; - /* baseline-station map on device/host */ - short *bbd; - - /* calculate no of cuda threads and blocks */ - int ThreadsPerBlock=DEFAULT_TH_PER_BK; - int BlocksPerGrid= 2*(M+ThreadsPerBlock-1)/ThreadsPerBlock; - - - /* reshape x to make J: 2Nx2 complex double - */ - complex float *x; - if ((x=(complex float*)malloc((size_t)4*N*sizeof(complex float)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - /* map x: [(re,im)J_1(0,0) (re,im)J_1(0,1) (re,im)J_1(1,0) (re,im)J_1(1,1)...] - to - J: [J_1(0,0) J_1(1,0) J_2(0,0) J_2(1,0) ..... J_1(0,1) J_1(1,1) J_2(0,1) J_2(1,1)....] - */ - float *Jd=(float*)x; - /* re J(0,0) */ - my_fcopy(N, &x0[0], 8, &Jd[0], 4); - /* im J(0,0) */ - my_fcopy(N, &x0[1], 8, &Jd[1], 4); - /* re J(1,0) */ - my_fcopy(N, &x0[4], 8, &Jd[2], 4); - /* im J(1,0) */ - my_fcopy(N, &x0[5], 8, &Jd[3], 4); - /* re J(0,1) */ - my_fcopy(N, &x0[2], 8, &Jd[4*N], 4); - /* im J(0,1) */ - my_fcopy(N, &x0[3], 8, &Jd[4*N+1], 4); - /* re J(1,1) */ - my_fcopy(N, &x0[6], 8, &Jd[4*N+2], 4); - /* im J(1,1) */ - my_fcopy(N, &x0[7], 8, &Jd[4*N+3], 4); - - complex float *Zx,*Yx; - if ((Zx=(complex float*)malloc((size_t)4*N*sizeof(complex float)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((Yx=(complex float*)malloc((size_t)4*N*sizeof(complex float)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - float *YY=(float*)Yx; - my_fcopy(N, &Y[0], 8, &YY[0], 4); - my_fcopy(N, &Y[1], 8, &YY[1], 4); - my_fcopy(N, &Y[4], 8, &YY[2], 4); - my_fcopy(N, &Y[5], 8, &YY[3], 4); - my_fcopy(N, &Y[2], 8, &YY[4*N], 4); - my_fcopy(N, &Y[3], 8, &YY[4*N+1], 4); - my_fcopy(N, &Y[6], 8, &YY[4*N+2], 4); - my_fcopy(N, &Y[7], 8, &YY[4*N+3], 4); - float *ZZ=(float*)Zx; - my_fcopy(N, &Z[0], 8, &ZZ[0], 4); - my_fcopy(N, &Z[1], 8, &ZZ[1], 4); - my_fcopy(N, &Z[4], 8, &ZZ[2], 4); - my_fcopy(N, &Z[5], 8, &ZZ[3], 4); - my_fcopy(N, &Z[2], 8, &ZZ[4*N], 4); - my_fcopy(N, &Z[3], 8, &ZZ[4*N+1], 4); - my_fcopy(N, &Z[6], 8, &ZZ[4*N+2], 4); - my_fcopy(N, &Z[7], 8, &ZZ[4*N+3], 4); - - - int ci; - -/***************************************************/ - cuFloatComplex *xd,*fgradxd,*etad,*Hetad,*x_propd,*Yd,*Zd; - float *yd; - float *wtd,*qd; /* for robust weight and log(weight) */ - float robust_nu=(float)dp->robust_nu; - float q_sum,robust_nu1; - float deltanu; - int Nd=100; /* no of points where nu is sampled, note NdM) { Nd=M; } - deltanu=(float)(robust_nuhigh-robust_nulow)/(float)Nd; - - /* for counting how many baselines contribute to each station - grad/hess calculation */ - float *iwd,*iw; - if ((iw=(float*)malloc((size_t)N*sizeof(float)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - - cudaMalloc((void**)&fgradxd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&etad, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&Hetad, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&x_propd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&xd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&yd, sizeof(float)*8*M); - cudaMalloc((void**)&cohd, sizeof(float)*8*Nbase); - cudaMalloc((void**)&bbd, sizeof(short)*2*Nbase); - cudaMalloc((void**)&iwd, sizeof(float)*N); - cudaMalloc((void**)&wtd, sizeof(float)*M); - cudaMalloc((void**)&qd, sizeof(float)*M); - - cudaMalloc((void **)&Yd, 4*N*sizeof(cuFloatComplex)); - cudaMalloc((void **)&Zd, 4*N*sizeof(cuFloatComplex)); - - /* yd <=y : V */ - err=cudaMemcpy(yd, y, 8*M*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - /* need to give right offset for coherencies */ - /* offset: cluster offset+time offset */ - /* C */ - err=cudaMemcpy(cohd, &(dp->ddcohf[(dp->Nbase)*(dp->tilesz)*(dp->clus)*8+(dp->Nbase)*tileoff*8]), Nbase*8*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - /* correct offset for baselines */ - err=cudaMemcpy(bbd, &(dp->ddbase[2*(dp->Nbase)*(tileoff)]), Nbase*2*sizeof(short), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - /* xd <=x : solution */ - err=cudaMemcpy(xd, x, 8*N*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMemcpy(Yd, Yx, 8*N*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMemcpy(Zd, Zx, 8*N*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - - float fx,fx0,norm_grad,Delta,fx_prop,rhonum,rhoden,rho; - - /* count how many baselines contribute to each station, store (inverse) in iwd */ - count_baselines(Nbase,N,iw,&(dp->ddbase[2*(dp->Nbase)*(tileoff)]),dp->Nt); - err=cudaMemcpy(iwd, iw, N*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - free(iw); - - /* set initial weights to 1 by a cuda kernel */ - cudakernel_setweights_fl(ThreadsPerBlock, (M+ThreadsPerBlock-1)/ThreadsPerBlock, M, wtd, 1.0f); - fx=cudakernel_fns_f_robust_admm(ThreadsPerBlock,BlocksPerGrid,N,M,xd,Yd,Zd,admm_rho,yd,cohd,bbd,wtd,cbhandle,solver_handle); - fx0=fx; -#ifdef DEBUG -printf("Initial Cost=%g\n",fx0); -#endif - - float Delta_new=itrr(ThreadsPerBlock, BlocksPerGrid, N, M, xd, Yd,Zd,admm_rho, etad, Hetad, yd, cohd, bbd, iwd, wtd, cbhandle,solver_handle); -#ifdef DEBUG - printf("TR radius given=%f est=%f\n",Delta0,Delta_new); -#endif - - - - //old values - //Delta_bar=MIN(fx,Delta_bar); - //Delta0=Delta_bar*0.125f; - Delta0=MIN(Delta_new,0.01f); /* need to be more restrictive for EM */ - Delta_bar=Delta0*8.0f; - -//printf("fx=%g Delta_bar=%g Delta0=%g\n",fx,Delta_bar,Delta0); - - cudakernel_fns_fupdate_weights(ThreadsPerBlock,BlocksPerGrid,N,M,xd,yd,cohd,bbd,wtd,robust_nu); - -#ifdef DEBUG -printf("NEW RSD cost=%g\n",fx); -#endif -/***************************************************/ - int min_inner,max_inner,min_outer,max_outer; - float epsilon,kappa,theta,rho_prime; - - min_inner=1; max_inner=itmax_rtr;//8*N; - min_outer=3;//itmax_rtr; //3; - max_outer=itmax_rtr; - epsilon=(float)CLM_EPSILON; - kappa=0.1f; - theta=1.0f; - /* default values 0.25, 0.75, 0.25, 2.0 */ - float eta1=0.0001f; float eta2=0.99f; float alpha1=0.25f; float alpha2=3.5f; - rho_prime=eta1; /* should be <= 0.25, tune for parallel solve */ - float rho_regularization; /* use large damping */ - rho_regularization=fx*1e-6f; - /* damping: too small => locally converge, globally diverge - |\ - |\ | \___ - -|\ | \| - \ - - - right damping: locally and globally converge - -|\ - \|\ - \|\ - \____ - - */ - float rho_reg; - int model_decreased=0; - - /* RTR solution */ - int k=0; - int stop_outer=(itmax_rtr>0?0:1); - int stop_inner=0; - if (!stop_outer) { - cudakernel_fns_fgrad_robust_admm(ThreadsPerBlock,BlocksPerGrid,N,M,xd, Yd,Zd,admm_rho,fgradxd,yd,cohd,bbd,iwd,wtd,1,cbhandle,solver_handle); - norm_grad=sqrtf(cudakernel_fns_g(N,xd,fgradxd,fgradxd,cbhandle,solver_handle)); - } - Delta=Delta0; - /* initial residual */ - info[0]=fx0; - - /* - % ** Start of TR loop ** - */ - while(!stop_outer) { - /* - % update counter - */ - k++; - /* eta = 0*fgradx; */ - cudaMemset(etad, 0, sizeof(cuFloatComplex)*4*N); - - - /* solve TR subproblem, also returns Hessian */ - stop_inner=tcg_solve_cuda(ThreadsPerBlock,BlocksPerGrid, N, M, xd, Yd,Zd,admm_rho,fgradxd, etad, Hetad, Delta, theta, kappa, max_inner, min_inner,yd,cohd,bbd,iwd,wtd,cbhandle,solver_handle); - /* - Heta = fns.fhess(x,eta); - */ - /* - compute the retraction of the proposal - */ - cudakernel_fns_R(N,xd,etad,x_propd,cbhandle,solver_handle); - - /* - compute cost of the proposal - */ - fx_prop=cudakernel_fns_f_robust_admm(ThreadsPerBlock,BlocksPerGrid,N,M,x_propd,Yd,Zd,admm_rho,yd,cohd,bbd,wtd, cbhandle,solver_handle); - - /* - check the performance of the quadratic model - */ - rhonum=fx-fx_prop; - rhoden=-cudakernel_fns_g(N,xd,fgradxd,etad,cbhandle,solver_handle)-0.5f*cudakernel_fns_g(N,xd,Hetad,etad,cbhandle,solver_handle); - /* regularization of rho ratio */ - /* - rho_reg = max(1, abs(fx)) * eps * options.rho_regularization; - rhonum = rhonum + rho_reg; - rhoden = rhoden + rho_reg; - */ - rho_reg=MAX(1.0f,fx)*rho_regularization; /* no epsilon */ - rhonum+=rho_reg; - rhoden+=rho_reg; - - /* - rho = rhonum / rhoden; - */ - rho=rhonum/rhoden; - - /* model_decreased = (rhoden >= 0); */ - /* OLD CODE if (fabsf(rhonum/fx) =0.0f?1:0); - -#ifdef DEBUG - printf("stop_inner=%d rho_reg=%g rho =%g/%g= %g rho'= %g\n",stop_inner,rho_reg,rhonum,rhoden,rho,rho_prime); -#endif - /* - choose new TR radius based on performance - */ - if ( !model_decreased || rhoeta2 && (stop_inner==2 || stop_inner==1)) { - Delta=MIN(alpha2*Delta,Delta_bar); - } - - /* - choose new iterate based on performance - */ - if (model_decreased && rho>rho_prime) { - cbstatus=cublasCcopy(cbhandle,4*N,x_propd,1,xd,1); - fx=fx_prop; - cudakernel_fns_fgrad_robust_admm(ThreadsPerBlock,BlocksPerGrid,N,M,xd,Yd,Zd,admm_rho, fgradxd,yd,cohd,bbd,iwd,wtd,1,cbhandle,solver_handle); - norm_grad=sqrtf(cudakernel_fns_g(N,xd,fgradxd,fgradxd,cbhandle,solver_handle)); - } - - /* - Testing for Stop Criteria - */ - if (norm_gradmin_outer) { - stop_outer=1; - } - - /* - stop after max_outer iterations - */ - if (k>=max_outer) { - stop_outer=1; - } - -#ifdef DEBUG -printf("Iter %d cost=%g\n",k,fx); -#endif - - } - /* final residual */ - info[1]=fx; -#ifdef DEBUG -printf("NEW RTR cost=%g\n",fx); -#endif - -/***************************************************/ - cudaDeviceSynchronize(); - /* w <= (p+nu)/(1+error^2), q<=w-log(w) */ - /* p = 2, use MAX() residual of XX,XY,YX,YY, not the sum */ - cudakernel_fns_fupdate_weights_q(ThreadsPerBlock,BlocksPerGrid,N,M,xd,yd,cohd,bbd,wtd,qd,robust_nu); - /* sumq<=sum(w-log(w))/N */ - cbstatus=cublasSasum(cbhandle, M, qd, 1, &q_sum); - q_sum/=(float)M; -#ifdef DEBUG - printf("deltanu=%f sum(w-log(w))=%f\n",deltanu,q_sum); -#endif - /* for nu range 2~numax evaluate, p-variate T - psi((nu0+p)/2)-ln((nu0+p)/2)-psi(nu/2)+ln(nu/2)+1/N sum(ln(w_i)-w_i) +1 - note: AECM not ECME - and find min(| |) */ - int ThreadsPerBlock2=ThreadsPerBlock/4; - cudakernel_evaluatenu_fl_eight(ThreadsPerBlock2, (Nd+ThreadsPerBlock-1)/ThreadsPerBlock2, Nd, q_sum, qd, deltanu,(float)robust_nulow,robust_nu); - /* find min(abs()) value */ - cbstatus=cublasIsamin(cbhandle, Nd, qd, 1, &ci); /* 1 based index */ - robust_nu1=(float)robust_nulow+(float)(ci-1)*deltanu; -#ifdef DEBUG - printf("nu updated %d from %f [%lf,%lf] to %f\n",ci,robust_nu,robust_nulow,robust_nuhigh,robust_nu1); -#endif - /* seems pedantic, but make sure new value for robust_nu fits within bounds */ - if (robust_nu1robust_nu=robust_nulow; - } else if (robust_nu1>robust_nuhigh) { - dp->robust_nu=robust_nuhigh; - } else { - dp->robust_nu=(double)robust_nu1; - } - -#ifdef DEBUG - printf("Cost final %g initial %g\n",fx,fx0); -#endif - if(fx0>fx) { - /* copy back current solution, only if cost is reduced */ - err=cudaMemcpy(x,xd,8*N*sizeof(float),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - - - /* copy back solution to x0 : format checked*/ - /* re J(0,0) */ - my_fcopy(N, &Jd[0], 4, &x0[0], 8); - /* im J(0,0) */ - my_fcopy(N, &Jd[1], 4, &x0[1], 8); - /* re J(1,0) */ - my_fcopy(N, &Jd[2], 4, &x0[4], 8); - /* im J(1,0) */ - my_fcopy(N, &Jd[3], 4, &x0[5], 8); - /* re J(0,1) */ - my_fcopy(N, &Jd[4*N], 4, &x0[2], 8); - /* im J(0,1) */ - my_fcopy(N, &Jd[4*N+1], 4, &x0[3], 8); - /* re J(1,1) */ - my_fcopy(N, &Jd[4*N+2], 4, &x0[6], 8); - /* im J(1,1) */ - my_fcopy(N, &Jd[4*N+3], 4, &x0[7], 8); - } - - - checkCublasError(cbstatus,__FILE__,__LINE__); - cudaFree(fgradxd); - cudaFree(etad); - cudaFree(Hetad); - cudaFree(x_propd); - cudaFree(xd); - cudaFree(yd); - cudaFree(cohd); - cudaFree(bbd); - cudaFree(iwd); - cudaFree(wtd); - cudaFree(qd); - - cudaFree(Yd); - cudaFree(Zd); - - free(x); - free(Yx); - free(Zx); - - return 0; -} - - - - -/* storage: - 8N * 6 + N + 8M * 2 + 2M + M (base storage) - MAX( 2 * Blocks + 4, 8N(1 + ceil(M/Threads))) for functions - Blocks = ceil(M/Threads) -*/ -int -nsd_solve_cuda_robust_admm_fl( - float *x0, /* initial values and updated solution at output (size 8*N float) */ - float *Y, /* Lagrange multiplier size 8N */ - float *Z, /* consensus term B Z size 8N */ - float *y, /* data vector (size 8*M float) */ - int N, /* no of stations */ - int M, /* no of constraints */ - int itmax, /* maximum number of iterations */ - float admm_rho, /* ADMM regularization */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - double *info, /* initial and final residuals */ - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - me_data_t *adata) -{ - - /* general note: all device variables end with a 'd' */ - cudaError_t err; - cublasStatus_t cbstatus=CUBLAS_STATUS_SUCCESS; - - /* ME data */ - me_data_t *dp=(me_data_t*)adata; - int Nbase=(dp->Nbase)*(ntiles); /* note: we do not use the total tile size */ - /* coherency on device */ - float *cohd; - /* baseline-station map on device/host */ - short *bbd; - - /* calculate no of cuda threads and blocks */ - int ThreadsPerBlock=DEFAULT_TH_PER_BK; - int BlocksPerGrid= 2*(M+ThreadsPerBlock-1)/ThreadsPerBlock; - - - /* reshape x to make J: 2Nx2 complex double - */ - complex float *x; - if ((x=(complex float*)malloc((size_t)4*N*sizeof(complex float)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - /* map x: [(re,im)J_1(0,0) (re,im)J_1(0,1) (re,im)J_1(1,0) (re,im)J_1(1,1)...] - to - J: [J_1(0,0) J_1(1,0) J_2(0,0) J_2(1,0) ..... J_1(0,1) J_1(1,1) J_2(0,1) J_2(1,1)....] - */ - float *Jd=(float*)x; - /* re J(0,0) */ - my_fcopy(N, &x0[0], 8, &Jd[0], 4); - /* im J(0,0) */ - my_fcopy(N, &x0[1], 8, &Jd[1], 4); - /* re J(1,0) */ - my_fcopy(N, &x0[4], 8, &Jd[2], 4); - /* im J(1,0) */ - my_fcopy(N, &x0[5], 8, &Jd[3], 4); - /* re J(0,1) */ - my_fcopy(N, &x0[2], 8, &Jd[4*N], 4); - /* im J(0,1) */ - my_fcopy(N, &x0[3], 8, &Jd[4*N+1], 4); - /* re J(1,1) */ - my_fcopy(N, &x0[6], 8, &Jd[4*N+2], 4); - /* im J(1,1) */ - my_fcopy(N, &x0[7], 8, &Jd[4*N+3], 4); - - - complex float *Zx,*Yx; - if ((Zx=(complex float*)malloc((size_t)4*N*sizeof(complex float)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((Yx=(complex float*)malloc((size_t)4*N*sizeof(complex float)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - float *YY=(float*)Yx; - my_fcopy(N, &Y[0], 8, &YY[0], 4); - my_fcopy(N, &Y[1], 8, &YY[1], 4); - my_fcopy(N, &Y[4], 8, &YY[2], 4); - my_fcopy(N, &Y[5], 8, &YY[3], 4); - my_fcopy(N, &Y[2], 8, &YY[4*N], 4); - my_fcopy(N, &Y[3], 8, &YY[4*N+1], 4); - my_fcopy(N, &Y[6], 8, &YY[4*N+2], 4); - my_fcopy(N, &Y[7], 8, &YY[4*N+3], 4); - float *ZZ=(float*)Zx; - my_fcopy(N, &Z[0], 8, &ZZ[0], 4); - my_fcopy(N, &Z[1], 8, &ZZ[1], 4); - my_fcopy(N, &Z[4], 8, &ZZ[2], 4); - my_fcopy(N, &Z[5], 8, &ZZ[3], 4); - my_fcopy(N, &Z[2], 8, &ZZ[4*N], 4); - my_fcopy(N, &Z[3], 8, &ZZ[4*N+1], 4); - my_fcopy(N, &Z[6], 8, &ZZ[4*N+2], 4); - my_fcopy(N, &Z[7], 8, &ZZ[4*N+3], 4); - - - int ci; - -/***************************************************/ - cuFloatComplex *xd,*fgradxd,*etad,*zd,*x_propd,*z_propd,*Yd,*Zd; - float *yd; - float *wtd,*qd; /* for robust weight and log(weight) */ - float robust_nu=(float)dp->robust_nu; - float q_sum,robust_nu1; - float deltanu; - int Nd=100; /* no of points where nu is sampled, note NdM) { Nd=M; } - deltanu=(float)(robust_nuhigh-robust_nulow)/(float)Nd; - - /* for counting how many baselines contribute to each station - grad/hess calculation */ - float *iwd,*iw; - if ((iw=(float*)malloc((size_t)N*sizeof(float)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - - cudaMalloc((void**)&fgradxd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&etad, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&zd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&x_propd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&xd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&z_propd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&yd, sizeof(float)*8*M); - cudaMalloc((void**)&cohd, sizeof(float)*8*Nbase); - cudaMalloc((void**)&bbd, sizeof(short)*2*Nbase); - cudaMalloc((void**)&iwd, sizeof(float)*N); - cudaMalloc((void**)&wtd, sizeof(float)*M); - cudaMalloc((void**)&qd, sizeof(float)*M); - - - cudaMalloc((void **)&Yd, 4*N*sizeof(cuFloatComplex)); - cudaMalloc((void **)&Zd, 4*N*sizeof(cuFloatComplex)); - - /* yd <=y : V */ - err=cudaMemcpy(yd, y, 8*M*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - /* need to give right offset for coherencies */ - /* offset: cluster offset+time offset */ - /* C */ - err=cudaMemcpy(cohd, &(dp->ddcohf[(dp->Nbase)*(dp->tilesz)*(dp->clus)*8+(dp->Nbase)*tileoff*8]), Nbase*8*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - /* correct offset for baselines */ - err=cudaMemcpy(bbd, &(dp->ddbase[2*(dp->Nbase)*(tileoff)]), Nbase*2*sizeof(short), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - /* xd <=x : solution */ - err=cudaMemcpy(xd, x, 8*N*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMemcpy(Yd, Yx, 8*N*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMemcpy(Zd, Zx, 8*N*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - - float fx,fx0; - - /* count how many baselines contribute to each station, store (inverse) in iwd */ - count_baselines(Nbase,N,iw,&(dp->ddbase[2*(dp->Nbase)*(tileoff)]),dp->Nt); - err=cudaMemcpy(iwd, iw, N*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - free(iw); - - /* set initial weights to 1 by a cuda kernel */ - cudakernel_setweights_fl(ThreadsPerBlock, (M+ThreadsPerBlock-1)/ThreadsPerBlock, M, wtd, 1.0f); - fx=cudakernel_fns_f_robust_admm(ThreadsPerBlock,BlocksPerGrid,N,M,xd,Yd,Zd,admm_rho,yd,cohd,bbd,wtd,cbhandle,solver_handle); - fx0=fx; -#ifdef DEBUG -printf("Initial Cost=%g\n",fx0); -#endif -/***************************************************/ - // gradient at x0; - cudakernel_fns_fgrad_robust_admm(ThreadsPerBlock,BlocksPerGrid,N,M,xd,Yd,Zd,admm_rho,fgradxd,yd,cohd,bbd,iwd,wtd,1,cbhandle,solver_handle); - // Hessian - cudakernel_fns_fhess_robust_admm(ThreadsPerBlock,BlocksPerGrid,N,M,xd,Yd,Zd,admm_rho,xd,zd,yd,cohd,bbd,iwd,wtd,cbhandle,solver_handle); - // initial step = 1/||Hess|| - float hess_nrm; - cublasScnrm2(cbhandle,4*N,zd,1,&hess_nrm); - float t=1.0f/hess_nrm; - /* if initial step too small */ - if (t<1e-6f) { - t=1e-6f; - } - - /* z <= x */ - cbstatus=cublasCcopy(cbhandle,4*N,xd,1,zd,1); - float theta=1.0f; - float ALPHA = 1.01f; // step-size growth factor - float BETA = 0.5f; // step-size shrinkage factor - int k; - cuFloatComplex alpha; - - for (k=0; krobust_nu=robust_nulow; - } else if (robust_nu1>robust_nuhigh) { - dp->robust_nu=robust_nuhigh; - } else { - dp->robust_nu=(double)robust_nu1; - } - - if(fx0>fx) { - //printf("Cost final %g initial %g\n",fx,fx0); - /* copy back current solution */ - err=cudaMemcpy(x,xd,8*N*sizeof(float),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - - - /* copy back solution to x0 : format checked*/ - /* re J(0,0) */ - my_fcopy(N, &Jd[0], 4, &x0[0], 8); - /* im J(0,0) */ - my_fcopy(N, &Jd[1], 4, &x0[1], 8); - /* re J(1,0) */ - my_fcopy(N, &Jd[2], 4, &x0[4], 8); - /* im J(1,0) */ - my_fcopy(N, &Jd[3], 4, &x0[5], 8); - /* re J(0,1) */ - my_fcopy(N, &Jd[4*N], 4, &x0[2], 8); - /* im J(0,1) */ - my_fcopy(N, &Jd[4*N+1], 4, &x0[3], 8); - /* re J(1,1) */ - my_fcopy(N, &Jd[4*N+2], 4, &x0[6], 8); - /* im J(1,1) */ - my_fcopy(N, &Jd[4*N+3], 4, &x0[7], 8); - - } - - - checkCublasError(cbstatus,__FILE__,__LINE__); - cudaFree(fgradxd); - cudaFree(etad); - cudaFree(zd); - cudaFree(x_propd); - cudaFree(xd); - cudaFree(z_propd); - cudaFree(yd); - cudaFree(cohd); - cudaFree(bbd); - cudaFree(iwd); - cudaFree(wtd); - cudaFree(qd); - cudaFree(Yd); - cudaFree(Zd); - - free(x); - free(Yx); - free(Zx); - - return 0; -} diff --git a/src/lib/Solvers/rtr_solve_robust_cuda_admm.o b/src/lib/Solvers/rtr_solve_robust_cuda_admm.o deleted file mode 100644 index 431f9d1788118f54da988b8e10999356e8a67893..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 109064 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 diff --git a/src/lib/Solvers/updatenu.c b/src/lib/Solvers/updatenu.c deleted file mode 100644 index fd4a03f..0000000 --- a/src/lib/Solvers/updatenu.c +++ /dev/null @@ -1,443 +0,0 @@ -/* - * - Copyright (C) 2006-2008 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - -#include "Solvers.h" -#include - -/* Digamma function - if x>7 use digamma(x) = digamma(x+1) - 1/x - for accuracy - using maple expansion - series(Psi(x+1/2), x=infinity, 21); - ln(x)+1/24/x^2-7/960/x^4+31/8064/x^6-127/30720/x^8+511/67584/x^10-1414477/67092480/x^12+8191/98304/x^14-118518239/267386880/x^16+5749691557/1882718208/x^18-91546277357/3460300800/x^20+O(1/x^21) - - - based on code by Mark Johnson, 2nd September 2007 -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static double -digamma(double x) { - /* FIXME catch -ve value as input */ - double result = 0.0, xx, xx2, xx4; - for ( ; x < 7.0; ++x) { /* reduce x till x<7 */ - result -= 1.0/x; - } - x -= 0.5; - xx = 1.0/x; - xx2 = xx*xx; - xx4 = xx2*xx2; - result += log(x)+(1./24.)*xx2-(7.0/960.0)*xx4+(31.0/8064.0)*xx4*xx2-(127.0/30720.0)*xx4*xx4; - return result; -} - - - -/* update w<= (nu+1)/(nu+delta^2) - then q <= w-log(w), so that it is +ve -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -w_nu_update_threadfn(void *data) { - thread_data_vecnu_t *t=(thread_data_vecnu_t*)data; - int ci; - for (ci=t->starti; ci<=t->endi; ci++) { - //t->ed[ci]*=t->wtd[ci]; ?? - t->wtd[ci]=(t->nu0+1.0)/(t->nu0+t->ed[ci]*t->ed[ci]); - t->q[ci]=t->wtd[ci]-log(t->wtd[ci]); - } - return NULL; -} - -/* update w<= sqrt(w) */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -w_sqrt_threadfn(void *data) { - thread_data_vecnu_t *t=(thread_data_vecnu_t*)data; - int ci; - for (ci=t->starti; ci<=t->endi; ci++) { - t->wtd[ci]=sqrt(t->wtd[ci]); - } - return NULL; -} - -/* update nu */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -q_update_threadfn(void *data) { - thread_data_vecnu_t *t=(thread_data_vecnu_t*)data; - int ci; - double thisnu,dgm; - for (ci=t->starti; ci<=t->endi; ci++) { - thisnu=(t->nulow+(double)ci*t->nu0); /* deltanu stored in nu0 */ - dgm=digamma(thisnu*0.5+0.5); - t->q[ci]=dgm-log((thisnu+1.0)*0.5); /* psi((nu+1)/2)-log((nu+1)/2) */ - dgm=digamma(thisnu*0.5); - t->q[ci]+=-dgm+log((thisnu)*0.5); /* -psi((nu)/2)+log((nu)/2) */ - t->q[ci]+=-t->sumq+1.0; /* q is w-log(w), so -ve: sum(ln(w_i))/N-sum(w_i)/N+1 */ - } - return NULL; -} - -/* update nu */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -q_update_threadfn_aecm(void *data) { - thread_data_vecnu_t *t=(thread_data_vecnu_t*)data; - int ci; - double thisnu,dgm; - for (ci=t->starti; ci<=t->endi; ci++) { - thisnu=(t->nulow+(double)ci*t->nu0); /* deltanu stored in nu0 */ - dgm=digamma(thisnu*0.5); - t->q[ci]=-dgm+log((thisnu)*0.5); /* -psi((nu)/2)+log((nu)/2) */ - t->q[ci]+=-t->sumq+1.0; /* q is w-log(w), so -ve: sum(ln(w_i))/N-sum(w_i)/N+1 */ - } - return NULL; -} - -/* update nu (degrees of freedom) - also update w - - nu0: current value of nu - w: Nx1 weight vector - ed: Nx1 residual error - - - psi() : digamma function - find soltion to - psi((nu+1)/2)-ln((nu+1)/2)-psi(nu/2)+ln(nu/2)+1/N sum(ln(w_i)-w_i) +1 = 0 - use ln(gamma()) => lgamma_r -*/ -double -update_w_and_nu(double nu0, double *w, double *ed, int N, int Nt, double nulow, double nuhigh) { - int Nd=30; /* no of samples to estimate nu */ - int nth,nth1,ci; - int Nthb0,Nthb; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_vecnu_t *threaddata; - - double deltanu,*q,thisnu,sumq; - if ((q=(double*)calloc((size_t)N,sizeof(double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((threaddata=(thread_data_vecnu_t*)malloc((size_t)Nt*sizeof(thread_data_vecnu_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - /* calculate min values a thread can handle */ - Nthb0=(N+Nt-1)/Nt; - /* iterate over threads, allocating indices per thread */ - ci=0; - for (nth=0; nth lgamma_r - - p: 1 or 8 -*/ -double -update_nu(double logsumw, int Nd, int Nt, double nulow, double nuhigh, int p, double nu_old) { - int ci,nth,nth1,Nthb,Nthb0; - double deltanu,thisnu,*q; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_vecnu_t *threaddata; - - if ((q=(double*)calloc((size_t)Nd,sizeof(double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((threaddata=(thread_data_vecnu_t*)malloc((size_t)Nt*sizeof(thread_data_vecnu_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - /* calculate psi((nu_old+p)/2)-ln((nu_old+p)/2) */ - double dgm=digamma((nu_old+(double)p)*0.5); - dgm=dgm-log((nu_old+(double)p)*0.5); /* psi((nu+p)/2)-log((nu+p)/2) */ - - - deltanu=(double)(nuhigh-nulow)/(double)Nd; - Nthb0=(Nd+Nt-1)/Nt; - /* check for too low number of values per thread, halve the threads */ - if (Nthb0<=2) { - Nt=Nt/2; - Nthb0=(Nd+Nt-1)/Nt; - } - ci=0; - for (nth=0; nth400.0) return 1.0; /* no effect on long baselines */ - //return 1.0/(1.0+0.4*exp(-0.05*ud)); - return 1.0/(1.0+1.8*exp(-0.05*ud)); -} - -static void * -threadfn_setblweight(void *data) { - thread_data_baselinewt_t *t=(thread_data_baselinewt_t*)data; - - int ci; - for (ci=0; ciNb; ci++) { - /* get sqrt(u^2+v^2) */ - double uu=t->u[ci+t->boff]*t->freq0; - double vv=t->v[ci+t->boff]*t->freq0; - double a=ncp_weight(sqrt(uu*uu+vv*vv)); - t->wt[8*(ci+t->boff)]*=a; - t->wt[8*(ci+t->boff)+1]*=a; - t->wt[8*(ci+t->boff)+2]*=a; - t->wt[8*(ci+t->boff)+3]*=a; - t->wt[8*(ci+t->boff)+4]*=a; - t->wt[8*(ci+t->boff)+5]*=a; - t->wt[8*(ci+t->boff)+6]*=a; - t->wt[8*(ci+t->boff)+7]*=a; - //printf("%lf %lf %lf\n",uu,vv,a); - } - - return NULL; -} - - -/* - taper data by weighting based on uv distance (for short baselines) - for example: use weights as the inverse density function - 1/( 1+f(u,v) ) - as u,v->inf, f(u,v) -> 0 so long baselines are not affected - x: Nbase*8 x 1 (input,output) data - u,v : Nbase x 1 - note: u = u/c, v=v/c here, so need freq to convert to wavelengths */ -void -whiten_data(int Nbase, double *x, double *u, double *v, double freq0, int Nt) { - pthread_attr_t attr; - pthread_t *th_array; - thread_data_baselinewt_t *threaddata; - - int ci,nth1,nth; - int Nthb0,Nthb; - - Nthb0=(Nbase+Nt-1)/Nt; - - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - if ((threaddata=(thread_data_baselinewt_t*)malloc((size_t)Nt*sizeof(thread_data_baselinewt_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nth%0P 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 z - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#include -#include -#include -#include -#include -#include "sagecal.h" - - -//#define DEBUG - -/* Jones matrix multiplication - C=A*B -*/ -static void -amb(complex double * __restrict a, complex double * __restrict b, complex double * __restrict c) { - c[0]=a[0]*b[0]+a[1]*b[2]; - c[1]=a[0]*b[1]+a[1]*b[3]; - c[2]=a[2]*b[0]+a[3]*b[2]; - c[3]=a[2]*b[1]+a[3]*b[3]; -} - -/* Jones matrix multiplication - C=A*B^H -*/ -static void -ambt(complex double * __restrict a, complex double * __restrict b, complex double * __restrict c) { - c[0]=a[0]*conj(b[0])+a[1]*conj(b[1]); - c[1]=a[0]*conj(b[2])+a[1]*conj(b[3]); - c[2]=a[2]*conj(b[0])+a[3]*conj(b[1]); - c[3]=a[2]*conj(b[2])+a[3]*conj(b[3]); -} - -/********************** sage minimization ***************************/ -/* worker thread function for prediction */ -static void * -predict_threadfn_withgain(void *data) { - thread_data_base_t *t=(thread_data_base_t*)data; - - int ci,cm,sta1,sta2; - complex double C[4],G1[4],G2[4],T1[4],T2[4]; - int M=(t->M); - cm=(t->clus); - int Ntilebase=(t->Nbase)*(t->tilesz); - int px; - double *pm; - - for (ci=0; ciNb; ci++) { - /* iterate over the sky model and calculate contribution */ - /* for this x[8*ci:8*(ci+1)-1] */ - memset(&(t->x[8*ci]),0,sizeof(double)*8); - - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - - px=(ci+t->boff)/((Ntilebase+t->carr[cm].nchunk-1)/t->carr[cm].nchunk); - pm=&(t->p[t->carr[cm].p[px]]); - /* gains for this cluster, for sta1,sta2 */ - G1[0]=(pm[sta1*8])+_Complex_I*(pm[sta1*8+1]); - G1[1]=(pm[sta1*8+2])+_Complex_I*(pm[sta1*8+3]); - G1[2]=(pm[sta1*8+4])+_Complex_I*(pm[sta1*8+5]); - G1[3]=(pm[sta1*8+6])+_Complex_I*(pm[sta1*8+7]); - G2[0]=(pm[sta2*8])+_Complex_I*(pm[sta2*8+1]); - G2[1]=(pm[sta2*8+2])+_Complex_I*(pm[sta2*8+3]); - G2[2]=(pm[sta2*8+4])+_Complex_I*(pm[sta2*8+5]); - G2[3]=(pm[sta2*8+6])+_Complex_I*(pm[sta2*8+7]); - - - /* use pre calculated values */ - C[0]=t->coh[4*M*ci+4*cm]; - C[1]=t->coh[4*M*ci+4*cm+1]; - C[2]=t->coh[4*M*ci+4*cm+2]; - C[3]=t->coh[4*M*ci+4*cm+3]; - - - /* form G1*C*G2' */ - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - - /* add to baseline visibilities */ - t->x[8*ci]+=creal(T2[0]); - t->x[8*ci+1]+=cimag(T2[0]); - t->x[8*ci+2]+=creal(T2[1]); - t->x[8*ci+3]+=cimag(T2[1]); - t->x[8*ci+4]+=creal(T2[2]); - t->x[8*ci+5]+=cimag(T2[2]); - t->x[8*ci+6]+=creal(T2[3]); - t->x[8*ci+7]+=cimag(T2[3]); - } - } - - return NULL; -} - - -/* minimization function (multithreaded) */ -/* p: size ??x1 parameters, not all belong to - this cluster - x: size nx1 data calculated - data: extra info needed */ -static void -mylm_fit_single_pth(double *p, double *x, int m, int n, void *data) { - - me_data_t *dp=(me_data_t*)data; - /* u,v,w : size Nbase*tilesz x 1 x: size Nbase*8*tilesz x 1 */ - /* barr: size Nbase*tilesz x 1 carr: size Mx1 */ - /* pp: size 8*N*M x 1 */ - /* pm: size Mx1 of double */ - - int nth,nth1,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthb0,Nthb; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_base_t *threaddata; - - int Nbase=(dp->Nbase); - int tilesz=(dp->tilesz); - - int Nbase1=Nbase*tilesz; - - /* calculate min baselines a thread can handle */ - //Nthb0=ceil((double)Nbase1/(double)Nt); - Nthb0=(Nbase1+Nt-1)/Nt; - - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((threaddata=(thread_data_base_t*)malloc((size_t)Nt*sizeof(thread_data_base_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr; - threaddata[nth].u=&(dp->u[ci]); - threaddata[nth].v=&(dp->v[ci]); - threaddata[nth].w=&(dp->w[ci]); - threaddata[nth].carr=dp->carr; - threaddata[nth].M=dp->M; - threaddata[nth].x=&(x[8*ci]); - threaddata[nth].N=dp->N; - threaddata[nth].Nbase=Nbase; - threaddata[nth].tilesz=tilesz; - threaddata[nth].p=p; - threaddata[nth].clus=(dp->clus); - threaddata[nth].coh=&(dp->coh[4*(dp->M)*ci]); - - - //printf("thread %d predict data from %d baselines %d\n",nth,8*ci,Nthb); - pthread_create(&th_array[nth],&attr,predict_threadfn_withgain,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - /* now wait for threads to finish */ - for(nth1=0; nth1M); - int Ntilebase=(t->Nbase)*(t->tilesz); - int px; - - for (ci=0; ciNb; ci++) { - /* iterate over the sky model and calculate contribution */ - /* for this x[8*ci:8*(ci+1)-1] */ - memset(&(t->x[8*ci]),0,sizeof(double)*8); - - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - for (cm=0; cm x[0.....Nbase*tilesz/nchunk-1] - p[1] -> x[Nbase*tilesz/nchunk......2*Nbase*tilesz-1] - .... - p[last] -> x[(nchunk-1)*Nbase*tilesz/nchunk......Nbase*tilesz] - - so given bindex, right p[] is bindex/((Nbase*tilesz+nchunk-1)/nchunk) - */ - - px=(ci+t->boff)/((Ntilebase+t->carr[cm].nchunk-1)/t->carr[cm].nchunk); - //pm=&(t->p[cm*8*N]); - pm=&(t->p[t->carr[cm].p[px]]); - G1[0]=(pm[sta1*8])+_Complex_I*(pm[sta1*8+1]); - G1[1]=(pm[sta1*8+2])+_Complex_I*(pm[sta1*8+3]); - G1[2]=(pm[sta1*8+4])+_Complex_I*(pm[sta1*8+5]); - G1[3]=(pm[sta1*8+6])+_Complex_I*(pm[sta1*8+7]); - G2[0]=(pm[sta2*8])+_Complex_I*(pm[sta2*8+1]); - G2[1]=(pm[sta2*8+2])+_Complex_I*(pm[sta2*8+3]); - G2[2]=(pm[sta2*8+4])+_Complex_I*(pm[sta2*8+5]); - G2[3]=(pm[sta2*8+6])+_Complex_I*(pm[sta2*8+7]); - - - /* use pre calculated values */ - C[0]=t->coh[4*M*ci+4*cm]; - C[1]=t->coh[4*M*ci+4*cm+1]; - C[2]=t->coh[4*M*ci+4*cm+2]; - C[3]=t->coh[4*M*ci+4*cm+3]; - - /* form G1*C*G2' */ - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - - /* add to baseline visibilities */ - t->x[8*ci]+=creal(T2[0]); - t->x[8*ci+1]+=cimag(T2[0]); - t->x[8*ci+2]+=creal(T2[1]); - t->x[8*ci+3]+=cimag(T2[1]); - t->x[8*ci+4]+=creal(T2[2]); - t->x[8*ci+5]+=cimag(T2[2]); - t->x[8*ci+6]+=creal(T2[3]); - t->x[8*ci+7]+=cimag(T2[3]); - } - } - } - - return NULL; -} - - -/* minimization function (multithreaded) */ -/* p: size mx1 parameters - x: size nx1 data calculated - data: extra info needed */ -static void -minimize_viz_full_pth(double *p, double *x, int m, int n, void *data) { - - me_data_t *dp=(me_data_t*)data; - /* u,v,w : size Nbase*tilesz x 1 x: size Nbase*8*tilesz x 1 */ - /* barr: size Nbase*tilesz x 1 carr: size Mx1 */ - /* pp: size 8*N*M x 1 */ - /* pm: size Mx1 of double */ - - int nth,nth1,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthb0,Nthb; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_base_t *threaddata; - - int Nbase1=(dp->Nbase)*(dp->tilesz); - - /* calculate min baselines a thread can handle */ - //Nthb0=ceil((double)Nbase1/(double)Nt); - Nthb0=(Nbase1+Nt-1)/Nt; - - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((threaddata=(thread_data_base_t*)malloc((size_t)Nt*sizeof(thread_data_base_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr; - threaddata[nth].u=&(dp->u[ci]); - threaddata[nth].v=&(dp->v[ci]); - threaddata[nth].w=&(dp->w[ci]); - threaddata[nth].carr=dp->carr; - threaddata[nth].M=dp->M; - threaddata[nth].x=&(x[8*ci]); - threaddata[nth].p=p; - threaddata[nth].N=dp->N; - threaddata[nth].Nbase=dp->Nbase; - threaddata[nth].tilesz=dp->tilesz; - threaddata[nth].coh=&(dp->coh[4*(dp->M)*ci]); - - //printf("thread %d predict data from %d baselines %d\n",nth,8*ci,Nthb); - pthread_create(&th_array[nth],&attr,predict_threadfn_withgain_full,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - /* now wait for threads to finish */ - for(nth1=0; nth10) { - /* calculate contribution from hidden data, subtract from x - actually, add the current model for this cluster to residual */ - lmdata.clus=cj; - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata); - my_daxpy(n, xsub, 1.0, xdummy); - - tilechunk=(tilesz+carr[cj].nchunk-1)/carr[cj].nchunk; - tcj=0; - init_res=final_res=0.0; - /* loop through hybrid parameter space */ - for (ck=0; ck0.0) { - nerr[cj]=(init_res-final_res)/init_res; - if (nerr[cj]<0.0) { nerr[cj]=0.0; } - } else { - nerr[cj]=0.0; - } - /* subtract current model */ - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata); - my_daxpy(n, xsub, -1.0, xdummy); - robust_nuM[cj]/=(double)carr[cj].nchunk; - } - } - - /* normalize nerr array so that the sum is 1 */ - total_err=my_dasum(M,nerr); - if (total_err>0.0) { - my_dscal(M, 1.0/total_err, nerr); - } - - /* flip weighting flag */ - if (randomize) { - weighted_iter=!weighted_iter; - } - } - free(nerr); - free(xdummy); - /* calculate mean robust_nu over all clusters */ - robust_nu0=my_dasum(M,robust_nuM)/(double)M; -#ifdef DEBUG - for (ci=0; cinuhigh) { - robust_nu0=nuhigh; - } - - /* final residual calculation */ - minimize_viz_full_pth(p, xsub, m, n, (void*)&lmdata); - my_daxpy(n, xsub, -1.0, x); - - *mean_nu=robust_nu0; - *res_1=my_dnrm2(n,x)/(double)n; - - free(xsub); - /* if final residual > initial residual, - return -1, else 0 - */ - if (*res_1>*res_0) { - return -1; - } - return 0; -} - - - - -/****************************************************************************/ -#ifdef HYBRID_CODE -/* slave thread 2GPU function */ -static void * -pipeline_slave_code_admm_flt(void *data) -{ - slave_tdata *td=(slave_tdata*)data; - gbdatafl_admm *gd=(gbdatafl_admm*)(td->pline->data); - int tid=td->tid; - - while(1) { - sync_barrier(&(td->pline->gate1)); /* stop at gate 1*/ - if(td->pline->terminate) break; /* if flag is set, break loop */ - sync_barrier(&(td->pline->gate2)); /* stop at gate 2 */ - /* do work : only one solver */ - //printf("state=%d, thread %d\n",gd->status[tid],tid); - if (gd->status[tid]==PT_DO_WORK_RRTR || gd->status[tid]==PT_DO_WORK_NSD) { -/************************* work *********************/ - me_data_t *t=(me_data_t *)gd->lmdata[tid]; - /* divide the tiles into chunks tilesz/nchunk */ - int tilechunk=(t->tilesz+t->carr[t->clus].nchunk-1)/t->carr[t->clus].nchunk; - - - int ci; - - int cj=0; - int ntiles; - double init_res,final_res; - init_res=final_res=0.0; - if (tid<2) { - /* for GPU, the cost func and jacobian are not used */ - /* loop over each chunk, with right parameter set and data set */ - for (ci=0; cicarr[t->clus].nchunk; ci++) { - /* divide the tiles into chunks tilesz/nchunk */ - if (cj+tilechunktilesz) { - ntiles=tilechunk; - } else { - ntiles=t->tilesz-cj; - } - - if (gd->status[tid]==PT_DO_WORK_NSD) { - nsd_solve_cuda_robust_admm_fl(&gd->p[tid][ci*(gd->M[tid])], &gd->Y[tid][ci*(gd->M[tid])], &gd->Z[tid][ci*(gd->M[tid])], &gd->x[tid][8*cj*t->Nbase], gd->M[tid]/8, ntiles*t->Nbase, gd->itermax[tid]+15, gd->admm_rho[tid], gd->nulow, gd->nuhigh, gd->info[tid], gd->cbhandle[tid], gd->solver_handle[tid], cj, ntiles, (void*)gd->lmdata[tid]); - } else { - /* max trust region radius: keep reasonable */ - float Delta0=2.0f; - rtr_solve_cuda_robust_admm_fl(&gd->p[tid][ci*(gd->M[tid])], &gd->Y[tid][ci*(gd->M[tid])], &gd->Z[tid][ci*(gd->M[tid])], &gd->x[tid][8*cj*t->Nbase], gd->M[tid]/8, ntiles*t->Nbase, gd->itermax[tid]+10, Delta0, Delta0*0.125f, gd->admm_rho[tid], gd->nulow, gd->nuhigh, gd->info[tid], gd->cbhandle[tid], gd->solver_handle[tid], cj, ntiles, (void*)gd->lmdata[tid]); - } - - init_res+=gd->info[tid][0]; - final_res+=gd->info[tid][1]; - cj=cj+tilechunk; - } - - } - - gd->info[tid][0]=init_res; - gd->info[tid][1]=final_res; - -/************************* work *********************/ - } else if (gd->status[tid]==PT_DO_AGPU) { - /* no cula needed: 0 at end */ - attach_gpu_to_thread2(select_work_gpu(MAX_GPU_ID,td->pline->thst),&gd->cbhandle[tid],&gd->solver_handle[tid],&gd->gWORK[tid],gd->data_size,0); - } else if (gd->status[tid]==PT_DO_DGPU) { - detach_gpu_from_thread2(gd->cbhandle[tid],gd->solver_handle[tid],gd->gWORK[tid],0); - } else if (gd->status[tid]==PT_DO_MEMRESET) { - reset_gpu_memory((double*)gd->gWORK[tid],gd->data_size); - } else if (gd->status[tid]!=PT_DO_NOTHING) { /* catch error */ - fprintf(stderr,"%s: %d: invalid mode for slave tid=%d status=%d\n",__FILE__,__LINE__,tid,gd->status[tid]); - exit(1); - } - } - return NULL; -} - -/* initialize the pipeline - and start the slaves rolling */ -static void -init_pipeline_admm_flt(th_pipeline *pline, - void *data) -{ - slave_tdata *t0,*t1; - pthread_attr_init(&(pline->attr)); - pthread_attr_setdetachstate(&(pline->attr),PTHREAD_CREATE_JOINABLE); - - init_th_barrier(&(pline->gate1),3); /* 3 threads, including master */ - init_th_barrier(&(pline->gate2),3); /* 3 threads, including master */ - pline->terminate=0; - pline->data=data; /* data should have pointers to t1 and t2 */ - - if ((t0=(slave_tdata*)malloc(sizeof(slave_tdata)))==0) { - fprintf(stderr,"no free memory\n"); - exit(1); - } - if ((t1=(slave_tdata*)malloc(sizeof(slave_tdata)))==0) { - fprintf(stderr,"no free memory\n"); - exit(1); - } - if ((pline->thst=(taskhist*)malloc(sizeof(taskhist)))==0) { - fprintf(stderr,"no free memory\n"); - exit(1); - } - - init_task_hist(pline->thst); - t0->pline=t1->pline=pline; - t0->tid=0; - t1->tid=1; /* link back t1, t2 to data so they could be freed */ - pline->sd0=t0; - pline->sd1=t1; - pthread_create(&(pline->slave0),&(pline->attr),pipeline_slave_code_admm_flt,(void*)t0); - pthread_create(&(pline->slave1),&(pline->attr),pipeline_slave_code_admm_flt,(void*)t1); - -} - -/* destroy the pipeline */ -/* need to kill the slaves first */ -static void -destroy_pipeline_admm_flt(th_pipeline *pline) -{ - - pline->terminate=1; - sync_barrier(&(pline->gate1)); - pthread_join(pline->slave0,NULL); - pthread_join(pline->slave1,NULL); - destroy_th_barrier(&(pline->gate1)); - destroy_th_barrier(&(pline->gate2)); - pthread_attr_destroy(&(pline->attr)); - destroy_task_hist(pline->thst); - free(pline->thst); - free(pline->sd0); - free(pline->sd1); - pline->data=NULL; -} - -//#define DEBUG -int -sagefit_visibilities_admm_dual_pt_flt(double *u, double *v, double *w, double *x, int N, - int Nbase, int tilesz, baseline_t *barr, clus_source_t *carr, complex double *coh, int M, int Mt, double freq0, double fdelta, double *pp, double *Y, double *BZ, double uvmin, int Nt, int max_emiter, int max_iter, int max_lbfgs, int lbfgs_m, int gpu_threads, int linsolv,int solver_mode, double nulow, double nuhigh, int randomize,double *admm_rho, double *mean_nu, double *res_0, double *res_1) { - - - int ci,cj; - double *p; // parameters: m x 1 - int m, n; - double opts[CLM_OPTS_SZ], info0[CLM_INFO_SZ], info1[CLM_INFO_SZ]; - me_data_t lmdata0,lmdata1; - int Nbase1; - - double *xdummy0,*xdummy1,*xsub,*xo; - double *nerr; /* array to store cost reduction per cluster */ - int weighted_iter,this_itermax0,this_itermax1,total_iter; - double total_err; - - /* rearraged memory for GPU use */ - double *ddcoh; - short *ddbase; - - int *cr=0; /* array for random permutation of clusters */ - int c0,c1; - - //opts[0]=LM_INIT_MU; opts[1]=1E-15; opts[2]=1E-15; opts[3]=1E-20; - opts[0]=CLM_INIT_MU; opts[1]=1E-9; opts[2]=1E-9; opts[3]=1E-9; - opts[4]=-CLM_DIFF_DELTA; - - /* robust */ - double robust_nu0; - double *robust_nuM; - - /* no. of parameters >= than the no of clusters*8N */ - m=N*Mt*8; - /* no of data */ - n=Nbase*tilesz*8; - - /* true no of baselines */ - Nbase1=Nbase*tilesz; - - float *ddcohf, *pf, *Yf, *Zf, *xdummy0f, *xdummy1f; -/********* thread data ******************/ - /* barrier */ - th_pipeline tp; - gbdatafl_admm tpg; -/****************************************/ - - /* use full parameter space */ - p=pp; - lmdata0.clus=lmdata1.clus=-1; - /* setup data for lmfit */ - lmdata0.u=lmdata1.u=u; - lmdata0.v=lmdata1.v=v; - lmdata0.w=lmdata1.w=w; - lmdata0.Nbase=lmdata1.Nbase=Nbase; - lmdata0.tilesz=lmdata1.tilesz=tilesz; - lmdata0.N=lmdata1.N=N; - lmdata0.barr=lmdata1.barr=barr; - lmdata0.carr=lmdata1.carr=carr; - lmdata0.M=lmdata1.M=M; - lmdata0.Mt=lmdata1.Mt=Mt; - lmdata0.freq0=lmdata1.freq0=&freq0; - lmdata0.Nt=lmdata1.Nt=Nt; - lmdata0.coh=lmdata1.coh=coh; - /* rearrange coh for GPU use */ - if ((ddcoh=(double*)calloc((size_t)(M*Nbase1*8),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((ddcohf=(float*)calloc((size_t)(M*Nbase1*8),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((ddbase=(short*)calloc((size_t)(Nbase1*2),sizeof(short)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - rearrange_coherencies(Nbase1, barr, coh, ddcoh, ddbase, M, Nt); - lmdata0.ddcoh=lmdata1.ddcoh=ddcoh; - lmdata0.ddbase=lmdata1.ddbase=ddbase; - - /* ddcohf (float) << ddcoh (double) */ - double_to_float(ddcohf,ddcoh,M*Nbase1*8,Nt); - lmdata0.ddcohf=lmdata1.ddcohf=ddcohf; - - if ((xsub=(double*)calloc((size_t)(n),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xo=(double*)calloc((size_t)(n),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xdummy0=(double*)calloc((size_t)(n),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xdummy1=(double*)calloc((size_t)(n),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((nerr=(double*)calloc((size_t)(M),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xdummy0f=(float*)calloc((size_t)(n),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xdummy1f=(float*)calloc((size_t)(n),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((pf=(float*)calloc((size_t)(Mt*8*N),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((Yf=(float*)calloc((size_t)(Mt*8*N),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((Zf=(float*)calloc((size_t)(Mt*8*N),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((robust_nuM=(double*)calloc((size_t)(M),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - /* starting guess of robust nu */ - robust_nu0=nulow; - - double_to_float(pf,p,Mt*8*N,Nt); - double_to_float(Yf,Y,Mt*8*N,Nt); - double_to_float(Zf,BZ,Mt*8*N,Nt); - /* remember for each partition how much the cost function decreases - in the next EM iteration, we allocate more LM iters to partitions - where const function significantly decreases. So two stages - 1) equal LM iters (find the decrease) 2) weighted LM iters */ - weighted_iter=0; - total_iter=M*max_iter; /* total iterations per EM */ -/********** setup threads *******************************/ - init_pipeline_admm_flt(&tp,&tpg); - sync_barrier(&(tp.gate1)); /* sync at gate 1*/ - tpg.status[0]=tpg.status[1]=PT_DO_AGPU; - /* also calculate the total storage needed to be allocated on a GPU */ - /* determine total size for memory allocation */ - int64_t data_sz=0; - /* size for RTR/NSD (float), 128 is the ThreadsPerBlock - NSD is a bit lower - Use dummy data size - */ - if (solver_mode==SM_NSD_RLBFGS) { - //data_sz=(8*N*(7+(Nbase1+128-1)/128)+N+8*Nbase1*2+3*Nbase1)*sizeof(float); - data_sz=8*sizeof(float); - } else { /* default is RTR */ - //data_sz=(8*N*(11+(Nbase1+128-1)/128)+N+8*Nbase1*2+3*Nbase1)*sizeof(float); - data_sz=8*sizeof(float); - } - - tpg.data_size=data_sz; - tpg.nulow=nulow; - tpg.nuhigh=nuhigh; - tpg.randomize=randomize; - sync_barrier(&(tp.gate2)); /* sync at gate 2*/ - - sync_barrier(&(tp.gate1)); /* sync at gate 1*/ - tpg.status[0]=tpg.status[1]=PT_DO_NOTHING; - sync_barrier(&(tp.gate2)); /* sync at gate 2*/ - -/********** done setup threads *******************************/ - - /* initial residual calculation - subtract full model from data */ - minimize_viz_full_pth(p, xsub, m, n, (void*)&lmdata0); - memcpy(xo,x,(size_t)(n)*sizeof(double)); - my_daxpy(n, xsub, -1.0, xo); - *res_0=my_dnrm2(n,xo)/(double)n; - - int iter_bar=(int)ceil((0.80/(double)M)*((double)total_iter)); - for (ci=0; ci1) { - /* find a random permutation of clusters */ - cr=random_permutation(M,weighted_iter,nerr); - } else { - cr=NULL; - } - - for (cj=0; cj0 || this_itermax1>0) { - /* calculate contribution from hidden data, subtract from x */ - /* since x has already subtracted this model, just add - the ones we are solving for */ - - sync_barrier(&(tp.gate1)); /* sync at gate 1 */ - memcpy(xdummy0,xo,(size_t)(n)*sizeof(double)); - memcpy(xdummy1,xo,(size_t)(n)*sizeof(double)); - lmdata0.clus=c0; - lmdata1.clus=c1; - - /* NOTE: conditional mean x^i = s^i + 0.5 * residual^i */ - /* so xdummy=0.5 ( 2*model + residual ) */ - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata0); - my_daxpy(n, xsub, 2.0, xdummy0); - my_dscal(n, 0.5, xdummy0); - my_daxpy(n, xsub, 1.0, xo); - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata1); - my_daxpy(n, xsub, 2.0, xdummy1); - my_dscal(n, 0.5, xdummy1); - my_daxpy(n, xsub, 1.0, xo); -/**************************************************************************/ - /* xdummy*f (float) << xdummy* (double) */ - double_to_float(xdummy0f,xdummy0,n,Nt); - double_to_float(xdummy1f,xdummy1,n,Nt); - /* run this from a separate thread */ - tpg.p[0]=&pf[carr[c0].p[0]]; /* length carr[c0].nchunk times */ - tpg.Y[0]=&Yf[carr[c0].p[0]]; /* length carr[c0].nchunk times */ - tpg.Z[0]=&Zf[carr[c0].p[0]]; /* length carr[c0].nchunk times */ - tpg.admm_rho[0]=(float)admm_rho[c0]; - tpg.x[0]=xdummy0f; - tpg.M[0]=8*N; /* even though size of p is > M, dont change this */ - tpg.N[0]=n; /* Nbase*tilesz*8 */ - tpg.itermax[0]=this_itermax0; - tpg.opts[0]=opts; - tpg.info[0]=info0; - tpg.linsolv=linsolv; - tpg.lmdata[0]=&lmdata0; - - tpg.p[1]=&pf[carr[c1].p[0]]; /* length carr[c1].nchunk times */ - tpg.Y[1]=&Yf[carr[c1].p[0]]; /* length carr[c1].nchunk times */ - tpg.Z[1]=&Zf[carr[c1].p[0]]; /* length carr[c1].nchunk times */ - tpg.admm_rho[1]=(float)admm_rho[c1]; - tpg.x[1]=xdummy1f; - tpg.M[1]=8*N; /* even though size of p is > M, dont change this */ - tpg.N[1]=n; /* Nbase*tilesz*8 */ - tpg.itermax[1]=this_itermax1; - tpg.opts[1]=opts; - tpg.info[1]=info1; - tpg.linsolv=linsolv; - tpg.lmdata[1]=&lmdata1; -/**************************************************************************/ - - /* both threads do work */ - if (solver_mode==SM_NSD_RLBFGS) { - tpg.status[0]=tpg.status[1]=PT_DO_WORK_NSD; - } else { - tpg.status[0]=tpg.status[1]=PT_DO_WORK_RRTR; - } - sync_barrier(&(tp.gate2)); /* sync at gate 2 */ - sync_barrier(&(tp.gate1)); /* sync at gate 1 */ - tpg.status[0]=tpg.status[1]=PT_DO_NOTHING; - sync_barrier(&(tp.gate2)); /* sync at gate 2 */ -#ifdef DEBUG -printf("1: %lf -> %lf 2: %lf -> %lf\n\n\n",info0[0],info0[1],info1[0],info1[1]); -#endif - /* catch -ve value here */ - if (info0[0]>0.0) { - nerr[c0]=(info0[0]-info0[1])/info0[0]; - if (nerr[c0]<0.0) { nerr[c0]=0.0; } - } else { - nerr[c0]=0.0; - } - if (info1[0]>0.0) { - nerr[c1]=(info1[0]-info1[1])/info1[0]; - if (nerr[c1]<0.0) { nerr[c1]=0.0; } - } else { - nerr[c1]=0.0; - } - /* update robust_nu */ - robust_nuM[c0]+=lmdata0.robust_nu; - robust_nuM[c1]+=lmdata1.robust_nu; - /* p (double) << pf (float) */ - float_to_double(&p[carr[c0].p[0]],&pf[carr[c0].p[0]],carr[c0].nchunk*8*N,Nt); - float_to_double(&p[carr[c1].p[0]],&pf[carr[c1].p[0]],carr[c1].nchunk*8*N,Nt); - /* once again subtract solved model from data */ - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata0); - my_daxpy(n, xsub, -1.0, xo); - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata1); - my_daxpy(n, xsub, -1.0, xo); - - } - } - /* odd cluster out, if M is odd */ - if (M%2) { - if (randomize && M>1) { - c0=cr[M-1]; - } else { - c0=M-1; - } - /* calculate max LM iter for this cluster */ - if (weighted_iter) { - this_itermax0=(int)((0.20*nerr[c0])*((double)total_iter))+iter_bar; - } else { - this_itermax0=max_iter; - } -#ifdef DEBUG - printf("Cluster %d(iter=%d, wt=%lf)\n",c0,this_itermax0,nerr[c0]); -#endif - if (this_itermax0>0) { -/**************************************************************************/ - sync_barrier(&(tp.gate1)); /* sync at gate 1 */ - /* calculate contribution from hidden data, subtract from x */ - memcpy(xdummy0,xo,(size_t)(n)*sizeof(double)); - lmdata0.clus=c0; - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata0); - my_daxpy(n, xsub, 1.0, xdummy0); - my_daxpy(n, xsub, 1.0, xo); - - double_to_float(xdummy0f,xdummy0,n,Nt); - /* run this from a separate thread */ - tpg.p[0]=&pf[carr[c0].p[0]]; - tpg.Y[0]=&Yf[carr[c0].p[0]]; - tpg.Z[0]=&Zf[carr[c0].p[0]]; - tpg.admm_rho[0]=(float)admm_rho[c0]; - tpg.x[0]=xdummy0f; - tpg.M[0]=8*N; - tpg.N[0]=n; - tpg.itermax[0]=this_itermax0; - tpg.opts[0]=opts; - tpg.info[0]=info0; - tpg.linsolv=linsolv; - tpg.lmdata[0]=&lmdata0; - - if (solver_mode==SM_NSD_RLBFGS) { - tpg.status[0]=PT_DO_WORK_NSD; - } else { - tpg.status[0]=PT_DO_WORK_RRTR; - } - - tpg.status[1]=PT_DO_NOTHING; - sync_barrier(&(tp.gate2)); /* sync at gate 2 */ -/**************************************************************************/ - sync_barrier(&(tp.gate1)); /* sync at gate 1 */ - tpg.status[0]=tpg.status[1]=PT_DO_NOTHING; - sync_barrier(&(tp.gate2)); /* sync at gate 2 */ - -#ifdef DEBUG -printf("1: %lf -> %lf\n\n\n",info0[0],info0[1]); -#endif - /* catch -ve value here */ - if (info0[0]>0.0) { - nerr[c0]=(info0[0]-info0[1])/info0[0]; - if (nerr[c0]<0.0) { nerr[c0]=0.0; } - } else { - nerr[c0]=0.0; - } - /* update robust_nu */ - robust_nuM[c0]+=lmdata0.robust_nu; - /* once again subtract solved model from data */ - float_to_double(&p[carr[c0].p[0]],&pf[carr[c0].p[0]],carr[c0].nchunk*8*N,Nt); - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata0); - my_daxpy(n, xsub, -1.0, xo); - } - } - /* normalize nerr array so that the sum is 1 */ - total_err=my_dasum(M,nerr); - if (total_err>0.0) { - my_dscal(M, 1.0/total_err, nerr); - } - if (randomize && M>1) { /* nothing to randomize if only 1 direction */ - /* flip weighting flag */ - weighted_iter=!weighted_iter; - free(cr); - } - /**************** End EM iteration ***********************/ - } - free(nerr); - free(xo); - free(xdummy0); - free(xdummy1); - free(ddcoh); - free(ddbase); - free(xdummy0f); - free(xdummy1f); - free(pf); - free(Yf); - free(Zf); - free(ddcohf); - /* calculate mean robust_nu over all clusters */ - robust_nu0=my_dasum(M,robust_nuM)/(double)M; -#ifdef DEBUG - for (ci=0; cinuhigh) { - robust_nu0=nuhigh; - } - - /******** free threads ***************/ - sync_barrier(&(tp.gate1)); /* sync at gate 1*/ - tpg.status[0]=tpg.status[1]=PT_DO_DGPU; - sync_barrier(&(tp.gate2)); /* sync at gate 2*/ - - - destroy_pipeline_admm_flt(&tp); - /******** done free threads ***************/ - - /* final residual calculation */ - minimize_viz_full_pth(p, xsub, m, n, (void*)&lmdata0); - my_daxpy(n, xsub, -1.0, x); - - *mean_nu=robust_nu0; - *res_1=my_dnrm2(n,x)/(double)n; - - free(xsub); - - /* if final residual > initial residual, - return -1, else 0 - */ - if (*res_1>*res_0) { - return -1; - } - return 0; -} - - -int -sagefit_visibilities_admm_dual_pt_flt_one(double *u, double *v, double *w, double *x, int N, - int Nbase, int tilesz, baseline_t *barr, clus_source_t *carr, complex double *coh, int M, int Mt, double freq0, double fdelta, double *pp, double *Y, double *BZ, double uvmin, int Nt, int max_emiter, int max_iter, int max_lbfgs, int lbfgs_m, int gpu_threads, int linsolv,int solver_mode, double nulow, double nuhigh, int randomize,double *admm_rho, double *mean_nu, double *res_0, double *res_1) { - - - int ci,cj; - double *p; // parameters: m x 1 - int m, n; - double opts[CLM_OPTS_SZ], info0[CLM_INFO_SZ]; - me_data_t lmdata0,lmdata1; - int Nbase1; - - double *xdummy0,*xdummy1,*xsub,*xo; - double *nerr; /* array to store cost reduction per cluster */ - int weighted_iter,this_itermax0,total_iter; - double total_err; - - /* rearraged memory for GPU use */ - double *ddcoh; - short *ddbase; - - int *cr=0; /* array for random permutation of clusters */ - int c0; - - //opts[0]=LM_INIT_MU; opts[1]=1E-15; opts[2]=1E-15; opts[3]=1E-20; - opts[0]=CLM_INIT_MU; opts[1]=1E-9; opts[2]=1E-9; opts[3]=1E-9; - opts[4]=-CLM_DIFF_DELTA; - - /* robust */ - double robust_nu0; - double *robust_nuM; - - /* no. of parameters >= than the no of clusters*8N */ - m=N*Mt*8; - /* no of data */ - n=Nbase*tilesz*8; - - /* true no of baselines */ - Nbase1=Nbase*tilesz; - - float *ddcohf, *pf, *Yf, *Zf, *xdummy0f, *xdummy1f; -/********* thread data ******************/ - /* barrier */ - th_pipeline tp; - gbdatafl_admm tpg; -/****************************************/ - - /* use full parameter space */ - p=pp; - lmdata0.clus=lmdata1.clus=-1; - /* setup data for lmfit */ - lmdata0.u=lmdata1.u=u; - lmdata0.v=lmdata1.v=v; - lmdata0.w=lmdata1.w=w; - lmdata0.Nbase=lmdata1.Nbase=Nbase; - lmdata0.tilesz=lmdata1.tilesz=tilesz; - lmdata0.N=lmdata1.N=N; - lmdata0.barr=lmdata1.barr=barr; - lmdata0.carr=lmdata1.carr=carr; - lmdata0.M=lmdata1.M=M; - lmdata0.Mt=lmdata1.Mt=Mt; - lmdata0.freq0=lmdata1.freq0=&freq0; - lmdata0.Nt=lmdata1.Nt=Nt; - lmdata0.coh=lmdata1.coh=coh; - /* rearrange coh for GPU use */ - if ((ddcoh=(double*)calloc((size_t)(M*Nbase1*8),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((ddcohf=(float*)calloc((size_t)(M*Nbase1*8),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((ddbase=(short*)calloc((size_t)(Nbase1*2),sizeof(short)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - rearrange_coherencies(Nbase1, barr, coh, ddcoh, ddbase, M, Nt); - lmdata0.ddcoh=lmdata1.ddcoh=ddcoh; - lmdata0.ddbase=lmdata1.ddbase=ddbase; - - /* ddcohf (float) << ddcoh (double) */ - double_to_float(ddcohf,ddcoh,M*Nbase1*8,Nt); - lmdata0.ddcohf=lmdata1.ddcohf=ddcohf; - - if ((xsub=(double*)calloc((size_t)(n),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xo=(double*)calloc((size_t)(n),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xdummy0=(double*)calloc((size_t)(n),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xdummy1=(double*)calloc((size_t)(n),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((nerr=(double*)calloc((size_t)(M),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xdummy0f=(float*)calloc((size_t)(n),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xdummy1f=(float*)calloc((size_t)(n),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((pf=(float*)calloc((size_t)(Mt*8*N),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((Yf=(float*)calloc((size_t)(Mt*8*N),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((Zf=(float*)calloc((size_t)(Mt*8*N),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((robust_nuM=(double*)calloc((size_t)(M),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - /* starting guess of robust nu */ - robust_nu0=nulow; - - double_to_float(pf,p,Mt*8*N,Nt); - double_to_float(Yf,Y,Mt*8*N,Nt); - double_to_float(Zf,BZ,Mt*8*N,Nt); - /* remember for each partition how much the cost function decreases - in the next EM iteration, we allocate more LM iters to partitions - where const function significantly decreases. So two stages - 1) equal LM iters (find the decrease) 2) weighted LM iters */ - weighted_iter=0; - total_iter=M*max_iter; /* total iterations per EM */ -/********** setup threads *******************************/ - init_pipeline_admm_flt(&tp,&tpg); - sync_barrier(&(tp.gate1)); /* sync at gate 1*/ - tpg.status[0]=tpg.status[1]=PT_DO_AGPU; - /* also calculate the total storage needed to be allocated on a GPU */ - /* determine total size for memory allocation */ - int64_t data_sz=0; - /* size for RTR/NSD (float), 128 is the ThreadsPerBlock - NSD is a bit lower, but use the same - */ - //data_sz=(8*N*(11+(Nbase1+128-1)/128)+N+8*Nbase1*2+3*Nbase1)*sizeof(float); - data_sz=8*sizeof(float); - - tpg.data_size=data_sz; - tpg.nulow=nulow; - tpg.nuhigh=nuhigh; - tpg.randomize=randomize; - sync_barrier(&(tp.gate2)); /* sync at gate 2*/ - - sync_barrier(&(tp.gate1)); /* sync at gate 1*/ - tpg.status[0]=tpg.status[1]=PT_DO_NOTHING; - sync_barrier(&(tp.gate2)); /* sync at gate 2*/ - -/********** done setup threads *******************************/ - - /* initial residual calculation - subtract full model from data */ - minimize_viz_full_pth(p, xsub, m, n, (void*)&lmdata0); - memcpy(xo,x,(size_t)(n)*sizeof(double)); - my_daxpy(n, xsub, -1.0, xo); - *res_0=my_dnrm2(n,xo)/(double)n; - - int iter_bar=(int)ceil((0.80/(double)M)*((double)total_iter)); - for (ci=0; ci1) { - /* find a random permutation of clusters */ - cr=random_permutation(M,weighted_iter,nerr); - } else { - cr=NULL; - } - - /* only one cluster at a time */ - for (cj=0; cj1) { - c0=cr[cj]; - } else { - c0=cj; - } - /* calculate max LM iter for this cluster */ - if (weighted_iter) { - this_itermax0=(int)((0.20*nerr[c0])*((double)total_iter))+iter_bar; - } else { - this_itermax0=max_iter; - } -#ifdef DEBUG - printf("Cluster %d(iter=%d, wt=%lf)\n",c0,this_itermax0,nerr[c0]); -#endif - if (this_itermax0>0) { -/**************************************************************************/ - sync_barrier(&(tp.gate1)); /* sync at gate 1 */ - /* calculate contribution from hidden data, subtract from x */ - memcpy(xdummy0,xo,(size_t)(n)*sizeof(double)); - lmdata0.clus=c0; - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata0); - my_daxpy(n, xsub, 1.0, xdummy0); - my_daxpy(n, xsub, 1.0, xo); - - double_to_float(xdummy0f,xdummy0,n,Nt); - /* run this from a separate thread */ - tpg.p[0]=&pf[carr[c0].p[0]]; - tpg.Y[0]=&Yf[carr[c0].p[0]]; - tpg.Z[0]=&Zf[carr[c0].p[0]]; - tpg.admm_rho[0]=(float)admm_rho[c0]; - tpg.x[0]=xdummy0f; - tpg.M[0]=8*N; - tpg.N[0]=n; - tpg.itermax[0]=this_itermax0; - tpg.opts[0]=opts; - tpg.info[0]=info0; - tpg.linsolv=linsolv; - tpg.lmdata[0]=&lmdata0; - - tpg.status[0]=PT_DO_WORK_RRTR; - - tpg.status[1]=PT_DO_NOTHING; - sync_barrier(&(tp.gate2)); /* sync at gate 2 */ -/**************************************************************************/ - sync_barrier(&(tp.gate1)); /* sync at gate 1 */ - tpg.status[0]=tpg.status[1]=PT_DO_NOTHING; - sync_barrier(&(tp.gate2)); /* sync at gate 2 */ - -#ifdef DEBUG -printf("1: %lf -> %lf\n\n\n",info0[0],info0[1]); -#endif - /* catch -ve value here */ - if (info0[0]>0.0) { - nerr[c0]=(info0[0]-info0[1])/info0[0]; - if (nerr[c0]<0.0) { nerr[c0]=0.0; } - } else { - nerr[c0]=0.0; - } - /* update robust_nu */ - robust_nuM[c0]+=lmdata0.robust_nu; - /* once again subtract solved model from data */ - float_to_double(&p[carr[c0].p[0]],&pf[carr[c0].p[0]],carr[c0].nchunk*8*N,Nt); - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata0); - my_daxpy(n, xsub, -1.0, xo); - } - } - /* normalize nerr array so that the sum is 1 */ - total_err=my_dasum(M,nerr); - if (total_err>0.0) { - my_dscal(M, 1.0/total_err, nerr); - } - if (randomize && M>1) { /* nothing to randomize if only 1 direction */ - /* flip weighting flag */ - weighted_iter=!weighted_iter; - free(cr); - } - /**************** End EM iteration ***********************/ - } - free(nerr); - free(xo); - free(xdummy0); - free(xdummy1); - free(ddcoh); - free(ddbase); - free(xdummy0f); - free(xdummy1f); - free(pf); - free(Yf); - free(Zf); - free(ddcohf); - /* calculate mean robust_nu over all clusters */ - robust_nu0=my_dasum(M,robust_nuM)/(double)M; -#ifdef DEBUG - for (ci=0; cinuhigh) { - robust_nu0=nuhigh; - } - - - /******** free threads ***************/ - sync_barrier(&(tp.gate1)); /* sync at gate 1*/ - tpg.status[0]=tpg.status[1]=PT_DO_DGPU; - sync_barrier(&(tp.gate2)); /* sync at gate 2*/ - - - destroy_pipeline_admm_flt(&tp); - /******** done free threads ***************/ - - /* final residual calculation */ - minimize_viz_full_pth(p, xsub, m, n, (void*)&lmdata0); - my_daxpy(n, xsub, -1.0, x); - - *mean_nu=robust_nu0; - *res_1=my_dnrm2(n,x)/(double)n; - - free(xsub); - - /* if final residual > initial residual, - return -1, else 0 - */ - if (*res_1>*res_0) { - return -1; - } - return 0; -} -#endif /* HYBRID_CODE */ diff --git a/src/lib/barrier.c b/src/lib/barrier.c deleted file mode 100644 index 733429f..0000000 --- a/src/lib/barrier.c +++ /dev/null @@ -1,121 +0,0 @@ -/* - * - Copyright (C) 2006-2008 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - -#include -#include -#include -#include - -#include "sagecal.h" -/* implementation of a barrier to sync threads. - The barrier has two doors (enter and exit). Only one door - can be open at a time. Initially the enter door is open. - All threads that enter the barrier are sleeping (wait). - The last thread to enter the barrier will - 1)close the enter door - 2)wakeup all sleeping threads. - 3)open the exit door. - So the woken up threads will leave the barrier one by - one, as they are awoken. The last thread to leave the barrier - will - 1)open the enter door - 2)close the exit door, - So finally the barrier reaches its initial state -*/ - -/* initialize barrier */ -/* N - no. of accomodated threads */ -void -init_th_barrier(th_barrier *barrier, int N) -{ - barrier->tcount=0; /* initially empty */ - barrier->nthreads=N; - pthread_mutex_init(&barrier->enter_mutex,NULL); - pthread_mutex_init(&barrier->exit_mutex,NULL); - pthread_cond_init(&barrier->lastthread_cond,NULL); - pthread_cond_init(&barrier->exit_cond,NULL); -} -/* destroy barrier */ -void -destroy_th_barrier(th_barrier *barrier) -{ - pthread_mutex_destroy(&barrier->enter_mutex); - pthread_mutex_destroy(&barrier->exit_mutex); - pthread_cond_destroy(&barrier->lastthread_cond); - pthread_cond_destroy(&barrier->exit_cond); - barrier->tcount=barrier->nthreads=0; -} -/* the main operation of the barrier */ -void -sync_barrier(th_barrier *barrier) -{ - /* trivial case */ - if(barrier->nthreads <2) return; - /* else */ - /* new threads enters the barrier. Now close the entry door - so that other threads cannot enter the barrier until we are done */ - pthread_mutex_lock(&barrier->enter_mutex); - /* next lock the exit mutex - no threads can leave either */ - pthread_mutex_lock(&barrier->exit_mutex); - /* now check to see if this is the last expected thread */ - if( ++(barrier->tcount) < barrier->nthreads) { - /* no. this is not the last thread. so open the entry door */ - pthread_mutex_unlock(&barrier->enter_mutex); - /* go to sleep */ - pthread_cond_wait(&barrier->exit_cond,&barrier->exit_mutex); - } else { - /* this is the last thread */ - /* wakeup sleeping threads */ - pthread_cond_broadcast(&barrier->exit_cond); - /* go to sleep until all threads are woken up - and leave the barrier */ - pthread_cond_wait(&barrier->lastthread_cond,&barrier->exit_mutex); -/* now all threads have left the barrier. so open the entry door again */ - pthread_mutex_unlock(&barrier->enter_mutex); - } - /* next to the last thread leaving the barrier */ - if(--(barrier->tcount)==1) { - /* wakeup the last sleeping thread */ - pthread_cond_broadcast(&barrier->lastthread_cond); - } - pthread_mutex_unlock(&barrier->exit_mutex); -} - - - -/* master and two slaves */ -//int -//main(int argc, char *argv[]) { -// th_pipeline p; -// -// gbdata g; -// -// init_pipeline(&p,&g); -//sync_barrier(&(p.gate1)); /* stop at gate 1 */ -// g.status=0; /* master work */ -//sync_barrier(&(p.gate2)); /* stop at gate 2 */ -// //exec_pipeline(&p); -//sync_barrier(&(p.gate1)); /* stop at gate 1 */ -// g.status=10; /* master work */ -//sync_barrier(&(p.gate2)); /* stop at gate 2 */ -// //exec_pipeline(&p); -// destroy_pipeline(&p); -// /* still need to free slave_data structs, from data */ -// return 0; -//} diff --git a/src/lib/clmfit.c b/src/lib/clmfit.c deleted file mode 100644 index 762ca78..0000000 --- a/src/lib/clmfit.c +++ /dev/null @@ -1,1978 +0,0 @@ -/* - * - Copyright (C) 2006-2008 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#include -#include -#include -#include -#include -#include - -#include "sagecal.h" -#include - -//#define DEBUG -/* helper functions for diagnostics */ -static void -checkCudaError(cudaError_t err, char *file, int line) -{ -#ifdef CUDA_DEBUG - if(!err) - return; - fprintf(stderr,"GPU (CUDA): %s %s %d\n", cudaGetErrorString(err),file,line); - exit(EXIT_FAILURE); -#endif -} - - -static void -checkCublasError(cublasStatus_t cbstatus, char *file, int line) -{ -#ifdef CUDA_DEBUG - if (cbstatus!=CUBLAS_STATUS_SUCCESS) { - fprintf(stderr,"%s: %d: CUBLAS failure\n",file,line); - exit(EXIT_FAILURE); - } -#endif -} - - - -/** keep interface almost the same as in levmar **/ -int -clevmar_der_single( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - * info[5]= # iterations, - * info[6]=reason for terminating: 1 - stopped by small gradient J^T e - * 2 - stopped by small Dp - * 3 - stopped by itmax - * 4 - singular matrix. Restart from current p with increased mu - * 5 - no further error reduction is possible. Restart with increased mu - * 6 - stopped by small ||e||_2 - * 7 - stopped by invalid (i.e. NaN or Inf) "func" values. This is a user error - * info[7]= # function evaluations - * info[8]= # Jacobian evaluations - * info[9]= # linear systems solved, i.e. # attempts for reducing error - */ - - int card, /* device 0, 1 */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - void *adata) /* pointer to possibly additional data, passed uninterpreted to func & jacf. - * Set to NULL if not needed - */ -{ - - /* general note: all device variables end with a 'd' */ - int stop=0; - cudaError_t err; - cublasStatus_t cbstatus; - cublasHandle_t cbhandle; - cusolverDnHandle_t solver_handle; - - int nu=2,nu2; - double p_L2, Dp_L2=DBL_MAX, dF, dL, p_eL2, jacTe_inf=0.0, pDp_eL2, init_p_eL2; - double tmp,mu=0.0; - double tau, eps1, eps2, eps2_sq, eps3; - int k,ci,issolved; - - double *hx; - double *hxd; - - double *ed; - double *xd; - - double *jac,*jacd; - - double *jacTjacd,*jacTjacd0; - - double *pnew,*Dpd,*bd; - double *pd,*pnewd; - double *jacTed; - - /* used in QR solver */ - double *taud; - - /* used in SVD solver */ - double *Ud; - double *VTd; - double *Sd; - - int solve_axb=linsolv; - - /* setup default settings */ - if(opts){ - tau=opts[0]; - eps1=opts[1]; - eps2=opts[2]; - eps2_sq=opts[2]*opts[2]; - eps3=opts[3]; - } else { - tau=CLM_INIT_MU; - eps1=CLM_STOP_THRESH; - eps2=CLM_STOP_THRESH; - eps2_sq=CLM_STOP_THRESH*CLM_STOP_THRESH; - eps3=CLM_STOP_THRESH; - } - - /* calculate no of cuda threads and blocks */ - int ThreadsPerBlock=DEFAULT_TH_PER_BK; - int BlocksPerGrid=(M+ThreadsPerBlock-1)/ThreadsPerBlock; - - err=cudaSetDevice(card); - checkCudaError(err,__FILE__,__LINE__); - cusolverDnCreate(&solver_handle); - - cbstatus=cublasCreate(&cbhandle); - if (cbstatus!=CUBLAS_STATUS_SUCCESS) { - fprintf(stderr,"%s: %d: CUBLAS create fail\n",__FILE__,__LINE__); - exit(1); - } - - err=cudaMalloc((void**)&xd, N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - /* xd <=x */ - err=cudaMemcpy(xd, x, N*sizeof(double), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - - - if ((hx=(double*)calloc((size_t)N,sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((jac=(double*)calloc((size_t)N*M,sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((pnew=(double*)calloc((size_t)M,sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - - - err=cudaMalloc((void**)&jacd, M*N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&jacTjacd, M*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&jacTed, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&jacTjacd0, M*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&Dpd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&bd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&pd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&pnewd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMemcpy(pd, p, M*sizeof(double), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - - - /* memory allocation: different solvers */ - int work_size=0; - int *devInfo; - int devInfo_h=0; - err=cudaMalloc((void**)&devInfo, sizeof(int)); - checkCudaError(err,__FILE__,__LINE__); - double *work; - double *rwork; - if (solve_axb==0) { - cusolverDnDpotrf_bufferSize(solver_handle, CUBLAS_FILL_MODE_UPPER, M, jacTjacd, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } else if (solve_axb==1) { - err=cudaMalloc((void**)&taud, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - cusolverDnDgeqrf_bufferSize(solver_handle, M, M, jacTjacd, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } else { - err=cudaMalloc((void**)&Ud, M*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&VTd, M*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&Sd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - cusolverDnDgesvd_bufferSize(solver_handle, M, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&rwork, 5*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } - - - /* ### compute e=x - f(p) and its L2 norm */ - /* ### e=x-hx, p_eL2=||e|| */ - (*func)(p, hx, M, N, adata); - /* copy to device */ - err=cudaMalloc((void**)&hxd, N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - /* hxd<=hx */ - err=cudaMemcpy(hxd, hx, N*sizeof(double), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - - err=cudaMalloc((void**)&ed, N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - /* e=x */ - cbstatus=cublasDcopy(cbhandle, N, xd, 1, ed, 1); - /* e=x-hx */ - double alpha=-1.0; - cbstatus=cublasDaxpy(cbhandle, N, &alpha, hxd, 1, ed, 1); - - /* norm ||e|| */ - cbstatus=cublasDnrm2(cbhandle, N, ed, 1, &p_eL2); - /* square */ - p_eL2=p_eL2*p_eL2; - - init_p_eL2=p_eL2; - if(!finite(p_eL2)) stop=7; - - - /**** iteration loop ***********/ - for(k=0; k A+ mu*I, increment diagonal entries */ - /* copy jacTjacd<=jacTjacd0 */ - cbstatus=cublasDcopy(cbhandle, M*M, jacTjacd0, 1, jacTjacd, 1); - cudakernel_diagmu(ThreadsPerBlock, BlocksPerGrid, M, jacTjacd, mu); - -#ifdef DEBUG - printf("mu=%lf\n",mu); -#endif -/*************************************************************************/ - /* solve augmented equations A x = b */ - /* A==jacTjacd, b==Dpd, after solving, x==Dpd */ - /* b=jacTed : intially right hand side, at exit the solution */ - if (solve_axb==0) { - /* Cholesky solver **********************/ - /* lower triangle of Ad is destroyed */ - //status=culaDeviceDpotrf('U',M,jacTjacd,M); - cusolverDnDpotrf(solver_handle, CUBLAS_FILL_MODE_UPPER, M, jacTjacd, M, work, work_size, devInfo); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (!devInfo_h) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - fprintf(stderr,"Singular matrix\n"); -#endif - } - if (issolved) { - /* copy Dpd<=jacTed */ - cbstatus=cublasDcopy(cbhandle, M, jacTed, 1, Dpd, 1); -#ifdef DEBUG - checkCublasError(cbstatus); -#endif - //status=culaDeviceDpotrs('U',M,1,jacTjacd,M,Dpd,M); - cusolverDnDpotrs(solver_handle, CUBLAS_FILL_MODE_UPPER,M,1,jacTjacd,M,Dpd,M,devInfo); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (devInfo_h) { - issolved=0; -#ifdef DEBUG - fprintf(stderr,"Singular matrix\n"); -#endif - } - } - } else if (solve_axb==1) { - /* QR solver ********************************/ - //status=culaDeviceDgeqrf(M,M,jacTjacd,M,taud); - cusolverDnDgeqrf(solver_handle, M, M, jacTjacd, M, taud, work, work_size, devInfo); - cudaDeviceSynchronize(); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (!devInfo_h) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - fprintf(stderr,"Singular matrix\n"); -#endif - } - - if (issolved) { - /* copy Dpd<=jacTed */ - cbstatus=cublasDcopy(cbhandle, M, jacTed, 1, Dpd, 1); - //status=culaDeviceDgeqrs(M,M,1,jacTjacd,M,taud,Dpd,M); - cusolverDnDormqr(solver_handle, CUBLAS_SIDE_LEFT, CUBLAS_OP_T, M, 1, M, jacTjacd, M, taud, Dpd, M, work, work_size, devInfo); - cudaDeviceSynchronize(); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (devInfo_h) { - issolved=0; -#ifdef DEBUG - fprintf(stderr,"Singular matrix\n"); -#endif - } else { - cone=1.0; - cbstatus=cublasDtrsm(cbhandle,CUBLAS_SIDE_LEFT,CUBLAS_FILL_MODE_UPPER,CUBLAS_OP_N,CUBLAS_DIAG_NON_UNIT,M,1,&cone,jacTjacd,M,Dpd,M); - } - } - } else { - /* SVD solver *********************************/ - /* U S VT = A */ - //status=culaDeviceDgesvd('A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M); - //checkStatus(status,__FILE__,__LINE__); - cusolverDnDgesvd(solver_handle,'A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M,work,work_size,rwork,devInfo); - cudaDeviceSynchronize(); - /* copy Dpd<=jacTed */ - cbstatus=cublasDcopy(cbhandle, M, jacTed, 1, Dpd, 1); - /* b<=U^T * b */ - //status=culaDeviceDgemv('T',M,M,1.0,Ud,M,Dpd,1,0.0,Dpd,1); - //checkStatus(status,__FILE__,__LINE__); - cone=1.0; czero=0.0; - cbstatus=cublasDgemv(cbhandle,CUBLAS_OP_T,M,M,&cone,Ud,M,Dpd,1,&czero,Dpd,1); - /* get back the values jTjdiag<=Sd, pnew<=Dpd*/ -// err=cudaMemcpy(pnew,Dpd,M*sizeof(double),cudaMemcpyDeviceToHost); -// checkCudaError(err); -// err=cudaMemcpy(jTjdiag,Sd,M*sizeof(double),cudaMemcpyDeviceToHost); -// checkCudaError(err); - - /* robust correction */ -// for (ci=0; ci eps1 */ - cudakernel_diagdiv(ThreadsPerBlock, BlocksPerGrid, M, eps1, Dpd, Sd); - - /* b<=VT^T * b */ - //status=culaDeviceDgemv('T',M,M,1.0,VTd,M,Dpd,1,0.0,Dpd,1); - //checkStatus(status,__FILE__,__LINE__); - cbstatus=cublasDgemv(cbhandle,CUBLAS_OP_T,M,M,&cone,VTd,M,Dpd,1,&czero,Dpd,1); - - issolved=1; - } -/*************************************************************************/ - - /* compute p's new estimate and ||Dp||^2 */ - if (issolved) { - /* compute p's new estimate and ||Dp||^2 */ - /* pnew=p+Dp */ - /* pnew=p */ - cbstatus=cublasDcopy(cbhandle, M, pd, 1, pnewd, 1); - /* pnew=pnew+Dp */ - alpha=1.0; - cbstatus=cublasDaxpy(cbhandle, M, &alpha, Dpd, 1, pnewd, 1); - - /* copy back the solution to host */ - err=cudaMemcpy(pnew,pnewd,M*sizeof(double),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - - /* norm ||Dp|| */ - cbstatus=cublasDnrm2(cbhandle, M, Dpd, 1, &Dp_L2); - Dp_L2=Dp_L2*Dp_L2; - -#ifdef DEBUG -printf("norm ||dp|| =%lf, norm ||p||=%lf\n",Dp_L2,p_L2); -#endif - if(Dp_L2<=eps2_sq*p_L2){ /* relative change in p is small, stop */ - stop=2; - break; - } - - if(Dp_L2>=(p_L2+eps2)/(CLM_EPSILON*CLM_EPSILON)){ /* almost singular */ - stop=4; - break; - } - - /* new function value */ - (*func)(pnew, hx, M, N, adata); /* evaluate function at p + Dp */ - - /* compute ||e(pDp)||_2 */ - /* ### hx=x-hx, pDp_eL2=||hx|| */ - /* copy to device */ - /* hxd<=hx */ - err=cudaMemcpy(hxd, hx, N*sizeof(double), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - - /* e=x */ - cbstatus=cublasDcopy(cbhandle, N, xd, 1, ed, 1); - /* e=x-hx */ - alpha=-1.0; - cbstatus=cublasDaxpy(cbhandle, N, &alpha, hxd, 1, ed, 1); - /* note: e is updated */ - - /* norm ||e|| */ - cbstatus=cublasDnrm2(cbhandle, N, ed, 1, &pDp_eL2); - pDp_eL2=pDp_eL2*pDp_eL2; - - - if(!finite(pDp_eL2)){ /* sum of squares is not finite, most probably due to a user error. - */ - stop=7; - break; - } - - /* dL=Dp'*(mu*Dp+jacTe) */ - /* bd=jacTe+mu*Dp */ - cbstatus=cublasDcopy(cbhandle, M, jacTed, 1, bd, 1); - cbstatus=cublasDaxpy(cbhandle, M, &mu, Dpd, 1, bd, 1); - cbstatus=cublasDdot(cbhandle, M, Dpd, 1, bd, 1, &dL); - - dF=p_eL2-pDp_eL2; - -#ifdef DEBUG - printf("dF=%lf, dL=%lf\n",dF,dL); -#endif - if(dL>0.0 && dF>0.0){ /* reduction in error, increment is accepted */ - tmp=(2.0*dF/dL-1.0); - tmp=1.0-tmp*tmp*tmp; - mu=mu*((tmp>=CLM_ONE_THIRD)? tmp : CLM_ONE_THIRD); - nu=2; - - /* update p's estimate */ - cbstatus=cublasDcopy(cbhandle, M, pnewd, 1, pd, 1); - - /* update ||e||_2 */ - p_eL2=pDp_eL2; - break; - } - - } - /* if this point is reached, either the linear system could not be solved or - * the error did not reduce; in any case, the increment must be rejected - */ - - mu*=(double)nu; - nu2=nu<<1; // 2*nu; - if(nu2<=nu){ /* nu has wrapped around (overflown). */ - stop=5; - break; - } - - nu=nu2; - - } /* inner loop */ - - /* copy back solution, need for jacobian calculation */ - err=cudaMemcpy(p,pd,M*sizeof(double),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - } - /**** end iteration loop ***********/ - - - if(k>=itmax) stop=3; - - /* copy back current solution */ - err=cudaMemcpy(p,pd,M*sizeof(double),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - - - - cudaFree(xd); - cudaFree(jacd); - cudaFree(jacTjacd); - cudaFree(jacTjacd0); - cudaFree(jacTed); - cudaFree(Dpd); - cudaFree(bd); - cudaFree(pd); - cudaFree(pnewd); - cudaFree(hxd); - cudaFree(ed); - cudaFree(devInfo); - cudaFree(work); - if (solve_axb==0) { - } else if (solve_axb==1) { - cudaFree(taud); - } else { - cudaFree(Ud); - cudaFree(VTd); - cudaFree(Sd); - cudaFree(rwork); - } - cublasDestroy(cbhandle); - cusolverDnDestroy(solver_handle); - free(hx); - free(jac); - free(pnew); - -#ifdef DEBUG - printf("stop=%d\n",stop); -#endif - if(info){ - info[0]=init_p_eL2; - info[1]=p_eL2; - info[2]=jacTe_inf; - info[3]=Dp_L2; - info[4]=mu; - info[5]=(double)k; - info[6]=(double)stop; - info[7]=(double)0; - info[8]=(double)0; - info[9]=(double)0; - } - return 0; -} - - -/* same as above, but f() and jac() calculations are done - entirely in the GPU */ -int -clevmar_der_single_cuda( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - * info[5]= # iterations, - * info[6]=reason for terminating: 1 - stopped by small gradient J^T e - * 2 - stopped by small Dp - * 3 - stopped by itmax - * 4 - singular matrix. Restart from current p with increased mu - * 5 - no further error reduction is possible. Restart with increased mu - * 6 - stopped by small ||e||_2 - * 7 - stopped by invalid (i.e. NaN or Inf) "func" values. This is a user error - * info[7]= # function evaluations - * info[8]= # Jacobian evaluations - * info[9]= # linear systems solved, i.e. # attempts for reducing error - */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - double *gWORK, /* GPU allocated memory */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - void *adata) /* pointer to possibly additional data, passed uninterpreted to func & jacf. - * Set to NULL if not needed - */ -{ - - /* general note: all device variables end with a 'd' */ - int stop=0; - cudaError_t err; - cublasStatus_t cbstatus; - - int nu=2,nu2; - double p_L2, Dp_L2=DBL_MAX, dF, dL, p_eL2, jacTe_inf=0.0, pDp_eL2, init_p_eL2; - double tmp,mu=0.0; - double tau, eps1, eps2, eps2_sq, eps3; - int k,ci,issolved; - - double *hxd; - - double *ed; - double *xd; - - double *jacd; - - double *jacTjacd,*jacTjacd0; - - double *Dpd,*bd; - double *pd,*pnewd; - double *jacTed; - - /* used in QR solver */ - double *taud; - - /* used in SVD solver */ - double *Ud; - double *VTd; - double *Sd; - - /* ME data */ - me_data_t *dp=(me_data_t*)adata; - int Nbase=(dp->Nbase)*(ntiles); /* note: we do not use the total tile size */ - /* coherency on device */ - double *cohd; - /* baseline-station map on device/host */ - short *bbd; - - int solve_axb=linsolv; - - /* setup default settings */ - if(opts){ - tau=opts[0]; - eps1=opts[1]; - eps2=opts[2]; - eps2_sq=opts[2]*opts[2]; - eps3=opts[3]; - } else { - tau=CLM_INIT_MU; - eps1=CLM_STOP_THRESH; - eps2=CLM_STOP_THRESH; - eps2_sq=CLM_STOP_THRESH*CLM_STOP_THRESH; - eps3=CLM_STOP_THRESH; - } - - /* calculate no of cuda threads and blocks */ - int ThreadsPerBlock=DEFAULT_TH_PER_BK; - int BlocksPerGrid=(M+ThreadsPerBlock-1)/ThreadsPerBlock; - - - unsigned long int moff; /* make sure offsets are multiples of 4 */ - if (!gWORK) { - err=cudaMalloc((void**)&xd, N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&jacd, M*N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&jacTjacd, M*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&jacTed, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&jacTjacd0, M*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&Dpd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&bd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&pd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&pnewd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - /* needed for calculating f() and jac() */ - err=cudaMalloc((void**) &bbd, Nbase*2*sizeof(short)); - checkCudaError(err,__FILE__,__LINE__); - /* we need coherencies for only this cluster */ - err=cudaMalloc((void**) &cohd, Nbase*8*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&hxd, N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&ed, N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - /* memory allocation: different solvers */ - if (solve_axb==1) { - err=cudaMalloc((void**)&taud, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } else if (solve_axb==2) { - err=cudaMalloc((void**)&Ud, M*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&VTd, M*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&Sd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } - } else { - moff=0; - xd=&gWORK[moff]; - moff+=N; - jacd=&gWORK[moff]; - moff+=M*N; - jacTjacd=&gWORK[moff]; - moff+=M*M; - jacTed=&gWORK[moff]; - moff+=M; - jacTjacd0=&gWORK[moff]; - moff+=M*M; - Dpd=&gWORK[moff]; - moff+=M; - bd=&gWORK[moff]; - moff+=M; - pd=&gWORK[moff]; - moff+=M; - pnewd=&gWORK[moff]; - moff+=M; - cohd=&gWORK[moff]; - moff+=Nbase*8; - hxd=&gWORK[moff]; - moff+=N; - ed=&gWORK[moff]; - moff+=N; - if (solve_axb==1) { - taud=&gWORK[moff]; - moff+=M; - } else if (solve_axb==2) { - Ud=&gWORK[moff]; - moff+=M*M; - VTd=&gWORK[moff]; - moff+=M*M; - Sd=&gWORK[moff]; - moff+=M; - } - bbd=(short*)&gWORK[moff]; - moff+=(Nbase*2*sizeof(short))/sizeof(double); - } - - /* extra storage for cusolver */ - int work_size=0; - int *devInfo; - int devInfo_h=0; - err=cudaMalloc((void**)&devInfo, sizeof(int)); - checkCudaError(err,__FILE__,__LINE__); - double *work; - double *rwork; - if (solve_axb==0) { - cusolverDnDpotrf_bufferSize(solver_handle, CUBLAS_FILL_MODE_UPPER, M, jacTjacd, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } else if (solve_axb==1) { - cusolverDnDgeqrf_bufferSize(solver_handle, M, M, jacTjacd, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } else { - cusolverDnDgesvd_bufferSize(solver_handle, M, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&rwork, 5*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } - - - err=cudaMemcpyAsync(pd, p, M*sizeof(double), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - /* need to give right offset for coherencies */ - /* offset: cluster offset+time offset */ - err=cudaMemcpyAsync(cohd, &(dp->ddcoh[(dp->Nbase)*(dp->tilesz)*(dp->clus)*8+(dp->Nbase)*tileoff*8]), Nbase*8*sizeof(double), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - /* correct offset for baselines */ - err=cudaMemcpyAsync(bbd, &(dp->ddbase[2*(dp->Nbase)*(tileoff)]), Nbase*2*sizeof(short), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - cudaDeviceSynchronize(); - /* xd <=x */ - err=cudaMemcpyAsync(xd, x, N*sizeof(double), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - - /* ### compute e=x - f(p) and its L2 norm */ - /* ### e=x-hx, p_eL2=||e|| */ - /* p: params (Mx1), x: data (Nx1), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations*/ - cudakernel_func(ThreadsPerBlock, (Nbase+ThreadsPerBlock-1)/ThreadsPerBlock, pd,hxd,M,N, cohd, bbd, Nbase, dp->M, dp->N); - - /* e=x */ - cbstatus=cublasDcopy(cbhandle, N, xd, 1, ed, 1); - /* e=x-hx */ - double alpha=-1.0; - cbstatus=cublasDaxpy(cbhandle, N, &alpha, hxd, 1, ed, 1); - - /* norm ||e|| */ - cbstatus=cublasDnrm2(cbhandle, N, ed, 1, &p_eL2); - /* square */ - p_eL2=p_eL2*p_eL2; - - init_p_eL2=p_eL2; - if(!finite(p_eL2)) stop=7; - - - /**** iteration loop ***********/ - for(k=0; kstat mapping, Nbase, Mclusters, Nstations*/ - /* FIXME thread/block sizes 16x16=256, so 16 is chosen */ - cudakernel_jacf(ThreadsPerBlock, ThreadsPerBlock/4, pd, jacd, M, N, cohd, bbd, Nbase, dp->M, dp->N); - - /* Compute J^T J and J^T e */ - /* Cache efficient computation of J^T J based on blocking - */ - /* since J is in ROW major order, assume it is transposed, - so actually calculate A=J*J^T, where J is size MxN */ - //status=culaDeviceDgemm('N','T',M,M,N,1.0,jacd,M,jacd,M,0.0,jacTjacd,M); - //checkStatus(status,__FILE__,__LINE__); - double cone=1.0; double czero=0.0; - cbstatus=cublasDgemm(cbhandle,CUBLAS_OP_N,CUBLAS_OP_T,M,M,N,&cone,jacd,M,jacd,M,&czero,jacTjacd,M); - /* create backup */ - /* copy jacTjacd0<=jacTjacd */ - cbstatus=cublasDcopy(cbhandle, M*M, jacTjacd, 1, jacTjacd0, 1); - /* J^T e */ - /* calculate b=J^T*e (actually compute b=J*e, where J in row major (size MxN) */ - //status=culaDeviceDgemv('N',M,N,1.0,jacd,M,ed,1,0.0,jacTed,1); - //checkStatus(status,__FILE__,__LINE__); - cbstatus=cublasDgemv(cbhandle,CUBLAS_OP_N,M,N,&cone,jacd,M,ed,1,&czero,jacTed,1); - - - /* Compute ||J^T e||_inf and ||p||^2 */ - /* find infinity norm of J^T e, 1 based indexing*/ - cbstatus=cublasIdamax(cbhandle, M, jacTed, 1, &ci); - err=cudaMemcpy(&jacTe_inf,&(jacTed[ci-1]),sizeof(double),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - /* L2 norm of current parameter values */ - /* norm ||Dp|| */ - cbstatus=cublasDnrm2(cbhandle, M, pd, 1, &p_L2); - p_L2=p_L2*p_L2; - if(jacTe_inf<0.0) {jacTe_inf=-jacTe_inf;} -#ifdef DEBUG - printf("Inf norm=%lf\n",jacTe_inf); -#endif - - /* check for convergence */ - if((jacTe_inf <= eps1)){ - Dp_L2=0.0; /* no increment for p in this case */ - stop=1; - break; - } - - /* compute initial (k=0) damping factor */ - if (k==0) { - /* find max diagonal element (stride is M+1) */ - /* should be MAX not MAX(ABS) */ - cbstatus=cublasIdamax(cbhandle, M, jacTjacd, M+1, &ci); /* 1 based index */ - ci=(ci-1)*(M+1); /* right value of the diagonal */ - - err=cudaMemcpy(&tmp,&(jacTjacd[ci]),sizeof(double),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - mu=tau*tmp; - } - - - /* determine increment using adaptive damping */ - while(1){ - /* augment normal equations */ - /* increment A => A+ mu*I, increment diagonal entries */ - /* copy jacTjacd<=jacTjacd0 */ - cbstatus=cublasDcopy(cbhandle, M*M, jacTjacd0, 1, jacTjacd, 1); - cudakernel_diagmu(ThreadsPerBlock, BlocksPerGrid, M, jacTjacd, mu); - -#ifdef DEBUG - printf("mu=%lf\n",mu); -#endif -/*************************************************************************/ - issolved=0; - /* solve augmented equations A x = b */ - /* A==jacTjacd, b==Dpd, after solving, x==Dpd */ - /* b=jacTed : intially right hand side, at exit the solution */ - if (solve_axb==0) { - /* Cholesky solver **********************/ - /* lower triangle of Ad is destroyed */ - //status=culaDeviceDpotrf('U',M,jacTjacd,M); - cusolverDnDpotrf(solver_handle, CUBLAS_FILL_MODE_UPPER, M, jacTjacd, M, work, work_size, devInfo); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (!devInfo_h) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - fprintf(stderr,"Singular matrix\n"); -#endif - } - if (issolved) { - /* copy Dpd<=jacTed */ - cbstatus=cublasDcopy(cbhandle, M, jacTed, 1, Dpd, 1); -#ifdef DEBUG - checkCublasError(cbstatus); -#endif - //status=culaDeviceDpotrs('U',M,1,jacTjacd,M,Dpd,M); - cusolverDnDpotrs(solver_handle, CUBLAS_FILL_MODE_UPPER,M,1,jacTjacd,M,Dpd,M,devInfo); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (devInfo_h) { - issolved=0; -#ifdef DEBUG - fprintf(stderr,"Singular matrix\n"); -#endif - } - } - } else if (solve_axb==1) { - /* QR solver ********************************/ - //status=culaDeviceDgeqrf(M,M,jacTjacd,M,taud); - cusolverDnDgeqrf(solver_handle, M, M, jacTjacd, M, taud, work, work_size, devInfo); - cudaDeviceSynchronize(); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (!devInfo_h) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - fprintf(stderr,"Singular matrix\n"); -#endif - } - - if (issolved) { - /* copy Dpd<=jacTed */ - cbstatus=cublasDcopy(cbhandle, M, jacTed, 1, Dpd, 1); - //status=culaDeviceDgeqrs(M,M,1,jacTjacd,M,taud,Dpd,M); - cusolverDnDormqr(solver_handle, CUBLAS_SIDE_LEFT, CUBLAS_OP_T, M, 1, M, jacTjacd, M, taud, Dpd, M, work, work_size, devInfo); - cudaDeviceSynchronize(); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (devInfo_h) { - issolved=0; -#ifdef DEBUG - fprintf(stderr,"Singular matrix\n"); -#endif - } else { - cone=1.0; - cbstatus=cublasDtrsm(cbhandle,CUBLAS_SIDE_LEFT,CUBLAS_FILL_MODE_UPPER,CUBLAS_OP_N,CUBLAS_DIAG_NON_UNIT,M,1,&cone,jacTjacd,M,Dpd,M); - } - } - } else { - /* SVD solver *********************************/ - /* U S VT = A */ - //status=culaDeviceDgesvd('A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M); - //checkStatus(status,__FILE__,__LINE__); - cusolverDnDgesvd(solver_handle,'A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M,work,work_size,rwork,devInfo); - cudaDeviceSynchronize(); - /* copy Dpd<=jacTed */ - cbstatus=cublasDcopy(cbhandle, M, jacTed, 1, Dpd, 1); - /* b<=U^T * b */ - //status=culaDeviceDgemv('T',M,M,1.0,Ud,M,Dpd,1,0.0,Dpd,1); - //checkStatus(status,__FILE__,__LINE__); - cone=1.0; czero=0.0; - cbstatus=cublasDgemv(cbhandle,CUBLAS_OP_T,M,M,&cone,Ud,M,Dpd,1,&czero,Dpd,1); - - /* divide by singular values Dpd[]/Sd[] for Sd[]> eps1 */ - cudakernel_diagdiv(ThreadsPerBlock, BlocksPerGrid, M, eps1, Dpd, Sd); - - /* b<=VT^T * b */ - //status=culaDeviceDgemv('T',M,M,1.0,VTd,M,Dpd,1,0.0,Dpd,1); - //checkStatus(status,__FILE__,__LINE__); - cbstatus=cublasDgemv(cbhandle,CUBLAS_OP_T,M,M,&cone,VTd,M,Dpd,1,&czero,Dpd,1); - - issolved=1; - } -/*************************************************************************/ - - /* compute p's new estimate and ||Dp||^2 */ - if (issolved) { - /* compute p's new estimate and ||Dp||^2 */ - /* pnew=p+Dp */ - /* pnew=p */ - cbstatus=cublasDcopy(cbhandle, M, pd, 1, pnewd, 1); - /* pnew=pnew+Dp */ - alpha=1.0; - cbstatus=cublasDaxpy(cbhandle, M, &alpha, Dpd, 1, pnewd, 1); - - /* norm ||Dp|| */ - cbstatus=cublasDnrm2(cbhandle, M, Dpd, 1, &Dp_L2); - Dp_L2=Dp_L2*Dp_L2; - -#ifdef DEBUG -printf("norm ||dp|| =%lf, norm ||p||=%lf\n",Dp_L2,p_L2); -#endif - if(Dp_L2<=eps2_sq*p_L2){ /* relative change in p is small, stop */ - stop=2; - break; - } - - if(Dp_L2>=(p_L2+eps2)/(CLM_EPSILON*CLM_EPSILON)){ /* almost singular */ - stop=4; - break; - } - - /* new function value */ - /* compute ||e(pDp)||_2 */ - /* ### hx=x-hx, pDp_eL2=||hx|| */ - /* copy to device */ - /* hxd<=hx */ - cudakernel_func(ThreadsPerBlock, (Nbase+ThreadsPerBlock-1)/ThreadsPerBlock, pnewd, hxd, M, N, cohd, bbd, Nbase, dp->M, dp->N); - - /* e=x */ - cbstatus=cublasDcopy(cbhandle, N, xd, 1, ed, 1); - /* e=x-hx */ - alpha=-1.0; - cbstatus=cublasDaxpy(cbhandle, N, &alpha, hxd, 1, ed, 1); - /* note: e is updated */ - - /* norm ||e|| */ - cbstatus=cublasDnrm2(cbhandle, N, ed, 1, &pDp_eL2); - pDp_eL2=pDp_eL2*pDp_eL2; - - - if(!finite(pDp_eL2)){ /* sum of squares is not finite, most probably due to a user error. - */ - stop=7; - break; - } - - /* dL=Dp'*(mu*Dp+jacTe) */ - /* bd=jacTe+mu*Dp */ - cbstatus=cublasDcopy(cbhandle, M, jacTed, 1, bd, 1); - cbstatus=cublasDaxpy(cbhandle, M, &mu, Dpd, 1, bd, 1); - cbstatus=cublasDdot(cbhandle, M, Dpd, 1, bd, 1, &dL); - - dF=p_eL2-pDp_eL2; - -#ifdef DEBUG - printf("dF=%lf, dL=%lf\n",dF,dL); -#endif - if(dL>0.0 && dF>0.0){ /* reduction in error, increment is accepted */ - tmp=(2.0*dF/dL-1.0); - tmp=1.0-tmp*tmp*tmp; - mu=mu*((tmp>=CLM_ONE_THIRD)? tmp : CLM_ONE_THIRD); - nu=2; - - /* update p's estimate */ - cbstatus=cublasDcopy(cbhandle, M, pnewd, 1, pd, 1); - - /* update ||e||_2 */ - p_eL2=pDp_eL2; - break; - } - - } - /* if this point is reached, either the linear system could not be solved or - * the error did not reduce; in any case, the increment must be rejected - */ - - mu*=(double)nu; - nu2=nu<<1; // 2*nu; - if(nu2<=nu){ /* nu has wrapped around (overflown). */ - stop=5; - break; - } - - nu=nu2; - - } /* inner loop */ - - } - /**** end iteration loop ***********/ - - - if(k>=itmax) stop=3; - - /* copy back current solution */ - err=cudaMemcpyAsync(p,pd,M*sizeof(double),cudaMemcpyDeviceToHost,0); - checkCudaError(err,__FILE__,__LINE__); - - /* check once CUBLAS error */ - checkCublasError(cbstatus,__FILE__,__LINE__); - - /* synchronize async operations */ - cudaDeviceSynchronize(); - - if (!gWORK) { - cudaFree(xd); - cudaFree(jacd); - cudaFree(jacTjacd); - cudaFree(jacTjacd0); - cudaFree(jacTed); - cudaFree(Dpd); - cudaFree(bd); - cudaFree(pd); - cudaFree(pnewd); - cudaFree(hxd); - cudaFree(ed); - if (solve_axb==1) { - cudaFree(taud); - } else if (solve_axb==2) { - cudaFree(Ud); - cudaFree(VTd); - cudaFree(Sd); - } - cudaFree(cohd); - cudaFree(bbd); - } - - cudaFree(devInfo); - cudaFree(work); - if (solve_axb==2) { - cudaFree(rwork); - } - -#ifdef DEBUG - printf("stop=%d\n",stop); -#endif - if(info){ - info[0]=init_p_eL2; - info[1]=p_eL2; - info[2]=jacTe_inf; - info[3]=Dp_L2; - info[4]=mu; - info[5]=(double)k; - info[6]=(double)stop; - info[7]=(double)0; - info[8]=(double)0; - info[9]=(double)0; - } - return 0; -} - - -/* function to set up a GPU, should be called only once */ -void -attach_gpu_to_thread(int card, cublasHandle_t *cbhandle, cusolverDnHandle_t *solver_handle) { - - cudaError_t err; - cublasStatus_t cbstatus; - cusolverStatus_t status; - err=cudaSetDevice(card); - checkCudaError(err,__FILE__,__LINE__); - status=cusolverDnCreate(solver_handle); - if (status != CUSOLVER_STATUS_SUCCESS) { - fprintf(stderr,"%s: %d: CUSOLV create fail %d\n",__FILE__,__LINE__,status); - exit(1); - } - - cbstatus=cublasCreate(cbhandle); - if (cbstatus!=CUBLAS_STATUS_SUCCESS) { - fprintf(stderr,"%s: %d: CUBLAS create fail\n",__FILE__,__LINE__); - exit(1); - } - -} -void -attach_gpu_to_thread1(int card, cublasHandle_t *cbhandle, cusolverDnHandle_t *solver_handle, double **WORK, int64_t work_size) { - - cudaError_t err; - cublasStatus_t cbstatus; - cusolverStatus_t status; - err=cudaSetDevice(card); - checkCudaError(err,__FILE__,__LINE__); - status=cusolverDnCreate(solver_handle); - if (status != CUSOLVER_STATUS_SUCCESS) { - fprintf(stderr,"%s: %d: CUSOLV create fail %d\n",__FILE__,__LINE__,status); - sleep(10); - status=cusolverDnCreate(solver_handle); - if (status != CUSOLVER_STATUS_SUCCESS) { - fprintf(stderr,"%s: %d: CUSOLV create fail %d\n",__FILE__,__LINE__,status); - exit(1); - } - } - - cbstatus=cublasCreate(cbhandle); - if (cbstatus!=CUBLAS_STATUS_SUCCESS) { - /* retry once more before exiting */ - fprintf(stderr,"%s: %d: CUBLAS create failure, retrying\n",__FILE__,__LINE__); - sleep(10); - cbstatus=cublasCreate(cbhandle); - if (cbstatus!=CUBLAS_STATUS_SUCCESS) { - fprintf(stderr,"%s: %d: CUBLAS create fail\n",__FILE__,__LINE__); - exit(1); - } - } - - err=cudaMalloc((void**)WORK, (size_t)work_size); - checkCudaError(err,__FILE__,__LINE__); - -} -void -attach_gpu_to_thread2(int card, cublasHandle_t *cbhandle, cusolverDnHandle_t *solver_handle, float **WORK, int64_t work_size, int usecula) { - - cudaError_t err; - cublasStatus_t cbstatus; - cusolverStatus_t status; - err=cudaSetDevice(card); /* we need this */ - checkCudaError(err,__FILE__,__LINE__); - if (usecula) { - status=cusolverDnCreate(solver_handle); - if (status != CUSOLVER_STATUS_SUCCESS) { - fprintf(stderr,"%s: %d: CUSOLV create fail %d\n",__FILE__,__LINE__,status); - sleep(10); - status=cusolverDnCreate(solver_handle); - if (status != CUSOLVER_STATUS_SUCCESS) { - fprintf(stderr,"%s: %d: CUSOLV create fail %d\n",__FILE__,__LINE__,status); - exit(1); - } - } - } - - cbstatus=cublasCreate(cbhandle); - if (cbstatus!=CUBLAS_STATUS_SUCCESS) { - /* retry once more before exiting */ - fprintf(stderr,"%s: %d: CUBLAS create failure, retrying\n",__FILE__,__LINE__); - sleep(10); - cbstatus=cublasCreate(cbhandle); - if (cbstatus!=CUBLAS_STATUS_SUCCESS) { - fprintf(stderr,"%s: %d: CUBLAS create fail\n",__FILE__,__LINE__); - exit(1); - } - } - - err=cudaMalloc((void**)WORK, (size_t)work_size); - checkCudaError(err,__FILE__,__LINE__); - -} -void -detach_gpu_from_thread(cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle) { - cublasDestroy(cbhandle); - cusolverDnDestroy(solver_handle); -} -void -detach_gpu_from_thread1(cublasHandle_t cbhandle,cusolverDnHandle_t solver_handle,double *WORK) { - - cublasDestroy(cbhandle); - cusolverDnDestroy(solver_handle); - cudaFree(WORK); -} -void -detach_gpu_from_thread2(cublasHandle_t cbhandle,cusolverDnHandle_t solver_handle,float *WORK, int usecula) { - - cublasDestroy(cbhandle); - if (usecula) { - cusolverDnDestroy(solver_handle); - } - cudaFree(WORK); -} -void -reset_gpu_memory(double *WORK, int64_t work_size) { - - cudaError_t err; - - err=cudaMemset((void*)WORK, 0, (size_t)work_size); - checkCudaError(err,__FILE__,__LINE__); -} - - -/** keep interface almost the same as in levmar **/ -int -mlm_der_single_cuda( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[6], /* I: minim. options [\mu, \m, \p0, \p1, \p2, \delta]. - delta: 1 or 2 - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - double *gWORK, /* GPU allocated memory */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - void *adata) /* pointer to possibly additional data, passed uninterpreted to func & jacf. - * Set to NULL if not needed - */ -{ - cudaError_t err; - cublasStatus_t cbstatus; - - /* NOTE F()=func()-data */ - double *xd,*Jkd,*Fxkd,*Fykd,*Jkdkd,*JkTed,*JkTed0,*JkTJkd,*JkTJkd0,*dkd,*dhatkd, *ykd, *skd, *pd; - - double lambda; - double mu,m,p0,p1,p2; - int delta; - double Fxknrm,Fyknrm,Fykdhatknrm,Fxksknrm,FJkdknrm; - int niter=0; - int p_update=1; - double Fxknrm2,Fxksknrm2; - - double Ak,Pk,rk; - - /* use cudaHostAlloc and cudaFreeHost */ - /* used in QR solver */ - double *taud=0; - /* used in SVD solver */ - double *Ud=0; - double *VTd=0; - double *Sd=0; - - int issolved; - int solve_axb=linsolv; - - /* ME data */ - me_data_t *dp=(me_data_t*)adata; - int Nbase=(dp->Nbase)*(ntiles); /* note: we do not use the total tile size */ - /* coherency on device */ - double *cohd; - /* baseline-station map on device/host */ - short *bbd; - - - /* calculate no of cuda threads and blocks */ - int ThreadsPerBlock=DEFAULT_TH_PER_BK; - int BlocksPerGrid=(M+ThreadsPerBlock-1)/ThreadsPerBlock; - - - if (opts) { - mu=opts[0]; - m=opts[1]; - p0=opts[2]; - p1=opts[3]; - p2=opts[4]; - delta=(int)opts[5]; - } else { - mu=1e-3;//1e-5; - m=1e-2;//1e-3; - p0=0.0001; - p1=0.25; - p2=0.75; - delta=1; /* 1 or 2 */ - } - - double epsilon=CLM_EPSILON; - - unsigned long int moff; - if (!gWORK) { - err=cudaMalloc((void**)&xd, N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&Jkd, M*N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&Fxkd, N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&Fykd, N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&Jkdkd, N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&JkTed, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&JkTed0, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&JkTJkd, M*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&JkTJkd0, M*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&dkd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&dhatkd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&ykd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&skd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&pd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - /* needed for calculating f() and jac() */ - err=cudaMalloc((void**) &bbd, Nbase*2*sizeof(short)); - checkCudaError(err,__FILE__,__LINE__); - /* we need coherencies for only this cluster */ - err=cudaMalloc((void**) &cohd, Nbase*8*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - /* memory allocation: different solvers */ - if (solve_axb==1) { - /* QR solver ********************************/ - err=cudaMalloc((void**)&taud, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } else if (solve_axb==2) { - err=cudaMalloc((void**)&Ud, M*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&VTd, M*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&Sd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } - } else { /* use pre allocated memory */ - moff=0; - xd=&gWORK[moff]; - moff+=N; - Jkd=&gWORK[moff]; - moff+=M*N; - Fxkd=&gWORK[moff]; - moff+=N; - Fykd=&gWORK[moff]; - moff+=N; - Jkdkd=&gWORK[moff]; - moff+=N; - JkTed=&gWORK[moff]; - moff+=M; - JkTed0=&gWORK[moff]; - moff+=M; - JkTJkd=&gWORK[moff]; - moff+=M*M; - JkTJkd0=&gWORK[moff]; - moff+=M*M; - dkd=&gWORK[moff]; - moff+=M; - dhatkd=&gWORK[moff]; - moff+=M; - ykd=&gWORK[moff]; - moff+=M; - skd=&gWORK[moff]; - moff+=M; - pd=&gWORK[moff]; - moff+=M; - cohd=&gWORK[moff]; - moff+=Nbase*8; - if (solve_axb==1) { - taud=&gWORK[moff]; - moff+=M; - } else if (solve_axb==2) { - Ud=&gWORK[moff]; - moff+=M*M; - VTd=&gWORK[moff]; - moff+=M*M; - Sd=&gWORK[moff]; - moff+=M; - } - bbd=(short*)&gWORK[moff]; - moff+=(Nbase*2*sizeof(short))/sizeof(double); - } - - /* extra storage for cusolver */ - int work_size=0; - int *devInfo; - int devInfo_h=0; - err=cudaMalloc((void**)&devInfo, sizeof(int)); - checkCudaError(err,__FILE__,__LINE__); - double *work; - double *rwork; - if (solve_axb==0) { - cusolverDnDpotrf_bufferSize(solver_handle, CUBLAS_FILL_MODE_UPPER, M, JkTJkd, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } else if (solve_axb==1) { - cusolverDnDgeqrf_bufferSize(solver_handle, M, M, JkTJkd, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } else { - cusolverDnDgesvd_bufferSize(solver_handle, M, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&rwork, 5*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } - - - err=cudaMemcpyAsync(pd, p, M*sizeof(double), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - /* need to give right offset for coherencies */ - /* offset: cluster offset+time offset */ - err=cudaMemcpyAsync(cohd, &(dp->ddcoh[(dp->Nbase)*(dp->tilesz)*(dp->clus)*8+(dp->Nbase)*tileoff*8]), Nbase*8*sizeof(double), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - /* correct offset for baselines */ - err=cudaMemcpyAsync(bbd, &(dp->ddbase[2*(dp->Nbase)*(tileoff)]), Nbase*2*sizeof(short), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - cudaDeviceSynchronize(); - /* xd <=x */ - err=cudaMemcpyAsync(xd, x, N*sizeof(double), cudaMemcpyHostToDevice, 0); - checkCudaError(err,__FILE__,__LINE__); - - /* F(x_k) = func()-data */ - /* func() */ - //(*func)(p, Fxk, M, N, adata); - /* p: params (Mx1), x: data (Nx1), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations*/ - cudakernel_func(ThreadsPerBlock, (Nbase+ThreadsPerBlock-1)/ThreadsPerBlock, pd,Fxkd,M,N, cohd, bbd, Nbase, dp->M, dp->N); - - /* func() - data */ - double alpha=-1.0; - cbstatus=cublasDaxpy(cbhandle, N, &alpha, xd, 1, Fxkd, 1); - //my_daxpy(N, x, -1.0, Fxk); - - /* find ||Fxk|| */ - //Fxknrm=my_dnrm2(N,Fxk); - cbstatus=cublasDnrm2(cbhandle, N, Fxkd, 1, &Fxknrm); - - double init_Fxknrm=Fxknrm; -#ifdef DEBUG - printf("init norm=%lf\n",Fxknrm); -#endif - - - double cone=1.0; double czero=0.0; - while (niter1) { - lambda=mu*Fxknrm*Fxknrm; - } else { - lambda=mu*Fxknrm; - } - Fxknrm2=Fxknrm*Fxknrm; - - if ( p_update==1 ) { - /* J_k */ - /* p: params (Mx1), jacd: jacobian (NxM), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations*/ - /* FIXME thread/block sizes 16x16=256, so 16 is chosen */ - cudakernel_jacf(ThreadsPerBlock, ThreadsPerBlock/4, pd, Jkd, M, N, cohd, bbd, Nbase, dp->M, dp->N); - - /* Compute J_k^T J_k and -J_k^T F(x_k) */ - //my_dgemm('N','T',M,M,N,1.0,Jk,M,Jk,M,0.0,JkTJk0,M); - //my_dgemv('N',M,N,-1.0,Jk,M,Fxk,1,0.0,JkTe0,1); - - //status=culaDeviceDgemm('N','T',M,M,N,1.0,Jkd,M,Jkd,M,0.0,JkTJkd0,M); - //checkStatus(status,__FILE__,__LINE__); - double cone=1.0; double czero=0.0; - cbstatus=cublasDgemm(cbhandle,CUBLAS_OP_N,CUBLAS_OP_T,M,M,N,&cone,Jkd,M,Jkd,M,&czero,JkTJkd0,M); - //status=culaDeviceDgemv('N',M,N,-1.0,Jkd,M,Fxkd,1,0.0,JkTed0,1); - //checkStatus(status,__FILE__,__LINE__); - cone=-1.0; - cbstatus=cublasDgemv(cbhandle,CUBLAS_OP_N,M,N,&cone,Jkd,M,Fxkd,1,&czero,JkTed0,1); - } - /* if || J_k^T F(x_k) || < epsilon, stop */ - //Fyknrm=my_dnrm2(M,JkTe0); - cbstatus=cublasDnrm2(cbhandle, M, JkTed0, 1, &Fyknrm); - - if (Fyknrm epsilon */ - /*for (ci=0; ciepsilon) { - dk[ci]=dk[ci]/Sd[ci]; - } else { - dk[ci]=0.0; - } - } */ - - /* dk <= VT^T dk */ - //memcpy(yk,dk,M*sizeof(double)); - //my_dgemv('T',M,M,1.0,VTd,M,yk,1,0.0,dk,1); - /* U S VT = A */ - //status=culaDeviceDgesvd('A','A',M,M,JkTJkd,M,Sd,Ud,M,VTd,M); - //checkStatus(status,__FILE__,__LINE__); - cusolverDnDgesvd(solver_handle,'A','A',M,M,JkTJkd,M,Sd,Ud,M,VTd,M,work,work_size,rwork,devInfo); - cudaDeviceSynchronize(); - /* b<=U^T * b */ - //status=culaDeviceDgemv('T',M,M,1.0,Ud,M,JkTed,1,0.0,dkd,1); - //checkStatus(status,__FILE__,__LINE__); - cone=1.0; czero=0.0; - cbstatus=cublasDgemv(cbhandle,CUBLAS_OP_T,M,M,&cone,Ud,M,JkTed,1,&czero,dkd,1); - - /* divide by singular values Dpd[]/Sd[] for Sd[]> eps1 */ - cudakernel_diagdiv(ThreadsPerBlock, BlocksPerGrid, M, epsilon, dkd, Sd); - - /* b<=VT^T * b */ - cbstatus=cublasDcopy(cbhandle, M, dkd, 1, ykd, 1); - //status=culaDeviceDgemv('T',M,M,1.0,VTd,M,ykd,1,0.0,dkd,1); - //checkStatus(status,__FILE__,__LINE__); - cbstatus=cublasDgemv(cbhandle,CUBLAS_OP_T,M,M,&cone,VTd,M,ykd,1,&czero,dkd,1); - - issolved=1; - } -/********************************************************************/ - - /* y_k<= x_k+ d_k */ - //my_dcopy(M,p,1,yk,1); - //my_daxpy(M,dk,1.0,yk); - - cbstatus=cublasDcopy(cbhandle, M, pd, 1, ykd, 1); - alpha=1.0; - cbstatus=cublasDaxpy(cbhandle, M, &alpha, dkd, 1, ykd, 1); - - /* compute F(y_k) */ - /* func() */ - //(*func)(yk, Fyk, M, N, adata); - cudakernel_func(ThreadsPerBlock, (Nbase+ThreadsPerBlock-1)/ThreadsPerBlock, ykd,Fykd,M,N, cohd, bbd, Nbase, dp->M, dp->N); - - /* func() - data */ - //my_daxpy(N, x, -1.0, Fyk); - /* copy to device */ - - /* func() - data */ - alpha=-1.0; - cbstatus=cublasDaxpy(cbhandle, N, &alpha, xd, 1, Fykd, 1); - - - /* Compute -J_k^T F(y_k) */ - //my_dgemv('N',M,N,-1.0,Jk,M,Fyk,1,0.0,JkTe,1); - //status=culaDeviceDgemv('N',M,N,1.0,Jkd,M,Fykd,1,0.0,JkTed,1); - //checkStatus(status,__FILE__,__LINE__); - cone=1.0; czero=0.0; - cbstatus=cublasDgemv(cbhandle,CUBLAS_OP_N,M,N,&cone,Jkd,M,Fykd,1,&czero,JkTed,1); - - -/********************************************************************/ - if (solve_axb==0) { - /* Cholesky solver **********************/ - /* copy dk<=JkTe */ - // memcpy(dhatk,JkTe,M*sizeof(double)); - // status=my_dpotrs('U',M,1,JkTJk,M,dhatk,M); - cbstatus=cublasDcopy(cbhandle, M, JkTed, 1, dhatkd, 1); - //status=culaDeviceDpotrs('U',M,1,JkTJkd,M,dhatkd,M); - cusolverDnDpotrs(solver_handle, CUBLAS_FILL_MODE_UPPER,M,1,JkTJkd,M,dhatkd,M,devInfo); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (devInfo_h) { - issolved=0; -#ifdef DEBUG - fprintf(stderr,"Singular matrix info=%d\n",status); -#endif - } - } else if (solve_axb==1) { - /* QR solver ********************************/ - /* dhatk <= Q^T jacTed */ - //my_dgemv('T',M,M,1.0,JkTJk,M,JkTe,1,0.0,dhatk,1); - /* solve R x = b */ - //status=my_dtrtrs('U','N','N',M,1,R,M,dhatk,M); - cbstatus=cublasDcopy(cbhandle, M, JkTed, 1, dhatkd, 1); - //status=culaDeviceDgeqrs(M,M,1,JkTJkd,M,taud,dhatkd,M); - cusolverDnDormqr(solver_handle, CUBLAS_SIDE_LEFT, CUBLAS_OP_T, M, 1, M, JkTJkd, M, taud, dhatkd, M, work, work_size, devInfo); - cudaDeviceSynchronize(); - cbstatus=cublasDtrsm(cbhandle,CUBLAS_SIDE_LEFT,CUBLAS_FILL_MODE_UPPER,CUBLAS_OP_N,CUBLAS_DIAG_NON_UNIT,M,1,&cone,JkTJkd,M,dhatkd,M); - - issolved=1; - } else { - /* SVD solver *********************************/ - /* dhatk <= U^T jacTed */ - //my_dgemv('T',M,M,1.0,Ud,M,JkTe,1,0.0,dhatk,1); - /* robust correction */ - /* divide by singular values dk[]/Sd[] for Sd[]> epsilon */ - /*for (ci=0; ciepsilon) { - dhatk[ci]=dhatk[ci]/Sd[ci]; - } else { - dhatk[ci]=0.0; - } - }*/ - /* dk <= VT^T dk */ - //memcpy(yk,dhatk,M*sizeof(double)); - //my_dgemv('T',M,M,1.0,VTd,M,yk,1,0.0,dhatk,1); - //status=culaDeviceDgemv('T',M,M,1.0,Ud,M,JkTed,1,0.0,dhatkd,1); - //checkStatus(status,__FILE__,__LINE__); - cone=1.0; czero=0.0; - cbstatus=cublasDgemv(cbhandle,CUBLAS_OP_T,M,M,&cone,Ud,M,JkTed,1,&czero,dhatkd,1); - - /* divide by singular values Dpd[]/Sd[] for Sd[]> eps1 */ - cudakernel_diagdiv(ThreadsPerBlock, BlocksPerGrid, M, epsilon, dhatkd, Sd); - - /* b<=VT^T * b */ - cbstatus=cublasDcopy(cbhandle, M, dhatkd, 1, ykd, 1); - //status=culaDeviceDgemv('T',M,M,1.0,VTd,M,ykd,1,0.0,dhatkd,1); - //checkStatus(status,__FILE__,__LINE__); - cbstatus=cublasDgemv(cbhandle,CUBLAS_OP_T,M,M,&cone,VTd,M,ykd,1,&czero,dhatkd,1); - - issolved=1; - - } -/********************************************************************/ - - - - /* s_k<= d_k+ dhat_k */ - //my_dcopy(M,dk,1,sk,1); - //my_daxpy(M,dhatk,1.0,sk); - cbstatus=cublasDcopy(cbhandle, M, dkd, 1, skd, 1); - alpha=1.0; - cbstatus=cublasDaxpy(cbhandle, M, &alpha, dhatkd, 1, skd, 1); - - - /* find norms */ - /* || F(y_k) || */ -// Fyknrm=my_dnrm2(N,Fyk); - cbstatus=cublasDnrm2(cbhandle, N, Fykd, 1, &Fyknrm); - Fyknrm=Fyknrm*Fyknrm; - - /* || F(y_k) + J_k dhat_k || */ - //my_dgemv('T',M,N,1.0,Jk,M,dhatk,1,0.0,Jkdk,1); - //status=culaDeviceDgemv('T',M,N,1.0,Jkd,M,dhatkd,1,0.0,Jkdkd,1); - cone=1.0; czero=0.0; - cbstatus=cublasDgemv(cbhandle,CUBLAS_OP_T,M,N,&cone,Jkd,M,dhatkd,1,&czero,Jkdkd,1); - - - /* Fyk <= Fyk+ J_k dhat_k */ -// my_daxpy(N,Jkdk,1.0,Fyk); -// Fykdhatknrm=my_dnrm2(N,Fyk); - cbstatus=cublasDaxpy(cbhandle, N, &alpha, Jkdkd, 1, Fykd, 1); - cbstatus=cublasDnrm2(cbhandle, N, Fykd, 1, &Fykdhatknrm); - Fykdhatknrm=Fykdhatknrm*Fykdhatknrm; - - /* ||F(x_k+d_k+dhat_k)|| == ||F(x_k+s_k)|| */ - /* y_k<= x_k+ s_k */ - //my_dcopy(M,p,1,yk,1); - //my_daxpy(M,sk,1.0,yk); - cbstatus=cublasDcopy(cbhandle, M, pd, 1, ykd, 1); - cbstatus=cublasDaxpy(cbhandle, M, &alpha, skd, 1, ykd, 1); - - //(*func)(yk, Fyk, M, N, adata); - cudakernel_func(ThreadsPerBlock, (Nbase+ThreadsPerBlock-1)/ThreadsPerBlock, ykd,Fykd,M,N, cohd, bbd, Nbase, dp->M, dp->N); - - /* func() - data */ - //my_daxpy(N, x, -1.0, Fyk); - alpha=-1.0; - cbstatus=cublasDaxpy(cbhandle, N, &alpha, xd, 1, Fykd, 1); - - //Fxksknrm=my_dnrm2(N,Fyk); - cbstatus=cublasDnrm2(cbhandle, N, Fykd, 1, &Fxksknrm); - - Fxksknrm2=Fxksknrm*Fxksknrm; - - /* || Fxk + J_k d_k || */ - /* J d_k : since J is row major, transpose */ -// my_dgemv('T',M,N,1.0,Jk,M,dk,1,0.0,Jkdk,1); - //status=culaDeviceDgemv('T',M,N,1.0,Jkd,M,dkd,1,0.0,Jkdkd,1); - cone=1.0; czero=0.0; - cbstatus=cublasDgemv(cbhandle,CUBLAS_OP_T,M,N,&cone,Jkd,M,dkd,1,&czero,Jkdkd,1); - - - /* Fxk <= Fxk+ J_k d_k or, J_k d_k <= Fxk+ J_k d_k */ - //my_daxpy(N,Fxk,1.0,Jkdk); - //FJkdknrm=my_dnrm2(N,Jkdk); - alpha=1.0; - cbstatus=cublasDaxpy(cbhandle, N, &alpha, Fxkd, 1, Jkdkd, 1); - cbstatus=cublasDnrm2(cbhandle, N, Jkdkd, 1, &FJkdknrm); - - - FJkdknrm=FJkdknrm*FJkdknrm; - - /* find ratio */ - Ak=Fxknrm2-Fxksknrm2; - Pk=Fxknrm2-FJkdknrm+Fyknrm-Fykdhatknrm; - /* if Pk=p0) { - p_update=1; - /* update p<= p+sk */ - //my_daxpy(M,sk,1.0,p); - alpha=1.0; - cbstatus=cublasDaxpy(cbhandle, M, &alpha, skd, 1, pd, 1); - /* also update auxiliary info */ - /* Fxk <= Fyk */ - //my_dcopy(N,Fyk,1,Fxk,1); - cbstatus=cublasDcopy(cbhandle, N, Fykd, 1, Fxkd, 1); - - Fxknrm=Fxksknrm; - /* new Jk needed */ - } else { /* else no p update */ - p_update=0; - /* use previous Jk, Fxk, JkTJk, JkTe */ - } - if (rk0.25*mu) { - mu=m; - } else { - mu=0.25*mu; - } - } - -#ifdef DEBUG - printf("Ak=%lf Pk=%lf rk=%lf mu=%lf ||Fxk||=%lf\n",Ak,Pk,rk,mu,Fxknrm); -#endif - niter++; - } - - /* copy back solution */ - //err=cudaMemcpy(p,pd,M*sizeof(double),cudaMemcpyDeviceToHost); - err=cudaMemcpyAsync(p,pd,M*sizeof(double),cudaMemcpyDeviceToHost,0); - checkCudaError(err,__FILE__,__LINE__); - - /* check once CUBLAS error */ - checkCublasError(cbstatus,__FILE__,__LINE__); - - - if (!gWORK) { - if (solve_axb==1) { - cudaFree(taud); - } else if (solve_axb==2) { - cudaFree(Ud); - cudaFree(VTd); - cudaFree(Sd); - } - cudaFree(xd); - cudaFree(Jkd); - cudaFree(Fxkd); - cudaFree(Fykd); - cudaFree(Jkdkd); - cudaFree(JkTed); - cudaFree(JkTed0); - cudaFree(JkTJkd); - cudaFree(JkTJkd0); - cudaFree(dkd); - cudaFree(dhatkd); - cudaFree(ykd); - cudaFree(skd); - cudaFree(pd); - - cudaFree(bbd); - cudaFree(cohd); - } - - cudaFree(devInfo); - cudaFree(work); - if (solve_axb==2) { - cudaFree(rwork); - } - - if(info){ - info[0]=init_Fxknrm; - info[1]=Fxknrm; - } - /* synchronize async operations */ - cudaDeviceSynchronize(); - return 0; -} diff --git a/src/lib/clmfit_fl.c b/src/lib/clmfit_fl.c deleted file mode 100644 index e6217ec..0000000 --- a/src/lib/clmfit_fl.c +++ /dev/null @@ -1,1116 +0,0 @@ -/* - * - Copyright (C) 2006-2008 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#include -#include -#include -#include -#include - -#include "sagecal.h" -#include - -//#define DEBUG -/* helper functions for diagnostics */ -static void -checkCudaError(cudaError_t err, const char *file, int line) -{ -#ifdef CUDA_DEBUG - if(!err) - return; - fprintf(stderr,"GPU (CUDA): %s %s %d\n", cudaGetErrorString(err),file,line); - exit(EXIT_FAILURE); -#endif -} - - -static void -checkCublasError(cublasStatus_t cbstatus, char *file, int line) -{ -#ifdef CUDA_DEBUG - if (cbstatus!=CUBLAS_STATUS_SUCCESS) { - fprintf(stderr,"%s: %d: CUBLAS failure\n",file,line); - exit(EXIT_FAILURE); - } -#endif -} - - - - -/* OS-LM, but f() and jac() calculations are done - entirely in the GPU */ -int -oslevmar_der_single_cuda_fl( - float *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - float *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - float *gWORK, /* GPU allocated memory */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - int randomize, /* if >0 randomize */ - void *adata) /* pointer to possibly additional data, passed uninterpreted to func & jacf. - * Set to NULL if not needed - */ -{ - - /* general note: all device variables end with a 'd' */ - int stop=0; - cudaError_t err; - cublasStatus_t cbstatus; - - int nu=2,nu2; - float p_L2, Dp_L2=(float)DBL_MAX, dF, dL, p_eL2, jacTe_inf=0.0f, pDp_eL2, init_p_eL2; - float tmp,mu=0.0f; - float tau, eps1, eps2, eps2_sq, eps3; - int k,ci,issolved; - - float *hxd; - - float *ed; - float *xd; - - float *jacd; - - float *jacTjacd,*jacTjacd0; - - float *Dpd,*bd; - float *pd,*pnewd; - float *jacTed; - - /* used in QR solver */ - float *taud=0; - - /* used in SVD solver */ - float *Ud=0; - float *VTd=0; - float *Sd=0; - - /* ME data */ - me_data_t *dp=(me_data_t*)adata; - int Nbase=(dp->Nbase)*(ntiles); /* note: we do not use the total tile size */ - /* coherency on device */ - float *cohd; - /* baseline-station map on device/host */ - short *bbd; - - int solve_axb=linsolv; - - /* setup default settings */ - if(opts){ - tau=(float)opts[0]; - eps1=(float)opts[1]; - eps2=(float)opts[2]; - eps2_sq=(float)opts[2]*opts[2]; - eps3=(float)opts[3]; - } else { - tau=(float)CLM_INIT_MU; - eps1=(float)CLM_STOP_THRESH; - eps2=(float)CLM_STOP_THRESH; - eps2_sq=(float)CLM_STOP_THRESH*CLM_STOP_THRESH; - eps3=(float)CLM_STOP_THRESH; - } - - /* calculate no of cuda threads and blocks */ - int ThreadsPerBlock=DEFAULT_TH_PER_BK; - int BlocksPerGrid=(M+ThreadsPerBlock-1)/ThreadsPerBlock; - - - unsigned long int moff; - moff=0; - xd=&gWORK[moff]; - moff+=N; - jacd=&gWORK[moff]; - moff+=M*N; - jacTjacd=&gWORK[moff]; - moff+=M*M; - jacTed=&gWORK[moff]; - moff+=M; - jacTjacd0=&gWORK[moff]; - moff+=M*M; - Dpd=&gWORK[moff]; - moff+=M; - bd=&gWORK[moff]; - moff+=M; - pd=&gWORK[moff]; - moff+=M; - pnewd=&gWORK[moff]; - moff+=M; - cohd=&gWORK[moff]; - moff+=Nbase*8; - hxd=&gWORK[moff]; - moff+=N; - ed=&gWORK[moff]; - moff+=N; - if (solve_axb==1) { - taud=&gWORK[moff]; - moff+=M; - } else if (solve_axb==2) { - Ud=&gWORK[moff]; - moff+=M*M; - VTd=&gWORK[moff]; - moff+=M*M; - Sd=&gWORK[moff]; - moff+=M; - } - bbd=(short*)&gWORK[moff]; - moff+=(Nbase*2*sizeof(short))/sizeof(float); - - /* extra storage for cusolver */ - int work_size=0; - int *devInfo; - int devInfo_h=0; - err=cudaMalloc((void**)&devInfo, sizeof(int)); - checkCudaError(err,__FILE__,__LINE__); - float *work; - float *rwork; - if (solve_axb==0) { - cusolverDnSpotrf_bufferSize(solver_handle, CUBLAS_FILL_MODE_UPPER, M, jacTjacd, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(float)); - checkCudaError(err,__FILE__,__LINE__); - } else if (solve_axb==1) { - cusolverDnSgeqrf_bufferSize(solver_handle, M, M, jacTjacd, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(float)); - checkCudaError(err,__FILE__,__LINE__); - } else { - cusolverDnSgesvd_bufferSize(solver_handle, M, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(float)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&rwork, 5*M*sizeof(float)); - checkCudaError(err,__FILE__,__LINE__); - } - - - err=cudaMemcpyAsync(pd, p, M*sizeof(float), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - /* need to give right offset for coherencies */ - /* offset: cluster offset+time offset */ - err=cudaMemcpyAsync(cohd, &(dp->ddcohf[(dp->Nbase)*(dp->tilesz)*(dp->clus)*8+(dp->Nbase)*tileoff*8]), Nbase*8*sizeof(float), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - /* correct offset for baselines */ - err=cudaMemcpyAsync(bbd, &(dp->ddbase[2*(dp->Nbase)*(tileoff)]), Nbase*2*sizeof(short), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - cudaDeviceSynchronize(); - /* xd <=x */ - err=cudaMemcpyAsync(xd, x, N*sizeof(float), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - - /* ### compute e=x - f(p) and its L2 norm */ - /* ### e=x-hx, p_eL2=||e|| */ - /* p: params (Mx1), x: data (Nx1), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations*/ - cudakernel_func_fl(ThreadsPerBlock, (Nbase+ThreadsPerBlock-1)/ThreadsPerBlock, pd,hxd,M,N, cohd, bbd, Nbase, dp->M, dp->N); - - /* e=x */ - cbstatus=cublasScopy(cbhandle, N, xd, 1, ed, 1); - /* e=x-hx */ - float alpha=-1.0f; - cbstatus=cublasSaxpy(cbhandle, N, &alpha, hxd, 1, ed, 1); - - /* norm ||e|| */ - cbstatus=cublasSnrm2(cbhandle, N, ed, 1, &p_eL2); - /* square */ - p_eL2=p_eL2*p_eL2; - - init_p_eL2=p_eL2; - if(!finitef(p_eL2)) stop=7; - - /* setup OS subsets and stating offsets */ - /* ed : N, cohd : Nbase*8, bbd : Nbase*2 full size */ - /* if ntiles= no. of OS iterations, so select - a random set of subsets */ - /* N, Nbase changes with subset, cohd,bbd,ed gets offsets */ - /* ed : N, cohd : Nbase*8, bbd : Nbase*2 full size */ - /* p: params (Mx1), jacd: jacobian (NxM), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations*/ - /* FIXME thread/block sizes 16x16=256, so 16 is chosen */ - cudakernel_jacf_fl(ThreadsPerBlock, ThreadsPerBlock/4, pd, jacd, M, Nos[l], &cohd[8*NbI[l]], &bbd[2*NbI[l]], Nbaseos[l], dp->M, dp->N); - - /* Compute J^T J and J^T e */ - /* Cache efficient computation of J^T J based on blocking - */ - /* since J is in ROW major order, assume it is transposed, - so actually calculate A=J*J^T, where J is size MxN */ - //status=culaDeviceSgemm('N','T',M,M,Nos[l],1.0f,jacd,M,jacd,M,0.0f,jacTjacd,M); - //checkStatus(status,__FILE__,__LINE__); - float cone=1.0f; float czero=0.0f; - cbstatus=cublasSgemm(cbhandle,CUBLAS_OP_N,CUBLAS_OP_T,M,M,Nos[l],&cone,jacd,M,jacd,M,&czero,jacTjacd,M); - - /* create backup */ - /* copy jacTjacd0<=jacTjacd */ - cbstatus=cublasScopy(cbhandle, M*M, jacTjacd, 1, jacTjacd0, 1); - /* J^T e */ - /* calculate b=J^T*e (actually compute b=J*e, where J in row major (size MxN) */ - //status=culaDeviceSgemv('N',M,Nos[l],1.0f,jacd,M,&ed[edI[l]],1,0.0f,jacTed,1); - //checkStatus(status,__FILE__,__LINE__); - cbstatus=cublasSgemv(cbhandle,CUBLAS_OP_N,M,Nos[l],&cone,jacd,M,&ed[edI[l]],1,&czero,jacTed,1); - - - /* Compute ||J^T e||_inf and ||p||^2 */ - /* find infinity norm of J^T e, 1 based indexing*/ - cbstatus=cublasIsamax(cbhandle, M, jacTed, 1, &ci); - err=cudaMemcpy(&jacTe_inf,&(jacTed[ci-1]),sizeof(float),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - /* L2 norm of current parameter values */ - /* norm ||Dp|| */ - cbstatus=cublasSnrm2(cbhandle, M, pd, 1, &p_L2); - p_L2=p_L2*p_L2; - if(jacTe_inf<0.0f) {jacTe_inf=-jacTe_inf;} -#ifdef DEBUG - printf("Inf norm=%f\n",jacTe_inf); -#endif - - /* check for convergence */ - if((jacTe_inf <= eps1)){ - Dp_L2=0.0f; /* no increment for p in this case */ - stop=1; - break; - } - - /* compute initial (k=0) damping factor */ - if (k==0) { - /* find max diagonal element (stride is M+1) */ - /* should be MAX not MAX(ABS) */ - cbstatus=cublasIsamax(cbhandle, M, jacTjacd, M+1, &ci); /* 1 based index */ - ci=(ci-1)*(M+1); /* right value of the diagonal */ - - err=cudaMemcpy(&tmp,&(jacTjacd[ci]),sizeof(float),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - mu=tau*tmp; - } - - - /* determine increment using adaptive damping */ - while(1){ - /* augment normal equations */ - /* increment A => A+ mu*I, increment diagonal entries */ - /* copy jacTjacd<=jacTjacd0 */ - cbstatus=cublasScopy(cbhandle, M*M, jacTjacd0, 1, jacTjacd, 1); - cudakernel_diagmu_fl(ThreadsPerBlock, BlocksPerGrid, M, jacTjacd, mu); - -#ifdef DEBUG - printf("mu=%f\n",mu); -#endif -/*************************************************************************/ - issolved=0; - /* solve augmented equations A x = b */ - /* A==jacTjacd, b==Dpd, after solving, x==Dpd */ - /* b=jacTed : intially right hand side, at exit the solution */ - if (solve_axb==0) { - /* Cholesky solver **********************/ - /* lower triangle of Ad is destroyed */ - //status=culaDeviceSpotrf('U',M,jacTjacd,M); - cusolverDnSpotrf(solver_handle, CUBLAS_FILL_MODE_UPPER, M, jacTjacd, M, work, work_size, devInfo); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (!devInfo_h) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - fprintf(stderr,"Singular matrix\n"); -#endif - } - if (issolved) { - /* copy Dpd<=jacTed */ - cbstatus=cublasScopy(cbhandle, M, jacTed, 1, Dpd, 1); -#ifdef DEBUG - checkCublasError(cbstatus,__FILE__,__LINE__); -#endif - //status=culaDeviceSpotrs('U',M,1,jacTjacd,M,Dpd,M); - cusolverDnSpotrs(solver_handle, CUBLAS_FILL_MODE_UPPER,M,1,jacTjacd,M,Dpd,M,devInfo); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (devInfo_h) { - issolved=0; -#ifdef DEBUG - fprintf(stderr,"Singular matrix\n"); -#endif - } - } - } else if (solve_axb==1) { - /* QR solver ********************************/ - //status=culaDeviceSgeqrf(M,M,jacTjacd,M,taud); - cusolverDnSgeqrf(solver_handle, M, M, jacTjacd, M, taud, work, work_size, devInfo); - cudaDeviceSynchronize(); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (!devInfo_h) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - fprintf(stderr,"Singular matrix\n"); -#endif - } - - if (issolved) { - /* copy Dpd<=jacTed */ - cbstatus=cublasScopy(cbhandle, M, jacTed, 1, Dpd, 1); - //status=culaDeviceSgeqrs(M,M,1,jacTjacd,M,taud,Dpd,M); - cusolverDnSormqr(solver_handle, CUBLAS_SIDE_LEFT, CUBLAS_OP_T, M, 1, M, jacTjacd, M, taud, Dpd, M, work, work_size, devInfo); - cudaDeviceSynchronize(); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (devInfo_h) { - issolved=0; -#ifdef DEBUG - fprintf(stderr,"Singular matrix\n"); -#endif - } else { - cone=1.0f; - cbstatus=cublasStrsm(cbhandle,CUBLAS_SIDE_LEFT,CUBLAS_FILL_MODE_UPPER,CUBLAS_OP_N,CUBLAS_DIAG_NON_UNIT,M,1,&cone,jacTjacd,M,Dpd,M); - } - } - } else { - /* SVD solver *********************************/ - /* U S VT = A */ - //status=culaDeviceSgesvd('A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M); - //checkStatus(status,__FILE__,__LINE__); - cusolverDnSgesvd(solver_handle,'A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M,work,work_size,rwork,devInfo); - cudaDeviceSynchronize(); - /* copy Dpd<=jacTed */ - cbstatus=cublasScopy(cbhandle, M, jacTed, 1, Dpd, 1); - /* b<=U^T * b */ - //status=culaDeviceSgemv('T',M,M,1.0f,Ud,M,Dpd,1,0.0f,Dpd,1); - //checkStatus(status,__FILE__,__LINE__); - cone=1.0f; czero=0.0f; - cbstatus=cublasSgemv(cbhandle,CUBLAS_OP_T,M,M,&cone,Ud,M,Dpd,1,&czero,Dpd,1); - /* divide by singular values Dpd[]/Sd[] for Sd[]> eps1 */ - cudakernel_diagdiv_fl(ThreadsPerBlock, BlocksPerGrid, M, eps1, Dpd, Sd); - - /* b<=VT^T * b */ - //status=culaDeviceSgemv('T',M,M,1.0f,VTd,M,Dpd,1,0.0f,Dpd,1); - //checkStatus(status,__FILE__,__LINE__); - cbstatus=cublasSgemv(cbhandle,CUBLAS_OP_T,M,M,&cone,VTd,M,Dpd,1,&czero,Dpd,1); - - issolved=1; - } -/*************************************************************************/ - - /* compute p's new estimate and ||Dp||^2 */ - if (issolved) { - /* compute p's new estimate and ||Dp||^2 */ - /* pnew=p+Dp */ - /* pnew=p */ - cbstatus=cublasScopy(cbhandle, M, pd, 1, pnewd, 1); - /* pnew=pnew+Dp */ - alpha=1.0f; - cbstatus=cublasSaxpy(cbhandle, M, &alpha, Dpd, 1, pnewd, 1); - - /* norm ||Dp|| */ - cbstatus=cublasSnrm2(cbhandle, M, Dpd, 1, &Dp_L2); - Dp_L2=Dp_L2*Dp_L2; - -#ifdef DEBUG -printf("norm ||dp|| =%f, norm ||p||=%f\n",Dp_L2,p_L2); -#endif - if(Dp_L2<=eps2_sq*p_L2){ /* relative change in p is small, stop */ - stop=2; - break; - } - - if(Dp_L2>=(p_L2+eps2)/(float)(CLM_EPSILON*CLM_EPSILON)){ /* almost singular */ - stop=4; - break; - } - - /* new function value */ - /* compute ||e(pDp)||_2 */ - /* ### hx=x-hx, pDp_eL2=||hx|| */ - /* copy to device */ - /* hxd<=hx */ - cudakernel_func_fl(ThreadsPerBlock, (Nbase+ThreadsPerBlock-1)/ThreadsPerBlock, pnewd, hxd, M, N, cohd, bbd, Nbase, dp->M, dp->N); - - /* e=x */ - cbstatus=cublasScopy(cbhandle, N, xd, 1, ed, 1); - /* e=x-hx */ - alpha=-1.0f; - cbstatus=cublasSaxpy(cbhandle, N, &alpha, hxd, 1, ed, 1); - /* note: e is updated */ - - /* norm ||e|| */ - cbstatus=cublasSnrm2(cbhandle, N, ed, 1, &pDp_eL2); - pDp_eL2=pDp_eL2*pDp_eL2; - - - if(!finitef(pDp_eL2)){ /* sum of squares is not finite, most probably due to a user error. - */ - stop=7; - break; - } - - /* dL=Dp'*(mu*Dp+jacTe) */ - /* bd=jacTe+mu*Dp */ - cbstatus=cublasScopy(cbhandle, M, jacTed, 1, bd, 1); - cbstatus=cublasSaxpy(cbhandle, M, &mu, Dpd, 1, bd, 1); - cbstatus=cublasSdot(cbhandle, M, Dpd, 1, bd, 1, &dL); - - dF=p_eL2-pDp_eL2; - -#ifdef DEBUG - printf("dF=%f, dL=%f\n",dF,dL); -#endif - if(dL>0.0f && dF>0.0f){ /* reduction in error, increment is accepted */ - tmp=(2.0f*dF/dL-1.0f); - tmp=1.0f-tmp*tmp*tmp; - mu=mu*((tmp>=(float)CLM_ONE_THIRD)? tmp : (float)CLM_ONE_THIRD); - nu=2; - - /* update p's estimate */ - cbstatus=cublasScopy(cbhandle, M, pnewd, 1, pd, 1); - - /* update ||e||_2 */ - p_eL2=pDp_eL2; - break; - } - - } - /* if this point is reached, either the linear system could not be solved or - * the error did not reduce; in any case, the increment must be rejected - */ - - mu*=(float)nu; - nu2=nu<<1; // 2*nu; - if(nu2<=nu){ /* nu has wrapped around (overflown). */ - stop=5; - break; - } - - nu=nu2; - - } /* inner loop */ - - } - if (randomize) { - free(subI); - } -/**************** end OS loop ***************************/ - - } - /**** end iteration loop ***********/ - free(Nos); - free(Nbaseos); - free(edI); - free(NbI); - - if(k>=itmax) stop=3; - - /* copy back current solution */ - err=cudaMemcpyAsync(p,pd,M*sizeof(float),cudaMemcpyDeviceToHost,0); - checkCudaError(err,__FILE__,__LINE__); - checkCublasError(cbstatus,__FILE__,__LINE__); - /* synchronize async operations */ - cudaDeviceSynchronize(); - - cudaFree(devInfo); - cudaFree(work); - if (solve_axb==2) { - cudaFree(rwork); - } - -#ifdef DEBUG - printf("stop=%d\n",stop); -#endif - if(info){ - info[0]=(double)init_p_eL2; - info[1]=(double)p_eL2; - info[2]=(double)jacTe_inf; - info[3]=(double)Dp_L2; - info[4]=(double)mu; - info[5]=(double)k; - info[6]=(double)stop; - info[7]=(double)0; - info[8]=(double)0; - info[9]=(double)0; - } - return 0; -} - - -int -clevmar_der_single_cuda_fl( - float *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - float *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - float *gWORK, /* GPU allocated memory */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - void *adata) /* pointer to possibly additional data, passed uninterpreted to func & jacf. - * Set to NULL if not needed - */ -{ - - /* general note: all device variables end with a 'd' */ - int stop=0; - cudaError_t err; - cublasStatus_t cbstatus; - - int nu=2,nu2; - float p_L2, Dp_L2=(float)DBL_MAX, dF, dL, p_eL2, jacTe_inf=0.0f, pDp_eL2, init_p_eL2; - float tmp,mu=0.0f; - float tau, eps1, eps2, eps2_sq, eps3; - int k,ci,issolved; - - float *hxd; - - float *ed; - float *xd; - - float *jacd; - - float *jacTjacd,*jacTjacd0; - - float *Dpd,*bd; - float *pd,*pnewd; - float *jacTed; - - /* used in QR solver */ - float *taud=0; - - /* used in SVD solver */ - float *Ud=0; - float *VTd=0; - float *Sd=0; - - /* ME data */ - me_data_t *dp=(me_data_t*)adata; - int Nbase=(dp->Nbase)*(ntiles); /* note: we do not use the total tile size */ - /* coherency on device */ - float *cohd; - /* baseline-station map on device/host */ - short *bbd; - - int solve_axb=linsolv; - - /* setup default settings */ - if(opts){ - tau=(float)opts[0]; - eps1=(float)opts[1]; - eps2=(float)opts[2]; - eps2_sq=(float)opts[2]*opts[2]; - eps3=(float)opts[3]; - } else { - tau=(float)CLM_INIT_MU; - eps1=(float)CLM_STOP_THRESH; - eps2=(float)CLM_STOP_THRESH; - eps2_sq=(float)CLM_STOP_THRESH*CLM_STOP_THRESH; - eps3=(float)CLM_STOP_THRESH; - } - - /* calculate no of cuda threads and blocks */ - int ThreadsPerBlock=DEFAULT_TH_PER_BK; - int BlocksPerGrid=(M+ThreadsPerBlock-1)/ThreadsPerBlock; - - - unsigned long int moff; - moff=0; - xd=&gWORK[moff]; - moff+=N; - jacd=&gWORK[moff]; - moff+=M*N; - jacTjacd=&gWORK[moff]; - moff+=M*M; - jacTed=&gWORK[moff]; - moff+=M; - jacTjacd0=&gWORK[moff]; - moff+=M*M; - Dpd=&gWORK[moff]; - moff+=M; - bd=&gWORK[moff]; - moff+=M; - pd=&gWORK[moff]; - moff+=M; - pnewd=&gWORK[moff]; - moff+=M; - cohd=&gWORK[moff]; - moff+=Nbase*8; - hxd=&gWORK[moff]; - moff+=N; - ed=&gWORK[moff]; - moff+=N; - if (solve_axb==1) { - taud=&gWORK[moff]; - moff+=M; - } else if (solve_axb==2) { - Ud=&gWORK[moff]; - moff+=M*M; - VTd=&gWORK[moff]; - moff+=M*M; - Sd=&gWORK[moff]; - moff+=M; - } - bbd=(short*)&gWORK[moff]; - moff+=(Nbase*2*sizeof(short))/sizeof(float); - - /* extra storage for cusolver */ - int work_size=0; - int *devInfo; - int devInfo_h=0; - err=cudaMalloc((void**)&devInfo, sizeof(int)); - checkCudaError(err,__FILE__,__LINE__); - float *work; - float *rwork; - if (solve_axb==0) { - cusolverDnSpotrf_bufferSize(solver_handle, CUBLAS_FILL_MODE_UPPER, M, jacTjacd, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(float)); - checkCudaError(err,__FILE__,__LINE__); - } else if (solve_axb==1) { - cusolverDnSgeqrf_bufferSize(solver_handle, M, M, jacTjacd, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(float)); - checkCudaError(err,__FILE__,__LINE__); - } else { - cusolverDnSgesvd_bufferSize(solver_handle, M, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(float)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&rwork, 5*M*sizeof(float)); - checkCudaError(err,__FILE__,__LINE__); - } - - - - err=cudaMemcpyAsync(pd, p, M*sizeof(float), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - /* need to give right offset for coherencies */ - /* offset: cluster offset+time offset */ - err=cudaMemcpyAsync(cohd, &(dp->ddcohf[(dp->Nbase)*(dp->tilesz)*(dp->clus)*8+(dp->Nbase)*tileoff*8]), Nbase*8*sizeof(float), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - /* correct offset for baselines */ - err=cudaMemcpyAsync(bbd, &(dp->ddbase[2*(dp->Nbase)*(tileoff)]), Nbase*2*sizeof(short), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - cudaDeviceSynchronize(); - /* xd <=x */ - err=cudaMemcpyAsync(xd, x, N*sizeof(float), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - - /* ### compute e=x - f(p) and its L2 norm */ - /* ### e=x-hx, p_eL2=||e|| */ - /* p: params (Mx1), x: data (Nx1), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations*/ - cudakernel_func_fl(ThreadsPerBlock, (Nbase+ThreadsPerBlock-1)/ThreadsPerBlock, pd,hxd,M,N, cohd, bbd, Nbase, dp->M, dp->N); - - /* e=x */ - cbstatus=cublasScopy(cbhandle, N, xd, 1, ed, 1); - /* e=x-hx */ - float alpha=-1.0f; - cbstatus=cublasSaxpy(cbhandle, N, &alpha, hxd, 1, ed, 1); - - /* norm ||e|| */ - cbstatus=cublasSnrm2(cbhandle, N, ed, 1, &p_eL2); - /* square */ - p_eL2=p_eL2*p_eL2; - - init_p_eL2=p_eL2; - if(!finitef(p_eL2)) stop=7; - - - /**** iteration loop ***********/ - for(k=0; kstat mapping, Nbase, Mclusters, Nstations*/ - /* FIXME thread/block sizes 16x16=256, so 16 is chosen */ - cudakernel_jacf_fl(ThreadsPerBlock, ThreadsPerBlock/4, pd, jacd, M, N, cohd, bbd, Nbase, dp->M, dp->N); - - /* Compute J^T J and J^T e */ - /* Cache efficient computation of J^T J based on blocking - */ - /* since J is in ROW major order, assume it is transposed, - so actually calculate A=J*J^T, where J is size MxN */ - //status=culaDeviceSgemm('N','T',M,M,N,1.0f,jacd,M,jacd,M,0.0f,jacTjacd,M); - //checkStatus(status,__FILE__,__LINE__); - float cone=1.0f; float czero=0.0f; - cbstatus=cublasSgemm(cbhandle,CUBLAS_OP_N,CUBLAS_OP_T,M,M,N,&cone,jacd,M,jacd,M,&czero,jacTjacd,M); - - - /* create backup */ - /* copy jacTjacd0<=jacTjacd */ - cbstatus=cublasScopy(cbhandle, M*M, jacTjacd, 1, jacTjacd0, 1); - /* J^T e */ - /* calculate b=J^T*e (actually compute b=J*e, where J in row major (size MxN) */ - //status=culaDeviceSgemv('N',M,N,1.0f,jacd,M,ed,1,0.0f,jacTed,1); - //checkStatus(status,__FILE__,__LINE__); - cbstatus=cublasSgemv(cbhandle,CUBLAS_OP_N,M,N,&cone,jacd,M,ed,1,&czero,jacTed,1); - - - /* Compute ||J^T e||_inf and ||p||^2 */ - /* find infinity norm of J^T e, 1 based indexing*/ - cbstatus=cublasIsamax(cbhandle, M, jacTed, 1, &ci); - err=cudaMemcpy(&jacTe_inf,&(jacTed[ci-1]),sizeof(float),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - /* L2 norm of current parameter values */ - /* norm ||Dp|| */ - cbstatus=cublasSnrm2(cbhandle, M, pd, 1, &p_L2); - p_L2=p_L2*p_L2; - if(jacTe_inf<0.0f) {jacTe_inf=-jacTe_inf;} -#ifdef DEBUG - printf("Inf norm=%f\n",jacTe_inf); -#endif - - /* check for convergence */ - if((jacTe_inf <= eps1)){ - Dp_L2=0.0f; /* no increment for p in this case */ - stop=1; - break; - } - - /* compute initial (k=0) damping factor */ - if (k==0) { - /* find max diagonal element (stride is M+1) */ - /* should be MAX not MAX(ABS) */ - cbstatus=cublasIsamax(cbhandle, M, jacTjacd, M+1, &ci); /* 1 based index */ - ci=(ci-1)*(M+1); /* right value of the diagonal */ - - err=cudaMemcpy(&tmp,&(jacTjacd[ci]),sizeof(float),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - mu=tau*tmp; - } - - - /* determine increment using adaptive damping */ - while(1){ - /* augment normal equations */ - /* increment A => A+ mu*I, increment diagonal entries */ - /* copy jacTjacd<=jacTjacd0 */ - cbstatus=cublasScopy(cbhandle, M*M, jacTjacd0, 1, jacTjacd, 1); - cudakernel_diagmu_fl(ThreadsPerBlock, BlocksPerGrid, M, jacTjacd, mu); - -#ifdef DEBUG - printf("mu=%f\n",mu); -#endif -/*************************************************************************/ - issolved=0; - /* solve augmented equations A x = b */ - /* A==jacTjacd, b==Dpd, after solving, x==Dpd */ - /* b=jacTed : intially right hand side, at exit the solution */ - if (solve_axb==0) { - /* Cholesky solver **********************/ - /* lower triangle of Ad is destroyed */ - //status=culaDeviceSpotrf('U',M,jacTjacd,M); - cusolverDnSpotrf(solver_handle, CUBLAS_FILL_MODE_UPPER, M, jacTjacd, M, work, work_size, devInfo); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (!devInfo_h) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - fprintf(stderr,"Singular matrix\n"); -#endif - } - if (issolved) { - /* copy Dpd<=jacTed */ - cbstatus=cublasScopy(cbhandle, M, jacTed, 1, Dpd, 1); -#ifdef DEBUG - checkCublasError(cbstatus,__FILE__,__LINE__); -#endif - //status=culaDeviceSpotrs('U',M,1,jacTjacd,M,Dpd,M); - cusolverDnSpotrs(solver_handle, CUBLAS_FILL_MODE_UPPER,M,1,jacTjacd,M,Dpd,M,devInfo); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (devInfo_h) { - issolved=0; -#ifdef DEBUG - fprintf(stderr,"Singular matrix\n"); -#endif - } - } - } else if (solve_axb==1) { - /* QR solver ********************************/ - //status=culaDeviceSgeqrf(M,M,jacTjacd,M,taud); - cusolverDnSgeqrf(solver_handle, M, M, jacTjacd, M, taud, work, work_size, devInfo); - cudaDeviceSynchronize(); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (!devInfo_h) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - fprintf(stderr,"Singular matrix\n"); -#endif - } - - if (issolved) { - /* copy Dpd<=jacTed */ - cbstatus=cublasScopy(cbhandle, M, jacTed, 1, Dpd, 1); - //status=culaDeviceSgeqrs(M,M,1,jacTjacd,M,taud,Dpd,M); - cusolverDnSormqr(solver_handle, CUBLAS_SIDE_LEFT, CUBLAS_OP_T, M, 1, M, jacTjacd, M, taud, Dpd, M, work, work_size, devInfo); - cudaDeviceSynchronize(); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (devInfo_h) { - issolved=0; -#ifdef DEBUG - fprintf(stderr,"Singular matrix\n"); -#endif - } else { - cone=1.0f; - cbstatus=cublasStrsm(cbhandle,CUBLAS_SIDE_LEFT,CUBLAS_FILL_MODE_UPPER,CUBLAS_OP_N,CUBLAS_DIAG_NON_UNIT,M,1,&cone,jacTjacd,M,Dpd,M); - } - } - } else { - /* SVD solver *********************************/ - /* U S VT = A */ - //status=culaDeviceSgesvd('A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M); - //checkStatus(status,__FILE__,__LINE__); - cusolverDnSgesvd(solver_handle,'A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M,work,work_size,rwork,devInfo); - cudaDeviceSynchronize(); - /* copy Dpd<=jacTed */ - cbstatus=cublasScopy(cbhandle, M, jacTed, 1, Dpd, 1); - /* b<=U^T * b */ - //status=culaDeviceSgemv('T',M,M,1.0f,Ud,M,Dpd,1,0.0f,Dpd,1); - //checkStatus(status,__FILE__,__LINE__); - cone=1.0f; czero=0.0f; - cbstatus=cublasSgemv(cbhandle,CUBLAS_OP_T,M,M,&cone,Ud,M,Dpd,1,&czero,Dpd,1); - - /* divide by singular values Dpd[]/Sd[] for Sd[]> eps1 */ - cudakernel_diagdiv_fl(ThreadsPerBlock, BlocksPerGrid, M, eps1, Dpd, Sd); - - /* b<=VT^T * b */ - //status=culaDeviceSgemv('T',M,M,1.0f,VTd,M,Dpd,1,0.0f,Dpd,1); - //checkStatus(status,__FILE__,__LINE__); - cbstatus=cublasSgemv(cbhandle,CUBLAS_OP_T,M,M,&cone,VTd,M,Dpd,1,&czero,Dpd,1); - - issolved=1; - } -/*************************************************************************/ - - /* compute p's new estimate and ||Dp||^2 */ - if (issolved) { - /* compute p's new estimate and ||Dp||^2 */ - /* pnew=p+Dp */ - /* pnew=p */ - cbstatus=cublasScopy(cbhandle, M, pd, 1, pnewd, 1); - /* pnew=pnew+Dp */ - alpha=1.0f; - cbstatus=cublasSaxpy(cbhandle, M, &alpha, Dpd, 1, pnewd, 1); - - /* norm ||Dp|| */ - cbstatus=cublasSnrm2(cbhandle, M, Dpd, 1, &Dp_L2); - Dp_L2=Dp_L2*Dp_L2; - -#ifdef DEBUG -printf("norm ||dp|| =%f, norm ||p||=%f\n",Dp_L2,p_L2); -#endif - if(Dp_L2<=eps2_sq*p_L2){ /* relative change in p is small, stop */ - stop=2; - break; - } - - if(Dp_L2>=(p_L2+eps2)/(float)(CLM_EPSILON*CLM_EPSILON)){ /* almost singular */ - stop=4; - break; - } - - /* new function value */ - /* compute ||e(pDp)||_2 */ - /* ### hx=x-hx, pDp_eL2=||hx|| */ - /* copy to device */ - /* hxd<=hx */ - cudakernel_func_fl(ThreadsPerBlock, (Nbase+ThreadsPerBlock-1)/ThreadsPerBlock, pnewd, hxd, M, N, cohd, bbd, Nbase, dp->M, dp->N); - - /* e=x */ - cbstatus=cublasScopy(cbhandle, N, xd, 1, ed, 1); - /* e=x-hx */ - alpha=-1.0f; - cbstatus=cublasSaxpy(cbhandle, N, &alpha, hxd, 1, ed, 1); - /* note: e is updated */ - - /* norm ||e|| */ - cbstatus=cublasSnrm2(cbhandle, N, ed, 1, &pDp_eL2); - pDp_eL2=pDp_eL2*pDp_eL2; - - - if(!finitef(pDp_eL2)){ /* sum of squares is not finite, most probably due to a user error. - */ - stop=7; - break; - } - - /* dL=Dp'*(mu*Dp+jacTe) */ - /* bd=jacTe+mu*Dp */ - cbstatus=cublasScopy(cbhandle, M, jacTed, 1, bd, 1); - cbstatus=cublasSaxpy(cbhandle, M, &mu, Dpd, 1, bd, 1); - cbstatus=cublasSdot(cbhandle, M, Dpd, 1, bd, 1, &dL); - - dF=p_eL2-pDp_eL2; - -#ifdef DEBUG - printf("dF=%f, dL=%f\n",dF,dL); -#endif - if(dL>0.0f && dF>0.0f){ /* reduction in error, increment is accepted */ - tmp=(2.0f*dF/dL-1.0f); - tmp=1.0f-tmp*tmp*tmp; - mu=mu*((tmp>=(float)CLM_ONE_THIRD)? tmp : (float)CLM_ONE_THIRD); - nu=2; - - /* update p's estimate */ - cbstatus=cublasScopy(cbhandle, M, pnewd, 1, pd, 1); - - /* update ||e||_2 */ - p_eL2=pDp_eL2; - break; - } - - } - /* if this point is reached, either the linear system could not be solved or - * the error did not reduce; in any case, the increment must be rejected - */ - - mu*=(float)nu; - nu2=nu<<1; // 2*nu; - if(nu2<=nu){ /* nu has wrapped around (overflown). */ - stop=5; - break; - } - - nu=nu2; - - } /* inner loop */ - - } - /**** end iteration loop ***********/ - - - if(k>=itmax) stop=3; - - /* copy back current solution */ - err=cudaMemcpyAsync(p,pd,M*sizeof(float),cudaMemcpyDeviceToHost,0); - checkCudaError(err,__FILE__,__LINE__); - checkCublasError(cbstatus,__FILE__,__LINE__); - /* synchronize async operations */ - cudaDeviceSynchronize(); - - cudaFree(devInfo); - cudaFree(work); - if (solve_axb==2) { - cudaFree(rwork); - } - -#ifdef DEBUG - printf("stop=%d\n",stop); -#endif - if(info){ - info[0]=(double)init_p_eL2; - info[1]=(double)p_eL2; - info[2]=(double)jacTe_inf; - info[3]=(double)Dp_L2; - info[4]=(double)mu; - info[5]=(double)k; - info[6]=(double)stop; - info[7]=(double)0; - info[8]=(double)0; - info[9]=(double)0; - } - return 0; -} diff --git a/src/lib/clmfit_nocuda.c b/src/lib/clmfit_nocuda.c deleted file mode 100644 index 28ed062..0000000 --- a/src/lib/clmfit_nocuda.c +++ /dev/null @@ -1,1666 +0,0 @@ -/* - * - Copyright (C) 2006-2008 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#include "sagecal.h" - -//#define DEBUG - - - -/** keep interface almost the same as in levmar **/ -int -clevmar_der_single_nocuda( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - * info[5]= # iterations, - * info[6]=reason for terminating: 1 - stopped by small gradient J^T e - * 2 - stopped by small Dp - * 3 - stopped by itmax - * 4 - singular matrix. Restart from current p with increased mu - * 5 - no further error reduction is possible. Restart with increased mu - * 6 - stopped by small ||e||_2 - * 7 - stopped by invalid (i.e. NaN or Inf) "func" values. This is a user error - * info[7]= # function evaluations - * info[8]= # Jacobian evaluations - * info[9]= # linear systems solved, i.e. # attempts for reducing error - */ - - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - void *adata) /* pointer to possibly additional data, passed uninterpreted to func & jacf. - * Set to NULL if not needed - */ -{ - - /* general note: all device variables end with a 'd' */ - int stop=0; - int nu=2,nu2; - double p_L2, Dp_L2=DBL_MAX, dF, dL, p_eL2, jacTe_inf=0.0, pDp_eL2, init_p_eL2; - double tmp,mu=0.0; - double tau, eps1, eps2, eps2_sq, eps3; - int k,ci,issolved; - - double *hxd,*hxm=0; - double *ed; - double *jac; - - double *jacTjacd,*jacTjacd0; - - double *pnew,*Dpd,*bd; - double *aones; - double *jacTed; - - /* used in QR solver */ - double *WORK; - int lwork=0; - double w[1]; - - int status; - - /* used in SVD solver */ - double *Ud; - double *VTd; - double *Sd; - - /* for Jacobian evaluation */ - int jac_given; - double delta,tempp,ddiff; - if (!jacf) { - jac_given=0; - /* need more memory for jacobian calculation */ - if ((hxm=(double*)calloc((size_t)N,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - delta=CLM_DIFF_DELTA; - } else { - jac_given=1; - } - - int solve_axb=linsolv; - - /* setup default settings */ - if(opts){ - tau=opts[0]; - eps1=opts[1]; - eps2=opts[2]; - eps2_sq=opts[2]*opts[2]; - eps3=opts[3]; - } else { - tau=CLM_INIT_MU; - eps1=CLM_STOP_THRESH; - eps2=CLM_STOP_THRESH; - eps2_sq=CLM_STOP_THRESH*CLM_STOP_THRESH; - eps3=CLM_STOP_THRESH; - } - - if ((hxd=(double*)calloc((size_t)N,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((ed=(double*)calloc((size_t)N,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((jac=(double*)calloc((size_t)N*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((jacTjacd=(double*)calloc((size_t)M*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((jacTjacd0=(double*)calloc((size_t)M*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((jacTed=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((Dpd=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((bd=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((pnew=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((aones=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - WORK=Ud=Sd=VTd=0; - me_data_t *dt=(me_data_t*)adata; - setweights(M,aones,1.0,dt->Nt); - /* memory allocation: different solvers */ - if (solve_axb==0) { - - } else if (solve_axb==1) { - /* workspace query */ - status=my_dgels('N',M,M,1,jacTjacd,M,Dpd,M,w,-1); - if (!status) { - lwork=(int)w[0]; - if ((WORK=(double*)calloc((size_t)lwork,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - } - } else { - if ((Ud=(double*)calloc((size_t)M*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((VTd=(double*)calloc((size_t)M*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((Sd=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - status=my_dgesvd('A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M,w,-1); - if (!status) { - lwork=(int)w[0]; - if ((WORK=(double*)calloc((size_t)lwork,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - } - } - - - /* ### compute e=x - f(p) and its L2 norm */ - /* ### e=x-hx, p_eL2=||e|| */ - (*func)(p, hxd, M, N, adata); - - - /* e=x */ - my_dcopy(N, x, 1, ed, 1); - /* e=x-hx */ - my_daxpy(N, hxd, -1.0, ed); - - /* norm ||e|| */ - p_eL2=my_dnrm2(N, ed); - /* square */ - p_eL2=p_eL2*p_eL2; - - init_p_eL2=p_eL2; - if(!finite(p_eL2)) stop=7; - - - /**** iteration loop ***********/ - for(k=0; k A+ mu*I, increment diagonal entries */ - /* copy jacTjacd<=jacTjacd0 */ - //cbstatus=cublasDcopy(cbhandle, M*M, jacTjacd0, 1, jacTjacd, 1); - memcpy(jacTjacd,jacTjacd0,M*M*sizeof(double)); - //cudakernel_diagmu(ThreadsPerBlock, BlocksPerGrid, M, jacTjacd, mu); - my_daxpys(M,aones,1,mu,jacTjacd,M+1); - -#ifdef DEBUG - printf("mu=%lf\n",mu); -#endif -/*************************************************************************/ - /* solve augmented equations A x = b */ - /* A==jacTjacd, b==Dpd, after solving, x==Dpd */ - /* b=jacTed : intially right hand side, at exit the solution */ - if (solve_axb==0) { - /* Cholesky solver **********************/ - /* lower triangle of Ad is destroyed */ - status=my_dpotrf('U',M,jacTjacd,M); - if (!status) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - printf("Singular matrix info=%d\n",status); -#endif - } - if (issolved) { - /* copy Dpd<=jacTed */ - //cbstatus=cublasDcopy(cbhandle, M, jacTed, 1, Dpd, 1); - memcpy(Dpd,jacTed,M*sizeof(double)); - status=my_dpotrs('U',M,1,jacTjacd,M,Dpd,M); - if (status) { - issolved=0; -#ifdef DEBUG - printf("Singular matrix info=%d\n",status); -#endif - } - } - } else if (solve_axb==1) { - /* QR solver ********************************/ - /* copy Dpd<=jacTed */ - memcpy(Dpd,jacTed,M*sizeof(double)); - status=my_dgels('N',M,M,1,jacTjacd,M,Dpd,M,WORK,lwork); - if (!status) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - printf("Singular matrix info=%d\n",status); -#endif - } - - } else { - /* SVD solver *********************************/ - /* U S VT = A */ - status=my_dgesvd('A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M,WORK,lwork); - /* copy Dpd<=jacTed */ - //memcpy(Dpd,jacTed,M*sizeof(double)); - memcpy(bd,jacTed,M*sizeof(double)); - /* b<=U^T * b */ - my_dgemv('T',M,M,1.0,Ud,M,bd,1,0.0,Dpd,1); - /* robust correction */ - /* divide by singular values Dpd[]/Sd[] for Sd[]> eps1 */ - for (ci=0; cieps1) { - Dpd[ci]=Dpd[ci]/Sd[ci]; - } else { - Dpd[ci]=0.0; - } - } - - /* b<=VT^T * b */ - memcpy(bd,Dpd,M*sizeof(double)); - my_dgemv('T',M,M,1.0,VTd,M,bd,1,0.0,Dpd,1); - - issolved=1; - } -/*************************************************************************/ - - /* compute p's new estimate and ||Dp||^2 */ - if (issolved) { - /* compute p's new estimate and ||Dp||^2 */ - /* pnew=p+Dp */ - /* pnew=p */ - // cbstatus=cublasDcopy(cbhandle, M, pd, 1, pnewd, 1); - memcpy(pnew,p,M*sizeof(double)); - /* pnew=pnew+Dp */ - //cbstatus=cublasDaxpy(cbhandle, M, &alpha, Dpd, 1, pnewd, 1); - my_daxpy(M,Dpd,1.0,pnew); - - /* norm ||Dp|| */ - //cbstatus=cublasDnrm2(cbhandle, M, Dpd, 1, &Dp_L2); - Dp_L2=my_dnrm2(M,Dpd); - Dp_L2=Dp_L2*Dp_L2; - -#ifdef DEBUG -printf("norm ||dp|| =%lf, norm ||p||=%lf\n",Dp_L2,p_L2); -#endif - if(Dp_L2<=eps2_sq*p_L2){ /* relative change in p is small, stop */ - stop=2; - break; - } - - if(Dp_L2>=(p_L2+eps2)/(CLM_EPSILON*CLM_EPSILON)){ /* almost singular */ - stop=4; - break; - } - - /* new function value */ - (*func)(pnew, hxd, M, N, adata); /* evaluate function at p + Dp */ - - /* compute ||e(pDp)||_2 */ - /* ### hx=x-hx, pDp_eL2=||hx|| */ - /* copy to device */ - /* hxd<=hx */ - //err=cudaMemcpy(hxd, hx, N*sizeof(double), cudaMemcpyHostToDevice); - - /* e=x */ - //cbstatus=cublasDcopy(cbhandle, N, xd, 1, ed, 1); - memcpy(ed,x,N*sizeof(double)); - /* e=x-hx */ - //cbstatus=cublasDaxpy(cbhandle, N, &alpha, hxd, 1, ed, 1); - my_daxpy(N,hxd,-1.0,ed); - /* note: e is updated */ - - /* norm ||e|| */ - //cbstatus=cublasDnrm2(cbhandle, N, ed, 1, &pDp_eL2); - pDp_eL2=my_dnrm2(N,ed); - pDp_eL2=pDp_eL2*pDp_eL2; - - - if(!finite(pDp_eL2)){ /* sum of squares is not finite, most probably due to a user error. - */ - stop=7; - break; - } - - /* dL=Dp'*(mu*Dp+jacTe) */ - /* bd=jacTe+mu*Dp */ - //cbstatus=cublasDcopy(cbhandle, M, jacTed, 1, bd, 1); - memcpy(bd,jacTed,M*sizeof(double)); - //cbstatus=cublasDaxpy(cbhandle, M, &mu, Dpd, 1, bd, 1); - my_daxpy(M,Dpd,mu,bd); - //cbstatus=cublasDdot(cbhandle, M, Dpd, 1, bd, 1, &dL); - dL=my_ddot(M,Dpd,bd); - - dF=p_eL2-pDp_eL2; - -#ifdef DEBUG - printf("dF=%lf, dL=%lf\n",dF,dL); -#endif - if(dL>0.0 && dF>0.0){ /* reduction in error, increment is accepted */ - tmp=(2.0*dF/dL-1.0); - tmp=1.0-tmp*tmp*tmp; - mu=mu*((tmp>=CLM_ONE_THIRD)? tmp : CLM_ONE_THIRD); - nu=2; - - /* update p's estimate */ - //cbstatus=cublasDcopy(cbhandle, M, pnewd, 1, pd, 1); - memcpy(p,pnew,M*sizeof(double)); - - /* update ||e||_2 */ - p_eL2=pDp_eL2; - break; - } - - } - /* if this point is reached, either the linear system could not be solved or - * the error did not reduce; in any case, the increment must be rejected - */ - - mu*=(double)nu; - nu2=nu<<1; // 2*nu; - if(nu2<=nu){ /* nu has wrapped around (overflown). */ - stop=5; - break; - } - - nu=nu2; - - } /* inner loop */ - - } - /**** end iteration loop ***********/ - - - if(k>=itmax) stop=3; - - - free(jac); - free(jacTjacd); - free(jacTjacd0); - free(jacTed); - free(Dpd); - free(bd); - free(hxd); - if (!jac_given) { free(hxm); } - free(ed); - free(aones); - free(pnew); - - if (solve_axb==0) { - } else if (solve_axb==1) { - free(WORK); - } else { - free(Ud); - free(VTd); - free(Sd); - free(WORK); - } -#ifdef DEBUG - printf("stop=%d\n",stop); -#endif - if(info){ - info[0]=init_p_eL2; - info[1]=p_eL2; - info[2]=jacTe_inf; - info[3]=Dp_L2; - info[4]=mu; - info[5]=(double)k; - info[6]=(double)stop; - info[7]=(double)0; - info[8]=(double)0; - info[9]=(double)0; - } - return 0; -} - - -int -mlm_der_single( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[6], /* I: minim. options [\mu, \m, \p0, \p1, \p2, \delta]. - delta: 1 or 2 - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - */ - - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - void *adata) /* pointer to possibly additional data, passed uninterpreted to func & jacf. - * Set to NULL if not needed - */ -{ - /* NOTE F()=func()-data */ - double *Fxk, *Fyk; - double *Jk, *JkTJk, *JkTJk0, *JkTe, *JkTe0; - double *dk,*yk,*dhatk,*sk; - double *Jkdk; - - double lambda; - double *aones; - double mu,m,p0,p1,p2; int delta; - double Fxknrm,Fyknrm,Fykdhatknrm,Fxksknrm,FJkdknrm; - int niter=0; - int p_update=1; - double Fxknrm2,Fxksknrm2; - - double Ak,Pk,rk; - - int ci; - - /* used in QR solver */ - double *WORK=0,*TAU=0,*R=0; - /* used in SVD solver */ - double *Ud=0; - double *VTd=0; - double *Sd=0; - - int lwork=0; - double w[1]; - int status,issolved; - int solve_axb=linsolv; - - - - if (opts) { - mu=opts[0]; - m=opts[1]; - p0=opts[2]; - p1=opts[3]; - p2=opts[4]; - delta=(int)opts[5]; - } else { - mu=1e-5; - m=1e-3; - p0=0.0001; - p1=0.25; - p2=0.75; - delta=1; /* 1 or 2 */ - } - - double epsilon=CLM_EPSILON; - - if ((aones=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } -// for (ci=0;ciNt); - - if ((dk=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((sk=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((dhatk=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((yk=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((Jkdk=(double*)calloc((size_t)N,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((Fxk=(double*)calloc((size_t)N,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((Fyk=(double*)calloc((size_t)N,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((Jk=(double*)calloc((size_t)N*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((JkTJk=(double*)calloc((size_t)M*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((JkTJk0=(double*)calloc((size_t)M*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((JkTe=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((JkTe0=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - /* memory allocation: different solvers */ - if (solve_axb==1) { - /* QR solver ********************************/ - /* workspace query */ - status=my_dgeqrf(M,M,JkTJk,M,TAU,w,-1); - if (!status) { - lwork=(int)w[0]; - if ((WORK=(double*)calloc((size_t)lwork,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - } - if ((R=(double*)calloc((size_t)M*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((TAU=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - } else if (solve_axb==2) { - if ((Ud=(double*)calloc((size_t)M*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((VTd=(double*)calloc((size_t)M*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((Sd=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - status=my_dgesvd('A','A',M,M,JkTJk,M,Sd,Ud,M,VTd,M,w,-1); - if (!status) { - lwork=(int)w[0]; - if ((WORK=(double*)calloc((size_t)lwork,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - } - } - - /* F(x_k) = func()-data */ - /* func() */ - (*func)(p, Fxk, M, N, adata); - /* func() - data */ - my_daxpy(N, x, -1.0, Fxk); - /* find ||Fxk|| */ - Fxknrm=my_dnrm2(N,Fxk); - - double init_Fxknrm=Fxknrm; - - while (niter1) { - lambda=mu*Fxknrm*Fxknrm; - } else { - lambda=mu*Fxknrm; - } - Fxknrm2=Fxknrm*Fxknrm; - - if ( p_update==1 ) { - /* J_k */ - (*jacf)(p, Jk, M, N, adata); - /* Compute J_k^T J_k and -J_k^T F(x_k) */ - my_dgemm('N','T',M,M,N,1.0,Jk,M,Jk,M,0.0,JkTJk0,M); - my_dgemv('N',M,N,-1.0,Jk,M,Fxk,1,0.0,JkTe0,1); - } - /* if || J_k^T F(x_k) || < epsilon, stop */ - Fyknrm=my_dnrm2(M,JkTe0); - if (Fyknrm epsilon */ - for (ci=0; ciepsilon) { - dk[ci]=dk[ci]/Sd[ci]; - } else { - dk[ci]=0.0; - } - } - - /* dk <= VT^T dk */ - memcpy(yk,dk,M*sizeof(double)); - my_dgemv('T',M,M,1.0,VTd,M,yk,1,0.0,dk,1); - - } -/********************************************************************/ - - /* y_k<= x_k+ d_k */ - my_dcopy(M,p,1,yk,1); - my_daxpy(M,dk,1.0,yk); - - /* compute F(y_k) */ - /* func() */ - (*func)(yk, Fyk, M, N, adata); - /* func() - data */ - my_daxpy(N, x, -1.0, Fyk); - - /* Compute -J_k^T F(y_k) */ - my_dgemv('N',M,N,-1.0,Jk,M,Fyk,1,0.0,JkTe,1); - -/********************************************************************/ - if (solve_axb==0) { - /* Cholesky solver **********************/ - /* copy dk<=JkTe */ - memcpy(dhatk,JkTe,M*sizeof(double)); - status=my_dpotrs('U',M,1,JkTJk,M,dhatk,M); - if (status) { - issolved=0; -#ifdef DEBUG - printf("Singular matrix info=%d\n",status); -#endif - } - } else if (solve_axb==1) { - /* QR solver ********************************/ - /* dhatk <= Q^T jacTed */ - my_dgemv('T',M,M,1.0,JkTJk,M,JkTe,1,0.0,dhatk,1); - /* solve R x = b */ - status=my_dtrtrs('U','N','N',M,1,R,M,dhatk,M); - if (!status) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - printf("Singular matrix info=%d\n",status); -#endif - } - } else { - /* SVD solver *********************************/ - /* dhatk <= U^T jacTed */ - my_dgemv('T',M,M,1.0,Ud,M,JkTe,1,0.0,dhatk,1); - /* robust correction */ - /* divide by singular values dk[]/Sd[] for Sd[]> epsilon */ - for (ci=0; ciepsilon) { - dhatk[ci]=dhatk[ci]/Sd[ci]; - } else { - dhatk[ci]=0.0; - } - } - /* dk <= VT^T dk */ - memcpy(yk,dhatk,M*sizeof(double)); - my_dgemv('T',M,M,1.0,VTd,M,yk,1,0.0,dhatk,1); - } -/********************************************************************/ - - - - /* s_k<= d_k+ dhat_k */ - my_dcopy(M,dk,1,sk,1); - my_daxpy(M,dhatk,1.0,sk); - - /* find norms */ - /* || F(y_k) || */ - Fyknrm=my_dnrm2(N,Fyk); - Fyknrm=Fyknrm*Fyknrm; - - /* || F(y_k) + J_k dhat_k || */ - my_dgemv('T',M,N,1.0,Jk,M,dhatk,1,0.0,Jkdk,1); - /* Fyk <= Fyk+ J_k dhat_k */ - my_daxpy(N,Jkdk,1.0,Fyk); - Fykdhatknrm=my_dnrm2(N,Fyk); - Fykdhatknrm=Fykdhatknrm*Fykdhatknrm; - - /* ||F(x_k+d_k+dhat_k)|| == ||F(x_k+s_k)|| */ - /* y_k<= x_k+ s_k */ - my_dcopy(M,p,1,yk,1); - my_daxpy(M,sk,1.0,yk); - (*func)(yk, Fyk, M, N, adata); - /* func() - data */ - my_daxpy(N, x, -1.0, Fyk); - Fxksknrm=my_dnrm2(N,Fyk); - Fxksknrm2=Fxksknrm*Fxksknrm; - - /* || Fxk + J_k d_k || */ - /* J d_k : since J is row major, transpose */ - my_dgemv('T',M,N,1.0,Jk,M,dk,1,0.0,Jkdk,1); - /* Fxk <= Fxk+ J_k d_k or, J_k d_k <= Fxk+ J_k d_k */ - my_daxpy(N,Fxk,1.0,Jkdk); - FJkdknrm=my_dnrm2(N,Jkdk); - FJkdknrm=FJkdknrm*FJkdknrm; - - /* find ratio */ - Ak=Fxknrm2-Fxksknrm2; - Pk=Fxknrm2-FJkdknrm+Fyknrm-Fykdhatknrm; - /* if Pk=p0) { - p_update=1; - /* update p<= p+sk */ - my_daxpy(M,sk,1.0,p); - /* also update auxiliary info */ - /* Fxk <= Fyk */ - my_dcopy(N,Fyk,1,Fxk,1); - Fxknrm=Fxksknrm; - /* new Jk needed */ - } else { /* else no p update */ - p_update=0; - /* use previous Jk, Fxk, JkTJk, JkTe */ - } - if (rk0.25*mu) { - mu=m; - } else { - mu=0.25*mu; - } - } - niter++; - } - - free(aones); - if (solve_axb==1) { - free(WORK); - free(TAU); - free(R); - } else if (solve_axb==2) { - free(WORK); - free(Ud); - free(VTd); - free(Sd); - } - free(Jkdk); - free(dk); - free(dhatk); - free(sk); - free(yk); - free(Fxk); - free(Fyk); - free(Jk); - free(JkTJk0); - free(JkTJk); - free(JkTe); - free(JkTe0); - - if(info){ - info[0]=init_Fxknrm; - info[1]=Fxknrm; - } - return 0; -} - - -/** keep interface almost the same as in levmar **/ -/* OS accel */ -int -oslevmar_der_single_nocuda( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - * info[5]= # iterations, - * info[6]=reason for terminating: 1 - stopped by small gradient J^T e - * 2 - stopped by small Dp - * 3 - stopped by itmax - * 4 - singular matrix. Restart from current p with increased mu - * 5 - no further error reduction is possible. Restart with increased mu - * 6 - stopped by small ||e||_2 - * 7 - stopped by invalid (i.e. NaN or Inf) "func" values. This is a user error - * info[7]= # function evaluations - * info[8]= # Jacobian evaluations - * info[9]= # linear systems solved, i.e. # attempts for reducing error - */ - - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int randomize, /* if >0 randomize */ - void *adata) /* pointer to possibly additional data, passed uninterpreted to func & jacf. - * Set to NULL if not needed - */ -{ - - /* general note: all device variables end with a 'd' */ - int stop=0; - int nu=2,nu2; - double p_L2, Dp_L2=DBL_MAX, dF, dL, p_eL2, jacTe_inf=0.0, pDp_eL2, init_p_eL2; - double tmp,mu=0.0; - double tau, eps1, eps2, eps2_sq, eps3; - int k,ci,issolved; - - double *hxd; - double *ed; - double *jac; - - double *jacTjacd,*jacTjacd0; - - double *pnew,*Dpd,*bd; - double *aones; - double *jacTed; - - /* used in QR solver */ - double *WORK; - int lwork=0; - double w[1]; - - int status; - - /* used in SVD solver */ - double *Ud; - double *VTd; - double *Sd; - - int solve_axb=linsolv; - - /* setup default settings */ - if(opts){ - tau=opts[0]; - eps1=opts[1]; - eps2=opts[2]; - eps2_sq=opts[2]*opts[2]; - eps3=opts[3]; - } else { - tau=CLM_INIT_MU; - eps1=CLM_STOP_THRESH; - eps2=CLM_STOP_THRESH; - eps2_sq=CLM_STOP_THRESH*CLM_STOP_THRESH; - eps3=CLM_STOP_THRESH; - } - - if ((hxd=(double*)calloc((size_t)N,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((ed=(double*)calloc((size_t)N,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((jac=(double*)calloc((size_t)N*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((jacTjacd=(double*)calloc((size_t)M*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((jacTjacd0=(double*)calloc((size_t)M*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((jacTed=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((Dpd=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((bd=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((pnew=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((aones=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - WORK=Ud=Sd=VTd=0; -// for (ci=0;ciNt); - - /* memory allocation: different solvers */ - if (solve_axb==0) { - - } else if (solve_axb==1) { - /* workspace query */ - status=my_dgels('N',M,M,1,jacTjacd,M,Dpd,M,w,-1); - if (!status) { - lwork=(int)w[0]; - if ((WORK=(double*)calloc((size_t)lwork,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - } - } else { - if ((Ud=(double*)calloc((size_t)M*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((VTd=(double*)calloc((size_t)M*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((Sd=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - status=my_dgesvd('A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M,w,-1); - if (!status) { - lwork=(int)w[0]; - lwork=(int)w[0]; - if ((WORK=(double*)calloc((size_t)lwork,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - } - } - - - /* ### compute e=x - f(p) and its L2 norm */ - /* ### e=x-hx, p_eL2=||e|| */ - (*func)(p, hxd, M, N, adata); - - - /* e=x */ - my_dcopy(N, x, 1, ed, 1); - /* e=x-hx */ - my_daxpy(N, hxd, -1.0, ed); - - /* norm ||e|| */ - p_eL2=my_dnrm2(N, ed); - /* square */ - p_eL2=p_eL2*p_eL2; - - init_p_eL2=p_eL2; - if(!finite(p_eL2)) stop=7; - - /* setup OS subsets and stating offsets */ - /* ME data for Jacobian calculation (need a new one) */ - me_data_t lmdata; - me_data_t *lmdata0=(me_data_t*)adata; - lmdata.clus=lmdata0->clus; - lmdata.u=lmdata.v=lmdata.w=0; /* not needed */ - lmdata.Nbase=lmdata0->Nbase; - lmdata.tilesz=lmdata0->tilesz; - lmdata.N=lmdata0->N; - lmdata.carr=lmdata0->carr; - lmdata.M=lmdata0->M; - lmdata.Mt=lmdata0->Mt; - lmdata.freq0=lmdata0->freq0; - lmdata.Nt=lmdata0->Nt; - lmdata.barr=lmdata0->barr; - lmdata.coh=lmdata0->coh; - lmdata.tileoff=lmdata0->tileoff; - /* we work with lmdata0->tilesz tiles, and offset from 0 is lmdata0->tileoff, - so, OS needs to divide this many tiles with the right offset per subset */ - /* barr and coh offsets will be calculated internally */ - /* ed : N, cohd : Nbase*8, bbd : Nbase*2 full size */ - /* if ntilestilesztilesz; } - /* FIXME: is 0.1 of subsets enough ? */ - int max_os_iter=(int)ceil(0.1*(double)Nsubsets); - int Npersubset=(N+Nsubsets-1)/Nsubsets; - int Ntpersubset=(lmdata0->tilesz+Nsubsets-1)/Nsubsets; - int *Nos,*edI,*subI=0,*tileI,*tileoff; - if ((Nos=(int*)calloc((size_t)Nsubsets,sizeof(int)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((edI=(int*)calloc((size_t)Nsubsets,sizeof(int)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((tileI=(int*)calloc((size_t)Nsubsets,sizeof(int)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((tileoff=(int*)calloc((size_t)Nsubsets,sizeof(int)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - int l,ositer;; - k=l=0; - for (ci=0; citileoff+l; - if (l+Ntpersubsettilesz) { - Nos[ci]=Npersubset; - tileI[ci]=Ntpersubset; - } else { - Nos[ci]=N-k; - tileI[ci]=lmdata0->tilesz-l; - } - k=k+Npersubset; - l=l+Ntpersubset; - } - -#ifdef DEBUG - for (ci=0; ci= no. of OS iterations, so select - a random set of subsets */ - /* N, Nbase changes with subset, cohd,bbd,ed gets offsets */ - /* ed : N, cohd : Nbase*8, bbd : Nbase*2 full size */ - /* Compute the Jacobian J at p, J^T J, J^T e, ||J^T e||_inf and ||p||^2. - * Since J^T J is symmetric, its computation can be sped up by computing - * only its upper triangular part and copying it to the lower part - */ - /* note: adata has to advance */ - lmdata.tileoff=tileoff[l]; - lmdata.tilesz=tileI[l]; - (*jacf)(p, jac, M, Nos[l], (void*)&lmdata); - /* Compute J^T J and J^T e */ - /* Cache efficient computation of J^T J based on blocking - */ - /* since J is in ROW major order, assume it is transposed, - so actually calculate A=J*J^T, where J is size MxN */ - my_dgemm('N','T',M,M,Nos[l],1.0,jac,M,jac,M,0.0,jacTjacd,M); - - /* create backup */ - /* copy jacTjacd0<=jacTjacd */ - my_dcopy(M*M,jacTjacd,1,jacTjacd0,1); - /* J^T e */ - /* calculate b=J^T*e (actually compute b=J*e, where J in row major (size MxN) */ - my_dgemv('N',M,Nos[l],1.0,jac,M,&ed[edI[l]],1,0.0,jacTed,1); - - - /* Compute ||J^T e||_inf and ||p||^2 */ - /* find infinity norm of J^T e, 1 based indexing*/ - ci=my_idamax(M,jacTed,1); - memcpy(&jacTe_inf,&(jacTed[ci-1]),sizeof(double)); - /* L2 norm of current parameter values */ - /* norm ||Dp|| */ - p_L2=my_dnrm2(M,p); - p_L2=p_L2*p_L2; - if(jacTe_inf<0.0) {jacTe_inf=-jacTe_inf;} -#ifdef DEBUG - printf("Inf norm=%lf\n",jacTe_inf); -#endif - - /* check for convergence */ - if((jacTe_inf <= eps1)){ - Dp_L2=0.0; /* no increment for p in this case */ - stop=1; - break; - } - - /* compute initial (k=0) damping factor */ - if (k==0) { - /* find max diagonal element (stride is M+1) */ - /* should be MAX not MAX(ABS) */ - ci=my_idamax(M,jacTjacd,M+1); - ci=(ci-1)*(M+1); /* right value of the diagonal */ - - memcpy(&tmp,&(jacTjacd[ci]),sizeof(double)); - mu=tau*tmp; - } - - - /* determine increment using adaptive damping */ - while(1){ - /* augment normal equations */ - /* increment A => A+ mu*I, increment diagonal entries */ - /* copy jacTjacd<=jacTjacd0 */ - memcpy(jacTjacd,jacTjacd0,M*M*sizeof(double)); - my_daxpys(M,aones,1,mu,jacTjacd,M+1); - -#ifdef DEBUG - printf("mu=%lf\n",mu); -#endif -/*************************************************************************/ - /* solve augmented equations A x = b */ - /* A==jacTjacd, b==Dpd, after solving, x==Dpd */ - /* b=jacTed : intially right hand side, at exit the solution */ - if (solve_axb==0) { - /* Cholesky solver **********************/ - /* lower triangle of Ad is destroyed */ - status=my_dpotrf('U',M,jacTjacd,M); - if (!status) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - printf("Singular matrix info=%d\n",status); -#endif - } - if (issolved) { - /* copy Dpd<=jacTed */ - memcpy(Dpd,jacTed,M*sizeof(double)); - status=my_dpotrs('U',M,1,jacTjacd,M,Dpd,M); - if (status) { - issolved=0; -#ifdef DEBUG - printf("Singular matrix info=%d\n",status); -#endif - } - } - } else if (solve_axb==1) { - /* QR solver ********************************/ - /* copy Dpd<=jacTed */ - memcpy(Dpd,jacTed,M*sizeof(double)); - status=my_dgels('N',M,M,1,jacTjacd,M,Dpd,M,WORK,lwork); - if (!status) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - printf("Singular matrix info=%d\n",status); -#endif - } - - } else { - /* SVD solver *********************************/ - /* U S VT = A */ - status=my_dgesvd('A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M,WORK,lwork); - /* copy Dpd<=jacTed */ - memcpy(bd,jacTed,M*sizeof(double)); - /* b<=U^T * b */ - my_dgemv('T',M,M,1.0,Ud,M,bd,1,0.0,Dpd,1); - /* robust correction */ - /* divide by singular values Dpd[]/Sd[] for Sd[]> eps1 */ - for (ci=0; cieps1) { - Dpd[ci]=Dpd[ci]/Sd[ci]; - } else { - Dpd[ci]=0.0; - } - } - - /* b<=VT^T * b */ - memcpy(bd,Dpd,M*sizeof(double)); - my_dgemv('T',M,M,1.0,VTd,M,bd,1,0.0,Dpd,1); - - issolved=1; - } -/*************************************************************************/ - - /* compute p's new estimate and ||Dp||^2 */ - if (issolved) { - /* compute p's new estimate and ||Dp||^2 */ - /* pnew=p+Dp */ - /* pnew=p */ - memcpy(pnew,p,M*sizeof(double)); - /* pnew=pnew+Dp */ - my_daxpy(M,Dpd,1.0,pnew); - - /* norm ||Dp|| */ - Dp_L2=my_dnrm2(M,Dpd); - Dp_L2=Dp_L2*Dp_L2; - -#ifdef DEBUG -printf("norm ||dp|| =%lf, norm ||p||=%lf\n",Dp_L2,p_L2); -#endif - if(Dp_L2<=eps2_sq*p_L2){ /* relative change in p is small, stop */ - stop=2; - break; - } - - if(Dp_L2>=(p_L2+eps2)/(CLM_EPSILON*CLM_EPSILON)){ /* almost singular */ - stop=4; - break; - } - - /* new function value */ - (*func)(pnew, hxd, M, N, adata); /* evaluate function at p + Dp */ - - /* compute ||e(pDp)||_2 */ - /* ### hx=x-hx, pDp_eL2=||hx|| */ - memcpy(ed,x,N*sizeof(double)); - /* e=x-hx */ - my_daxpy(N,hxd,-1.0,ed); - /* note: e is updated */ - - /* norm ||e|| */ - pDp_eL2=my_dnrm2(N,ed); - pDp_eL2=pDp_eL2*pDp_eL2; - - - if(!finite(pDp_eL2)){ /* sum of squares is not finite, most probably due to a user error. - */ - stop=7; - break; - } - - /* dL=Dp'*(mu*Dp+jacTe) */ - /* bd=jacTe+mu*Dp */ - memcpy(bd,jacTed,M*sizeof(double)); - my_daxpy(M,Dpd,mu,bd); - dL=my_ddot(M,Dpd,bd); - - dF=p_eL2-pDp_eL2; - -#ifdef DEBUG - printf("dF=%lf, dL=%lf\n",dF,dL); -#endif - if(dL>0.0 && dF>0.0){ /* reduction in error, increment is accepted */ - tmp=(2.0*dF/dL-1.0); - tmp=1.0-tmp*tmp*tmp; - mu=mu*((tmp>=CLM_ONE_THIRD)? tmp : CLM_ONE_THIRD); - nu=2; - - /* update p's estimate */ - memcpy(p,pnew,M*sizeof(double)); - - /* update ||e||_2 */ - p_eL2=pDp_eL2; - break; - } - - } - /* if this point is reached, either the linear system could not be solved or - * the error did not reduce; in any case, the increment must be rejected - */ - - mu*=(double)nu; - nu2=nu<<1; // 2*nu; - if(nu2<=nu){ /* nu has wrapped around (overflown). */ - stop=5; - break; - } - - nu=nu2; - - } /* inner loop */ - - } - if (randomize) { - free(subI); - } -/**************** end OS loop ***************************/ - - } - /**** end iteration loop ***********/ - free(Nos); - free(edI); - free(tileI); - free(tileoff); - - if(k>=itmax) stop=3; - - - free(jac); - free(jacTjacd); - free(jacTjacd0); - free(jacTed); - free(Dpd); - free(bd); - free(hxd); - free(ed); - free(aones); - free(pnew); - - if (solve_axb==0) { - } else if (solve_axb==1) { - free(WORK); - } else { - free(Ud); - free(VTd); - free(Sd); - free(WORK); - } -#ifdef DEBUG - printf("stop=%d\n",stop); -#endif - if(info){ - info[0]=init_p_eL2; - info[1]=p_eL2; - info[2]=jacTe_inf; - info[3]=Dp_L2; - info[4]=mu; - info[5]=(double)k; - info[6]=(double)stop; - info[7]=(double)0; - info[8]=(double)0; - info[9]=(double)0; - } - return 0; -} diff --git a/src/lib/consensus_poly.c b/src/lib/consensus_poly.c deleted file mode 100644 index f907f26..0000000 --- a/src/lib/consensus_poly.c +++ /dev/null @@ -1,349 +0,0 @@ -/* - * - Copyright (C) 2014 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - -#include "sagecal.h" -#include -#include - -//#define DEBUG -/* build matrix with polynomial terms - B : Npoly x Nf, each row is one basis function - Npoly : total basis functions - Nf: frequencies - freqs: Nfx1 array freqs - freq0: reference freq - type : - 0 :[1 ((f-fo)/fo) ((f-fo)/fo)^2 ...] basis functions - 1 : normalize each row such that norm is 1 - 2 : Bernstein poly \sum N_C_r x^r (1-x)^r where x in [0,1] : use min,max values of freq to normalize - Note: freqs might not be in sorted order, so need to search array to find min,max values - 3: [1 ((f-fo)/fo) (fo/f-1) ((f-fo)/fo)^2 (fo/f-1)^2 ... ] basis, for this case odd Npoly preferred -*/ -int -setup_polynomials(double *B, int Npoly, int Nf, double *freqs, double freq0, int type) { - - if (type==0 || type==1) { - double frat,dsum; - double invf=1.0/freq0; - int ci,cm; - for (ci=0; ci0.0) { - invf=1.0/sqrt(dsum); - } else { - invf=0.0; - } - for (ci=0; ciCLM_EPSILON) { - S[ci]=1.0/S[ci]; - } else { - S[ci]=0.0; - } - my_dscal(Npoly,S[ci],&U[ci*Npoly]); - } - - /* find product U 1/S V^T */ - my_dgemm('N','N',Npoly,Npoly,Npoly,1.0,U,Npoly,VT,Npoly,0.0,Bi,Npoly); - -#ifdef DEBUG - printf("Bii=[\n"); - for (ci=0; ci Q - Bi : NpolyxNpoly matrix = B^T - - for each direction (M values) - select 2N,2N,... : 2Nx Npoly complex values from z (ordered by M) - select real,imag: size 2NxNpoly, 2NxNpoly vectors - reshape to 2NxNpoly => R - reshape to 2NxNpoly => I (imag) - - then Q=([R I] Bi^T) for each column - Q=[R_1^T I_1^T R_2^T I_2^T]^T Bi^T for 2 columns - R_1,I_1,R_2,I_2 : size 2NxNpoly - R : (2N 4) x Npoly - so find Q - */ - double *R,*Q; - if ((R=(double*)calloc((size_t)2*N*Npoly*4,sizeof(double)))==0) { - printf("%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((Q=(double*)calloc((size_t)2*N*Npoly*4,sizeof(double)))==0) { - printf("%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - - int ci,np; - for (ci=0; ci - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#include -#include -#include -#include -#include -#include -#include - -#include "sagecal.h" - -int -open_data_stream(int file, double **d, int *count, int *N, double *freq0, double *ra0, double *dec0) { - struct stat statbuf; - - int ig; - - - /* find the file size */ - if (fstat (file,&statbuf) < 0) { - fprintf(stderr,"%s: %d: no file open\n",__FILE__,__LINE__); - exit(1); - } - - //printf("file size (bytes) %d\n",(int)statbuf.st_size); - /* total double values is size/8 */ - *count=statbuf.st_size/8; - //printf("total double values %d\n",*count); - - /* map the file to memory */ - *d= (double*)mmap(NULL, statbuf.st_size, PROT_READ|PROT_WRITE, MAP_SHARED, file, 0); - if ( !d) { - fprintf(stderr,"%s: %d: no file open\n",__FILE__,__LINE__); - exit(1); - } - - /* remove header from data */ - *N=(int)(*d)[0]; - *freq0=(*d)[1]; - *ra0=(*d)[2]; - *dec0=(*d)[3]; - /* read ignored stations and discard them */ - ig=(int)(*d)[4]; - /* make correct value for N */ - *N=*N-ig; - - printf("Ignoring %d stations\n",ig); - /* increment to data */ - *d=&((*d)[5+ig]); - - return(0); -} - - -int -close_data_stream(double *d, int count) { - - /* sync to disk */ - msync(d, (size_t)count*sizeof(double), MS_SYNC ); - munmap((void*)d, (size_t)count*sizeof(double)); - return 0; -} - diff --git a/src/lib/diag_fl.cu b/src/lib/diag_fl.cu deleted file mode 100644 index a222a4e..0000000 --- a/src/lib/diag_fl.cu +++ /dev/null @@ -1,270 +0,0 @@ -/* - * - Copyright (C) 2006-2008 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - -#include "cuda.h" -#include -#include - -/* enable this for checking for kernel failure */ -#define CUDA_DBG - -__global__ void -kernel_sqrtdiv_fl(int M, float eps, float *__restrict__ x){ - unsigned int tid = blockIdx.x*blockDim.x + threadIdx.x; - /* make sure to use only M threads */ - if (tideps) { - x[tid]=1.0f/sqrtf(x[tid]); - } else { - x[tid]=0.0f; - } - } -} - -__global__ void -kernel_diagmult_fl(int M, float *__restrict__ U, const float *__restrict__ D) { - - unsigned int tid = blockIdx.x*blockDim.x + threadIdx.x; - /* which column this tid operates on */ - unsigned int col = tid/M; - if (tid>3; /* 0...Ns-1 (because M=total par= 8 * Nstations */ - /* flags are not taken into account */ - if (((stc==sta2)||(stc==sta1))) { - - cuFloatComplex C[4]; - C[0].x=coh[8*n]; - C[0].y=coh[8*n+1]; - C[1].x=coh[8*n+2]; - C[1].y=coh[8*n+3]; - C[2].x=coh[8*n+4]; - C[2].y=coh[8*n+5]; - C[3].x=coh[8*n+6]; - C[3].y=coh[8*n+7]; - - /* which parameter exactly 0..7 */ - int stoff=m-stc*8; - float pp1[8]; - float pp2[8]; - if (stc==sta1) { - for (int cn=0; cn<8; cn++) { - pp1[cn]=0.0f; - pp2[cn]=p[sta2*8+cn]; - } - pp1[stoff]=1.0f; - } else if (stc==sta2) { - for (int cn=0; cn<8; cn++) { - pp2[cn]=0.0f; - pp1[cn]=p[sta1*8+cn]; - } - pp2[stoff]=1.0f; - } - - - cuFloatComplex G1[4]; - G1[0].x=pp1[0]; - G1[0].y=pp1[1]; - G1[1].x=pp1[2]; - G1[1].y=pp1[3]; - G1[2].x=pp1[4]; - G1[2].y=pp1[5]; - G1[3].x=pp1[6]; - G1[3].y=pp1[7]; - - cuFloatComplex T1[4]; - /* T=G1*C */ - T1[0]=cuCaddf(cuCmulf(G1[0],C[0]),cuCmulf(G1[1],C[2])); - T1[1]=cuCaddf(cuCmulf(G1[0],C[1]),cuCmulf(G1[1],C[3])); - T1[2]=cuCaddf(cuCmulf(G1[2],C[0]),cuCmulf(G1[3],C[2])); - T1[3]=cuCaddf(cuCmulf(G1[2],C[1]),cuCmulf(G1[3],C[3])); - - cuFloatComplex G2[4]; - /* conjugate this */ - G2[0].x=pp2[0]; - G2[0].y=-pp2[1]; - G2[2].x=pp2[2]; - G2[2].y=-pp2[3]; - G2[1].x=pp2[4]; - G2[1].y=-pp2[5]; - G2[3].x=pp2[6]; - G2[3].y=-pp2[7]; - - cuFloatComplex T2[4]; - T2[0]=cuCaddf(cuCmulf(T1[0],G2[0]),cuCmulf(T1[1],G2[2])); - T2[1]=cuCaddf(cuCmulf(T1[0],G2[1]),cuCmulf(T1[1],G2[3])); - T2[2]=cuCaddf(cuCmulf(T1[2],G2[0]),cuCmulf(T1[3],G2[2])); - T2[3]=cuCaddf(cuCmulf(T1[2],G2[1]),cuCmulf(T1[3],G2[3])); - /* update jacobian */ - /* NOTE: row major order */ - jac[m+M*8*n]=T2[0].x; - jac[m+M*(8*n+1)]=T2[0].y; - jac[m+M*(8*n+2)]=T2[1].x; - jac[m+M*(8*n+3)]=T2[1].y; - jac[m+M*(8*n+4)]=T2[2].x; - jac[m+M*(8*n+5)]=T2[2].y; - jac[m+M*(8*n+6)]=T2[3].x; - jac[m+M*(8*n+7)]=T2[3].y; - - } - } - -} - - -/* only use extern if calling code is C */ -extern "C" -{ - - -/* cuda driver for calculating jacf() */ -/* p: params (Mx1), jac: jacobian (NxM), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -void -cudakernel_jacf_fl2(float *p, float *jac, int M, int N, float *coh, short *bbh, int Nbase, int Mclus, int Nstations) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - /* NOTE: use small value for ThreadsPerBlock here, like 8 */ - dim3 threadsPerBlock(16, 8); - /* jacobian: Nbase x Nstations (proportional to N), so */ - dim3 numBlocks((Nbase+threadsPerBlock.x-1)/threadsPerBlock.x, - (M+threadsPerBlock.y-1)/threadsPerBlock.y); - /* set memory of jac to zero */ - cudaMemset(jac, 0, N*M*sizeof(float)); - // printf("Kernel Jax data size=%d, params=%d, block=%d,%d, thread=%d,%d, baselines=%d\n",N, M, numBlocks.x,numBlocks.y, threadsPerBlock.x, threadsPerBlock.y, Nbase); - kernel_jacf_fl2<<< numBlocks, threadsPerBlock>>>(Nbase, M, jac, coh, p, bbh, Nstations); - - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - -} - -/* invert sqrt(singular values) 1/Sd[] for Sd[]> eps */ -void -cudakernel_sqrtdiv_fl(int ThreadsPerBlock, int BlocksPerGrid, int M, float eps, float *Sd) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - kernel_sqrtdiv_fl<<< BlocksPerGrid, ThreadsPerBlock >>>(M, eps, Sd); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - -} - - -/* U <= U D, - U : MxM - D : Mx1, diagonal matrix -*/ -void -cudakernel_diagmult_fl(int ThreadsPerBlock, int BlocksPerGrid, int M, float *U, float *D) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - kernel_diagmult_fl<<< BlocksPerGrid, ThreadsPerBlock >>>(M, U, D); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - -} - - -/* diag(J^T J) - d[i] = J[i,:] * J[i,:] - J: NxM (in row major order, so J[i,:] is actually J[:,i] - d: Nx1 -*/ -void -cudakernel_jnorm_fl(int ThreadsPerBlock, int BlocksPerGrid, float *J, int N, int M, float *d) { -#ifdef CUDA_DBG - cudaError_t error; -#endif - kernel_jnorm_fl<<< BlocksPerGrid, ThreadsPerBlock >>>(N,M,J,d); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif -} - -} diff --git a/src/lib/diagnostics.c b/src/lib/diagnostics.c deleted file mode 100644 index 07d9a29..0000000 --- a/src/lib/diagnostics.c +++ /dev/null @@ -1,550 +0,0 @@ -/* - * - Copyright (C) 2014 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - -#include "sagecal.h" -#include -#include -#include -#include -#include - - -static void -checkCudaError(cudaError_t err, const char *file, int line) -{ -#ifdef CUDA_DEBUG - if(!err) - return; - fprintf(stderr,"GPU (CUDA): %s %s %d\n", cudaGetErrorString(err),file,line); - exit(EXIT_FAILURE); -#endif -} - -static void -checkCublasError(cublasStatus_t cbstatus, char *file, int line) -{ -#ifdef CUDA_DEBUG - if (cbstatus!=CUBLAS_STATUS_SUCCESS) { - fprintf(stderr,"%s: %d: CUBLAS failure\n",file,line); - exit(EXIT_FAILURE); - } -#endif -} - - -/* find for one cluster J (J^T W J+ eW)^-1 J^T and extract diagonal as output - p: parameters M x 1 - rd: residual vector N x 1 (on the device, invarient) - x: (output) diagonal of leverage matrix - - cbhandle,gWORK: BLAS/storage pointers - - tileoff: need for hybrid parameters - - adata: has all additional info: coherency,baselines,flags -*/ -static int -calculate_leverage(float *p, float *rd, float *x, int M, int N, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle, float *gWORK, int tileoff, int ntiles, me_data_t *dp) { - - /* p needs to be copied to device and x needs to be copied back from device - rd always remains in the device (select part with the right offset) - N will change in hybrid mode, so copy back to x with right offset */ - - int Nbase=(dp->Nbase)*(ntiles); /* note: we do not use the total tile size */ - float *jacd,*xd,*jacTjacd,*pd,*cohd,*Ud,*VTd,*Sd; - unsigned long int moff=0; - short *bbd; - - cudaError_t err; - - /* total storage N+M*N+M*M+M+Nbase*8+M*M+M*M+M+M+Nbase*3(short)/(float) */ - xd=&gWORK[moff]; - moff+=N; - jacd=&gWORK[moff]; - moff+=M*N; - jacTjacd=&gWORK[moff]; - moff+=M*M; - pd=&gWORK[moff]; - moff+=M; - cohd=&gWORK[moff]; - moff+=Nbase*8; - Ud=&gWORK[moff]; - moff+=M*M; - VTd=&gWORK[moff]; - moff+=M*M; - Sd=&gWORK[moff]; - moff+=M; - - bbd=(short*)&gWORK[moff]; - moff+=(Nbase*3*sizeof(short))/sizeof(float); - - err=cudaMemcpyAsync(pd, p, M*sizeof(float), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - /* need to give right offset for coherencies */ - /* offset: cluster offset+time offset */ - err=cudaMemcpyAsync(cohd, &(dp->ddcohf[(dp->Nbase)*(dp->tilesz)*(dp->clus)*8+(dp->Nbase)*tileoff*8]), Nbase*8*sizeof(float), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - /* correct offset for baselines */ - err=cudaMemcpyAsync(bbd, &(dp->ddbase[3*(dp->Nbase)*(tileoff)]), Nbase*3*sizeof(short), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - cudaDeviceSynchronize(); - - int ThreadsPerBlock=DEFAULT_TH_PER_BK; - int ci,Mi; - - /* extra storage for cusolver */ - int work_size=0; - int *devInfo; - err=cudaMalloc((void**)&devInfo, sizeof(int)); - checkCudaError(err,__FILE__,__LINE__); - float *work; - float *rwork; - cusolverDnSgesvd_bufferSize(solver_handle, M, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(float)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&rwork, 5*M*sizeof(float)); - checkCudaError(err,__FILE__,__LINE__); - - - /* set mem to 0 */ - cudaMemset(xd, 0, N*sizeof(float)); - - /* calculate J^T, not taking flags into account */ - cudakernel_jacf_fl2(pd, jacd, M, N, cohd, bbd, Nbase, dp->M, dp->N); - - /* calculate JTJ=(J^T J - [e] [W]) */ - //status=culaDeviceSgemm('N','T',M,M,N,1.0f,jacd,M,jacd,M,0.0f,jacTjacd,M); - //checkStatus(status,__FILE__,__LINE__); - cublasStatus_t cbstatus=CUBLAS_STATUS_SUCCESS; - float cone=1.0f; float czero=0.0f; - cbstatus=cublasSgemm(cbhandle,CUBLAS_OP_N,CUBLAS_OP_T,M,M,N,&cone,jacd,M,jacd,M,&czero,jacTjacd,M); - - - /* add mu * I to JTJ */ - cudakernel_diagmu_fl(ThreadsPerBlock, (M+ThreadsPerBlock-1)/ThreadsPerBlock, M, jacTjacd, 1e-9f); - - /* calculate inv(JTJ) using SVD */ - /* inv(JTJ) = Ud x Sid x VTd : we take into account that JTJ is symmetric */ - //status=culaDeviceSgesvd('A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M); - //checkStatus(status,__FILE__,__LINE__); - cusolverDnSgesvd(solver_handle,'A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M,work,work_size,rwork,devInfo); - cudaDeviceSynchronize(); - - - /* find Sd= 1/sqrt(Sd) of the singular values (positive singular values) */ - cudakernel_sqrtdiv_fl(ThreadsPerBlock, (M+ThreadsPerBlock-1)/ThreadsPerBlock, M, 1e-9f, Sd); - - /* multiply Ud with Sid (diagonal) Ud <= Ud Sid (columns modified) */ - cudakernel_diagmult_fl(ThreadsPerBlock, (M*M+ThreadsPerBlock-1)/ThreadsPerBlock, M, Ud, Sd); - /* now multiply Ud VTd to get the square root */ - //status=culaDeviceSgemm('N','N',M,M,M,1.0f,Ud,M,VTd,M,0.0f,jacTjacd,M); - //checkStatus(status,__FILE__,__LINE__); - cbstatus=cublasSgemm(cbhandle,CUBLAS_OP_N,CUBLAS_OP_N,M,M,M,&cone,Ud,M,VTd,M,&czero,jacTjacd,M); - - /* calculate J^T, without taking flags into account (use same storage as previous J^T) */ - cudakernel_jacf_fl2(pd, jacd, M, N, cohd, bbd, Nbase, dp->M, dp->N); - - /* multiply (J^T)^T sqrt(B) == sqrt(B)^T J^T, taking M columns at a time */ - for (ci=0; ci<(N+M-1)/M;ci++) { - if (ci*M+Mpline->data); - int tid=td->tid; - - while(1) { - sync_barrier(&(td->pline->gate1)); /* stop at gate 1*/ - if(td->pline->terminate) break; /* if flag is set, break loop */ - sync_barrier(&(td->pline->gate2)); /* stop at gate 2 */ - /* do work */ - if (gd->status[tid]==PT_DO_CDERIV) { - me_data_t *t=(me_data_t *)gd->lmdata[tid]; - /* divide the tiles into chunks tilesz/nchunk */ - int tilechunk=(t->tilesz+t->carr[t->clus].nchunk-1)/t->carr[t->clus].nchunk; - - int ci; - int cj=0; - int ntiles; - - /* loop over chunk, righ set of parameters and residual vector */ - for (ci=0; cicarr[t->clus].nchunk; ci++) { - /* divide the tiles into chunks tilesz/nchunk */ - if (cj+tilechunktilesz) { - ntiles=tilechunk; - } else { - ntiles=t->tilesz-cj; - } - - /* right offset for rd[] and x[] needed and since no overlap, - can wait for all chunks to complete */ - calculate_leverage(&gd->p[tid][ci*(gd->M[tid])],&gd->rd[tid][8*cj*t->Nbase],&gd->x[tid][8*cj*t->Nbase], gd->M[tid], 8*ntiles*t->Nbase, gd->cbhandle[tid], gd->solver_handle[tid], gd->gWORK[tid], cj, ntiles, gd->lmdata[tid]); - - cj=cj+tilechunk; - } - - } else if (gd->status[tid]==PT_DO_AGPU) { - attach_gpu_to_thread2(tid,&gd->cbhandle[tid],&gd->solver_handle[tid],&gd->gWORK[tid],gd->data_size,1); - - /* copy residual vector to device */ - cudaError_t err; - me_data_t *t=(me_data_t *)gd->lmdata[tid]; - err=cudaMalloc((void**)&gd->rd[tid], (size_t)8*t->tilesz*t->Nbase*sizeof(float)); - checkCudaError(err,__FILE__,__LINE__); - - err=cudaMemcpy(gd->rd[tid], gd->xo, 8*t->tilesz*t->Nbase*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - } else if (gd->status[tid]==PT_DO_DGPU) { - cudaFree(gd->rd[tid]); - detach_gpu_from_thread2(gd->cbhandle[tid],gd->solver_handle[tid],gd->gWORK[tid],1); - } else if (gd->status[tid]!=PT_DO_NOTHING) { /* catch error */ - fprintf(stderr,"%s: %d: invalid mode for slave tid=%d status=%d\n",__FILE__,__LINE__,tid,gd->status[tid]); - exit(1); - } - } - return NULL; -} - -/* initialize the pipeline - and start the slaves rolling */ -static void -init_pipeline_dg(th_pipeline *pline, - void *data) -{ - slave_tdata *t0,*t1; - pthread_attr_init(&(pline->attr)); - pthread_attr_setdetachstate(&(pline->attr),PTHREAD_CREATE_JOINABLE); - - init_th_barrier(&(pline->gate1),3); /* 3 threads, including master */ - init_th_barrier(&(pline->gate2),3); /* 3 threads, including master */ - pline->terminate=0; - pline->data=data; /* data should have pointers to t1 and t2 */ - - if ((t0=(slave_tdata*)malloc(sizeof(slave_tdata)))==0) { - fprintf(stderr,"no free memory\n"); - exit(1); - } - if ((t1=(slave_tdata*)malloc(sizeof(slave_tdata)))==0) { - fprintf(stderr,"no free memory\n"); - exit(1); - } - if ((pline->thst=(taskhist*)malloc(sizeof(taskhist)))==0) { - fprintf(stderr,"no free memory\n"); - exit(1); - } - - init_task_hist(pline->thst); - t0->pline=t1->pline=pline; - t0->tid=0; - t1->tid=1; /* link back t1, t2 to data so they could be freed */ - pline->sd0=t0; - pline->sd1=t1; - pthread_create(&(pline->slave0),&(pline->attr),pipeline_slave_code_dg,(void*)t0); - pthread_create(&(pline->slave1),&(pline->attr),pipeline_slave_code_dg,(void*)t1); -} - - - -/* destroy the pipeline */ -/* need to kill the slaves first */ -static void -destroy_pipeline_dg(th_pipeline *pline) -{ - - pline->terminate=1; - sync_barrier(&(pline->gate1)); - pthread_join(pline->slave0,NULL); - pthread_join(pline->slave1,NULL); - destroy_th_barrier(&(pline->gate1)); - destroy_th_barrier(&(pline->gate2)); - pthread_attr_destroy(&(pline->attr)); - destroy_task_hist(pline->thst); - free(pline->thst); - free(pline->sd0); - free(pline->sd1); - pline->data=NULL; -} -/******************** end pipeline functions **************************/ - - - -/* Calculate St.Laurent-Cook Jacobian leverage - xo: residual (modified) - flags: 2 for flags based on uvcut, 1 for normal flags - coh: coherencies are calculated for all baselines, regardless of flag - diagmode: 1: replace residual, 2: calc noise/leverage ratio - */ -int -calculate_diagnostics(double *u,double *v,double *w,double *p,double *xo,int N,int Nbase,int tilesz,baseline_t *barr, clus_source_t *carr, complex double *coh, int M,int Mt,int diagmode, int Nt) { - - - int cj; - int n; - me_data_t lmdata0,lmdata1; - int Nbase1; - - /* no of data */ - n=Nbase*tilesz*8; - - /* true no of baselines */ - Nbase1=Nbase*tilesz; - - double *ddcoh; - short *ddbase; - - int c0,c1; - - float *ddcohf, *pf, *xdummy0f, *xdummy1f, *res0, *dgf; -/********* thread data ******************/ - /* barrier */ - th_pipeline tp; - gbdatadg tpg; -/****************************************/ - - lmdata0.clus=lmdata1.clus=-1; - /* setup data for lmfit */ - lmdata0.u=lmdata1.u=u; - lmdata0.v=lmdata1.v=v; - lmdata0.w=lmdata1.w=w; - lmdata0.Nbase=lmdata1.Nbase=Nbase; - lmdata0.tilesz=lmdata1.tilesz=tilesz; - lmdata0.N=lmdata1.N=N; - lmdata0.barr=lmdata1.barr=barr; - lmdata0.carr=lmdata1.carr=carr; - lmdata0.M=lmdata1.M=M; - lmdata0.Mt=lmdata1.Mt=Mt; - lmdata0.freq0=lmdata1.freq0=NULL; /* not used */ - lmdata0.Nt=lmdata1.Nt=Nt; - lmdata0.coh=lmdata1.coh=coh; - /* rearrange coh for GPU use */ - if ((ddcoh=(double*)calloc((size_t)(M*Nbase1*8),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((ddcohf=(float*)calloc((size_t)(M*Nbase1*8),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((ddbase=(short*)calloc((size_t)(Nbase1*3),sizeof(short)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - rearrange_coherencies2(Nbase1, barr, coh, ddcoh, ddbase, M, Nt); - lmdata0.ddcoh=lmdata1.ddcoh=ddcoh; - lmdata0.ddbase=lmdata1.ddbase=ddbase; - /* ddcohf (float) << ddcoh (double) */ - double_to_float(ddcohf,ddcoh,M*Nbase1*8,Nt); - lmdata0.ddcohf=lmdata1.ddcohf=ddcohf; - - if ((pf=(float*)calloc((size_t)(Mt*8*N),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - double_to_float(pf,p,Mt*8*N,Nt); - /* residual */ - if ((res0=(float*)calloc((size_t)(n),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - double_to_float(res0,xo,n,Nt); - - /* sum of diagonal values of leverage */ - if ((dgf=(float*)calloc((size_t)(n),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xdummy0f=(float*)calloc((size_t)(n),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xdummy1f=(float*)calloc((size_t)(n),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } -/********** setup threads *******************************/ - /* also calculate the total storage needed to be allocated on a GPU */ - /* determine total size for memory allocation - residual = n (separately allocated) - diagonal = n - For one cluster, - Jacobian = nxm, J^T J = mxm, (also inverse) - */ - int Mm=8*N; /* no of parameters */ - int64_t data_sz=0; - data_sz=(int64_t)(n+Mm*n+3*Mm*Mm+3*Mm+Nbase1*8)*sizeof(float)+(int64_t)Nbase1*3*sizeof(short); - tpg.data_size=data_sz; - tpg.lmdata[0]=&lmdata0; - tpg.lmdata[1]=&lmdata1; - tpg.xo=res0; /* residual */ - - init_pipeline_dg(&tp,&tpg); - sync_barrier(&(tp.gate1)); /* sync at gate 1*/ - tpg.status[0]=tpg.status[1]=PT_DO_AGPU; - sync_barrier(&(tp.gate2)); /* sync at gate 2*/ - - sync_barrier(&(tp.gate1)); /* sync at gate 1*/ - tpg.status[0]=tpg.status[1]=PT_DO_NOTHING; - sync_barrier(&(tp.gate2)); /* sync at gate 2*/ - -/********** done setup threads *******************************/ - tpg.x[0]=xdummy0f; - tpg.M[0]=8*N; /* even though size of p is > M, dont change this */ - tpg.N[0]=n; /* Nbase*tilesz*8 */ - tpg.x[1]=xdummy1f; - tpg.M[1]=8*N; /* even though size of p is > M, dont change this */ - tpg.N[1]=n; /* Nbase*tilesz*8 */ - - for (cj=0; cj1e-6f) { /* can be solved */ - alpha=(r00*a11-r01*a01)/denom; - } else { - alpha=0.0f; - } - beta=(r00-a00*alpha)/a01; - printf("Error Noise/Model %e/%e\n",beta,alpha); - } - free(dgf); - return 0; -} diff --git a/src/lib/lbfgs.c b/src/lib/lbfgs.c deleted file mode 100644 index ec79fed..0000000 --- a/src/lib/lbfgs.c +++ /dev/null @@ -1,1106 +0,0 @@ -/* - * - Copyright (C) 2006-2008 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#include "sagecal.h" -#include -#include -#include -#include -#include - - -static void -checkCudaError(cudaError_t err, char *file, int line) -{ -#ifdef CUDA_DEBUG - if(!err) - return; - fprintf(stderr,"GPU (CUDA): %s %s %d\n", cudaGetErrorString(err),file,line); - exit(EXIT_FAILURE); -#endif -} -/************************ pipeline **************************/ -/* data struct shared by all threads */ -typedef struct gb_data_b_ { - int status[2]; /* 0: do nothing, - 1: allocate GPU memory, attach GPU - 2: free GPU memory, detach GPU - 3,4..: do work on GPU - 99: reset GPU memory (memest all memory) */ - thread_gpu_data *lmdata[2]; /* two for each thread */ - - /* GPU related info */ - cublasHandle_t cbhandle[2]; /* CUBLAS handles */ - cusolverDnHandle_t solver_handle[2]; /* solver handles */ - double *gWORK[2]; /* GPU buffers */ - int64_t data_size[2]; /* size of buffer (bytes), size gradient vector has different lengths, will be different for each thread */ - /* different pointers to GPU data */ - double *cxo[2]; /* data vector */ - double *ccoh[2]; /* coherency vector */ - double *cpp[2]; /* parameter vector */ - double *cgrad[2]; /* gradient vector */ - short *cbb[2]; /* baseline map */ - int *cptoclus[2]; /* param to cluster map */ - - /* for cost calculation */ - int Nbase[2]; - int boff[2]; - double fcost[2]; - - /* for robust LBFGS */ - int do_robust; -} gbdata_b; - -/* slave thread 2GPU function */ -static void * -pipeline_slave_code_b(void *data) -{ - cudaError_t err; - - slave_tdata *td=(slave_tdata*)data; - gbdata_b *dp=(gbdata_b*)(td->pline->data); - int tid=td->tid; - int Nbase=(dp->lmdata[tid]->Nbase)*(dp->lmdata[tid]->tilesz); - int M=dp->lmdata[tid]->M; - int N=dp->lmdata[tid]->N; - int Nparam=(dp->lmdata[tid]->g_end-dp->lmdata[tid]->g_start+1); - int m=dp->lmdata[tid]->m; - - while(1) { - sync_barrier(&(td->pline->gate1)); /* stop at gate 1*/ - if(td->pline->terminate) break; /* if flag is set, break loop */ - sync_barrier(&(td->pline->gate2)); /* stop at gate 2 */ - /* do work */ - if (dp->status[tid]==PT_DO_CDERIV) { - /* copy the current solution to device */ - err=cudaMemcpy(dp->cpp[tid], dp->lmdata[tid]->p, m*sizeof(double), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - if (!dp->do_robust) { - cudakernel_lbfgs_r(dp->lmdata[tid]->ThreadsPerBlock, dp->lmdata[tid]->BlocksPerGrid, Nbase, dp->lmdata[tid]->tilesz, M, N, Nparam, dp->lmdata[tid]->g_start, dp->cxo[tid], dp->ccoh[tid], dp->cpp[tid], dp->cbb[tid], dp->cptoclus[tid], dp->cgrad[tid]); - } else { - cudakernel_lbfgs_r_robust(dp->lmdata[tid]->ThreadsPerBlock, dp->lmdata[tid]->BlocksPerGrid, Nbase, dp->lmdata[tid]->tilesz, M, N, Nparam, dp->lmdata[tid]->g_start, dp->cxo[tid], dp->ccoh[tid], dp->cpp[tid], dp->cbb[tid], dp->cptoclus[tid], dp->cgrad[tid],dp->lmdata[tid]->robust_nu); - } - /* read back the result */ - err=cudaMemcpy(&(dp->lmdata[tid]->g[dp->lmdata[tid]->g_start]), dp->cgrad[tid], Nparam*sizeof(double), cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - } else if (dp->status[tid]==PT_DO_CCOST) { - /* divide total baselines by 2 */ - int BlocksPerGrid=(dp->Nbase[tid]+dp->lmdata[tid]->ThreadsPerBlock-1)/dp->lmdata[tid]->ThreadsPerBlock; - int boff=dp->boff[tid]; - /* copy the current solution to device */ - err=cudaMemcpy(dp->cpp[tid], dp->lmdata[tid]->p, m*sizeof(double), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - if (!dp->do_robust) { - dp->fcost[tid]=cudakernel_lbfgs_cost(dp->lmdata[tid]->ThreadsPerBlock, BlocksPerGrid, dp->Nbase[tid], boff, M, N, Nbase, &dp->cxo[tid][8*boff], &dp->ccoh[tid][boff*8*M], dp->cpp[tid], &dp->cbb[tid][boff*2], dp->cptoclus[tid]); - } else { - dp->fcost[tid]=cudakernel_lbfgs_cost_robust(dp->lmdata[tid]->ThreadsPerBlock, BlocksPerGrid, dp->Nbase[tid], boff, M, N, Nbase, &dp->cxo[tid][8*boff], &dp->ccoh[tid][boff*8*M], dp->cpp[tid], &dp->cbb[tid][boff*2], dp->cptoclus[tid], dp->lmdata[tid]->robust_nu); - } - } else if (dp->status[tid]==PT_DO_AGPU) { - attach_gpu_to_thread1(select_work_gpu(MAX_GPU_ID,td->pline->thst),&dp->cbhandle[tid],&dp->solver_handle[tid],&dp->gWORK[tid],dp->data_size[tid]); - err=cudaMalloc((void**)&(dp->cxo[tid]),dp->lmdata[tid]->n*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&(dp->ccoh[tid]),Nbase*8*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&(dp->cpp[tid]),m*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&(dp->cgrad[tid]),Nparam*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&(dp->cptoclus[tid]),M*2*sizeof(int)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&(dp->cbb[tid]),Nbase*2*sizeof(short)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMemcpy(dp->cxo[tid], dp->lmdata[tid]->xo, dp->lmdata[tid]->n*sizeof(double), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMemcpy(dp->ccoh[tid], dp->lmdata[tid]->coh, Nbase*8*M*sizeof(double), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMemcpy(dp->cptoclus[tid], dp->lmdata[tid]->ptoclus, M*2*sizeof(int), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMemcpy(dp->cbb[tid], dp->lmdata[tid]->hbb, Nbase*2*sizeof(short), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - - } else if (dp->status[tid]==PT_DO_DGPU) { - cudaFree(dp->cxo[tid]); - cudaFree(dp->ccoh[tid]); - cudaFree(dp->cptoclus[tid]); - cudaFree(dp->cbb[tid]); - cudaFree(dp->cpp[tid]); - cudaFree(dp->cgrad[tid]); - - detach_gpu_from_thread1(dp->cbhandle[tid],dp->solver_handle[tid],dp->gWORK[tid]); - } - - } - return NULL; -} - -/* initialize the pipeline - and start the slaves rolling */ -static void -init_pipeline_b(th_pipeline *pline, - void *data) -{ - slave_tdata *t0,*t1; - pthread_attr_init(&(pline->attr)); - pthread_attr_setdetachstate(&(pline->attr),PTHREAD_CREATE_JOINABLE); - - init_th_barrier(&(pline->gate1),3); /* 3 threads, including master */ - init_th_barrier(&(pline->gate2),3); /* 3 threads, including master */ - pline->terminate=0; - pline->data=data; /* data should have pointers to t1 and t2 */ - - if ((t0=(slave_tdata*)malloc(sizeof(slave_tdata)))==0) { - fprintf(stderr,"no free memory\n"); - exit(1); - } - if ((t1=(slave_tdata*)malloc(sizeof(slave_tdata)))==0) { - fprintf(stderr,"no free memory\n"); - exit(1); - } - if ((pline->thst=(taskhist*)malloc(sizeof(taskhist)))==0) { - fprintf(stderr,"no free memory\n"); - exit(1); - } - - init_task_hist(pline->thst); - t0->pline=t1->pline=pline; - t0->tid=0; - t1->tid=1; /* link back t1, t2 to data so they could be freed */ - pline->sd0=t0; - pline->sd1=t1; - pthread_create(&(pline->slave0),&(pline->attr),pipeline_slave_code_b,(void*)t0); - pthread_create(&(pline->slave1),&(pline->attr),pipeline_slave_code_b,(void*)t1); -} - - -/* destroy the pipeline */ -/* need to kill the slaves first */ -static void -destroy_pipeline_b(th_pipeline *pline) -{ - pline->terminate=1; - sync_barrier(&(pline->gate1)); - pthread_join(pline->slave0,NULL); - pthread_join(pline->slave1,NULL); - destroy_th_barrier(&(pline->gate1)); - destroy_th_barrier(&(pline->gate2)); - pthread_attr_destroy(&(pline->attr)); - destroy_task_hist(pline->thst); - free(pline->thst); - free(pline->sd0); - free(pline->sd1); - pline->data=NULL; -} -/************************ end pipeline **************************/ - -/* use algorithm 9.1 to compute pk=Hk gk */ -/* pk,gk: size m x 1 - s, y: size mM x 1 - rho: size M x 1 - ii: true location of the k th values in s,y */ -static void -mult_hessian(int m, double *pk, double *gk, double *s, double *y, double *rho, int M, int ii) { - int ci; - double *alphai; - int *idx; /* store sorted locations of s, y here */ - double gamma,beta; - - if ((alphai=(double*)calloc((size_t)M,sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((idx=(int*)calloc((size_t)M,sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if (M>0) { - /* find the location of k-1 th value */ - if (ii>0) { - ii=ii-1; - } else { - ii=M-1; - } - /* s,y will have 0,1,...,ii,ii+1,...M-1 */ - /* map this to ii+1,ii+2,...,M-1,0,1,..,ii */ - for (ci=0; ci%d ",ci,idx[ci]); - } - printf("\n"); -#endif - /* q = grad(f)k : pk<=gk */ - my_dcopy(m,gk,1,pk,1); - /* this should be done in the right order */ - for (ci=0; ci0) { - gamma=my_ddot(m,&s[m*idx[M-1]],&y[m*idx[M-1]]); - gamma/=my_ddot(m,&y[m*idx[M-1]],&y[m*idx[M-1]]); - /* Hk(0)=gamma I, so scale q by gamma */ - /* r= Hk(0) q */ - my_dscal(m,gamma,pk); - } - - for (ci=0; cib is possible) - to find step that minimizes cost function */ -/* func: vector function - xk: parameter values size m x 1 (at which step is calculated) - pk: step direction size m x 1 (x(k+1)=x(k)+alphak * pk) - a/b: interval for interpolation - x: size n x 1 (storage) - xp: size m x 1 (storage) - xo: observed data size n x 1 - n: size of vector function - step: step size for differencing - adata: additional data passed to the function -*/ -static double -cubic_interp( - void (*func)(double *p, double *hx, int m, int n, void *adata), - double *xk, double *pk, double a, double b, double *x, double *xp, double *xo, int m, int n, double step, void *adata, th_pipeline *tp, gbdata_b *tpg) { - - double f0,f1,f0d,f1d; /* function values and derivatives at a,b */ - double p01,p02,z0,fz0; - double aa,cc; - - my_dcopy(m,xk,1,xp,1); /* xp<=xk */ - my_daxpy(m,pk,a,xp); /* xp<=xp+(a)*pk */ -// func(xp,x,m,n,adata); -// my_daxpy(n,xo,-1.0,x); -// f0=my_dnrm2(n,x); -// f0*=f0; - sync_barrier(&(tp->gate1)); - tpg->lmdata[0]->p=tpg->lmdata[1]->p=xp; - tpg->status[0]=tpg->status[1]=PT_DO_CCOST; - sync_barrier(&(tp->gate2)); - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_NOTHING; - f0=tpg->fcost[0]+tpg->fcost[1]; - sync_barrier(&(tp->gate2)); - - /* grad(phi_0): evaluate at -step and +step */ - my_daxpy(m,pk,step,xp); /* xp<=xp+(a+step)*pk */ -// func(xp,x,m,n,adata); -// my_daxpy(n,xo,-1.0,x); -// p01=my_dnrm2(n,x); - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_CCOST; - sync_barrier(&(tp->gate2)); - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_NOTHING; - p01=tpg->fcost[0]+tpg->fcost[1]; - sync_barrier(&(tp->gate2)); - - - my_daxpy(m,pk,-2.0*step,xp); /* xp<=xp+(a-step)*pk */ -// func(xp,x,m,n,adata); -// my_daxpy(n,xo,-1.0,x); -// p02=my_dnrm2(n,x); - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_CCOST; - sync_barrier(&(tp->gate2)); - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_NOTHING; - p02=tpg->fcost[0]+tpg->fcost[1]; - sync_barrier(&(tp->gate2)); - - -// f0d=(p01*p01-p02*p02)/(2.0*step); - f0d=(p01-p02)/(2.0*step); - - my_dcopy(m,xk,1,xp,1); /* xp<=xk */ - my_daxpy(m,pk,b,xp); /* xp<=xp+(b)*pk */ -// func(xp,x,m,n,adata); -// my_daxpy(n,xo,-1.0,x); -// f1=my_dnrm2(n,x); -// f1*=f1; - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_CCOST; - sync_barrier(&(tp->gate2)); - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_NOTHING; - f1=tpg->fcost[0]+tpg->fcost[1]; - sync_barrier(&(tp->gate2)); - - - /* grad(phi_1): evaluate at -step and +step */ - my_daxpy(m,pk,step,xp); /* xp<=xp+(b+step)*pk */ -// func(xp,x,m,n,adata); -// my_daxpy(n,xo,-1.0,x); -// p01=my_dnrm2(n,x); - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_CCOST; - sync_barrier(&(tp->gate2)); - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_NOTHING; - p01=tpg->fcost[0]+tpg->fcost[1]; - sync_barrier(&(tp->gate2)); - - - my_daxpy(m,pk,-2.0*step,xp); /* xp<=xp+(b-step)*pk */ -// func(xp,x,m,n,adata); -// my_daxpy(n,xo,-1.0,x); -// p02=my_dnrm2(n,x); - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_CCOST; - sync_barrier(&(tp->gate2)); - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_NOTHING; - p02=tpg->fcost[0]+tpg->fcost[1]; - sync_barrier(&(tp->gate2)); - - -// f1d=(p01*p01-p02*p02)/(2.0*step); - f1d=(p01-p02)/(2.0*step); - - - //printf("Interp a,f(a),f'(a): (%lf,%lf,%lf) (%lf,%lf,%lf)\n",a,f0,f0d,b,f1,f1d); - /* cubic poly in [0,1] is f0+f0d z+eta z^2+xi z^3 - where eta=3(f1-f0)-2f0d-f1d, xi=f0d+f1d-2(f1-f0) - derivative f0d+2 eta z+3 xi z^2 => cc+bb z+aa z^2 */ - aa=3.0*(f0-f1)/(b-a)+(f1d-f0d); - p01=aa*aa-f0d*f1d; - /* root exist? */ - if (p01>0.0) { - /* root */ - cc=sqrt(p01); - z0=b-(f1d+cc-aa)*(b-a)/(f1d-f0d+2.0*cc); - /* FIXME: check if this is within boundary */ - aa=MAX(a,b); - cc=MIN(a,b); - //printf("Root=%lf, in [%lf,%lf]\n",z0,cc,aa); - if (z0>aa || z0gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_CCOST; - sync_barrier(&(tp->gate2)); - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_NOTHING; - fz0=tpg->fcost[0]+tpg->fcost[1]; - sync_barrier(&(tp->gate2)); - - } - //printf("Val=%lf, [%lf,%lf]\n",fz0,f0,f1); - - /* now choose between f0,f1,fz0,fz1 */ - if (f0b) is possible - x: size n x 1 (storage) - xp: size m x 1 (storage) - phi_0: phi(0) - gphi_0: grad(phi(0)) - xo: observed data size n x 1 - n: size of vector function - step: step size for differencing - adata: additional data passed to the function -*/ -static double -linesearch_zoom( - void (*func)(double *p, double *hx, int m, int n, void *adata), - double *xk, double *pk, double a, double b, double *x, double *xp, double phi_0, double gphi_0, double sigma, double rho, double t1, double t2, double t3, double *xo, int m, int n, double step, void *adata, th_pipeline *tp, gbdata_b *tpg) { - - double alphaj,phi_j,phi_aj; - double gphi_j,p01,p02,aj,bj; - double alphak=1.0; - int ci,found_step=0; - - aj=a; - bj=b; - ci=0; - while(ci<10) { - /* choose alphaj from [a+t2(b-a),b-t3(b-a)] */ - p01=aj+t2*(bj-aj); - p02=bj-t3*(bj-aj); - alphaj=cubic_interp(func,xk,pk,p01,p02,x,xp,xo,m,n,step,adata,tp,tpg); - //printf("cubic intep [%lf,%lf]->%lf\n",p01,p02,alphaj); - - /* evaluate phi(alphaj) */ - my_dcopy(m,xk,1,xp,1); /* xp<=xk */ - my_daxpy(m,pk,alphaj,xp); /* xp<=xp+(alphaj)*pk */ -// func(xp,x,m,n,adata); - /* calculate x<=x-xo */ -// my_daxpy(n,xo,-1.0,x); -// phi_j=my_dnrm2(n,x); -// phi_j*=phi_j; - sync_barrier(&(tp->gate1)); - tpg->lmdata[0]->p=tpg->lmdata[1]->p=xp; - tpg->status[0]=tpg->status[1]=PT_DO_CCOST; - sync_barrier(&(tp->gate2)); - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_NOTHING; - phi_j=tpg->fcost[0]+tpg->fcost[1]; - sync_barrier(&(tp->gate2)); - - - /* evaluate phi(aj) */ - my_dcopy(m,xk,1,xp,1); /* xp<=xk */ - my_daxpy(m,pk,aj,xp); /* xp<=xp+(alphaj)*pk */ -// func(xp,x,m,n,adata); - /* calculate x<=x-xo */ -// my_daxpy(n,xo,-1.0,x); -// phi_aj=my_dnrm2(n,x); -// phi_aj*=phi_aj; - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_CCOST; - sync_barrier(&(tp->gate2)); - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_NOTHING; - phi_aj=tpg->fcost[0]+tpg->fcost[1]; - sync_barrier(&(tp->gate2)); - - - if ((phi_j>phi_0+rho*alphaj*gphi_0) || phi_j>=phi_aj) { - bj=alphaj; /* aj unchanged */ - } else { - /* evaluate grad(alphaj) */ - my_dcopy(m,xk,1,xp,1); /* xp<=xk */ - my_daxpy(m,pk,alphaj+step,xp); /* xp<=xp+(alphaj+step)*pk */ -// func(xp,x,m,n,adata); - /* calculate x<=x-xo */ -// my_daxpy(n,xo,-1.0,x); -// p01=my_dnrm2(n,x); - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_CCOST; - sync_barrier(&(tp->gate2)); - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_NOTHING; - p01=tpg->fcost[0]+tpg->fcost[1]; - sync_barrier(&(tp->gate2)); - - - my_daxpy(m,pk,-2.0*step,xp); /* xp<=xp+(alphaj-step)*pk */ -// func(xp,x,m,n,adata); - /* calculate x<=x-xo */ -// my_daxpy(n,xo,-1.0,x); -// p02=my_dnrm2(n,x); - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_CCOST; - sync_barrier(&(tp->gate2)); - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_NOTHING; - p02=tpg->fcost[0]+tpg->fcost[1]; - sync_barrier(&(tp->gate2)); - - -// gphi_j=(p01*p01-p02*p02)/(2.0*step); - gphi_j=(p01-p02)/(2.0*step); - - /* termination due to roundoff/other errors pp. 38, Fletcher */ - if ((aj-alphaj)*gphi_j<=step) { - alphak=alphaj; - found_step=1; - break; - } - - if (fabs(gphi_j)<=-sigma*gphi_0) { - alphak=alphaj; - found_step=1; - break; - } - - if (gphi_j*(bj-aj)>=0) { - bj=aj; - } /* else bj unchanged */ - aj=alphaj; - } - ci++; - } - - if (!found_step) { - /* use bound to find possible step */ - alphak=alphaj; - } - -#ifdef DEBUG - printf("Found %lf Interval [%lf,%lf]\n",alphak,a,b); -#endif - return alphak; -} - - - -/* line search */ -/* func: vector function - xk: parameter values size m x 1 (at which step is calculated) - pk: step direction size m x 1 (x(k+1)=x(k)+alphak * pk) - alpha1: initial value for step - sigma,rho,t1,t2,t3: line search parameters (from Fletcher) - xo: observed data size n x 1 - n: size of vector function - step: step size for differencing - adata: additional data passed to the function -*/ -static double -linesearch( - void (*func)(double *p, double *hx, int m, int n, void *adata), - double *xk, double *pk, double alpha1, double sigma, double rho, double t1, double t2, double t3, double *xo, int m, int n, double step, void *adata, th_pipeline *tp, gbdata_b *tpg) { - /* phi(alpha)=f(xk+alpha pk) - for vector function func - f(xk) =||func(xk)||^2 */ - - double *x,*xp; - double alphai,alphai1; - double phi_0,phi_alphai,phi_alphai1; - double p01,p02; - double gphi_0,gphi_i; - double alphak; - - double mu; - double tol; /* lower limit for minimization, need to be just about min value of cost function */ - - int ci; - - if ((x=(double*)calloc((size_t)n,sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xp=(double*)calloc((size_t)m,sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - - alphak=1.0; - /* evaluate phi_0 and grad(phi_0) */ -//func(xk,x,m,n,adata); -//my_daxpy(n,xo,-1.0,x); -//phi_0=my_dnrm2(n,x); -//phi_0*=phi_0; -//printf("CPU cost=%lf\n",phi_0); - sync_barrier(&(tp->gate1)); - tpg->lmdata[0]->p=tpg->lmdata[1]->p=xk; - tpg->status[0]=tpg->status[1]=PT_DO_CCOST; - sync_barrier(&(tp->gate2)); - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_NOTHING; - phi_0=tpg->fcost[0]+tpg->fcost[1]; - sync_barrier(&(tp->gate2)); -//printf("GPU cost=%lf\n",phi_0); - /* select tolarance 1/100 of current function value */ - tol=MIN(0.01*phi_0,1e-6); - - /* grad(phi_0): evaluate at -step and +step */ - my_dcopy(m,xk,1,xp,1); /* xp<=xk */ - my_daxpy(m,pk,step,xp); /* xp<=xp+(0.0+step)*pk */ - -//func(xp,x,m,n,adata); - /* calculate x<=x-xo */ -//my_daxpy(n,xo,-1.0,x); -//p01=my_dnrm2(n,x); - sync_barrier(&(tp->gate1)); - tpg->lmdata[0]->p=tpg->lmdata[1]->p=xp; - tpg->status[0]=tpg->status[1]=PT_DO_CCOST; - sync_barrier(&(tp->gate2)); - sync_barrier(&(tp->gate1)); - p01=tpg->fcost[0]+tpg->fcost[1]; - tpg->status[0]=tpg->status[1]=PT_DO_NOTHING; - sync_barrier(&(tp->gate2)); - - my_daxpy(m,pk,-2.0*step,xp); /* xp<=xp+(0.0-step)*pk */ -//func(xp,x,m,n,adata); - /* calculate x<=x-xo */ -//my_daxpy(n,xo,-1.0,x); -//p02=my_dnrm2(n,x); - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_CCOST; - sync_barrier(&(tp->gate2)); - sync_barrier(&(tp->gate1)); - p02=tpg->fcost[0]+tpg->fcost[1]; - tpg->status[0]=tpg->status[1]=PT_DO_NOTHING; - sync_barrier(&(tp->gate2)); - - -// gphi_0=(p01*p01-p02*p02)/(2.0*step); - gphi_0=(p01-p02)/(2.0*step); - - - /* estimate for mu */ - /* mu = (tol-phi_0)/(rho gphi_0) */ - mu=(tol-phi_0)/(rho*gphi_0); -#ifdef DEBUG - printf("cost=%lf grad=%lf mu=%lf, alpha1=%lf\n",phi_0,gphi_0,mu,alpha1); -#endif - - ci=1; - alphai=alpha1; /* initial value for alpha(i) : check if 0gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_CCOST; - sync_barrier(&(tp->gate2)); - sync_barrier(&(tp->gate1)); - phi_alphai=tpg->fcost[0]+tpg->fcost[1]; - tpg->status[0]=tpg->status[1]=PT_DO_NOTHING; - sync_barrier(&(tp->gate2)); - - - if (phi_alphaiphi_0+alphai*gphi_0) || (ci>1 && phi_alphai>=phi_alphai1)) { - /* ai=alphai1, bi=alphai bracket */ - alphak=linesearch_zoom(func,xk,pk,alphai1,alphai,x,xp,phi_0,gphi_0,sigma,rho,t1,t2,t3,xo,m,n,step,adata,tp,tpg); -#ifdef DEBUG - printf("Linesearch : Condition 1 met\n"); -#endif - break; - } - - /* evaluate grad(phi(alpha(i))) */ - my_dcopy(m,xk,1,xp,1); /* NOT NEEDED here?? xp<=xk */ - my_daxpy(m,pk,alphai+step,xp); /* xp<=xp+(alphai+step)*pk */ -// func(xp,x,m,n,adata); - /* calculate x<=x-xo */ -// my_daxpy(n,xo,-1.0,x); -// p01=my_dnrm2(n,x); - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_CCOST; - sync_barrier(&(tp->gate2)); - sync_barrier(&(tp->gate1)); - p01=tpg->fcost[0]+tpg->fcost[1]; - tpg->status[0]=tpg->status[1]=PT_DO_NOTHING; - sync_barrier(&(tp->gate2)); - - my_daxpy(m,pk,-2.0*step,xp); /* xp<=xp+(alphai-step)*pk */ -// func(xp,x,m,n,adata); - /* calculate x<=x-xo */ -// my_daxpy(n,xo,-1.0,x); -// p02=my_dnrm2(n,x); - sync_barrier(&(tp->gate1)); - tpg->status[0]=tpg->status[1]=PT_DO_CCOST; - sync_barrier(&(tp->gate2)); - sync_barrier(&(tp->gate1)); - p02=tpg->fcost[0]+tpg->fcost[1]; - tpg->status[0]=tpg->status[1]=PT_DO_NOTHING; - sync_barrier(&(tp->gate2)); - - -// gphi_i=(p01*p01-p02*p02)/(2.0*step); - gphi_i=(p01-p02)/(2.0*step); - - if (fabs(gphi_i)<=-sigma*gphi_0) { - alphak=alphai; -#ifdef DEBUG - printf("Linesearch : Condition 2 met\n"); -#endif - break; - } - - if (gphi_i>=0) { - /* ai=alphai, bi=alphai1 bracket */ - alphak=linesearch_zoom(func,xk,pk,alphai,alphai1,x,xp,phi_0,gphi_0,sigma,rho,t1,t2,t3,xo,m,n,step,adata,tp,tpg); -#ifdef DEBUG - printf("Linesearch : Condition 3 met\n"); -#endif - break; - } - - /* else preserve old values */ - if (mu<=(2.0*alphai-alphai1)) { - /* next step */ - alphai1=alphai; - alphai=mu; - } else { - /* choose by interpolation in [2*alphai-alphai1,min(mu,alphai+t1*(alphai-alphai1)] */ - p01=2.0*alphai-alphai1; - p02=MIN(mu,alphai+t1*(alphai-alphai1)); - alphai=cubic_interp(func,xk,pk,p01,p02,x,xp,xo,m,n,step,adata,tp,tpg); - //printf("cubic interp [%lf,%lf]->%lf\n",p01,p02,alphai); - } - phi_alphai1=phi_alphai; - - ci++; - } - - - - free(x); - free(xp); -#ifdef DEBUG - printf("Step size=%lf\n",alphak); -#endif - return alphak; -} -/*************** END Fletcher line search **********************************/ - - -/* note M here is LBFGS memory size */ -static int -lbfgs_fit_common( - void (*func)(double *p, double *hx, int m, int n, void *adata), - double *p, double *x, int m, int n, int itmax, int M, int gpu_threads, int do_robust, void *adata) { - - double *gk; /* gradients at both k+1 and k iter */ - double *xk1,*xk; /* parameters at k+1 and k iter */ - double *pk; /* step direction H_k * grad(f) */ - - double step; /* FIXME tune for GPU, use larger if far away from convergence */ - double *y, *s; /* storage for delta(grad) and delta(p) */ - double *rho; /* storage for 1/yk^T*sk */ - int ci,ck,cm; - double alphak=1.0; - - - me_data_t *dp=(me_data_t*)adata; - short *hbb; - int *ptoclus; - int Nbase1=dp->Nbase*dp->tilesz; - - thread_gpu_data threaddata[2]; /* 2 for 2 threads/cards */ - - if ((gk=(double*)calloc((size_t)m,sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xk1=(double*)calloc((size_t)m,sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xk=(double*)calloc((size_t)m,sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - - if ((pk=(double*)calloc((size_t)m,sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - - - /* storage size mM x 1*/ - if ((s=(double*)calloc((size_t)m*M,sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((y=(double*)calloc((size_t)m*M,sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((rho=(double*)calloc((size_t)M,sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - -/*********** following are not part of LBFGS, but done here only for GPU use */ - /* auxilliary arrays for GPU */ - if ((hbb=(short*)calloc((size_t)(Nbase1*2),sizeof(short)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - /* baseline->station mapping */ - rearrange_baselines(Nbase1, dp->barr, hbb, dp->Nt); - - /* parameter->cluster mapping */ - /* for each cluster: chunk size, start param index */ - if ((ptoclus=(int*)calloc((size_t)(2*dp->M),sizeof(int)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - for(ci=0; ciM; ci++) { - ptoclus[2*ci]=dp->carr[ci].nchunk; - ptoclus[2*ci+1]=dp->carr[ci].p[0]; /* so end at p[0]+nchunk*8*N-1 */ - } - dp->hbb=hbb; - dp->ptoclus=ptoclus; -/*****************************************************************************/ - /* choose 256 threads per block for high occupancy */ - int ThreadsPerBlock = gpu_threads; - - /* partition parameters, per each parameter, one thread */ - /* also account for the no of GPUs using */ - /* parameters per thread (GPU) */ - int Nparm=(m+2-1)/2; - /* find number of blocks */ - int BlocksPerGrid = (Nparm+ThreadsPerBlock-1)/ThreadsPerBlock; - ci=0; - int nth; - for (nth=0; nth<2; nth++) { - threaddata[nth].ThreadsPerBlock=ThreadsPerBlock; - threaddata[nth].BlocksPerGrid=BlocksPerGrid; - threaddata[nth].card=nth; - threaddata[nth].Nbase=dp->Nbase; - threaddata[nth].tilesz=dp->tilesz; - threaddata[nth].barr=dp->barr; - threaddata[nth].M=dp->M; - threaddata[nth].N=dp->N; - threaddata[nth].coh=dp->coh; - threaddata[nth].xo=x; - threaddata[nth].p=p; - threaddata[nth].g=gk; - threaddata[nth].m=m; - threaddata[nth].n=n; - threaddata[nth].hbb=dp->hbb; - threaddata[nth].ptoclus=dp->ptoclus; - threaddata[nth].g_start=ci; - threaddata[nth].g_end=ci+Nparm-1; - if (threaddata[nth].g_end>=m) { - threaddata[nth].g_end=m-1; - } - /* for robust mode */ - if (do_robust) { - threaddata[nth].robust_nu=dp->robust_nu; - } - ci=ci+Nparm; - } - - /* pipeline data */ - th_pipeline tp; - gbdata_b tpg; - - tpg.do_robust=do_robust; - /* divide no of baselines */ - int Nthb0=(Nbase1+2-1)/2; - tpg.Nbase[0]=Nthb0; - tpg.Nbase[1]=Nbase1-Nthb0; - tpg.boff[0]=0; - tpg.boff[1]=Nthb0; - - tpg.lmdata[0]=&threaddata[0]; - tpg.lmdata[1]=&threaddata[1]; - /* calculate total size of memory need to be allocated in GPU, in bytes +2 added to align memory */ - /* note: we do not allocate memory here, use pinned memory for transfer */ - //tpg.data_size[0]=(n+(dp->Nbase*dp->tilesz)*8*dp->M+m+(tpg.lmdata[0]->g_end-tpg.lmdata[0]->g_start+1)+2)*sizeof(double)+(2*dp->M*sizeof(int))+(2*dp->Nbase*dp->tilesz*sizeof(char)); - //tpg.data_size[1]=(n+(dp->Nbase*dp->tilesz)*8*dp->M+m+(tpg.lmdata[1]->g_end-tpg.lmdata[1]->g_start+1)+2)*sizeof(double)+(2*dp->M*sizeof(int))+(2*dp->Nbase*dp->tilesz*sizeof(char)); - tpg.data_size[0]=tpg.data_size[1]=sizeof(float); - - tpg.status[0]=tpg.status[1]=PT_DO_NOTHING; - init_pipeline_b(&tp,&tpg); - sync_barrier(&(tp.gate1)); - tpg.status[0]=tpg.status[1]=PT_DO_AGPU; - sync_barrier(&(tp.gate2)); - sync_barrier(&(tp.gate1)); - tpg.status[0]=tpg.status[1]=PT_DO_NOTHING; - sync_barrier(&(tp.gate2)); - -/*****************************************************************************/ - /* initial value for params xk=p */ - my_dcopy(m,p,1,xk,1); - sync_barrier(&(tp.gate1)); - threaddata[0].p=threaddata[1].p=xk; - tpg.status[0]=tpg.status[1]=PT_DO_CDERIV; - sync_barrier(&(tp.gate2)); - sync_barrier(&(tp.gate1)); - tpg.status[0]=tpg.status[1]=PT_DO_NOTHING; - sync_barrier(&(tp.gate2)); -// for (ci=0; ci<20; ci++) { -// printf("GPU %d %lf\n",ci,gk[ci]); -// } - /* gradient gk=grad(f)_k */ -// func_grad(func,xk,gk,x,m,n,step,gpu_threads,adata); -// for (ci=0; ci<20; ci++) { -// printf("CPU %d %lf\n",ci,gk[ci]); -// } - - double gradnrm=my_dnrm2(m,gk); - /* if gradient is too small, no need to solve, so stop */ - if (gradnrmp=tpg.lmdata[1]->p=xk1; - tpg.status[0]=tpg.status[1]=PT_DO_CDERIV; - sync_barrier(&(tp.gate2)); - sync_barrier(&(tp.gate1)); - tpg.status[0]=tpg.status[1]=PT_DO_NOTHING; - sync_barrier(&(tp.gate2)); - -// func_grad(func,xk1,gk,x,m,n,step,gpu_threads,adata); - /* yk=yk+gk1 */ - my_daxpy(m,gk,1.0,&y[cm]); - - /* calculate 1/yk^T*sk */ - rho[ci]=1.0/my_ddot(m,&y[cm],&s[cm]); - - /* update xk=xk1 */ - my_dcopy(m,xk1,1,xk,1); - - //printf("iter %d store %d\n",ck,cm); - ck++; - /* increment storage appropriately */ - if (cm<(M-1)*m) { - /* offset of m */ - cm=cm+m; - ci++; - } else { - cm=ci=0; - } - } - - - /* copy back solution to p */ - my_dcopy(m,xk,1,p,1); - - /* for (ci=0; cihbb=NULL; - dp->ptoclus=NULL; - - /******** free threads ***************/ - sync_barrier(&(tp.gate1)); /* sync at gate 1*/ - tpg.status[0]=tpg.status[1]=PT_DO_DGPU; - sync_barrier(&(tp.gate2)); /* sync at gate 2*/ - - destroy_pipeline_b(&tp); - - return 0; -} - - - -int -lbfgs_fit( - void (*func)(double *p, double *hx, int m, int n, void *adata), - double *p, double *x, int m, int n, int itmax, int M, int gpu_threads, void *adata) { - return lbfgs_fit_common(func, p, x, m, n, itmax, M, gpu_threads, 0, adata); -} - -int -lbfgs_fit_robust_cuda( - void (*func)(double *p, double *hx, int m, int n, void *adata), - double *p, double *x, int m, int n, int itmax, int M, int gpu_threads, void *adata) { - return lbfgs_fit_common(func, p, x, m, n, itmax, M, gpu_threads, 1, adata); -} diff --git a/src/lib/lbfgs_nocuda.c b/src/lib/lbfgs_nocuda.c deleted file mode 100644 index c564c55..0000000 --- a/src/lib/lbfgs_nocuda.c +++ /dev/null @@ -1,926 +0,0 @@ -/* - * - Copyright (C) 2006-2008 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#include "sagecal.h" -#include - - -/**** repeated code here ********************/ -/* Jones matrix multiplication - C=A*B -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -amb(complex double * __restrict a, complex double * __restrict b, complex double * __restrict c) { - c[0]=a[0]*b[0]+a[1]*b[2]; - c[1]=a[0]*b[1]+a[1]*b[3]; - c[2]=a[2]*b[0]+a[3]*b[2]; - c[3]=a[2]*b[1]+a[3]*b[3]; -} - -/* Jones matrix multiplication - C=A*B^H -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -ambt(complex double * __restrict a, complex double * __restrict b, complex double * __restrict c) { - c[0]=a[0]*conj(b[0])+a[1]*conj(b[1]); - c[1]=a[0]*conj(b[2])+a[1]*conj(b[3]); - c[2]=a[2]*conj(b[0])+a[3]*conj(b[1]); - c[3]=a[2]*conj(b[2])+a[3]*conj(b[3]); -} - -/**** end repeated code ********************/ - -/* worker thread for a cpu */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -cpu_calc_deriv(void *adata) { - thread_data_grad_t *t=(thread_data_grad_t*)adata; - - int ci,nb; - int stc,stoff,stm,sta1,sta2; - int N=t->N; /* stations */ - int M=t->M; /* clusters */ - int Nbase=(t->Nbase)*(t->tilesz); - - - complex double xr[4]; /* residuals */ - complex double G1[4],G2[4],C[4],T1[4],T2[4]; - double pp[8]; - complex double csum; - int cli,tpchunk,pstart,nchunk,tilesperchunk,stci,ttile,tptile,poff; - - /* iterate over each paramter */ - for (ci=t->g_start; ci<=t->g_end; ++ci) { - t->g[ci]=0.0; - /* find station and parameter corresponding to this value of ci */ - /* this parameter should correspond to the right baseline (x tilesz) - to contribute to the derivative */ - cli=0; - while((clicarr[cli].p[0] || ci>t->carr[cli].p[0]+8*N*t->carr[cli].nchunk-1)) { - cli++; - } - /* now either cli>=M: cluster not found - or cli=t->carr[cli-1].p[0] && ci<=t->carr[cli-1].p[0]+8*N*t->carr[cli-1].nchunk-1) { - cli--; - } - - if (clicarr[cli].p[0]; - - stc=(stci%(8*N))/8; /* 0..N-1 */ - /* make sure this baseline contribute to this parameter */ - tpchunk=stci/(8*N); - nchunk=t->carr[cli].nchunk; - pstart=t->carr[cli].p[0]; - tilesperchunk=(t->tilesz+nchunk-1)/nchunk; - - - /* iterate over all baselines and accumulate sum */ - for (nb=0; nbNbase; - /* which chunk this tile belongs to */ - tptile=ttile/tilesperchunk; - /* now tptile has to match tpchunk, otherwise ignore calculation */ - if (tptile==tpchunk) { - - sta1=t->barr[nb].sta1; - sta2=t->barr[nb].sta2; - - if (((stc==sta1)||(stc==sta2))&& !t->barr[nb].flag) { - /* this baseline has a contribution */ - /* which paramter of this station */ - stoff=(stci%(8*N))%8; /* 0..7 */ - /* which cluster */ - stm=cli; /* 0..M-1 */ - - /* exact expression for derivative - 2 real( vec^H(residual_this_baseline) - * vec(-J_{pm}C_{pqm} J_{qm}^H) - where m: chosen cluster - J_{pm},J_{qm} Jones matrices for baseline p-q - depending on the parameter, J ==> E - E: zero matrix, except 1 at location of m - - residual : in x[8*nb:8*nb+7] - C coh: in coh[8*M*nb+m*8:8*M*nb+m*8+7] (double storage) - coh[4*M*nb+4*m:4*M*nb+4*m+3] (complex storage) - J_p,J_q: in p[sta1*8+m*8*N: sta1*8+m*8*N+7] - and p[sta2*8+m*8*N: sta2*8+m*8*N+ 7] - */ - /* read in residual vector, conjugated */ - xr[0]=(t->x[nb*8])-_Complex_I*(t->x[nb*8+1]); - xr[1]=(t->x[nb*8+2])-_Complex_I*(t->x[nb*8+3]); - xr[2]=(t->x[nb*8+4])-_Complex_I*(t->x[nb*8+5]); - xr[3]=(t->x[nb*8+6])-_Complex_I*(t->x[nb*8+7]); - - /* read in coherency */ - C[0]=t->coh[4*M*nb+4*stm]; - C[1]=t->coh[4*M*nb+4*stm+1]; - C[2]=t->coh[4*M*nb+4*stm+2]; - C[3]=t->coh[4*M*nb+4*stm+3]; - - memset(pp,0,sizeof(double)*8); - if (stc==sta1) { - /* this station parameter gradient */ - pp[stoff]=1.0; - memset(G1,0,sizeof(complex double)*4); - G1[0]=pp[0]+_Complex_I*pp[1]; - G1[1]=pp[2]+_Complex_I*pp[3]; - G1[2]=pp[4]+_Complex_I*pp[5]; - G1[3]=pp[6]+_Complex_I*pp[7]; - poff=pstart+tpchunk*8*N+sta2*8; - G2[0]=(t->p[poff])+_Complex_I*(t->p[poff+1]); - G2[1]=(t->p[poff+2])+_Complex_I*(t->p[poff+3]); - G2[2]=(t->p[poff+4])+_Complex_I*(t->p[poff+4]); - G2[3]=(t->p[poff+6])+_Complex_I*(t->p[poff+7]); - - } else if (stc==sta2) { - memset(G2,0,sizeof(complex double)*4); - pp[stoff]=1.0; - G2[0]=pp[0]+_Complex_I*pp[1]; - G2[1]=pp[2]+_Complex_I*pp[3]; - G2[2]=pp[4]+_Complex_I*pp[5]; - G2[3]=pp[6]+_Complex_I*pp[7]; - poff=pstart+tpchunk*8*N+sta1*8; - G1[0]=(t->p[poff])+_Complex_I*(t->p[poff+1]); - G1[1]=(t->p[poff+2])+_Complex_I*(t->p[poff+3]); - G1[2]=(t->p[poff+4])+_Complex_I*(t->p[poff+5]); - G1[3]=(t->p[poff+6])+_Complex_I*(t->p[poff+7]); - } - - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - - /* calculate product xr*vec(J_p C J_q^H ) */ - csum=xr[0]*T2[0]; - csum+=xr[1]*T2[1]; - csum+=xr[2]*T2[2]; - csum+=xr[3]*T2[3]; - - /* accumulate sum */ - t->g[ci]+=-2.0*creal(csum); - } - } - } - } - } - - - return NULL; -} - -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static int -func_grad( - void (*func)(double *p, double *hx, int m, int n, void *adata), - double *p, double *g, double *xo, int m, int n, double step, void *adata) { - /* gradient for each parameter is - (||func(p+step*e_i)-x||^2-||func(p-step*e_i)-x||^2)/2*step - i=0,...,m-1 for all parameters - e_i: unit vector, 1 only at i-th location - */ - - double *x; /* array to store residual */ - int ci; - me_data_t *dp=(me_data_t*)adata; - - int Nt=dp->Nt; - - pthread_attr_t attr; - pthread_t *th_array; - thread_data_grad_t *threaddata; - - - if ((x=(double*)calloc((size_t)n,sizeof(double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - /* evaluate func once, store in x, and create threads */ - /* and calculate the residual x=xo-func */ - func(p,x,m,n,adata); - /* calculate x<=x-xo */ - my_daxpy(n,xo,-1.0,x); - - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((threaddata=(thread_data_grad_t*)malloc((size_t)Nt*sizeof(thread_data_grad_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - int nth,nth1,Nparm; - - /* parameters per thread */ - Nparm=(m+Nt-1)/Nt; - - /* each thread will calculate derivative of part of - parameters */ - ci=0; - for (nth=0; nthNbase; - threaddata[nth].tilesz=dp->tilesz; - threaddata[nth].barr=dp->barr; - threaddata[nth].carr=dp->carr; - threaddata[nth].M=dp->M; - threaddata[nth].N=dp->N; - threaddata[nth].coh=dp->coh; - threaddata[nth].m=m; - threaddata[nth].n=n; - threaddata[nth].x=x; - threaddata[nth].p=p; - threaddata[nth].g=g; - threaddata[nth].g_start=ci; - threaddata[nth].g_end=ci+Nparm-1; - if (threaddata[nth].g_end>=m) { - threaddata[nth].g_end=m-1; - } - ci=ci+Nparm; - pthread_create(&th_array[nth],&attr,cpu_calc_deriv,(void*)(&threaddata[nth])); - } - - /* now wait for threads to finish */ - for(nth1=0; nth10) { - /* find the location of k-1 th value */ - if (ii>0) { - ii=ii-1; - } else { - ii=M-1; - } - /* s,y will have 0,1,...,ii,ii+1,...M-1 */ - /* map this to ii+1,ii+2,...,M-1,0,1,..,ii */ - for (ci=0; ci%d ",ci,idx[ci]); - } - printf("\n"); -#endif - /* q = grad(f)k : pk<=gk */ - my_dcopy(m,gk,1,pk,1); - /* this should be done in the right order */ - for (ci=0; ci0) { - gamma=my_ddot(m,&s[m*idx[M-1]],&y[m*idx[M-1]]); - gamma/=my_ddot(m,&y[m*idx[M-1]],&y[m*idx[M-1]]); - /* Hk(0)=gamma I, so scale q by gamma */ - /* r= Hk(0) q */ - my_dscal(m,gamma,pk); - } - - for (ci=0; cib is possible) - to find step that minimizes cost function */ -/* func: vector function - xk: parameter values size m x 1 (at which step is calculated) - pk: step direction size m x 1 (x(k+1)=x(k)+alphak * pk) - a/b: interval for interpolation - x: size n x 1 (storage) - xp: size m x 1 (storage) - xo: observed data size n x 1 - n: size of vector function - step: step size for differencing - adata: additional data passed to the function -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static double -cubic_interp( - void (*func)(double *p, double *hx, int m, int n, void *adata), - double *xk, double *pk, double a, double b, double *x, double *xp, double *xo, int m, int n, double step, void *adata) { - - double f0,f1,f0d,f1d; /* function values and derivatives at a,b */ - double p01,p02,z0,fz0; - double aa,cc; - - my_dcopy(m,xk,1,xp,1); /* xp<=xk */ - my_daxpy(m,pk,a,xp); /* xp<=xp+(a)*pk */ - func(xp,x,m,n,adata); - my_daxpy(n,xo,-1.0,x); - f0=my_dnrm2(n,x); - f0*=f0; - /* grad(phi_0): evaluate at -step and +step */ - my_daxpy(m,pk,step,xp); /* xp<=xp+(a+step)*pk */ - func(xp,x,m,n,adata); - my_daxpy(n,xo,-1.0,x); - p01=my_dnrm2(n,x); - my_daxpy(m,pk,-2.0*step,xp); /* xp<=xp+(a-step)*pk */ - func(xp,x,m,n,adata); - my_daxpy(n,xo,-1.0,x); - p02=my_dnrm2(n,x); - f0d=(p01*p01-p02*p02)/(2.0*step); - - my_dcopy(m,xk,1,xp,1); /* xp<=xk */ - my_daxpy(m,pk,b,xp); /* xp<=xp+(b)*pk */ - func(xp,x,m,n,adata); - my_daxpy(n,xo,-1.0,x); - f1=my_dnrm2(n,x); - f1*=f1; - /* grad(phi_1): evaluate at -step and +step */ - my_daxpy(m,pk,step,xp); /* xp<=xp+(b+step)*pk */ - func(xp,x,m,n,adata); - my_daxpy(n,xo,-1.0,x); - p01=my_dnrm2(n,x); - my_daxpy(m,pk,-2.0*step,xp); /* xp<=xp+(b-step)*pk */ - func(xp,x,m,n,adata); - my_daxpy(n,xo,-1.0,x); - p02=my_dnrm2(n,x); - f1d=(p01*p01-p02*p02)/(2.0*step); - - - //printf("Interp a,f(a),f'(a): (%lf,%lf,%lf) (%lf,%lf,%lf)\n",a,f0,f0d,b,f1,f1d); - /* cubic poly in [0,1] is f0+f0d z+eta z^2+xi z^3 - where eta=3(f1-f0)-2f0d-f1d, xi=f0d+f1d-2(f1-f0) - derivative f0d+2 eta z+3 xi z^2 => cc+bb z+aa z^2 */ - aa=3.0*(f0-f1)/(b-a)+(f1d-f0d); - p01=aa*aa-f0d*f1d; - /* root exist? */ - if (p01>0.0) { - /* root */ - cc=sqrt(p01); - z0=b-(f1d+cc-aa)*(b-a)/(f1d-f0d+2.0*cc); - /* FIXME: check if this is within boundary */ - aa=MAX(a,b); - cc=MIN(a,b); - //printf("Root=%lf, in [%lf,%lf]\n",z0,cc,aa); - if (z0>aa || z0b) is possible - x: size n x 1 (storage) - xp: size m x 1 (storage) - phi_0: phi(0) - gphi_0: grad(phi(0)) - xo: observed data size n x 1 - n: size of vector function - step: step size for differencing - adata: additional data passed to the function -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static double -linesearch_zoom( - void (*func)(double *p, double *hx, int m, int n, void *adata), - double *xk, double *pk, double a, double b, double *x, double *xp, double phi_0, double gphi_0, double sigma, double rho, double t1, double t2, double t3, double *xo, int m, int n, double step, void *adata) { - - double alphaj,phi_j,phi_aj; - double gphi_j,p01,p02,aj,bj; - double alphak=1.0; - int ci,found_step=0; - - aj=a; - bj=b; - ci=0; - while(ci<10) { - /* choose alphaj from [a+t2(b-a),b-t3(b-a)] */ - p01=aj+t2*(bj-aj); - p02=bj-t3*(bj-aj); - alphaj=cubic_interp(func,xk,pk,p01,p02,x,xp,xo,m,n,step,adata); - //printf("cubic intep [%lf,%lf]->%lf\n",p01,p02,alphaj); - - /* evaluate phi(alphaj) */ - my_dcopy(m,xk,1,xp,1); /* xp<=xk */ - my_daxpy(m,pk,alphaj,xp); /* xp<=xp+(alphaj)*pk */ - func(xp,x,m,n,adata); - /* calculate x<=x-xo */ - my_daxpy(n,xo,-1.0,x); - phi_j=my_dnrm2(n,x); - phi_j*=phi_j; - - /* evaluate phi(aj) */ - my_dcopy(m,xk,1,xp,1); /* xp<=xk */ - my_daxpy(m,pk,aj,xp); /* xp<=xp+(alphaj)*pk */ - func(xp,x,m,n,adata); - /* calculate x<=x-xo */ - my_daxpy(n,xo,-1.0,x); - phi_aj=my_dnrm2(n,x); - phi_aj*=phi_aj; - - - if ((phi_j>phi_0+rho*alphaj*gphi_0) || phi_j>=phi_aj) { - bj=alphaj; /* aj unchanged */ - } else { - /* evaluate grad(alphaj) */ - my_dcopy(m,xk,1,xp,1); /* xp<=xk */ - my_daxpy(m,pk,alphaj+step,xp); /* xp<=xp+(alphaj+step)*pk */ - func(xp,x,m,n,adata); - /* calculate x<=x-xo */ - my_daxpy(n,xo,-1.0,x); - p01=my_dnrm2(n,x); - my_daxpy(m,pk,-2.0*step,xp); /* xp<=xp+(alphaj-step)*pk */ - func(xp,x,m,n,adata); - /* calculate x<=x-xo */ - my_daxpy(n,xo,-1.0,x); - p02=my_dnrm2(n,x); - gphi_j=(p01*p01-p02*p02)/(2.0*step); - - /* termination due to roundoff/other errors pp. 38, Fletcher */ - if ((aj-alphaj)*gphi_j<=step) { - alphak=alphaj; - found_step=1; - break; - } - - if (fabs(gphi_j)<=-sigma*gphi_0) { - alphak=alphaj; - found_step=1; - break; - } - - if (gphi_j*(bj-aj)>=0) { - bj=aj; - } /* else bj unchanged */ - aj=alphaj; - } - ci++; - } - - if (!found_step) { - /* use bound to find possible step */ - alphak=alphaj; - } - -#ifdef DEBUG - printf("Found %g Interval [%lf,%lf]\n",alphak,a,b); -#endif - return alphak; -} - - - -/* line search */ -/* func: vector function - xk: parameter values size m x 1 (at which step is calculated) - pk: step direction size m x 1 (x(k+1)=x(k)+alphak * pk) - alpha1: initial value for step - sigma,rho,t1,t2,t3: line search parameters (from Fletcher) - xo: observed data size n x 1 - n: size of vector function - step: step size for differencing - adata: additional data passed to the function -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static double -linesearch( - void (*func)(double *p, double *hx, int m, int n, void *adata), - double *xk, double *pk, double alpha1, double sigma, double rho, double t1, double t2, double t3, double *xo, int m, int n, double step, void *adata) { - - /* phi(alpha)=f(xk+alpha pk) - for vector function func - f(xk) =||func(xk)||^2 */ - - double *x,*xp; - double alphai,alphai1; - double phi_0,phi_alphai,phi_alphai1; - double p01,p02; - double gphi_0,gphi_i; - double alphak; - - double mu; - double tol; /* lower limit for minimization */ - - int ci; - - if ((x=(double*)calloc((size_t)n,sizeof(double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((xp=(double*)calloc((size_t)m,sizeof(double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - alphak=1.0; - /* evaluate phi_0 and grad(phi_0) */ - func(xk,x,m,n,adata); - my_daxpy(n,xo,-1.0,x); - phi_0=my_dnrm2(n,x); - phi_0*=phi_0; - /* select tolarance 1/100 of current function value */ - tol=MIN(0.01*phi_0,1e-6); - - /* grad(phi_0): evaluate at -step and +step */ - my_dcopy(m,xk,1,xp,1); /* xp<=xk */ - my_daxpy(m,pk,step,xp); /* xp<=xp+(0.0+step)*pk */ - func(xp,x,m,n,adata); - /* calculate x<=x-xo */ - my_daxpy(n,xo,-1.0,x); - p01=my_dnrm2(n,x); - my_daxpy(m,pk,-2.0*step,xp); /* xp<=xp+(0.0-step)*pk */ - func(xp,x,m,n,adata); - /* calculate x<=x-xo */ - my_daxpy(n,xo,-1.0,x); - p02=my_dnrm2(n,x); - gphi_0=(p01*p01-p02*p02)/(2.0*step); - - /* estimate for mu */ - /* mu = (tol-phi_0)/(rho gphi_0) */ - mu=(tol-phi_0)/(rho*gphi_0); -#ifdef DEBUG - printf("mu=%lf, alpha1=%lf\n",mu,alpha1); -#endif - - ci=1; - alphai=alpha1; /* initial value for alpha(i) : check if 0phi_0+alphai*gphi_0) || (ci>1 && phi_alphai>=phi_alphai1)) { - /* ai=alphai1, bi=alphai bracket */ - alphak=linesearch_zoom(func,xk,pk,alphai1,alphai,x,xp,phi_0,gphi_0,sigma,rho,t1,t2,t3,xo,m,n,step,adata); -#ifdef DEBUG - printf("Linesearch : Condition 1 met\n"); -#endif - break; - } - - /* evaluate grad(phi(alpha(i))) */ - my_dcopy(m,xk,1,xp,1); /* NOT NEEDED here?? xp<=xk */ - my_daxpy(m,pk,alphai+step,xp); /* xp<=xp+(alphai+step)*pk */ - func(xp,x,m,n,adata); - /* calculate x<=x-xo */ - my_daxpy(n,xo,-1.0,x); - p01=my_dnrm2(n,x); - my_daxpy(m,pk,-2.0*step,xp); /* xp<=xp+(alphai-step)*pk */ - func(xp,x,m,n,adata); - /* calculate x<=x-xo */ - my_daxpy(n,xo,-1.0,x); - p02=my_dnrm2(n,x); - gphi_i=(p01*p01-p02*p02)/(2.0*step); - - if (fabs(gphi_i)<=-sigma*gphi_0) { - alphak=alphai; -#ifdef DEBUG - printf("Linesearch : Condition 2 met\n"); -#endif - break; - } - - if (gphi_i>=0) { - /* ai=alphai, bi=alphai1 bracket */ - alphak=linesearch_zoom(func,xk,pk,alphai,alphai1,x,xp,phi_0,gphi_0,sigma,rho,t1,t2,t3,xo,m,n,step,adata); -#ifdef DEBUG - printf("Linesearch : Condition 3 met\n"); -#endif - break; - } - - /* else preserve old values */ - if (mu<=(2.0*alphai-alphai1)) { - /* next step */ - alphai1=alphai; - alphai=mu; - } else { - /* choose by interpolation in [2*alphai-alphai1,min(mu,alphai+t1*(alphai-alphai1)] */ - p01=2.0*alphai-alphai1; - p02=MIN(mu,alphai+t1*(alphai-alphai1)); - alphai=cubic_interp(func,xk,pk,p01,p02,x,xp,xo,m,n,step,adata); - //printf("cubic interp [%lf,%lf]->%lf\n",p01,p02,alphai); - } - phi_alphai1=phi_alphai; - - ci++; - } - - - - free(x); - free(xp); -#ifdef DEBUG - printf("Step size=%g\n",alphak); -#endif - return alphak; -} -/*************** END Fletcher line search **********************************/ - -int -lbfgs_fit( - void (*func)(double *p, double *hx, int m, int n, void *adata), - double *p, double *x, int m, int n, int itmax, int M, int gpu_threads, void *adata) { - - double *gk; /* gradients at both k+1 and k iter */ - double *xk1,*xk; /* parameters at k+1 and k iter */ - double *pk; /* step direction H_k * grad(f) */ - - double step=1e-6; /* step for interpolation */ - double *y, *s; /* storage for delta(grad) and delta(p) */ - double *rho; /* storage for 1/yk^T*sk */ - int ci,ck,cm; - double alphak=1.0; - - - if ((gk=(double*)calloc((size_t)m,sizeof(double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((xk1=(double*)calloc((size_t)m,sizeof(double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((xk=(double*)calloc((size_t)m,sizeof(double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - if ((pk=(double*)calloc((size_t)m,sizeof(double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - - /* storage size mM x 1*/ - if ((s=(double*)calloc((size_t)m*M,sizeof(double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((y=(double*)calloc((size_t)m*M,sizeof(double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((rho=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - /* initial value for params xk=p */ - my_dcopy(m,p,1,xk,1); - /* gradient gk=grad(f)_k */ - func_grad(func,xk,gk,x,m,n,step,adata); - double gradnrm=my_dnrm2(m,gk); - /* if gradient is too small, no need to solve, so stop */ - if (gradnrm - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#include -#include -#include -#include -#include -#include "sagecal.h" - - -//#define DEBUG - -/* Jones matrix multiplication - C=A*B -*/ -static void -amb(complex double * __restrict a, complex double * __restrict b, complex double * __restrict c) { - c[0]=a[0]*b[0]+a[1]*b[2]; - c[1]=a[0]*b[1]+a[1]*b[3]; - c[2]=a[2]*b[0]+a[3]*b[2]; - c[3]=a[2]*b[1]+a[3]*b[3]; -} - -/* Jones matrix multiplication - C=A*B^H -*/ -static void -ambt(complex double * __restrict a, complex double * __restrict b, complex double * __restrict c) { - c[0]=a[0]*conj(b[0])+a[1]*conj(b[1]); - c[1]=a[0]*conj(b[2])+a[1]*conj(b[3]); - c[2]=a[2]*conj(b[0])+a[3]*conj(b[1]); - c[3]=a[2]*conj(b[2])+a[3]*conj(b[3]); -} - -/********************** sage minimization ***************************/ -/* worker thread function for prediction */ -static void * -predict_threadfn_withgain(void *data) { - thread_data_base_t *t=(thread_data_base_t*)data; - - int ci,cm,sta1,sta2; - complex double C[4],G1[4],G2[4],T1[4],T2[4]; - int M=(t->M); - cm=(t->clus); - int Ntilebase=(t->Nbase)*(t->tilesz); - int px; - double *pm; - - for (ci=0; ciNb; ci++) { - /* iterate over the sky model and calculate contribution */ - /* for this x[8*ci:8*(ci+1)-1] */ - memset(&(t->x[8*ci]),0,sizeof(double)*8); - - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - - px=(ci+t->boff)/((Ntilebase+t->carr[cm].nchunk-1)/t->carr[cm].nchunk); - pm=&(t->p[t->carr[cm].p[px]]); - /* gains for this cluster, for sta1,sta2 */ - G1[0]=(pm[sta1*8])+_Complex_I*(pm[sta1*8+1]); - G1[1]=(pm[sta1*8+2])+_Complex_I*(pm[sta1*8+3]); - G1[2]=(pm[sta1*8+4])+_Complex_I*(pm[sta1*8+5]); - G1[3]=(pm[sta1*8+6])+_Complex_I*(pm[sta1*8+7]); - G2[0]=(pm[sta2*8])+_Complex_I*(pm[sta2*8+1]); - G2[1]=(pm[sta2*8+2])+_Complex_I*(pm[sta2*8+3]); - G2[2]=(pm[sta2*8+4])+_Complex_I*(pm[sta2*8+5]); - G2[3]=(pm[sta2*8+6])+_Complex_I*(pm[sta2*8+7]); - - - /* use pre calculated values */ - C[0]=t->coh[4*M*ci+4*cm]; - C[1]=t->coh[4*M*ci+4*cm+1]; - C[2]=t->coh[4*M*ci+4*cm+2]; - C[3]=t->coh[4*M*ci+4*cm+3]; - - - /* form G1*C*G2' */ - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - - /* add to baseline visibilities */ - t->x[8*ci]+=creal(T2[0]); - t->x[8*ci+1]+=cimag(T2[0]); - t->x[8*ci+2]+=creal(T2[1]); - t->x[8*ci+3]+=cimag(T2[1]); - t->x[8*ci+4]+=creal(T2[2]); - t->x[8*ci+5]+=cimag(T2[2]); - t->x[8*ci+6]+=creal(T2[3]); - t->x[8*ci+7]+=cimag(T2[3]); - } - } - - return NULL; -} - - -/* minimization function (multithreaded) */ -/* p: size ??x1 parameters, not all belong to - this cluster - x: size nx1 data calculated - data: extra info needed */ -static void -mylm_fit_single_pth(double *p, double *x, int m, int n, void *data) { - - me_data_t *dp=(me_data_t*)data; - /* u,v,w : size Nbase*tilesz x 1 x: size Nbase*8*tilesz x 1 */ - /* barr: size Nbase*tilesz x 1 carr: size Mx1 */ - /* pp: size 8*N*M x 1 */ - /* pm: size Mx1 of double */ - - int nth,nth1,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthb0,Nthb; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_base_t *threaddata; - - int Nbase=(dp->Nbase); - int tilesz=(dp->tilesz); - - int Nbase1=Nbase*tilesz; - - /* calculate min baselines a thread can handle */ - //Nthb0=ceil((double)Nbase1/(double)Nt); - Nthb0=(Nbase1+Nt-1)/Nt; - - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((threaddata=(thread_data_base_t*)malloc((size_t)Nt*sizeof(thread_data_base_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr; - threaddata[nth].u=&(dp->u[ci]); - threaddata[nth].v=&(dp->v[ci]); - threaddata[nth].w=&(dp->w[ci]); - threaddata[nth].carr=dp->carr; - threaddata[nth].M=dp->M; - threaddata[nth].x=&(x[8*ci]); - threaddata[nth].N=dp->N; - threaddata[nth].Nbase=Nbase; - threaddata[nth].tilesz=tilesz; - threaddata[nth].p=p; - threaddata[nth].clus=(dp->clus); - threaddata[nth].coh=&(dp->coh[4*(dp->M)*ci]); - - - //printf("thread %d predict data from %d baselines %d\n",nth,8*ci,Nthb); - pthread_create(&th_array[nth],&attr,predict_threadfn_withgain,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - /* now wait for threads to finish */ - for(nth1=0; nth1M); - int Ntilebase=(t->Nbase)*(t->tilesz); - int px; - - for (ci=0; ciNb; ci++) { - /* iterate over the sky model and calculate contribution */ - /* for this x[8*ci:8*(ci+1)-1] */ - memset(&(t->x[8*ci]),0,sizeof(double)*8); - - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - for (cm=0; cm x[0.....Nbase*tilesz/nchunk-1] - p[1] -> x[Nbase*tilesz/nchunk......2*Nbase*tilesz-1] - .... - p[last] -> x[(nchunk-1)*Nbase*tilesz/nchunk......Nbase*tilesz] - - so given bindex, right p[] is bindex/((Nbase*tilesz+nchunk-1)/nchunk) - */ - - px=(ci+t->boff)/((Ntilebase+t->carr[cm].nchunk-1)/t->carr[cm].nchunk); - //pm=&(t->p[cm*8*N]); - pm=&(t->p[t->carr[cm].p[px]]); - G1[0]=(pm[sta1*8])+_Complex_I*(pm[sta1*8+1]); - G1[1]=(pm[sta1*8+2])+_Complex_I*(pm[sta1*8+3]); - G1[2]=(pm[sta1*8+4])+_Complex_I*(pm[sta1*8+5]); - G1[3]=(pm[sta1*8+6])+_Complex_I*(pm[sta1*8+7]); - G2[0]=(pm[sta2*8])+_Complex_I*(pm[sta2*8+1]); - G2[1]=(pm[sta2*8+2])+_Complex_I*(pm[sta2*8+3]); - G2[2]=(pm[sta2*8+4])+_Complex_I*(pm[sta2*8+5]); - G2[3]=(pm[sta2*8+6])+_Complex_I*(pm[sta2*8+7]); - - - /* use pre calculated values */ - C[0]=t->coh[4*M*ci+4*cm]; - C[1]=t->coh[4*M*ci+4*cm+1]; - C[2]=t->coh[4*M*ci+4*cm+2]; - C[3]=t->coh[4*M*ci+4*cm+3]; - - /* form G1*C*G2' */ - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - - /* add to baseline visibilities */ - t->x[8*ci]+=creal(T2[0]); - t->x[8*ci+1]+=cimag(T2[0]); - t->x[8*ci+2]+=creal(T2[1]); - t->x[8*ci+3]+=cimag(T2[1]); - t->x[8*ci+4]+=creal(T2[2]); - t->x[8*ci+5]+=cimag(T2[2]); - t->x[8*ci+6]+=creal(T2[3]); - t->x[8*ci+7]+=cimag(T2[3]); - } - } - } - - return NULL; -} - - -/* minimization function (multithreaded) */ -/* p: size mx1 parameters - x: size nx1 data calculated - data: extra info needed */ -static void -minimize_viz_full_pth(double *p, double *x, int m, int n, void *data) { - - me_data_t *dp=(me_data_t*)data; - /* u,v,w : size Nbase*tilesz x 1 x: size Nbase*8*tilesz x 1 */ - /* barr: size Nbase*tilesz x 1 carr: size Mx1 */ - /* pp: size 8*N*M x 1 */ - /* pm: size Mx1 of double */ - - int nth,nth1,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthb0,Nthb; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_base_t *threaddata; - - int Nbase1=(dp->Nbase)*(dp->tilesz); - - /* calculate min baselines a thread can handle */ - //Nthb0=ceil((double)Nbase1/(double)Nt); - Nthb0=(Nbase1+Nt-1)/Nt; - - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((threaddata=(thread_data_base_t*)malloc((size_t)Nt*sizeof(thread_data_base_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr; - threaddata[nth].u=&(dp->u[ci]); - threaddata[nth].v=&(dp->v[ci]); - threaddata[nth].w=&(dp->w[ci]); - threaddata[nth].carr=dp->carr; - threaddata[nth].M=dp->M; - threaddata[nth].x=&(x[8*ci]); - threaddata[nth].p=p; - threaddata[nth].N=dp->N; - threaddata[nth].Nbase=dp->Nbase; - threaddata[nth].tilesz=dp->tilesz; - threaddata[nth].coh=&(dp->coh[4*(dp->M)*ci]); - - //printf("thread %d predict data from %d baselines %d\n",nth,8*ci,Nthb); - pthread_create(&th_array[nth],&attr,predict_threadfn_withgain_full,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - /* now wait for threads to finish */ - for(nth1=0; nth1w>bw->w) return -1; - if (aw->w==bw->w) return 0; - - return 1; -} - -/* generate a random permutation of given integers */ -/* note: free returned value after use */ -/* n: no of entries, - weighter_iter: if 1, take weight into account - if 0, only generate a random permutation - w: weights (size nx1): sort them in descending order and - give permutation accordingly -*/ -int* -random_permutation(int n, int weighted_iter, double *w) { - int *p; - int i; - if ((p=(int*)malloc((size_t)(n)*sizeof(int)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if (!weighted_iter) { - for (i = 0; i < n; ++i) { - int j = rand() % (i + 1); - p[i] = p[j]; - p[j] = i; - } - } else { - /* we take weight into account */ - w_n *wn_arr; - if ((wn_arr=(w_n*)malloc((size_t)(n)*sizeof(w_n)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - for (i=0; ipline->data); - int tid=td->tid; - - while(1) { - sync_barrier(&(td->pline->gate1)); /* stop at gate 1*/ - if(td->pline->terminate) break; /* if flag is set, break loop */ - sync_barrier(&(td->pline->gate2)); /* stop at gate 2 */ - /* do work */ - //printf("state=%d, thread %d\n",gd->status[tid],tid); - if (gd->status[tid]==PT_DO_WORK_LM || gd->status[tid]==PT_DO_WORK_OSLM - || gd->status[tid]==PT_DO_WORK_RLM) { -/************************* work *********************/ - me_data_t *t=(me_data_t *)gd->lmdata[tid]; - /* divide the tiles into chunks tilesz/nchunk */ - int tilechunk=(t->tilesz+t->carr[t->clus].nchunk-1)/t->carr[t->clus].nchunk; - - - int ci; - - int cj=0; - int ntiles; - double init_res,final_res; - init_res=final_res=0.0; - if (tid<2) { - /* for GPU, the cost func and jacobian are not used */ - /* loop over each chunk, with right parameter set and data set */ - for (ci=0; cicarr[t->clus].nchunk; ci++) { - /* divide the tiles into chunks tilesz/nchunk */ - if (cj+tilechunktilesz) { - ntiles=tilechunk; - } else { - ntiles=t->tilesz-cj; - } - if (gd->status[tid]==PT_DO_WORK_LM) { - clevmar_der_single_cuda(NULL, NULL, &gd->p[tid][ci*(gd->M[tid])], &gd->x[tid][8*cj*t->Nbase], gd->M[tid], 8*ntiles*t->Nbase, gd->itermax[tid], gd->opts[tid], gd->info[tid], gd->cbhandle[tid], gd->solver_handle[tid], gd->gWORK[tid], gd->linsolv, cj, ntiles, (void*)gd->lmdata[tid]); - } else if (gd->status[tid]==PT_DO_WORK_OSLM) { - oslevmar_der_single_cuda(NULL, NULL, &gd->p[tid][ci*(gd->M[tid])], &gd->x[tid][8*cj*t->Nbase], gd->M[tid], 8*ntiles*t->Nbase, gd->itermax[tid], gd->opts[tid], gd->info[tid], gd->cbhandle[tid], gd->solver_handle[tid], gd->gWORK[tid], gd->linsolv, cj, ntiles, gd->randomize, (void*)gd->lmdata[tid]); - } else if (gd->status[tid]==PT_DO_WORK_RLM) { - rlevmar_der_single_cuda(NULL, NULL, &gd->p[tid][ci*(gd->M[tid])], &gd->x[tid][8*cj*t->Nbase], gd->M[tid], 8*ntiles*t->Nbase, gd->itermax[tid], gd->opts[tid], gd->info[tid], gd->cbhandle[tid], gd->solver_handle[tid], gd->gWORK[tid], gd->linsolv, cj, ntiles, gd->nulow,gd->nuhigh, (void*)gd->lmdata[tid]); - } - init_res+=gd->info[tid][0]; - final_res+=gd->info[tid][1]; - cj=cj+tilechunk; - } - - } - - gd->info[tid][0]=init_res; - gd->info[tid][1]=final_res; - -/************************* work *********************/ - } else if (gd->status[tid]==PT_DO_AGPU) { - attach_gpu_to_thread1(select_work_gpu(MAX_GPU_ID,td->pline->thst),&gd->cbhandle[tid],&gd->solver_handle[tid],&gd->gWORK[tid],gd->data_size); - } else if (gd->status[tid]==PT_DO_DGPU) { - detach_gpu_from_thread1(gd->cbhandle[tid],gd->solver_handle[tid],gd->gWORK[tid]); - } else if (gd->status[tid]==PT_DO_MEMRESET) { - reset_gpu_memory(gd->gWORK[tid],gd->data_size); - } - } - return NULL; -} - -/* initialize the pipeline - and start the slaves rolling */ -static void -init_pipeline(th_pipeline *pline, - void *data) -{ - slave_tdata *t0,*t1; - pthread_attr_init(&(pline->attr)); - pthread_attr_setdetachstate(&(pline->attr),PTHREAD_CREATE_JOINABLE); - - init_th_barrier(&(pline->gate1),3); /* 3 threads, including master */ - init_th_barrier(&(pline->gate2),3); /* 3 threads, including master */ - pline->terminate=0; - pline->data=data; /* data should have pointers to t1 and t2 */ - - if ((t0=(slave_tdata*)malloc(sizeof(slave_tdata)))==0) { - fprintf(stderr,"no free memory\n"); - exit(1); - } - if ((t1=(slave_tdata*)malloc(sizeof(slave_tdata)))==0) { - fprintf(stderr,"no free memory\n"); - exit(1); - } - if ((pline->thst=(taskhist*)malloc(sizeof(taskhist)))==0) { - fprintf(stderr,"no free memory\n"); - exit(1); - } - - init_task_hist(pline->thst); - t0->pline=t1->pline=pline; - t0->tid=0; - t1->tid=1; /* link back t1, t2 to data so they could be freed */ - pline->sd0=t0; - pline->sd1=t1; - pthread_create(&(pline->slave0),&(pline->attr),pipeline_slave_code,(void*)t0); - pthread_create(&(pline->slave1),&(pline->attr),pipeline_slave_code,(void*)t1); -} - -/* destroy the pipeline */ -/* need to kill the slaves first */ -static void -destroy_pipeline(th_pipeline *pline) -{ - - pline->terminate=1; - sync_barrier(&(pline->gate1)); - pthread_join(pline->slave0,NULL); - pthread_join(pline->slave1,NULL); - destroy_th_barrier(&(pline->gate1)); - destroy_th_barrier(&(pline->gate2)); - pthread_attr_destroy(&(pline->attr)); - destroy_task_hist(pline->thst); - free(pline->thst); - free(pline->sd0); - free(pline->sd1); - pline->data=NULL; -} - -int -sagefit_visibilities_dual_pt(double *u, double *v, double *w, double *x, int N, - int Nbase, int tilesz, baseline_t *barr, clus_source_t *carr, complex double *coh, int M, int Mt, double freq0, double fdelta, double *pp, double uvmin, int Nt, int max_emiter, int max_iter, int max_lbfgs, int lbfgs_m, int gpu_threads, int linsolv,int solver_mode, double nulow, double nuhigh, int randomize, double *mean_nu, double *res_0, double *res_1) { - /* u,v,w : size Nbase*tilesz x 1 x: size Nbase*8*tilesz x 1 */ - /* barr: size Nbase*tilesz x 1 carr: size Mx1 */ - /* pp: size 8*N*M x 1 */ - /* pm: size Mx1 of double */ - - - int ci,cj; - double *p; // parameters: m x 1 - int m, n; - double opts[CLM_OPTS_SZ], info0[CLM_INFO_SZ], info1[CLM_INFO_SZ]; - me_data_t lmdata0,lmdata1; - int Nbase1; - - double *xdummy0,*xdummy1,*xsub,*xo; - double *nerr; /* array to store cost reduction per cluster */ - double *robust_nuM; - int weighted_iter,this_itermax0,this_itermax1,total_iter; - double total_err; - - /* rearraged memory for GPU use */ - double *ddcoh; - short *ddbase; - - int *cr=0; /* array for random permutation of clusters */ - int c0,c1; - - //opts[0]=LM_INIT_MU; opts[1]=1E-15; opts[2]=1E-15; opts[3]=1E-20; - opts[0]=CLM_INIT_MU; opts[1]=1E-9; opts[2]=1E-9; opts[3]=1E-9; - opts[4]=-CLM_DIFF_DELTA; - - /* no. of parameters >= than the no of clusters*8N */ - m=N*Mt*8; - /* no of data */ - n=Nbase*tilesz*8; - - /* true no of baselines */ - Nbase1=Nbase*tilesz; - -/********* thread data ******************/ - /* barrier */ - th_pipeline tp; - gbdata tpg; -/****************************************/ - - /* use full parameter space */ - p=pp; - lmdata0.clus=lmdata1.clus=-1; - /* setup data for lmfit */ - lmdata0.u=lmdata1.u=u; - lmdata0.v=lmdata1.v=v; - lmdata0.w=lmdata1.w=w; - lmdata0.Nbase=lmdata1.Nbase=Nbase; - lmdata0.tilesz=lmdata1.tilesz=tilesz; - lmdata0.N=lmdata1.N=N; - lmdata0.barr=lmdata1.barr=barr; - lmdata0.carr=lmdata1.carr=carr; - lmdata0.M=lmdata1.M=M; - lmdata0.Mt=lmdata1.Mt=Mt; - lmdata0.freq0=lmdata1.freq0=&freq0; - lmdata0.Nt=lmdata1.Nt=Nt; - lmdata0.coh=lmdata1.coh=coh; - /* rearrange coh for GPU use */ - if ((ddcoh=(double*)calloc((size_t)(M*Nbase1*8),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((ddbase=(short*)calloc((size_t)(Nbase1*2),sizeof(short)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - rearrange_coherencies(Nbase1, barr, coh, ddcoh, ddbase, M, Nt); - lmdata0.ddcoh=lmdata1.ddcoh=ddcoh; - lmdata0.ddbase=lmdata1.ddbase=ddbase; - - if ((xsub=(double*)calloc((size_t)(n),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xo=(double*)calloc((size_t)(n),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xdummy0=(double*)calloc((size_t)(n),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xdummy1=(double*)calloc((size_t)(n),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((nerr=(double*)calloc((size_t)(M),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if (solver_mode==2) { - if ((robust_nuM=(double*)calloc((size_t)(M),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - } else { - robust_nuM=0; - } - /* starting guess of robust nu */ - double robust_nu0=nulow; - - /* remember for each partition how much the cost function decreases - in the next EM iteration, we allocate more LM iters to partitions - where const function significantly decreases. So two stages - 1) equal LM iters (find the decrease) 2) weighted LM iters */ - weighted_iter=0; - total_iter=M*max_iter; /* total iterations per EM */ -/********** setup threads *******************************/ - init_pipeline(&tp,&tpg); - sync_barrier(&(tp.gate1)); /* sync at gate 1*/ - tpg.status[0]=tpg.status[1]=PT_DO_AGPU; - /* also calculate the total storage needed to be allocated on a GPU */ - /* determine total size for memory allocation */ - int Mm=8*N; - int64_t data_sz=0; - if (solver_mode==0 || solver_mode==1) { - /* size for LM */ - data_sz=(int64_t)(n+Mm*n+Mm*Mm+Mm+Mm*Mm+Mm+Mm+Mm+Mm+Nbase1*8+n+n)*sizeof(double)+(int64_t)Nbase1*2*sizeof(short); - } else if (solver_mode==2) { - /* size for ROBUSTLM */ - data_sz=(int64_t)(n+Mm*n+Mm*Mm+Mm+Mm*Mm+Mm+Mm+Mm+Mm+Nbase1*8+n+n+n+n)*sizeof(double)+(int64_t)Nbase1*2*sizeof(short); - } else { - fprintf(stderr,"%s: %d: invalid mode for solver\n",__FILE__,__LINE__); - exit(1); - } - if (linsolv==1) { - data_sz+=(int64_t)Mm*sizeof(double); - } else if (linsolv==2) { - data_sz+=(int64_t)(Mm*Mm+Mm*Mm+Mm)*sizeof(double); - } - tpg.data_size=data_sz; - tpg.nulow=nulow; - tpg.nuhigh=nuhigh; - tpg.randomize=randomize; - sync_barrier(&(tp.gate2)); /* sync at gate 2*/ - - sync_barrier(&(tp.gate1)); /* sync at gate 1*/ - tpg.status[0]=tpg.status[1]=PT_DO_NOTHING; - sync_barrier(&(tp.gate2)); /* sync at gate 2*/ - -/********** done setup threads *******************************/ - - /* initial residual calculation - subtract full model from data */ - minimize_viz_full_pth(p, xsub, m, n, (void*)&lmdata0); - memcpy(xo,x,(size_t)(n)*sizeof(double)); - my_daxpy(n, xsub, -1.0, xo); - *res_0=my_dnrm2(n,xo)/(double)n; - - for (ci=0; ci0 || this_itermax1>0) { - /* calculate contribution from hidden data, subtract from x */ - /* since x has already subtracted this model, just add - the ones we are solving for */ - - sync_barrier(&(tp.gate1)); /* sync at gate 1 */ - memcpy(xdummy0,xo,(size_t)(n)*sizeof(double)); - memcpy(xdummy1,xo,(size_t)(n)*sizeof(double)); - lmdata0.clus=c0; - lmdata1.clus=c1; - - /* NOTE: conditional mean x^i = s^i + 0.5 * residual^i */ - /* so xdummy=0.5 ( 2*model + residual ) */ - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata0); - my_daxpy(n, xsub, 2.0, xdummy0); - my_dscal(n, 0.5, xdummy0); - my_daxpy(n, xsub, 1.0, xo); - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata1); - my_daxpy(n, xsub, 2.0, xdummy1); - my_dscal(n, 0.5, xdummy1); - my_daxpy(n, xsub, 1.0, xo); -/**************************************************************************/ - /* run this from a separate thread */ - tpg.p[0]=&p[carr[c0].p[0]]; - tpg.x[0]=xdummy0; - tpg.M[0]=8*N; /* even though size of p is > M, dont change this */ - tpg.N[0]=n; /* Nbase*tilesz*8 */ - tpg.itermax[0]=this_itermax0; - tpg.opts[0]=opts; - tpg.info[0]=info0; - tpg.linsolv=linsolv; - tpg.lmdata[0]=&lmdata0; - - tpg.p[1]=&p[carr[c1].p[0]]; - tpg.x[1]=xdummy1; - tpg.M[1]=8*N; /* even though size of p is > M, dont change this */ - tpg.N[1]=n; /* Nbase*tilesz*8 */ - tpg.itermax[1]=this_itermax1; - tpg.opts[1]=opts; - tpg.info[1]=info1; - tpg.linsolv=linsolv; - tpg.lmdata[1]=&lmdata1; -/**************************************************************************/ - - /* both threads do work */ - /* if solver_mode>0 last EM iteration full LM, the rest OS LM */ - if (!solver_mode) { - if (ci==max_emiter-1) { - tpg.status[0]=tpg.status[1]=PT_DO_WORK_LM; - } else { - tpg.status[0]=tpg.status[1]=PT_DO_WORK_OSLM; - } - } else if (solver_mode==1) { - /* normal LM */ - tpg.status[0]=tpg.status[1]=PT_DO_WORK_LM; - } else if (solver_mode==2) { - /* last EM iteration robust LM, the rest OS LM */ - if (ci==max_emiter-1) { - lmdata0.robust_nu=lmdata1.robust_nu=robust_nu0; /* initial robust nu */ - tpg.status[0]=tpg.status[1]=PT_DO_WORK_RLM; - } else { - tpg.status[0]=tpg.status[1]=PT_DO_WORK_OSLM; - } - } - sync_barrier(&(tp.gate2)); /* sync at gate 2 */ - sync_barrier(&(tp.gate1)); /* sync at gate 1 */ - tpg.status[0]=tpg.status[1]=PT_DO_NOTHING; - sync_barrier(&(tp.gate2)); /* sync at gate 2 */ - nerr[c0]=(info0[0]-info0[1])/info0[0]; - nerr[c1]=(info1[0]-info1[1])/info1[0]; - /* update robust_nu */ - if (solver_mode==2 && (ci==max_emiter-1)) { - robust_nuM[c0]+=lmdata0.robust_nu; - robust_nuM[c1]+=lmdata1.robust_nu; - } - - /* once again subtract solved model from data */ - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata0); - my_daxpy(n, xsub, -1.0, xo); - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata1); - my_daxpy(n, xsub, -1.0, xo); - - } - } - /* odd cluster out, if M is odd */ - if (M%2) { - if (randomize) { - c0=cr[M-1]; - } else { - c0=M-1; - } - /* calculate max LM iter for this cluster */ - if (weighted_iter) { - this_itermax0=(int)floor((0.33*nerr[c0]+0.66/(double)M)*((double)total_iter)); - } else { - this_itermax0=max_iter; - } - //printf("Cluster %d(iter=%d, wt=%lf)\n",c0,this_itermax0,nerr[c0]); - if (this_itermax0>0) { - /* calculate contribution from hidden data, subtract from x */ - memcpy(xdummy0,xo,(size_t)(n)*sizeof(double)); - lmdata0.clus=c0; - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata0); - my_daxpy(n, xsub, 1.0, xdummy0); - my_daxpy(n, xsub, 1.0, xo); - -/**************************************************************************/ - sync_barrier(&(tp.gate1)); /* sync at gate 1 */ - /* run this from a separate thread */ - tpg.p[0]=&p[carr[c0].p[0]]; - tpg.x[0]=xdummy0; - tpg.M[0]=8*N; - tpg.N[0]=n; - tpg.itermax[0]=this_itermax0; - tpg.opts[0]=opts; - tpg.info[0]=info0; - tpg.linsolv=linsolv; - tpg.lmdata[0]=&lmdata0; - - if (!solver_mode) { - if (ci==max_emiter-1) { - tpg.status[0]=PT_DO_WORK_LM; - } else { - tpg.status[0]=PT_DO_WORK_OSLM; - } - } else if (solver_mode==1) { - tpg.status[0]=PT_DO_WORK_LM; - } else if (solver_mode==2) { - if (ci==max_emiter-1) { - lmdata0.robust_nu=robust_nu0; /* initial robust nu */ - tpg.status[0]=PT_DO_WORK_RLM; - } else { - tpg.status[0]=PT_DO_WORK_OSLM; - } - } - tpg.status[1]=PT_DO_NOTHING; - sync_barrier(&(tp.gate2)); /* sync at gate 2 */ -/**************************************************************************/ - sync_barrier(&(tp.gate1)); /* sync at gate 1 */ - tpg.status[0]=tpg.status[1]=PT_DO_NOTHING; - sync_barrier(&(tp.gate2)); /* sync at gate 2 */ - - nerr[c0]=(info0[0]-info0[1])/info0[0]; - /* update robust_nu */ - if (solver_mode==2 && (ci==max_emiter-1)) { - robust_nuM[c0]+=lmdata0.robust_nu; - } - /* once again subtract solved model from data */ - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata0); - my_daxpy(n, xsub, -1.0, xo); - } - } - /* normalize nerr array so that the sum is 1 */ - total_err=my_dasum(M,nerr); - my_dscal(M, 1.0/total_err, nerr); - if (randomize && M>1) { - /* flip weighting flag */ - weighted_iter=!weighted_iter; - free(cr); - } - /**************** End EM iteration ***********************/ - } - free(nerr); - free(xo); - free(xdummy0); - free(xdummy1); - free(ddcoh); - free(ddbase); - - if (solver_mode==2) { - /* calculate mean robust_nu over all clusters */ - robust_nu0=my_dasum(M,robust_nuM)/(double)M; -#ifdef DEBUG - for (ci=0; cinuhigh) { - robust_nu0=nuhigh; - } - } - - /******** free threads ***************/ - sync_barrier(&(tp.gate1)); /* sync at gate 1*/ - tpg.status[0]=tpg.status[1]=PT_DO_DGPU; - sync_barrier(&(tp.gate2)); /* sync at gate 2*/ - - - destroy_pipeline(&tp); - /******** done free threads ***************/ - - if (max_lbfgs>0) { - /* use LBFGS */ - if (solver_mode==2) { - lmdata0.robust_nu=robust_nu0; - lbfgs_fit_robust_cuda(minimize_viz_full_pth, p, x, m, n, max_lbfgs, lbfgs_m, gpu_threads, (void*)&lmdata0); - /* also print robust nu to output */ - } else { - lbfgs_fit(minimize_viz_full_pth, p, x, m, n, max_lbfgs, lbfgs_m, gpu_threads, (void*)&lmdata0); - } - } - - - /* final residual calculation */ - minimize_viz_full_pth(p, xsub, m, n, (void*)&lmdata0); - my_daxpy(n, xsub, -1.0, x); - - *mean_nu=robust_nu0; - *res_1=my_dnrm2(n,x)/(double)n; - - free(xsub); - - /* if final residual > initial residual, - return -1, else 0 - */ - if (*res_1>*res_0) { - return -1; - } - return 0; -} - - - - -/*************************** 1 GPU version *********************************/ -/* slave thread 1GPU function */ -static void * -pipeline_slave_code_one_gpu(void *data) -{ - slave_tdata *td=(slave_tdata*)data; - gbdata *gd=(gbdata*)(td->pline->data); - int tid=td->tid; - - while(1) { - sync_barrier(&(td->pline->gate1)); /* stop at gate 1*/ - if(td->pline->terminate) break; /* if flag is set, break loop */ - sync_barrier(&(td->pline->gate2)); /* stop at gate 2 */ - /* do work */ - //printf("state=%d, thread %d\n",gd->status[tid],tid); - if (gd->status[tid]==PT_DO_WORK_LM || gd->status[tid]==PT_DO_WORK_OSLM - || gd->status[tid]==PT_DO_WORK_RLM || gd->status[tid]==PT_DO_WORK_OSRLM) { -/************************* work *********************/ - me_data_t *t=(me_data_t *)gd->lmdata[tid]; - /* divide the tiles into chunks tilesz/nchunk */ - int tilechunk=(t->tilesz+t->carr[t->clus].nchunk-1)/t->carr[t->clus].nchunk; - - - int ci; - - int cj=0; - int ntiles; - double init_res,final_res; - init_res=final_res=0.0; - if (tid<2) { - /* for GPU, the cost func and jacobian are not used */ - /* loop over each chunk, with right parameter set and data set */ - for (ci=0; cicarr[t->clus].nchunk; ci++) { - /* divide the tiles into chunks tilesz/nchunk */ - if (cj+tilechunktilesz) { - ntiles=tilechunk; - } else { - ntiles=t->tilesz-cj; - } - - if (gd->status[tid]==PT_DO_WORK_LM) { - clevmar_der_single_cuda(NULL, NULL, &gd->p[tid][ci*(gd->M[tid])], &gd->x[tid][8*cj*t->Nbase], gd->M[tid], 8*ntiles*t->Nbase, gd->itermax[tid], gd->opts[tid], gd->info[tid], gd->cbhandle[tid], gd->solver_handle[tid], gd->gWORK[tid], gd->linsolv, cj, ntiles, (void*)gd->lmdata[tid]); - } else if (gd->status[tid]==PT_DO_WORK_OSLM) { - oslevmar_der_single_cuda(NULL, NULL, &gd->p[tid][ci*(gd->M[tid])], &gd->x[tid][8*cj*t->Nbase], gd->M[tid], 8*ntiles*t->Nbase, gd->itermax[tid], gd->opts[tid], gd->info[tid], gd->cbhandle[tid], gd->solver_handle[tid], gd->gWORK[tid], gd->linsolv, cj, ntiles, gd->randomize, (void*)gd->lmdata[tid]); - } else if (gd->status[tid]==PT_DO_WORK_RLM || gd->status[tid]==PT_DO_WORK_OSRLM) { - rlevmar_der_single_cuda(NULL, NULL, &gd->p[tid][ci*(gd->M[tid])], &gd->x[tid][8*cj*t->Nbase], gd->M[tid], 8*ntiles*t->Nbase, gd->itermax[tid], gd->opts[tid], gd->info[tid], gd->cbhandle[tid], gd->solver_handle[tid], gd->gWORK[tid], gd->linsolv, cj, ntiles, gd->nulow, gd->nuhigh, (void*)gd->lmdata[tid]); - } - init_res+=gd->info[tid][0]; - final_res+=gd->info[tid][1]; - cj=cj+tilechunk; - } - - } - - gd->info[tid][0]=init_res; - gd->info[tid][1]=final_res; - -/************************* work *********************/ - } else if (gd->status[tid]==PT_DO_AGPU) { - attach_gpu_to_thread1(select_work_gpu(MAX_GPU_ID,td->pline->thst),&gd->cbhandle[tid],&gd->solver_handle[tid],&gd->gWORK[tid],gd->data_size); - } else if (gd->status[tid]==PT_DO_DGPU) { - detach_gpu_from_thread1(gd->cbhandle[tid],gd->solver_handle[tid],gd->gWORK[tid]); - } else if (gd->status[tid]==PT_DO_MEMRESET) { - reset_gpu_memory(gd->gWORK[tid],gd->data_size); - } - } - return NULL; -} - -/* initialize the pipeline - and start the slaves rolling */ -static void -init_pipeline_one_gpu(th_pipeline *pline, - void *data) -{ - slave_tdata *t0; - pthread_attr_init(&(pline->attr)); - pthread_attr_setdetachstate(&(pline->attr),PTHREAD_CREATE_JOINABLE); - - init_th_barrier(&(pline->gate1),2); /* 2 threads, including master */ - init_th_barrier(&(pline->gate2),2); /* 2 threads, including master */ - pline->terminate=0; - pline->data=data; /* data should have pointers to t1 */ - - if ((t0=(slave_tdata*)malloc(sizeof(slave_tdata)))==0) { - fprintf(stderr,"no free memory\n"); - exit(1); - } - if ((pline->thst=(taskhist*)malloc(sizeof(taskhist)))==0) { - fprintf(stderr,"no free memory\n"); - exit(1); - } - - init_task_hist(pline->thst); - t0->pline=pline; - t0->tid=0; - pline->sd0=t0; - pthread_create(&(pline->slave0),&(pline->attr),pipeline_slave_code_one_gpu,(void*)t0); -} - -/* destroy the pipeline */ -/* need to kill the slaves first */ -static void -destroy_pipeline_one_gpu(th_pipeline *pline) -{ - - pline->terminate=1; - sync_barrier(&(pline->gate1)); - pthread_join(pline->slave0,NULL); - destroy_th_barrier(&(pline->gate1)); - destroy_th_barrier(&(pline->gate2)); - pthread_attr_destroy(&(pline->attr)); - destroy_task_hist(pline->thst); - free(pline->thst); - free(pline->sd0); - pline->data=NULL; -} - -int -sagefit_visibilities_dual_pt_one_gpu(double *u, double *v, double *w, double *x, int N, - int Nbase, int tilesz, baseline_t *barr, clus_source_t *carr, complex double *coh, int M, int Mt, double freq0, double fdelta, double *pp, double uvmin, int Nt, int max_emiter, int max_iter, int max_lbfgs, int lbfgs_m, int gpu_threads, int linsolv, int solver_mode, double nulow, double nuhigh, int randomize, double *mean_nu, double *res_0, double *res_1) { - int ci,cj; - double *p; // parameters: m x 1 - int m, n; - double opts[CLM_OPTS_SZ], info[CLM_INFO_SZ]; - me_data_t lmdata; - - double *xdummy,*xsub; - double *nerr; /* array to store cost reduction per cluster */ - double *robust_nuM; - int weighted_iter,this_itermax,total_iter; - double total_err; - - double init_res,final_res; - - opts[0]=CLM_INIT_MU; opts[1]=1E-15; opts[2]=1E-15; opts[3]=1E-20; - opts[4]=-CLM_DIFF_DELTA; - - /* no. of true parameters */ - m=N*Mt*8; - /* no of data */ - n=Nbase*tilesz*8; - - /* rearraged memory for GPU use */ - double *ddcoh; - short *ddbase; - -/********* thread data ******************/ - /* barrier */ - th_pipeline tp; - gbdata tpg; -/****************************************/ - - /* use full parameter space */ - p=pp; - lmdata.clus=-1; - /* setup data for lmfit */ - lmdata.u=u; - lmdata.v=v; - lmdata.w=w; - lmdata.Nbase=Nbase; - lmdata.tilesz=tilesz; - lmdata.N=N; - lmdata.barr=barr; - lmdata.carr=carr; - lmdata.M=M; - lmdata.Mt=Mt; - lmdata.freq0=&freq0; - lmdata.Nt=Nt; - lmdata.coh=coh; - - if ((xsub=(double*)calloc((size_t)(n),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xdummy=(double*)calloc((size_t)(n),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((nerr=(double*)calloc((size_t)(M),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if (solver_mode==2||solver_mode==3) { - if ((robust_nuM=(double*)calloc((size_t)(M),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - } else { - robust_nuM=0; - } - /* starting guess of robust nu */ - double robust_nu0=nulow; - - int Nbase1=Nbase*tilesz; - /* rearrange coh for GPU use */ - if ((ddcoh=(double*)calloc((size_t)(M*Nbase1*8),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((ddbase=(short*)calloc((size_t)(Nbase1*2),sizeof(short)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - rearrange_coherencies(Nbase1, barr, coh, ddcoh, ddbase, M, Nt); - lmdata.ddcoh=ddcoh; - lmdata.ddbase=ddbase; - - init_pipeline_one_gpu(&tp,&tpg); - sync_barrier(&(tp.gate1)); /* sync at gate 1*/ - tpg.status[0]=PT_DO_AGPU; - -/************ setup GPU *********************/ - int64_t data_sz=0; - int Mm=8*N; - if (solver_mode==0 || solver_mode==1) { - /* size for LM */ - data_sz=(int64_t)(n+Mm*n+Mm*Mm+Mm+Mm*Mm+Mm+Mm+Mm+Mm+Nbase1*8+n+n)*sizeof(double)+(int64_t)Nbase1*2*sizeof(short); - } else if (solver_mode==2||solver_mode==3) { - /* size for ROBUSTLM */ - data_sz=(int64_t)(n+Mm*n+Mm*Mm+Mm+Mm*Mm+Mm+Mm+Mm+Mm+Nbase1*8+n+n+n+n)*sizeof(double)+(int64_t)Nbase1*2*sizeof(short); - } else { - fprintf(stderr,"%s: %d: invalid mode for solver\n",__FILE__,__LINE__); - exit(1); - } - - if (linsolv==1) { - data_sz+=(int64_t)Mm*sizeof(double); - } else if (linsolv==2) { - data_sz+=(int64_t)(Mm*Mm+Mm*Mm+Mm)*sizeof(double); - } - - - tpg.data_size=data_sz; - tpg.nulow=nulow; - tpg.nuhigh=nuhigh; - tpg.randomize=randomize; - sync_barrier(&(tp.gate2)); /* sync at gate 2*/ - - sync_barrier(&(tp.gate1)); /* sync at gate 1*/ - tpg.status[0]=PT_DO_NOTHING; - sync_barrier(&(tp.gate2)); /* sync at gate 2*/ - -/************ done setup GPU *********************/ - - /* remember for each partition how much the cost function decreases - in the next EM iteration, we allocate more LM iters to partitions - where const function significantly decreases. So two stages - 1) equal LM iters (find the decrease) 2) weighted LM iters */ - weighted_iter=0; - total_iter=M*max_iter; /* total iterations per EM */ - /* calculate current model and subtract from data */ - minimize_viz_full_pth(p, xsub, m, n, (void*)&lmdata); - memcpy(xdummy,x,(size_t)(n)*sizeof(double)); - my_daxpy(n, xsub, -1.0, xdummy); - *res_0=my_dnrm2(n,xdummy)/(double)n; - - for (ci=0; ci0) { - sync_barrier(&(tp.gate1)); /* sync at gate 1 */ - /* calculate contribution from hidden data, subtract from x - actually, add the current model for this cluster to residual */ - lmdata.clus=cj; - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata); - my_daxpy(n, xsub, 1.0, xdummy); - - /* run this from a separate thread */ - tpg.p[0]=&p[carr[cj].p[0]]; - tpg.x[0]=xdummy; - tpg.M[0]=8*N; /* even though size of p is > M, dont change this */ - tpg.N[0]=n; /* Nbase*tilesz*8 */ - tpg.itermax[0]=this_itermax; - tpg.opts[0]=opts; - tpg.info[0]=info; - tpg.linsolv=linsolv; - tpg.lmdata[0]=&lmdata; - - /* if solver_mode>0 last EM iteration full LM, the rest OS LM */ - if (!solver_mode) { - if (ci==max_emiter-1) { - tpg.status[0]=PT_DO_WORK_LM; - } else { - tpg.status[0]=PT_DO_WORK_OSLM; - } - } else if (solver_mode==1) { - tpg.status[0]=PT_DO_WORK_LM; - } else if (solver_mode==2) { - if (ci==max_emiter-1) { - lmdata.robust_nu=robust_nu0; /* initial robust nu */ - tpg.status[0]=PT_DO_WORK_RLM; - } else { - tpg.status[0]=PT_DO_WORK_OSLM; - } - } - sync_barrier(&(tp.gate2)); /* sync at gate 2 */ - sync_barrier(&(tp.gate1)); /* sync at gate 1 */ - tpg.status[0]=tpg.status[1]=PT_DO_NOTHING; - sync_barrier(&(tp.gate2)); /* sync at gate 2 */ - - init_res=info[0]; - final_res=info[1]; - - nerr[cj]=(init_res-final_res)/init_res; - /* update robust_nu */ - if ((solver_mode==2 || solver_mode==3) && (ci==max_emiter-1)) { - robust_nuM[cj]+=lmdata.robust_nu; - } - - /* subtract current model */ - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata); - my_daxpy(n, xsub, -1.0, xdummy); - } - } - - /* normalize nerr array so that the sum is 1 */ - total_err=my_dasum(M,nerr); - my_dscal(M, 1.0/total_err, nerr); - - /* flip weighting flag */ - if (M>1) { - weighted_iter=!weighted_iter; - } - } - free(nerr); - free(xdummy); - free(ddcoh); - free(ddbase); - if (solver_mode==2 ||solver_mode==3) { - /* calculate mean robust_nu over all clusters */ - robust_nu0=my_dasum(M,robust_nuM)/(double)M; -#ifdef DEBUG - for (ci=0; cinuhigh) { - robust_nu0=nuhigh; - } - - } - - /******** free threads ***************/ - sync_barrier(&(tp.gate1)); /* sync at gate 1*/ - tpg.status[0]=PT_DO_DGPU; - sync_barrier(&(tp.gate2)); /* sync at gate 2*/ - - destroy_pipeline_one_gpu(&tp); - /******** done free threads ***************/ - - - if (max_lbfgs>0) { - /* use LBFGS */ - if (solver_mode==2 || solver_mode==3) { - lmdata.robust_nu=robust_nu0; - lbfgs_fit_robust_cuda(minimize_viz_full_pth, p, x, m, n, max_lbfgs, lbfgs_m, gpu_threads, (void*)&lmdata); - } else { - lbfgs_fit(minimize_viz_full_pth, p, x, m, n, max_lbfgs, lbfgs_m, gpu_threads, (void*)&lmdata); - } - } - /* final residual calculation */ - minimize_viz_full_pth(p, xsub, m, n, (void*)&lmdata); - my_daxpy(n, xsub, -1.0, x); - - *mean_nu=robust_nu0; - *res_1=my_dnrm2(n,x)/(double)n; - - free(xsub); - - /* if final residual > initial residual, - return -1, else 0 - */ - if (*res_1>*res_0) { - return -1; - } - - return 0; -} - - -int -bfgsfit_visibilities_gpu(double *u, double *v, double *w, double *x, int N, - int Nbase, int tilesz, baseline_t *barr, clus_source_t *carr, complex double *coh, int M, int Mt, double freq0, double fdelta, double *pp, double uvmin, int Nt, int max_lbfgs, int lbfgs_m, int gpu_threads, int solver_mode, double mean_nu, double *res_0, double *res_1) { - double *p; // parameters: m x 1 - int m, n; - me_data_t lmdata; - - double *xdummy,*xsub; - - /* no. of true parameters */ - m=N*Mt*8; - /* no of data */ - n=Nbase*tilesz*8; - - /* use full parameter space */ - p=pp; - lmdata.clus=-1; - /* setup data for lmfit */ - lmdata.u=u; - lmdata.v=v; - lmdata.w=w; - lmdata.Nbase=Nbase; - lmdata.tilesz=tilesz; - lmdata.N=N; - lmdata.barr=barr; - lmdata.carr=carr; - lmdata.M=M; - lmdata.Mt=Mt; - lmdata.freq0=&freq0; - lmdata.Nt=Nt; - lmdata.coh=coh; - - if ((xsub=(double*)calloc((size_t)(n),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xdummy=(double*)calloc((size_t)(n),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - - /* calculate current model and subtract from data */ - minimize_viz_full_pth(p, xsub, m, n, (void*)&lmdata); - memcpy(xdummy,x,(size_t)(n)*sizeof(double)); - my_daxpy(n, xsub, -1.0, xdummy); - *res_0=my_dnrm2(n,xdummy)/(double)n; - - - if (max_lbfgs>0) { - /* use LBFGS */ - if (solver_mode==2 || solver_mode==3) { - lmdata.robust_nu=mean_nu; - lbfgs_fit_robust_cuda(minimize_viz_full_pth, p, x, m, n, max_lbfgs, lbfgs_m, gpu_threads, (void*)&lmdata); - } else { - lbfgs_fit(minimize_viz_full_pth, p, x, m, n, max_lbfgs, lbfgs_m, gpu_threads, (void*)&lmdata); - } - } - /* final residual calculation */ - minimize_viz_full_pth(p, xsub, m, n, (void*)&lmdata); - my_daxpy(n, xsub, -1.0, x); - - *res_1=my_dnrm2(n,x)/(double)n; - - free(xsub); - free(xdummy); - - /* if final residual > initial residual, - return -1, else 0 - */ - if (*res_1>*res_0) { - return -1; - } - - return 0; -} - - - - -#ifdef HYBRID_CODE -/****************************************************************************/ -/*************************** hybrid implementation **************************/ -/* slave thread 2GPU function */ -static void * -pipeline_slave_code_flt(void *data) -{ - slave_tdata *td=(slave_tdata*)data; - gbdatafl *gd=(gbdatafl*)(td->pline->data); - int tid=td->tid; - - while(1) { - sync_barrier(&(td->pline->gate1)); /* stop at gate 1*/ - if(td->pline->terminate) break; /* if flag is set, break loop */ - sync_barrier(&(td->pline->gate2)); /* stop at gate 2 */ - /* do work */ - //printf("state=%d, thread %d\n",gd->status[tid],tid); - if (gd->status[tid]==PT_DO_WORK_LM || gd->status[tid]==PT_DO_WORK_OSLM - || gd->status[tid]==PT_DO_WORK_RLM || gd->status[tid]==PT_DO_WORK_OSRLM - || gd->status[tid]==PT_DO_WORK_RTR || gd->status[tid]==PT_DO_WORK_RRTR || gd->status[tid]==PT_DO_WORK_NSD) { -/************************* work *********************/ - me_data_t *t=(me_data_t *)gd->lmdata[tid]; - /* divide the tiles into chunks tilesz/nchunk */ - int tilechunk=(t->tilesz+t->carr[t->clus].nchunk-1)/t->carr[t->clus].nchunk; - - - int ci; - - int cj=0; - int ntiles; - double init_res,final_res; - init_res=final_res=0.0; - if (tid<2) { - /* for GPU, the cost func and jacobian are not used */ - /* loop over each chunk, with right parameter set and data set */ - for (ci=0; cicarr[t->clus].nchunk; ci++) { - /* divide the tiles into chunks tilesz/nchunk */ - if (cj+tilechunktilesz) { - ntiles=tilechunk; - } else { - ntiles=t->tilesz-cj; - } - - if (gd->status[tid]==PT_DO_WORK_LM) { - clevmar_der_single_cuda_fl(&gd->p[tid][ci*(gd->M[tid])], &gd->x[tid][8*cj*t->Nbase], gd->M[tid], 8*ntiles*t->Nbase, gd->itermax[tid], gd->opts[tid], gd->info[tid], gd->cbhandle[tid], gd->solver_handle[tid], gd->gWORK[tid], gd->linsolv, cj, ntiles, (void*)gd->lmdata[tid]); - } else if (gd->status[tid]==PT_DO_WORK_OSLM) { - oslevmar_der_single_cuda_fl(&gd->p[tid][ci*(gd->M[tid])], &gd->x[tid][8*cj*t->Nbase], gd->M[tid], 8*ntiles*t->Nbase, gd->itermax[tid], gd->opts[tid], gd->info[tid], gd->cbhandle[tid], gd->solver_handle[tid], gd->gWORK[tid], gd->linsolv, cj, ntiles, gd->randomize, (void*)gd->lmdata[tid]); - } else if (gd->status[tid]==PT_DO_WORK_RLM) { - rlevmar_der_single_cuda_fl(&gd->p[tid][ci*(gd->M[tid])], &gd->x[tid][8*cj*t->Nbase], gd->M[tid], 8*ntiles*t->Nbase, gd->itermax[tid], gd->opts[tid], gd->info[tid], gd->cbhandle[tid], gd->solver_handle[tid], gd->gWORK[tid], gd->linsolv, cj, ntiles, gd->nulow,gd->nuhigh,(void*)gd->lmdata[tid]); - } else if (gd->status[tid]==PT_DO_WORK_OSRLM) { - osrlevmar_der_single_cuda_fl(&gd->p[tid][ci*(gd->M[tid])], &gd->x[tid][8*cj*t->Nbase], gd->M[tid], 8*ntiles*t->Nbase, gd->itermax[tid], gd->opts[tid], gd->info[tid], gd->cbhandle[tid], gd->solver_handle[tid], gd->gWORK[tid], gd->linsolv, cj, ntiles, gd->nulow,gd->nuhigh,gd->randomize,(void*)gd->lmdata[tid]); - } else if (gd->status[tid]==PT_DO_WORK_RTR) { - /* note stations: M/8, baselines ntiles*Nbase RSD+RTR */ - float Delta0=0.01f; /* use very small value because previous LM has already made the solution close to true value */ - /* storage: see function header - */ - rtr_solve_cuda_fl(&gd->p[tid][ci*(gd->M[tid])], &gd->x[tid][8*cj*t->Nbase], gd->M[tid]/8, ntiles*t->Nbase, gd->itermax[tid]+5, gd->itermax[tid]+10, Delta0, Delta0*0.125f, gd->info[tid], gd->cbhandle[tid], gd->solver_handle[tid], cj, ntiles, (void*)gd->lmdata[tid]); - } else if (gd->status[tid]==PT_DO_WORK_RRTR) { - float Delta0=0.01f; - rtr_solve_cuda_robust_fl(&gd->p[tid][ci*(gd->M[tid])], &gd->x[tid][8*cj*t->Nbase], gd->M[tid]/8, ntiles*t->Nbase, gd->itermax[tid]+5, gd->itermax[tid]+10, Delta0, Delta0*0.125f, gd->nulow, gd->nuhigh, gd->info[tid], gd->cbhandle[tid], gd->solver_handle[tid], cj, ntiles, (void*)gd->lmdata[tid]); - } else if (gd->status[tid]==PT_DO_WORK_NSD) { - nsd_solve_cuda_robust_fl(&gd->p[tid][ci*(gd->M[tid])], &gd->x[tid][8*cj*t->Nbase], gd->M[tid]/8, ntiles*t->Nbase, gd->itermax[tid]+15, gd->nulow, gd->nuhigh, gd->info[tid], gd->cbhandle[tid], gd->solver_handle[tid], cj, ntiles, (void*)gd->lmdata[tid]); - } - init_res+=gd->info[tid][0]; - final_res+=gd->info[tid][1]; - cj=cj+tilechunk; - } - - } - - gd->info[tid][0]=init_res; - gd->info[tid][1]=final_res; - -/************************* work *********************/ - } else if (gd->status[tid]==PT_DO_AGPU) { - /* also enable cula : 1 at end */ - attach_gpu_to_thread2(select_work_gpu(MAX_GPU_ID,td->pline->thst),&gd->cbhandle[tid],&gd->solver_handle[tid],&gd->gWORK[tid],gd->data_size,1); - } else if (gd->status[tid]==PT_DO_DGPU) { - detach_gpu_from_thread2(gd->cbhandle[tid],gd->solver_handle[tid],gd->gWORK[tid],1); - } else if (gd->status[tid]==PT_DO_MEMRESET) { - reset_gpu_memory((double*)gd->gWORK[tid],gd->data_size); - } else if (gd->status[tid]!=PT_DO_NOTHING) { /* catch error */ - fprintf(stderr,"%s: %d: invalid mode for slave tid=%d status=%d\n",__FILE__,__LINE__,tid,gd->status[tid]); - exit(1); - } - } - return NULL; -} - -/* initialize the pipeline - and start the slaves rolling */ -static void -init_pipeline_flt(th_pipeline *pline, - void *data) -{ - slave_tdata *t0,*t1; - pthread_attr_init(&(pline->attr)); - pthread_attr_setdetachstate(&(pline->attr),PTHREAD_CREATE_JOINABLE); - - init_th_barrier(&(pline->gate1),3); /* 3 threads, including master */ - init_th_barrier(&(pline->gate2),3); /* 3 threads, including master */ - pline->terminate=0; - pline->data=data; /* data should have pointers to t1 and t2 */ - - if ((t0=(slave_tdata*)malloc(sizeof(slave_tdata)))==0) { - fprintf(stderr,"no free memory\n"); - exit(1); - } - if ((t1=(slave_tdata*)malloc(sizeof(slave_tdata)))==0) { - fprintf(stderr,"no free memory\n"); - exit(1); - } - if ((pline->thst=(taskhist*)malloc(sizeof(taskhist)))==0) { - fprintf(stderr,"no free memory\n"); - exit(1); - } - - init_task_hist(pline->thst); - t0->pline=t1->pline=pline; - t0->tid=0; - t1->tid=1; /* link back t1, t2 to data so they could be freed */ - pline->sd0=t0; - pline->sd1=t1; - pthread_create(&(pline->slave0),&(pline->attr),pipeline_slave_code_flt,(void*)t0); - pthread_create(&(pline->slave1),&(pline->attr),pipeline_slave_code_flt,(void*)t1); -} - -/* destroy the pipeline */ -/* need to kill the slaves first */ -static void -destroy_pipeline_flt(th_pipeline *pline) -{ - - pline->terminate=1; - sync_barrier(&(pline->gate1)); - pthread_join(pline->slave0,NULL); - pthread_join(pline->slave1,NULL); - destroy_th_barrier(&(pline->gate1)); - destroy_th_barrier(&(pline->gate2)); - pthread_attr_destroy(&(pline->attr)); - destroy_task_hist(pline->thst); - free(pline->thst); - free(pline->sd0); - free(pline->sd1); - pline->data=NULL; -} - - -int -sagefit_visibilities_dual_pt_flt(double *u, double *v, double *w, double *x, int N, - int Nbase, int tilesz, baseline_t *barr, clus_source_t *carr, complex double *coh, int M, int Mt, double freq0, double fdelta, double *pp, double uvmin, int Nt, int max_emiter, int max_iter, int max_lbfgs, int lbfgs_m, int gpu_threads, int linsolv,int solver_mode, double nulow, double nuhigh, int randomize, double *mean_nu, double *res_0, double *res_1) { - - - int ci,cj; - double *p; // parameters: m x 1 - int m, n; - double opts[CLM_OPTS_SZ], info0[CLM_INFO_SZ], info1[CLM_INFO_SZ]; - me_data_t lmdata0,lmdata1; - int Nbase1; - - double *xdummy0,*xdummy1,*xsub,*xo; - double *nerr; /* array to store cost reduction per cluster */ - int weighted_iter,this_itermax0,this_itermax1,total_iter; - double total_err; - - /* rearraged memory for GPU use */ - double *ddcoh; - short *ddbase; - - int *cr=0; /* array for random permutation of clusters */ - int c0,c1; - - opts[0]=CLM_INIT_MU; opts[1]=1E-9; opts[2]=1E-9; opts[3]=1E-9; - opts[4]=-CLM_DIFF_DELTA; - - /* robust */ - double robust_nu0; - double *robust_nuM; - - /* no. of parameters >= than the no of clusters*8N */ - m=N*Mt*8; - /* no of data */ - n=Nbase*tilesz*8; - - /* true no of baselines */ - Nbase1=Nbase*tilesz; - - float *ddcohf, *pf, *xdummy0f, *xdummy1f; -/********* thread data ******************/ - /* barrier */ - th_pipeline tp; - gbdatafl tpg; -/****************************************/ - - /* use full parameter space */ - p=pp; - lmdata0.clus=lmdata1.clus=-1; - /* setup data for lmfit */ - lmdata0.u=lmdata1.u=u; - lmdata0.v=lmdata1.v=v; - lmdata0.w=lmdata1.w=w; - lmdata0.Nbase=lmdata1.Nbase=Nbase; - lmdata0.tilesz=lmdata1.tilesz=tilesz; - lmdata0.N=lmdata1.N=N; - lmdata0.barr=lmdata1.barr=barr; - lmdata0.carr=lmdata1.carr=carr; - lmdata0.M=lmdata1.M=M; - lmdata0.Mt=lmdata1.Mt=Mt; - lmdata0.freq0=lmdata1.freq0=&freq0; - lmdata0.Nt=lmdata1.Nt=Nt; - lmdata0.coh=lmdata1.coh=coh; - /* rearrange coh for GPU use */ - if ((ddcoh=(double*)calloc((size_t)(M*Nbase1*8),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((ddcohf=(float*)calloc((size_t)(M*Nbase1*8),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((ddbase=(short*)calloc((size_t)(Nbase1*2),sizeof(short)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - rearrange_coherencies(Nbase1, barr, coh, ddcoh, ddbase, M, Nt); - lmdata0.ddcoh=lmdata1.ddcoh=ddcoh; - lmdata0.ddbase=lmdata1.ddbase=ddbase; - - /* ddcohf (float) << ddcoh (double) */ - double_to_float(ddcohf,ddcoh,M*Nbase1*8,Nt); - lmdata0.ddcohf=lmdata1.ddcohf=ddcohf; - - if ((xsub=(double*)calloc((size_t)(n),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xo=(double*)calloc((size_t)(n),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xdummy0=(double*)calloc((size_t)(n),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xdummy1=(double*)calloc((size_t)(n),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((nerr=(double*)calloc((size_t)(M),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xdummy0f=(float*)calloc((size_t)(n),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((xdummy1f=(float*)calloc((size_t)(n),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((pf=(float*)calloc((size_t)(Mt*8*N),sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if (solver_mode==SM_OSLM_OSRLM_RLBFGS || solver_mode==SM_RLM_RLBFGS || solver_mode==SM_RTR_OSRLM_RLBFGS || solver_mode==SM_NSD_RLBFGS) { - if ((robust_nuM=(double*)calloc((size_t)(M),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - } else { - robust_nuM=0; - } - /* starting guess of robust nu */ - robust_nu0=nulow; - - double_to_float(pf,p,Mt*8*N,Nt); - /* remember for each partition how much the cost function decreases - in the next EM iteration, we allocate more LM iters to partitions - where const function significantly decreases. So two stages - 1) equal LM iters (find the decrease) 2) weighted LM iters */ - weighted_iter=0; - total_iter=M*max_iter; /* total iterations per EM */ -/********** setup threads *******************************/ - init_pipeline_flt(&tp,&tpg); - sync_barrier(&(tp.gate1)); /* sync at gate 1*/ - tpg.status[0]=tpg.status[1]=PT_DO_AGPU; - /* also calculate the total storage needed to be allocated on a GPU */ - /* determine total size for memory allocation */ - int Mm=8*N; - int64_t data_sz=0; - /* Do NOT use fixed buffer for for RTR/NSD - */ - if (solver_mode==SM_RTR_OSLM_LBFGS) { - /* use dummy data size */ - data_sz=8*sizeof(float); - } else if (solver_mode==SM_RTR_OSRLM_RLBFGS) { - data_sz=8*sizeof(float); - } else if (solver_mode==SM_NSD_RLBFGS) { - data_sz=8*sizeof(float); - } else if (solver_mode==SM_LM_LBFGS || solver_mode==SM_OSLM_LBFGS) { - /* size for LM */ - data_sz=(int64_t)(n+Mm*n+Mm*Mm+Mm+Mm*Mm+Mm+Mm+Mm+Mm+Nbase1*8+n+n)*sizeof(float)+(int64_t)Nbase1*2*sizeof(short); - if (linsolv==1) { - data_sz+=(int64_t)Mm*sizeof(float); - } else if (linsolv==2) { - data_sz+=(int64_t)(Mm*Mm+Mm*Mm+Mm)*sizeof(float); - } - } else if (solver_mode==SM_RLM_RLBFGS || solver_mode==SM_OSLM_OSRLM_RLBFGS) { - /* size for ROBUSTLM */ - data_sz=(int64_t)(n+Mm*n+Mm*Mm+Mm+Mm*Mm+Mm+Mm+Mm+Mm+Nbase1*8+n+n+n+n)*sizeof(float)+(int64_t)Nbase1*2*sizeof(short); - if (linsolv==1) { - data_sz+=(int64_t)Mm*sizeof(float); - } else if (linsolv==2) { - data_sz+=(int64_t)(Mm*Mm+Mm*Mm+Mm)*sizeof(float); - } - } else { - fprintf(stderr,"%s: %d: invalid mode for solver\n",__FILE__,__LINE__); - exit(1); - } - - tpg.data_size=data_sz; - tpg.nulow=nulow; - tpg.nuhigh=nuhigh; - tpg.randomize=randomize; - sync_barrier(&(tp.gate2)); /* sync at gate 2*/ - - sync_barrier(&(tp.gate1)); /* sync at gate 1*/ - tpg.status[0]=tpg.status[1]=PT_DO_NOTHING; - sync_barrier(&(tp.gate2)); /* sync at gate 2*/ - -/********** done setup threads *******************************/ - - /* initial residual calculation - subtract full model from data */ - minimize_viz_full_pth(p, xsub, m, n, (void*)&lmdata0); - memcpy(xo,x,(size_t)(n)*sizeof(double)); - my_daxpy(n, xsub, -1.0, xo); - *res_0=my_dnrm2(n,xo)/(double)n; - - int iter_bar=(int)ceil((0.80/(double)M)*((double)total_iter)); - for (ci=0; ci1) { - /* find a random permutation of clusters */ - cr=random_permutation(M,weighted_iter,nerr); - } else { - cr=NULL; - } - - for (cj=0; cj0 || this_itermax1>0) { - /* calculate contribution from hidden data, subtract from x */ - /* since x has already subtracted this model, just add - the ones we are solving for */ - - sync_barrier(&(tp.gate1)); /* sync at gate 1 */ - memcpy(xdummy0,xo,(size_t)(n)*sizeof(double)); - memcpy(xdummy1,xo,(size_t)(n)*sizeof(double)); - lmdata0.clus=c0; - lmdata1.clus=c1; - - /* NOTE: conditional mean x^i = s^i + 0.5 * residual^i */ - /* so xdummy=0.5 ( 2*model + residual ) */ - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata0); - my_daxpy(n, xsub, 2.0, xdummy0); - my_dscal(n, 0.5, xdummy0); - my_daxpy(n, xsub, 1.0, xo); - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata1); - my_daxpy(n, xsub, 2.0, xdummy1); - my_dscal(n, 0.5, xdummy1); - my_daxpy(n, xsub, 1.0, xo); -/**************************************************************************/ - /* xdummy*f (float) << xdummy* (double) */ - double_to_float(xdummy0f,xdummy0,n,Nt); - double_to_float(xdummy1f,xdummy1,n,Nt); - /* run this from a separate thread */ - tpg.p[0]=&pf[carr[c0].p[0]]; /* length carr[c0].nchunk times */ - tpg.x[0]=xdummy0f; - tpg.M[0]=8*N; /* even though size of p is > M, dont change this */ - tpg.N[0]=n; /* Nbase*tilesz*8 */ - tpg.itermax[0]=this_itermax0; - tpg.opts[0]=opts; - tpg.info[0]=info0; - tpg.linsolv=linsolv; - tpg.lmdata[0]=&lmdata0; - - tpg.p[1]=&pf[carr[c1].p[0]]; /* length carr[c1].nchunk times */ - tpg.x[1]=xdummy1f; - tpg.M[1]=8*N; /* even though size of p is > M, dont change this */ - tpg.N[1]=n; /* Nbase*tilesz*8 */ - tpg.itermax[1]=this_itermax1; - tpg.opts[1]=opts; - tpg.info[1]=info1; - tpg.linsolv=linsolv; - tpg.lmdata[1]=&lmdata1; -/**************************************************************************/ - - /* both threads do work */ - /* if solver_mode>0 last EM iteration full LM, the rest OS LM */ - if (solver_mode==SM_OSLM_LBFGS) { - if (ci==max_emiter-1) { - tpg.status[0]=tpg.status[1]=PT_DO_WORK_LM; - } else { - tpg.status[0]=tpg.status[1]=PT_DO_WORK_OSLM; - } - } else if (solver_mode==SM_LM_LBFGS) { - tpg.status[0]=tpg.status[1]=PT_DO_WORK_LM; - } else if (solver_mode==SM_OSLM_OSRLM_RLBFGS) { - /* last EM iteration robust OS-LM, the one before LM, the rest OS LM */ - if (ci==max_emiter-2) { - tpg.status[0]=tpg.status[1]=PT_DO_WORK_LM; - } else if (ci==max_emiter-1) { - lmdata0.robust_nu=lmdata1.robust_nu=robust_nu0; /* initial robust nu */ - //tpg.status[0]=tpg.status[1]=PT_DO_WORK_RLM; - tpg.status[0]=tpg.status[1]=PT_DO_WORK_OSRLM; - } else { - tpg.status[0]=tpg.status[1]=PT_DO_WORK_OSLM; - } - } else if (solver_mode==SM_RLM_RLBFGS) { - /* last EM iteration robust LM, the rest OS LM */ - if (ci==max_emiter-1) { - lmdata0.robust_nu=lmdata1.robust_nu=robust_nu0; /* initial robust nu */ - tpg.status[0]=tpg.status[1]=PT_DO_WORK_OSRLM; - } else { - tpg.status[0]=tpg.status[1]=PT_DO_WORK_OSLM; - } - } else if (solver_mode==SM_RTR_OSLM_LBFGS) { - tpg.status[0]=tpg.status[1]=PT_DO_WORK_RTR; - } else if (solver_mode==SM_RTR_OSRLM_RLBFGS) { - if (!ci) { - lmdata0.robust_nu=lmdata1.robust_nu=robust_nu0; /* initial robust nu */ - } - tpg.status[0]=tpg.status[1]=PT_DO_WORK_RRTR; - } else if (solver_mode==SM_NSD_RLBFGS) { - if (!ci) { - lmdata0.robust_nu=lmdata1.robust_nu=robust_nu0; /* initial robust nu */ - } - tpg.status[0]=tpg.status[1]=PT_DO_WORK_NSD; - } else { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: undefined solver mode\n",__FILE__,__LINE__); -#endif - exit(1); - } - sync_barrier(&(tp.gate2)); /* sync at gate 2 */ - sync_barrier(&(tp.gate1)); /* sync at gate 1 */ - tpg.status[0]=tpg.status[1]=PT_DO_NOTHING; - sync_barrier(&(tp.gate2)); /* sync at gate 2 */ -#ifdef DEBUG -printf("1: %lf -> %lf 2: %lf -> %lf\n\n\n",info0[0],info0[1],info1[0],info1[1]); -#endif - /* catch -ve value here */ - if (info0[0]>0.0) { - nerr[c0]=(info0[0]-info0[1])/info0[0]; - if (nerr[c0]<0.0) { nerr[c0]=0.0; } - } else { - nerr[c0]=0.0; - } - if (info1[0]>0.0) { - nerr[c1]=(info1[0]-info1[1])/info1[0]; - if (nerr[c1]<0.0) { nerr[c1]=0.0; } - } else { - nerr[c1]=0.0; - } - /* update robust_nu */ - if ((solver_mode==SM_RLM_RLBFGS || solver_mode==SM_OSLM_OSRLM_RLBFGS || solver_mode==SM_RTR_OSRLM_RLBFGS || solver_mode==SM_NSD_RLBFGS) && (ci==max_emiter-1)) { - robust_nuM[c0]+=lmdata0.robust_nu; - robust_nuM[c1]+=lmdata1.robust_nu; - } - /* p (double) << pf (float) */ - float_to_double(&p[carr[c0].p[0]],&pf[carr[c0].p[0]],carr[c0].nchunk*8*N,Nt); - float_to_double(&p[carr[c1].p[0]],&pf[carr[c1].p[0]],carr[c1].nchunk*8*N,Nt); - /* once again subtract solved model from data */ - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata0); - my_daxpy(n, xsub, -1.0, xo); - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata1); - my_daxpy(n, xsub, -1.0, xo); - - } - } - /* odd cluster out, if M is odd */ - if (M%2) { - if (randomize && M>1) { - c0=cr[M-1]; - } else { - c0=M-1; - } - /* calculate max LM iter for this cluster */ - if (weighted_iter) { - this_itermax0=(int)((0.20*nerr[c0])*((double)total_iter))+iter_bar; - } else { - this_itermax0=max_iter; - } -#ifdef DEBUG - printf("Cluster %d(iter=%d, wt=%lf)\n",c0,this_itermax0,nerr[c0]); -#endif - if (this_itermax0>0) { -/**************************************************************************/ - sync_barrier(&(tp.gate1)); /* sync at gate 1 */ - /* calculate contribution from hidden data, subtract from x */ - memcpy(xdummy0,xo,(size_t)(n)*sizeof(double)); - lmdata0.clus=c0; - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata0); - my_daxpy(n, xsub, 1.0, xdummy0); - my_daxpy(n, xsub, 1.0, xo); - - double_to_float(xdummy0f,xdummy0,n,Nt); - /* run this from a separate thread */ - tpg.p[0]=&pf[carr[c0].p[0]]; - tpg.x[0]=xdummy0f; - tpg.M[0]=8*N; - tpg.N[0]=n; - tpg.itermax[0]=this_itermax0; - tpg.opts[0]=opts; - tpg.info[0]=info0; - tpg.linsolv=linsolv; - tpg.lmdata[0]=&lmdata0; - - if (solver_mode==SM_OSLM_LBFGS) { - if (ci==max_emiter-1) { - tpg.status[0]=PT_DO_WORK_LM; - } else { - tpg.status[0]=PT_DO_WORK_OSLM; - } - } else if (solver_mode==SM_LM_LBFGS) { - tpg.status[0]=PT_DO_WORK_LM; - } else if (solver_mode==SM_OSLM_OSRLM_RLBFGS) { - /* last EM iteration robust OS-LM, the one before LM, the rest OS LM */ - if (ci==max_emiter-2) { - tpg.status[0]=PT_DO_WORK_LM; - } else if (ci==max_emiter-1) { - lmdata0.robust_nu=robust_nu0; /* initial robust nu */ - //tpg.status[0]=PT_DO_WORK_RLM; - tpg.status[0]=PT_DO_WORK_OSRLM; - } else { - tpg.status[0]=PT_DO_WORK_OSLM; - } - } else if (solver_mode==SM_RLM_RLBFGS) { - if (ci==max_emiter-1) { - lmdata0.robust_nu=robust_nu0; /* initial robust nu */ - tpg.status[0]=PT_DO_WORK_OSRLM; - } else { - tpg.status[0]=PT_DO_WORK_OSLM; - } - } else if (solver_mode==SM_RTR_OSLM_LBFGS) { - tpg.status[0]=PT_DO_WORK_RTR; - } else if (solver_mode==SM_RTR_OSRLM_RLBFGS) { - if (!ci) { - lmdata0.robust_nu=robust_nu0; /* initial robust nu */ - } - tpg.status[0]=PT_DO_WORK_RRTR; - } else if (solver_mode==SM_NSD_RLBFGS) { - if (!ci) { - lmdata0.robust_nu=robust_nu0; /* initial robust nu */ - } - tpg.status[0]=PT_DO_WORK_NSD; - } else { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: undefined solver mode\n",__FILE__,__LINE__); -#endif - exit(1); - } - - tpg.status[1]=PT_DO_NOTHING; - sync_barrier(&(tp.gate2)); /* sync at gate 2 */ -/**************************************************************************/ - sync_barrier(&(tp.gate1)); /* sync at gate 1 */ - tpg.status[0]=tpg.status[1]=PT_DO_NOTHING; - sync_barrier(&(tp.gate2)); /* sync at gate 2 */ - -#ifdef DEBUG -printf("1: %lf -> %lf\n\n\n",info0[0],info0[1]); -#endif - /* catch -ve value here */ - if (info0[0]>0.0) { - nerr[c0]=(info0[0]-info0[1])/info0[0]; - if (nerr[c0]<0.0) { nerr[c0]=0.0; } - } else { - nerr[c0]=0.0; - } - /* update robust_nu */ - if ((solver_mode==SM_RLM_RLBFGS || solver_mode==SM_OSLM_OSRLM_RLBFGS || solver_mode==SM_RTR_OSRLM_RLBFGS || solver_mode==SM_NSD_RLBFGS) && (ci==max_emiter-1)) { - robust_nuM[c0]+=lmdata0.robust_nu; - } - /* once again subtract solved model from data */ - float_to_double(&p[carr[c0].p[0]],&pf[carr[c0].p[0]],carr[c0].nchunk*8*N,Nt); - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata0); - my_daxpy(n, xsub, -1.0, xo); - } - } - /* normalize nerr array so that the sum is 1 */ - total_err=my_dasum(M,nerr); - if (total_err>0.0) { - my_dscal(M, 1.0/total_err, nerr); - } - if (randomize && M>1) { /* nothing to randomize if only 1 direction */ - /* flip weighting flag */ - weighted_iter=!weighted_iter; - free(cr); - } - /**************** End EM iteration ***********************/ - } - free(nerr); - free(xo); - free(xdummy0); - free(xdummy1); - free(ddcoh); - free(ddbase); - free(xdummy0f); - free(xdummy1f); - free(pf); - free(ddcohf); - if (solver_mode==SM_RLM_RLBFGS || solver_mode==SM_OSLM_OSRLM_RLBFGS || solver_mode==SM_RTR_OSRLM_RLBFGS || solver_mode==SM_NSD_RLBFGS) { - /* calculate mean robust_nu over all clusters */ - robust_nu0=my_dasum(M,robust_nuM)/(double)M; -#ifdef DEBUG - for (ci=0; cinuhigh) { - robust_nu0=nuhigh; - } - - } - /******** free threads ***************/ - sync_barrier(&(tp.gate1)); /* sync at gate 1*/ - tpg.status[0]=tpg.status[1]=PT_DO_DGPU; - sync_barrier(&(tp.gate2)); /* sync at gate 2*/ - - - destroy_pipeline_flt(&tp); - /******** done free threads ***************/ - - if (max_lbfgs>0) { - /* use LBFGS */ - if (solver_mode==SM_RLM_RLBFGS || solver_mode==SM_OSLM_OSRLM_RLBFGS || solver_mode==SM_RTR_OSRLM_RLBFGS || solver_mode==SM_NSD_RLBFGS) { - lmdata0.robust_nu=robust_nu0; - lbfgs_fit_robust_cuda(minimize_viz_full_pth, p, x, m, n, max_lbfgs, lbfgs_m, gpu_threads, (void*)&lmdata0); - } else { - lbfgs_fit(minimize_viz_full_pth, p, x, m, n, max_lbfgs, lbfgs_m, gpu_threads, (void*)&lmdata0); - } - } - - /* final residual calculation */ - minimize_viz_full_pth(p, xsub, m, n, (void*)&lmdata0); - my_daxpy(n, xsub, -1.0, x); - - *mean_nu=robust_nu0; - *res_1=my_dnrm2(n,x)/(double)n; - - free(xsub); - - /* if final residual > initial residual, - return -1, else 0 - */ - if (*res_1>*res_0) { - return -1; - } - return 0; -} -#endif /* HYBRID_CODE */ diff --git a/src/lib/lmfit_nocuda.c b/src/lib/lmfit_nocuda.c deleted file mode 100644 index b40b7e5..0000000 --- a/src/lib/lmfit_nocuda.c +++ /dev/null @@ -1,1283 +0,0 @@ -/* - * - Copyright (C) 2006-2008 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#include -#include -#include -#include -#include -#include "sagecal.h" - -//#define DEBUG - -/* Jones matrix multiplication - C=A*B -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -amb(complex double * __restrict a, complex double * __restrict b, complex double * __restrict c) { - c[0]=a[0]*b[0]+a[1]*b[2]; - c[1]=a[0]*b[1]+a[1]*b[3]; - c[2]=a[2]*b[0]+a[3]*b[2]; - c[3]=a[2]*b[1]+a[3]*b[3]; -} - -/* Jones matrix multiplication - C=A*B^H -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -ambt(complex double * __restrict a, complex double * __restrict b, complex double * __restrict c) { - c[0]=a[0]*conj(b[0])+a[1]*conj(b[1]); - c[1]=a[0]*conj(b[2])+a[1]*conj(b[3]); - c[2]=a[2]*conj(b[0])+a[3]*conj(b[1]); - c[3]=a[2]*conj(b[2])+a[3]*conj(b[3]); -} - -/********************** sage minimization ***************************/ -/* worker thread function for prediction */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -predict_threadfn_withgain(void *data) { - thread_data_base_t *t=(thread_data_base_t*)data; - - int ci,cm,sta1,sta2; - complex double C[4],G1[4],G2[4],T1[4],T2[4]; - int M=(t->M); - cm=(t->clus); - int Ntilebase=(t->Nbase)*(t->tilesz); - int px; - double *pm; - - for (ci=0; ciNb; ci++) { - /* iterate over the sky model and calculate contribution */ - /* for this x[8*ci:8*(ci+1)-1] */ - memset(&(t->x[8*ci]),0,sizeof(double)*8); - - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - px=(ci+t->boff)/((Ntilebase+t->carr[cm].nchunk-1)/t->carr[cm].nchunk); - pm=&(t->p[t->carr[cm].p[px]]); - - /* gains for this cluster, for sta1,sta2 */ - G1[0]=(pm[sta1*8])+_Complex_I*(pm[sta1*8+1]); - G1[1]=(pm[sta1*8+2])+_Complex_I*(pm[sta1*8+3]); - G1[2]=(pm[sta1*8+4])+_Complex_I*(pm[sta1*8+5]); - G1[3]=(pm[sta1*8+6])+_Complex_I*(pm[sta1*8+7]); - G2[0]=(pm[sta2*8])+_Complex_I*(pm[sta2*8+1]); - G2[1]=(pm[sta2*8+2])+_Complex_I*(pm[sta2*8+3]); - G2[2]=(pm[sta2*8+4])+_Complex_I*(pm[sta2*8+5]); - G2[3]=(pm[sta2*8+6])+_Complex_I*(pm[sta2*8+7]); - - - /* use pre calculated values */ - C[0]=t->coh[4*M*ci+4*cm]; - C[1]=t->coh[4*M*ci+4*cm+1]; - C[2]=t->coh[4*M*ci+4*cm+2]; - C[3]=t->coh[4*M*ci+4*cm+3]; - - - /* form G1*C*G2' */ - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - - /* add to baseline visibilities */ - t->x[8*ci]+=creal(T2[0]); - t->x[8*ci+1]+=cimag(T2[0]); - t->x[8*ci+2]+=creal(T2[1]); - t->x[8*ci+3]+=cimag(T2[1]); - t->x[8*ci+4]+=creal(T2[2]); - t->x[8*ci+5]+=cimag(T2[2]); - t->x[8*ci+6]+=creal(T2[3]); - t->x[8*ci+7]+=cimag(T2[3]); - } - } - - return NULL; -} - - -/* minimization function (multithreaded) */ -/* p: size mx1 parameters - x: size nx1 data calculated - data: extra info needed */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -mylm_fit_single_pth(double *p, double *x, int m, int n, void *data) { - - me_data_t *dp=(me_data_t*)data; - /* u,v,w : size Nbase*tilesz x 1 x: size Nbase*8*tilesz x 1 */ - /* barr: size Nbase*tilesz x 1 carr: size Mx1 */ - /* pp: size 8*N*M x 1 */ - /* pm: size Mx1 of double */ - - int nth,nth1,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthb0,Nthb; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_base_t *threaddata; - - int Nbase=(dp->Nbase); - int tilesz=(dp->tilesz); - - int Nbase1=Nbase*tilesz; - - /* calculate min baselines a thread can handle */ - //Nthb0=ceil((double)Nbase1/(double)Nt); - Nthb0=(Nbase1+Nt-1)/Nt; - - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((threaddata=(thread_data_base_t*)malloc((size_t)Nt*sizeof(thread_data_base_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr; - threaddata[nth].u=&(dp->u[ci]); - threaddata[nth].v=&(dp->v[ci]); - threaddata[nth].w=&(dp->w[ci]); - threaddata[nth].carr=dp->carr; - threaddata[nth].M=dp->M; - threaddata[nth].x=&(x[8*ci]); - threaddata[nth].N=dp->N; - threaddata[nth].Nbase=Nbase; - threaddata[nth].tilesz=tilesz; - threaddata[nth].p=p; - threaddata[nth].clus=(dp->clus); - threaddata[nth].coh=&(dp->coh[4*(dp->M)*ci]); - - //printf("thread %d predict data from %d baselines %d\n",nth,8*ci,Nthb); - pthread_create(&th_array[nth],&attr,predict_threadfn_withgain,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - /* now wait for threads to finish */ - for(nth1=0; nth1M); - cm=(t->clus); - for (ci=0; ciNb; ci++) { - /* iterate over the sky model and calculate contribution */ - /* for this x[8*ci:8*(ci+1)-1] */ - memset(&(t->x[8*ci]),0,sizeof(double)*8); - - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - /* gains for this cluster, for sta1,sta2 */ - G1[0]=(t->p[sta1*8])+_Complex_I*(t->p[sta1*8+1]); - G1[1]=(t->p[sta1*8+2])+_Complex_I*(t->p[sta1*8+3]); - G1[2]=(t->p[sta1*8+4])+_Complex_I*(t->p[sta1*8+5]); - G1[3]=(t->p[sta1*8+6])+_Complex_I*(t->p[sta1*8+7]); - G2[0]=(t->p[sta2*8])+_Complex_I*(t->p[sta2*8+1]); - G2[1]=(t->p[sta2*8+2])+_Complex_I*(t->p[sta2*8+3]); - G2[2]=(t->p[sta2*8+4])+_Complex_I*(t->p[sta2*8+5]); - G2[3]=(t->p[sta2*8+6])+_Complex_I*(t->p[sta2*8+7]); - - - /* use pre calculated values */ - C[0]=t->coh[4*M*ci+4*cm]; - C[1]=t->coh[4*M*ci+4*cm+1]; - C[2]=t->coh[4*M*ci+4*cm+2]; - C[3]=t->coh[4*M*ci+4*cm+3]; - - - /* form G1*C*G2' */ - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - - /* add to baseline visibilities */ - t->x[8*ci]+=creal(T2[0]); - t->x[8*ci+1]+=cimag(T2[0]); - t->x[8*ci+2]+=creal(T2[1]); - t->x[8*ci+3]+=cimag(T2[1]); - t->x[8*ci+4]+=creal(T2[2]); - t->x[8*ci+5]+=cimag(T2[2]); - t->x[8*ci+6]+=creal(T2[3]); - t->x[8*ci+7]+=cimag(T2[3]); - } - } - - return NULL; -} - - -/* minimization function (multithreaded) : not considering - hybrid parameter space */ -/* p: size mx1 parameters - x: size nx1 data calculated - data: extra info needed */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -mylm_fit_single_pth0(double *p, double *x, int m, int n, void *data) { - - me_data_t *dp=(me_data_t*)data; - /* u,v,w : size Nbase*tilesz x 1 x: size Nbase*8*tilesz x 1 */ - /* barr: size Nbase*tilesz x 1 carr: size Mx1 */ - /* pp: size 8*N*M x 1 */ - /* pm: size Mx1 of double */ - - int nth,nth1,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthb0,Nthb; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_base_t *threaddata; - - int Nbase1=(dp->Nbase)*(dp->tilesz); - int boff=(dp->Nbase)*(dp->tileoff); - - /* calculate min baselines a thread can handle */ - //Nthb0=ceil((double)Nbase1/(double)Nt); - Nthb0=(Nbase1+Nt-1)/Nt; - - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((threaddata=(thread_data_base_t*)malloc((size_t)Nt*sizeof(thread_data_base_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr; - threaddata[nth].u=&(dp->u[ci]); - threaddata[nth].v=&(dp->v[ci]); - threaddata[nth].w=&(dp->w[ci]); - threaddata[nth].carr=dp->carr; - threaddata[nth].M=dp->M; - threaddata[nth].x=&(x[8*ci]); - threaddata[nth].N=dp->N; - threaddata[nth].p=p; /* note the difference: here p assumes no hybrid */ - threaddata[nth].clus=(dp->clus); - threaddata[nth].coh=&(dp->coh[4*(dp->M)*(ci+boff)]); - - //printf("thread %d predict data from %d baselines %d\n",nth,8*ci,Nthb); - pthread_create(&th_array[nth],&attr,predict_threadfn_withgain0,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - /* now wait for threads to finish */ - for(nth1=0; nth1M); - cm=(t->clus); - int stc,stoff; - - /* Loop order to minimize cache misses */ - /* we calculate the jacobian (nxm) columns [startc...endc] */ - for (col=t->start_col; col<=t->end_col; col++) { - /* iterate over row */ - for (ci=0; ciNb; ci++) { - - /* if this baseline is flagged, - or if this parameter does not belong to sta1 or sta2 - we do not compute */ - stc=col/8; /* 0..N-1 */ - /* stations for this baseline */ - sta1=t->barr[ci].sta1; - sta2=t->barr[ci].sta2; - - /* change order for checking condition to minimize cache misses - since sta2 will appear more, first check that ??? */ - if ( ((stc==sta2)||(stc==sta1)) && (!t->barr[ci].flag) ) { - - /* use pre calculated values */ - C[0]=t->coh[4*M*ci+4*cm]; - C[1]=t->coh[4*M*ci+4*cm+1]; - C[2]=t->coh[4*M*ci+4*cm+2]; - C[3]=t->coh[4*M*ci+4*cm+3]; - - /* which parameter exactly 0..7 */ - stoff=col%8; - //printf("sta1=%d,sta2=%d,stc=%d,off=%d,col=%d,param=%d\n",sta1,sta2,stc,col%8,col,stc*8+stoff); - if (stc==sta1) { - for (cn=0; cn<8; cn++) { - pp1[cn]=0.0; - pp2[cn]=t->p[sta2*8+cn]; - } - pp1[stoff]=1.0; - } else if (stc==sta2) { - for (cn=0; cn<8; cn++) { - pp2[cn]=0.0; - pp1[cn]=t->p[sta1*8+cn]; - } - pp2[stoff]=1.0; - } - /* gains for this cluster, for sta1,sta2 */ - G1[0]=pp1[0]+_Complex_I*pp1[1]; - G1[1]=pp1[2]+_Complex_I*pp1[3]; - G1[2]=pp1[4]+_Complex_I*pp1[5]; - G1[3]=pp1[6]+_Complex_I*pp1[7]; - G2[0]=pp2[0]+_Complex_I*pp2[1]; - G2[1]=pp2[2]+_Complex_I*pp2[3]; - G2[2]=pp2[4]+_Complex_I*pp2[5]; - G2[3]=pp2[6]+_Complex_I*pp2[7]; - - /* form G1*C*G2' */ - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - - /* add to baseline visibilities */ - /* NOTE: row major order */ - t->jac[col+(t->m)*8*ci]=creal(T2[0]); - t->jac[col+(t->m)*(8*ci+1)]=cimag(T2[0]); - t->jac[col+(t->m)*(8*ci+2)]=creal(T2[1]); - t->jac[col+(t->m)*(8*ci+3)]=cimag(T2[1]); - t->jac[col+(t->m)*(8*ci+4)]=creal(T2[2]); - t->jac[col+(t->m)*(8*ci+5)]=cimag(T2[2]); - t->jac[col+(t->m)*(8*ci+6)]=creal(T2[3]); - t->jac[col+(t->m)*(8*ci+7)]=cimag(T2[3]); - - } - } - } - - return NULL; -} - -/* jacobian function (multithreaded) */ -/* p: size mx1 parameters - jac: size nxm jacobian to be calculated (row major) - data: extra info needed */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -mylm_jac_single_pth(double *p, double *jac, int m, int n, void *data) { - - me_data_t *dp=(me_data_t*)data; - /* u,v,w : size Nbase*tilesz x 1 x: size Nbase*8*tilesz x 1 */ - /* barr: size Nbase*tilesz x 1 carr: size Mx1 */ - /* pp: size 8*N*M x 1 */ - /* pm: size Mx1 of double */ - - int nth,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthcol; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_jac_t *threaddata; - - int Nbase=(dp->Nbase)*(dp->tilesz); - int boff=(dp->Nbase)*(dp->tileoff); - - /* calculate min columns of the jacobian one thread can handle */ - Nthcol=(m+Nt-1)/Nt; - - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((threaddata=(thread_data_jac_t*)malloc((size_t)Nt*sizeof(thread_data_jac_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - /* set jacobian to all zeros */ - memset(jac,0,sizeof(double)*n*m); - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr[boff]); - threaddata[nth].u=dp->u; - threaddata[nth].v=dp->v; - threaddata[nth].w=dp->w; - threaddata[nth].carr=dp->carr; - threaddata[nth].M=dp->M; - threaddata[nth].jac=jac; /* NOTE: jacobian is in row major order */ - threaddata[nth].N=dp->N; - threaddata[nth].p=p; - threaddata[nth].clus=(dp->clus); - threaddata[nth].coh=&(dp->coh[4*(dp->M)*(boff)]); - threaddata[nth].start_col=ci; - threaddata[nth].end_col=ci+Nthcol-1; - if (threaddata[nth].end_col>=m) { - threaddata[nth].end_col=m-1; - } - - //printf("thread %d calculate cols %d to %d\n",nth,threaddata[nth].start_col, threaddata[nth].end_col); - pthread_create(&th_array[nth],&attr,jacobian_threadfn,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthcol; - } - - /* now wait for threads to finish */ - for(nth=0; nthM); - int Ntilebase=(t->Nbase)*(t->tilesz); - int px; - - for (ci=0; ciNb; ci++) { - /* iterate over the sky model and calculate contribution */ - /* for this x[8*ci:8*(ci+1)-1] */ - memset(&(t->x[8*ci]),0,sizeof(double)*8); - - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - for (cm=0; cm x[0.....Nbase*tilesz/nchunk-1] - p[1] -> x[Nbase*tilesz/nchunk......2*Nbase*tilesz-1] - .... - p[last] -> x[(nchunk-1)*Nbase*tilesz/nchunk......Nbase*tilesz] - - so given bindex, right p[] is bindex/((Nbase*tilesz+nchunk-1)/nchunk) - */ - - px=(ci+t->boff)/((Ntilebase+t->carr[cm].nchunk-1)/t->carr[cm].nchunk); - pm=&(t->p[t->carr[cm].p[px]]); - G1[0]=(pm[sta1*8])+_Complex_I*(pm[sta1*8+1]); - G1[1]=(pm[sta1*8+2])+_Complex_I*(pm[sta1*8+3]); - G1[2]=(pm[sta1*8+4])+_Complex_I*(pm[sta1*8+5]); - G1[3]=(pm[sta1*8+6])+_Complex_I*(pm[sta1*8+7]); - G2[0]=(pm[sta2*8])+_Complex_I*(pm[sta2*8+1]); - G2[1]=(pm[sta2*8+2])+_Complex_I*(pm[sta2*8+3]); - G2[2]=(pm[sta2*8+4])+_Complex_I*(pm[sta2*8+5]); - G2[3]=(pm[sta2*8+6])+_Complex_I*(pm[sta2*8+7]); - - - /* use pre calculated values */ - C[0]=t->coh[4*M*ci+4*cm]; - C[1]=t->coh[4*M*ci+4*cm+1]; - C[2]=t->coh[4*M*ci+4*cm+2]; - C[3]=t->coh[4*M*ci+4*cm+3]; - - /* form G1*C*G2' */ - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - - /* add to baseline visibilities */ - t->x[8*ci]+=creal(T2[0]); - t->x[8*ci+1]+=cimag(T2[0]); - t->x[8*ci+2]+=creal(T2[1]); - t->x[8*ci+3]+=cimag(T2[1]); - t->x[8*ci+4]+=creal(T2[2]); - t->x[8*ci+5]+=cimag(T2[2]); - t->x[8*ci+6]+=creal(T2[3]); - t->x[8*ci+7]+=cimag(T2[3]); - } - } - } - - return NULL; -} - -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -minimize_viz_full_pth(double *p, double *x, int m, int n, void *data) { - - me_data_t *dp=(me_data_t*)data; - /* u,v,w : size Nbase*tilesz x 1 x: size Nbase*8*tilesz x 1 */ - /* barr: size Nbase*tilesz x 1 carr: size Mx1 */ - /* pp: size 8*N*M x 1 */ - /* pm: size Mx1 of double */ - - int nth,nth1,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthb0,Nthb; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_base_t *threaddata; - - int Nbase1=(dp->Nbase)*(dp->tilesz); - - /* calculate min baselines a thread can handle */ - Nthb0=(Nbase1+Nt-1)/Nt; - - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((threaddata=(thread_data_base_t*)malloc((size_t)Nt*sizeof(thread_data_base_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr; - threaddata[nth].u=&(dp->u[ci]); - threaddata[nth].v=&(dp->v[ci]); - threaddata[nth].w=&(dp->w[ci]); - threaddata[nth].carr=dp->carr; - threaddata[nth].M=dp->M; - threaddata[nth].x=&(x[8*ci]); - threaddata[nth].p=p; - threaddata[nth].N=dp->N; - threaddata[nth].Nbase=dp->Nbase; - threaddata[nth].tilesz=dp->tilesz; - threaddata[nth].coh=&(dp->coh[4*(dp->M)*ci]); - - //printf("thread %d predict data from %d baselines %d\n",nth,8*ci,Nthb); - pthread_create(&th_array[nth],&attr,predict_threadfn_withgain_full,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - /* now wait for threads to finish */ - for(nth1=0; nth10) { - /* calculate contribution from hidden data, subtract from x - actually, add the current model for this cluster to residual */ - lmdata.clus=cj; - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata); - my_daxpy(n, xsub, 1.0, xdummy); - - tilechunk=(tilesz+carr[cj].nchunk-1)/carr[cj].nchunk; - tcj=0; - init_res=final_res=0.0; - /* loop through hybrid parameter space */ - for (ck=0; ck0.0) { - nerr[cj]=(init_res-final_res)/init_res; - if (nerr[cj]<0.0) { nerr[cj]=0.0; } - } else { - nerr[cj]=0.0; - } - /* subtract current model */ - mylm_fit_single_pth(p, xsub, 8*N, n, (void*)&lmdata); - my_daxpy(n, xsub, -1.0, xdummy); - /* if robust LM, calculate average nu over hybrid clusters */ - if ((solver_mode==SM_OSLM_OSRLM_RLBFGS || solver_mode==SM_RLM_RLBFGS || solver_mode==SM_RTR_OSRLM_RLBFGS || solver_mode==SM_NSD_RLBFGS) && (ci==max_emiter-1)) { - robust_nuM[cj]/=(double)carr[cj].nchunk; - } - } - } - - /* normalize nerr array so that the sum is 1 */ - total_err=my_dasum(M,nerr); - if (total_err>0.0) { - my_dscal(M, 1.0/total_err, nerr); - } - - /* flip weighting flag */ - if (randomize) { - weighted_iter=!weighted_iter; - } - } - free(nerr); - free(xdummy); - if (solver_mode==SM_OSLM_OSRLM_RLBFGS || solver_mode==SM_RLM_RLBFGS || solver_mode==SM_RTR_OSRLM_RLBFGS || solver_mode==SM_NSD_RLBFGS) { - /* calculate mean robust_nu over all clusters */ - robust_nu0=my_dasum(M,robust_nuM)/(double)M; -#ifdef DEBUG - for (ci=0; cinuhigh) { - robust_nu0=nuhigh; - } - } - - if (max_lbfgs>0) { -#ifdef USE_MIC - lmdata.Nt=32; /* FIXME increase threads for MIC */ -#endif - /* use LBFGS */ - if (solver_mode==SM_OSLM_OSRLM_RLBFGS || solver_mode==SM_RLM_RLBFGS || solver_mode==SM_RTR_OSRLM_RLBFGS || solver_mode==SM_NSD_RLBFGS) { - lmdata.robust_nu=robust_nu0; - lbfgs_fit_robust(minimize_viz_full_pth, p, x, m, n, max_lbfgs, lbfgs_m, gpu_threads, (void*)&lmdata); - } else { - lbfgs_fit(minimize_viz_full_pth, p, x, m, n, max_lbfgs, lbfgs_m, gpu_threads, (void*)&lmdata); - } -#ifdef USE_MIC - lmdata.Nt=Nt; /* reset threads for MIC */ -#endif - } - /* final residual calculation */ - minimize_viz_full_pth(p, xsub, m, n, (void*)&lmdata); - my_daxpy(n, xsub, -1.0, x); - - *mean_nu=robust_nu0; - *res_1=my_dnrm2(n,x)/(double)n; - - free(xsub); - /* if final residual > initial residual, - return -1, else 0 - */ - if (*res_1>*res_0) { - return -1; - } - return 0; -} - - - -/* struct and function for qsort */ -typedef struct w_n_ { - int i; - double w; -} w_n; -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static int -weight_compare(const void *a, const void *b) { - w_n *aw,*bw; - aw=(w_n*)a; - bw=(w_n*)b; - if (aw->w>bw->w) return -1; - if (aw->w==bw->w) return 0; - - return 1; -} - -/* generate a random permutation of given integers */ -/* note: free returned value after use */ -/* n: no of entries, - weighter_iter: if 1, take weight into account - if 0, only generate a random permutation - w: weights (size nx1): sort them in descending order and - give permutation accordingly -*/ -int* -random_permutation(int n, int weighted_iter, double *w) { - int *p; - int i; - if ((p=(int*)malloc((size_t)(n)*sizeof(int)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if (!weighted_iter) { - for (i = 0; i < n; ++i) { - int j = rand() % (i + 1); - p[i] = p[j]; - p[j] = i; - } - } else { - /* we take weight into account */ - w_n *wn_arr; - if ((wn_arr=(w_n*)malloc((size_t)(n)*sizeof(w_n)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - for (i=0; i0) { -#ifdef USE_MIC - lmdata.Nt=64; /* increase threads for MIC */ -#endif - /* use LBFGS */ - if (solver_mode==2 || solver_mode==3) { - lmdata.robust_nu=mean_nu; - lbfgs_fit_robust(minimize_viz_full_pth, p, x, m, n, max_lbfgs, lbfgs_m, gpu_threads, (void*)&lmdata); - } else { - lbfgs_fit(minimize_viz_full_pth, p, x, m, n, max_lbfgs, lbfgs_m, gpu_threads, (void*)&lmdata); - } -#ifdef USE_MIC - lmdata.Nt=Nt; /* reset threads for MIC */ -#endif - } - /* final residual calculation */ - minimize_viz_full_pth(p, xsub, m, n, (void*)&lmdata); - my_daxpy(n, xsub, -1.0, x); - - *res_1=my_dnrm2(n,x)/(double)n; - - free(xsub); - free(xdummy); - /* if final residual > initial residual, - return -1, else 0 - */ - if (*res_1>*res_0) { - return -1; - } - return 0; -} - - -#ifdef USE_MIC -/* wrapper function with bitwise copyable carr[] for MIC */ -/* nchunks: Mx1 array of chunk sizes for each cluster */ -/* pindex: Mt x 1 array of index of solutions for each cluster in pp */ -int -sagefit_visibilities_mic(double *u, double *v, double *w, double *x, int N, - int Nbase, int tilesz, baseline_t *barr, int *nchunks, int *pindex, complex double *coh, int M, int Mt, double freq0, double fdelta, double *pp, double uvmin, int Nt, int max_emiter, int max_iter, int max_lbfgs, int lbfgs_m, int gpu_threads, int linsolv,int solver_mode,double nulow, double nuhigh,int randomize, double *mean_nu, double *res_0, double *res_1) { - - clus_source_t *carr; - /* create a dummy carr[] structure to pass on */ - if ((carr=(clus_source_t*)calloc((size_t)M,sizeof(clus_source_t)))==0) { - exit(1); - } - int ci,cj,retval; - /* only need two fields in carr[] */ - cj=0; - for (ci=0; ci - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#include -#include -#include -#include -#include -#include "sagecal.h" - - -#include - -//#define MPI_BUILD -#ifdef MPI_BUILD -#include -#endif - -//#define DEBUG - -/* return random value in 0,1,..,maxval */ -#ifndef MPI_BUILD -static int -random_pick(int maxval, taskhist *th) { - double rat=(double)random()/(double)RAND_MAX; - double y=rat*(double)(maxval+1); - int x=(int)floor(y); - return x; -} -#endif - -void -init_task_hist(taskhist *th) { - th->prev=-1; - th->rseed=0; - pthread_mutex_init(&th->prev_mutex,NULL); -} - -void -destroy_task_hist(taskhist *th) { - th->prev=-1; - th->rseed=0; - pthread_mutex_destroy(&th->prev_mutex); -} - -/* select a GPU from 0,1..,max_gpu - in such a way to allow load balancing */ -int -select_work_gpu(int max_gpu, taskhist *th) { -#ifdef MPI_BUILD - int rank; - MPI_Comm_rank(MPI_COMM_WORLD, &rank); - /* check if max_gpu > no. of actual devices */ - int actual_devcount; - cudaGetDeviceCount(&actual_devcount); - if (max_gpu+1>actual_devcount) { - return rank%(actual_devcount); - } - return rank%(max_gpu+1); /* modulo value */ -#endif - -#ifndef MPI_BUILD - /* sequentially query the devices to find - one with the min load/memory usage */ - nvmlReturn_t result; - result = nvmlInit(); - int retval; - int minid=-1; - int maxid=-1; - - - if (result!=NVML_SUCCESS) { - fprintf(stderr,"%s: %d: cannot access NVML\n",__FILE__,__LINE__); - /* return random pick */ - retval=random_pick(max_gpu, th); - /* if this matches the previous value, select again */ - pthread_mutex_lock(&th->prev_mutex); - while (retval==th->prev) { - retval=random_pick(max_gpu, th); - } - - th->prev=retval; - pthread_mutex_unlock(&th->prev_mutex); - return retval; - } else { - /* iterate */ - nvmlDevice_t device; - nvmlUtilization_t nvmlUtilization; - nvmlMemory_t nvmlMemory; - unsigned int min_util=101; /* GPU utilization */ - unsigned int max_util=0; /* GPU utilization */ - unsigned long long int max_free=0; /* max free memory */ - unsigned long long int min_free=ULLONG_MAX; /* max free memory */ - int ci; - for (ci=0; ci<=max_gpu; ci++) { - result=nvmlDeviceGetHandleByIndex(ci, &device); - result=nvmlDeviceGetUtilizationRates(device, &nvmlUtilization); - result=nvmlDeviceGetMemoryInfo(device, &nvmlMemory); - if (min_util>nvmlUtilization.gpu) { - min_util=nvmlUtilization.gpu; - minid=ci; - } - if (max_utilnvmlMemory.free) { - min_free=nvmlMemory.free; - } - } - result = nvmlShutdown(); - /* give priority for selection a GPU with max free memory, - if there is a tie, use min utilization as second criterion */ - /* if all have 0 usage, again use random */ - if (max_free==min_free && max_util==min_util) { - retval=random_pick(max_gpu,th); - /* if this value matches previous one, select again */ - pthread_mutex_lock(&th->prev_mutex); - while(retval==th->prev) { - retval=random_pick(max_gpu,th); - } - th->prev=retval; - pthread_mutex_unlock(&th->prev_mutex); - return retval; - } else { - if (max_free==min_free) { /* all cards have equal free mem */ - retval=(int)minid; - } else { - retval=(int)maxid; - } - } - } - - /* update last pick */ - pthread_mutex_lock(&th->prev_mutex); - th->prev=retval; - pthread_mutex_unlock(&th->prev_mutex); - - return retval; -#endif -} diff --git a/src/lib/manifold_average.c b/src/lib/manifold_average.c deleted file mode 100644 index 2e56207..0000000 --- a/src/lib/manifold_average.c +++ /dev/null @@ -1,627 +0,0 @@ -/* - * - Copyright (C) 2014 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - -#include "sagecal.h" -#include - -//#define DEBUG -typedef struct thread_data_manavg_ { - double *Y; - int startM; - int endM; - int Niter; - int N; - int M; - int Nf; -} thread_data_manavg_t; - -/* worker thread function for manifold average+projection */ -static void* -manifold_average_threadfn(void *data) { - thread_data_manavg_t *t=(thread_data_manavg_t*)data; - int ci,cj,iter; - double *Yl; - complex double *J3,*Jp; - /* local storage 2Nx2 x Nf complex values */ - if ((Yl=(double*)malloc((size_t)t->N*8*t->Nf*sizeof(double)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((J3=(complex double*)malloc((size_t)t->N*4*sizeof(complex double)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((Jp=(complex double*)malloc((size_t)t->N*4*sizeof(complex double)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } -#ifdef DEBUG - complex double *Jerr; - if ((Jerr=(complex double*)malloc((size_t)t->N*4*sizeof(complex double)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } -#endif - - complex double *Yc=(complex double*)Yl; - complex double a=1.0/(double)t->Nf+0.0*_Complex_I; - - /* work for SVD */ - complex double *WORK=0; - complex double w[1]; - double RWORK[32]; /* size > 5*max_matrix_dimension */ - complex double JTJ[4],U[4],VT[4]; - double S[2]; - - int status=my_zgesvd('A','A',2,2,JTJ,2,S,U,2,VT,2,w,-1,RWORK); - if (status!=0) { - fprintf(stderr,"%s: %d: LAPACK error %d\n",__FILE__,__LINE__,status); - exit(1); - } - int lwork=(int)w[0]; - if ((WORK=(complex double*)malloc((size_t)(int)lwork*sizeof(complex double)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - for (ci=t->startM; ci<=t->endM; ci++) { - /* copy to local storage */ - for (cj=0; cjNf; cj++) { - my_dcopy(t->N, &t->Y[cj*8*t->N*t->M+ci*8*t->N], 8, &Yl[cj*8*t->N], 4); - my_dcopy(t->N, &t->Y[cj*8*t->N*t->M+ci*8*t->N+1], 8, &Yl[cj*8*t->N+1], 4); - my_dcopy(t->N, &t->Y[cj*8*t->N*t->M+ci*8*t->N+4], 8, &Yl[cj*8*t->N+2], 4); - my_dcopy(t->N, &t->Y[cj*8*t->N*t->M+ci*8*t->N+5], 8, &Yl[cj*8*t->N+3], 4); - my_dcopy(t->N, &t->Y[cj*8*t->N*t->M+ci*8*t->N+2], 8, &Yl[cj*8*t->N+4*t->N], 4); - my_dcopy(t->N, &t->Y[cj*8*t->N*t->M+ci*8*t->N+3], 8, &Yl[cj*8*t->N+4*t->N+1], 4); - my_dcopy(t->N, &t->Y[cj*8*t->N*t->M+ci*8*t->N+6], 8, &Yl[cj*8*t->N+4*t->N+2], 4); - my_dcopy(t->N, &t->Y[cj*8*t->N*t->M+ci*8*t->N+7], 8, &Yl[cj*8*t->N+4*t->N+3], 4); - - } - /* first averaging, select random block in [0,Nf-1] to project to */ - int cr=rand()%(t->Nf); /* remainder always in [0,Nf-1] */ - /* J3 <= cr th block */ - my_ccopy(t->N*4,&Yc[cr*t->N*4],1,J3,1); - /* project the remainder */ - for (cj=0; cjN,J3,&Yc[cj*t->N*4]); - } - for (cj=cr+1; cjNf; cj++) { - project_procrustes_block(t->N,J3,&Yc[cj*t->N*4]); - } - - - /* now each 2, 2N complex vales is one J block */ - /* average values and project to common average */ - for (iter=0; iterNiter; iter++) { - /* J3 <= 1st block */ - my_ccopy(t->N*4,Yc,1,J3,1); - /* add the remainder */ - for (cj=1; cjNf; cj++) { - my_caxpy(t->N*4,&Yc[cj*t->N*4],1.0+_Complex_I*0.0,J3); - } - my_cscal(t->N*4,a,J3); - /* now find unitary matrix using Procrustes problem */ - for (cj=0; cjNf; cj++) { - /* find product JTJ = J^H J3 */ - my_zgemm('C','N',2,2,2*t->N,1.0+_Complex_I*0.0,&Yc[cj*t->N*4],2*t->N,J3,2*t->N,0.0+_Complex_I*0.0,JTJ,2); - status=my_zgesvd('A','A',2,2,JTJ,2,S,U,2,VT,2,WORK,lwork,RWORK); - //printf("%d %d %lf %lf\n",ci,cj,S[0],S[1]); - /* find JTJ= U V^H */ - my_zgemm('N','N',2,2,2,1.0+_Complex_I*0.0,U,2,VT,2,0.0+_Complex_I*0.0,JTJ,2); - /* find J*(JTJ) : projected matrix */ - my_zgemm('N','N',2*t->N,2,2,1.0+_Complex_I*0.0,&Yc[cj*t->N*4],2*t->N,JTJ,2,0.0+_Complex_I*0.0,Jp,2*t->N); - /* copy back */ - my_ccopy(t->N*4,Jp,1,&Yc[cj*t->N*4],1); -#ifdef DEBUG - /* calculate error between projected value and global mean */ - my_ccopy(t->N*4,J3,1,Jerr,1); - my_caxpy(t->N*4,&Yc[cj*t->N*4],-1.0+_Complex_I*0.0,Jerr); - printf("Error freq=%d dir=%d iter=%d %lf\n",cj,ci,iter,my_cnrm2(t->N*4,Jerr)); -#endif - } - } - - /* now get a fresh copy, because we should modify Y only by - one unitary matrix */ - my_ccopy(t->N*4,Yc,1,J3,1); - /* add the remainder */ - for (cj=1; cjNf; cj++) { - my_caxpy(t->N*4,&Yc[cj*t->N*4],1.0+_Complex_I*0.0,J3); - } - my_cscal(t->N*4,a,J3); - for (cj=0; cjNf; cj++) { - my_dcopy(t->N, &t->Y[cj*8*t->N*t->M+ci*8*t->N], 8, &Yl[cj*8*t->N], 4); - my_dcopy(t->N, &t->Y[cj*8*t->N*t->M+ci*8*t->N+1], 8, &Yl[cj*8*t->N+1], 4); - my_dcopy(t->N, &t->Y[cj*8*t->N*t->M+ci*8*t->N+4], 8, &Yl[cj*8*t->N+2], 4); - my_dcopy(t->N, &t->Y[cj*8*t->N*t->M+ci*8*t->N+5], 8, &Yl[cj*8*t->N+3], 4); - my_dcopy(t->N, &t->Y[cj*8*t->N*t->M+ci*8*t->N+2], 8, &Yl[cj*8*t->N+4*t->N], 4); - my_dcopy(t->N, &t->Y[cj*8*t->N*t->M+ci*8*t->N+3], 8, &Yl[cj*8*t->N+4*t->N+1], 4); - my_dcopy(t->N, &t->Y[cj*8*t->N*t->M+ci*8*t->N+6], 8, &Yl[cj*8*t->N+4*t->N+2], 4); - my_dcopy(t->N, &t->Y[cj*8*t->N*t->M+ci*8*t->N+7], 8, &Yl[cj*8*t->N+4*t->N+3], 4); - } - - for (cj=0; cjNf; cj++) { - /* find product JTJ = J^H J3 */ - my_zgemm('C','N',2,2,2*t->N,1.0+_Complex_I*0.0,&Yc[cj*t->N*4],2*t->N,J3,2*t->N,0.0+_Complex_I*0.0,JTJ,2); - - status=my_zgesvd('A','A',2,2,JTJ,2,S,U,2,VT,2,WORK,lwork,RWORK); - /* find JTJ= U V^H */ - my_zgemm('N','N',2,2,2,1.0+_Complex_I*0.0,U,2,VT,2,0.0+_Complex_I*0.0,JTJ,2); - /* find J*(JTJ) : projected matrix */ - my_zgemm('N','N',2*t->N,2,2,1.0+_Complex_I*0.0,&Yc[cj*t->N*4],2*t->N,JTJ,2,0.0+_Complex_I*0.0,Jp,2*t->N); - /* copy back */ - my_ccopy(t->N*4,Jp,1,&Yc[cj*t->N*4],1); - } - - /* copy back from local storage */ - for (cj=0; cjNf; cj++) { - my_dcopy(t->N, &Yl[cj*8*t->N], 4, &t->Y[cj*8*t->N*t->M+ci*8*t->N], 8); - my_dcopy(t->N, &Yl[cj*8*t->N+1], 4, &t->Y[cj*8*t->N*t->M+ci*8*t->N+1], 8); - my_dcopy(t->N, &Yl[cj*8*t->N+2], 4, &t->Y[cj*8*t->N*t->M+ci*8*t->N+4], 8); - my_dcopy(t->N, &Yl[cj*8*t->N+3], 4, &t->Y[cj*8*t->N*t->M+ci*8*t->N+5], 8); - my_dcopy(t->N, &Yl[cj*8*t->N+4*t->N], 4, &t->Y[cj*8*t->N*t->M+ci*8*t->N+2], 8); - my_dcopy(t->N, &Yl[cj*8*t->N+4*t->N+1], 4, &t->Y[cj*8*t->N*t->M+ci*8*t->N+3], 8); - my_dcopy(t->N, &Yl[cj*8*t->N+4*t->N+2], 4, &t->Y[cj*8*t->N*t->M+ci*8*t->N+6], 8); - my_dcopy(t->N, &Yl[cj*8*t->N+4*t->N+3], 4, &t->Y[cj*8*t->N*t->M+ci*8*t->N+7], 8); - - } - } - -#ifdef DEBUG - free(Jerr); -#endif - free(Yl); - free(J3); - free(Jp); - free(WORK); - return NULL; -} - -int -calculate_manifold_average(int N,int M,int Nf,double *Y,int Niter,int Nt) { - /* Y : each 2Nx2xM blocks belong to one freq, - select one 2Nx2 from this, reorder to J format : Nf blocks - and average */ - pthread_attr_t attr; - pthread_t *th_array; - thread_data_manavg_t *threaddata; - - int ci,Nthb0,Nthb,nth,nth1; - /* clusters per thread */ - Nthb0=(M+Nt-1)/Nt; - - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((threaddata=(thread_data_manavg_t*)malloc((size_t)Nt*sizeof(thread_data_manavg_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - ci=0; - for (nth=0; nth 5*max_matrix_dimension */ - complex double JTJ[4],U[4],VT[4]; - double S[2]; - - int status=my_zgesvd('A','A',2,2,JTJ,2,S,U,2,VT,2,w,-1,RWORK); - if (status!=0) { - fprintf(stderr,"%s: %d: LAPACK error %d\n",__FILE__,__LINE__,status); - exit(1); - } - int lwork=(int)w[0]; - if ((WORK=(complex double*)malloc((size_t)(int)lwork*sizeof(complex double)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - /* find product JTJ = Y^H X */ - my_zgemm('C','N',2,2,2*N,1.0+_Complex_I*0.0,Y,2*N,X,2*N,0.0+_Complex_I*0.0,JTJ,2); - /* JTJ = U S V^H */ - status=my_zgesvd('A','A',2,2,JTJ,2,S,U,2,VT,2,WORK,lwork,RWORK); - /* find JTJ= U V^H */ - my_zgemm('N','N',2,2,2,1.0+_Complex_I*0.0,U,2,VT,2,0.0+_Complex_I*0.0,JTJ,2); - /* find Y*(JTJ) : projected matrix -> store in X */ - my_zgemm('N','N',2*N,2,2,1.0+_Complex_I*0.0,Y,2*N,JTJ,2,0.0+_Complex_I*0.0,X,2*N); - - my_dcopy(N, &Jx[0], 4, &J1[0], 8); - my_dcopy(N, &Jx[1], 4, &J1[0+1], 8); - my_dcopy(N, &Jx[2], 4, &J1[0+4], 8); - my_dcopy(N, &Jx[3], 4, &J1[0+5], 8); - my_dcopy(N, &Jx[4*N], 4, &J1[0+2], 8); - my_dcopy(N, &Jx[4*N+1], 4, &J1[0+3], 8); - my_dcopy(N, &Jx[4*N+2], 4, &J1[0+6], 8); - my_dcopy(N, &Jx[4*N+3], 4, &J1[0+7], 8); - - - free(WORK); - free(X); - free(Y); - return 0; -} - - - -int -project_procrustes_block(int N,complex double *X,complex double *Y) { - /* min ||X - Y U || find U */ - complex double *Jlocal; - /* local storage */ - if ((Jlocal=(complex double*)malloc((size_t)N*4*sizeof(complex double)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - /* work for SVD */ - complex double *WORK=0; - complex double w[1]; - double RWORK[32]; /* size > 5*max_matrix_dimension */ - complex double JTJ[4],U[4],VT[4]; - double S[2]; - - int status=my_zgesvd('A','A',2,2,JTJ,2,S,U,2,VT,2,w,-1,RWORK); - if (status!=0) { - fprintf(stderr,"%s: %d: LAPACK error %d\n",__FILE__,__LINE__,status); - exit(1); - } - int lwork=(int)w[0]; - if ((WORK=(complex double*)malloc((size_t)(int)lwork*sizeof(complex double)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - /* find product JTJ = Y^H X */ - my_zgemm('C','N',2,2,2*N,1.0+_Complex_I*0.0,Y,2*N,X,2*N,0.0+_Complex_I*0.0,JTJ,2); - /* JTJ = U S V^H */ - status=my_zgesvd('A','A',2,2,JTJ,2,S,U,2,VT,2,WORK,lwork,RWORK); - /* find JTJ= U V^H */ - my_zgemm('N','N',2,2,2,1.0+_Complex_I*0.0,U,2,VT,2,0.0+_Complex_I*0.0,JTJ,2); - /* find Y*(JTJ) : projected matrix -> store in Jlocal */ - my_zgemm('N','N',2*N,2,2,1.0+_Complex_I*0.0,Y,2*N,JTJ,2,0.0+_Complex_I*0.0,Jlocal,2*N); - - /* copy Jlocal -> Y */ - my_dcopy(8*N, (double*)Jlocal, 1, (double*)Y, 1); - - free(WORK); - free(Jlocal); - return 0; -} - - - - -//#define DEBUG -/* Extract only the phase of diagonal entries from solutions - p: 8Nx1 solutions, orders as [(real,imag)vec(J1),(real,imag)vec(J2),...] - pout: 8Nx1 phases (exp(j*phase)) of solutions, after joint diagonalization of p - N: no. of 2x2 Jones matrices in p, having common unitary ambiguity - niter: no of iterations for Jacobi rotation */ -int -extract_phases(double *p, double *pout, int N, int niter) { - - /* local storage */ - complex double *J,*Jcopy; - /* local storage, change ordering of solutions [J_1^T,J_2^T,...]^T */ - if ((J=(complex double*)malloc((size_t)N*4*sizeof(complex double)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((Jcopy=(complex double*)malloc((size_t)N*4*sizeof(complex double)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - double *Jx=(double *)J; - /* copy to get correct format */ - my_dcopy(N, &p[0], 8, &Jx[0], 4); - my_dcopy(N, &p[0+1], 8, &Jx[1], 4); - my_dcopy(N, &p[0+4], 8, &Jx[2], 4); - my_dcopy(N, &p[0+5], 8, &Jx[3], 4); - my_dcopy(N, &p[0+2], 8, &Jx[4*N], 4); - my_dcopy(N, &p[0+3], 8, &Jx[4*N+1], 4); - my_dcopy(N, &p[0+6], 8, &Jx[4*N+2], 4); - my_dcopy(N, &p[0+7], 8, &Jx[4*N+3], 4); - - complex double h[3],Hc[9]; - double H[9]; - double W[3],Z[3]; - double w[1],*WORK; - int IWORK[15],IFAIL[3],info; - int ni,ci; - complex double c,s,G[4]; - -#ifdef DEBUG - printf("J=[\n"); - for (ci=0; ci=0.0) { - c=sqrt(0.5+Z[0]*0.5)+_Complex_I*0.0; - s=0.5*(Z[1]-_Complex_I*Z[2])/c; - } else { - /* flip sign of eigenvector */ - c=sqrt(0.5-Z[0]*0.5)+_Complex_I*0.0; - s=0.5*(-Z[1]+_Complex_I*Z[2])/c; - } - /* form Givens rotation matrix */ - G[0]=c; - G[1]=-s; - G[2]=conj(s); - G[3]=conj(c); -#ifdef DEBUG - printf("G=[\n"); - printf("%lf+j*(%lf), %lf+j*(%lf)\n",creal(G[0]),cimag(G[0]),creal(G[2]),cimag(G[2])); - printf("%lf+j*(%lf), %lf+j*(%lf)\n",creal(G[1]),cimag(G[1]),creal(G[3]),cimag(G[3])); - printf("];\n"); -#endif - /* rotate J <= J * G^H: Jcopy = 1 x J x G^H + 0 x Jcopy */ - my_zgemm('N','C',2*N,2,2,1.0+_Complex_I*0.0,J,2*N,G,2,0.0+_Complex_I*0.0,Jcopy,2*N); - memcpy(J,Jcopy,(size_t)4*N*sizeof(complex double)); -#ifdef DEBUG - printf("JGH=[\n"); - for (ci=0; ci=0.0) { - c=sqrt(0.5+Z[0]*0.5)+_Complex_I*0.0; - s=0.5*(Z[1]-_Complex_I*Z[2])/c; - } else { - /* flip sign of eigenvector */ - c=sqrt(0.5-Z[0]*0.5)+_Complex_I*0.0; - s=0.5*(-Z[1]+_Complex_I*Z[2])/c; - } - /* form Givens rotation matrix */ - G[0]=c; - G[1]=-s; - G[2]=conj(s); - G[3]=conj(c); -#ifdef DEBUG - printf("G=[\n"); - printf("%lf+j*(%lf), %lf+j*(%lf)\n",creal(G[0]),cimag(G[0]),creal(G[2]),cimag(G[2])); - printf("%lf+j*(%lf), %lf+j*(%lf)\n",creal(G[1]),cimag(G[1]),creal(G[3]),cimag(G[3])); - printf("];\n"); -#endif - /* rotate J <= J * G^H: Jcopy = 1 x J x G^H + 0 x Jcopy */ - my_zgemm('N','C',2*N,2,2,1.0+_Complex_I*0.0,J,2*N,G,2,0.0+_Complex_I*0.0,Jcopy,2*N); - /* before copying updated result, find residual norm */ - /* J = -Jcopy + J */ - my_caxpy(4*N,Jcopy,-1.0+_Complex_I*0.0,J); -#ifdef DEBUG - printf("Iter %d residual=%lf\n",ni,my_cnrm2(4*N,J)); -#endif - memcpy(J,Jcopy,(size_t)4*N*sizeof(complex double)); -#ifdef DEBUG - printf("JGH=[\n"); - for (ci=0; ci - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - -#include "cuda.h" -#include -#include -#include -#include "sagecal.h" - -/* enable this for checking for kernel failure */ -//#define CUDA_DBG - -/* matrix multiplications */ -/* C=A*B */ -__device__ void -amb(const cuFloatComplex *__restrict__ a, const cuFloatComplex *__restrict__ b, cuFloatComplex *__restrict__ c) { - c[0]=cuCaddf(cuCmulf(a[0],b[0]),cuCmulf(a[1],b[2])); - c[1]=cuCaddf(cuCmulf(a[0],b[1]),cuCmulf(a[1],b[3])); - c[2]=cuCaddf(cuCmulf(a[2],b[0]),cuCmulf(a[3],b[2])); - c[3]=cuCaddf(cuCmulf(a[2],b[1]),cuCmulf(a[3],b[3])); -} -/* C=A*B^H */ -__device__ void -ambt(const cuFloatComplex *__restrict__ a, const cuFloatComplex *__restrict__ b, cuFloatComplex *__restrict__ c) { - c[0]=cuCaddf(cuCmulf(a[0],cuConjf(b[0])),cuCmulf(a[1],cuConjf(b[1]))); - c[1]=cuCaddf(cuCmulf(a[0],cuConjf(b[2])),cuCmulf(a[1],cuConjf(b[3]))); - c[2]=cuCaddf(cuCmulf(a[2],cuConjf(b[0])),cuCmulf(a[3],cuConjf(b[1]))); - c[3]=cuCaddf(cuCmulf(a[2],cuConjf(b[2])),cuCmulf(a[3],cuConjf(b[3]))); -} - -/* C=A^H * B */ -__device__ void -atmb(const cuFloatComplex *__restrict__ a, const cuFloatComplex *__restrict__ b, cuFloatComplex *__restrict__ c) { - c[0]=cuCaddf(cuCmulf(cuConjf(a[0]),b[0]),cuCmulf(cuConjf(a[2]),b[2])); - c[1]=cuCaddf(cuCmulf(cuConjf(a[0]),b[1]),cuCmulf(cuConjf(a[2]),b[3])); - c[2]=cuCaddf(cuCmulf(cuConjf(a[1]),b[0]),cuCmulf(cuConjf(a[3]),b[2])); - c[3]=cuCaddf(cuCmulf(cuConjf(a[1]),b[1]),cuCmulf(cuConjf(a[3]),b[3])); -} - - -__global__ void -kernel_fns_fhess(int N, int Nbase, const cuFloatComplex *__restrict__ x, const cuFloatComplex *__restrict__ eta, cuFloatComplex *__restrict__ hess0, const float *__restrict__ y, const float *__restrict__ coh, const short *__restrict__ bbh) { - - /* eta0: each block will store result in its own block */ - /* global thread index : equal to the baseline */ - unsigned int n = threadIdx.x + blockDim.x*blockIdx.x; - int bid=blockIdx.x; - int tid=threadIdx.x; - /* 4x2xblockDim.x cuFloatComplex values and 2xblockDim.x int values */ - extern __shared__ cuFloatComplex hs[]; - int *stm= (int*)&hs[8*blockDim.x]; - stm[2*tid]=-1; - stm[2*tid+1]=-1; - /* x,eta: 2Nx2 matrix */ - if(n=0 - */ - if (sta1>=0 && sta2>=0) { - stm[2*tid]=sta1; - stm[2*tid+1]=sta2; - - cuFloatComplex G1[4]; - cuFloatComplex G2[4]; - cuFloatComplex E1[4]; - cuFloatComplex E2[4]; - /* J1 */ - G1[0]=x[sta1*2]; - G1[1]=x[sta1*2+N*2]; - G1[2]=x[sta1*2+1]; - G1[3]=x[sta1*2+N*2+1]; - /* conjugate this to get J2^H */ - G2[0]=x[sta2*2]; - G2[1]=x[sta2*2+N*2]; - G2[2]=x[sta2*2+1]; - G2[3]=x[sta2*2+N*2+1]; - E1[0]=eta[2*sta1]; - E1[1]=eta[2*sta1+2*N]; - E1[2]=eta[2*sta1+1]; - E1[3]=eta[2*sta1+2*N+1]; - E2[0]=eta[2*sta2]; - E2[1]=eta[2*sta2+2*N]; - E2[2]=eta[2*sta2+1]; - E2[3]=eta[2*sta2+2*N+1]; - - - cuFloatComplex C[4]; - C[0].x=coh[8*n]; - C[0].y=coh[8*n+1]; - C[1].x=coh[8*n+2]; - C[1].y=coh[8*n+3]; - C[2].x=coh[8*n+4]; - C[2].y=coh[8*n+5]; - C[3].x=coh[8*n+6]; - C[3].y=coh[8*n+7]; - - /* G1*C*G2' */ - cuFloatComplex T1[4]; - cuFloatComplex T2[4]; - /* T=G1*C */ - amb(G1,C,T1); - ambt(T1,G2,T2); - - /* res=V(2*ck-1:2*ck,:)-x(2*p-1:2*p,:)*C*x(2*q-1:2*q,:)'; */ - /* V->U */ - cuFloatComplex res[4],res1[4]; - res[0]=cuCsubf(make_cuFloatComplex(y[8*n],y[8*n+1]),T2[0]); - res[1]=cuCsubf(make_cuFloatComplex(y[8*n+2],y[8*n+3]),T2[1]); - res[2]=cuCsubf(make_cuFloatComplex(y[8*n+4],y[8*n+5]),T2[2]); - res[3]=cuCsubf(make_cuFloatComplex(y[8*n+6],y[8*n+7]),T2[3]); - - /* - res1=x(2*p-1:2*p,:)*C*eta(2*q-1:2*q,:)'+eta(2*p-1:2*p,:)*C*x(2*q-1:2*q,:)'; - */ - /* G1*C*E2' */ - amb(G1,C,T1); - ambt(T1,E2,T2); - res1[0]=T2[0]; - res1[1]=T2[1]; - res1[2]=T2[2]; - res1[3]=T2[3]; - /* E1*C*G2' */ - amb(E1,C,T1); - ambt(T1,G2,T2); - res1[0]=cuCaddf(res1[0],T2[0]); - res1[1]=cuCaddf(res1[1],T2[1]); - res1[2]=cuCaddf(res1[2],T2[2]); - res1[3]=cuCaddf(res1[3],T2[3]); - - - /* - hess(2*p-1:2*p,:)=hess(2*p-1:2*p,:)+(res*eta(2*q-1:2*q,:)-res1*x(2*q-1:2*q,:))*C'; - */ - - /* (res*E2-res1*G2)*C' */ - amb(res,E2,T1); - amb(res1,G2,T2); - T1[0]=cuCsubf(T1[0],T2[0]); - T1[1]=cuCsubf(T1[1],T2[1]); - T1[2]=cuCsubf(T1[2],T2[2]); - T1[3]=cuCsubf(T1[3],T2[3]); - ambt(T1,C,T2); - - hs[8*tid]=T2[0]; - hs[8*tid+1]=T2[1]; - hs[8*tid+2]=T2[2]; - hs[8*tid+3]=T2[3]; - - - /* - hess(2*q-1:2*q,:)=hess(2*q-1:2*q,:)+(res'*eta(2*p-1:2*p,:)-res1'*x(2*p-1:2*p,:))*C; - */ - - /* (res'*E1-res1'*G1)*C */ - atmb(res,E1,T1); - atmb(res1,G1,T2); - T1[0]=cuCsubf(T1[0],T2[0]); - T1[1]=cuCsubf(T1[1],T2[1]); - T1[2]=cuCsubf(T1[2],T2[2]); - T1[3]=cuCsubf(T1[3],T2[3]); - amb(T1,C,T2); - - - hs[8*tid+4]=T2[0]; - hs[8*tid+5]=T2[1]; - hs[8*tid+6]=T2[2]; - hs[8*tid+7]=T2[3]; - - } - } - - __syncthreads(); - /* copy back to global memory */ - if (tid==0) { - for(int ci=0; ci=0 && sta2>=0) { - hess0[2*sta1+bid*4*N]=cuCaddf(hess0[2*sta1+bid*4*N],hs[8*ci]); - hess0[2*sta1+2*N+bid*4*N]=cuCaddf(hess0[2*sta1+2*N+bid*4*N],hs[8*ci+1]); - hess0[2*sta1+1+bid*4*N]=cuCaddf(hess0[2*sta1+1+bid*4*N],hs[8*ci+2]); - hess0[2*sta1+2*N+1+bid*4*N]=cuCaddf(hess0[2*sta1+2*N+1+bid*4*N],hs[8*ci+3]); - hess0[2*sta2+bid*4*N]=cuCaddf(hess0[2*sta2+bid*4*N],hs[8*ci+4]); - hess0[2*sta2+2*N+bid*4*N]=cuCaddf(hess0[2*sta2+2*N+bid*4*N],hs[8*ci+5]); - hess0[2*sta2+1+bid*4*N]=cuCaddf(hess0[2*sta2+1+bid*4*N],hs[8*ci+6]); - hess0[2*sta2+2*N+1+bid*4*N]=cuCaddf(hess0[2*sta2+2*N+1+bid*4*N],hs[8*ci+7]); - } - } - } - __syncthreads(); -} - -__global__ void -kernel_fns_fhess_robust1(int N, int Nbase, const cuFloatComplex *__restrict__ x, const cuFloatComplex *__restrict__ eta, cuFloatComplex *__restrict__ hess0, const float *__restrict__ y, const float *__restrict__ coh, const short *__restrict__ bbh, const float *__restrict__ wtd) { - - /* eta0: each block will store result in its own block */ - /* global thread index : equal to the baseline */ - unsigned int n = threadIdx.x + blockDim.x*blockIdx.x; - int bid=blockIdx.x; - int tid=threadIdx.x; - /* 4x2xblockDim.x cuFloatComplex values and 2xblockDim.x int values */ - extern __shared__ cuFloatComplex hs[]; - int *stm= (int*)&hs[8*blockDim.x]; - stm[2*tid]=-1; - stm[2*tid+1]=-1; - /* x,eta: 2Nx2 matrix */ - if(n=0 - */ - if (sta1>=0 && sta2>=0) { - stm[2*tid]=sta1; - stm[2*tid+1]=sta2; - - cuFloatComplex G1[4]; - cuFloatComplex G2[4]; - cuFloatComplex E1[4]; - cuFloatComplex E2[4]; - /* J1 */ - G1[0]=x[sta1*2]; - G1[1]=x[sta1*2+N*2]; - G1[2]=x[sta1*2+1]; - G1[3]=x[sta1*2+N*2+1]; - /* conjugate this to get J2^H */ - G2[0]=x[sta2*2]; - G2[1]=x[sta2*2+N*2]; - G2[2]=x[sta2*2+1]; - G2[3]=x[sta2*2+N*2+1]; - E1[0]=eta[2*sta1]; - E1[1]=eta[2*sta1+2*N]; - E1[2]=eta[2*sta1+1]; - E1[3]=eta[2*sta1+2*N+1]; - E2[0]=eta[2*sta2]; - E2[1]=eta[2*sta2+2*N]; - E2[2]=eta[2*sta2+1]; - E2[3]=eta[2*sta2+2*N+1]; - - - cuFloatComplex C[4]; - C[0].x=coh[8*n]; - C[0].y=coh[8*n+1]; - C[1].x=coh[8*n+2]; - C[1].y=coh[8*n+3]; - C[2].x=coh[8*n+4]; - C[2].y=coh[8*n+5]; - C[3].x=coh[8*n+6]; - C[3].y=coh[8*n+7]; - - /* G1*C*G2' */ - cuFloatComplex T1[4]; - cuFloatComplex T2[4]; - /* T=G1*C */ - amb(G1,C,T1); - ambt(T1,G2,T2); - - /* res=V(2*ck-1:2*ck,:)-x(2*p-1:2*p,:)*C*x(2*q-1:2*q,:)'; */ - /* V->U */ - cuFloatComplex res[4],res1[4]; - res[0]=cuCsubf(make_cuFloatComplex(y[8*n],y[8*n+1]),T2[0]); - res[1]=cuCsubf(make_cuFloatComplex(y[8*n+2],y[8*n+3]),T2[1]); - res[2]=cuCsubf(make_cuFloatComplex(y[8*n+4],y[8*n+5]),T2[2]); - res[3]=cuCsubf(make_cuFloatComplex(y[8*n+6],y[8*n+7]),T2[3]); - - /* - res1=x(2*p-1:2*p,:)*C*eta(2*q-1:2*q,:)'+eta(2*p-1:2*p,:)*C*x(2*q-1:2*q,:)'; - */ - /* G1*C*E2' */ - amb(G1,C,T1); - ambt(T1,E2,T2); - res1[0]=T2[0]; - res1[1]=T2[1]; - res1[2]=T2[2]; - res1[3]=T2[3]; - /* E1*C*G2' */ - amb(E1,C,T1); - ambt(T1,G2,T2); - res1[0]=cuCaddf(res1[0],T2[0]); - res1[1]=cuCaddf(res1[1],T2[1]); - res1[2]=cuCaddf(res1[2],T2[2]); - res1[3]=cuCaddf(res1[3],T2[3]); - - - /* - hess(2*p-1:2*p,:)=hess(2*p-1:2*p,:)+(res*eta(2*q-1:2*q,:)-res1*x(2*q-1:2*q,:))*C'; - */ - - /* (res*E2-res1*G2)*C' */ - amb(res,E2,T1); - amb(res1,G2,T2); - T1[0]=cuCsubf(T1[0],T2[0]); - T1[1]=cuCsubf(T1[1],T2[1]); - T1[2]=cuCsubf(T1[2],T2[2]); - T1[3]=cuCsubf(T1[3],T2[3]); - ambt(T1,C,T2); - - float wtdn=wtd[n]; - /* mult with weights */ - T2[0].x=wtdn*T2[0].x; - T2[0].y=wtdn*T2[0].y; - T2[1].x=wtdn*T2[1].x; - T2[1].y=wtdn*T2[1].y; - T2[2].x=wtdn*T2[2].x; - T2[2].y=wtdn*T2[2].y; - T2[3].x=wtdn*T2[3].x; - T2[3].y=wtdn*T2[3].y; - - - hs[8*tid]=T2[0]; - hs[8*tid+1]=T2[1]; - hs[8*tid+2]=T2[2]; - hs[8*tid+3]=T2[3]; - - - /* - hess(2*q-1:2*q,:)=hess(2*q-1:2*q,:)+(res'*eta(2*p-1:2*p,:)-res1'*x(2*p-1:2*p,:))*C; - */ - - /* (res'*E1-res1'*G1)*C */ - atmb(res,E1,T1); - atmb(res1,G1,T2); - T1[0]=cuCsubf(T1[0],T2[0]); - T1[1]=cuCsubf(T1[1],T2[1]); - T1[2]=cuCsubf(T1[2],T2[2]); - T1[3]=cuCsubf(T1[3],T2[3]); - amb(T1,C,T2); - - /* mult with weights */ - T2[0].x=wtdn*T2[0].x; - T2[0].y=wtdn*T2[0].y; - T2[1].x=wtdn*T2[1].x; - T2[1].y=wtdn*T2[1].y; - T2[2].x=wtdn*T2[2].x; - T2[2].y=wtdn*T2[2].y; - T2[3].x=wtdn*T2[3].x; - T2[3].y=wtdn*T2[3].y; - - - hs[8*tid+4]=T2[0]; - hs[8*tid+5]=T2[1]; - hs[8*tid+6]=T2[2]; - hs[8*tid+7]=T2[3]; - - } - } - - __syncthreads(); - /* copy back to global memory */ - if (tid==0) { - for(int ci=0; ci=0 && sta2>=0) { - hess0[2*sta1+bid*4*N]=cuCaddf(hess0[2*sta1+bid*4*N],hs[8*ci]); - hess0[2*sta1+2*N+bid*4*N]=cuCaddf(hess0[2*sta1+2*N+bid*4*N],hs[8*ci+1]); - hess0[2*sta1+1+bid*4*N]=cuCaddf(hess0[2*sta1+1+bid*4*N],hs[8*ci+2]); - hess0[2*sta1+2*N+1+bid*4*N]=cuCaddf(hess0[2*sta1+2*N+1+bid*4*N],hs[8*ci+3]); - hess0[2*sta2+bid*4*N]=cuCaddf(hess0[2*sta2+bid*4*N],hs[8*ci+4]); - hess0[2*sta2+2*N+bid*4*N]=cuCaddf(hess0[2*sta2+2*N+bid*4*N],hs[8*ci+5]); - hess0[2*sta2+1+bid*4*N]=cuCaddf(hess0[2*sta2+1+bid*4*N],hs[8*ci+6]); - hess0[2*sta2+2*N+1+bid*4*N]=cuCaddf(hess0[2*sta2+2*N+1+bid*4*N],hs[8*ci+7]); - } - } - } - __syncthreads(); -} - - -__global__ void -kernel_fns_fhess_robust(int N, int Nbase, const cuFloatComplex *__restrict__ x, const cuFloatComplex *__restrict__ eta, cuFloatComplex *__restrict__ hess0, const float *__restrict__ y, const float *__restrict__ coh, const short *__restrict__ bbh, const float *__restrict__ wtd) { - - /* hess0: each block will store result in its own block */ - int bid=blockIdx.x; - int tid=threadIdx.x; - /* baselines */ - int nbase=N*(N-1)/2; - /* blocks per timeslot */ - int Bt=(nbase+blockDim.x-1)/blockDim.x; - - /* which timeslot */ - int ntime=bid/Bt; - /* which offset */ - int noff=bid%Bt; - /* local index within one timeslot, 0...N(N-1)/2-1 */ - unsigned int m = noff*blockDim.x+threadIdx.x; - /* global thread index : less than the total baselines */ - unsigned int n = ntime*nbase+m; - - /* 4x2xblockDim.x cuFloatComplex values and 2xblockDim.x int values */ - extern __shared__ cuFloatComplex hs[]; - int *stm= (int*)&hs[8*blockDim.x]; - stm[2*tid]=-1; - stm[2*tid+1]=-1; - /* x,eta: 2Nx2 matrix */ - if(m=0 - */ - if (sta1>=0 && sta2>=0) { - stm[2*tid]=sta1; - stm[2*tid+1]=sta2; - - cuFloatComplex G1[4]; - cuFloatComplex G2[4]; - cuFloatComplex E1[4]; - cuFloatComplex E2[4]; - /* J1 */ - G1[0]=x[sta1*2]; - G1[1]=x[sta1*2+N*2]; - G1[2]=x[sta1*2+1]; - G1[3]=x[sta1*2+N*2+1]; - /* conjugate this to get J2^H */ - G2[0]=x[sta2*2]; - G2[1]=x[sta2*2+N*2]; - G2[2]=x[sta2*2+1]; - G2[3]=x[sta2*2+N*2+1]; - E1[0]=eta[2*sta1]; - E1[1]=eta[2*sta1+2*N]; - E1[2]=eta[2*sta1+1]; - E1[3]=eta[2*sta1+2*N+1]; - E2[0]=eta[2*sta2]; - E2[1]=eta[2*sta2+2*N]; - E2[2]=eta[2*sta2+1]; - E2[3]=eta[2*sta2+2*N+1]; - - - cuFloatComplex C[4]; - C[0].x=coh[8*n]; - C[0].y=coh[8*n+1]; - C[1].x=coh[8*n+2]; - C[1].y=coh[8*n+3]; - C[2].x=coh[8*n+4]; - C[2].y=coh[8*n+5]; - C[3].x=coh[8*n+6]; - C[3].y=coh[8*n+7]; - - /* G1*C*G2' */ - cuFloatComplex T1[4]; - cuFloatComplex T2[4]; - /* T=G1*C */ - amb(G1,C,T1); - ambt(T1,G2,T2); - - /* res=V(2*ck-1:2*ck,:)-x(2*p-1:2*p,:)*C*x(2*q-1:2*q,:)'; */ - /* V->U */ - cuFloatComplex res[4],res1[4]; - res[0]=cuCsubf(make_cuFloatComplex(y[8*n],y[8*n+1]),T2[0]); - res[1]=cuCsubf(make_cuFloatComplex(y[8*n+2],y[8*n+3]),T2[1]); - res[2]=cuCsubf(make_cuFloatComplex(y[8*n+4],y[8*n+5]),T2[2]); - res[3]=cuCsubf(make_cuFloatComplex(y[8*n+6],y[8*n+7]),T2[3]); - - /* - res1=x(2*p-1:2*p,:)*C*eta(2*q-1:2*q,:)'+eta(2*p-1:2*p,:)*C*x(2*q-1:2*q,:)'; - */ - /* G1*C*E2' */ - amb(G1,C,T1); - ambt(T1,E2,T2); - res1[0]=T2[0]; - res1[1]=T2[1]; - res1[2]=T2[2]; - res1[3]=T2[3]; - /* E1*C*G2' */ - amb(E1,C,T1); - ambt(T1,G2,T2); - res1[0]=cuCaddf(res1[0],T2[0]); - res1[1]=cuCaddf(res1[1],T2[1]); - res1[2]=cuCaddf(res1[2],T2[2]); - res1[3]=cuCaddf(res1[3],T2[3]); - - - /* - hess(2*p-1:2*p,:)=hess(2*p-1:2*p,:)+(res*eta(2*q-1:2*q,:)-res1*x(2*q-1:2*q,:))*C'; - */ - - /* (res*E2-res1*G2)*C' */ - amb(res,E2,T1); - amb(res1,G2,T2); - T1[0]=cuCsubf(T1[0],T2[0]); - T1[1]=cuCsubf(T1[1],T2[1]); - T1[2]=cuCsubf(T1[2],T2[2]); - T1[3]=cuCsubf(T1[3],T2[3]); - ambt(T1,C,T2); - - float wtdn=wtd[n]; - /* mult with weights */ - T2[0].x=wtdn*T2[0].x; - T2[0].y=wtdn*T2[0].y; - T2[1].x=wtdn*T2[1].x; - T2[1].y=wtdn*T2[1].y; - T2[2].x=wtdn*T2[2].x; - T2[2].y=wtdn*T2[2].y; - T2[3].x=wtdn*T2[3].x; - T2[3].y=wtdn*T2[3].y; - - - hs[8*tid]=T2[0]; - hs[8*tid+1]=T2[1]; - hs[8*tid+2]=T2[2]; - hs[8*tid+3]=T2[3]; - - - /* - hess(2*q-1:2*q,:)=hess(2*q-1:2*q,:)+(res'*eta(2*p-1:2*p,:)-res1'*x(2*p-1:2*p,:))*C; - */ - - /* (res'*E1-res1'*G1)*C */ - atmb(res,E1,T1); - atmb(res1,G1,T2); - T1[0]=cuCsubf(T1[0],T2[0]); - T1[1]=cuCsubf(T1[1],T2[1]); - T1[2]=cuCsubf(T1[2],T2[2]); - T1[3]=cuCsubf(T1[3],T2[3]); - amb(T1,C,T2); - - /* mult with weights */ - T2[0].x=wtdn*T2[0].x; - T2[0].y=wtdn*T2[0].y; - T2[1].x=wtdn*T2[1].x; - T2[1].y=wtdn*T2[1].y; - T2[2].x=wtdn*T2[2].x; - T2[2].y=wtdn*T2[2].y; - T2[3].x=wtdn*T2[3].x; - T2[3].y=wtdn*T2[3].y; - - - hs[8*tid+4]=T2[0]; - hs[8*tid+5]=T2[1]; - hs[8*tid+6]=T2[2]; - hs[8*tid+7]=T2[3]; - - } - } - __syncthreads(); - - /* copy back to global memory */ - if (tid=0 always */ - hess0[2*tid+bid*4*N]=cuCaddf(hess0[2*tid+bid*4*N],hs[8*ci]); - hess0[2*tid+2*N+bid*4*N]=cuCaddf(hess0[2*tid+2*N+bid*4*N],hs[8*ci+1]); - hess0[2*tid+1+bid*4*N]=cuCaddf(hess0[2*tid+1+bid*4*N],hs[8*ci+2]); - hess0[2*tid+2*N+1+bid*4*N]=cuCaddf(hess0[2*tid+2*N+1+bid*4*N],hs[8*ci+3]); - } - if (sta2==tid) { /* note, tid >=0 always */ - hess0[2*tid+bid*4*N]=cuCaddf(hess0[2*tid+bid*4*N],hs[8*ci+4]); - hess0[2*tid+2*N+bid*4*N]=cuCaddf(hess0[2*tid+2*N+bid*4*N],hs[8*ci+5]); - hess0[2*tid+1+bid*4*N]=cuCaddf(hess0[2*tid+1+bid*4*N],hs[8*ci+6]); - hess0[2*tid+2*N+1+bid*4*N]=cuCaddf(hess0[2*tid+2*N+1+bid*4*N],hs[8*ci+7]); - } - } - } - __syncthreads(); -} - - -__global__ void -kernel_fns_fgrad(int N, int Nbase, const cuFloatComplex *__restrict__ x, cuFloatComplex *__restrict__ eta0, const float *__restrict__ y, const float *__restrict__ coh, const short *__restrict__ bbh) { - - /* eta0: each block will store result in its own block */ - /* global thread index : equal to the baseline */ - unsigned int n = threadIdx.x + blockDim.x*blockIdx.x; - int bid=blockIdx.x; - int tid=threadIdx.x; - /* 4x2xblockDim.x cuFloatComplex values and 2xblockDim.x int values */ - extern __shared__ cuFloatComplex eta[]; - int *stm= (int*)&eta[8*blockDim.x]; - stm[2*tid]=-1; - stm[2*tid+1]=-1; - /* x,eta: 2Nx2 matrix */ - if(n=0 - */ - if (sta1>=0 && sta2>=0) { - stm[2*tid]=sta1; - stm[2*tid+1]=sta2; - - cuFloatComplex G1[4]; - cuFloatComplex G2[4]; - /* J1 */ - G1[0]=x[sta1*2]; - G1[1]=x[sta1*2+N*2]; - G1[2]=x[sta1*2+1]; - G1[3]=x[sta1*2+N*2+1]; - /* conjugate this to get J2^H */ - G2[0]=x[sta2*2]; - G2[1]=x[sta2*2+N*2]; - G2[2]=x[sta2*2+1]; - G2[3]=x[sta2*2+N*2+1]; - - cuFloatComplex C[4]; - C[0].x=coh[8*n]; - C[0].y=coh[8*n+1]; - C[1].x=coh[8*n+2]; - C[1].y=coh[8*n+3]; - C[2].x=coh[8*n+4]; - C[2].y=coh[8*n+5]; - C[3].x=coh[8*n+6]; - C[3].y=coh[8*n+7]; - - /* G1*C*G2' */ - cuFloatComplex T1[4]; - cuFloatComplex T2[4]; - /* T=G1*C */ - amb(G1,C,T1); - ambt(T1,G2,T2); - - /* res=V(2*ck-1:2*ck,:)-x(2*p-1:2*p,:)*C*x(2*q-1:2*q,:)'; */ - /* V->U */ - cuFloatComplex res[4]; - res[0]=cuCsubf(make_cuFloatComplex(y[8*n],y[8*n+1]),T2[0]); - res[1]=cuCsubf(make_cuFloatComplex(y[8*n+2],y[8*n+3]),T2[1]); - res[2]=cuCsubf(make_cuFloatComplex(y[8*n+4],y[8*n+5]),T2[2]); - res[3]=cuCsubf(make_cuFloatComplex(y[8*n+6],y[8*n+7]),T2[3]); - - /* - grad(2*p-1:2*p,:)=grad(2*p-1:2*p,:)+res*x(2*q-1:2*q,:)*C'; - grad(2*q-1:2*q,:)=grad(2*q-1:2*q,:)+res'*x(2*p-1:2*p,:)*C; - */ - /* res*G2*C' */ - amb(res,G2,T1); - ambt(T1,C,T2); - - eta[8*tid]=T2[0]; - eta[8*tid+1]=T2[1]; - eta[8*tid+2]=T2[2]; - eta[8*tid+3]=T2[3]; - - - /* res'*G1*C */ - atmb(res,G1,T1); - amb(T1,C,T2); - - eta[8*tid+4]=T2[0]; - eta[8*tid+5]=T2[1]; - eta[8*tid+6]=T2[2]; - eta[8*tid+7]=T2[3]; - - } - } - - __syncthreads(); - /* copy back to global memory */ - if (tid==0) { - for(int ci=0; ci=0 && sta2>=0) { - eta0[2*sta1+bid*4*N]=cuCaddf(eta0[2*sta1+bid*4*N],eta[8*ci]); - eta0[2*sta1+2*N+bid*4*N]=cuCaddf(eta0[2*sta1+2*N+bid*4*N],eta[8*ci+1]); - eta0[2*sta1+1+bid*4*N]=cuCaddf(eta0[2*sta1+1+bid*4*N],eta[8*ci+2]); - eta0[2*sta1+2*N+1+bid*4*N]=cuCaddf(eta0[2*sta1+2*N+1+bid*4*N],eta[8*ci+3]); - eta0[2*sta2+bid*4*N]=cuCaddf(eta0[2*sta2+bid*4*N],eta[8*ci+4]); - eta0[2*sta2+2*N+bid*4*N]=cuCaddf(eta0[2*sta2+2*N+bid*4*N],eta[8*ci+5]); - eta0[2*sta2+1+bid*4*N]=cuCaddf(eta0[2*sta2+1+bid*4*N],eta[8*ci+6]); - eta0[2*sta2+2*N+1+bid*4*N]=cuCaddf(eta0[2*sta2+2*N+1+bid*4*N],eta[8*ci+7]); - - } - } - } - __syncthreads(); -} - -__global__ void -kernel_fns_fgrad_robust(int N, int Nbase, const cuFloatComplex *__restrict__ x, cuFloatComplex *__restrict__ eta0, const float *__restrict__ y, const float *__restrict__ coh, const short *__restrict__ bbh, const float *__restrict__ wtd) { - - /* eta0: each block will store result in its own block */ - int bid=blockIdx.x; - int tid=threadIdx.x; - /* baselines */ - int nbase=N*(N-1)/2; - /* blocks per timeslot */ - int Bt=(nbase+blockDim.x-1)/blockDim.x; - - /* which timeslot */ - int ntime=bid/Bt; - /* which offset */ - int noff=bid%Bt; - /* local index within one timeslot, 0...N(N-1)/2-1 */ - unsigned int m = noff*blockDim.x+threadIdx.x; - /* global thread index : less than the total baselines */ - unsigned int n = ntime*nbase+m; - /* 4x2xblockDim.x cuFloatComplex values and 2xblockDim.x int values */ - extern __shared__ cuFloatComplex eta[]; - int *stm= (int*)&eta[8*blockDim.x]; - stm[2*tid]=-1; - stm[2*tid+1]=-1; - /* x,eta: 2Nx2 matrix */ - if(m=0 - */ - if (sta1>=0 && sta2>=0) { - stm[2*tid]=sta1; - stm[2*tid+1]=sta2; - - cuFloatComplex G1[4]; - cuFloatComplex G2[4]; - /* J1 */ - G1[0]=x[sta1*2]; - G1[1]=x[sta1*2+N*2]; - G1[2]=x[sta1*2+1]; - G1[3]=x[sta1*2+N*2+1]; - /* conjugate this to get J2^H */ - G2[0]=x[sta2*2]; - G2[1]=x[sta2*2+N*2]; - G2[2]=x[sta2*2+1]; - G2[3]=x[sta2*2+N*2+1]; - - cuFloatComplex C[4]; - C[0].x=coh[8*n]; - C[0].y=coh[8*n+1]; - C[1].x=coh[8*n+2]; - C[1].y=coh[8*n+3]; - C[2].x=coh[8*n+4]; - C[2].y=coh[8*n+5]; - C[3].x=coh[8*n+6]; - C[3].y=coh[8*n+7]; - - /* G1*C*G2' */ - cuFloatComplex T1[4]; - cuFloatComplex T2[4]; - /* T=G1*C */ - amb(G1,C,T1); - ambt(T1,G2,T2); - - /* res=V(2*ck-1:2*ck,:)-x(2*p-1:2*p,:)*C*x(2*q-1:2*q,:)'; */ - /* V->U */ - cuFloatComplex res[4]; - res[0]=cuCsubf(make_cuFloatComplex(y[8*n],y[8*n+1]),T2[0]); - res[1]=cuCsubf(make_cuFloatComplex(y[8*n+2],y[8*n+3]),T2[1]); - res[2]=cuCsubf(make_cuFloatComplex(y[8*n+4],y[8*n+5]),T2[2]); - res[3]=cuCsubf(make_cuFloatComplex(y[8*n+6],y[8*n+7]),T2[3]); - - /* - grad(2*p-1:2*p,:)=grad(2*p-1:2*p,:)+res*x(2*q-1:2*q,:)*C'; - grad(2*q-1:2*q,:)=grad(2*q-1:2*q,:)+res'*x(2*p-1:2*p,:)*C; - */ - /* res*G2*C' */ - amb(res,G2,T1); - ambt(T1,C,T2); - - float wtdn=wtd[n]; - /* mult with weights */ - T2[0].x=wtdn*T2[0].x; - T2[0].y=wtdn*T2[0].y; - T2[1].x=wtdn*T2[1].x; - T2[1].y=wtdn*T2[1].y; - T2[2].x=wtdn*T2[2].x; - T2[2].y=wtdn*T2[2].y; - T2[3].x=wtdn*T2[3].x; - T2[3].y=wtdn*T2[3].y; - - eta[8*tid]=T2[0]; - eta[8*tid+1]=T2[1]; - eta[8*tid+2]=T2[2]; - eta[8*tid+3]=T2[3]; - - - /* res'*G1*C */ - atmb(res,G1,T1); - amb(T1,C,T2); - - /* mult with weights */ - T2[0].x=wtdn*T2[0].x; - T2[0].y=wtdn*T2[0].y; - T2[1].x=wtdn*T2[1].x; - T2[1].y=wtdn*T2[1].y; - T2[2].x=wtdn*T2[2].x; - T2[2].y=wtdn*T2[2].y; - T2[3].x=wtdn*T2[3].x; - T2[3].y=wtdn*T2[3].y; - - - eta[8*tid+4]=T2[0]; - eta[8*tid+5]=T2[1]; - eta[8*tid+6]=T2[2]; - eta[8*tid+7]=T2[3]; - - } - } - - __syncthreads(); - /* copy back to global memory */ - if (tid=0 always */ - eta0[2*tid+bid*4*N]=cuCaddf(eta0[2*tid+bid*4*N],eta[8*ci]); - eta0[2*tid+2*N+bid*4*N]=cuCaddf(eta0[2*tid+2*N+bid*4*N],eta[8*ci+1]); - eta0[2*tid+1+bid*4*N]=cuCaddf(eta0[2*tid+1+bid*4*N],eta[8*ci+2]); - eta0[2*tid+2*N+1+bid*4*N]=cuCaddf(eta0[2*tid+2*N+1+bid*4*N],eta[8*ci+3]); - } - if (sta2==tid) { /* note, tid >=0 always */ - eta0[2*tid+bid*4*N]=cuCaddf(eta0[2*tid+bid*4*N],eta[8*ci+4]); - eta0[2*tid+2*N+bid*4*N]=cuCaddf(eta0[2*tid+2*N+bid*4*N],eta[8*ci+5]); - eta0[2*tid+1+bid*4*N]=cuCaddf(eta0[2*tid+1+bid*4*N],eta[8*ci+6]); - eta0[2*tid+2*N+1+bid*4*N]=cuCaddf(eta0[2*tid+2*N+1+bid*4*N],eta[8*ci+7]); - } - } - } - __syncthreads(); -} - -__global__ void -kernel_fns_sumblocks_pertime(int N, int Nblocks, int offset, cuFloatComplex *__restrict__ eta0) { - /* offset: values in 0...4N - each block will sum Nblocks in eta0 and store it in first value */ - extern __shared__ cuFloatComplex etas[]; - int bid=blockIdx.x; - int tid=threadIdx.x; - int gtid=tid+offset; - /* this block will work on blocks bid*Nblocks,bid*Nblocks+1,...,(bid+1)Nblocks-1 */ - /* each thread will work with Nblocks values */ - /* load global data */ - if (gtid < 4*N) { - for (int ci=0; ci0; s=s/2) { - if(tid < s) { etas[tid] = cuCaddf(etas[tid],etas[tid + s]); } - __syncthreads(); - } - - - /* add to proper location in eta */ - if(tid==0 && bid<4*N) { - eta[bid]=cuCaddf(etas[tid],eta[bid]); - } - __syncthreads(); -} - - -__global__ void -kernel_fns_sumelements_alltime(int Ntime,int offset, const cuFloatComplex *__restrict__ eta0, cuFloatComplex *__restrict__ C) { - /* C: 2x2, eta0: 2x2Ntime, sum eta0 and store it in C (C initialized to 0) */ - /* 4 blocks, blockDim.x threads */ - extern __shared__ cuFloatComplex etas[]; - int bid=blockIdx.x; /* 0..3 add to C[bid] */ - int tid=threadIdx.x; /* 0...Ntime-1 */ - int gtid=4*(tid+offset)+bid; - etas[tid]=make_cuFloatComplex(0.0f,0.0f); - if (tid+offset0; s=s/2) { - if(tid < s) { etas[tid] = cuCaddf(etas[tid],etas[tid + s]); } - __syncthreads(); - } - - /* add to proper location in C */ - if(tid==0 && bid<4) { - C[bid]=cuCaddf(etas[tid],C[bid]); - } - __syncthreads(); - -} - - -__global__ void -kernel_fns_rhs_alltime(cuFloatComplex *__restrict__ C) { - /* C: 2 x 2 Nblocks , each block (4) threads will work on 2x2 matrix */ - extern __shared__ cuFloatComplex etas[]; - int bid=blockIdx.x; /* 0..ntime-1 */ - int tid=threadIdx.x; /* 0..3 */ - - /* load data to shared mem, X^H Z */ - if (tid<4) { - etas[tid]=C[bid*4+tid]; - } - __syncthreads(); - - /* now find X^H-Z^H X */ - cuFloatComplex a,b; - if (tid==0) { - a=etas[0]; b=etas[0]; - } else if (tid==1) { - a=etas[2]; b=etas[1]; - } else if (tid==2) { - a=etas[1]; b=etas[2]; - } else { - a=etas[3]; b=etas[3]; - } - etas[tid]=cuCsubf(a,cuConjf(b)); - __syncthreads(); - - /* write back to C */ - if (tid<4) { - C[bid*4+tid]=etas[tid]; - } - __syncthreads(); - -} - -__global__ void -kernel_fns_fgrad_robust1(int N, int Nbase, const cuFloatComplex *__restrict__ x, cuFloatComplex *__restrict__ eta0, const float *__restrict__ y, const float *__restrict__ coh, const short *__restrict__ bbh, const float *__restrict__ wtd) { - - /* eta0: each block will store result in its own block */ - /* global thread index : equal to the baseline */ - unsigned int n = threadIdx.x + blockDim.x*blockIdx.x; - int bid=blockIdx.x; - int tid=threadIdx.x; - /* 4x2xblockDim.x cuFloatComplex values and 2xblockDim.x int values */ - extern __shared__ cuFloatComplex eta[]; - int *stm= (int*)&eta[8*blockDim.x]; - stm[2*tid]=-1; - stm[2*tid+1]=-1; - /* x,eta: 2Nx2 matrix */ - if(n=0 - */ - if (sta1>=0 && sta2>=0) { - stm[2*tid]=sta1; - stm[2*tid+1]=sta2; - - cuFloatComplex G1[4]; - cuFloatComplex G2[4]; - /* J1 */ - G1[0]=x[sta1*2]; - G1[1]=x[sta1*2+N*2]; - G1[2]=x[sta1*2+1]; - G1[3]=x[sta1*2+N*2+1]; - /* conjugate this to get J2^H */ - G2[0]=x[sta2*2]; - G2[1]=x[sta2*2+N*2]; - G2[2]=x[sta2*2+1]; - G2[3]=x[sta2*2+N*2+1]; - - cuFloatComplex C[4]; - C[0].x=coh[8*n]; - C[0].y=coh[8*n+1]; - C[1].x=coh[8*n+2]; - C[1].y=coh[8*n+3]; - C[2].x=coh[8*n+4]; - C[2].y=coh[8*n+5]; - C[3].x=coh[8*n+6]; - C[3].y=coh[8*n+7]; - - /* G1*C*G2' */ - cuFloatComplex T1[4]; - cuFloatComplex T2[4]; - /* T=G1*C */ - amb(G1,C,T1); - ambt(T1,G2,T2); - - /* res=V(2*ck-1:2*ck,:)-x(2*p-1:2*p,:)*C*x(2*q-1:2*q,:)'; */ - /* V->U */ - cuFloatComplex res[4]; - res[0]=cuCsubf(make_cuFloatComplex(y[8*n],y[8*n+1]),T2[0]); - res[1]=cuCsubf(make_cuFloatComplex(y[8*n+2],y[8*n+3]),T2[1]); - res[2]=cuCsubf(make_cuFloatComplex(y[8*n+4],y[8*n+5]),T2[2]); - res[3]=cuCsubf(make_cuFloatComplex(y[8*n+6],y[8*n+7]),T2[3]); - - /* - grad(2*p-1:2*p,:)=grad(2*p-1:2*p,:)+res*x(2*q-1:2*q,:)*C'; - grad(2*q-1:2*q,:)=grad(2*q-1:2*q,:)+res'*x(2*p-1:2*p,:)*C; - */ - /* res*G2*C' */ - amb(res,G2,T1); - ambt(T1,C,T2); - - float wtdn=wtd[n]; - /* mult with weights */ - T2[0].x=wtdn*T2[0].x; - T2[0].y=wtdn*T2[0].y; - T2[1].x=wtdn*T2[1].x; - T2[1].y=wtdn*T2[1].y; - T2[2].x=wtdn*T2[2].x; - T2[2].y=wtdn*T2[2].y; - T2[3].x=wtdn*T2[3].x; - T2[3].y=wtdn*T2[3].y; - - eta[8*tid]=T2[0]; - eta[8*tid+1]=T2[1]; - eta[8*tid+2]=T2[2]; - eta[8*tid+3]=T2[3]; - - - /* res'*G1*C */ - atmb(res,G1,T1); - amb(T1,C,T2); - - /* mult with weights */ - T2[0].x=wtdn*T2[0].x; - T2[0].y=wtdn*T2[0].y; - T2[1].x=wtdn*T2[1].x; - T2[1].y=wtdn*T2[1].y; - T2[2].x=wtdn*T2[2].x; - T2[2].y=wtdn*T2[2].y; - T2[3].x=wtdn*T2[3].x; - T2[3].y=wtdn*T2[3].y; - - - eta[8*tid+4]=T2[0]; - eta[8*tid+5]=T2[1]; - eta[8*tid+6]=T2[2]; - eta[8*tid+7]=T2[3]; - - } - } - - __syncthreads(); - /* copy back to global memory */ - if (tid==0) { - for(int ci=0; ci=0 && sta2>=0) { - eta0[2*sta1+bid*4*N]=cuCaddf(eta0[2*sta1+bid*4*N],eta[8*ci]); - eta0[2*sta1+2*N+bid*4*N]=cuCaddf(eta0[2*sta1+2*N+bid*4*N],eta[8*ci+1]); - eta0[2*sta1+1+bid*4*N]=cuCaddf(eta0[2*sta1+1+bid*4*N],eta[8*ci+2]); - eta0[2*sta1+2*N+1+bid*4*N]=cuCaddf(eta0[2*sta1+2*N+1+bid*4*N],eta[8*ci+3]); - eta0[2*sta2+bid*4*N]=cuCaddf(eta0[2*sta2+bid*4*N],eta[8*ci+4]); - eta0[2*sta2+2*N+bid*4*N]=cuCaddf(eta0[2*sta2+2*N+bid*4*N],eta[8*ci+5]); - eta0[2*sta2+1+bid*4*N]=cuCaddf(eta0[2*sta2+1+bid*4*N],eta[8*ci+6]); - eta0[2*sta2+2*N+1+bid*4*N]=cuCaddf(eta0[2*sta2+2*N+1+bid*4*N],eta[8*ci+7]); - - } - } - } - __syncthreads(); -} - -__global__ void -kernel_fns_fgradsum(int N, int B, int blockDim_2, const cuFloatComplex *__restrict__ etaloc, cuFloatComplex *__restrict__ eta) { - int bid=blockIdx.x; - int tid=threadIdx.x; - /* B x cuFloatComplex values */ - extern __shared__ cuFloatComplex etas[]; - etas[tid]=make_cuFloatComplex(0.0f,0.0f); - if (tid 1) { - int halfPoint = (nTotalThreads >> 1); // divide by two - if (tid < halfPoint) { - int thread2 = tid + halfPoint; - if (thread2 < blockDim.x) { // Skipping the fictitious threads blockDim.x ... blockDim_2-1 - etas[tid] = cuCaddf(etas[tid],etas[thread2]); - } - } - __syncthreads(); - nTotalThreads = halfPoint; // Reducing the binary tree size by two - } - - /* add back the sum to proper location in eta */ - if(tid==0) { - eta[bid]=cuCaddf(eta[bid],etas[0]); - } -} - -__global__ void -kernel_fns_f(int N, int Nbase, const cuFloatComplex *__restrict__ x, const float *__restrict__ y, const float *__restrict__ coh, const short *__restrict__ bbh, float *__restrict__ ed) { - - // Each block saves error into shared memory - extern __shared__ float ek[]; - /* global thread index : equal to the baseline */ - unsigned int n = threadIdx.x + blockDim.x*blockIdx.x; - int tid = threadIdx.x; - - /* this thread works on - coh[8*M*n:8*M*n+8*M-1] - bb[2*n:2*n+1] (sta1,sta2) - organization of p (N stations and M clusters) - sta 0 sta 1 sta 2 .... sta N-1 - clus 0 0...7 8...15 16...23 ... 8N-8 8N-1 - clus 1 8N..8N+7 8N+8..8N+15 8N+16..8N+23 .... 8N+8N-8...8N+8N-1 - ...... - clus M-1 (M-1)N..(M-1)N+7 (M-1)N+8..(M-1)N+15.... ...(M-1)N+8N-8 (M-1)N+8N-1 - - organization of coherencies (coh) - [0, 8*M-1] : baseline 0 - [8*M, 8*M+8*M-1]: baseline 1 - [n*8*M, n*8*M+8*M-1]: baseline n - ...... - [n*8*M+cm*8, n*8*M+cm*8+7] cluster cm, baseline n - - x: 2Nx2 matrix - */ - ek[tid]=0.0f; - if(n=0 - */ - float sumn=0.0f; - float temp1,temp2,tt,yy,c=0.0f; - if (sta1>=0 && sta2>=0) { - cuFloatComplex G1[4]; - cuFloatComplex G2[4]; - G1[0]=x[sta1*2]; - G1[1]=x[sta1*2+N*2]; - G1[2]=x[sta1*2+1]; - G1[3]=x[sta1*2+N*2+1]; - G2[0]=x[sta2*2]; - G2[1]=x[sta2*2+N*2]; - G2[2]=x[sta2*2+1]; - G2[3]=x[sta2*2+N*2+1]; - - cuFloatComplex C[4]; - C[0].x=coh[8*n]; - C[0].y=coh[8*n+1]; - C[1].x=coh[8*n+2]; - C[1].y=coh[8*n+3]; - C[2].x=coh[8*n+4]; - C[2].y=coh[8*n+5]; - C[3].x=coh[8*n+6]; - C[3].y=coh[8*n+7]; - - cuFloatComplex T1[4]; - cuFloatComplex T2[4]; - /* T=G1*C */ - amb(G1,C,T1); - /* T=T*G2' */ - ambt(T1,G2,T2); - - /* error using Kahan summation */ - /* V->U */ - temp1=y[8*n]-T2[0].x; - temp2=temp1*temp1; yy=temp2-c; tt=sumn+yy; c=(tt-sumn)-yy; sumn=tt; - temp1=y[8*n+1]-T2[0].y; - temp2=temp1*temp1; yy=temp2-c; tt=sumn+yy; c=(tt-sumn)-yy; sumn=tt; - temp1=y[8*n+2]-T2[1].x; - temp2=temp1*temp1; yy=temp2-c; tt=sumn+yy; c=(tt-sumn)-yy; sumn=tt; - temp1=y[8*n+3]-T2[1].y; - temp2=temp1*temp1; yy=temp2-c; tt=sumn+yy; c=(tt-sumn)-yy; sumn=tt; - temp1=y[8*n+4]-T2[2].x; - temp2=temp1*temp1; yy=temp2-c; tt=sumn+yy; c=(tt-sumn)-yy; sumn=tt; - temp1=y[8*n+5]-T2[2].y; - temp2=temp1*temp1; yy=temp2-c; tt=sumn+yy; c=(tt-sumn)-yy; sumn=tt; - temp1=y[8*n+6]-T2[3].x; - temp2=temp1*temp1; yy=temp2-c; tt=sumn+yy; c=(tt-sumn)-yy; sumn=tt; - temp1=y[8*n+7]-T2[3].y; - temp2=temp1*temp1; yy=temp2-c; tt=sumn+yy; c=(tt-sumn)-yy; sumn=tt; - ek[tid]=sumn; - } - } - - __syncthreads(); - // Build summation tree over elements, assuming blockDim.x is power of 2. - for(int s=blockDim.x/2; s>0; s=s/2) { - if(tid < s) ek[tid] += ek[tid + s]; - __syncthreads(); - } - - /* copy back the sum to proper location in ed */ - if(tid==0) { - ed[blockIdx.x]=ek[0]; - } -} - -__global__ void -kernel_fns_f_robust(int N, int Nbase, const cuFloatComplex *__restrict__ x, const float *__restrict__ y, const float *__restrict__ coh, const short *__restrict__ bbh, const float *__restrict__ wtd, float *__restrict__ ed) { - - // Each block saves error into shared memory - extern __shared__ float ek[]; - /* global thread index : equal to the baseline */ - unsigned int n = threadIdx.x + blockDim.x*blockIdx.x; - int tid = threadIdx.x; - - /* this thread works on - coh[8*M*n:8*M*n+8*M-1] - bb[2*n:2*n+1] (sta1,sta2) - organization of p (N stations and M clusters) - sta 0 sta 1 sta 2 .... sta N-1 - clus 0 0...7 8...15 16...23 ... 8N-8 8N-1 - clus 1 8N..8N+7 8N+8..8N+15 8N+16..8N+23 .... 8N+8N-8...8N+8N-1 - ...... - clus M-1 (M-1)N..(M-1)N+7 (M-1)N+8..(M-1)N+15.... ...(M-1)N+8N-8 (M-1)N+8N-1 - - organization of coherencies (coh) - [0, 8*M-1] : baseline 0 - [8*M, 8*M+8*M-1]: baseline 1 - [n*8*M, n*8*M+8*M-1]: baseline n - ...... - [n*8*M+cm*8, n*8*M+cm*8+7] cluster cm, baseline n - - x: 2Nx2 matrix - */ - ek[tid]=0.0f; - if(n=0 - */ - float sumn=0.0f; - float temp1,temp2,tt,yy,c=0.0f; - if (sta1>=0 && sta2>=0) { - cuFloatComplex G1[4]; - cuFloatComplex G2[4]; - G1[0]=x[sta1*2]; - G1[1]=x[sta1*2+N*2]; - G1[2]=x[sta1*2+1]; - G1[3]=x[sta1*2+N*2+1]; - G2[0]=x[sta2*2]; - G2[1]=x[sta2*2+N*2]; - G2[2]=x[sta2*2+1]; - G2[3]=x[sta2*2+N*2+1]; - - cuFloatComplex C[4]; - C[0].x=coh[8*n]; - C[0].y=coh[8*n+1]; - C[1].x=coh[8*n+2]; - C[1].y=coh[8*n+3]; - C[2].x=coh[8*n+4]; - C[2].y=coh[8*n+5]; - C[3].x=coh[8*n+6]; - C[3].y=coh[8*n+7]; - - cuFloatComplex T1[4]; - cuFloatComplex T2[4]; - /* T=G1*C */ - amb(G1,C,T1); - /* T=T*G2' */ - ambt(T1,G2,T2); - - /* error using Kahan summation */ - /* V->U */ - temp1=y[8*n]-T2[0].x; - temp2=temp1*temp1; yy=temp2-c; tt=sumn+yy; c=(tt-sumn)-yy; sumn=tt; - temp1=y[8*n+1]-T2[0].y; - temp2=temp1*temp1; yy=temp2-c; tt=sumn+yy; c=(tt-sumn)-yy; sumn=tt; - temp1=y[8*n+2]-T2[1].x; - temp2=temp1*temp1; yy=temp2-c; tt=sumn+yy; c=(tt-sumn)-yy; sumn=tt; - temp1=y[8*n+3]-T2[1].y; - temp2=temp1*temp1; yy=temp2-c; tt=sumn+yy; c=(tt-sumn)-yy; sumn=tt; - temp1=y[8*n+4]-T2[2].x; - temp2=temp1*temp1; yy=temp2-c; tt=sumn+yy; c=(tt-sumn)-yy; sumn=tt; - temp1=y[8*n+5]-T2[2].y; - temp2=temp1*temp1; yy=temp2-c; tt=sumn+yy; c=(tt-sumn)-yy; sumn=tt; - temp1=y[8*n+6]-T2[3].x; - temp2=temp1*temp1; yy=temp2-c; tt=sumn+yy; c=(tt-sumn)-yy; sumn=tt; - temp1=y[8*n+7]-T2[3].y; - temp2=temp1*temp1; yy=temp2-c; tt=sumn+yy; c=(tt-sumn)-yy; sumn=tt; - ek[tid]=wtd[n]*sumn; - } - } - - __syncthreads(); - // Build summation tree over elements, assuming blockDim.x is power of 2. - for(int s=blockDim.x/2; s>0; s=s/2) { - if(tid < s) { ek[tid] += ek[tid + s]; } - __syncthreads(); - } - - /* copy back the sum to proper location in ed */ - if(tid==0) { - ed[blockIdx.x]=ek[0]; - } -} - - -/* update weights */ -__global__ void -kernel_fns_fupdate_weights(int N, int Nbase, const cuFloatComplex *__restrict__ x, const float *__restrict__ y, const float *__restrict__ coh, const short *__restrict__ bbh, float *__restrict__ wtd, float nu0) { - - /* global thread index : equal to the baseline */ - unsigned int n = threadIdx.x + blockDim.x*blockIdx.x; - - if(n=0 - */ - float sumn=0.0f; - float temp1,temp2,tt; - if (sta1>=0 && sta2>=0) { - cuFloatComplex G1[4]; - cuFloatComplex G2[4]; - G1[0]=x[sta1*2]; - G1[1]=x[sta1*2+N*2]; - G1[2]=x[sta1*2+1]; - G1[3]=x[sta1*2+N*2+1]; - G2[0]=x[sta2*2]; - G2[1]=x[sta2*2+N*2]; - G2[2]=x[sta2*2+1]; - G2[3]=x[sta2*2+N*2+1]; - - cuFloatComplex C[4]; - C[0].x=coh[8*n]; - C[0].y=coh[8*n+1]; - C[1].x=coh[8*n+2]; - C[1].y=coh[8*n+3]; - C[2].x=coh[8*n+4]; - C[2].y=coh[8*n+5]; - C[3].x=coh[8*n+6]; - C[3].y=coh[8*n+7]; - - cuFloatComplex T1[4]; - cuFloatComplex T2[4]; - /* T=G1*C */ - amb(G1,C,T1); - /* T=T*G2' */ - ambt(T1,G2,T2); - - /* use p=2, find MAX value of residual error out of XX,XY,YX,YY - instead of the sum */ - /* V->U */ - temp1=y[8*n]-T2[0].x; - temp2=y[8*n+1]-T2[0].y; - sumn=temp1*temp1+temp2*temp2; - temp1=y[8*n+2]-T2[1].x; - temp2=y[8*n+3]-T2[1].y; - tt=temp1*temp1+temp2*temp2; - if (sumn=0 - */ - float sumn=0.0f; - float temp1,temp2,tt; - if (sta1>=0 && sta2>=0) { - cuFloatComplex G1[4]; - cuFloatComplex G2[4]; - G1[0]=x[sta1*2]; - G1[1]=x[sta1*2+N*2]; - G1[2]=x[sta1*2+1]; - G1[3]=x[sta1*2+N*2+1]; - G2[0]=x[sta2*2]; - G2[1]=x[sta2*2+N*2]; - G2[2]=x[sta2*2+1]; - G2[3]=x[sta2*2+N*2+1]; - - cuFloatComplex C[4]; - C[0].x=coh[8*n]; - C[0].y=coh[8*n+1]; - C[1].x=coh[8*n+2]; - C[1].y=coh[8*n+3]; - C[2].x=coh[8*n+4]; - C[2].y=coh[8*n+5]; - C[3].x=coh[8*n+6]; - C[3].y=coh[8*n+7]; - - cuFloatComplex T1[4]; - cuFloatComplex T2[4]; - /* T=G1*C */ - amb(G1,C,T1); - /* T=T*G2' */ - ambt(T1,G2,T2); - - /* use p=2, find MAX value of residual error out of XX,XY,YX,YY - instead of the sum */ - /* V->U */ - temp1=y[8*n]-T2[0].x; - temp2=y[8*n+1]-T2[0].y; - sumn=temp1*temp1+temp2*temp2; - temp1=y[8*n+2]-T2[1].x; - temp2=y[8*n+3]-T2[1].y; - tt=temp1*temp1+temp2*temp2; - if (sumn number of blocks) */ -__global__ void -plus_reduce_multi(const float *__restrict__ input, int N, int blockDim_2, float *__restrict__ output) { - // Each block loads its elements into shared memory - extern __shared__ float x[]; - int tid = threadIdx.x; - int i = blockIdx.x*blockDim.x + threadIdx.x; - x[tid] = (i 1) { - int halfPoint = (nTotalThreads >> 1); // divide by two - if (tid < halfPoint) { - int thread2 = tid + halfPoint; - if (thread2 < blockDim.x) { // Skipping the fictitious threads blockDim.x ... blockDim_2-1 - x[tid] = x[tid]+x[thread2]; - } - } - __syncthreads(); - nTotalThreads = halfPoint; // Reducing the binary tree size by two - } - - /* add back to total */ - if( tid == 0 ) { - output[blockIdx.x]=x[tid]; - } -} - - -/* sum up all N elements of vector input - NOTE: only 1 block should be used */ -__global__ void -plus_reduce(const float *__restrict__ input, int N, int blockDim_2, float *total) { - // Each block loads its elements into shared memory - extern __shared__ float x[]; - int tid = threadIdx.x; - int i = blockIdx.x*blockDim.x + threadIdx.x; - x[tid] = (i 1) { - int halfPoint = (nTotalThreads >> 1); // divide by two - if (tid < halfPoint) { - int thread2 = tid + halfPoint; - if (thread2 < blockDim.x) { // Skipping the fictitious threads blockDim.x ... blockDim_2-1 - x[tid] = x[tid]+x[thread2]; - } - } - __syncthreads(); - nTotalThreads = halfPoint; // Reducing the binary tree size by two - } - - /* add back to total */ - if( tid == 0 ) { - *total=*total+x[tid]; - } -} - - -__global__ void -kernel_fns_fscale(int N, cuFloatComplex *__restrict__ eta, const float *__restrict__ iw) { - unsigned int n = threadIdx.x + blockDim.x*blockIdx.x; - if (nstation mapping - - return ed: error vector, BlocksPerGridx1 -*/ -/* need BlocksPerGrid+1+L float storage */ -float -cudakernel_fns_f(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, float *y, float *coh, short *bbh) { -#ifdef CUDA_DBG - cudaError_t error; -#endif - float *ed,*eo; - cudaMalloc((void**)&ed, sizeof(float)*BlocksPerGrid); - cudaMemset(ed, 0, sizeof(float)*BlocksPerGrid); - kernel_fns_f<<< BlocksPerGrid, ThreadsPerBlock, sizeof(float)*ThreadsPerBlock >>>(N, M, x, y, coh, bbh,ed); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - float total; - float *totald; - cudaMalloc((void**)&totald, sizeof(float)); - cudaMemset(totald, 0, sizeof(float)); - int T=DEFAULT_TH_PER_BK; /* max possible threads, use a smaller no to have large no. of blocks, but not too large to exceed no. of. SMs in the card*/ - /* we use 1 block, so need to launch BlocksPerGrid number of threads */ - if (BlocksPerGrid>>(ed, BlocksPerGrid, NearestPowerOf2(BlocksPerGrid), totald); - } else { - /* multiple kernel launches */ - int L=(BlocksPerGrid+T-1)/T; - cudaMalloc((void**)&eo, sizeof(float)*L); - plus_reduce_multi<<< L, T, sizeof(float)*T>>>(ed, BlocksPerGrid, NearestPowerOf2(T), eo); - plus_reduce<<< 1, L, sizeof(float)*L>>>(eo, L, NearestPowerOf2(L), totald); - cudaFree(eo); - } -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - cudaMemcpy(&total,totald,sizeof(float),cudaMemcpyDeviceToHost); - cudaFree(ed); - cudaFree(totald); - return total; -} - -/* - robust cost function: - N: no of stations - M: no of constraints (baselines) - x: solution 2Nx2 complex float - y: data 8M float (8 for each baseline) - coh: coherency - bbh: baseline->station mapping - wtd: weight Mx1 - - return ed: error vector, BlocksPerGridx1 -*/ -/* need BlocksPerGrid+4+L float storage <= (2 BlocksPerGrid + 4) */ -float -cudakernel_fns_f_robust(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, float *y, float *coh, short *bbh, float *wtd) { -#ifdef CUDA_DBG - cudaError_t error; -#endif - float *ed,*eo; - cudaMalloc((void**)&ed, sizeof(float)*BlocksPerGrid); - cudaMemset(ed, 0, sizeof(float)*BlocksPerGrid); - kernel_fns_f_robust<<< BlocksPerGrid, ThreadsPerBlock, sizeof(float)*ThreadsPerBlock >>>(N, M, x, y, coh, bbh, wtd, ed); - -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - float total; - float *totald; - cudaMalloc((void**)&totald, sizeof(float)); - cudaMemset(totald, 0, sizeof(float)); - int T=DEFAULT_TH_PER_BK; /* max possible threads, use a smaller no to have large no. of blocks, but not too large to exceed no. of. SMs in the card*/ - /* we use 1 block, so need to launch BlocksPerGrid number of threads */ - if (BlocksPerGrid>>(ed, BlocksPerGrid, NearestPowerOf2(BlocksPerGrid), totald); - } else { - /* multiple kernel launches */ - int L=(BlocksPerGrid+T-1)/T; - cudaMalloc((void**)&eo, sizeof(float)*L); - plus_reduce_multi<<< L, T, sizeof(float)*T>>>(ed, BlocksPerGrid, NearestPowerOf2(T), eo); - plus_reduce<<< 1, L, sizeof(float)*L>>>(eo, L, NearestPowerOf2(L), totald); - cudaFree(eo); - } -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - cudaMemcpy(&total,totald,sizeof(float),cudaMemcpyDeviceToHost); - cudaFree(ed); - cudaFree(totald); - - return total; -} - -/* gradient, output eta: reset to 0 initially */ -/* need 8N*BlocksPerGrid float storage */ -void -cudakernel_fns_fgradflat(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, float *y, float *coh, short *bbh) { -#ifdef CUDA_DBG - cudaError_t error; -#endif - cuFloatComplex *etaloc; - /* each block stores result in 2Nx2 block, so need BlocksPerGrid x 2Nx 2 storage */ - cudaMalloc((void**)&etaloc, sizeof(cuFloatComplex)*BlocksPerGrid*4*N); - cudaMemset(etaloc, 0, sizeof(cuFloatComplex)*BlocksPerGrid*4*N); - cudaMemset(eta, 0, sizeof(cuFloatComplex)*4*N); - /* eachekernel require 2xThreadsPerBlocx2 x 2 complex float for storing eta values - 2*ThreadsPerBloc x1 int array for station numbers - and */ - kernel_fns_fgrad<<< BlocksPerGrid, ThreadsPerBlock, sizeof(cuFloatComplex)*8*ThreadsPerBlock + sizeof(int)*2*ThreadsPerBlock >>>(N, M, x, etaloc, y, coh, bbh); - -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - int T=256; /* max possible threads */ - /* now create 4N blocks, threads in each block will read BlocksPerGrid values from etalocal and find average, so no of threads>= BlocksPerGrid */ - /* each block need BlocksPerGrid float complex values */ - if (T>BlocksPerGrid) { - int B=((BlocksPerGrid+1)/2)*2; /* even no of threads */ - kernel_fns_fgradsum<<< 4*N, B, sizeof(cuFloatComplex)*B>>>(N, BlocksPerGrid, NearestPowerOf2(B), etaloc, eta); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - } else { - /* iterate over T values */ - int L=(BlocksPerGrid+T-1)/T; - int ct=0; - int myT; - for (int ci=0; ci>>(N, myT, NearestPowerOf2(myT), &etaloc[ct*4*N], eta); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - ct=ct+T; - } - } - - cudaFree(etaloc); -} - -/* Robust gradient, output eta: reset to 0 initially */ -/* need 8N*BlocksPerGrid float storage */ -void -cudakernel_fns_fgradflat_robust1(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, float *y, float *coh, short *bbh, float *wtd) { -#ifdef CUDA_DBG - cudaError_t error; -#endif - cuFloatComplex *etaloc; - /* each block stores result in 2Nx2 block, so need BlocksPerGrid x 2Nx 2 storage */ - cudaMalloc((void**)&etaloc, sizeof(cuFloatComplex)*BlocksPerGrid*4*N); - cudaMemset(etaloc, 0, sizeof(cuFloatComplex)*BlocksPerGrid*4*N); - cudaMemset(eta, 0, sizeof(cuFloatComplex)*4*N); - /* eachekernel require 2xThreadsPerBlocx2 x 2 complex float for storing eta values - 2*ThreadsPerBloc x1 int array for station numbers - and */ - kernel_fns_fgrad_robust1<<< BlocksPerGrid, ThreadsPerBlock, sizeof(cuFloatComplex)*8*ThreadsPerBlock + sizeof(int)*2*ThreadsPerBlock >>>(N, M, x, etaloc, y, coh, bbh, wtd); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - int T=256; /* max possible threads */ - /* now create 4N blocks, threads in each block will read BlocksPerGrid values from etalocal and find average, so no of threads>= BlocksPerGrid */ - /* each block need BlocksPerGrid float complex values */ - if (T>BlocksPerGrid) { - int B=((BlocksPerGrid+1)/2)*2; /* even no of threads */ - kernel_fns_fgradsum<<< 4*N, B, sizeof(cuFloatComplex)*B>>>(N, BlocksPerGrid, NearestPowerOf2(B), etaloc, eta); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - } else { - /* iterate over T values */ - int L=(BlocksPerGrid+T-1)/T; - int ct=0; - int myT; - for (int ci=0; ci>>(N, myT, NearestPowerOf2(myT), &etaloc[ct*4*N], eta); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - ct=ct+T; - } - } - cudaFree(etaloc); -} - - -/* Robust gradient, output eta: reset to 0 initially */ -/* Ai: inverse of A matrix for projection */ -/* need 8N*BlocksPerGrid float storage */ -void -cudakernel_fns_fgradflat_robust(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, float *y, float *coh, short *bbh, float *wtd, cuFloatComplex *Ai, cublasHandle_t cbhandle) { -#ifdef CUDA_DBG - cudaError_t error; -#endif - cuFloatComplex *etaloc; - /* each block stores result in 2Nx2 block, so need BlocksPerGrid x 2Nx 2 storage */ - cudaMalloc((void**)&etaloc, sizeof(cuFloatComplex)*BlocksPerGrid*4*N); - cudaMemset(etaloc, 0, sizeof(cuFloatComplex)*BlocksPerGrid*4*N); - cudaMemset(eta, 0, sizeof(cuFloatComplex)*4*N); - /* each block requires 2xThreadsPerBloc x2 x 2 complex float for storing eta values - and 2*ThreadsPerBloc x1 int array for station numbers - each block requires Nx2x2 complex float to store calculated value - - also ThreadsPerBlock>= N - */ - kernel_fns_fgrad_robust<<< BlocksPerGrid, ThreadsPerBlock, sizeof(cuFloatComplex)*8*ThreadsPerBlock + sizeof(int)*2*ThreadsPerBlock >>>(N, M, x, etaloc, y, coh, bbh, wtd); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - /* now etaloc has [Z_1,Z_2,....,Z_K] where K: total blocks, - need to add it to - [Z_1, Z_2,....Z_t] where t: total timeslots. - so each blocks_per_timeslot blocks will be added to just one block - - project [P_1,P_2,...,P_t]=[Z_1,Z_2,..,Z_t]-J[U_1,U_2,...,U_t] - where U_i is the projection matrix obtained by solving Sylvester equation, - for that we need J^H [Z_1,Z_2,...,Z_t] - */ - /* baselines */ - int nbase=N*(N-1)/2; - /* blocks per timeslot */ - int Bt=(nbase+ThreadsPerBlock-1)/ThreadsPerBlock; - /* timeslots */ - int ntime=(M+nbase-1)/nbase; - /* threads to use (need to use small value to enable enough shared memory) */ - int T=DEFAULT_TH_PER_BK_2; - /* sum Bt values each to the first value */ - for (int ci=0; ci<(4*N+T-1)/T; ci++) { - /* create blocks equal to timeslots, each will need Bt*T complex float storage, ci*T is the offset of 0...4N-1 values */ - /* each thread will sum Bt values */ - kernel_fns_sumblocks_pertime<<< ntime, T, sizeof(cuFloatComplex)*Bt*T >>>(N, Bt, ci*T, etaloc); -#ifdef DEBUG - printf("sum blocks %d, threads %d thread offset %d, numblocks/time %d\n",ntime,T,ci*T,Bt); -#endif - } - -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - /* now create 4N blocks, each block will sum elements in 0...4N-1 of ntime values, separated by Bt blocks */ - T=DEFAULT_TH_PER_BK_2; /* even number */ - for (int ci=0; ci<(ntime+T-1)/T; ci++) { - kernel_fns_sumblocks_alltime<<< 4*N, T, sizeof(cuFloatComplex)*T >>>(N, Bt, ntime, ci*T, etaloc, eta); -#ifdef DEBUG - printf("sum all blocks %d, timeslots %d, threads %d block offset %d, spacing %d\n",4*N,ntime,T,ci*T,Bt); -#endif - } - -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - /* now etaloc : 4N x ntime (== 2N x 2ntime) blocks correspond to eta for each timeslot */ - /* find the product x^H etaloc == x^H Z, - reuse tail end of etaloc to store result, since BlocksPerGrid >> ntime */ - cuFloatComplex *C; - C=&etaloc[8*N*ntime]; /* size 2 x 2ntime */ - //cudaMemset(C, 0, sizeof(cuFloatComplex)*4*ntime); Not needed because a2=0 - cublasStatus_t cbstatus; - cuFloatComplex a1,a2; - a1.x=1.0f; a1.y=0.0f; - a2.x=0.0f; a2.y=0.0f; - cbstatus=cublasCgemm(cbhandle,CUBLAS_OP_C,CUBLAS_OP_N,2,2*ntime,2*N,&a1,x,2*N,etaloc,2*N,&a2,C,2); - checkCublasError(cbstatus,__FILE__,__LINE__); - - /* setup RHS matrices x^H Z - Z^H x */ - /* 2x2 matrix: 4 threads per block, ntime blocks */ - T=4; - kernel_fns_rhs_alltime<<< ntime, T, sizeof(cuFloatComplex)*T >>>(C); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - - /* now consider C as 4xntime matrix and multiply it with Ai */ - /* reuse etaloc first block, size needed 4 x ntime << 4N x ntime */ - cbstatus=cublasCgemm(cbhandle,CUBLAS_OP_N,CUBLAS_OP_N,4,ntime,4,&a1,Ai,4,C,4,&a2,etaloc,4); - checkCublasError(cbstatus,__FILE__,__LINE__); - - /* now average 2x 2ntime matrix etaloc to one 2x2 matrix, stoared at C */ - cudaMemset(C, 0, sizeof(cuFloatComplex)*4); - T=DEFAULT_TH_PER_BK_2; /* even number */ - for (int ci=0; ci<(ntime+T-1)/T; ci++) { - kernel_fns_sumelements_alltime<<< 4, T, sizeof(cuFloatComplex)*T >>>(ntime,ci*T, etaloc, C); - } -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - /* now find eta = -1 x C + eta => C = A B + C */ - a1.x=-1.0f; a1.y=0.0f; - a2.x=1.0f; a2.y=0.0f; - cbstatus=cublasCgemm(cbhandle,CUBLAS_OP_N,CUBLAS_OP_N,2*N,2,2,&a1,x,2*N,C,2,&a2,eta,2*N); - checkCublasError(cbstatus,__FILE__,__LINE__); - - cudaFree(etaloc); -} - - -/* Robust gradient (Euclidean), output eta: reset to 0 initially */ -/* need 8N*BlocksPerGrid float storage */ -void -cudakernel_fns_fgradflat_robust_admm(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, float *y, float *coh, short *bbh, float *wtd, cublasHandle_t cbhandle) { -#ifdef CUDA_DBG - cudaError_t error; -#endif - cuFloatComplex *etaloc; - /* each block stores result in 2Nx2 block, so need BlocksPerGrid x 2Nx 2 storage */ - cudaMalloc((void**)&etaloc, sizeof(cuFloatComplex)*BlocksPerGrid*4*N); - cudaMemset(etaloc, 0, sizeof(cuFloatComplex)*BlocksPerGrid*4*N); - cudaMemset(eta, 0, sizeof(cuFloatComplex)*4*N); - /* each block requires 2xThreadsPerBloc x2 x 2 complex float for storing eta values - and 2*ThreadsPerBloc x1 int array for station numbers - each block requires Nx2x2 complex float to store calculated value - - also ThreadsPerBlock>= N - */ - kernel_fns_fgrad_robust<<< BlocksPerGrid, ThreadsPerBlock, sizeof(cuFloatComplex)*8*ThreadsPerBlock + sizeof(int)*2*ThreadsPerBlock >>>(N, M, x, etaloc, y, coh, bbh, wtd); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - /* now etaloc has [Z_1,Z_2,....,Z_K] where K: total blocks, - need to add it to - [Z_1, Z_2,....Z_t] where t: total timeslots. - so each blocks_per_timeslot blocks will be added to just one block - - */ - /* baselines */ - int nbase=N*(N-1)/2; - /* blocks per timeslot */ - int Bt=(nbase+ThreadsPerBlock-1)/ThreadsPerBlock; - /* timeslots */ - int ntime=(M+nbase-1)/nbase; - /* threads to use (need to use small value to enable enough shared memory) */ - int T=DEFAULT_TH_PER_BK_2; - /* sum Bt values each to the first value */ - for (int ci=0; ci<(4*N+T-1)/T; ci++) { - /* create blocks equal to timeslots, each will need Bt*T complex float storage, ci*T is the offset of 0...4N-1 values */ - /* each thread will sum Bt values */ - kernel_fns_sumblocks_pertime<<< ntime, T, sizeof(cuFloatComplex)*Bt*T >>>(N, Bt, ci*T, etaloc); -#ifdef DEBUG - printf("sum blocks %d, threads %d thread offset %d, numblocks/time %d\n",ntime,T,ci*T,Bt); -#endif - } - -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - /* now create 4N blocks, each block will sum elements in 0...4N-1 of ntime values, separated by Bt blocks */ - T=DEFAULT_TH_PER_BK_2; /* even number */ - for (int ci=0; ci<(ntime+T-1)/T; ci++) { - kernel_fns_sumblocks_alltime<<< 4*N, T, sizeof(cuFloatComplex)*T >>>(N, Bt, ntime, ci*T, etaloc, eta); -#ifdef DEBUG - printf("sum all blocks %d, timeslots %d, threads %d block offset %d, spacing %d\n",4*N,ntime,T,ci*T,Bt); -#endif - } - -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - /* now etaloc : 4N x ntime (== 2N x 2ntime) blocks correspond to eta for each timeslot */ - /* now average 2x 2ntime matrix etaloc to one 2x2 matrix, stoared at eta */ - T=DEFAULT_TH_PER_BK_2; /* even number */ - for (int ci=0; ci<(ntime+T-1)/T; ci++) { - kernel_fns_sumelements_alltime<<< 4, T, sizeof(cuFloatComplex)*T >>>(ntime,ci*T, etaloc, eta); - } -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - cudaFree(etaloc); -} - - - -/* Hessian - output fhess: reset to 0 initially */ -/* need 8N*BlocksPerGrid float storage */ -void -cudakernel_fns_fhessflat(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, cuFloatComplex *fhess, float *y, float *coh, short *bbh) { -#ifdef CUDA_DBG - cudaError_t error; -#endif - cuFloatComplex *etaloc; - /* each block stores result in 2Nx2 block, so need BlocksPerGrid x 2Nx 2 storage */ - cudaMalloc((void**)&etaloc, sizeof(cuFloatComplex)*BlocksPerGrid*4*N); - cudaMemset(etaloc, 0, sizeof(cuFloatComplex)*BlocksPerGrid*4*N); - cudaMemset(fhess, 0, sizeof(cuFloatComplex)*4*N); - /* eachekernel require 2xThreadsPerBlocx2 x 2 complex float for storing eta values - 2*ThreadsPerBloc x1 int array for station numbers - and */ - kernel_fns_fhess<<< BlocksPerGrid, ThreadsPerBlock, sizeof(cuFloatComplex)*8*ThreadsPerBlock + sizeof(int)*2*ThreadsPerBlock >>>(N, M, x, eta, etaloc, y, coh, bbh); - -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - int T=256; - /* now create 4N blocks, threads in each block will read BlocksPerGrid values from etalocal and find average, so no of threads>= BlocksPerGrid */ - /* each block need BlocksPerGrid float complex values */ - if (T>BlocksPerGrid) { - int B=((BlocksPerGrid+1)/2)*2; /* even no of threads */ - kernel_fns_fgradsum<<< 4*N, B, sizeof(cuFloatComplex)*B>>>(N, BlocksPerGrid, NearestPowerOf2(B), etaloc, fhess); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - } else { - /* iterate over T values */ - int L=(BlocksPerGrid+T-1)/T; - int ct=0; - int myT; - for (int ci=0; ci>>(N, myT, NearestPowerOf2(myT), &etaloc[ct*4*N], fhess); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - ct=ct+T; - } - } - - cudaFree(etaloc); -} - - -/* Robust Hessian - output fhess: reset to 0 initially */ -/* need 8N*BlocksPerGrid float storage */ -void -cudakernel_fns_fhessflat_robust1(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, cuFloatComplex *fhess, float *y, float *coh, short *bbh, float *wtd) { -#ifdef CUDA_DBG - cudaError_t error; -#endif - cuFloatComplex *etaloc; - /* each block stores result in 2Nx2 block, so need BlocksPerGrid x 2Nx 2 storage */ - cudaMalloc((void**)&etaloc, sizeof(cuFloatComplex)*BlocksPerGrid*4*N); - cudaMemset(etaloc, 0, sizeof(cuFloatComplex)*BlocksPerGrid*4*N); - cudaMemset(fhess, 0, sizeof(cuFloatComplex)*4*N); - /* eachekernel require 2xThreadsPerBlocx2 x 2 complex float for storing eta values - 2*ThreadsPerBloc x1 int array for station numbers - and */ - kernel_fns_fhess_robust1<<< BlocksPerGrid, ThreadsPerBlock, sizeof(cuFloatComplex)*8*ThreadsPerBlock + sizeof(int)*2*ThreadsPerBlock >>>(N, M, x, eta, etaloc, y, coh, bbh, wtd); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - int T=256; - /* now create 4N blocks, threads in each block will read BlocksPerGrid values from etalocal and find average, so no of threads>= BlocksPerGrid */ - /* each block need BlocksPerGrid float complex values */ - if (T>BlocksPerGrid) { - int B=((BlocksPerGrid+1)/2)*2; /* even no of threads */ - kernel_fns_fgradsum<<< 4*N, B, sizeof(cuFloatComplex)*B>>>(N, BlocksPerGrid, NearestPowerOf2(B), etaloc, fhess); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - } else { - /* iterate over T values */ - int L=(BlocksPerGrid+T-1)/T; - int ct=0; - int myT; - for (int ci=0; ci>>(N, myT, NearestPowerOf2(myT), &etaloc[ct*4*N], fhess); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - ct=ct+T; - } - } - - cudaFree(etaloc); -} - - -/* Robust Hessian - output fhess: reset to 0 initially */ -/* need 8N*BlocksPerGrid float storage */ -void -cudakernel_fns_fhessflat_robust(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, cuFloatComplex *fhess, float *y, float *coh, short *bbh, float *wtd, cuFloatComplex *Ai, cublasHandle_t cbhandle) { -#ifdef CUDA_DBG - cudaError_t error; -#endif - cuFloatComplex *etaloc; - /* each block stores result in 2Nx2 block, so need BlocksPerGrid x 2Nx 2 storage */ - cudaMalloc((void**)&etaloc, sizeof(cuFloatComplex)*BlocksPerGrid*4*N); - cudaMemset(etaloc, 0, sizeof(cuFloatComplex)*BlocksPerGrid*4*N); - cudaMemset(fhess, 0, sizeof(cuFloatComplex)*4*N); - /* eachekernel require 2xThreadsPerBlocx2 x 2 complex float for storing eta values - 2*ThreadsPerBloc x1 int array for station numbers - */ - kernel_fns_fhess_robust<<< BlocksPerGrid, ThreadsPerBlock, sizeof(cuFloatComplex)*8*ThreadsPerBlock + sizeof(int)*2*ThreadsPerBlock >>>(N, M, x, eta, etaloc, y, coh, bbh, wtd); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - /* baselines */ - int nbase=N*(N-1)/2; - /* blocks per timeslot */ - int Bt=(nbase+ThreadsPerBlock-1)/ThreadsPerBlock; - /* timeslots */ - int ntime=(M+nbase-1)/nbase; - /* threads to use (need to use small value to enable enough shared memory) */ - int T=DEFAULT_TH_PER_BK_2; - /* sum Bt values each to the first value */ - for (int ci=0; ci<(4*N+T-1)/T; ci++) { - /* create blocks equal to timeslots, each will need Bt*T complex float storage, ci*T is the offset of 0...4N-1 values */ - /* each thread will sum Bt values */ - kernel_fns_sumblocks_pertime<<< ntime, T, sizeof(cuFloatComplex)*Bt*T >>>(N, Bt, ci*T, etaloc); -#ifdef DEBUG - printf("sum blocks %d, threads %d thread offset %d, numblocks/time %d\n",ntime,T,ci*T,Bt); -#endif - } -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - /* now create 4N blocks, each block will sum elements in 0...4N-1 of ntime values, separated by Bt blocks */ - T=DEFAULT_TH_PER_BK_2; /* even number */ - for (int ci=0; ci<(ntime+T-1)/T; ci++) { - kernel_fns_sumblocks_alltime<<< 4*N, T, sizeof(cuFloatComplex)*T >>>(N, Bt, ntime, ci*T, etaloc, fhess); -#ifdef DEBUG - printf("sum all blocks %d, timeslots %d, threads %d block offset %d, spacing %d\n",4*N,ntime,T,ci*T,Bt); -#endif - } - -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - /* now etaloc : 4N x ntime (== 2N x 2ntime) blocks correspond to eta for each timeslot */ - /* find the product x^H etaloc == x^H Z, - reuse tail end of etaloc to store result, since BlocksPerGrid >> ntime */ - cuFloatComplex *C; - C=&etaloc[8*N*ntime]; /* size 2 x 2ntime */ - //cudaMemset(C, 0, sizeof(cuFloatComplex)*4*ntime); Not needed because a2=0 - cublasStatus_t cbstatus; - cuFloatComplex a1,a2; - a1.x=1.0f; a1.y=0.0f; - a2.x=0.0f; a2.y=0.0f; - cbstatus=cublasCgemm(cbhandle,CUBLAS_OP_C,CUBLAS_OP_N,2,2*ntime,2*N,&a1,x,2*N,etaloc,2*N,&a2,C,2); - checkCublasError(cbstatus,__FILE__,__LINE__); - - /* setup RHS matrices x^H Z - Z^H x */ - /* 2x2 matrix: 4 threads per block, ntime blocks */ - T=4; - kernel_fns_rhs_alltime<<< ntime, T, sizeof(cuFloatComplex)*T >>>(C); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - - /* now consider C as 4xntime matrix and multiply it with Ai */ - /* reuse etaloc first block, size needed 4 x ntime << 4N x ntime */ - cbstatus=cublasCgemm(cbhandle,CUBLAS_OP_N,CUBLAS_OP_N,4,ntime,4,&a1,Ai,4,C,4,&a2,etaloc,4); - checkCublasError(cbstatus,__FILE__,__LINE__); - - /* now average 2x 2ntime matrix etaloc to one 2x2 matrix, stoared at C */ - cudaMemset(C, 0, sizeof(cuFloatComplex)*4); - T=DEFAULT_TH_PER_BK_2; /* even number */ - for (int ci=0; ci<(ntime+T-1)/T; ci++) { - kernel_fns_sumelements_alltime<<< 4, T, sizeof(cuFloatComplex)*T >>>(ntime,ci*T, etaloc, C); - } -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - /* now find fhess = -1 x C + fhess => C = A B + C */ - a1.x=-1.0f; a1.y=0.0f; - a2.x=1.0f; a2.y=0.0f; - cbstatus=cublasCgemm(cbhandle,CUBLAS_OP_N,CUBLAS_OP_N,2*N,2,2,&a1,x,2*N,C,2,&a2,fhess,2*N); - checkCublasError(cbstatus,__FILE__,__LINE__); - - cudaFree(etaloc); -} - - -/* Robust Hessian (Euclidean) - output fhess: reset to 0 initially */ -/* need 8N*BlocksPerGrid float storage */ -void -cudakernel_fns_fhessflat_robust_admm(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, cuFloatComplex *fhess, float *y, float *coh, short *bbh, float *wtd, cublasHandle_t cbhandle) { -#ifdef CUDA_DBG - cudaError_t error; -#endif - cuFloatComplex *etaloc; - /* each block stores result in 2Nx2 block, so need BlocksPerGrid x 2Nx 2 storage */ - cudaMalloc((void**)&etaloc, sizeof(cuFloatComplex)*BlocksPerGrid*4*N); - cudaMemset(etaloc, 0, sizeof(cuFloatComplex)*BlocksPerGrid*4*N); - cudaMemset(fhess, 0, sizeof(cuFloatComplex)*4*N); - /* eachekernel require 2xThreadsPerBlocx2 x 2 complex float for storing eta values - 2*ThreadsPerBloc x1 int array for station numbers - */ - kernel_fns_fhess_robust<<< BlocksPerGrid, ThreadsPerBlock, sizeof(cuFloatComplex)*8*ThreadsPerBlock + sizeof(int)*2*ThreadsPerBlock >>>(N, M, x, eta, etaloc, y, coh, bbh, wtd); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - /* baselines */ - int nbase=N*(N-1)/2; - /* blocks per timeslot */ - int Bt=(nbase+ThreadsPerBlock-1)/ThreadsPerBlock; - /* timeslots */ - int ntime=(M+nbase-1)/nbase; - /* threads to use (need to use small value to enable enough shared memory) */ - int T=DEFAULT_TH_PER_BK_2; - /* sum Bt values each to the first value */ - for (int ci=0; ci<(4*N+T-1)/T; ci++) { - /* create blocks equal to timeslots, each will need Bt*T complex float storage, ci*T is the offset of 0...4N-1 values */ - /* each thread will sum Bt values */ - kernel_fns_sumblocks_pertime<<< ntime, T, sizeof(cuFloatComplex)*Bt*T >>>(N, Bt, ci*T, etaloc); -#ifdef DEBUG - printf("sum blocks %d, threads %d thread offset %d, numblocks/time %d\n",ntime,T,ci*T,Bt); -#endif - } - -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - /* now create 4N blocks, each block will sum elements in 0...4N-1 of ntime values, separated by Bt blocks */ - T=DEFAULT_TH_PER_BK_2; /* even number */ - for (int ci=0; ci<(ntime+T-1)/T; ci++) { - kernel_fns_sumblocks_alltime<<< 4*N, T, sizeof(cuFloatComplex)*T >>>(N, Bt, ntime, ci*T, etaloc, fhess); -#ifdef DEBUG - printf("sum all blocks %d, timeslots %d, threads %d block offset %d, spacing %d\n",4*N,ntime,T,ci*T,Bt); -#endif - } - -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - /* now etaloc : 4N x ntime (== 2N x 2ntime) blocks correspond to eta for each timeslot */ - T=DEFAULT_TH_PER_BK_2; /* even number */ - for (int ci=0; ci<(ntime+T-1)/T; ci++) { - kernel_fns_sumelements_alltime<<< 4, T, sizeof(cuFloatComplex)*T >>>(ntime,ci*T, etaloc, fhess); - } -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - cudaFree(etaloc); -} - - - -/* scale eta with weights wt - N stations - eta: 4Nx2 complex float - iw: N x 1 weights, per station -*/ -void -cudakernel_fns_fscale(int N, cuFloatComplex *eta, float *iw) { -#ifdef CUDA_DBG - cudaError_t error; -#endif - /* since N is small ~60, use small no. of threads per block */ - int T=32; - int B=(N+T-1)/T; - kernel_fns_fscale<<< T, B>>>(N, eta, iw); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif -} - - -/* - update weight vector (nu+1)/(nu+error^2): - N: no of stations - M: no of constraints (baselines) - x: solution 2Nx2 complex float - y: data 8M float (8 for each baseline) - coh: coherency - bbh: baseline->station mapping - wtd: weight Mx1 - -*/ -void -cudakernel_fns_fupdate_weights(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, float *y, float *coh, short *bbh, float *wtd, float nu0) { -#ifdef CUDA_DBG - cudaError_t error; -#endif - kernel_fns_fupdate_weights<<< BlocksPerGrid, ThreadsPerBlock >>>(N, M, x, y, coh, bbh, wtd, nu0); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif -} - -/* - update weight vector (nu+1)/(nu+error^2) and log(weight) : - N: no of stations - M: no of constraints (baselines) - x: solution 2Nx2 complex float - y: data 8M float (8 for each baseline) - coh: coherency - bbh: baseline->station mapping - wtd: weight Mx1 - qd: weight Mx1 -*/ -void -cudakernel_fns_fupdate_weights_q(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, float *y, float *coh, short *bbh, float *wtd, float *qd, float nu0) { -#ifdef CUDA_DBG - cudaError_t error; -#endif - kernel_fns_fupdate_weights_q<<< BlocksPerGrid, ThreadsPerBlock >>>(N, M, x, y, coh, bbh, wtd, qd, nu0); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif -} -} diff --git a/src/lib/mderiv.cu b/src/lib/mderiv.cu deleted file mode 100644 index c7049b1..0000000 --- a/src/lib/mderiv.cu +++ /dev/null @@ -1,1625 +0,0 @@ -/* - * - Copyright (C) 2006-2008 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - -#include "cuda.h" -#include -#include -#include "sagecal.h" - -/* enable this for checking for kernel failure */ -//#define CUDA_DBG - - -__global__ void -kernel_deriv(int Nbase, int tilesz, int M, int Ns, int Nparam, int goff, const double *__restrict__ x, const double *__restrict__ coh, const double *__restrict__ p, const short *__restrict__ bb, const int *__restrict__ ptoclus, double *__restrict__ grad){ - /* global thread index */ - unsigned int n = threadIdx.x + blockDim.x*blockIdx.x; - /* parameter number of this thread */ - unsigned int np=n+goff; - - - /* this thread works on - x[8*n:8*n+7], coh[8*M*n:8*M*n+8*M-1] - bb[2*n:2*n+1] (sta1,sta2) - organization of p (N stations and M clusters) - sta 0 sta 1 sta 2 .... sta N-1 - clus 0 0...7 8...15 16...23 ... 8N-8 8N-1 - clus 1 8N..8N+7 8N+8..8N+15 8N+16..8N+23 .... 8N+8N-8...8N+8N-1 - ...... - clus M-1 (M-1)N..(M-1)N+7 (M-1)N+8..(M-1)N+15.... ...(M-1)N+8N-8 (M-1)N+8N-1 - - organization of coherencies (coh) - [0, 8*M-1] : baseline 0 - [8*M, 8*M+8*M-1]: baseline 1 - [n*8*M, n*8*M+8*M-1]: baseline n - ...... - [n*8*M+cm*8, n*8*M+cm*8+7] cluster cm, baseline n - - residual error stored at sum[n] - */ - - if (nptoclus[2*cli+1]+ptoclus[2*cli]*8*Ns-1)) { cli++; } - /* now either ci>=M: cluster not found - or ci=ptoclus[2*cli-1] && np<=ptoclus[2*cli-1]+ptoclus[2*cli-2]*8*Ns-1) { - cli--; - } - - if (cli=0 && sta2>=0) { - /* which parameter 0..7 */ - unsigned int stoff=np_s-stc*8; - /* which cluster 0..M-1 */ - unsigned int stm=cli; - - /* read residual vector, conjugated */ - cuDoubleComplex xr[4]; - xr[0].x=x[nb*8]; - xr[0].y=-x[nb*8+1]; - xr[1].x=x[nb*8+2]; - xr[1].y=-x[nb*8+3]; - xr[2].x=x[nb*8+4]; - xr[2].y=-x[nb*8+5]; - xr[3].x=x[nb*8+6]; - xr[3].y=-x[nb*8+7]; - - /* read in coherency */ - cuDoubleComplex C[4]; - C[0].x=coh[8*nb*M+8*stm]; - C[0].y=coh[8*nb*M+8*stm+1]; - C[1].x=coh[8*nb*M+8*stm+2]; - C[1].y=coh[8*nb*M+8*stm+3]; - C[2].x=coh[8*nb*M+8*stm+4]; - C[2].y=coh[8*nb*M+8*stm+5]; - C[3].x=coh[8*nb*M+8*stm+6]; - C[3].y=coh[8*nb*M+8*stm+7]; - - cuDoubleComplex G1[4]; - cuDoubleComplex G2[4]; - double pp[8]; - pp[0]=0.0; - pp[1]=0.0; - pp[2]=0.0; - pp[3]=0.0; - pp[4]=0.0; - pp[5]=0.0; - pp[6]=0.0; - pp[7]=0.0; - - if(stc==sta1) { - pp[stoff]=1.0; - G1[0].x=pp[0]; - G1[0].y=pp[1]; - G1[1].x=pp[2]; - G1[1].y=pp[3]; - G1[2].x=pp[4]; - G1[2].y=pp[5]; - G1[3].x=pp[6]; - G1[3].y=pp[7]; - - /* conjugate and transpose G2 */ - G2[0].x=p[pstart+tpchunk*8*Ns+sta2*8]; - G2[0].y=-p[pstart+tpchunk*8*Ns+sta2*8+1]; - G2[2].x=p[pstart+tpchunk*8*Ns+sta2*8+2]; - G2[2].y=-p[pstart+tpchunk*8*Ns+sta2*8+3]; - G2[1].x=p[pstart+tpchunk*8*Ns+sta2*8+4]; - G2[1].y=-p[pstart+tpchunk*8*Ns+sta2*8+5]; - G2[3].x=p[pstart+tpchunk*8*Ns+sta2*8+6]; - G2[3].y=-p[pstart+tpchunk*8*Ns+sta2*8+7]; - } else if (stc==sta2) { - pp[stoff]=1.0; - /* conjugate and transpose G2 */ - G2[0].x=pp[0]; - G2[0].y=-pp[1]; - G2[2].x=pp[2]; - G2[2].y=-pp[3]; - G2[1].x=pp[4]; - G2[1].y=-pp[5]; - G2[3].x=pp[6]; - G2[3].y=-pp[7]; - - /* conjugate and transpose G2 */ - G1[0].x=p[pstart+tpchunk*8*Ns+sta1*8]; - G1[0].y=p[pstart+tpchunk*8*Ns+sta1*8+1]; - G1[1].x=p[pstart+tpchunk*8*Ns+sta1*8+2]; - G1[1].y=p[pstart+tpchunk*8*Ns+sta1*8+3]; - G1[2].x=p[pstart+tpchunk*8*Ns+sta1*8+4]; - G1[2].y=p[pstart+tpchunk*8*Ns+sta1*8+5]; - G1[3].x=p[pstart+tpchunk*8*Ns+sta1*8+6]; - G1[3].y=p[pstart+tpchunk*8*Ns+sta1*8+7]; - } - cuDoubleComplex T1[4]; - /* T1=G1*C */ - T1[0]=cuCadd(cuCmul(G1[0],C[0]),cuCmul(G1[1],C[2])); - T1[1]=cuCadd(cuCmul(G1[0],C[1]),cuCmul(G1[1],C[3])); - T1[2]=cuCadd(cuCmul(G1[2],C[0]),cuCmul(G1[3],C[2])); - T1[3]=cuCadd(cuCmul(G1[2],C[1]),cuCmul(G1[3],C[3])); - - cuDoubleComplex T2[4]; - /* T2=T1*G2 , G2 conjugate transposed */ - T2[0]=cuCadd(cuCmul(T1[0],G2[0]),cuCmul(T1[1],G2[2])); - T2[1]=cuCadd(cuCmul(T1[0],G2[1]),cuCmul(T1[1],G2[3])); - T2[2]=cuCadd(cuCmul(T1[2],G2[0]),cuCmul(T1[3],G2[2])); - T2[3]=cuCadd(cuCmul(T1[2],G2[1]),cuCmul(T1[3],G2[3])); - - /* calculate product xr*vec(J_p C J_q^H ) */ - cuDoubleComplex csum; - csum=cuCmul(xr[0],T2[0]); - csum=cuCadd(csum,cuCmul(xr[1],T2[1])); - csum=cuCadd(csum,cuCmul(xr[2],T2[2])); - csum=cuCadd(csum,cuCmul(xr[3],T2[3])); - - - - gsum+=-2.0*csum.x; - } - - } - - } - } - - - grad[n]=gsum; - } - -} - - -/* note x is residual, not data */ -__global__ void -kernel_deriv_r_robust(int Nbase, int tilesz, int M, int Ns, int Nparam, int goff, const double *__restrict__ x, const double *__restrict__ coh, const double *__restrict__ p, const short *__restrict__ bb, const int *__restrict__ ptoclus, double *__restrict__ grad, double robust_nu){ - /* global thread index */ - unsigned int n = threadIdx.x + blockDim.x*blockIdx.x; - /* parameter number of this thread */ - unsigned int np=n+goff; - - - /* this thread works on - x[8*n:8*n+7], coh[8*M*n:8*M*n+8*M-1] - bb[2*n:2*n+1] (sta1,sta2) - organization of p (N stations and M clusters) - sta 0 sta 1 sta 2 .... sta N-1 - clus 0 0...7 8...15 16...23 ... 8N-8 8N-1 - clus 1 8N..8N+7 8N+8..8N+15 8N+16..8N+23 .... 8N+8N-8...8N+8N-1 - ...... - clus M-1 (M-1)N..(M-1)N+7 (M-1)N+8..(M-1)N+15.... ...(M-1)N+8N-8 (M-1)N+8N-1 - - organization of coherencies (coh) - [0, 8*M-1] : baseline 0 - [8*M, 8*M+8*M-1]: baseline 1 - [n*8*M, n*8*M+8*M-1]: baseline n - ...... - [n*8*M+cm*8, n*8*M+cm*8+7] cluster cm, baseline n - - residual error stored at sum[n] - */ - - if (nptoclus[2*cli+1]+ptoclus[2*cli]*8*Ns-1)) { cli++; } - /* now either ci>=M: cluster not found - or ci=ptoclus[2*cli-1] && np<=ptoclus[2*cli-1]+ptoclus[2*cli-2]*8*Ns-1) { - cli--; - } - - if (cli=0 && sta2>=0) { - /* which parameter 0..7 */ - unsigned int stoff=np_s-stc*8; - /* which cluster 0..M-1 */ - unsigned int stm=cli; - - /* read residual vector */ - double xr[8]; - xr[0]=x[nb*8]; - xr[1]=x[nb*8+1]; - xr[2]=x[nb*8+2]; - xr[3]=x[nb*8+3]; - xr[4]=x[nb*8+4]; - xr[5]=x[nb*8+5]; - xr[6]=x[nb*8+6]; - xr[7]=x[nb*8+7]; - - /* read in coherency */ - cuDoubleComplex C[4]; - C[0].x=coh[8*nb*M+8*stm]; - C[0].y=coh[8*nb*M+8*stm+1]; - C[1].x=coh[8*nb*M+8*stm+2]; - C[1].y=coh[8*nb*M+8*stm+3]; - C[2].x=coh[8*nb*M+8*stm+4]; - C[2].y=coh[8*nb*M+8*stm+5]; - C[3].x=coh[8*nb*M+8*stm+6]; - C[3].y=coh[8*nb*M+8*stm+7]; - - cuDoubleComplex G1[4]; - cuDoubleComplex G2[4]; - cuDoubleComplex T1[4]; - cuDoubleComplex T2[4]; - - G1[0].x=p[pstart+tpchunk*8*Ns+sta1*8]; - G1[0].y=p[pstart+tpchunk*8*Ns+sta1*8+1]; - G1[1].x=p[pstart+tpchunk*8*Ns+sta1*8+2]; - G1[1].y=p[pstart+tpchunk*8*Ns+sta1*8+3]; - G1[2].x=p[pstart+tpchunk*8*Ns+sta1*8+4]; - G1[2].y=p[pstart+tpchunk*8*Ns+sta1*8+5]; - G1[3].x=p[pstart+tpchunk*8*Ns+sta1*8+6]; - G1[3].y=p[pstart+tpchunk*8*Ns+sta1*8+7]; - /* conjugate and transpose G2 */ - G2[0].x=p[pstart+tpchunk*8*Ns+sta2*8]; - G2[0].y=-p[pstart+tpchunk*8*Ns+sta2*8+1]; - G2[2].x=p[pstart+tpchunk*8*Ns+sta2*8+2]; - G2[2].y=-p[pstart+tpchunk*8*Ns+sta2*8+3]; - G2[1].x=p[pstart+tpchunk*8*Ns+sta2*8+4]; - G2[1].y=-p[pstart+tpchunk*8*Ns+sta2*8+5]; - G2[3].x=p[pstart+tpchunk*8*Ns+sta2*8+6]; - G2[3].y=-p[pstart+tpchunk*8*Ns+sta2*8+7]; - - double pp[8]; - pp[0]=0.0; - pp[1]=0.0; - pp[2]=0.0; - pp[3]=0.0; - pp[4]=0.0; - pp[5]=0.0; - pp[6]=0.0; - pp[7]=0.0; - - pp[stoff]=1.0; - if(stc==sta1) { - G1[0].x=pp[0]; - G1[0].y=pp[1]; - G1[1].x=pp[2]; - G1[1].y=pp[3]; - G1[2].x=pp[4]; - G1[2].y=pp[5]; - G1[3].x=pp[6]; - G1[3].y=pp[7]; - } else if (stc==sta2) { - /* conjugate and transpose G2 */ - G2[0].x=pp[0]; - G2[0].y=-pp[1]; - G2[2].x=pp[2]; - G2[2].y=-pp[3]; - G2[1].x=pp[4]; - G2[1].y=-pp[5]; - G2[3].x=pp[6]; - G2[3].y=-pp[7]; - } - /* T1=G1*C */ - T1[0]=cuCadd(cuCmul(G1[0],C[0]),cuCmul(G1[1],C[2])); - T1[1]=cuCadd(cuCmul(G1[0],C[1]),cuCmul(G1[1],C[3])); - T1[2]=cuCadd(cuCmul(G1[2],C[0]),cuCmul(G1[3],C[2])); - T1[3]=cuCadd(cuCmul(G1[2],C[1]),cuCmul(G1[3],C[3])); - - /* T2=T1*G2 , G2 conjugate transposed */ - T2[0]=cuCadd(cuCmul(T1[0],G2[0]),cuCmul(T1[1],G2[2])); - T2[1]=cuCadd(cuCmul(T1[0],G2[1]),cuCmul(T1[1],G2[3])); - T2[2]=cuCadd(cuCmul(T1[2],G2[0]),cuCmul(T1[3],G2[2])); - T2[3]=cuCadd(cuCmul(T1[2],G2[1]),cuCmul(T1[3],G2[3])); - - - /* calculate product xr*vec(J_p C J_q^H )/(nu+residual^2) */ - double dsum; - dsum=xr[0]*T2[0].x/(robust_nu+xr[0]*xr[0]); - dsum+=xr[1]*T2[0].y/(robust_nu+xr[1]*xr[1]); - dsum+=xr[2]*T2[1].x/(robust_nu+xr[2]*xr[2]); - dsum+=xr[3]*T2[1].y/(robust_nu+xr[3]*xr[3]); - dsum+=xr[4]*T2[2].x/(robust_nu+xr[4]*xr[4]); - dsum+=xr[5]*T2[2].y/(robust_nu+xr[5]*xr[5]); - dsum+=xr[6]*T2[3].x/(robust_nu+xr[6]*xr[6]); - dsum+=xr[7]*T2[3].y/(robust_nu+xr[7]*xr[7]); - /* accumulate sum NOTE - its important to get the sign right, - depending on res=data-model or res=model-data */ - gsum+=-2.0*dsum; - - } - - } - - } - } - - - grad[n]=gsum; - } - -} - - -/* note x is residual, not data */ -__global__ void -kernel_deriv_r(int Nbase, int tilesz, int M, int Ns, int Nparam, int goff, const double *__restrict__ x, const double *__restrict__ coh, const double *__restrict__ p, const short *__restrict__ bb, const int *__restrict__ ptoclus, double *__restrict__ grad){ - /* global thread index */ - unsigned int n = threadIdx.x + blockDim.x*blockIdx.x; - /* parameter number of this thread */ - unsigned int np=n+goff; - - - /* this thread works on - x[8*n:8*n+7], coh[8*M*n:8*M*n+8*M-1] - bb[2*n:2*n+1] (sta1,sta2) - organization of p (N stations and M clusters) - sta 0 sta 1 sta 2 .... sta N-1 - clus 0 0...7 8...15 16...23 ... 8N-8 8N-1 - clus 1 8N..8N+7 8N+8..8N+15 8N+16..8N+23 .... 8N+8N-8...8N+8N-1 - ...... - clus M-1 (M-1)N..(M-1)N+7 (M-1)N+8..(M-1)N+15.... ...(M-1)N+8N-8 (M-1)N+8N-1 - - organization of coherencies (coh) - [0, 8*M-1] : baseline 0 - [8*M, 8*M+8*M-1]: baseline 1 - [n*8*M, n*8*M+8*M-1]: baseline n - ...... - [n*8*M+cm*8, n*8*M+cm*8+7] cluster cm, baseline n - - residual error stored at sum[n] - */ - - if (nptoclus[2*cli+1]+ptoclus[2*cli]*8*Ns-1)) { cli++; } - /* now either ci>=M: cluster not found - or ci=ptoclus[2*cli-1] && np<=ptoclus[2*cli-1]+ptoclus[2*cli-2]*8*Ns-1) { - cli--; - } - - if (cli=0 && sta2>=0) { - /* which parameter 0..7 */ - unsigned int stoff=np_s-stc*8; - /* which cluster 0..M-1 */ - unsigned int stm=cli; - - /* read residual vector, conjugated */ - cuDoubleComplex xr[4]; - xr[0].x=x[nb*8]; - xr[0].y=-x[nb*8+1]; - xr[1].x=x[nb*8+2]; - xr[1].y=-x[nb*8+3]; - xr[2].x=x[nb*8+4]; - xr[2].y=-x[nb*8+5]; - xr[3].x=x[nb*8+6]; - xr[3].y=-x[nb*8+7]; - - /* read in coherency */ - cuDoubleComplex C[4]; - C[0].x=coh[8*nb*M+8*stm]; - C[0].y=coh[8*nb*M+8*stm+1]; - C[1].x=coh[8*nb*M+8*stm+2]; - C[1].y=coh[8*nb*M+8*stm+3]; - C[2].x=coh[8*nb*M+8*stm+4]; - C[2].y=coh[8*nb*M+8*stm+5]; - C[3].x=coh[8*nb*M+8*stm+6]; - C[3].y=coh[8*nb*M+8*stm+7]; - - cuDoubleComplex G1[4]; - cuDoubleComplex G2[4]; - cuDoubleComplex T1[4]; - cuDoubleComplex T2[4]; - - G1[0].x=p[pstart+tpchunk*8*Ns+sta1*8]; - G1[0].y=p[pstart+tpchunk*8*Ns+sta1*8+1]; - G1[1].x=p[pstart+tpchunk*8*Ns+sta1*8+2]; - G1[1].y=p[pstart+tpchunk*8*Ns+sta1*8+3]; - G1[2].x=p[pstart+tpchunk*8*Ns+sta1*8+4]; - G1[2].y=p[pstart+tpchunk*8*Ns+sta1*8+5]; - G1[3].x=p[pstart+tpchunk*8*Ns+sta1*8+6]; - G1[3].y=p[pstart+tpchunk*8*Ns+sta1*8+7]; - /* conjugate and transpose G2 */ - G2[0].x=p[pstart+tpchunk*8*Ns+sta2*8]; - G2[0].y=-p[pstart+tpchunk*8*Ns+sta2*8+1]; - G2[2].x=p[pstart+tpchunk*8*Ns+sta2*8+2]; - G2[2].y=-p[pstart+tpchunk*8*Ns+sta2*8+3]; - G2[1].x=p[pstart+tpchunk*8*Ns+sta2*8+4]; - G2[1].y=-p[pstart+tpchunk*8*Ns+sta2*8+5]; - G2[3].x=p[pstart+tpchunk*8*Ns+sta2*8+6]; - G2[3].y=-p[pstart+tpchunk*8*Ns+sta2*8+7]; - - double pp[8]; - pp[0]=0.0; - pp[1]=0.0; - pp[2]=0.0; - pp[3]=0.0; - pp[4]=0.0; - pp[5]=0.0; - pp[6]=0.0; - pp[7]=0.0; - - pp[stoff]=1.0; - if(stc==sta1) { - G1[0].x=pp[0]; - G1[0].y=pp[1]; - G1[1].x=pp[2]; - G1[1].y=pp[3]; - G1[2].x=pp[4]; - G1[2].y=pp[5]; - G1[3].x=pp[6]; - G1[3].y=pp[7]; - } else if (stc==sta2) { - /* conjugate and transpose G2 */ - G2[0].x=pp[0]; - G2[0].y=-pp[1]; - G2[2].x=pp[2]; - G2[2].y=-pp[3]; - G2[1].x=pp[4]; - G2[1].y=-pp[5]; - G2[3].x=pp[6]; - G2[3].y=-pp[7]; - } - /* T1=G1*C */ - T1[0]=cuCadd(cuCmul(G1[0],C[0]),cuCmul(G1[1],C[2])); - T1[1]=cuCadd(cuCmul(G1[0],C[1]),cuCmul(G1[1],C[3])); - T1[2]=cuCadd(cuCmul(G1[2],C[0]),cuCmul(G1[3],C[2])); - T1[3]=cuCadd(cuCmul(G1[2],C[1]),cuCmul(G1[3],C[3])); - - /* T2=T1*G2 , G2 conjugate transposed */ - T2[0]=cuCadd(cuCmul(T1[0],G2[0]),cuCmul(T1[1],G2[2])); - T2[1]=cuCadd(cuCmul(T1[0],G2[1]),cuCmul(T1[1],G2[3])); - T2[2]=cuCadd(cuCmul(T1[2],G2[0]),cuCmul(T1[3],G2[2])); - T2[3]=cuCadd(cuCmul(T1[2],G2[1]),cuCmul(T1[3],G2[3])); - - - /* calculate product xr*vec(J_p C J_q^H ) */ - cuDoubleComplex csum; - csum=cuCmul(xr[0],T2[0]); - csum=cuCadd(csum,cuCmul(xr[1],T2[1])); - csum=cuCadd(csum,cuCmul(xr[2],T2[2])); - csum=cuCadd(csum,cuCmul(xr[3],T2[3])); - - - /* notice no -ve sign */ - gsum+=2.0*csum.x; - } - - } - - } - } - - - grad[n]=gsum; - } - -} - - -__global__ void -kernel_residual(int Nbase, int M, int Ns, const double *__restrict__ x, const double *__restrict__ coh, const double *__restrict__ p, const short *__restrict__ bb, const int *__restrict__ ptoclus, double *__restrict__ ed){ - - /* global thread index */ - unsigned int n = threadIdx.x + blockDim.x*blockIdx.x; - - if (n=0 && sta2>=0) { - /* read data vector */ - cuDoubleComplex xr[4]; - xr[0].x=x[n*8]; - xr[0].y=x[n*8+1]; - xr[1].x=x[n*8+2]; - xr[1].y=x[n*8+3]; - xr[2].x=x[n*8+4]; - xr[2].y=x[n*8+5]; - xr[3].x=x[n*8+6]; - xr[3].y=x[n*8+7]; - - for (int cm=0; cm=0 && sta2>=0) { - /* read data vector */ - cuDoubleComplex xr[4]; - xr[0].x=x[n*8]; - xr[0].y=x[n*8+1]; - xr[1].x=x[n*8+2]; - xr[1].y=x[n*8+3]; - xr[2].x=x[n*8+4]; - xr[2].y=x[n*8+5]; - xr[3].x=x[n*8+6]; - xr[3].y=x[n*8+7]; - - - for (int cm=0; cm0; s=s/2) { - if(tid < s) ek[tid] += ek[tid + s]; - __syncthreads(); - } - - /* copy back to global array */ - if(tid==0) { - ed[blockIdx.x]=ek[0]; - } - -} - - -__global__ void -kernel_fcost(int Nbase, int boff, int M, int Ns, int Nbasetotal, const double *__restrict__ x, const double *__restrict__ coh, const double *__restrict__ p, const short *__restrict__ bb, const int *__restrict__ ptoclus, double *__restrict__ ed){ - /* shared memory */ - extern __shared__ double ek[]; - - /* global thread index */ - unsigned int n = threadIdx.x + blockDim.x*blockIdx.x; - int tid=threadIdx.x; - ek[tid]=0.0; - - if (n=0 && sta2>=0) { - /* read data vector */ - cuDoubleComplex xr[4]; - xr[0].x=x[n*8]; - xr[0].y=x[n*8+1]; - xr[1].x=x[n*8+2]; - xr[1].y=x[n*8+3]; - xr[2].x=x[n*8+4]; - xr[2].y=x[n*8+5]; - xr[3].x=x[n*8+6]; - xr[3].y=x[n*8+7]; - - - for (int cm=0; cm0; s=s/2) { - if(tid < s) ek[tid] += ek[tid + s]; - __syncthreads(); - } - - /* copy back to global array */ - if(tid==0) { - ed[blockIdx.x]=ek[0]; - } - -} - - -__global__ void -kernel_diagdiv(int M, double eps, double *__restrict__ y,const double *__restrict__ x){ - unsigned int tid = blockIdx.x*blockDim.x + threadIdx.x; - /* make sure to use only M threads */ - if (tideps) { - y[tid]=y[tid]/x[tid]; - } else { - y[tid]=0.0; - } - } -} - -__global__ void -kernel_diagmu(int M, double *__restrict__ A,double mu){ - unsigned int tid = blockIdx.x*blockDim.x + threadIdx.x; - /* make sure to use only M threads */ - if (tid=0 - */ - if (sta1>=0 && sta2>=0) { - cuDoubleComplex G1[4]; - double pp[8]; - pp[0]=p[sta1*8]; - pp[1]=p[sta1*8+1]; - pp[2]=p[sta1*8+2]; - pp[3]=p[sta1*8+3]; - pp[4]=p[sta1*8+4]; - pp[5]=p[sta1*8+5]; - pp[6]=p[sta1*8+6]; - pp[7]=p[sta1*8+7]; - G1[0].x=pp[0]; - G1[0].y=pp[1]; - G1[1].x=pp[2]; - G1[1].y=pp[3]; - G1[2].x=pp[4]; - G1[2].y=pp[5]; - G1[3].x=pp[6]; - G1[3].y=pp[7]; - - - cuDoubleComplex C[4]; - C[0].x=coh[8*n]; - C[0].y=coh[8*n+1]; - C[1].x=coh[8*n+2]; - C[1].y=coh[8*n+3]; - C[2].x=coh[8*n+4]; - C[2].y=coh[8*n+5]; - C[3].x=coh[8*n+6]; - C[3].y=coh[8*n+7]; - - cuDoubleComplex T1[4]; - /* T=G1*C */ - T1[0]=cuCadd(cuCmul(G1[0],C[0]),cuCmul(G1[1],C[2])); - T1[1]=cuCadd(cuCmul(G1[0],C[1]),cuCmul(G1[1],C[3])); - T1[2]=cuCadd(cuCmul(G1[2],C[0]),cuCmul(G1[3],C[2])); - T1[3]=cuCadd(cuCmul(G1[2],C[1]),cuCmul(G1[3],C[3])); - - cuDoubleComplex G2[4]; - /* conjugate this */ - pp[0]=p[sta2*8]; - pp[1]=-p[sta2*8+1]; - pp[2]=p[sta2*8+2]; - pp[3]=-p[sta2*8+3]; - pp[4]=p[sta2*8+4]; - pp[5]=-p[sta2*8+5]; - pp[6]=p[sta2*8+6]; - pp[7]=-p[sta2*8+7]; - G2[0].x=pp[0]; - G2[0].y=pp[1]; - G2[2].x=pp[2]; - G2[2].y=pp[3]; - G2[1].x=pp[4]; - G2[1].y=pp[5]; - G2[3].x=pp[6]; - G2[3].y=pp[7]; - - cuDoubleComplex T2[4]; - T2[0]=cuCadd(cuCmul(T1[0],G2[0]),cuCmul(T1[1],G2[2])); - T2[1]=cuCadd(cuCmul(T1[0],G2[1]),cuCmul(T1[1],G2[3])); - T2[2]=cuCadd(cuCmul(T1[2],G2[0]),cuCmul(T1[3],G2[2])); - T2[3]=cuCadd(cuCmul(T1[2],G2[1]),cuCmul(T1[3],G2[3])); - /* update model vector */ - x[8*n]=T2[0].x; - x[8*n+1]=T2[0].y; - x[8*n+2]=T2[1].x; - x[8*n+3]=T2[1].y; - x[8*n+4]=T2[2].x; - x[8*n+5]=T2[2].y; - x[8*n+6]=T2[3].x; - x[8*n+7]=T2[3].y; - - } - } - -} - -__global__ void -kernel_jacf(int Nbase, int M, double *__restrict__ jac, const double *__restrict__ coh, const double *__restrict__ p, const short *__restrict__ bb, int N){ - /* global thread index : equal to the baseline */ - unsigned int n = threadIdx.x + blockDim.x*blockIdx.x; - /* which parameter:0...M */ - unsigned int m = threadIdx.y + blockDim.y*blockIdx.y; - - /* this thread works on - x[8*n:8*n+7], coh[8*M*n:8*M*n+8*M-1] - bb[2*n:2*n+1] (sta1,sta2) - organization of p (N stations and M clusters) - sta 0 sta 1 sta 2 .... sta N-1 - clus 0 0...7 8...15 16...23 ... 8N-8 8N-1 - clus 1 8N..8N+7 8N+8..8N+15 8N+16..8N+23 .... 8N+8N-8...8N+8N-1 - ...... - clus M-1 (M-1)N..(M-1)N+7 (M-1)N+8..(M-1)N+15.... ...(M-1)N+8N-8 (M-1)N+8N-1 - - organization of coherencies (coh) - [0, 8*M-1] : baseline 0 - [8*M, 8*M+8*M-1]: baseline 1 - [n*8*M, n*8*M+8*M-1]: baseline n - ...... - [n*8*M+cm*8, n*8*M+cm*8+7] cluster cm, baseline n - - residual error stored at sum[n] - */ - - if(n>3; /* 0...Ns-1 (because M=total par= 8 * Nstations */ - - if (((stc==sta2)||(stc==sta1)) && sta1>=0 && sta2>=0 ) { - - cuDoubleComplex C[4]; - C[0].x=coh[8*n]; - C[0].y=coh[8*n+1]; - C[1].x=coh[8*n+2]; - C[1].y=coh[8*n+3]; - C[2].x=coh[8*n+4]; - C[2].y=coh[8*n+5]; - C[3].x=coh[8*n+6]; - C[3].y=coh[8*n+7]; - - /* which parameter exactly 0..7 */ - //int stoff=m%8; - int stoff=m-stc*8; - double pp1[8]; - double pp2[8]; - if (stc==sta1) { - for (int cn=0; cn<8; cn++) { - pp1[cn]=0.0; - pp2[cn]=p[sta2*8+cn]; - } - pp1[stoff]=1.0; - } else if (stc==sta2) { - for (int cn=0; cn<8; cn++) { - pp2[cn]=0.0; - pp1[cn]=p[sta1*8+cn]; - } - pp2[stoff]=1.0; - } - - - cuDoubleComplex G1[4]; - G1[0].x=pp1[0]; - G1[0].y=pp1[1]; - G1[1].x=pp1[2]; - G1[1].y=pp1[3]; - G1[2].x=pp1[4]; - G1[2].y=pp1[5]; - G1[3].x=pp1[6]; - G1[3].y=pp1[7]; - - cuDoubleComplex T1[4]; - /* T=G1*C */ - T1[0]=cuCadd(cuCmul(G1[0],C[0]),cuCmul(G1[1],C[2])); - T1[1]=cuCadd(cuCmul(G1[0],C[1]),cuCmul(G1[1],C[3])); - T1[2]=cuCadd(cuCmul(G1[2],C[0]),cuCmul(G1[3],C[2])); - T1[3]=cuCadd(cuCmul(G1[2],C[1]),cuCmul(G1[3],C[3])); - - cuDoubleComplex G2[4]; - /* conjugate this */ - G2[0].x=pp2[0]; - G2[0].y=-pp2[1]; - G2[2].x=pp2[2]; - G2[2].y=-pp2[3]; - G2[1].x=pp2[4]; - G2[1].y=-pp2[5]; - G2[3].x=pp2[6]; - G2[3].y=-pp2[7]; - - cuDoubleComplex T2[4]; - T2[0]=cuCadd(cuCmul(T1[0],G2[0]),cuCmul(T1[1],G2[2])); - T2[1]=cuCadd(cuCmul(T1[0],G2[1]),cuCmul(T1[1],G2[3])); - T2[2]=cuCadd(cuCmul(T1[2],G2[0]),cuCmul(T1[3],G2[2])); - T2[3]=cuCadd(cuCmul(T1[2],G2[1]),cuCmul(T1[3],G2[3])); - /* update jacobian */ - /* NOTE: row major order */ - jac[m+M*8*n]=T2[0].x; - jac[m+M*(8*n+1)]=T2[0].y; - jac[m+M*(8*n+2)]=T2[1].x; - jac[m+M*(8*n+3)]=T2[1].y; - jac[m+M*(8*n+4)]=T2[2].x; - jac[m+M*(8*n+5)]=T2[2].y; - jac[m+M*(8*n+6)]=T2[3].x; - jac[m+M*(8*n+7)]=T2[3].y; - - } - } - -} - - -/* sum up all N elements of vector input - and save (per block) in output (size > number of blocks) */ -__global__ void -plus_reduce_multi(const double *__restrict__ input, int N, int blockDim_2, double *__restrict__ output) { - // Each block loads its elements into shared memory - extern __shared__ double x[]; - int tid = threadIdx.x; - int i = blockIdx.x*blockDim.x + threadIdx.x; - x[tid] = (i 1) { - int halfPoint = (nTotalThreads >> 1); // divide by two - if (tid < halfPoint) { - int thread2 = tid + halfPoint; - if (thread2 < blockDim.x) { // Skipping the fictitious threads blockDim.x ... blockDim_2-1 - x[tid] = x[tid]+x[thread2]; - } - } - __syncthreads(); - nTotalThreads = halfPoint; // Reducing the binary tree size by two - } - - /* add back to total */ - if( tid == 0 ) { - output[blockIdx.x]=x[tid]; - } -} - -/* sum up all N elements of vector input - NOTE: only 1 block should be used */ -__global__ void -plus_reduce(const double *__restrict__ input, int N, int blockDim_2, double *total) { - // Each block loads its elements into shared memory - extern __shared__ double x[]; - int tid = threadIdx.x; - int i = blockIdx.x*blockDim.x + threadIdx.x; - x[tid] = (i 1) { - int halfPoint = (nTotalThreads >> 1); // divide by two - if (tid < halfPoint) { - int thread2 = tid + halfPoint; - if (thread2 < blockDim.x) { // Skipping the fictitious threads blockDim.x ... blockDim_2-1 - x[tid] = x[tid]+x[thread2]; - } - } - __syncthreads(); - nTotalThreads = halfPoint; // Reducing the binary tree size by two - } - - /* add back to total */ - if( tid == 0 ) { - *total=*total+x[tid]; - } -} - - -/* only use extern if calling code is C */ -extern "C" -{ - - -static void -checkCudaError(cudaError_t err, const char *file, int line) -{ - -#ifdef CUDA_DEBUG - if(!err) - return; - fprintf(stderr,"GPU (CUDA): %s %s %d\n", cudaGetErrorString(err),file,line); - exit(EXIT_FAILURE); -#endif -} - -/* need power of 2 for tree reduction to work */ -static int -NearestPowerOf2 (int n){ - if (!n) return n; //(0 == 2^0) - - int x = 1; - while(x < n) { - x <<= 1; - } - return x; -} - - -/* cuda driver for kernel */ -/* ThreadsPerBlock: keep <= 128 ??? - BlocksPerGrid: depends on the threads/baselines> Threads*Blocks approx baselines - Nbase: no of baselines (total, including tilesz >1) - tilesz: tile size - M: no of clusters - Ns: no of stations - Nparam: no of actual parameters <=total - goff: starting point of gradient calculation 0..Nparams - x: N*8 x 1 residual - coh: N*8*M x 1 - p: M*Ns*8 x 1 - bb: 2*N x 1 - ptoclus: 2*M x 1 - - grad: Nparamsx1 gradient values -*/ -void -cudakernel_lbfgs(int ThreadsPerBlock, int BlocksPerGrid, int Nbase, int tilesz, int M, int Ns, int Nparam, int goff, double *x, double *coh, double *p, short *bb, int *ptoclus, double *grad){ - -#ifdef CUDA_DBG - cudaError_t error; -#endif - /* invoke device on this block/thread grid (last argument is buffer size in bytes) */ - kernel_deriv<<< BlocksPerGrid, ThreadsPerBlock, ThreadsPerBlock*sizeof(double) >>> (Nbase, tilesz, M, Ns, Nparam, goff, x, coh, p, bb, ptoclus, grad); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - -} - -void -cudakernel_lbfgs_r_robust(int ThreadsPerBlock, int BlocksPerGrid, int Nbase, int tilesz, int M, int Ns, int Nparam, int goff, double *x, double *coh, double *p, short *bb, int *ptoclus, double *grad, double robust_nu){ - - cudaError_t error; - /* invoke kernel to calculate residuals first */ - double *eo; - if((error=cudaMalloc((void**)&eo, Nbase*8*sizeof(double)))!=cudaSuccess) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - cudaMemset(eo, 0, sizeof(double)*Nbase*8); - checkCudaError(error,__FILE__,__LINE__); - - int L=(Nbase+ThreadsPerBlock-1)/ThreadsPerBlock; -#ifdef CUDA_DBG - error = cudaGetLastError(); /* reset all previous errors */ -#endif - - kernel_residual<<< L, ThreadsPerBlock >>> (Nbase, M, Ns, x, coh, p, bb, ptoclus, eo); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - /* invoke device on this block/thread grid (last argument is buffer size in bytes) */ - kernel_deriv_r_robust<<< BlocksPerGrid, ThreadsPerBlock >>> (Nbase, tilesz, M, Ns, Nparam, goff, eo, coh, p, bb, ptoclus, grad, robust_nu); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - cudaFree(eo); -} - -void -cudakernel_lbfgs_r(int ThreadsPerBlock, int BlocksPerGrid, int Nbase, int tilesz, int M, int Ns, int Nparam, int goff, double *x, double *coh, double *p, short *bb, int *ptoclus, double *grad){ - - cudaError_t error; - /* invoke kernel to calculate residuals first */ - double *eo; - if((error=cudaMalloc((void**)&eo, Nbase*8*sizeof(double)))!=cudaSuccess) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - cudaMemset(eo, 0, sizeof(double)*Nbase*8); - checkCudaError(error,__FILE__,__LINE__); - int L=(Nbase+ThreadsPerBlock-1)/ThreadsPerBlock; - -#ifdef CUDA_DBG - error = cudaGetLastError(); /* reset all previous errors */ -#endif - - kernel_residual<<< L, ThreadsPerBlock >>> (Nbase, M, Ns, x, coh, p, bb, ptoclus, eo); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - /* invoke device on this block/thread grid (last argument is buffer size in bytes) */ - kernel_deriv_r<<< BlocksPerGrid, ThreadsPerBlock >>> (Nbase, tilesz, M, Ns, Nparam, goff, eo, coh, p, bb, ptoclus, grad); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - cudaFree(eo); -} - -/* note x,coh and bb are with the right offset */ -double -cudakernel_lbfgs_cost_robust(int ThreadsPerBlock, int BlocksPerGrid, int Nbase, int boff, int M, int Ns, int Nbasetotal, double *x, double *coh, double *p, short *bb, int *ptoclus, double robust_nu){ - - double *ed; - cudaError_t error; - if((error=cudaMalloc((void**)&ed, sizeof(double)*BlocksPerGrid))!=cudaSuccess) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - - cudaMemset(ed, 0, sizeof(double)*BlocksPerGrid); - kernel_fcost_robust<<< BlocksPerGrid, ThreadsPerBlock, ThreadsPerBlock*sizeof(double) >>> (Nbase, boff, M, Ns, Nbasetotal, x, coh, p, bb, ptoclus, ed, 1.0/robust_nu); - -#ifdef CUDA_DBG - error = cudaGetLastError(); - checkCudaError(error,__FILE__,__LINE__); -#endif - - int T=DEFAULT_TH_PER_BK; - double *totald,total; - if((error=cudaMalloc((void**)&totald, sizeof(double)))!=cudaSuccess) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - cudaMemset(totald, 0, sizeof(double)); - checkCudaError(error,__FILE__,__LINE__); - - - if (T>BlocksPerGrid) { - /* one kernel launch is enough */ - plus_reduce<<< 1, BlocksPerGrid, sizeof(double)*BlocksPerGrid>>>(ed, BlocksPerGrid, NearestPowerOf2(BlocksPerGrid), totald); -#ifdef CUDA_DBG - error = cudaGetLastError(); - checkCudaError(error,__FILE__,__LINE__); -#endif - } else { - /* multiple kernel launches */ - int L=(BlocksPerGrid+T-1)/T; - double *eo; - if((error=cudaMalloc((void**)&eo, L*sizeof(double)))!=cudaSuccess) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - plus_reduce_multi<<< L, T, sizeof(double)*T>>>(ed, BlocksPerGrid, NearestPowerOf2(T), eo); - -#ifdef CUDA_DBG - error = cudaGetLastError(); - checkCudaError(error,__FILE__,__LINE__); -#endif - plus_reduce<<< 1, L, sizeof(double)*L>>>(eo, L, NearestPowerOf2(L), totald); -#ifdef CUDA_DBG - error = cudaGetLastError(); - checkCudaError(error,__FILE__,__LINE__); -#endif - cudaFree(eo); - } - cudaMemcpy(&total,totald,sizeof(double),cudaMemcpyDeviceToHost); - cudaFree(totald); - cudaFree(ed); - - return total; -} - -double -cudakernel_lbfgs_cost(int ThreadsPerBlock, int BlocksPerGrid, int Nbase, int boff, int M, int Ns, int Nbasetotal, double *x, double *coh, double *p, short *bb, int *ptoclus){ - double *ed; - cudaError_t error; - if((error=cudaMalloc((void**)&ed, sizeof(double)*BlocksPerGrid))!=cudaSuccess) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - cudaMemset(ed, 0, sizeof(double)*BlocksPerGrid); - kernel_fcost<<< BlocksPerGrid, ThreadsPerBlock, ThreadsPerBlock*sizeof(double) >>> (Nbase, boff, M, Ns, Nbasetotal, x, coh, p, bb, ptoclus, ed); -#ifdef CUDA_DBG - error = cudaGetLastError(); - checkCudaError(error,__FILE__,__LINE__); -#endif - - int T=DEFAULT_TH_PER_BK; - double *totald,total; - if((error=cudaMalloc((void**)&totald, sizeof(double)))!=cudaSuccess) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - cudaMemset(totald, 0, sizeof(double)); - checkCudaError(error,__FILE__,__LINE__); - - - if (T>BlocksPerGrid) { - /* one kernel launch is enough */ - plus_reduce<<< 1, BlocksPerGrid, sizeof(double)*BlocksPerGrid>>>(ed, BlocksPerGrid, NearestPowerOf2(BlocksPerGrid), totald); - -#ifdef CUDA_DBG - error = cudaGetLastError(); - checkCudaError(error,__FILE__,__LINE__); -#endif - } else { - /* multiple kernel launches */ - int L=(BlocksPerGrid+T-1)/T; - double *eo; - if((error=cudaMalloc((void**)&eo, L*sizeof(double)))!=cudaSuccess) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - plus_reduce_multi<<< L, T, sizeof(double)*T>>>(ed, BlocksPerGrid, NearestPowerOf2(T), eo); -#ifdef CUDA_DBG - error = cudaGetLastError(); - checkCudaError(error,__FILE__,__LINE__); -#endif - plus_reduce<<< 1, L, sizeof(double)*L>>>(eo, L, NearestPowerOf2(L), totald); -#ifdef CUDA_DBG - error = cudaGetLastError(); - checkCudaError(error,__FILE__,__LINE__); -#endif - cudaFree(eo); - } - cudaMemcpy(&total,totald,sizeof(double),cudaMemcpyDeviceToHost); - cudaFree(totald); - cudaFree(ed); - - return total; -} - - - -/* divide by singular values Dpd[]/Sd[] for Sd[]> eps */ -void -cudakernel_diagdiv(int ThreadsPerBlock, int BlocksPerGrid, int M, double eps, double *Dpd, double *Sd) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - kernel_diagdiv<<< BlocksPerGrid, ThreadsPerBlock >>>(M, eps, Dpd, Sd); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - -} - -/* cuda driver for calculating - A<= A+mu I, adding mu to diagonal entries of A - A: size MxM - ThreadsPerBlock, BlocksPerGrid calculated to meet M -*/ -void -cudakernel_diagmu(int ThreadsPerBlock, int BlocksPerGrid, int M, double *A, double mu) { -#ifdef CUDA_DBG - cudaError_t error; -#endif - kernel_diagmu<<< BlocksPerGrid, ThreadsPerBlock >>>(M, A, mu); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif -} - - -/* cuda driver for calculating f() */ -/* p: params (Mx1), x: data (Nx1), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -void -cudakernel_func(int ThreadsPerBlock, int BlocksPerGrid, double *p, double *x, int M, int N, double *coh, short *bbh, int Nbase, int Mclus, int Nstations) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - cudaMemset(x, 0, N*sizeof(double)); -// printf("Kernel data size=%d, block=%d, thread=%d, baselines=%d\n",N,BlocksPerGrid, ThreadsPerBlock,Nbase); - kernel_func<<< BlocksPerGrid, ThreadsPerBlock >>>(Nbase, x, coh, p, bbh, Nstations); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - -} - -/* cuda driver for calculating jacf() */ -/* p: params (Mx1), jac: jacobian (NxM), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -void -cudakernel_jacf(int ThreadsPerBlock_row, int ThreadsPerBlock_col, double *p, double *jac, int M, int N, double *coh, short *bbh, int Nbase, int Mclus, int Nstations) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - /* NOTE: use small value for ThreadsPerBlock here, like 8 */ - dim3 threadsPerBlock(16, 8); - /* jacobian: Nbase x Nstations (proportional to N), so */ - dim3 numBlocks((Nbase+threadsPerBlock.x-1)/threadsPerBlock.x, - (M+threadsPerBlock.y-1)/threadsPerBlock.y); - /* set memory of jac to zero */ - cudaMemset(jac, 0, N*M*sizeof(double)); - // printf("Kernel Jax data size=%d, params=%d, block=%d,%d, thread=%d,%d, baselines=%d\n",N, M, numBlocks.x,numBlocks.y, threadsPerBlock.x, threadsPerBlock.y, Nbase); - kernel_jacf<<< numBlocks, threadsPerBlock>>>(Nbase, M, jac, coh, p, bbh, Nstations); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - -} - -} diff --git a/src/lib/mderiv_fl.cu b/src/lib/mderiv_fl.cu deleted file mode 100644 index 9a27702..0000000 --- a/src/lib/mderiv_fl.cu +++ /dev/null @@ -1,380 +0,0 @@ -/* - * - Copyright (C) 2006-2008 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - -#include "cuda.h" -#include -#include - -/* enable this for checking for kernel failure */ -//#define CUDA_DBG - -__global__ void kernel_diagdiv_fl(int M, float eps, float *y, float *x){ - unsigned int tid = blockIdx.x*blockDim.x + threadIdx.x; - /* make sure to use only M threads */ - if (tideps) { - y[tid]=y[tid]/x[tid]; - } else { - y[tid]=0.0f; - } - } -} - -__global__ void kernel_diagmu_fl(int M, float *A,float mu){ - unsigned int tid = blockIdx.x*blockDim.x + threadIdx.x; - /* make sure to use only M threads */ - if (tid=0 - */ - if (sta1>=0 && sta2>=0) { - cuFloatComplex G1[4]; - float pp[8]; - pp[0]=p[sta1*8]; - pp[1]=p[sta1*8+1]; - pp[2]=p[sta1*8+2]; - pp[3]=p[sta1*8+3]; - pp[4]=p[sta1*8+4]; - pp[5]=p[sta1*8+5]; - pp[6]=p[sta1*8+6]; - pp[7]=p[sta1*8+7]; - G1[0].x=pp[0]; - G1[0].y=pp[1]; - G1[1].x=pp[2]; - G1[1].y=pp[3]; - G1[2].x=pp[4]; - G1[2].y=pp[5]; - G1[3].x=pp[6]; - G1[3].y=pp[7]; - - - cuFloatComplex C[4]; - C[0].x=coh[8*n]; - C[0].y=coh[8*n+1]; - C[1].x=coh[8*n+2]; - C[1].y=coh[8*n+3]; - C[2].x=coh[8*n+4]; - C[2].y=coh[8*n+5]; - C[3].x=coh[8*n+6]; - C[3].y=coh[8*n+7]; - - cuFloatComplex T1[4]; - /* T=G1*C */ - T1[0]=cuCaddf(cuCmulf(G1[0],C[0]),cuCmulf(G1[1],C[2])); - T1[1]=cuCaddf(cuCmulf(G1[0],C[1]),cuCmulf(G1[1],C[3])); - T1[2]=cuCaddf(cuCmulf(G1[2],C[0]),cuCmulf(G1[3],C[2])); - T1[3]=cuCaddf(cuCmulf(G1[2],C[1]),cuCmulf(G1[3],C[3])); - - cuFloatComplex G2[4]; - /* conjugate this */ - pp[0]=p[sta2*8]; - pp[1]=-p[sta2*8+1]; - pp[2]=p[sta2*8+2]; - pp[3]=-p[sta2*8+3]; - pp[4]=p[sta2*8+4]; - pp[5]=-p[sta2*8+5]; - pp[6]=p[sta2*8+6]; - pp[7]=-p[sta2*8+7]; - G2[0].x=pp[0]; - G2[0].y=pp[1]; - G2[2].x=pp[2]; - G2[2].y=pp[3]; - G2[1].x=pp[4]; - G2[1].y=pp[5]; - G2[3].x=pp[6]; - G2[3].y=pp[7]; - - cuFloatComplex T2[4]; - T2[0]=cuCaddf(cuCmulf(T1[0],G2[0]),cuCmulf(T1[1],G2[2])); - T2[1]=cuCaddf(cuCmulf(T1[0],G2[1]),cuCmulf(T1[1],G2[3])); - T2[2]=cuCaddf(cuCmulf(T1[2],G2[0]),cuCmulf(T1[3],G2[2])); - T2[3]=cuCaddf(cuCmulf(T1[2],G2[1]),cuCmulf(T1[3],G2[3])); - /* update model vector */ - x[8*n]=T2[0].x; - x[8*n+1]=T2[0].y; - x[8*n+2]=T2[1].x; - x[8*n+3]=T2[1].y; - x[8*n+4]=T2[2].x; - x[8*n+5]=T2[2].y; - x[8*n+6]=T2[3].x; - x[8*n+7]=T2[3].y; - - } - } - -} - -__global__ void kernel_jacf_fl(int Nbase, int M, float *jac, float *coh, float *p, short *bb, int N){ - /* global thread index : equal to the baseline */ - unsigned int n = threadIdx.x + blockDim.x*blockIdx.x; - /* which parameter:0...M */ - unsigned int m = threadIdx.y + blockDim.y*blockIdx.y; - - /* this thread works on - x[8*n:8*n+7], coh[8*M*n:8*M*n+8*M-1] - bb[2*n:2*n+1] (sta1,sta2) - organization of p (N stations and M clusters) - sta 0 sta 1 sta 2 .... sta N-1 - clus 0 0...7 8...15 16...23 ... 8N-8 8N-1 - clus 1 8N..8N+7 8N+8..8N+15 8N+16..8N+23 .... 8N+8N-8...8N+8N-1 - ...... - clus M-1 (M-1)N..(M-1)N+7 (M-1)N+8..(M-1)N+15.... ...(M-1)N+8N-8 (M-1)N+8N-1 - - organization of coherencies (coh) - [0, 8*M-1] : baseline 0 - [8*M, 8*M+8*M-1]: baseline 1 - [n*8*M, n*8*M+8*M-1]: baseline n - ...... - [n*8*M+cm*8, n*8*M+cm*8+7] cluster cm, baseline n - - residual error stored at sum[n] - */ - - if(n>3; /* 0...Ns-1 (because M=total par= 8 * Nstations */ - - if (((stc==sta2)||(stc==sta1)) && sta1>=0 && sta2>=0 ) { - - cuFloatComplex C[4]; - C[0].x=coh[8*n]; - C[0].y=coh[8*n+1]; - C[1].x=coh[8*n+2]; - C[1].y=coh[8*n+3]; - C[2].x=coh[8*n+4]; - C[2].y=coh[8*n+5]; - C[3].x=coh[8*n+6]; - C[3].y=coh[8*n+7]; - - /* which parameter exactly 0..7 */ - //int stoff=m%8; - int stoff=m-stc*8; - float pp1[8]; - float pp2[8]; - if (stc==sta1) { - for (int cn=0; cn<8; cn++) { - pp1[cn]=0.0f; - pp2[cn]=p[sta2*8+cn]; - } - pp1[stoff]=1.0f; - } else if (stc==sta2) { - for (int cn=0; cn<8; cn++) { - pp2[cn]=0.0f; - pp1[cn]=p[sta1*8+cn]; - } - pp2[stoff]=1.0f; - } - - - cuFloatComplex G1[4]; - G1[0].x=pp1[0]; - G1[0].y=pp1[1]; - G1[1].x=pp1[2]; - G1[1].y=pp1[3]; - G1[2].x=pp1[4]; - G1[2].y=pp1[5]; - G1[3].x=pp1[6]; - G1[3].y=pp1[7]; - - cuFloatComplex T1[4]; - /* T=G1*C */ - T1[0]=cuCaddf(cuCmulf(G1[0],C[0]),cuCmulf(G1[1],C[2])); - T1[1]=cuCaddf(cuCmulf(G1[0],C[1]),cuCmulf(G1[1],C[3])); - T1[2]=cuCaddf(cuCmulf(G1[2],C[0]),cuCmulf(G1[3],C[2])); - T1[3]=cuCaddf(cuCmulf(G1[2],C[1]),cuCmulf(G1[3],C[3])); - - cuFloatComplex G2[4]; - /* conjugate this */ - G2[0].x=pp2[0]; - G2[0].y=-pp2[1]; - G2[2].x=pp2[2]; - G2[2].y=-pp2[3]; - G2[1].x=pp2[4]; - G2[1].y=-pp2[5]; - G2[3].x=pp2[6]; - G2[3].y=-pp2[7]; - - cuFloatComplex T2[4]; - T2[0]=cuCaddf(cuCmulf(T1[0],G2[0]),cuCmulf(T1[1],G2[2])); - T2[1]=cuCaddf(cuCmulf(T1[0],G2[1]),cuCmulf(T1[1],G2[3])); - T2[2]=cuCaddf(cuCmulf(T1[2],G2[0]),cuCmulf(T1[3],G2[2])); - T2[3]=cuCaddf(cuCmulf(T1[2],G2[1]),cuCmulf(T1[3],G2[3])); - /* update jacobian */ - /* NOTE: row major order */ - jac[m+M*8*n]=T2[0].x; - jac[m+M*(8*n+1)]=T2[0].y; - jac[m+M*(8*n+2)]=T2[1].x; - jac[m+M*(8*n+3)]=T2[1].y; - jac[m+M*(8*n+4)]=T2[2].x; - jac[m+M*(8*n+5)]=T2[2].y; - jac[m+M*(8*n+6)]=T2[3].x; - jac[m+M*(8*n+7)]=T2[3].y; - - } - } - -} - - -/* only use extern if calling code is C */ -extern "C" -{ - - -/* divide by singular values Dpd[]/Sd[] for Sd[]> eps */ -void -cudakernel_diagdiv_fl(int ThreadsPerBlock, int BlocksPerGrid, int M, float eps, float *Dpd, float *Sd) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - kernel_diagdiv_fl<<< BlocksPerGrid, ThreadsPerBlock >>>(M, eps, Dpd, Sd); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - -} - -/* cuda driver for calculating - A<= A+mu I, adding mu to diagonal entries of A - A: size MxM - ThreadsPerBlock, BlocksPerGrid calculated to meet M -*/ -void -cudakernel_diagmu_fl(int ThreadsPerBlock, int BlocksPerGrid, int M, float *A, float mu) { -#ifdef CUDA_DBG - cudaError_t error; -#endif - kernel_diagmu_fl<<< BlocksPerGrid, ThreadsPerBlock >>>(M, A, mu); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif -} - - -/* cuda driver for calculating f() */ -/* p: params (Mx1), x: data (Nx1), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -void -cudakernel_func_fl(int ThreadsPerBlock, int BlocksPerGrid, float *p, float *x, int M, int N, float *coh, short *bbh, int Nbase, int Mclus, int Nstations) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - cudaMemset(x, 0, N*sizeof(float)); -// printf("Kernel data size=%d, block=%d, thread=%d, baselines=%d\n",N,BlocksPerGrid, ThreadsPerBlock,Nbase); - kernel_func_fl<<< BlocksPerGrid, ThreadsPerBlock >>>(Nbase, x, coh, p, bbh, Nstations); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - -} - -/* cuda driver for calculating jacf() */ -/* p: params (Mx1), jac: jacobian (NxM), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -void -cudakernel_jacf_fl(int ThreadsPerBlock_row, int ThreadsPerBlock_col, float *p, float *jac, int M, int N, float *coh, short *bbh, int Nbase, int Mclus, int Nstations) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - /* NOTE: use small value for ThreadsPerBlock here, like 8 */ - dim3 threadsPerBlock(16, 8); - /* jacobian: Nbase x Nstations (proportional to N), so */ - dim3 numBlocks((Nbase+threadsPerBlock.x-1)/threadsPerBlock.x, - (M+threadsPerBlock.y-1)/threadsPerBlock.y); - /* set memory of jac to zero */ - cudaMemset(jac, 0, N*M*sizeof(float)); - // printf("Kernel Jax data size=%d, params=%d, block=%d,%d, thread=%d,%d, baselines=%d\n",N, M, numBlocks.x,numBlocks.y, threadsPerBlock.x, threadsPerBlock.y, Nbase); - kernel_jacf_fl<<< numBlocks, threadsPerBlock>>>(Nbase, M, jac, coh, p, bbh, Nstations); - - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - -} - -} diff --git a/src/lib/myblas.c b/src/lib/myblas.c deleted file mode 100644 index ea719ce..0000000 --- a/src/lib/myblas.c +++ /dev/null @@ -1,462 +0,0 @@ -/* - * - Copyright (C) 2006-2008 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#include "sagecal.h" -#include /* for memcpy */ - -/* machine precision */ -double -dlamch(char CMACH) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern double dlamch_(char *CMACH); - return(dlamch_(&CMACH)); -} - - -/* blas dcopy */ -/* y = x */ -/* read x values spaced by Nx (so x size> N*Nx) */ -/* write to y values spaced by Ny (so y size > N*Ny) */ -void -my_dcopy(int N, double *x, int Nx, double *y, int Ny) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void dcopy_(int *N, double *x, int *incx, double *y, int *incy); - /* use memcpy if Nx=Ny=1 */ - if (Nx==1&&Ny==1) { - memcpy((void*)y,(void*)x,sizeof(double)*(size_t)N); - } else { - dcopy_(&N,x,&Nx,y,&Ny); - } -} -/* blas scale */ -/* x = a. x */ -void -my_dscal(int N, double a, double *x) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void dscal_(int *N, double *alpha, double *x, int *incx); - int i=1; - dscal_(&N,&a,x,&i); -} -void -my_sscal(int N, float a, float *x) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void sscal_(int *N, float *alpha, float *x, int *incx); - int i=1; - sscal_(&N,&a,x,&i); -} - -/* x^T*y */ -double -my_ddot(int N, double *x, double *y) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern double ddot_(int *N, double *x, int *incx, double *y, int *incy); - int i=1; - return(ddot_(&N,x,&i,y,&i)); -} - -/* ||x||_2 */ -double -my_dnrm2(int N, double *x) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern double dnrm2_(int *N, double *x, int *incx); - int i=1; - return(dnrm2_(&N,x,&i)); -} -float -my_fnrm2(int N, float *x) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern float snrm2_(int *N, float *x, int *incx); - int i=1; - return(snrm2_(&N,x,&i)); -} - - - -/* sum||x||_1 */ -double -my_dasum(int N, double *x) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern double dasum_(int *N, double *x, int *incx); - int i=1; - return(dasum_(&N,x,&i)); -} -float -my_fasum(int N, float *x) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern float sasum_(int *N, float *x, int *incx); - int i=1; - return(sasum_(&N,x,&i)); -} - -/* BLAS y = a.x + y */ -void -my_daxpy(int N, double *x, double a, double *y) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void daxpy_(int *N, double *alpha, double *x, int *incx, double *y, int *incy); - int i=1; /* strides */ - daxpy_(&N,&a,x,&i,y,&i); -} - -/* BLAS y = a.x + y */ -void -my_daxpys(int N, double *x, int incx, double a, double *y, int incy) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void daxpy_(int *N, double *alpha, double *x, int *incx, double *y, int *incy); - daxpy_(&N,&a,x,&incx,y,&incy); -} - -void -my_saxpy(int N, float *x, float a, float *y) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void saxpy_(int *N, float *alpha, float *x, int *incx, float *y, int *incy); - int i=1; /* strides */ - saxpy_(&N,&a,x,&i,y,&i); -} - - - -/* max |x| index (start from 1...)*/ -int -my_idamax(int N, double *x, int incx) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern int idamax_(int *N, double *x, int *incx); - return idamax_(&N,x,&incx); -} - -int -my_isamax(int N, float *x, int incx) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern int isamax_(int *N, float *x, int *incx); - return isamax_(&N,x,&incx); -} - -/* min |x| index (start from 1...)*/ -int -my_idamin(int N, double *x, int incx) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern int idamin_(int *N, double *x, int *incx); - return idamin_(&N,x,&incx); -} - -/* BLAS DGEMM C = alpha*op(A)*op(B)+ beta*C */ -void -my_dgemm(char transa, char transb, int M, int N, int K, double alpha, double *A, int lda, double *B, int ldb, double beta, double *C, int ldc) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void dgemm_(char *TRANSA, char *TRANSB, int *M, int *N, int *K, double *ALPHA, double *A, int *LDA, double *B, int * LDB, double *BETA, double *C, int *LDC); - dgemm_(&transa, &transb, &M, &N, &K, &alpha, A, &lda, B, &ldb, &beta, C, &ldc); -} - -/* BLAS DGEMV y = alpha*op(A)*x+ beta*y : op 'T' or 'N' */ -void -my_dgemv(char trans, int M, int N, double alpha, double *A, int lda, double *x, int incx, double beta, double *y, int incy) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void dgemv_(char *TRANS, int *M, int *N, double *ALPHA, double *A, int *LDA, double *X, int *INCX, double *BETA, double *Y, int *INCY); - dgemv_(&trans, &M, &N, &alpha, A, &lda, x, &incx, &beta, y, &incy); -} - - -/* following routines used in LAPACK solvers */ -/* cholesky factorization: real symmetric */ -int -my_dpotrf(char uplo, int N, double *A, int lda) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void dpotrf_(char *uplo, int *N, double *A, int *lda, int *info); - int info; - dpotrf_(&uplo,&N,A,&lda,&info); - return info; -} - -/* solve Ax=b using cholesky factorization */ -int -my_dpotrs(char uplo, int N, int nrhs, double *A, int lda, double *b, int ldb){ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void dpotrs_(char *uplo, int *N, int *nrhs, double *A, int *lda, double *b, int *ldb, int *info); - int info; - dpotrs_(&uplo,&N,&nrhs,A,&lda,b,&ldb,&info); - return info; -} - -/* solve Ax=b using QR factorization */ -int -my_dgels(char TRANS, int M, int N, int NRHS, double *A, int LDA, double *B, int LDB, double *WORK, int LWORK) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void dgels_(char *TRANS, int *M, int *N, int *NRHS, double *A, int *LDA, double *B, int *LDB, double *WORK, int *LWORK, int *INFO); - int info; - dgels_(&TRANS,&M,&N,&NRHS,A,&LDA,B,&LDB,WORK,&LWORK,&info); - return info; -} - - -/* A=U S VT, so V needs NOT to be transposed */ -int -my_dgesvd(char JOBU, char JOBVT, int M, int N, double *A, int LDA, double *S, - double *U, int LDU, double *VT, int LDVT, double *WORK, int LWORK) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void dgesvd_(char *JOBU, char *JOBVT, int *M, int *N, double *A, - int *LDA, double *S, double *U, int *LDU, double *VT, int *LDVT, - double *WORK, int *LWORK, int *info); - int info; - dgesvd_(&JOBU,&JOBVT,&M,&N,A,&LDA,S,U,&LDU,VT,&LDVT,WORK,&LWORK,&info); - return info; -} - -/* QR factorization QR=A, only TAU is used for Q, R stored in A*/ -int -my_dgeqrf(int M, int N, double *A, int LDA, double *TAU, double *WORK, int LWORK) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void dgeqrf_(int *M, int *N, double *A, int *LDA, double *TAU, double *WORK, int *LWORK, int *INFO); - int info; - dgeqrf_(&M,&N,A,&LDA,TAU,WORK,&LWORK,&info); - return info; -} - -/* calculate Q using elementary reflections */ -int -my_dorgqr(int M,int N,int K,double *A,int LDA,double *TAU,double *WORK,int LWORK) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void dorgqr_(int *M, int *N, int *K, double *A, int *LDA, double *TAU, double *WORK, int *LWORK, int *INFO); - int info; - dorgqr_(&M, &N, &K, A, &LDA, TAU, WORK, &LWORK, &info); - - return info; -} - -/* solves a triangular system of equations Ax=b, A triangular */ -int -my_dtrtrs(char UPLO, char TRANS, char DIAG,int N,int NRHS,double *A,int LDA,double *B,int LDB) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void dtrtrs_(char *UPLO,char *TRANS,char *DIAG,int *N,int *NRHS,double *A,int *LDA,double *B,int *LDB,int *INFO); - int info; - dtrtrs_(&UPLO,&TRANS,&DIAG,&N,&NRHS,A,&LDA,B,&LDB,&info); - - return info; -} - - -/* blas ccopy */ -/* y = x */ -/* read x values spaced by Nx (so x size> N*Nx) */ -/* write to y values spaced by Ny (so y size > N*Ny) */ -void -my_ccopy(int N, complex double *x, int Nx, complex double *y, int Ny) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void zcopy_(int *N, complex double *x, int *incx, complex double *y, int *incy); - /* use memcpy if Nx=Ny=1 */ - if (Nx==1&&Ny==1) { - memcpy((void*)y,(void*)x,sizeof(complex double)*(size_t)N); - } else { - zcopy_(&N,x,&Nx,y,&Ny); - } -} - -/* blas scale */ -/* x = a. x */ -void -my_cscal(int N, complex double a, complex double *x) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void zscal_(int *N, complex double *alpha, complex double *x, int *incx); - int i=1; - zscal_(&N,&a,x,&i); -} - -/* BLAS y = a.x + y */ -void -my_caxpy(int N, complex double *x, complex double a, complex double *y) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void zaxpy_(int *N, complex double *alpha, complex double *x, int *incx, complex double *y, int *incy); - int i=1; /* strides */ - zaxpy_(&N,&a,x,&i,y,&i); -} - - -/* BLAS x^H*y */ -complex double -my_cdot(int N, complex double *x, complex double *y) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern complex double zdotc_(int *N, complex double *x, int *incx, complex double *y, int *incy); - int i=1; - return(zdotc_(&N,x,&i,y,&i)); -} - -/* A=U S VT, so V needs NOT to be transposed */ -int -my_zgesvd(char JOBU, char JOBVT, int M, int N, complex double *A, int LDA, double *S, - complex double *U, int LDU, complex double *VT, int LDVT, complex double *WORK, int LWORK, double *RWORK) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void zgesvd_(char *JOBU, char *JOBVT, int *M, int *N, complex double *A, - int *LDA, double *S, complex double *U, int *LDU, complex double *VT, int *LDVT, - complex double *WORK, int *LWORK, double *RWORK, int *info); - int info; - zgesvd_(&JOBU,&JOBVT,&M,&N,A,&LDA,S,U,&LDU,VT,&LDVT,WORK,&LWORK,RWORK,&info); - return info; -} - -/* solve Ax=b using QR factorization */ -int -my_zgels(char TRANS, int M, int N, int NRHS, complex double *A, int LDA, complex double *B, int LDB, complex double *WORK, int LWORK) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void zgels_(char *TRANS, int *M, int *N, int *NRHS, complex double *A, int *LDA, complex double *B, int *LDB, complex double *WORK, int *LWORK, int *INFO); - int info; - zgels_(&TRANS,&M,&N,&NRHS,A,&LDA,B,&LDB,WORK,&LWORK,&info); - return info; -} - - -/* solve Ax=b using QR factorization */ -int -my_cgels(char TRANS, int M, int N, int NRHS, complex float *A, int LDA, complex float *B, int LDB, complex float *WORK, int LWORK) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void cgels_(char *TRANS, int *M, int *N, int *NRHS, complex float *A, int *LDA, complex float *B, int *LDB, complex float *WORK, int *LWORK, int *INFO); - int info; - cgels_(&TRANS,&M,&N,&NRHS,A,&LDA,B,&LDB,WORK,&LWORK,&info); - return info; -} - - - - -/* BLAS ZGEMM C = alpha*op(A)*op(B)+ beta*C */ -void -my_zgemm(char transa, char transb, int M, int N, int K, complex double alpha, complex double *A, int lda, complex double *B, int ldb, complex double beta, complex double *C, int ldc) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void zgemm_(char *TRANSA, char *TRANSB, int *M, int *N, int *K, complex double *ALPHA, complex double *A, int *LDA, complex double *B, int * LDB, complex double *BETA, complex double *C, int *LDC); - zgemm_(&transa, &transb, &M, &N, &K, &alpha, A, &lda, B, &ldb, &beta, C, &ldc); -} - -/* ||x||_2 */ -double -my_cnrm2(int N, complex double *x) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern double dznrm2_(int *N, complex double *x, int *incx); - int i=1; - return(dznrm2_(&N,x,&i)); -} - -/* blas fcopy */ -/* y = x */ -/* read x values spaced by Nx (so x size> N*Nx) */ -/* write to y values spaced by Ny (so y size > N*Ny) */ -void -my_fcopy(int N, float *x, int Nx, float *y, int Ny) { -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif - extern void scopy_(int *N, float *x, int *incx, float *y, int *incy); - /* use memcpy if Nx=Ny=1 */ - if (Nx==1&&Ny==1) { - memcpy((void*)y,(void*)x,sizeof(float)*(size_t)N); - } else { - scopy_(&N,x,&Nx,y,&Ny); - } -} - - -/* LAPACK eigen value expert routine, real symmetric matrix */ -int -my_dsyevx(char jobz, char range, char uplo, int N, double *A, int lda, - double vl, double vu, int il, int iu, double abstol, int M, double *W, - double *Z, int ldz, double *WORK, int lwork, int *iwork, int *ifail) { - - extern void dsyevx_(char *JOBZ, char *RANGE, char *UPLO, int *N, double *A, int *LDA, - double *VL, double *VU, int *IL, int *IU, double *ABSTOL, int *M, double *W, double *Z, - int *LDZ, double *WORK, int *LWORK, int *IWORK, int *IFAIL, int *INFO); - int info; - dsyevx_(&jobz,&range,&uplo,&N,A,&lda,&vl,&vu,&il,&iu,&abstol,&M,W,Z,&ldz,WORK,&lwork,iwork,ifail,&info); - return info; -} - - - -/* BLAS vector outer product - A= alpha x x^H + A -*/ -void -my_zher(char uplo, int N, double alpha, complex double *x, int incx, complex double *A, int lda) { - - extern void zher_(char *UPLO, int *N, double *ALPHA, complex double *X, int *INCX, complex double *A, int *LDA); - - zher_(&uplo,&N,&alpha,x,&incx,A,&lda); -} diff --git a/src/lib/oslmfit.c b/src/lib/oslmfit.c deleted file mode 100644 index 072289d..0000000 --- a/src/lib/oslmfit.c +++ /dev/null @@ -1,705 +0,0 @@ -/* - * - Copyright (C) 2006-2008 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#include -#include -#include -#include -#include - -#include "sagecal.h" -#include - -//#define DEBUG -/* helper functions for diagnostics */ -static void -checkCudaError(cudaError_t err, char *file, int line) -{ -#ifdef CUDA_DEBUG - if(!err) - return; - fprintf(stderr,"GPU (CUDA): %s %s %d\n", cudaGetErrorString(err),file,line); - exit(EXIT_FAILURE); -#endif -} - - -static void -checkCublasError(cublasStatus_t cbstatus, char *file, int line) -{ -#ifdef CUDA_DEBUG - if (cbstatus!=CUBLAS_STATUS_SUCCESS) { - fprintf(stderr,"%s: %d: CUBLAS failure\n",file,line); - exit(EXIT_FAILURE); - } -#endif -} - - - - -/* OS-LM, but f() and jac() calculations are done - entirely in the GPU */ -int -oslevmar_der_single_cuda( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - * info[5]= # iterations, - * info[6]=reason for terminating: 1 - stopped by small gradient J^T e - * 2 - stopped by small Dp - * 3 - stopped by itmax - * 4 - singular matrix. Restart from current p with increased mu - * 5 - no further error reduction is possible. Restart with increased mu - * 6 - stopped by small ||e||_2 - * 7 - stopped by invalid (i.e. NaN or Inf) "func" values. This is a user error - * info[7]= # function evaluations - * info[8]= # Jacobian evaluations - * info[9]= # linear systems solved, i.e. # attempts for reducing error - */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - double *gWORK, /* GPU allocated memory */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - int randomize, /* if >0 randomize */ - void *adata) /* pointer to possibly additional data, passed uninterpreted to func & jacf. - * Set to NULL if not needed - */ -{ - - /* general note: all device variables end with a 'd' */ - int stop=0; - cudaError_t err; - cublasStatus_t cbstatus; - - int nu=2,nu2; - double p_L2, Dp_L2=DBL_MAX, dF, dL, p_eL2, jacTe_inf=0.0, pDp_eL2, init_p_eL2; - double tmp,mu=0.0; - double tau, eps1, eps2, eps2_sq, eps3; - int k,ci,issolved; - - double *hxd; - - double *ed; - double *xd; - - double *jacd; - - double *jacTjacd,*jacTjacd0; - - double *Dpd,*bd; - double *pd,*pnewd; - double *jacTed; - - /* used in QR solver */ - double *taud; - - /* used in SVD solver */ - double *Ud; - double *VTd; - double *Sd; - - /* ME data */ - me_data_t *dp=(me_data_t*)adata; - int Nbase=(dp->Nbase)*(ntiles); /* note: we do not use the total tile size */ - /* coherency on device */ - double *cohd; - /* baseline-station map on device/host */ - short *bbd; - - int solve_axb=linsolv; - - /* setup default settings */ - if(opts){ - tau=opts[0]; - eps1=opts[1]; - eps2=opts[2]; - eps2_sq=opts[2]*opts[2]; - eps3=opts[3]; - } else { - tau=CLM_INIT_MU; - eps1=CLM_STOP_THRESH; - eps2=CLM_STOP_THRESH; - eps2_sq=CLM_STOP_THRESH*CLM_STOP_THRESH; - eps3=CLM_STOP_THRESH; - } - - /* calculate no of cuda threads and blocks */ - int ThreadsPerBlock=DEFAULT_TH_PER_BK; - int BlocksPerGrid=(M+ThreadsPerBlock-1)/ThreadsPerBlock; - - - unsigned long int moff; - if (!gWORK) { - err=cudaMalloc((void**)&xd, N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&jacd, M*N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&jacTjacd, M*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&jacTed, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&jacTjacd0, M*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&Dpd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&bd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&pd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&pnewd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - /* needed for calculating f() and jac() */ - err=cudaMalloc((void**) &bbd, Nbase*2*sizeof(short)); - checkCudaError(err,__FILE__,__LINE__); - /* we need coherencies for only this cluster */ - err=cudaMalloc((void**) &cohd, Nbase*8*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&hxd, N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&ed, N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - /* memory allocation: different solvers */ - if (solve_axb==1) { - err=cudaMalloc((void**)&taud, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } else if (solve_axb==2) { - err=cudaMalloc((void**)&Ud, M*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&VTd, M*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&Sd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } - } else { - moff=0; - xd=&gWORK[moff]; - moff+=N; - jacd=&gWORK[moff]; - moff+=M*N; - jacTjacd=&gWORK[moff]; - moff+=M*M; - jacTed=&gWORK[moff]; - moff+=M; - jacTjacd0=&gWORK[moff]; - moff+=M*M; - Dpd=&gWORK[moff]; - moff+=M; - bd=&gWORK[moff]; - moff+=M; - pd=&gWORK[moff]; - moff+=M; - pnewd=&gWORK[moff]; - moff+=M; - cohd=&gWORK[moff]; - moff+=Nbase*8; - hxd=&gWORK[moff]; - moff+=N; - ed=&gWORK[moff]; - moff+=N; - if (solve_axb==1) { - taud=&gWORK[moff]; - moff+=M; - } else if (solve_axb==2) { - Ud=&gWORK[moff]; - moff+=M*M; - VTd=&gWORK[moff]; - moff+=M*M; - Sd=&gWORK[moff]; - moff+=M; - } - bbd=(short*)&gWORK[moff]; - moff+=(Nbase*2*sizeof(short))/sizeof(double); - } - - /* extra storage for cusolver */ - int work_size=0; - int *devInfo; - int devInfo_h=0; - err=cudaMalloc((void**)&devInfo, sizeof(int)); - checkCudaError(err,__FILE__,__LINE__); - double *work; - double *rwork; - if (solve_axb==0) { - cusolverDnDpotrf_bufferSize(solver_handle, CUBLAS_FILL_MODE_UPPER, M, jacTjacd, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } else if (solve_axb==1) { - cusolverDnDgeqrf_bufferSize(solver_handle, M, M, jacTjacd, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } else { - cusolverDnDgesvd_bufferSize(solver_handle, M, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&rwork, 5*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } - - - err=cudaMemcpyAsync(pd, p, M*sizeof(double), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - /* need to give right offset for coherencies */ - /* offset: cluster offset+time offset */ - err=cudaMemcpyAsync(cohd, &(dp->ddcoh[(dp->Nbase)*(dp->tilesz)*(dp->clus)*8+(dp->Nbase)*tileoff*8]), Nbase*8*sizeof(double), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - /* correct offset for baselines */ - err=cudaMemcpyAsync(bbd, &(dp->ddbase[2*(dp->Nbase)*(tileoff)]), Nbase*2*sizeof(short), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - cudaDeviceSynchronize(); - /* xd <=x */ - err=cudaMemcpyAsync(xd, x, N*sizeof(double), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - - /* ### compute e=x - f(p) and its L2 norm */ - /* ### e=x-hx, p_eL2=||e|| */ - /* p: params (Mx1), x: data (Nx1), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations*/ - cudakernel_func(ThreadsPerBlock, (Nbase+ThreadsPerBlock-1)/ThreadsPerBlock, pd,hxd,M,N, cohd, bbd, Nbase, dp->M, dp->N); - - /* e=x */ - cbstatus=cublasDcopy(cbhandle, N, xd, 1, ed, 1); - /* e=x-hx */ - double alpha=-1.0; - cbstatus=cublasDaxpy(cbhandle, N, &alpha, hxd, 1, ed, 1); - - /* norm ||e|| */ - cbstatus=cublasDnrm2(cbhandle, N, ed, 1, &p_eL2); - /* square */ - p_eL2=p_eL2*p_eL2; - - init_p_eL2=p_eL2; - if(!finite(p_eL2)) stop=7; - - /* setup OS subsets and stating offsets */ - /* ed : N, cohd : Nbase*8, bbd : Nbase*2 full size */ - /* if ntiles= no. of OS iterations, so select - a random set of subsets */ - /* N, Nbase changes with subset, cohd,bbd,ed gets offsets */ - /* ed : N, cohd : Nbase*8, bbd : Nbase*2 full size */ - /* p: params (Mx1), jacd: jacobian (NxM), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations*/ - /* FIXME thread/block sizes 16x16=256, so 16 is chosen */ - //cudakernel_jacf(ThreadsPerBlock, ThreadsPerBlock/4, pd, jacd, M, N, cohd, bbd, Nbase, dp->M, dp->N); - cudakernel_jacf(ThreadsPerBlock, ThreadsPerBlock/4, pd, jacd, M, Nos[l], &cohd[8*NbI[l]], &bbd[2*NbI[l]], Nbaseos[l], dp->M, dp->N); - - /* Compute J^T J and J^T e */ - /* Cache efficient computation of J^T J based on blocking - */ - /* since J is in ROW major order, assume it is transposed, - so actually calculate A=J*J^T, where J is size MxN */ - //status=culaDeviceDgemm('N','T',M,M,Nos[l],1.0,jacd,M,jacd,M,0.0,jacTjacd,M); - //checkStatus(status,__FILE__,__LINE__); - double cone=1.0; double czero=0.0; - cbstatus=cublasDgemm(cbhandle,CUBLAS_OP_N,CUBLAS_OP_T,M,M,Nos[l],&cone,jacd,M,jacd,M,&czero,jacTjacd,M); - - /* create backup */ - /* copy jacTjacd0<=jacTjacd */ - cbstatus=cublasDcopy(cbhandle, M*M, jacTjacd, 1, jacTjacd0, 1); - /* J^T e */ - /* calculate b=J^T*e (actually compute b=J*e, where J in row major (size MxN) */ - //status=culaDeviceDgemv('N',M,Nos[l],1.0,jacd,M,&ed[edI[l]],1,0.0,jacTed,1); - //checkStatus(status,__FILE__,__LINE__); - cbstatus=cublasDgemv(cbhandle,CUBLAS_OP_N,M,Nos[l],&cone,jacd,M,&ed[edI[l]],1,&czero,jacTed,1); - - - - /* Compute ||J^T e||_inf and ||p||^2 */ - /* find infinity norm of J^T e, 1 based indexing*/ - cbstatus=cublasIdamax(cbhandle, M, jacTed, 1, &ci); - err=cudaMemcpy(&jacTe_inf,&(jacTed[ci-1]),sizeof(double),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - /* L2 norm of current parameter values */ - /* norm ||Dp|| */ - cbstatus=cublasDnrm2(cbhandle, M, pd, 1, &p_L2); - p_L2=p_L2*p_L2; - if(jacTe_inf<0.0) {jacTe_inf=-jacTe_inf;} -#ifdef DEBUG - printf("Inf norm=%lf\n",jacTe_inf); -#endif - - /* check for convergence */ - if((jacTe_inf <= eps1)){ - Dp_L2=0.0; /* no increment for p in this case */ - stop=1; - break; - } - - /* compute initial (k=0) damping factor */ - if (k==0) { - /* find max diagonal element (stride is M+1) */ - /* should be MAX not MAX(ABS) */ - cbstatus=cublasIdamax(cbhandle, M, jacTjacd, M+1, &ci); /* 1 based index */ - ci=(ci-1)*(M+1); /* right value of the diagonal */ - - err=cudaMemcpy(&tmp,&(jacTjacd[ci]),sizeof(double),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - mu=tau*tmp; - } - - - /* determine increment using adaptive damping */ - while(1){ - /* augment normal equations */ - /* increment A => A+ mu*I, increment diagonal entries */ - /* copy jacTjacd<=jacTjacd0 */ - cbstatus=cublasDcopy(cbhandle, M*M, jacTjacd0, 1, jacTjacd, 1); - cudakernel_diagmu(ThreadsPerBlock, BlocksPerGrid, M, jacTjacd, mu); - -#ifdef DEBUG - printf("mu=%lf\n",mu); -#endif -/*************************************************************************/ - issolved=0; - /* solve augmented equations A x = b */ - /* A==jacTjacd, b==Dpd, after solving, x==Dpd */ - /* b=jacTed : intially right hand side, at exit the solution */ - if (solve_axb==0) { - /* Cholesky solver **********************/ - /* lower triangle of Ad is destroyed */ - //status=culaDeviceDpotrf('U',M,jacTjacd,M); - cusolverDnDpotrf(solver_handle, CUBLAS_FILL_MODE_UPPER, M, jacTjacd, M, work, work_size, devInfo); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (!devInfo_h) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - fprintf(stderr,"Singular matrix\n"); -#endif - } - if (issolved) { - /* copy Dpd<=jacTed */ - cbstatus=cublasDcopy(cbhandle, M, jacTed, 1, Dpd, 1); -#ifdef DEBUG - checkCublasError(cbstatus,__FILE__,__LINE__); -#endif - //status=culaDeviceDpotrs('U',M,1,jacTjacd,M,Dpd,M); - cusolverDnDpotrs(solver_handle, CUBLAS_FILL_MODE_UPPER,M,1,jacTjacd,M,Dpd,M,devInfo); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (devInfo_h) { - issolved=0; -#ifdef DEBUG - fprintf(stderr,"Singular matrix\n"); -#endif - } - } - } else if (solve_axb==1) { - /* QR solver ********************************/ - //status=culaDeviceDgeqrf(M,M,jacTjacd,M,taud); - cusolverDnDgeqrf(solver_handle, M, M, jacTjacd, M, taud, work, work_size, devInfo); - cudaDeviceSynchronize(); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (!devInfo_h) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - fprintf(stderr,"Singular matrix\n"); -#endif - } - - if (issolved) { - /* copy Dpd<=jacTed */ - cbstatus=cublasDcopy(cbhandle, M, jacTed, 1, Dpd, 1); - //status=culaDeviceDgeqrs(M,M,1,jacTjacd,M,taud,Dpd,M); - cusolverDnDormqr(solver_handle, CUBLAS_SIDE_LEFT, CUBLAS_OP_T, M, 1, M, jacTjacd, M, taud, Dpd, M, work, work_size, devInfo); - cudaDeviceSynchronize(); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (devInfo_h) { - issolved=0; -#ifdef DEBUG - fprintf(stderr,"Singular matrix\n"); -#endif - } else { - cone=1.0; - cbstatus=cublasDtrsm(cbhandle,CUBLAS_SIDE_LEFT,CUBLAS_FILL_MODE_UPPER,CUBLAS_OP_N,CUBLAS_DIAG_NON_UNIT,M,1,&cone,jacTjacd,M,Dpd,M); - } - } - } else { - /* SVD solver *********************************/ - /* U S VT = A */ - //status=culaDeviceDgesvd('A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M); - //checkStatus(status,__FILE__,__LINE__); - cusolverDnDgesvd(solver_handle,'A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M,work,work_size,rwork,devInfo); - cudaDeviceSynchronize(); - /* copy Dpd<=jacTed */ - cbstatus=cublasDcopy(cbhandle, M, jacTed, 1, Dpd, 1); - /* b<=U^T * b */ - //status=culaDeviceDgemv('T',M,M,1.0,Ud,M,Dpd,1,0.0,Dpd,1); - //checkStatus(status,__FILE__,__LINE__); - cone=1.0; czero=0.0; - cbstatus=cublasDgemv(cbhandle,CUBLAS_OP_T,M,M,&cone,Ud,M,Dpd,1,&czero,Dpd,1); - - /* divide by singular values Dpd[]/Sd[] for Sd[]> eps1 */ - cudakernel_diagdiv(ThreadsPerBlock, BlocksPerGrid, M, eps1, Dpd, Sd); - - /* b<=VT^T * b */ - //status=culaDeviceDgemv('T',M,M,1.0,VTd,M,Dpd,1,0.0,Dpd,1); - //checkStatus(status,__FILE__,__LINE__); - cbstatus=cublasDgemv(cbhandle,CUBLAS_OP_T,M,M,&cone,VTd,M,Dpd,1,&czero,Dpd,1); - - - issolved=1; - } -/*************************************************************************/ - - /* compute p's new estimate and ||Dp||^2 */ - if (issolved) { - /* compute p's new estimate and ||Dp||^2 */ - /* pnew=p+Dp */ - /* pnew=p */ - cbstatus=cublasDcopy(cbhandle, M, pd, 1, pnewd, 1); - /* pnew=pnew+Dp */ - alpha=1.0; - cbstatus=cublasDaxpy(cbhandle, M, &alpha, Dpd, 1, pnewd, 1); - - /* norm ||Dp|| */ - cbstatus=cublasDnrm2(cbhandle, M, Dpd, 1, &Dp_L2); - Dp_L2=Dp_L2*Dp_L2; - -#ifdef DEBUG -printf("norm ||dp|| =%lf, norm ||p||=%lf\n",Dp_L2,p_L2); -#endif - if(Dp_L2<=eps2_sq*p_L2){ /* relative change in p is small, stop */ - stop=2; - break; - } - - if(Dp_L2>=(p_L2+eps2)/(CLM_EPSILON*CLM_EPSILON)){ /* almost singular */ - stop=4; - break; - } - - /* new function value */ - /* compute ||e(pDp)||_2 */ - /* ### hx=x-hx, pDp_eL2=||hx|| */ - /* copy to device */ - /* hxd<=hx */ - cudakernel_func(ThreadsPerBlock, (Nbase+ThreadsPerBlock-1)/ThreadsPerBlock, pnewd, hxd, M, N, cohd, bbd, Nbase, dp->M, dp->N); - - /* e=x */ - cbstatus=cublasDcopy(cbhandle, N, xd, 1, ed, 1); - /* e=x-hx */ - alpha=-1.0; - cbstatus=cublasDaxpy(cbhandle, N, &alpha, hxd, 1, ed, 1); - /* note: e is updated */ - - /* norm ||e|| */ - cbstatus=cublasDnrm2(cbhandle, N, ed, 1, &pDp_eL2); - pDp_eL2=pDp_eL2*pDp_eL2; - - - if(!finite(pDp_eL2)){ /* sum of squares is not finite, most probably due to a user error. - */ - stop=7; - break; - } - - /* dL=Dp'*(mu*Dp+jacTe) */ - /* bd=jacTe+mu*Dp */ - cbstatus=cublasDcopy(cbhandle, M, jacTed, 1, bd, 1); - cbstatus=cublasDaxpy(cbhandle, M, &mu, Dpd, 1, bd, 1); - cbstatus=cublasDdot(cbhandle, M, Dpd, 1, bd, 1, &dL); - - dF=p_eL2-pDp_eL2; - -#ifdef DEBUG - printf("dF=%lf, dL=%lf\n",dF,dL); -#endif - if(dL>0.0 && dF>0.0){ /* reduction in error, increment is accepted */ - tmp=(2.0*dF/dL-1.0); - tmp=1.0-tmp*tmp*tmp; - mu=mu*((tmp>=CLM_ONE_THIRD)? tmp : CLM_ONE_THIRD); - nu=2; - - /* update p's estimate */ - cbstatus=cublasDcopy(cbhandle, M, pnewd, 1, pd, 1); - - /* update ||e||_2 */ - p_eL2=pDp_eL2; - break; - } - - } - /* if this point is reached, either the linear system could not be solved or - * the error did not reduce; in any case, the increment must be rejected - */ - - mu*=(double)nu; - nu2=nu<<1; // 2*nu; - if(nu2<=nu){ /* nu has wrapped around (overflown). */ - stop=5; - break; - } - - nu=nu2; - - } /* inner loop */ - - } - if (randomize) { - free(subI); - } -/**************** end OS loop ***************************/ - - } - /**** end iteration loop ***********/ - free(Nos); - free(Nbaseos); - free(edI); - free(NbI); - - if(k>=itmax) stop=3; - - /* copy back current solution */ - err=cudaMemcpyAsync(p,pd,M*sizeof(double),cudaMemcpyDeviceToHost,0); - checkCudaError(err,__FILE__,__LINE__); - checkCublasError(cbstatus,__FILE__,__LINE__); - - /* synchronize async operations */ - cudaDeviceSynchronize(); - - if (!gWORK) { - cudaFree(xd); - cudaFree(jacd); - cudaFree(jacTjacd); - cudaFree(jacTjacd0); - cudaFree(jacTed); - cudaFree(Dpd); - cudaFree(bd); - cudaFree(pd); - cudaFree(pnewd); - cudaFree(hxd); - cudaFree(ed); - if (solve_axb==1) { - cudaFree(taud); - } else if (solve_axb==2) { - cudaFree(Ud); - cudaFree(VTd); - cudaFree(Sd); - } - cudaFree(cohd); - cudaFree(bbd); - } - - cudaFree(devInfo); - cudaFree(work); - if (solve_axb==2) { - cudaFree(rwork); - } - -#ifdef DEBUG - printf("stop=%d\n",stop); -#endif - if(info){ - info[0]=init_p_eL2; - info[1]=p_eL2; - info[2]=jacTe_inf; - info[3]=Dp_L2; - info[4]=mu; - info[5]=(double)k; - info[6]=(double)stop; - info[7]=(double)0; - info[8]=(double)0; - info[9]=(double)0; - } - return 0; -} diff --git a/src/lib/predict.c b/src/lib/predict.c deleted file mode 100644 index e73dbb1..0000000 --- a/src/lib/predict.c +++ /dev/null @@ -1,1693 +0,0 @@ -/* - * - Copyright (C) 2006-2008 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#define _GNU_SOURCE -#include -#include -#include -#include -#include "sagecal.h" - -/******************** shapalet stuff **********************/ -/* evaluate Hermite polynomial value using recursion - */ -static double -H_e(double x, int n) { - if(n==0) return 1.0; - if(n==1) return 2*x; - return 2*x*H_e(x,n-1)-2*(n-1)*H_e(x,n-2); -} - -/** calculate the UV mode vectors (scalar version, only 1 point) - * in: u,v: arrays of the grid points in UV domain - * beta: scale factor - * n0: number of modes in each dimension - * out: - * Av: array of mode vectors size 1 times n0.n0, in column major order - * cplx: array of integers, size n0*n0, if 1 this mode is imaginary, else real - * - */ -static int -calculate_uv_mode_vectors_scalar(double u, double v, double beta, int n0, double **Av, int **cplx) { - - int xci,zci,Ntot; - - double **shpvl, *fact; - int n1,n2,start; - double xval; - int signval; - - Ntot=2; /* u,v seperately */ - /* set up factorial array */ - if ((fact=(double*)calloc((size_t)(n0),sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - fact[0]=1.0; - for (xci=1; xci<(n0); xci++) { - fact[xci]=(xci+1)*fact[xci-1]; - } - - /* setup array to store calculated shapelet value */ - /* need max storage Ntot x n0 */ - if ((shpvl=(double**)calloc((size_t)(Ntot),sizeof(double*)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - for (xci=0; xcicxi)-v*(dp->cphi)*(dp->sxi)+w*(dp->sphi)*(dp->sxi); - // vp=u*(dp->sxi)+v*(dp->cphi)*(dp->cxi)-w*(dp->sphi)*(dp->cxi); - if (dp->use_projection) { - up=-u*(dp->cxi)+v*(dp->cphi)*(dp->sxi)-w*(dp->sphi)*(dp->sxi); - vp=-u*(dp->sxi)-v*(dp->cphi)*(dp->cxi)+w*(dp->sphi)*(dp->cxi); - } else { - up=u; - vp=v; - } - - /* linear transformations, if any */ -// a=1.0; -// b=dp->eY/dp->eX; - a=1.0/dp->eX; - b=1.0/dp->eY; - //cosph=cos(dp->eP); - //sinph=sin(dp->eP); - sincos(dp->eP,&sinph,&cosph); - ut=a*(cosph*up-sinph*vp); - vt=b*(sinph*up+cosph*vp); - /* note: we decompose f(-l,m) so the Fourier transform is F(-u,v) - so negate the u grid */ - calculate_uv_mode_vectors_scalar(-ut, vt, dp->beta, dp->n0, &Av, &cplx); - realsum=imagsum=0.0; - M=(dp->n0)*(dp->n0); - for (ci=0; cimodes[ci]*Av[ci]; - } else { - realsum+=dp->modes[ci]*Av[ci]; - } - } - - free(Av); - free(cplx); - //return 2.0*M_PI*(realsum+_Complex_I*imagsum); - return 2.0*M_PI*(realsum+_Complex_I*imagsum)*a*b; -} - - -complex double -gaussian_contrib(void*dd, double u, double v, double w) { - exinfo_gaussian *dp=(exinfo_gaussian*)dd; - double up,vp,a,b,ut,vt,cosph,sinph; - - /* first the rotation due to projection */ - if (dp->use_projection) { - up=u*(dp->cxi)-v*(dp->cphi)*(dp->sxi)+w*(dp->sphi)*(dp->sxi); - vp=u*(dp->sxi)+v*(dp->cphi)*(dp->cxi)-w*(dp->sphi)*(dp->cxi); - } else { - up=u; - vp=v; - } - - /* linear transformations, if any */ - a=dp->eX; - b=dp->eY; - //cosph=cos(dp->eP); - //sinph=sin(dp->eP); - sincos(dp->eP,&sinph,&cosph); - ut=a*(cosph*up-sinph*vp); - vt=b*(sinph*up+cosph*vp); - - /* Fourier TF is normalized with integrated flux, - so to get the peak value right, scale the flux */ - //return 0.5*exp(-(ut*ut+vt*vt))/sqrt(2.0*a*b); - return M_PI_2*exp(-(ut*ut+vt*vt)); -} - -complex double -ring_contrib(void*dd, double u, double v, double w) { - exinfo_ring *dp=(exinfo_ring*)dd; - double up,vp,a,b; - - /* first the rotation due to projection */ - up=u*(dp->cxi)-v*(dp->cphi)*(dp->sxi)+w*(dp->sphi)*(dp->sxi); - vp=u*(dp->sxi)+v*(dp->cphi)*(dp->cxi)-w*(dp->sphi)*(dp->cxi); - - a=dp->eX; /* diameter */ - b=sqrt(up*up+vp*vp)*a*2.0*M_PI; - - return j0(b); -} - -complex double -disk_contrib(void*dd, double u, double v, double w) { - exinfo_disk *dp=(exinfo_disk*)dd; - double up,vp,a,b; - - /* first the rotation due to projection */ - up=u*(dp->cxi)-v*(dp->cphi)*(dp->sxi)+w*(dp->sphi)*(dp->sxi); - vp=u*(dp->sxi)+v*(dp->cphi)*(dp->cxi)-w*(dp->sphi)*(dp->cxi); - - a=dp->eX; /* diameter */ - b=sqrt(up*up+vp*vp)*a*2.0*M_PI; - - return j1(b); -} - -/* time smearing TMS eq. 6.80 for EW-array formula - note u,v,w: meter/c so multiply by freq. to get wavelength */ -double -time_smear(double ll,double mm,double dec0,double tdelta,double u,double v,double w,double freq0) { - /* baseline length in lambda */ - double bl=sqrt(u*u+v*v+w*w)*freq0; - /* source dist */ - double ds=sin(dec0)*mm; - double r1=sqrt(ll*ll+ds*ds); - /* earch angular vel x time x baseline length x source dist */ - double prod=7.2921150e-5*tdelta*bl*r1; - if (prod>CLM_EPSILON) { - return 1.0645*erf(0.8326*prod)/prod; - } else { - return 1.0; - } -} - -/* worker thread function for prediction */ -static void * -predict_threadfn(void *data) { - thread_data_base_t *t=(thread_data_base_t*)data; - - int ci,cm,cn; - double *PHr=0,*PHi=0,*G=0,*II=0,*QQ=0,*UU=0,*VV=0; /* arrays to store calculations */ - - complex double C[4]; - double freq0=t->freq0; - double fdelta2=t->fdelta*0.5; - for (ci=0; ciNb; ci++) { - /* iterate over the sky model and calculate contribution */ - /* for this x[8*ci:8*(ci+1)-1] */ - memset(&(t->x[8*ci]),0,sizeof(double)*8); - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - for (cm=0; cm<(t->M); cm++) { /* clusters */ - - memset(C,0,sizeof(complex double)*4); -/*****************************************************************/ - /* setup memory */ - if (posix_memalign((void*)&PHr,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&PHi,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&G,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&II,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&QQ,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&UU,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&VV,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - /* phase (real,imag) parts */ - /* note u=u/c, v=v/c, w=w/c here */ - /* phterm is 2pi(u/c l +v/c m +w/c n) */ - for (cn=0; cncarr[cm].N; cn++) { - G[cn]=2.0*M_PI*(t->u[ci]*t->carr[cm].ll[cn]+t->v[ci]*t->carr[cm].mm[cn]+t->w[ci]*t->carr[cm].nn[cn]); - } - for (cn=0; cncarr[cm].N; cn++) { - sincos(G[cn]*freq0,&PHi[cn],&PHr[cn]); - } - - /* term due to shape of source, also multiplied by freq/time smearing */ - for (cn=0; cncarr[cm].N; cn++) { - /* freq smearing : extra term delta * sinc(delta/2 * phterm) */ - if (G[cn]!=0.0) { - double smfac=G[cn]*fdelta2; - double sinph=sin(smfac)/smfac; - G[cn]=fabs(sinph); - } else { - G[cn]=1.0; - } - } - - /* multiply (re,im) phase term with smearing/shape factor */ - for (cn=0; cncarr[cm].N; cn++) { - PHr[cn]*=G[cn]; - PHi[cn]*=G[cn]; - } - - - for (cn=0; cncarr[cm].N; cn++) { - /* check if source type is not a point source for additional - calculations */ - if (t->carr[cm].stype[cn]!=STYPE_POINT) { - complex double sterm=PHr[cn]+_Complex_I*PHi[cn]; - if (t->carr[cm].stype[cn]==STYPE_SHAPELET) { - sterm*=shapelet_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } else if (t->carr[cm].stype[cn]==STYPE_GAUSSIAN) { - sterm*=gaussian_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } else if (t->carr[cm].stype[cn]==STYPE_DISK) { - sterm*=disk_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } else if (t->carr[cm].stype[cn]==STYPE_RING) { - sterm*=ring_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } - PHr[cn]=creal(sterm); - PHi[cn]=cimag(sterm); - } - - } - - - /* flux of each source, at each freq */ - for (cn=0; cncarr[cm].N; cn++) { - II[cn]=t->carr[cm].sI[cn]; - QQ[cn]=t->carr[cm].sQ[cn]; - UU[cn]=t->carr[cm].sU[cn]; - VV[cn]=t->carr[cm].sV[cn]; - } - - /* add up terms together */ - for (cn=0; cncarr[cm].N; cn++) { - complex double Ph,IIl,QQl,UUl,VVl; - Ph=(PHr[cn]+_Complex_I*PHi[cn]); - IIl=Ph*II[cn]; - QQl=Ph*QQ[cn]; - UUl=Ph*UU[cn]; - VVl=Ph*VV[cn]; - C[0]+=IIl+QQl; - C[1]+=UUl+_Complex_I*VVl; - C[2]+=UUl-_Complex_I*VVl; - C[3]+=IIl-QQl; - } - - free(PHr); - free(PHi); - free(G); - free(II); - free(QQ); - free(UU); - free(VV); - -/*****************************************************************/ - /* add to baseline visibilities */ - t->x[8*ci]+=creal(C[0]); - t->x[8*ci+1]+=cimag(C[0]); - t->x[8*ci+2]+=creal(C[1]); - t->x[8*ci+3]+=cimag(C[1]); - t->x[8*ci+4]+=creal(C[2]); - t->x[8*ci+5]+=cimag(C[2]); - t->x[8*ci+6]+=creal(C[3]); - t->x[8*ci+7]+=cimag(C[3]); - } - } - } - - return NULL; -} - -int -predict_visibilities(double *u, double *v, double *w, double *x, int N, - int Nbase, int tilesz, baseline_t *barr, clus_source_t *carr, int M, double freq0, double fdelta, double tdelta, double dec0, int Nt) { - /* u,v,w : size Nbase*tilesz x 1 x: size Nbase*8*tilesz x 1 */ - /* barr: size Nbase*tilesz x 1 carr: size Mx1 */ - - int nth,nth1,ci; - - int Nthb0,Nthb; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_base_t *threaddata; - - int Nbase1=Nbase*tilesz; - - /* calculate min baselines a thread can handle */ - //Nthb0=ceil((double)Nbase1/(double)Nt); - Nthb0=(Nbase1+Nt-1)/Nt; - - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((threaddata=(thread_data_base_t*)malloc((size_t)Nt*sizeof(thread_data_base_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthM); - double uvdist; - double *PHr=0,*PHi=0,*G=0,*II=0,*QQ=0,*UU=0,*VV=0; /* arrays to store calculations */ - complex double C[4]; - double freq0=t->freq0; - double fdelta2=t->fdelta*0.5; - - for (ci=0; ciNb; ci++) { - /* iterate over the sky model and calculate contribution */ - /* for this x[8*ci:8*(ci+1)-1] */ - memset(&(t->coh[4*M*ci]),0,sizeof(complex double)*4*M); - /* even if this baseline is flagged, we do compute */ - for (cm=0; cmcarr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&PHi,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&G,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&II,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&QQ,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&UU,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&VV,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - /* phase (real,imag) parts */ - /* note u=u/c, v=v/c, w=w/c here */ - /* phterm is 2pi(u/c l +v/c m +w/c n) */ - for (cn=0; cncarr[cm].N; cn++) { - G[cn]=2.0*M_PI*(t->u[ci]*t->carr[cm].ll[cn]+t->v[ci]*t->carr[cm].mm[cn]+t->w[ci]*t->carr[cm].nn[cn]); - } - for (cn=0; cncarr[cm].N; cn++) { - sincos(G[cn]*freq0,&PHi[cn],&PHr[cn]); - } - - /* term due to shape of source, also multiplied by freq/time smearing */ - for (cn=0; cncarr[cm].N; cn++) { - /* freq smearing : extra term delta * sinc(delta/2 * phterm) */ - if (G[cn]!=0.0) { - double smfac=G[cn]*fdelta2; - double sinph=sin(smfac)/smfac; - G[cn]=fabs(sinph); - } else { - G[cn]=1.0; - } - } - - /* multiply (re,im) phase term with smearing/shape factor */ - for (cn=0; cncarr[cm].N; cn++) { - PHr[cn]*=G[cn]; - PHi[cn]*=G[cn]; - } - - - for (cn=0; cncarr[cm].N; cn++) { - /* check if source type is not a point source for additional - calculations */ - if (t->carr[cm].stype[cn]!=STYPE_POINT) { - complex double sterm=PHr[cn]+_Complex_I*PHi[cn]; - if (t->carr[cm].stype[cn]==STYPE_SHAPELET) { - sterm*=shapelet_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } else if (t->carr[cm].stype[cn]==STYPE_GAUSSIAN) { - sterm*=gaussian_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } else if (t->carr[cm].stype[cn]==STYPE_DISK) { - sterm*=disk_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } else if (t->carr[cm].stype[cn]==STYPE_RING) { - sterm*=ring_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } - PHr[cn]=creal(sterm); - PHi[cn]=cimag(sterm); - } - - } - - - /* flux of each source, at each freq */ - for (cn=0; cncarr[cm].N; cn++) { - II[cn]=t->carr[cm].sI[cn]; - QQ[cn]=t->carr[cm].sQ[cn]; - UU[cn]=t->carr[cm].sU[cn]; - VV[cn]=t->carr[cm].sV[cn]; - } - - - /* add up terms together */ - for (cn=0; cncarr[cm].N; cn++) { - complex double Ph,IIl,QQl,UUl,VVl; - Ph=(PHr[cn]+_Complex_I*PHi[cn]); - IIl=Ph*II[cn]; - QQl=Ph*QQ[cn]; - UUl=Ph*UU[cn]; - VVl=Ph*VV[cn]; - C[0]+=IIl+QQl; - C[1]+=UUl+_Complex_I*VVl; - C[2]+=UUl-_Complex_I*VVl; - C[3]+=IIl-QQl; - } - - free(PHr); - free(PHi); - free(G); - free(II); - free(QQ); - free(UU); - free(VV); - -/*****************************************************************/ - /* add to baseline visibilities */ - t->coh[4*M*ci+4*cm]=C[0]; - t->coh[4*M*ci+4*cm+1]=C[1]; - t->coh[4*M*ci+4*cm+2]=C[2]; - t->coh[4*M*ci+4*cm+3]=C[3]; - } - if (!t->barr[ci+t->boff].flag) { - /* change the flag to 2 if baseline length is < uvmin or > uvmax */ - uvdist=sqrt(t->u[ci]*t->u[ci]+t->v[ci]*t->v[ci])*t->freq0; - if (uvdistuvmin || uvdist>t->uvmax) { - t->barr[ci+t->boff].flag=2; - } - } - } - - return NULL; -} - - -int -precalculate_coherencies(double *u, double *v, double *w, complex double *x, int N, - int Nbase, baseline_t *barr, clus_source_t *carr, int M, double freq0, double fdelta, double tdelta, double dec0, double uvmin, double uvmax, int Nt) { - /* u,v,w : size Nbasex 1 x: size Nbase*4*M x 1 */ - /* barr: size Nbasex 1 carr: size Mx1 */ - /* ordering of x: [0,4M-1] coherencies for baseline 0 - [4M,2*4M-1] coherencies for baseline 1 ... */ - - int nth,nth1,ci; - - int Nthb0,Nthb; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_base_t *threaddata; - - /* calculate min baselines a thread can handle */ - Nthb0=(Nbase+Nt-1)/Nt; - - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((threaddata=(thread_data_base_t*)malloc((size_t)Nt*sizeof(thread_data_base_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthddcoh must have all zeros intially (using calloc) */ - int ci,cj; - double *realcoh=(double*)t->coh; - for (ci=t->startbase; ci<=t->endbase; ci++) { - if (!t->barr[ci].flag) { - t->ddbase[2*ci]=(short)t->barr[ci].sta1; - t->ddbase[2*ci+1]=(short)t->barr[ci].sta2; - /* loop over directions and copy coherencies */ - for (cj=0; cjM; cj++) { - memcpy(&(t->ddcoh[cj*(t->Nbase)*8+8*ci]),&realcoh[8*cj+8*(t->M)*ci],8*sizeof(double)); - } - } else { - t->ddbase[2*ci]=t->ddbase[2*ci+1]=-1; - } - } - - return NULL; -} - -/* rearranges coherencies for GPU use later */ -/* barr: 2*Nbase x 1 - coh: M*Nbase*4 x 1 complex - ddcoh: M*Nbase*8 x 1 - ddbase: 2*Nbase x 1 (sta1,sta2) == -1 if flagged -*/ -int -rearrange_coherencies(int Nbase, baseline_t *barr, complex double *coh, double *ddcoh, short *ddbase, int M, int Nt) { - - int nth,nth1,ci; - int Nthb0,Nthb; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_coharr_t *threaddata; - - /* calculate min baselines a thread can handle */ - Nthb0=(Nbase+Nt-1)/Nt; - - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((threaddata=(thread_data_coharr_t*)malloc((size_t)Nt*sizeof(thread_data_coharr_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthddcoh must have all zeros intially (using calloc) */ - int ci,cj; - double *realcoh=(double*)t->coh; - for (ci=t->startbase; ci<=t->endbase; ci++) { - t->ddbase[3*ci]=(short)t->barr[ci].sta1; - t->ddbase[3*ci+1]=(short)t->barr[ci].sta2; - t->ddbase[3*ci+2]=(short)t->barr[ci].flag; - /* loop over directions and copy coherencies */ - for (cj=0; cjM; cj++) { - memcpy(&(t->ddcoh[cj*(t->Nbase)*8+8*ci]),&realcoh[8*cj+8*(t->M)*ci],8*sizeof(double)); - } - } - - return NULL; -} - - -/* rearranges coherencies for GPU use later */ -/* barr: 2*Nbase x 1 - coh: M*Nbase*4 x 1 complex - ddcoh: M*Nbase*8 x 1 - ddbase: 3*Nbase x 1 (sta1,sta2,flag) -*/ -int -rearrange_coherencies2(int Nbase, baseline_t *barr, complex double *coh, double *ddcoh, short *ddbase, int M, int Nt) { - - int nth,nth1,ci; - int Nthb0,Nthb; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_coharr_t *threaddata; - - /* calculate min baselines a thread can handle */ - Nthb0=(Nbase+Nt-1)/Nt; - - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((threaddata=(thread_data_coharr_t*)malloc((size_t)Nt*sizeof(thread_data_coharr_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthstartbase; ci<=t->endbase; ci++) { - if (!t->barr[ci].flag) { - t->ddbase[2*ci]=(short)t->barr[ci].sta1; - t->ddbase[2*ci+1]=(short)t->barr[ci].sta2; - } else { - t->ddbase[2*ci]=t->ddbase[2*ci+1]=-1; - } - } - - return NULL; -} - -/* rearranges baselines for GPU use later */ -/* barr: 2*Nbase x 1 - ddbase: 2*Nbase x 1 -*/ -int -rearrange_baselines(int Nbase, baseline_t *barr, short *ddbase, int Nt) { - - int nth,nth1,ci; - int Nthb0,Nthb; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_coharr_t *threaddata; - - /* calculate min baselines a thread can handle */ - Nthb0=(Nbase+Nt-1)/Nt; - - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((threaddata=(thread_data_coharr_t*)malloc((size_t)Nt*sizeof(thread_data_coharr_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthstartbase; ci<=t->endbase; ci++) { - if (t->flag[ci]>0.0) { /* flagged data */ - t->barr[ci].flag=1; - /* set data points to 0 */ - t->x[8*ci]=0.0; - t->x[8*ci+1]=0.0; - t->x[8*ci+2]=0.0; - t->x[8*ci+3]=0.0; - t->x[8*ci+4]=0.0; - t->x[8*ci+5]=0.0; - t->x[8*ci+6]=0.0; - t->x[8*ci+7]=0.0; - } else { - t->barr[ci].flag=0; - } - } - - return NULL; -} - -/* update baseline flags, also make data zero if flagged - this is needed for solving (calculate error) ignore flagged data */ -/* Nbase: total actual data points = Nbasextilesz - flag: flag array Nbasex1 - barr: baseline array Nbasex1 - x: data Nbase*8 x 1 ( 8 value per baseline ) - Nt: no of threads -*/ -int -preset_flags_and_data(int Nbase, double *flag, baseline_t *barr, double *x, int Nt){ - int nth,nth1,ci; - int Nthb0,Nthb; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_preflag_t *threaddata; - - /* calculate min baselines a thread can handle */ - Nthb0=(Nbase+Nt-1)/Nt; - - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((threaddata=(thread_data_preflag_t*)malloc((size_t)Nt*sizeof(thread_data_preflag_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthstarti; ci<=t->endi; ci++) { - t->farr[ci]=(float)t->darr[ci]; - } - return NULL; -} -static void * -float_to_double_threadfn(void *data) { - thread_data_typeconv_t *t=(thread_data_typeconv_t*)data; - - int ci; - for (ci=t->starti; ci<=t->endi; ci++) { - t->darr[ci]=(double)t->farr[ci]; - } - return NULL; -} - -/* convert types */ -/* both arrays size nx1 -*/ -int -double_to_float(float *farr, double *darr,int n, int Nt) { - - int nth,nth1,ci; - int Nthb0,Nthb; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_typeconv_t *threaddata; - - /* calculate min values a thread can handle */ - Nthb0=(n+Nt-1)/Nt; - - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((threaddata=(thread_data_typeconv_t*)malloc((size_t)Nt*sizeof(thread_data_typeconv_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - /* iterate over threads, allocating indices per thread */ - ci=0; - for (nth=0; nthstarti; ci<=t->endi; ci++) { - sta1=0; sta2=sta1+1; - for (cj=0; cjNbase; cj++) { - t->barr[ci*t->Nbase+cj].sta1=sta1; - t->barr[ci*t->Nbase+cj].sta2=sta2; - if(sta2<(t->N-1)) { - sta2=sta2+1; - } else { - if (sta1<(t->N-2)) { - sta1=sta1+1; - sta2=sta1+1; - } else { - sta1=0; - sta2=sta1+1; - } - } - } - } - return NULL; -} - -/* generte baselines -> sta1,sta2 pairs for later use */ -/* barr: Nbasextilesz - N : stations - Nt : threads -*/ -int -generate_baselines(int Nbase, int tilesz, int N, baseline_t *barr,int Nt) { - int nth,nth1,ci; - int Nthb0,Nthb; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_baselinegen_t *threaddata; - - /* calculate min values a thread can handle */ - Nthb0=(tilesz+Nt-1)/Nt; - - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((threaddata=(thread_data_baselinegen_t*)malloc((size_t)Nt*sizeof(thread_data_baselinegen_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - /* iterate over threads, allocating indices per thread */ - ci=0; - for (nth=0; nthNb; ci++) { - /* if this baseline is flagged, we do not compute */ - - /* stations for this baseline */ - sta1=(int)t->ddbase[2*(ci+t->boff)]; - sta2=(int)t->ddbase[2*(ci+t->boff)+1]; - - if (sta1!=-1 && sta2!=-1) { - pthread_mutex_lock(&t->mx_array[sta1]); - t->bcount[sta1]+=1; - pthread_mutex_unlock(&t->mx_array[sta1]); - - pthread_mutex_lock(&t->mx_array[sta2]); - t->bcount[sta2]+=1; - pthread_mutex_unlock(&t->mx_array[sta2]); - } - } - - return NULL; -} - - -/* cont how many baselines contribute to each station */ -int -count_baselines(int Nbase, int N, float *iw, short *ddbase, int Nt) { - pthread_attr_t attr; - pthread_t *th_array; - thread_data_count_t *threaddata; - pthread_mutex_t *mx_array; - - int *bcount; - - int ci,nth1,nth; - int Nthb0,Nthb; - - Nthb0=(Nbase+Nt-1)/Nt; - - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - if ((threaddata=(thread_data_count_t*)malloc((size_t)Nt*sizeof(thread_data_count_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - if ((mx_array=(pthread_mutex_t*)malloc((size_t)N*sizeof(pthread_mutex_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - for (ci=0; ci0?1.0f/(float)bcount[nth1]:0.0f); - } - - /* scale back weight such that max value is 1 */ - nth1=my_isamax(N,iw,1); - float maxw=iw[nth1-1]; /* 1 based index */ - if (maxw>0.0f) { /* all baselines are flagged */ - my_sscal(N,1.0f/maxw,iw); - } - - for (ci=0; ciNb; ci++) { - t->b[ci+t->boff]=t->a; - } - - return NULL; -} - - - -/* initialize array b (size Nx1) to given value a */ -void -setweights(int N, double *b, double a, int Nt) { - pthread_attr_t attr; - pthread_t *th_array; - thread_data_setwt_t *threaddata; - - int ci,nth1,nth; - int Nthb0,Nthb; - - Nthb0=(N+Nt-1)/Nt; - - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - if ((threaddata=(thread_data_setwt_t*)malloc((size_t)Nt*sizeof(thread_data_setwt_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthddcoh must have all zeros intially (using calloc) */ - int ci; - for (ci=t->startbase; ci<=t->endbase; ci++) { - if (t->ddbase[3*ci+2]) { - t->x[8*ci]=1.0f; - t->x[8*ci+1]=1.0f; - t->x[8*ci+2]=1.0f; - t->x[8*ci+3]=1.0f; - t->x[8*ci+4]=1.0f; - t->x[8*ci+5]=1.0f; - t->x[8*ci+6]=1.0f; - t->x[8*ci+7]=1.0f; - } - } - - return NULL; -} - -/* create a vector with 1's at flagged data points */ -/* - ddbase: 3*Nbase x 1 (sta1,sta2,flag) - x: 8*Nbase (set to 0's and 1's) -*/ -int -create_onezerovec(int Nbase, short *ddbase, float *x, int Nt) { - - int nth,nth1,ci; - int Nthb0,Nthb; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_onezero_t *threaddata; - - /* calculate min baselines a thread can handle */ - Nthb0=(Nbase+Nt-1)/Nt; - - memset(x,0,sizeof(float)*8*Nbase); - - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((threaddata=(thread_data_onezero_t*)malloc((size_t)Nt*sizeof(thread_data_onezero_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthstartbase; ci<=t->endbase; ci++) { - float xabs=fabsf(t->x[ci]); - t->sum1 +=xabs; - t->sum2 +=xabs*fabsf(t->y[ci]); - } - - return NULL; -} - -/* - find sum1=sum(|x|), and sum2=y^T |x| - x,y: nx1 arrays -*/ -int -find_sumproduct(int N, float *x, float *y, float *sum1, float *sum2, int Nt) { - - int nth,nth1,ci; - int Nthb0,Nthb; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_findsumprod_t *threaddata; - - /* calculate min baselines a thread can handle */ - Nthb0=(N+Nt-1)/Nt; - - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((threaddata=(thread_data_findsumprod_t*)malloc((size_t)Nt*sizeof(thread_data_findsumprod_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nth - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - -#include "cuda.h" -#include -#include -#include -#include "sagecal.h" - -/* enable this for checking for kernel failure */ -//#define CUDA_DBG - -__device__ void -radec2azel_gmst__(float ra, float dec, float longitude, float latitude, float thetaGMST, float *az, float *el) { - float thetaLST=thetaGMST+longitude*180.0f/M_PI; - - float LHA=fmodf(thetaLST-ra*180.0f/M_PI,360.0f); - - float sinlat,coslat,sindec,cosdec,sinLHA,cosLHA; - sincosf(latitude,&sinlat,&coslat); - sincosf(dec,&sindec,&cosdec); - sincosf(LHA*M_PI/180.0f,&sinLHA,&cosLHA); - - float tmp=sinlat*sindec+coslat*cosdec*cosLHA; - float eld=asinf(tmp); - - float sinel,cosel; - sincosf(eld,&sinel,&cosel); - - float azd=fmodf(atan2f(-sinLHA*cosdec/cosel,(sindec-sinel*sinlat)/(cosel*coslat)),2.0f*M_PI); - if (azd<0.0f) { - azd+=2.0f*M_PI; - } - *el=eld; - *az=azd; -} - -/* slave kernel to calculate phase of manifold vector for given station */ -/* x,y,z: Nx1 arrays of element coords */ -/* sum: scalar to store result */ -/* NOTE: only 1 block should be used here */ -__global__ void -kernel_array_beam_slave_sin(int N, float r1, float r2, float r3, float *x, float *y, float *z, float *sum, int blockDim_2) { - unsigned int n=threadIdx.x+blockDim.x*blockIdx.x; - extern __shared__ float tmpsum[]; /* assumed to be size Nx1 */ - if (n 1) { - int halfPoint = (nTotalThreads >> 1); // divide by two - if (n < halfPoint) { - int thread2 = n + halfPoint; - if (thread2 < blockDim.x) { // Skipping the fictitious threads >N ( blockDim.x ... blockDim_2-1 ) - tmpsum[n] = tmpsum[n]+tmpsum[thread2]; - } - } - __syncthreads(); - nTotalThreads = halfPoint; // Reducing the binary tree size by two - } - - /* now thread 0 will add up results */ - if (threadIdx.x==0) { - *sum=tmpsum[0]; - } -} - -__global__ void -kernel_array_beam_slave_cos(int N, float r1, float r2, float r3, float *x, float *y, float *z, float *sum, int blockDim_2) { - unsigned int n=threadIdx.x+blockDim.x*blockIdx.x; - extern __shared__ float tmpsum[]; /* assumed to be size Nx1 */ - if (n 1) { - int halfPoint = (nTotalThreads >> 1); // divide by two - if (n < halfPoint) { - int thread2 = n + halfPoint; - if (thread2 < blockDim.x) { // Skipping the fictitious threads >N ( blockDim.x ... blockDim_2-1 ) - tmpsum[n] = tmpsum[n]+tmpsum[thread2]; - } - } - __syncthreads(); - nTotalThreads = halfPoint; // Reducing the binary tree size by two - } - - /* now thread 0 will add up results */ - if (threadIdx.x==0) { - *sum=tmpsum[0]; - } -} - -/* sum: 2x1 array */ -__global__ void -kernel_array_beam_slave_sincos(int N, float r1, float r2, float r3, float *x, float *y, float *z, float *sum, int blockDim_2) { - unsigned int n=threadIdx.x+blockDim.x*blockIdx.x; - extern __shared__ float tmpsum[]; /* assumed to be size 2*Nx1 */ - if (n 1) { - int halfPoint = (nTotalThreads >> 1); // divide by two - if (n < halfPoint) { - int thread2 = n + halfPoint; - if (thread2 < blockDim.x) { // Skipping the fictitious threads >N ( blockDim.x ... blockDim_2-1 ) - tmpsum[2*n] = tmpsum[2*n]+tmpsum[2*thread2]; - tmpsum[2*n+1] = tmpsum[2*n+1]+tmpsum[2*thread2+1]; - } - } - __syncthreads(); - nTotalThreads = halfPoint; // Reducing the binary tree size by two - } - - /* now thread 0 will add up results */ - if (threadIdx.x==0) { - sum[0]=tmpsum[0]; - sum[1]=tmpsum[1]; - } -} - - -__device__ int -NearestPowerOf2 (int n){ - if (!n) return n; //(0 == 2^0) - - int x = 1; - while(x < n) { - x <<= 1; - } - return x; -} - - -/* master kernel to calculate beam */ -/* tarr: size NTKFx2 buffer to store sin() cos() sums */ -__global__ void -kernel_array_beam(int N, int T, int K, int F, float *freqs, float *longitude, float *latitude, - double *time_utc, int *Nelem, float **xx, float **yy, float **zz, float *ra, float *dec, float ph_ra0, float ph_dec0, float ph_freq0, float *beam, float *tarr) { - - /* global thread index */ - unsigned int n=threadIdx.x+blockDim.x*blockIdx.x; - /* allowed max threads */ - int Ntotal=N*T*K*F; - if (n>>(Nelems,r1,r2,r3,xx[istat],yy[istat],zz[istat],&tarr[2*n],NearestPowerOf2(Nelems)); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - printf("CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - } -#endif - float ssum=__ldg(&tarr[2*n]); - float csum=__ldg(&tarr[2*n+1]); - - float Nnor=1.0f/(float)Nelems; - ssum*=Nnor; - csum*=Nnor; - /* store output (amplitude of beam)*/ - beam[boffset]=sqrtf(ssum*ssum+csum*csum); - //printf("thread %d stat %d src %d freq %d time %d : %lf longitude=%lf latitude=%lf time=%lf freq=%lf elem=%d ra=%lf dec=%lf beam=%lf\n",n,istat,isrc,ifrq,itm,time_utc[itm],longitude[istat],latitude[istat],time_utc[itm],freqs[ifrq],Nelem[istat],ra[isrc],dec[isrc],beam[boffset]); - } - -} - -/***************************************************************************/ -__device__ cuFloatComplex -gaussian_contrib(int *dd, float u, float v, float w) { - exinfo_gaussian *dp=(exinfo_gaussian*)dd; - float up,vp,a,b,ut,vt,cosph,sinph; - - /* first the rotation due to projection */ - if (dp->use_projection) { - up=u*(dp->cxi)-v*(dp->cphi)*(dp->sxi)+w*(dp->sphi)*(dp->sxi); - vp=u*(dp->sxi)+v*(dp->cphi)*(dp->cxi)-w*(dp->sphi)*(dp->cxi); - } else { - up=u; - vp=v; - } - - /* linear transformations, if any */ - a=dp->eX; - b=dp->eY; - sincosf(dp->eP,&sinph,&cosph); - ut=a*(cosph*up-sinph*vp); - vt=b*(sinph*up+cosph*vp); - - return make_cuFloatComplex(0.5f*M_PI*expf(-(ut*ut+vt*vt)),0.0f); -} - - - -__device__ cuFloatComplex -ring_contrib(int *dd, float u, float v, float w) { - exinfo_ring *dp=(exinfo_ring*)dd; - float up,vp,a,b; - - /* first the rotation due to projection */ - up=u*(dp->cxi)-v*(dp->cphi)*(dp->sxi)+w*(dp->sphi)*(dp->sxi); - vp=u*(dp->sxi)+v*(dp->cphi)*(dp->cxi)-w*(dp->sphi)*(dp->cxi); - - a=dp->eX; /* diameter */ - b=sqrtf(up*up+vp*vp)*a*2.0f*M_PI; - - return make_cuFloatComplex(j0f(b),0.0f); -} - -__device__ cuFloatComplex -disk_contrib(int *dd, float u, float v, float w) { - exinfo_disk *dp=(exinfo_disk*)dd; - float up,vp,a,b; - - /* first the rotation due to projection */ - up=u*(dp->cxi)-v*(dp->cphi)*(dp->sxi)+w*(dp->sphi)*(dp->sxi); - vp=u*(dp->sxi)+v*(dp->cphi)*(dp->cxi)-w*(dp->sphi)*(dp->cxi); - - a=dp->eX; /* diameter */ - b=sqrtf(up*up+vp*vp)*a*2.0f*M_PI; - - return make_cuFloatComplex(j1f(b),0.0f); -} - - -/* Hermite polynomial, non recursive version */ -__device__ float -H_e(float x, int n) { - if(n==0) return 1.0f; - if(n==1) return 2.0f*x; - /* else iterate */ - float Hn_1,Hn,Hnp1; - Hn_1=1.0f; - Hn=2.0f*x; - int ci; - for (ci=1; ciuse_projection) { - up=-u*(dp->cxi)+v*(dp->cphi)*(dp->sxi)-w*(dp->sphi)*(dp->sxi); - vp=-u*(dp->sxi)-v*(dp->cphi)*(dp->cxi)+w*(dp->sphi)*(dp->cxi); - } else { - up=u; - vp=v; - } - - /* linear transformations, if any */ - a=1.0f/dp->eX; - b=1.0f/dp->eY; - __sincosf((float)dp->eP,&sinph,&cosph); - ut=a*(cosph*up-sinph*vp); - vt=b*(sinph*up+cosph*vp); - /* note: we decompose f(-l,m) so the Fourier transform is F(-u,v) - so negate the u grid */ - Av=(float*)malloc((size_t)((dp->n0)*(dp->n0))*sizeof(float)); - cplx=(int*)malloc((size_t)((dp->n0)*(dp->n0))*sizeof(int)); - - calculate_uv_mode_vectors_scalar(-ut, vt, dp->beta, dp->n0, Av, cplx); - realsum=imagsum=0.0f; - M=(dp->n0)*(dp->n0); - for (ci=0; cimodes[ci]*Av[ci]; - } else { - realsum+=dp->modes[ci]*Av[ci]; - } - } - - free(Av); - free(cplx); - //return 2.0*M_PI*(realsum+_Complex_I*imagsum); - realsum*=2.0f*M_PI*a*b; - imagsum*=2.0f*M_PI*a*b; - return make_cuFloatComplex(realsum,imagsum); -} - - - -/* slave thread to calculate coherencies, for 1 source */ -/* baseline (sta1,sta2) at time itm */ -/* K: total sources, uset to find right offset - Kused: actual sources calculated in this thread block - Koff: offset in source array to start calculation - NOTE: only 1 block is used - */ -__global__ void -kernel_coherencies_slave(int sta1, int sta2, int itm, int B, int N, int T, int K, int Kused, int Koff, int F, float u, float v, float w, float *freqs, float *beam, float *ll, float *mm, float *nn, float *sI, - unsigned char *stype, float *sI0, float *f0, float *spec_idx, float *spec_idx1, float *spec_idx2, int **exs, float deltaf, float deltat, float dec0, float *__restrict__ coh,int dobeam,int blockDim_2) { - /* which source we work on */ - unsigned int k=threadIdx.x+blockDim.x*blockIdx.x; - - extern __shared__ float tmpcoh[]; /* assumed to be size 8*F*Kusedx1 */ - - if (k1) { - sI0f=__ldg(&sI0[k]); - spec_idxf=__ldg(&spec_idx[k]); - spec_idx1f=__ldg(&spec_idx1[k]); - spec_idx2f=__ldg(&spec_idx2[k]); - myf0=__ldg(&f0[k]); - } - unsigned char stypeT=__ldg(&stype[k]); - for(int cf=0; cf0.0f) { - If=__expf(__logf(sI0f)+spec_idxf*fratio+spec_idx1f*fratio1+spec_idx2f*fratio2); - } else if (sI0f<0.0f) { - If=-__expf(__logf(-sI0f)+spec_idxf*fratio+spec_idx1f*fratio1+spec_idx2f*fratio2); - } else { - If=0.0f; - } - } - /* smearing */ - float phterm =phterm0*0.5f*deltaf; - if (phterm!=0.0f) { - sinph=__sinf(phterm)/phterm; - If *=fabsf(sinph); /* catch -ve values due to rounding off */ - } - - if (dobeam) { - /* get beam info */ - //int boffset1=sta1*K*T*F + k1*T*F + cf*T + itm; - int boffset1=itm*N*K*F+k1*N*F+cf*N+sta1; - float beam1=__ldg(&beam[boffset1]); - //int boffset2=sta2*K*T*F + k1*T*F + cf*T + itm; - int boffset2=itm*N*K*F+k1*N*F+cf*N+sta2; - float beam2=__ldg(&beam[boffset2]); - If *=beam1*beam2; - } - - /* form complex value */ - prodterm.x *=If; - prodterm.y *=If; - - /* check for type of source */ - if (stypeT!=STYPE_POINT) { - float uscaled=u*myfreq; - float vscaled=v*myfreq; - float wscaled=w*myfreq; - if (stypeT==STYPE_SHAPELET) { - prodterm=cuCmulf(shapelet_contrib(exs[k],uscaled,vscaled,wscaled),prodterm); - } else if (stypeT==STYPE_GAUSSIAN) { - prodterm=cuCmulf(gaussian_contrib(exs[k],uscaled,vscaled,wscaled),prodterm); - } else if (stypeT==STYPE_DISK) { - prodterm=cuCmulf(disk_contrib(exs[k],uscaled,vscaled,wscaled),prodterm); - } else if (stypeT==STYPE_RING) { - prodterm=cuCmulf(ring_contrib(exs[k],uscaled,vscaled,wscaled),prodterm); - } - } - -//printf("k=%d cf=%d freq=%f uvw %f,%f,%f lmn %f,%f,%f phterm %f If %f\n",k,cf,freqs[cf],u,v,w,ll[k],mm[k],nn[k],phterm,If); - - /* write output to shared array */ - tmpcoh[k*8*F+8*cf]=prodterm.x; - tmpcoh[k*8*F+8*cf+1]=prodterm.y; - tmpcoh[k*8*F+8*cf+2]=0.0f; - tmpcoh[k*8*F+8*cf+3]=0.0f; - tmpcoh[k*8*F+8*cf+4]=0.0f; - tmpcoh[k*8*F+8*cf+5]=0.0f; - tmpcoh[k*8*F+8*cf+6]=prodterm.x; - tmpcoh[k*8*F+8*cf+7]=prodterm.y; - } - } - __syncthreads(); - - // Build summation tree over elements, handling case where total threads is not a power of two. - int nTotalThreads = blockDim_2; // Total number of threads (==Kused), rounded up to the next power of two - while(nTotalThreads > 1) { - int halfPoint = (nTotalThreads >> 1); // divide by two - if (k < halfPoint) { - int thread2 = k + halfPoint; - if (thread2 < blockDim.x) { // Skipping the fictitious threads >Kused ( blockDim.x ... blockDim_2-1 ) - for(int cf=0; cf>>(sta1,sta2,tslot,B,N,T,K,K,0,F,__ldg(&u[n]),__ldg(&v[n]),__ldg(&w[n]),freqs,beam,ll,mm,nn,sI,stype,sI0,f0,spec_idx,spec_idx1,spec_idx2,exs,deltaf,deltat,dec0,&coh[8*n],dobeam,NearestPowerOf2(K)); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - printf("CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - } -#endif - } else { - /* more than 1 kernel */ - int L=(K+ThreadsPerBlock-1)/ThreadsPerBlock; - int ct=0; - int myT; - for (int ci=0; ci>>(sta1,sta2,tslot,B,N,T,K,myT,ct,F,__ldg(&u[n]),__ldg(&v[n]),__ldg(&w[n]),freqs,beam,&ll[ct],&mm[ct],&nn[ct],&sI[ct],&stype[ct],&sI0[ct],&f0[ct],&spec_idx[ct],&spec_idx1[ct],&spec_idx2[ct],&exs[ct],deltaf,deltat,dec0,&coh[8*n],dobeam,NearestPowerOf2(myT)); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - printf("CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - } -#endif - ct=ct+ThreadsPerBlock; - } - } - - } - -} - - -/* kernel to convert time (JD) to GMST angle*/ -__global__ void -kernel_convert_time(int T, double *time_utc) { - - /* global thread index */ - unsigned int n=threadIdx.x+blockDim.x*blockIdx.x; - if (n>>(N,T,K,F,freqs,longitude,latitude,time_utc,Nelem,xx,yy,zz,ra,dec,ph_ra0,ph_dec0,ph_freq0,beam,buffer); - cudaDeviceSynchronize(); - - cudaFree(buffer); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif -} - - -/* - calculate coherencies: - B: total baselines - N: no of stations - T: no of time slots - K: no of sources - F: no of frequencies - u,v,w: Bx1 uvw coords - barr: Bx1 array of baseline/flag info - freqs: Fx1 frequencies - beam: NxTxKxF beam gain - ll,mm,nn : Kx1 source coordinates - sI: Kx1 source flux at reference freq - stype: Kx1 source type info - sI0: Kx1 original source referene flux - f0: Kx1 source reference freq for calculating flux - spec_idx,spec_idx1,spec_idx2: Kx1 spectra info - exs: Kx1 array of pointers to extended source info - deltaf,deltat: freq/time smearing integration interval - dec0: phace reference dec - coh: coherency Bx8 values, all K sources are added together - - dobeam: enable beam if >0 -*/ -void -cudakernel_coherencies(int B, int N, int T, int K, int F, float *u, float *v, float *w,baseline_t *barr, float *freqs, float *beam, float *ll, float *mm, float *nn, float *sI, - unsigned char *stype, float *sI0, float *f0, float *spec_idx, float *spec_idx1, float *spec_idx2, int **exs, float deltaf, float deltat, float dec0, float *coh,int dobeam) { -#ifdef CUDA_DBG - cudaError_t error; - error = cudaGetLastError(); - error=cudaMemset(coh,0,sizeof(float)*8*B*F); - checkCudaError(error,__FILE__,__LINE__); -#endif -#ifndef CUDA_DBG - cudaMemset(coh,0,sizeof(float)*8*B*F); -#endif - - /* spawn threads to handle baselines, these threads will spawn threads for sources */ - int ThreadsPerBlock=DEFAULT_TH_PER_BK; - /* note: make sure we do not exceed max no of blocks available, - otherwise (too many baselines, loop over source id) */ - int BlocksPerGrid=(B+ThreadsPerBlock-1)/ThreadsPerBlock; - kernel_coherencies<<>>(B, N, T, K, F,u,v,w,barr,freqs, beam, ll, mm, nn, sI, - stype, sI0, f0, spec_idx, spec_idx1, spec_idx2, exs, deltaf, deltat, dec0, coh, dobeam); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif -} - - -/* convert time JD to GMST angle - store result at the same location */ -void -cudakernel_convert_time(int T, double *time_utc) { -#ifdef CUDA_DBG - cudaError_t error; - error = cudaGetLastError(); -#endif - - int ThreadsPerBlock=DEFAULT_TH_PER_BK; - /* note: make sure we do not exceed max no of blocks available, - otherwise (too many baselines, loop over source id) */ - int BlocksPerGrid=(T+ThreadsPerBlock-1)/ThreadsPerBlock; - kernel_convert_time<<>>(T,time_utc); - cudaDeviceSynchronize(); - #ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif -} - - -} /* extern "C" */ diff --git a/src/lib/predict_withbeam.c b/src/lib/predict_withbeam.c deleted file mode 100644 index 4a75604..0000000 --- a/src/lib/predict_withbeam.c +++ /dev/null @@ -1,1358 +0,0 @@ -/* - * - Copyright (C) 2006-2008 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#define _GNU_SOURCE -#include -#include -#include -#include -#include "sagecal.h" - -/* worker thread function for precalculation*/ -static void * -precal_threadfn(void *data) { - thread_data_base_t *t=(thread_data_base_t*)data; - - /* memory ordering: x[0:4M-1] baseline 0 - x[4M:2*4M-1] baseline 2 ... */ - int ci,cm,cn,sta1,sta2,tslot; - int M=(t->M); - double uvdist; - double *PHr=0,*PHi=0,*G=0,*II=0,*QQ=0,*UU=0,*VV=0; /* arrays to store calculations */ - - complex double C[4]; - double freq0=t->freq0; - double fdelta2=t->fdelta*0.5; - for (ci=0; ciNb; ci++) { - /* get station ids */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - /* get timeslot */ - tslot=(ci+t->boff)/t->Nbase; -#ifdef DEBUG - if (tslot>t->tilesz) { - fprintf(stderr,"%s: %d: timeslot exceed available timeslots\n",__FILE__,__LINE__); - exit(1); - } -#endif - /* reset memory only for initial cluster */ - if (t->clus==0) { - memset(&(t->coh[4*M*ci]),0,sizeof(complex double)*4*M); - } - /* even if this baseline is flagged, we do compute */ - cm=t->clus; /* predict for only 1 cluster */ - memset(C,0,sizeof(complex double)*4); - /* iterate over the sky model and calculate contribution */ -/************************************************************/ - /* setup memory */ - if (posix_memalign((void*)&PHr,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&PHi,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&G,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&II,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&QQ,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&UU,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&VV,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - /* phase (real,imag) parts */ - /* note u=u/c, v=v/c, w=w/c here */ - /* phterm is 2pi(u/c l +v/c m +w/c n) */ - for (cn=0; cncarr[cm].N; cn++) { - G[cn]=2.0*M_PI*(t->u[ci]*t->carr[cm].ll[cn]+t->v[ci]*t->carr[cm].mm[cn]+t->w[ci]*t->carr[cm].nn[cn]); - } - for (cn=0; cncarr[cm].N; cn++) { - sincos(G[cn]*freq0,&PHi[cn],&PHr[cn]); - } - - /* term due to shape of source, also multiplied by freq/time smearing */ - for (cn=0; cncarr[cm].N; cn++) { - /* get array factor for these 2 stations, at given time */ - double af1=t->arrayfactor[cn*(t->N*t->tilesz)+tslot*t->N+sta1]; - double af2=t->arrayfactor[cn*(t->N*t->tilesz)+tslot*t->N+sta2]; - - /* freq smearing : extra term delta * sinc(delta/2 * phterm) */ - if (G[cn]!=0.0) { - double smfac=G[cn]*fdelta2; - double sinph=sin(smfac)/smfac; - G[cn]=fabs(sinph); - } else { - G[cn]=1.0; - } - G[cn]*=af1*af2; - } - - /* multiply (re,im) phase term with smearing/shape factor */ - for (cn=0; cncarr[cm].N; cn++) { - PHr[cn]*=G[cn]; - PHi[cn]*=G[cn]; - } - - - for (cn=0; cncarr[cm].N; cn++) { - /* check if source type is not a point source for additional - calculations */ - if (t->carr[cm].stype[cn]!=STYPE_POINT) { - complex double sterm=PHr[cn]+_Complex_I*PHi[cn]; - if (t->carr[cm].stype[cn]==STYPE_SHAPELET) { - sterm*=shapelet_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } else if (t->carr[cm].stype[cn]==STYPE_GAUSSIAN) { - sterm*=gaussian_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } else if (t->carr[cm].stype[cn]==STYPE_DISK) { - sterm*=disk_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } else if (t->carr[cm].stype[cn]==STYPE_RING) { - sterm*=ring_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } - PHr[cn]=creal(sterm); - PHi[cn]=cimag(sterm); - } - - } - - - /* flux of each source, at each freq */ - for (cn=0; cncarr[cm].N; cn++) { - II[cn]=t->carr[cm].sI[cn]; - QQ[cn]=t->carr[cm].sQ[cn]; - UU[cn]=t->carr[cm].sU[cn]; - VV[cn]=t->carr[cm].sV[cn]; - } - - /* add up terms together */ - for (cn=0; cncarr[cm].N; cn++) { - complex double Ph,IIl,QQl,UUl,VVl; - Ph=(PHr[cn]+_Complex_I*PHi[cn]); - IIl=Ph*II[cn]; - QQl=Ph*QQ[cn]; - UUl=Ph*UU[cn]; - VVl=Ph*VV[cn]; - C[0]+=IIl+QQl; - C[1]+=UUl+_Complex_I*VVl; - C[2]+=UUl-_Complex_I*VVl; - C[3]+=IIl-QQl; - } - - - free(PHr); - free(PHi); - free(G); - free(II); - free(QQ); - free(UU); - free(VV); - - -/************************************************************/ - /* add to baseline visibilities */ - t->coh[4*M*ci+4*cm]+=C[0]; - t->coh[4*M*ci+4*cm+1]+=C[1]; - t->coh[4*M*ci+4*cm+2]+=C[2]; - t->coh[4*M*ci+4*cm+3]+=C[3]; - if (t->clus==0) { - if (!t->barr[ci+t->boff].flag) { - /* change the flag to 2 if baseline length is < uvmin or > uvmax */ - uvdist=sqrt(t->u[ci]*t->u[ci]+t->v[ci]*t->v[ci])*t->freq0; - if (uvdistuvmin || uvdist>t->uvmax) { - t->barr[ci+t->boff].flag=2; - } - } - } - } - - return NULL; -} - -/* worker thread function for precalculation of array factor */ -static void * -precalbeam_threadfn(void *data) { - thread_data_arrayfac_t *t=(thread_data_arrayfac_t*)data; - - int cm,cn,ct,cf; - /* ordering of beamgain : Nstationxtime x source */ - cm=t->cid; /* predict for only this cluster */ - for (cn=t->soff; cnsoff+t->Ns; cn++) { - //printf("clus=%d src=%d total=%d freq=%d %e %e \n",cm,cn,t->Ns,t->Nf,t->carr[cm].ra[cn],t->carr[cm].sI[cn]); - /* iterate over frequencies */ - for (cf=0; cfNf; cf++) { - /* iterate over all timeslots */ - for (ct=0;ctNtime;ct++) { - arraybeam(t->carr[cm].ra[cn], t->carr[cm].dec[cn], t->ra0, t->dec0, t->freqs[cf], t->freq0, t->N, t->longitude, t->latitude, t->time_utc[ct], t->Nelem, t->xx, t->yy, t->zz, &(t->beamgain[cn*(t->N*t->Ntime*t->Nf)+cf*(t->N*t->Ntime)+ct*t->N])); - } - } - } - - return NULL; -} - - - -int -precalculate_coherencies_withbeam(double *u, double *v, double *w, complex double *x, int N, - int Nbase, baseline_t *barr, clus_source_t *carr, int M, double freq0, double fdelta, double tdelta, double dec0, double uvmin, double uvmax, - double ph_ra0, double ph_dec0, double ph_freq0, double *longitude, double *latitude, double *time_utc, int tilesz, int *Nelem, double **xx, double **yy, double **zz, int Nt) { - - int nth,ci,ncl; - - int Nthb0,Nthb,nth1,Ns0; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_base_t *threaddata; - double *beamgain; - thread_data_arrayfac_t *beamdata; - - /* calculate min baselines a thread can handle */ - Nthb0=(Nbase+Nt-1)/Nt; - - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((threaddata=(thread_data_base_t*)malloc((size_t)Nt*sizeof(thread_data_base_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((beamdata=(thread_data_arrayfac_t*)malloc((size_t)Nt*sizeof(thread_data_arrayfac_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - /* set common parameters, and split baselines to threads */ - ci=0; - for (nth=0; nthNbase)*(t->tilesz); - double fdelta2=t->fdelta*0.5; - for (ci=0; ciNb; ci++) { - /* get station ids */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - /* get timeslot */ - tslot=(ci+t->boff)/t->Nbase; -#ifdef DEBUG - if (tslot>t->tilesz) { - fprintf(stderr,"%s: %d: timeslot exceed available timeslots\n",__FILE__,__LINE__); - exit(1); - } -#endif - - /* iterate over the sky model and calculate contribution */ - /* if this baseline is flagged, we do not compute */ - cm=t->clus; /* only 1 cluster */ - /* iterate over frequencies */ - for (cf=0; cfNchan; cf++) { - freq0=t->freqs[cf]; -/***********************************************/ - /* calculate coherencies for each freq */ - memset(C,0,sizeof(complex double)*4); - /* setup memory */ - if (posix_memalign((void*)&PHr,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&PHi,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&G,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&II,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&QQ,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&UU,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&VV,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - /* phase (real,imag) parts */ - /* note u=u/c, v=v/c, w=w/c here */ - /* phterm is 2pi(u/c l +v/c m +w/c n) */ - for (cn=0; cncarr[cm].N; cn++) { - G[cn]=2.0*M_PI*(t->u[ci]*t->carr[cm].ll[cn]+t->v[ci]*t->carr[cm].mm[cn]+t->w[ci]*t->carr[cm].nn[cn]); - } - for (cn=0; cncarr[cm].N; cn++) { - sincos(G[cn]*freq0,&PHi[cn],&PHr[cn]); - } - - /* term due to shape of source, also multiplied by freq/time smearing */ - for (cn=0; cncarr[cm].N; cn++) { - /* freq smearing : extra term delta * sinc(delta/2 * phterm) */ - if (G[cn]!=0.0) { - double smfac=G[cn]*fdelta2; - double sinph=sin(smfac)/smfac; - G[cn]=fabs(sinph); - } else { - G[cn]=1.0; - } - /* get array factor for these 2 stations, at given time */ - double af1=t->arrayfactor[cn*(t->N*t->tilesz*t->Nchan)+cf*(t->N*t->tilesz)+tslot*t->N+sta1]; - double af2=t->arrayfactor[cn*(t->N*t->tilesz*t->Nchan)+cf*(t->N*t->tilesz)+tslot*t->N+sta2]; - G[cn] *=af1*af2; - } - - /* multiply (re,im) phase term with smearing/shape factor */ - for (cn=0; cncarr[cm].N; cn++) { - PHr[cn]*=G[cn]; - PHi[cn]*=G[cn]; - } - - - for (cn=0; cncarr[cm].N; cn++) { - /* check if source type is not a point source for additional - calculations */ - if (t->carr[cm].stype[cn]!=STYPE_POINT) { - complex double sterm=PHr[cn]+_Complex_I*PHi[cn]; - if (t->carr[cm].stype[cn]==STYPE_SHAPELET) { - sterm*=shapelet_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } else if (t->carr[cm].stype[cn]==STYPE_GAUSSIAN) { - sterm*=gaussian_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } else if (t->carr[cm].stype[cn]==STYPE_DISK) { - sterm*=disk_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } else if (t->carr[cm].stype[cn]==STYPE_RING) { - sterm*=ring_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } - PHr[cn]=creal(sterm); - PHi[cn]=cimag(sterm); - } - - } - - - /* flux of each source, at each freq */ - for (cn=0; cncarr[cm].N; cn++) { - /* coherencies are NOT scaled by 1/2, with spectral index */ - if (t->carr[cm].spec_idx[cn]!=0.0) { - double fratio=log(freq0/t->carr[cm].f0[cn]); - double fratio1=fratio*fratio; - double fratio2=fratio1*fratio; - double tempfr=t->carr[cm].spec_idx[cn]*fratio+t->carr[cm].spec_idx1[cn]*fratio1+t->carr[cm].spec_idx2[cn]*fratio2; - /* catch -ve and 0 sI */ - if (t->carr[cm].sI0[cn]>0.0) { - II[cn]=exp(log(t->carr[cm].sI0[cn])+tempfr); - } else { - II[cn]=(t->carr[cm].sI0[cn]==0.0?0.0:-exp(log(-t->carr[cm].sI0[cn])+tempfr)); - } - if (t->carr[cm].sQ0[cn]>0.0) { - QQ[cn]=exp(log(t->carr[cm].sQ0[cn])+tempfr); - } else { - QQ[cn]=(t->carr[cm].sQ0[cn]==0.0?0.0:-exp(log(-t->carr[cm].sQ0[cn])+tempfr)); - } - if (t->carr[cm].sU0[cn]>0.0) { - UU[cn]=exp(log(t->carr[cm].sU0[cn])+tempfr); - } else { - UU[cn]=(t->carr[cm].sU0[cn]==0.0?0.0:-exp(log(-t->carr[cm].sU0[cn])+tempfr)); - } - if (t->carr[cm].sV0[cn]>0.0) { - VV[cn]=exp(log(t->carr[cm].sV0[cn])+tempfr); - } else { - VV[cn]=(t->carr[cm].sV0[cn]==0.0?0.0:-exp(log(-t->carr[cm].sV0[cn])+tempfr)); - } - } else { - II[cn]=t->carr[cm].sI[cn]; - QQ[cn]=t->carr[cm].sQ[cn]; - UU[cn]=t->carr[cm].sU[cn]; - VV[cn]=t->carr[cm].sV[cn]; - } - } - - /* add up terms together */ - for (cn=0; cncarr[cm].N; cn++) { - complex double Ph,IIl,QQl,UUl,VVl; - Ph=(PHr[cn]+_Complex_I*PHi[cn]); - IIl=Ph*II[cn]; - QQl=Ph*QQ[cn]; - UUl=Ph*UU[cn]; - VVl=Ph*VV[cn]; - C[0]+=IIl+QQl; - C[1]+=UUl+_Complex_I*VVl; - C[2]+=UUl-_Complex_I*VVl; - C[3]+=IIl-QQl; - } - - free(PHr); - free(PHi); - free(G); - free(II); - free(QQ); - free(UU); - free(VV); - - -/***********************************************/ - /* add to baseline visibilities */ - t->x[8*ci+cf*Ntilebase*8]+=creal(C[0]); - t->x[8*ci+1+cf*Ntilebase*8]+=cimag(C[0]); - t->x[8*ci+2+cf*Ntilebase*8]+=creal(C[1]); - t->x[8*ci+3+cf*Ntilebase*8]+=cimag(C[1]); - t->x[8*ci+4+cf*Ntilebase*8]+=creal(C[2]); - t->x[8*ci+5+cf*Ntilebase*8]+=cimag(C[2]); - t->x[8*ci+6+cf*Ntilebase*8]+=creal(C[3]); - t->x[8*ci+7+cf*Ntilebase*8]+=cimag(C[3]); - } - - } - return NULL; -} - - - - -int -predict_visibilities_multifreq_withbeam(double *u,double *v,double *w,double *x,int N,int Nbase,int tilesz,baseline_t *barr, clus_source_t *carr, int M,double *freqs,int Nchan, double fdelta,double tdelta, double dec0, -double ph_ra0, double ph_dec0, double ph_freq0, double *longitude, double *latitude, double *time_utc, int *Nelem, double **xx, double **yy, double **zz, int Nt, int add_to_data) { - int nth,nth1,ci,ncl,Ns0; - - int Nthb0,Nthb; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_base_t *threaddata; - double *beamgain; - thread_data_arrayfac_t *beamdata; - - int Nbase1=Nbase*tilesz; - - /* calculate min baselines a thread can handle */ - Nthb0=(Nbase1+Nt-1)/Nt; - - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((threaddata=(thread_data_base_t*)malloc((size_t)Nt*sizeof(thread_data_base_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((beamdata=(thread_data_arrayfac_t*)malloc((size_t)Nt*sizeof(thread_data_arrayfac_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - - if (!add_to_data) { - /* set output column to zero */ - memset(x,0,sizeof(double)*8*Nbase*tilesz*Nchan); - } - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthfdelta*0.5; - - complex double C[4],G1[4],G2[4],T1[4],T2[4]; - int Ntilebase=(t->Nbase)*(t->tilesz); - int px; - for (ci=0; ciNb; ci++) { - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - /* get timeslot */ - tslot=(ci+t->boff)/t->Nbase; -#ifdef DEBUG - if (tslot>t->tilesz) { - fprintf(stderr,"%s: %d: timeslot exceed available timeslots\n",__FILE__,__LINE__); - exit(1); - } -#endif - - int cmt=t->clus; /* only 1 cluster, assumed positive */ - /* check if cluster id >=0 to do a subtraction */ - if (cmt>=0 && t->carr[cmt].id>=0) { - cm=cmt; - /* gains for this cluster, for sta1,sta2 */ - /* depending on the chunk size and the baseline index, - select right set of parameters - data x=[0,........,Nbase*tilesz] - divided into nchunk chunks - p[0] -> x[0.....Nbase*tilesz/nchunk-1] - p[1] -> x[Nbase*tilesz/nchunk......2*Nbase*tilesz-1] - .... - p[last] -> x[(nchunk-1)*Nbase*tilesz/nchunk......Nbase*tilesz] - - so given bindex, right p[] is bindex/((Nbase*tilesz+nchunk-1)/nchunk) - */ - px=(ci+t->boff)/((Ntilebase+t->carr[cmt].nchunk-1)/t->carr[cmt].nchunk); - //printf("base %d, cluster %d, parm off %d abs %d\n",t->bindex[ci],cm,px,t->carr[cm].p[px]); - pm=&(t->p[t->carr[cmt].p[px]]); - G1[0]=(pm[sta1*8])+_Complex_I*(pm[sta1*8+1]); - G1[1]=(pm[sta1*8+2])+_Complex_I*(pm[sta1*8+3]); - G1[2]=(pm[sta1*8+4])+_Complex_I*(pm[sta1*8+5]); - G1[3]=(pm[sta1*8+6])+_Complex_I*(pm[sta1*8+7]); - G2[0]=(pm[sta2*8])+_Complex_I*(pm[sta2*8+1]); - G2[1]=(pm[sta2*8+2])+_Complex_I*(pm[sta2*8+3]); - G2[2]=(pm[sta2*8+4])+_Complex_I*(pm[sta2*8+5]); - G2[3]=(pm[sta2*8+6])+_Complex_I*(pm[sta2*8+7]); - - - /* iterate over frequencies */ - for (cf=0; cfNchan; cf++) { - freq0=t->freqs[cf]; -/***********************************************/ - /* calculate coherencies for each freq */ - memset(C,0,sizeof(complex double)*4); - /* setup memory */ - if (posix_memalign((void*)&PHr,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&PHi,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&G,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&II,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&QQ,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&UU,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&VV,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - /* phase (real,imag) parts */ - /* note u=u/c, v=v/c, w=w/c here */ - /* phterm is 2pi(u/c l +v/c m +w/c n) */ - for (cn=0; cncarr[cm].N; cn++) { - G[cn]=2.0*M_PI*(t->u[ci]*t->carr[cm].ll[cn]+t->v[ci]*t->carr[cm].mm[cn]+t->w[ci]*t->carr[cm].nn[cn]); - } - for (cn=0; cncarr[cm].N; cn++) { - sincos(G[cn]*freq0,&PHi[cn],&PHr[cn]); - } - - /* term due to shape of source, also multiplied by freq/time smearing */ - for (cn=0; cncarr[cm].N; cn++) { - /* freq smearing : extra term delta * sinc(delta/2 * phterm) */ - if (G[cn]!=0.0) { - double smfac=G[cn]*fdelta2; - double sinph=sin(smfac)/smfac; - G[cn]=fabs(sinph); - } else { - G[cn]=1.0; - } - /* get array factor for these 2 stations, at given time */ - double af1=t->arrayfactor[cn*(t->N*t->tilesz*t->Nchan)+cf*(t->N*t->tilesz)+tslot*t->N+sta1]; - double af2=t->arrayfactor[cn*(t->N*t->tilesz*t->Nchan)+cf*(t->N*t->tilesz)+tslot*t->N+sta2]; - G[cn] *=af1*af2; - } - - /* multiply (re,im) phase term with smearing/shape factor */ - for (cn=0; cncarr[cm].N; cn++) { - PHr[cn]*=G[cn]; - PHi[cn]*=G[cn]; - } - - - for (cn=0; cncarr[cm].N; cn++) { - /* check if source type is not a point source for additional - calculations */ - if (t->carr[cm].stype[cn]!=STYPE_POINT) { - complex double sterm=PHr[cn]+_Complex_I*PHi[cn]; - if (t->carr[cm].stype[cn]==STYPE_SHAPELET) { - sterm*=shapelet_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } else if (t->carr[cm].stype[cn]==STYPE_GAUSSIAN) { - sterm*=gaussian_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } else if (t->carr[cm].stype[cn]==STYPE_DISK) { - sterm*=disk_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } else if (t->carr[cm].stype[cn]==STYPE_RING) { - sterm*=ring_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } - PHr[cn]=creal(sterm); - PHi[cn]=cimag(sterm); - } - - } - - - /* flux of each source, at each freq */ - for (cn=0; cncarr[cm].N; cn++) { - /* coherencies are NOT scaled by 1/2, with spectral index */ - if (t->carr[cm].spec_idx[cn]!=0.0) { - double fratio=log(freq0/t->carr[cm].f0[cn]); - double fratio1=fratio*fratio; - double fratio2=fratio1*fratio; - double tempfr=t->carr[cm].spec_idx[cn]*fratio+t->carr[cm].spec_idx1[cn]*fratio1+t->carr[cm].spec_idx2[cn]*fratio2; - /* catch -ve and 0 sI */ - if (t->carr[cm].sI0[cn]>0.0) { - II[cn]=exp(log(t->carr[cm].sI0[cn])+tempfr); - } else { - II[cn]=(t->carr[cm].sI0[cn]==0.0?0.0:-exp(log(-t->carr[cm].sI0[cn])+tempfr)); - } - if (t->carr[cm].sQ0[cn]>0.0) { - QQ[cn]=exp(log(t->carr[cm].sQ0[cn])+tempfr); - } else { - QQ[cn]=(t->carr[cm].sQ0[cn]==0.0?0.0:-exp(log(-t->carr[cm].sQ0[cn])+tempfr)); - } - if (t->carr[cm].sU0[cn]>0.0) { - UU[cn]=exp(log(t->carr[cm].sU0[cn])+tempfr); - } else { - UU[cn]=(t->carr[cm].sU0[cn]==0.0?0.0:-exp(log(-t->carr[cm].sU0[cn])+tempfr)); - } - if (t->carr[cm].sV0[cn]>0.0) { - VV[cn]=exp(log(t->carr[cm].sV0[cn])+tempfr); - } else { - VV[cn]=(t->carr[cm].sV0[cn]==0.0?0.0:-exp(log(-t->carr[cm].sV0[cn])+tempfr)); - } - } else { - II[cn]=t->carr[cm].sI[cn]; - QQ[cn]=t->carr[cm].sQ[cn]; - UU[cn]=t->carr[cm].sU[cn]; - VV[cn]=t->carr[cm].sV[cn]; - } - } - - /* add up terms together */ - for (cn=0; cncarr[cm].N; cn++) { - complex double Ph,IIl,QQl,UUl,VVl; - Ph=(PHr[cn]+_Complex_I*PHi[cn]); - IIl=Ph*II[cn]; - QQl=Ph*QQ[cn]; - UUl=Ph*UU[cn]; - VVl=Ph*VV[cn]; - C[0]+=IIl+QQl; - C[1]+=UUl+_Complex_I*VVl; - C[2]+=UUl-_Complex_I*VVl; - C[3]+=IIl-QQl; - } - - - free(PHr); - free(PHi); - free(G); - free(II); - free(QQ); - free(UU); - free(VV); - - -/***********************************************/ - /* form G1*C*G2' */ - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - - /* subtract from baseline visibilities */ - t->x[8*ci+cf*Ntilebase*8]-=creal(T2[0]); - t->x[8*ci+1+cf*Ntilebase*8]-=cimag(T2[0]); - t->x[8*ci+2+cf*Ntilebase*8]-=creal(T2[1]); - t->x[8*ci+3+cf*Ntilebase*8]-=cimag(T2[1]); - t->x[8*ci+4+cf*Ntilebase*8]-=creal(T2[2]); - t->x[8*ci+5+cf*Ntilebase*8]-=cimag(T2[2]); - t->x[8*ci+6+cf*Ntilebase*8]-=creal(T2[3]); - t->x[8*ci+7+cf*Ntilebase*8]-=cimag(T2[3]); - } - } - /* if -ve cluster is given (only once), final correction is done */ - if (cmt<0 && t->pinv) { - cm=t->ccid; - px=(ci+t->boff)/((Ntilebase+t->carr[cm].nchunk-1)/t->carr[cm].nchunk); - pm=&(t->pinv[8*t->N*px]); - G1[0]=(pm[sta1*8])+_Complex_I*(pm[sta1*8+1]); - G1[1]=(pm[sta1*8+2])+_Complex_I*(pm[sta1*8+3]); - G1[2]=(pm[sta1*8+4])+_Complex_I*(pm[sta1*8+5]); - G1[3]=(pm[sta1*8+6])+_Complex_I*(pm[sta1*8+7]); - G2[0]=(pm[sta2*8])+_Complex_I*(pm[sta2*8+1]); - G2[1]=(pm[sta2*8+2])+_Complex_I*(pm[sta2*8+3]); - G2[2]=(pm[sta2*8+4])+_Complex_I*(pm[sta2*8+5]); - G2[3]=(pm[sta2*8+6])+_Complex_I*(pm[sta2*8+7]); - - /* iterate over frequencies */ - for (cf=0; cfNchan; cf++) { - /* now do correction, if any */ - C[0]=t->x[8*ci+cf*Ntilebase*8]+_Complex_I*t->x[8*ci+1+cf*Ntilebase*8]; - C[1]=t->x[8*ci+2+cf*Ntilebase*8]+_Complex_I*t->x[8*ci+3+cf*Ntilebase*8]; - C[2]=t->x[8*ci+4+cf*Ntilebase*8]+_Complex_I*t->x[8*ci+5+cf*Ntilebase*8]; - C[3]=t->x[8*ci+6+cf*Ntilebase*8]+_Complex_I*t->x[8*ci+7+cf*Ntilebase*8]; - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - t->x[8*ci+cf*Ntilebase*8]=creal(T2[0]); - t->x[8*ci+1+cf*Ntilebase*8]=cimag(T2[0]); - t->x[8*ci+2+cf*Ntilebase*8]=creal(T2[1]); - t->x[8*ci+3+cf*Ntilebase*8]=cimag(T2[1]); - t->x[8*ci+4+cf*Ntilebase*8]=creal(T2[2]); - t->x[8*ci+5+cf*Ntilebase*8]=cimag(T2[2]); - t->x[8*ci+6+cf*Ntilebase*8]=creal(T2[3]); - t->x[8*ci+7+cf*Ntilebase*8]=cimag(T2[3]); - } - } - } - return NULL; -} - - -int -calculate_residuals_multifreq_withbeam(double *u,double *v,double *w,double *p,double *x,int N,int Nbase,int tilesz,baseline_t *barr, clus_source_t *carr, int M,double *freqs,int Nchan, double fdelta,double tdelta,double dec0, -double ph_ra0, double ph_dec0, double ph_freq0, double *longitude, double *latitude, double *time_utc,int *Nelem, double **xx, double **yy, double **zz, int Nt, int ccid, double rho, int phase_only) { - int nth,nth1,ci,cj,ncl,Ns0; - - int Nthb0,Nthb; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_base_t *threaddata; - double *beamgain; - thread_data_arrayfac_t *beamdata; - - int Nbase1=Nbase*tilesz; - - int cm; - double *pm,*pinv=0,*pphase=0; - cm=-1; - /* find if any cluster is specified for correction of data */ - for (cj=0; cj=0) { /* valid cluser for correction */ - /* allocate memory for inverse J */ - if ((pinv=(double*)malloc((size_t)8*N*carr[cm].nchunk*sizeof(double)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (!phase_only) { - for (cj=0; cjsoff; nclsoff+t->Ns; ncl++) { - for (ci=0; cicarr[ncl].N; ci++) { - precession(t->carr[ncl].ra[ci], t->carr[ncl].dec[ci],t->Tr,&newra,&newdec); - t->carr[ncl].ra[ci]=newra; - t->carr[ncl].dec[ci]=newdec; - } - } - return NULL; -} - -int -precess_source_locations(double jd_tdb, clus_source_t *carr, int M, double *ra_beam, double *dec_beam, int Nt) { - - int nth,ci; - - int Nthb0,Nthb; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_precess_t *threaddata; - - /* calculate min clusters thread can handle */ - Nthb0=(M+Nt-1)/Nt; - - /* setup threads : note: Ngpu is no of GPUs used */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((threaddata=(thread_data_precess_t*)malloc((size_t)Nt*sizeof(thread_data_precess_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - double Tr[9]; - get_precession_params(jd_tdb,Tr); - - /* set common parameters, and split clusters to threads */ - ci=0; - for (nth=0; nth - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - - -#include "sagecal.h" - -static void -checkCudaError(cudaError_t err, char *file, int line) -{ -#ifdef CUDA_DEBUG - if(!err) - return; - fprintf(stderr,"GPU (CUDA): %s %s %d\n", cudaGetErrorString(err),file,line); - exit(EXIT_FAILURE); -#endif -} - - -/* struct to pass data to worker threads attached to GPUs */ -typedef struct thread_data_pred_t_ { - int tid; /* this thread id */ - taskhist *hst; /* for load balancing GPUs */ - double *u,*v,*w; /* uvw coords */ - complex double *coh; /* coherencies for M clusters */ - double *x; /* data/residual vector */ - int N; /* stations */ - int Nbase; /* total baselines (N-1)N/2 x tilesz */ - baseline_t *barr; /* baseline info */ - clus_source_t *carr; /* Mx1 cluster data */ - int M; /* no of clusters */ - int Nf; /* of of freqs */ - double *freqs; /* Nfx1 freqs for prediction */ - double fdelta; /* bandwidth */ - double tdelta; /* integration time */ - double dec0; /* phase center dec */ - - /* following used in beam prediction */ - int dobeam; - double ph_ra0,ph_dec0; /* beam pointing */ - double ph_freq0; /* beam central freq */ - double *longitude,*latitude; /* Nx1 array of station locations */ - double *time_utc; /* tileszx1 array of time */ - int tilesz; - int *Nelem; /* Nx1 array of station sizes */ - double **xx,**yy,**zz; /* Nx1 arrays of station element coords */ - - int Ns; /* total no of sources (clusters) per thread */ - int soff; /* starting source for this thread */ - - /* following are only used while predict with gain */ - double *p; /* parameter array, size could be 8*N*Mx1 (full) or 8*Nx1 (single)*/ - -} thread_data_pred_t; - - -/* copy Nx1 double array x to device as float - first allocate device memory */ -static void -dtofcopy(int N, float **x_d, double *x) { - float *xhost; - cudaError_t err; - /* first alloc pinned temp buffer */ - err=cudaMallocHost((void**)&xhost,sizeof(float)*N); - checkCudaError(err,__FILE__,__LINE__); - /* double to float */ - int ci; - for (ci=0; ciM<=MAX_GPU_ID) { - card=select_work_gpu(MAX_GPU_ID,t->hst); - } else { - card=t->tid; - } - cudaError_t err; - int ci,ncl,cj; - - err=cudaSetDevice(card); - checkCudaError(err,__FILE__,__LINE__); - - float *ud,*vd,*wd,*cohd; - baseline_t *barrd; - float *freqsd; - float *longd,*latd; double *timed; - int *Nelemd; - float **xx_p,**yy_p,**zz_p; - float **xxd,**yyd,**zzd; - /* allocate memory in GPU */ - err=cudaMalloc((void**) &cohd, t->Nbase*8*sizeof(float)); /* coherencies only for 1 cluster, Nf=1 */ - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**) &barrd, t->Nbase*sizeof(baseline_t)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**) &Nelemd, t->N*sizeof(int)); - checkCudaError(err,__FILE__,__LINE__); - - /* copy to device */ - dtofcopy(t->Nbase,&ud,t->u); - dtofcopy(t->Nbase,&vd,t->v); - dtofcopy(t->Nbase,&wd,t->w); - err=cudaMemcpy(barrd, t->barr, t->Nbase*sizeof(baseline_t), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - - dtofcopy(t->Nf,&freqsd,t->freqs); - dtofcopy(t->N,&longd,t->longitude); - dtofcopy(t->N,&latd,t->latitude); - err=cudaMalloc((void**) &timed, t->tilesz*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMemcpy(timed, t->time_utc, t->tilesz*sizeof(double), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - /* convert time jd to GMST angle */ - cudakernel_convert_time(t->tilesz,timed); - - err=cudaMemcpy(Nelemd, t->Nelem, t->N*sizeof(int), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - - /* temp host storage to copy coherencies */ - complex float *tempcoh; - if ((tempcoh=(complex float*)calloc((size_t)t->Nbase*4,sizeof(complex float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - - - /* jagged arrays for element locations */ - err=cudaMalloc((void**)&xxd, t->N*sizeof(int*)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&yyd, t->N*sizeof(int*)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&zzd, t->N*sizeof(int*)); - checkCudaError(err,__FILE__,__LINE__); - /* allocate host memory to store pointers */ - if ((xx_p=(float**)calloc((size_t)t->N,sizeof(int*)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((yy_p=(float**)calloc((size_t)t->N,sizeof(int*)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((zz_p=(float**)calloc((size_t)t->N,sizeof(int*)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - for (ci=0; ciN; ci++) { - err=cudaMalloc((void**)&xx_p[ci], t->Nelem[ci]*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&yy_p[ci], t->Nelem[ci]*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&zz_p[ci], t->Nelem[ci]*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } - /* now copy data */ - for (ci=0; ciN; ci++) { - dtofcopy(t->Nelem[ci],&xx_p[ci],t->xx[ci]); - dtofcopy(t->Nelem[ci],&yy_p[ci],t->yy[ci]); - dtofcopy(t->Nelem[ci],&zz_p[ci],t->zz[ci]); - } - /* now copy pointer locations to device */ - err=cudaMemcpy(xxd, xx_p, t->N*sizeof(int*), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMemcpy(yyd, yy_p, t->N*sizeof(int*), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMemcpy(zzd, zz_p, t->N*sizeof(int*), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - - - float *beamd; - float *lld,*mmd,*nnd,*sId,*rad,*decd; - unsigned char *styped; - float *sI0d,*f0d,*spec_idxd,*spec_idx1d,*spec_idx2d; - int **host_p,**dev_p; -/******************* begin loop over clusters **************************/ - for (ncl=t->soff; nclsoff+t->Ns; ncl++) { - /* allocate memory for this clusters beam */ - err=cudaMalloc((void**)&beamd, t->N*t->tilesz*t->carr[ncl].N*t->Nf*sizeof(float)); - checkCudaError(err,__FILE__,__LINE__); - - - /* copy cluster details to GPU */ - err=cudaMalloc((void**)&styped, t->carr[ncl].N*sizeof(unsigned char)); - checkCudaError(err,__FILE__,__LINE__); - - dtofcopy(t->carr[ncl].N,&lld,t->carr[ncl].ll); - dtofcopy(t->carr[ncl].N,&mmd,t->carr[ncl].mm); - dtofcopy(t->carr[ncl].N,&nnd,t->carr[ncl].nn); - dtofcopy(t->carr[ncl].N,&sId,t->carr[ncl].sI); - dtofcopy(t->carr[ncl].N,&rad,t->carr[ncl].ra); - dtofcopy(t->carr[ncl].N,&decd,t->carr[ncl].dec); - err=cudaMemcpy(styped, t->carr[ncl].stype, t->carr[ncl].N*sizeof(unsigned char), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - - /* for multi channel data */ - dtofcopy(t->carr[ncl].N,&sI0d,t->carr[ncl].sI0); - dtofcopy(t->carr[ncl].N,&f0d,t->carr[ncl].f0); - dtofcopy(t->carr[ncl].N,&spec_idxd,t->carr[ncl].spec_idx); - dtofcopy(t->carr[ncl].N,&spec_idx1d,t->carr[ncl].spec_idx1); - dtofcopy(t->carr[ncl].N,&spec_idx2d,t->carr[ncl].spec_idx2); - - /* extra info for source, if any */ - if ((host_p=(int**)calloc((size_t)t->carr[ncl].N,sizeof(int*)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - err=cudaMalloc((void**)&dev_p, t->carr[ncl].N*sizeof(int*)); - checkCudaError(err,__FILE__,__LINE__); - - - for (cj=0; cjcarr[ncl].N; cj++) { - - if (t->carr[ncl].stype[cj]==STYPE_POINT) { - host_p[cj]=0; - } else if (t->carr[ncl].stype[cj]==STYPE_SHAPELET) { - exinfo_shapelet *d=(exinfo_shapelet*)t->carr[ncl].ex[cj]; - err=cudaMalloc((void**)&host_p[cj], sizeof(exinfo_shapelet)); - checkCudaError(err,__FILE__,__LINE__); - double *modes; - err=cudaMalloc((void**)&modes, d->n0*d->n0*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMemcpy(host_p[cj], d, sizeof(exinfo_shapelet), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMemcpy(modes, d->modes, d->n0*d->n0*sizeof(double), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - exinfo_shapelet *d_p=(exinfo_shapelet *)host_p[cj]; - err=cudaMemcpy(&(d_p->modes), &modes, sizeof(double*), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - } else if (t->carr[ncl].stype[cj]==STYPE_GAUSSIAN) { - exinfo_gaussian *d=(exinfo_gaussian*)t->carr[ncl].ex[cj]; - err=cudaMalloc((void**)&host_p[cj], sizeof(exinfo_gaussian)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMemcpy(host_p[cj], d, sizeof(exinfo_gaussian), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - } else if (t->carr[ncl].stype[cj]==STYPE_DISK) { - exinfo_disk *d=(exinfo_disk*)t->carr[ncl].ex[cj]; - err=cudaMalloc((void**)&host_p[cj], sizeof(exinfo_disk)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMemcpy(host_p[cj], d, sizeof(exinfo_disk), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - } else if (t->carr[ncl].stype[cj]==STYPE_RING) { - exinfo_ring *d=(exinfo_ring*)t->carr[ncl].ex[cj]; - err=cudaMalloc((void**)&host_p[cj], sizeof(exinfo_ring)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMemcpy(host_p[cj], d, sizeof(exinfo_ring), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - } - - - } - /* now copy pointer locations to device */ - err=cudaMemcpy(dev_p, host_p, t->carr[ncl].N*sizeof(int*), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - - - /* now calculate beam for all sources in this cluster */ - cudakernel_array_beam(t->N,t->tilesz,t->carr[ncl].N,t->Nf,freqsd,longd,latd,timed,Nelemd,xxd,yyd,zzd,rad,decd,(float)t->ph_ra0,(float)t->ph_dec0,(float)t->ph_freq0,beamd); - - - /* calculate coherencies for all sources in this cluster, add them up */ - cudakernel_coherencies(t->Nbase,t->N,t->tilesz,t->carr[ncl].N,t->Nf,ud,vd,wd,barrd,freqsd,beamd, - lld,mmd,nnd,sId,styped,sI0d,f0d,spec_idxd,spec_idx1d,spec_idx2d,dev_p,(float)t->fdelta,(float)t->tdelta,(float)t->dec0,cohd,t->dobeam); - - /* copy back coherencies to host, - coherencies on host have 8M stride, on device have 8 stride */ - err=cudaMemcpy((float*)tempcoh, cohd, sizeof(float)*t->Nbase*8, cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - complex double *tempdcoh; - if ((tempdcoh=(complex double*)calloc((size_t)t->Nbase*4,sizeof(complex double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - int di; - double *dcp=(double*)tempdcoh; - float *fcp=(float*)tempcoh; - for (di=0; diNbase; di++) { - dcp[8*di]=(double)fcp[8*di]; - dcp[8*di+1]=(double)fcp[8*di+1]; - dcp[8*di+2]=(double)fcp[8*di+2]; - dcp[8*di+3]=(double)fcp[8*di+3]; - dcp[8*di+4]=(double)fcp[8*di+4]; - dcp[8*di+5]=(double)fcp[8*di+5]; - dcp[8*di+6]=(double)fcp[8*di+6]; - dcp[8*di+7]=(double)fcp[8*di+7]; - } - /* now copy this with right offset and stride */ - my_ccopy(t->Nbase,&tempdcoh[0],4,&(t->coh[4*ncl]),4*t->M); - my_ccopy(t->Nbase,&tempdcoh[1],4,&(t->coh[4*ncl+1]),4*t->M); - my_ccopy(t->Nbase,&tempdcoh[2],4,&(t->coh[4*ncl+2]),4*t->M); - my_ccopy(t->Nbase,&tempdcoh[3],4,&(t->coh[4*ncl+3]),4*t->M); - free(tempdcoh); - - - for (cj=0; cjcarr[ncl].N; cj++) { - if (t->carr[ncl].stype[cj]==STYPE_POINT) { - } else if (t->carr[ncl].stype[cj]==STYPE_SHAPELET) { - exinfo_shapelet *d_p=(exinfo_shapelet *)host_p[cj]; - double *modes=0; - err=cudaMemcpy(&modes, &(d_p->modes), sizeof(double*), cudaMemcpyDeviceToHost); - err=cudaFree(modes); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(host_p[cj]); - checkCudaError(err,__FILE__,__LINE__); - } else if (t->carr[ncl].stype[cj]==STYPE_GAUSSIAN) { - err=cudaFree(host_p[cj]); - checkCudaError(err,__FILE__,__LINE__); - } else if (t->carr[ncl].stype[cj]==STYPE_DISK) { - err=cudaFree(host_p[cj]); - checkCudaError(err,__FILE__,__LINE__); - } else if (t->carr[ncl].stype[cj]==STYPE_RING) { - err=cudaFree(host_p[cj]); - checkCudaError(err,__FILE__,__LINE__); - } - } - free(host_p); - - err=cudaFree(dev_p); - checkCudaError(err,__FILE__,__LINE__); - - - err=cudaFree(beamd); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(lld); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(mmd); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(nnd); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(sId); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(rad); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(decd); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(styped); - checkCudaError(err,__FILE__,__LINE__); - - err=cudaFree(sI0d); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(f0d); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(spec_idxd); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(spec_idx1d); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(spec_idx2d); - checkCudaError(err,__FILE__,__LINE__); - } -/******************* end loop over clusters **************************/ - - free(tempcoh); - - /* free memory */ - err=cudaFree(ud); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(vd); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(wd); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(cohd); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(barrd); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(freqsd); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(longd); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(latd); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(timed); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(Nelemd); - checkCudaError(err,__FILE__,__LINE__); - - - for (ci=0; ciN; ci++) { - err=cudaFree(xx_p[ci]); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(yy_p[ci]); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(zz_p[ci]); - checkCudaError(err,__FILE__,__LINE__); - } - - err=cudaFree(xxd); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(yyd); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(zzd); - checkCudaError(err,__FILE__,__LINE__); - - free(xx_p); - free(yy_p); - free(zz_p); - - /* reset error state */ - err=cudaGetLastError(); - return NULL; - -} - -/* worker thread function to (re)set flags */ -static void * -resetflags_threadfn(void *data) { - thread_data_base_t *t=(thread_data_base_t*)data; - int ci; - for (ci=0; ciNb; ci++) { - if (!t->barr[ci+t->boff].flag) { - /* change the flag to 2 if baseline length is < uvmin or > uvmax */ - double uvdist=sqrt(t->u[ci]*t->u[ci]+t->v[ci]*t->v[ci])*t->freq0; - if (uvdistuvmin || uvdist>t->uvmax) { - t->barr[ci+t->boff].flag=2; - } - } - } - return NULL; -} - -int -precalculate_coherencies_withbeam_gpu(double *u, double *v, double *w, complex double *x, int N, - int Nbase, baseline_t *barr, clus_source_t *carr, int M, double freq0, double fdelta, double tdelta, double dec0, double uvmin, double uvmax, - double ph_ra0, double ph_dec0, double ph_freq0, double *longitude, double *latitude, double *time_utc, int tilesz, int *Nelem, double **xx, double **yy, double **zz, int dobeam, int Nt) { - - int nth,ci; - - int Nthb0,Nthb; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_pred_t *threaddata; - taskhist thst; - init_task_hist(&thst); - - int Ngpu; - if (M<4) { - Ngpu=2; - } else { - Ngpu=4; - } - - /* calculate min clusters thread can handle */ - Nthb0=(M+Ngpu-1)/Ngpu; - - /* setup threads : note: Ngpu is no of GPUs used */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Ngpu*sizeof(pthread_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((threaddata=(thread_data_pred_t*)malloc((size_t)Ngpu*sizeof(thread_data_pred_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - /* set common parameters, and split clusters to threads */ - ci=0; - for (nth=0; nthM<=MAX_GPU_ID) { - card=select_work_gpu(MAX_GPU_ID,t->hst); - } else { - card=t->tid; - } - cudaError_t err; - int ci,ncl,cj; - - err=cudaSetDevice(card); - checkCudaError(err,__FILE__,__LINE__); - - float *ud,*vd,*wd,*cohd; - baseline_t *barrd; - float *freqsd; - float *longd,*latd; double *timed; - int *Nelemd; - float **xx_p,**yy_p,**zz_p; - float **xxd,**yyd,**zzd; - /* allocate memory in GPU */ - err=cudaMalloc((void**) &cohd, t->Nbase*8*t->Nf*sizeof(float)); /* coherencies only for 1 cluster, Nf freq, used to store sum of clusters*/ - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**) &barrd, t->Nbase*sizeof(baseline_t)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**) &Nelemd, t->N*sizeof(int)); - checkCudaError(err,__FILE__,__LINE__); - - /* copy to device */ - dtofcopy(t->Nbase,&ud,t->u); - dtofcopy(t->Nbase,&vd,t->v); - dtofcopy(t->Nbase,&wd,t->w); - err=cudaMemcpy(barrd, t->barr, t->Nbase*sizeof(baseline_t), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - - dtofcopy(t->Nf,&freqsd,t->freqs); - dtofcopy(t->N,&longd,t->longitude); - dtofcopy(t->N,&latd,t->latitude); - err=cudaMalloc((void**) &timed, t->tilesz*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMemcpy(timed, t->time_utc, t->tilesz*sizeof(double), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - /* convert time jd to GMST angle */ - cudakernel_convert_time(t->tilesz,timed); - - err=cudaMemcpy(Nelemd, t->Nelem, t->N*sizeof(int), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - - /* jagged arrays for element locations */ - err=cudaMalloc((void**)&xxd, t->N*sizeof(int*)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&yyd, t->N*sizeof(int*)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&zzd, t->N*sizeof(int*)); - checkCudaError(err,__FILE__,__LINE__); - /* allocate host memory to store pointers */ - if ((xx_p=(float**)calloc((size_t)t->N,sizeof(int*)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((yy_p=(float**)calloc((size_t)t->N,sizeof(int*)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((zz_p=(float**)calloc((size_t)t->N,sizeof(int*)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - for (ci=0; ciN; ci++) { - err=cudaMalloc((void**)&xx_p[ci], t->Nelem[ci]*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&yy_p[ci], t->Nelem[ci]*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&zz_p[ci], t->Nelem[ci]*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } - /* now copy data */ - for (ci=0; ciN; ci++) { - dtofcopy(t->Nelem[ci],&xx_p[ci],t->xx[ci]); - dtofcopy(t->Nelem[ci],&yy_p[ci],t->yy[ci]); - dtofcopy(t->Nelem[ci],&zz_p[ci],t->zz[ci]); - } - /* now copy pointer locations to device */ - err=cudaMemcpy(xxd, xx_p, t->N*sizeof(int*), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMemcpy(yyd, yy_p, t->N*sizeof(int*), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMemcpy(zzd, zz_p, t->N*sizeof(int*), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - - - float *beamd; - float *lld,*mmd,*nnd,*sId,*rad,*decd; - unsigned char *styped; - float *sI0d,*f0d,*spec_idxd,*spec_idx1d,*spec_idx2d; - int **host_p,**dev_p; -/******************* begin loop over clusters **************************/ - for (ncl=t->soff; nclsoff+t->Ns; ncl++) { - /* allocate memory for this clusters beam */ - err=cudaMalloc((void**)&beamd, t->N*t->tilesz*t->carr[ncl].N*t->Nf*sizeof(float)); - checkCudaError(err,__FILE__,__LINE__); - - - /* copy cluster details to GPU */ - err=cudaMalloc((void**)&styped, t->carr[ncl].N*sizeof(unsigned char)); - checkCudaError(err,__FILE__,__LINE__); - - dtofcopy(t->carr[ncl].N,&lld,t->carr[ncl].ll); - dtofcopy(t->carr[ncl].N,&mmd,t->carr[ncl].mm); - dtofcopy(t->carr[ncl].N,&nnd,t->carr[ncl].nn); - dtofcopy(t->carr[ncl].N,&sId,t->carr[ncl].sI); - dtofcopy(t->carr[ncl].N,&rad,t->carr[ncl].ra); - dtofcopy(t->carr[ncl].N,&decd,t->carr[ncl].dec); - err=cudaMemcpy(styped, t->carr[ncl].stype, t->carr[ncl].N*sizeof(unsigned char), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - - /* for multi channel data */ - dtofcopy(t->carr[ncl].N,&sI0d,t->carr[ncl].sI0); - dtofcopy(t->carr[ncl].N,&f0d,t->carr[ncl].f0); - dtofcopy(t->carr[ncl].N,&spec_idxd,t->carr[ncl].spec_idx); - dtofcopy(t->carr[ncl].N,&spec_idx1d,t->carr[ncl].spec_idx1); - dtofcopy(t->carr[ncl].N,&spec_idx2d,t->carr[ncl].spec_idx2); - - /* extra info for source, if any */ - if ((host_p=(int**)calloc((size_t)t->carr[ncl].N,sizeof(int*)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - err=cudaMalloc((void**)&dev_p, t->carr[ncl].N*sizeof(int*)); - checkCudaError(err,__FILE__,__LINE__); - - - for (cj=0; cjcarr[ncl].N; cj++) { - - if (t->carr[ncl].stype[cj]==STYPE_POINT) { - host_p[cj]=0; - } else if (t->carr[ncl].stype[cj]==STYPE_SHAPELET) { - exinfo_shapelet *d=(exinfo_shapelet*)t->carr[ncl].ex[cj]; - err=cudaMalloc((void**)&host_p[cj], sizeof(exinfo_shapelet)); - checkCudaError(err,__FILE__,__LINE__); - double *modes; - err=cudaMalloc((void**)&modes, d->n0*d->n0*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMemcpy(host_p[cj], d, sizeof(exinfo_shapelet), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMemcpy(modes, d->modes, d->n0*d->n0*sizeof(double), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - exinfo_shapelet *d_p=(exinfo_shapelet *)host_p[cj]; - err=cudaMemcpy(&(d_p->modes), &modes, sizeof(double*), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - } else if (t->carr[ncl].stype[cj]==STYPE_GAUSSIAN) { - exinfo_gaussian *d=(exinfo_gaussian*)t->carr[ncl].ex[cj]; - err=cudaMalloc((void**)&host_p[cj], sizeof(exinfo_gaussian)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMemcpy(host_p[cj], d, sizeof(exinfo_gaussian), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - } else if (t->carr[ncl].stype[cj]==STYPE_DISK) { - exinfo_disk *d=(exinfo_disk*)t->carr[ncl].ex[cj]; - err=cudaMalloc((void**)&host_p[cj], sizeof(exinfo_disk)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMemcpy(host_p[cj], d, sizeof(exinfo_disk), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - } else if (t->carr[ncl].stype[cj]==STYPE_RING) { - exinfo_ring *d=(exinfo_ring*)t->carr[ncl].ex[cj]; - err=cudaMalloc((void**)&host_p[cj], sizeof(exinfo_ring)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMemcpy(host_p[cj], d, sizeof(exinfo_ring), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - } - - - } - /* now copy pointer locations to device */ - err=cudaMemcpy(dev_p, host_p, t->carr[ncl].N*sizeof(int*), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - - - /* now calculate beam for all sources in this cluster */ - cudakernel_array_beam(t->N,t->tilesz,t->carr[ncl].N,t->Nf,freqsd,longd,latd,timed,Nelemd,xxd,yyd,zzd,rad,decd,(float)t->ph_ra0,(float)t->ph_dec0,(float)t->ph_freq0,beamd); - - - /* calculate coherencies for all sources in this cluster, add them up */ - cudakernel_coherencies(t->Nbase,t->N,t->tilesz,t->carr[ncl].N,t->Nf,ud,vd,wd,barrd,freqsd,beamd, - lld,mmd,nnd,sId,styped,sI0d,f0d,spec_idxd,spec_idx1d,spec_idx2d,dev_p,(float)t->fdelta,(float)t->tdelta,(float)t->dec0,cohd,t->dobeam); - - /* copy back coherencies to host, - coherencies on host have 8M stride, on device have 8 stride */ - float *tempx; - if ((tempx=(float*)calloc((size_t)t->Nbase*8*t->Nf,sizeof(float)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - err=cudaMemcpy(tempx, cohd, sizeof(float)*t->Nbase*8*t->Nf, cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - /* copy back as double */ - int di; - for (di=0; diNbase*t->Nf; di++) { - t->x[8*di]=(double)tempx[8*di]; - t->x[8*di+1]=(double)tempx[8*di+1]; - t->x[8*di+2]=(double)tempx[8*di+2]; - t->x[8*di+3]=(double)tempx[8*di+3]; - t->x[8*di+4]=(double)tempx[8*di+4]; - t->x[8*di+5]=(double)tempx[8*di+5]; - t->x[8*di+6]=(double)tempx[8*di+6]; - t->x[8*di+7]=(double)tempx[8*di+7]; - } - free(tempx); - - - for (cj=0; cjcarr[ncl].N; cj++) { - if (t->carr[ncl].stype[cj]==STYPE_POINT) { - } else if (t->carr[ncl].stype[cj]==STYPE_SHAPELET) { - exinfo_shapelet *d_p=(exinfo_shapelet *)host_p[cj]; - double *modes=0; - err=cudaMemcpy(&modes, &(d_p->modes), sizeof(double*), cudaMemcpyDeviceToHost); - err=cudaFree(modes); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(host_p[cj]); - checkCudaError(err,__FILE__,__LINE__); - } else if (t->carr[ncl].stype[cj]==STYPE_GAUSSIAN) { - err=cudaFree(host_p[cj]); - checkCudaError(err,__FILE__,__LINE__); - } else if (t->carr[ncl].stype[cj]==STYPE_DISK) { - err=cudaFree(host_p[cj]); - checkCudaError(err,__FILE__,__LINE__); - } else if (t->carr[ncl].stype[cj]==STYPE_RING) { - err=cudaFree(host_p[cj]); - checkCudaError(err,__FILE__,__LINE__); - } - } - free(host_p); - - err=cudaFree(dev_p); - checkCudaError(err,__FILE__,__LINE__); - - - err=cudaFree(beamd); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(lld); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(mmd); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(nnd); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(sId); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(rad); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(decd); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(styped); - checkCudaError(err,__FILE__,__LINE__); - - err=cudaFree(sI0d); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(f0d); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(spec_idxd); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(spec_idx1d); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(spec_idx2d); - checkCudaError(err,__FILE__,__LINE__); - } -/******************* end loop over clusters **************************/ - - /* free memory */ - err=cudaFree(ud); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(vd); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(wd); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(cohd); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(barrd); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(freqsd); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(longd); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(latd); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(timed); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(Nelemd); - checkCudaError(err,__FILE__,__LINE__); - - - for (ci=0; ciN; ci++) { - err=cudaFree(xx_p[ci]); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(yy_p[ci]); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(zz_p[ci]); - checkCudaError(err,__FILE__,__LINE__); - } - - err=cudaFree(xxd); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(yyd); - checkCudaError(err,__FILE__,__LINE__); - err=cudaFree(zzd); - checkCudaError(err,__FILE__,__LINE__); - - free(xx_p); - free(yy_p); - free(zz_p); - - /* reset error state */ - err=cudaGetLastError(); - return NULL; - -} - -int -predict_visibilities_multifreq_withbeam_gpu(double *u,double *v,double *w,double *x,int N,int Nbase,int tilesz,baseline_t *barr, clus_source_t *carr, int M,double *freqs,int Nchan, double fdelta,double tdelta, double dec0, -double ph_ra0, double ph_dec0, double ph_freq0, double *longitude, double *latitude, double *time_utc, int *Nelem, double **xx, double **yy, double **zz, int dobeam, int Nt, int add_to_data) { - - int nth,ci; - - int Nthb0,Nthb; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_pred_t *threaddata; - taskhist thst; - init_task_hist(&thst); - - int Ngpu; - if (M<4) { - Ngpu=2; - } else { - Ngpu=4; - } - - /* calculate min clusters thread can handle */ - Nthb0=(M+Ngpu-1)/Ngpu; - - /* setup threads : note: Ngpu is no of GPUs used */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Ngpu*sizeof(pthread_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((threaddata=(thread_data_pred_t*)malloc((size_t)Ngpu*sizeof(thread_data_pred_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - /* arrays to store result */ - double *xlocal; - if ((xlocal=(double*)calloc((size_t)Nbase*8*tilesz*Nchan*Ngpu,sizeof(double)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - if (!add_to_data) { - /* set output column to zero */ - memset(x,0,sizeof(double)*Nbase*8*tilesz*Nchan); - } - - - - /* set common parameters, and split baselines to threads */ - ci=0; - for (nth=0; nth - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#include "sagecal.h" -#include - -//#define DEBUG - -/* key destroy function */ -static void -destroy_hash_key(gpointer data) { - free((char*)data); -} -/* value destroy function */ -static void -destroy_hash_value(gpointer data) { - sinfo_t *ss=(sinfo_t*)data; - free(ss); -} - -/* skips comment lines */ -static int -skip_lines(FILE *fin) -{ - - int c; - do { - if ( ( c = getc(fin) ) == EOF ) - return(-1); - /* handle empty lines */ - if ( c == '\n' ) - continue; /* next line */ - if ( (c != '#') ) { - ungetc(c,fin); - return(0); - } else { /* skip this line */ - do { - if ( ( c = getc(fin) ) == EOF ) - return(-1); - } while ( c != '\n') ; - } - } while( 1 ); -} - -/* skips rest of line */ -static int -skip_restof_line(FILE *fin) -{ - int c; - do { - if ( ( c = getc(fin) ) == EOF ) - return(-1); - } while ( c != '\n') ; - return(1); -} - - -/* reads the next string (isalphanumeric() contiguous set of characters) - separated by spaces, tabs or a newline. If the last character read is newline - 1 is returned, else 0 returned. */ -/* buffer is automatically adjusted is length is not enough */ -static int -read_next_string(char **buff, int *buff_len, FILE *infd) { - int k,c,flag; - k = 0; - /* intialize buffer */ - (*buff)[0]='\0'; - /* skip leading white space */ - do { - c=fgetc(infd); - /* also handle DOS end of line \r\n */ - if(c=='\n' || c=='\r' || c==EOF) return 1; - } while(c != EOF && isblank(c)); - if(c=='\n' || c=='\r' || c==EOF) return 1; - /* now we have read a non whitespace character */ - (*buff)[k++]=c; - if (k==*buff_len) { - /* now we have run out of buffer */ - *buff_len += 30; - if ((*buff = (char*)realloc((void*)(*buff),sizeof(char)*(size_t)(*buff_len)))==NULL) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - } - flag=0; - while ( ((c = fgetc(infd)) != EOF ) && k < *buff_len) { - if ( c == '\n' || c=='\r' ) { flag=1; break; } - if ( isblank(c) ) { break; }/* not end of line */ - (*buff)[k++] = c; - if (k==*buff_len) { - /* now we have run out of buffer */ - *buff_len += 30; - if((*buff = (char*)realloc((void*)(*buff),sizeof(char)*(size_t)(*buff_len)))==NULL) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - } - } - /* now c == blank , \n or EOF */ - if (k==*buff_len-1) { - /* now we have run out of buffer */ - *buff_len += 2; - if((*buff = (char*)realloc((void*)(*buff),sizeof(char)*(size_t)(*buff_len)))==NULL) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - } - - /* add '\0' to end */ - (*buff)[k++]='\0'; - return flag; -} - - - - -/* read shapalet mode file and build model */ -/* buff: source name, mode file will be buff.fits.modes - n0: model order, total modes will be n0*n0 - beta: scale - modes: n0*n0 array of model parameters, memory will be allocated -*/ -static int -read_shapelet_modes(char *buff,int *n0,double *beta,double **modes) { - char *input_modes; - int c,M,ci; - double ra_s,dec_s; - int ra_h,ra_m,dec_d,dec_m; - - FILE *cfp; - if((input_modes= (char*)malloc(sizeof(char)*(size_t)(strlen(buff)+strlen(".fits.modes")+1)))==NULL) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - strcpy(input_modes,buff); - strcpy((char*)&(input_modes[strlen(buff)]),".fits.modes\0"); - if ((cfp=fopen(input_modes,"r"))==0) { - fprintf(stderr,"%s: %d: no file %s\n",__FILE__,__LINE__,input_modes); - exit(1); - } - - /* read RA, Dec: ignored */ - c=fscanf(cfp,"%d %d %lf %d %d %lf",&ra_h,&ra_m,&ra_s,&dec_d,&dec_m,&dec_s); - - /* read modes, beta */ - c=fscanf(cfp,"%d %lf",n0,beta); - - /* there are n0*n0 values for modes */ - M=(*n0)*(*n0); - if ((*modes=(double*)calloc((size_t)M,sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - int retval; - for (ci=0; ci=0) { - /* we have a new line */ - memset(buff,0,buff_len); - /* first read cluster number */ - c=read_next_string(&buff,&buff_len,cfp); - clus=NULL; - if (c!=1) { - /* new cluster found */ - if ((clus= (clust_t*)malloc(sizeof(clust_t)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - sscanf(buff,"%d",&clus->id); - clus->slist=NULL; - } - /* next read no of chunks */ - memset(buff,0,buff_len); - c=read_next_string(&buff,&buff_len,cfp); - sscanf(buff,"%d",&clus->nchunk); - - while (c!=1) { - memset(buff,0,buff_len); - c=read_next_string(&buff,&buff_len,cfp); - if (strlen(buff)>0) { - /* source found for this cluster */ - if ((sclus= (clust_n*)malloc(sizeof(clust_n)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((sclus->name=(char*)malloc((size_t)(strlen(buff)+1)*sizeof(char)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - strcpy(sclus->name,buff); - clus->slist=g_list_prepend(clus->slist,sclus); - } - } - - /* add this cluster */ - clusters=g_list_prepend(clusters,clus); - c=skip_lines(cfp); - } - fclose(cfp); - - - /* now read the sky model */ - /* format: LSM format */ - /* ### Name | RA (hr,min,sec) | DEC (deg,min,sec) | I | Q | U | V | SI | RM | eX (rad) | eY (rad) | eP (rad) | ref_freq */ - /* NAME first letter : G/g Gaussian - D/d : disk - R/r : ring - S/s : shapelet - else: point - */ - if ((cfp=fopen(skymodel,"r"))==0) { - fprintf(stderr,"%s: %d: no file %s\n",__FILE__,__LINE__,skymodel); - exit(1); - } - - if ((buff = (char*)realloc((void*)(buff),sizeof(char)*(size_t)(MAX_SNAME)))==NULL) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - stable=g_hash_table_new_full(g_str_hash,g_str_equal,destroy_hash_key,destroy_hash_value); - c=skip_lines(cfp); - while(c>=0) { - if (format==0) { - c=fscanf(cfp,"%s %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf",buff,&rahr,&ramin,&rasec,&decd,&decmin,&decsec,&sI,&sQ,&sU,&sV,&spec_idx,&dummy_RM,&eX,&eY,&eP, &f0); - spec_idx1=spec_idx2=0.0; - } else { /* 3 order spectral idx */ - c=fscanf(cfp,"%s %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf",buff,&rahr,&ramin,&rasec,&decd,&decmin,&decsec,&sI,&sQ,&sU,&sV,&spec_idx,&spec_idx1,&spec_idx2,&dummy_RM,&eX,&eY,&eP, &f0); - } - - /* add this to hash table */ - if (c!=EOF && c>0) { - if ((hkey=(char*)malloc((size_t)(strlen(buff)+1)*sizeof(char)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - strcpy(hkey,buff); - if ((source=(sinfo_t*)malloc(sizeof(sinfo_t)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - /* calculate l,m */ - /* Rad=(hr+min/60+sec/60*60)*pi/12 */ - if (rahr<0.0) { - myra=-(-rahr+ramin/60.0+rasec/3600.0)*M_PI/12.0; - } else { - myra=(rahr+ramin/60.0+rasec/3600.0)*M_PI/12.0; - } - /* Rad=(hr+min/60+sec/60*60)*pi/180 */ - if (decd<0.0) { - mydec=-(-decd+decmin/60.0+decsec/3600.0)*M_PI/180.0; - } else { - mydec=(decd+decmin/60.0+decsec/3600.0)*M_PI/180.0; - } - /* convert to l,m: NOTE we use -l here */ - source->ll=cos(mydec)*sin(myra-ra0); - source->mm=sin(mydec)*cos(dec0)-cos(mydec)*sin(dec0)*cos(myra-ra0); - source->ra=myra; - source->dec=mydec; - - /* use spetral index, if != 0, to update sI to match data freq */ - if (spec_idx!=0.0) { - fratio=log(freq0/f0); - fratio1=fratio*fratio; - fratio2=fratio1*fratio; - /* catch -ve and 0 sI */ - if (sI>0.0) { - source->sI[0]=exp(log(sI)+spec_idx*fratio+spec_idx1*fratio1+spec_idx2*fratio2); - } else { - source->sI[0]=(sI==0.0?0.0:-exp(log(-sI)+spec_idx*fratio+spec_idx1*fratio1+spec_idx2*fratio2)); - } - if (sQ>0.0) { - source->sI[1]=exp(log(sQ)+spec_idx*fratio+spec_idx1*fratio1+spec_idx2*fratio2); - } else { - source->sI[1]=(sQ==0.0?0.0:-exp(log(-sQ)+spec_idx*fratio+spec_idx1*fratio1+spec_idx2*fratio2)); - } - if (sU>0.0) { - source->sI[2]=exp(log(sU)+spec_idx*fratio+spec_idx1*fratio1+spec_idx2*fratio2); - } else { - source->sI[2]=(sU==0.0?0.0:-exp(log(-sU)+spec_idx*fratio+spec_idx1*fratio1+spec_idx2*fratio2)); - } - if (sV>0.0) { - source->sI[3]=exp(log(sV)+spec_idx*fratio+spec_idx1*fratio1+spec_idx2*fratio2); - } else { - source->sI[3]=(sV==0.0?0.0:-exp(log(-sV)+spec_idx*fratio+spec_idx1*fratio1+spec_idx2*fratio2)); - } - - } else { - source->sI[0]=sI; - source->sI[1]=sQ; - source->sI[2]=sU; - source->sI[3]=sV; - } - source->sI0[0]=sI; /* original sI */ - source->sI0[1]=sQ; /* original sQ */ - source->sI0[2]=sU; /* original sU */ - source->sI0[3]=sV; /* original sV */ - source->f0=f0; - source->spec_idx=spec_idx; - source->spec_idx1=spec_idx1; - source->spec_idx2=spec_idx2; - - /* correction for projection, only for extended sources */ - /* calculate n */ - nn=sqrt(1.0-source->ll*source->ll-source->mm*source->mm); - /* calculate projection from [0,0,1] -> [l,m,n] */ - /* the whole story is: - [0,0,1]->[l,m,n] with - l=sin(phi)sin(xi), m=-sin(phi)cos(xi), n=cos(phi) so - phi=acos(n), xi=atan2(-l,m) and then map - [u,v,w] ->[ut,vt,wt] with - |cos(xi) -cos(phi)sin(xi) sin(phi)sin(xi)| - |sin(xi) cos(phi)cos(xi) -sin(phi)cos(xi)| - |0 sin(phi) cos(phi) | - */ - //printf("nn=%lf\n",nn); - phi=acos(nn); - xi=atan2(-source->ll,source->mm); - - /* determine source type */ - if (buff[0]=='G' || buff[0]=='g') { - source->stype=STYPE_GAUSSIAN; - if((exg=(exinfo_gaussian *)malloc(sizeof(exinfo_gaussian)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - exg->eX=2.0*eX; /* scale by 2 */ - exg->eY=2.0*eY; - exg->eP=eP; - /* negate angles */ - exg->cxi=cos(xi); - exg->sxi=sin(-xi); - exg->cphi=cos(phi); - exg->sphi=sin(-phi); - if (nnuse_projection=1; - } else { - exg->use_projection=0; - } - source->exdata=(void*)exg; - - } else if (buff[0]=='D' || buff[0]=='d') { - source->stype=STYPE_DISK; - if((exd=(exinfo_disk*)malloc(sizeof(exinfo_disk)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - exd->eX=eX; - /* negate angles */ - exd->cxi=cos(xi); - exd->sxi=sin(-xi); - exd->cphi=cos(phi); - exd->sphi=sin(-phi); - if (nnuse_projection=1; - } else { - exd->use_projection=0; - } - source->exdata=(void*)exd; - - } else if (buff[0]=='R' || buff[0]=='r') { - source->stype=STYPE_RING; - if((exr=(exinfo_ring*)malloc(sizeof(exinfo_ring)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - exr->eX=eX; - /* negate angles */ - exr->cxi=cos(xi); - exr->sxi=sin(-xi); - exr->cphi=cos(phi); - exr->sphi=sin(-phi); - if (nnuse_projection=1; - } else { - exr->use_projection=0; - } - source->exdata=(void*)exr; - - } else if (buff[0]=='S' || buff[0]=='s') { - source->stype=STYPE_SHAPELET; - if((exs=(exinfo_shapelet*)malloc(sizeof(exinfo_shapelet)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - exs->eX=eX; - exs->eY=eY; - /* sanity check if eX !=0 and eY !=0 */ - if (!exs->eX) { - exs->eX=1.0; - fprintf(stderr,"Warning: shapelet %s eX is zero. resetting to 1\n",buff); - } - if (!exs->eY) { - exs->eY=1.0; - fprintf(stderr,"Warning: shapelet %s eY is zero. resetting to 1\n",buff); - } - exs->eP=eP; - /* open mode file and build up info */ - read_shapelet_modes(buff,&exs->n0,&exs->beta,&exs->modes); - - /* negate angles */ - exs->cxi=cos(xi); - exs->sxi=sin(-xi); - exs->cphi=cos(phi); - exs->sphi=sin(-phi); - if (nnuse_projection=1; - } else { - exs->use_projection=0; - } - source->exdata=(void*)exs; - - } else { - source->stype=STYPE_POINT; - source->exdata=NULL; - } - - g_hash_table_insert(stable,(gpointer)hkey,(gpointer)source); - } - c=skip_restof_line(cfp); - c=skip_lines(cfp); - } - fclose(cfp); - free(buff); - - *M=g_list_length(clusters); - /* setup the array of cluster/source information */ - if ((*carr=(clus_source_t*)malloc((size_t)(g_list_length(clusters))*sizeof(clus_source_t)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - - ci=0; - for(li=clusters; li!=NULL; li=g_list_next(li)) { - clus=li->data; -#ifdef DEBUG - printf("cluster %d has %d elements\n",clus->id,g_list_length(clus->slist)); -#endif - - /* remember id, because -ve ids are not subtracted */ - (*carr)[ci].id=clus->id; - (*carr)[ci].nchunk=clus->nchunk; - (*carr)[ci].N=g_list_length(clus->slist); - - if (((*carr)[ci].ll=(double*)malloc((size_t)((*carr)[ci].N)*sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if (((*carr)[ci].mm=(double*)malloc((size_t)((*carr)[ci].N)*sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if (((*carr)[ci].nn=(double*)malloc((size_t)((*carr)[ci].N)*sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if (((*carr)[ci].sI=(double*)malloc((size_t)((*carr)[ci].N)*sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if (((*carr)[ci].sQ=(double*)malloc((size_t)((*carr)[ci].N)*sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if (((*carr)[ci].sU=(double*)malloc((size_t)((*carr)[ci].N)*sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if (((*carr)[ci].sV=(double*)malloc((size_t)((*carr)[ci].N)*sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if (((*carr)[ci].stype=(unsigned char*)malloc((size_t)((*carr)[ci].N)*sizeof(unsigned char)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if (((*carr)[ci].ex=(void**)malloc((size_t)((*carr)[ci].N)*sizeof(void*)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - /* for handling multi channel data */ - if (((*carr)[ci].sI0=(double*)malloc((size_t)((*carr)[ci].N)*sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if (((*carr)[ci].sQ0=(double*)malloc((size_t)((*carr)[ci].N)*sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if (((*carr)[ci].sU0=(double*)malloc((size_t)((*carr)[ci].N)*sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if (((*carr)[ci].sV0=(double*)malloc((size_t)((*carr)[ci].N)*sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if (((*carr)[ci].f0=(double*)malloc((size_t)((*carr)[ci].N)*sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if (((*carr)[ci].spec_idx=(double*)malloc((size_t)((*carr)[ci].N)*sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if (((*carr)[ci].spec_idx1=(double*)malloc((size_t)((*carr)[ci].N)*sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if (((*carr)[ci].spec_idx2=(double*)malloc((size_t)((*carr)[ci].N)*sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if (((*carr)[ci].ra=(double*)malloc((size_t)((*carr)[ci].N)*sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if (((*carr)[ci].dec=(double*)malloc((size_t)((*carr)[ci].N)*sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - - - cj=0; - for(ln=clus->slist; ln!=NULL; ln=g_list_next(ln)) { - sclus=ln->data; -#ifdef DEBUG - printf(" %s",sclus->name); -#endif - /* lookup hash table */ - source=NULL; - source=(sinfo_t*)g_hash_table_lookup(stable,sclus->name); - if (source) { - (*carr)[ci].ll[cj]=source->ll; - (*carr)[ci].mm[cj]=source->mm; - (*carr)[ci].nn[cj]=sqrt(1.0-source->ll*source->ll-source->mm*source->mm)-1.0; - (*carr)[ci].sI[cj]=source->sI[0]; - (*carr)[ci].sQ[cj]=source->sI[1]; - (*carr)[ci].sU[cj]=source->sI[2]; - (*carr)[ci].sV[cj]=source->sI[3]; - (*carr)[ci].stype[cj]=source->stype; -#ifdef DEBUG - printf(" (%lf,%lf,%lf,%lf,%lf,%lf,%lf,%lf,%lf)",source->ll,source->mm,source->ra,source->dec,(*carr)[ci].nn[cj],source->sI[0],source->sI[1],source->sI[2],source->sI[3]); -#endif - (*carr)[ci].ex[cj]=source->exdata; /* FIXME: duplicate sources could create double free error */ - (*carr)[ci].ra[cj]=source->ra; - (*carr)[ci].dec[cj]=source->dec; - - /* for multi channel data */ - (*carr)[ci].sI0[cj]=source->sI0[0]; - (*carr)[ci].sQ0[cj]=source->sI0[1]; - (*carr)[ci].sU0[cj]=source->sI0[2]; - (*carr)[ci].sV0[cj]=source->sI0[3]; - (*carr)[ci].f0[cj]=source->f0; - (*carr)[ci].spec_idx[cj]=source->spec_idx; - (*carr)[ci].spec_idx1[cj]=source->spec_idx1; - (*carr)[ci].spec_idx2[cj]=source->spec_idx2; - cj++; - } else { - fprintf(stderr,"Error: source %s not found\n",sclus->name); - } - } - /* sanity check */ - if (cj!=(*carr)[ci].N) { - fprintf(stderr,"Error: Expected %d no of sources for cluster %d but found %d, check your sky model!\nError: Continuing anyway but will get wrong results.\n",(*carr)[ci].N,*M-ci,cj); - } -// printf("\n"); - ci++; - } - - - - /* free cluster data */ - for(li=clusters; li!=NULL; li=g_list_next(li)) { - clus=li->data; - for(ln=clus->slist; ln!=NULL; ln=g_list_next(ln)) { - sclus=ln->data; - free(sclus->name); - free(sclus); - } - g_list_free(clus->slist); - free(clus); - } - g_list_free(clusters); - g_hash_table_destroy(stable); - return 0; -} - - - -int -read_solutions(FILE *cfp,double *p,clus_source_t *carr,int N,int M) { - /* read 8N valid rows and Mt columns */ - int Nc=8*N-1; - int c,buff_len,ci,ck,cn; - double jtmp; - char *buf; - /* allocate memory for buffer */ - buff_len = 128; - if((buf = (char*)malloc(sizeof(char)*(size_t)(buff_len)))==NULL) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } -#ifdef DEBUG -printf("Nc=%d\n",Nc); -#endif - c=skip_lines(cfp); - while(Nc>=0 && c>=0) { - /* we have a new line */ - memset(buf,0,buff_len); - c=read_next_string(&buf,&buff_len,cfp); - if (c!=1) { - /* first column is solution number (int) 1..8N */ - sscanf(buf,"%d",&cn); - } -#ifdef DEBUG - printf("%d ",cn); -#endif - /* read the rest of the line */ - for (ci=M-1; ci>=0; ci--) { - for (ck=0; ck0) { - memset(buf,0,buff_len); - c=read_next_string(&buf,&buff_len,cfp); - sscanf(buf,"%lf",&jtmp); - p[carr[ci].p[ck]+cn]=jtmp; -#ifdef DEBUG - printf("%e ",jtmp); -#endif - } - } - } -#ifdef DEBUG - printf("\n"); -#endif - c=skip_lines(cfp); - Nc--; - } - /* if Nc>=0 and we have reached the EOF, something wrong with solution file - so display warning */ - if (Nc>=0) { - printf("Warning: solution file EOF reached, check your solution file\n"); - } - - free(buf); - return 0; -} - - -int -update_ignorelist(const char *ignfile, int *ignlist, int M, clus_source_t *carr) { - FILE *cfp; - int ci,c,ignc,cn; - if ((cfp=fopen(ignfile,"r"))==0) { - fprintf(stderr,"%s: %d: no file %s\n",__FILE__,__LINE__,ignfile); - exit(1); - } - cn=0; - do { - c=fscanf(cfp,"%d",&ignc); - if (c>0) { -#ifdef DEBUG - printf("searching for %d\n",ignc); -#endif - /* search for this id in carr */ - for (ci=0; ci= 0); - - fclose(cfp); - printf("Total %d clusters ignored in simulation.\n",cn); - return 0; -} - - - - -int -read_arho_fromfile(const char *admm_rho_file,int Mt,double *arho, int M, double *arhoslave) { - - FILE *cfp; - int c,ci,cj,cluster_id,hybrid,hb; - double admm_rho; - if ((cfp=fopen(admm_rho_file,"r"))==0) { - fprintf(stderr,"%s: %d: no file\n",__FILE__,__LINE__); - exit(1); - } - - c=skip_lines(cfp); - ci=0; /* store it in reverse order */ - cj=0; - while(c>=0) { - c=fscanf(cfp,"%d %d %lf",&cluster_id,&hybrid,&admm_rho); - /* add this value to arho array */ - if (c!=EOF && c>0) { - /* found a valid line */ - arhoslave[M-1-cj]=admm_rho; /* reverse order */ - for (hb=0; hb - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#define _GNU_SOURCE /* for sincos() */ -#include -#include -#include -#include -#include -#include "sagecal.h" - -/* Jones matrix multiplication - C=A*B -*/ -static void -amb(complex double * __restrict a, complex double * __restrict b, complex double * __restrict c) { - c[0]=(a[0]*b[0]+a[1]*b[2]); - c[1]=(a[0]*b[1]+a[1]*b[3]); - c[2]=(a[2]*b[0]+a[3]*b[2]); - c[3]=(a[2]*b[1]+a[3]*b[3]); -} - - -/* Jones matrix multiplication - C=A*B^H -*/ -static void -ambt(complex double * __restrict a, complex double * __restrict b, complex double * __restrict c) { - c[0]=a[0]*conj(b[0])+a[1]*conj(b[1]); - c[1]=a[0]*conj(b[2])+a[1]*conj(b[3]); - c[2]=a[2]*conj(b[0])+a[3]*conj(b[1]); - c[3]=a[2]*conj(b[2])+a[3]*conj(b[3]); -} - - -/* worker thread function for subtraction - also correct residual with solutions for cluster id 0 */ -static void * -residual_threadfn_nointerpolation(void *data) { - thread_data_base_t *t=(thread_data_base_t*)data; - - int ci,cm,sta1,sta2; - double *pm; - complex double C[4],G1[4],G2[4],T1[4],T2[4]; - int M=(t->M); - int Ntilebase=(t->Nbase)*(t->tilesz); - int px; - for (ci=0; ciNb; ci++) { - /* iterate over the sky model and calculate contribution */ - /* for this x[8*ci:8*(ci+1)-1] */ - /* if this baseline is flagged, we do not compute */ - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - for (cm=0; cm=0 to do a subtraction */ - if (t->carr[cm].id>=0) { - /* gains for this cluster, for sta1,sta2 */ - /* depending on the chunk size and the baseline index, - select right set of parameters - data x=[0,........,Nbase*tilesz] - divided into nchunk chunks - p[0] -> x[0.....Nbase*tilesz/nchunk-1] - p[1] -> x[Nbase*tilesz/nchunk......2*Nbase*tilesz-1] - .... - p[last] -> x[(nchunk-1)*Nbase*tilesz/nchunk......Nbase*tilesz] - - so given bindex, right p[] is bindex/((Nbase*tilesz+nchunk-1)/nchunk) - */ - px=(ci+t->boff)/((Ntilebase+t->carr[cm].nchunk-1)/t->carr[cm].nchunk); - //printf("base %d, cluster %d, parm off %d abs %d\n",t->bindex[ci],cm,px,t->carr[cm].p[px]); - //pm=&(t->p0[cm*8*N]); - pm=&(t->p0[t->carr[cm].p[px]]); - G1[0]=(pm[sta1*8])+_Complex_I*(pm[sta1*8+1]); - G1[1]=(pm[sta1*8+2])+_Complex_I*(pm[sta1*8+3]); - G1[2]=(pm[sta1*8+4])+_Complex_I*(pm[sta1*8+5]); - G1[3]=(pm[sta1*8+6])+_Complex_I*(pm[sta1*8+7]); - G2[0]=(pm[sta2*8])+_Complex_I*(pm[sta2*8+1]); - G2[1]=(pm[sta2*8+2])+_Complex_I*(pm[sta2*8+3]); - G2[2]=(pm[sta2*8+4])+_Complex_I*(pm[sta2*8+5]); - G2[3]=(pm[sta2*8+6])+_Complex_I*(pm[sta2*8+7]); - - - /* use pre calculated values */ - C[0]=t->coh[4*M*ci+4*cm]; - C[1]=t->coh[4*M*ci+4*cm+1]; - C[2]=t->coh[4*M*ci+4*cm+2]; - C[3]=t->coh[4*M*ci+4*cm+3]; - - /* form G1*C*G2' */ - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - - /* subtract from baseline visibilities */ - t->x[8*ci]-=creal(T2[0]); - t->x[8*ci+1]-=cimag(T2[0]); - t->x[8*ci+2]-=creal(T2[1]); - t->x[8*ci+3]-=cimag(T2[1]); - t->x[8*ci+4]-=creal(T2[2]); - t->x[8*ci+5]-=cimag(T2[2]); - t->x[8*ci+6]-=creal(T2[3]); - t->x[8*ci+7]-=cimag(T2[3]); - } - } - if (t->pinv) { - cm=t->ccid; - /* now do correction, if any */ - C[0]=t->x[8*ci]+_Complex_I*t->x[8*ci+1]; - C[1]=t->x[8*ci+2]+_Complex_I*t->x[8*ci+3]; - C[2]=t->x[8*ci+4]+_Complex_I*t->x[8*ci+5]; - C[3]=t->x[8*ci+6]+_Complex_I*t->x[8*ci+7]; - px=(ci+t->boff)/((Ntilebase+t->carr[cm].nchunk-1)/t->carr[cm].nchunk); - pm=&(t->pinv[8*t->N*px]); - G1[0]=(pm[sta1*8])+_Complex_I*(pm[sta1*8+1]); - G1[1]=(pm[sta1*8+2])+_Complex_I*(pm[sta1*8+3]); - G1[2]=(pm[sta1*8+4])+_Complex_I*(pm[sta1*8+5]); - G1[3]=(pm[sta1*8+6])+_Complex_I*(pm[sta1*8+7]); - G2[0]=(pm[sta2*8])+_Complex_I*(pm[sta2*8+1]); - G2[1]=(pm[sta2*8+2])+_Complex_I*(pm[sta2*8+3]); - G2[2]=(pm[sta2*8+4])+_Complex_I*(pm[sta2*8+5]); - G2[3]=(pm[sta2*8+6])+_Complex_I*(pm[sta2*8+7]); - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - t->x[8*ci]=creal(T2[0]); - t->x[8*ci+1]=cimag(T2[0]); - t->x[8*ci+2]=creal(T2[1]); - t->x[8*ci+3]=cimag(T2[1]); - t->x[8*ci+4]=creal(T2[2]); - t->x[8*ci+5]=cimag(T2[2]); - t->x[8*ci+6]=creal(T2[3]); - t->x[8*ci+7]=cimag(T2[3]); - } - } - return NULL; -} - -/* invert matrix xx - 8x1 array - * store it in yy - 8x1 array - */ -static int -mat_invert(double xx[8],double yy[8], double rho) { - complex double a[4]; - complex double det; - complex double b[4]; - - a[0]=xx[0]+xx[1]*_Complex_I+rho; - a[1]=xx[2]+xx[3]*_Complex_I; - a[2]=xx[4]+xx[5]*_Complex_I; - a[3]=xx[6]+xx[7]*_Complex_I+rho; - - - - det=a[0]*a[3]-a[1]*a[2]; - if (sqrt(cabs(det))<=rho) { - det+=rho; - } - det=1.0/det; - b[0]=a[3]*det; - b[1]=-a[1]*det; - b[2]=-a[2]*det; - b[3]=a[0]*det; - - - yy[0]=creal(b[0]); - yy[1]=cimag(b[0]); - yy[2]=creal(b[1]); - yy[3]=cimag(b[1]); - yy[4]=creal(b[2]); - yy[5]=cimag(b[2]); - yy[6]=creal(b[3]); - yy[7]=cimag(b[3]); - - return 0; -} - - - -int -calculate_residuals_interp(double *u,double *v,double *w,double *p0,double *p,double *x,int N,int Nbase,int tilesz,baseline_t *barr, clus_source_t *carr, complex double *coh, int M,double freq0,double fdelta,int Nt, int ccid, double rho) { - int nth,nth1,ci,cj; - - int Nthb0,Nthb; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_base_t *threaddata; - - int Nbase1=Nbase*tilesz; - - int cm; - double *pm,*pinv=0; - cm=-1; - /* find if any cluster is specified for correction of data */ - for (cj=0; cj=0) { /* valid cluser for correction */ - /* allocate memory for inverse J */ - if ((pinv=(double*)malloc((size_t)8*N*carr[cm].nchunk*sizeof(double)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - for (cj=0; cjfdelta*0.5; - - complex double C[4],G1[4],G2[4],T1[4],T2[4]; - int M=(t->M); - int Ntilebase=(t->Nbase)*(t->tilesz); - int px; - for (ci=0; ciNb; ci++) { - /* iterate over the sky model and calculate contribution */ - /* for this x[8*ci:8*(ci+1)-1] */ - /* even if this baseline is flagged, we do compute */ - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - for (cm=0; cm=0 to do a subtraction */ - if (t->carr[cm].id>=0) { - /* gains for this cluster, for sta1,sta2 */ - /* depending on the chunk size and the baseline index, - select right set of parameters - data x=[0,........,Nbase*tilesz] - divided into nchunk chunks - p[0] -> x[0.....Nbase*tilesz/nchunk-1] - p[1] -> x[Nbase*tilesz/nchunk......2*Nbase*tilesz-1] - .... - p[last] -> x[(nchunk-1)*Nbase*tilesz/nchunk......Nbase*tilesz] - - so given bindex, right p[] is bindex/((Nbase*tilesz+nchunk-1)/nchunk) - */ - px=(ci+t->boff)/((Ntilebase+t->carr[cm].nchunk-1)/t->carr[cm].nchunk); - //printf("base %d, cluster %d, parm off %d abs %d\n",t->bindex[ci],cm,px,t->carr[cm].p[px]); - pm=&(t->p[t->carr[cm].p[px]]); - G1[0]=(pm[sta1*8])+_Complex_I*(pm[sta1*8+1]); - G1[1]=(pm[sta1*8+2])+_Complex_I*(pm[sta1*8+3]); - G1[2]=(pm[sta1*8+4])+_Complex_I*(pm[sta1*8+5]); - G1[3]=(pm[sta1*8+6])+_Complex_I*(pm[sta1*8+7]); - G2[0]=(pm[sta2*8])+_Complex_I*(pm[sta2*8+1]); - G2[1]=(pm[sta2*8+2])+_Complex_I*(pm[sta2*8+3]); - G2[2]=(pm[sta2*8+4])+_Complex_I*(pm[sta2*8+5]); - G2[3]=(pm[sta2*8+6])+_Complex_I*(pm[sta2*8+7]); - - - /* iterate over frequencies */ - freq0=t->freq0; -/***********************************************/ - /* calculate coherencies for each freq */ - memset(C,0,sizeof(complex double)*4); - - /* setup memory */ - if (posix_memalign((void*)&PHr,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&PHi,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&G,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&II,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&QQ,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&UU,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&VV,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - /* phase (real,imag) parts */ - /* note u=u/c, v=v/c, w=w/c here */ - /* phterm is 2pi(u/c l +v/c m +w/c n) */ - for (cn=0; cncarr[cm].N; cn++) { - G[cn]=2.0*M_PI*(t->u[ci]*t->carr[cm].ll[cn]+t->v[ci]*t->carr[cm].mm[cn]+t->w[ci]*t->carr[cm].nn[cn]); - } - for (cn=0; cncarr[cm].N; cn++) { - sincos(G[cn]*freq0,&PHi[cn],&PHr[cn]); - } - - /* term due to shape of source, also multiplied by freq/time smearing */ - for (cn=0; cncarr[cm].N; cn++) { - /* freq smearing : extra term delta * sinc(delta/2 * phterm) */ - if (G[cn]!=0.0) { - double smfac=G[cn]*fdelta2; - double sinph=sin(smfac)/smfac; - G[cn]=fabs(sinph); - } else { - G[cn]=1.0; - } - } - - /* multiply (re,im) phase term with smearing/shape factor */ - for (cn=0; cncarr[cm].N; cn++) { - PHr[cn]*=G[cn]; - PHi[cn]*=G[cn]; - } - - - for (cn=0; cncarr[cm].N; cn++) { - /* time smearing TMS eq. 6.81 for EW-array formula */ - //G[cn]*=time_smear(t->carr[cm].ll[cn],t->carr[cm].mm[cn],t->dec0,t->tdelta,t->u[ci],t->v[ci],t->w[ci],t->freq0); - - /* check if source type is not a point source for additional - calculations */ - if (t->carr[cm].stype[cn]!=STYPE_POINT) { - complex double sterm=PHr[cn]+_Complex_I*PHi[cn]; - if (t->carr[cm].stype[cn]==STYPE_SHAPELET) { - sterm*=shapelet_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } else if (t->carr[cm].stype[cn]==STYPE_GAUSSIAN) { - sterm*=gaussian_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } else if (t->carr[cm].stype[cn]==STYPE_DISK) { - sterm*=disk_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } else if (t->carr[cm].stype[cn]==STYPE_RING) { - sterm*=ring_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } - PHr[cn]=creal(sterm); - PHi[cn]=cimag(sterm); - } - - } - - /* flux of each source, at each freq */ - for (cn=0; cncarr[cm].N; cn++) { - /* coherencies are NOT scaled by 1/2, with spectral index */ - if (t->carr[cm].spec_idx[cn]!=0.0) { - double fratio=log(freq0/t->carr[cm].f0[cn]); - double fratio1=fratio*fratio; - double fratio2=fratio1*fratio; - double tempfr=t->carr[cm].spec_idx[cn]*fratio+t->carr[cm].spec_idx1[cn]*fratio1+t->carr[cm].spec_idx2[cn]*fratio2; - /* catch -ve and 0 sI */ - if (t->carr[cm].sI0[cn]>0.0) { - II[cn]=exp(log(t->carr[cm].sI0[cn])+tempfr); - } else { - II[cn]=(t->carr[cm].sI0[cn]==0.0?0.0:-exp(log(-t->carr[cm].sI0[cn])+tempfr)); - } - if (t->carr[cm].sQ0[cn]>0.0) { - QQ[cn]=exp(log(t->carr[cm].sQ0[cn])+tempfr); - } else { - QQ[cn]=(t->carr[cm].sQ0[cn]==0.0?0.0:-exp(log(-t->carr[cm].sQ0[cn])+tempfr)); - } - if (t->carr[cm].sU0[cn]>0.0) { - UU[cn]=exp(log(t->carr[cm].sU0[cn])+tempfr); - } else { - UU[cn]=(t->carr[cm].sU0[cn]==0.0?0.0:-exp(log(-t->carr[cm].sU0[cn])+tempfr)); - } - if (t->carr[cm].sV0[cn]>0.0) { - VV[cn]=exp(log(t->carr[cm].sV0[cn])+tempfr); - } else { - VV[cn]=(t->carr[cm].sV0[cn]==0.0?0.0:-exp(log(-t->carr[cm].sV0[cn])+tempfr)); - } - } else { - II[cn]=t->carr[cm].sI[cn]; - QQ[cn]=t->carr[cm].sQ[cn]; - UU[cn]=t->carr[cm].sU[cn]; - VV[cn]=t->carr[cm].sV[cn]; - } - } - - - /* add up terms together */ - for (cn=0; cncarr[cm].N; cn++) { - complex double Ph,IIl,QQl,UUl,VVl; - Ph=(PHr[cn]+_Complex_I*PHi[cn]); - IIl=Ph*II[cn]; - QQl=Ph*QQ[cn]; - UUl=Ph*UU[cn]; - VVl=Ph*VV[cn]; - C[0]+=IIl+QQl; - C[1]+=UUl+_Complex_I*VVl; - C[2]+=UUl-_Complex_I*VVl; - C[3]+=IIl-QQl; - } - - free(PHr); - free(PHi); - free(G); - free(II); - free(QQ); - free(UU); - free(VV); - -/***********************************************/ - /* form G1*C*G2' */ - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - - /* subtract from baseline visibilities */ - t->x[8*ci]-=creal(T2[0]); - t->x[8*ci+1]-=cimag(T2[0]); - t->x[8*ci+2]-=creal(T2[1]); - t->x[8*ci+3]-=cimag(T2[1]); - t->x[8*ci+4]-=creal(T2[2]); - t->x[8*ci+5]-=cimag(T2[2]); - t->x[8*ci+6]-=creal(T2[3]); - t->x[8*ci+7]-=cimag(T2[3]); - } - } - if (t->pinv) { - cm=t->ccid; - px=(ci+t->boff)/((Ntilebase+t->carr[cm].nchunk-1)/t->carr[cm].nchunk); - pm=&(t->pinv[8*t->N*px]); - G1[0]=(pm[sta1*8])+_Complex_I*(pm[sta1*8+1]); - G1[1]=(pm[sta1*8+2])+_Complex_I*(pm[sta1*8+3]); - G1[2]=(pm[sta1*8+4])+_Complex_I*(pm[sta1*8+5]); - G1[3]=(pm[sta1*8+6])+_Complex_I*(pm[sta1*8+7]); - G2[0]=(pm[sta2*8])+_Complex_I*(pm[sta2*8+1]); - G2[1]=(pm[sta2*8+2])+_Complex_I*(pm[sta2*8+3]); - G2[2]=(pm[sta2*8+4])+_Complex_I*(pm[sta2*8+5]); - G2[3]=(pm[sta2*8+6])+_Complex_I*(pm[sta2*8+7]); - - /* now do correction, if any */ - C[0]=t->x[8*ci]+_Complex_I*t->x[8*ci+1]; - C[1]=t->x[8*ci+2]+_Complex_I*t->x[8*ci+3]; - C[2]=t->x[8*ci+4]+_Complex_I*t->x[8*ci+5]; - C[3]=t->x[8*ci+6]+_Complex_I*t->x[8*ci+7]; - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - t->x[8*ci]=creal(T2[0]); - t->x[8*ci+1]=cimag(T2[0]); - t->x[8*ci+2]=creal(T2[1]); - t->x[8*ci+3]=cimag(T2[1]); - t->x[8*ci+4]=creal(T2[2]); - t->x[8*ci+5]=cimag(T2[2]); - t->x[8*ci+6]=creal(T2[3]); - t->x[8*ci+7]=cimag(T2[3]); - } - } - return NULL; -} - - -int -calculate_residuals(double *u,double *v,double *w,double *p,double *x,int N,int Nbase,int tilesz,baseline_t *barr, clus_source_t *carr, int M,double freq0, double fdelta,double tdelta,double dec0,int Nt, int ccid, double rho) { - int nth,nth1,ci,cj; - - int Nthb0,Nthb; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_base_t *threaddata; - - int Nbase1=Nbase*tilesz; - - int cm; - double *pm,*pinv=0; - cm=-1; - /* find if any cluster is specified for correction of data */ - for (cj=0; cj=0) { /* valid cluser for correction */ - /* allocate memory for inverse J */ - if ((pinv=(double*)malloc((size_t)8*N*carr[cm].nchunk*sizeof(double)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - for (cj=0; cjfdelta*0.5; - - complex double C[4],G1[4],G2[4],T1[4],T2[4]; - int M=(t->M); - int Ntilebase=(t->Nbase)*(t->tilesz); - int px; - for (ci=0; ciNb; ci++) { - /* iterate over the sky model and calculate contribution */ - /* for this x[8*ci:8*(ci+1)-1] */ - /* if this baseline is flagged, we do not compute */ - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - for (cm=0; cm=0 to do a subtraction */ - if (t->carr[cm].id>=0) { - /* gains for this cluster, for sta1,sta2 */ - /* depending on the chunk size and the baseline index, - select right set of parameters - data x=[0,........,Nbase*tilesz] - divided into nchunk chunks - p[0] -> x[0.....Nbase*tilesz/nchunk-1] - p[1] -> x[Nbase*tilesz/nchunk......2*Nbase*tilesz-1] - .... - p[last] -> x[(nchunk-1)*Nbase*tilesz/nchunk......Nbase*tilesz] - - so given bindex, right p[] is bindex/((Nbase*tilesz+nchunk-1)/nchunk) - */ - px=(ci+t->boff)/((Ntilebase+t->carr[cm].nchunk-1)/t->carr[cm].nchunk); - //printf("base %d, cluster %d, parm off %d abs %d\n",t->bindex[ci],cm,px,t->carr[cm].p[px]); - pm=&(t->p[t->carr[cm].p[px]]); - G1[0]=(pm[sta1*8])+_Complex_I*(pm[sta1*8+1]); - G1[1]=(pm[sta1*8+2])+_Complex_I*(pm[sta1*8+3]); - G1[2]=(pm[sta1*8+4])+_Complex_I*(pm[sta1*8+5]); - G1[3]=(pm[sta1*8+6])+_Complex_I*(pm[sta1*8+7]); - G2[0]=(pm[sta2*8])+_Complex_I*(pm[sta2*8+1]); - G2[1]=(pm[sta2*8+2])+_Complex_I*(pm[sta2*8+3]); - G2[2]=(pm[sta2*8+4])+_Complex_I*(pm[sta2*8+5]); - G2[3]=(pm[sta2*8+6])+_Complex_I*(pm[sta2*8+7]); - - - /* iterate over frequencies */ - for (cf=0; cfNchan; cf++) { - freq0=t->freqs[cf]; -/***********************************************/ - /* calculate coherencies for each freq */ - memset(C,0,sizeof(complex double)*4); - - /* setup memory */ - if (posix_memalign((void*)&PHr,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&PHi,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&G,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&II,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&QQ,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&UU,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&VV,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - /* phase (real,imag) parts */ - /* note u=u/c, v=v/c, w=w/c here */ - /* phterm is 2pi(u/c l +v/c m +w/c n) */ - for (cn=0; cncarr[cm].N; cn++) { - G[cn]=2.0*M_PI*(t->u[ci]*t->carr[cm].ll[cn]+t->v[ci]*t->carr[cm].mm[cn]+t->w[ci]*t->carr[cm].nn[cn]); - } - for (cn=0; cncarr[cm].N; cn++) { - sincos(G[cn]*freq0,&PHi[cn],&PHr[cn]); - } - - /* term due to shape of source, also multiplied by freq/time smearing */ - for (cn=0; cncarr[cm].N; cn++) { - /* freq smearing : extra term delta * sinc(delta/2 * phterm) */ - if (G[cn]!=0.0) { - double smfac=G[cn]*fdelta2; - double sinph=sin(smfac)/smfac; - G[cn]=fabs(sinph); - } else { - G[cn]=1.0; - } - } - - /* multiply (re,im) phase term with smearing/shape factor */ - for (cn=0; cncarr[cm].N; cn++) { - PHr[cn]*=G[cn]; - PHi[cn]*=G[cn]; - } - - - for (cn=0; cncarr[cm].N; cn++) { - /* check if source type is not a point source for additional - calculations */ - if (t->carr[cm].stype[cn]!=STYPE_POINT) { - complex double sterm=PHr[cn]+_Complex_I*PHi[cn]; - if (t->carr[cm].stype[cn]==STYPE_SHAPELET) { - sterm*=shapelet_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } else if (t->carr[cm].stype[cn]==STYPE_GAUSSIAN) { - sterm*=gaussian_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } else if (t->carr[cm].stype[cn]==STYPE_DISK) { - sterm*=disk_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } else if (t->carr[cm].stype[cn]==STYPE_RING) { - sterm*=ring_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } - PHr[cn]=creal(sterm); - PHi[cn]=cimag(sterm); - } - - } - - - for (cn=0; cncarr[cm].N; cn++) { - /* coherencies are NOT scaled by 1/2, with spectral index */ - if (t->carr[cm].spec_idx[cn]!=0.0) { - double fratio=log(freq0/t->carr[cm].f0[cn]); - double fratio1=fratio*fratio; - double fratio2=fratio1*fratio; - double tempfr=t->carr[cm].spec_idx[cn]*fratio+t->carr[cm].spec_idx1[cn]*fratio1+t->carr[cm].spec_idx2[cn]*fratio2; - /* catch -ve and 0 sI */ - if (t->carr[cm].sI0[cn]>0.0) { - II[cn]=exp(log(t->carr[cm].sI0[cn])+tempfr); - } else { - II[cn]=(t->carr[cm].sI0[cn]==0.0?0.0:-exp(log(-t->carr[cm].sI0[cn])+tempfr)); - } - if (t->carr[cm].sQ0[cn]>0.0) { - QQ[cn]=exp(log(t->carr[cm].sQ0[cn])+tempfr); - } else { - QQ[cn]=(t->carr[cm].sQ0[cn]==0.0?0.0:-exp(log(-t->carr[cm].sQ0[cn])+tempfr)); - } - if (t->carr[cm].sU0[cn]>0.0) { - UU[cn]=exp(log(t->carr[cm].sU0[cn])+tempfr); - } else { - UU[cn]=(t->carr[cm].sU0[cn]==0.0?0.0:-exp(log(-t->carr[cm].sU0[cn])+tempfr)); - } - if (t->carr[cm].sV0[cn]>0.0) { - VV[cn]=exp(log(t->carr[cm].sV0[cn])+tempfr); - } else { - VV[cn]=(t->carr[cm].sV0[cn]==0.0?0.0:-exp(log(-t->carr[cm].sV0[cn])+tempfr)); - } - } else { - II[cn]=t->carr[cm].sI[cn]; - QQ[cn]=t->carr[cm].sQ[cn]; - UU[cn]=t->carr[cm].sU[cn]; - VV[cn]=t->carr[cm].sV[cn]; - } - } - - /* add up terms together */ - for (cn=0; cncarr[cm].N; cn++) { - complex double Ph,IIl,QQl,UUl,VVl; - Ph=(PHr[cn]+_Complex_I*PHi[cn]); - IIl=Ph*II[cn]; - QQl=Ph*QQ[cn]; - UUl=Ph*UU[cn]; - VVl=Ph*VV[cn]; - C[0]+=IIl+QQl; - C[1]+=UUl+_Complex_I*VVl; - C[2]+=UUl-_Complex_I*VVl; - C[3]+=IIl-QQl; - } - - free(PHr); - free(PHi); - free(G); - free(II); - free(QQ); - free(UU); - free(VV); - - -/***********************************************/ - /* form G1*C*G2' */ - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - - /* subtract from baseline visibilities */ - t->x[8*ci+cf*Ntilebase*8]-=creal(T2[0]); - t->x[8*ci+1+cf*Ntilebase*8]-=cimag(T2[0]); - t->x[8*ci+2+cf*Ntilebase*8]-=creal(T2[1]); - t->x[8*ci+3+cf*Ntilebase*8]-=cimag(T2[1]); - t->x[8*ci+4+cf*Ntilebase*8]-=creal(T2[2]); - t->x[8*ci+5+cf*Ntilebase*8]-=cimag(T2[2]); - t->x[8*ci+6+cf*Ntilebase*8]-=creal(T2[3]); - t->x[8*ci+7+cf*Ntilebase*8]-=cimag(T2[3]); - } - } - } - if (t->pinv) { - cm=t->ccid; - px=(ci+t->boff)/((Ntilebase+t->carr[cm].nchunk-1)/t->carr[cm].nchunk); - pm=&(t->pinv[8*t->N*px]); - G1[0]=(pm[sta1*8])+_Complex_I*(pm[sta1*8+1]); - G1[1]=(pm[sta1*8+2])+_Complex_I*(pm[sta1*8+3]); - G1[2]=(pm[sta1*8+4])+_Complex_I*(pm[sta1*8+5]); - G1[3]=(pm[sta1*8+6])+_Complex_I*(pm[sta1*8+7]); - G2[0]=(pm[sta2*8])+_Complex_I*(pm[sta2*8+1]); - G2[1]=(pm[sta2*8+2])+_Complex_I*(pm[sta2*8+3]); - G2[2]=(pm[sta2*8+4])+_Complex_I*(pm[sta2*8+5]); - G2[3]=(pm[sta2*8+6])+_Complex_I*(pm[sta2*8+7]); - - /* iterate over frequencies */ - for (cf=0; cfNchan; cf++) { - /* now do correction, if any */ - C[0]=t->x[8*ci+cf*Ntilebase*8]+_Complex_I*t->x[8*ci+1+cf*Ntilebase*8]; - C[1]=t->x[8*ci+2+cf*Ntilebase*8]+_Complex_I*t->x[8*ci+3+cf*Ntilebase*8]; - C[2]=t->x[8*ci+4+cf*Ntilebase*8]+_Complex_I*t->x[8*ci+5+cf*Ntilebase*8]; - C[3]=t->x[8*ci+6+cf*Ntilebase*8]+_Complex_I*t->x[8*ci+7+cf*Ntilebase*8]; - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - t->x[8*ci+cf*Ntilebase*8]=creal(T2[0]); - t->x[8*ci+1+cf*Ntilebase*8]=cimag(T2[0]); - t->x[8*ci+2+cf*Ntilebase*8]=creal(T2[1]); - t->x[8*ci+3+cf*Ntilebase*8]=cimag(T2[1]); - t->x[8*ci+4+cf*Ntilebase*8]=creal(T2[2]); - t->x[8*ci+5+cf*Ntilebase*8]=cimag(T2[2]); - t->x[8*ci+6+cf*Ntilebase*8]=creal(T2[3]); - t->x[8*ci+7+cf*Ntilebase*8]=cimag(T2[3]); - } - } - } - return NULL; -} - - -int -calculate_residuals_multifreq(double *u,double *v,double *w,double *p,double *x,int N,int Nbase,int tilesz,baseline_t *barr, clus_source_t *carr, int M,double *freqs,int Nchan, double fdelta,double tdelta,double dec0,int Nt, int ccid, double rho, int phase_only) { - int nth,nth1,ci,cj; - - int Nthb0,Nthb; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_base_t *threaddata; - - int Nbase1=Nbase*tilesz; - - int cm; - double *pm,*pinv=0,*pphase=0; - cm=-1; - /* find if any cluster is specified for correction of data */ - for (cj=0; cj=0) { /* valid cluser for correction */ - /* allocate memory for inverse J */ - if ((pinv=(double*)malloc((size_t)8*N*carr[cm].nchunk*sizeof(double)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (!phase_only) { - for (cj=0; cjfdelta*0.5; - - complex double C[4]; - int M=(t->M); - int Ntilebase=(t->Nbase)*(t->tilesz); - for (ci=0; ciNb; ci++) { - /* iterate over the sky model and calculate contribution */ - /* if this baseline is flagged, we do not compute */ - for (cm=0; cmNchan; cf++) { - freq0=t->freqs[cf]; -/***********************************************/ - /* calculate coherencies for each freq */ - memset(C,0,sizeof(complex double)*4); - /* FIXME: use arrays Nx1 to try to vectorize this part */ - - /* setup memory */ - if (posix_memalign((void*)&PHr,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&PHi,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&G,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&II,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&QQ,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&UU,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&VV,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - /* phase (real,imag) parts */ - /* note u=u/c, v=v/c, w=w/c here */ - /* phterm is 2pi(u/c l +v/c m +w/c n) */ - for (cn=0; cncarr[cm].N; cn++) { - G[cn]=2.0*M_PI*(t->u[ci]*t->carr[cm].ll[cn]+t->v[ci]*t->carr[cm].mm[cn]+t->w[ci]*t->carr[cm].nn[cn]); - } - for (cn=0; cncarr[cm].N; cn++) { - sincos(G[cn]*freq0,&PHi[cn],&PHr[cn]); - } - - /* term due to shape of source, also multiplied by freq/time smearing */ - for (cn=0; cncarr[cm].N; cn++) { - /* freq smearing : extra term delta * sinc(delta/2 * phterm) */ - if (G[cn]!=0.0) { - double smfac=G[cn]*fdelta2; - double sinph=sin(smfac)/smfac; - G[cn]=fabs(sinph); - } else { - G[cn]=1.0; - } - } - - /* multiply (re,im) phase term with smearing/shape factor */ - for (cn=0; cncarr[cm].N; cn++) { - PHr[cn]*=G[cn]; - PHi[cn]*=G[cn]; - } - - - for (cn=0; cncarr[cm].N; cn++) { - /* check if source type is not a point source for additional - calculations */ - if (t->carr[cm].stype[cn]!=STYPE_POINT) { - complex double sterm=PHr[cn]+_Complex_I*PHi[cn]; - if (t->carr[cm].stype[cn]==STYPE_SHAPELET) { - sterm*=shapelet_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } else if (t->carr[cm].stype[cn]==STYPE_GAUSSIAN) { - sterm*=gaussian_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } else if (t->carr[cm].stype[cn]==STYPE_DISK) { - sterm*=disk_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } else if (t->carr[cm].stype[cn]==STYPE_RING) { - sterm*=ring_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } - PHr[cn]=creal(sterm); - PHi[cn]=cimag(sterm); - } - - } - - - /* flux of each source, at each freq */ - for (cn=0; cncarr[cm].N; cn++) { - /* coherencies are NOT scaled by 1/2, with spectral index */ - if (t->carr[cm].spec_idx[cn]!=0.0) { - double fratio=log(freq0/t->carr[cm].f0[cn]); - double fratio1=fratio*fratio; - double fratio2=fratio1*fratio; - double tempfr=t->carr[cm].spec_idx[cn]*fratio+t->carr[cm].spec_idx1[cn]*fratio1+t->carr[cm].spec_idx2[cn]*fratio2; - /* catch -ve and 0 sI */ - if (t->carr[cm].sI0[cn]>0.0) { - II[cn]=exp(log(t->carr[cm].sI0[cn])+tempfr); - } else { - II[cn]=(t->carr[cm].sI0[cn]==0.0?0.0:-exp(log(-t->carr[cm].sI0[cn])+tempfr)); - } - if (t->carr[cm].sQ0[cn]>0.0) { - QQ[cn]=exp(log(t->carr[cm].sQ0[cn])+tempfr); - } else { - QQ[cn]=(t->carr[cm].sQ0[cn]==0.0?0.0:-exp(log(-t->carr[cm].sQ0[cn])+tempfr)); - } - if (t->carr[cm].sU0[cn]>0.0) { - UU[cn]=exp(log(t->carr[cm].sU0[cn])+tempfr); - } else { - UU[cn]=(t->carr[cm].sU0[cn]==0.0?0.0:-exp(log(-t->carr[cm].sU0[cn])+tempfr)); - } - if (t->carr[cm].sV0[cn]>0.0) { - VV[cn]=exp(log(t->carr[cm].sV0[cn])+tempfr); - } else { - VV[cn]=(t->carr[cm].sV0[cn]==0.0?0.0:-exp(log(-t->carr[cm].sV0[cn])+tempfr)); - } - } else { - II[cn]=t->carr[cm].sI[cn]; - QQ[cn]=t->carr[cm].sQ[cn]; - UU[cn]=t->carr[cm].sU[cn]; - VV[cn]=t->carr[cm].sV[cn]; - } - } - - - /* add up terms together */ - for (cn=0; cncarr[cm].N; cn++) { - complex double Ph,IIl,QQl,UUl,VVl; - Ph=(PHr[cn]+_Complex_I*PHi[cn]); - IIl=Ph*II[cn]; - QQl=Ph*QQ[cn]; - UUl=Ph*UU[cn]; - VVl=Ph*VV[cn]; - C[0]+=IIl+QQl; - C[1]+=UUl+_Complex_I*VVl; - C[2]+=UUl-_Complex_I*VVl; - C[3]+=IIl-QQl; - } - - free(PHr); - free(PHi); - free(G); - free(II); - free(QQ); - free(UU); - free(VV); - -/***********************************************/ - /* add to baseline visibilities */ - t->x[8*ci+cf*Ntilebase*8]+=creal(C[0]); - t->x[8*ci+1+cf*Ntilebase*8]+=cimag(C[0]); - t->x[8*ci+2+cf*Ntilebase*8]+=creal(C[1]); - t->x[8*ci+3+cf*Ntilebase*8]+=cimag(C[1]); - t->x[8*ci+4+cf*Ntilebase*8]+=creal(C[2]); - t->x[8*ci+5+cf*Ntilebase*8]+=cimag(C[2]); - t->x[8*ci+6+cf*Ntilebase*8]+=creal(C[3]); - t->x[8*ci+7+cf*Ntilebase*8]+=cimag(C[3]); - } - } - - } - return NULL; -} - - - - -int -predict_visibilities_multifreq(double *u,double *v,double *w,double *x,int N,int Nbase,int tilesz,baseline_t *barr, clus_source_t *carr, int M,double *freqs,int Nchan, double fdelta,double tdelta, double dec0,int Nt, int add_to_data) { - int nth,nth1,ci; - - int Nthb0,Nthb; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_base_t *threaddata; - - int Nbase1=Nbase*tilesz; - - /* calculate min baselines a thread can handle */ - Nthb0=(Nbase1+Nt-1)/Nt; - - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((threaddata=(thread_data_base_t*)malloc((size_t)Nt*sizeof(thread_data_base_t)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - if (!add_to_data) { - /* set output column to zero */ - memset(x,0,sizeof(double)*8*Nbase*tilesz*Nchan); - } - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthfdelta*0.5; - - complex double C[4],G1[4],G2[4],T1[4],T2[4]; - int M=(t->M); - int Ntilebase=(t->Nbase)*(t->tilesz); - int px; - for (ci=0; ciNb; ci++) { - /* iterate over the sky model and calculate contribution */ - /* for this x[8*ci:8*(ci+1)-1] */ - /* if this baseline is flagged, we do not compute */ - if (!t->add_to_data) { /* only model is written as output */ - for (cf=0; cfNchan; cf++) { - memset(&t->x[8*ci+cf*Ntilebase*8],0,sizeof(double)*8); - } - } - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - for (cm=0; cmignlist[cm]) { - /* gains for this cluster, for sta1,sta2 */ - /* depending on the chunk size and the baseline index, - select right set of parameters - data x=[0,........,Nbase*tilesz] - divided into nchunk chunks - p[0] -> x[0.....Nbase*tilesz/nchunk-1] - p[1] -> x[Nbase*tilesz/nchunk......2*Nbase*tilesz-1] - .... - p[last] -> x[(nchunk-1)*Nbase*tilesz/nchunk......Nbase*tilesz] - - so given bindex, right p[] is bindex/((Nbase*tilesz+nchunk-1)/nchunk) - */ - px=(ci+t->boff)/((Ntilebase+t->carr[cm].nchunk-1)/t->carr[cm].nchunk); - //printf("base %d, cluster %d, parm off %d abs %d\n",t->bindex[ci],cm,px,t->carr[cm].p[px]); - pm=&(t->p[t->carr[cm].p[px]]); - G1[0]=(pm[sta1*8])+_Complex_I*(pm[sta1*8+1]); - G1[1]=(pm[sta1*8+2])+_Complex_I*(pm[sta1*8+3]); - G1[2]=(pm[sta1*8+4])+_Complex_I*(pm[sta1*8+5]); - G1[3]=(pm[sta1*8+6])+_Complex_I*(pm[sta1*8+7]); - G2[0]=(pm[sta2*8])+_Complex_I*(pm[sta2*8+1]); - G2[1]=(pm[sta2*8+2])+_Complex_I*(pm[sta2*8+3]); - G2[2]=(pm[sta2*8+4])+_Complex_I*(pm[sta2*8+5]); - G2[3]=(pm[sta2*8+6])+_Complex_I*(pm[sta2*8+7]); - - - /* iterate over frequencies */ - for (cf=0; cfNchan; cf++) { - freq0=t->freqs[cf]; -/***********************************************/ - /* calculate coherencies for each freq */ - memset(C,0,sizeof(complex double)*4); - /* setup memory */ - if (posix_memalign((void*)&PHr,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&PHi,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&G,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&II,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&QQ,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&UU,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (posix_memalign((void*)&VV,sizeof(double),((size_t)t->carr[cm].N*sizeof(double)))!=0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - - /* phase (real,imag) parts */ - /* note u=u/c, v=v/c, w=w/c here */ - /* phterm is 2pi(u/c l +v/c m +w/c n) */ - for (cn=0; cncarr[cm].N; cn++) { - G[cn]=2.0*M_PI*(t->u[ci]*t->carr[cm].ll[cn]+t->v[ci]*t->carr[cm].mm[cn]+t->w[ci]*t->carr[cm].nn[cn]); - } - for (cn=0; cncarr[cm].N; cn++) { - sincos(G[cn]*freq0,&PHi[cn],&PHr[cn]); - } - - /* term due to shape of source, also multiplied by freq/time smearing */ - for (cn=0; cncarr[cm].N; cn++) { - /* freq smearing : extra term delta * sinc(delta/2 * phterm) */ - if (G[cn]!=0.0) { - double smfac=G[cn]*fdelta2; - double sinph=sin(smfac)/smfac; - G[cn]=fabs(sinph); - } else { - G[cn]=1.0; - } - } - - /* multiply (re,im) phase term with smearing/shape factor */ - for (cn=0; cncarr[cm].N; cn++) { - PHr[cn]*=G[cn]; - PHi[cn]*=G[cn]; - } - - - for (cn=0; cncarr[cm].N; cn++) { - /* check if source type is not a point source for additional - calculations */ - if (t->carr[cm].stype[cn]!=STYPE_POINT) { - complex double sterm=PHr[cn]+_Complex_I*PHi[cn]; - if (t->carr[cm].stype[cn]==STYPE_SHAPELET) { - sterm*=shapelet_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } else if (t->carr[cm].stype[cn]==STYPE_GAUSSIAN) { - sterm*=gaussian_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } else if (t->carr[cm].stype[cn]==STYPE_DISK) { - sterm*=disk_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } else if (t->carr[cm].stype[cn]==STYPE_RING) { - sterm*=ring_contrib(t->carr[cm].ex[cn],t->u[ci]*freq0,t->v[ci]*freq0,t->w[ci]*freq0); - } - PHr[cn]=creal(sterm); - PHi[cn]=cimag(sterm); - } - - } - - - /* flux of each source, at each freq */ - for (cn=0; cncarr[cm].N; cn++) { - /* coherencies are NOT scaled by 1/2, with spectral index */ - if (t->carr[cm].spec_idx[cn]!=0.0) { - double fratio=log(freq0/t->carr[cm].f0[cn]); - double fratio1=fratio*fratio; - double fratio2=fratio1*fratio; - double tempfr=t->carr[cm].spec_idx[cn]*fratio+t->carr[cm].spec_idx1[cn]*fratio1+t->carr[cm].spec_idx2[cn]*fratio2; - /* catch -ve and 0 sI */ - if (t->carr[cm].sI0[cn]>0.0) { - II[cn]=exp(log(t->carr[cm].sI0[cn])+tempfr); - } else { - II[cn]=(t->carr[cm].sI0[cn]==0.0?0.0:-exp(log(-t->carr[cm].sI0[cn])+tempfr)); - } - if (t->carr[cm].sQ0[cn]>0.0) { - QQ[cn]=exp(log(t->carr[cm].sQ0[cn])+tempfr); - } else { - QQ[cn]=(t->carr[cm].sQ0[cn]==0.0?0.0:-exp(log(-t->carr[cm].sQ0[cn])+tempfr)); - } - if (t->carr[cm].sU0[cn]>0.0) { - UU[cn]=exp(log(t->carr[cm].sU0[cn])+tempfr); - } else { - UU[cn]=(t->carr[cm].sU0[cn]==0.0?0.0:-exp(log(-t->carr[cm].sU0[cn])+tempfr)); - } - if (t->carr[cm].sV0[cn]>0.0) { - VV[cn]=exp(log(t->carr[cm].sV0[cn])+tempfr); - } else { - VV[cn]=(t->carr[cm].sV0[cn]==0.0?0.0:-exp(log(-t->carr[cm].sV0[cn])+tempfr)); - } - } else { - II[cn]=t->carr[cm].sI[cn]; - QQ[cn]=t->carr[cm].sQ[cn]; - UU[cn]=t->carr[cm].sU[cn]; - VV[cn]=t->carr[cm].sV[cn]; - } - } - - /* add up terms together */ - for (cn=0; cncarr[cm].N; cn++) { - complex double Ph,IIl,QQl,UUl,VVl; - Ph=(PHr[cn]+_Complex_I*PHi[cn]); - IIl=Ph*II[cn]; - QQl=Ph*QQ[cn]; - UUl=Ph*UU[cn]; - VVl=Ph*VV[cn]; - C[0]+=IIl+QQl; - C[1]+=UUl+_Complex_I*VVl; - C[2]+=UUl-_Complex_I*VVl; - C[3]+=IIl-QQl; - } - - free(PHr); - free(PHi); - free(G); - free(II); - free(QQ); - free(UU); - free(VV); - - -/***********************************************/ - /* form G1*C*G2' */ - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - - /* add to baseline visibilities */ - t->x[8*ci+cf*Ntilebase*8]+=creal(T2[0]); - t->x[8*ci+1+cf*Ntilebase*8]+=cimag(T2[0]); - t->x[8*ci+2+cf*Ntilebase*8]+=creal(T2[1]); - t->x[8*ci+3+cf*Ntilebase*8]+=cimag(T2[1]); - t->x[8*ci+4+cf*Ntilebase*8]+=creal(T2[2]); - t->x[8*ci+5+cf*Ntilebase*8]+=cimag(T2[2]); - t->x[8*ci+6+cf*Ntilebase*8]+=creal(T2[3]); - t->x[8*ci+7+cf*Ntilebase*8]+=cimag(T2[3]); - } - } - } - /* if valid cluster is given, correct with its solutions */ - if (t->pinv) { - cm=t->ccid; - px=(ci+t->boff)/((Ntilebase+t->carr[cm].nchunk-1)/t->carr[cm].nchunk); - pm=&(t->pinv[8*t->N*px]); - G1[0]=(pm[sta1*8])+_Complex_I*(pm[sta1*8+1]); - G1[1]=(pm[sta1*8+2])+_Complex_I*(pm[sta1*8+3]); - G1[2]=(pm[sta1*8+4])+_Complex_I*(pm[sta1*8+5]); - G1[3]=(pm[sta1*8+6])+_Complex_I*(pm[sta1*8+7]); - G2[0]=(pm[sta2*8])+_Complex_I*(pm[sta2*8+1]); - G2[1]=(pm[sta2*8+2])+_Complex_I*(pm[sta2*8+3]); - G2[2]=(pm[sta2*8+4])+_Complex_I*(pm[sta2*8+5]); - G2[3]=(pm[sta2*8+6])+_Complex_I*(pm[sta2*8+7]); - - /* now do correction, if any */ - C[0]=t->x[8*ci]+_Complex_I*t->x[8*ci+1]; - C[1]=t->x[8*ci+2]+_Complex_I*t->x[8*ci+3]; - C[2]=t->x[8*ci+4]+_Complex_I*t->x[8*ci+5]; - C[3]=t->x[8*ci+6]+_Complex_I*t->x[8*ci+7]; - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - t->x[8*ci]=creal(T2[0]); - t->x[8*ci+1]=cimag(T2[0]); - t->x[8*ci+2]=creal(T2[1]); - t->x[8*ci+3]=cimag(T2[1]); - t->x[8*ci+4]=creal(T2[2]); - t->x[8*ci+5]=cimag(T2[2]); - t->x[8*ci+6]=creal(T2[3]); - t->x[8*ci+7]=cimag(T2[3]); - } - } - return NULL; -} - -int -predict_visibilities_multifreq_withsol(double *u,double *v,double *w,double *p,double *x,int *ignlist,int N,int Nbase,int tilesz,baseline_t *barr, clus_source_t *carr, int M,double *freqs,int Nchan, double fdelta,double tdelta,double dec0,int Nt, int add_to_data, int ccid, double rho, int phase_only) { - int nth,nth1,ci; - - int Nthb0,Nthb; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_base_t *threaddata; - - int Nbase1=Nbase*tilesz; - - - int cm,cj; - double *pm,*pinv=0,*pphase=0; - cm=-1; - /* find if any cluster is specified for correction of data */ - for (cj=0; cj=0) { /* valid cluser for correction */ - /* allocate memory for inverse J */ - if ((pinv=(double*)malloc((size_t)8*N*carr[cm].nchunk*sizeof(double)))==0) { - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); - exit(1); - } - if (!phase_only) { - for (cj=0; cj - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - -#include "cuda.h" -#include -#include - -/* enable this for kernel failure detection */ -//#define CUDA_DBG - -__global__ void kernel_deriv_robust(int Nbase, int tilesz, int M, int Ns, int Nparam, int goff, double robust_nu, double *x, double *coh, double *p, short *bb, int *ptoclus, double *grad){ - /* global thread index */ - unsigned int n = threadIdx.x + blockDim.x*blockIdx.x; - /* parameter number of this thread */ - unsigned int np=n+goff; - - - /* this thread works on - x[8*n:8*n+7], coh[8*M*n:8*M*n+8*M-1] - bb[2*n:2*n+1] (sta1,sta2) - organization of p (N stations and M clusters) - sta 0 sta 1 sta 2 .... sta N-1 - clus 0 0...7 8...15 16...23 ... 8N-8 8N-1 - clus 1 8N..8N+7 8N+8..8N+15 8N+16..8N+23 .... 8N+8N-8...8N+8N-1 - ...... - clus M-1 (M-1)N..(M-1)N+7 (M-1)N+8..(M-1)N+15.... ...(M-1)N+8N-8 (M-1)N+8N-1 - - organization of coherencies (coh) - [0, 8*M-1] : baseline 0 - [8*M, 8*M+8*M-1]: baseline 1 - [n*8*M, n*8*M+8*M-1]: baseline n - ...... - [n*8*M+cm*8, n*8*M+cm*8+7] cluster cm, baseline n - - residual error stored at sum[n] - */ - - if (nptoclus[2*cli+1]+ptoclus[2*cli]*8*Ns-1)) { cli++; } - /* now either ci>=M: cluster not found - or ci=ptoclus[2*cli-1] && np<=ptoclus[2*cli-1]+ptoclus[2*cli-2]*8*Ns-1) { - cli--; - } - - if (cli=0 && sta2>=0) { - /* which parameter 0..7 */ - unsigned int stoff=np_s-stc*8; - /* which cluster 0..M-1 */ - unsigned int stm=cli; - - /* read residual vector, real,imag separate*/ - double xr[8]; - xr[0]=x[nb*8]; - xr[1]=x[nb*8+1]; - xr[2]=x[nb*8+2]; - xr[3]=x[nb*8+3]; - xr[4]=x[nb*8+4]; - xr[5]=x[nb*8+5]; - xr[6]=x[nb*8+6]; - xr[7]=x[nb*8+7]; - - /* read in coherency */ - cuDoubleComplex C[4]; - C[0].x=coh[8*nb*M+8*stm]; - C[0].y=coh[8*nb*M+8*stm+1]; - C[1].x=coh[8*nb*M+8*stm+2]; - C[1].y=coh[8*nb*M+8*stm+3]; - C[2].x=coh[8*nb*M+8*stm+4]; - C[2].y=coh[8*nb*M+8*stm+5]; - C[3].x=coh[8*nb*M+8*stm+6]; - C[3].y=coh[8*nb*M+8*stm+7]; - - cuDoubleComplex G1[4]; - cuDoubleComplex G2[4]; - if(stc==sta1) { - double pp[8]; - pp[0]=0.0; - pp[1]=0.0; - pp[2]=0.0; - pp[3]=0.0; - pp[4]=0.0; - pp[5]=0.0; - pp[6]=0.0; - pp[7]=0.0; - pp[stoff]=1.0; - G1[0].x=pp[0]; - G1[0].y=pp[1]; - G1[1].x=pp[2]; - G1[1].y=pp[3]; - G1[2].x=pp[4]; - G1[2].y=pp[5]; - G1[3].x=pp[6]; - G1[3].y=pp[7]; - - /* conjugate and transpose G2 */ - G2[0].x=p[pstart+tpchunk*8*Ns+sta2*8]; - G2[0].y=-p[pstart+tpchunk*8*Ns+sta2*8+1]; - G2[2].x=p[pstart+tpchunk*8*Ns+sta2*8+2]; - G2[2].y=-p[pstart+tpchunk*8*Ns+sta2*8+3]; - G2[1].x=p[pstart+tpchunk*8*Ns+sta2*8+4]; - G2[1].y=-p[pstart+tpchunk*8*Ns+sta2*8+5]; - G2[3].x=p[pstart+tpchunk*8*Ns+sta2*8+6]; - G2[3].y=-p[pstart+tpchunk*8*Ns+sta2*8+7]; - } else if (stc==sta2) { - double pp[8]; - pp[0]=0.0; - pp[1]=0.0; - pp[2]=0.0; - pp[3]=0.0; - pp[4]=0.0; - pp[5]=0.0; - pp[6]=0.0; - pp[7]=0.0; - pp[stoff]=1.0; - /* conjugate and transpose G2 */ - G2[0].x=pp[0]; - G2[0].y=-pp[1]; - G2[2].x=pp[2]; - G2[2].y=-pp[3]; - G2[1].x=pp[4]; - G2[1].y=-pp[5]; - G2[3].x=pp[6]; - G2[3].y=-pp[7]; - - /* conjugate and transpose G2 */ - G1[0].x=p[pstart+tpchunk*8*Ns+sta1*8]; - G1[0].y=p[pstart+tpchunk*8*Ns+sta1*8+1]; - G1[1].x=p[pstart+tpchunk*8*Ns+sta1*8+2]; - G1[1].y=p[pstart+tpchunk*8*Ns+sta1*8+3]; - G1[2].x=p[pstart+tpchunk*8*Ns+sta1*8+4]; - G1[2].y=p[pstart+tpchunk*8*Ns+sta1*8+5]; - G1[3].x=p[pstart+tpchunk*8*Ns+sta1*8+6]; - G1[3].y=p[pstart+tpchunk*8*Ns+sta1*8+7]; - } - cuDoubleComplex T1[4]; - /* T1=G1*C */ - T1[0]=cuCadd(cuCmul(G1[0],C[0]),cuCmul(G1[1],C[2])); - T1[1]=cuCadd(cuCmul(G1[0],C[1]),cuCmul(G1[1],C[3])); - T1[2]=cuCadd(cuCmul(G1[2],C[0]),cuCmul(G1[3],C[2])); - T1[3]=cuCadd(cuCmul(G1[2],C[1]),cuCmul(G1[3],C[3])); - - cuDoubleComplex T2[4]; - /* T2=T1*G2 , G2 conjugate transposed */ - T2[0]=cuCadd(cuCmul(T1[0],G2[0]),cuCmul(T1[1],G2[2])); - T2[1]=cuCadd(cuCmul(T1[0],G2[1]),cuCmul(T1[1],G2[3])); - T2[2]=cuCadd(cuCmul(T1[2],G2[0]),cuCmul(T1[3],G2[2])); - T2[3]=cuCadd(cuCmul(T1[2],G2[1]),cuCmul(T1[3],G2[3])); - - /* calculate product xr*vec(J_p C J_q^H )/(nu+residual^2) */ - double dsum; - dsum=xr[0]*T2[0].x/(robust_nu+xr[0]*xr[0]); - dsum+=xr[1]*T2[0].y/(robust_nu+xr[1]*xr[1]); - dsum+=xr[2]*T2[1].x/(robust_nu+xr[2]*xr[2]); - dsum+=xr[3]*T2[1].y/(robust_nu+xr[3]*xr[3]); - dsum+=xr[4]*T2[2].x/(robust_nu+xr[4]*xr[4]); - dsum+=xr[5]*T2[2].y/(robust_nu+xr[5]*xr[5]); - dsum+=xr[6]*T2[3].x/(robust_nu+xr[6]*xr[6]); - dsum+=xr[7]*T2[3].y/(robust_nu+xr[7]*xr[7]); - /* accumulate sum NOTE - its important to get the sign right, - depending on res=data-model or res=model-data */ - gsum+=2.0*dsum; - } - - } - - } - } - - - grad[n]=gsum; - } - -} - - -__global__ void kernel_func_wt(int Nbase, double *x, double *coh, double *p, short *bb, double *wt, int N){ - /* global thread index : equal to the baseline */ - unsigned int n = threadIdx.x + blockDim.x*blockIdx.x; - - /* this thread works on - x[8*n:8*n+7], coh[8*M*n:8*M*n+8*M-1] - bb[2*n:2*n+1] (sta1,sta2) - organization of p (N stations and M clusters) - sta 0 sta 1 sta 2 .... sta N-1 - clus 0 0...7 8...15 16...23 ... 8N-8 8N-1 - clus 1 8N..8N+7 8N+8..8N+15 8N+16..8N+23 .... 8N+8N-8...8N+8N-1 - ...... - clus M-1 (M-1)N..(M-1)N+7 (M-1)N+8..(M-1)N+15.... ...(M-1)N+8N-8 (M-1)N+8N-1 - - organization of coherencies (coh) - [0, 8*M-1] : baseline 0 - [8*M, 8*M+8*M-1]: baseline 1 - [n*8*M, n*8*M+8*M-1]: baseline n - ...... - [n*8*M+cm*8, n*8*M+cm*8+7] cluster cm, baseline n - - residual error stored at sum[n] - */ - - if(n=0 - */ - if (sta1>=0 && sta2>=0) { - cuDoubleComplex G1[4]; - double pp[8]; - pp[0]=p[sta1*8]; - pp[1]=p[sta1*8+1]; - pp[2]=p[sta1*8+2]; - pp[3]=p[sta1*8+3]; - pp[4]=p[sta1*8+4]; - pp[5]=p[sta1*8+5]; - pp[6]=p[sta1*8+6]; - pp[7]=p[sta1*8+7]; - G1[0].x=pp[0]; - G1[0].y=pp[1]; - G1[1].x=pp[2]; - G1[1].y=pp[3]; - G1[2].x=pp[4]; - G1[2].y=pp[5]; - G1[3].x=pp[6]; - G1[3].y=pp[7]; - - - cuDoubleComplex C[4]; - C[0].x=coh[8*n]; - C[0].y=coh[8*n+1]; - C[1].x=coh[8*n+2]; - C[1].y=coh[8*n+3]; - C[2].x=coh[8*n+4]; - C[2].y=coh[8*n+5]; - C[3].x=coh[8*n+6]; - C[3].y=coh[8*n+7]; - - cuDoubleComplex T1[4]; - /* T=G1*C */ - T1[0]=cuCadd(cuCmul(G1[0],C[0]),cuCmul(G1[1],C[2])); - T1[1]=cuCadd(cuCmul(G1[0],C[1]),cuCmul(G1[1],C[3])); - T1[2]=cuCadd(cuCmul(G1[2],C[0]),cuCmul(G1[3],C[2])); - T1[3]=cuCadd(cuCmul(G1[2],C[1]),cuCmul(G1[3],C[3])); - - cuDoubleComplex G2[4]; - /* conjugate this */ - pp[0]=p[sta2*8]; - pp[1]=-p[sta2*8+1]; - pp[2]=p[sta2*8+2]; - pp[3]=-p[sta2*8+3]; - pp[4]=p[sta2*8+4]; - pp[5]=-p[sta2*8+5]; - pp[6]=p[sta2*8+6]; - pp[7]=-p[sta2*8+7]; - G2[0].x=pp[0]; - G2[0].y=pp[1]; - G2[2].x=pp[2]; - G2[2].y=pp[3]; - G2[1].x=pp[4]; - G2[1].y=pp[5]; - G2[3].x=pp[6]; - G2[3].y=pp[7]; - - cuDoubleComplex T2[4]; - T2[0]=cuCadd(cuCmul(T1[0],G2[0]),cuCmul(T1[1],G2[2])); - T2[1]=cuCadd(cuCmul(T1[0],G2[1]),cuCmul(T1[1],G2[3])); - T2[2]=cuCadd(cuCmul(T1[2],G2[0]),cuCmul(T1[3],G2[2])); - T2[3]=cuCadd(cuCmul(T1[2],G2[1]),cuCmul(T1[3],G2[3])); - /* update model vector, with weights */ - x[8*n]=wt[8*n]*T2[0].x; - x[8*n+1]=wt[8*n+1]*T2[0].y; - x[8*n+2]=wt[8*n+2]*T2[1].x; - x[8*n+3]=wt[8*n+3]*T2[1].y; - x[8*n+4]=wt[8*n+4]*T2[2].x; - x[8*n+5]=wt[8*n+5]*T2[2].y; - x[8*n+6]=wt[8*n+6]*T2[3].x; - x[8*n+7]=wt[8*n+7]*T2[3].y; - - } - } - -} - -__global__ void kernel_jacf_wt(int Nbase, int M, double *jac, double *coh, double *p, short *bb, double *wt, int N){ - /* global thread index : equal to the baseline */ - unsigned int n = threadIdx.x + blockDim.x*blockIdx.x; - /* which parameter:0...M */ - unsigned int m = threadIdx.y + blockDim.y*blockIdx.y; - - /* this thread works on - x[8*n:8*n+7], coh[8*M*n:8*M*n+8*M-1] - bb[2*n:2*n+1] (sta1,sta2) - organization of p (N stations and M clusters) - sta 0 sta 1 sta 2 .... sta N-1 - clus 0 0...7 8...15 16...23 ... 8N-8 8N-1 - clus 1 8N..8N+7 8N+8..8N+15 8N+16..8N+23 .... 8N+8N-8...8N+8N-1 - ...... - clus M-1 (M-1)N..(M-1)N+7 (M-1)N+8..(M-1)N+15.... ...(M-1)N+8N-8 (M-1)N+8N-1 - - organization of coherencies (coh) - [0, 8*M-1] : baseline 0 - [8*M, 8*M+8*M-1]: baseline 1 - [n*8*M, n*8*M+8*M-1]: baseline n - ...... - [n*8*M+cm*8, n*8*M+cm*8+7] cluster cm, baseline n - - residual error stored at sum[n] - */ - - if(n>3; /* 0...Ns-1 (because M=total par= 8 * Nstations */ - - if (((stc==sta2)||(stc==sta1)) && sta1>=0 && sta2>=0 ) { - - cuDoubleComplex C[4]; - C[0].x=coh[8*n]; - C[0].y=coh[8*n+1]; - C[1].x=coh[8*n+2]; - C[1].y=coh[8*n+3]; - C[2].x=coh[8*n+4]; - C[2].y=coh[8*n+5]; - C[3].x=coh[8*n+6]; - C[3].y=coh[8*n+7]; - - /* which parameter exactly 0..7 */ - //int stoff=m%8; - int stoff=m-stc*8; - double pp1[8]; - double pp2[8]; - if (stc==sta1) { - for (int cn=0; cn<8; cn++) { - pp1[cn]=0.0; - pp2[cn]=p[sta2*8+cn]; - } - pp1[stoff]=1.0; - } else if (stc==sta2) { - for (int cn=0; cn<8; cn++) { - pp2[cn]=0.0; - pp1[cn]=p[sta1*8+cn]; - } - pp2[stoff]=1.0; - } - - - cuDoubleComplex G1[4]; - G1[0].x=pp1[0]; - G1[0].y=pp1[1]; - G1[1].x=pp1[2]; - G1[1].y=pp1[3]; - G1[2].x=pp1[4]; - G1[2].y=pp1[5]; - G1[3].x=pp1[6]; - G1[3].y=pp1[7]; - - cuDoubleComplex T1[4]; - /* T=G1*C */ - T1[0]=cuCadd(cuCmul(G1[0],C[0]),cuCmul(G1[1],C[2])); - T1[1]=cuCadd(cuCmul(G1[0],C[1]),cuCmul(G1[1],C[3])); - T1[2]=cuCadd(cuCmul(G1[2],C[0]),cuCmul(G1[3],C[2])); - T1[3]=cuCadd(cuCmul(G1[2],C[1]),cuCmul(G1[3],C[3])); - - cuDoubleComplex G2[4]; - /* conjugate this */ - G2[0].x=pp2[0]; - G2[0].y=-pp2[1]; - G2[2].x=pp2[2]; - G2[2].y=-pp2[3]; - G2[1].x=pp2[4]; - G2[1].y=-pp2[5]; - G2[3].x=pp2[6]; - G2[3].y=-pp2[7]; - - cuDoubleComplex T2[4]; - T2[0]=cuCadd(cuCmul(T1[0],G2[0]),cuCmul(T1[1],G2[2])); - T2[1]=cuCadd(cuCmul(T1[0],G2[1]),cuCmul(T1[1],G2[3])); - T2[2]=cuCadd(cuCmul(T1[2],G2[0]),cuCmul(T1[3],G2[2])); - T2[3]=cuCadd(cuCmul(T1[2],G2[1]),cuCmul(T1[3],G2[3])); - /* update jacobian , with row weights */ - /* NOTE: row major order */ - jac[m+M*8*n]=wt[8*n]*T2[0].x; - jac[m+M*(8*n+1)]=wt[8*n+1]*T2[0].y; - jac[m+M*(8*n+2)]=wt[8*n+2]*T2[1].x; - jac[m+M*(8*n+3)]=wt[8*n+3]*T2[1].y; - jac[m+M*(8*n+4)]=wt[8*n+4]*T2[2].x; - jac[m+M*(8*n+5)]=wt[8*n+5]*T2[2].y; - jac[m+M*(8*n+6)]=wt[8*n+6]*T2[3].x; - jac[m+M*(8*n+7)]=wt[8*n+7]*T2[3].y; - - } - } - -} - -__global__ void kernel_setweights(int N, double *wt, double alpha){ - unsigned int tid = blockIdx.x*blockDim.x + threadIdx.x; - /* make sure to use only M threads */ - if (tid>>(N, wt, alpha); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - -} - -/* hadamard product by a cuda kernel x<= x*wt */ -void -cudakernel_hadamard(int ThreadsPerBlock, int BlocksPerGrid, int N, double *wt, double *x) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - kernel_hadamard<<< BlocksPerGrid, ThreadsPerBlock >>>(N, wt, x); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - -} - -/* update weights by a cuda kernel */ -void -cudakernel_updateweights(int ThreadsPerBlock, int BlocksPerGrid, int N, double *wt, double *x, double *q, double robust_nu) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - kernel_updateweights<<< BlocksPerGrid, ThreadsPerBlock >>>(N, wt, x, q, robust_nu); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - -} - -/* update weights by a cuda kernel */ -void -cudakernel_sqrtweights(int ThreadsPerBlock, int BlocksPerGrid, int N, double *wt) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - kernel_sqrtweights<<< BlocksPerGrid, ThreadsPerBlock >>>(N, wt); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - -} - -/* evaluate expression for finding optimum nu for - a range of nu values */ -void -cudakernel_evaluatenu(int ThreadsPerBlock, int BlocksPerGrid, int Nd, double qsum, double *q, double deltanu,double nulow) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - kernel_evaluatenu<<< BlocksPerGrid, ThreadsPerBlock >>>(Nd, qsum, q, deltanu, nulow); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - -} - -/* cuda driver for calculating wt \odot f() */ -/* p: params (Mx1), x: data (Nx1), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -void -cudakernel_func_wt(int ThreadsPerBlock, int BlocksPerGrid, double *p, double *x, int M, int N, double *coh, short *bbh, double *wt, int Nbase, int Mclus, int Nstations) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - cudaMemset(x, 0, N*sizeof(double)); -// printf("Kernel data size=%d, block=%d, thread=%d, baselines=%d\n",N,BlocksPerGrid, ThreadsPerBlock,Nbase); - kernel_func_wt<<< BlocksPerGrid, ThreadsPerBlock >>>(Nbase, x, coh, p, bbh, wt, Nstations); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - -} - -/* cuda driver for calculating wt \odot jacf() */ -/* p: params (Mx1), jac: jacobian (NxM), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -void -cudakernel_jacf_wt(int ThreadsPerBlock_row, int ThreadsPerBlock_col, double *p, double *jac, int M, int N, double *coh, short *bbh, double *wt, int Nbase, int Mclus, int Nstations, int clus) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - /* NOTE: use small value for ThreadsPerBlock here, like 8 */ - dim3 threadsPerBlock(16, 8); - /* jacobian: Nbase x Nstations (proportional to N), so */ - dim3 numBlocks((Nbase+threadsPerBlock.x-1)/threadsPerBlock.x, - (M+threadsPerBlock.y-1)/threadsPerBlock.y); - /* set memory of jac to zero */ - cudaMemset(jac, 0, N*M*sizeof(double)); - // printf("Kernel Jax data size=%d, params=%d, block=%d,%d, thread=%d,%d, baselines=%d\n",N, M, numBlocks.x,numBlocks.y, threadsPerBlock.x, threadsPerBlock.y, Nbase); - kernel_jacf_wt<<< numBlocks, threadsPerBlock>>>(Nbase, M, jac, coh, p, bbh, wt, Nstations); - - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - -} - -/* cuda driver for kernel */ -/* ThreadsPerBlock: keep <= 128 ??? - BlocksPerGrid: depends on the threads/baselines> Threads*Blocks approx baselines - Nbase: no of baselines (total, including tilesz >1) - tilesz: tile size - M: no of clusters - Ns: no of stations - Nparam: no of actual parameters <=total - goff: starting point of gradient calculation 0..Nparams - x: N*8 x 1 residual - coh: N*8*M x 1 - p: M*Ns*8 x 1 - bb: 2*N x 1 - ptoclus: 2*M x 1 - - grad: Nparamsx1 gradient values -*/ -void cudakernel_lbfgs_robust(int ThreadsPerBlock, int BlocksPerGrid, int Nbase, int tilesz, int M, int Ns, int Nparam, int goff, double robust_nu, double *x, double *coh, double *p, short *bb, int *ptoclus, double *grad){ - -#ifdef CUDA_DBG - cudaError_t error; -#endif - /* invoke device on this block/thread grid (last argument is buffer size in bytes) */ - kernel_deriv_robust<<< BlocksPerGrid, ThreadsPerBlock, ThreadsPerBlock*sizeof(double) >>> (Nbase, tilesz, M, Ns, Nparam, goff, robust_nu, x, coh, p, bb, ptoclus, grad); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - -} - -} diff --git a/src/lib/robust_fl.cu b/src/lib/robust_fl.cu deleted file mode 100644 index 440efa1..0000000 --- a/src/lib/robust_fl.cu +++ /dev/null @@ -1,536 +0,0 @@ -/* - * - Copyright (C) 2006-2008 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - -#include "cuda.h" -#include -#include - -/* enable this for checking for kernel failure */ -//#define CUDA_DBG - -__global__ void -kernel_func_wt_fl(int Nbase, float *x, float *coh, float *p, short *bb, float *wt, int N){ - /* global thread index : equal to the baseline */ - unsigned int n = threadIdx.x + blockDim.x*blockIdx.x; - - /* this thread works on - x[8*n:8*n+7], coh[8*M*n:8*M*n+8*M-1] - bb[2*n:2*n+1] (sta1,sta2) - organization of p (N stations and M clusters) - sta 0 sta 1 sta 2 .... sta N-1 - clus 0 0...7 8...15 16...23 ... 8N-8 8N-1 - clus 1 8N..8N+7 8N+8..8N+15 8N+16..8N+23 .... 8N+8N-8...8N+8N-1 - ...... - clus M-1 (M-1)N..(M-1)N+7 (M-1)N+8..(M-1)N+15.... ...(M-1)N+8N-8 (M-1)N+8N-1 - - organization of coherencies (coh) - [0, 8*M-1] : baseline 0 - [8*M, 8*M+8*M-1]: baseline 1 - [n*8*M, n*8*M+8*M-1]: baseline n - ...... - [n*8*M+cm*8, n*8*M+cm*8+7] cluster cm, baseline n - - residual error stored at sum[n] - */ - - if(n=0 - */ - if (sta1>=0 && sta2>=0) { - cuComplex G1[4]; - float pp[8]; - pp[0]=p[sta1*8]; - pp[1]=p[sta1*8+1]; - pp[2]=p[sta1*8+2]; - pp[3]=p[sta1*8+3]; - pp[4]=p[sta1*8+4]; - pp[5]=p[sta1*8+5]; - pp[6]=p[sta1*8+6]; - pp[7]=p[sta1*8+7]; - G1[0].x=pp[0]; - G1[0].y=pp[1]; - G1[1].x=pp[2]; - G1[1].y=pp[3]; - G1[2].x=pp[4]; - G1[2].y=pp[5]; - G1[3].x=pp[6]; - G1[3].y=pp[7]; - - - cuComplex C[4]; - C[0].x=coh[8*n]; - C[0].y=coh[8*n+1]; - C[1].x=coh[8*n+2]; - C[1].y=coh[8*n+3]; - C[2].x=coh[8*n+4]; - C[2].y=coh[8*n+5]; - C[3].x=coh[8*n+6]; - C[3].y=coh[8*n+7]; - - cuComplex T1[4]; - /* T=G1*C */ - T1[0]=cuCaddf(cuCmulf(G1[0],C[0]),cuCmulf(G1[1],C[2])); - T1[1]=cuCaddf(cuCmulf(G1[0],C[1]),cuCmulf(G1[1],C[3])); - T1[2]=cuCaddf(cuCmulf(G1[2],C[0]),cuCmulf(G1[3],C[2])); - T1[3]=cuCaddf(cuCmulf(G1[2],C[1]),cuCmulf(G1[3],C[3])); - - cuComplex G2[4]; - /* conjugate this */ - pp[0]=p[sta2*8]; - pp[1]=-p[sta2*8+1]; - pp[2]=p[sta2*8+2]; - pp[3]=-p[sta2*8+3]; - pp[4]=p[sta2*8+4]; - pp[5]=-p[sta2*8+5]; - pp[6]=p[sta2*8+6]; - pp[7]=-p[sta2*8+7]; - G2[0].x=pp[0]; - G2[0].y=pp[1]; - G2[2].x=pp[2]; - G2[2].y=pp[3]; - G2[1].x=pp[4]; - G2[1].y=pp[5]; - G2[3].x=pp[6]; - G2[3].y=pp[7]; - - cuComplex T2[4]; - T2[0]=cuCaddf(cuCmulf(T1[0],G2[0]),cuCmulf(T1[1],G2[2])); - T2[1]=cuCaddf(cuCmulf(T1[0],G2[1]),cuCmulf(T1[1],G2[3])); - T2[2]=cuCaddf(cuCmulf(T1[2],G2[0]),cuCmulf(T1[3],G2[2])); - T2[3]=cuCaddf(cuCmulf(T1[2],G2[1]),cuCmulf(T1[3],G2[3])); - /* update model vector, with weights */ - x[8*n]=wt[8*n]*T2[0].x; - x[8*n+1]=wt[8*n+1]*T2[0].y; - x[8*n+2]=wt[8*n+2]*T2[1].x; - x[8*n+3]=wt[8*n+3]*T2[1].y; - x[8*n+4]=wt[8*n+4]*T2[2].x; - x[8*n+5]=wt[8*n+5]*T2[2].y; - x[8*n+6]=wt[8*n+6]*T2[3].x; - x[8*n+7]=wt[8*n+7]*T2[3].y; - - } - } - -} - -__global__ void -kernel_jacf_wt_fl(int Nbase, int M, float *jac, float *coh, float *p, short *bb, float *wt, int N){ - /* global thread index : equal to the baseline */ - unsigned int n = threadIdx.x + blockDim.x*blockIdx.x; - /* which parameter:0...M */ - unsigned int m = threadIdx.y + blockDim.y*blockIdx.y; - - /* this thread works on - x[8*n:8*n+7], coh[8*M*n:8*M*n+8*M-1] - bb[2*n:2*n+1] (sta1,sta2) - organization of p (N stations and M clusters) - sta 0 sta 1 sta 2 .... sta N-1 - clus 0 0...7 8...15 16...23 ... 8N-8 8N-1 - clus 1 8N..8N+7 8N+8..8N+15 8N+16..8N+23 .... 8N+8N-8...8N+8N-1 - ...... - clus M-1 (M-1)N..(M-1)N+7 (M-1)N+8..(M-1)N+15.... ...(M-1)N+8N-8 (M-1)N+8N-1 - - organization of coherencies (coh) - [0, 8*M-1] : baseline 0 - [8*M, 8*M+8*M-1]: baseline 1 - [n*8*M, n*8*M+8*M-1]: baseline n - ...... - [n*8*M+cm*8, n*8*M+cm*8+7] cluster cm, baseline n - - residual error stored at sum[n] - */ - - if(n>3; /* 0...Ns-1 (because M=total par= 8 * Nstations */ - - if (((stc==sta2)||(stc==sta1)) && sta1>=0 && sta2>=0 ) { - - cuComplex C[4]; - C[0].x=coh[8*n]; - C[0].y=coh[8*n+1]; - C[1].x=coh[8*n+2]; - C[1].y=coh[8*n+3]; - C[2].x=coh[8*n+4]; - C[2].y=coh[8*n+5]; - C[3].x=coh[8*n+6]; - C[3].y=coh[8*n+7]; - - /* which parameter exactly 0..7 */ - //int stoff=m%8; - int stoff=m-stc*8; - float pp1[8]; - float pp2[8]; - if (stc==sta1) { - for (int cn=0; cn<8; cn++) { - pp1[cn]=0.0f; - pp2[cn]=p[sta2*8+cn]; - } - pp1[stoff]=1.0f; - } else if (stc==sta2) { - for (int cn=0; cn<8; cn++) { - pp2[cn]=0.0f; - pp1[cn]=p[sta1*8+cn]; - } - pp2[stoff]=1.0f; - } - - - cuComplex G1[4]; - G1[0].x=pp1[0]; - G1[0].y=pp1[1]; - G1[1].x=pp1[2]; - G1[1].y=pp1[3]; - G1[2].x=pp1[4]; - G1[2].y=pp1[5]; - G1[3].x=pp1[6]; - G1[3].y=pp1[7]; - - cuComplex T1[4]; - /* T=G1*C */ - T1[0]=cuCaddf(cuCmulf(G1[0],C[0]),cuCmulf(G1[1],C[2])); - T1[1]=cuCaddf(cuCmulf(G1[0],C[1]),cuCmulf(G1[1],C[3])); - T1[2]=cuCaddf(cuCmulf(G1[2],C[0]),cuCmulf(G1[3],C[2])); - T1[3]=cuCaddf(cuCmulf(G1[2],C[1]),cuCmulf(G1[3],C[3])); - - cuComplex G2[4]; - /* conjugate this */ - G2[0].x=pp2[0]; - G2[0].y=-pp2[1]; - G2[2].x=pp2[2]; - G2[2].y=-pp2[3]; - G2[1].x=pp2[4]; - G2[1].y=-pp2[5]; - G2[3].x=pp2[6]; - G2[3].y=-pp2[7]; - - cuComplex T2[4]; - T2[0]=cuCaddf(cuCmulf(T1[0],G2[0]),cuCmulf(T1[1],G2[2])); - T2[1]=cuCaddf(cuCmulf(T1[0],G2[1]),cuCmulf(T1[1],G2[3])); - T2[2]=cuCaddf(cuCmulf(T1[2],G2[0]),cuCmulf(T1[3],G2[2])); - T2[3]=cuCaddf(cuCmulf(T1[2],G2[1]),cuCmulf(T1[3],G2[3])); - /* update jacobian , with row weights */ - /* NOTE: row major order */ - jac[m+M*8*n]=wt[8*n]*T2[0].x; - jac[m+M*(8*n+1)]=wt[8*n+1]*T2[0].y; - jac[m+M*(8*n+2)]=wt[8*n+2]*T2[1].x; - jac[m+M*(8*n+3)]=wt[8*n+3]*T2[1].y; - jac[m+M*(8*n+4)]=wt[8*n+4]*T2[2].x; - jac[m+M*(8*n+5)]=wt[8*n+5]*T2[2].y; - jac[m+M*(8*n+6)]=wt[8*n+6]*T2[3].x; - jac[m+M*(8*n+7)]=wt[8*n+7]*T2[3].y; - - } - } - -} - -__global__ void -kernel_setweights_fl(int N, float *wt, float alpha){ - unsigned int tid = blockIdx.x*blockDim.x + threadIdx.x; - /* make sure to use only M threads */ - if (tid>>(N, wt, alpha); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - -} - -/* hadamard product by a cuda kernel x<= x*wt */ -void -cudakernel_hadamard_fl(int ThreadsPerBlock, int BlocksPerGrid, int N, float *wt, float *x) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - kernel_hadamard_fl<<< BlocksPerGrid, ThreadsPerBlock >>>(N, wt, x); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - -} - -/* update weights by a cuda kernel */ -void -cudakernel_updateweights_fl(int ThreadsPerBlock, int BlocksPerGrid, int N, float *wt, float *x, float *q, float robust_nu) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - kernel_updateweights_fl<<< BlocksPerGrid, ThreadsPerBlock >>>(N, wt, x, q, robust_nu); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - -} - -/* update weights by a cuda kernel */ -void -cudakernel_sqrtweights_fl(int ThreadsPerBlock, int BlocksPerGrid, int N, float *wt) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - kernel_sqrtweights_fl<<< BlocksPerGrid, ThreadsPerBlock >>>(N, wt); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - -} - -/* evaluate expression for finding optimum nu for - a range of nu values */ -void -cudakernel_evaluatenu_fl(int ThreadsPerBlock, int BlocksPerGrid, int Nd, float qsum, float *q, float deltanu,float nulow) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - kernel_evaluatenu_fl<<< BlocksPerGrid, ThreadsPerBlock >>>(Nd, qsum, q, deltanu,nulow); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - -} - - -/* evaluate expression for finding optimum nu for - a range of nu values, using AECM (p=8 before, but now p=2) - nu0: current value of robust_nu*/ -void -cudakernel_evaluatenu_fl_eight(int ThreadsPerBlock, int BlocksPerGrid, int Nd, float qsum, float *q, float deltanu,float nulow, float nu0) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - kernel_evaluatenu_fl_eight<<< BlocksPerGrid, ThreadsPerBlock >>>(Nd, qsum, q, deltanu,nulow, nu0); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - - -} - -/* cuda driver for calculating wt \odot f() */ -/* p: params (Mx1), x: data (Nx1), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -void -cudakernel_func_wt_fl(int ThreadsPerBlock, int BlocksPerGrid, float *p, float *x, int M, int N, float *coh, short *bbh, float *wt, int Nbase, int Mclus, int Nstations) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - cudaMemset(x, 0, N*sizeof(float)); -// printf("Kernel data size=%d, block=%d, thread=%d, baselines=%d\n",N,BlocksPerGrid, ThreadsPerBlock,Nbase); - kernel_func_wt_fl<<< BlocksPerGrid, ThreadsPerBlock >>>(Nbase, x, coh, p, bbh, wt, Nstations); - cudaDeviceSynchronize(); -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - -} - -/* cuda driver for calculating wt \odot jacf() */ -/* p: params (Mx1), jac: jacobian (NxM), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -void -cudakernel_jacf_wt_fl(int ThreadsPerBlock_row, int ThreadsPerBlock_col, float *p, float *jac, int M, int N, float *coh, short *bbh, float *wt, int Nbase, int Mclus, int Nstations, int clus) { - -#ifdef CUDA_DBG - cudaError_t error; -#endif - /* NOTE: use small value for ThreadsPerBlock here, like 8 */ - dim3 threadsPerBlock(16, 8); - /* jacobian: Nbase x Nstations (proportional to N), so */ - dim3 numBlocks((Nbase+threadsPerBlock.x-1)/threadsPerBlock.x, - (M+threadsPerBlock.y-1)/threadsPerBlock.y); - /* set memory of jac to zero */ - cudaMemset(jac, 0, N*M*sizeof(float)); - // printf("Kernel Jax data size=%d, params=%d, block=%d,%d, thread=%d,%d, baselines=%d\n",N, M, numBlocks.x,numBlocks.y, threadsPerBlock.x, threadsPerBlock.y, Nbase); - kernel_jacf_wt_fl<<< numBlocks, threadsPerBlock>>>(Nbase, M, jac, coh, p, bbh, wt, Nstations); - - cudaDeviceSynchronize(); - -#ifdef CUDA_DBG - error = cudaGetLastError(); - if(error != cudaSuccess) - { - // print the CUDA error message and exit - fprintf(stderr,"CUDA error: %s :%s: %d\n", cudaGetErrorString(error),__FILE__,__LINE__); - exit(-1); - } -#endif - -} - -} diff --git a/src/lib/robust_lbfgs_nocuda.c b/src/lib/robust_lbfgs_nocuda.c deleted file mode 100644 index 75be4d0..0000000 --- a/src/lib/robust_lbfgs_nocuda.c +++ /dev/null @@ -1,1063 +0,0 @@ -/* - * - Copyright (C) 2006-2008 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#include "sagecal.h" -#include -#ifdef HAVE_CUDA -#include -#endif - -/**** repeated code here ********************/ -/* Jones matrix multiplication - C=A*B -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -amb(complex double * __restrict a, complex double * __restrict b, complex double * __restrict c) { - c[0]=a[0]*b[0]+a[1]*b[2]; - c[1]=a[0]*b[1]+a[1]*b[3]; - c[2]=a[2]*b[0]+a[3]*b[2]; - c[3]=a[2]*b[1]+a[3]*b[3]; -} - -/* Jones matrix multiplication - C=A*B^H -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -ambt(complex double * __restrict a, complex double * __restrict b, complex double * __restrict c) { - c[0]=a[0]*conj(b[0])+a[1]*conj(b[1]); - c[1]=a[0]*conj(b[2])+a[1]*conj(b[3]); - c[2]=a[2]*conj(b[0])+a[3]*conj(b[1]); - c[3]=a[2]*conj(b[2])+a[3]*conj(b[3]); -} - -/**** end repeated code ********************/ -/***************************************************************/ -/* worker thread to calculate - sum ( log(1+ (y_i-f_i)^2/nu) ) -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -func_robust_th(void *data) { - thread_data_logf_t *t=(thread_data_logf_t*)data; - double inv_nu=1.0/t->nu; - t->sum=0.0; - int ci; - double err; - for (ci=t->start; ci<=t->end; ci++) { - err=t->x[ci]-t->f[ci]; - err=err*err*inv_nu; - t->sum+=log(1.0+err); - } - return NULL; -} -/* recalculate log(1+ (y_i-f_i)^2/nu) - from function() that calculates f_i - y (data) - f=function() - x=log(1+(y_i-f_i)^2/nu) output - all size n x 1 - Nt: no of threads - - return sum(log(..)) -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static double -func_robust( - double *f, double *y, int n, double robust_nu, int Nt) { - - pthread_attr_t attr; - pthread_t *th_array; - thread_data_logf_t *threaddata; - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((threaddata=(thread_data_logf_t*)malloc((size_t)Nt*sizeof(thread_data_logf_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - int ci,nth,Nparm; - Nparm=(n+Nt-1)/Nt; - - ci=0; - for (nth=0; nth=n) { - threaddata[nth].end=n-1; - } - ci=ci+Nparm; - pthread_create(&th_array[nth],&attr,func_robust_th,(void*)(&threaddata[nth])); - - } - /* now wait for threads to finish */ - double mysum=0.0; - for(nth=0; nth0) { - /* find the location of k-1 th value */ - if (ii>0) { - ii=ii-1; - } else { - ii=M-1; - } - /* s,y will have 0,1,...,ii,ii+1,...M-1 */ - /* map this to ii+1,ii+2,...,M-1,0,1,..,ii */ - for (ci=0; ci%d ",ci,idx[ci]); - } - printf("\n"); -#endif - /* q = grad(f)k : pk<=gk */ - my_dcopy(m,gk,1,pk,1); - /* this should be done in the right order */ - for (ci=0; ci0) { - gamma=my_ddot(m,&s[m*idx[M-1]],&y[m*idx[M-1]]); - gamma/=my_ddot(m,&y[m*idx[M-1]],&y[m*idx[M-1]]); - /* Hk(0)=gamma I, so scale q by gamma */ - /* r= Hk(0) q */ - my_dscal(m,gamma,pk); - } - - for (ci=0; cib is possible) - to find step that minimizes cost function */ -/* func: vector function - xk: parameter values size m x 1 (at which step is calculated) - pk: step direction size m x 1 (x(k+1)=x(k)+alphak * pk) - a/b: interval for interpolation - x: size n x 1 (storage) - xp: size m x 1 (storage) - xo: observed data size n x 1 - n: size of vector function - step: step size for differencing - adata: additional data passed to the function -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static double -cubic_interp( - void (*func)(double *p, double *hx, int m, int n, void *adata), - double *xk, double *pk, double a, double b, double *x, double *xp, double *xo, int m, int n, double step, void *adata) { - - me_data_t *dp=(me_data_t*)adata; - double f0,f1,f0d,f1d; /* function values and derivatives at a,b */ - double p01,p02,z0,fz0; - double aa,cc; - - my_dcopy(m,xk,1,xp,1); /* xp<=xk */ - my_daxpy(m,pk,a,xp); /* xp<=xp+(a)*pk */ - func(xp,x,m,n,adata); - //my_daxpy(n,xo,-1.0,x); - //f0=my_dnrm2(n,x); - //f0*=f0; - f0=func_robust(x,xo,n,dp->robust_nu,dp->Nt); - /* grad(phi_0): evaluate at -step and +step */ - my_daxpy(m,pk,step,xp); /* xp<=xp+(a+step)*pk */ - func(xp,x,m,n,adata); - //my_daxpy(n,xo,-1.0,x); - //p01=my_dnrm2(n,x); - p01=func_robust(x,xo,n,dp->robust_nu,dp->Nt); - my_daxpy(m,pk,-2.0*step,xp); /* xp<=xp+(a-step)*pk */ - func(xp,x,m,n,adata); - //my_daxpy(n,xo,-1.0,x); - //p02=my_dnrm2(n,x); - p02=func_robust(x,xo,n,dp->robust_nu,dp->Nt); - f0d=(p01-p02)/(2.0*step); - - my_dcopy(m,xk,1,xp,1); /* xp<=xk */ - my_daxpy(m,pk,b,xp); /* xp<=xp+(b)*pk */ - func(xp,x,m,n,adata); - //my_daxpy(n,xo,-1.0,x); - //f1=my_dnrm2(n,x); - //f1*=f1; - f1=func_robust(x,xo,n,dp->robust_nu,dp->Nt); - /* grad(phi_1): evaluate at -step and +step */ - my_daxpy(m,pk,step,xp); /* xp<=xp+(b+step)*pk */ - func(xp,x,m,n,adata); - //my_daxpy(n,xo,-1.0,x); - //p01=my_dnrm2(n,x); - p01=func_robust(x,xo,n,dp->robust_nu,dp->Nt); - my_daxpy(m,pk,-2.0*step,xp); /* xp<=xp+(b-step)*pk */ - func(xp,x,m,n,adata); - //my_daxpy(n,xo,-1.0,x); - //p02=my_dnrm2(n,x); - p02=func_robust(x,xo,n,dp->robust_nu,dp->Nt); - f1d=(p01-p02)/(2.0*step); - - - //printf("Interp a,f(a),f'(a): (%lf,%lf,%lf) (%lf,%lf,%lf)\n",a,f0,f0d,b,f1,f1d); - /* cubic poly in [0,1] is f0+f0d z+eta z^2+xi z^3 - where eta=3(f1-f0)-2f0d-f1d, xi=f0d+f1d-2(f1-f0) - derivative f0d+2 eta z+3 xi z^2 => cc+bb z+aa z^2 */ - aa=3.0*(f0-f1)/(b-a)+(f1d-f0d); - p01=aa*aa-f0d*f1d; - /* root exist? */ - if (p01>0.0) { - /* root */ - cc=sqrt(p01); - z0=b-(f1d+cc-aa)*(b-a)/(f1d-f0d+2.0*cc); - /* FIXME: check if this is within boundary */ - aa=MAX(a,b); - cc=MIN(a,b); - //printf("Root=%lf, in [%lf,%lf]\n",z0,cc,aa); - if (z0>aa || z0robust_nu,dp->Nt); - } - - /* now choose between f0,f1,fz0,fz1 */ - if (f0b) is possible - x: size n x 1 (storage) - xp: size m x 1 (storage) - phi_0: phi(0) - gphi_0: grad(phi(0)) - xo: observed data size n x 1 - n: size of vector function - step: step size for differencing - adata: additional data passed to the function -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static double -linesearch_zoom( - void (*func)(double *p, double *hx, int m, int n, void *adata), - double *xk, double *pk, double a, double b, double *x, double *xp, double phi_0, double gphi_0, double sigma, double rho, double t1, double t2, double t3, double *xo, int m, int n, double step, void *adata) { - - me_data_t *dp=(me_data_t*)adata; - double alphaj,phi_j,phi_aj; - double gphi_j,p01,p02,aj,bj; - double alphak=1.0; - int ci,found_step=0; - - aj=a; - bj=b; - ci=0; - while(ci<10) { - /* choose alphaj from [a+t2(b-a),b-t3(b-a)] */ - p01=aj+t2*(bj-aj); - p02=bj-t3*(bj-aj); - alphaj=cubic_interp(func,xk,pk,p01,p02,x,xp,xo,m,n,step,adata); - //printf("cubic intep [%lf,%lf]->%lf\n",p01,p02,alphaj); - - /* evaluate phi(alphaj) */ - my_dcopy(m,xk,1,xp,1); /* xp<=xk */ - my_daxpy(m,pk,alphaj,xp); /* xp<=xp+(alphaj)*pk */ - func(xp,x,m,n,adata); - /* calculate x<=x-xo */ - //my_daxpy(n,xo,-1.0,x); - //phi_j=my_dnrm2(n,x); - //phi_j*=phi_j; - phi_j=func_robust(x,xo,n,dp->robust_nu,dp->Nt); - - /* evaluate phi(aj) */ - my_dcopy(m,xk,1,xp,1); /* xp<=xk */ - my_daxpy(m,pk,aj,xp); /* xp<=xp+(alphaj)*pk */ - func(xp,x,m,n,adata); - /* calculate x<=x-xo */ - //my_daxpy(n,xo,-1.0,x); - //phi_aj=my_dnrm2(n,x); - //phi_aj*=phi_aj; - phi_aj=func_robust(x,xo,n,dp->robust_nu,dp->Nt); - -#ifdef DEBUG - printf("phi_j=%lf, phi_aj=%lf\n",phi_j,phi_aj); -#endif - if ((phi_j>phi_0+rho*alphaj*gphi_0) || phi_j>=phi_aj) { - bj=alphaj; /* aj unchanged */ - } else { - /* evaluate grad(alphaj) */ - my_dcopy(m,xk,1,xp,1); /* xp<=xk */ - my_daxpy(m,pk,alphaj+step,xp); /* xp<=xp+(alphaj+step)*pk */ - func(xp,x,m,n,adata); - /* calculate x<=x-xo */ - //my_daxpy(n,xo,-1.0,x); - //p01=my_dnrm2(n,x); - p01=func_robust(x,xo,n,dp->robust_nu,dp->Nt); - - my_daxpy(m,pk,-2.0*step,xp); /* xp<=xp+(alphaj-step)*pk */ - func(xp,x,m,n,adata); - /* calculate x<=x-xo */ - //my_daxpy(n,xo,-1.0,x); - //p02=my_dnrm2(n,x); - p02=func_robust(x,xo,n,dp->robust_nu,dp->Nt); - gphi_j=(p01-p02)/(2.0*step); -#ifdef DEBUG - printf("p01=%lf, p02=%lf\n",p01,p02); -#endif - - /* termination due to roundoff/other errors pp. 38, Fletcher */ - if ((aj-alphaj)*gphi_j<=step) { - alphak=alphaj; - found_step=1; - break; - } - - if (fabs(gphi_j)<=-sigma*gphi_0) { - alphak=alphaj; - found_step=1; - break; - } - - if (gphi_j*(bj-aj)>=0) { - bj=aj; - } /* else bj unchanged */ - aj=alphaj; - } - ci++; - } - - if (!found_step) { - /* use bound to find possible step */ - alphak=alphaj; - } - -#ifdef DEBUG - printf("Found %lf Interval [%lf,%lf]\n",alphak,a,b); -#endif - return alphak; -} - - - -/* line search */ -/* func: vector function - xk: parameter values size m x 1 (at which step is calculated) - pk: step direction size m x 1 (x(k+1)=x(k)+alphak * pk) - alpha1: initial value for step - sigma,rho,t1,t2,t3: line search parameters (from Fletcher) - xo: observed data size n x 1 - n: size of vector function - step: step size for differencing - adata: additional data passed to the function -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static double -linesearch( - void (*func)(double *p, double *hx, int m, int n, void *adata), - double *xk, double *pk, double alpha1, double sigma, double rho, double t1, double t2, double t3, double *xo, int m, int n, double step, void *adata) { - - /* phi(alpha)=f(xk+alpha pk) - for vector function func - f(xk) =||func(xk)||^2 */ - - me_data_t *dp=(me_data_t*)adata; - double *x,*xp; - double alphai,alphai1; - double phi_0,phi_alphai,phi_alphai1; - double p01,p02; - double gphi_0,gphi_i; - double alphak; - - double mu; - double tol; /* lower limit for minimization */ - - int ci; - - if ((x=(double*)calloc((size_t)n,sizeof(double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((xp=(double*)calloc((size_t)m,sizeof(double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - alphak=1.0; - /* evaluate phi_0 and grad(phi_0) */ - func(xk,x,m,n,adata); - //my_daxpy(n,xo,-1.0,x); - //phi_0=my_dnrm2(n,x); - //phi_0*=phi_0; - phi_0=func_robust(x,xo,n,dp->robust_nu,dp->Nt); - /* select tolarance 1/100 of current function value */ - tol=MIN(0.01*phi_0,1e-6); - - - /* grad(phi_0): evaluate at -step and +step */ - my_dcopy(m,xk,1,xp,1); /* xp<=xk */ - my_daxpy(m,pk,step,xp); /* xp<=xp+(0.0+step)*pk */ - func(xp,x,m,n,adata); - /* calculate x<=x-xo */ - //my_daxpy(n,xo,-1.0,x); - //p01=my_dnrm2(n,x); - p01=func_robust(x,xo,n,dp->robust_nu,dp->Nt); - my_daxpy(m,pk,-2.0*step,xp); /* xp<=xp+(0.0-step)*pk */ - func(xp,x,m,n,adata); - /* calculate x<=x-xo */ - //my_daxpy(n,xo,-1.0,x); - //p02=my_dnrm2(n,x); - p02=func_robust(x,xo,n,dp->robust_nu,dp->Nt); - gphi_0=(p01-p02)/(2.0*step); - - - /* estimate for mu */ - /* mu = (tol-phi_0)/(rho gphi_0) */ - mu=(tol-phi_0)/(rho*gphi_0); -#ifdef DEBUG - printf("cost=%lf grad=%lf mu=%lf, alpha1=%lf\n",phi_0,gphi_0,mu,alpha1); -#endif - - ci=1; - alphai=alpha1; /* initial value for alpha(i) : check if 0robust_nu,dp->Nt); - - if (phi_alphaiphi_0+alphai*gphi_0) || (ci>1 && phi_alphai>=phi_alphai1)) { - /* ai=alphai1, bi=alphai bracket */ - alphak=linesearch_zoom(func,xk,pk,alphai1,alphai,x,xp,phi_0,gphi_0,sigma,rho,t1,t2,t3,xo,m,n,step,adata); -#ifdef DEBUG - printf("Linesearch : Condition 1 met\n"); -#endif - break; - } - - /* evaluate grad(phi(alpha(i))) */ - my_dcopy(m,xk,1,xp,1); /* NOT NEEDED here?? xp<=xk */ - my_daxpy(m,pk,alphai+step,xp); /* xp<=xp+(alphai+step)*pk */ - func(xp,x,m,n,adata); - /* calculate x<=x-xo */ - //my_daxpy(n,xo,-1.0,x); - //p01=my_dnrm2(n,x); - p01=func_robust(x,xo,n,dp->robust_nu,dp->Nt); - my_daxpy(m,pk,-2.0*step,xp); /* xp<=xp+(alphai-step)*pk */ - func(xp,x,m,n,adata); - /* calculate x<=x-xo */ - //my_daxpy(n,xo,-1.0,x); - //p02=my_dnrm2(n,x); - p01=func_robust(x,xo,n,dp->robust_nu,dp->Nt); - gphi_i=(p01-p02)/(2.0*step); - - if (fabs(gphi_i)<=-sigma*gphi_0) { - alphak=alphai; -#ifdef DEBUG - printf("Linesearch : Condition 2 met\n"); -#endif - break; - } - - if (gphi_i>=0) { - /* ai=alphai, bi=alphai1 bracket */ - alphak=linesearch_zoom(func,xk,pk,alphai,alphai1,x,xp,phi_0,gphi_0,sigma,rho,t1,t2,t3,xo,m,n,step,adata); -#ifdef DEBUG - printf("Linesearch : Condition 3 met\n"); -#endif - break; - } - - /* else preserve old values */ - if (mu<=(2*alphai-alphai1)) { - /* next step */ - alphai1=alphai; - alphai=mu; - } else { - /* choose by interpolation in [2*alphai-alphai1,min(mu,alphai+t1*(alphai-alphai1)] */ - p01=2*alphai-alphai1; - p02=MIN(mu,alphai+t1*(alphai-alphai1)); - alphai=cubic_interp(func,xk,pk,p01,p02,x,xp,xo,m,n,step,adata); - //printf("cubic interp [%lf,%lf]->%lf\n",p01,p02,alphai); - } - phi_alphai1=phi_alphai; - - ci++; - } - - - - free(x); - free(xp); -#ifdef DEBUG - printf("Step size=%lf\n",alphak); -#endif - return alphak; -} -/*************** END Fletcher line search **********************************/ - -/*************************************** ROBUST ***************************/ -/* worker thread for a cpu */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -cpu_calc_deriv_robust(void *adata) { - thread_data_grad_t *t=(thread_data_grad_t*)adata; - - int ci,nb; - int stc,stoff,stm,sta1,sta2; - int N=t->N; /* stations */ - int M=t->M; /* clusters */ - int Nbase=(t->Nbase)*(t->tilesz); - - - double xr[8]; /* residuals */ - complex double G1[4],G2[4],C[4],T1[4],T2[4]; - double pp[8]; - double dsum; - int cli,tpchunk,pstart,nchunk,tilesperchunk,stci,ttile,tptile,poff; - double nu=t->robust_nu; - - /* iterate over each paramter */ - for (ci=t->g_start; ci<=t->g_end; ++ci) { - t->g[ci]=0.0; - /* find station and parameter corresponding to this value of ci */ - /* this parameter should correspond to the right baseline (x tilesz) - to contribute to the derivative */ - cli=0; - while((clicarr[cli].p[0] || ci>t->carr[cli].p[0]+8*N*t->carr[cli].nchunk-1)) { - cli++; - } - /* now either cli>=M: cluster not found - or cli=t->carr[cli-1].p[0] && ci<=t->carr[cli-1].p[0]+8*N*t->carr[cli-1].nchunk-1) { - cli--; - } - - if (clicarr[cli].p[0]; - - stc=(stci%(8*N))/8; /* 0..N-1 */ - /* make sure this baseline contribute to this parameter */ - tpchunk=stci/(8*N); - nchunk=t->carr[cli].nchunk; - pstart=t->carr[cli].p[0]; - tilesperchunk=(t->tilesz+nchunk-1)/nchunk; - - - /* iterate over all baselines and accumulate sum */ - for (nb=0; nbNbase; - /* which chunk this tile belongs to */ - tptile=ttile/tilesperchunk; - /* now tptile has to match tpchunk, otherwise ignore calculation */ - if (tptile==tpchunk) { - - sta1=t->barr[nb].sta1; - sta2=t->barr[nb].sta2; - - if (((stc==sta1)||(stc==sta2))&& !t->barr[nb].flag) { - /* this baseline has a contribution */ - /* which paramter of this station */ - stoff=(stci%(8*N))%8; /* 0..7 */ - /* which cluster */ - stm=cli; /* 0..M-1 */ - - /* exact expression for derivative - for Gaussian \sum( y_i - f_i(\theta))^2 - 2 real( vec^H(residual_this_baseline) - * vec(-J_{pm}C_{pqm} J_{qm}^H) - where m: chosen cluster - J_{pm},J_{qm} Jones matrices for baseline p-q - depending on the parameter, J ==> E - E: zero matrix, except 1 at location of m - \sum( 2 (y_i-f_i) * -\partical (f_i)/ \partial\theta - - for robust \sum( log(1+ (y_i-f_i(\theta))^2/\nu) ) - all calculations are like for the Gaussian case, except - when taking summation - \sum( 1/(\nu+(y_i-f_i)^2) 2 (y_i-f_i) * -\partical (f_i)/ \partial\theta - - so additonal multiplication by 1/(\nu+(y_i-f_i)^2) - */ - /* read in residual vector, (real,imag) separately */ - xr[0]=t->x[nb*8]; - xr[1]=t->x[nb*8+1]; - xr[2]=t->x[nb*8+2]; - xr[3]=t->x[nb*8+3]; - xr[4]=t->x[nb*8+4]; - xr[5]=t->x[nb*8+5]; - xr[6]=t->x[nb*8+6]; - xr[7]=t->x[nb*8+7]; - - /* read in coherency */ - C[0]=t->coh[4*M*nb+4*stm]; - C[1]=t->coh[4*M*nb+4*stm+1]; - C[2]=t->coh[4*M*nb+4*stm+2]; - C[3]=t->coh[4*M*nb+4*stm+3]; - - memset(pp,0,sizeof(double)*8); - if (stc==sta1) { - /* this station parameter gradient */ - pp[stoff]=1.0; - memset(G1,0,sizeof(complex double)*4); - G1[0]=pp[0]+_Complex_I*pp[1]; - G1[1]=pp[2]+_Complex_I*pp[3]; - G1[2]=pp[4]+_Complex_I*pp[5]; - G1[3]=pp[6]+_Complex_I*pp[7]; - poff=pstart+tpchunk*8*N+sta2*8; - G2[0]=(t->p[poff])+_Complex_I*(t->p[poff+1]); - G2[1]=(t->p[poff+2])+_Complex_I*(t->p[poff+3]); - G2[2]=(t->p[poff+4])+_Complex_I*(t->p[poff+4]); - G2[3]=(t->p[poff+6])+_Complex_I*(t->p[poff+7]); - - } else if (stc==sta2) { - memset(G2,0,sizeof(complex double)*4); - pp[stoff]=1.0; - G2[0]=pp[0]+_Complex_I*pp[1]; - G2[1]=pp[2]+_Complex_I*pp[3]; - G2[2]=pp[4]+_Complex_I*pp[5]; - G2[3]=pp[6]+_Complex_I*pp[7]; - poff=pstart+tpchunk*8*N+sta1*8; - G1[0]=(t->p[poff])+_Complex_I*(t->p[poff+1]); - G1[1]=(t->p[poff+2])+_Complex_I*(t->p[poff+3]); - G1[2]=(t->p[poff+4])+_Complex_I*(t->p[poff+5]); - G1[3]=(t->p[poff+6])+_Complex_I*(t->p[poff+7]); - } - - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - - /* calculate product xr*vec(J_p C J_q^H )/(nu+residual^2) */ - dsum=xr[0]*creal(T2[0])/(nu+xr[0]*xr[0]); - dsum+=xr[1]*cimag(T2[0])/(nu+xr[1]*xr[1]); - dsum+=xr[2]*creal(T2[1])/(nu+xr[2]*xr[2]); - dsum+=xr[3]*cimag(T2[1])/(nu+xr[3]*xr[3]); - dsum+=xr[4]*creal(T2[2])/(nu+xr[4]*xr[4]); - dsum+=xr[5]*cimag(T2[2])/(nu+xr[5]*xr[5]); - dsum+=xr[6]*creal(T2[3])/(nu+xr[6]*xr[6]); - dsum+=xr[7]*cimag(T2[3])/(nu+xr[7]*xr[7]); - - /* accumulate sum NOTE - its important to get the sign right, - depending on res=data-model or res=model-data */ - t->g[ci]+=2.0*(dsum); - } - } - } - } - } - - - return NULL; -} -/* calculate gradient */ -/* func: vector function - p: parameter values size m x 1 (at which grad is calculated) - g: gradient size m x 1 - xo: observed data size n x 1 - robust_nu: nu in T distribution - n: size of vector function - step: step size for differencing - adata: additional data passed to the function -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static int -func_grad_robust( - void (*func)(double *p, double *hx, int m, int n, void *adata), - double *p, double *g, double *xo, int m, int n, double step, void *adata) { - /* gradient for each parameter is - (||func(p+step*e_i)-x||^2-||func(p-step*e_i)-x||^2)/2*step - i=0,...,m-1 for all parameters - e_i: unit vector, 1 only at i-th location - */ - - double *x; /* array to store residual */ - int ci; - me_data_t *dp=(me_data_t*)adata; - - int Nt=dp->Nt; - - pthread_attr_t attr; - pthread_t *th_array; - thread_data_grad_t *threaddata; - - - if ((x=(double*)calloc((size_t)n,sizeof(double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - /* evaluate func once, store in x, and create threads */ - /* and calculate the residual x=xo-func */ - func(p,x,m,n,adata); - /* calculate x<=x-xo */ - my_daxpy(n,xo,-1.0,x); - - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((threaddata=(thread_data_grad_t*)malloc((size_t)Nt*sizeof(thread_data_grad_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - int nth,Nparm; - - /* parameters per thread */ - Nparm=(m+Nt-1)/Nt; - - /* each thread will calculate derivative of part of - parameters */ - ci=0; - for (nth=0; nthNbase; - threaddata[nth].tilesz=dp->tilesz; - threaddata[nth].barr=dp->barr; - threaddata[nth].carr=dp->carr; - threaddata[nth].M=dp->M; - threaddata[nth].N=dp->N; - threaddata[nth].coh=dp->coh; - threaddata[nth].m=m; - threaddata[nth].n=n; - threaddata[nth].x=x; - threaddata[nth].p=p; - threaddata[nth].g=g; - threaddata[nth].robust_nu=dp->robust_nu; - threaddata[nth].g_start=ci; - threaddata[nth].g_end=ci+Nparm-1; - if (threaddata[nth].g_end>=m) { - threaddata[nth].g_end=m-1; - } - ci=ci+Nparm; - pthread_create(&th_array[nth],&attr,cpu_calc_deriv_robust,(void*)(&threaddata[nth])); - } - - /* now wait for threads to finish */ - for(nth=0; nth - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#include -#include -#include -#include -#include - -#include "sagecal.h" - -#ifdef HAVE_CUDA -#include -/* helper functions for diagnostics */ -static void -checkCudaError(cudaError_t err, char *file, int line) -{ -#ifdef CUDA_DEBUG - if(!err) - return; - printf("GPU (CUDA): %s %s %d\n", cudaGetErrorString(err),file,line); - exit(EXIT_FAILURE); -#endif -} - - -static void -checkCublasError(cublasStatus_t cbstatus, char *file, int line) -{ -#ifdef CUDA_DEBUG - if (cbstatus!=CUBLAS_STATUS_SUCCESS) { - printf("%s: %d: CUBLAS failure\n",file,line); - exit(EXIT_FAILURE); - } -#endif -} - - - -/* robust, iteratively weighted non linear least squares using LM - entirely in the GPU */ -int -rlevmar_der_single_cuda( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - double *gWORK, /* GPU allocated memory */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - void *adata) /* pointer to possibly additional data, passed uninterpreted to func & jacf. - * Set to NULL if not needed - */ -{ - - /* general note: all device variables end with a 'd' */ - int stop=0; - cudaError_t err; - cublasStatus_t cbstatus; - - int nu=2,nu2; - double p_L2, Dp_L2=DBL_MAX, dF, dL, p_eL2, jacTe_inf=0.0, pDp_eL2, init_p_eL2; - double tmp,mu=0.0; - double tau, eps1, eps2, eps2_sq, eps3; - int k,ci,issolved; - - double *hxd; - double *wtd,*qd; - - int nw,wt_itmax=3; - /* ME data */ - me_data_t *dp=(me_data_t*)adata; - double wt_sum,lambda,robust_nu=dp->robust_nu; - double q_sum,robust_nu1; - double deltanu; - int Nd=100; /* no of points where nu is sampled, note NdN) { Nd=N; } - /* only search for nu in [2,30] because 30 is almost Gaussian */ - deltanu=(robust_nuhigh-robust_nulow)/(double)Nd; - - - double *ed; - double *xd; - - double *jacd; - - double *jacTjacd,*jacTjacd0; - - double *Dpd,*bd; - double *pd,*pnewd; - double *jacTed; - - /* used in QR solver */ - double *taud; - - /* used in SVD solver */ - double *Ud; - double *VTd; - double *Sd; - - int Nbase=(dp->Nbase)*(ntiles); /* note: we do not use the total tile size */ - /* coherency on device */ - double *cohd; - /* baseline-station map on device/host */ - short *bbd; - - int solve_axb=linsolv; - double alpha; - - /* setup default settings */ - if(opts){ - tau=opts[0]; - eps1=opts[1]; - eps2=opts[2]; - eps2_sq=opts[2]*opts[2]; - eps3=opts[3]; - } else { - tau=CLM_INIT_MU; - eps1=CLM_STOP_THRESH; - eps2=CLM_STOP_THRESH; - eps2_sq=CLM_STOP_THRESH*CLM_STOP_THRESH; - eps3=CLM_STOP_THRESH; - } - - /* calculate no of cuda threads and blocks */ - int ThreadsPerBlock=DEFAULT_TH_PER_BK; - int ThreadsPerBlock1=DEFAULT_TH_PER_BK; /* DEFAULT_TH_PER_BK/8 for accessing each element of a baseline */ - int ThreadsPerBlock2=Nd/2; /* for evaluating nu */ - int BlocksPerGrid=(M+ThreadsPerBlock-1)/ThreadsPerBlock; - - - unsigned long int moff; - if (!gWORK) { - err=cudaMalloc((void**)&xd, N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&jacd, M*N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&jacTjacd, M*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&jacTed, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&jacTjacd0, M*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&Dpd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&bd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&pd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&pnewd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - /* needed for calculating f() and jac() */ - err=cudaMalloc((void**) &bbd, Nbase*2*sizeof(short)); - checkCudaError(err,__FILE__,__LINE__); - /* we need coherencies for only this cluster */ - err=cudaMalloc((void**) &cohd, Nbase*8*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&hxd, N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&wtd, N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&qd, N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&ed, N*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - /* memory allocation: different solvers */ - if (solve_axb==1) { - err=cudaMalloc((void**)&taud, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } else if (solve_axb==2) { - err=cudaMalloc((void**)&Ud, M*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&VTd, M*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&Sd, M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } - } else { - moff=0; - xd=&gWORK[moff]; - moff+=N; - jacd=&gWORK[moff]; - moff+=M*N; - jacTjacd=&gWORK[moff]; - moff+=M*M; - jacTed=&gWORK[moff]; - moff+=M; - jacTjacd0=&gWORK[moff]; - moff+=M*M; - Dpd=&gWORK[moff]; - moff+=M; - bd=&gWORK[moff]; - moff+=M; - pd=&gWORK[moff]; - moff+=M; - pnewd=&gWORK[moff]; - moff+=M; - cohd=&gWORK[moff]; - moff+=Nbase*8; - hxd=&gWORK[moff]; - moff+=N; - ed=&gWORK[moff]; - moff+=N; - wtd=&gWORK[moff]; - moff+=N; - qd=&gWORK[moff]; - moff+=N; - if (solve_axb==1) { - taud=&gWORK[moff]; - moff+=M; - } else if (solve_axb==2) { - Ud=&gWORK[moff]; - moff+=M*M; - VTd=&gWORK[moff]; - moff+=M*M; - Sd=&gWORK[moff]; - moff+=M; - } - bbd=(short*)&gWORK[moff]; - moff+=(Nbase*2*sizeof(short))/sizeof(double); - } - - /* extra storage for cusolver */ - int work_size=0; - int *devInfo; - int devInfo_h=0; - err=cudaMalloc((void**)&devInfo, sizeof(int)); - checkCudaError(err,__FILE__,__LINE__); - double *work; - double *rwork; - if (solve_axb==0) { - cusolverDnDpotrf_bufferSize(solver_handle, CUBLAS_FILL_MODE_UPPER, M, jacTjacd, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } else if (solve_axb==1) { - cusolverDnDgeqrf_bufferSize(solver_handle, M, M, jacTjacd, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } else { - cusolverDnDgesvd_bufferSize(solver_handle, M, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&rwork, 5*M*sizeof(double)); - checkCudaError(err,__FILE__,__LINE__); - } - - err=cudaMemcpy(pd, p, M*sizeof(double), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - /* need to give right offset for coherencies */ - /* offset: cluster offset+time offset */ - err=cudaMemcpy(cohd, &(dp->ddcoh[(dp->Nbase)*(dp->tilesz)*(dp->clus)*8+(dp->Nbase)*tileoff*8]), Nbase*8*sizeof(double), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - /* correct offset for baselines */ - err=cudaMemcpy(bbd, &(dp->ddbase[2*(dp->Nbase)*(tileoff)]), Nbase*2*sizeof(short), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - cudaDeviceSynchronize(); - /* xd <=x */ - err=cudaMemcpy(xd, x, N*sizeof(double), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - - /* set initial weights to 1 by a cuda kernel */ - cudakernel_setweights(ThreadsPerBlock1, (N+ThreadsPerBlock1-1)/ThreadsPerBlock1, N, wtd, 1.0); - /* weight calculation loop */ - for (nw=0; nwstat mapping, Nbase, Mclusters, Nstations*/ - cudakernel_func_wt(ThreadsPerBlock, (Nbase+ThreadsPerBlock-1)/ThreadsPerBlock, pd,hxd,M,N, cohd, bbd, wtd, Nbase, dp->M, dp->N); - - /* e=x */ - cbstatus=cublasDcopy(cbhandle, N, xd, 1, ed, 1); - /* e = e \odot wt */ - cudakernel_hadamard(ThreadsPerBlock1, (N+ThreadsPerBlock1-1)/ThreadsPerBlock1, N, wtd, ed); - /* e=x-hx */ - alpha=-1.0; - cbstatus=cublasDaxpy(cbhandle, N, &alpha, hxd, 1, ed, 1); - - /* norm ||e|| */ - cbstatus=cublasDnrm2(cbhandle, N, ed, 1, &p_eL2); - /* square */ - p_eL2=p_eL2*p_eL2; - - init_p_eL2=p_eL2; - if(!finite(p_eL2)) stop=7; - - - /**** iteration loop ***********/ - for(k=0; kstat mapping, Nbase, Mclusters, Nstations*/ - /* FIXME thread/block sizes 16x16=256, so 16 is chosen */ - cudakernel_jacf_wt(ThreadsPerBlock, ThreadsPerBlock/4, pd, jacd, M, N, cohd, bbd, wtd, Nbase, dp->M, dp->N); - - /* Compute J^T J and J^T e */ - /* Cache efficient computation of J^T J based on blocking - */ - /* since J is in ROW major order, assume it is transposed, - so actually calculate A=J*J^T, where J is size MxN */ - //status=culaDeviceDgemm('N','T',M,M,N,1.0,jacd,M,jacd,M,0.0,jacTjacd,M); - //checkStatus(status,__FILE__,__LINE__); - double cone=1.0; double czero=0.0; - cbstatus=cublasDgemm(cbhandle,CUBLAS_OP_N,CUBLAS_OP_T,M,M,N,&cone,jacd,M,jacd,M,&czero,jacTjacd,M); - - /* create backup */ - /* copy jacTjacd0<=jacTjacd */ - cbstatus=cublasDcopy(cbhandle, M*M, jacTjacd, 1, jacTjacd0, 1); - /* J^T e */ - /* calculate b=J^T*e (actually compute b=J*e, where J in row major (size MxN) */ - //status=culaDeviceDgemv('N',M,N,1.0,jacd,M,ed,1,0.0,jacTed,1); - //checkStatus(status,__FILE__,__LINE__); - cbstatus=cublasDgemv(cbhandle,CUBLAS_OP_N,M,N,&cone,jacd,M,ed,1,&czero,jacTed,1); - - - /* Compute ||J^T e||_inf and ||p||^2 */ - /* find infinity norm of J^T e, 1 based indexing*/ - cbstatus=cublasIdamax(cbhandle, M, jacTed, 1, &ci); - err=cudaMemcpy(&jacTe_inf,&(jacTed[ci-1]),sizeof(double),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - /* L2 norm of current parameter values */ - /* norm ||Dp|| */ - cbstatus=cublasDnrm2(cbhandle, M, pd, 1, &p_L2); - p_L2=p_L2*p_L2; - if(jacTe_inf<0.0) {jacTe_inf=-jacTe_inf;} -#ifdef DEBUG - printf("Inf norm=%lf\n",jacTe_inf); -#endif - - /* check for convergence */ - if((jacTe_inf <= eps1)){ - Dp_L2=0.0; /* no increment for p in this case */ - stop=1; - break; - } - - /* compute initial (k=0) damping factor */ - if (k==0) { - /* find max diagonal element (stride is M+1) */ - /* should be MAX not MAX(ABS) */ - cbstatus=cublasIdamax(cbhandle, M, jacTjacd, M+1, &ci); /* 1 based index */ - ci=(ci-1)*(M+1); /* right value of the diagonal */ - - err=cudaMemcpy(&tmp,&(jacTjacd[ci]),sizeof(double),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - mu=tau*tmp; - } - - - /* determine increment using adaptive damping */ - while(1){ - /* augment normal equations */ - /* increment A => A+ mu*I, increment diagonal entries */ - /* copy jacTjacd<=jacTjacd0 */ - cbstatus=cublasDcopy(cbhandle, M*M, jacTjacd0, 1, jacTjacd, 1); - cudakernel_diagmu(ThreadsPerBlock, BlocksPerGrid, M, jacTjacd, mu); - -#ifdef DEBUG - printf("mu=%lf\n",mu); -#endif -/*************************************************************************/ - issolved=0; - /* solve augmented equations A x = b */ - /* A==jacTjacd, b==Dpd, after solving, x==Dpd */ - /* b=jacTed : intially right hand side, at exit the solution */ - if (solve_axb==0) { - /* Cholesky solver **********************/ - /* lower triangle of Ad is destroyed */ - //status=culaDeviceDpotrf('U',M,jacTjacd,M); - cusolverDnDpotrf(solver_handle, CUBLAS_FILL_MODE_UPPER, M, jacTjacd, M, work, work_size, devInfo); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (!devInfo_h) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - printf("Singular matrix\n"); -#endif - } - if (issolved) { - /* copy Dpd<=jacTed */ - cbstatus=cublasDcopy(cbhandle, M, jacTed, 1, Dpd, 1); -#ifdef DEBUG - checkCublasError(cbstatus,__FILE__,__LINE__); -#endif - //status=culaDeviceDpotrs('U',M,1,jacTjacd,M,Dpd,M); - cusolverDnDpotrs(solver_handle, CUBLAS_FILL_MODE_UPPER,M,1,jacTjacd,M,Dpd,M,devInfo); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (devInfo_h) { - issolved=0; -#ifdef DEBUG - printf("Singular matrix\n"); -#endif - } - } - } else if (solve_axb==1) { - /* QR solver ********************************/ - //status=culaDeviceDgeqrf(M,M,jacTjacd,M,taud); - cusolverDnDgeqrf(solver_handle, M, M, jacTjacd, M, taud, work, work_size, devInfo); - cudaDeviceSynchronize(); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (!devInfo_h) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - printf("Singular matrix\n"); -#endif - } - - if (issolved) { - /* copy Dpd<=jacTed */ - cbstatus=cublasDcopy(cbhandle, M, jacTed, 1, Dpd, 1); - //status=culaDeviceDgeqrs(M,M,1,jacTjacd,M,taud,Dpd,M); - cusolverDnDormqr(solver_handle, CUBLAS_SIDE_LEFT, CUBLAS_OP_T, M, 1, M, jacTjacd, M, taud, Dpd, M, work, work_size, devInfo); - cudaDeviceSynchronize(); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (devInfo_h) { - issolved=0; -#ifdef DEBUG - printf("Singular matrix\n"); -#endif - } else { - cone=1.0; - cbstatus=cublasDtrsm(cbhandle,CUBLAS_SIDE_LEFT,CUBLAS_FILL_MODE_UPPER,CUBLAS_OP_N,CUBLAS_DIAG_NON_UNIT,M,1,&cone,jacTjacd,M,Dpd,M); - } - } - } else { - /* SVD solver *********************************/ - /* U S VT = A */ - //status=culaDeviceDgesvd('A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M); - //checkStatus(status,__FILE__,__LINE__); - cusolverDnDgesvd(solver_handle,'A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M,work,work_size,rwork,devInfo); - cudaDeviceSynchronize(); - /* copy Dpd<=jacTed */ - cbstatus=cublasDcopy(cbhandle, M, jacTed, 1, Dpd, 1); - /* b<=U^T * b */ - //status=culaDeviceDgemv('T',M,M,1.0,Ud,M,Dpd,1,0.0,Dpd,1); - cone=1.0; czero=0.0; - cbstatus=cublasDgemv(cbhandle,CUBLAS_OP_T,M,M,&cone,Ud,M,Dpd,1,&czero,Dpd,1); - /* divide by singular values Dpd[]/Sd[] for Sd[]> eps1 */ - cudakernel_diagdiv(ThreadsPerBlock, BlocksPerGrid, M, eps1, Dpd, Sd); - - /* b<=VT^T * b */ - //status=culaDeviceDgemv('T',M,M,1.0,VTd,M,Dpd,1,0.0,Dpd,1); - //checkStatus(status,__FILE__,__LINE__); - cbstatus=cublasDgemv(cbhandle,CUBLAS_OP_T,M,M,&cone,VTd,M,Dpd,1,&czero,Dpd,1); - - issolved=1; - } -/*************************************************************************/ - - /* compute p's new estimate and ||Dp||^2 */ - if (issolved) { - /* compute p's new estimate and ||Dp||^2 */ - /* pnew=p+Dp */ - /* pnew=p */ - cbstatus=cublasDcopy(cbhandle, M, pd, 1, pnewd, 1); - /* pnew=pnew+Dp */ - alpha=1.0; - cbstatus=cublasDaxpy(cbhandle, M, &alpha, Dpd, 1, pnewd, 1); - - /* norm ||Dp|| */ - cbstatus=cublasDnrm2(cbhandle, M, Dpd, 1, &Dp_L2); - Dp_L2=Dp_L2*Dp_L2; - -#ifdef DEBUG -printf("norm ||dp|| =%lf, norm ||p||=%lf\n",Dp_L2,p_L2); -#endif - if(Dp_L2<=eps2_sq*p_L2){ /* relative change in p is small, stop */ - stop=2; - break; - } - - if(Dp_L2>=(p_L2+eps2)/(CLM_EPSILON*CLM_EPSILON)){ /* almost singular */ - stop=4; - break; - } - - /* new function value */ - /* compute ||e(pDp)||_2 */ - /* ### hx=x-hx, pDp_eL2=||hx|| */ - /* copy to device */ - /* hxd<=hx */ - cudakernel_func_wt(ThreadsPerBlock, (Nbase+ThreadsPerBlock-1)/ThreadsPerBlock, pnewd, hxd, M, N, cohd, bbd, wtd, Nbase, dp->M, dp->N); - - /* e=x */ - cbstatus=cublasDcopy(cbhandle, N, xd, 1, ed, 1); - /* e = e \odot wt */ - cudakernel_hadamard(ThreadsPerBlock1, (N+ThreadsPerBlock1-1)/ThreadsPerBlock1, N, wtd, ed); - /* e=x-hx */ - alpha=-1.0; - cbstatus=cublasDaxpy(cbhandle, N, &alpha, hxd, 1, ed, 1); - /* note: e is updated */ - - /* norm ||e|| */ - cbstatus=cublasDnrm2(cbhandle, N, ed, 1, &pDp_eL2); - pDp_eL2=pDp_eL2*pDp_eL2; - - - if(!finite(pDp_eL2)){ /* sum of squares is not finite, most probably due to a user error. - */ - stop=7; - break; - } - - /* dL=Dp'*(mu*Dp+jacTe) */ - /* bd=jacTe+mu*Dp */ - cbstatus=cublasDcopy(cbhandle, M, jacTed, 1, bd, 1); - cbstatus=cublasDaxpy(cbhandle, M, &mu, Dpd, 1, bd, 1); - cbstatus=cublasDdot(cbhandle, M, Dpd, 1, bd, 1, &dL); - - dF=p_eL2-pDp_eL2; - -#ifdef DEBUG - printf("dF=%lf, dL=%lf\n",dF,dL); -#endif - if(dL>0.0 && dF>0.0){ /* reduction in error, increment is accepted */ - tmp=(2.0*dF/dL-1.0); - tmp=1.0-tmp*tmp*tmp; - mu=mu*((tmp>=CLM_ONE_THIRD)? tmp : CLM_ONE_THIRD); - nu=2; - - /* update p's estimate */ - cbstatus=cublasDcopy(cbhandle, M, pnewd, 1, pd, 1); - - /* update ||e||_2 */ - p_eL2=pDp_eL2; - break; - } - - } - /* if this point is reached, either the linear system could not be solved or - * the error did not reduce; in any case, the increment must be rejected - */ - - mu*=(double)nu; - nu2=nu<<1; // 2*nu; - if(nu2<=nu){ /* nu has wrapped around (overflown). */ - stop=5; - break; - } - - nu=nu2; - - } /* inner loop */ - - } - /**** end iteration loop ***********/ - if(k>=itmax) stop=3; - - if (nw>0 && nwM, dp->N); - /* e=x */ - cbstatus=cublasDcopy(cbhandle, N, xd, 1, ed, 1); - /* e=x-hx */ - alpha=-1.0; - cbstatus=cublasDaxpy(cbhandle, N, &alpha, hxd, 1, ed, 1); - } - - - if (nwrobust_nu=robust_nu; - - /* copy back current solution */ - err=cudaMemcpyAsync(p,pd,M*sizeof(double),cudaMemcpyDeviceToHost,0); - checkCudaError(err,__FILE__,__LINE__); - - checkCublasError(cbstatus,__FILE__,__LINE__); - - /* synchronize async operations */ - cudaDeviceSynchronize(); - - if (!gWORK) { - cudaFree(xd); - cudaFree(jacd); - cudaFree(jacTjacd); - cudaFree(jacTjacd0); - cudaFree(jacTed); - cudaFree(Dpd); - cudaFree(bd); - cudaFree(pd); - cudaFree(pnewd); - cudaFree(hxd); - cudaFree(wtd); - cudaFree(qd); - cudaFree(ed); - if (solve_axb==1) { - cudaFree(taud); - } else if (solve_axb==2) { - cudaFree(Ud); - cudaFree(VTd); - cudaFree(Sd); - } - cudaFree(cohd); - cudaFree(bbd); - } - - cudaFree(devInfo); - cudaFree(work); - if (solve_axb==2) { - cudaFree(rwork); - } - - -#ifdef DEBUG - printf("stop=%d\n",stop); -#endif - if(info){ - info[0]=init_p_eL2; - info[1]=p_eL2; - info[2]=jacTe_inf; - info[3]=Dp_L2; - info[4]=mu; - info[5]=(double)k; - info[6]=(double)stop; - info[7]=(double)0; - info[8]=(double)0; - info[9]=(double)0; - } - return 0; -} - - -/* robust, iteratively weighted non linear least squares using LM - entirely in the GPU, using float data */ -int -rlevmar_der_single_cuda_fl( - float *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - float *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - float *gWORK, /* GPU allocated memory */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - void *adata) /* pointer to possibly additional data, passed uninterpreted to func & jacf. - * Set to NULL if not needed - */ -{ - - /* general note: all device variables end with a 'd' */ - int stop=0; - cudaError_t err; - cublasStatus_t cbstatus; - - int nu=2,nu2; - float p_L2, Dp_L2=(float)DBL_MAX, dF, dL, p_eL2, jacTe_inf=0.0f, pDp_eL2, init_p_eL2; - float tmp,mu=0.0f; - float tau, eps1, eps2, eps2_sq, eps3; - int k,ci,issolved; - - float *hxd; - float *wtd,*qd; - - int nw,wt_itmax=3; - /* ME data */ - me_data_t *dp=(me_data_t*)adata; - float wt_sum,lambda,robust_nu=(float)dp->robust_nu; - float q_sum,robust_nu1; - float deltanu; - int Nd=100; /* no of points where nu is sampled, note NdN) { Nd=N; } - /* only search for nu in [2,30] because 30 is almost Gaussian */ - deltanu=(float)(robust_nuhigh-robust_nulow)/(float)Nd; - - - float *ed; - float *xd; - - float *jacd; - - float *jacTjacd,*jacTjacd0; - - float *Dpd,*bd; - float *pd,*pnewd; - float *jacTed; - - /* used in QR solver */ - float *taud=0; - - /* used in SVD solver */ - float *Ud=0; - float *VTd=0; - float *Sd=0; - - int Nbase=(dp->Nbase)*(ntiles); /* note: we do not use the total tile size */ - /* coherency on device */ - float *cohd; - /* baseline-station map on device/host */ - short *bbd; - - int solve_axb=linsolv; - float alpha; - - /* setup default settings */ - if(opts){ - tau=(float)opts[0]; - eps1=(float)opts[1]; - eps2=(float)opts[2]; - eps2_sq=(float)opts[2]*opts[2]; - eps3=(float)opts[3]; - } else { - tau=(float)CLM_INIT_MU; - eps1=(float)CLM_STOP_THRESH; - eps2=(float)CLM_STOP_THRESH; - eps2_sq=(float)CLM_STOP_THRESH*CLM_STOP_THRESH; - eps3=(float)CLM_STOP_THRESH; - } - - /* calculate no of cuda threads and blocks */ - int ThreadsPerBlock=DEFAULT_TH_PER_BK; - /* FIXME: might need a large value for large no of baselines */ - int ThreadsPerBlock1=DEFAULT_TH_PER_BK; /* for accessing each element of a baseline */ - int ThreadsPerBlock2=Nd/2; /* for evaluating nu */ - int BlocksPerGrid=(M+ThreadsPerBlock-1)/ThreadsPerBlock; - - - unsigned long int moff; - moff=0; - xd=&gWORK[moff]; - moff+=N; - jacd=&gWORK[moff]; - moff+=M*N; - jacTjacd=&gWORK[moff]; - moff+=M*M; - jacTed=&gWORK[moff]; - moff+=M; - jacTjacd0=&gWORK[moff]; - moff+=M*M; - Dpd=&gWORK[moff]; - moff+=M; - bd=&gWORK[moff]; - moff+=M; - pd=&gWORK[moff]; - moff+=M; - pnewd=&gWORK[moff]; - moff+=M; - cohd=&gWORK[moff]; - moff+=Nbase*8; - hxd=&gWORK[moff]; - moff+=N; - ed=&gWORK[moff]; - moff+=N; - wtd=&gWORK[moff]; - moff+=N; - qd=&gWORK[moff]; - moff+=N; - if (solve_axb==1) { - taud=&gWORK[moff]; - moff+=M; - } else if (solve_axb==2) { - Ud=&gWORK[moff]; - moff+=M*M; - VTd=&gWORK[moff]; - moff+=M*M; - Sd=&gWORK[moff]; - moff+=M; - } - bbd=(short*)&gWORK[moff]; - moff+=(Nbase*2*sizeof(short))/sizeof(float); - - /* extra storage for cusolver */ - int work_size=0; - int *devInfo; - int devInfo_h=0; - err=cudaMalloc((void**)&devInfo, sizeof(int)); - checkCudaError(err,__FILE__,__LINE__); - float *work; - float *rwork; - if (solve_axb==0) { - cusolverDnSpotrf_bufferSize(solver_handle, CUBLAS_FILL_MODE_UPPER, M, jacTjacd, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(float)); - checkCudaError(err,__FILE__,__LINE__); - } else if (solve_axb==1) { - cusolverDnSgeqrf_bufferSize(solver_handle, M, M, jacTjacd, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(float)); - checkCudaError(err,__FILE__,__LINE__); - } else { - cusolverDnSgesvd_bufferSize(solver_handle, M, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(float)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&rwork, 5*M*sizeof(float)); - checkCudaError(err,__FILE__,__LINE__); - } - - - err=cudaMemcpyAsync(pd, p, M*sizeof(float), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - /* need to give right offset for coherencies */ - /* offset: cluster offset+time offset */ - err=cudaMemcpyAsync(cohd, &(dp->ddcohf[(dp->Nbase)*(dp->tilesz)*(dp->clus)*8+(dp->Nbase)*tileoff*8]), Nbase*8*sizeof(float), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - /* correct offset for baselines */ - err=cudaMemcpyAsync(bbd, &(dp->ddbase[2*(dp->Nbase)*(tileoff)]), Nbase*2*sizeof(short), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - cudaDeviceSynchronize(); - /* xd <=x */ - err=cudaMemcpyAsync(xd, x, N*sizeof(float), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - - /* set initial weights to 1 by a cuda kernel */ - cudakernel_setweights_fl(ThreadsPerBlock1, (N+ThreadsPerBlock1-1)/ThreadsPerBlock1, N, wtd, 1.0f); - /* weight calculation loop */ - for (nw=0; nwstat mapping, Nbase, Mclusters, Nstations*/ - cudakernel_func_wt_fl(ThreadsPerBlock, (Nbase+ThreadsPerBlock-1)/ThreadsPerBlock, pd,hxd,M,N, cohd, bbd, wtd, Nbase, dp->M, dp->N); - - /* e=x */ - cbstatus=cublasScopy(cbhandle, N, xd, 1, ed, 1); - /* e = e \odot wt */ - cudakernel_hadamard_fl(ThreadsPerBlock1, (N+ThreadsPerBlock1-1)/ThreadsPerBlock1, N, wtd, ed); - /* e=x-hx */ - alpha=-1.0f; - cbstatus=cublasSaxpy(cbhandle, N, &alpha, hxd, 1, ed, 1); - - /* norm ||e|| */ - cbstatus=cublasSnrm2(cbhandle, N, ed, 1, &p_eL2); - /* square */ - p_eL2=p_eL2*p_eL2; - - init_p_eL2=p_eL2; - if(!finitef(p_eL2)) stop=7; - - - /**** iteration loop ***********/ - for(k=0; kstat mapping, Nbase, Mclusters, Nstations*/ - /* FIXME thread/block sizes 16x16=256, so 16 is chosen */ - cudakernel_jacf_wt_fl(ThreadsPerBlock, ThreadsPerBlock/4, pd, jacd, M, N, cohd, bbd, wtd, Nbase, dp->M, dp->N); - - /* Compute J^T J and J^T e */ - /* Cache efficient computation of J^T J based on blocking - */ - /* since J is in ROW major order, assume it is transposed, - so actually calculate A=J*J^T, where J is size MxN */ - //status=culaDeviceSgemm('N','T',M,M,N,1.0f,jacd,M,jacd,M,0.0f,jacTjacd,M); - //checkStatus(status,__FILE__,__LINE__); - float cone=1.0f; float czero=0.0f; - cbstatus=cublasSgemm(cbhandle,CUBLAS_OP_N,CUBLAS_OP_T,M,M,N,&cone,jacd,M,jacd,M,&czero,jacTjacd,M); - - /* create backup */ - /* copy jacTjacd0<=jacTjacd */ - cbstatus=cublasScopy(cbhandle, M*M, jacTjacd, 1, jacTjacd0, 1); - /* J^T e */ - /* calculate b=J^T*e (actually compute b=J*e, where J in row major (size MxN) */ - //status=culaDeviceSgemv('N',M,N,1.0f,jacd,M,ed,1,0.0f,jacTed,1); - //checkStatus(status,__FILE__,__LINE__); - cbstatus=cublasSgemv(cbhandle,CUBLAS_OP_N,M,N,&cone,jacd,M,ed,1,&czero,jacTed,1); - - - /* Compute ||J^T e||_inf and ||p||^2 */ - /* find infinity norm of J^T e, 1 based indexing*/ - cbstatus=cublasIsamax(cbhandle, M, jacTed, 1, &ci); - err=cudaMemcpy(&jacTe_inf,&(jacTed[ci-1]),sizeof(float),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - /* L2 norm of current parameter values */ - /* norm ||Dp|| */ - cbstatus=cublasSnrm2(cbhandle, M, pd, 1, &p_L2); - p_L2=p_L2*p_L2; - if(jacTe_inf<0.0f) {jacTe_inf=-jacTe_inf;} -#ifdef DEBUG - printf("Inf norm=%f\n",jacTe_inf); -#endif - - /* check for convergence */ - if((jacTe_inf <= eps1)){ - Dp_L2=0.0f; /* no increment for p in this case */ - stop=1; - break; - } - - /* compute initial (k=0) damping factor */ - if (k==0) { - /* find max diagonal element (stride is M+1) */ - /* should be MAX not MAX(ABS) */ - cbstatus=cublasIsamax(cbhandle, M, jacTjacd, M+1, &ci); /* 1 based index */ - ci=(ci-1)*(M+1); /* right value of the diagonal */ - - err=cudaMemcpy(&tmp,&(jacTjacd[ci]),sizeof(float),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - mu=tau*tmp; - } - - - /* determine increment using adaptive damping */ - while(1){ - /* augment normal equations */ - /* increment A => A+ mu*I, increment diagonal entries */ - /* copy jacTjacd<=jacTjacd0 */ - cbstatus=cublasScopy(cbhandle, M*M, jacTjacd0, 1, jacTjacd, 1); - cudakernel_diagmu_fl(ThreadsPerBlock, BlocksPerGrid, M, jacTjacd, mu); - -#ifdef DEBUG - printf("mu=%lf\n",mu); -#endif -/*************************************************************************/ - issolved=0; - /* solve augmented equations A x = b */ - /* A==jacTjacd, b==Dpd, after solving, x==Dpd */ - /* b=jacTed : intially right hand side, at exit the solution */ - if (solve_axb==0) { - /* Cholesky solver **********************/ - /* lower triangle of Ad is destroyed */ - //status=culaDeviceSpotrf('U',M,jacTjacd,M); - cusolverDnSpotrf(solver_handle, CUBLAS_FILL_MODE_UPPER, M, jacTjacd, M, work, work_size, devInfo); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (!devInfo_h) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - printf("Singular matrix\n"); -#endif - } - if (issolved) { - /* copy Dpd<=jacTed */ - cbstatus=cublasScopy(cbhandle, M, jacTed, 1, Dpd, 1); -#ifdef DEBUG - checkCublasError(cbstatus,__FILE__,__LINE__); -#endif - //status=culaDeviceSpotrs('U',M,1,jacTjacd,M,Dpd,M); - cusolverDnSpotrs(solver_handle, CUBLAS_FILL_MODE_UPPER,M,1,jacTjacd,M,Dpd,M,devInfo); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (devInfo_h) { - issolved=0; -#ifdef DEBUG - printf("Singular matrix\n"); -#endif - } - } - } else if (solve_axb==1) { - /* QR solver ********************************/ - //status=culaDeviceSgeqrf(M,M,jacTjacd,M,taud); - cusolverDnSgeqrf(solver_handle, M, M, jacTjacd, M, taud, work, work_size, devInfo); - cudaDeviceSynchronize(); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (!devInfo_h) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - printf("Singular matrix\n"); -#endif - } - - if (issolved) { - /* copy Dpd<=jacTed */ - cbstatus=cublasScopy(cbhandle, M, jacTed, 1, Dpd, 1); - //status=culaDeviceSgeqrs(M,M,1,jacTjacd,M,taud,Dpd,M); - cusolverDnSormqr(solver_handle, CUBLAS_SIDE_LEFT, CUBLAS_OP_T, M, 1, M, jacTjacd, M, taud, Dpd, M, work, work_size, devInfo); - cudaDeviceSynchronize(); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (devInfo_h) { - issolved=0; -#ifdef DEBUG - printf("Singular matrix\n"); -#endif - } else { - cone=1.0f; - cbstatus=cublasStrsm(cbhandle,CUBLAS_SIDE_LEFT,CUBLAS_FILL_MODE_UPPER,CUBLAS_OP_N,CUBLAS_DIAG_NON_UNIT,M,1,&cone,jacTjacd,M,Dpd,M); - } - } - } else { - /* SVD solver *********************************/ - /* U S VT = A */ - //status=culaDeviceSgesvd('A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M); - //checkStatus(status,__FILE__,__LINE__); - cusolverDnSgesvd(solver_handle,'A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M,work,work_size,rwork,devInfo); - cudaDeviceSynchronize(); - /* copy Dpd<=jacTed */ - cbstatus=cublasScopy(cbhandle, M, jacTed, 1, Dpd, 1); - /* b<=U^T * b */ - //status=culaDeviceSgemv('T',M,M,1.0f,Ud,M,Dpd,1,0.0f,Dpd,1); - //checkStatus(status,__FILE__,__LINE__); - cone=1.0f; czero=0.0f; - cbstatus=cublasSgemv(cbhandle,CUBLAS_OP_T,M,M,&cone,Ud,M,Dpd,1,&czero,Dpd,1); - /* divide by singular values Dpd[]/Sd[] for Sd[]> eps1 */ - cudakernel_diagdiv_fl(ThreadsPerBlock, BlocksPerGrid, M, eps1, Dpd, Sd); - - /* b<=VT^T * b */ - //status=culaDeviceSgemv('T',M,M,1.0f,VTd,M,Dpd,1,0.0f,Dpd,1); - //checkStatus(status,__FILE__,__LINE__); - cbstatus=cublasSgemv(cbhandle,CUBLAS_OP_T,M,M,&cone,VTd,M,Dpd,1,&czero,Dpd,1); - - issolved=1; - } -/*************************************************************************/ - - /* compute p's new estimate and ||Dp||^2 */ - if (issolved) { - /* compute p's new estimate and ||Dp||^2 */ - /* pnew=p+Dp */ - /* pnew=p */ - cbstatus=cublasScopy(cbhandle, M, pd, 1, pnewd, 1); - /* pnew=pnew+Dp */ - alpha=1.0f; - cbstatus=cublasSaxpy(cbhandle, M, &alpha, Dpd, 1, pnewd, 1); - - /* norm ||Dp|| */ - cbstatus=cublasSnrm2(cbhandle, M, Dpd, 1, &Dp_L2); - Dp_L2=Dp_L2*Dp_L2; - -#ifdef DEBUG -printf("norm ||dp|| =%f, norm ||p||=%f\n",Dp_L2,p_L2); -#endif - if(Dp_L2<=eps2_sq*p_L2){ /* relative change in p is small, stop */ - stop=2; - break; - } - - if(Dp_L2>=(p_L2+eps2)/(float)(CLM_EPSILON*CLM_EPSILON)){ /* almost singular */ - stop=4; - break; - } - - /* new function value */ - /* compute ||e(pDp)||_2 */ - /* ### hx=x-hx, pDp_eL2=||hx|| */ - /* copy to device */ - /* hxd<=hx */ - cudakernel_func_wt_fl(ThreadsPerBlock, (Nbase+ThreadsPerBlock-1)/ThreadsPerBlock, pnewd, hxd, M, N, cohd, bbd, wtd, Nbase, dp->M, dp->N); - - /* e=x */ - cbstatus=cublasScopy(cbhandle, N, xd, 1, ed, 1); - /* e = e \odot wt */ - cudakernel_hadamard_fl(ThreadsPerBlock1, (N+ThreadsPerBlock1-1)/ThreadsPerBlock1, N, wtd, ed); - /* e=x-hx */ - alpha=-1.0f; - cbstatus=cublasSaxpy(cbhandle, N, &alpha, hxd, 1, ed, 1); - /* note: e is updated */ - - /* norm ||e|| */ - cbstatus=cublasSnrm2(cbhandle, N, ed, 1, &pDp_eL2); - pDp_eL2=pDp_eL2*pDp_eL2; - - - if(!finite(pDp_eL2)){ /* sum of squares is not finite, most probably due to a user error. - */ - stop=7; - break; - } - - /* dL=Dp'*(mu*Dp+jacTe) */ - /* bd=jacTe+mu*Dp */ - cbstatus=cublasScopy(cbhandle, M, jacTed, 1, bd, 1); - cbstatus=cublasSaxpy(cbhandle, M, &mu, Dpd, 1, bd, 1); - cbstatus=cublasSdot(cbhandle, M, Dpd, 1, bd, 1, &dL); - - dF=p_eL2-pDp_eL2; - -#ifdef DEBUG - printf("dF=%f, dL=%f\n",dF,dL); -#endif - if(dL>0.0f && dF>0.0f){ /* reduction in error, increment is accepted */ - tmp=(2.0f*dF/dL-1.0f); - tmp=1.0f-tmp*tmp*tmp; - mu=mu*((tmp>=(float)CLM_ONE_THIRD)? tmp : (float)CLM_ONE_THIRD); - nu=2; - - /* update p's estimate */ - cbstatus=cublasScopy(cbhandle, M, pnewd, 1, pd, 1); - - /* update ||e||_2 */ - p_eL2=pDp_eL2; - break; - } - - } - /* if this point is reached, either the linear system could not be solved or - * the error did not reduce; in any case, the increment must be rejected - */ - - mu*=(float)nu; - nu2=nu<<1; // 2*nu; - if(nu2<=nu){ /* nu has wrapped around (overflown). */ - stop=5; - break; - } - - nu=nu2; - - } /* inner loop */ - - } - /**** end iteration loop ***********/ - if(k>=itmax) stop=3; - - if (nw>0 && nwM, dp->N); - /* e=x */ - cbstatus=cublasScopy(cbhandle, N, xd, 1, ed, 1); - /* e=x-hx */ - alpha=-1.0f; - cbstatus=cublasSaxpy(cbhandle, N, &alpha, hxd, 1, ed, 1); - } - - - if (nwrobust_nu=(double)robust_nu; - - /* copy back current solution */ - err=cudaMemcpyAsync(p,pd,M*sizeof(float),cudaMemcpyDeviceToHost,0); - checkCudaError(err,__FILE__,__LINE__); - - checkCublasError(cbstatus,__FILE__,__LINE__); - /* synchronize async operations */ - cudaDeviceSynchronize(); - - cudaFree(devInfo); - cudaFree(work); - if (solve_axb==2) { - cudaFree(rwork); - } - -#ifdef DEBUG - printf("stop=%d\n",stop); -#endif - if(info){ - info[0]=(double)init_p_eL2; - info[1]=(double)p_eL2; - info[2]=(double)jacTe_inf; - info[3]=(double)Dp_L2; - info[4]=(double)mu; - info[5]=(double)k; - info[6]=(double)stop; - info[7]=(double)0; - info[8]=(double)0; - info[9]=(double)0; - } - return 0; -} - - -/* robust, iteratively weighted non linear least squares using LM - entirely in the GPU, using float data, OS acceleration */ -int -osrlevmar_der_single_cuda_fl( - float *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - float *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - float *gWORK, /* GPU allocated memory */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - int randomize, /* if >0 randomize */ - void *adata) /* pointer to possibly additional data, passed uninterpreted to func & jacf. - * Set to NULL if not needed - */ -{ - - /* general note: all device variables end with a 'd' */ - int stop=0; - cudaError_t err; - cublasStatus_t cbstatus; - - int nu=2,nu2; - float p_L2, Dp_L2=(float)DBL_MAX, dF, dL, p_eL2, jacTe_inf=0.0f, pDp_eL2, init_p_eL2; - float tmp,mu=0.0f; - float tau, eps1, eps2, eps2_sq, eps3; - int k,ci,issolved; - - float *hxd; - float *wtd,*qd; - - int nw,wt_itmax=3; - /* ME data */ - me_data_t *dp=(me_data_t*)adata; - float wt_sum,lambda,robust_nu=(float)dp->robust_nu; - float q_sum,robust_nu1; - float deltanu; - int Nd=100; /* no of points where nu is sampled, note NdN) { Nd=N; } - /* only search for nu in [2,30] because 30 is almost Gaussian */ - deltanu=(float)(robust_nuhigh-robust_nulow)/(float)Nd; - - - float *ed; - float *xd; - - float *jacd; - - float *jacTjacd,*jacTjacd0; - - float *Dpd,*bd; - float *pd,*pnewd; - float *jacTed; - - /* used in QR solver */ - float *taud=0; - - /* used in SVD solver */ - float *Ud=0; - float *VTd=0; - float *Sd=0; - - int Nbase=(dp->Nbase)*(ntiles); /* note: we do not use the total tile size */ - /* coherency on device */ - float *cohd; - /* baseline-station map on device/host */ - short *bbd; - - int solve_axb=linsolv; - float alpha; - - /* setup default settings */ - if(opts){ - tau=(float)opts[0]; - eps1=(float)opts[1]; - eps2=(float)opts[2]; - eps2_sq=(float)opts[2]*opts[2]; - eps3=(float)opts[3]; - } else { - tau=(float)CLM_INIT_MU; - eps1=(float)CLM_STOP_THRESH; - eps2=(float)CLM_STOP_THRESH; - eps2_sq=(float)CLM_STOP_THRESH*CLM_STOP_THRESH; - eps3=(float)CLM_STOP_THRESH; - } - - /* calculate no of cuda threads and blocks */ - int ThreadsPerBlock=DEFAULT_TH_PER_BK; - /* FIXME: might need a large value for large no of baselines */ - int ThreadsPerBlock1=DEFAULT_TH_PER_BK; /* for accessing each element of a baseline */ - int ThreadsPerBlock2=Nd/2; /* for evaluating nu */ - int BlocksPerGrid=(M+ThreadsPerBlock-1)/ThreadsPerBlock; - - - unsigned long int moff; - moff=0; - xd=&gWORK[moff]; - moff+=N; - jacd=&gWORK[moff]; - moff+=M*N; - jacTjacd=&gWORK[moff]; - moff+=M*M; - jacTed=&gWORK[moff]; - moff+=M; - jacTjacd0=&gWORK[moff]; - moff+=M*M; - Dpd=&gWORK[moff]; - moff+=M; - bd=&gWORK[moff]; - moff+=M; - pd=&gWORK[moff]; - moff+=M; - pnewd=&gWORK[moff]; - moff+=M; - cohd=&gWORK[moff]; - moff+=Nbase*8; - hxd=&gWORK[moff]; - moff+=N; - ed=&gWORK[moff]; - moff+=N; - wtd=&gWORK[moff]; - moff+=N; - qd=&gWORK[moff]; - moff+=N; - if (solve_axb==1) { - taud=&gWORK[moff]; - moff+=M; - } else if (solve_axb==2) { - Ud=&gWORK[moff]; - moff+=M*M; - VTd=&gWORK[moff]; - moff+=M*M; - Sd=&gWORK[moff]; - moff+=M; - } - bbd=(short*)&gWORK[moff]; - moff+=(Nbase*2*sizeof(short))/sizeof(float); - - /* extra storage for cusolver */ - int work_size=0; - int *devInfo; - int devInfo_h=0; - err=cudaMalloc((void**)&devInfo, sizeof(int)); - checkCudaError(err,__FILE__,__LINE__); - float *work; - float *rwork; - if (solve_axb==0) { - cusolverDnSpotrf_bufferSize(solver_handle, CUBLAS_FILL_MODE_UPPER, M, jacTjacd, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(float)); - checkCudaError(err,__FILE__,__LINE__); - } else if (solve_axb==1) { - cusolverDnSgeqrf_bufferSize(solver_handle, M, M, jacTjacd, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(float)); - checkCudaError(err,__FILE__,__LINE__); - } else { - cusolverDnSgesvd_bufferSize(solver_handle, M, M, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(float)); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMalloc((void**)&rwork, 5*M*sizeof(float)); - checkCudaError(err,__FILE__,__LINE__); - } - - - err=cudaMemcpyAsync(pd, p, M*sizeof(float), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - /* need to give right offset for coherencies */ - /* offset: cluster offset+time offset */ - err=cudaMemcpyAsync(cohd, &(dp->ddcohf[(dp->Nbase)*(dp->tilesz)*(dp->clus)*8+(dp->Nbase)*tileoff*8]), Nbase*8*sizeof(float), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - /* correct offset for baselines */ - err=cudaMemcpyAsync(bbd, &(dp->ddbase[2*(dp->Nbase)*(tileoff)]), Nbase*2*sizeof(short), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - cudaDeviceSynchronize(); - /* xd <=x */ - err=cudaMemcpyAsync(xd, x, N*sizeof(float), cudaMemcpyHostToDevice,0); - checkCudaError(err,__FILE__,__LINE__); - - /* set initial weights to 1 by a cuda kernel */ - cudakernel_setweights_fl(ThreadsPerBlock1, (N+ThreadsPerBlock1-1)/ThreadsPerBlock1, N, wtd, 1.0f); - - /* setup OS subsets and stating offsets */ - /* ed : N, cohd : Nbase*8, bbd : Nbase*2 full size */ - /* if ntilesstat mapping, Nbase, Mclusters, Nstations*/ - cudakernel_func_wt_fl(ThreadsPerBlock, (Nbase+ThreadsPerBlock-1)/ThreadsPerBlock, pd,hxd,M,N, cohd, bbd, wtd, Nbase, dp->M, dp->N); - - /* e=x */ - cbstatus=cublasScopy(cbhandle, N, xd, 1, ed, 1); - /* e = e \odot wt */ - cudakernel_hadamard_fl(ThreadsPerBlock1, (N+ThreadsPerBlock1-1)/ThreadsPerBlock1, N, wtd, ed); - /* e=x-hx */ - alpha=-1.0f; - cbstatus=cublasSaxpy(cbhandle, N, &alpha, hxd, 1, ed, 1); - - /* norm ||e|| */ - cbstatus=cublasSnrm2(cbhandle, N, ed, 1, &p_eL2); - /* square */ - p_eL2=p_eL2*p_eL2; - - init_p_eL2=p_eL2; - if(!finitef(p_eL2)) stop=7; - - - - /**** iteration loop ***********/ - for(k=0; kstat mapping, Nbase, Mclusters, Nstations*/ - /* FIXME thread/block sizes 16x16=256, so 16 is chosen */ - cudakernel_jacf_wt_fl(ThreadsPerBlock, ThreadsPerBlock/4, pd, jacd, M, Nos[l], &cohd[8*NbI[l]], &bbd[2*NbI[l]], &wtd[edI[l]], Nbaseos[l], dp->M, dp->N); - - /* Compute J^T J and J^T e */ - /* Cache efficient computation of J^T J based on blocking - */ - /* since J is in ROW major order, assume it is transposed, - so actually calculate A=J*J^T, where J is size MxN */ - //status=culaDeviceSgemm('N','T',M,M,Nos[l],1.0f,jacd,M,jacd,M,0.0f,jacTjacd,M); - //checkStatus(status,__FILE__,__LINE__); - float cone=1.0f; float czero=0.0f; - cbstatus=cublasSgemm(cbhandle,CUBLAS_OP_N,CUBLAS_OP_T,M,M,Nos[l],&cone,jacd,M,jacd,M,&czero,jacTjacd,M); - - /* create backup */ - /* copy jacTjacd0<=jacTjacd */ - cbstatus=cublasScopy(cbhandle, M*M, jacTjacd, 1, jacTjacd0, 1); - /* J^T e */ - /* calculate b=J^T*e (actually compute b=J*e, where J in row major (size MxN) */ - //status=culaDeviceSgemv('N',M,Nos[l],1.0f,jacd,M,&ed[edI[l]],1,0.0f,jacTed,1); - //checkStatus(status,__FILE__,__LINE__); - cbstatus=cublasSgemv(cbhandle,CUBLAS_OP_N,M,Nos[l],&cone,jacd,M,&ed[edI[l]],1,&czero,jacTed,1); - - - /* Compute ||J^T e||_inf and ||p||^2 */ - /* find infinity norm of J^T e, 1 based indexing*/ - cbstatus=cublasIsamax(cbhandle, M, jacTed, 1, &ci); - err=cudaMemcpy(&jacTe_inf,&(jacTed[ci-1]),sizeof(float),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - /* L2 norm of current parameter values */ - /* norm ||Dp|| */ - cbstatus=cublasSnrm2(cbhandle, M, pd, 1, &p_L2); - p_L2=p_L2*p_L2; - if(jacTe_inf<0.0f) {jacTe_inf=-jacTe_inf;} -#ifdef DEBUG - printf("Inf norm=%f\n",jacTe_inf); -#endif - - /* check for convergence */ - if((jacTe_inf <= eps1)){ - Dp_L2=0.0f; /* no increment for p in this case */ - stop=1; - break; - } - - /* compute initial (k=0) damping factor */ - if (k==0) { - /* find max diagonal element (stride is M+1) */ - /* should be MAX not MAX(ABS) */ - cbstatus=cublasIsamax(cbhandle, M, jacTjacd, M+1, &ci); /* 1 based index */ - ci=(ci-1)*(M+1); /* right value of the diagonal */ - - err=cudaMemcpy(&tmp,&(jacTjacd[ci]),sizeof(float),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - mu=tau*tmp; - } - - - /* determine increment using adaptive damping */ - while(1){ - /* augment normal equations */ - /* increment A => A+ mu*I, increment diagonal entries */ - /* copy jacTjacd<=jacTjacd0 */ - cbstatus=cublasScopy(cbhandle, M*M, jacTjacd0, 1, jacTjacd, 1); - cudakernel_diagmu_fl(ThreadsPerBlock, BlocksPerGrid, M, jacTjacd, mu); - -#ifdef DEBUG - printf("mu=%lf\n",mu); -#endif -/*************************************************************************/ - issolved=0; - /* solve augmented equations A x = b */ - /* A==jacTjacd, b==Dpd, after solving, x==Dpd */ - /* b=jacTed : intially right hand side, at exit the solution */ - if (solve_axb==0) { - /* Cholesky solver **********************/ - /* lower triangle of Ad is destroyed */ - //status=culaDeviceSpotrf('U',M,jacTjacd,M); - cusolverDnSpotrf(solver_handle, CUBLAS_FILL_MODE_UPPER, M, jacTjacd, M, work, work_size, devInfo); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (!devInfo_h) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - printf("Singular matrix\n"); -#endif - } - if (issolved) { - /* copy Dpd<=jacTed */ - cbstatus=cublasScopy(cbhandle, M, jacTed, 1, Dpd, 1); -#ifdef DEBUG - checkCublasError(cbstatus,__FILE__,__LINE__); -#endif - //status=culaDeviceSpotrs('U',M,1,jacTjacd,M,Dpd,M); - cusolverDnSpotrs(solver_handle, CUBLAS_FILL_MODE_UPPER,M,1,jacTjacd,M,Dpd,M,devInfo); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (devInfo_h) { - issolved=0; -#ifdef DEBUG - printf("Singular matrix\n"); -#endif - } - } - } else if (solve_axb==1) { - /* QR solver ********************************/ - //status=culaDeviceSgeqrf(M,M,jacTjacd,M,taud); - cusolverDnSgeqrf(solver_handle, M, M, jacTjacd, M, taud, work, work_size, devInfo); - cudaDeviceSynchronize(); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (!devInfo_h) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - printf("Singular matrix\n"); -#endif - } - - if (issolved) { - /* copy Dpd<=jacTed */ - cbstatus=cublasScopy(cbhandle, M, jacTed, 1, Dpd, 1); - //status=culaDeviceSgeqrs(M,M,1,jacTjacd,M,taud,Dpd,M); - cusolverDnSormqr(solver_handle, CUBLAS_SIDE_LEFT, CUBLAS_OP_T, M, 1, M, jacTjacd, M, taud, Dpd, M, work, work_size, devInfo); - cudaDeviceSynchronize(); - cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost); - if (devInfo_h) { - issolved=0; -#ifdef DEBUG - printf("Singular matrix\n"); -#endif - } else { - cone=1.0f; - cbstatus=cublasStrsm(cbhandle,CUBLAS_SIDE_LEFT,CUBLAS_FILL_MODE_UPPER,CUBLAS_OP_N,CUBLAS_DIAG_NON_UNIT,M,1,&cone,jacTjacd,M,Dpd,M); - } - } - } else { - /* SVD solver *********************************/ - /* U S VT = A */ - //status=culaDeviceSgesvd('A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M); - //checkStatus(status,__FILE__,__LINE__); - cusolverDnSgesvd(solver_handle,'A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M,work,work_size,rwork,devInfo); - cudaDeviceSynchronize(); - /* copy Dpd<=jacTed */ - cbstatus=cublasScopy(cbhandle, M, jacTed, 1, Dpd, 1); - /* b<=U^T * b */ - //status=culaDeviceSgemv('T',M,M,1.0f,Ud,M,Dpd,1,0.0f,Dpd,1); - //checkStatus(status,__FILE__,__LINE__); - cone=1.0f; czero=0.0f; - cbstatus=cublasSgemv(cbhandle,CUBLAS_OP_T,M,M,&cone,Ud,M,Dpd,1,&czero,Dpd,1); - - /* divide by singular values Dpd[]/Sd[] for Sd[]> eps1 */ - cudakernel_diagdiv_fl(ThreadsPerBlock, BlocksPerGrid, M, eps1, Dpd, Sd); - - /* b<=VT^T * b */ - //status=culaDeviceSgemv('T',M,M,1.0f,VTd,M,Dpd,1,0.0f,Dpd,1); - //checkStatus(status,__FILE__,__LINE__); - cbstatus=cublasSgemv(cbhandle,CUBLAS_OP_T,M,M,&cone,VTd,M,Dpd,1,&czero,Dpd,1); - - issolved=1; - } -/*************************************************************************/ - - /* compute p's new estimate and ||Dp||^2 */ - if (issolved) { - /* compute p's new estimate and ||Dp||^2 */ - /* pnew=p+Dp */ - /* pnew=p */ - cbstatus=cublasScopy(cbhandle, M, pd, 1, pnewd, 1); - /* pnew=pnew+Dp */ - alpha=1.0f; - cbstatus=cublasSaxpy(cbhandle, M, &alpha, Dpd, 1, pnewd, 1); - - /* norm ||Dp|| */ - cbstatus=cublasSnrm2(cbhandle, M, Dpd, 1, &Dp_L2); - Dp_L2=Dp_L2*Dp_L2; - -#ifdef DEBUG -printf("norm ||dp|| =%f, norm ||p||=%f\n",Dp_L2,p_L2); -#endif - if(Dp_L2<=eps2_sq*p_L2){ /* relative change in p is small, stop */ - stop=2; - break; - } - - if(Dp_L2>=(p_L2+eps2)/(float)(CLM_EPSILON*CLM_EPSILON)){ /* almost singular */ - stop=4; - break; - } - - /* new function value */ - /* compute ||e(pDp)||_2 */ - /* ### hx=x-hx, pDp_eL2=||hx|| */ - /* copy to device */ - /* hxd<=hx */ - cudakernel_func_wt_fl(ThreadsPerBlock, (Nbase+ThreadsPerBlock-1)/ThreadsPerBlock, pnewd, hxd, M, N, cohd, bbd, wtd, Nbase, dp->M, dp->N); - - /* e=x */ - cbstatus=cublasScopy(cbhandle, N, xd, 1, ed, 1); - /* e = e \odot wt */ - cudakernel_hadamard_fl(ThreadsPerBlock1, (N+ThreadsPerBlock1-1)/ThreadsPerBlock1, N, wtd, ed); - /* e=x-hx */ - alpha=-1.0f; - cbstatus=cublasSaxpy(cbhandle, N, &alpha, hxd, 1, ed, 1); - /* note: e is updated */ - - /* norm ||e|| */ - cbstatus=cublasSnrm2(cbhandle, N, ed, 1, &pDp_eL2); - pDp_eL2=pDp_eL2*pDp_eL2; - - - if(!finite(pDp_eL2)){ /* sum of squares is not finite, most probably due to a user error. - */ - stop=7; - break; - } - - /* dL=Dp'*(mu*Dp+jacTe) */ - /* bd=jacTe+mu*Dp */ - cbstatus=cublasScopy(cbhandle, M, jacTed, 1, bd, 1); - cbstatus=cublasSaxpy(cbhandle, M, &mu, Dpd, 1, bd, 1); - cbstatus=cublasSdot(cbhandle, M, Dpd, 1, bd, 1, &dL); - - dF=p_eL2-pDp_eL2; - -#ifdef DEBUG - printf("dF=%f, dL=%f\n",dF,dL); -#endif - if(dL>0.0f && dF>0.0f){ /* reduction in error, increment is accepted */ - tmp=(2.0f*dF/dL-1.0f); - tmp=1.0f-tmp*tmp*tmp; - mu=mu*((tmp>=(float)CLM_ONE_THIRD)? tmp : (float)CLM_ONE_THIRD); - nu=2; - - /* update p's estimate */ - cbstatus=cublasScopy(cbhandle, M, pnewd, 1, pd, 1); - - /* update ||e||_2 */ - p_eL2=pDp_eL2; - break; - } - - } - /* if this point is reached, either the linear system could not be solved or - * the error did not reduce; in any case, the increment must be rejected - */ - - mu*=(float)nu; - nu2=nu<<1; // 2*nu; - if(nu2<=nu){ /* nu has wrapped around (overflown). */ - stop=5; - break; - } - - nu=nu2; - - } /* inner loop */ - - } - if (randomize) { - free(subI); - } -/**************** end OS loop ***************************/ - - } - /**** end iteration loop ***********/ - if(k>=itmax) stop=3; - - if (nw>0 && nwM, dp->N); - /* e=x */ - cbstatus=cublasScopy(cbhandle, N, xd, 1, ed, 1); - /* e=x-hx */ - alpha=-1.0f; - cbstatus=cublasSaxpy(cbhandle, N, &alpha, hxd, 1, ed, 1); - } - - - if (nwrobust_nu=(double)robust_nu; - - free(Nos); - free(Nbaseos); - free(edI); - free(NbI); - - /* copy back current solution */ - err=cudaMemcpyAsync(p,pd,M*sizeof(float),cudaMemcpyDeviceToHost,0); - checkCudaError(err,__FILE__,__LINE__); - checkCublasError(cbstatus,__FILE__,__LINE__); - - /* synchronize async operations */ - cudaDeviceSynchronize(); - - cudaFree(devInfo); - cudaFree(work); - if (solve_axb==2) { - cudaFree(rwork); - } - -#ifdef DEBUG - printf("stop=%d\n",stop); -#endif - if(info){ - info[0]=(double)init_p_eL2; - info[1]=(double)p_eL2; - info[2]=(double)jacTe_inf; - info[3]=(double)Dp_L2; - info[4]=(double)mu; - info[5]=(double)k; - info[6]=(double)stop; - info[7]=(double)0; - info[8]=(double)0; - info[9]=(double)0; - } - return 0; -} -#endif /* HAVE_CUDA */ - -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -odot_threadfn(void *data) { - thread_data_vec_t *t=(thread_data_vec_t*)data; - int ci; - for (ci=t->starti; ci<=t->endi; ci++) { - t->ed[ci]*=t->wtd[ci]; - } - return NULL; -} - - -/* Hadamard product */ -/* ed <= ed*wtd , size Nx1 - Nt threads */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static int -my_odot(double *ed,double *wtd,int N,int Nt) { - int nth,nth1,ci; - int Nthb0,Nthb; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_vec_t *threaddata; - - /* calculate min values a thread can handle */ - Nthb0=(N+Nt-1)/Nt; - - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { -#ifndef USE_MIC - printf("%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((threaddata=(thread_data_vec_t*)malloc((size_t)Nt*sizeof(thread_data_vec_t)))==0) { -#ifndef USE_MIC - printf("%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - /* iterate over threads, allocating indices per thread */ - ci=0; - for (nth=0; nthrobust_nu; - double robust_nu1; - - setweights(M,aones,1.0,lmdata->Nt); - /*W set initial weights to 1 */ - setweights(N,wtd,1.0,lmdata->Nt); - /* memory allocation: different solvers */ - if (solve_axb==0) { - - } else if (solve_axb==1) { - /* workspace query */ - status=my_dgels('N',M,M,1,jacTjacd,M,Dpd,M,w,-1); - if (!status) { - lwork=(int)w[0]; - if ((WORK=(double*)calloc((size_t)lwork,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - } - } else { - if ((Ud=(double*)calloc((size_t)M*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((VTd=(double*)calloc((size_t)M*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((Sd=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - status=my_dgesvd('A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M,w,-1); - if (!status) { - lwork=(int)w[0]; - if ((WORK=(double*)calloc((size_t)lwork,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - } - } - - - /* EM iteration loop */ - /************************************************************/ - for (nw=0; nw A+ mu*I, increment diagonal entries */ - /* copy jacTjacd<=jacTjacd0 */ - memcpy(jacTjacd,jacTjacd0,M*M*sizeof(double)); - my_daxpys(M,aones,1,mu,jacTjacd,M+1); - -#ifdef DEBUG - printf("mu=%lf\n",mu); -#endif -/*************************************************************************/ - /* solve augmented equations A x = b */ - /* A==jacTjacd, b==Dpd, after solving, x==Dpd */ - /* b=jacTed : intially right hand side, at exit the solution */ - if (solve_axb==0) { - /* Cholesky solver **********************/ - /* lower triangle of Ad is destroyed */ - status=my_dpotrf('U',M,jacTjacd,M); - if (!status) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - printf("Singular matrix info=%d\n",status); -#endif - } - if (issolved) { - /* copy Dpd<=jacTed */ - memcpy(Dpd,jacTed,M*sizeof(double)); - status=my_dpotrs('U',M,1,jacTjacd,M,Dpd,M); - if (status) { - issolved=0; -#ifdef DEBUG - printf("Singular matrix info=%d\n",status); -#endif - } - } - } else if (solve_axb==1) { - /* QR solver ********************************/ - /* copy Dpd<=jacTed */ - memcpy(Dpd,jacTed,M*sizeof(double)); - status=my_dgels('N',M,M,1,jacTjacd,M,Dpd,M,WORK,lwork); - if (!status) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - printf("Singular matrix info=%d\n",status); -#endif - } - - } else { - /* SVD solver *********************************/ - /* U S VT = A */ - status=my_dgesvd('A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M,WORK,lwork); - /* copy Dpd<=jacTed */ - memcpy(bd,jacTed,M*sizeof(double)); - /* b<=U^T * b */ - my_dgemv('T',M,M,1.0,Ud,M,bd,1,0.0,Dpd,1); - /* robust correction */ - /* divide by singular values Dpd[]/Sd[] for Sd[]> eps1 */ - for (ci=0; cieps1) { - Dpd[ci]=Dpd[ci]/Sd[ci]; - } else { - Dpd[ci]=0.0; - } - } - - /* b<=VT^T * b */ - memcpy(bd,Dpd,M*sizeof(double)); - my_dgemv('T',M,M,1.0,VTd,M,bd,1,0.0,Dpd,1); - - issolved=1; - } -/*************************************************************************/ - - /* compute p's new estimate and ||Dp||^2 */ - if (issolved) { - /* compute p's new estimate and ||Dp||^2 */ - /* pnew=p+Dp */ - /* pnew=p */ - memcpy(pnew,p,M*sizeof(double)); - /* pnew=pnew+Dp */ - my_daxpy(M,Dpd,1.0,pnew); - - /* norm ||Dp|| */ - Dp_L2=my_dnrm2(M,Dpd); - Dp_L2=Dp_L2*Dp_L2; - -#ifdef DEBUG -printf("norm ||dp|| =%lf, norm ||p||=%lf\n",Dp_L2,p_L2); -#endif - if(Dp_L2<=eps2_sq*p_L2){ /* relative change in p is small, stop */ - stop=2; - break; - } - - if(Dp_L2>=(p_L2+eps2)/(CLM_EPSILON*CLM_EPSILON)){ /* almost singular */ - stop=4; - break; - } - - /* new function value */ - (*func)(pnew, hxd, M, N, adata); /* evaluate function at p + Dp */ - - /* compute ||e(pDp)||_2 */ - /* ### hx=x-hx, pDp_eL2=||hx|| */ - /* copy to device */ - /* hxd<=hx */ - - /* e=x */ - memcpy(ed,x,N*sizeof(double)); - /* e=x-hx */ - my_daxpy(N,hxd,-1.0,ed); - /* note: e is updated */ - - /*W e<= wt\odot e */ - my_odot(ed,wtd,N,Nt); - - /* norm ||e|| */ - pDp_eL2=my_dnrm2(N,ed); - pDp_eL2=pDp_eL2*pDp_eL2; - - - if(!finite(pDp_eL2)){ /* sum of squares is not finite, most probably due to a user error. - */ - stop=7; - break; - } - - /* dL=Dp'*(mu*Dp+jacTe) */ - /* bd=jacTe+mu*Dp */ - memcpy(bd,jacTed,M*sizeof(double)); - my_daxpy(M,Dpd,mu,bd); - dL=my_ddot(M,Dpd,bd); - - dF=p_eL2-pDp_eL2; - -#ifdef DEBUG - printf("dF=%lf, dL=%lf\n",dF,dL); -#endif - if(dL>0.0 && dF>0.0){ /* reduction in error, increment is accepted */ - tmp=(2.0*dF/dL-1.0); - tmp=1.0-tmp*tmp*tmp; - mu=mu*((tmp>=CLM_ONE_THIRD)? tmp : CLM_ONE_THIRD); - nu=2; - - /* update p's estimate */ - memcpy(p,pnew,M*sizeof(double)); - - /* update ||e||_2 */ - p_eL2=pDp_eL2; - break; - } - - } - /* if this point is reached, either the linear system could not be solved or - * the error did not reduce; in any case, the increment must be rejected - */ - - mu*=(double)nu; - nu2=nu<<1; // 2*nu; - if(nu2<=nu){ /* nu has wrapped around (overflown). */ - stop=5; - break; - } - - nu=nu2; - - } /* inner loop */ - - } - /**** end iteration loop ***********/ - - - if(k>=itmax) stop=3; - - /*W if not at first or last iteration, recalculate error */ - if (nw>0 && nwrobust_nu=robust_nu; - - free(jac); - free(jacTjacd); - free(jacTjacd0); - free(jacTed); - free(Dpd); - free(bd); - free(hxd); - if (!jac_given) { free(hxm); } - free(ed); - free(wtd); - free(aones); - free(pnew); - - if (solve_axb==0) { - } else if (solve_axb==1) { - free(WORK); - } else { - free(Ud); - free(VTd); - free(Sd); - free(WORK); - } -#ifdef DEBUG - printf("stop=%d\n",stop); -#endif - if(info){ - info[0]=init_p_eL2; - info[1]=p_eL2; - info[2]=jacTe_inf; - info[3]=Dp_L2; - info[4]=mu; - info[5]=(double)k; - info[6]=(double)stop; - info[7]=(double)0; - info[8]=(double)0; - info[9]=(double)0; - } - return 0; -} - - - -/* robust LM, OS acceleration */ -int -osrlevmar_der_single_nocuda( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - * info[5]= # iterations, - * info[6]=reason for terminating: 1 - stopped by small gradient J^T e - * 2 - stopped by small Dp - * 3 - stopped by itmax - * 4 - singular matrix. Restart from current p with increased mu - * 5 - no further error reduction is possible. Restart with increased mu - * 6 - stopped by small ||e||_2 - * 7 - stopped by invalid (i.e. NaN or Inf) "func" values. This is a user error - * info[7]= # function evaluations - * info[8]= # Jacobian evaluations - * info[9]= # linear systems solved, i.e. # attempts for reducing error - */ - - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int Nt, /* no of threads */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - int randomize, /* if >0 randomize */ - void *adata) /* pointer to possibly additional data, passed uninterpreted to func & jacf. - * Set to NULL if not needed - */ -{ - - /* general note: all device variables end with a 'd' */ - int stop=0; - int nu=2,nu2; - double p_L2, Dp_L2=DBL_MAX, dF, dL, p_eL2, jacTe_inf=0.0, pDp_eL2, init_p_eL2; - double tmp,mu=0.0; - double tau, eps1, eps2, eps2_sq, eps3; - int k,ci,issolved; - - double *hxd; - double *ed,*wtd; - double *jac; - - double *jacTjacd,*jacTjacd0; - - double *pnew,*Dpd,*bd; - double *aones; - double *jacTed; - - /* used in QR solver */ - double *WORK; - int lwork=0; - double w[1]; - - int status; - - /* used in SVD solver */ - double *Ud; - double *VTd; - double *Sd; - - - int solve_axb=linsolv; - - /* setup default settings */ - if(opts){ - tau=opts[0]; - eps1=opts[1]; - eps2=opts[2]; - eps2_sq=opts[2]*opts[2]; - eps3=opts[3]; - } else { - tau=CLM_INIT_MU; - eps1=CLM_STOP_THRESH; - eps2=CLM_STOP_THRESH; - eps2_sq=CLM_STOP_THRESH*CLM_STOP_THRESH; - eps3=CLM_STOP_THRESH; - } - - if ((hxd=(double*)calloc((size_t)N,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((ed=(double*)calloc((size_t)N,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((jac=(double*)calloc((size_t)N*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((jacTjacd=(double*)calloc((size_t)M*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((jacTjacd0=(double*)calloc((size_t)M*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((jacTed=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((Dpd=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((bd=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((pnew=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((aones=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((wtd=(double*)calloc((size_t)N,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - WORK=Ud=Sd=VTd=0; - me_data_t *lmdata0=(me_data_t*)adata; - int nw,wt_itmax=3; - double wt_sum,lambda,robust_nu=lmdata0->robust_nu; - double robust_nu1; - - - setweights(M,aones,1.0,lmdata0->Nt); - /*W set initial weights to 1 */ - setweights(N,wtd,1.0,lmdata0->Nt); - - /* memory allocation: different solvers */ - if (solve_axb==0) { - - } else if (solve_axb==1) { - /* workspace query */ - status=my_dgels('N',M,M,1,jacTjacd,M,Dpd,M,w,-1); - if (!status) { - lwork=(int)w[0]; - if ((WORK=(double*)calloc((size_t)lwork,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - } - } else { - if ((Ud=(double*)calloc((size_t)M*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((VTd=(double*)calloc((size_t)M*M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((Sd=(double*)calloc((size_t)M,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - status=my_dgesvd('A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M,w,-1); - if (!status) { - lwork=(int)w[0]; - if ((WORK=(double*)calloc((size_t)lwork,sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - } - } - - - /* setup OS subsets and stating offsets */ - /* ME data for Jacobian calculation (need a new one) */ - me_data_t lmdata; - lmdata.clus=lmdata0->clus; - lmdata.u=lmdata.v=lmdata.w=0; /* not needed */ - lmdata.Nbase=lmdata0->Nbase; - lmdata.tilesz=lmdata0->tilesz; - lmdata.N=lmdata0->N; - lmdata.carr=lmdata0->carr; - lmdata.M=lmdata0->M; - lmdata.Mt=lmdata0->Mt; - lmdata.freq0=lmdata0->freq0; - lmdata.Nt=lmdata0->Nt; - lmdata.barr=lmdata0->barr; - lmdata.coh=lmdata0->coh; - lmdata.tileoff=lmdata0->tileoff; - - - int Nsubsets=10; - if (lmdata0->tilesztilesz; } - /* FIXME: is 0.1 enough ? */ - int max_os_iter=(int)ceil(0.1*(double)Nsubsets); - int Npersubset=(N+Nsubsets-1)/Nsubsets; - int Ntpersubset=(lmdata0->tilesz+Nsubsets-1)/Nsubsets; - int *Nos,*edI,*subI=0,*tileI,*tileoff; - if ((Nos=(int*)calloc((size_t)Nsubsets,sizeof(int)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((edI=(int*)calloc((size_t)Nsubsets,sizeof(int)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((tileI=(int*)calloc((size_t)Nsubsets,sizeof(int)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((tileoff=(int*)calloc((size_t)Nsubsets,sizeof(int)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - int l,ositer;; - k=l=0; - for (ci=0; citileoff+l; - if (l+Ntpersubsettilesz) { - Nos[ci]=Npersubset; - tileI[ci]=Ntpersubset; - } else { - Nos[ci]=N-k; - tileI[ci]=lmdata0->tilesz-l; - } - k=k+Npersubset; - l=l+Ntpersubset; - } - - /* EM iteration loop */ - /************************************************************/ - for (nw=0; nw A+ mu*I, increment diagonal entries */ - /* copy jacTjacd<=jacTjacd0 */ - memcpy(jacTjacd,jacTjacd0,M*M*sizeof(double)); - my_daxpys(M,aones,1,mu,jacTjacd,M+1); - -#ifdef DEBUG - printf("mu=%lf\n",mu); -#endif -/*************************************************************************/ - /* solve augmented equations A x = b */ - /* A==jacTjacd, b==Dpd, after solving, x==Dpd */ - /* b=jacTed : intially right hand side, at exit the solution */ - if (solve_axb==0) { - /* Cholesky solver **********************/ - /* lower triangle of Ad is destroyed */ - status=my_dpotrf('U',M,jacTjacd,M); - if (!status) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - printf("Singular matrix info=%d\n",status); -#endif - } - if (issolved) { - /* copy Dpd<=jacTed */ - memcpy(Dpd,jacTed,M*sizeof(double)); - status=my_dpotrs('U',M,1,jacTjacd,M,Dpd,M); - if (status) { - issolved=0; -#ifdef DEBUG - printf("Singular matrix info=%d\n",status); -#endif - } - } - } else if (solve_axb==1) { - /* QR solver ********************************/ - /* copy Dpd<=jacTed */ - memcpy(Dpd,jacTed,M*sizeof(double)); - status=my_dgels('N',M,M,1,jacTjacd,M,Dpd,M,WORK,lwork); - if (!status) { - issolved=1; - } else { - issolved=0; -#ifdef DEBUG - printf("Singular matrix info=%d\n",status); -#endif - } - - } else { - /* SVD solver *********************************/ - /* U S VT = A */ - status=my_dgesvd('A','A',M,M,jacTjacd,M,Sd,Ud,M,VTd,M,WORK,lwork); - /* copy Dpd<=jacTed */ - memcpy(bd,jacTed,M*sizeof(double)); - /* b<=U^T * b */ - my_dgemv('T',M,M,1.0,Ud,M,bd,1,0.0,Dpd,1); - /* robust correction */ - /* divide by singular values Dpd[]/Sd[] for Sd[]> eps1 */ - for (ci=0; cieps1) { - Dpd[ci]=Dpd[ci]/Sd[ci]; - } else { - Dpd[ci]=0.0; - } - } - - /* b<=VT^T * b */ - memcpy(bd,Dpd,M*sizeof(double)); - my_dgemv('T',M,M,1.0,VTd,M,bd,1,0.0,Dpd,1); - - issolved=1; - } -/*************************************************************************/ - - /* compute p's new estimate and ||Dp||^2 */ - if (issolved) { - /* compute p's new estimate and ||Dp||^2 */ - /* pnew=p+Dp */ - /* pnew=p */ - memcpy(pnew,p,M*sizeof(double)); - /* pnew=pnew+Dp */ - my_daxpy(M,Dpd,1.0,pnew); - - /* norm ||Dp|| */ - Dp_L2=my_dnrm2(M,Dpd); - Dp_L2=Dp_L2*Dp_L2; - -#ifdef DEBUG -printf("norm ||dp|| =%lf, norm ||p||=%lf\n",Dp_L2,p_L2); -#endif - if(Dp_L2<=eps2_sq*p_L2){ /* relative change in p is small, stop */ - stop=2; - break; - } - - if(Dp_L2>=(p_L2+eps2)/(CLM_EPSILON*CLM_EPSILON)){ /* almost singular */ - stop=4; - break; - } - - /* new function value */ - (*func)(pnew, hxd, M, N, adata); /* evaluate function at p + Dp */ - - /* compute ||e(pDp)||_2 */ - /* ### hx=x-hx, pDp_eL2=||hx|| */ - /* copy to device */ - /* hxd<=hx */ - - /* e=x */ - memcpy(ed,x,N*sizeof(double)); - /* e=x-hx */ - my_daxpy(N,hxd,-1.0,ed); - /* note: e is updated */ - - /*W e<= wt\odot e */ - my_odot(ed,wtd,N,Nt); - - /* norm ||e|| */ - pDp_eL2=my_dnrm2(N,ed); - pDp_eL2=pDp_eL2*pDp_eL2; - - - if(!finite(pDp_eL2)){ /* sum of squares is not finite, most probably due to a user error. - */ - stop=7; - break; - } - - /* dL=Dp'*(mu*Dp+jacTe) */ - /* bd=jacTe+mu*Dp */ - memcpy(bd,jacTed,M*sizeof(double)); - my_daxpy(M,Dpd,mu,bd); - dL=my_ddot(M,Dpd,bd); - - dF=p_eL2-pDp_eL2; - -#ifdef DEBUG - printf("dF=%lf, dL=%lf\n",dF,dL); -#endif - if(dL>0.0 && dF>0.0){ /* reduction in error, increment is accepted */ - tmp=(2.0*dF/dL-1.0); - tmp=1.0-tmp*tmp*tmp; - mu=mu*((tmp>=CLM_ONE_THIRD)? tmp : CLM_ONE_THIRD); - nu=2; - - /* update p's estimate */ - memcpy(p,pnew,M*sizeof(double)); - - /* update ||e||_2 */ - p_eL2=pDp_eL2; - break; - } - - } - /* if this point is reached, either the linear system could not be solved or - * the error did not reduce; in any case, the increment must be rejected - */ - - mu*=(double)nu; - nu2=nu<<1; // 2*nu; - if(nu2<=nu){ /* nu has wrapped around (overflown). */ - stop=5; - break; - } - - nu=nu2; - - } /* inner loop */ - - } - if (randomize) { - free(subI); - } -/**************** end OS loop ***************************/ - } - /**** end iteration loop ***********/ - - - if(k>=itmax) stop=3; - - /*W if not at first or last iteration, recalculate error */ - if (nw>0 && nwrobust_nu=robust_nu; - - free(jac); - free(jacTjacd); - free(jacTjacd0); - free(jacTed); - free(Dpd); - free(bd); - free(hxd); - free(ed); - free(wtd); - free(aones); - free(pnew); - - if (solve_axb==0) { - } else if (solve_axb==1) { - free(WORK); - } else { - free(Ud); - free(VTd); - free(Sd); - free(WORK); - } -#ifdef DEBUG - printf("stop=%d\n",stop); -#endif - if(info){ - info[0]=init_p_eL2; - info[1]=p_eL2; - info[2]=jacTe_inf; - info[3]=Dp_L2; - info[4]=mu; - info[5]=(double)k; - info[6]=(double)stop; - info[7]=(double)0; - info[8]=(double)0; - info[9]=(double)0; - } - return 0; -} diff --git a/src/lib/rtr_solve.c b/src/lib/rtr_solve.c deleted file mode 100644 index fc22193..0000000 --- a/src/lib/rtr_solve.c +++ /dev/null @@ -1,1604 +0,0 @@ -/* - * - Copyright (C) 2014 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#include "sagecal.h" - -//#define DEBUG -/* Jones matrix multiplication - C=A*B -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -amb(complex double * __restrict a, complex double * __restrict b, complex double * __restrict c) { - c[0]=a[0]*b[0]+a[1]*b[2]; - c[1]=a[0]*b[1]+a[1]*b[3]; - c[2]=a[2]*b[0]+a[3]*b[2]; - c[3]=a[2]*b[1]+a[3]*b[3]; -} - -/* Jones matrix multiplication - C=A*B^H -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -ambt(complex double * __restrict a, complex double * __restrict b, complex double * __restrict c) { - c[0]=a[0]*conj(b[0])+a[1]*conj(b[1]); - c[1]=a[0]*conj(b[2])+a[1]*conj(b[3]); - c[2]=a[2]*conj(b[0])+a[3]*conj(b[1]); - c[3]=a[2]*conj(b[2])+a[3]*conj(b[3]); -} - -/* Jones matrix multiplication - C=A^H*B -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -atmb(complex double * __restrict a, complex double * __restrict b, complex double * __restrict c) { - c[0]=conj(a[0])*b[0]+conj(a[2])*b[2]; - c[1]=conj(a[0])*b[1]+conj(a[2])*b[3]; - c[2]=conj(a[1])*b[0]+conj(a[3])*b[2]; - c[3]=conj(a[1])*b[1]+conj(a[3])*b[3]; -} - - -/* worker thread function for counting */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -threadfn_fns_fcount(void *data) { - thread_data_rtr_t *t=(thread_data_rtr_t*)data; - - int ci,sta1,sta2; - for (ci=0; ciNb; ci++) { - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - - pthread_mutex_lock(&t->mx_array[sta1]); - t->bcount[sta1]+=1; - pthread_mutex_unlock(&t->mx_array[sta1]); - - pthread_mutex_lock(&t->mx_array[sta2]); - t->bcount[sta2]+=1; - pthread_mutex_unlock(&t->mx_array[sta2]); - } - } - - return NULL; -} - - -/* function to count how many baselines contribute to the calculation of - grad and hess, so normalization can be made */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -fns_fcount(global_data_rtr_t *gdata) { - - me_data_t *dp=(me_data_t*)gdata->medata; - - int nth,nth1,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthb0,Nthb; - thread_data_rtr_t *threaddata; - - int Nbase1=(dp->Nbase)*(dp->tilesz); - int boff=(dp->Nbase)*(dp->tileoff); - - /* calculate min baselines a thread can handle */ - Nthb0=(Nbase1+Nt-1)/Nt; - - if ((threaddata=(thread_data_rtr_t*)malloc((size_t)Nt*sizeof(thread_data_rtr_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - int *bcount; - if ((bcount=(int*)calloc((size_t)dp->N,sizeof(int)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr; - threaddata[nth].bcount=bcount; /* note this should be 0 first */ - threaddata[nth].mx_array=gdata->mx_array; - - pthread_create(&gdata->th_array[nth],&gdata->attr,threadfn_fns_fcount,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - /* now wait for threads to finish */ - for(nth1=0; nth1th_array[nth1],NULL); - } - free(threaddata); - - /* calculate inverse count */ - for (nth1=0; nth1N; nth1++) { - gdata->iw[nth1]=(bcount[nth1]>0?1.0/(double)bcount[nth1]:0.0); - } - free(bcount); - /* scale back weight such that max value is 1 */ - nth1=my_idamax(dp->N,gdata->iw,1); - double maxw=gdata->iw[nth1-1]; /* 1 based index */ - if (maxw>0.0) { /* all baselines can be flagged */ - my_dscal(dp->N,1.0/maxw,gdata->iw); - } -} - - - -/* worker thread function for cost function calculation */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -threadfn_fns_f(void *data) { - thread_data_rtr_t *t=(thread_data_rtr_t*)data; - - int ci,cm,sta1,sta2; - complex double C[4],G1[4],G2[4],T1[4],T2[4]; - int M=(t->M); - cm=(t->clus); - for (ci=0; ciNb; ci++) { - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - /* gains for this cluster, for sta1,sta2 */ - G1[0]=(t->x[sta1*2]); - G1[1]=(t->x[sta1*2+2*t->N]); - G1[2]=(t->x[sta1*2+1]); - G1[3]=(t->x[sta1*2+2*t->N+1]); - G2[0]=(t->x[sta2*2]); - G2[1]=(t->x[sta2*2+2*t->N]); - G2[2]=(t->x[sta2*2+1]); - G2[3]=(t->x[sta2*2+2*t->N+1]); - - /* use pre calculated values */ - C[0]=t->coh[4*M*ci+4*cm]; - C[1]=t->coh[4*M*ci+4*cm+1]; - C[2]=t->coh[4*M*ci+4*cm+2]; - C[3]=t->coh[4*M*ci+4*cm+3]; - - - /* form G1*C*G2' */ - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - - /* add to baseline visibilities V->U */ - double r00=t->y[8*ci]-creal(T2[0]); - double i00=t->y[8*ci+1]-cimag(T2[0]); - double r01=t->y[8*ci+2]-creal(T2[1]); - double i01=t->y[8*ci+3]-cimag(T2[1]); - double r10=t->y[8*ci+4]-creal(T2[2]); - double i10=t->y[8*ci+5]-cimag(T2[2]); - double r11=t->y[8*ci+6]-creal(T2[3]); - double i11=t->y[8*ci+7]-cimag(T2[3]); - - t->fcost+=r00*r00+i00*i00+r01*r01+i01*i01+r10*r10+i10*i10+r11*r11+i11*i11; - } - } - - return NULL; -} - - -/* cost function */ -/* x: 2Nx2 solution - y: visibilities, vectorized V(:) 8*Nbase x 1 -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static double -fns_f(complex double *x, double *y, global_data_rtr_t *gdata) { - - me_data_t *dp=(me_data_t*)gdata->medata; - - int nth,nth1,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthb0,Nthb; - thread_data_rtr_t *threaddata; - - int Nbase1=(dp->Nbase)*(dp->tilesz); - int boff=(dp->Nbase)*(dp->tileoff); - - /* calculate min baselines a thread can handle */ - Nthb0=(Nbase1+Nt-1)/Nt; - - if ((threaddata=(thread_data_rtr_t*)malloc((size_t)Nt*sizeof(thread_data_rtr_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr; - threaddata[nth].carr=dp->carr; - threaddata[nth].M=dp->M; - threaddata[nth].y=&(y[8*ci]); - threaddata[nth].N=dp->N; - threaddata[nth].x=x; /* note the difference: here x assumes no hybrid, also ordering different */ - threaddata[nth].clus=(dp->clus); - threaddata[nth].coh=&(dp->coh[4*(dp->M)*(ci+boff)]); - threaddata[nth].fcost=0.0; - - //printf("thread %d predict data from %d baselines %d\n",nth,8*ci,Nthb); - pthread_create(&gdata->th_array[nth],&gdata->attr,threadfn_fns_f,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - /* now wait for threads to finish */ - double fcost=0.0; - for(nth1=0; nth1th_array[nth1],NULL); - fcost+=threaddata[nth1].fcost; - } - - free(threaddata); - - return fcost; -} - - -/* inner product (metric) */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static double -fns_g(int N,complex double *x, complex double *eta, complex double *gamma) { - /* 2 x real( trace(eta'*gamma) ) - = 2 x real( eta(:,1)'*gamma(:,1) + eta(:,2)'*gamma(:,2) ) - no need to calculate off diagonal terms - )*/ - complex double v1=my_cdot(2*N,eta,gamma); - complex double v2=my_cdot(2*N,&eta[2*N],&gamma[2*N]); - - return 2.0*creal(v1+v2); -} - -/* Projection - rnew: new value */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -fns_proj(int N,complex double *x, complex double *z,complex double *rnew) { - /* projection = Z-X Om, where - Om X^H X+X^H X Om = X^H Z - Z^H X - is solved to find Om */ - - /* find X^H X */ - complex double xx00,xx01,xx10,xx11; - xx00=my_cdot(2*N,x,x); - xx01=my_cdot(2*N,x,&x[2*N]); - xx10=conj(xx01); - xx11=my_cdot(2*N,&x[2*N],&x[2*N]); - - /* find X^H Z (and using this just calculte Z^H X directly) */ - complex double xz00,xz01,xz10,xz11; - xz00=my_cdot(2*N,x,z); - xz01=my_cdot(2*N,x,&z[2*N]); - xz10=my_cdot(2*N,&x[2*N],z); - xz11=my_cdot(2*N,&x[2*N],&z[2*N]); - - /* find X^H Z - Z^H X */ - complex double rr00,rr01,rr10,rr11; - rr00=xz00-conj(xz00); - rr01=xz01-conj(xz10); - rr10=-conj(rr01); - rr11=xz11-conj(xz11); - - /* find I_2 kron (X^H X) + (X^H X)^T kron I_2 */ - /* A = [2*xx00 xx01 xx10 0 - xx10 xx11+xx00 0 xx10 - xx01 0 xx11+xx00 xx01 - 0 xx01 xx10 2*xx11 ] - */ - complex double A[16]; - A[0]=2.0*xx00; - A[5]=A[10]=xx11+xx00; - A[15]=2.0*xx11; - A[1]=A[8]=A[11]=A[13]=xx10; - A[2]=A[4]=A[7]=A[14]=xx01; - A[3]=A[6]=A[9]=A[12]=0.0; - complex double b[4]; - b[0]=rr00; - b[1]=rr10; - b[2]=rr01; - b[3]=rr11; - - /* solve A u = b to find u */ - complex double w,*WORK; - /* workspace query */ - int status=my_zgels('N',4,4,1,A,4,b,4,&w,-1); - int lwork=(int)w; - if ((WORK=(complex double*)calloc((size_t)lwork,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - status=my_zgels('N',4,4,1,A,4,b,4,WORK,lwork); - if (status) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: LAPACK error %d\n",__FILE__,__LINE__,status); -#endif - } - - free(WORK); - /* form Z - X * Om, where Om is given by solution b - but no need to rearrange b because it is already in col major order */ - my_ccopy(4*N,z,1,rnew,1); - my_zgemm('N','N',2*N,2,2,-1.0+0.0*_Complex_I,x,2*N,b,2,1.0+0.0*_Complex_I,rnew,2*N); - - -} - - -/* Retraction - rnew: new value */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -fns_R(int N,complex double *x, complex double *r,complex double *rnew) { - /* rnew = x + r */ - my_ccopy(4*N,x,1,rnew,1); - my_caxpy(4*N,r,1.0+_Complex_I*0.0,rnew); -} - - -/* worker thread function for gradient/hessian weighting */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -threadfn_fns_fscale(void *data) { - thread_data_rtr_t *t=(thread_data_rtr_t*)data; - - int ci,sta1; - for (ci=0; ciNb; ci++) { - sta1=ci+t->boff; - t->grad[2*sta1]*=t->iw[sta1]; - t->grad[2*sta1+2*t->N]*=t->iw[sta1]; - t->grad[2*sta1+1]*=t->iw[sta1]; - t->grad[2*sta1+2*t->N+1]*=t->iw[sta1]; - } - - return NULL; -} - - - - -/* worker thread function for gradient calculation */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -threadfn_fns_fgrad(void *data) { - thread_data_rtr_t *t=(thread_data_rtr_t*)data; - - int ci,cm,sta1,sta2; - complex double C[4],G1[4],G2[4],T1[4],T2[4],res[4]; - int M=(t->M); - cm=(t->clus); - for (ci=0; ciNb; ci++) { - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - /* gains for this cluster, for sta1,sta2 */ - G1[0]=(t->x[sta1*2]); - G1[1]=(t->x[sta1*2+2*t->N]); - G1[2]=(t->x[sta1*2+1]); - G1[3]=(t->x[sta1*2+2*t->N+1]); - G2[0]=(t->x[sta2*2]); - G2[1]=(t->x[sta2*2+2*t->N]); - G2[2]=(t->x[sta2*2+1]); - G2[3]=(t->x[sta2*2+2*t->N+1]); - - /* use pre calculated values */ - C[0]=t->coh[4*M*ci+4*cm]; - C[1]=t->coh[4*M*ci+4*cm+1]; - C[2]=t->coh[4*M*ci+4*cm+2]; - C[3]=t->coh[4*M*ci+4*cm+3]; - - - /* G1*C*G2' */ - amb(G1,C,T1); - ambt(T1,G2,T2); - - /* res=V(2*ck-1:2*ck,:)-x(2*p-1:2*p,:)*C*x(2*q-1:2*q,:)'; */ - /* V->U */ - res[0]=(t->y[8*ci]+_Complex_I*t->y[8*ci+1])-T2[0]; - res[1]=(t->y[8*ci+2]+_Complex_I*t->y[8*ci+3])-T2[1]; - res[2]=(t->y[8*ci+4]+_Complex_I*t->y[8*ci+5])-T2[2]; - res[3]=(t->y[8*ci+6]+_Complex_I*t->y[8*ci+7])-T2[3]; - - /* - grad(2*p-1:2*p,:)=grad(2*p-1:2*p,:)+res*x(2*q-1:2*q,:)*C'; - grad(2*q-1:2*q,:)=grad(2*q-1:2*q,:)+res'*x(2*p-1:2*p,:)*C; - */ - /* res*G2*C' */ - amb(res,G2,T1); - ambt(T1,C,T2); - - pthread_mutex_lock(&t->mx_array[sta1]); - t->grad[2*sta1]+=T2[0]; - t->grad[2*sta1+2*t->N]+=T2[1]; - t->grad[2*sta1+1]+=T2[2]; - t->grad[2*sta1+2*t->N+1]+=T2[3]; - pthread_mutex_unlock(&t->mx_array[sta1]); - - /* res'*G1*C */ - atmb(res,G1,T1); - amb(T1,C,T2); - - pthread_mutex_lock(&t->mx_array[sta2]); - t->grad[2*sta2]+=T2[0]; - t->grad[2*sta2+2*t->N]+=T2[1]; - t->grad[2*sta2+1]+=T2[2]; - t->grad[2*sta2+2*t->N+1]+=T2[3]; - pthread_mutex_unlock(&t->mx_array[sta2]); - - } - } - - return NULL; -} - - - -/* gradient function */ -/* x: 2Nx2 solution - fgradx: output, same shape as x - y: visibilities, vectorized V(:) 8*Nbase x 1 - if negate==1, return -grad, else just grad -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -fns_fgrad(complex double *x, complex double *fgradx, double *y, global_data_rtr_t *gdata, int negate) { - - me_data_t *dp=(me_data_t*)gdata->medata; - - int nth,nth1,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthb0,Nthb; - thread_data_rtr_t *threaddata; - - int Nbase1=(dp->Nbase)*(dp->tilesz); - int boff=(dp->Nbase)*(dp->tileoff); - - /* calculate min baselines a thread can handle */ - Nthb0=(Nbase1+Nt-1)/Nt; - - if ((threaddata=(thread_data_rtr_t*)malloc((size_t)Nt*sizeof(thread_data_rtr_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - complex double *grad; - if ((grad=(complex double*)calloc((size_t)4*dp->N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr; - threaddata[nth].carr=dp->carr; - threaddata[nth].M=dp->M; - threaddata[nth].y=&(y[8*ci]); - threaddata[nth].N=dp->N; - threaddata[nth].x=x; /* note the difference: here x assumes no hybrid, also ordering different */ - threaddata[nth].clus=(dp->clus); - threaddata[nth].coh=&(dp->coh[4*(dp->M)*(ci+boff)]); - threaddata[nth].grad=grad; - threaddata[nth].mx_array=gdata->mx_array; - - //printf("thread %d predict data from %d baselines %d\n",nth,8*ci,Nthb); - pthread_create(&gdata->th_array[nth],&gdata->attr,threadfn_fns_fgrad,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - /* now wait for threads to finish */ - for(nth1=0; nth1th_array[nth1],NULL); - } -/******************* scale *************/ - Nthb0=(dp->N+Nt-1)/Nt; - ci=0; - for (nth=0; nthN; nth++) { - if (ci+Nthb0N) { - Nthb=Nthb0; - } else { - Nthb=dp->N-ci; - } - threaddata[nth].boff=ci; - threaddata[nth].Nb=Nthb; - threaddata[nth].N=dp->N; - threaddata[nth].grad=grad; - threaddata[nth].iw=gdata->iw; - pthread_create(&gdata->th_array[nth],&gdata->attr,threadfn_fns_fscale,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - for(nth1=0; nth1th_array[nth1],NULL); - } -/******************* scale *************/ - free(threaddata); - if (negate) { - my_cscal(4*dp->N,-1.0+0.0*_Complex_I,grad); - } - fns_proj(dp->N,x,grad,fgradx); - free(grad); - -} - - -/* worker thread function for Hessian calculation */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -threadfn_fns_fhess(void *data) { - thread_data_rtr_t *t=(thread_data_rtr_t*)data; - - int ci,cm,sta1,sta2; - complex double C[4],G1[4],G2[4],T1[4],T2[4],res[4],res1[4],E1[4],E2[4]; - int M=(t->M); - cm=(t->clus); - for (ci=0; ciNb; ci++) { - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - /* gains for this cluster, for sta1,sta2 */ - G1[0]=(t->x[sta1*2]); - G1[1]=(t->x[sta1*2+2*t->N]); - G1[2]=(t->x[sta1*2+1]); - G1[3]=(t->x[sta1*2+2*t->N+1]); - G2[0]=(t->x[sta2*2]); - G2[1]=(t->x[sta2*2+2*t->N]); - G2[2]=(t->x[sta2*2+1]); - G2[3]=(t->x[sta2*2+2*t->N+1]); - E1[0]=t->eta[2*sta1]; - E1[1]=t->eta[2*sta1+2*t->N]; - E1[2]=t->eta[2*sta1+1]; - E1[3]=t->eta[2*sta1+2*t->N+1]; - E2[0]=t->eta[2*sta2]; - E2[1]=t->eta[2*sta2+2*t->N]; - E2[2]=t->eta[2*sta2+1]; - E2[3]=t->eta[2*sta2+2*t->N+1]; - - - - /* use pre calculated values */ - C[0]=t->coh[4*M*ci+4*cm]; - C[1]=t->coh[4*M*ci+4*cm+1]; - C[2]=t->coh[4*M*ci+4*cm+2]; - C[3]=t->coh[4*M*ci+4*cm+3]; - - /* G1*C*G2' */ - amb(G1,C,T1); - ambt(T1,G2,T2); - - - /* res=V(2*ck-1:2*ck,:)-x(2*p-1:2*p,:)*C*x(2*q-1:2*q,:)'; */ - /* V->U */ - res[0]=(t->y[8*ci]+_Complex_I*t->y[8*ci+1])-T2[0]; - res[1]=(t->y[8*ci+2]+_Complex_I*t->y[8*ci+3])-T2[1]; - res[2]=(t->y[8*ci+4]+_Complex_I*t->y[8*ci+5])-T2[2]; - res[3]=(t->y[8*ci+6]+_Complex_I*t->y[8*ci+7])-T2[3]; - - - /* - res1=x(2*p-1:2*p,:)*C*eta(2*q-1:2*q,:)'+eta(2*p-1:2*p,:)*C*x(2*q-1:2*q,:)'; - */ - /* G1*C*E2' */ - amb(G1,C,T1); - ambt(T1,E2,T2); - res1[0]=T2[0]; - res1[1]=T2[1]; - res1[2]=T2[2]; - res1[3]=T2[3]; - /* E1*C*G2' */ - amb(E1,C,T1); - ambt(T1,G2,T2); - res1[0]+=T2[0]; - res1[1]+=T2[1]; - res1[2]+=T2[2]; - res1[3]+=T2[3]; - - - /* - hess(2*p-1:2*p,:)=hess(2*p-1:2*p,:)+(res*eta(2*q-1:2*q,:)-res1*x(2*q-1:2*q,:))*C'; - */ - - /* (res*E2-res1*G2)*C' */ - amb(res,E2,T1); - amb(res1,G2,T2); - T1[0]-=T2[0]; - T1[1]-=T2[1]; - T1[2]-=T2[2]; - T1[3]-=T2[3]; - ambt(T1,C,T2); - - pthread_mutex_lock(&t->mx_array[sta1]); - t->hess[2*sta1]+=T2[0]; - t->hess[2*sta1+2*t->N]+=T2[1]; - t->hess[2*sta1+1]+=T2[2]; - t->hess[2*sta1+2*t->N+1]+=T2[3]; - pthread_mutex_unlock(&t->mx_array[sta1]); - - - /* - hess(2*q-1:2*q,:)=hess(2*q-1:2*q,:)+(res'*eta(2*p-1:2*p,:)-res1'*x(2*p-1:2*p,:))*C; - */ - - /* (res'*E1-res1'*G1)*C */ - atmb(res,E1,T1); - atmb(res1,G1,T2); - T1[0]-=T2[0]; - T1[1]-=T2[1]; - T1[2]-=T2[2]; - T1[3]-=T2[3]; - amb(T1,C,T2); - - pthread_mutex_lock(&t->mx_array[sta2]); - t->hess[2*sta2]+=T2[0]; - t->hess[2*sta2+2*t->N]+=T2[1]; - t->hess[2*sta2+1]+=T2[2]; - t->hess[2*sta2+2*t->N+1]+=T2[3]; - pthread_mutex_unlock(&t->mx_array[sta2]); - - } - } - - return NULL; -} - - - -/* Hessian function */ -/* x: 2Nx2 solution - eta: same shape as x - fhess: output, same shape as x - y: visibilities, vectorized V(:) 8*Nbase x 1 -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -fns_fhess(complex double *x, complex double *eta,complex double *fhess, double *y, global_data_rtr_t *gdata) { - - me_data_t *dp=(me_data_t*)gdata->medata; - - int nth,nth1,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthb0,Nthb; - thread_data_rtr_t *threaddata; - - int Nbase1=(dp->Nbase)*(dp->tilesz); - int boff=(dp->Nbase)*(dp->tileoff); - - /* calculate min baselines a thread can handle */ - Nthb0=(Nbase1+Nt-1)/Nt; - - if ((threaddata=(thread_data_rtr_t*)malloc((size_t)Nt*sizeof(thread_data_rtr_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - complex double *hess; - if ((hess=(complex double*)calloc((size_t)4*dp->N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr; - threaddata[nth].carr=dp->carr; - threaddata[nth].M=dp->M; - threaddata[nth].y=&(y[8*ci]); - threaddata[nth].N=dp->N; - threaddata[nth].x=x; /* note the difference: here x assumes no hybrid, also ordering different */ - threaddata[nth].clus=(dp->clus); - threaddata[nth].coh=&(dp->coh[4*(dp->M)*(ci+boff)]); - threaddata[nth].eta=eta; - threaddata[nth].hess=hess; - threaddata[nth].mx_array=gdata->mx_array; - - //printf("thread %d predict data from %d baselines %d\n",nth,8*ci,Nthb); - pthread_create(&gdata->th_array[nth],&gdata->attr,threadfn_fns_fhess,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - /* now wait for threads to finish */ - for(nth1=0; nth1th_array[nth1],NULL); - } - -/******************* scale *************/ - Nthb0=(dp->N+Nt-1)/Nt; - ci=0; - for (nth=0; nthN; nth++) { - if (ci+Nthb0N) { - Nthb=Nthb0; - } else { - Nthb=dp->N-ci; - } - threaddata[nth].boff=ci; - threaddata[nth].Nb=Nthb; - threaddata[nth].N=dp->N; - threaddata[nth].grad=fhess; - threaddata[nth].iw=gdata->iw; - pthread_create(&gdata->th_array[nth],&gdata->attr,threadfn_fns_fscale,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - for(nth1=0; nth1th_array[nth1],NULL); - } -/******************* scale *************/ - free(threaddata); - fns_proj(dp->N,x,hess,fhess); - free(hess); - -} - - -/* truncated conjugate gradient method - x, grad, eta, r, z, delta, Hxd : size 2N x 2 complex - so, vector size is 4N complex double - - output: eta - output: fhess (can be reused in calling func) - return value: stop_tCG code - - y: vec(V) visibilities -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static int -tcg_solve(int N, complex double *x, complex double *grad, complex double *eta, complex double *fhess, - double Delta, double theta, double kappa, int max_inner, int min_inner, double *y, global_data_rtr_t *gdata) { - - complex double *r,*z,*delta,*Hxd, *rnew; - double e_Pe, r_r, norm_r, z_r, d_Pd, d_Hd, alpha, e_Pe_new, - e_Pd, Deltasq, tau, zold_rold, beta, norm_r0; - int cj, stop_tCG; - - if ((r=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((z=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((delta=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((Hxd=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((rnew=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - /* - initial values - % eta = 0*grad; << zero matrix provided - r = grad; - e_Pe = 0; - */ - my_ccopy(4*N,grad,1,r,1); - e_Pe=0.0; - - /* - r_r = fns.g(x,r,r); - norm_r = sqrt(r_r); - norm_r0 = norm_r; - */ - - r_r=fns_g(N,x,r,r); - norm_r=sqrt(r_r); - norm_r0=norm_r; - - /* - z = r; - */ - my_ccopy(4*N,r,1,z,1); - - /* - % compute z'*r - z_r = fns.g(x,z,r); - d_Pd = z_r; - */ - z_r=fns_g(N,x,z,r); - d_Pd=z_r; - - /* - % initial search direction - delta = -z; - e_Pd = fns.g(x,eta,delta); - */ - memset(delta,0,sizeof(complex double)*N*4); - my_caxpy(4*N,z,-1.0+_Complex_I*0.0,delta); - e_Pd=fns_g(N,x,eta,delta); - - /* - % pre-assume termination b/c j == end - stop_tCG = 5; - */ - stop_tCG=5; - - /* % begin inner/tCG loop - for j = 1:max_inner, - */ - for(cj=1; cj<=max_inner; cj++) { - /**************************************************/ - /* - Hxd = fns.fhess(x,delta); - - % compute curvature - d_Hd = fns.g(x,delta,Hxd); - */ - fns_fhess(x,delta,Hxd,y,gdata); - d_Hd=fns_g(N,x,delta,Hxd); - - /* - alpha = z_r/d_Hd; - e_Pe_new = e_Pe + 2.0*alpha*e_Pd + alpha*alpha*d_Pd; - */ - alpha=z_r/d_Hd; - e_Pe_new = e_Pe + 2.0*alpha*e_Pd + alpha*alpha*d_Pd; - - /* - - % check curvature and trust-region radius - if d_Hd <= 0 || e_Pe_new >= Delta^2, - - */ - Deltasq=Delta*Delta; - if (d_Hd <= 0.0 || e_Pe_new >= Deltasq) { - /* - tau = (-e_Pd + sqrt(e_Pd*e_Pd + d_Pd*(Delta^2-e_Pe))) / d_Pd; - - */ - tau = (-e_Pd + sqrt(e_Pd*e_Pd + d_Pd*(Deltasq-e_Pe)))/d_Pd; - - /* - eta = eta + tau*delta; - - */ - my_caxpy(4*N,delta,tau+_Complex_I*0.0,eta); - - /* NEW Heta = Heta + tau*Hdelta */ - my_caxpy(4*N,fhess,tau+_Complex_I*0.0,Hxd); - - /* - if d_Hd <= 0, - stop_tCG = 1; % negative curvature - else - stop_tCG = 2; % exceeded trust region - end - */ - stop_tCG=(d_Hd<=0.0?1:2); - - /* - break (for) - */ - break; - /* - end if - */ - } - - - /* - % no negative curvature and eta_prop inside TR: accept it - e_Pe = e_Pe_new; - eta = eta + alpha*delta; - - */ - e_Pe=e_Pe_new; - my_caxpy(4*N,delta,alpha+_Complex_I*0.0,eta); - - /* NEW Heta = Heta + alpha*Hdelta */ - my_caxpy(4*N,fhess,alpha+_Complex_I*0.0,Hxd); - - - /* - % update the residual - r = r + alpha*Hxd; - - */ - my_caxpy(4*N,Hxd,alpha+_Complex_I*0.0,r); - - /* - % compute new norm of r - r_r = fns.g(x,r,r); - norm_r = sqrt(r_r); - - */ - r_r=fns_g(N,x,r,r); - norm_r=sqrt(r_r); - - - /* - % check kappa/theta stopping criterion - if j >= min_inner && norm_r <= norm_r0*min(norm_r0^theta,kappa) - */ - if (cj >= min_inner) { - double norm_r0pow=pow(norm_r0,theta); - if (norm_r <= norm_r0*MIN(norm_r0pow,kappa)) { - - /* - % residual is small enough to quit - if kappa < norm_r0^theta, - stop_tCG = 3; % linear convergence - else - stop_tCG = 4; % superlinear convergence - end - - */ - stop_tCG=(kappamedata; - - double fx=fns_f(x,y,gdata); - fns_fgrad(x,eta,y,gdata,0); - double beta0=beta; - double minfx=fx; double minbeta=beta0; - double lhs,rhs,metric; - int m,nocostred=0; - *mincost=fx; - double metric0=fns_g(dp->N,x,eta,eta); - for (m=0; m<50; m++) { - /* abeta=(beta0)*alphabar*eta; */ - my_ccopy(4*dp->N,eta,1,teta,1); - my_cscal(4*dp->N,beta0*alphabar+0.0*_Complex_I,teta); - /* Rx=R(x,teta); */ - fns_R(dp->N,x,teta,x_prop); - lhs=fns_f(x_prop,y,gdata); - if (lhsN,x,eta,teta); - rhs=fx+sigma*metric; - /* break loop also if no further cost improvement is seen */ - if (lhs<=rhs) { - minbeta=beta0; - break; - } - beta0=beta0*beta; - } - - /* if no further cost improvement is seen */ - if (lhs>fx) { - nocostred=1; - } - - my_ccopy(4*dp->N,eta,1,teta,1); - my_cscal(4*dp->N,minbeta*alphabar+0.0*_Complex_I,teta); - - return nocostred; -} - - -int -rtr_solve_nocuda( - double *x0, /* initial values and updated solution at output (size 8*N double) */ - double *y, /* data vector (size 8*M double) */ - int N, /* no. of stations */ - int M, /* no. of constraints */ - int itmax_rsd, /* maximum number of iterations RSD */ - int itmax_rtr, /* maximum number of iterations RTR */ - double Delta_bar, double Delta0, /* Trust region radius and initial value */ - double *info, /* initial and final residuals */ - me_data_t *adata) { /* pointer to additional data */ - - /* reshape x to make J: 2Nx2 complex double - */ - complex double *x; - if ((x=(complex double*)malloc((size_t)4*N*sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - /* map x: [(re,im)J_1(0,0) (re,im)J_1(0,1) (re,im)J_1(1,0) (re,im)J_1(1,1)...] - to - J: [J_1(0,0) J_1(1,0) J_2(0,0) J_2(1,0) ..... J_1(0,1) J_1(1,1) J_2(0,1) J_2(1,1)....] - */ - double *Jd=(double*)x; - /* re J(0,0) */ - my_dcopy(N, &x0[0], 8, &Jd[0], 4); - /* im J(0,0) */ - my_dcopy(N, &x0[1], 8, &Jd[1], 4); - /* re J(1,0) */ - my_dcopy(N, &x0[4], 8, &Jd[2], 4); - /* im J(1,0) */ - my_dcopy(N, &x0[5], 8, &Jd[3], 4); - /* re J(0,1) */ - my_dcopy(N, &x0[2], 8, &Jd[4*N], 4); - /* im J(0,1) */ - my_dcopy(N, &x0[3], 8, &Jd[4*N+1], 4); - /* re J(1,1) */ - my_dcopy(N, &x0[6], 8, &Jd[4*N+2], 4); - /* im J(1,1) */ - my_dcopy(N, &x0[7], 8, &Jd[4*N+3], 4); - - - - int Nt=adata->Nt; - int ci; - global_data_rtr_t gdata; - - gdata.medata=adata; - /* setup threads */ - pthread_attr_init(&gdata.attr); - pthread_attr_setdetachstate(&gdata.attr,PTHREAD_CREATE_JOINABLE); - - if ((gdata.th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - if ((gdata.mx_array=(pthread_mutex_t*)malloc((size_t)N*sizeof(pthread_mutex_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((gdata.iw=(double*)malloc((size_t)N*sizeof(double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - for (ci=0; cistation contributions - NOTE: has to be done here because the baseline offset would change */ - fns_fcount(&gdata); -/***************************************************/ - int min_inner,max_inner,min_outer,max_outer; - double epsilon,kappa,theta,rho_prime; - /* - min_inner = 0; - max_inner = inf; - min_outer = 3; - max_outer = 100; - epsilon = 1e-6; - kappa = 0.1; - theta = 1.0; - rho_prime = 0.1; - %Delta_bar = user must specify - %Delta0 = user must specify - %x0 = user must specify - */ - min_inner=1; max_inner=itmax_rtr;//8*N; - min_outer=3; max_outer=itmax_rtr; - epsilon=CLM_EPSILON; - kappa=0.1; - theta=1.0; - /* default values 0.25, 0.75, 0.25, 2.0 */ - double eta1=0.0001; double eta2=0.99; double alpha1=0.25; double alpha2=3.5; - rho_prime=eta1; /* default 0.1 should be <= 0.25 */ - double rho_regularization; /* default 1e2 use large damping (but less than GPU version) */ - double rho_reg; - int model_decreased=0; - - complex double *fgradx,*eta,*Heta,*x_prop; - if ((fgradx=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((eta=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((Heta=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((x_prop=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - - double fx,norm_grad,Delta,fx_prop,rhonum,rhoden,rho; - fx=fns_f(x,y,&gdata); - double fx0=fx; - int rsdstat=0; -/***************************************************/ - /* RSD solution */ - for (ci=0; ci0?0:1); - int stop_inner=0; - // x0 is already copied to x: my_ccopy(4*N,x0,1,x,1); - if(!stop_outer) { - fns_fgrad(x,fgradx,y,&gdata,1); - norm_grad = sqrt(fns_g(N,x,fgradx,fgradx)); - } - Delta=Delta0; - - /* initial residual */ - info[0]=fx; - - /* - % ** Start of TR loop ** - while stop_outer==0, - */ - while(!stop_outer) { - /* - % update counter - k = k+1; - */ - k++; - - - /* - ** Begin TR Subproblem ** - % determine eta0 - % without randT, 0*fgradx is the only way that we - % know how to create a tangent vector - eta = 0*fgradx; - */ - memset(eta,0,sizeof(complex double)*N*4); - - - /* - % solve TR subproblem - [eta,numit,stop_inner] = tCG(fns,x,fgradx,eta,Delta,theta,kappa,min_inner,max_inner,useRand,debug); - */ - stop_inner=tcg_solve(N, x, fgradx, eta, Heta, Delta, theta, kappa, max_inner, min_inner,y,&gdata); - - /* - norm_eta = sqrt(fns.g(x,eta,eta)); - */ - - /* - Heta = fns.fhess(x,eta); - */ - //OLD fns_fhess(x,eta,Heta,y,&gdata); - - /* - % compute the retraction of the proposal - x_prop = fns.R(x,eta); - */ - fns_R(N,x,eta,x_prop); - - /* - % compute function value of the proposal - fx_prop = fns.f(x_prop); - */ - fx_prop=fns_f(x_prop,y,&gdata); - - /* - % do we accept the proposed solution or not? - % compute the Hessian at the proposal - Heta = fns.fhess(x,eta); - FIXME: do we need to do this, because Heta is already there - or change x to x_prop ??? - */ - //Disabled fns_fhess(x,eta,Heta,y,&gdata); - - /* - % check the performance of the quadratic model - rhonum = fx-fx_prop; - rhoden = -fns.g(x,fgradx,eta) - 0.5*fns.g(x,Heta,eta); - */ - rhonum=fx-fx_prop; - rhoden=-fns_g(N,x,fgradx,eta)-0.5*fns_g(N,x,Heta,eta); - /* regularization of rho ratio */ - /* - rho_reg = max(1, abs(fx)) * eps * options.rho_regularization; - rhonum = rhonum + rho_reg; - rhoden = rhoden + rho_reg; - */ - rho_reg=MAX(1.0,fx)*rho_regularization; /* no epsilon */ - rhonum+=rho_reg; - rhoden+=rho_reg; - - - /* - rho = rhonum / rhoden; - */ - rho=rhonum/rhoden; - - - - /* - % HEURISTIC WARNING: - % if abs(model change) is relatively zero, we are probably near a critical - % point. set rho to 1. - if abs(rhonum/fx) < sqrt(eps), - small_rhonum = rhonum; - rho = 1; - else - small_rhonum = 0; - end - FIXME: use constant for sqrt(eps) - */ - /* OLD CODE if (fabs(rhonum/fx) = 0); */ - model_decreased=(rhoden>=0.0?1:0); - - /* NOTE: if too many values of rho are -ve, it means TR radius is too big - so initial TR radius should be reduced */ -#ifdef DEBUG - printf("stop_inner=%d rho_reg=%g rho =%g/%g= %g rho'= %g\n",stop_inner,rho_reg,rhonum,rhoden,rho,rho_prime); -#endif - - /* - % choose new TR radius based on performance - if rho < 1/4 - Delta = 1/4*Delta; - elseif rho > 3/4 && (stop_inner == 2 || stop_inner == 1), - Delta = min(2*Delta,Delta_bar); - end - */ - if (!model_decreased || rhoeta2 && (stop_inner==2 || stop_inner==1)) { - /* we have a good reduction, so increase TR radius */ - Delta=MIN(alpha2*Delta,Delta_bar); - } - - /* - % choose new iterate based on performance - oldgradx = fgradx; - if rho > rho_prime, - accept = true; - x = x_prop; - fx = fx_prop; - fgradx = fns.fgrad(x); - norm_grad = sqrt(fns.g(x,fgradx,fgradx)); - else - accept = false; - end - */ - if (model_decreased && rho>rho_prime) { - my_ccopy(4*N,x_prop,1,x,1); - fx=fx_prop; - fns_fgrad(x,fgradx,y,&gdata,1); - norm_grad=sqrt(fns_g(N,x,fgradx,fgradx)); - } - - - /* - % ** Testing for Stop Criteria - % min_outer is the minimum number of inner iterations - % before we can exit. this gives randomization a chance to - % escape a saddle point. - if norm_grad < epsilon && (~useRand || k > min_outer), - stop_outer = 1; - end - */ - if (norm_gradmin_outer) { - stop_outer=1; - } - - - /* - % stop after max_outer iterations - if k >= max_outer, - if (verbosity > 0), - fprintf('\n*** timed out -- k == %d***\n',k); - end - stop_outer = 1; - end - */ - if (k>=max_outer) { - stop_outer=1; - } - -#ifdef DEBUG - printf("Iter %d cost=%lf\n",k,fx); -#endif - /* end of TR loop (counter: k) */ - } - - /* final residual */ - info[1]=fx; - - free(fgradx); - free(eta); - free(Heta); - free(x_prop); -/***************************************************/ - for (ci=0; cifx) { - /* copy back solution to x0 */ - /* re J(0,0) */ - my_dcopy(N, &Jd[0], 4, &x0[0], 8); - /* im J(0,0) */ - my_dcopy(N, &Jd[1], 4, &x0[1], 8); - /* re J(1,0) */ - my_dcopy(N, &Jd[2], 4, &x0[4], 8); - /* im J(1,0) */ - my_dcopy(N, &Jd[3], 4, &x0[5], 8); - /* re J(0,1) */ - my_dcopy(N, &Jd[4*N], 4, &x0[2], 8); - /* im J(0,1) */ - my_dcopy(N, &Jd[4*N+1], 4, &x0[3], 8); - /* re J(1,1) */ - my_dcopy(N, &Jd[4*N+2], 4, &x0[6], 8); - /* im J(1,1) */ - my_dcopy(N, &Jd[4*N+3], 4, &x0[7], 8); - } - - free(x); - return 0; -} diff --git a/src/lib/rtr_solve_cuda.c b/src/lib/rtr_solve_cuda.c deleted file mode 100644 index d31941f..0000000 --- a/src/lib/rtr_solve_cuda.c +++ /dev/null @@ -1,894 +0,0 @@ -/* - * - Copyright (C) 2006-2008 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#include -#include -#include -#include -#include - -#include "sagecal.h" -#include - -//#define DEBUG -/* helper functions for diagnostics */ -static void -checkCudaError(cudaError_t err, char *file, int line) -{ -#ifdef CUDA_DEBUG - if(!err) - return; - fprintf(stderr,"GPU (CUDA): %s %s %d\n", cudaGetErrorString(err),file,line); - exit(EXIT_FAILURE); -#endif -} - - -static void -checkCublasError(cublasStatus_t cbstatus, char *file, int line) -{ -#ifdef CUDA_DEBUG - if (cbstatus!=CUBLAS_STATUS_SUCCESS) { - fprintf(stderr,"%s: %d: CUBLAS failure\n",file,line); - exit(EXIT_FAILURE); - } -#endif -} - - -/* Retraction - rnew: new value */ -/* rnew = x + r */ -void -cudakernel_fns_R(int N, cuFloatComplex *x, cuFloatComplex *r, cuFloatComplex *rnew, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle) { - cublasStatus_t cbstatus; - cbstatus=cublasCcopy(cbhandle,4*N,x,1,rnew,1); - cuFloatComplex alpha; - alpha.x=1.0f; alpha.y=0.0f; - cbstatus=cublasCaxpy(cbhandle,4*N, &alpha, r, 1, rnew, 1); - checkCublasError(cbstatus,__FILE__,__LINE__); -} - - -/* inner product (metric) */ -float -cudakernel_fns_g(int N,cuFloatComplex *x,cuFloatComplex *eta, cuFloatComplex *gamma,cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle) { - /* 2 x real( trace(eta'*gamma) ) - = 2 x real( eta(:,1)'*gamma(:,1) + eta(:,2)'*gamma(:,2) ) - no need to calculate off diagonal terms - )*/ - cublasStatus_t cbstatus; - cuFloatComplex r1,r2; - //complex double v1=my_cdot(2*N,eta,gamma); - cbstatus=cublasCdotc(cbhandle,2*N,eta,1,gamma,1,&r1); - //complex double v2=my_cdot(2*N,&eta[2*N],&gamma[2*N]); - cbstatus=cublasCdotc(cbhandle,2*N,&eta[2*N],1,&gamma[2*N],1,&r2); - - checkCublasError(cbstatus,__FILE__,__LINE__); - return 2.0f*(r1.x+r2.x); -} - - -/* Projection - rnew: new value */ -void -cudakernel_fns_proj(int N, cuFloatComplex *x, cuFloatComplex *z, cuFloatComplex *rnew, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle) { - /* projection = Z-X Om, where - Om X^H X+X^H X Om = X^H Z - Z^H X - is solved to find Om */ - - cublasStatus_t cbstatus; - /* find X^H X */ - cuFloatComplex xx00,xx01,xx10,xx11,*bd; - //xx00=my_cdot(2*N,x,x); - cbstatus=cublasCdotc(cbhandle,2*N,x,1,x,1,&xx00); - //xx01=my_cdot(2*N,x,&x[2*N]); - cbstatus=cublasCdotc(cbhandle,2*N,x,1,&x[2*N],1,&xx01); - xx10=cuConjf(xx01); - //xx11=my_cdot(2*N,&x[2*N],&x[2*N]); - cbstatus=cublasCdotc(cbhandle,2*N,&x[2*N],1,&x[2*N],1,&xx11); - - /* find X^H Z (and using this just calculte Z^H X directly) */ - cuFloatComplex xz00,xz01,xz10,xz11; - //xz00=my_cdot(2*N,x,z); - cbstatus=cublasCdotc(cbhandle,2*N,x,1,z,1,&xz00); - //xz01=my_cdot(2*N,x,&z[2*N]); - cbstatus=cublasCdotc(cbhandle,2*N,x,1,&z[2*N],1,&xz01); - //xz10=my_cdot(2*N,&x[2*N],z); - cbstatus=cublasCdotc(cbhandle,2*N,&x[2*N],1,z,1,&xz10); - //xz11=my_cdot(2*N,&x[2*N],&z[2*N]); - cbstatus=cublasCdotc(cbhandle,2*N,&x[2*N],1,&z[2*N],1,&xz11); - - /* find X^H Z - Z^H X */ - cuFloatComplex rr00,rr01,rr10,rr11; - //rr00=xz00-conj(xz00); - rr00=cuCsubf(xz00,cuConjf(xz00)); - //rr01=xz01-conj(xz10); - rr01=cuCsubf(xz01,cuConjf(xz10)); - //rr10=-conj(rr01); - rr10.x=-rr01.x; rr10.y=rr01.y; - //rr11=xz11-conj(xz11); - rr11=cuCsubf(xz11,cuConjf(xz11)); - - /* find I_2 kron (X^H X) + (X^H X)^T kron I_2 */ - /* A = [2*xx00 xx01 xx10 0 - xx10 xx11+xx00 0 xx10 - xx01 0 xx11+xx00 xx01 - 0 xx01 xx10 2*xx11 ] - */ - cuFloatComplex A[16],*Ad; - A[0]=cuCmulf(make_cuFloatComplex(2.0f,0.0f),xx00); - A[5]=A[10]=cuCaddf(xx00,xx11); - A[15]=cuCmulf(make_cuFloatComplex(2.0f,0.0f),xx11); - A[1]=A[8]=A[11]=A[13]=xx10; - A[2]=A[4]=A[7]=A[14]=xx01; - A[3]=A[6]=A[9]=A[12]=make_cuFloatComplex(0.0f,0.0f); - cuFloatComplex b[4]; - b[0]=rr00; - b[1]=rr10; - b[2]=rr01; - b[3]=rr11; - -#ifdef DEBUG - printf("BEFOREA=[\n"); - printf("%f+j*(%f) %f+j*(%f) %f+j*(%f) %f+j*(%f)\n",A[0].x,A[0].y,A[4].x,A[4].y,A[8].x,A[8].y,A[12].x,A[12].y); - printf("%f+j*(%f) %f+j*(%f) %f+j*(%f) %f+j*(%f)\n",A[1].x,A[1].y,A[5].x,A[5].y,A[9].x,A[9].y,A[13].x,A[13].y); - printf("%f+j*(%f) %f+j*(%f) %f+j*(%f) %f+j*(%f)\n",A[2].x,A[2].y,A[6].x,A[6].y,A[10].x,A[10].y,A[14].x,A[14].y); - printf("%f+j*(%f) %f+j*(%f) %f+j*(%f) %f+j*(%f)\n",A[3].x,A[3].y,A[7].x,A[7].y,A[11].x,A[11].y,A[15].x,A[15].y); - printf("];\n"); - printf("BEFOREb=[\n"); - printf("%f+j*(%f)\n",b[0].x,b[0].y); - printf("%f+j*(%f)\n",b[1].x,b[1].y); - printf("%f+j*(%f)\n",b[2].x,b[2].y); - printf("%f+j*(%f)\n",b[3].x,b[3].y); - printf("];\n"); -#endif - - - /* solve A u = b to find u , using double precision */ - cudaMalloc((void **)&Ad, 16*sizeof(cuFloatComplex)); - cudaMemcpy(Ad,A,16*sizeof(cuFloatComplex),cudaMemcpyHostToDevice); - /* copy b to device */ - cudaMalloc((void **)&bd, 4*sizeof(cuFloatComplex)); - cudaMemcpy(bd,b,4*sizeof(cuFloatComplex),cudaMemcpyHostToDevice); - - //culaStatus status; - //status=culaDeviceCgels('N',4,4,1,(culaDeviceFloatComplex *)Ad,4,(culaDeviceFloatComplex *)bd,4); - //checkStatus(status,__FILE__,__LINE__); - int work_size=0; - int *devInfo; - cudaError_t err; - err=cudaMalloc((void**)&devInfo, sizeof(int)); - checkCudaError(err,__FILE__,__LINE__); - cuFloatComplex *work,*taud; - cusolverDnCgeqrf_bufferSize(solver_handle, 4, 4, (cuFloatComplex *)Ad, 4, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(cuFloatComplex)); - err=cudaMalloc((void**)&taud, 4*sizeof(cuFloatComplex)); - checkCudaError(err,__FILE__,__LINE__); - cusolverDnCgeqrf(solver_handle, 4, 4, Ad, 4, taud, work, work_size, devInfo); - cusolverDnCunmqr(solver_handle, CUBLAS_SIDE_LEFT, CUBLAS_OP_C, 4, 1, 4, Ad, 4, taud, bd, 4, work, work_size, devInfo); - cuFloatComplex cone; cone.x=1.0f; cone.y=0.0f; - cbstatus=cublasCtrsm(cbhandle,CUBLAS_SIDE_LEFT,CUBLAS_FILL_MODE_UPPER,CUBLAS_OP_N,CUBLAS_DIAG_NON_UNIT,4,1,&cone,Ad,4,bd,4); - - - cudaFree(work); - cudaFree(taud); - cudaFree(devInfo); - - -#ifdef DEBUG - cudaMemcpy(b,bd,4*sizeof(cuFloatComplex),cudaMemcpyDeviceToHost); - printf("Afterb=[\n"); - printf("%f+j*(%f)\n",b[0].x,b[0].y); - printf("%f+j*(%f)\n",b[1].x,b[1].y); - printf("%f+j*(%f)\n",b[2].x,b[2].y); - printf("%f+j*(%f)\n",b[3].x,b[3].y); - printf("];\n"); -#endif - - /* form Z - X * Om, where Om is given by solution b - but no need to rearrange b because it is already in col major order */ - //my_ccopy(4*N,z,1,rnew,1); - cbstatus=cublasCcopy(cbhandle,4*N,z,1,rnew,1); - checkCublasError(cbstatus,__FILE__,__LINE__); - //my_zgemm('N','N',2*N,2,2,-1.0+0.0*_Complex_I,z,2*N,b,2,1.0+0.0*_Complex_I,rnew,2*N); - cuFloatComplex a1,a2; - a1.x=-1.0f; a1.y=0.0f; - a2.x=1.0f; a2.y=0.0f; -#ifdef DEBUG -/* read back eta for checking */ - cuFloatComplex *etalocal; - cudaHostAlloc((void **)&etalocal, sizeof(cuFloatComplex)*4*N,cudaHostAllocDefault); - cudaMemcpy(etalocal, rnew, 4*N*sizeof(cuFloatComplex), cudaMemcpyDeviceToHost); -printf("Rnewbefore=[\n"); - int ci; - for (ci=0; ci<2*N; ci++) { - printf("%f+j*(%f) %f+j*(%f);\n",etalocal[ci].x,etalocal[ci].y,etalocal[ci+2*N].x,etalocal[ci+2*N].y); - } -printf("]\n"); -#endif - - cbstatus=cublasCgemm(cbhandle,CUBLAS_OP_N,CUBLAS_OP_N,2*N,2,2,&a1,x,2*N,bd,2,&a2,rnew,2*N); - -#ifdef DEBUG - checkCublasError(cbstatus,__FILE__,__LINE__); - cudaMemcpy(etalocal, rnew, 4*N*sizeof(cuFloatComplex), cudaMemcpyDeviceToHost); -printf("Rnewafter=[\n"); - for (ci=0; ci<2*N; ci++) { - printf("%f+j*(%f) %f+j*(%f);\n",etalocal[ci].x,etalocal[ci].y,etalocal[ci+2*N].x,etalocal[ci+2*N].y); - } -printf("]\n"); - cudaFreeHost(etalocal); -#endif - checkCublasError(cbstatus,__FILE__,__LINE__); - cudaFree(Ad); - cudaFree(bd); -} - -/* gradient, also projected to tangent space */ -/* need 8N*BlocksPerGrid+ 8N*2 float storage */ -static void -cudakernel_fns_fgrad(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, float *y, float *coh, short *bbh, float *iw, int negate, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle) { - cuFloatComplex *tempeta,*tempb; - cublasStatus_t cbstatus=CUBLAS_STATUS_SUCCESS; - cuFloatComplex alpha; - cudaMalloc((void**)&tempeta, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&tempb, sizeof(cuFloatComplex)*4*N); - /* max size of M for one kernel call, to determine optimal blocks */ - int T=DEFAULT_TH_PER_BK*ThreadsPerBlock; - if (MN,eta,1,teta,1); - cbstatus=cublasCcopy(cbhandle,4*N,eta,1,teta,1); - //my_cscal(4*dp->N,beta0*alphabar+0.0*_Complex_I,teta); - alpha.x=beta0*alphabar;alpha.y=0.0f; - cbstatus=cublasCscal(cbhandle,4*N,&alpha,teta,1); - /* Rx=R(x,teta); */ - //fns_R(dp->N,x,teta,x_prop); - cudakernel_fns_R(N,x,teta,x_prop,cbhandle,solver_handle); - //lhs=fns_f(x_prop,y,gdata); - lhs=cudakernel_fns_f(ThreadsPerBlock,BlocksPerGrid,N,M,x_prop,y,coh,bbh); - if (lhsN,x,eta,teta); - //metric=cudakernel_fns_g(N,x,eta,teta,cbhandle); - metric=beta0*alphabar*metric0; - rhs=fx+sigma*metric; -#ifdef DEBUG -printf("m=%d lhs=%e rhs=%e rat=%e norm=%e\n",m,lhs,rhs,lhs/rhs,metric); -#endif - if ((!isnan(lhs) && lhs<=rhs)) { - minbeta=beta0; - break; - } - beta0=beta0*beta; - } - - /* if no further cost improvement is seen */ - if (lhs>fx) { - nocostred=1; - } - - //my_ccopy(4*dp->N,eta,1,teta,1); - cbstatus=cublasCcopy(cbhandle,4*N,eta,1,teta,1); - alpha.x=minbeta*alphabar; alpha.y=0.0f; - //my_cscal(4*dp->N,minbeta*alphabar+0.0*_Complex_I,teta); - cbstatus=cublasCscal(cbhandle,4*N,&alpha,teta,1); - - checkCublasError(cbstatus,__FILE__,__LINE__); - cudaFree(eta); - cudaFree(x_prop); - - return nocostred; -} - - -/* truncated conjugate gradient method - x, grad, eta, r, z, delta, Hxd : size 2N x 2 complex - so, vector size is 4N complex double - - output: eta - return value: stop_tCG code - - y: vec(V) visibilities -*/ -/* need 8N*(BlocksPerGrid+2)+ 8N*6 float storage */ -static int -tcg_solve_cuda(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *grad, cuFloatComplex *eta, cuFloatComplex *fhess, float Delta, float theta, float kappa, int max_inner, int min_inner, float *y, float *coh, short *bbh, float *iw, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle) { - cuFloatComplex *r,*z,*delta,*Hxd, *rnew; - float e_Pe, r_r, norm_r, z_r, d_Pd, d_Hd, alpha, e_Pe_new, - e_Pd, Deltasq, tau, zold_rold, beta, norm_r0; - int cj, stop_tCG; - cudaMalloc((void**)&r, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&z, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&delta, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&Hxd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&rnew, sizeof(cuFloatComplex)*4*N); - - cublasStatus_t cbstatus; - cuFloatComplex a0; - - /* - initial values - */ - cbstatus=cublasCcopy(cbhandle,4*N,grad,1,r,1); - e_Pe=0.0f; - - - r_r=cudakernel_fns_g(N,x,r,r,cbhandle,solver_handle); - norm_r=sqrtf(r_r); - norm_r0=norm_r; - - cbstatus=cublasCcopy(cbhandle,4*N,r,1,z,1); - - z_r=cudakernel_fns_g(N,x,z,r,cbhandle,solver_handle); - d_Pd=z_r; - - /* - initial search direction - */ - cudaMemset(delta, 0, sizeof(cuFloatComplex)*4*N); - a0.x=-1.0f; a0.y=0.0f; - cbstatus=cublasCaxpy(cbhandle,4*N, &a0, z, 1, delta, 1); - e_Pd=cudakernel_fns_g(N,x,eta,delta,cbhandle,solver_handle); - - stop_tCG=5; - - /* % begin inner/tCG loop - for j = 1:max_inner, - */ - for(cj=1; cj<=max_inner; cj++) { - cudakernel_fns_fhess(ThreadsPerBlock,BlocksPerGrid,N,M,x,delta,Hxd,y,coh,bbh,iw, cbhandle, solver_handle); - d_Hd=cudakernel_fns_g(N,x,delta,Hxd,cbhandle,solver_handle); - - alpha=z_r/d_Hd; - e_Pe_new = e_Pe + 2.0f*alpha*e_Pd + alpha*alpha*d_Pd; - - - Deltasq=Delta*Delta; - if (d_Hd <= 0.0f || e_Pe_new >= Deltasq) { - tau = (-e_Pd + sqrtf(e_Pd*e_Pd + d_Pd*(Deltasq-e_Pe)))/d_Pd; - a0.x=tau; - cbstatus=cublasCaxpy(cbhandle,4*N, &a0, delta, 1, eta, 1); - /* Heta = Heta + tau *Hdelta */ - cbstatus=cublasCaxpy(cbhandle,4*N, &a0, Hxd, 1, fhess, 1); - stop_tCG=(d_Hd<=0.0f?1:2); - break; - } - - e_Pe=e_Pe_new; - a0.x=alpha; - cbstatus=cublasCaxpy(cbhandle,4*N, &a0, delta, 1, eta, 1); - /* Heta = Heta + alpha*Hdelta */ - cbstatus=cublasCaxpy(cbhandle,4*N, &a0, Hxd, 1, fhess, 1); - - cbstatus=cublasCaxpy(cbhandle,4*N, &a0, Hxd, 1, r, 1); - cudakernel_fns_proj(N, x, r, rnew, cbhandle,solver_handle); - cbstatus=cublasCcopy(cbhandle,4*N,rnew,1,r,1); - r_r=cudakernel_fns_g(N,x,r,r,cbhandle,solver_handle); - norm_r=sqrtf(r_r); - - /* - check kappa/theta stopping criterion - */ - if (cj >= min_inner) { - float norm_r0pow=powf(norm_r0,theta); - if (norm_r <= norm_r0*MIN(norm_r0pow,kappa)) { - stop_tCG=(kappaNbase)*(ntiles); /* note: we do not use the total tile size */ - /* coherency on device */ - float *cohd; - /* baseline-station map on device/host */ - short *bbd; - - /* calculate no of cuda threads and blocks */ - int ThreadsPerBlock=DEFAULT_TH_PER_BK; - int BlocksPerGrid=(M+ThreadsPerBlock-1)/ThreadsPerBlock; - - - /* reshape x to make J: 2Nx2 complex double - */ - complex float *x; - if ((x=(complex float*)malloc((size_t)4*N*sizeof(complex float)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - /* map x: [(re,im)J_1(0,0) (re,im)J_1(0,1) (re,im)J_1(1,0) (re,im)J_1(1,1)...] - to - J: [J_1(0,0) J_1(1,0) J_2(0,0) J_2(1,0) ..... J_1(0,1) J_1(1,1) J_2(0,1) J_2(1,1)....] - */ - float *Jd=(float*)x; - /* re J(0,0) */ - my_fcopy(N, &x0[0], 8, &Jd[0], 4); - /* im J(0,0) */ - my_fcopy(N, &x0[1], 8, &Jd[1], 4); - /* re J(1,0) */ - my_fcopy(N, &x0[4], 8, &Jd[2], 4); - /* im J(1,0) */ - my_fcopy(N, &x0[5], 8, &Jd[3], 4); - /* re J(0,1) */ - my_fcopy(N, &x0[2], 8, &Jd[4*N], 4); - /* im J(0,1) */ - my_fcopy(N, &x0[3], 8, &Jd[4*N+1], 4); - /* re J(1,1) */ - my_fcopy(N, &x0[6], 8, &Jd[4*N+2], 4); - /* im J(1,1) */ - my_fcopy(N, &x0[7], 8, &Jd[4*N+3], 4); - - - int ci; - -/***************************************************/ - cuFloatComplex *xd,*fgradxd,*etad,*Hetad,*x_propd; - float *yd; - /* for counting how many baselines contribute to each station - grad/hess calculation */ - float *iwd,*iw; - if ((iw=(float*)malloc((size_t)N*sizeof(float)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - - cudaMalloc((void**)&fgradxd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&etad, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&Hetad, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&x_propd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&xd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&yd, sizeof(float)*8*M); - cudaMalloc((void**)&cohd, sizeof(float)*8*Nbase); - cudaMalloc((void**)&bbd, sizeof(short)*2*Nbase); - cudaMalloc((void**)&iwd, sizeof(float)*N); - - /* need 8N*(BlocksPerGrid+8) for tcg_solve+grad/hess storage, - so total storage needed is - 8N*(BlocksPerGrid+8) + 8N*5 + 8*M + 8*Nbase + 2*Nbase + N - */ - /* yd <=y : V */ - err=cudaMemcpy(yd, y, 8*M*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - /* need to give right offset for coherencies */ - /* offset: cluster offset+time offset */ - /* C */ - err=cudaMemcpy(cohd, &(dp->ddcohf[(dp->Nbase)*(dp->tilesz)*(dp->clus)*8+(dp->Nbase)*tileoff*8]), Nbase*8*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - /* correct offset for baselines */ - err=cudaMemcpy(bbd, &(dp->ddbase[2*(dp->Nbase)*(tileoff)]), Nbase*2*sizeof(short), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - /* xd <=x : solution */ - err=cudaMemcpy(xd, x, 8*N*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - - float fx,fx0,norm_grad,Delta,fx_prop,rhonum,rhoden,rho; - - /* count how many baselines contribute to each station, store (inverse) in iwd */ - count_baselines(Nbase,N,iw,&(dp->ddbase[2*(dp->Nbase)*(tileoff)]),dp->Nt); - err=cudaMemcpy(iwd, iw, N*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - free(iw); - - fx=cudakernel_fns_f(ThreadsPerBlock,BlocksPerGrid,N,M,xd,yd,cohd,bbd); - fx0=fx; -#ifdef DEBUG -printf("Initial Cost=%g\n",fx0); -#endif -/***************************************************/ - int rsdstat=0; - /* RSD solution */ - for (ci=0; ci locally converge, globally diverge - |\ - |\ | \___ - -|\ | \| - \ - - - right damping: locally and globally converge - -|\ - \|\ - \|\ - \____ - - */ - float rho_reg; - int model_decreased=0; - - /* RTR solution */ - int k=0; - int stop_outer=(itmax_rtr>0?0:1); - int stop_inner=0; - if (!stop_outer) { - cudakernel_fns_fgrad(ThreadsPerBlock,BlocksPerGrid,N,M,xd,fgradxd,yd,cohd,bbd,iwd,1,cbhandle,solver_handle); - norm_grad=sqrtf(cudakernel_fns_g(N,xd,fgradxd,fgradxd,cbhandle,solver_handle)); - } - Delta=Delta0; - /* initial residual */ - info[0]=fx0; - - /* - % ** Start of TR loop ** - */ - while(!stop_outer) { - /* - % update counter - */ - k++; - /* eta = 0*fgradx; */ - cudaMemset(etad, 0, sizeof(cuFloatComplex)*4*N); - - - /* solve TR subproblem, also returns Hessian */ - stop_inner=tcg_solve_cuda(ThreadsPerBlock,BlocksPerGrid, N, M, xd, fgradxd, etad, Hetad, Delta, theta, kappa, max_inner, min_inner,yd,cohd,bbd,iwd,cbhandle, solver_handle); - /* - Heta = fns.fhess(x,eta); - */ - /* - compute the retraction of the proposal - */ - cudakernel_fns_R(N,xd,etad,x_propd,cbhandle,solver_handle); - - /* - compute cost of the proposal - */ - fx_prop=cudakernel_fns_f(ThreadsPerBlock,BlocksPerGrid,N,M,x_propd,yd,cohd,bbd); - - /* - check the performance of the quadratic model - */ - rhonum=fx-fx_prop; - rhoden=-cudakernel_fns_g(N,xd,fgradxd,etad,cbhandle,solver_handle)-0.5f*cudakernel_fns_g(N,xd,Hetad,etad,cbhandle,solver_handle); - /* regularization of rho ratio */ - /* - rho_reg = max(1, abs(fx)) * eps * options.rho_regularization; - rhonum = rhonum + rho_reg; - rhoden = rhoden + rho_reg; - */ - rho_reg=MAX(1.0f,fx)*rho_regularization; /* no epsilon */ - rhonum+=rho_reg; - rhoden+=rho_reg; - - /* - rho = rhonum / rhoden; - */ - rho=rhonum/rhoden; - - /* model_decreased = (rhoden >= 0); */ - /* OLD CODE if (fabsf(rhonum/fx) =0.0f?1:0); - -#ifdef DEBUG - printf("stop_inner=%d rho_reg=%g rho =%g/%g= %g rho'= %g\n",stop_inner,rho_reg,rhonum,rhoden,rho,rho_prime); -#endif - /* - choose new TR radius based on performance - */ - if ( !model_decreased || rhoeta2 && (stop_inner==2 || stop_inner==1)) { - Delta=MIN(alpha2*Delta,Delta_bar); - } - - /* - choose new iterate based on performance - */ - if (model_decreased && rho>rho_prime) { - cbstatus=cublasCcopy(cbhandle,4*N,x_propd,1,xd,1); - fx=fx_prop; - cudakernel_fns_fgrad(ThreadsPerBlock,BlocksPerGrid,N,M,xd,fgradxd,yd,cohd,bbd,iwd,1,cbhandle,solver_handle); - norm_grad=sqrtf(cudakernel_fns_g(N,xd,fgradxd,fgradxd,cbhandle,solver_handle)); - } - - /* - Testing for Stop Criteria - */ - if (norm_gradmin_outer) { - stop_outer=1; - } - - /* - stop after max_outer iterations - */ - if (k>=max_outer) { - stop_outer=1; - } - -#ifdef DEBUG -printf("Iter %d cost=%g\n",k,fx); -#endif - - } - /* final residual */ - info[1]=fx; -#ifdef DEBUG -printf("NEW RTR cost=%g\n",fx); -#endif - -/***************************************************/ - checkCublasError(cbstatus,__FILE__,__LINE__); - cudaDeviceSynchronize(); - - if(fx0>fx) { - //printf("Cost final %g initial %g\n",fx,fx0); - /* copy back current solution */ - err=cudaMemcpy(x,xd,8*N*sizeof(float),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - - - /* copy back solution to x0 : format checked*/ - /* re J(0,0) */ - my_fcopy(N, &Jd[0], 4, &x0[0], 8); - /* im J(0,0) */ - my_fcopy(N, &Jd[1], 4, &x0[1], 8); - /* re J(1,0) */ - my_fcopy(N, &Jd[2], 4, &x0[4], 8); - /* im J(1,0) */ - my_fcopy(N, &Jd[3], 4, &x0[5], 8); - /* re J(0,1) */ - my_fcopy(N, &Jd[4*N], 4, &x0[2], 8); - /* im J(0,1) */ - my_fcopy(N, &Jd[4*N+1], 4, &x0[3], 8); - /* re J(1,1) */ - my_fcopy(N, &Jd[4*N+2], 4, &x0[6], 8); - /* im J(1,1) */ - my_fcopy(N, &Jd[4*N+3], 4, &x0[7], 8); - - } - free(x); - - cudaFree(fgradxd); - cudaFree(etad); - cudaFree(Hetad); - cudaFree(x_propd); - cudaFree(xd); - cudaFree(yd); - cudaFree(cohd); - cudaFree(bbd); - cudaFree(iwd); - - - return 0; -} diff --git a/src/lib/rtr_solve_robust.c b/src/lib/rtr_solve_robust.c deleted file mode 100644 index 875850c..0000000 --- a/src/lib/rtr_solve_robust.c +++ /dev/null @@ -1,2246 +0,0 @@ -/* - * - Copyright (C) 2014 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#include "sagecal.h" - -//#define DEBUG -/* Jones matrix multiplication - C=A*B -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -amb(complex double * __restrict a, complex double * __restrict b, complex double * __restrict c) { - c[0]=a[0]*b[0]+a[1]*b[2]; - c[1]=a[0]*b[1]+a[1]*b[3]; - c[2]=a[2]*b[0]+a[3]*b[2]; - c[3]=a[2]*b[1]+a[3]*b[3]; -} - -/* Jones matrix multiplication - C=A*B^H -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -ambt(complex double * __restrict a, complex double * __restrict b, complex double * __restrict c) { - c[0]=a[0]*conj(b[0])+a[1]*conj(b[1]); - c[1]=a[0]*conj(b[2])+a[1]*conj(b[3]); - c[2]=a[2]*conj(b[0])+a[3]*conj(b[1]); - c[3]=a[2]*conj(b[2])+a[3]*conj(b[3]); -} - -/* Jones matrix multiplication - C=A^H*B -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -atmb(complex double * __restrict a, complex double * __restrict b, complex double * __restrict c) { - c[0]=conj(a[0])*b[0]+conj(a[2])*b[2]; - c[1]=conj(a[0])*b[1]+conj(a[2])*b[3]; - c[2]=conj(a[1])*b[0]+conj(a[3])*b[2]; - c[3]=conj(a[1])*b[1]+conj(a[3])*b[3]; -} - - -/* worker thread function for counting */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -threadfn_fns_fcount(void *data) { - thread_data_rtr_t *t=(thread_data_rtr_t*)data; - - int ci,sta1,sta2; - for (ci=0; ciNb; ci++) { - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - - pthread_mutex_lock(&t->mx_array[sta1]); - t->bcount[sta1]+=1; - pthread_mutex_unlock(&t->mx_array[sta1]); - - pthread_mutex_lock(&t->mx_array[sta2]); - t->bcount[sta2]+=1; - pthread_mutex_unlock(&t->mx_array[sta2]); - } - } - - return NULL; -} - - -/* function to count how many baselines contribute to the calculation of - grad and hess, so normalization can be made */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -fns_fcount(global_data_rtr_t *gdata) { - - me_data_t *dp=(me_data_t*)gdata->medata; - - int nth,nth1,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthb0,Nthb; - thread_data_rtr_t *threaddata; - - int Nbase1=(dp->Nbase)*(dp->tilesz); - int boff=(dp->Nbase)*(dp->tileoff); - - /* calculate min baselines a thread can handle */ - Nthb0=(Nbase1+Nt-1)/Nt; - - if ((threaddata=(thread_data_rtr_t*)malloc((size_t)Nt*sizeof(thread_data_rtr_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - int *bcount; - if ((bcount=(int*)calloc((size_t)dp->N,sizeof(int)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr; - threaddata[nth].bcount=bcount; /* note this should be 0 first */ - threaddata[nth].mx_array=gdata->mx_array; - - pthread_create(&gdata->th_array[nth],&gdata->attr,threadfn_fns_fcount,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - /* now wait for threads to finish */ - for(nth1=0; nth1th_array[nth1],NULL); - } - free(threaddata); - - /* calculate inverse count */ - for (nth1=0; nth1N; nth1++) { - gdata->iw[nth1]=(bcount[nth1]>0?1.0/(double)bcount[nth1]:0.0); - } - free(bcount); - /* scale back weight such that max value is 1 */ - nth1=my_idamax(dp->N,gdata->iw,1); - double maxw=gdata->iw[nth1-1]; /* 1 based index */ - if (maxw>0.0) { /* all baselines can be flagged */ - my_dscal(dp->N,1.0/maxw,gdata->iw); - } -} - - - -/* worker thread function for cost function calculation */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -threadfn_fns_f(void *data) { - thread_data_rtr_t *t=(thread_data_rtr_t*)data; - - int ci,cm,sta1,sta2; - complex double C[4],G1[4],G2[4],T1[4],T2[4]; - int M=(t->M); - cm=(t->clus); - for (ci=0; ciNb; ci++) { - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - /* gains for this cluster, for sta1,sta2 */ - G1[0]=(t->x[sta1*2]); - G1[1]=(t->x[sta1*2+2*t->N]); - G1[2]=(t->x[sta1*2+1]); - G1[3]=(t->x[sta1*2+2*t->N+1]); - G2[0]=(t->x[sta2*2]); - G2[1]=(t->x[sta2*2+2*t->N]); - G2[2]=(t->x[sta2*2+1]); - G2[3]=(t->x[sta2*2+2*t->N+1]); - - /* use pre calculated values */ - C[0]=t->coh[4*M*ci+4*cm]; - C[1]=t->coh[4*M*ci+4*cm+1]; - C[2]=t->coh[4*M*ci+4*cm+2]; - C[3]=t->coh[4*M*ci+4*cm+3]; - - - /* form G1*C*G2' */ - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - - /* add to baseline visibilities V->U */ - double r00=t->y[8*ci]-creal(T2[0]); - double i00=t->y[8*ci+1]-cimag(T2[0]); - double r01=t->y[8*ci+2]-creal(T2[1]); - double i01=t->y[8*ci+3]-cimag(T2[1]); - double r10=t->y[8*ci+4]-creal(T2[2]); - double i10=t->y[8*ci+5]-cimag(T2[2]); - double r11=t->y[8*ci+6]-creal(T2[3]); - double i11=t->y[8*ci+7]-cimag(T2[3]); - - t->fcost+=t->wtd[ci]*(r00*r00+i00*i00+r01*r01+i01*i01+r10*r10+i10*i10+r11*r11+i11*i11); - } - } - - return NULL; -} - - -/* cost function */ -/* x: 2Nx2 solution - y: visibilities, vectorized V(:) 8*Nbase x 1 -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static double -fns_f(complex double *x, double *y, global_data_rtr_t *gdata) { - - me_data_t *dp=(me_data_t*)gdata->medata; - - int nth,nth1,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthb0,Nthb; - thread_data_rtr_t *threaddata; - - int Nbase1=(dp->Nbase)*(dp->tilesz); - int boff=(dp->Nbase)*(dp->tileoff); - - /* calculate min baselines a thread can handle */ - Nthb0=(Nbase1+Nt-1)/Nt; - - if ((threaddata=(thread_data_rtr_t*)malloc((size_t)Nt*sizeof(thread_data_rtr_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr; - threaddata[nth].carr=dp->carr; - threaddata[nth].M=dp->M; - threaddata[nth].y=&(y[8*ci]); - threaddata[nth].N=dp->N; - threaddata[nth].x=x; /* note the difference: here x assumes no hybrid, also ordering different */ - threaddata[nth].clus=(dp->clus); - threaddata[nth].coh=&(dp->coh[4*(dp->M)*(ci+boff)]); - threaddata[nth].fcost=0.0; - - threaddata[nth].wtd=&(gdata->wtd[ci]); /* weights for baselines */ - - //printf("thread %d predict data from %d baselines %d\n",nth,8*ci,Nthb); - pthread_create(&gdata->th_array[nth],&gdata->attr,threadfn_fns_f,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - /* now wait for threads to finish */ - double fcost=0.0; - for(nth1=0; nth1th_array[nth1],NULL); - fcost+=threaddata[nth1].fcost; - } - - free(threaddata); - - return fcost; -} - - -/* worker thread function for weight update (nu+8)/(nu+error^2) */ -/* update: error: min of XX,XY,YX,YY errors, so p=2 */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -threadfn_fns_fupdate_weights(void *data) { - thread_data_rtr_t *t=(thread_data_rtr_t*)data; - - int ci,cm,sta1,sta2; - complex double C[4],G1[4],G2[4],T1[4],T2[4]; - int M=(t->M); - cm=(t->clus); - for (ci=0; ciNb; ci++) { - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - /* gains for this cluster, for sta1,sta2 */ - G1[0]=(t->x[sta1*2]); - G1[1]=(t->x[sta1*2+2*t->N]); - G1[2]=(t->x[sta1*2+1]); - G1[3]=(t->x[sta1*2+2*t->N+1]); - G2[0]=(t->x[sta2*2]); - G2[1]=(t->x[sta2*2+2*t->N]); - G2[2]=(t->x[sta2*2+1]); - G2[3]=(t->x[sta2*2+2*t->N+1]); - - /* use pre calculated values */ - C[0]=t->coh[4*M*ci+4*cm]; - C[1]=t->coh[4*M*ci+4*cm+1]; - C[2]=t->coh[4*M*ci+4*cm+2]; - C[3]=t->coh[4*M*ci+4*cm+3]; - - - /* form G1*C*G2' */ - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - - /* add to baseline visibilities V->U */ - double r00=t->y[8*ci]-creal(T2[0]); - double i00=t->y[8*ci+1]-cimag(T2[0]); - double r01=t->y[8*ci+2]-creal(T2[1]); - double i01=t->y[8*ci+3]-cimag(T2[1]); - double r10=t->y[8*ci+4]-creal(T2[2]); - double i10=t->y[8*ci+5]-cimag(T2[2]); - double r11=t->y[8*ci+6]-creal(T2[3]); - double i11=t->y[8*ci+7]-cimag(T2[3]); - - //t->wtd[ci] = (t->nu0+8.0)/(t->nu0+(r00*r00+i00*i00+r01*r01+i01*i01+r10*r10+i10*i10+r11*r11+i11*i11)); - t->wtd[ci] = (t->nu0+2.0)/(t->nu0+MAX(r00*r00+i00*i00,MAX(r01*r01+i01*i01,MAX(r10*r10+i10*i10,r11*r11+i11*i11)))); - } - } - - return NULL; -} - - -/* calculate log(w_i) - w_i */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -threadfn_fns_flogsum_weights(void *data) { - thread_data_rtr_t *t=(thread_data_rtr_t*)data; - - int ci; - for (ci=0; ciNb; ci++) { - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - t->fcost+=log(t->wtd[ci])-t->wtd[ci]; - } - } - - return NULL; -} - - - -/* calculate weight w = (nu+1)/(nu+error^2) per baseline - then update robust_nu */ -/* x: 2Nx2 solution - y: visibilities, vectorized V(:) 8*Nbase x 1 - returns updated value for robust_nu -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static double -fns_fupdate_weights(complex double *x, double *y, global_data_rtr_t *gdata) { - - me_data_t *dp=(me_data_t*)gdata->medata; - - int nth,nth1,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthb0,Nthb; - thread_data_rtr_t *threaddata; - - int Nbase1=(dp->Nbase)*(dp->tilesz); - int boff=(dp->Nbase)*(dp->tileoff); - - /* calculate min baselines a thread can handle */ - Nthb0=(Nbase1+Nt-1)/Nt; - - if ((threaddata=(thread_data_rtr_t*)malloc((size_t)Nt*sizeof(thread_data_rtr_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr; - threaddata[nth].carr=dp->carr; - threaddata[nth].M=dp->M; - threaddata[nth].y=&(y[8*ci]); - threaddata[nth].N=dp->N; - threaddata[nth].x=x; /* note the difference: here x assumes no hybrid, also ordering different */ - threaddata[nth].clus=(dp->clus); - threaddata[nth].coh=&(dp->coh[4*(dp->M)*(ci+boff)]); - - threaddata[nth].wtd=&(gdata->wtd[ci]); /* weights for baselines */ - threaddata[nth].nu0=dp->robust_nu; - - //printf("thread %d predict data from %d baselines %d\n",nth,8*ci,Nthb); - pthread_create(&gdata->th_array[nth],&gdata->attr,threadfn_fns_fupdate_weights,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - for(nth1=0; nth1th_array[nth1],NULL); - } - - /* now calculate sum( ln(w_i)-w_i ) */ - ci=0; - for (nth1=0; nth1th_array[nth1],&gdata->attr,threadfn_fns_flogsum_weights,(void*)(&threaddata[nth1])); - /* next baseline set */ - } - - double sumlogw=0.0; - for(nth1=0; nth1th_array[nth1],NULL); - } - sumlogw/=(double)Nbase1; - free(threaddata); - - /* find new value for nu, p-variate T dist, p=8 (update p=2 because using MAX() for residual calculation, not sum) */ - /* psi((nu_old+p)/2)-ln((nu_old+p)/2)-psi(nu/2)+ln(nu/2)+1/N sum(ln(w_i)-w_i) +1 = 0, AECM */ - double nu1=update_nu(sumlogw, 30, Nt, gdata->nulow, gdata->nuhigh, 2, dp->robust_nu); - - /* make sure new value stays within bounds */ - if (nu1nulow) { return gdata->nulow; } - if (nu1>gdata->nuhigh) { return gdata->nuhigh; } - return nu1; -} - - - -/* inner product (metric) */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static double -fns_g(int N,complex double *x, complex double *eta, complex double *gamma) { - /* 2 x real( trace(eta'*gamma) ) - = 2 x real( eta(:,1)'*gamma(:,1) + eta(:,2)'*gamma(:,2) ) - no need to calculate off diagonal terms - )*/ - complex double v1=my_cdot(2*N,eta,gamma); - complex double v2=my_cdot(2*N,&eta[2*N],&gamma[2*N]); - - return 2.0*creal(v1+v2); -} - -/* Projection - rnew: new value */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -fns_proj(int N,complex double *x, complex double *z,complex double *rnew) { - /* projection = Z-X Om, where - Om X^H X+X^H X Om = X^H Z - Z^H X - is solved to find Om */ - - /* find X^H X */ - complex double xx00,xx01,xx10,xx11; - xx00=my_cdot(2*N,x,x); - xx01=my_cdot(2*N,x,&x[2*N]); - xx10=conj(xx01); - xx11=my_cdot(2*N,&x[2*N],&x[2*N]); - - /* find X^H Z (and using this just calculte Z^H X directly) */ - complex double xz00,xz01,xz10,xz11; - xz00=my_cdot(2*N,x,z); - xz01=my_cdot(2*N,x,&z[2*N]); - xz10=my_cdot(2*N,&x[2*N],z); - xz11=my_cdot(2*N,&x[2*N],&z[2*N]); - - /* find X^H Z - Z^H X */ - complex double rr00,rr01,rr10,rr11; - rr00=xz00-conj(xz00); - rr01=xz01-conj(xz10); - rr10=-conj(rr01); - rr11=xz11-conj(xz11); - - /* find I_2 kron (X^H X) + (X^H X)^T kron I_2 */ - /* A = [2*xx00 xx01 xx10 0 - xx10 xx11+xx00 0 xx10 - xx01 0 xx11+xx00 xx01 - 0 xx01 xx10 2*xx11 ] - */ - complex double A[16]; - A[0]=2.0*xx00; - A[5]=A[10]=xx11+xx00; - A[15]=2.0*xx11; - A[1]=A[8]=A[11]=A[13]=xx10; - A[2]=A[4]=A[7]=A[14]=xx01; - A[3]=A[6]=A[9]=A[12]=0.0; - complex double b[4]; - b[0]=rr00; - b[1]=rr10; - b[2]=rr01; - b[3]=rr11; - - /* solve A u = b to find u */ - complex double w,*WORK; - /* workspace query */ - int status=my_zgels('N',4,4,1,A,4,b,4,&w,-1); - int lwork=(int)w; - if ((WORK=(complex double*)calloc((size_t)lwork,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - status=my_zgels('N',4,4,1,A,4,b,4,WORK,lwork); - if (status) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: LAPACK error %d\n",__FILE__,__LINE__,status); -#endif - } - - free(WORK); - /* form Z - X * Om, where Om is given by solution b - but no need to rearrange b because it is already in col major order */ - my_ccopy(4*N,z,1,rnew,1); - my_zgemm('N','N',2*N,2,2,-1.0+0.0*_Complex_I,x,2*N,b,2,1.0+0.0*_Complex_I,rnew,2*N); - - -} - - -/* Retraction - rnew: new value */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -fns_R(int N,complex double *x, complex double *r,complex double *rnew) { - /* rnew = x + r */ - my_ccopy(4*N,x,1,rnew,1); - my_caxpy(4*N,r,1.0+_Complex_I*0.0,rnew); -} - - -/* worker thread function for gradient/hessian weighting */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -threadfn_fns_fscale(void *data) { - thread_data_rtr_t *t=(thread_data_rtr_t*)data; - - int ci,sta1; - for (ci=0; ciNb; ci++) { - sta1=ci+t->boff; - t->grad[2*sta1]*=t->iw[sta1]; - t->grad[2*sta1+2*t->N]*=t->iw[sta1]; - t->grad[2*sta1+1]*=t->iw[sta1]; - t->grad[2*sta1+2*t->N+1]*=t->iw[sta1]; - } - - return NULL; -} - - - - -/* worker thread function for gradient calculation */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -threadfn_fns_fgrad(void *data) { - thread_data_rtr_t *t=(thread_data_rtr_t*)data; - - int ci,cm,sta1,sta2; - complex double C[4],G1[4],G2[4],T1[4],T2[4],res[4]; - int M=(t->M); - cm=(t->clus); - for (ci=0; ciNb; ci++) { - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - /* gains for this cluster, for sta1,sta2 */ - G1[0]=(t->x[sta1*2]); - G1[1]=(t->x[sta1*2+2*t->N]); - G1[2]=(t->x[sta1*2+1]); - G1[3]=(t->x[sta1*2+2*t->N+1]); - G2[0]=(t->x[sta2*2]); - G2[1]=(t->x[sta2*2+2*t->N]); - G2[2]=(t->x[sta2*2+1]); - G2[3]=(t->x[sta2*2+2*t->N+1]); - - /* use pre calculated values */ - C[0]=t->coh[4*M*ci+4*cm]; - C[1]=t->coh[4*M*ci+4*cm+1]; - C[2]=t->coh[4*M*ci+4*cm+2]; - C[3]=t->coh[4*M*ci+4*cm+3]; - - - /* G1*C*G2' */ - amb(G1,C,T1); - ambt(T1,G2,T2); - - /* res=V(2*ck-1:2*ck,:)-x(2*p-1:2*p,:)*C*x(2*q-1:2*q,:)'; */ - /* V->U */ - res[0]=(t->y[8*ci]+_Complex_I*t->y[8*ci+1])-T2[0]; - res[1]=(t->y[8*ci+2]+_Complex_I*t->y[8*ci+3])-T2[1]; - res[2]=(t->y[8*ci+4]+_Complex_I*t->y[8*ci+5])-T2[2]; - res[3]=(t->y[8*ci+6]+_Complex_I*t->y[8*ci+7])-T2[3]; - - /* - grad(2*p-1:2*p,:)=grad(2*p-1:2*p,:)+res*x(2*q-1:2*q,:)*C'; - grad(2*q-1:2*q,:)=grad(2*q-1:2*q,:)+res'*x(2*p-1:2*p,:)*C; - */ - /* res*G2*C' */ - amb(res,G2,T1); - ambt(T1,C,T2); - - /* multiply by baseline weight */ - T2[0]=T2[0]*t->wtd[ci]; - T2[1]=T2[1]*t->wtd[ci]; - T2[2]=T2[2]*t->wtd[ci]; - T2[3]=T2[3]*t->wtd[ci]; - - pthread_mutex_lock(&t->mx_array[sta1]); - t->grad[2*sta1]+=T2[0]; - t->grad[2*sta1+2*t->N]+=T2[1]; - t->grad[2*sta1+1]+=T2[2]; - t->grad[2*sta1+2*t->N+1]+=T2[3]; - pthread_mutex_unlock(&t->mx_array[sta1]); - - /* res'*G1*C */ - atmb(res,G1,T1); - amb(T1,C,T2); - - /* multiply by baseline weight */ - T2[0]=T2[0]*t->wtd[ci]; - T2[1]=T2[1]*t->wtd[ci]; - T2[2]=T2[2]*t->wtd[ci]; - T2[3]=T2[3]*t->wtd[ci]; - - pthread_mutex_lock(&t->mx_array[sta2]); - t->grad[2*sta2]+=T2[0]; - t->grad[2*sta2+2*t->N]+=T2[1]; - t->grad[2*sta2+1]+=T2[2]; - t->grad[2*sta2+2*t->N+1]+=T2[3]; - pthread_mutex_unlock(&t->mx_array[sta2]); - - } - } - - return NULL; -} - - - -/* gradient function */ -/* x: 2Nx2 solution - fgradx: output, same shape as x - y: visibilities, vectorized V(:) 8*Nbase x 1 - if negate==1, return -grad, else just grad -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -fns_fgrad(complex double *x, complex double *fgradx, double *y, global_data_rtr_t *gdata, int negate) { - - me_data_t *dp=(me_data_t*)gdata->medata; - - int nth,nth1,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthb0,Nthb; - thread_data_rtr_t *threaddata; - - int Nbase1=(dp->Nbase)*(dp->tilesz); - int boff=(dp->Nbase)*(dp->tileoff); - - /* calculate min baselines a thread can handle */ - Nthb0=(Nbase1+Nt-1)/Nt; - - if ((threaddata=(thread_data_rtr_t*)malloc((size_t)Nt*sizeof(thread_data_rtr_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - complex double *grad; - if ((grad=(complex double*)calloc((size_t)4*dp->N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr; - threaddata[nth].carr=dp->carr; - threaddata[nth].M=dp->M; - threaddata[nth].y=&(y[8*ci]); - threaddata[nth].N=dp->N; - threaddata[nth].x=x; /* note the difference: here x assumes no hybrid, also ordering different */ - threaddata[nth].clus=(dp->clus); - threaddata[nth].coh=&(dp->coh[4*(dp->M)*(ci+boff)]); - threaddata[nth].grad=grad; - threaddata[nth].mx_array=gdata->mx_array; - threaddata[nth].wtd=&(gdata->wtd[ci]); /* weights for baselines */ - - //printf("thread %d predict data from %d baselines %d\n",nth,8*ci,Nthb); - pthread_create(&gdata->th_array[nth],&gdata->attr,threadfn_fns_fgrad,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - /* now wait for threads to finish */ - for(nth1=0; nth1th_array[nth1],NULL); - } -/******************* scale *************/ - Nthb0=(dp->N+Nt-1)/Nt; - ci=0; - for (nth=0; nthN; nth++) { - if (ci+Nthb0N) { - Nthb=Nthb0; - } else { - Nthb=dp->N-ci; - } - threaddata[nth].boff=ci; - threaddata[nth].Nb=Nthb; - threaddata[nth].N=dp->N; - threaddata[nth].grad=grad; - threaddata[nth].iw=gdata->iw; - pthread_create(&gdata->th_array[nth],&gdata->attr,threadfn_fns_fscale,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - for(nth1=0; nth1th_array[nth1],NULL); - } -/******************* scale *************/ - free(threaddata); - if (negate) { - my_cscal(4*dp->N,-1.0+0.0*_Complex_I,grad); - } - fns_proj(dp->N,x,grad,fgradx); - free(grad); - -} - - -/* worker thread function for Hessian calculation */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -threadfn_fns_fhess(void *data) { - thread_data_rtr_t *t=(thread_data_rtr_t*)data; - - int ci,cm,sta1,sta2; - complex double C[4],G1[4],G2[4],T1[4],T2[4],res[4],res1[4],E1[4],E2[4]; - int M=(t->M); - cm=(t->clus); - for (ci=0; ciNb; ci++) { - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - /* gains for this cluster, for sta1,sta2 */ - G1[0]=(t->x[sta1*2]); - G1[1]=(t->x[sta1*2+2*t->N]); - G1[2]=(t->x[sta1*2+1]); - G1[3]=(t->x[sta1*2+2*t->N+1]); - G2[0]=(t->x[sta2*2]); - G2[1]=(t->x[sta2*2+2*t->N]); - G2[2]=(t->x[sta2*2+1]); - G2[3]=(t->x[sta2*2+2*t->N+1]); - E1[0]=t->eta[2*sta1]; - E1[1]=t->eta[2*sta1+2*t->N]; - E1[2]=t->eta[2*sta1+1]; - E1[3]=t->eta[2*sta1+2*t->N+1]; - E2[0]=t->eta[2*sta2]; - E2[1]=t->eta[2*sta2+2*t->N]; - E2[2]=t->eta[2*sta2+1]; - E2[3]=t->eta[2*sta2+2*t->N+1]; - - - - /* use pre calculated values */ - C[0]=t->coh[4*M*ci+4*cm]; - C[1]=t->coh[4*M*ci+4*cm+1]; - C[2]=t->coh[4*M*ci+4*cm+2]; - C[3]=t->coh[4*M*ci+4*cm+3]; - - /* G1*C*G2' */ - amb(G1,C,T1); - ambt(T1,G2,T2); - - - /* res=V(2*ck-1:2*ck,:)-x(2*p-1:2*p,:)*C*x(2*q-1:2*q,:)'; */ - /* V->U */ - res[0]=(t->y[8*ci]+_Complex_I*t->y[8*ci+1])-T2[0]; - res[1]=(t->y[8*ci+2]+_Complex_I*t->y[8*ci+3])-T2[1]; - res[2]=(t->y[8*ci+4]+_Complex_I*t->y[8*ci+5])-T2[2]; - res[3]=(t->y[8*ci+6]+_Complex_I*t->y[8*ci+7])-T2[3]; - - - /* - res1=x(2*p-1:2*p,:)*C*eta(2*q-1:2*q,:)'+eta(2*p-1:2*p,:)*C*x(2*q-1:2*q,:)'; - */ - /* G1*C*E2' */ - amb(G1,C,T1); - ambt(T1,E2,T2); - res1[0]=T2[0]; - res1[1]=T2[1]; - res1[2]=T2[2]; - res1[3]=T2[3]; - /* E1*C*G2' */ - amb(E1,C,T1); - ambt(T1,G2,T2); - res1[0]+=T2[0]; - res1[1]+=T2[1]; - res1[2]+=T2[2]; - res1[3]+=T2[3]; - - - /* - hess(2*p-1:2*p,:)=hess(2*p-1:2*p,:)+(res*eta(2*q-1:2*q,:)-res1*x(2*q-1:2*q,:))*C'; - */ - - /* (res*E2-res1*G2)*C' */ - amb(res,E2,T1); - amb(res1,G2,T2); - T1[0]-=T2[0]; - T1[1]-=T2[1]; - T1[2]-=T2[2]; - T1[3]-=T2[3]; - ambt(T1,C,T2); - - /* multiply by baseline weight */ - T2[0]=T2[0]*t->wtd[ci]; - T2[1]=T2[1]*t->wtd[ci]; - T2[2]=T2[2]*t->wtd[ci]; - T2[3]=T2[3]*t->wtd[ci]; - - pthread_mutex_lock(&t->mx_array[sta1]); - t->hess[2*sta1]+=T2[0]; - t->hess[2*sta1+2*t->N]+=T2[1]; - t->hess[2*sta1+1]+=T2[2]; - t->hess[2*sta1+2*t->N+1]+=T2[3]; - pthread_mutex_unlock(&t->mx_array[sta1]); - - - /* - hess(2*q-1:2*q,:)=hess(2*q-1:2*q,:)+(res'*eta(2*p-1:2*p,:)-res1'*x(2*p-1:2*p,:))*C; - */ - - /* (res'*E1-res1'*G1)*C */ - atmb(res,E1,T1); - atmb(res1,G1,T2); - T1[0]-=T2[0]; - T1[1]-=T2[1]; - T1[2]-=T2[2]; - T1[3]-=T2[3]; - amb(T1,C,T2); - - /* multiply by baseline weight */ - T2[0]=T2[0]*t->wtd[ci]; - T2[1]=T2[1]*t->wtd[ci]; - T2[2]=T2[2]*t->wtd[ci]; - T2[3]=T2[3]*t->wtd[ci]; - - pthread_mutex_lock(&t->mx_array[sta2]); - t->hess[2*sta2]+=T2[0]; - t->hess[2*sta2+2*t->N]+=T2[1]; - t->hess[2*sta2+1]+=T2[2]; - t->hess[2*sta2+2*t->N+1]+=T2[3]; - pthread_mutex_unlock(&t->mx_array[sta2]); - - } - } - - return NULL; -} - - - -/* Hessian function */ -/* x: 2Nx2 solution - eta: same shape as x - fhess: output, same shape as x - y: visibilities, vectorized V(:) 8*Nbase x 1 -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -fns_fhess(complex double *x, complex double *eta,complex double *fhess, double *y, global_data_rtr_t *gdata) { - - me_data_t *dp=(me_data_t*)gdata->medata; - - int nth,nth1,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthb0,Nthb; - thread_data_rtr_t *threaddata; - - int Nbase1=(dp->Nbase)*(dp->tilesz); - int boff=(dp->Nbase)*(dp->tileoff); - - /* calculate min baselines a thread can handle */ - Nthb0=(Nbase1+Nt-1)/Nt; - - if ((threaddata=(thread_data_rtr_t*)malloc((size_t)Nt*sizeof(thread_data_rtr_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - complex double *hess; - if ((hess=(complex double*)calloc((size_t)4*dp->N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr; - threaddata[nth].carr=dp->carr; - threaddata[nth].M=dp->M; - threaddata[nth].y=&(y[8*ci]); - threaddata[nth].N=dp->N; - threaddata[nth].x=x; /* note the difference: here x assumes no hybrid, also ordering different */ - threaddata[nth].clus=(dp->clus); - threaddata[nth].coh=&(dp->coh[4*(dp->M)*(ci+boff)]); - threaddata[nth].eta=eta; - threaddata[nth].hess=hess; - threaddata[nth].mx_array=gdata->mx_array; - threaddata[nth].wtd=&(gdata->wtd[ci]); /* weights for baselines */ - - //printf("thread %d predict data from %d baselines %d\n",nth,8*ci,Nthb); - pthread_create(&gdata->th_array[nth],&gdata->attr,threadfn_fns_fhess,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - /* now wait for threads to finish */ - for(nth1=0; nth1th_array[nth1],NULL); - } - -/******************* scale *************/ - Nthb0=(dp->N+Nt-1)/Nt; - ci=0; - for (nth=0; nthN; nth++) { - if (ci+Nthb0N) { - Nthb=Nthb0; - } else { - Nthb=dp->N-ci; - } - threaddata[nth].boff=ci; - threaddata[nth].Nb=Nthb; - threaddata[nth].N=dp->N; - threaddata[nth].grad=fhess; - threaddata[nth].iw=gdata->iw; - pthread_create(&gdata->th_array[nth],&gdata->attr,threadfn_fns_fscale,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - for(nth1=0; nth1th_array[nth1],NULL); - } -/******************* scale *************/ - free(threaddata); - fns_proj(dp->N,x,hess,fhess); - free(hess); - -} - - -/* truncated conjugate gradient method - x, grad, eta, r, z, delta, Hxd : size 2N x 2 complex - so, vector size is 4N complex double - - output: eta - output: fhess (can be reused in calling func) - return value: stop_tCG code - - y: vec(V) visibilities -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static int -tcg_solve(int N, complex double *x, complex double *grad, complex double *eta, complex double *fhess, - double Delta, double theta, double kappa, int max_inner, int min_inner, double *y, global_data_rtr_t *gdata) { - - complex double *r,*z,*delta,*Hxd, *rnew; - double e_Pe, r_r, norm_r, z_r, d_Pd, d_Hd, alpha, e_Pe_new, - e_Pd, Deltasq, tau, zold_rold, beta, norm_r0; - int cj, stop_tCG; - - if ((r=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((z=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((delta=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((Hxd=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((rnew=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - /* - initial values - % eta = 0*grad; << zero matrix provided - r = grad; - e_Pe = 0; - */ - my_ccopy(4*N,grad,1,r,1); - e_Pe=0.0; - - /* - r_r = fns.g(x,r,r); - norm_r = sqrt(r_r); - norm_r0 = norm_r; - */ - - r_r=fns_g(N,x,r,r); - norm_r=sqrt(r_r); - norm_r0=norm_r; - - /* - z = r; - */ - my_ccopy(4*N,r,1,z,1); - - /* - % compute z'*r - z_r = fns.g(x,z,r); - d_Pd = z_r; - */ - z_r=fns_g(N,x,z,r); - d_Pd=z_r; - - /* - % initial search direction - delta = -z; - e_Pd = fns.g(x,eta,delta); - */ - memset(delta,0,sizeof(complex double)*N*4); - my_caxpy(4*N,z,-1.0+_Complex_I*0.0,delta); - e_Pd=fns_g(N,x,eta,delta); - - /* - % pre-assume termination b/c j == end - stop_tCG = 5; - */ - stop_tCG=5; - - /* % begin inner/tCG loop - for j = 1:max_inner, - */ - for(cj=1; cj<=max_inner; cj++) { - /**************************************************/ - /* - Hxd = fns.fhess(x,delta); - - % compute curvature - d_Hd = fns.g(x,delta,Hxd); - */ - fns_fhess(x,delta,Hxd,y,gdata); - d_Hd=fns_g(N,x,delta,Hxd); - - /* - alpha = z_r/d_Hd; - e_Pe_new = e_Pe + 2.0*alpha*e_Pd + alpha*alpha*d_Pd; - */ - alpha=z_r/d_Hd; - e_Pe_new = e_Pe + 2.0*alpha*e_Pd + alpha*alpha*d_Pd; - - /* - - % check curvature and trust-region radius - if d_Hd <= 0 || e_Pe_new >= Delta^2, - - */ - Deltasq=Delta*Delta; - if (d_Hd <= 0.0 || e_Pe_new >= Deltasq) { - /* - tau = (-e_Pd + sqrt(e_Pd*e_Pd + d_Pd*(Delta^2-e_Pe))) / d_Pd; - - */ - tau = (-e_Pd + sqrt(e_Pd*e_Pd + d_Pd*(Deltasq-e_Pe)))/d_Pd; - - /* - eta = eta + tau*delta; - - */ - my_caxpy(4*N,delta,tau+_Complex_I*0.0,eta); - - /* NEW Heta = Heta + tau*Hdelta */ - my_caxpy(4*N,fhess,tau+_Complex_I*0.0,Hxd); - - /* - if d_Hd <= 0, - stop_tCG = 1; % negative curvature - else - stop_tCG = 2; % exceeded trust region - end - */ - stop_tCG=(d_Hd<=0.0?1:2); - - /* - break (for) - */ - break; - /* - end if - */ - } - - - /* - % no negative curvature and eta_prop inside TR: accept it - e_Pe = e_Pe_new; - eta = eta + alpha*delta; - - */ - e_Pe=e_Pe_new; - my_caxpy(4*N,delta,alpha+_Complex_I*0.0,eta); - - /* NEW Heta = Heta + alpha*Hdelta */ - my_caxpy(4*N,fhess,alpha+_Complex_I*0.0,Hxd); - - - /* - % update the residual - r = r + alpha*Hxd; - - */ - my_caxpy(4*N,Hxd,alpha+_Complex_I*0.0,r); - - /* - % compute new norm of r - r_r = fns.g(x,r,r); - norm_r = sqrt(r_r); - - */ - r_r=fns_g(N,x,r,r); - norm_r=sqrt(r_r); - - - /* - % check kappa/theta stopping criterion - if j >= min_inner && norm_r <= norm_r0*min(norm_r0^theta,kappa) - */ - if (cj >= min_inner) { - double norm_r0pow=pow(norm_r0,theta); - if (norm_r <= norm_r0*MIN(norm_r0pow,kappa)) { - - /* - % residual is small enough to quit - if kappa < norm_r0^theta, - stop_tCG = 3; % linear convergence - else - stop_tCG = 4; % superlinear convergence - end - - */ - stop_tCG=(kappamedata; - - double fx=fns_f(x,y,gdata); - fns_fgrad(x,eta,y,gdata,0); - double beta0=beta; - double minfx=fx; double minbeta=beta0; - double lhs,rhs,metric; - int m,nocostred=0; - double metric0=fns_g(dp->N,x,eta,eta); - *mincost=minfx; - for (m=0; m<50; m++) { - /* abeta=(beta0)*alphabar*eta; */ - my_ccopy(4*dp->N,eta,1,teta,1); - my_cscal(4*dp->N,beta0*alphabar+0.0*_Complex_I,teta); - /* Rx=R(x,teta); */ - fns_R(dp->N,x,teta,x_prop); - lhs=fns_f(x_prop,y,gdata); - if (lhsN,x,eta,teta); - rhs=fx+sigma*metric; - /* break loop also if no further cost improvement is seen */ - if (lhs<=rhs) { - minbeta=beta0; - break; - } - beta0=beta0*beta; - } - - /* if no further cost improvement is seen */ - if (lhs>fx) { - nocostred=1; - } - - my_ccopy(4*dp->N,eta,1,teta,1); - my_cscal(4*dp->N,minbeta*alphabar+0.0*_Complex_I,teta); - - return nocostred; -} - - -/* Fine tune initial trust region radius, also update initial value for x - A. Sartenaer, 1995 - returns : trust region estimate, - also modifies x - eta,Heta,s,x_prop: used as storage - */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static double -itrr(int N,complex double *x,complex double *eta, complex double *Heta, double *y, global_data_rtr_t *gdata, complex double *s, complex double *x_prop) { - - double f0,fk,mk,rho,rho1,Delta0; - - /* initialize trust region radii */ - double delta_0=1.0; - double delta_m=0.0; - - double sigma=0.0; - double delta=0.0; - - // initial cost - f0=fns_f(x,y,gdata); - // gradient at x0 - fns_fgrad(x,eta,y,gdata,1); - //normalize - double eta_nrm=my_cnrm2(4*N,eta); - my_cscal(4*N, 1.0/eta_nrm+0.0*_Complex_I, eta); - - my_ccopy(4*N,eta,1,s,1); - my_cscal(4*N, delta_0+0.0*_Complex_I, s); - //Hessian at s - fns_fhess(x,s,Heta,y,gdata); - - /* constants used */ - double gamma_1=0.0625; double gamma_2=5.0; double gamma_3=0.5; double gamma_4=2.0; - double mu_0=0.5; double mu_1=0.5; double mu_2=0.35; - double teta=0.25; - - - int m,MK=4; - for (m=0; mdelta) { - delta=f0-fk; - sigma=delta_0; - } - /* radius update */ - double beta_1,beta_2,beta_i; - beta_1=0.0; - beta_2=0.0; - if (mmu_1) { - if (minbeta>1.0) { - beta_i=gamma_3; - } else if ((maxbeta=1.0)) { - beta_i=gamma_1; - } else if ((beta_1>=gamma_1 && beta_1<1.0) && (beta_2=1.0)) { - beta_i=beta_1; - } else if ((beta_2>=gamma_1 && beta_2<1.0) && (beta_1=1.0)) { - beta_i=beta_2; - } else { - beta_i=maxbeta; - } - } else if (rho1<=mu_2) { - if (maxbeta<1.0) { - beta_i=gamma_4; - } else if (maxbeta>gamma_2) { - beta_i=gamma_2; - } else if ((beta_1>=1.0 && beta_1<=gamma_2) && beta_2<1.0) { - beta_i=beta_1; - } else if ((beta_2>=1.0 && beta_2<=gamma_2) && beta_1<1.0) { - beta_i=beta_2; - } else { - beta_i=maxbeta; - } - } else { - if (maxbetagamma_4) { - beta_i=gamma_4; - } else { - beta_i=maxbeta; - } - } - /* update radius */ - delta_0=delta_0/beta_i; - } - -#ifdef DEBUG -printf("m=%d delta_0=%e delta_max=%e beta=%e rho=%e\n",m,delta_0,delta_m,beta_i,rho); -#endif - - my_ccopy(4*N,eta,1,s,1); - my_cscal(4*N,delta_0+0.0*_Complex_I, s); - } - - - // update initial value - if (delta>0.0) { - my_caxpy(4*N, eta, -sigma+0.0*_Complex_I, x); - } - - if (delta_m>0.0) { - Delta0=delta_m; - } else { - Delta0=delta_0; - } - - return Delta0; -} - - - -int -rtr_solve_nocuda_robust( - double *x0, /* initial values and updated solution at output (size 8*N double) */ - double *y, /* data vector (size 8*M double) */ - int N, /* no. of stations */ - int M, /* no. of constraints */ - int itmax_rsd, /* maximum number of iterations RSD */ - int itmax_rtr, /* maximum number of iterations RTR */ - double Delta_bar, double Delta0, /* Trust region radius and initial value */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - double *info, /* initial and final residuals */ - me_data_t *adata) { /* pointer to additional data */ - - /* reshape x to make J: 2Nx2 complex double - */ - complex double *x; - if ((x=(complex double*)malloc((size_t)4*N*sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - /* map x: [(re,im)J_1(0,0) (re,im)J_1(0,1) (re,im)J_1(1,0) (re,im)J_1(1,1)...] - to - J: [J_1(0,0) J_1(1,0) J_2(0,0) J_2(1,0) ..... J_1(0,1) J_1(1,1) J_2(0,1) J_2(1,1)....] - */ - double *Jd=(double*)x; - /* re J(0,0) */ - my_dcopy(N, &x0[0], 8, &Jd[0], 4); - /* im J(0,0) */ - my_dcopy(N, &x0[1], 8, &Jd[1], 4); - /* re J(1,0) */ - my_dcopy(N, &x0[4], 8, &Jd[2], 4); - /* im J(1,0) */ - my_dcopy(N, &x0[5], 8, &Jd[3], 4); - /* re J(0,1) */ - my_dcopy(N, &x0[2], 8, &Jd[4*N], 4); - /* im J(0,1) */ - my_dcopy(N, &x0[3], 8, &Jd[4*N+1], 4); - /* re J(1,1) */ - my_dcopy(N, &x0[6], 8, &Jd[4*N+2], 4); - /* im J(1,1) */ - my_dcopy(N, &x0[7], 8, &Jd[4*N+3], 4); - - - - int Nt=adata->Nt; - int ci; - global_data_rtr_t gdata; - - gdata.medata=adata; - /* setup threads */ - pthread_attr_init(&gdata.attr); - pthread_attr_setdetachstate(&gdata.attr,PTHREAD_CREATE_JOINABLE); - - if ((gdata.th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - if ((gdata.mx_array=(pthread_mutex_t*)malloc((size_t)N*sizeof(pthread_mutex_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((gdata.iw=(double*)malloc((size_t)N*sizeof(double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - /* weights for robust LS, length could be less than total no of baselines - therefore use relative offset boff */ - if ((gdata.wtd=(double*)malloc((size_t)M*sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - - for (ci=0; cistation contributions - NOTE: has to be done here because the baseline offset would change */ - fns_fcount(&gdata); -/***************************************************/ - int min_inner,max_inner,min_outer,max_outer; - double epsilon,kappa,theta,rho_prime; - /* - min_inner = 0; - max_inner = inf; - min_outer = 3; - max_outer = 100; - epsilon = 1e-6; - kappa = 0.1; - theta = 1.0; - rho_prime = 0.1; - %Delta_bar = user must specify - %Delta0 = user must specify - %x0 = user must specify - */ - min_inner=1; max_inner=itmax_rtr;//8*N; - min_outer=3; max_outer=itmax_rtr; - epsilon=CLM_EPSILON; - kappa=0.1; - theta=1.0; - /* default values 0.25, 0.75, 0.25, 2.0 */ - double eta1=0.0001; double eta2=0.99; double alpha1=0.25; double alpha2=3.5; - rho_prime=eta1; /* default 0.1 should be <= 0.25 */ - double rho_regularization; /* use large damping (but less than GPU version) */ - double rho_reg; - int model_decreased=0; - - complex double *fgradx,*eta,*Heta,*x_prop; - if ((fgradx=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((eta=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((Heta=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((x_prop=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - /*set initial weights to 1 */ - setweights(M,gdata.wtd,1.0,Nt); - gdata.nulow=robust_nulow; - gdata.nuhigh=robust_nuhigh; - - double fx,norm_grad,Delta,fx_prop,rhonum,rhoden,rho; - fx=fns_f(x,y,&gdata); - double fx0=fx; - int rsdstat=0; -/***************************************************/ - /* RSD solution */ - //for (ci=0; cirobust_nu,robust_nu1); - adata->robust_nu=robust_nu1; -/***************************************************/ - /* - % initialize counters/sentinals - % allocate storage for dist, counters - k = 0; % counter for outer (TR) iteration. - stop_outer = 0; % stopping criterion for TR. - - x = x0; -fx = fns.f(x); -fgradx = fns.fgrad(x); -norm_grad = sqrt(fns.g(x,fgradx,fgradx)); - -% initialize trust-region radius -Delta = Delta0; - */ - int k=0; - int stop_outer=(itmax_rtr>0?0:1); - int stop_inner=0; - // x0 is already copied to x: my_ccopy(4*N,x0,1,x,1); - if(!stop_outer) { - fns_fgrad(x,fgradx,y,&gdata,1); - norm_grad = sqrt(fns_g(N,x,fgradx,fgradx)); - } - Delta=Delta0; - - /* initial residual */ - info[0]=fx; - - /* - % ** Start of TR loop ** - while stop_outer==0, - */ - while(!stop_outer) { - /* - % update counter - k = k+1; - */ - k++; - - - /* - ** Begin TR Subproblem ** - % determine eta0 - % without randT, 0*fgradx is the only way that we - % know how to create a tangent vector - eta = 0*fgradx; - */ - memset(eta,0,sizeof(complex double)*N*4); - - - /* - % solve TR subproblem - [eta,numit,stop_inner] = tCG(fns,x,fgradx,eta,Delta,theta,kappa,min_inner,max_inner,useRand,debug); - */ - stop_inner=tcg_solve(N, x, fgradx, eta, Heta, Delta, theta, kappa, max_inner, min_inner,y,&gdata); - - /* - norm_eta = sqrt(fns.g(x,eta,eta)); - */ - - /* - Heta = fns.fhess(x,eta); - */ - //OLD fns_fhess(x,eta,Heta,y,&gdata); - - /* - % compute the retraction of the proposal - x_prop = fns.R(x,eta); - */ - fns_R(N,x,eta,x_prop); - - /* - % compute function value of the proposal - fx_prop = fns.f(x_prop); - */ - fx_prop=fns_f(x_prop,y,&gdata); - - /* - % do we accept the proposed solution or not? - % compute the Hessian at the proposal - Heta = fns.fhess(x,eta); - FIXME: do we need to do this, because Heta is already there - or change x to x_prop ??? - */ - //Disabled fns_fhess(x,eta,Heta,y,&gdata); - - /* - % check the performance of the quadratic model - rhonum = fx-fx_prop; - rhoden = -fns.g(x,fgradx,eta) - 0.5*fns.g(x,Heta,eta); - */ - rhonum=fx-fx_prop; - rhoden=-fns_g(N,x,fgradx,eta)-0.5*fns_g(N,x,Heta,eta); - /* regularization of rho ratio */ - /* - rho_reg = max(1, abs(fx)) * eps * options.rho_regularization; - rhonum = rhonum + rho_reg; - rhoden = rhoden + rho_reg; - */ - rho_reg=MAX(1.0,fx)*rho_regularization; /* no epsilon */ - rhonum+=rho_reg; - rhoden+=rho_reg; - - - /* - rho = rhonum / rhoden; - */ - rho=rhonum/rhoden; - - - - /* - % HEURISTIC WARNING: - % if abs(model change) is relatively zero, we are probably near a critical - % point. set rho to 1. - if abs(rhonum/fx) < sqrt(eps), - small_rhonum = rhonum; - rho = 1; - else - small_rhonum = 0; - end - FIXME: use constant for sqrt(eps) - */ - /* OLD CODE if (fabs(rhonum/fx) = 0); */ - model_decreased=(rhoden>=0.0?1:0); - - /* NOTE: if too many values of rho are -ve, it means TR radius is too big - so initial TR radius should be reduced */ -#ifdef DEBUG - printf("stop_inner=%d rho_reg=%g rho =%g/%g= %g rho'= %g\n",stop_inner,rho_reg,rhonum,rhoden,rho,rho_prime); -#endif - - /* - % choose new TR radius based on performance - if rho < 1/4 - Delta = 1/4*Delta; - elseif rho > 3/4 && (stop_inner == 2 || stop_inner == 1), - Delta = min(2*Delta,Delta_bar); - end - */ - if (!model_decreased || rhoeta2 && (stop_inner==2 || stop_inner==1)) { - /* we have a good reduction, so increase TR radius */ - Delta=MIN(alpha2*Delta,Delta_bar); - } - - /* - % choose new iterate based on performance - oldgradx = fgradx; - if rho > rho_prime, - accept = true; - x = x_prop; - fx = fx_prop; - fgradx = fns.fgrad(x); - norm_grad = sqrt(fns.g(x,fgradx,fgradx)); - else - accept = false; - end - */ - if (model_decreased && rho>rho_prime) { - my_ccopy(4*N,x_prop,1,x,1); - fx=fx_prop; - fns_fgrad(x,fgradx,y,&gdata,1); - norm_grad=sqrt(fns_g(N,x,fgradx,fgradx)); - } - - - /* - % ** Testing for Stop Criteria - % min_outer is the minimum number of inner iterations - % before we can exit. this gives randomization a chance to - % escape a saddle point. - if norm_grad < epsilon && (~useRand || k > min_outer), - stop_outer = 1; - end - */ - if (norm_gradmin_outer) { - stop_outer=1; - } - - - /* - % stop after max_outer iterations - if k >= max_outer, - if (verbosity > 0), - fprintf('\n*** timed out -- k == %d***\n',k); - end - stop_outer = 1; - end - */ - if (k>=max_outer) { - stop_outer=1; - } - -#ifdef DEBUG - printf("Iter %d cost=%lf\n",k,fx); -#endif - /* end of TR loop (counter: k) */ - } - - /* final residual */ - info[1]=fx; - - free(fgradx); - free(eta); - free(Heta); - free(x_prop); -/***************************************************/ - robust_nu1=fns_fupdate_weights(x,y,&gdata); - adata->robust_nu=robust_nu1; - if (fx0>fx) { - /* copy back solution to x0 */ - /* re J(0,0) */ - my_dcopy(N, &Jd[0], 4, &x0[0], 8); - /* im J(0,0) */ - my_dcopy(N, &Jd[1], 4, &x0[1], 8); - /* re J(1,0) */ - my_dcopy(N, &Jd[2], 4, &x0[4], 8); - /* im J(1,0) */ - my_dcopy(N, &Jd[3], 4, &x0[5], 8); - /* re J(0,1) */ - my_dcopy(N, &Jd[4*N], 4, &x0[2], 8); - /* im J(0,1) */ - my_dcopy(N, &Jd[4*N+1], 4, &x0[3], 8); - /* re J(1,1) */ - my_dcopy(N, &Jd[4*N+2], 4, &x0[6], 8); - /* im J(1,1) */ - my_dcopy(N, &Jd[4*N+3], 4, &x0[7], 8); - } - - for (ci=0; ciNt; - int ci; - global_data_rtr_t gdata; - - gdata.medata=adata; - /* setup threads */ - pthread_attr_init(&gdata.attr); - pthread_attr_setdetachstate(&gdata.attr,PTHREAD_CREATE_JOINABLE); - - if ((gdata.th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - if ((gdata.mx_array=(pthread_mutex_t*)malloc((size_t)N*sizeof(pthread_mutex_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((gdata.iw=(double*)malloc((size_t)N*sizeof(double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - /* weights for robust LS, length could be less than total no of baselines - therefore use relative offset boff */ - if ((gdata.wtd=(double*)malloc((size_t)M*sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - - for (ci=0; cistation contributions - NOTE: has to be done here because the baseline offset would change */ - fns_fcount(&gdata); -/***************************************************/ - complex double *fgradx,*eta,*z,*x_prop,*z_prop; - if ((fgradx=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((eta=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((z=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((x_prop=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((z_prop=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - /*set initial weights to 1 */ - setweights(M,gdata.wtd,1.0,Nt); - gdata.nulow=robust_nulow; - gdata.nuhigh=robust_nuhigh; - - double fx; - fx=fns_f(x,y,&gdata); - double fx0=fx; -/***************************************************/ - /* gradient at x0 */ - fns_fgrad(x,fgradx,y,&gdata,1); - /* Hessian at x0,x0 */ - fns_fhess(x,x,z,y,&gdata); - /* intial step ~ 1/||Hessian|| */ - double hess_nrm=my_cnrm2(4*N,z); - double t=1.0/hess_nrm; - /* if initial step too small */ - if (t<1e-6) { - t=1e-6; - } - - /* z <= x */ - my_ccopy(4*N,x,1,z,1); - double theta=1.0; - double ALPHA=1.01; /* step-size growth factor */ - double BETA=0.5; /* step-size shrinkage factor */ - - int k; - for (k=0; krobust_nu=robust_nu1; - if (fx0>fx) { - /* copy back solution to x0 */ - /* re J(0,0) */ - my_dcopy(N, &Jd[0], 4, &x0[0], 8); - /* im J(0,0) */ - my_dcopy(N, &Jd[1], 4, &x0[1], 8); - /* re J(1,0) */ - my_dcopy(N, &Jd[2], 4, &x0[4], 8); - /* im J(1,0) */ - my_dcopy(N, &Jd[3], 4, &x0[5], 8); - /* re J(0,1) */ - my_dcopy(N, &Jd[4*N], 4, &x0[2], 8); - /* im J(0,1) */ - my_dcopy(N, &Jd[4*N+1], 4, &x0[3], 8); - /* re J(1,1) */ - my_dcopy(N, &Jd[4*N+2], 4, &x0[6], 8); - /* im J(1,1) */ - my_dcopy(N, &Jd[4*N+3], 4, &x0[7], 8); - } - - for (ci=0; ci - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#include "sagecal.h" - -//#define DEBUG -/* Jones matrix multiplication - C=A*B -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -amb(complex double * __restrict a, complex double * __restrict b, complex double * __restrict c) { - c[0]=a[0]*b[0]+a[1]*b[2]; - c[1]=a[0]*b[1]+a[1]*b[3]; - c[2]=a[2]*b[0]+a[3]*b[2]; - c[3]=a[2]*b[1]+a[3]*b[3]; -} - -/* Jones matrix multiplication - C=A*B^H -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -ambt(complex double * __restrict a, complex double * __restrict b, complex double * __restrict c) { - c[0]=a[0]*conj(b[0])+a[1]*conj(b[1]); - c[1]=a[0]*conj(b[2])+a[1]*conj(b[3]); - c[2]=a[2]*conj(b[0])+a[3]*conj(b[1]); - c[3]=a[2]*conj(b[2])+a[3]*conj(b[3]); -} - -/* Jones matrix multiplication - C=A^H*B -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -atmb(complex double * __restrict a, complex double * __restrict b, complex double * __restrict c) { - c[0]=conj(a[0])*b[0]+conj(a[2])*b[2]; - c[1]=conj(a[0])*b[1]+conj(a[2])*b[3]; - c[2]=conj(a[1])*b[0]+conj(a[3])*b[2]; - c[3]=conj(a[1])*b[1]+conj(a[3])*b[3]; -} - - -/* worker thread function for counting */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -threadfn_fns_fcount(void *data) { - thread_data_rtr_t *t=(thread_data_rtr_t*)data; - - int ci,sta1,sta2; - for (ci=0; ciNb; ci++) { - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - - pthread_mutex_lock(&t->mx_array[sta1]); - t->bcount[sta1]+=1; - pthread_mutex_unlock(&t->mx_array[sta1]); - - pthread_mutex_lock(&t->mx_array[sta2]); - t->bcount[sta2]+=1; - pthread_mutex_unlock(&t->mx_array[sta2]); - } - } - - return NULL; -} - - -/* function to count how many baselines contribute to the calculation of - grad and hess, so normalization can be made */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -fns_fcount(global_data_rtr_t *gdata) { - - me_data_t *dp=(me_data_t*)gdata->medata; - - int nth,nth1,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthb0,Nthb; - thread_data_rtr_t *threaddata; - - int Nbase1=(dp->Nbase)*(dp->tilesz); - int boff=(dp->Nbase)*(dp->tileoff); - - /* calculate min baselines a thread can handle */ - Nthb0=(Nbase1+Nt-1)/Nt; - - if ((threaddata=(thread_data_rtr_t*)malloc((size_t)Nt*sizeof(thread_data_rtr_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - int *bcount; - if ((bcount=(int*)calloc((size_t)dp->N,sizeof(int)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr; - threaddata[nth].bcount=bcount; /* note this should be 0 first */ - threaddata[nth].mx_array=gdata->mx_array; - - pthread_create(&gdata->th_array[nth],&gdata->attr,threadfn_fns_fcount,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - /* now wait for threads to finish */ - for(nth1=0; nth1th_array[nth1],NULL); - } - free(threaddata); - - /* calculate inverse count */ - for (nth1=0; nth1N; nth1++) { - gdata->iw[nth1]=(bcount[nth1]>0?1.0/(double)bcount[nth1]:0.0); - } - free(bcount); - /* scale back weight such that max value is 1 */ - nth1=my_idamax(dp->N,gdata->iw,1); - double maxw=gdata->iw[nth1-1]; /* 1 based index */ - if (maxw>0.0) { /* all baselines can be flagged */ - my_dscal(dp->N,1.0/maxw,gdata->iw); - } -} - - - -/* worker thread function for cost function calculation */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -threadfn_fns_f(void *data) { - thread_data_rtr_t *t=(thread_data_rtr_t*)data; - - int ci,cm,sta1,sta2; - complex double C[4],G1[4],G2[4],T1[4],T2[4]; - int M=(t->M); - cm=(t->clus); - for (ci=0; ciNb; ci++) { - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - /* gains for this cluster, for sta1,sta2 */ - G1[0]=(t->x[sta1*2]); - G1[1]=(t->x[sta1*2+2*t->N]); - G1[2]=(t->x[sta1*2+1]); - G1[3]=(t->x[sta1*2+2*t->N+1]); - G2[0]=(t->x[sta2*2]); - G2[1]=(t->x[sta2*2+2*t->N]); - G2[2]=(t->x[sta2*2+1]); - G2[3]=(t->x[sta2*2+2*t->N+1]); - - /* use pre calculated values */ - C[0]=t->coh[4*M*ci+4*cm]; - C[1]=t->coh[4*M*ci+4*cm+1]; - C[2]=t->coh[4*M*ci+4*cm+2]; - C[3]=t->coh[4*M*ci+4*cm+3]; - - - /* form G1*C*G2' */ - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - - /* add to baseline visibilities V->U */ - double r00=t->y[8*ci]-creal(T2[0]); - double i00=t->y[8*ci+1]-cimag(T2[0]); - double r01=t->y[8*ci+2]-creal(T2[1]); - double i01=t->y[8*ci+3]-cimag(T2[1]); - double r10=t->y[8*ci+4]-creal(T2[2]); - double i10=t->y[8*ci+5]-cimag(T2[2]); - double r11=t->y[8*ci+6]-creal(T2[3]); - double i11=t->y[8*ci+7]-cimag(T2[3]); - - t->fcost+=t->wtd[ci]*(r00*r00+i00*i00+r01*r01+i01*i01+r10*r10+i10*i10+r11*r11+i11*i11); - } - } - - return NULL; -} - - -/* cost function */ -/* x: 2Nx2 solution - y: visibilities, vectorized V(:) 8*Nbase x 1 -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static double -fns_f(complex double *x, double *y, global_data_rtr_t *gdata) { - - me_data_t *dp=(me_data_t*)gdata->medata; - - int nth,nth1,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthb0,Nthb; - thread_data_rtr_t *threaddata; - - int Nbase1=(dp->Nbase)*(dp->tilesz); - int boff=(dp->Nbase)*(dp->tileoff); - - /* calculate min baselines a thread can handle */ - Nthb0=(Nbase1+Nt-1)/Nt; - - if ((threaddata=(thread_data_rtr_t*)malloc((size_t)Nt*sizeof(thread_data_rtr_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr; - threaddata[nth].carr=dp->carr; - threaddata[nth].M=dp->M; - threaddata[nth].y=&(y[8*ci]); - threaddata[nth].N=dp->N; - threaddata[nth].x=x; /* note the difference: here x assumes no hybrid, also ordering different */ - threaddata[nth].clus=(dp->clus); - threaddata[nth].coh=&(dp->coh[4*(dp->M)*(ci+boff)]); - threaddata[nth].fcost=0.0; - - threaddata[nth].wtd=&(gdata->wtd[ci]); /* weights for baselines */ - - //printf("thread %d predict data from %d baselines %d\n",nth,8*ci,Nthb); - pthread_create(&gdata->th_array[nth],&gdata->attr,threadfn_fns_f,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - /* now wait for threads to finish */ - double fcost=0.0; - for(nth1=0; nth1th_array[nth1],NULL); - fcost+=threaddata[nth1].fcost; - } - - free(threaddata); - /* add ||Y^H(J-BZ)|| + rho/2 ||J-BZ||^2 */ - complex double *Yd; - if ((Yd=(complex double*)malloc((size_t)4*dp->N*sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - /* Yd=J-BZ */ - my_ccopy(4*dp->N,x,1,Yd,1); - my_caxpy(4*dp->N,gdata->BZ,-1.0,Yd); - - /* ||Y^H Yd|| = 2 real(Y(:)^H Yd(:)) */ - fcost+=2.0*creal(my_cdot(4*dp->N,gdata->Y,Yd)); - - /* rho/2 ||J-BZ||^2 = rho/2 real(Yd(:)^H Yd(:)) */ - fcost+=0.5*gdata->admm_rho*creal(my_cdot(4*dp->N,Yd,Yd)); - - free(Yd); - - - return fcost; -} - - -/* worker thread function for weight update (nu+p)/(nu+error^2) */ -/* p=2, not p=8 because using MAX() not sum for error^2 */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -threadfn_fns_fupdate_weights(void *data) { - thread_data_rtr_t *t=(thread_data_rtr_t*)data; - - int ci,cm,sta1,sta2; - complex double C[4],G1[4],G2[4],T1[4],T2[4]; - int M=(t->M); - cm=(t->clus); - for (ci=0; ciNb; ci++) { - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - /* gains for this cluster, for sta1,sta2 */ - G1[0]=(t->x[sta1*2]); - G1[1]=(t->x[sta1*2+2*t->N]); - G1[2]=(t->x[sta1*2+1]); - G1[3]=(t->x[sta1*2+2*t->N+1]); - G2[0]=(t->x[sta2*2]); - G2[1]=(t->x[sta2*2+2*t->N]); - G2[2]=(t->x[sta2*2+1]); - G2[3]=(t->x[sta2*2+2*t->N+1]); - - /* use pre calculated values */ - C[0]=t->coh[4*M*ci+4*cm]; - C[1]=t->coh[4*M*ci+4*cm+1]; - C[2]=t->coh[4*M*ci+4*cm+2]; - C[3]=t->coh[4*M*ci+4*cm+3]; - - - /* form G1*C*G2' */ - /* T1=G1*C */ - amb(G1,C,T1); - /* T2=T1*G2' */ - ambt(T1,G2,T2); - - /* add to baseline visibilities V->U */ - double r00=t->y[8*ci]-creal(T2[0]); - double i00=t->y[8*ci+1]-cimag(T2[0]); - double r01=t->y[8*ci+2]-creal(T2[1]); - double i01=t->y[8*ci+3]-cimag(T2[1]); - double r10=t->y[8*ci+4]-creal(T2[2]); - double i10=t->y[8*ci+5]-cimag(T2[2]); - double r11=t->y[8*ci+6]-creal(T2[3]); - double i11=t->y[8*ci+7]-cimag(T2[3]); - - //t->wtd[ci] = (t->nu0+8.0)/(t->nu0+(r00*r00+i00*i00+r01*r01+i01*i01+r10*r10+i10*i10+r11*r11+i11*i11)); - t->wtd[ci] = (t->nu0+2.0)/(t->nu0+MAX(r00*r00+i00*i00,MAX(r01*r01+i01*i01,MAX(r10*r10+i10*i10,r11*r11+i11*i11)))); - } - } - - return NULL; -} - - -/* calculate log(w_i) - w_i */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -threadfn_fns_flogsum_weights(void *data) { - thread_data_rtr_t *t=(thread_data_rtr_t*)data; - - int ci; - for (ci=0; ciNb; ci++) { - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - t->fcost+=log(t->wtd[ci])-t->wtd[ci]; - } - } - - return NULL; -} - - - -/* calculate weight w = (nu+1)/(nu+error^2) per baseline - then update robust_nu */ -/* x: 2Nx2 solution - y: visibilities, vectorized V(:) 8*Nbase x 1 - returns updated value for robust_nu -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static double -fns_fupdate_weights(complex double *x, double *y, global_data_rtr_t *gdata) { - - me_data_t *dp=(me_data_t*)gdata->medata; - - int nth,nth1,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthb0,Nthb; - thread_data_rtr_t *threaddata; - - int Nbase1=(dp->Nbase)*(dp->tilesz); - int boff=(dp->Nbase)*(dp->tileoff); - - /* calculate min baselines a thread can handle */ - Nthb0=(Nbase1+Nt-1)/Nt; - - if ((threaddata=(thread_data_rtr_t*)malloc((size_t)Nt*sizeof(thread_data_rtr_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr; - threaddata[nth].carr=dp->carr; - threaddata[nth].M=dp->M; - threaddata[nth].y=&(y[8*ci]); - threaddata[nth].N=dp->N; - threaddata[nth].x=x; /* note the difference: here x assumes no hybrid, also ordering different */ - threaddata[nth].clus=(dp->clus); - threaddata[nth].coh=&(dp->coh[4*(dp->M)*(ci+boff)]); - - threaddata[nth].wtd=&(gdata->wtd[ci]); /* weights for baselines */ - threaddata[nth].nu0=dp->robust_nu; - - //printf("thread %d predict data from %d baselines %d\n",nth,8*ci,Nthb); - pthread_create(&gdata->th_array[nth],&gdata->attr,threadfn_fns_fupdate_weights,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - for(nth1=0; nth1th_array[nth1],NULL); - } - - /* now calculate sum( ln(w_i)-w_i ) */ - ci=0; - for (nth1=0; nth1th_array[nth1],&gdata->attr,threadfn_fns_flogsum_weights,(void*)(&threaddata[nth1])); - /* next baseline set */ - } - - double sumlogw=0.0; - for(nth1=0; nth1th_array[nth1],NULL); - } - sumlogw/=(double)Nbase1; - free(threaddata); - - /* find new value for nu, p-variate T dist, p=8 (update p=2 because using MAX() for residual calculation, not sum) */ - /* psi((nu_old+p)/2)-ln((nu_old+p)/2)-psi(nu/2)+ln(nu/2)+1/N sum(ln(w_i)-w_i) +1 = 0, AECM */ - double nu1=update_nu(sumlogw, 30, Nt, gdata->nulow, gdata->nuhigh, 2, dp->robust_nu); - /* make sure new value stays within bounds */ - if (nu1nulow) { return gdata->nulow; } - if (nu1>gdata->nuhigh) { return gdata->nuhigh; } - - return nu1; -} - - - -/* inner product (metric) */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static double -fns_g(int N,complex double *x, complex double *eta, complex double *gamma) { - /* 2 x real( trace(eta'*gamma) ) - = 2 x real( eta(:,1)'*gamma(:,1) + eta(:,2)'*gamma(:,2) ) - no need to calculate off diagonal terms - )*/ - complex double v1=my_cdot(2*N,eta,gamma); - complex double v2=my_cdot(2*N,&eta[2*N],&gamma[2*N]); - - return 2.0*creal(v1+v2); -} - -/* Projection - rnew: new value */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -fns_proj(int N,complex double *x, complex double *z,complex double *rnew) { - /* projection = Z, since Euclidean space - */ - my_ccopy(4*N,z,1,rnew,1); -} - - -/* Retraction - rnew: new value */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -fns_R(int N,complex double *x, complex double *r,complex double *rnew) { - /* rnew = x + r */ - my_ccopy(4*N,x,1,rnew,1); - my_caxpy(4*N,r,1.0+_Complex_I*0.0,rnew); -} - - -/* worker thread function for gradient/hessian weighting */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -threadfn_fns_fscale(void *data) { - thread_data_rtr_t *t=(thread_data_rtr_t*)data; - - int ci,sta1; - for (ci=0; ciNb; ci++) { - sta1=ci+t->boff; - t->grad[2*sta1]*=t->iw[sta1]; - t->grad[2*sta1+2*t->N]*=t->iw[sta1]; - t->grad[2*sta1+1]*=t->iw[sta1]; - t->grad[2*sta1+2*t->N+1]*=t->iw[sta1]; - } - - return NULL; -} - - - - -/* worker thread function for gradient calculation */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -threadfn_fns_fgrad(void *data) { - thread_data_rtr_t *t=(thread_data_rtr_t*)data; - - int ci,cm,sta1,sta2; - complex double C[4],G1[4],G2[4],T1[4],T2[4],res[4]; - int M=(t->M); - cm=(t->clus); - for (ci=0; ciNb; ci++) { - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - /* gains for this cluster, for sta1,sta2 */ - G1[0]=(t->x[sta1*2]); - G1[1]=(t->x[sta1*2+2*t->N]); - G1[2]=(t->x[sta1*2+1]); - G1[3]=(t->x[sta1*2+2*t->N+1]); - G2[0]=(t->x[sta2*2]); - G2[1]=(t->x[sta2*2+2*t->N]); - G2[2]=(t->x[sta2*2+1]); - G2[3]=(t->x[sta2*2+2*t->N+1]); - - /* use pre calculated values */ - C[0]=t->coh[4*M*ci+4*cm]; - C[1]=t->coh[4*M*ci+4*cm+1]; - C[2]=t->coh[4*M*ci+4*cm+2]; - C[3]=t->coh[4*M*ci+4*cm+3]; - - - /* G1*C*G2' */ - amb(G1,C,T1); - ambt(T1,G2,T2); - - /* res=V(2*ck-1:2*ck,:)-x(2*p-1:2*p,:)*C*x(2*q-1:2*q,:)'; */ - /* V->U */ - res[0]=(t->y[8*ci]+_Complex_I*t->y[8*ci+1])-T2[0]; - res[1]=(t->y[8*ci+2]+_Complex_I*t->y[8*ci+3])-T2[1]; - res[2]=(t->y[8*ci+4]+_Complex_I*t->y[8*ci+5])-T2[2]; - res[3]=(t->y[8*ci+6]+_Complex_I*t->y[8*ci+7])-T2[3]; - - /* - grad(2*p-1:2*p,:)=grad(2*p-1:2*p,:)+res*x(2*q-1:2*q,:)*C'; - grad(2*q-1:2*q,:)=grad(2*q-1:2*q,:)+res'*x(2*p-1:2*p,:)*C; - */ - /* res*G2*C' */ - amb(res,G2,T1); - ambt(T1,C,T2); - - /* multiply by baseline weight */ - T2[0]=T2[0]*t->wtd[ci]; - T2[1]=T2[1]*t->wtd[ci]; - T2[2]=T2[2]*t->wtd[ci]; - T2[3]=T2[3]*t->wtd[ci]; - - pthread_mutex_lock(&t->mx_array[sta1]); - t->grad[2*sta1]+=T2[0]; - t->grad[2*sta1+2*t->N]+=T2[1]; - t->grad[2*sta1+1]+=T2[2]; - t->grad[2*sta1+2*t->N+1]+=T2[3]; - pthread_mutex_unlock(&t->mx_array[sta1]); - - /* res'*G1*C */ - atmb(res,G1,T1); - amb(T1,C,T2); - - /* multiply by baseline weight */ - T2[0]=T2[0]*t->wtd[ci]; - T2[1]=T2[1]*t->wtd[ci]; - T2[2]=T2[2]*t->wtd[ci]; - T2[3]=T2[3]*t->wtd[ci]; - - pthread_mutex_lock(&t->mx_array[sta2]); - t->grad[2*sta2]+=T2[0]; - t->grad[2*sta2+2*t->N]+=T2[1]; - t->grad[2*sta2+1]+=T2[2]; - t->grad[2*sta2+2*t->N+1]+=T2[3]; - pthread_mutex_unlock(&t->mx_array[sta2]); - - } - } - - return NULL; -} - - - -/* gradient function */ -/* x: 2Nx2 solution - fgradx: output, same shape as x - y: visibilities, vectorized V(:) 8*Nbase x 1 - if negate==1, return -grad, else just grad -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -fns_fgrad(complex double *x, complex double *fgradx, double *y, global_data_rtr_t *gdata, int negate) { - - me_data_t *dp=(me_data_t*)gdata->medata; - - int nth,nth1,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthb0,Nthb; - thread_data_rtr_t *threaddata; - - int Nbase1=(dp->Nbase)*(dp->tilesz); - int boff=(dp->Nbase)*(dp->tileoff); - - /* calculate min baselines a thread can handle */ - Nthb0=(Nbase1+Nt-1)/Nt; - - if ((threaddata=(thread_data_rtr_t*)malloc((size_t)Nt*sizeof(thread_data_rtr_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - complex double *grad; - if ((grad=(complex double*)calloc((size_t)4*dp->N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr; - threaddata[nth].carr=dp->carr; - threaddata[nth].M=dp->M; - threaddata[nth].y=&(y[8*ci]); - threaddata[nth].N=dp->N; - threaddata[nth].x=x; /* note the difference: here x assumes no hybrid, also ordering different */ - threaddata[nth].clus=(dp->clus); - threaddata[nth].coh=&(dp->coh[4*(dp->M)*(ci+boff)]); - threaddata[nth].grad=grad; - threaddata[nth].mx_array=gdata->mx_array; - threaddata[nth].wtd=&(gdata->wtd[ci]); /* weights for baselines */ - - //printf("thread %d predict data from %d baselines %d\n",nth,8*ci,Nthb); - pthread_create(&gdata->th_array[nth],&gdata->attr,threadfn_fns_fgrad,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - /* now wait for threads to finish */ - for(nth1=0; nth1th_array[nth1],NULL); - } -/******************* scale *************/ - Nthb0=(dp->N+Nt-1)/Nt; - ci=0; - for (nth=0; nthN; nth++) { - if (ci+Nthb0N) { - Nthb=Nthb0; - } else { - Nthb=dp->N-ci; - } - threaddata[nth].boff=ci; - threaddata[nth].Nb=Nthb; - threaddata[nth].N=dp->N; - threaddata[nth].grad=grad; - threaddata[nth].iw=gdata->iw; - pthread_create(&gdata->th_array[nth],&gdata->attr,threadfn_fns_fscale,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - for(nth1=0; nth1th_array[nth1],NULL); - } -/******************* scale *************/ - free(threaddata); - if (negate) { - my_cscal(4*dp->N,-1.0+0.0*_Complex_I,grad); - } - - /********************/ - /* print the norms */ -// complex double *Jdiff; -// if ((Jdiff=(complex double*)calloc((size_t)4*dp->N,sizeof(complex double)))==0) { -// fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -// exit(1); -// } -// my_caxpy(4*dp->N,x,0.5*gdata->admm_rho,Jdiff); -// my_caxpy(4*dp->N,gdata->BZ,-0.5*gdata->admm_rho,Jdiff); -// printf("Norm %lf %lf %lf\n",my_cnrm2(4*dp->N,grad),0.5*my_cnrm2(4*dp->N,gdata->Y),my_cnrm2(4*dp->N,Jdiff)); -// free(Jdiff); - /********************/ - - /* extra terms 0.5*Y+0.5*rho*(J-BZ) - add to -ve grad */ - if (negate) { - my_caxpy(4*dp->N,gdata->Y,0.5,grad); - my_caxpy(4*dp->N,x,0.5*gdata->admm_rho,grad); - my_caxpy(4*dp->N,gdata->BZ,-0.5*gdata->admm_rho,grad); - } else { - my_caxpy(4*dp->N,gdata->Y,-0.5,grad); - my_caxpy(4*dp->N,x,-0.5*gdata->admm_rho,grad); - my_caxpy(4*dp->N,gdata->BZ,0.5*gdata->admm_rho,grad); - } - fns_proj(dp->N,x,grad,fgradx); - - - free(grad); - -} - - -/* worker thread function for Hessian calculation */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -threadfn_fns_fhess(void *data) { - thread_data_rtr_t *t=(thread_data_rtr_t*)data; - - int ci,cm,sta1,sta2; - complex double C[4],G1[4],G2[4],T1[4],T2[4],res[4],res1[4],E1[4],E2[4]; - int M=(t->M); - cm=(t->clus); - for (ci=0; ciNb; ci++) { - /* if this baseline is flagged, we do not compute */ - if (!t->barr[ci+t->boff].flag) { - - /* stations for this baseline */ - sta1=t->barr[ci+t->boff].sta1; - sta2=t->barr[ci+t->boff].sta2; - /* gains for this cluster, for sta1,sta2 */ - G1[0]=(t->x[sta1*2]); - G1[1]=(t->x[sta1*2+2*t->N]); - G1[2]=(t->x[sta1*2+1]); - G1[3]=(t->x[sta1*2+2*t->N+1]); - G2[0]=(t->x[sta2*2]); - G2[1]=(t->x[sta2*2+2*t->N]); - G2[2]=(t->x[sta2*2+1]); - G2[3]=(t->x[sta2*2+2*t->N+1]); - E1[0]=t->eta[2*sta1]; - E1[1]=t->eta[2*sta1+2*t->N]; - E1[2]=t->eta[2*sta1+1]; - E1[3]=t->eta[2*sta1+2*t->N+1]; - E2[0]=t->eta[2*sta2]; - E2[1]=t->eta[2*sta2+2*t->N]; - E2[2]=t->eta[2*sta2+1]; - E2[3]=t->eta[2*sta2+2*t->N+1]; - - - - /* use pre calculated values */ - C[0]=t->coh[4*M*ci+4*cm]; - C[1]=t->coh[4*M*ci+4*cm+1]; - C[2]=t->coh[4*M*ci+4*cm+2]; - C[3]=t->coh[4*M*ci+4*cm+3]; - - /* G1*C*G2' */ - amb(G1,C,T1); - ambt(T1,G2,T2); - - - /* res=V(2*ck-1:2*ck,:)-x(2*p-1:2*p,:)*C*x(2*q-1:2*q,:)'; */ - /* V->U */ - res[0]=(t->y[8*ci]+_Complex_I*t->y[8*ci+1])-T2[0]; - res[1]=(t->y[8*ci+2]+_Complex_I*t->y[8*ci+3])-T2[1]; - res[2]=(t->y[8*ci+4]+_Complex_I*t->y[8*ci+5])-T2[2]; - res[3]=(t->y[8*ci+6]+_Complex_I*t->y[8*ci+7])-T2[3]; - - - /* - res1=x(2*p-1:2*p,:)*C*eta(2*q-1:2*q,:)'+eta(2*p-1:2*p,:)*C*x(2*q-1:2*q,:)'; - */ - /* G1*C*E2' */ - amb(G1,C,T1); - ambt(T1,E2,T2); - res1[0]=T2[0]; - res1[1]=T2[1]; - res1[2]=T2[2]; - res1[3]=T2[3]; - /* E1*C*G2' */ - amb(E1,C,T1); - ambt(T1,G2,T2); - res1[0]+=T2[0]; - res1[1]+=T2[1]; - res1[2]+=T2[2]; - res1[3]+=T2[3]; - - - /* - hess(2*p-1:2*p,:)=hess(2*p-1:2*p,:)+(res*eta(2*q-1:2*q,:)-res1*x(2*q-1:2*q,:))*C'; - */ - - /* (res*E2-res1*G2)*C' */ - amb(res,E2,T1); - amb(res1,G2,T2); - T1[0]-=T2[0]; - T1[1]-=T2[1]; - T1[2]-=T2[2]; - T1[3]-=T2[3]; - ambt(T1,C,T2); - - /* multiply by baseline weight */ - T2[0]=T2[0]*t->wtd[ci]; - T2[1]=T2[1]*t->wtd[ci]; - T2[2]=T2[2]*t->wtd[ci]; - T2[3]=T2[3]*t->wtd[ci]; - - pthread_mutex_lock(&t->mx_array[sta1]); - t->hess[2*sta1]+=T2[0]; - t->hess[2*sta1+2*t->N]+=T2[1]; - t->hess[2*sta1+1]+=T2[2]; - t->hess[2*sta1+2*t->N+1]+=T2[3]; - pthread_mutex_unlock(&t->mx_array[sta1]); - - - /* - hess(2*q-1:2*q,:)=hess(2*q-1:2*q,:)+(res'*eta(2*p-1:2*p,:)-res1'*x(2*p-1:2*p,:))*C; - */ - - /* (res'*E1-res1'*G1)*C */ - atmb(res,E1,T1); - atmb(res1,G1,T2); - T1[0]-=T2[0]; - T1[1]-=T2[1]; - T1[2]-=T2[2]; - T1[3]-=T2[3]; - amb(T1,C,T2); - - /* multiply by baseline weight */ - T2[0]=T2[0]*t->wtd[ci]; - T2[1]=T2[1]*t->wtd[ci]; - T2[2]=T2[2]*t->wtd[ci]; - T2[3]=T2[3]*t->wtd[ci]; - - pthread_mutex_lock(&t->mx_array[sta2]); - t->hess[2*sta2]+=T2[0]; - t->hess[2*sta2+2*t->N]+=T2[1]; - t->hess[2*sta2+1]+=T2[2]; - t->hess[2*sta2+2*t->N+1]+=T2[3]; - pthread_mutex_unlock(&t->mx_array[sta2]); - - } - } - - return NULL; -} - - - -/* Hessian function */ -/* x: 2Nx2 solution - eta: same shape as x - fhess: output, same shape as x - y: visibilities, vectorized V(:) 8*Nbase x 1 -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void -fns_fhess(complex double *x, complex double *eta,complex double *fhess, double *y, global_data_rtr_t *gdata) { - - me_data_t *dp=(me_data_t*)gdata->medata; - - int nth,nth1,ci; - - /* no of threads */ - int Nt=(dp->Nt); - int Nthb0,Nthb; - thread_data_rtr_t *threaddata; - - int Nbase1=(dp->Nbase)*(dp->tilesz); - int boff=(dp->Nbase)*(dp->tileoff); - - /* calculate min baselines a thread can handle */ - Nthb0=(Nbase1+Nt-1)/Nt; - - if ((threaddata=(thread_data_rtr_t*)malloc((size_t)Nt*sizeof(thread_data_rtr_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - complex double *hess; - if ((hess=(complex double*)calloc((size_t)4*dp->N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nthbarr; - threaddata[nth].carr=dp->carr; - threaddata[nth].M=dp->M; - threaddata[nth].y=&(y[8*ci]); - threaddata[nth].N=dp->N; - threaddata[nth].x=x; /* note the difference: here x assumes no hybrid, also ordering different */ - threaddata[nth].clus=(dp->clus); - threaddata[nth].coh=&(dp->coh[4*(dp->M)*(ci+boff)]); - threaddata[nth].eta=eta; - threaddata[nth].hess=hess; - threaddata[nth].mx_array=gdata->mx_array; - threaddata[nth].wtd=&(gdata->wtd[ci]); /* weights for baselines */ - - //printf("thread %d predict data from %d baselines %d\n",nth,8*ci,Nthb); - pthread_create(&gdata->th_array[nth],&gdata->attr,threadfn_fns_fhess,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - /* now wait for threads to finish */ - for(nth1=0; nth1th_array[nth1],NULL); - } - -/******************* scale *************/ - Nthb0=(dp->N+Nt-1)/Nt; - ci=0; - for (nth=0; nthN; nth++) { - if (ci+Nthb0N) { - Nthb=Nthb0; - } else { - Nthb=dp->N-ci; - } - threaddata[nth].boff=ci; - threaddata[nth].Nb=Nthb; - threaddata[nth].N=dp->N; - threaddata[nth].grad=fhess; - threaddata[nth].iw=gdata->iw; - pthread_create(&gdata->th_array[nth],&gdata->attr,threadfn_fns_fscale,(void*)(&threaddata[nth])); - /* next baseline set */ - ci=ci+Nthb; - } - - for(nth1=0; nth1th_array[nth1],NULL); - } -/******************* scale *************/ - free(threaddata); - - /* extra terms 0.5*rho*eta*/ - my_caxpy(4*dp->N,eta,0.5*gdata->admm_rho,hess); - - - fns_proj(dp->N,x,hess,fhess); - free(hess); - -} - - -/* truncated conjugate gradient method - x, grad, eta, r, z, delta, Hxd : size 2N x 2 complex - so, vector size is 4N complex double - - output: eta - output: fhess (can be reused in calling func) - return value: stop_tCG code - - y: vec(V) visibilities -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static int -tcg_solve(int N, complex double *x, complex double *grad, complex double *eta, complex double *fhess, - double Delta, double theta, double kappa, int max_inner, int min_inner, double *y, global_data_rtr_t *gdata) { - - complex double *r,*z,*delta,*Hxd, *rnew; - double e_Pe, r_r, norm_r, z_r, d_Pd, d_Hd, alpha, e_Pe_new, - e_Pd, Deltasq, tau, zold_rold, beta, norm_r0; - int cj, stop_tCG; - - if ((r=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((z=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((delta=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((Hxd=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((rnew=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - /* - initial values - % eta = 0*grad; << zero matrix provided - r = grad; - e_Pe = 0; - */ - my_ccopy(4*N,grad,1,r,1); - e_Pe=0.0; - - /* - r_r = fns.g(x,r,r); - norm_r = sqrt(r_r); - norm_r0 = norm_r; - */ - - r_r=fns_g(N,x,r,r); - norm_r=sqrt(r_r); - norm_r0=norm_r; - - /* - z = r; - */ - my_ccopy(4*N,r,1,z,1); - - /* - % compute z'*r - z_r = fns.g(x,z,r); - d_Pd = z_r; - */ - z_r=fns_g(N,x,z,r); - d_Pd=z_r; - - /* - % initial search direction - delta = -z; - e_Pd = fns.g(x,eta,delta); - */ - memset(delta,0,sizeof(complex double)*N*4); - my_caxpy(4*N,z,-1.0+_Complex_I*0.0,delta); - e_Pd=fns_g(N,x,eta,delta); - - /* - % pre-assume termination b/c j == end - stop_tCG = 5; - */ - stop_tCG=5; - - /* % begin inner/tCG loop - for j = 1:max_inner, - */ - for(cj=1; cj<=max_inner; cj++) { - /**************************************************/ - /* - Hxd = fns.fhess(x,delta); - - % compute curvature - d_Hd = fns.g(x,delta,Hxd); - */ - fns_fhess(x,delta,Hxd,y,gdata); - d_Hd=fns_g(N,x,delta,Hxd); - - /* - alpha = z_r/d_Hd; - e_Pe_new = e_Pe + 2.0*alpha*e_Pd + alpha*alpha*d_Pd; - */ - alpha=z_r/d_Hd; - e_Pe_new = e_Pe + 2.0*alpha*e_Pd + alpha*alpha*d_Pd; - - /* - - % check curvature and trust-region radius - if d_Hd <= 0 || e_Pe_new >= Delta^2, - - */ - Deltasq=Delta*Delta; - if (d_Hd <= 0.0 || e_Pe_new >= Deltasq) { - /* - tau = (-e_Pd + sqrt(e_Pd*e_Pd + d_Pd*(Delta^2-e_Pe))) / d_Pd; - - */ - tau = (-e_Pd + sqrt(e_Pd*e_Pd + d_Pd*(Deltasq-e_Pe)))/d_Pd; - - /* - eta = eta + tau*delta; - - */ - my_caxpy(4*N,delta,tau+_Complex_I*0.0,eta); - - /* NEW Heta = Heta + tau*Hdelta */ - my_caxpy(4*N,fhess,tau+_Complex_I*0.0,Hxd); - - /* - if d_Hd <= 0, - stop_tCG = 1; % negative curvature - else - stop_tCG = 2; % exceeded trust region - end - */ - stop_tCG=(d_Hd<=0.0?1:2); - - /* - break (for) - */ - break; - /* - end if - */ - } - - - /* - % no negative curvature and eta_prop inside TR: accept it - e_Pe = e_Pe_new; - eta = eta + alpha*delta; - - */ - e_Pe=e_Pe_new; - my_caxpy(4*N,delta,alpha+_Complex_I*0.0,eta); - - /* NEW Heta = Heta + alpha*Hdelta */ - my_caxpy(4*N,fhess,alpha+_Complex_I*0.0,Hxd); - - - /* - % update the residual - r = r + alpha*Hxd; - - */ - my_caxpy(4*N,Hxd,alpha+_Complex_I*0.0,r); - - /* - % compute new norm of r - r_r = fns.g(x,r,r); - norm_r = sqrt(r_r); - - */ - r_r=fns_g(N,x,r,r); - norm_r=sqrt(r_r); - - - /* - % check kappa/theta stopping criterion - if j >= min_inner && norm_r <= norm_r0*min(norm_r0^theta,kappa) - */ - if (cj >= min_inner) { - double norm_r0pow=pow(norm_r0,theta); - if (norm_r <= norm_r0*MIN(norm_r0pow,kappa)) { - - /* - % residual is small enough to quit - if kappa < norm_r0^theta, - stop_tCG = 3; % linear convergence - else - stop_tCG = 4; % superlinear convergence - end - - */ - stop_tCG=(kappamedata; - - double fx=fns_f(x,y,gdata); - fns_fgrad(x,eta,y,gdata,0); - double beta0=beta; - double minfx=fx; double minbeta=beta0; - double lhs,rhs,metric; - int m,nocostred=0; - double metric0=fns_g(dp->N,x,eta,eta); - *mincost=minfx; - for (m=0; m<50; m++) { - /* abeta=(beta0)*alphabar*eta; */ - my_ccopy(4*dp->N,eta,1,teta,1); - my_cscal(4*dp->N,beta0*alphabar+0.0*_Complex_I,teta); - /* Rx=R(x,teta); */ - fns_R(dp->N,x,teta,x_prop); - lhs=fns_f(x_prop,y,gdata); - if (lhsN,x,eta,teta); - rhs=fx+sigma*metric; - /* break loop also if no further cost improvement is seen */ - if (lhs<=rhs) { - minbeta=beta0; - break; - } - beta0=beta0*beta; - } - - /* if no further cost improvement is seen */ - if (lhs>fx) { - nocostred=1; - } - - my_ccopy(4*dp->N,eta,1,teta,1); - my_cscal(4*dp->N,minbeta*alphabar+0.0*_Complex_I,teta); - - return nocostred; -} - -/* Fine tune initial trust region radius, also update initial value for x - A. Sartenaer, 1995 - returns : trust region estimate, - also modifies x - eta,Heta,s,x_prop: used as storage - */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static double -itrr(int N,complex double *x,complex double *eta, complex double *Heta, double *y, global_data_rtr_t *gdata, complex double *s, complex double *x_prop) { - - double f0,fk,mk,rho,rho1,Delta0; - - /* initialize trust region radii */ - double delta_0=1.0; - double delta_m=0.0; - - double sigma=0.0; - double delta=0.0; - - // initial cost - f0=fns_f(x,y,gdata); - // gradient at x0 - fns_fgrad(x,eta,y,gdata,1); - //normalize - double eta_nrm=my_cnrm2(4*N,eta); - my_cscal(4*N, 1.0/eta_nrm+0.0*_Complex_I, eta); - - my_ccopy(4*N,eta,1,s,1); - my_cscal(4*N, delta_0+0.0*_Complex_I, s); - //Hessian at s - fns_fhess(x,s,Heta,y,gdata); - - /* constants used */ - double gamma_1=0.0625; double gamma_2=5.0; double gamma_3=0.5; double gamma_4=2.0; - double mu_0=0.5; double mu_1=0.5; double mu_2=0.35; - double teta=0.25; - - - int m,MK=4; - for (m=0; mdelta) { - delta=f0-fk; - sigma=delta_0; - } - /* radius update */ - double beta_1,beta_2,beta_i; - beta_1=0.0; - beta_2=0.0; - if (mmu_1) { - if (minbeta>1.0) { - beta_i=gamma_3; - } else if ((maxbeta=1.0)) { - beta_i=gamma_1; - } else if ((beta_1>=gamma_1 && beta_1<1.0) && (beta_2=1.0)) { - beta_i=beta_1; - } else if ((beta_2>=gamma_1 && beta_2<1.0) && (beta_1=1.0)) { - beta_i=beta_2; - } else { - beta_i=maxbeta; - } - } else if (rho1<=mu_2) { - if (maxbeta<1.0) { - beta_i=gamma_4; - } else if (maxbeta>gamma_2) { - beta_i=gamma_2; - } else if ((beta_1>=1.0 && beta_1<=gamma_2) && beta_2<1.0) { - beta_i=beta_1; - } else if ((beta_2>=1.0 && beta_2<=gamma_2) && beta_1<1.0) { - beta_i=beta_2; - } else { - beta_i=maxbeta; - } - } else { - if (maxbetagamma_4) { - beta_i=gamma_4; - } else { - beta_i=maxbeta; - } - } - /* update radius */ - delta_0=delta_0/beta_i; - } - -#ifdef DEBUG -printf("m=%d delta_0=%e delta_max=%e beta=%e rho=%e\n",m,delta_0,delta_m,beta_i,rho); -#endif - - my_ccopy(4*N,eta,1,s,1); - my_cscal(4*N,delta_0+0.0*_Complex_I, s); - } - - - // update initial value - if (delta>0.0) { - my_caxpy(4*N, eta, -sigma+0.0*_Complex_I, x); - } - - if (delta_m>0.0) { - Delta0=delta_m; - } else { - Delta0=delta_0; - } - - return Delta0; -} - - - -int -rtr_solve_nocuda_robust_admm( - double *x0, /* initial values and updated solution at output (size 8*N double) */ - double *Y, /* Lagrange multiplier (size 8*N double) */ - double *BZ, /* consensus B Z (size 8*N double) */ - double *y, /* data vector (size 8*M double) */ - int N, /* no. of stations */ - int M, /* no. of constraints */ - int itmax_rsd, /* maximum number of iterations RSD */ - int itmax_rtr, /* maximum number of iterations RTR */ - double Delta_bar, double Delta0, /* Trust region radius and initial value */ - double admm_rho, /* ADMM regularization value */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - double *info, /* initial and final residuals */ - me_data_t *adata) { /* pointer to additional data */ - - /* reshape x to make J: 2Nx2 complex double - */ - complex double *x; - if ((x=(complex double*)malloc((size_t)4*N*sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - /* map x: [(re,im)J_1(0,0) (re,im)J_1(0,1) (re,im)J_1(1,0) (re,im)J_1(1,1)...] - to - J: [J_1(0,0) J_1(1,0) J_2(0,0) J_2(1,0) ..... J_1(0,1) J_1(1,1) J_2(0,1) J_2(1,1)....] - */ - double *Jd=(double*)x; - /* re J(0,0) */ - my_dcopy(N, &x0[0], 8, &Jd[0], 4); - /* im J(0,0) */ - my_dcopy(N, &x0[1], 8, &Jd[1], 4); - /* re J(1,0) */ - my_dcopy(N, &x0[4], 8, &Jd[2], 4); - /* im J(1,0) */ - my_dcopy(N, &x0[5], 8, &Jd[3], 4); - /* re J(0,1) */ - my_dcopy(N, &x0[2], 8, &Jd[4*N], 4); - /* im J(0,1) */ - my_dcopy(N, &x0[3], 8, &Jd[4*N+1], 4); - /* re J(1,1) */ - my_dcopy(N, &x0[6], 8, &Jd[4*N+2], 4); - /* im J(1,1) */ - my_dcopy(N, &x0[7], 8, &Jd[4*N+3], 4); - - /* reshape Y and BZ to form complex double */ - complex double *Yd, *Zd; - if ((Yd=(complex double*)malloc((size_t)4*N*sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((Zd=(complex double*)malloc((size_t)4*N*sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - double *YY=(double*)Yd; - double *ZZ=(double*)Zd; - my_dcopy(N, &Y[0], 8, &YY[0], 4); - my_dcopy(N, &Y[1], 8, &YY[1], 4); - my_dcopy(N, &Y[4], 8, &YY[2], 4); - my_dcopy(N, &Y[5], 8, &YY[3], 4); - my_dcopy(N, &Y[2], 8, &YY[4*N], 4); - my_dcopy(N, &Y[3], 8, &YY[4*N+1], 4); - my_dcopy(N, &Y[6], 8, &YY[4*N+2], 4); - my_dcopy(N, &Y[7], 8, &YY[4*N+3], 4); - my_dcopy(N, &BZ[0], 8, &ZZ[0], 4); - my_dcopy(N, &BZ[1], 8, &ZZ[1], 4); - my_dcopy(N, &BZ[4], 8, &ZZ[2], 4); - my_dcopy(N, &BZ[5], 8, &ZZ[3], 4); - my_dcopy(N, &BZ[2], 8, &ZZ[4*N], 4); - my_dcopy(N, &BZ[3], 8, &ZZ[4*N+1], 4); - my_dcopy(N, &BZ[6], 8, &ZZ[4*N+2], 4); - my_dcopy(N, &BZ[7], 8, &ZZ[4*N+3], 4); - - - - int Nt=adata->Nt; - int ci; - global_data_rtr_t gdata; - - gdata.Y=Yd; - gdata.BZ=Zd; - gdata.admm_rho=admm_rho; - - gdata.medata=adata; - /* setup threads */ - pthread_attr_init(&gdata.attr); - pthread_attr_setdetachstate(&gdata.attr,PTHREAD_CREATE_JOINABLE); - - if ((gdata.th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - if ((gdata.mx_array=(pthread_mutex_t*)malloc((size_t)N*sizeof(pthread_mutex_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((gdata.iw=(double*)malloc((size_t)N*sizeof(double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - /* weights for robust LS, length could be less than total no of baselines - therefore use relative offset boff */ - if ((gdata.wtd=(double*)malloc((size_t)M*sizeof(double)))==0) { -#ifndef USE_MIC - printf("%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - - for (ci=0; cistation contributions - NOTE: has to be done here because the baseline offset would change */ - fns_fcount(&gdata); -/***************************************************/ - int min_inner,max_inner,min_outer,max_outer; - double epsilon,kappa,theta,rho_prime; - /* - min_inner = 0; - max_inner = inf; - min_outer = 3; - max_outer = 100; - epsilon = 1e-6; - kappa = 0.1; - theta = 1.0; - rho_prime = 0.1; - %Delta_bar = user must specify - %Delta0 = user must specify - %x0 = user must specify - */ - min_inner=1; max_inner=itmax_rtr;//8*N; - min_outer=3; max_outer=itmax_rtr; - epsilon=CLM_EPSILON; - kappa=0.1; - theta=1.0; - /* default values 0.25, 0.75, 0.25, 2.0 */ - double eta1=0.0001; double eta2=0.99; double alpha1=0.25; double alpha2=3.5; - rho_prime=eta1; /* default 0.1 should be <= 0.25 */ - double rho_regularization; /* use large damping (but less than GPU version) */ - double rho_reg; - int model_decreased=0; - - complex double *fgradx,*eta,*Heta,*x_prop; - if ((fgradx=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((eta=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((Heta=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((x_prop=(complex double*)calloc((size_t)4*N,sizeof(complex double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - /*set initial weights to 1 */ - setweights(M,gdata.wtd,1.0,Nt); - gdata.nulow=robust_nulow; - gdata.nuhigh=robust_nuhigh; - - double fx,norm_grad,Delta,fx_prop,rhonum,rhoden,rho; - fx=fns_f(x,y,&gdata); - double fx0=fx; - int rsdstat=0; -/***************************************************/ - /* RSD solution */ - //for (ci=0; cirobust_nu,robust_nu1); - adata->robust_nu=robust_nu1; -/***************************************************/ - /* - % initialize counters/sentinals - % allocate storage for dist, counters - k = 0; % counter for outer (TR) iteration. - stop_outer = 0; % stopping criterion for TR. - - x = x0; -fx = fns.f(x); -fgradx = fns.fgrad(x); -norm_grad = sqrt(fns.g(x,fgradx,fgradx)); - -% initialize trust-region radius -Delta = Delta0; - */ - int k=0; - int stop_outer=(itmax_rtr>0?0:1); - int stop_inner=0; - // x0 is already copied to x: my_ccopy(4*N,x0,1,x,1); - if(!stop_outer) { - fns_fgrad(x,fgradx,y,&gdata,1); - norm_grad = sqrt(fns_g(N,x,fgradx,fgradx)); - } - Delta=Delta0; - - /* initial residual */ - info[0]=fx; - - /* - % ** Start of TR loop ** - while stop_outer==0, - */ - while(!stop_outer) { - /* - % update counter - k = k+1; - */ - k++; - - - /* - ** Begin TR Subproblem ** - % determine eta0 - % without randT, 0*fgradx is the only way that we - % know how to create a tangent vector - eta = 0*fgradx; - */ - memset(eta,0,sizeof(complex double)*N*4); - - - /* - % solve TR subproblem - [eta,numit,stop_inner] = tCG(fns,x,fgradx,eta,Delta,theta,kappa,min_inner,max_inner,useRand,debug); - */ - stop_inner=tcg_solve(N, x, fgradx, eta, Heta, Delta, theta, kappa, max_inner, min_inner,y,&gdata); - - /* - norm_eta = sqrt(fns.g(x,eta,eta)); - */ - - /* - Heta = fns.fhess(x,eta); - */ - //OLD fns_fhess(x,eta,Heta,y,&gdata); - - /* - % compute the retraction of the proposal - x_prop = fns.R(x,eta); - */ - fns_R(N,x,eta,x_prop); - - /* - % compute function value of the proposal - fx_prop = fns.f(x_prop); - */ - fx_prop=fns_f(x_prop,y,&gdata); - - /* - % do we accept the proposed solution or not? - % compute the Hessian at the proposal - Heta = fns.fhess(x,eta); - FIXME: do we need to do this, because Heta is already there - or change x to x_prop ??? - */ - //Disabled fns_fhess(x,eta,Heta,y,&gdata); - - /* - % check the performance of the quadratic model - rhonum = fx-fx_prop; - rhoden = -fns.g(x,fgradx,eta) - 0.5*fns.g(x,Heta,eta); - */ - rhonum=fx-fx_prop; - rhoden=-fns_g(N,x,fgradx,eta)-0.5*fns_g(N,x,Heta,eta); - /* regularization of rho ratio */ - /* - rho_reg = max(1, abs(fx)) * eps * options.rho_regularization; - rhonum = rhonum + rho_reg; - rhoden = rhoden + rho_reg; - */ - rho_reg=MAX(1.0,fx)*rho_regularization; /* no epsilon */ - rhonum+=rho_reg; - rhoden+=rho_reg; - - - /* - rho = rhonum / rhoden; - */ - rho=rhonum/rhoden; - - - - /* - % HEURISTIC WARNING: - % if abs(model change) is relatively zero, we are probably near a critical - % point. set rho to 1. - if abs(rhonum/fx) < sqrt(eps), - small_rhonum = rhonum; - rho = 1; - else - small_rhonum = 0; - end - FIXME: use constant for sqrt(eps) - */ - /* OLD CODE if (fabs(rhonum/fx) = 0); */ - model_decreased=(rhoden>=0.0?1:0); - - /* NOTE: if too many values of rho are -ve, it means TR radius is too big - so initial TR radius should be reduced */ -#ifdef DEBUG - printf("stop_inner=%d rho_reg=%g rho =%g/%g= %g rho'= %g\n",stop_inner,rho_reg,rhonum,rhoden,rho,rho_prime); -#endif - - /* - % choose new TR radius based on performance - if rho < 1/4 - Delta = 1/4*Delta; - elseif rho > 3/4 && (stop_inner == 2 || stop_inner == 1), - Delta = min(2*Delta,Delta_bar); - end - */ - if (!model_decreased || rhoeta2 && (stop_inner==2 || stop_inner==1)) { - /* we have a good reduction, so increase TR radius */ - Delta=MIN(alpha2*Delta,Delta_bar); - } - - /* - % choose new iterate based on performance - oldgradx = fgradx; - if rho > rho_prime, - accept = true; - x = x_prop; - fx = fx_prop; - fgradx = fns.fgrad(x); - norm_grad = sqrt(fns.g(x,fgradx,fgradx)); - else - accept = false; - end - */ - if (model_decreased && rho>rho_prime) { - my_ccopy(4*N,x_prop,1,x,1); - fx=fx_prop; - fns_fgrad(x,fgradx,y,&gdata,1); - norm_grad=sqrt(fns_g(N,x,fgradx,fgradx)); - } - - - /* - % ** Testing for Stop Criteria - % min_outer is the minimum number of inner iterations - % before we can exit. this gives randomization a chance to - % escape a saddle point. - if norm_grad < epsilon && (~useRand || k > min_outer), - stop_outer = 1; - end - */ - if (norm_gradmin_outer) { - stop_outer=1; - } - - - /* - % stop after max_outer iterations - if k >= max_outer, - if (verbosity > 0), - fprintf('\n*** timed out -- k == %d***\n',k); - end - stop_outer = 1; - end - */ - if (k>=max_outer) { - stop_outer=1; - } - -#ifdef DEBUG - printf("Iter %d cost=%lf\n",k,fx); -#endif - /* end of TR loop (counter: k) */ - } - - /* final residual */ - info[1]=fx; - - free(fgradx); - free(eta); - free(Heta); - free(x_prop); -/***************************************************/ - robust_nu1=fns_fupdate_weights(x,y,&gdata); - adata->robust_nu=robust_nu1; - if (fx0>fx) { - /* copy back solution to x0 */ - /* re J(0,0) */ - my_dcopy(N, &Jd[0], 4, &x0[0], 8); - /* im J(0,0) */ - my_dcopy(N, &Jd[1], 4, &x0[1], 8); - /* re J(1,0) */ - my_dcopy(N, &Jd[2], 4, &x0[4], 8); - /* im J(1,0) */ - my_dcopy(N, &Jd[3], 4, &x0[5], 8); - /* re J(0,1) */ - my_dcopy(N, &Jd[4*N], 4, &x0[2], 8); - /* im J(0,1) */ - my_dcopy(N, &Jd[4*N+1], 4, &x0[3], 8); - /* re J(1,1) */ - my_dcopy(N, &Jd[4*N+2], 4, &x0[6], 8); - /* im J(1,1) */ - my_dcopy(N, &Jd[4*N+3], 4, &x0[7], 8); - } - - for (ci=0; ci - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#include -#include -#include -#include -#include - -#include "sagecal.h" -#include - -//#define DEBUG -/* helper functions for diagnostics */ -static void -checkCudaError(cudaError_t err, char *file, int line) -{ -#ifdef CUDA_DEBUG - if(!err) - return; - fprintf(stderr,"GPU (CUDA): %s %s %d\n", cudaGetErrorString(err),file,line); - exit(EXIT_FAILURE); -#endif -} - - -static void -checkCublasError(cublasStatus_t cbstatus, char *file, int line) -{ -#ifdef CUDA_DEBUG - if (cbstatus!=CUBLAS_STATUS_SUCCESS) { - fprintf(stderr,"%s: %d: CUBLAS failure\n",file,line); - exit(EXIT_FAILURE); - } -#endif -} - - - -/* gradient, also projected to tangent space */ -/* for many time samples, gradient for each time sample is projected - to tangent space before it is averaged - so calculate grad using N(N-1)/2 constraints each (total M) -*/ -/* need 8N*M/ThreadsPerBlock+ 8N complex float storage - so actual float is 2 x this value */ -static void -cudakernel_fns_fgrad_robust(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, float *y, float *coh, short *bbh, float *iw, float *wtd, int negate, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle) { - /* baselines per timeslot = N(N-1)/2 ~2400, timeslots = M/baselines ~120 - blocks per timeslot = baselines/ThreadsPerBlock ~2400/120=20 - so total blocks ~20x120=2400 - - each block needs 8*N global storage - */ - cuFloatComplex *tempeta; - cublasStatus_t cbstatus=CUBLAS_STATUS_SUCCESS; - cuFloatComplex alpha; - cudaMalloc((void**)&tempeta, sizeof(cuFloatComplex)*4*N); - - /*************************/ - /* find A=I_2 kron (X^H X) + (X^H X)^T kron I_2 - and find inv(A) by solving A x B = I_4 - use temp storage - */ - /* find X^H X */ - cuFloatComplex xx00,xx01,xx10,xx11; - cbstatus=cublasCdotc(cbhandle,2*N,x,1,x,1,&xx00); - cbstatus=cublasCdotc(cbhandle,2*N,x,1,&x[2*N],1,&xx01); - xx10=cuConjf(xx01); - cbstatus=cublasCdotc(cbhandle,2*N,&x[2*N],1,&x[2*N],1,&xx11); - - cuFloatComplex A[16],*Ad,B[16],*Bd; - A[0]=cuCmulf(make_cuFloatComplex(2.0f,0.0f),xx00); - A[5]=A[10]=cuCaddf(xx00,xx11); - A[15]=cuCmulf(make_cuFloatComplex(2.0f,0.0f),xx11); - A[1]=A[8]=A[11]=A[13]=xx10; - A[2]=A[4]=A[7]=A[14]=xx01; - A[3]=A[6]=A[9]=A[12]=make_cuFloatComplex(0.0f,0.0f); - - B[0]=B[5]=B[10]=B[15]=make_cuFloatComplex(1.0f,0.0f); - B[1]=B[2]=B[3]=B[4]=B[6]=B[7]=B[8]=B[9]=B[11]=B[12]=B[13]=B[14]=make_cuFloatComplex(0.0f,0.0f); - -#ifdef DEBUG - printf("A=[\n"); - printf("%f+j*(%f) %f+j*(%f) %f+j*(%f) %f+j*(%f)\n",A[0].x,A[0].y,A[4].x,A[4].y,A[8].x,A[8].y,A[12].x,A[12].y); - printf("%f+j*(%f) %f+j*(%f) %f+j*(%f) %f+j*(%f)\n",A[1].x,A[1].y,A[5].x,A[5].y,A[9].x,A[9].y,A[13].x,A[13].y); - printf("%f+j*(%f) %f+j*(%f) %f+j*(%f) %f+j*(%f)\n",A[2].x,A[2].y,A[6].x,A[6].y,A[10].x,A[10].y,A[14].x,A[14].y); - printf("%f+j*(%f) %f+j*(%f) %f+j*(%f) %f+j*(%f)\n",A[3].x,A[3].y,A[7].x,A[7].y,A[11].x,A[11].y,A[15].x,A[15].y); - printf("];\n"); -#endif - - - cudaMalloc((void **)&Ad, 16*sizeof(cuFloatComplex)); - cudaMalloc((void **)&Bd, 16*sizeof(cuFloatComplex)); - - cudaMemcpy(Ad,A,16*sizeof(cuFloatComplex),cudaMemcpyHostToDevice); - cudaMemcpy(Bd,B,16*sizeof(cuFloatComplex),cudaMemcpyHostToDevice); - - int work_size=0; - int *devInfo; - cudaError_t err; - err=cudaMalloc((void**)&devInfo, sizeof(int)); /* FIXME: get too many errors here */ - checkCudaError(err,__FILE__,__LINE__); - cuFloatComplex *work,*taud; - cusolverDnCgeqrf_bufferSize(solver_handle, 4, 4, (cuFloatComplex *)Ad, 4, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(cuFloatComplex)); - err=cudaMalloc((void**)&taud, 4*sizeof(cuFloatComplex)); - checkCudaError(err,__FILE__,__LINE__); - cusolverDnCgeqrf(solver_handle, 4, 4, Ad, 4, taud, work, work_size, devInfo); - cusolverDnCunmqr(solver_handle, CUBLAS_SIDE_LEFT, CUBLAS_OP_C, 4, 4, 4, Ad, 4, taud, Bd, 4, work, work_size, devInfo); - cuFloatComplex cone; cone.x=1.0f; cone.y=0.0f; - cbstatus=cublasCtrsm(cbhandle,CUBLAS_SIDE_LEFT,CUBLAS_FILL_MODE_UPPER,CUBLAS_OP_N,CUBLAS_DIAG_NON_UNIT,4,4,&cone,Ad,4,Bd,4); - - - cudaFree(work); - cudaFree(taud); - cudaFree(devInfo); - - - cudaFree(Ad); - -#ifdef DEBUG - /* copy back the result */ - cudaMemcpy(B,Bd,16*sizeof(cuFloatComplex),cudaMemcpyDeviceToHost); - printf("B=[\n"); - printf("%f+j*(%f) %f+j*(%f) %f+j*(%f) %f+j*(%f)\n",B[0].x,B[0].y,B[4].x,B[4].y,B[8].x,B[8].y,B[12].x,B[12].y); - printf("%f+j*(%f) %f+j*(%f) %f+j*(%f) %f+j*(%f)\n",B[1].x,B[1].y,B[5].x,B[5].y,B[9].x,B[9].y,B[13].x,B[13].y); - printf("%f+j*(%f) %f+j*(%f) %f+j*(%f) %f+j*(%f)\n",B[2].x,B[2].y,B[6].x,B[6].y,B[10].x,B[10].y,B[14].x,B[14].y); - printf("%f+j*(%f) %f+j*(%f) %f+j*(%f) %f+j*(%f)\n",B[3].x,B[3].y,B[7].x,B[7].y,B[11].x,B[11].y,B[15].x,B[15].y); - printf("];\n"); -#endif - - - /*************************/ - /* baselines */ - int nbase=N*(N-1)/2; - /* timeslots */ - int ntime=(M+nbase-1)/nbase; - /* blocks per timeslot */ - /* total blocks is Bt x ntime */ - int Bt=(nbase+ThreadsPerBlock-1)/ThreadsPerBlock; - - -#ifdef DEBUG -printf("N=%d Baselines=%d timeslots=%d total=%d,Threads=%d Blocks=%d\n",N,nbase,ntime,M,ThreadsPerBlock,Bt*ntime); -#endif - - /* max size of M for one kernel call, to determine optimal blocks */ - cudakernel_fns_fgradflat_robust(ThreadsPerBlock, Bt*ntime, N, M, x, tempeta, y, coh, bbh, wtd, Bd, cbhandle,solver_handle); - /* weight for missing (flagged) baselines */ - cudakernel_fns_fscale(N, tempeta, iw); - /* find -ve gradient */ - if (negate) { - alpha.x=-1.0f;alpha.y=0.0f; - cbstatus=cublasCscal(cbhandle,4*N,&alpha,tempeta,1); - } - cudaMemcpy(eta,tempeta,4*N*sizeof(cuFloatComplex),cudaMemcpyDeviceToDevice); - checkCublasError(cbstatus,__FILE__,__LINE__); - cudaFree(Bd); - cudaFree(tempeta); -} - -/* Hessian, also projected to tangent space */ -/* for many time samples, gradient for each time sample is projected - to tangent space before it is averaged - so calculate grad using N(N-1)/2 constraints each (total M) -*/ -/* need 8N*M/ThreadsPerBlock+ 8N float storage */ -static void -cudakernel_fns_fhess_robust(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, cuFloatComplex *fhess, float *y, float *coh, short *bbh, float *iw, float *wtd, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle) { - cuFloatComplex *tempeta; - cublasStatus_t cbstatus=CUBLAS_STATUS_SUCCESS; - cudaMalloc((void**)&tempeta, sizeof(cuFloatComplex)*4*N); - - /*************************/ - /* find A=I_2 kron (X^H X) + (X^H X)^T kron I_2 - and find inv(A) by solving A x B = I_4 - use temp storage - */ - /* find X^H X */ - cuFloatComplex xx00,xx01,xx10,xx11; - cbstatus=cublasCdotc(cbhandle,2*N,x,1,x,1,&xx00); - cbstatus=cublasCdotc(cbhandle,2*N,x,1,&x[2*N],1,&xx01); - xx10=cuConjf(xx01); - cbstatus=cublasCdotc(cbhandle,2*N,&x[2*N],1,&x[2*N],1,&xx11); - - cuFloatComplex A[16],*Ad,B[16],*Bd; - A[0]=cuCmulf(make_cuFloatComplex(2.0f,0.0f),xx00); - A[5]=A[10]=cuCaddf(xx00,xx11); - A[15]=cuCmulf(make_cuFloatComplex(2.0f,0.0f),xx11); - A[1]=A[8]=A[11]=A[13]=xx10; - A[2]=A[4]=A[7]=A[14]=xx01; - A[3]=A[6]=A[9]=A[12]=make_cuFloatComplex(0.0f,0.0f); - - B[0]=B[5]=B[10]=B[15]=make_cuFloatComplex(1.0f,0.0f); - B[1]=B[2]=B[3]=B[4]=B[6]=B[7]=B[8]=B[9]=B[11]=B[12]=B[13]=B[14]=make_cuFloatComplex(0.0f,0.0f); - -#ifdef DEBUG - printf("A=[\n"); - printf("%f+j*(%f) %f+j*(%f) %f+j*(%f) %f+j*(%f)\n",A[0].x,A[0].y,A[4].x,A[4].y,A[8].x,A[8].y,A[12].x,A[12].y); - printf("%f+j*(%f) %f+j*(%f) %f+j*(%f) %f+j*(%f)\n",A[1].x,A[1].y,A[5].x,A[5].y,A[9].x,A[9].y,A[13].x,A[13].y); - printf("%f+j*(%f) %f+j*(%f) %f+j*(%f) %f+j*(%f)\n",A[2].x,A[2].y,A[6].x,A[6].y,A[10].x,A[10].y,A[14].x,A[14].y); - printf("%f+j*(%f) %f+j*(%f) %f+j*(%f) %f+j*(%f)\n",A[3].x,A[3].y,A[7].x,A[7].y,A[11].x,A[11].y,A[15].x,A[15].y); - printf("];\n"); -#endif - - - cudaMalloc((void **)&Ad, 16*sizeof(cuFloatComplex)); - cudaMalloc((void **)&Bd, 16*sizeof(cuFloatComplex)); - - cudaMemcpy(Ad,A,16*sizeof(cuFloatComplex),cudaMemcpyHostToDevice); - cudaMemcpy(Bd,B,16*sizeof(cuFloatComplex),cudaMemcpyHostToDevice); - //culaStatus status; - //status=culaDeviceCgels('N',4,4,4,(culaDeviceFloatComplex *)Ad,4,(culaDeviceFloatComplex *)Bd,4); - //checkStatus(status,__FILE__,__LINE__); - int work_size=0; - int *devInfo; - cudaError_t err; - err=cudaMalloc((void**)&devInfo, sizeof(int)); - checkCudaError(err,__FILE__,__LINE__); - cuFloatComplex *work,*taud; - cusolverDnCgeqrf_bufferSize(solver_handle, 4, 4, (cuFloatComplex *)Ad, 4, &work_size); - err=cudaMalloc((void**)&work, work_size*sizeof(cuFloatComplex)); - err=cudaMalloc((void**)&taud, 4*sizeof(cuFloatComplex)); - checkCudaError(err,__FILE__,__LINE__); - cusolverDnCgeqrf(solver_handle, 4, 4, Ad, 4, taud, work, work_size, devInfo); - cusolverDnCunmqr(solver_handle, CUBLAS_SIDE_LEFT, CUBLAS_OP_C, 4, 4, 4, Ad, 4, taud, Bd, 4, work, work_size, devInfo); - cuFloatComplex cone; cone.x=1.0f; cone.y=0.0f; - cbstatus=cublasCtrsm(cbhandle,CUBLAS_SIDE_LEFT,CUBLAS_FILL_MODE_UPPER,CUBLAS_OP_N,CUBLAS_DIAG_NON_UNIT,4,4,&cone,Ad,4,Bd,4); - - - cudaFree(work); - cudaFree(taud); - cudaFree(devInfo); - cudaFree(Ad); - -#ifdef DEBUG - /* copy back the result */ - cudaMemcpy(B,Bd,16*sizeof(cuFloatComplex),cudaMemcpyDeviceToHost); - printf("B=[\n"); - printf("%f+j*(%f) %f+j*(%f) %f+j*(%f) %f+j*(%f)\n",B[0].x,B[0].y,B[4].x,B[4].y,B[8].x,B[8].y,B[12].x,B[12].y); - printf("%f+j*(%f) %f+j*(%f) %f+j*(%f) %f+j*(%f)\n",B[1].x,B[1].y,B[5].x,B[5].y,B[9].x,B[9].y,B[13].x,B[13].y); - printf("%f+j*(%f) %f+j*(%f) %f+j*(%f) %f+j*(%f)\n",B[2].x,B[2].y,B[6].x,B[6].y,B[10].x,B[10].y,B[14].x,B[14].y); - printf("%f+j*(%f) %f+j*(%f) %f+j*(%f) %f+j*(%f)\n",B[3].x,B[3].y,B[7].x,B[7].y,B[11].x,B[11].y,B[15].x,B[15].y); - printf("];\n"); -#endif - /*************************/ - - /* baselines */ - int nbase=N*(N-1)/2; - /* timeslots */ - int ntime=(M+nbase-1)/nbase; - /* blocks per timeslot */ - /* total blocks is Bt x ntime */ - int Bt=(nbase+ThreadsPerBlock-1)/ThreadsPerBlock; - - -#ifdef DEBUG -printf("N=%d Baselines=%d timeslots=%d total=%d,Threads=%d Blocks=%d\n",N,nbase,ntime,M,ThreadsPerBlock,Bt*ntime); -#endif - - - cudakernel_fns_fhessflat_robust(ThreadsPerBlock, Bt*ntime, N, M, x, eta, tempeta, y, coh, bbh, wtd, Bd, cbhandle, solver_handle); - - cudakernel_fns_fscale(N, tempeta, iw); - cudaMemcpy(fhess,tempeta,4*N*sizeof(cuFloatComplex),cudaMemcpyDeviceToDevice); - checkCublasError(cbstatus,__FILE__,__LINE__); - cudaFree(Bd); - cudaFree(tempeta); -} - - -/* Fine tune initial trust region radius, also update initial value for x - A. Sartenaer, 1995 - returns : trust region estimate, - also modifies x - eta,Heta: used as storage - */ -/* need 8N*2 + MAX(2 Blocks + 4, 8N (1 + ceil(M/Threads))) float storage */ -static float -itrr(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, cuFloatComplex *Heta, float *y, float *coh, short *bbh, float *iw, float *wtd, cublasHandle_t cbhandle,cusolverDnHandle_t solver_handle) { - cuFloatComplex alpha; - cublasStatus_t cbstatus=CUBLAS_STATUS_SUCCESS; - /* temp storage, re-using global storage */ - cuFloatComplex *s, *x_prop; - cudaMalloc((void**)&s, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&x_prop, sizeof(cuFloatComplex)*4*N); - - float f0,fk,mk,rho,rho1,Delta0; - /* initialize trust region radii */ - float delta_0=1.0f; - float delta_m=0.0f; - - float sigma=0.0f; - float delta=0.0f; - - // initial cost - f0=cudakernel_fns_f_robust(ThreadsPerBlock,BlocksPerGrid,N,M,x,y,coh,bbh,wtd); - // gradient at x0; - cudakernel_fns_fgrad_robust(ThreadsPerBlock,BlocksPerGrid,N,M,x,eta,y,coh,bbh,iw,wtd,1,cbhandle, solver_handle); - // normalize - float eta_nrm; - cublasScnrm2(cbhandle,4*N,eta,1,&eta_nrm); - alpha.x=1.0f/eta_nrm;alpha.y=0.0f; - cbstatus=cublasCscal(cbhandle,4*N,&alpha,eta,1); - - cbstatus=cublasCcopy(cbhandle,4*N,eta,1,s,1); - alpha.x=delta_0;alpha.y=0.0f; - cbstatus=cublasCscal(cbhandle,4*N,&alpha,s,1); - /* Hessian at s */ - cudakernel_fns_fhess_robust(ThreadsPerBlock,BlocksPerGrid,N,M,x,s,Heta,y,coh,bbh,iw,wtd,cbhandle,solver_handle); - - /* constants used */ - float gamma_1=0.0625f; float gamma_2=5.0f; float gamma_3=0.5f; float gamma_4=2.0f; - float mu_0=0.5f; float mu_1=0.5f; float mu_2=0.35f; - float teta=0.25f; - - - int MK=4; - int m; - for (m=0; mdelta) { - delta=f0-fk; - sigma=delta_0; - } - /* radius update */ - float beta_1,beta_2,beta_i; - beta_1=0.0f; - beta_2=0.0f; - - if (mmu_1) { - if (minbeta>1.0f) { - beta_i=gamma_3; - } else if ((maxbeta=1.0f)) { - beta_i=gamma_1; - } else if ((beta_1>=gamma_1 && beta_1<1.0f) && (beta_2=1.0f)) { - beta_i=beta_1; - } else if ((beta_2>=gamma_1 && beta_2<1.0f) && (beta_1=1.0f)) { - beta_i=beta_2; - } else { - beta_i=maxbeta; - } - } else if (rho1<=mu_2) { - if (maxbeta<1.0f) { - beta_i=gamma_4; - } else if (maxbeta>gamma_2) { - beta_i=gamma_2; - } else if ((beta_1>=1.0f && beta_1<=gamma_2) && beta_2<1.0f) { - beta_i=beta_1; - } else if ((beta_2>=1.0f && beta_2<=gamma_2) && beta_1<1.0f) { - beta_i=beta_2; - } else { - beta_i=maxbeta; - } - } else { - if (maxbetagamma_4) { - beta_i=gamma_4; - } else { - beta_i=maxbeta; - } - } - /* update radius */ - delta_0=delta_0/beta_i; - } -#ifdef DEBUG -printf("m=%d delta_0=%e delta_max=%e beta=%e rho=%e\n",m,delta_0,delta_m,beta_i,rho); -#endif - - cbstatus=cublasCcopy(cbhandle,4*N,eta,1,s,1); - alpha.x=delta_0;alpha.y=0.0f; - cbstatus=cublasCscal(cbhandle,4*N,&alpha,s,1); - } - - // update initial value - if (delta>0.0f) { - alpha.x=-sigma; alpha.y=0.0f; - cbstatus=cublasCaxpy(cbhandle,4*N, &alpha, eta, 1, x, 1); - } - - if (delta_m>0.0f) { - Delta0=delta_m; - } else { - Delta0=delta_0; - } - - checkCublasError(cbstatus,__FILE__,__LINE__); - cudaFree(s); - cudaFree(x_prop); - return Delta0; -} - - -/* truncated conjugate gradient method - x, grad, eta, r, z, delta, Hxd : size 2N x 2 complex - so, vector size is 4N complex double - - output: eta - return value: stop_tCG code - - y: vec(V) visibilities -*/ -/* need 8N*(BlocksPerGrid+2)+ 8N*6 float storage */ -static int -tcg_solve_cuda(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *grad, cuFloatComplex *eta, cuFloatComplex *fhess, float Delta, float theta, float kappa, int max_inner, int min_inner, float *y, float *coh, short *bbh, float *iw, float *wtd, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle) { - cuFloatComplex *r,*z,*delta,*Hxd, *rnew; - float e_Pe, r_r, norm_r, z_r, d_Pd, d_Hd, alpha, e_Pe_new, - e_Pd, Deltasq, tau, zold_rold, beta, norm_r0; - int cj, stop_tCG; - - cudaMalloc((void**)&r, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&z, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&delta, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&Hxd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&rnew, sizeof(cuFloatComplex)*4*N); - - cublasStatus_t cbstatus=CUBLAS_STATUS_SUCCESS; - cuFloatComplex a0; - - /* - initial values - */ - cbstatus=cublasCcopy(cbhandle,4*N,grad,1,r,1); - e_Pe=0.0f; - - - r_r=cudakernel_fns_g(N,x,r,r,cbhandle,solver_handle); - norm_r=sqrtf(r_r); - norm_r0=norm_r; - - cbstatus=cublasCcopy(cbhandle,4*N,r,1,z,1); - - z_r=cudakernel_fns_g(N,x,z,r,cbhandle,solver_handle); - d_Pd=z_r; - - /* - initial search direction - */ - cudaMemset(delta, 0, sizeof(cuFloatComplex)*4*N); - a0.x=-1.0f; a0.y=0.0f; - cbstatus=cublasCaxpy(cbhandle,4*N, &a0, z, 1, delta, 1); - e_Pd=cudakernel_fns_g(N,x,eta,delta,cbhandle,solver_handle); - - stop_tCG=5; - - /* % begin inner/tCG loop - for j = 1:max_inner, - */ - for(cj=1; cj<=max_inner; cj++) { - cudakernel_fns_fhess_robust(ThreadsPerBlock,BlocksPerGrid,N,M,x,delta,Hxd,y,coh,bbh,iw,wtd,cbhandle,solver_handle); - d_Hd=cudakernel_fns_g(N,x,delta,Hxd,cbhandle,solver_handle); - - alpha=z_r/d_Hd; - e_Pe_new = e_Pe + 2.0f*alpha*e_Pd + alpha*alpha*d_Pd; - - - Deltasq=Delta*Delta; - if (d_Hd <= 0.0f || e_Pe_new >= Deltasq) { - tau = (-e_Pd + sqrtf(e_Pd*e_Pd + d_Pd*(Deltasq-e_Pe)))/d_Pd; - a0.x=tau; - cbstatus=cublasCaxpy(cbhandle,4*N, &a0, delta, 1, eta, 1); - /* Heta = Heta + tau *Hdelta */ - cbstatus=cublasCaxpy(cbhandle,4*N, &a0, Hxd, 1, fhess, 1); - stop_tCG=(d_Hd<=0.0f?1:2); - break; - } - - e_Pe=e_Pe_new; - a0.x=alpha; - cbstatus=cublasCaxpy(cbhandle,4*N, &a0, delta, 1, eta, 1); - /* Heta = Heta + alpha*Hdelta */ - cbstatus=cublasCaxpy(cbhandle,4*N, &a0, Hxd, 1, fhess, 1); - - cbstatus=cublasCaxpy(cbhandle,4*N, &a0, Hxd, 1, r, 1); - cudakernel_fns_proj(N, x, r, rnew, cbhandle, solver_handle); - cbstatus=cublasCcopy(cbhandle,4*N,rnew,1,r,1); - r_r=cudakernel_fns_g(N,x,r,r,cbhandle,solver_handle); - norm_r=sqrtf(r_r); - - /* - check kappa/theta stopping criterion - */ - if (cj >= min_inner) { - float norm_r0pow=powf(norm_r0,theta); - if (norm_r <= norm_r0*MIN(norm_r0pow,kappa)) { - stop_tCG=(kappaNbase)*(ntiles); /* note: we do not use the total tile size */ - /* coherency on device */ - float *cohd; - /* baseline-station map on device/host */ - short *bbd; - - /* calculate no of cuda threads and blocks */ - int ThreadsPerBlock=DEFAULT_TH_PER_BK; - int BlocksPerGrid=(M+ThreadsPerBlock-1)/ThreadsPerBlock; - - - /* reshape x to make J: 2Nx2 complex double - */ - complex float *x; - if ((x=(complex float*)malloc((size_t)4*N*sizeof(complex float)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - /* map x: [(re,im)J_1(0,0) (re,im)J_1(0,1) (re,im)J_1(1,0) (re,im)J_1(1,1)...] - to - J: [J_1(0,0) J_1(1,0) J_2(0,0) J_2(1,0) ..... J_1(0,1) J_1(1,1) J_2(0,1) J_2(1,1)....] - */ - float *Jd=(float*)x; - /* re J(0,0) */ - my_fcopy(N, &x0[0], 8, &Jd[0], 4); - /* im J(0,0) */ - my_fcopy(N, &x0[1], 8, &Jd[1], 4); - /* re J(1,0) */ - my_fcopy(N, &x0[4], 8, &Jd[2], 4); - /* im J(1,0) */ - my_fcopy(N, &x0[5], 8, &Jd[3], 4); - /* re J(0,1) */ - my_fcopy(N, &x0[2], 8, &Jd[4*N], 4); - /* im J(0,1) */ - my_fcopy(N, &x0[3], 8, &Jd[4*N+1], 4); - /* re J(1,1) */ - my_fcopy(N, &x0[6], 8, &Jd[4*N+2], 4); - /* im J(1,1) */ - my_fcopy(N, &x0[7], 8, &Jd[4*N+3], 4); - - - int ci; - -/***************************************************/ - cuFloatComplex *xd,*fgradxd,*etad,*Hetad,*x_propd; - float *yd; - float *wtd,*qd; /* for robust weight and log(weight) */ - float robust_nu=(float)dp->robust_nu; - float q_sum,robust_nu1; - float deltanu; - int Nd=100; /* no of points where nu is sampled, note NdM) { Nd=M; } - deltanu=(float)(robust_nuhigh-robust_nulow)/(float)Nd; - - /* for counting how many baselines contribute to each station - grad/hess calculation */ - float *iwd,*iw; - if ((iw=(float*)malloc((size_t)N*sizeof(float)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - cudaMalloc((void**)&fgradxd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&etad, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&Hetad, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&x_propd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&xd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&yd, sizeof(float)*8*M); - cudaMalloc((void**)&cohd, sizeof(float)*8*Nbase); - cudaMalloc((void**)&bbd, sizeof(short)*2*Nbase); - cudaMalloc((void**)&iwd, sizeof(float)*N); - cudaMalloc((void**)&wtd, sizeof(float)*M); - cudaMalloc((void**)&qd, sizeof(float)*M); - - - /* need 8N*(BlocksPerGrid+8) for tcg_solve+grad/hess storage, - so total storage needed is - 8N*(BlocksPerGrid+8) + 8N*5 + 8*M + 8*Nbase + 2*Nbase + N + M + M - */ - - /* yd <=y : V */ - err=cudaMemcpy(yd, y, 8*M*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - /* need to give right offset for coherencies */ - /* offset: cluster offset+time offset */ - /* C */ - err=cudaMemcpy(cohd, &(dp->ddcohf[(dp->Nbase)*(dp->tilesz)*(dp->clus)*8+(dp->Nbase)*tileoff*8]), Nbase*8*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - /* correct offset for baselines */ - err=cudaMemcpy(bbd, &(dp->ddbase[2*(dp->Nbase)*(tileoff)]), Nbase*2*sizeof(short), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - /* xd <=x : solution */ - err=cudaMemcpy(xd, x, 8*N*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - - float fx,fx0,norm_grad,Delta,fx_prop,rhonum,rhoden,rho; - - /* count how many baselines contribute to each station, store (inverse) in iwd */ - count_baselines(Nbase,N,iw,&(dp->ddbase[2*(dp->Nbase)*(tileoff)]),dp->Nt); - err=cudaMemcpy(iwd, iw, N*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - free(iw); - - /* set initial weights to 1 by a cuda kernel */ - cudakernel_setweights_fl(ThreadsPerBlock, (M+ThreadsPerBlock-1)/ThreadsPerBlock, M, wtd, 1.0f); - fx=cudakernel_fns_f_robust(ThreadsPerBlock,BlocksPerGrid,N,M,xd,yd,cohd,bbd,wtd); - fx0=fx; -#ifdef DEBUG -printf("Initial Cost=%g\n",fx0); -#endif - - float Delta_new=itrr(ThreadsPerBlock, BlocksPerGrid, N, M, xd, etad, Hetad, yd, cohd, bbd, iwd, wtd, cbhandle,solver_handle); - -#ifdef DEBUG - printf("TR radius given=%f est=%f\n",Delta0,Delta_new); -#endif - - //old values - //Delta_bar=MIN(fx,0.01f); - //Delta0=Delta_bar*0.125f; - Delta0=MIN(Delta_new,0.01f); /* need to be more restrictive for EM */ - Delta_bar=Delta0*8.0f; - - cudakernel_fns_fupdate_weights(ThreadsPerBlock,BlocksPerGrid,N,M,xd,yd,cohd,bbd,wtd,robust_nu); -//printf("fx=%g Delta_bar=%g Delta0=%g\n",fx,Delta_bar,Delta0); - -#ifdef DEBUG -printf("NEW RSD cost=%g\n",fx); -#endif -/***************************************************/ - int min_inner,max_inner,min_outer,max_outer; - float epsilon,kappa,theta,rho_prime; - - min_inner=1; max_inner=itmax_rtr;//8*N; - min_outer=3;//itmax_rtr; //3; - max_outer=itmax_rtr; - epsilon=(float)CLM_EPSILON; - kappa=0.1f; - theta=1.0f; - /* default values 0.25, 0.75, 0.25, 2.0 */ - float eta1=0.0001f; float eta2=0.99f; float alpha1=0.25f; float alpha2=3.5f; - rho_prime=eta1; /* should be <= 0.25, tune for parallel solve */ - float rho_regularization; /* use large damping */ - rho_regularization=fx*1e-6f; - /* damping: too small => locally converge, globally diverge - |\ - |\ | \___ - -|\ | \| - \ - - - right damping: locally and globally converge - -|\ - \|\ - \|\ - \____ - - */ - float rho_reg; - int model_decreased=0; - - /* RTR solution */ - int k=0; - int stop_outer=(itmax_rtr>0?0:1); - int stop_inner=0; - if (!stop_outer) { - cudakernel_fns_fgrad_robust(ThreadsPerBlock,BlocksPerGrid,N,M,xd,fgradxd,yd,cohd,bbd,iwd,wtd,1,cbhandle,solver_handle); - norm_grad=sqrtf(cudakernel_fns_g(N,xd,fgradxd,fgradxd,cbhandle,solver_handle)); - } - Delta=Delta0; - /* initial residual */ - info[0]=fx0; - - /* - % ** Start of TR loop ** - */ - while(!stop_outer) { - /* - % update counter - */ - k++; - /* eta = 0*fgradx; */ - cudaMemset(etad, 0, sizeof(cuFloatComplex)*4*N); - - - /* solve TR subproblem, also returns Hessian */ - stop_inner=tcg_solve_cuda(ThreadsPerBlock,BlocksPerGrid, N, M, xd, fgradxd, etad, Hetad, Delta, theta, kappa, max_inner, min_inner,yd,cohd,bbd,iwd,wtd,cbhandle,solver_handle); - /* - Heta = fns.fhess(x,eta); - */ - /* - compute the retraction of the proposal - */ - cudakernel_fns_R(N,xd,etad,x_propd,cbhandle,solver_handle); - - /* - compute cost of the proposal - */ - fx_prop=cudakernel_fns_f_robust(ThreadsPerBlock,BlocksPerGrid,N,M,x_propd,yd,cohd,bbd,wtd); - - /* - check the performance of the quadratic model - */ - rhonum=fx-fx_prop; - rhoden=-cudakernel_fns_g(N,xd,fgradxd,etad,cbhandle,solver_handle)-0.5f*cudakernel_fns_g(N,xd,Hetad,etad,cbhandle,solver_handle); - /* regularization of rho ratio */ - /* - rho_reg = max(1, abs(fx)) * eps * options.rho_regularization; - rhonum = rhonum + rho_reg; - rhoden = rhoden + rho_reg; - */ - rho_reg=MAX(1.0f,fx)*rho_regularization; /* no epsilon */ - rhonum+=rho_reg; - rhoden+=rho_reg; - - /* - rho = rhonum / rhoden; - */ - rho=rhonum/rhoden; - - /* model_decreased = (rhoden >= 0); */ - /* OLD CODE if (fabsf(rhonum/fx) =0.0f?1:0); - -#ifdef DEBUG - printf("stop_inner=%d rho_reg=%g rho =%g/%g= %g rho'= %g\n",stop_inner,rho_reg,rhonum,rhoden,rho,rho_prime); -#endif - /* - choose new TR radius based on performance - */ - if ( !model_decreased || rhoeta2 && (stop_inner==2 || stop_inner==1)) { - Delta=MIN(alpha2*Delta,Delta_bar); - } - - /* - choose new iterate based on performance - */ - if (model_decreased && rho>rho_prime) { - cbstatus=cublasCcopy(cbhandle,4*N,x_propd,1,xd,1); - fx=fx_prop; - cudakernel_fns_fgrad_robust(ThreadsPerBlock,BlocksPerGrid,N,M,xd,fgradxd,yd,cohd,bbd,iwd,wtd,1,cbhandle,solver_handle); - norm_grad=sqrtf(cudakernel_fns_g(N,xd,fgradxd,fgradxd,cbhandle,solver_handle)); - } - - /* - Testing for Stop Criteria - */ - if (norm_gradmin_outer) { - stop_outer=1; - } - - /* - stop after max_outer iterations - */ - if (k>=max_outer) { - stop_outer=1; - } - -#ifdef DEBUG -printf("Iter %d cost=%g\n",k,fx); -#endif - - } - /* final residual */ - info[1]=fx; -#ifdef DEBUG -printf("NEW RTR cost=%g\n",fx); -#endif - -/***************************************************/ - cudaDeviceSynchronize(); - /* w <= (p+nu)/(1+error^2), q<=w-log(w) */ - /* p = 2, use MAX() residual of XX,XY,YX,YY, not the sum */ - cudakernel_fns_fupdate_weights_q(ThreadsPerBlock,BlocksPerGrid,N,M,xd,yd,cohd,bbd,wtd,qd,robust_nu); - /* sumq<=sum(w-log(w))/N */ - cbstatus=cublasSasum(cbhandle, M, qd, 1, &q_sum); - q_sum/=(float)M; -#ifdef DEBUG - printf("deltanu=%f sum(w-log(w))=%f\n",deltanu,q_sum); -#endif - /* for nu range 2~numax evaluate, p-variate T - psi((nu0+p)/2)-ln((nu0+p)/2)-psi(nu/2)+ln(nu/2)+1/N sum(ln(w_i)-w_i) +1 - note: AECM not ECME - and find min(| |) */ - int ThreadsPerBlock2=ThreadsPerBlock/4; - cudakernel_evaluatenu_fl_eight(ThreadsPerBlock2, (Nd+ThreadsPerBlock-1)/ThreadsPerBlock2, Nd, q_sum, qd, deltanu,(float)robust_nulow,robust_nu); - /* find min(abs()) value */ - cbstatus=cublasIsamin(cbhandle, Nd, qd, 1, &ci); /* 1 based index */ - robust_nu1=(float)robust_nulow+(float)(ci-1)*deltanu; -#ifdef DEBUG - printf("nu updated %d from %f [%lf,%lf] to %f\n",ci,robust_nu,robust_nulow,robust_nuhigh,robust_nu1); -#endif - /* seems pedantic, but make sure new value for robust_nu fits within bounds */ - if (robust_nu1robust_nu=robust_nulow; - } else if (robust_nu1>robust_nuhigh) { - dp->robust_nu=robust_nuhigh; - } else { - dp->robust_nu=(double)robust_nu1; - } - - if(fx0>fx) { - //printf("Cost final %g initial %g\n",fx,fx0); - /* copy back current solution */ - err=cudaMemcpy(x,xd,8*N*sizeof(float),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - - - /* copy back solution to x0 : format checked*/ - /* re J(0,0) */ - my_fcopy(N, &Jd[0], 4, &x0[0], 8); - /* im J(0,0) */ - my_fcopy(N, &Jd[1], 4, &x0[1], 8); - /* re J(1,0) */ - my_fcopy(N, &Jd[2], 4, &x0[4], 8); - /* im J(1,0) */ - my_fcopy(N, &Jd[3], 4, &x0[5], 8); - /* re J(0,1) */ - my_fcopy(N, &Jd[4*N], 4, &x0[2], 8); - /* im J(0,1) */ - my_fcopy(N, &Jd[4*N+1], 4, &x0[3], 8); - /* re J(1,1) */ - my_fcopy(N, &Jd[4*N+2], 4, &x0[6], 8); - /* im J(1,1) */ - my_fcopy(N, &Jd[4*N+3], 4, &x0[7], 8); - - } - free(x); - - checkCublasError(cbstatus,__FILE__,__LINE__); - cudaFree(fgradxd); - cudaFree(etad); - cudaFree(Hetad); - cudaFree(x_propd); - cudaFree(xd); - cudaFree(yd); - cudaFree(cohd); - cudaFree(bbd); - cudaFree(iwd); - cudaFree(wtd); - cudaFree(qd); - - - return 0; -} - - - -/* storage: - 8N * 6 + N + 8M * 2 + 2M + M (base storage) - MAX( 2 * Blocks + 4, 8N(1 + ceil(M/Threads))) for functions - Blocks = ceil(M/Threads) -*/ -int -nsd_solve_cuda_robust_fl( - float *x0, /* initial values and updated solution at output (size 8*N float) */ - float *y, /* data vector (size 8*M float) */ - int N, /* no of stations */ - int M, /* no of constraints */ - int itmax, /* maximum number of iterations */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - double *info, /* initial and final residuals */ - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - me_data_t *adata) -{ - - /* general note: all device variables end with a 'd' */ - cudaError_t err; - cublasStatus_t cbstatus=CUBLAS_STATUS_SUCCESS; - - /* ME data */ - me_data_t *dp=(me_data_t*)adata; - int Nbase=(dp->Nbase)*(ntiles); /* note: we do not use the total tile size */ - /* coherency on device */ - float *cohd; - /* baseline-station map on device/host */ - short *bbd; - - /* calculate no of cuda threads and blocks */ - int ThreadsPerBlock=DEFAULT_TH_PER_BK; - int BlocksPerGrid=(M+ThreadsPerBlock-1)/ThreadsPerBlock; - - - /* reshape x to make J: 2Nx2 complex double - */ - complex float *x; - if ((x=(complex float*)malloc((size_t)4*N*sizeof(complex float)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - /* map x: [(re,im)J_1(0,0) (re,im)J_1(0,1) (re,im)J_1(1,0) (re,im)J_1(1,1)...] - to - J: [J_1(0,0) J_1(1,0) J_2(0,0) J_2(1,0) ..... J_1(0,1) J_1(1,1) J_2(0,1) J_2(1,1)....] - */ - float *Jd=(float*)x; - /* re J(0,0) */ - my_fcopy(N, &x0[0], 8, &Jd[0], 4); - /* im J(0,0) */ - my_fcopy(N, &x0[1], 8, &Jd[1], 4); - /* re J(1,0) */ - my_fcopy(N, &x0[4], 8, &Jd[2], 4); - /* im J(1,0) */ - my_fcopy(N, &x0[5], 8, &Jd[3], 4); - /* re J(0,1) */ - my_fcopy(N, &x0[2], 8, &Jd[4*N], 4); - /* im J(0,1) */ - my_fcopy(N, &x0[3], 8, &Jd[4*N+1], 4); - /* re J(1,1) */ - my_fcopy(N, &x0[6], 8, &Jd[4*N+2], 4); - /* im J(1,1) */ - my_fcopy(N, &x0[7], 8, &Jd[4*N+3], 4); - - - int ci; - -/***************************************************/ - cuFloatComplex *xd,*fgradxd,*etad,*zd,*x_propd,*z_propd; - float *yd; - float *wtd,*qd; /* for robust weight and log(weight) */ - float robust_nu=(float)dp->robust_nu; - float q_sum,robust_nu1; - float deltanu; - int Nd=100; /* no of points where nu is sampled, note NdM) { Nd=M; } - deltanu=(float)(robust_nuhigh-robust_nulow)/(float)Nd; - - /* for counting how many baselines contribute to each station - grad/hess calculation */ - float *iwd,*iw; - if ((iw=(float*)malloc((size_t)N*sizeof(float)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - cudaMalloc((void**)&fgradxd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&etad, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&zd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&x_propd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&xd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&z_propd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&yd, sizeof(float)*8*M); - cudaMalloc((void**)&cohd, sizeof(float)*8*Nbase); - cudaMalloc((void**)&bbd, sizeof(short)*2*Nbase); - cudaMalloc((void**)&iwd, sizeof(float)*N); - cudaMalloc((void**)&wtd, sizeof(float)*M); - cudaMalloc((void**)&qd, sizeof(float)*M); - - - /* yd <=y : V */ - err=cudaMemcpy(yd, y, 8*M*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - /* need to give right offset for coherencies */ - /* offset: cluster offset+time offset */ - /* C */ - err=cudaMemcpy(cohd, &(dp->ddcohf[(dp->Nbase)*(dp->tilesz)*(dp->clus)*8+(dp->Nbase)*tileoff*8]), Nbase*8*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - /* correct offset for baselines */ - err=cudaMemcpy(bbd, &(dp->ddbase[2*(dp->Nbase)*(tileoff)]), Nbase*2*sizeof(short), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - /* xd <=x : solution */ - err=cudaMemcpy(xd, x, 8*N*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - - float fx,fx0; - - /* count how many baselines contribute to each station, store (inverse) in iwd */ - count_baselines(Nbase,N,iw,&(dp->ddbase[2*(dp->Nbase)*(tileoff)]),dp->Nt); - err=cudaMemcpy(iwd, iw, N*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - free(iw); - - /* set initial weights to 1 by a cuda kernel */ - cudakernel_setweights_fl(ThreadsPerBlock, (M+ThreadsPerBlock-1)/ThreadsPerBlock, M, wtd, 1.0f); - fx=cudakernel_fns_f_robust(ThreadsPerBlock,BlocksPerGrid,N,M,xd,yd,cohd,bbd,wtd); - fx0=fx; -#ifdef DEBUG -printf("Initial Cost=%g\n",fx0); -#endif -/***************************************************/ - // gradient at x0; - cudakernel_fns_fgrad_robust(ThreadsPerBlock,BlocksPerGrid,N,M,xd,fgradxd,yd,cohd,bbd,iwd,wtd,1,cbhandle,solver_handle); - // Hessian - cudakernel_fns_fhess_robust(ThreadsPerBlock,BlocksPerGrid,N,M,xd,xd,zd,yd,cohd,bbd,iwd,wtd,cbhandle,solver_handle); - // initial step = 1/||Hess|| - float hess_nrm; - cublasScnrm2(cbhandle,4*N,zd,1,&hess_nrm); - float t=1.0f/hess_nrm; - /* if initial step too small */ - if (t<1e-6f) { - t=1e-6f; - } - - /* z <= x */ - cbstatus=cublasCcopy(cbhandle,4*N,xd,1,zd,1); - float theta=1.0f; - float ALPHA = 1.01f; // step-size growth factor - float BETA = 0.5f; // step-size shrinkage factor - int k; - cuFloatComplex alpha; - - for (k=0; krobust_nu=robust_nulow; - } else if (robust_nu1>robust_nuhigh) { - dp->robust_nu=robust_nuhigh; - } else { - dp->robust_nu=(double)robust_nu1; - } - - if(fx0>fx) { - //printf("Cost final %g initial %g\n",fx,fx0); - /* copy back current solution */ - err=cudaMemcpy(x,xd,8*N*sizeof(float),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - - - /* copy back solution to x0 : format checked*/ - /* re J(0,0) */ - my_fcopy(N, &Jd[0], 4, &x0[0], 8); - /* im J(0,0) */ - my_fcopy(N, &Jd[1], 4, &x0[1], 8); - /* re J(1,0) */ - my_fcopy(N, &Jd[2], 4, &x0[4], 8); - /* im J(1,0) */ - my_fcopy(N, &Jd[3], 4, &x0[5], 8); - /* re J(0,1) */ - my_fcopy(N, &Jd[4*N], 4, &x0[2], 8); - /* im J(0,1) */ - my_fcopy(N, &Jd[4*N+1], 4, &x0[3], 8); - /* re J(1,1) */ - my_fcopy(N, &Jd[4*N+2], 4, &x0[6], 8); - /* im J(1,1) */ - my_fcopy(N, &Jd[4*N+3], 4, &x0[7], 8); - - } - free(x); - checkCublasError(cbstatus,__FILE__,__LINE__); - cudaFree(fgradxd); - cudaFree(etad); - cudaFree(zd); - cudaFree(x_propd); - cudaFree(xd); - cudaFree(z_propd); - cudaFree(yd); - cudaFree(cohd); - cudaFree(bbd); - cudaFree(iwd); - cudaFree(wtd); - cudaFree(qd); - - return 0; -} diff --git a/src/lib/rtr_solve_robust_cuda_admm.c b/src/lib/rtr_solve_robust_cuda_admm.c deleted file mode 100644 index 4c7857f..0000000 --- a/src/lib/rtr_solve_robust_cuda_admm.c +++ /dev/null @@ -1,1272 +0,0 @@ -/* - * - Copyright (C) 2006-2008 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#include -#include -#include -#include -#include - -#include "sagecal.h" -#include - -//#define DEBUG -/* helper functions for diagnostics */ -static void -checkCudaError(cudaError_t err, char *file, int line) -{ -#ifdef CUDA_DEBUG - if(!err) - return; - fprintf(stderr,"GPU (CUDA): %s %s %d\n", cudaGetErrorString(err),file,line); - exit(EXIT_FAILURE); -#endif -} - - -static void -checkCublasError(cublasStatus_t cbstatus, char *file, int line) -{ -#ifdef CUDA_DEBUG - if (cbstatus!=CUBLAS_STATUS_SUCCESS) { - fprintf(stderr,"%s: %d: CUBLAS failure\n",file,line); - exit(EXIT_FAILURE); - } -#endif -} - - -/* cost function */ -/* storage <= (2 Blocks+4) + 8N */ -static float -cudakernel_fns_f_robust_admm(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *Y, cuFloatComplex *Z, float admm_rho, float *y, float *coh, short *bbh, float *wtd, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle){ - cuFloatComplex *Yd; - cublasStatus_t cbstatus=CUBLAS_STATUS_SUCCESS; - cuFloatComplex alpha,a; - cudaMalloc((void**)&Yd, sizeof(cuFloatComplex)*4*N); - /* original cost function */ - float f0=cudakernel_fns_f_robust(ThreadsPerBlock,BlocksPerGrid,N,M,x,y,coh,bbh,wtd); -#ifdef DEBUG - printf("orig cost %f ",f0); -#endif - /* extra cost from ADMM */ - /* add ||Y^H(J-BZ)|| + rho/2 ||J-BZ||^2 */ - - /* Yd=J-BZ */ - cublasCcopy(cbhandle,4*N,x,1,Yd,1); - alpha.x=-1.0f;alpha.y=0.0f; - cbstatus=cublasCaxpy(cbhandle,4*N, &alpha, Z, 1, Yd, 1); - - /* ||Y^H Yd|| = 2 real(Y(:)^H Yd(:)) */ - cbstatus=cublasCdotc(cbhandle,4*N, Y, 1, Yd, 1, &a); -#ifdef DEBUG - printf("up %f ",2.0f*a.x); -#endif - f0+=2.0f*a.x; - - /* rho/2 ||J-BZ||^2 = rho/2 real(Yd(:)^H Yd(:)) */ - cbstatus=cublasCdotc(cbhandle,4*N, Yd, 1, Yd, 1, &a); -#ifdef DEBUG - printf("up %f\n",0.5f*admm_rho*a.x); -#endif - f0+=0.5f*admm_rho*a.x; - checkCublasError(cbstatus,__FILE__,__LINE__); - cudaFree(Yd); - return f0; -} - -/* Projection - rnew: new value : Euclidean space, just old value */ -static void -cudakernel_fns_proj_admm(int N, cuFloatComplex *x, cuFloatComplex *z, cuFloatComplex *rnew, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle) { - cublasStatus_t cbstatus; - - cbstatus=cublasCcopy(cbhandle,4*N,z,1,rnew,1); - checkCublasError(cbstatus,__FILE__,__LINE__); -} - - -/* gradient, also projected to tangent space */ -/* need 8N*M/ThreadsPerBlock+ 8N float storage */ -static void -cudakernel_fns_fgrad_robust_admm(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *Y, cuFloatComplex *Z, float admm_rho, cuFloatComplex *eta, float *y, float *coh, short *bbh, float *iw, float *wtd, int negate, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle) { - - cuFloatComplex *tempeta; - cublasStatus_t cbstatus=CUBLAS_STATUS_SUCCESS; - cuFloatComplex alpha; - cudaMalloc((void**)&tempeta, sizeof(cuFloatComplex)*4*N); - - /*************************/ - /* baselines */ - int nbase=N*(N-1)/2; - /* timeslots */ - int ntime=(M+nbase-1)/nbase; - /* blocks per timeslot */ - /* total blocks is Bt x ntime */ - int Bt=(nbase+ThreadsPerBlock-1)/ThreadsPerBlock; - /* max size of M for one kernel call, to determine optimal blocks */ - cudakernel_fns_fgradflat_robust_admm(ThreadsPerBlock, Bt*ntime, N, M, x, tempeta, y, coh, bbh, wtd, cbhandle, solver_handle); - - /* weight for missing (flagged) baselines */ - cudakernel_fns_fscale(N, tempeta, iw); - /* find -ve gradient */ - if (negate) { - alpha.x=-1.0f;alpha.y=0.0f; - cbstatus=cublasCscal(cbhandle,4*N,&alpha,tempeta,1); - } - -#ifdef DEBUG - /******************************/ - /* print norms , use eta as temp storage */ - float n1,n2,n3; - cublasScnrm2(cbhandle,4*N,tempeta,1,&n1); - cublasScnrm2(cbhandle,4*N,Y,1,&n2); - cudaMemcpy(eta,x,4*N*sizeof(cuFloatComplex),cudaMemcpyDeviceToDevice); - alpha.x=-1.0f; alpha.y=0.0f; - cublasCaxpy(cbhandle,4*N, &alpha, Z, 1, eta, 1); - cublasScnrm2(cbhandle,4*N,eta,1,&n3); - printf("Norm %lf %lf %lf\n",n1,0.5f*n2,0.5f*admm_rho*n3); - /******************************/ -#endif - - /* extra terms 0.5*Y+0.5*rho*(J-BZ) - add to -ve grad */ - if (negate) { - alpha.x=0.5f; alpha.y=0.0f; - cbstatus=cublasCaxpy(cbhandle,4*N, &alpha, Y, 1, tempeta, 1); - alpha.x=0.5f*admm_rho; alpha.y=0.0f; - cbstatus=cublasCaxpy(cbhandle,4*N, &alpha, x, 1, tempeta, 1); - alpha.x=-0.5f*admm_rho; alpha.y=0.0f; - cbstatus=cublasCaxpy(cbhandle,4*N, &alpha, Z, 1, tempeta, 1); - } else { - alpha.x=-0.5f; alpha.y=0.0f; - cbstatus=cublasCaxpy(cbhandle,4*N, &alpha, Y, 1, tempeta, 1); - alpha.x=-0.5f*admm_rho; alpha.y=0.0f; - cbstatus=cublasCaxpy(cbhandle,4*N, &alpha, x, 1, tempeta, 1); - alpha.x=0.5f*admm_rho; alpha.y=0.0f; - cbstatus=cublasCaxpy(cbhandle,4*N, &alpha, Z, 1, tempeta, 1); - } - - checkCublasError(cbstatus,__FILE__,__LINE__); - cudaMemcpy(eta,tempeta,4*N*sizeof(cuFloatComplex),cudaMemcpyDeviceToDevice); - - cudaFree(tempeta); -} - -/* Hessian, also projected to tangent space */ -/* need 8N*M/ThreadsPerBlock+ 8N float storage */ -static void -cudakernel_fns_fhess_robust_admm(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *Y, cuFloatComplex *Z, float admm_rho, cuFloatComplex *eta, cuFloatComplex *fhess, float *y, float *coh, short *bbh, float *iw, float *wtd, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle) { - cuFloatComplex *tempeta; - cublasStatus_t cbstatus=CUBLAS_STATUS_SUCCESS; - cudaMalloc((void**)&tempeta, sizeof(cuFloatComplex)*4*N); - /* baselines */ - int nbase=N*(N-1)/2; - /* timeslots */ - int ntime=(M+nbase-1)/nbase; - /* blocks per timeslot */ - /* total blocks is Bt x ntime */ - int Bt=(nbase+ThreadsPerBlock-1)/ThreadsPerBlock; - - cudakernel_fns_fhessflat_robust_admm(ThreadsPerBlock, Bt*ntime, N, M, x, eta, tempeta, y, coh, bbh, wtd, cbhandle, solver_handle); - - cudakernel_fns_fscale(N, tempeta, iw); - - /* extra terms 0.5*rho*eta*/ - cuFloatComplex alpha; - alpha.x=0.5f*admm_rho;alpha.y=0.0f; - cbstatus=cublasCaxpy(cbhandle,4*N, &alpha, eta, 1, tempeta, 1); - - checkCublasError(cbstatus,__FILE__,__LINE__); - cudaMemcpy(fhess,tempeta,4*N*sizeof(cuFloatComplex),cudaMemcpyDeviceToDevice); - - cudaFree(tempeta); -} - -/* Fine tune initial trust region radius, also update initial value for x - A. Sartenaer, 1995 - returns : trust region estimate, - also modifies x - eta,Heta: used as storage - */ -/* need 8N*2 + MAX(8N+2 Blocks + 4, 8N (1 + ceil(M/Threads))) float storage */ -static float -itrr(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *Y, cuFloatComplex *Z, float admm_rho, cuFloatComplex *eta, cuFloatComplex *Heta, float *y, float *coh, short *bbh, float *iw, float *wtd, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle) { - cuFloatComplex alpha; - cublasStatus_t cbstatus=CUBLAS_STATUS_SUCCESS; - /* temp storage, re-using global storage */ - cuFloatComplex *s, *x_prop; - cudaMalloc((void**)&s, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&x_prop, sizeof(cuFloatComplex)*4*N); - - float f0,fk,mk,rho,rho1,Delta0; - /* initialize trust region radii */ - float delta_0=1.0f; - float delta_m=0.0f; - - float sigma=0.0f; - float delta=0.0f; - - // initial cost - f0=cudakernel_fns_f_robust_admm(ThreadsPerBlock,BlocksPerGrid,N,M,x,Y,Z,admm_rho,y,coh,bbh,wtd,cbhandle,solver_handle); - // gradient at x0; - cudakernel_fns_fgrad_robust_admm(ThreadsPerBlock,BlocksPerGrid,N,M,x,Y,Z,admm_rho,eta,y,coh,bbh,iw,wtd,1,cbhandle,solver_handle); - // normalize - float eta_nrm; - cublasScnrm2(cbhandle,4*N,eta,1,&eta_nrm); - alpha.x=1.0f/eta_nrm;alpha.y=0.0f; - cbstatus=cublasCscal(cbhandle,4*N,&alpha,eta,1); - - cbstatus=cublasCcopy(cbhandle,4*N,eta,1,s,1); - alpha.x=delta_0;alpha.y=0.0f; - cbstatus=cublasCscal(cbhandle,4*N,&alpha,s,1); - /* Hessian at s */ - cudakernel_fns_fhess_robust_admm(ThreadsPerBlock,BlocksPerGrid,N,M,x,Y,Z,admm_rho,s,Heta,y,coh,bbh,iw,wtd,cbhandle,solver_handle); - - /* constants used */ - float gamma_1=0.0625f; float gamma_2=5.0f; float gamma_3=0.5f; float gamma_4=2.0f; - float mu_0=0.5f; float mu_1=0.5f; float mu_2=0.35f; - float teta=0.25f; - - - int MK=4; - int m; - for (m=0; mdelta) { - delta=f0-fk; - sigma=delta_0; - } - /* radius update */ - float beta_1,beta_2,beta_i; - beta_1=0.0f; - beta_2=0.0f; - - if (mmu_1) { - if (minbeta>1.0f) { - beta_i=gamma_3; - } else if ((maxbeta=1.0f)) { - beta_i=gamma_1; - } else if ((beta_1>=gamma_1 && beta_1<1.0f) && (beta_2=1.0f)) { - beta_i=beta_1; - } else if ((beta_2>=gamma_1 && beta_2<1.0f) && (beta_1=1.0f)) { - beta_i=beta_2; - } else { - beta_i=maxbeta; - } - } else if (rho1<=mu_2) { - if (maxbeta<1.0f) { - beta_i=gamma_4; - } else if (maxbeta>gamma_2) { - beta_i=gamma_2; - } else if ((beta_1>=1.0f && beta_1<=gamma_2) && beta_2<1.0f) { - beta_i=beta_1; - } else if ((beta_2>=1.0f && beta_2<=gamma_2) && beta_1<1.0f) { - beta_i=beta_2; - } else { - beta_i=maxbeta; - } - } else { - if (maxbetagamma_4) { - beta_i=gamma_4; - } else { - beta_i=maxbeta; - } - } - /* update radius */ - delta_0=delta_0/beta_i; - } -#ifdef DEBUG -printf("m=%d delta_0=%e delta_max=%e beta=%e rho=%e\n",m,delta_0,delta_m,beta_i,rho); -#endif - - cbstatus=cublasCcopy(cbhandle,4*N,eta,1,s,1); - alpha.x=delta_0;alpha.y=0.0f; - cbstatus=cublasCscal(cbhandle,4*N,&alpha,s,1); - } - - // update initial value - if (delta>0.0f) { - alpha.x=-sigma; alpha.y=0.0f; - cbstatus=cublasCaxpy(cbhandle,4*N, &alpha, eta, 1, x, 1); - } - - if (delta_m>0.0f) { - Delta0=delta_m; - } else { - Delta0=delta_0; - } - - checkCublasError(cbstatus,__FILE__,__LINE__); - cudaFree(s); - cudaFree(x_prop); - return Delta0; -} - - - -/* truncated conjugate gradient method - x, grad, eta, r, z, delta, Hxd : size 2N x 2 complex - so, vector size is 4N complex double - - output: eta - return value: stop_tCG code - - y: vec(V) visibilities -*/ -/* need 8N*(BlocksPerGrid+2)+ 8N*6 float storage */ -static int -tcg_solve_cuda(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *Y, cuFloatComplex *Z, float admm_rho, cuFloatComplex *grad, cuFloatComplex *eta, cuFloatComplex *fhess, float Delta, float theta, float kappa, int max_inner, int min_inner, float *y, float *coh, short *bbh, float *iw, float *wtd, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle) { - cuFloatComplex *r,*z,*delta,*Hxd, *rnew; - float e_Pe, r_r, norm_r, z_r, d_Pd, d_Hd, alpha, e_Pe_new, - e_Pd, Deltasq, tau, zold_rold, beta, norm_r0; - int cj, stop_tCG; - cudaMalloc((void**)&r, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&z, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&delta, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&Hxd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&rnew, sizeof(cuFloatComplex)*4*N); - - - cublasStatus_t cbstatus=CUBLAS_STATUS_SUCCESS; - cuFloatComplex a0; - - /* - initial values - */ - cbstatus=cublasCcopy(cbhandle,4*N,grad,1,r,1); - e_Pe=0.0f; - - - r_r=cudakernel_fns_g(N,x,r,r,cbhandle,solver_handle); - norm_r=sqrtf(r_r); - norm_r0=norm_r; - - cbstatus=cublasCcopy(cbhandle,4*N,r,1,z,1); - - z_r=cudakernel_fns_g(N,x,z,r,cbhandle,solver_handle); - d_Pd=z_r; - - /* - initial search direction - */ - cudaMemset(delta, 0, sizeof(cuFloatComplex)*4*N); - a0.x=-1.0f; a0.y=0.0f; - cbstatus=cublasCaxpy(cbhandle,4*N, &a0, z, 1, delta, 1); - e_Pd=cudakernel_fns_g(N,x,eta,delta,cbhandle,solver_handle); - - stop_tCG=5; - - /* % begin inner/tCG loop - for j = 1:max_inner, - */ - for(cj=1; cj<=max_inner; cj++) { - cudakernel_fns_fhess_robust_admm(ThreadsPerBlock,BlocksPerGrid,N,M,x,Y,Z,admm_rho,delta,Hxd,y,coh,bbh,iw,wtd,cbhandle,solver_handle); - d_Hd=cudakernel_fns_g(N,x,delta,Hxd,cbhandle,solver_handle); - - alpha=z_r/d_Hd; - e_Pe_new = e_Pe + 2.0f*alpha*e_Pd + alpha*alpha*d_Pd; - - - Deltasq=Delta*Delta; - if (d_Hd <= 0.0f || e_Pe_new >= Deltasq) { - tau = (-e_Pd + sqrtf(e_Pd*e_Pd + d_Pd*(Deltasq-e_Pe)))/d_Pd; - a0.x=tau; - cbstatus=cublasCaxpy(cbhandle,4*N, &a0, delta, 1, eta, 1); - /* Heta = Heta + tau *Hdelta */ - cbstatus=cublasCaxpy(cbhandle,4*N, &a0, Hxd, 1, fhess, 1); - stop_tCG=(d_Hd<=0.0f?1:2); - break; - } - - e_Pe=e_Pe_new; - a0.x=alpha; - cbstatus=cublasCaxpy(cbhandle,4*N, &a0, delta, 1, eta, 1); - /* Heta = Heta + alpha*Hdelta */ - cbstatus=cublasCaxpy(cbhandle,4*N, &a0, Hxd, 1, fhess, 1); - - cbstatus=cublasCaxpy(cbhandle,4*N, &a0, Hxd, 1, r, 1); - cudakernel_fns_proj_admm(N, x, r, rnew, cbhandle,solver_handle); - cbstatus=cublasCcopy(cbhandle,4*N,rnew,1,r,1); - r_r=cudakernel_fns_g(N,x,r,r,cbhandle,solver_handle); - norm_r=sqrtf(r_r); - - /* - check kappa/theta stopping criterion - */ - if (cj >= min_inner) { - float norm_r0pow=powf(norm_r0,theta); - if (norm_r <= norm_r0*MIN(norm_r0pow,kappa)) { - stop_tCG=(kappaNbase)*(ntiles); /* note: we do not use the total tile size */ - /* coherency on device */ - float *cohd; - /* baseline-station map on device/host */ - short *bbd; - - /* calculate no of cuda threads and blocks */ - int ThreadsPerBlock=DEFAULT_TH_PER_BK; - int BlocksPerGrid=(M+ThreadsPerBlock-1)/ThreadsPerBlock; - - - /* reshape x to make J: 2Nx2 complex double - */ - complex float *x; - if ((x=(complex float*)malloc((size_t)4*N*sizeof(complex float)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - /* map x: [(re,im)J_1(0,0) (re,im)J_1(0,1) (re,im)J_1(1,0) (re,im)J_1(1,1)...] - to - J: [J_1(0,0) J_1(1,0) J_2(0,0) J_2(1,0) ..... J_1(0,1) J_1(1,1) J_2(0,1) J_2(1,1)....] - */ - float *Jd=(float*)x; - /* re J(0,0) */ - my_fcopy(N, &x0[0], 8, &Jd[0], 4); - /* im J(0,0) */ - my_fcopy(N, &x0[1], 8, &Jd[1], 4); - /* re J(1,0) */ - my_fcopy(N, &x0[4], 8, &Jd[2], 4); - /* im J(1,0) */ - my_fcopy(N, &x0[5], 8, &Jd[3], 4); - /* re J(0,1) */ - my_fcopy(N, &x0[2], 8, &Jd[4*N], 4); - /* im J(0,1) */ - my_fcopy(N, &x0[3], 8, &Jd[4*N+1], 4); - /* re J(1,1) */ - my_fcopy(N, &x0[6], 8, &Jd[4*N+2], 4); - /* im J(1,1) */ - my_fcopy(N, &x0[7], 8, &Jd[4*N+3], 4); - - complex float *Zx,*Yx; - if ((Zx=(complex float*)malloc((size_t)4*N*sizeof(complex float)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((Yx=(complex float*)malloc((size_t)4*N*sizeof(complex float)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - float *YY=(float*)Yx; - my_fcopy(N, &Y[0], 8, &YY[0], 4); - my_fcopy(N, &Y[1], 8, &YY[1], 4); - my_fcopy(N, &Y[4], 8, &YY[2], 4); - my_fcopy(N, &Y[5], 8, &YY[3], 4); - my_fcopy(N, &Y[2], 8, &YY[4*N], 4); - my_fcopy(N, &Y[3], 8, &YY[4*N+1], 4); - my_fcopy(N, &Y[6], 8, &YY[4*N+2], 4); - my_fcopy(N, &Y[7], 8, &YY[4*N+3], 4); - float *ZZ=(float*)Zx; - my_fcopy(N, &Z[0], 8, &ZZ[0], 4); - my_fcopy(N, &Z[1], 8, &ZZ[1], 4); - my_fcopy(N, &Z[4], 8, &ZZ[2], 4); - my_fcopy(N, &Z[5], 8, &ZZ[3], 4); - my_fcopy(N, &Z[2], 8, &ZZ[4*N], 4); - my_fcopy(N, &Z[3], 8, &ZZ[4*N+1], 4); - my_fcopy(N, &Z[6], 8, &ZZ[4*N+2], 4); - my_fcopy(N, &Z[7], 8, &ZZ[4*N+3], 4); - - - int ci; - -/***************************************************/ - cuFloatComplex *xd,*fgradxd,*etad,*Hetad,*x_propd,*Yd,*Zd; - float *yd; - float *wtd,*qd; /* for robust weight and log(weight) */ - float robust_nu=(float)dp->robust_nu; - float q_sum,robust_nu1; - float deltanu; - int Nd=100; /* no of points where nu is sampled, note NdM) { Nd=M; } - deltanu=(float)(robust_nuhigh-robust_nulow)/(float)Nd; - - /* for counting how many baselines contribute to each station - grad/hess calculation */ - float *iwd,*iw; - if ((iw=(float*)malloc((size_t)N*sizeof(float)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - - cudaMalloc((void**)&fgradxd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&etad, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&Hetad, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&x_propd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&xd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&yd, sizeof(float)*8*M); - cudaMalloc((void**)&cohd, sizeof(float)*8*Nbase); - cudaMalloc((void**)&bbd, sizeof(short)*2*Nbase); - cudaMalloc((void**)&iwd, sizeof(float)*N); - cudaMalloc((void**)&wtd, sizeof(float)*M); - cudaMalloc((void**)&qd, sizeof(float)*M); - - cudaMalloc((void **)&Yd, 4*N*sizeof(cuFloatComplex)); - cudaMalloc((void **)&Zd, 4*N*sizeof(cuFloatComplex)); - - /* yd <=y : V */ - err=cudaMemcpy(yd, y, 8*M*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - /* need to give right offset for coherencies */ - /* offset: cluster offset+time offset */ - /* C */ - err=cudaMemcpy(cohd, &(dp->ddcohf[(dp->Nbase)*(dp->tilesz)*(dp->clus)*8+(dp->Nbase)*tileoff*8]), Nbase*8*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - /* correct offset for baselines */ - err=cudaMemcpy(bbd, &(dp->ddbase[2*(dp->Nbase)*(tileoff)]), Nbase*2*sizeof(short), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - /* xd <=x : solution */ - err=cudaMemcpy(xd, x, 8*N*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMemcpy(Yd, Yx, 8*N*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMemcpy(Zd, Zx, 8*N*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - - float fx,fx0,norm_grad,Delta,fx_prop,rhonum,rhoden,rho; - - /* count how many baselines contribute to each station, store (inverse) in iwd */ - count_baselines(Nbase,N,iw,&(dp->ddbase[2*(dp->Nbase)*(tileoff)]),dp->Nt); - err=cudaMemcpy(iwd, iw, N*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - free(iw); - - /* set initial weights to 1 by a cuda kernel */ - cudakernel_setweights_fl(ThreadsPerBlock, (M+ThreadsPerBlock-1)/ThreadsPerBlock, M, wtd, 1.0f); - fx=cudakernel_fns_f_robust_admm(ThreadsPerBlock,BlocksPerGrid,N,M,xd,Yd,Zd,admm_rho,yd,cohd,bbd,wtd,cbhandle,solver_handle); - fx0=fx; -#ifdef DEBUG -printf("Initial Cost=%g\n",fx0); -#endif - - float Delta_new=itrr(ThreadsPerBlock, BlocksPerGrid, N, M, xd, Yd,Zd,admm_rho, etad, Hetad, yd, cohd, bbd, iwd, wtd, cbhandle,solver_handle); -#ifdef DEBUG - printf("TR radius given=%f est=%f\n",Delta0,Delta_new); -#endif - - - - //old values - //Delta_bar=MIN(fx,Delta_bar); - //Delta0=Delta_bar*0.125f; - Delta0=MIN(Delta_new,0.01f); /* need to be more restrictive for EM */ - Delta_bar=Delta0*8.0f; - -//printf("fx=%g Delta_bar=%g Delta0=%g\n",fx,Delta_bar,Delta0); - - cudakernel_fns_fupdate_weights(ThreadsPerBlock,BlocksPerGrid,N,M,xd,yd,cohd,bbd,wtd,robust_nu); - -#ifdef DEBUG -printf("NEW RSD cost=%g\n",fx); -#endif -/***************************************************/ - int min_inner,max_inner,min_outer,max_outer; - float epsilon,kappa,theta,rho_prime; - - min_inner=1; max_inner=itmax_rtr;//8*N; - min_outer=3;//itmax_rtr; //3; - max_outer=itmax_rtr; - epsilon=(float)CLM_EPSILON; - kappa=0.1f; - theta=1.0f; - /* default values 0.25, 0.75, 0.25, 2.0 */ - float eta1=0.0001f; float eta2=0.99f; float alpha1=0.25f; float alpha2=3.5f; - rho_prime=eta1; /* should be <= 0.25, tune for parallel solve */ - float rho_regularization; /* use large damping */ - rho_regularization=fx*1e-6f; - /* damping: too small => locally converge, globally diverge - |\ - |\ | \___ - -|\ | \| - \ - - - right damping: locally and globally converge - -|\ - \|\ - \|\ - \____ - - */ - float rho_reg; - int model_decreased=0; - - /* RTR solution */ - int k=0; - int stop_outer=(itmax_rtr>0?0:1); - int stop_inner=0; - if (!stop_outer) { - cudakernel_fns_fgrad_robust_admm(ThreadsPerBlock,BlocksPerGrid,N,M,xd, Yd,Zd,admm_rho,fgradxd,yd,cohd,bbd,iwd,wtd,1,cbhandle,solver_handle); - norm_grad=sqrtf(cudakernel_fns_g(N,xd,fgradxd,fgradxd,cbhandle,solver_handle)); - } - Delta=Delta0; - /* initial residual */ - info[0]=fx0; - - /* - % ** Start of TR loop ** - */ - while(!stop_outer) { - /* - % update counter - */ - k++; - /* eta = 0*fgradx; */ - cudaMemset(etad, 0, sizeof(cuFloatComplex)*4*N); - - - /* solve TR subproblem, also returns Hessian */ - stop_inner=tcg_solve_cuda(ThreadsPerBlock,BlocksPerGrid, N, M, xd, Yd,Zd,admm_rho,fgradxd, etad, Hetad, Delta, theta, kappa, max_inner, min_inner,yd,cohd,bbd,iwd,wtd,cbhandle,solver_handle); - /* - Heta = fns.fhess(x,eta); - */ - /* - compute the retraction of the proposal - */ - cudakernel_fns_R(N,xd,etad,x_propd,cbhandle,solver_handle); - - /* - compute cost of the proposal - */ - fx_prop=cudakernel_fns_f_robust_admm(ThreadsPerBlock,BlocksPerGrid,N,M,x_propd,Yd,Zd,admm_rho,yd,cohd,bbd,wtd, cbhandle,solver_handle); - - /* - check the performance of the quadratic model - */ - rhonum=fx-fx_prop; - rhoden=-cudakernel_fns_g(N,xd,fgradxd,etad,cbhandle,solver_handle)-0.5f*cudakernel_fns_g(N,xd,Hetad,etad,cbhandle,solver_handle); - /* regularization of rho ratio */ - /* - rho_reg = max(1, abs(fx)) * eps * options.rho_regularization; - rhonum = rhonum + rho_reg; - rhoden = rhoden + rho_reg; - */ - rho_reg=MAX(1.0f,fx)*rho_regularization; /* no epsilon */ - rhonum+=rho_reg; - rhoden+=rho_reg; - - /* - rho = rhonum / rhoden; - */ - rho=rhonum/rhoden; - - /* model_decreased = (rhoden >= 0); */ - /* OLD CODE if (fabsf(rhonum/fx) =0.0f?1:0); - -#ifdef DEBUG - printf("stop_inner=%d rho_reg=%g rho =%g/%g= %g rho'= %g\n",stop_inner,rho_reg,rhonum,rhoden,rho,rho_prime); -#endif - /* - choose new TR radius based on performance - */ - if ( !model_decreased || rhoeta2 && (stop_inner==2 || stop_inner==1)) { - Delta=MIN(alpha2*Delta,Delta_bar); - } - - /* - choose new iterate based on performance - */ - if (model_decreased && rho>rho_prime) { - cbstatus=cublasCcopy(cbhandle,4*N,x_propd,1,xd,1); - fx=fx_prop; - cudakernel_fns_fgrad_robust_admm(ThreadsPerBlock,BlocksPerGrid,N,M,xd,Yd,Zd,admm_rho, fgradxd,yd,cohd,bbd,iwd,wtd,1,cbhandle,solver_handle); - norm_grad=sqrtf(cudakernel_fns_g(N,xd,fgradxd,fgradxd,cbhandle,solver_handle)); - } - - /* - Testing for Stop Criteria - */ - if (norm_gradmin_outer) { - stop_outer=1; - } - - /* - stop after max_outer iterations - */ - if (k>=max_outer) { - stop_outer=1; - } - -#ifdef DEBUG -printf("Iter %d cost=%g\n",k,fx); -#endif - - } - /* final residual */ - info[1]=fx; -#ifdef DEBUG -printf("NEW RTR cost=%g\n",fx); -#endif - -/***************************************************/ - cudaDeviceSynchronize(); - /* w <= (p+nu)/(1+error^2), q<=w-log(w) */ - /* p = 2, use MAX() residual of XX,XY,YX,YY, not the sum */ - cudakernel_fns_fupdate_weights_q(ThreadsPerBlock,BlocksPerGrid,N,M,xd,yd,cohd,bbd,wtd,qd,robust_nu); - /* sumq<=sum(w-log(w))/N */ - cbstatus=cublasSasum(cbhandle, M, qd, 1, &q_sum); - q_sum/=(float)M; -#ifdef DEBUG - printf("deltanu=%f sum(w-log(w))=%f\n",deltanu,q_sum); -#endif - /* for nu range 2~numax evaluate, p-variate T - psi((nu0+p)/2)-ln((nu0+p)/2)-psi(nu/2)+ln(nu/2)+1/N sum(ln(w_i)-w_i) +1 - note: AECM not ECME - and find min(| |) */ - int ThreadsPerBlock2=ThreadsPerBlock/4; - cudakernel_evaluatenu_fl_eight(ThreadsPerBlock2, (Nd+ThreadsPerBlock-1)/ThreadsPerBlock2, Nd, q_sum, qd, deltanu,(float)robust_nulow,robust_nu); - /* find min(abs()) value */ - cbstatus=cublasIsamin(cbhandle, Nd, qd, 1, &ci); /* 1 based index */ - robust_nu1=(float)robust_nulow+(float)(ci-1)*deltanu; -#ifdef DEBUG - printf("nu updated %d from %f [%lf,%lf] to %f\n",ci,robust_nu,robust_nulow,robust_nuhigh,robust_nu1); -#endif - /* seems pedantic, but make sure new value for robust_nu fits within bounds */ - if (robust_nu1robust_nu=robust_nulow; - } else if (robust_nu1>robust_nuhigh) { - dp->robust_nu=robust_nuhigh; - } else { - dp->robust_nu=(double)robust_nu1; - } - -#ifdef DEBUG - printf("Cost final %g initial %g\n",fx,fx0); -#endif - if(fx0>fx) { - /* copy back current solution, only if cost is reduced */ - err=cudaMemcpy(x,xd,8*N*sizeof(float),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - - - /* copy back solution to x0 : format checked*/ - /* re J(0,0) */ - my_fcopy(N, &Jd[0], 4, &x0[0], 8); - /* im J(0,0) */ - my_fcopy(N, &Jd[1], 4, &x0[1], 8); - /* re J(1,0) */ - my_fcopy(N, &Jd[2], 4, &x0[4], 8); - /* im J(1,0) */ - my_fcopy(N, &Jd[3], 4, &x0[5], 8); - /* re J(0,1) */ - my_fcopy(N, &Jd[4*N], 4, &x0[2], 8); - /* im J(0,1) */ - my_fcopy(N, &Jd[4*N+1], 4, &x0[3], 8); - /* re J(1,1) */ - my_fcopy(N, &Jd[4*N+2], 4, &x0[6], 8); - /* im J(1,1) */ - my_fcopy(N, &Jd[4*N+3], 4, &x0[7], 8); - } - - - checkCublasError(cbstatus,__FILE__,__LINE__); - cudaFree(fgradxd); - cudaFree(etad); - cudaFree(Hetad); - cudaFree(x_propd); - cudaFree(xd); - cudaFree(yd); - cudaFree(cohd); - cudaFree(bbd); - cudaFree(iwd); - cudaFree(wtd); - cudaFree(qd); - - cudaFree(Yd); - cudaFree(Zd); - - free(x); - free(Yx); - free(Zx); - - return 0; -} - - - - -/* storage: - 8N * 6 + N + 8M * 2 + 2M + M (base storage) - MAX( 2 * Blocks + 4, 8N(1 + ceil(M/Threads))) for functions - Blocks = ceil(M/Threads) -*/ -int -nsd_solve_cuda_robust_admm_fl( - float *x0, /* initial values and updated solution at output (size 8*N float) */ - float *Y, /* Lagrange multiplier size 8N */ - float *Z, /* consensus term B Z size 8N */ - float *y, /* data vector (size 8*M float) */ - int N, /* no of stations */ - int M, /* no of constraints */ - int itmax, /* maximum number of iterations */ - float admm_rho, /* ADMM regularization */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - double *info, /* initial and final residuals */ - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - me_data_t *adata) -{ - - /* general note: all device variables end with a 'd' */ - cudaError_t err; - cublasStatus_t cbstatus=CUBLAS_STATUS_SUCCESS; - - /* ME data */ - me_data_t *dp=(me_data_t*)adata; - int Nbase=(dp->Nbase)*(ntiles); /* note: we do not use the total tile size */ - /* coherency on device */ - float *cohd; - /* baseline-station map on device/host */ - short *bbd; - - /* calculate no of cuda threads and blocks */ - int ThreadsPerBlock=DEFAULT_TH_PER_BK; - int BlocksPerGrid=(M+ThreadsPerBlock-1)/ThreadsPerBlock; - - - /* reshape x to make J: 2Nx2 complex double - */ - complex float *x; - if ((x=(complex float*)malloc((size_t)4*N*sizeof(complex float)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - /* map x: [(re,im)J_1(0,0) (re,im)J_1(0,1) (re,im)J_1(1,0) (re,im)J_1(1,1)...] - to - J: [J_1(0,0) J_1(1,0) J_2(0,0) J_2(1,0) ..... J_1(0,1) J_1(1,1) J_2(0,1) J_2(1,1)....] - */ - float *Jd=(float*)x; - /* re J(0,0) */ - my_fcopy(N, &x0[0], 8, &Jd[0], 4); - /* im J(0,0) */ - my_fcopy(N, &x0[1], 8, &Jd[1], 4); - /* re J(1,0) */ - my_fcopy(N, &x0[4], 8, &Jd[2], 4); - /* im J(1,0) */ - my_fcopy(N, &x0[5], 8, &Jd[3], 4); - /* re J(0,1) */ - my_fcopy(N, &x0[2], 8, &Jd[4*N], 4); - /* im J(0,1) */ - my_fcopy(N, &x0[3], 8, &Jd[4*N+1], 4); - /* re J(1,1) */ - my_fcopy(N, &x0[6], 8, &Jd[4*N+2], 4); - /* im J(1,1) */ - my_fcopy(N, &x0[7], 8, &Jd[4*N+3], 4); - - - complex float *Zx,*Yx; - if ((Zx=(complex float*)malloc((size_t)4*N*sizeof(complex float)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((Yx=(complex float*)malloc((size_t)4*N*sizeof(complex float)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - float *YY=(float*)Yx; - my_fcopy(N, &Y[0], 8, &YY[0], 4); - my_fcopy(N, &Y[1], 8, &YY[1], 4); - my_fcopy(N, &Y[4], 8, &YY[2], 4); - my_fcopy(N, &Y[5], 8, &YY[3], 4); - my_fcopy(N, &Y[2], 8, &YY[4*N], 4); - my_fcopy(N, &Y[3], 8, &YY[4*N+1], 4); - my_fcopy(N, &Y[6], 8, &YY[4*N+2], 4); - my_fcopy(N, &Y[7], 8, &YY[4*N+3], 4); - float *ZZ=(float*)Zx; - my_fcopy(N, &Z[0], 8, &ZZ[0], 4); - my_fcopy(N, &Z[1], 8, &ZZ[1], 4); - my_fcopy(N, &Z[4], 8, &ZZ[2], 4); - my_fcopy(N, &Z[5], 8, &ZZ[3], 4); - my_fcopy(N, &Z[2], 8, &ZZ[4*N], 4); - my_fcopy(N, &Z[3], 8, &ZZ[4*N+1], 4); - my_fcopy(N, &Z[6], 8, &ZZ[4*N+2], 4); - my_fcopy(N, &Z[7], 8, &ZZ[4*N+3], 4); - - - int ci; - -/***************************************************/ - cuFloatComplex *xd,*fgradxd,*etad,*zd,*x_propd,*z_propd,*Yd,*Zd; - float *yd; - float *wtd,*qd; /* for robust weight and log(weight) */ - float robust_nu=(float)dp->robust_nu; - float q_sum,robust_nu1; - float deltanu; - int Nd=100; /* no of points where nu is sampled, note NdM) { Nd=M; } - deltanu=(float)(robust_nuhigh-robust_nulow)/(float)Nd; - - /* for counting how many baselines contribute to each station - grad/hess calculation */ - float *iwd,*iw; - if ((iw=(float*)malloc((size_t)N*sizeof(float)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - - cudaMalloc((void**)&fgradxd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&etad, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&zd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&x_propd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&xd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&z_propd, sizeof(cuFloatComplex)*4*N); - cudaMalloc((void**)&yd, sizeof(float)*8*M); - cudaMalloc((void**)&cohd, sizeof(float)*8*Nbase); - cudaMalloc((void**)&bbd, sizeof(short)*2*Nbase); - cudaMalloc((void**)&iwd, sizeof(float)*N); - cudaMalloc((void**)&wtd, sizeof(float)*M); - cudaMalloc((void**)&qd, sizeof(float)*M); - - - cudaMalloc((void **)&Yd, 4*N*sizeof(cuFloatComplex)); - cudaMalloc((void **)&Zd, 4*N*sizeof(cuFloatComplex)); - - /* yd <=y : V */ - err=cudaMemcpy(yd, y, 8*M*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - /* need to give right offset for coherencies */ - /* offset: cluster offset+time offset */ - /* C */ - err=cudaMemcpy(cohd, &(dp->ddcohf[(dp->Nbase)*(dp->tilesz)*(dp->clus)*8+(dp->Nbase)*tileoff*8]), Nbase*8*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - /* correct offset for baselines */ - err=cudaMemcpy(bbd, &(dp->ddbase[2*(dp->Nbase)*(tileoff)]), Nbase*2*sizeof(short), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - /* xd <=x : solution */ - err=cudaMemcpy(xd, x, 8*N*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMemcpy(Yd, Yx, 8*N*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - err=cudaMemcpy(Zd, Zx, 8*N*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - - float fx,fx0; - - /* count how many baselines contribute to each station, store (inverse) in iwd */ - count_baselines(Nbase,N,iw,&(dp->ddbase[2*(dp->Nbase)*(tileoff)]),dp->Nt); - err=cudaMemcpy(iwd, iw, N*sizeof(float), cudaMemcpyHostToDevice); - checkCudaError(err,__FILE__,__LINE__); - free(iw); - - /* set initial weights to 1 by a cuda kernel */ - cudakernel_setweights_fl(ThreadsPerBlock, (M+ThreadsPerBlock-1)/ThreadsPerBlock, M, wtd, 1.0f); - fx=cudakernel_fns_f_robust_admm(ThreadsPerBlock,BlocksPerGrid,N,M,xd,Yd,Zd,admm_rho,yd,cohd,bbd,wtd,cbhandle,solver_handle); - fx0=fx; -#ifdef DEBUG -printf("Initial Cost=%g\n",fx0); -#endif -/***************************************************/ - // gradient at x0; - cudakernel_fns_fgrad_robust_admm(ThreadsPerBlock,BlocksPerGrid,N,M,xd,Yd,Zd,admm_rho,fgradxd,yd,cohd,bbd,iwd,wtd,1,cbhandle,solver_handle); - // Hessian - cudakernel_fns_fhess_robust_admm(ThreadsPerBlock,BlocksPerGrid,N,M,xd,Yd,Zd,admm_rho,xd,zd,yd,cohd,bbd,iwd,wtd,cbhandle,solver_handle); - // initial step = 1/||Hess|| - float hess_nrm; - cublasScnrm2(cbhandle,4*N,zd,1,&hess_nrm); - float t=1.0f/hess_nrm; - /* if initial step too small */ - if (t<1e-6f) { - t=1e-6f; - } - - /* z <= x */ - cbstatus=cublasCcopy(cbhandle,4*N,xd,1,zd,1); - float theta=1.0f; - float ALPHA = 1.01f; // step-size growth factor - float BETA = 0.5f; // step-size shrinkage factor - int k; - cuFloatComplex alpha; - - for (k=0; krobust_nu=robust_nulow; - } else if (robust_nu1>robust_nuhigh) { - dp->robust_nu=robust_nuhigh; - } else { - dp->robust_nu=(double)robust_nu1; - } - - if(fx0>fx) { - //printf("Cost final %g initial %g\n",fx,fx0); - /* copy back current solution */ - err=cudaMemcpy(x,xd,8*N*sizeof(float),cudaMemcpyDeviceToHost); - checkCudaError(err,__FILE__,__LINE__); - - - /* copy back solution to x0 : format checked*/ - /* re J(0,0) */ - my_fcopy(N, &Jd[0], 4, &x0[0], 8); - /* im J(0,0) */ - my_fcopy(N, &Jd[1], 4, &x0[1], 8); - /* re J(1,0) */ - my_fcopy(N, &Jd[2], 4, &x0[4], 8); - /* im J(1,0) */ - my_fcopy(N, &Jd[3], 4, &x0[5], 8); - /* re J(0,1) */ - my_fcopy(N, &Jd[4*N], 4, &x0[2], 8); - /* im J(0,1) */ - my_fcopy(N, &Jd[4*N+1], 4, &x0[3], 8); - /* re J(1,1) */ - my_fcopy(N, &Jd[4*N+2], 4, &x0[6], 8); - /* im J(1,1) */ - my_fcopy(N, &Jd[4*N+3], 4, &x0[7], 8); - - } - - - checkCublasError(cbstatus,__FILE__,__LINE__); - cudaFree(fgradxd); - cudaFree(etad); - cudaFree(zd); - cudaFree(x_propd); - cudaFree(xd); - cudaFree(z_propd); - cudaFree(yd); - cudaFree(cohd); - cudaFree(bbd); - cudaFree(iwd); - cudaFree(wtd); - cudaFree(qd); - cudaFree(Yd); - cudaFree(Zd); - - free(x); - free(Yx); - free(Zx); - - return 0; -} diff --git a/src/lib/sagecal.h b/src/lib/sagecal.h deleted file mode 100644 index 235b955..0000000 --- a/src/lib/sagecal.h +++ /dev/null @@ -1,2641 +0,0 @@ -/* - * - Copyright (C) 2006-2008 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - -#ifndef SAGECAL_H -#define SAGECAL_H -#ifdef __cplusplus - extern "C" { -#endif - -#include -#include - -#include -#include -#include -#include -#include -#include - -#include - -/* for gcc 4.8 and above */ -#ifndef complex -#define complex _Complex -#endif - -#ifdef HAVE_CUDA -#include -#include -#include -#endif /* HAVE_CUDA */ - -#ifndef MAX_GPU_ID -#define MAX_GPU_ID 3 /* use 0 (1 GPU), 1 (2 GPUs), ... */ -#endif -/* default value for threads per block */ -#ifndef DEFAULT_TH_PER_BK -#define DEFAULT_TH_PER_BK 64 -#endif -#ifndef DEFAULT_TH_PER_BK_2 -#define DEFAULT_TH_PER_BK_2 32 -#endif - - -/* speed of light */ -#ifndef CONST_C -#define CONST_C 299792458.0 -#endif - -#ifndef MIN -#define MIN(x,y) \ - ((x)<=(y)? (x): (y)) -#endif - -#ifndef MAX -#define MAX(x,y) \ - ((x)>=(y)? (x): (y)) -#endif - -/* soure types */ -#define STYPE_POINT 0 -#define STYPE_GAUSSIAN 1 -#define STYPE_DISK 2 -#define STYPE_RING 3 -#define STYPE_SHAPELET 4 - -/* max source name length, increase it if names get longer */ -#define MAX_SNAME 2048 - -/********* constants - from levmar ******************/ -#define CLM_INIT_MU 1E-03 -#define CLM_STOP_THRESH 1E-17 -#define CLM_DIFF_DELTA 1E-06 -#define CLM_EPSILON 1E-12 -#define CLM_ONE_THIRD 0.3333333334 /* 1.0/3.0 */ -#define CLM_OPTS_SZ 5 /* max(4, 5) */ -#define CLM_INFO_SZ 10 -#define CLM_DBL_MAX 1E12 /* max double value */ - -/* structures to store extra source info for extended sources */ -typedef struct exinfo_gaussian_ { - double eX,eY,eP; /* major,minor,PA */ - - double cxi,sxi,cphi,sphi; /* projection of [0,0,1] to [l,m,n] */ - int use_projection; -} exinfo_gaussian; - -typedef struct exinfo_disk_ { - double eX; /* diameter */ - - double cxi,sxi,cphi,sphi; /* projection of [0,0,1] to [l,m,n] */ - int use_projection; -} exinfo_disk; - -typedef struct exinfo_ring_ { - double eX; /* diameter */ - - double cxi,sxi,cphi,sphi; /* projection of [0,0,1] to [l,m,n] */ - int use_projection; -} exinfo_ring; - -typedef struct exinfo_shapelet_ { - int n0; /* model order, no of modes=n0*n0 */ - double beta; /* scale*/ - double *modes; /* array of n0*n0 x 1 values */ - double eX,eY,eP; /* linear transform parameters */ - - double cxi,sxi,cphi,sphi; /* projection of [0,0,1] to [l,m,n] */ - int use_projection; -} exinfo_shapelet; - - -/* when to project l,m coordinates */ -#ifndef PROJ_CUT -#define PROJ_CUT 0.998 -#endif - - -/* struct for a cluster GList item */ -typedef struct clust_t_{ - int id; /* cluster id */ - int nchunk; /* no of chunks the data is divided for solving */ - GList *slist; /* list of sources in this cluster (string)*/ -} clust_t; - -typedef struct clust_n_{ - char *name; /* source name (string)*/ -} clust_n; - -/* struct to store source info in hash table */ -typedef struct sinfo_t_ { - double ll,mm,ra,dec,sI[4]; /* sI:4x1 for I,Q,U,V, note sI is updated for central freq (ra,dec) for Az,El */ - unsigned char stype; /* source type */ - void *exdata; /* pointer to carry additional data, if needed */ - double sI0[4],f0,spec_idx,spec_idx1,spec_idx2; /* for multi channel data, original sI,Q,U,V, f0 and spectral index */ -} sinfo_t; - -/* struct for array of the sky model, with clusters */ -typedef struct clus_source_t_ { - int N; /* no of source in this cluster */ - int id; /* cluster id */ - double *ll,*mm,*nn,*sI,*sQ,*sU,*sV; /* arrays Nx1 of source info, note: sI is at reference freq of data */ - /* nn=sqrt(1-ll^2-mm^2)-1 */ - double *ra,*dec; /* arrays Nx1 for Az,El calculation */ - unsigned char *stype; /* source type array Nx1 */ - void **ex; /* array for extra source information Nx1 */ - - int nchunk; /* no of chunks the data is divided for solving */ - int *p; /* array nchunkx1 points to parameter array indices */ - - - double *sI0,*sQ0,*sU0,*sV0,*f0,*spec_idx,*spec_idx1,*spec_idx2; /* for multi channel data, original sI, f0 and spectral index */ -} clus_source_t; - -/* strutct to store baseline to station mapping */ -typedef struct baseline_t_ { - int sta1,sta2; - unsigned char flag; /* if this baseline is flagged, set to 1, otherwise 0: - special case: 2 if baseline is not used in solution, but will be - subtracted */ -} baseline_t; - - -/* structure for worker threads for various function calculations */ -typedef struct thread_data_base_ { - int Nb; /* no of baselines this handle */ - int boff; /* baseline offset per thread */ - baseline_t *barr; /* pointer to baseline-> stations mapping array */ - double *u,*v,*w; /* pointers to uwv arrays,size Nbx1 */ - clus_source_t *carr; /* sky model, with clusters Mx1 */ - int M; /* no of clusters */ - double *x; /* output vector Nbx8 array re,im,re,im .... */ - complex double *coh; /* output vector in complex form, (not used always) size 4*M*Nb */ - /* following are only used while predict with gain */ - double *p; /* parameter array, size could be 8*N*Mx1 (full) or 8*Nx1 (single)*/ - int N; /* no of stations */ - int clus; /* which cluster to process, 0,1,...,M-1 if -1 all clusters */ - double uvmin; /* baseline length sqrt(u^2+v^2) lower limit, below this is not - included in calibration, but will be subtracted */ - double uvmax; - /* following used for freq/time smearing calculation */ - double freq0; - double fdelta; - double tdelta; /* integration time for time smearing */ - double dec0; /* declination for time smearing */ - - /* following used for interpolation */ - double *p0; /* old parameters, same as p */ - int tilesz; /* tile size */ - int Nbase; /* total no of baselines */ - /* following for correction of data */ - double *pinv; /* inverted solution array, if null no correction */ - int ccid; /* which cluster id (not user specified id) for correction, >=0 */ - - /* following for ignoring clusters in simulation */ - int *ignlist; /* Mx1 array, if any value 1, that cluster will not be simulated */ - /* flag for adding model to data */ - int add_to_data; - - /* following used for multifrequency (channel) data */ - double *freqs; - int Nchan; - - /* following used for calculating beam */ - double *arrayfactor; /* storage for precomputed beam */ - /* if clus==0, reset memory before adding */ - -} thread_data_base_t; - -/* structure for worker threads for - precalculating beam array factor */ -typedef struct thread_data_arrayfac_ { - int Ns; /* total no of sources per thread */ - int soff; /* starting source */ - int Ntime; /* total timeslots */ - double *time_utc; /* Ntimex1 array */ - int N; /* no. of stations */ - double *longitude, *latitude; - - double ra0,dec0,freq0; /* reference pointing and freq */ - int Nf; /* no. of frequencies to calculate */ - double *freqs; /* Nfx1 array */ - - int *Nelem; /* Nx1 array of element counts */ - double **xx,**yy,**zz; /* Nx1 arrays to element coords of each station, size Nelem[]x1 */ - - clus_source_t *carr; /* sky model, with clusters Mx1 */ - int cid; /* cluster id to calculate beam */ - baseline_t *barr; /* pointer to baseline-> stations mapping array */ - double *beamgain; /* output */ -} thread_data_arrayfac_t; - - -/* structure for worker threads for presetting - flagged data before solving */ -typedef struct thread_data_preflag_ { - int Nbase; /* total no of baselines */ - int startbase; /* starting baseline */ - int endbase; /* ending baseline */ - baseline_t *barr; /* pointer to baseline-> stations mapping array */ - double *x; /* data */ - double *flag; /* flag array 0 or 1 */ -} thread_data_preflag_t; - - -/* structure for worker threads for arranging coherencies for GPU use */ -typedef struct thread_data_coharr_ { - int M; /* no of clusters */ - int Nbase; /* no of baselines */ - int startbase; /* starting baseline */ - int endbase; /* ending baseline */ - baseline_t *barr; /* pointer to baseline-> stations mapping array */ - complex double *coh; /* output vector in complex form, (not used always) size 4*M*Nb */ - double *ddcoh; /* coherencies, rearranged for easy copying to GPU, also real,imag instead of complex */ - short *ddbase; /* baseline to station maps, same as barr, assume no of stations < 32k, if flagged set to -1 OR (sta1,sta2,flag) 3 values for each baseline */ -} thread_data_coharr_t; - -/* structure for worker threads for type conversion */ -typedef struct thread_data_typeconv_{ - int starti; /* starting baseline */ - int endi; /* ending baseline */ - double *darr; /* double array */ - float *farr; /* float array */ -} thread_data_typeconv_t; - -/* structure for worker threads for baseline generation */ -typedef struct thread_data_baselinegen_{ - int starti; /* starting tile */ - int endi; /* ending tile */ - baseline_t *barr; /* baseline array */ - int N; /* stations */ - int Nbase; /* baselines */ -} thread_data_baselinegen_t; - -/* structure for counting baselines for each station (RTR)*/ -typedef struct thread_data_count_ { - int Nb; /* no of baselines this handle */ - int boff; /* baseline offset per thread */ - - short *ddbase; - - int *bcount; - - /* mutexs: N x 1, one for each station */ - pthread_mutex_t *mx_array; -} thread_data_count_t; - - -/* structure for initializing an array */ -typedef struct thread_data_setwt_ { - int Nb; /* no of baselines this handle */ - int boff; /* baseline offset per thread */ - - double *b; - double a; - -} thread_data_setwt_t; - -/* structure for weight calculation for baselines */ -typedef struct thread_data_baselinewt_ { - int Nb; /* no of baselines this handle */ - int boff; /* baseline offset per thread */ - - double *wt; /* 8 values per baseline */ - double *u,*v; - double freq0; - -} thread_data_baselinewt_t; - - - -/* structure for worker threads for jacobian calculation */ -typedef struct thread_data_jac_ { - int Nb; /* no of baselines this handle */ - int n; /* function dimension n=8*Nb is implied */ - int m; /* no of parameters */ - baseline_t *barr; /* pointer to baseline-> stations mapping array */ - double *u,*v,*w; /* pointers to uwv arrays,size Nbx1 */ - clus_source_t *carr; /* sky model, with clusters Mx1 */ - int M; /* no of clusters */ - double *jac; /* output jacobian Nbx8 rows, re,im,re,im .... */ - complex double *coh; /* output vector in complex form, (not used always) size 4*M*Nb */ - /* following are only used while predict with gain */ - double *p; /* parameter array, size could be 8*N*Mx1 (full) or 8*Nx1 (single)*/ - int N; /* no of stations */ - int clus; /* which cluster to process, 0,1,...,M-1 if -1 all clusters */ - int start_col; - int end_col; /* which column of jacobian we calculate */ -} thread_data_jac_t; - - -/* structure for levmar */ -typedef struct me_data_t_ { - int clus; /* which cluster 0,1,...,M-1 if -1 all clusters */ - double *u,*v,*w; /* uvw coords size Nbase*tilesz x 1 */ - int Nbase; /* no of baselines */ - int tilesz; /* tile size */ - int N; /* no of stations */ - baseline_t *barr; /* baseline->station mapping, size Nbase*tilesz x 1 */ - clus_source_t *carr; /* sky model, with clusters size Mx1 */ - int M; /* no of clusters */ - int Mt; /* apparent no of clusters, due to hybrid solving, Mt>=M */ - double *freq0; /* frequency */ - int Nt; /* no of threads */ - - complex double *coh; /* pre calculated cluster coherencies, per cluster 4xNbase values, total size 4*M*Nbase*tilesz x 1 */ - /* following only used by CPU LM */ - int tileoff; /* tile offset for hybrid solution */ - - /* following only used by GPU LM version */ - double *ddcoh; /* coherencies, rearranged for easy copying to GPU, also real,imag instead of complex */ - short *ddbase; /* baseline to station maps, same as barr, size 2*Nbase*tilesz x 1, assume no of stations < 32k, if flagged set to -1 */ - /* following used only by LBFGS */ - short *hbb; /* baseline to station maps, same as ddbase size 2*Nbase*tilesz x 1, assume no of stations < 32k, if flagged set to -1 */ - int *ptoclus; /* param no -> cluster mapping, size 2*M x 1 - for each cluster : chunk size, start param index */ - - /* following used only by mixed precision solver */ - float *ddcohf; /* float version of ddcoh */ - - /* following used only by robust T cost/grad functions */ - double robust_nu; - - /* following used only by RTR */ -} me_data_t; - - -/* structure for gpu driver threads for LBFGS */ -typedef struct thread_gpu_data_t { - int ThreadsPerBlock; - int BlocksPerGrid; - int card; /* which gpu ? */ - - int Nbase; /* no of baselines */ - int tilesz; /* tile size */ - baseline_t *barr; /* baseline->station mapping, size Nbase*tilesz x 1 */ - int M; /* no of clusters */ - int N; /* no of stations */ - complex double *coh; /* pre calculated cluster coherencies, per cluster 4xNbase values, total size 4*M*Nbase*tilesz x 1 */ - int m; /* no of parameters */ - int n; /* no of observations */ - double *xo; /* observed data size n x 1 */ - double *p;/* parameter vectors size m x 1 */ - double *g; /* gradient vector (output) size m x 1*/ - int g_start; /* at which point in g do we start calculation */ - int g_end; /* at which point in g do we end calculation */ - - short *hbb; /* baseline to station maps, same as ddbase size 2*Nbase*tilesz x 1, assume no of stations < 32k, if flagged set to -1 */ - int *ptoclus; /* param no -> cluster mapping, size 2*M x 1 - for each cluster : chunk size, start param index */ - - /* only used in robust LBFGS */ - double robust_nu; -} thread_gpu_data; - - -/* structure for driver threads to evaluate gradient */ -typedef struct thread_data_grad_ { - int Nbase; /* no of baselines */ - int tilesz; /* tile size */ - baseline_t *barr; /* baseline->station mapping, size Nbase*tilesz x 1 */ - clus_source_t *carr; /* sky model, with clusters Mx1 */ - int M; /* no of clusters */ - int N; /* no of stations */ - complex double *coh; /* pre calculated cluster coherencies, per cluster 4xNbase values, total size 4*M*Nbase*tilesz x 1 */ - int m; /* no of parameters */ - int n; /* no of observations */ - double *x; /* residual data size n x 1 x=observed-func*/ - double *p;/* parameter vectors size m x 1 */ - double *g; /* gradient vector (output) size m x 1*/ - int g_start; /* at which point in g do we start calculation */ - int g_end; /* at which point in g do we end calculation */ - - /* only used in robust version */ - double robust_nu; -} thread_data_grad_t; - -/* structure for weight product calculation in robust LM */ -typedef struct thread_data_vec_{ - int starti,endi; - double *ed; - double *wtd; -} thread_data_vec_t; - -/* structure for weight calculation + nu update in robust LM */ -typedef struct thread_data_vecnu_{ - int starti,endi; - double *ed; - double *wtd; - double *q; - double nu0; - double sumq; - double nulow,nuhigh; -} thread_data_vecnu_t; - - -/* structure for worker threads for setting 1/0 */ -typedef struct thread_data_onezero_ { - int startbase; /* starting baseline */ - int endbase; /* ending baseline */ - short *ddbase; /* baseline to station maps, (sta1,sta2,flag) */ - float *x; /* data vector */ -} thread_data_onezero_t; - - -/* structure for worker threads for finding sum(|x|) and y^T |x| */ -typedef struct thread_data_findsumprod_ { - int startbase; /* starting baseline */ - int endbase; /* ending baseline */ - float *x; /* can be -ve*/ - float *y; - float sum1; /* sum(|x|) */ - float sum2; /* y^T |x| */ -} thread_data_findsumprod_t; - - -/****************************** readsky.c ****************************/ -/* read sky/cluster files, - carr: return array size Mx1 of clusters - M : no of clusters - freq0: obs frequency Hz - ra0,dec0 : ra,dec of phase center (radians) - format: 0: LSM, 1: LSM with 3 order spec index - each element has source infor for that cluster */ -extern int -read_sky_cluster(const char *skymodel, const char *clusterfile, clus_source_t **carr, int *M, double freq0, double ra0, double dec0,int format); - -/* read solution file, only a set of solutions and load to p - sfp: solution file pointer - p: solutions vector Mt x 1 - carr: for getting correct offset in p - N : stations - M : clusters -*/ -extern int -read_solutions(FILE *sfp,double *p,clus_source_t *carr,int N,int M); - -/* set ignlist[ci]=1 if - cluster id 'cid' is mentioned in ignfile and carr[ci].id==cid -*/ -extern int -update_ignorelist(const char *ignfile, int *ignlist, int M, clus_source_t *carr); - -/* read ADMM regularization factor per cluster from text file, format: - cluster_id hybrid_parameter admm_rho - ... - ... - (M values) - and store it in array arho : size Mtx1, taking into account the hybrid parameter - also in array arhoslave : size Mx1, without taking hybrid params into account - - admm_rho : can be 0 to ignore consensus, just normal calibration -*/ - -extern int -read_arho_fromfile(const char *admm_rho_file,int Mt,double *arho, int M, double *arhoslave); - -/****************************** dataio.c ****************************/ -/* open binary file for input/output - datfile: data file descriptor id - d: array of input/output stream, size (count-(header length))x1 - N: no of stations - freq0: frequency Hz - ra0,dec0: ra,dec of phase center (radians) -*/ -extern int -open_data_stream(int file, double **d, int *count, int *N, double *freq0, double *ra0, double *dec0); - -/* close the data stream */ -extern int -close_data_stream(double *d, int count); - - -/****************************** predict.c ****************************/ -/************* extended source contributions ************/ -extern complex double -shapelet_contrib(void*dd, double u, double v, double w); - -extern complex double -gaussian_contrib(void*dd, double u, double v, double w); - -extern complex double -ring_contrib(void*dd, double u, double v, double w); - -extern complex double -disk_contrib(void*dd, double u, double v, double w); - - -/* time smearing TMS eq. 6.80 for EW-array formula - note u,v,w: meter/c so multiply by freq. to get wavelength - ll,mm: source - dec0: phase center declination - tdelta: integration time */ -extern double -time_smear(double ll,double mm,double dec0,double tdelta,double u,double v,double w,double freq0); - -/* predict visibilities - u,v,w: u,v,w coordinates (wavelengths) size Nbase*tilesz x 1 - u,v,w are ordered with baselines, timeslots - x: data to write size Nbase*8*tileze x 1 - ordered by XX(re,im),XY(re,im),YX(re,im), YY(re,im), baseline, timeslots - N: no of stations - Nbase: no of baselines - tilesz: tile size - barr: baseline to station map, size Nbase*tilesz x 1 - carr: sky model/cluster info size Mx1 of clusters - M: no of clusters - freq0: frequency - fdelta: bandwidth for freq smearing - tdelta: integration time for time smearing - dec0: declination for time smearing - Nt: no of threads -*/ -extern int -predict_visibilities(double *u, double *v, double *w, double *x, int N, - int Nbase, int tilesz, baseline_t *barr, clus_source_t *carr, int M, double freq0, double fdelta, double tdelta, double dec0, int Nt); - - -/* precalculate cluster coherencies - u,v,w: u,v,w coordinates (wavelengths) size Nbase*tilesz x 1 - u,v,w are ordered with baselines, timeslots - x: coherencies size Nbase*4*Mx 1 - ordered by XX(re,im),XY(re,im),YX(re,im), YY(re,im), baseline, timeslots - N: no of stations - Nbase: no of baselines (including more than one tile) - barr: baseline to station map, size Nbase*tilesz x 1 - carr: sky model/cluster info size Mx1 of clusters - M: no of clusters - freq0: frequency - fdelta: bandwidth for freq smearing - tdelta: integration time for time smearing - dec0: declination for time smearing - uvmin: baseline length sqrt(u^2+v^2) below which not to include in solution - uvmax: baseline length higher than this not included in solution - Nt: no of threads - - NOTE: prediction is done for all baselines, even flagged ones - and flags are set to 2 for baselines lower than uvcut -*/ -extern int -precalculate_coherencies(double *u, double *v, double *w, complex double *x, int N, - int Nbase, baseline_t *barr, clus_source_t *carr, int M, double freq0, double fdelta, double tdelta, double dec0, double uvmin, double uvmax, int Nt); - - - -/* rearranges coherencies for GPU use later */ -/* barr: 2*Nbase x 1 - coh: M*Nbase*4 x 1 complex - ddcoh: M*Nbase*8 x 1 - ddbase: 2*Nbase x 1 (sta1,sta2) = -1 if flagged -*/ -extern int -rearrange_coherencies(int Nbase, baseline_t *barr, complex double *coh, double *ddcoh, short *ddbase, int M, int Nt); -/* ddbase: 3*Nbase x 1 (sta1,sta2,flag) */ -extern int -rearrange_coherencies2(int Nbase, baseline_t *barr, complex double *coh, double *ddcoh, short *ddbase, int M, int Nt); - -/* rearranges baselines for GPU use later */ -/* barr: 2*Nbase x 1 - ddbase: 2*Nbase x 1 -*/ -extern int -rearrange_baselines(int Nbase, baseline_t *barr, short *ddbase, int Nt); - -/* cont how many baselines contribute to each station */ -extern int -count_baselines(int Nbase, int N, float *iw, short *ddbase, int Nt); - -/* initialize array b (size Nx1) to given value a */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern void -setweights(int N, double *b, double a, int Nt); - -/* update baseline flags, also make data zero if flagged - this is needed for solving (calculate error) ignore flagged data */ -/* Nbase: total actual data points = Nbasextilesz - flag: flag array Nbasex1 - barr: baseline array Nbasex1 - x: data Nbase*8 x 1 ( 8 value per baseline ) - Nt: no of threads -*/ -extern int -preset_flags_and_data(int Nbase, double *flag, baseline_t *barr, double *x, int Nt); - -/* generte baselines -> sta1,sta2 pairs for later use */ -/* barr: Nbasextilesz - N : stations - Nt : threads -*/ -extern int -generate_baselines(int Nbase, int tilesz, int N, baseline_t *barr,int Nt); - -/* convert types */ -/* both arrays size nx1 - Nt: no of threads -*/ -extern int -double_to_float(float *farr, double *darr,int n, int Nt); -extern int -float_to_double(double *darr, float *farr,int n, int Nt); - -/* create a vector with 1's at flagged data points */ -/* - ddbase: 3*Nbase x 1 (sta1,sta2,flag) - x: 8*Nbase (set to 0's and 1's) -*/ -extern int -create_onezerovec(int Nbase, short *ddbase, float *x, int Nt); - -/* - find sum1=sum(|x|), and sum2=y^T |x| - x,y: nx1 arrays -*/ -extern int -find_sumproduct(int N, float *x, float *y, float *sum1, float *sum2, int Nt); - -/****************************** myblas.c ****************************/ -/* BLAS wrappers */ -/* machine precision */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern double -dlamch(char CMACH); - -/* blas dcopy */ -/* y = x */ -/* read x values spaced by Nx (so x size> N*Nx) */ -/* write to y values spaced by Ny (so y size > N*Ny) */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern void -my_dcopy(int N, double *x, int Nx, double *y, int Ny); - -/* blas scale */ -/* x = a. x */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern void -my_dscal(int N, double a, double *x); -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern void -my_sscal(int N, float a, float *x); - -/* x^T*y */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern double -my_ddot(int N, double *x, double *y); - -/* ||x||_2 */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern double -my_dnrm2(int N, double *x); -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern float -my_fnrm2(int N, float *x); - -/* sum||x||_1 */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern double -my_dasum(int N, double *x); -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern float -my_fasum(int N, float *x); - -/* BLAS y = a.x + y */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern void -my_daxpy(int N, double *x, double a, double *y); - -/* BLAS y = a.x + y */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern void -my_daxpys(int N, double *x, int incx, double a, double *y, int incy); - -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern void -my_saxpy(int N, float *x, float a, float *y); - -/* max |x| index (start from 1...)*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -my_idamax(int N, double *x, int incx); -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -my_isamax(int N, float *x, int incx); - -/* min |x| index (start from 1...)*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -int -my_idamin(int N, double *x, int incx); - -/* BLAS DGEMM C = alpha*op(A)*op(B)+ beta*C */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern void -my_dgemm(char transa, char transb, int M, int N, int K, double alpha, double *A, int lda, double *B, int ldb, double beta, double *C, int ldc); - -/* BLAS DGEMV y = alpha*op(A)*x+ beta*y : op 'T' or 'N' */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern void -my_dgemv(char trans, int M, int N, double alpha, double *A, int lda, double *x, int incx, double beta, double *y, int incy); - -/* following routines used in LAPACK solvers */ -/* cholesky factorization: real symmetric */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -my_dpotrf(char uplo, int N, double *A, int lda); - -/* solve Ax=b using cholesky factorization */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -my_dpotrs(char uplo, int N, int nrhs, double *A, int lda, double *b, int ldb); - -/* solve Ax=b using QR factorization */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -my_dgels(char TRANS, int M, int N, int NRHS, double *A, int LDA, double *B, int LDB, double *WORK, int LWORK); - -/* A=U S VT, so V needs NOT to be transposed */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -my_dgesvd(char JOBU, char JOBVT, int M, int N, double *A, int LDA, double *S, - double *U, int LDU, double *VT, int LDVT, double *WORK, int LWORK); -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -my_zgesvd(char JOBU, char JOBVT, int M, int N, complex double *A, int LDA, double *S, - complex double *U, int LDU, complex double *VT, int LDVT, complex double *WORK, int LWORK, double *RWORK); - -/* QR factorization QR=A, only TAU is used for Q, R stored in A*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -my_dgeqrf(int M, int N, double *A, int LDA, double *TAU, double *WORK, int LWORK); - -/* calculate Q using elementary reflections */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -my_dorgqr(int M,int N,int K,double *A,int LDA,double *TAU,double *WORK,int LWORK); - -/* solves a triangular system of equations Ax=b, A triangular */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -my_dtrtrs(char UPLO, char TRANS, char DIAG,int N,int NRHS,double *A,int LDA,double *B,int LDB); - - -/* blas ccopy */ -/* y = x */ -/* read x values spaced by Nx (so x size> N*Nx) */ -/* write to y values spaced by Ny (so y size > N*Ny) */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern void -my_ccopy(int N, complex double *x, int Nx, complex double *y, int Ny); - -/* blas scale */ -/* x = a. x */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern void -my_cscal(int N, complex double a, complex double *x); - - -/* BLAS y = a.x + y */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern void -my_caxpy(int N, complex double *x, complex double a, complex double *y); - - -/* BLAS x^H*y */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern complex double -my_cdot(int N, complex double *x, complex double *y); - -/* solve Ax=b using QR factorization */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -my_zgels(char TRANS, int M, int N, int NRHS, complex double *A, int LDA, complex double *B, int LDB, complex double *WORK, int LWORK); -extern int -my_cgels(char TRANS, int M, int N, int NRHS, complex float *A, int LDA, complex float *B, int LDB, complex float *WORK, int LWORK); - -/* BLAS ZGEMM C = alpha*op(A)*op(B)+ beta*C */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern void -my_zgemm(char transa, char transb, int M, int N, int K, complex double alpha, complex double *A, int lda, complex double *B, int ldb, complex double beta, complex double *C, int ldc); - -/* ||x||_2 */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern double -my_cnrm2(int N, complex double *x); - - -/* blas fcopy */ -/* y = x */ -/* read x values spaced by Nx (so x size> N*Nx) */ -/* write to y values spaced by Ny (so y size > N*Ny) */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern void -my_fcopy(int N, float *x, int Nx, float *y, int Ny); - - -/* LAPACK eigen value expert routine, real symmetric matrix */ -extern int -my_dsyevx(char jobz, char range, char uplo, int N, double *A, int lda, - double vl, double vu, int il, int iu, double abstol, int M, double *W, - double *Z, int ldz, double *WORK, int lwork, int *iwork, int *ifail); - -/* BLAS vector outer product - A= alpha x x^H + A -*/ -extern void -my_zher(char uplo, int N, double alpha, complex double *x, int incx, complex double *A, int lda); -/****************************** lbfgs.c ****************************/ -/****************************** lbfgs_nocuda.c ****************************/ -/* LBFGS routines */ -/* func: vector function to minimize, actual cost minimized is ||func-x||^2 - NOTE: gradient function given seperately - p: parameters m x 1 (used as initial value, output final value) - x: data n x 1 - itmax: max iterations - lbfgs_m: memory size - gpu_threads: GPU threads per block - adata: additional data -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -lbfgs_fit( - void (*func)(double *p, double *hx, int m, int n, void *adata), - double *p, double *x, int m, int n, int itmax, int lbfgs_m, int gpu_threads, void *adata); - -/****************************** robust_lbfgs_nocuda.c ****************************/ -typedef struct thread_data_logf_t_ { - double *f; - double *x; - double nu; - int start,end; - double sum; -} thread_data_logf_t; - -/* robust_nu: nu in T distribution */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -lbfgs_fit_robust( - void (*func)(double *p, double *hx, int m, int n, void *adata), - double *p, double *x, int m, int n, int itmax, int lbfgs_m, int gpu_threads, - void *adata); -#ifdef HAVE_CUDA -extern int -lbfgs_fit_robust_cuda( - void (*func)(double *p, double *hx, int m, int n, void *adata), - double *p, double *x, int m, int n, int itmax, int lbfgs_m, int gpu_threads, void *adata); -#endif - -/****************************** residual.c ****************************/ -/* residual calculation, with/without linear interpolation */ -/* - u,v,w: u,v,w coordinates (wavelengths) size Nbase*tilesz x 1 - u,v,w are ordered with baselines, timeslots - p0,p: parameter arrays 8*N*M x1 double values (re,img) for each station/direction - p0: old value, p new one, interpolate between the two - x: data to write size Nbase*8*tilesz x 1 - ordered by XX(re,im),XY(re,im),YX(re,im), YY(re,im), baseline, timeslots - input: x is actual data, output: x is the residual - N: no of stations - Nbase: no of baselines - tilesz: tile size - barr: baseline to station map, size Nbase*tilesz x 1 - carr: sky model/cluster info size Mx1 of clusters - coh: coherencies size Nbase*tilesz*4*M x 1 - M: no of clusters - freq0: frequency - fdelta: bandwidth for freq smearing - tdelta: integration time for time smearing - dec0: declination for time smearing - Nt: no. of threads - ccid: which cluster to use as correction - rho: MMSE robust parameter J+rho I inverted - - phase_only: if >0, and if there is any correction done, use only phase of diagonal elements for correction -*/ -extern int -calculate_residuals(double *u,double *v,double *w,double *p,double *x,int N,int Nbase,int tilesz,baseline_t *barr, clus_source_t *carr, int M,double freq0,double fdelta,double tdelta,double dec0, int Nt, int ccid, double rho); - -/* - residuals for multiple channels - data to write size Nbase*8*tilesz*Nchan x 1 - ordered by XX(re,im),XY(re,im),YX(re,im), YY(re,im), baseline, timeslots, channels - input: x is actual data, output: x is the residual - freqs: Nchanx1 of frequency values - fdelta: total bandwidth, so divide by Nchan to get each channel bandwith - tdelta: integration time for time smearing - dec0: declination for time smearing -*/ -extern int -calculate_residuals_multifreq(double *u,double *v,double *w,double *p,double *x,int N,int Nbase,int tilesz,baseline_t *barr, clus_source_t *carr, int M,double *freqs,int Nchan, double fdelta,double tdelta,double dec0, int Nt, int ccid, double rho, int phase_only); - -/* - calculate visibilities for multiple channels, no solutions are used - note: output column x is set to 0 if add_to_data ==0, else model is added to data -*/ -extern int -predict_visibilities_multifreq(double *u,double *v,double *w,double *x,int N,int Nbase,int tilesz,baseline_t *barr, clus_source_t *carr, int M,double *freqs,int Nchan, double fdelta,double tdelta,double dec0,int Nt,int add_to_data); - - -/* predict with solutions in p , ignore clusters flagged in ignorelist (Mx1) array - also correct final data with solutions for cluster ccid, if valid -*/ -extern int -predict_visibilities_multifreq_withsol(double *u,double *v,double *w,double *p,double *x,int *ignorelist,int N,int Nbase,int tilesz,baseline_t *barr, clus_source_t *carr, int M,double *freqs,int Nchan, double fdelta,double tdelta,double dec0,int Nt,int add_to_data, int ccid, double rho,int phase_only); -/****************************** mderiv.cu ****************************/ -/* cuda driver for kernel */ -/* ThreadsPerBlock: keep <= 128 - BlocksPerGrid: depends on the threads/baselines> Threads*Blocks approx baselines - N: no of baselines (total, including tilesz >1) - tilesz: tile size - M: no of clusters - Ns: no of stations - Nparam: no of actual parameters <=total - goff: starting point of gradient calculation 0..Nparams - x: N*8 x 1 residual - coh: N*8*M x 1 - p: M*Ns*8 x 1 - bb: 2*N x 1 - ptoclus: 2*M x 1 - grad: Nparamsx1 gradient values -*/ -extern void -cudakernel_lbfgs(int ThreadsPerBlock, int BlocksPerGrid, int N, int tilesz, int M, int Ns, int Nparam, int goff, double *x, double *coh, double *p, short *bb, int *ptoclus, double *grad); -/* x: data vector, not residual */ -extern void -cudakernel_lbfgs_r(int ThreadsPerBlock, int BlocksPerGrid, int N, int tilesz, int M, int Ns, int Nparam, int goff, double *x, double *coh, double *p, short *bb, int *ptoclus, double *grad); -extern void -cudakernel_lbfgs_r_robust(int ThreadsPerBlock, int BlocksPerGrid, int N, int tilesz, int M, int Ns, int Nparam, int goff, double *x, double *coh, double *p, short *bb, int *ptoclus, double *grad, double robust_nu); - - -/* cost function calculation, each GPU works with Nbase baselines out of Nbasetotal baselines - */ -extern double -cudakernel_lbfgs_cost(int ThreadsPerBlock, int BlocksPerGrid, int Nbase, int boff, int M, int Ns, int Nbasetotal, double *x, double *coh, double *p, short *bb, int *ptoclus); -extern double -cudakernel_lbfgs_cost_robust(int ThreadsPerBlock, int BlocksPerGrid, int Nbase, int boff, int M, int Ns, int Nbasetotal, double *x, double *coh, double *p, short *bb, int *ptoclus, double robust_nu); - - -/* divide by singular values Dpd[]/Sd[] for Sd[]> eps */ -extern void -cudakernel_diagdiv(int ThreadsPerBlock, int BlocksPerGrid, int M, double eps, double *Dpd, double *Sd); - -/* cuda driver for calculating - A<= A+mu I, adding mu to diagonal entries of A - A: size MxM - ThreadsPerBlock, BlocksPerGrid calculated to meet M -*/ -extern void -cudakernel_diagmu(int ThreadsPerBlock, int BlocksPerGrid, int M, double *A, double mu); - -/* cuda driver for calculating f() */ -/* p: params (Mx1): for all chunks, x: data (Nx1), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -extern void -cudakernel_func(int ThreadsPerBlock, int BlocksPerGrid, double *p, double *x, int M, int N, double *coh, short *bbh, int Nbase, int Mclus, int Nstations); - -/* cuda driver for calculating jacf() */ -/* p: params (Mx1): for all chunks, jac: jacobian (NxM), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -extern void -cudakernel_jacf(int ThreadsPerBlock_row, int ThreadsPerBlock_col, double *p, double *jac, int M, int N, double *coh, short *bbh, int Nbase, int Mclus, int Nstations); - - -/****************************** mderiv_fl.cu ****************************/ -/* divide by singular values Dpd[]/Sd[] for Sd[]> eps */ -extern void -cudakernel_diagdiv_fl(int ThreadsPerBlock, int BlocksPerGrid, int M, float eps, float *Dpd, float *Sd); -/* cuda driver for calculating - A<= A+mu I, adding mu to diagonal entries of A - A: size MxM - ThreadsPerBlock, BlocksPerGrid calculated to meet M -*/ -extern void -cudakernel_diagmu_fl(int ThreadsPerBlock, int BlocksPerGrid, int M, float *A, float mu); -/* cuda driver for calculating f() */ -/* p: params (Mx1), x: data (Nx1), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -extern void -cudakernel_func_fl(int ThreadsPerBlock, int BlocksPerGrid, float *p, float *x, int M, int N, float *coh, short *bbh, int Nbase, int Mclus, int Nstations); -/* cuda driver for calculating jacf() */ -/* p: params (Mx1), jac: jacobian (NxM), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -extern void -cudakernel_jacf_fl(int ThreadsPerBlock_row, int ThreadsPerBlock_col, float *p, float *jac, int M, int N, float *coh, short *bbh, int Nbase, int Mclus, int Nstations); -/****************************** robust.cu ****************************/ -/* cuda driver for calculating wt \odot f() */ -/* p: params (Mx1): for all chunks, x: data (Nx1), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -extern void -cudakernel_func_wt(int ThreadsPerBlock, int BlocksPerGrid, double *p, double *x, int M, int N, double *coh, short *bbh, double *wt, int Nbase, int Mclus, int Nstations); - -/* cuda driver for calculating wt \odot jacf() */ -/* p: params (Mx1): for all chunks, jac: jacobian (NxM), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -extern void -cudakernel_jacf_wt(int ThreadsPerBlock_row, int ThreadsPerBlock_col, double *p, double *jac, int M, int N, double *coh, short *bbh, double *wt, int Nbase, int Mclus, int Nstations); - - -/* set initial weights to 1 by a cuda kernel */ -extern void -cudakernel_setweights(int ThreadsPerBlock, int BlocksPerGrid, int N, double *wtd, double alpha); - -/* hadamard product by a cuda kernel x<= x*wt */ -extern void -cudakernel_hadamard(int ThreadsPerBlock, int BlocksPerGrid, int N, double *wt, double *x); - -/* update weights by a cuda kernel */ -extern void -cudakernel_updateweights(int ThreadsPerBlock, int BlocksPerGrid, int N, double *wt, double *x, double *q, double robust_nu); - -/* make sqrt() weights */ -extern void -cudakernel_sqrtweights(int ThreadsPerBlock, int BlocksPerGrid, int N, double *wt); - -/* evaluate expression for finding optimum nu for - a range of nu values */ -extern void -cudakernel_evaluatenu(int ThreadsPerBlock, int BlocksPerGrid, int Nd, double qsum, double *q, double deltanu,double nulow); - -/* ThreadsPerBlock: keep <= 128 - BlocksPerGrid: depends on the threads/baselines> Threads*Blocks approx baselines - N: no of baselines (total, including tilesz >1) - tilesz: tile size - M: no of clusters - Ns: no of stations - Nparam: no of actual parameters <=total - goff: starting point of gradient calculation 0..Nparams - x: N*8 x 1 residual - coh: N*8*M x 1 - p: M*Ns*8 x 1 - bb: 2*N x 1 - ptoclus: 2*M x 1 - grad: Nparamsx1 gradient values -*/ -extern void -cudakernel_lbfgs_robust(int ThreadsPerBlock, int BlocksPerGrid, int N, int tilesz, int M, int Ns, int Nparam, int goff, double robust_nu, double *x, double *coh, double *p, short *bb, int *ptoclus, double *grad); - -/****************************** robust_fl.cu ****************************/ -/* cuda driver for calculating wt \odot f() */ -/* p: params (Mx1): for all chunks, x: data (Nx1), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -extern void -cudakernel_func_wt_fl(int ThreadsPerBlock, int BlocksPerGrid, float *p, float *x, int M, int N, float *coh, short *bbh, float *wt, int Nbase, int Mclus, int Nstations); - -/* cuda driver for calculating wt \odot jacf() */ -/* p: params (Mx1): for all chunks, jac: jacobian (NxM), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -extern void -cudakernel_jacf_wt_fl(int ThreadsPerBlock_row, int ThreadsPerBlock_col, float *p, float *jac, int M, int N, float *coh, short *bbh, float *wt, int Nbase, int Mclus, int Nstations); - - -/* set initial weights to 1 by a cuda kernel */ -extern void -cudakernel_setweights_fl(int ThreadsPerBlock, int BlocksPerGrid, int N, float *wtd, float alpha); - -/* hadamard product by a cuda kernel x<= x*wt */ -extern void -cudakernel_hadamard_fl(int ThreadsPerBlock, int BlocksPerGrid, int N, float *wt, float *x); - -/* update weights by a cuda kernel */ -extern void -cudakernel_updateweights_fl(int ThreadsPerBlock, int BlocksPerGrid, int N, float *wt, float *x, float *q, float robust_nu); - -/* make sqrt() weights */ -extern void -cudakernel_sqrtweights_fl(int ThreadsPerBlock, int BlocksPerGrid, int N, float *wt); - -/* evaluate expression for finding optimum nu for - a range of nu values */ -extern void -cudakernel_evaluatenu_fl(int ThreadsPerBlock, int BlocksPerGrid, int Nd, float qsum, float *q, float deltanu,float nulow); - -/* evaluate expression for finding optimum nu for - a range of nu values , 8 variate T distrubution - using AECM */ -extern void -cudakernel_evaluatenu_fl_eight(int ThreadsPerBlock, int BlocksPerGrid, int Nd, float qsum, float *q, float deltanu,float nulow, float nu0); - - -/****************************** barrier.c ****************************/ -typedef struct t_barrier_ { - int tcount; /* current no. of threads inside barrier */ - int nthreads; /* the no. of threads the barrier works - with. This is a constant */ - pthread_mutex_t enter_mutex; - pthread_mutex_t exit_mutex; - pthread_cond_t lastthread_cond; - pthread_cond_t exit_cond; -} th_barrier; - - -/* initialize barrier */ -/* N - no. of accomodated threads */ -extern void -init_th_barrier(th_barrier *barrier, int N); - -/* destroy barrier */ -extern void -destroy_th_barrier(th_barrier *barrier); - -/* the main operation of the barrier */ -extern void -sync_barrier(th_barrier *barrier); - - - -/****************************** clmfit.c ****************************/ -#ifdef HAVE_CUDA -/* LM with GPU */ -extern int -clevmar_der_single( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - * info[5]= # iterations, - * info[6]=reason for terminating: 1 - stopped by small gradient J^T e - * 2 - stopped by small Dp - * 3 - stopped by itmax - * 4 - singular matrix. Restart from current p with increased mu - * 5 - no further error reduction is possible. Restart with increased mu - * 6 - stopped by small ||e||_2 - * 7 - stopped by invalid (i.e. NaN or Inf) "func" values. This is a user error - * info[7]= # function evaluations - * info[8]= # Jacobian evaluations - * info[9]= # linear systems solved, i.e. # attempts for reducing error - */ - - int card, /* GPU to use */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - void *adata); /* pointer to possibly additional data */ - -/* function to set up a GPU, should be called only once */ -extern void -attach_gpu_to_thread(int card, cublasHandle_t *cbhandle, cusolverDnHandle_t *solver_handle); -extern void -attach_gpu_to_thread1(int card, cublasHandle_t *cbhandle, cusolverDnHandle_t *solver_handle, double **WORK, int64_t work_size); -extern void -attach_gpu_to_thread2(int card, cublasHandle_t *cbhandle, cusolverDnHandle_t *solver_handle, float **WORK, int64_t work_size, int usecula); - - -/* function to detach a GPU from a thread */ -extern void -detach_gpu_from_thread(cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle); -extern void -detach_gpu_from_thread1(cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle, double *WORK); -extern void -detach_gpu_from_thread2(cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle, float *WORK, int usecula); -/* function to set memory to zero */ -extern void -reset_gpu_memory(double *WORK, int64_t work_size); - - -/* same as above, but f() and jac() calculations are done - entirely in the GPU */ -extern int -clevmar_der_single_cuda( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - * info[5]= # iterations, - * info[6]=reason for terminating: 1 - stopped by small gradient J^T e - * 2 - stopped by small Dp - * 3 - stopped by itmax - * 4 - singular matrix. Restart from current p with increased mu - * 5 - no further error reduction is possible. Restart with increased mu - * 6 - stopped by small ||e||_2 - * 7 - stopped by invalid (i.e. NaN or Inf) "func" values. This is a user error - * info[7]= # function evaluations - * info[8]= # Jacobian evaluations - * info[9]= # linear systems solved, i.e. # attempts for reducing error - */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - double *gWORK, /* GPU allocated memory */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - void *adata); /* pointer to possibly additional data */ - -/** keep interface almost the same as in levmar **/ -extern int -mlm_der_single_cuda( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[6], /* I: minim. options [\mu, \m, \p0, \p1, \p2, \delta]. - delta: 1 or 2 - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - */ - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - double *gWORK, /* GPU allocated memory */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - - void *adata); /* pointer to possibly additional data, passed uninterpreted to func & jacf. - * Set to NULL if not needed - */ - -#endif /* HAVE_CUDA */ -/****************************** robustlm.c ****************************/ -/* robust, iteratively weighted non linear least squares using LM - entirely in the GPU */ -#ifdef HAVE_CUDA -extern int -rlevmar_der_single_cuda( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - double *gWORK, /* GPU allocated memory */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - void *adata); - -/* robust, iteratively weighted non linear least squares using LM - entirely in the GPU, using float data */ -int -rlevmar_der_single_cuda_fl( - float *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - float *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - float *gWORK, /* GPU allocated memory */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - void *adata); - -/* robust, iteratively weighted non linear least squares using LM - entirely in the GPU, using float data, OS acceleration */ -extern int -osrlevmar_der_single_cuda_fl( - float *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - float *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - float *gWORK, /* GPU allocated memory */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - int randomize, /* if >0 randomize */ - void *adata); -#endif /* HAVE_CUDA */ - -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -rlevmar_der_single_nocuda( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - */ - - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int Nt, /* no of threads */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - void *adata); - -/* robust LM, OS acceleration */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -osrlevmar_der_single_nocuda( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - * info[5]= # iterations, - * info[6]=reason for terminating: 1 - stopped by small gradient J^T e - * 2 - stopped by small Dp - * 3 - stopped by itmax - * 4 - singular matrix. Restart from current p with increased mu - * 5 - no further error reduction is possible. Restart with increased mu - * 6 - stopped by small ||e||_2 - * 7 - stopped by invalid (i.e. NaN or Inf) "func" values. This is a user error - * info[7]= # function evaluations - * info[8]= # Jacobian evaluations - * info[9]= # linear systems solved, i.e. # attempts for reducing error - */ - - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int Nt, /* no of threads */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - int randomize, /* if >0 randomize */ - void *adata); - -/****************************** updatenu.c ****************************/ -/* update nu (degrees of freedom) - - nu0: current value of nu (need for AECM update) - sumlogw = 1/N sum(log(w_i)-w_i) - use Nd values in [nulow,nuhigh] to find nu - p: 1 or 8 depending on scalar or 2x2 matrix formulation -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern double -update_nu(double sumlogw, int Nd, int Nt, double nulow, double nuhigh, int p, double nu0); - -/* update w and nu together - nu0: current value of nu - w: Nx1 weight vector - ed: Nx1 error vector - Nt: no of threads - - return new nu, w is also updated, search range [nulow,nuhigh] -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern double -update_w_and_nu(double nu0, double *w, double *ed, int N, int Nt, double nulow, double nuhigh); - -/* - taper data by weighting based on uv distance (for short baselines) - for example: use weights as the inverse density function - 1/( 1+f(u,v) ) - as u,v->inf, f(u,v) -> 0 so long baselines are not affected - x: Nbase*8 x 1 (input,output) data - u,v : Nbase x 1 - note: u = u/c, v=v/c here, so need freq to convert to wavelengths */ -extern void -whiten_data(int Nbase, double *x, double *u, double *v, double freq0, int Nt); -/****************************** clmfit_nocuda.c ****************************/ -/* LM with LAPACK */ -/** keep interface almost the same as in levmar **/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -clevmar_der_single_nocuda( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - * info[5]= # iterations, - * info[6]=reason for terminating: 1 - stopped by small gradient J^T e - * 2 - stopped by small Dp - * 3 - stopped by itmax - * 4 - singular matrix. Restart from current p with increased mu - * 5 - no further error reduction is possible. Restart with increased mu - * 6 - stopped by small ||e||_2 - * 7 - stopped by invalid (i.e. NaN or Inf) "func" values. This is a user error - * info[7]= # function evaluations - * info[8]= # Jacobian evaluations - * info[9]= # linear systems solved, i.e. # attempts for reducing error - */ - - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - void *adata); /* pointer to possibly additional data, passed uninterpreted to func & jacf. - * Set to NULL if not needed - */ - -extern int -mlm_der_single( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[6], /* I: minim. options [\mu, \m, \p0, \p1, \p2, \delta]. - delta: 1 or 2 - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - */ - - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - void *adata); /* pointer to possibly additional data, passed uninterpreted to func & jacf. - * Set to NULL if not needed - */ - -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -oslevmar_der_single_nocuda( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - */ - - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int randomize, /* if >0 randomize */ - void *adata); -/****************************** oslmfit.c ****************************/ -#ifdef HAVE_CUDA -/* OS-LM, but f() and jac() calculations are done - entirely in the GPU */ -extern int -oslevmar_der_single_cuda( - void (*func)(double *p, double *hx, int m, int n, void *adata), /* functional relation describing measurements. A p \in R^m yields a \hat{x} \in R^n */ - void (*jacf)(double *p, double *j, int m, int n, void *adata), /* function to evaluate the Jacobian \part x / \part p */ - double *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - double *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - * info[5]= # iterations, - * info[6]=reason for terminating: 1 - stopped by small gradient J^T e - * 2 - stopped by small Dp - * 3 - stopped by itmax - * 4 - singular matrix. Restart from current p with increased mu - * 5 - no further error reduction is possible. Restart with increased mu - * 6 - stopped by small ||e||_2 - * 7 - stopped by invalid (i.e. NaN or Inf) "func" values. This is a user error - * info[7]= # function evaluations - * info[8]= # Jacobian evaluations - * info[9]= # linear systems solved, i.e. # attempts for reducing error - */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - double *gWORK, /* GPU allocated memory */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - int randomize, /* if >0 randomize */ - void *adata); /* pointer to possibly additional data, passed uninterpreted to func & jacf. - * Set to NULL if not needed - */ - -#endif /* !HAVE_CUDA */ - -/****************************** clmfit_fl.c ****************************/ -#ifdef HAVE_CUDA -extern int -clevmar_der_single_cuda_fl( - float *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - float *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - * info[5]= # iterations, - * info[6]=reason for terminating: 1 - stopped by small gradient J^T e - * 2 - stopped by small Dp - * 3 - stopped by itmax - * 4 - singular matrix. Restart from current p with increased mu - * 5 - no further error reduction is possible. Restart with increased mu - * 6 - stopped by small ||e||_2 - * 7 - stopped by invalid (i.e. NaN or Inf) "func" values. This is a user error - * info[7]= # function evaluations - * info[8]= # Jacobian evaluations - * info[9]= # linear systems solved, i.e. # attempts for reducing error - */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - float *gWORK, /* GPU allocated memory */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - void *adata); /* pointer to possibly additional data */ - -extern int -oslevmar_der_single_cuda_fl( - float *p, /* I/O: initial parameter estimates. On output has the estimated solution */ - float *x, /* I: measurement vector. NULL implies a zero vector */ - int M, /* I: parameter vector dimension (i.e. #unknowns) */ - int N, /* I: measurement vector dimension */ - int itmax, /* I: maximum number of iterations */ - double opts[4], /* I: minim. options [\mu, \epsilon1, \epsilon2, \epsilon3]. Respectively the scale factor for initial \mu, - * stopping thresholds for ||J^T e||_inf, ||Dp||_2 and ||e||_2. Set to NULL for defaults to be used - */ - double info[10], - /* O: information regarding the minimization. Set to NULL if don't care - * info[0]= ||e||_2 at initial p. - * info[1-4]=[ ||e||_2, ||J^T e||_inf, ||Dp||_2, mu/max[J^T J]_ii ], all computed at estimated p. - * info[5]= # iterations, - * info[6]=reason for terminating: 1 - stopped by small gradient J^T e - * 2 - stopped by small Dp - * 3 - stopped by itmax - * 4 - singular matrix. Restart from current p with increased mu - * 5 - no further error reduction is possible. Restart with increased mu - * 6 - stopped by small ||e||_2 - * 7 - stopped by invalid (i.e. NaN or Inf) "func" values. This is a user error - * info[7]= # function evaluations - * info[8]= # Jacobian evaluations - * info[9]= # linear systems solved, i.e. # attempts for reducing error - */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - float *gWORK, /* GPU allocated memory */ - int linsolv, /* 0 Cholesky, 1 QR, 2 SVD */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - int randomize, /* if >0 randomize */ - void *adata); /* pointer to possibly additional data, passed uninterpreted to func & jacf. - * Set to NULL if not needed - */ - -#endif /* !HAVE_CUDA */ -/****************************** rtr_solve.c ****************************/ -/* structure for worker threads for function calculation */ -typedef struct thread_data_rtr_ { - int Nb; /* no of baselines this handle */ - int boff; /* baseline offset per thread */ - baseline_t *barr; /* pointer to baseline-> stations mapping array */ - clus_source_t *carr; /* sky model, with clusters Mx1 */ - int M; /* no of clusters */ - double *y; /* data vector Nbx8 array re,im,re,im .... */ - complex double *coh; /* output vector in complex form, (not used always) size 4*M*Nb */ - /* following are only used while predict with gain */ - complex double *x; /* parameter array, */ - /* general format of element in manifold x - x: size 4N x 1 vector - x[0:2N-1] : first column, x[2N:4N-1] : second column - x=[J_1(1,1) J_1(1,2); - J_1(2,1) J_1(2,2); - ... .... - J_N(1,1) J_N(1,2); - J_N(2,1) J_N(2,2)]; - */ - int N; /* no of stations */ - int clus; /* which cluster to process, 0,1,...,M-1 if -1 all clusters */ - - /* output of cost function */ - double fcost; - /* gradient */ - complex double *grad; - /* Hessian */ - complex double *hess; - /* Eta (used in Hessian) */ - complex double *eta; - - /* normalization factors for grad,hess calculation */ - /* size Nx1 */ - int *bcount; - double *iw; /* 1/bcount */ - - /* for robust solver */ - double *wtd; /* weights for baseline */ - double nu0; - - /* mutexs: N x 1, one for each station */ - pthread_mutex_t *mx_array; -} thread_data_rtr_t; - -/* structure for common data */ -typedef struct global_data_rtr_ { - me_data_t *medata; /* passed from caller */ - - /* normalization factors for grad,hess calculation */ - /* size Nx1 */ - double *iw; /* 1/bcount */ - - /* for robust solver */ - double *wtd; /* weights for baseline */ - double nulow,nuhigh; - - /* for ADMM cost */ - complex double *Y; /* size 2Nx2 */ - complex double *BZ; /* size 2Nx2 */ - double admm_rho; - - /* thread stuff Nt x 1 threads */ - pthread_t *th_array; - /* mutexs: N x 1, one for each station */ - pthread_mutex_t *mx_array; - pthread_attr_t attr; -} global_data_rtr_t; - - -/* RTR (ICASSP 2013) */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -rtr_solve_nocuda( - double *x, /* initial values and updated solution at output (size 8*N double) */ - double *y, /* data vector (size 8*M double) */ - int N, /* no. of stations */ - int M, /* no. of constraints */ - int itmax_sd, /* maximum number of iterations RSD */ - int itmax_rtr, /* maximum number of iterations RTR */ - double Delta_bar, double Delta0, /* Trust region radius and initial value */ - double *info, /* initial and final residuals */ - me_data_t *adata); /* pointer to additional data - */ - -/****************************** rtr_solve_robust.c ****************************/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -rtr_solve_nocuda_robust( - double *x0, /* initial values and updated solution at output (size 8*N double) */ - double *y, /* data vector (size 8*M double) */ - int N, /* no. of stations */ - int M, /* no. of constraints */ - int itmax_rsd, /* maximum number of iterations RSD */ - int itmax_rtr, /* maximum number of iterations RTR */ - double Delta_bar, double Delta0, /* Trust region radius and initial value */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - double *info, /* initial and final residuals */ - me_data_t *adata); - -/* Nesterov's SD */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -nsd_solve_nocuda_robust( - double *x, /* initial values and updated solution at output (size 8*N double) */ - double *y, /* data vector (size 8*M double) */ - int N, /* no. of stations */ - int M, /* no. of constraints */ - int itmax, /* maximum number of iterations */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - double *info, /* initial and final residuals */ - me_data_t *adata); /* pointer to additional data - */ - -/****************************** rtr_solve_robust_admm.c ****************************/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -rtr_solve_nocuda_robust_admm( - double *x0, /* initial values and updated solution at output (size 8*N double) */ - double *Y, /* Lagrange multiplier (size 8*N double) */ - double *BZ, /* consensus B Z (size 8*N double) */ - double *y, /* data vector (size 8*M double) */ - int N, /* no. of stations */ - int M, /* no. of constraints */ - int itmax_rsd, /* maximum number of iterations RSD */ - int itmax_rtr, /* maximum number of iterations RTR */ - double Delta_bar, double Delta0, /* Trust region radius and initial value */ - double admm_rho, /* ADMM regularization value */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - double *info, /* initial and final residuals */ - me_data_t *adata); -#ifdef HAVE_CUDA -/****************************** manifold_fl.cu ****************************/ -extern float -cudakernel_fns_f(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, float *y, float *coh, short *bbh); -extern void -cudakernel_fns_fgradflat(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, float *y, float *coh, short *bbh); -extern void -cudakernel_fns_fhessflat(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, cuFloatComplex *fhess, float *y, float *coh, short *bbh); -extern void -cudakernel_fns_fscale(int N, cuFloatComplex *eta, float *iw); -extern float -cudakernel_fns_f_robust(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, float *y, float *coh, short *bbh, float *wtd); -extern void -cudakernel_fns_fgradflat_robust(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, float *y, float *coh, short *bbh, float *wtd, cuFloatComplex *Ai, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle); -extern void -cudakernel_fns_fgradflat_robust1(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, float *y, float *coh, short *bbh, float *wtd); -extern void -cudakernel_fns_fgradflat_robust_admm(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, float *y, float *coh, short *bbh, float *wtd, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle); -extern void -cudakernel_fns_fhessflat_robust1(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, cuFloatComplex *fhess, float *y, float *coh, short *bbh, float *wtd); -extern void -cudakernel_fns_fhessflat_robust(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, cuFloatComplex *fhess, float *y, float *coh, short *bbh, float *wtd, cuFloatComplex *Ai, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle); -extern void -cudakernel_fns_fhessflat_robust_admm(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, cuFloatComplex *eta, cuFloatComplex *fhess, float *y, float *coh, short *bbh, float *wtd, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle); -extern void -cudakernel_fns_fupdate_weights(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, float *y, float *coh, short *bbh, float *wtd, float nu0); -extern void -cudakernel_fns_fupdate_weights_q(int ThreadsPerBlock, int BlocksPerGrid, int N, int M, cuFloatComplex *x, float *y, float *coh, short *bbh, float *wtd, float *qd, float nu0); -/****************************** rtr_solve_cuda.c ****************************/ -extern int -rtr_solve_cuda_fl( - float *x, /* initial values and updated solution at output (size 8*N float) */ - float *y, /* data vector (size 8*M float) */ - int N, /* no of stations */ - int M, /* no of constraints */ - int itmax_sd, /* maximum number of iterations RSD */ - int itmax_rtr, /* maximum number of iterations RTR */ - float Delta_bar, float Delta0, /* Trust region radius and initial value */ - double *info, /* initial and final residuals */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - me_data_t *adata); /* pointer to possibly additional data */ - - -extern void -cudakernel_fns_R(int N, cuFloatComplex *x, cuFloatComplex *r, cuFloatComplex *rnew, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle); -extern float -cudakernel_fns_g(int N,cuFloatComplex *x,cuFloatComplex *eta, cuFloatComplex *gamma,cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle); -extern void -cudakernel_fns_proj(int N, cuFloatComplex *x, cuFloatComplex *z, cuFloatComplex *rnew, cublasHandle_t cbhandle, cusolverDnHandle_t solver_handle); -/****************************** rtr_solve_robust_cuda.c ****************************/ -extern int -rtr_solve_cuda_robust_fl( - float *x0, /* initial values and updated solution at output (size 8*N float) */ - float *y, /* data vector (size 8*M float) */ - int N, /* no of stations */ - int M, /* no of constraints */ - int itmax_sd, /* maximum number of iterations RSD */ - int itmax_rtr, /* maximum number of iterations RTR */ - float Delta_bar, float Delta0, /* Trust region radius and initial value */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - double *info, /* initial and final residuals */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - me_data_t *adata); - - -/* Nesterov's steepest descent */ -extern int -nsd_solve_cuda_robust_fl( - float *x0, /* initial values and updated solution at output (size 8*N float) */ - float *y, /* data vector (size 8*M float) */ - int N, /* no of stations */ - int M, /* no of constraints */ - int itmax, /* maximum number of iterations */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - double *info, /* initial and final residuals */ - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - me_data_t *adata); - -/****************************** rtr_solve_robust_cuda_admm.c ****************************/ -/* ADMM solver */ -extern int -rtr_solve_cuda_robust_admm_fl( - float *x0, /* initial values and updated solution at output (size 8*N float) */ - float *Y, /* Lagrange multiplier size 8N */ - float *Z, /* consensus term B Z size 8N */ - float *y, /* data vector (size 8*M float) */ - int N, /* no of stations */ - int M, /* no of constraints */ - int itmax_rtr, /* maximum number of iterations */ - float Delta_bar, float Delta0, /* Trust region radius and initial value */ - float admm_rho, /* ADMM regularization */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - double *info, /* initial and final residuals */ - - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - me_data_t *adata); - - -/* Nesterov's SD */ -extern int -nsd_solve_cuda_robust_admm_fl( - float *x0, /* initial values and updated solution at output (size 8*N float) */ - float *Y, /* Lagrange multiplier size 8N */ - float *Z, /* consensus term B Z size 8N */ - float *y, /* data vector (size 8*M float) */ - int N, /* no of stations */ - int M, /* no of constraints */ - int itmax, /* maximum number of iterations */ - float admm_rho, /* ADMM regularization */ - double robust_nulow, double robust_nuhigh, /* robust nu range */ - double *info, /* initial and final residuals */ - cublasHandle_t cbhandle, /* device handle */ - cusolverDnHandle_t solver_handle, /* solver handle */ - int tileoff, /* tile offset when solving for many chunks */ - int ntiles, /* total tile (data) size being solved for */ - me_data_t *adata); -#endif /* HAVE_CUDA */ -/****************************** lmfit.c ****************************/ -/****************************** lmfit_nocuda.c ****************************/ -/* struct for calling parallel LM jobs */ -typedef struct thread_clm_data_t { - double *p; /* parameters */ - double *x; /* data */ - int M; - int N; - int itermax; - double *opts; - double *info; - int card; - int linsolv; - me_data_t *lmdata; -} thread_clm_data; - - -/* generate a random permutation of given integers */ -/* note: free returned value after use */ -/* n: no of entries, - weighter_iter: if 1, take weight into account - if 0, only generate a random permutation - w: weights (size nx1): sort them in descending order and - give permutation accordingly -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int* -random_permutation(int n, int weighted_iter, double *w); - -/********* solver modes *********/ -#define SM_LM_LBFGS 1 -#define SM_OSLM_LBFGS 0 -#define SM_OSLM_OSRLM_RLBFGS 3 -#define SM_RLM_RLBFGS 2 -#define SM_RTR_OSLM_LBFGS 4 -#define SM_RTR_OSRLM_RLBFGS 5 -#define SM_NSD_RLBFGS 6 -/* fit visibilities - u,v,w: u,v,w coordinates (wavelengths) size Nbase*tilesz x 1 - u,v,w are ordered with baselines, timeslots - x: data to write size Nbase*8*tileze x 1 - ordered by XX(re,im),XY(re,im),YX(re,im), YY(re,im), baseline, timeslots - N: no of stations - Nbase: no of baselines - tilesz: tile size - barr: baseline to station map, size Nbase*tilesz x 1 - carr: sky model/cluster info size Mx1 of clusters - coh: coherencies size Nbase*tilesz*4*M x 1 - M: no of clusters - Mt: actual no of cluster/parameters (for hybrid solutions) Mt>=M - freq0: frequency - fdelta: bandwidth for freq smearing - pp: parameter array 8*N*M x1 double values (re,img) for each station/direction - uvmin: baseline length sqrt(u^2+v^2) below which not to include in solution - Nt: no. of threads - max_emiter: EM iterations - max_iter: iterations within a single EM - max_lbfgs: LBFGS iterations (if>0 outside minimization will be LBFGS) - lbfgs_m: memory size for LBFGS - gpu_threads: GPU threads per block (LBFGS) - linsolv: (GPU/CPU versions) 0: Cholesky, 1: QR, 2: SVD - solver_mode: 0: OS-LM, 1: LM , 2: OS-Robust LM, 3: Robust LM, 4: OS-LM + RTR, 5: OS-LM, RTR, OS-Robust LM - nulow,nuhigh: robust nu search range - randomize: if >0, randomize cluster selection in SAGE and OS subset selection - - mean_nu: output mean value of nu - res_0,res_1: initial and final residuals (output) - return val=0 if final residual< initial residual - return val=-1 if final residual>initial residual -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -sagefit_visibilities(double *u, double *v, double *w, double *x, int N, - int Nbase, int tilesz, baseline_t *barr, clus_source_t *carr, complex double *coh, int M, int Mt, double freq0, double fdelta, double *pp, double uvmin, int Nt,int max_emiter, int max_iter, int max_lbfgs, int lbfgs_m, int gpu_threads, int linsolv, int solver_mode, double nulow, double nuhigh, int randomize, double *mean_nu, double *res_0, double *res_1); - -/* same as above, but uses 2 GPUS in the LM stage */ -extern int -sagefit_visibilities_dual(double *u, double *v, double *w, double *x, int N, - int Nbase, int tilesz, baseline_t *barr, clus_source_t *carr, complex double *coh, int M, int Mt, double freq0, double fdelta, double *pp, double uvmin, int Nt,int max_emiter, int max_iter, int max_lbfgs, int lbfgs_m, int gpu_threads, int linsolv, double nulow, double nuhigh, int randomize, double *mean_nu, double *res_0, double *res_1); - - - -#ifdef USE_MIC -/* wrapper function with bitwise copyable carr[] for MIC */ -/* nchunks: Mx1 array of chunk sizes for each cluster */ -/* pindex: Mt x 1 array of index of solutions for each cluster in pp */ -__attribute__ ((target(MIC))) -extern int -sagefit_visibilities_mic(double *u, double *v, double *w, double *x, int N, - int Nbase, int tilesz, baseline_t *barr, int *nchunks, int *pindex, complex double *coh, int M, int Mt, double freq0, double fdelta, double *pp, double uvmin, int Nt, int max_emiter, int max_iter, int max_lbfgs, int lbfgs_m, int gpu_threads, int linsolv,int solver_mode,double nulow, double nuhigh,int randomize, double *mean_nu, double *res_0, double *res_1); - -__attribute__ ((target(MIC))) -extern int -bfgsfit_visibilities_mic(double *u, double *v, double *w, double *x, int N, - int Nbase, int tilesz, baseline_t *barr, int *nchunks, int *pindex, complex double *coh, int M, int Mt, double freq0, double fdelta, double *pp, double uvmin, int Nt, int max_lbfgs, int lbfgs_m, int gpu_threads, int solver_mode,double nu_mean, double *res_0, double *res_1); -#endif - - -/* BFGS only fit for multi channel data, interface same as sagefit_visibilities_xxx - NO EM iterations are taken */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -extern int -bfgsfit_visibilities(double *u, double *v, double *w, double *x, int N, - int Nbase, int tilesz, baseline_t *barr, clus_source_t *carr, complex double *coh, int M, int Mt, double freq0, double fdelta, double *pp, double uvmin, int Nt, int max_lbfgs, int lbfgs_m, int gpu_threads, int solver_mode, double mean_nu, double *res_0, double *res_1); - - -extern int -bfgsfit_visibilities_gpu(double *u, double *v, double *w, double *x, int N, - int Nbase, int tilesz, baseline_t *barr, clus_source_t *carr, complex double *coh, int M, int Mt, double freq0, double fdelta, double *pp, double uvmin, int Nt, int max_lbfgs, int lbfgs_m, int gpu_threads, int solver_mode, double mean_nu, double *res_0, double *res_1); - - - -/* struct to keep histoty of last used GPU */ -typedef struct taskhist_{ - int prev; /* last used GPU (by any thread) */ - pthread_mutex_t prev_mutex; /* mutex to lock changing prev value */ - unsigned int rseed; /* random seed used in rand_r() */ -} taskhist; - -/* structs for thread pool (reusable), using a barrier */ -/* slave thread data struct */ -typedef struct slave_tdata_ { - struct pipeline_ *pline; /* forward declaration */ - int tid; /* 0,1 for 2 GPUs */ -} slave_tdata; - -/* pipeline struct */ -typedef struct pipeline_ { - void *data; /* all data needed by two threads */ - int terminate; /* 1: terminate, default 0*/ - pthread_t slave0; - pthread_t slave1; - slave_tdata *sd0; /* note recursive types */ - slave_tdata *sd1; - th_barrier gate1; - th_barrier gate2; - pthread_attr_t attr; - taskhist *thst; -} th_pipeline; - -/* pipeline state values */ -#ifndef PT_DO_NOTHING -#define PT_DO_NOTHING 0 -#endif -#ifndef PT_DO_AGPU -#define PT_DO_AGPU 1 /* allocate GPU memory, attach GPU */ -#endif -#ifndef PT_DO_DGPU -#define PT_DO_DGPU 2 /* free GPU memory, detach GPU */ -#endif -#ifndef PT_DO_WORK_LM /* plain LM */ -#define PT_DO_WORK_LM 3 -#endif -#ifndef PT_DO_WORK_OSLM /* OS accel LM */ -#define PT_DO_WORK_OSLM 4 -#endif -#ifndef PT_DO_WORK_RLM /* robust LM */ -#define PT_DO_WORK_RLM 5 -#endif -#ifndef PT_DO_WORK_OSRLM /* robust LM, OS accel */ -#define PT_DO_WORK_OSRLM 6 -#endif -#ifndef PT_DO_WORK_RTR /* RTR */ -#define PT_DO_WORK_RTR 7 -#endif -#ifndef PT_DO_WORK_RRTR /* Robust RTR */ -#define PT_DO_WORK_RRTR 8 -#endif -#ifndef PT_DO_WORK_NSD /* Nesterov's SD */ -#define PT_DO_WORK_NSD 9 -#endif -#ifndef PT_DO_MEMRESET -#define PT_DO_MEMRESET 99 -#endif -/* for BFGS pipeline */ -#ifndef PT_DO_CDERIV -#define PT_DO_CDERIV 20 -#endif -#ifndef PT_DO_CCOST -#define PT_DO_CCOST 21 -#endif - - - -#ifdef HAVE_CUDA -/* data struct shared by all threads */ -typedef struct gb_data_ { - int status[2]; /* 0: do nothing, - 1: allocate GPU memory, attach GPU - 2: free GPU memory, detach GPU - 3,4..: do work on GPU - 99: reset GPU memory (memest all memory) */ - double *p[2]; /* pointer to parameters being solved by each thread */ - double *x[2]; /* pointer to data being fit by each thread */ - int M[2]; - int N[2]; - int itermax[2]; - double *opts[2]; - double *info[2]; - int linsolv; - me_data_t *lmdata[2]; /* two for each thread */ - - /* GPU related info */ - cublasHandle_t cbhandle[2]; /* CUBLAS handles */ - cusolverDnHandle_t solver_handle[2]; /* solver handle */ - double *gWORK[2]; /* GPU buffers */ - int64_t data_size; /* size of buffer (bytes) */ - - double nulow,nuhigh; /* used only in robust version */ - int randomize; /* >0 for randomization */ -} gbdata; - -/* same as above, but using floats */ -typedef struct gb_data_fl_ { - int status[2]; /* 0: do nothing, - 1: allocate GPU memory, attach GPU - 3: free GPU memory, detach GPU - 3,4..: do work on GPU - 99: reset GPU memory (memest all memory) */ - float *p[2]; /* pointer to parameters being solved by each thread */ - float *x[2]; /* pointer to data being fit by each thread */ - int M[2]; - int N[2]; - int itermax[2]; - double *opts[2]; - double *info[2]; - int linsolv; - me_data_t *lmdata[2]; /* two for each thread */ - - /* GPU related info */ - cublasHandle_t cbhandle[2]; /* CUBLAS handles */ - cusolverDnHandle_t solver_handle[2]; /* solver handle */ - float *gWORK[2]; /* GPU buffers */ - int64_t data_size; /* size of buffer (bytes) */ - - double nulow,nuhigh; /* used only in robust version */ - int randomize; /* >0 for randomization */ -} gbdatafl; - -/* for ADMM solver */ -typedef struct gb_data_admm_fl_ { - int status[2]; /* 0: do nothing, - 1: allocate GPU memory, attach GPU - 3: free GPU memory, detach GPU - 3,4..: do work on GPU - 99: reset GPU memory (memest all memory) */ - float *p[2]; /* pointer to parameters being solved by each thread */ - float *Y[2]; /* pointer to Lagrange multiplier */ - float *Z[2]; /* pointer to consensus term */ - float admm_rho[2]; - float *x[2]; /* pointer to data being fit by each thread */ - int M[2]; - int N[2]; - int itermax[2]; - double *opts[2]; - double *info[2]; - int linsolv; - me_data_t *lmdata[2]; /* two for each thread */ - - /* GPU related info */ - cublasHandle_t cbhandle[2]; /* CUBLAS handles */ - cusolverDnHandle_t solver_handle[2]; /* solver handle */ - float *gWORK[2]; /* GPU buffers */ - int64_t data_size; /* size of buffer (bytes) */ - - double nulow,nuhigh; /* used only in robust version */ - int randomize; /* >0 for randomization */ -} gbdatafl_admm; - - -#endif /* !HAVE_CUDA */ - -/* with 2 GPUs */ -extern int -sagefit_visibilities_dual_pt(double *u, double *v, double *w, double *x, int N, - int Nbase, int tilesz, baseline_t *barr, clus_source_t *carr, complex double *coh, int M, int Mt, double freq0, double fdelta, double *pp, double uvmin, int Nt,int max_emiter, int max_iter, int max_lbfgs, int lbfgs_m, int gpu_threads, int linsolv, int solver_mode, double nulow, double nuhigh, int randomize, double *mean_nu, double *res_0, double *res_1); - -/* with 1 GPU and 1 CPU thread */ -extern int -sagefit_visibilities_dual_pt_one_gpu(double *u, double *v, double *w, double *x, int N, - int Nbase, int tilesz, baseline_t *barr, clus_source_t *carr, complex double *coh, int M, int Mt, double freq0, double fdelta, double *pp, double uvmin, int Nt, int max_emiter, int max_iter, int max_lbfgs, int lbfgs_m, int gpu_threads, int linsolv,int solver_mode, double nulow, double nuhigh, int randomize, double *mean_nu, double *res_0, double *res_1); - -/* with mixed precision */ -extern int -sagefit_visibilities_dual_pt_flt(double *u, double *v, double *w, double *x, int N, - int Nbase, int tilesz, baseline_t *barr, clus_source_t *carr, complex double *coh, int M, int Mt, double freq0, double fdelta, double *pp, double uvmin, int Nt, int max_emiter, int max_iter, int max_lbfgs, int lbfgs_m, int gpu_threads, int linsolv,int solver_mode, double nulow, double nuhigh, int randomize, double *mean_nu, double *res_0, double *res_1); - -/****************************** diagnostics.c ****************************/ -#ifdef HAVE_CUDA -/* Calculate St.Laurent-Cook Jacobian leverage -x: input: residual, output: levarage - flags: 2 for flags based on uvcut, 1 for normal flags - coh: coherencies are calculated for all baselines, regardless of flag - diagmode: 1: replaces residual with Jacobian Leverage, 2: calculates (prints) fraction of leverage/noise - */ -extern int -calculate_diagnostics(double *u,double *v,double *w,double *p,double *x,int N,int Nbase,int tilesz,baseline_t *barr, clus_source_t *carr, complex double *coh, int M,int Mt,int diagmode,int Nt); -#endif - - -/****************************** diag_fl.cu ****************************/ -#ifdef HAVE_CUDA -/* cuda driver for calculating Jacobian for leverage */ -/* p: params (Mx1), jac: jacobian (NxM), other data : coh, baseline->stat mapping, Nbase, Mclusters, Nstations */ -/* flags are always ignored */ -extern void -cudakernel_jacf_fl2(float *p, float *jac, int M, int N, float *coh, short *bbh, int Nbase, int Mclus, int Nstations); - -/* invert sqrt(singular values) Sd[]=1/sqrt(Sd[]) for Sd[]> eps */ -extern void -cudakernel_sqrtdiv_fl(int ThreadsPerBlock, int BlocksPerGrid, int M, float eps, float *Sd); - -/* U <= U D, - U : MxM - D : Mx1, diagonal matrix -*/ -extern void -cudakernel_diagmult_fl(int ThreadsPerBlock, int BlocksPerGrid, int M, float * U, float *D); - -/* diag(J^T J) - d[i] = J[i,:] * J[i,:] - J: NxM (in row major order, so J[i,:] is actually J[:,i] - d: Nx1 -*/ -extern void -cudakernel_jnorm_fl(int ThreadsPerBlock, int BlocksPerGrid, float *J, int N, int M, float *d); -#endif - - -/****************************** manifold_average.c ****************************/ -/* calculate manifold average of 2Nx2 solution blocks, - then project each solution to this average - Y: 2Nx2 x M x Nf values (average calculated for each 2Nx2 x Nf blocks) - N: no of stations - M: no of directions - Nf: no of frequencies - Niter: everaging iterations - Nt: threads -*/ -extern int -calculate_manifold_average(int N,int M,int Nf,double *Y,int Niter,int Nt); - - -/* find U to minimize - ||J-J1 U|| solving Procrustes problem - J,J1 : 8N x 1 vectors, in standard format - will be reshaped to 2Nx2 format and J1 will be modified as J1 U -*/ -extern int -project_procrustes(int N,double *J,double *J1); - -/* same as above, but J,J1 are in right 2Nx2 matrix format */ -/* J1 is modified */ -extern int -project_procrustes_block(int N,complex double *J,complex double *J1); - - -/* Extract only the phase of diagonal entries from solutions - p: 8Nx1 solutions, orders as [(real,imag)vec(J1),(real,imag)vec(J2),...] - pout: 8Nx1 phases (exp(j*phase)) of solutions, after joint diagonalization of p - N: no. of 2x2 Jones matrices in p, having common unitary ambiguity - niter: no of iterations for Jacobi rotation */ -extern int -extract_phases(double *p, double *pout, int N, int niter); -/****************************** consensus_poly.c ****************************/ -/* build matrix with polynomial terms - B : Npoly x Nf, each row is one basis function - Npoly : total basis functions - Nf: frequencies - freqs: Nfx1 array freqs - freq0: reference freq - type : 0 for [1 ((f-fo)/fo) ((f-fo)/fo)^2 ...] basis functions - 1 : same as type 0, normalize each row such that norm is 1 - 2 : Bernstein poly \sum N_C_r x^r (1-x)^r where x in [0,1] : use min,max values of freq to normalize -*/ -extern int -setup_polynomials(double *B, int Npoly, int Nf, double *freqs, double freq0, int type); - -/* build matrix with polynomial terms - B : Npoly x Nf, each row is one basis function - Bi: Npoly x Npoly pseudo inverse of sum( B(:,col) x B(:,col)' ) - Npoly : total basis functions - Nf: frequencies - fratio: Nfx1 array of weighing factors depending on the flagged data of each freq - Sum taken is a weighted sum, using weights in fratio -*/ -extern int -find_prod_inverse(double *B, double *Bi, int Npoly, int Nf, double *fratio); - -/* update Z - Z: 8NxNpoly x M double array (real and complex need to be updated separate) - N : stations - M : clusters - Npoly: no of basis functions - z : right hand side 8NMxNpoly (note the different ordering from Z) - Bi : NpolyxNpoly matrix, Bi^T=Bi assumed -*/ -extern int -update_global_z(double *Z,int N,int M,int Npoly,double *z,double *Bi); - - -/****************************** admm_solve.c ****************************/ -/* ADMM cost function = normal_cost + ||Y^H(J-BZ)|| + rho/2 ||J-BZ||^2 */ -/* extra params - Y : Lagrange multiplier - BZ : consensus term - Y,BZ : size same as pp : 8*N*Mt x1 double values (re,img) for each station/direction - admm_rho : regularization factor array size Mx1 -*/ -extern int -sagefit_visibilities_admm(double *u, double *v, double *w, double *x, int N, - int Nbase, int tilesz, baseline_t *barr, clus_source_t *carr, complex double *coh, int M, int Mt, double freq0, double fdelta, double *pp, double *Y, double *BZ, double uvmin, int Nt, int max_emiter, int max_iter, int max_lbfgs, int lbfgs_m, int gpu_threads, int linsolv,int solver_mode,double nulow, double nuhigh,int randomize, double *admm_rho, double *mean_nu, double *res_0, double *res_1); - -/* ADMM cost function = normal_cost + ||Y^H(J-BZ)|| + rho/2 ||J-BZ||^2 */ -/* extra params - Y : Lagrange multiplier - BZ : consensus term - Y,BZ : size same as pp : 8*N*Mt x1 double values (re,img) for each station/direction - admm_rho : regularization factor array size Mx1 -*/ -#ifdef HAVE_CUDA -extern int -sagefit_visibilities_admm_dual_pt_flt(double *u, double *v, double *w, double *x, int N, - int Nbase, int tilesz, baseline_t *barr, clus_source_t *carr, complex double *coh, int M, int Mt, double freq0, double fdelta, double *pp, double *Y, double *BZ, double uvmin, int Nt, int max_emiter, int max_iter, int max_lbfgs, int lbfgs_m, int gpu_threads, int linsolv,int solver_mode, double nulow, double nuhigh, int randomize, double *admm_rho, double *mean_nu, double *res_0, double *res_1); -#endif - - -/****************************** load_balance.c ****************************/ -/* select a GPU from 0,1..,max_gpu - in such a way to allow load balancing */ -/* also keep a global variableto ensure same GPU is - not assigned to one process */ -#ifdef HAVE_CUDA -extern void -init_task_hist(taskhist *th); -extern void -destroy_task_hist(taskhist *th); - -extern int -select_work_gpu(int max_gpu, taskhist *th); -#endif -/****************************** OpenBLAS ************************************/ -/* prototype declaration */ -extern void -openblas_set_num_threads(int num_threads); - -/****************************** transforms.c ****************************/ -#ifndef ASEC2RAD -#define ASEC2RAD 4.848136811095359935899141e-6 -#endif - -/* - convert xyz ITRF 2000 coords (m) to - long,lat, (rad) height (m) - References: -*/ -extern int -xyz2llh(double *x, double *y, double *z, double *longitude, double *latitude, double *height, int N); - -/* convert ra,dec to az,el - ra,dec: radians - longitude,latitude: rad,rad - time_jd: JD days - - az,el: output rad,rad - -References: Darin C. Koblick MATLAB code, based on - % Fundamentals of Astrodynamics and Applications - % D. Vallado, Second Edition - % Example 3-5. Finding Local Siderial Time (pg. 192) - % Algorithm 28: AzElToRaDec (pg. 259) -*/ -extern int -radec2azel(double ra, double dec, double longitude, double latitude, double time_jd, double *az, double *el); - -/* convert time to Greenwitch Mean Sideral Angle (deg) - time_jd : JD days - thetaGMST : GMST angle (deg) -*/ -extern int -jd2gmst(double time_jd, double *thetaGMST); - - -/* convert ra,dec to az,el - ra,dec: radians - longitude,latitude: rad,rad - thetaGMST : GMST angle (deg) - - az,el: output rad,rad - -*/ -extern int -radec2azel_gmst(double ra, double dec, double longitude, double latitude, double thetaGMST, double *az, double *el); - - - -/* given the epoch jd_tdb2, - calculate rotation matrix params needed to precess from J2000 - NOVAS (Naval Observatory Vector Astronomy Software) - PURPOSE: - Precesses equatorial rectangular coordinates from one epoch to - another. One of the two epochs must be J2000.0. The coordinates - are referred to the mean dynamical equator and equinox of the two - respective epochs. - - REFERENCES: - Explanatory Supplement To The Astronomical Almanac, pp. 103-104. - Capitaine, N. et al. (2003), Astronomy And Astrophysics 412, - pp. 567-586. - Hilton, J. L. et al. (2006), IAU WG report, Celest. Mech., 94, - pp. 351-367. - -*/ -extern int -get_precession_params(double jd_tdb2, double Tr[9]); -/* precess ra0,dec0 at J2000 - to ra,dec at epoch given by transform Tr - using NOVAS library */ -extern int -precession(double ra0, double dec0, double Tr[9], double *ra, double *dec); - -/****************************** stationbeam.c ****************************/ -/* - ra,dec: source direction (rad) - ra0,dec0: beam center (rad) - f: frequency (Hz) - f0: beam forming frequency (Hz) - - longitude,latitude : Nx1 array of station positions (rad,rad) - time_jd: JD (day) time - Nelem : Nx1 array, no. of elements used in each station - x,y,z: Nx1 pointer arrays to station positions, each station has Nelem[]x1 arrays - - beamgain: Nx1 array of station beam gain along the source direction -*/ -extern int -arraybeam(double ra, double dec, double ra0, double dec0, double f, double f0, int N, double *longitude, double *latitude, double time_jd, int *Nelem, double **x, double **y, double **z, double *beamgain); - - -/****************************** predict_withbeam.c ****************************/ -/* precalculate cluster coherencies - u,v,w: u,v,w coordinates (wavelengths) size Nbase*tilesz x 1 - u,v,w are ordered with baselines, timeslots - x: coherencies size Nbase*4*Mx 1 - ordered by XX(re,im),XY(re,im),YX(re,im), YY(re,im), baseline, timeslots - N: no of stations - Nbase: total no of baselines (including more than one tile or timeslot) - barr: baseline to station map, size Nbase*tilesz x 1 - carr: sky model/cluster info size Mx1 of clusters - M: no of clusters - freq0: frequency - fdelta: bandwidth for freq smearing - tdelta: integration time for time smearing - dec0: declination for time smearing - uvmin: baseline length sqrt(u^2+v^2) below which not to include in solution - uvmax: baseline length higher than this not included in solution - - Station beam specific parameters - ph_ra0,ph_dec0: beam pointing rad,rad - ph_freq0: beam reference freq - longitude,latitude: Nx1 arrays (rad,rad) station locations - time_utc: JD (day) : tilesz x 1 - tilesz: how many tiles: == unique time_utc - Nelem: Nx1 array, size of stations (elements) - xx,yy,zz: Nx1 arrays of station element locations arrays xx[],yy[],zz[] - Nt: no of threads - - NOTE: prediction is done for all baselines, even flagged ones - and flags are set to 2 for baselines lower than uvcut -*/ - -extern int -precalculate_coherencies_withbeam(double *u, double *v, double *w, complex double *x, int N, - int Nbase, baseline_t *barr, clus_source_t *carr, int M, double freq0, double fdelta, double tdelta, double dec0, double uvmin, double uvmax, - double ph_ra0, double ph_dec0, double ph_freq0, double *longitude, double *latitude, double *time_utc, int tileze, int *Nelem, double **xx, double **yy, double **zz, int Nt); - - -extern int -predict_visibilities_multifreq_withbeam(double *u,double *v,double *w,double *x,int N,int Nbase,int tilesz,baseline_t *barr, clus_source_t *carr, int M,double *freqs,int Nchan, double fdelta,double tdelta, double dec0, - double ph_ra0, double ph_dec0, double ph_freq0, double *longitude, double *latitude, double *time_utc,int *Nelem, double **xx, double **yy, double **zz, int Nt, int add_to_data); - -extern int -calculate_residuals_multifreq_withbeam(double *u,double *v,double *w,double *p,double *x,int N,int Nbase,int tilesz,baseline_t *barr, clus_source_t *carr, int M,double *freqs,int Nchan, double fdelta,double tdelta,double dec0, -double ph_ra0, double ph_dec0, double ph_freq0, double *longitude, double *latitude, double *time_utc,int *Nelem, double **xx, double **yy, double **zz, int Nt, int ccid, double rho, int phase_only); - - -/* change epoch of soure ra,dec from J2000 to JAPP */ -/* also the beam pointing ra_beam,dec_beam */ -extern int -precess_source_locations(double jd_tdb, clus_source_t *carr, int M, double *ra_beam, double *dec_beam, int Nt); - -/****************************** predict_withbeam_gpu.c ****************************/ -/* if dobeam==0, beam calculation is off */ -extern int -precalculate_coherencies_withbeam_gpu(double *u, double *v, double *w, complex double *x, int N, - int Nbase, baseline_t *barr, clus_source_t *carr, int M, double freq0, double fdelta, double tdelta, double dec0, double uvmin, double uvmax, - double ph_ra0, double ph_dec0, double ph_freq0, double *longitude, double *latitude, double *time_utc, int tileze, int *Nelem, double **xx, double **yy, double **zz, int dobeam, int Nt); - -extern int -predict_visibilities_multifreq_withbeam_gpu(double *u,double *v,double *w,double *x,int N,int Nbase,int tilesz,baseline_t *barr, clus_source_t *carr, int M,double *freqs,int Nchan, double fdelta,double tdelta, double dec0, - double ph_ra0, double ph_dec0, double ph_freq0, double *longitude, double *latitude, double *time_utc,int *Nelem, double **xx, double **yy, double **zz, int dobeam, int Nt, int add_to_data); - - - -/****************************** predict_model.cu ****************************/ -extern void -cudakernel_array_beam(int N, int T, int K, int F, float *freqs, float *longitude, float *latitude, - double *time_utc, int *Nelem, float **xx, float **yy, float **zz, float *ra, float *dec, float ph_ra0, float ph_dec0, float ph_freq0, float *beam); - - -extern void -cudakernel_coherencies(int B, int N, int T, int K, int F, float *u, float *v, float *w,baseline_t *barr, float *freqs, float *beam, float *ll, float *mm, float *nn, float *sI, - unsigned char *stype, float *sI0, float *f0, float *spec_idx, float *spec_idx1, float *spec_idx2, int **exs, float deltaf, float deltat, float dec0, float *coh,int dobeam); - - -extern void -cudakernel_convert_time(int T, double *time_utc); -#ifdef __cplusplus - } /* extern "C" */ -#endif -#endif /* SAGECAL_H */ diff --git a/src/lib/stationbeam.c b/src/lib/stationbeam.c deleted file mode 100644 index 7c7aec8..0000000 --- a/src/lib/stationbeam.c +++ /dev/null @@ -1,112 +0,0 @@ -/* - * - Copyright (C) 2016 Sarod Yatawatta - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - - -#define _GNU_SOURCE -#include -#include -#include - -#include "sagecal.h" - - -/* - ra,dec: source direction (rad) - ra0,dec0: beam center (rad) - f: frequency (Hz) - f0: beam forming frequency (Hz) - - longitude,latitude : Nx1 array of station positions (rad,rad) - time_jd: JD (day) time - Nelem : Nx1 array, no. of elements used in each station - x,y,z: Nx1 pointer arrays to station positions, each station has Nelem[]x1 arrays - - beamgain: Nx1 array of station beam gain along the source direction -*/ -int -arraybeam(double ra, double dec, double ra0, double dec0, double f, double f0, int N, double *longitude, double *latitude, double time_jd, int *Nelem, double **x, double **y, double **z, double *beamgain) { - - double gmst; - jd2gmst(time_jd,&gmst); /* JD (day) to GMST (deg) */ - int ci,cj,K; - double az,el,az0,el0; - double theta,phi,theta0,phi0; - double *px,*py,*pz; - double r1,r2,r3; - double sint,cost,sint0,cost0,sinph,cosph,sinph0,cosph0; - double csum,ssum,tmpc,tmps; - /* 2*PI/C */ - const double tpc=2.0*M_PI/CONST_C; - - /* iterate over stations */ - for (ci=0; ci=0.0) { - K=Nelem[ci]; - px=x[ci]; - py=y[ci]; - pz=z[ci]; - - sincos(theta,&sint,&cost); - sincos(phi,&sinph,&cosph); - sincos(theta0,&sint0,&cost0); - sincos(phi0,&sinph0,&cosph0); - - /*r1=f0*sint0*cosph0-f*sint*cosph; - r2=f0*sint0*sinph0-f*sint*sinph; - r3=f0*cost0-f*cost; - */ - - /* try to improve computations */ - double rat1=f0*sint0; - double rat2=f*sint; - r1=(rat1*cosph0-rat2*cosph); - r2=(rat1*sinph0-rat2*sinph); - r3=(f0*cost0-f*cost); - - csum=0.0; - ssum=0.0; - for (cj=0; cj - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - - -#define _GNU_SOURCE -#include -#include -#include - -#include "sagecal.h" -/* - convert xyz ITRF 2000 coords (m) to - long,lat, (rad) height (m) - References: xyz2llh.m MATLAB routine - Also : Hoffmann-Wellenhof, B., Lichtenegger, H. and J. Collins (1997). GPS. - Theory and Practice. 4th revised edition. Springer, New York, pp. 389 -*/ - -int -xyz2llh(double *x, double *y, double *z, double *longitude, double *latitude, double *height, int N) { - /* constants */ - double a=6378137.0; /* semimajor axis */ - double f=1.0/298.257223563; /* flattening */ - double b=(1.0-f)*a; /* semiminor axis */ - double e2=2*f-f*f; /* exxentricity squared */ - double ep2=(a*a-b*b)/(b*b); /* second numerical eccentricity */ - double *p,*theta; - if ((p=(double*)calloc((size_t)N,sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - if ((theta=(double*)calloc((size_t)N,sizeof(double)))==0) { - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); - exit(1); - } - - int ci; - - for (ci=0; ci - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - $Id$ - */ - -#include "sagecal.h" -#include - -/* Digamma function - if x>7 use digamma(x) = digamma(x+1) - 1/x - for accuracy - using maple expansion - series(Psi(x+1/2), x=infinity, 21); - ln(x)+1/24/x^2-7/960/x^4+31/8064/x^6-127/30720/x^8+511/67584/x^10-1414477/67092480/x^12+8191/98304/x^14-118518239/267386880/x^16+5749691557/1882718208/x^18-91546277357/3460300800/x^20+O(1/x^21) - - - based on code by Mark Johnson, 2nd September 2007 -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static double -digamma(double x) { - /* FIXME catch -ve value as input */ - double result = 0.0, xx, xx2, xx4; - for ( ; x < 7.0; ++x) { /* reduce x till x<7 */ - result -= 1.0/x; - } - x -= 0.5; - xx = 1.0/x; - xx2 = xx*xx; - xx4 = xx2*xx2; - result += log(x)+(1./24.)*xx2-(7.0/960.0)*xx4+(31.0/8064.0)*xx4*xx2-(127.0/30720.0)*xx4*xx4; - return result; -} - - - -/* update w<= (nu+1)/(nu+delta^2) - then q <= w-log(w), so that it is +ve -*/ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -w_nu_update_threadfn(void *data) { - thread_data_vecnu_t *t=(thread_data_vecnu_t*)data; - int ci; - for (ci=t->starti; ci<=t->endi; ci++) { - //t->ed[ci]*=t->wtd[ci]; ?? - t->wtd[ci]=(t->nu0+1.0)/(t->nu0+t->ed[ci]*t->ed[ci]); - t->q[ci]=t->wtd[ci]-log(t->wtd[ci]); - } - return NULL; -} - -/* update w<= sqrt(w) */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -w_sqrt_threadfn(void *data) { - thread_data_vecnu_t *t=(thread_data_vecnu_t*)data; - int ci; - for (ci=t->starti; ci<=t->endi; ci++) { - t->wtd[ci]=sqrt(t->wtd[ci]); - } - return NULL; -} - -/* update nu */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -q_update_threadfn(void *data) { - thread_data_vecnu_t *t=(thread_data_vecnu_t*)data; - int ci; - double thisnu,dgm; - for (ci=t->starti; ci<=t->endi; ci++) { - thisnu=(t->nulow+(double)ci*t->nu0); /* deltanu stored in nu0 */ - dgm=digamma(thisnu*0.5+0.5); - t->q[ci]=dgm-log((thisnu+1.0)*0.5); /* psi((nu+1)/2)-log((nu+1)/2) */ - dgm=digamma(thisnu*0.5); - t->q[ci]+=-dgm+log((thisnu)*0.5); /* -psi((nu)/2)+log((nu)/2) */ - t->q[ci]+=-t->sumq+1.0; /* q is w-log(w), so -ve: sum(ln(w_i))/N-sum(w_i)/N+1 */ - } - return NULL; -} - -/* update nu */ -#ifdef USE_MIC -__attribute__ ((target(MIC))) -#endif -static void * -q_update_threadfn_aecm(void *data) { - thread_data_vecnu_t *t=(thread_data_vecnu_t*)data; - int ci; - double thisnu,dgm; - for (ci=t->starti; ci<=t->endi; ci++) { - thisnu=(t->nulow+(double)ci*t->nu0); /* deltanu stored in nu0 */ - dgm=digamma(thisnu*0.5); - t->q[ci]=-dgm+log((thisnu)*0.5); /* -psi((nu)/2)+log((nu)/2) */ - t->q[ci]+=-t->sumq+1.0; /* q is w-log(w), so -ve: sum(ln(w_i))/N-sum(w_i)/N+1 */ - } - return NULL; -} - -/* update nu (degrees of freedom) - also update w - - nu0: current value of nu - w: Nx1 weight vector - ed: Nx1 residual error - - - psi() : digamma function - find soltion to - psi((nu+1)/2)-ln((nu+1)/2)-psi(nu/2)+ln(nu/2)+1/N sum(ln(w_i)-w_i) +1 = 0 - use ln(gamma()) => lgamma_r -*/ -double -update_w_and_nu(double nu0, double *w, double *ed, int N, int Nt, double nulow, double nuhigh) { - int Nd=30; /* no of samples to estimate nu */ - int nth,nth1,ci; - int Nthb0,Nthb; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_vecnu_t *threaddata; - - double deltanu,*q,thisnu,sumq; - if ((q=(double*)calloc((size_t)N,sizeof(double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((threaddata=(thread_data_vecnu_t*)malloc((size_t)Nt*sizeof(thread_data_vecnu_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - /* calculate min values a thread can handle */ - Nthb0=(N+Nt-1)/Nt; - /* iterate over threads, allocating indices per thread */ - ci=0; - for (nth=0; nth lgamma_r - - p: 1 or 8 -*/ -double -update_nu(double logsumw, int Nd, int Nt, double nulow, double nuhigh, int p, double nu_old) { - int ci,nth,nth1,Nthb,Nthb0; - double deltanu,thisnu,*q; - pthread_attr_t attr; - pthread_t *th_array; - thread_data_vecnu_t *threaddata; - - if ((q=(double*)calloc((size_t)Nd,sizeof(double)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: no free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - /* setup threads */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - if ((threaddata=(thread_data_vecnu_t*)malloc((size_t)Nt*sizeof(thread_data_vecnu_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - /* calculate psi((nu_old+p)/2)-ln((nu_old+p)/2) */ - double dgm=digamma((nu_old+(double)p)*0.5); - dgm=dgm-log((nu_old+(double)p)*0.5); /* psi((nu+p)/2)-log((nu+p)/2) */ - - - deltanu=(double)(nuhigh-nulow)/(double)Nd; - Nthb0=(Nd+Nt-1)/Nt; - /* check for too low number of values per thread, halve the threads */ - if (Nthb0<=2) { - Nt=Nt/2; - Nthb0=(Nd+Nt-1)/Nt; - } - ci=0; - for (nth=0; nth400.0) return 1.0; /* no effect on long baselines */ - //return 1.0/(1.0+0.4*exp(-0.05*ud)); - return 1.0/(1.0+1.8*exp(-0.05*ud)); -} - -static void * -threadfn_setblweight(void *data) { - thread_data_baselinewt_t *t=(thread_data_baselinewt_t*)data; - - int ci; - for (ci=0; ciNb; ci++) { - /* get sqrt(u^2+v^2) */ - double uu=t->u[ci+t->boff]*t->freq0; - double vv=t->v[ci+t->boff]*t->freq0; - double a=ncp_weight(sqrt(uu*uu+vv*vv)); - t->wt[8*(ci+t->boff)]*=a; - t->wt[8*(ci+t->boff)+1]*=a; - t->wt[8*(ci+t->boff)+2]*=a; - t->wt[8*(ci+t->boff)+3]*=a; - t->wt[8*(ci+t->boff)+4]*=a; - t->wt[8*(ci+t->boff)+5]*=a; - t->wt[8*(ci+t->boff)+6]*=a; - t->wt[8*(ci+t->boff)+7]*=a; - //printf("%lf %lf %lf\n",uu,vv,a); - } - - return NULL; -} - - -/* - taper data by weighting based on uv distance (for short baselines) - for example: use weights as the inverse density function - 1/( 1+f(u,v) ) - as u,v->inf, f(u,v) -> 0 so long baselines are not affected - x: Nbase*8 x 1 (input,output) data - u,v : Nbase x 1 - note: u = u/c, v=v/c here, so need freq to convert to wavelengths */ -void -whiten_data(int Nbase, double *x, double *u, double *v, double freq0, int Nt) { - pthread_attr_t attr; - pthread_t *th_array; - thread_data_baselinewt_t *threaddata; - - int ci,nth1,nth; - int Nthb0,Nthb; - - Nthb0=(Nbase+Nt-1)/Nt; - - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); - - if ((th_array=(pthread_t*)malloc((size_t)Nt*sizeof(pthread_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - if ((threaddata=(thread_data_baselinewt_t*)malloc((size_t)Nt*sizeof(thread_data_baselinewt_t)))==0) { -#ifndef USE_MIC - fprintf(stderr,"%s: %d: No free memory\n",__FILE__,__LINE__); -#endif - exit(1); - } - - - /* iterate over threads, allocating baselines per thread */ - ci=0; - for (nth=0; nth sm.ms.output +../src/MS/sagecal -d sm.ms -s extended_source_list.txt -c extended_source_list.txt.cluster -n 4 -t 10 -p sm.ms.solutions -e 4 -g 2 -l 10 -m 7 -x 30 -F 1 -j 2 -k -1 -B 1 -W 0 > sm.ms.output