From dd6284e1363206dc741423a496bc9560f3f5c0db Mon Sep 17 00:00:00 2001 From: Hanno Spreeuw Date: Thu, 7 Sep 2017 14:25:04 +0200 Subject: [PATCH 1/8] Modifications in order to make a calltree for the CPU version of Sagecal --- src/MS/Makefile | 8 +++++--- src/MS/main.cpp | 1 + src/lib/Dirac/Makefile | 6 ++++-- src/lib/Radio/Makefile | 6 ++++-- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/MS/Makefile b/src/MS/Makefile index 1fafa54..cd2fe2a 100644 --- a/src/MS/Makefile +++ b/src/MS/Makefile @@ -1,8 +1,10 @@ OUTPUT= CXX=g++ -CXXFLAGS=-O3 -Wall -g #-pg #-fnostack-protector -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 +# CXXFLAGS=-O3 -Wall -g -pg -std=c++11 #-pg #-fnostack-protector +# Extra arguments for making callgraphs. +CXXFLAGS=-O3 -Wall -g -pg -std=c++11 -ansi -fPIC -fpermissive -fno-omit-frame-pointer -DNDEBUG -fno-inline-functions -fno-inline-functions-called-once -fno-optimize-sibling-calls +CASA_LIBDIR=-L/cm/shared/package/casacore/v2.3.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.3.0-gcc-4.9.3/include -I/cm/shared/package/casacore/v2.3.0-gcc-4.9.3/include/casacore CASA_LIBS=-lcasa_casa -lcasa_tables -lcasa_measures -lcasa_ms -lcfitsio #LAPACK=-llapack -lblas LAPACK=-lopenblas -lgfortran -lpthread diff --git a/src/MS/main.cpp b/src/MS/main.cpp index 05bb0a0..680a50d 100644 --- a/src/MS/main.cpp +++ b/src/MS/main.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include "cuda_profiler_api.h" diff --git a/src/lib/Dirac/Makefile b/src/lib/Dirac/Makefile index e88b1eb..350dfe9 100644 --- a/src/lib/Dirac/Makefile +++ b/src/lib/Dirac/Makefile @@ -1,7 +1,9 @@ CC=gcc CXX=g++ -#CFLAGS= -Wall -O3 -g #-pg -CFLAGS= -Wall -O3 -fopt-info-optimized +CFLAGS= -Wall -g -pg +# Extra args for making callgraphs. +# CFLAGS= -Wall -pg -O2 -ansi -fPIC -fpermissive -fno-omit-frame-pointer -DNDEBUG -fno-inline-functions -fno-inline-functions-called-once -fno-optimize-sibling-calls +# 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 diff --git a/src/lib/Radio/Makefile b/src/lib/Radio/Makefile index 3bbe6fe..06f3214 100644 --- a/src/lib/Radio/Makefile +++ b/src/lib/Radio/Makefile @@ -1,7 +1,9 @@ CC=gcc CXX=g++ -#CFLAGS= -Wall -O3 -g #-pg -CFLAGS= -Wall -O3 -fopt-info-optimized +CFLAGS= -Wall -g -pg +# Extra args for making callgraphs. +# CFLAGS= -Wall -pg -O2 -ansi -fPIC -fpermissive -fno-omit-frame-pointer -DNDEBUG -fno-inline-functions -fno-inline-functions-called-once -fno-optimize-sibling-calls +# 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 From d6b518116edadd9870ad9138047b7d1a92b47bde Mon Sep 17 00:00:00 2001 From: Hanno Spreeuw Date: Fri, 3 Nov 2017 14:24:06 +0100 Subject: [PATCH 2/8] These source and Makefiles are redundant, since they are present in the Dirac and Radio subdirectories, albeit somewhat altered. --- src/lib/Makefile | 66 - src/lib/Makefile.MIC | 66 - src/lib/Makefile.gpu | 108 - 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/stationbeam.c | 112 - src/lib/transforms.c | 289 --- src/lib/updatenu.c | 443 ---- 42 files changed, 42956 deletions(-) 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/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/stationbeam.c delete mode 100644 src/lib/transforms.c delete mode 100644 src/lib/updatenu.c diff --git a/src/lib/Makefile b/src/lib/Makefile deleted file mode 100644 index 7382d84..0000000 --- a/src/lib/Makefile +++ /dev/null @@ -1,66 +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=readsky.o dataio.o predict.o 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 transforms.o stationbeam.o predict_withbeam.o - -default:libsagecal.a -readsky.o:readsky.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -dataio.o:dataio.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -predict.o:predict.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -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 $< -transforms.o:transforms.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -stationbeam.o:stationbeam.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -predict_withbeam.o:predict_withbeam.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< - - -RANLIB=ranlib -libsagecal.a:$(OBJECTS) sagecal.h - ar rv $@ $(OBJECTS); \ - $(RANLIB) $@; diff --git a/src/lib/Makefile.MIC b/src/lib/Makefile.MIC deleted file mode 100644 index add265b..0000000 --- a/src/lib/Makefile.MIC +++ /dev/null @@ -1,66 +0,0 @@ -CC=icc -CXX=icpc -LD=icc - -# MKL -MKLROOT=/opt/intel/composer_xe_2013.5.192/mkl -IFACE_LIB=mkl_intel_lp64 -THREADING_LIB=mkl_intel_thread -CORE_LIB=mkl_core - -LDFLAGS=-L$(MKLROOT)/lib/intel64 -l$(IFACE_LIB) -l$(THREADING_LIB) -l$(CORE_LIB) -lpthread -lm -MIC_LDFLAGS=-L$(MKLROOT)/lib/mic -l$(IFACE_LIB) -l$(THREADING_LIB) -l$(CORE_LIB) - - -##CFLAGS +=-DUSE_MIC -Wall -DDEBUG -g -O0 -openmp -vec-report=1 -#CFLAGS +=-DUSE_MIC -Wall -O1 -profile-functions -profile-loops=all -profile-loops-report=2 -openmp -CFLAGS +=-DUSE_MIC -Wall -O3 -openmp -vec-report=1 -#MICFLAGS =-offload-option,mic,compiler,"-DUSE_MIC -vec-report1 -g -O0 -Wall" -MICFLAGS =-offload-option,mic,compiler,"-DUSE_MIC -vec-report1 -O3 -openmp -Wall" -#MICFLAGS =-offload-option,mic,compiler,"-DUSE_MIC -O1 -profile-functions -profile-loops=all -profile-loops-report=2 -openmp" -MICLDFLAGS=-offload-option,mic,ld,"$(MIC_LDFLAGS)" - -#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/lib64/glib-2.0/include/ -GLIBL=-lglib-2.0 - -OBJECTS=readsky.o dataio.o predict.o lmfit_nocuda.o clmfit_nocuda.o lbfgs_nocuda.o myblas.o residual.o robustlm.o updatenu.o robust_lbfgs_nocuda.o -# clmfit_nocudaMIC.o lmfit_nocudaMIC.o robust_lbfgs_nocudaMIC.o updatenuMIC.o\ - lbfgs_nocudaMIC.o myblasMIC.o robustlmMIC.o - -default:libsagecal.a -readsky.o:readsky.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -dataio.o:dataio.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -predict.o:predict.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -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 $< - -RANLIB=ranlib -libsagecal.a:$(OBJECTS) sagecal.h - xiar -qoffload-build rv $@ $(OBJECTS); \ - $(RANLIB) $@; diff --git a/src/lib/Makefile.gpu b/src/lib/Makefile.gpu deleted file mode 100644 index 635af65..0000000 --- a/src/lib/Makefile.gpu +++ /dev/null @@ -1,108 +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=readsky.o dataio.o predict.o 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 transforms.o stationbeam.o predict_withbeam.o predict_withbeam_gpu.o predict_model.o predict_model_device.o - - -default:libsagecal.a -readsky.o:readsky.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -dataio.o:dataio.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -predict.o:predict.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -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 $< -transforms.o:transforms.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -stationbeam.o:stationbeam.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -predict_withbeam.o:predict_withbeam.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -predict_withbeam_gpu.o:predict_withbeam_gpu.c - $(CC) $(CFLAGS) $(INCLUDES) $(GLIBI) -c $< -## for dynamic parallelism, two stage compilation -predict_model.o:predict_model_device.o - $(NVCC) $(NVCFLAGS) -lineinfo -dlink $(INCLUDES) $(GLIBI) -o $@ $< -predict_model_device.o:predict_model.cu - $(NVCC) $(NVCFLAGS) -lineinfo -rdc=true $(INCLUDES) $(GLIBI) -o $@ -c $< - - -RANLIB=ranlib -libsagecal.a:$(OBJECTS) sagecal.h - ar rv $@ $(OBJECTS); \ - $(RANLIB) $@; diff --git a/src/lib/admm_solve.c b/src/lib/admm_solve.c deleted file mode 100644 index cc69e28..0000000 --- a/src/lib/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 "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/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 Date: Fri, 3 Nov 2017 14:27:07 +0100 Subject: [PATCH 3/8] Header file and Solvers folder redundant as well --- 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/sagecal.h | 2641 -------------- 74 files changed, 43385 deletions(-) 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/sagecal.h 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 zcmdqKeSB5LwLiSi*$Hk4<^%(X?-36gMIeEQ_<|%5-~z?1T1x zet$gAvp=%;S~F|btXZ>W&CHs~ar46BMZrM8kSAc=U^tkh8bF)|hBK@&&XQaP8Tv^y(93J~# zXxwArLCV}VSh(Y{un`3KgTC-FDu9RJ6ACm!eS5|+w|WaK=b&g~I0sOD2EfDNEg-84 zW&fSik-LhZu5cD8Iwvt$zpMPbo*jq7Cn40X`SZ5@C{*<9GbcwC^#9;ERT0^BID8Oa z8#y4zw!zT0FQM*8O=Ea0ky+HdT`BKyvHll}`d^In7l!jyNz_33-^D+pxc|N4{@-!> z_buR&{&yA8wm~Y99qRi>2B>O&iwq_x3Vq{mZi+Rbz5r_GyyE`fyR$MveR;^*QK)3f zKz*TaJQvv;d1ly*D2ZB`zW*4>JW1!*97n|^k!Rin(H}W~$*z<)xh0^AfC`0mizcc1 zkeLD`27^fgRb9CV&MV!sDf1Q zEZp`^sPDhi4WppMQtn$M+*D@ z1QR%QG_s>HoKMA1Qk`1$qy@rR3z7b}#8`;VplUQ7v1NS69yYE5RREEThBNX=6g6ij9Ryy3Vr$_ zbSQ`$!}UKX43`rj$Q}!C2N3H0;aC{lt|wT;&_!D)7Yb#yuklfPXa9)SdU?2&nn+F7 zwx-4-xhG1r^#voN_S2L5Q9=KRt+TRIPq6(LtSl?&KUUKJ+V_a8tRw=Z3;`@5{-idg zy58sWsk^d}6G3-P!WRa@L0yvqz*} z7z8f$hX7FAEW zN$5k8K*_h0B?14!a8Cd0h5i3{Iv)je$0#vjAE?U9pPGAG{>q3{-zIM%YQ6v!8WG!b z^#2mAddTHCO1P$-wvmu=cpOK0I6JaKS+p`Sc*3j%>!O0Bilg`@SQjNin4RGv1(ee~ zPlmSYhiq(=51Tq#d2^_D;~7L-Ssccce;fP>=R6bY{R+}awd9OxMwsYs!Qy;k z;iEa_a*2R6bAmW(!nnFr7Aj@0YL7sow;k-w1-C#!aIs$NR(`vg#J9&r{R2I+#@#2Otc>h=E zo_FC1;B3(USG*8-r=a%_0W@05!1qwgLw)~+j;314-%_t-*)4@eOY9z|u45r+{v%o_ zQnBz1Ia*_EyW_8?PQj71f)1>yES!)GK=LeVXseM+Z5(viKcN;1aJNq&k;lUKXkEj# zSKf@mw5Hj1B(&`!R3fgNnviXap0tztU*i@*woVXZt^h>(rs8w(WfwuEsW()6d!Nsu zfk|!AzW*>w;0T3W7U`qVww?btb!y}pv_5o^vTJI#t_@Wj1lRswit8ms5L;3yRi03< zR=BF1);3g)MD*^6aTU-JsU@_o7`?s#5X@S>s0P z-N+UhiJhF;Enu@07=&Z8z#7 z%RhiGJ?>Ee?pKETE=3pzOkHs;QXa>(y22%}Ru$BuMX(BLk?+t!tq29RT+yrueAjV^ zKsp&4>U*6cM7-`*!MNg@w<9&LMlk4USQhUJ|CB^3?jMerih#0)k<@>JCLQ9Wf=Yp- zkzHdE=Gw{rqyM$gH(rj^9Ek*m;5J??4jhm4KcSaisuU$tE|O1xoiKAzpY%^QP^AyG z<)574Nv8V9IiSr~B5YG!^P=!74h$-pjuOp~r}9YPXhH9(0A7ej0#Pjuyh8-osHo@&`2b==K41&Jyt<1Yuar#E(Hv<qVK(su&4Lg zd5F`}-U_i=C?&uDMbw*Jf>(aY3M}I4QLX+IA|!+AWP{?}-=eFB-hGKQWbyVRn5Wm)b=B`TD50K_UNgPzK#o}vQOQ>0W_O@^ztq^mf3 zmW7hJ9~JhGV3SX*`*^*N#kH=KgD}(#>JXAi^%CrXxKBcYGQwSj;V$f9B}QLU9`2$B z5&;dZ0}w;40urNUmgc^lXaHk(Bz&MN2!y1#fEgul4soTmQs|x!EZvh?jQ6IhfLT5T z%u0|~@Dv5i3J&wKYYbLRGJnBwh5EjNH4oZL-br^PdI8e!Mj3CstQADmBjhaTyG9l2 zB$PIDa{$l~%|TViQ65JByb~`|j$0|t)g?}gNohoc_D$i^aF4{B9=&;nc|6K{M(lw4 z-$f`@ZKYRGfJ+6VKWhSxKwwcSp;p1Gj_aO{zj}CN7p;>h=ykCUdQAd7 ziBAs0WlSz_LVe=IyT~u7co!*{O43NO<6X2+SF-4M7d1nB?ZdoCbL4@vEDhpa_fxzp zfW1u8C751%#hnsvfy@buE`3VKdT^Z*jsDo4-4ZHGu zik0OfRyGJ?cdQIcM(p;G$O~C)R#}zIOEc7as!-n$1{*b#6t7$7ic4V&fepibM1yVE zQP3E%9-0ja?ofy6Jkf(QMo5}v;~Ze z!d*2SK%#aO?~=7oVgIkWVbB#;KWgzsCUyu^te4*70qhE%$D*=JZ3yCmMz~vmDM}6m zwUZht_%jEI$RN=lFcD*9Jw}{Q+=$mcXolJ^hqnGz$Bn4z^r*PeA{16}qcA}nH#!#( zCc#1IW-d|UYsZbKbEz>I_p9>k08WKWAgIt|%ENjm;Hw1ym+_=LI3fX=Dp}wEO zPLQrv?#Dx0SAQT8p>-GtvVK>Mn~xM{6FOe)xYZ#CZ8)ie$Rv5=>D`>lM%1-QWf^5p zT8yra-;ml)utx1eu^*+~acJ8gF{;S?hgqNTn^JECX$u;e;2cL9ID#ZGg7GhMj9`yC zgWYALIm90*2Nu-UV`Mxg45(`w*pk|GA0yI4qd})4x)g!&s2CGp{y~1p$T5VONbEIW z%S|6yts`v5z>HJ4Rc?e*?VJ;Cggl>}L(`Ux815XhIXj08&qjvaWsVBM8t!`pgvyOH zVVICq`R<|-K}I%UmZtq~HtCp}v9#vRqG#SPmq*G}+@glVft~C2gq>yG~9h7g67XK&?x0F{ye}(HBX@>#H zfE=mRwvz>cm~Ny3t_OSukw6a|K7kUN%pdve|B(G@wPi~&JB6&>R&6CoH^)x-grbg= zLrh}G#cjzxX-h{g(`|AIhwYy&13;*#Vg zUsmV`F%Wip-+lG;WFCFsc4krks|Dp1h5i3oM(XH)ANo;U^iFJSasQvCyL_AXXWz

@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 zcmeHveSB5bmG%hQKcNmA-YR)G`BaOIwT-!S#u-a{17J3h^`ZJp1F`mCHTacK(?^ z-t!~(?EUPu*Is+=wb$NzovnA2N5}NZ$Z+hF;au$`a~5@+4Wp9j5|u1*a-Gwh@Rac6 zakX2+mElR16U+SVkvoq>P(RXoBI0lMIzSomH+uPm{FPp_$}jcy;q8}s)seP!-V&DW zig^ct@rn3Z9NLkW?&j9#;^%uz-I{&e9kkgCq0US< z{}W_3dTUYdxI2}Zb>1G80m%}T$o*cPs?d#Gq-~V9B^wDPL!`AM;>WyI$h)!YbJ7-< zc@1tXV#=bmhgI!y?)pz8hYgBD7bG3;A5j$J-SvNo`fpjHasDnhb^zt7P!vR&6ZR6qUHK{V0m~Pes~d-WsUZ ze-*0ki+(w66%|S(6s$r)Ylj>A#Q?`?*{re|Zfp*6>G~CNV~-@;OXz~4K(=tcNb5uT(~*0Q6dwF3s# zMXB*bc9nTeOt`VHK}7bf1G%W};eoa&4RfP1fJobBuNkF1dm?}v2-9}Mo6wfiw%Ds~ zXF@mBHc1oEa9CtDWl9^p2BInl3++bnFi_G!k%nF7fz?cR(p3aOuxqAUaL~Q$=ocNQ ztZlW|jMDPfKfB9chR~?H+3Qx7b2plb1)sVt6)5xfDcj$KQuO*ga>L`U-wt@5Hyf6n z1y+$&k*%GXRC>j=%d(D=gqiF}Tb38umE|cwQN+AXWR{KYi7d<7nXTwz-fq0h+E%Jr zTHd5gn~wI z`f;~v54a7QG|UP2{0VhItl34q>2}mXzDMzi;5X_O`v*AG^gGAh>gT~bg+nCjAGZ`` z{#z`Cv|z6020P6R7%3SwN;>zZt*Ve#NB0o+_bCek8H%r6HZXr&%g$^?68-u*rDnIa z>(ic|Xq(X~6vtwEPNh(YNriH1%yi#*dSZBrzkedx?*Wz4l@e*oBxIkQfS-zqsUgwL%*}|l&GYghzU{OBisBxwWYJQkx(MEs z0uy7gZ6IXWz3soMA;{khn#d}+mav=KxRaA%qw1p~z{niLDB~|zuMsGo=S@dnk79zX zQ-tn4ThK{RXqOMX%WZiXP#GlP3|KZgqrCNLH+C5?Sa~sAK;$dH!@uu?U)oy)LRLiD z8ok1(-=i8XYok>GDT(?YfOnRsL^pC%f%JRjbbJ?NNym3Pa!F4GI!@Hy?y90^qgsa2 zdMk>8dD!UXMg5aTZ&vG-{O|Mq9e6ZuSeOrOkH z!w=?aQPEYd;20L#V!|jADU3cWY$~lRW)|+LWt`K4s=e8ecDvg$B~$fEp;g-K(LKcx z1pC~UpJF`hJg0BhbDLf|LP6{TKo`GKO$O`bS=^A2vw<*c(TiJ|O=*g6hibCyq^q<^& zj=``&*@1B#y0bjvXv;h=k^ynDJjjyexvc{}>FIfbvb5+$A_IoXsWLLAmYPiv&2cB} z;{r%Wr8;V-1{G6iy1v-U(sm5!7cBYM$lC|Kd3QC|GVA9Bso zo5tj%qb1ob6$dCEb$j;2ulb;dCat~xOn1s{EOqq{LFc7gd(3Ay%3|J+&pEJH<@j>@|ZH?qLtOZ^L}qiZ>Sisr*Ae_e>O{yjsm# zls$9Y-L|_AW-SOSpPA`S##FX*NNb_@?D@K$J=pz1bxP=NFvOJ9X6N3u>jc#L1z_?2 z11&$%kE!o61t;ai8<0}9xEB<=H@hQ4=U{fKmQoE}KzuYR&Fx7UUv7wt!Ne^2y$9HZ zaOFjBP=op4pti&zCem$_;|JcOP#4F%Lhw^N`bcdz_6@Wur2}@HD4ygHXTQ2HIX}kTGC_ns4`~Sr&eti!r{En##rvAj#7^eP{M31*WPv9~DkVnZUh9jYS zJ1%NO_29&xfo--gW9^feo9+M1)GRW7kg3;+J;Ky}rtW0wIi~JmY9~^1{JH7pV|8NN zO9+6(VsAEP5-Fdc&&I&ejmJlGiljT&Qu~eHL~rPk`NV(O^;ZsXqnFld;+v@aI8e5O6IR7uMQj9T+Qzhn<9wXff0DL{ zArLp#gtBPu`-)>?Ri&^vrn#|OQF>~T4O@&)Q>vm3XOXFYQq_#O>l5p^<{NTj$8y;d zC`XYXau9XbccnCskimA4rE7kJQscvlNjh~Pbn)H5!YVg58HHHHmq7W5A^aN7+vNJV zu^%Gd0=sETnTAfpYqBSDjCDPW^Wt@qtlN#eVyF0!2uCQ3|23LUxmyX#u z0LrtS-3pqO8atK-2n;)msclB<33vI~j8svN%f5ki5+~FOc-unCu zm;r|z8`fWUpvoLuP*pistVrGNJp=l(bdncgAFF7;W-o`0Fqal~bAx&(H|$X_muH_& zz-nhgnhs=?Pz|OT9J@GzJaYX3Z6pWuSuA+wY~=3;n6e+-=bhB_H616$4aZvf*Nhkga1z57VpBYnqz@h2|M;TFr$x z3fQ-Qn5bgUMWT)Fbfb>Oy1C$Bv1*2;X@)QXADY3yS$+op8;zh>7dz1ioF5wDFpp(&h}M^u_C)v zDFEfzbtlft2fs57R`q-lxvVzA7pzn(Jt&`XTYSCc-mKPq%-j#$yK|IR#!;;V&8tSy zbL3j!iZkFf3ctQod2Y9LrP?m(prq1p(r5L7&q^*r`{=>oDNT^h1oNYb9xwLv;gowX zGgRUrEKks(L@|A=$s`?Z(it!Is&px&P&>j#7+Fjup^KGj$w8u;E5jgck_J^};{eKS zR<8r7T2GYn=wYFt{q8;Q>J8@*01k?DOcA=8L5)p2qO}!&7F6PVnTudn$_cg=jU`qR z#I4>k0w2kSb-}U_k&Jpe_9v?n#jy+_dof)Teon-OlJ3tQD*d?fdK?OH!0M;{AFWHd zY#y`q%tJIA(m8Z~+N-JX8T_CnRhFOP0HI;CE}}Qd^SJUSa16RXtSA&wb$$F!9B7oD zSzSdB^OJi>XcM0Z@QaZ$!@X3+NJQt8^HljSh=?f>AA-%ajvoM+q;|jaINDPYwA5VV zJtx(EJDp=mC^X%2VpkH{t8ceLpSPUM&4~Y-vI(`A(;Vzt{%Qpos@*&^5Q;8as*a7O zyDQaF$N`6bbxcT=pb8Hy)F)yS{;S*uaL>j_a!8pL=BhEN9d>Qfi$4dm5T<|^PYP=6 zF(@(`ny2-PxOjL{JL4V`$(J&2VJ;bqiL~s1T>tymri8M5R(D>QGb6z{RdLMHt@)j*t?lYi0L>RH;lDZ;)!+r_AbNR% z){bf6D!27s7?p}FagOUFsfsM|gs0olXLF|UwHE7px+5psc4HYRrM0G>z;&DhpV`sG zXPg-0M~|iHCMFg@^NEBPP!705Kx5( z>G}Kdr90(2cOh!xJNIWEknqX7u|k}cdGMv4_%Kc9YST*7$ixp}m{aN`%39P$0YSjO zYN&Xu3u-dnqQXr)5yyW3(`|PVw)(87-4}b6D~Xcv#P&>-9`0=a7{h;ffBOl(_O`#n z*B{#dm9HJ`YxsJo{WZRR+x{|N>)T)AYi;`;zJA*N3|~KP-+@=Wfx)_85%AmB5tv3` z3sbi;wTY>3GqsYb8m7*I6!9rY@ji@=ckwbzw;E=|VnNCqF&n5E#Jlo{Bi_{zK{$Z! zuMBaoCRzwaU?1{)+}I_#Y9be6sxthAXgEh7xrS`yR@)$`-hE>m{4Vwc_yb)t>XR-{ z<+vG))FMW!XPJ(F={UZz$|b733(%hO`HD{IT9xCp;l_@rkOmio+ILksF1(n}!g`+W zRy;-^#5CT2Pf_5)%bSVaAY)~SdG$}mrpz``2cK}^{ zEoD2nSo7^P1zd>`y_ykFRN}@iLXn98R$2IMByMag!e=};Yyiw< zUUiSRU78rf;rMgp)jk?GBZ$#1_1*~uO=|C}O#)6`5wH@AU}$RNWlC}Df~d;DD7$FW zvmq=Wkg8>g*z^Q8Rnb%>@-Se28^ZBVeG>o^#$vk9>cHn}CYIm>J~EyC%>4!Si~VV1 z{F{1703{(H4ZRT0-^v&&aNbt2lr0~8u}wvjn*~PHfU1yG&AqWIVrvD~Rx+JCD4-jFy7wN(H6I*hI*z+T`8_JBdMu{9 z;ZL$zw>FAf1pYt|`GL&)&uz(q{`C;ab(OcVxTiX$xEthd?3~jAin~I7%V2=qEWl$dD=X@G@Z<+!e_QFYvM(};iz@z}ll%N< z{O6#6D!&VrDo!5o&+v-&UicqAGKVkG=b|$IjQeS_6v`iXe|g59NXxd2jOQ{@tt@k( zH*%$F^!wNF#t}P4P9t%QFZZ+VFIzU^YF4JmW&Q}ohuD;jd)?(FkR7*VI4k#%EgmF0RRWTOak6e~0xXWxhO#Cu`JAzY zNp>kxyDi$bS}$?X>TWPmo+Y}>%uPtM0$|%2ce08avX#oT4MN@HwkT((f1qvPomd(x z!aXk}jvnDkbH_;$@;;cF0x72shN+vuiL;3#PGWe}$%WY=|x!xJ!L|wXKGD>djLPCC=kfFApOM9C357 zuHM?M7WIALp{ikylCx#zS}t#7>K7yBJ-C%N8bV9%3WprE7}TEmuAY*#CA z%4_=EOHTT}l^xx=8Mx$>E=C=E$%&(L$GL}#@x2ipu;J&w=YCVLD1X)cCX6I4Jy#Dp z>;QWwiE6fe;Cnb^Z^;Td5r4}uL=XX!);jxjiXqhxZJc>5hX4+b-FE= z!1C>4&ZXQ}7Z;a+P6=eu z{urXt#tqZMMQ~SZ58m)NI)Tetbl~p2(?DoH%kk19y*#fbuH43ZkF7kO0OevZ5&=0B zx{q|6zs=tr!>k^GLa2JPTDH)FT9}l}S%bFRyMBw?IsW$e2)K}$D~vSY;Kb`GIKrIK z#6>l%G~lEUDeii%M(R>fBR>cY8sWviukU-THX-^cYSqIu;N4^mqPlQocU^%B=&N0{ zUO~G?RovKCK?=J}p=%B{hxjiL;Eg%L#0i`1~7hvvoJ%k4rcj zjo;&ppkXkp#xoz?Q~F$ka_kki<0@M*k{pa;q)V8_JHj`xDx4`XcwByvz*h(So`Wxt zgBp1BY|0}^2tW41+)%9{BE0d|jpllNa4@!l4<=4L3`^(rti+T<&>n{k)aBZ)_Atq7 zSvD+Hy$!a3MVI$Kr}FZhvKM#)b$ttEFW`FC9$wD^VdV?Bo`pMH)(t52pbq$>6@5!G z-j2IcMl6`K6MqbOs7y@^y^&KBgJZ|d%@SC*;^Y1nDMN@k)A~o$!<<-=h|8k_H?>83 zi%#fqf%%XF>ZmPNhkNfs2zK;t%SZx2Fdw%a_OX5y?M-LcD%`-mAa;>7c(TMlCfO=c z?%?BRtE*bpJ=^^z|L-30l zaUDgN?Y4dyp}h8EkS=}=YP7%Es~4x-3PT6 zhF@|~iC{M!fg;k}@N2v%?L94c%-%79_)(1V=WDdFG%dUMdfLcl3iv0?gZ2$f{WFDr zj49km=xP5gQ;)NBFH`H0a-7*6g}2kF9p{1nH)hQHIkQGx8~y0Mdmid6{`eQi_Kv@~f&+`^ojaBt{mq(_Pi?sR zM{8$QR#v`vjjH5y+4qyfMxS%|wad>o`Oa&8zI^S2+orsGrKUS*@IBWYoBjHd-*mrm z^`Jw;?tJipThz30oZONhKgj2CLx+u&e(uoq6Ti)g)Y#FZuMFjnoltpUXhgxtf=f-o z8Rnhw-ETP=O}QEWIIvXI;x_&djxotuk9X-@9FeM)okmS>jdgqCHM=HxH!8_p@b z`wQWm;+B5b<}_z!Uf(BYNoFKxMdp;8Rhi+OHJN2OYw_NI_omF!oQ}+KqbHFsqj!F%|%MvO7xr$1{N5n1VVE_byTBj87LX z>j{0Kl~eRJKBucsrjwP^-DfIV8mn4Nc5o~-QVP9IK=~Mym!g~+n}u5FR2p!m0m}?! zTB1qmYK{gt3vIAwDcYF8HueK7O#w3>%1EoYF$4PC0`jpS?{hbpaQN!LWHLZ%tP<}q znI7zKxJCw)AvJ*&%29zTCb8`44CGmvJtxB($l&ujnXXa;7?pz&Ra^!})n+WeX&ST& zqiU~D5kjf^^BPq|51=L$Fct4oaLPPC!yww2(hY4_A1-Lk*yQpV{15m^&arehb8XIs ztjw$XzZ+L*L+wm?lRmomS z`C|H+7#@&$T_)&nOrckLatapRUf)=AGv190^?SBWEv%bYQ_xt~)aVqTPpuWCtKA6p4&{$Uh&pB*S zV@>V1LB@9KW>1^Bu%^CFOZ7kg4S@sQ2e%gTDKVMhmrE2YK<}*ckH}*sJLTyZtD(-p z`i)q-YA4%J=9%}`3~tQPpxC~rv}+4e+qxJ9nc+W82S{AkxOwCf+3m zOV!`%0Qqfw%l0}`pRQXzYfSwm5=7eS&%UdHk!NcC!Yg#ekbWh8?fBXET$HeWVS+*@ zDop)UzE*zwUS#U0RCjJR^;2-Kw(zu>0|s+9%e0C;(p@fd480ea-%kHZM9_)ljsGbhye7 z*Ojp=1dA)wui`)aQZ!w6TYb&KX*22;PFpl*{;YX*CT8H&*36r?pw?Mb*Z7^fIkRRr zE^_K`pEhGw-J;uOs8sEOhTB!5rpYAcFRUM~kVU9#sO#%(;x;Dc%&4iaX>t&Ks1>y} z>lq6g9mXb9g@y%<3uh)$w(WU~oQ8#S<~PoC>YC;>I`#AFh2W;4TTHzL3uoQ3P+{0y zFI?m-SQOmVx;gWlg*EeMEU2H>P`9xD*2bE~ISb~iLB-E=Cc*tg2!9Bcspstgd=SQ& zPb&T80r+(R_}l=T7s1oedDT>!o}0DoWL+~3&#ej@Pm1%83qXr=bIGyuOb0Dnl} zg9ZODn2k7#In}H@=LsBFAQHGIa8KaF1wKUJR|^~$F%odA+wS}JAr2^;K$KsO({#AiLDsVZzy9Lg@iRE)x z;F3>Yb3mc|lFx+#&lh|~3H(BV-ze}Q0)I*1Lk0exz%LT`H_TR$@(dPujldr=Ohfrqe z|0@9BViZn3lKu^WOM5sXaJg^!NZ^vsm-NA3$tm$m1Mmp}I4?)@NtN^N0Q}Jae0u(Q<(hm{1Z1)m@%XV)Nxa`+_flL02 z1TOjiSm1Je9~HQy|962)d0r5>Z1)3!%W*lKgKhL9>CY0lY676o6kEfL|4WUmJi=3czm)z-I^G zy99nVbYb<}Bk%%&pG6`(ss8Xvfy@2jWPwXNuNAnopL&5y`}vW;rJX+_a7o`Ga7q8X zz@7w<@C#68$E8x>QqCHI z%Xa@s;Bp*)CUDvAdVxzm{~>TWE(ZiI`Me=;spkRwh>3^&mGYk{a7kY*a7jN#;8M={ z0+;f9SKyMqP2ij&R?iO$oG!uQn*`1yi^V$x&ZW}gy9G{DwfJ#?%XOzG03R4aWjv|t z)bNmkPno~h3!F*YZc_lhDgb{;;8M=l1Msc@ygLBz&qEp>_A7)xD`$QH9tpr#2z;>M z)0Y`Msr(}b?~k&+_%p{tXM76Ix_l-HK1Ik|Id2m9aDmTG;bZyNr{LtxXQALD`7ai@ zAppNE0KYc?UmJjL55V6PxSZE-3!Elk z^^nKSC7#s&UK)T;2*8&MoI_#x9}~FrhyA~%npN#ef8`3CB3nM^3Y;u0ewn~e6Zp*n zm;2#XE)sayUoJJ4&kqD%Ebw0mJS6bv1Msc@+@&+cld9)&4C)zN%D;q*kiq3Rt`j($ zvhqJCaH;3x0+)OSU!>S8d8D7eLf|sqI91?M|62skrmQ?K3!F)d7harfSK7%KflEF! z1wKIVX%x7W|9OE+KF0(u`Sb`}^7%5II3A9d61Y4^ILbzFuEyb$Grw;8c~x*9PFf3Bb1n;D-Y6 z;{o``0r(fV_~Bu{q?|bc_?H6kuLj`50`MyW@NfV=Jpi8@fG-Td7YE=|xY*;N9^`yn zAaI5%?Ku8K;L_e+7r4~t8JCi~=06f|%jbT9GX!YyhXu}1nZ>sXT<&{cGkAZ{_QhW= z{`kC=f@{=qjtD+fiRFJn;OvUU`|_}ghx(E8{UU+O{uT$| - 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 zcmeHv4R~ACefR&~E7{kwBgr3eW zRjy?#z|-e_p1052<3~E@{LcUD{Qv)R?zvZzKeDl7lPpPO7KtvU3q2DhYHz>LPU+1Q z)lxOJpZEax+Ml!QAl`n$jb4{;R0rC&wQap>>|5Kmw2!?U`d<6kx2_s{w*A=8lP5Mb z?jHMaqpkgJyp3fV)hBGAAKTDa@h0=#naj*?KlW^L$F8=WZM&Z74VdznUVV&dbp}B^ z{4(S2$5h7ot$l1~quU((8V`CNb6*(jZ}gah5AvY)n8zIK80&7-#GvcWBj?YbX9E9Y z+699CMt`2*dO=X}n4byQu>9B>j6Dg!>K%RXCrdB_7i}Qg!cO^x&Ih-+$JREskNwjx zT$LLdZ9-0_0*-L+c*dY-V{EhM{~F`!%=xQA!0?lE8u7C)yL>6)!2As?2z}4A!H=+1 zu(*6A%3*+i$F66Xo(GEg`7{p}^Yi0#8}Y?nz2xJ2AyMECHbvEEh775&IyX})Y|;&# z?T>%Bn4QmF%+5cW+s+A>FUz6awq@+)jiGOD4CRo^FtPowc(uUJAWYPr;LdX!8l$3a z6)wo{|M^fqJ*UY(`_tcu z{mV z_<0^Ia{V42<-wxY?!DsLy?c&p_vJ;e-IwsSJAb`uyT0wZwz2QCTgIN}i`Rd)x&HjQ z&@VqY_A9=CwU7NP4m>AzHtxP^?Ay37@Oq&32hj3x>+UVCr+#W1+uG=EyZidmr+%uS zS<`-Z&7BxC&JoYq8hQT$gN>6L+erI~n#L0yjVf;}xSY68Wb&I@W{Gu27>>j9xNh@q z&~{^6+PHon`?=aa_S37z{tYLvBUg>R*)jGH8^+!l`%$OPy8VWqeSMB){1j9dUB(zA z>ip+zk4faF<4L_sGD%Vj&Hc?r-+V8{>9WD>pq_7OTbmy-&ZUu30m`@nby^5=exs9W zH0lO>1EnZQ3f`;u1REIIKa?5FbnQXQtdAZr`g)xn+CR{JgaQMH1APO%2Pu&0Ig$ya zZwMuC?in2D=}&j}b?rMa+@DGJ_V;!6c3#<=r+s&-q5WNhJ>Bn4xA)+{P^N1j6MPp^ z)3%tf?WV51y|63XXIQo+l_72@LnCb32%&zS1 zoaxAmkX*3uB1EQLVRqqrpwnGljLx(>Kfi&4g9q5^KE%Jk@Qcx0u>4|Frs=v##R%Vj zCSN><%_;srnC9t2U4vZ*2=?clQ6$gJNxU1KSlpZ%?}2oVs1?x4t%tfY z``;@oZwRw^c`r7Nyx20eMze5%n*Pj(yO6-*(2LPr@aV;;W>{N{&~n+D)w~*rLe5!u zPRGk;;WfClDdd`k->2h_S@;iioR1JQ%O;uSl+Ogw%K%@B>-Zt?ad}D9e=hKpV&LXO zqMzw%}TZ5YSnyt#xuC%m4!-p&`Op|YeCf^2)c_v|+d>b_RHfYS5FipMa=MtG`b(Ww;JBp28u zu@<&T$b(9w4;wqtv^DLgmr$R^s~vVdj~D0XX}n&?>rK4g!s~6k_QNR?c%8)SG+uAx zHPw(C@{@RN$BT8T@W}-mDu``3f>%56 z-s z*P~^s7fYlxg>i|-vp?45P_B!aPr9d6=bMm6(my&?)GB??S>eS+Bli!E3do~uXFcon z?%sjR7W&6`&#TBnY&PdU={`Aia!S3_96#stU59;`Sjx|)w_Z` zwuxGG61Dr@?8&LD`UO(U+>;*}b<4Y}$lI)s(00{C5_s$vr+kp3Ua{55HHlzwI`!hI!^-4-T2^ ze|Tsgu~Q zNb9ID>S=d&5-vpippM8>)(QW6a!)yPKK56pXDj;Wo_P*_YoQ9=Z>Zh(mbuxVqd7O+ zckKDURGl@Le=7U>F81Suw7W|+YS_(kZ>{g1EJsScYWnfgZX9-~ne)eeGCK+VjZEkH z;JMF9)R}t}`i=Z)N4%gHWB19Ft#P&^ZqVPR=hbL#f)gNnQvDF?L;N;X7cz=xG`CFj>r}RLG*>VBm8us<>(N}T=x4bc z&#G-HLw{@1s6#=cZuT4X63wXVJVsq9^36@~;6qupwtLb+6)(MxIPmeWY99Mxcka=8 z(+?HimE^7X_&Zl~K241{D<;wRd;)97J?{Rp+9&)mAnfWA^9McrI5pC=-_YAD#zC+9 z!>#AfBP!_Nuc?s_>^A5ua&$jo9Q0Eoy9(qW_c#GrzwS5CfqrV_mf3Q2Kbra%i+Ew~ zlkV&zYMJnBpOBCF>G{+pWL>1nk#=OfDbGN6N=sS(tO z)6@v^kMsD}dVO6^jVvR>AD5Cb-|6i0ci_))BN_7>B*T8>sm^y38G6k;-h&_*a*h39 zdK&9@d|bu4)%Df#z6HI34z9J4cn9m#MmBY@K5gW12kX&BZtGw@+Q|60d-^10)qxJy zqmA@-uzYRA*}?L(5$q!soDcQ9UcB31T`QB#$d4~bv@`c8RuBHB)cRb5v0f@PBTp1f zx5HU6rRPbXs$qOIH&8KU_*K>P^})&G9?fF^;_<1G*#0Sf9rX%2@acJiaXmja?l#6v zIS;wB?ne;+E>$z~L}B`=k>S~LjQmiTZffLrg&a;E&VN%LUJeeo>95wI*@}xG`VC09w@{u13%bObEzjz>Ya@GzQ)Klg>KISp-JA%vyJOz0* zbgI@r3H_+1b>GMQ zhu!E$=@jh1{8DG{_4B$-osDmw#6y|X*-hIg@wmdASMJP;q|s;Nv&i2YI#_@1%w`RI z(0e#(@QrAYHvxN7ppUAjmX7lEGL$YV66c+}vh1(inRVN7y@I{~ zO(Gt!NB3{)?CK!+{FtA|uy2Cd@5`#Uc?`Sn*glEJGpVyHMp38e)Itq$X`xdA4RR*b zQ@w7`!9VLpAwR30%DNxG7W{yE?{@YJ;^gt_`kQ(G-;jHB6M6M{-_hbld~#>Zb5+2P z{bH;7UHES@J3ghZ(7aBb&!$nYKHs+XNrXR}8wg_F8THh)dVJxx7UO)gDTVoNRj-+H zPh$(1Py;Q-d1u`QWBjM}NEv(| zJ#Ns2HQ0~1Bfb)SSUvTjcHR%OInxff|BFZ<)tO}f<<7`aFXEj$bF|%ye9q?Lqwov- zX4<#9--vV7al=mN#ukuOab3~lb)cQ|KAS`Q^!as)c`W7Y1?1KqH|B#pMLy@wK>vN& z@h@`x^*G;2_FAgU+g>j{fBwZ#r~n+ zci{T1pa0h)8Ymv~e0Xx|wUanP@%6Wv&xzne?Y`Ho>i&7Q-VMKbr*^F+Ed#zPiS5iD zO`(m)P}!Q3G(A1V*H7$IDQ|EGehcFvo%n#13h17I=+9y9-{I*&%LAEhx-+M z9C4FUZW@<4f7mbVclf0e^JhBnPwM;}J=i~=CT~BlWS?JL1K6AT%39p~@VLi~`;^N_ z3+J#O;(o;Ift;Lg$x^SO2h2D?PpWQ~Pbsb53%weRFY`|-tak;kH!XFl{@mwe3g&*) z;y2{!>rl&{n#$q;GC8HTw17WFkjwi&ovHVO&-?Qq)N?$~J@ZdX*l!wr19`8HH$e~R zp2p^h^>b3i^^D_wroPee;}Y`T!Sj*yY0}g6e1AmOyjt6W_&}cSSK9un?me1T=htK* zE}PT+UXyzi>p&)t_V>C!A=iwn#s8qEIz&&8AF-d#>v@GZa=f%oj8`H5(I5W-`g(p< z37>d z;6G#kaL?R7WN}}05)*KroK_uf+-DN@4>#^F$$c;Uub&6Zb+nl8W4)fQ02lZ9Y%ZP< zfEyXC)(ZNiyx-vY2=W+9to|+~j6O|hD zAs^;zoM+kGr5g6N4eomvx{dJ=pL2k_C*9){I6FZZ{GZJ=i~CQ+U8i578MwZGLoV{p zj(NOl-iMA)OitkrUe)(=$X^{4`o;a}r9nf^d?6R}=e)nQ-VeVv(kUE&?|8my8s|^8 z6S&8#Ae8y@qn@vNUytvt%W9C<*ay~Pee?5?llXW93y}98?9(3XznY$J<~gT|{lU*` zc>Z`EgZpURFA~QC^TnmVdKw2Q^M2Zt1A8z|w=U!T3U)%@Y1Y?hTo>~DVnP1B=jS&0 z{9`S@Mq#J%!M(7g1n>f{DobH{YvIpu=Hhn<@aclVLVi4Kq5>7V_?K`x;*u2%OvAc( zbKv4JDrz?BL!z6nItAfrftY6S#mQQ>8J$`De*WMKrQ4|VIpTm%h5WxX#%J-53x57A ziVN%KPj?IXf8Q9N#XpI=EiOBaO6PZ@UC4jE;k;S=Zwr22^jug!Zail3e*qYqP#A+O zI~#>RLo>@<^WTi|x%f}xG|i>o!avvi|FH=F23Ce5#OUq0=I?~zbLsCC{CtQq%Ut}Q zFTy{?#-do~KbQab^L$nz(v?|^!o(<9p`ZPaxs;|Dw3Kh7Jr{o#r%=6!I50mB^i3%P z&A;CU<~jKn8zd=_Fw75^%uD4tjYeF|ztH~N5@S3o5`+2SlB$#e=iix+4u!FL$F(=`H)r?`di}n1AaH z7|nK6?8-|HYZ@cf4vGHV8#vrEIMjD=K#K)}jzFesa9>YG8#<7V#2kUH?(V^!p`r9p z-$;)ZkFKKgO?^Xsd-{8{K+iyC@TgYwyGMRUK>vc678s6%ms4qFx`|@xV9P4<^{yg! zZDaG}zrh>l}{$?hljncXY66Fx;Z;_z^jm?OKx{`4G7hROtyNsA~95awVy9D7~GRH|iMzn|)Mt4y)UdzL)2c-6Rixyl*p5b-WFc^=}=c20}gFTrwj^E8H zN_Dba&YEASt&lhK_i2wQt`$_`yh~B8S6pG5=L?0Y-ZidB-tf5MjgBjeljDl7bzHH# z-lR&`c7=SQHB{~Tgd({PQ-$l3$_;d-V)s#Hd_qy~S6n9*dtLmbQW+{y8oT>O$dfp& zxSmtWe4*!*`3qyuDXy)g_~MIfc4>mViLJCO30HX%i)e%(9K1`Ld z|Dw_b)DUkeA#cbpEyFUP%HT?Aeq^OYAGf(CRC{&o-^mwy*j96g`Nha*OKub2R({0h zT15`$<2J0UI_J||Rr$gRYVbw)Ey{1PYN??rK5YNDlKz8xyZW_2SAXBW0WGQp_9V4* zdOrPCy1VDl!AxXFJi2SYedU%;7p?4A^L2Y|>x1@zP#nv8sA1mPP$Kw}{gNo&mPDi9 zwM*qTWr=-dq-Bbfw@amJlE1Ai-O_o~wm9-V`=Zvg-F1`fa8{Q1l9!RkwV*`u#Zze1 zqme+v7fE5$KTmbOC^tx|TNT^&@iM#oroF25i#A&gEe*CzOA8`iN9oIw?eq2(pU?ZNUz zNB2=sA#h$lU+)?grU4#|i8$AJ|d~}Ln9>%!-~2*+Hx(e3`M-9Rl}QYw$Hh$ zqI+dJL|Xzwp?KNj6|06|x>AGCT(S~*DjgyB(5n^8_YXGDqt%kFqO76kmUwAn6#I%d z3Zr6+Xe;&!tPzPEl9ZsOhhhid!vm7NI*bNhNZq&^8QR~!+V1L;$|Nr>j^A9iJb^i` z3dP^7D7!3_*ySVHcC~a_Vr6s*Df1&sXjO98kJYktD6ujWUP?-BY$^HTXw1jks&J5@ z#8PtoG1+R9OUa+y^&zPtlz0i>E0Z@3?+FY&@035OR3>6&^JCwZ7UESM`L?t?wt5@Y zggeUXA}7mh!f!Z~oJ~3Fs0^QVc*0LQ<-5q;9VU4*Ip(|K;YdrbeN{NTm87PX(d)>C znl`J8*QZJ*q|ia$#O_)@kuT(NewHb^+>9E zlB<&Sv?|t$RzoQ4gJ2(FUqB-PFE)gOXrZSlp9WP74clk1-7LVC8vTByygVGs%xexu ze@FQd)^I2ok40jUa43G8ELFumEX((ll-y0UMzTd^-!9z0H6>dPDz17o5-o@H#}pn~6N(XwdEkkN>=WE9t@frMLz6~}jN)$#AzmPS_pcS*xQ&wrPAk~O6p zaoI~&+bKe&*V?O-duqylWm^?${gq9rioEWsZ~aF}#yMj%mH%DI0-&ox$@k&^*KD$K zCpI)N+HGe_e5gyB;tBX1KvO)fH%gYp+0i-zPon`OB3vKV0a^J2n=k&9O`0G6mCXlt z%gP?xs(1no8*GZ}?&SvDYamhf^QXxdG5qbSQ6*mxlTzPW*c?WKJM-k8Y9vgvjg;Y1 zUu2_AQVya1l})zYEb3mpR%~6O-YM$qMSX3la!sl9FEH-za(iPq{%}pn!Rm%^bgyezpytSs}zvYq>)P$=91|v~g5RZ~Keu5SxZYH^clom6 zfxJZ{o=6BY7bFFFD|D_WPckUQlR>DgOF(0YH6~hb0p2XvMHfQ)ZrKxil4405#;7?N zhC)nsQS?bHqBumvrLuE6wc~nPklZcTMKHrwR+em#m$$;eMd1^)yfp;to+p265c*of ztgjXNBG6aYx<&D{Hp^w0L~Cp}*RfU{foNeXlqC#vTa(cB0dkxpRi|7U3O35FD<#?# z3Wi#i(u&qroGrJ>uFaB?2n8dDNPdL;!EjvK7z##P{*_jP&da`kg`1Hh)D&+ivq@j5 zC_l=JZ=DSn`W!8cN> zdz9+XYq%1rB zN-M%RQhH)AHVR|oP#c5V1k^T%!Vi>e3Wbv`tTr5748KEd*@Mc`P-GX5LNCd-<&n?} zvV5m)MJQ4&Esun~RQ{6ek3=pb#}Ac6BnmMrBDh9j9v@X)-<7F4@*&w1{hquqvR-bE zV1ISIj5^sxj#uPFq_w4y>LQPoTpo%BTVPux4yB=%*U0gRZFw}@FF9Y4mq#NXl$2NG zh0z6)KN`hMUy=RM*o|DrqjA<4iXN7mqlt29-rt~3woA$ea$JsPEA)L|ZjJ?^@3fqV zg;-zoHOw*=hQ8IID6SpLBT$%(h1PthW@)Tt;UaHj+9O9AJ+WUlR2*xNZdNWC3{TZc zy^4Kz`KoXt*&x}}c`c#1o&m?peaWL_m;CO<;T7|mBf+EOYduQ1vOPJkIh1(2ygK=I zxi@?d$v0zh-9u&4Sy@^^<@b;WAWkxka&=_f=?`N}{wCGOKP7>3RU~*1IT|7@I%Qq- z9&-JqAB0aho5QX5P;L0Q$6<(s5Qlti-QvhSID0~146t&-)f|NY+aJ#Javz!@!O@T~ z#Fc$jUKL3&-B+<#zp$hx@};_p7i9%xkl>JZ%Zno~!g-M3dQp~ZqQ5u44r**K%3kKW znNf%Ifc=s|kkv(BlvhPVz-?P=ZLM{=gn8HDaPXrZzURR2w*q^IY5lehZCcOZ;K9Mw zTGNoWx@ib?w`0c7jS7A&f?LtzKa(nYp9-}zgqla+nZMOzD*XS4C;9zNe!I!|iRDdb z+=PE%@m%}Z3;4Nb+cZ8GIIsWwzJ2VO_A!1qfKh9ubMr5x^`B_AkNwCH@B&veCH$I{ z5XLeMlVF~1RbHk(lN~={`!1BfSiffzKhv01E?>WXwdTKk^R{L!8fXnf1OYyX(~C56 zEe@J)X;DR~IspOvj7ooUjCz4Vg0By`*lvD4|Cn-bRUy9JGBovobEQKdO2wPfF}ga?Skem z8Dfqp*EmzxIh-Cafp(*y{kouia3*bsGnLNMwh3C@YoOyV5wUI|7uL#yd14qE#4!Kn zhl~DiS}=uiw&5<@<_scB);Tp$8p*M-e1miUbvepJ5nz(&6-6&OG1@4m;gfFtfH%?_U3I-AzuFcd9bG+ z&wTJ}WD0b5Wx6P^XK09j+MDj}%IxVI5WiF&9_a78xu^dq1qKf?8UM^V6bQ}iCebye zfuL(JGki!ueFJ@&bl2cu7fWE8fy0}+GVA&Vw)E^ffG2T;pW+=b8UqJ2JpnA$OCVwI z&4RpVf4UdH*6iWQ{Q5r9H7HPc$lmFO_W&+V0HT#|EZ1ivEXOFPHxuqy`TEg@NMhKyb{)yF04MrZ>XM%b9 z!c~rkOBj=*L3s(zD?j} zewg%3rsRL=E0{#3|yVoyfq={q4uPUS6R_G4=nCu*bwrdsZOn*#4v# zFmdkA_4{rEzaU?*>6QK)Q!60abN!B+hvxbndME5LeQy!=HP740u!uu|lX9S)T`0WNvuM3>*;-a5HQU7m&za?<}91gq^i*PRc8;anqMer+% z;Jb?8-9_;JBKR%9_5X7tmZbSF9FGg!EtZ~s{>QxVtvj7QC6=D)w^IVo3EcFX1i37~ z6W^NUV)`v6@V5l6pVKjZtH8~aRzw_50H4dBpDcp^SrPmjMR3X-ICP{plMbRJ_r$vN z26rxr>i@vUxcBew>g@>y_8vM!#u0i0AFIp!d$1Pub5jMoq2?A?w}TKao10^V>M1*h`v;R6Sb8vnW^J=oLRd3a+i%?+lCCerwT z>TuuQp7c;=cu(5;pDX5kEHLvQ`fQA()4H5U>nu5Q|DzB7_d0p5C`>HSUi5K>@o?e- zD<)pRTIAnbh<}Y^a&{zO>QJVSox}er?szmbixc*8es~h`})$MWV}1v z9q!Qs5rR(2YBfxq#4rKZ=M@?<(yZZb0;9jNE8jTM8pArt3kpuhn zP^Nni4yF%u4Rr16=}r$qs&4d!d_drhlz_f>`xi27#+Dx3VYLnJ8`kff80qL59@x8| z+2@WJj}(o{46OwZr&#!`f3HN0u0J@?+qZ9cu!r5CBZl9$9EBx=2M6%Y3mv_@C)3e2 zl-bD7#7sc%Y{?Av4eX<#OgEmFQSTx2GQHGuq%WhB39aVm`B-CCSpRgKvGfh^D{Vpl z{u7p&-fyZh&#$*GXG~YDV;#Gy=K#LgA+pF!MoSLOSQ49%7#Vy^hPrUG57%Uw(jxdufwP?&Gl~+n(6gQ9 zxst!<#)a+FxFO0K3%%9OTP(QM&WR%U!$olO`?EZ3mGiQoXZx3!#-RRtiyXG!JX8Eo z;B3E@9zV3x3s+YAqhf(E-hh|6EQJO9>#FSp=t2%PN^U-d)_|JAzyXZ>8>vdHnHZ~F6X z3*KzO*Yjjh3jM#+f?NI8W5FB2XUf0Hf?NH6zXi9-`O6~s;{q>SFHc+OxoNJKzZQ7m zdU?@8Z}tBx7ToIpS1q{J|349Uq5uD-05|>rpB6b*|NqK@Tm8@f+lWh{|A#HO)o-_1 z@J8^N{y%2Hn=JTA3vQM3ts?kOi{QTyIQzl6uK&kE&r>w_fp-MXezDRk;=ZPEA8=W4 zYg}qAxHT?~0xyh9a{+F~C1{ajjZ4ykTjP?p;8wq7i{PIvf`3WiY^U|Q^RR`U{ifl? z%owHN4Ccw$QVk=DLduobA+1V$^Fa z^j14>vEWvFCW_z>7r|dBg1;>A!u`=-CgVOK*w>`@u^8&lY;CA9jjIXY2?5jKti3e$Rq8 zSnvr8-e|$^x8O|{{6!1C%7Xu`1z%>te_+9vTkxL=oc&0TIuxsnT$!#pK)+udUNmOKHI>hFpkX2h4pY8 z&HIs722o7hl%LYk3wTuYH(GH1tly;HEO66r97`@Y8I^92bw58SaMo*;bHqZ={=Zc8 z@3Y{VXme#g&nlGj2ZF}5XNTxNVZp8YBD&O?CiJNqX3g}ZT5XJP5NuLvVP9HU7^O*Vk^ydY*`TW!)_)Ix^ yvVgXu05_jY9xuSn=aLT<;O2A5(*?NsT++r0QB1w&bIDWzZa$ZMz=B)-{C@x<9p#Jw 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 zhU`^kpX{R!!B6)Dj&|^Dn-zblgXf&Q;;+eqhpWrQW}Rth`_6jCi@3w^Q#s#q@Z3|X z_$wVe`)0+@bMUNEisyfD@uGYeul*(Q9J(q!*Vgf(o-)NLAn_*OMWVI7p zJpQ>+EcgbSA7oXga9t)mw463JtLzSc-5zWh+grhhwz&2xgor!dtnH# z>3-Y6yXoHS;9WntD+~VNEclnQ;6KWOKg14+!+v;j7W{cx@Z%i3+fGi%f^W!zU+Uo9 z_AP1g;db@b5MJB2?>lnb_HC_$ciXrB>)_pVH-zNFRph!agx7Q*aOAk@Zguc(x=%QG zH{D$!`I_!4A-sKk^#1I~ant>~gLl*I%gTT&+zu>v@NRwlse^ahjdll5Q0vPFA-vX? z9Q#V8^-=4~fg!xsmzaYucI02^;9Wf@Ie1r}D?)hHXHE#O`ZR{{s?U!dysOXs4&F`o zu@GLmQ|R0r?+;amsrrrQ*fFNxx|EQDA8=YlIi@Smc-KGgb?|PwFF1JmkNU${3xvzJISalm3qDsqvK#xj{maNK_~lvf zPh`Ot@cRm`u%5>|_+t^T_NjO9$2s^PI{0H8{3}`T$8y3A7t?j+3~}(TJu5pb=&t(q2kArV<@NRv-BZSxb{$dEP>GrTM$JD>rk$<^^cl~FsgJ-%b=T{Eim9yQ! zyYvUygG0jURycUqp3Ae~zwh8(JO3brS3O^H@UERdckr&Aui#`RF6!y}?Oj>$uVlgZ zunkmL&c#{qYqH>9$bvuAW-u(j%E7z#xiExR`+PfuS3jH^!mD4s;^2#6Bc<=p#swGk zasB5y2VaahrGF|5K8G*WxG2ZAvgW-E{vMl5bMQw{^rtH zxR`DUeu^(~@UDIS?BLn8D*eGUE-uPpSFiY!9lT3l>EMrd=&K#POaE;L@5*29;7@Sm z93bn+jXrLEXE}H`zyIps+2yMKPdIp2pKf#lT+AXH%i{k2(1M4*mdkuDEjWbMa?6_)^5FoGA{T zuA%trv*7Q@g1;{d{vikN>c1@uK5h*~{oQ(Zvx9f**LNMfTOa@E;7@k)^-dQ2XIb!t z97N)xKCYZ296Xy+wOb#H*ZzpSyiN$=wSPR>k>mDLLmfPQO64aUyqoT*kbFrLx3M9- z_K$Ur95>y12k)jk%fY+p&I`%cbgvEJHNQ=c95>xt9K4%uu@y-B^QYHr3n*T{%W|zA zFU9Kz@PU?D@#|PXxK`$xr{eWH=*dIA+8KyL%LiD7{XYY7v>F8}REQd>GZ4AK59i5X zk6-=Ylh0t6-+B;KF4^nHZt8(70?NS&hR_;N?!0nc9(*sT9-ye-@@AL);*=b6r!Bmibykq#@|*m^mnmk zbFo~y<1n+IB$cbTAhvzcEaWOt{}~p-Go#!)SJOO>8TuTXl^Ud_)&iUA-RQfFSx%x1y zZpXf;($?55?_%~HxF?XawwmPb@~36*^Ael^6D#79c&usf<|X|0q0cOSlSf648v3{S zEgk>F()mR4_i}aw&LG5I*>+|yfkRL%uBN^n1OYa&=C?G=yCblLp<^$%2cpQZ!vQ%i zny?wi&tNwp{*u>hPeV*^E0FsUm&Th!Hh5x7o1t%_{t=NTVyeyAfh9GudpE{cfw0DI zFvVt6oQagcUJS5Z05(LV*o)bc+>C*(rlSisu_;(|)U;Kw z{_he?wm|z6+F)mErxAOHw$JCj8tiOc#_a*mzy*1T#l#)4TdxLh4fa*w$39ZXVXD#^=5T4&1y^&D}eJt*HFz>gIX*XeFjq#!B+2%g1o6 zX|V~9BC#$Weg{m8)olf0Z>(EhCm@IAuu@hHeR0lqY?LXg^tTO#E$5w4U5}j>T8WDC zHdp$uQQ^i%;i5J}!yXeNZUvn1A|uBk5|7@Gp*e`%`XO3no|jgFGY@N$*f$A1aV!)Y zX`c3kqcf{kG~j3jn6i1`lPnv0S5M}=hTe+9AIcKV3yMZHPsm5qym)b9$=?wgh7Hm< z_OdZPq(%;Y9hOLblGyy_@S!gzpO2mMY4VdL`+US=2lcr4a{w`UgO}j+T0ge5g9>2p&g2@DWltWTk>HuOqj|7sEj6EBo&2~a z7sjpj_g2Hu<@?TSo{{g3^7mA4{$uWW&EpON{0|`qv~66v;*o#QI)t-UDb2qQB|LLMCLulEa&|}^87i@5b?#os{Ji1hQ$;9 zV+kK_`f`nr{PEWFbnIe^ExQcOcg+gpe^8`s$=iN%S7HTrx1rc@onZ%wQjRsZy;<}SmLcG0-ud$EE2vFgo#%B_Z@Nc&BE^Xz_(z@5>ZAP6#!_X}!;eRspk;Q+iMn_b=qyydIt2O@4YP4w^ zs35hHR#Oi-(1y0-?;q{k2P4vtJAQ@yVbk{@Gy*j!zUI8a@Yk;87!w>_h4!E2wiEZd zJhYA7Xiy-Y7XE<4R(?9N*t(C#q@8yY`s(qfNm`x+!7;iBI0jyJKB>VYZ9&%^kojeJsD zqaL^vQE$B}6c5xXnOG=AWYbI3Z^1DOsL{#Il^5X{#n`=V=Cp+mFe2j~yLZ?RE%{qw z6nd=Y?oTpRIc!0?IFX6dqZeJy&4k63%@^;hNxoIn-1tmEP4h`IuF`a{4-@&wt-J_# zX^Y2KUc}9qf}F(}oali6L{4ZoGWn0{Wc!u-tR9urgEt-_STVZtB00QE z?tl^|`6cFH+uI@H-KyA0Xr?8F>iU1W>fz9WtI`sw97kbD$Vf%D4IpRiQo1CZmIMzD z9l7koMYCvVoSZs=);=E<7A*(|6R2Y~%?)h@Zw=6Kq;A`u$lh-YU3er08fe!)!gkJr zYX3EnKWgY-<{g2^qLFB$OY@Om?;32ceW&%UC#7DPJ?HQIuJWSsBa?e7FJgk!9Hl74 zEVA=yD&FM44yZOnPq}2d={8L%I!QA)mld+iwp7q1(uV90yh@q)WpVN3<_A9hVu}ae zL;PD0g76&z&pq(hG1gxm zcj!85&|x&6^a{9P+~CMdsUfkYFTn3CYg*Kh*wUBTb2Y{jJbC?8z$2RTKA;d3WT&&6 zhutS|azcFcSoaJ|7wDr|49GCYBa!l2F9p*1pk^V zhppvOk&2pRdo>2+=1>{*W<(ZYsLCNkB8fAXkFMa!?>)IAe+T@m2IGxlZVWCfZ)N9L zP>B;fZU0@9{A22@QjG9CoY9Me*^IBQ$crcB*l`FkJ7$xw@dy)%#Uazf5R0=hNh=gCOI$P8Z9`3^^5H_{tM03 zQIB5BbT7k@33>mPq*E2j#8zl*#-`l*Sy#=Sdez+db0^K7HRD=mH|mmvi68^Ppc(*tE-*Ct6-#J5q!A5=RvRKdXvy)2&WjIaf21`*9Opd)}HWvd->W zvNFFi*n2s)Ym=XIR@deJ*l*)c)d&kb3TIK16>D>0`gHe-6^G>e`0i4(PGY&)JzJty;@e8NnpUfAhVD z?$$%BlaJhbSo%M{<c3o46ZSo_thX8e5d+Q@Qb)=@tUJk9*|iout#D?Czi|5OLL$@WhX`Do%_F#n z(+?s-0O|fW?Kz}+{}X!)O);v~Q${kyL+htsrf<*OTS%&?J%q;q@9rU#F#&J`@QbPf zr)9yjorEOF2piVw&7L;1MU6YV+<#2RXtaTw(;Bch4u{6f#TGcT2MqiTmruo(Gy~XOY7Mg4 zZ2s&9?zOvo#;nVx%$RhYkjTC?W)jz6^N??TY^(HJX@$jyzr~Jp@NS<(KjOu7a#m2sPeCM@LY$W_>B(!PzSI7KgVbne~4{7 z!+7`qpWS@9|0m3sM@@H|?L;WYrLVS42k~5=D4%VT%0JEG zwS4W;Pj7e#4;H!3cI2>cQ#qp@yt_t0|8Fxae_9s#-*)7<^5;8vSN_8e-u)K-p@Vnr z;I74YzmcD2zh#HZ_u?%0d0FuHJNP3V`@G=bUB7bIth@9R3XQ$Pe$wdRk8fl{J+>-^rHw!+8iQx+S^N|)$b8wo9^$k;M=p{ z53=u>Vf{xsc-L<)$bz5e;N5&RIe0f;KXdSIzJ6ozVSj!!golabdfbuY`opsh-t~tU zLUPpK{uIKi{5KsruKf2Myeq%hGSZ&>={3Uwiq~-%=jC`QUdKr%mzNDJb}svWr-RF} zqNlvf{L`uXf2ZYTL(2vW$NsPNDLdRIj`S<21n!5UH{<7ln)>I>zny-_|G#_&-v#rl zlh0P;1Jshg;Y0NwMkVprMpf~W_wWra-X*c(9{C<#!x?K%w)ezhjCM+FK5O5;jq!DW z#@ii{QASIyDlu9stCb`+x93?Rw@~j*IYXM0%=SACzy7Fc63(Bd z%C{m@zV`p@{=oA66dxw?llYXl9l0*S*C+Xk*ck6=ink_dm*=1~X_isRJt(nNP}e@B zR7`PN>~a&y2eVXGncPx|a;@aV{^&}73qL9D`~?51d+5H(=5rU_QTY)THEcl{SH%)L z@efLyKf!8_d$*(txPlYovst?M>qE&JtPj_NZDc*H8nAib50EAfR;JBS5RwCY#Mi#6vjq$nVRG$_%I}Pw8+4iOSTSiOvrCf_!gQJ`{6H~c;x+i_#Y?KkL@%Y>c^<$ z@A-eWNDAj{Lk)a}DlnEbqQ!)}tFRy|(~1^_%$C%wSZUaICSO#47BMAo_+qY!fYz@Y zPm@E#P@!RByQ76tsh z_Gu}Co(@+v{@l|TJ{||b7E!mSs07-YcyAPw^rXTGLGV+cHiH%)w$iDA;P*zy^!6Jy z?O&B_WBatEbvW(+9R9-yrGWn=#;U|2XiF1V&(aFZv#{4849Vl-!_YbnY#uSZ3dl7= zgfr&x`TkBh;d2cHi4RJE|%lDd2c5yxw#X) zS}p%gDB`&0kDoNGOb}7oR`99NjUp7woA4J^f1r|BpLa33EwOeYP07)jN{T?EmkKoE zP3p4tvMsS>8~!bu{SW>M30a>EIZ#wAX-S4u$fsEhmf|{|V&*OE(`D|#SHb~U% zjAD*oHHqtAL|Ac4yfi5#fyDmBg-S>?Cknz+@ZWDVxI@y#lB_gR zxBv^HLN1s=gHNh+yQIs?pyB^<=x5ggBnn$)(4ae?+dERuopGAZiNYsvvGxl_Nb`Al z__e)M*;s!?#d`5`MOuMM z2v|J`vm189t)cKb6XpU5TL?L4fE0nuQmWObtg#uO&fDN3tC&5G5cZdLNb73e3S>Bto$MrYG=#TZet%ZNnM{JCXf zMR#;4ro_8%q#!?SE^2T zAjl@RLqw|;Ct^_%*Fg>$jd*m?OcvdkDSp&q;lES<%cTbNtlp$I=C0wI;MMUaB9klS zrHadnXV*ZfA?B5ci;Ur^#gJ=V(PByh42)q+fpIy;=9BT84SR@GsLmv9j%koJ2(nol z0)L-wOwj)J`}_7GyH)VTC0k3XF?b(WiGNuB2$E_qbi~<_Eyb)8F)QQi<%vaF@Z<#| zuCaa5Kd@W|3-+X(6G`mwcu*{g;c|6yuP)vK@fl!7-F{HahUUtU*>|U82bMj91@!n= ziSEcQFQ{qA#TwLvrZLq)(=$xgTq}h@6EU(u0Ff*!jIf70(vhO7momZV3xC5d1$LN@ z-vWBma)ezgjD{!~Qo{;r)Y^B&3S}6`FJMB!DQr?PIMKA-FeW zQoUH|3;0un*e$1GfkbRsIqxNpAnHh(63IRqE5%#F)dvIBz}Is4HTq2~^u^y<7;muo zMqy!0%giHsg3N?q>6<8RIw?rtOVEVf00tT9X00c}Pz0UwOL+C!iIDKsy^oimKY;$Z zSm`rBLgaOwVoTpaQ za~La?JYru4R&~D@yA*m&YKz_U6e44#eW+mUruBwiU(qIDE+egypqjmqUQs0TIvJtW zWwlff<57|o{BsAZB8cylD8~!M36{L&$CeGio*-o5&)}FB9~dir|LXGr_P3@(MR2(I zLTlv5F2>HEjzIp>cu%;3nYh8|%JJXYOCY?JJqas4B%7CW{6&pX@x9oVNB!8iw%GVb zVi%dQ5388yN3NjF1i3Y+*=-cNX&b`8nzwnBj4keN8{;$Bgi9Oyu5?0Z1ldjCu|D-+ zqhPukyafV{2DTQJ>_MassY_EYdbv;TeO#ZWU(KpVE#>V$E>6o{|IfwAgzQQaGe+(hj(x z6q^7FOTJpuY}zUDA8jb1WZq%0WAJ)WsgA_*|s!|PSl9LAR0kIyD->#ytN-h zVU)a4Qo{f4Koh7?XU4^-cw66#UDApr*?VF)t;IJ@9g)MP8|Xb!Vl04I={;ii*iD0w zp9J!_30St-#qy6vY$De&#tv$j^Hxwee{19X_aS3Fkz=L?94c*%Nm67<3h>NAQfx_j zW+y4LBqiBNDlEy6>?ETtNg_MRWJ^+;jl{ff1T}I4=z_o|H_++^*13UQZeW!gSmOrv zxPjenfbJM1+U5p!xB-@KK(@gR>7Xg~Y#@RIg6uar)u^k5F zcguJZ0A2&o`@Dhvz@R#AG6emZbgZ-^cJr@92h(9nT3a^fOXIvHc2Qf$WGDnNsZ6o0 z)J)y@V|;0fmA)Li=?(xOGr2nnJgCWLVlP!sFWrs0x{h_V;{tqj1GzOmRAcK69ZfUe zF%afK7c?EnyNq@s7$`PZ4U}-t9_$b*d9tGw*)iR?QSp0QVx=#}zSp{R%e*5sg;?qC ztBcSTcYKe;B~Ns$5GGTgWY{GPv88j3ZA20}sb^q^$7ADn#4dWgqZU!7d-;e%PBhnh zp&cF^p5noJka`fwrkgIzew|@969w|bzvcn&jKtsn3m2}NrjjFaZKBKo2<=QFxl>&H zz1WzxSn2O#x10cbn`YbeHhe2Xc1;%qJ8`te3pQP8-=Z#|6PPXu<$%pHX9q=q4*-wF zj=wv;4jl4T$ltMZZ*oiXgxr0YzE}?IlGa7|Xn;vtyc{h>*v!qY1jKqIjVpU@wiD$a zE-~rr-T?E@rX1SSWPAfD=6wBn3EKOlM%14a{a> zlMP_ph}kw9sIW}}6GO@#44;l+QKVy7Na+}MS%P61Qh8_4FC$V8wJZz%DG>xZROf%^ zI)%13RtjUo`c1UhEwxzVL6OvopykIss z<>SQCxE@Wd-zQee=q>ds!px^$MVi29#T!D3ADxl7q*iokEwVBj{M;@x6ytn2BAOk% zNR=@8MDi6N%sVx3J*g8Iu}v#h@+lfW{18ZVEQSR8-2wCse1T|)Ct^!>qq7Hz`3u3+ zXcQ<+O_|{*T*s&xkpq1XBnitifsrb+XR>Y>?B_noG3iY;-Cz2p@)pJtBA??TPC2r) zwepu`rBWGYHdW0XRQ(+j@xOVKU>wYJ$8LGTyg^u>D%pk?4cm|4UnNVo#FpF!f-m(l zG2_6XtQ{Y=9%pnB0-VP;>Jj5CT0Kf~o9xI&;7qhqVD$y;d=t^n0v(#5ifhd;+3loAI#t$4B$SrzAGUn-Ii<1bD17kI6_5Zy^gp-(S)GZIsk?kcnJK z>B5LhS%YvPb5(I{Vt*kh1Y`cC;@_!?&Y=(g@!7PXI@Kp%7tQ=a@J+ke`oV)T?O+48 z8o(X+zNb#f>3%Jn8WzDs{2wA-ic5CJN?w4OF;1y_Ay(Rs2PSNBpAfGA+I-H$TQ|I- zxbl+9Z^drppC4jOC54#4kVcBOXc)#lPl1g;v}j<(JBIE2Oo2`4f_Hwrf8bCNeM2=B zt{iXs6Y&y**p>2UrlCe6&9%O2^H5n^S%-|W>qOGA(tpPOvI>K9G|qZ_aSoB9@sM z?#3?0)Vg>v`Uch%W7P?5GE1~YoZW{zfk_5`!F++tso=G+O_*!j;2Lm{SSdVZ;${rw zVmCG$v4*V_L-j@=>|Wlh3zvL~V|0E5O{c9=T)|TO+xmAwgv!j3lWiIn^+V#KNUU_J zm@#%!H*BC^f)4?p%8lKcixAH^km(M6@4v*<{En^|qME(O5OX9LSYu5;2-_N<<63MS z1QEwno+lHS9Y2%V8ScSnAhou9cBr-ceR$=JC6Io^X zcC5wrOkdHbo|?I7IzH^p=?;qWs@Mf&1CR zy@mbD6|(-UnxAL5xBI#A7miGB#j=hyC`6uGf_iTT&eAGykjF-jUjk?21p+HNxPqfQ zK(r~bd(SE6RLgEqu#?295v&-&Vi5!yuo7h1(|C8_O9Mvpsns0pPwna{e)2@iA6q&O z3k7AXHCAl!3|hp@4ZD;vK{%ieN`?J}bIq<^Ec7!{DL{bCPM_KOQvfvi_ZQL zQ5WpG9?p+dZOSXlELtVSKS*j;jC|2VWvgz!*nX#%q_{l8---1fSfOG5KiMuI*#!|+ zTU|inh_Fi%tccNx=CiRod#BJ-&TdIxtjgrA3XSQ7pNK8}DZZm8kfk)ID2^>%L40yE z{^{E(b|hv6RmF>|o3Bn^ZtwL#?+KdpH5U#)6BrGG^i>FkfP3E)PR?<{3^I@PWg@@2`TV@< z=Fn;j!-yy-9a?P>M0G(_DAR$$WDDiB#y9EV+=4R^8_I3KiCl(OTEG&9lOHsZ$IwcP zAQ~$zy30xnj-Sw{zJ!VAHLRCtfN=0b?7M%%tOtC*JK}x^Q4O-pq9)#mmSX`XoT~8O zKnPV2V-pzVZ8-bhH|B&)e0{nK%;8grsFAV zY%Z|CS5Ctc&@Pa4a8ivW14&al%4UA0T_}arH2*7Q(hcE&8zV-%7vWpA`5J{Tf%&is zV2K=xstPr>NCvcsS#fspiu2R7z_uKDtSg|^hQ3=B30 zG8tu7j44$RlOXMM(1v1+W-AsIS0(@Is6%xFbyi$FyK2QbqpOnps9Bd0So3M+nl%Qn4N7&Chc9g7FgvZ5aNS;H?UxXygmg>Hs7k9_!6xblQVR%3LQ; zGY7yl#VL^{<0UQWT|eDnMwqinjfqc_R69a8spSBX6s6&jNIfvQMzM@wR;;0H7M4EE z$y&a+tQH<)=;o#Ie~^T*7mO`R~Pg3KKdF_M#H)tGU>oD-K9j4XdUtV_r1bR>@d zA-Ol+gZb6`3}}vU9pH(0O*1ZmS07CK*tg=Y1th4Ik+7S=hdSLK=CK=!h za*^a8XLzwNX$&*TAlb$o1Q*;Gjo>JxRRbCUcvF=(`2vvkwxo`L*u_F7Ue*)QW)i3t zkh&vPC|0q|#b}8%#ThOJNxK&Qu?Ix^>7$gn-pX8#Cz=VYzBYcU{tGLb_rdlHe=i>- zsDzzQ)uU$NW0O@hHg+K%6c68-{e#ff&o9TGXTN&7}W@1u1<|py}pR;nYr3s|rW}yt> z$WkSd@ry4gdJ<8BR`*CrlFK5;b*&SoW{;$}LOxj`V$%yt3P981m+c}Dh02G~dWciAmC~;F!|V*JC%mgjY@T z`u`=3|BQH5mE9wuRV>^SARW_ua;eQb?M-lbj=?x5$|)?Y<$-N5uwuiCPw}td#jn;3 zUQqLzkVpGSb^sV+!hBREUuROQ0BVs}CI61ps^YUBu0Y0&J(p}PrqL*9RB~^~oHF36 ziZ_amY_HivE&vuwEzRgg)-80`bqgFVR`0ReBS+P(PpCH8@ zbO0#@OnpOlq!o@^*Zu@+Nlm*Di?(obSHT>{I| zf*c#bV4KWn7OV}i($8WyoeYsYx~?RFVb+jfd4f1|usi{uIh-X0vNl2A8_W_nj+L5V zNQWx*Ze-Uhf`n;jAz6lC)+g;aemV+L4`v?m&{=*+?)u4YL`q z!KjASEC{e+2?C?t0Bst?ut^C5v)w?k8z^xD%iX{NH$a~aq)m1M(^CPwr(2pvyB?1( zx(S23Ve1-DW~@)Z=d#AM58!yuX@FTeVw_RpdLp#O(^LL+>ErORzs$#fc-|cR-SOO6Gp?RGY0j+6 z@W;cG>gP91nKY$g=FHN1ug_d_9@+V!bIhP6uHpFMXtcHw91N9w`>ONt{UK=>P>@s{Cek*yJugIxXBgUA6$7N zXexew&JDvcx4IZK?$zW55@YpjoC28N^W^3dJT@d1%E^OKFzcI_f5lbvJb`nbJsX&u z0(|qt!!M9WjtS*iBtF6+E=LADIR)}Ca#qepgaMlgS~2+roeJ(`&=*c2$SeCfFd}eQ z{)Z={Es>=1KN<+Mh5vaQL4*H{aFKyOC*0G(`v{u@74kiwff6}wAm5ijnF-`bV2BCi zN}$37@+FWkfld-I$0y_;AOU{Y#_sse{^d|{vI!LWGZC0>0tfljc(w_2lfXO^IK;mO zF$+wfhd&MhvvW7Ur+*m&H=3AU{#-~~Vgg6{OAuIY0)71(LAJ^SPWJiovc?3;B(Tl| z$|bPg1Wu8_1``ey|QF1?8?!|IHM()ST z{W!UE!=>l-k$Ydc_mg{x+>e+0335MC?kCB;zuZgZezM%lUkrAn$8w$^O1MO#z^S#0|S%179 zS2TEq!wKf~m@&gEtl(}9s?Ly&M)M0dwsSCWqs<#2w8uZyzV{(t#Ug0@+L*eO2ugohv zmqKPEL4_9}MTMJ?q{7R4OjB@`5h$!{9C)1ruZQ7AA;C@stC@TJyu$OILt0{}E`{gw zUz!d51!5`(&NJ|6@)ua%3qMCr&}@OLQ^9)z*8t8b!eD@?E96e!kp$Kh?)wg;NK0Di zNwey8pYIj=zLY@ss}(HjevN`X4RwxC7n?w?1mfLMqIm+A7<|5e8qz5WFP_UW0cOid{~+Mk4|P6f0}Pa4fX z#{UK2UQ_4H0nCbfxIwdvwxAS^q3-X$25N)v&&p=O6m@@x`Z9k2H0pJ}=U+x_uLS@E zbDaeYx8N*zS0MAU`vLNp5znt@O3<3M+~~2vAB59G49^D2c0i^lxL%_aK*Ad&;W`B_ zv4G{H&-9u)Z%R5=Sgr+*_Uy%yvfyQx5oBTXy5br|UNf)3f>vlMFB?*M`2~fVZ2c#* zWx)HG2Lt|^VDAz0JpX|dax=5;$!oelOZ>yX5?>^+M;PWY2p2vYCNS8eVFL@n2(zR> zj)oO11Tz>&xoHT6sp-*Tlyn;6mByvd1~VHRhX77hVRantQEtv40H?Q|5bqCf=oS;o|x$J%6qgyFt6~LZ5C_Jf+O@8>JsS*3kUcbfWUFr=EX03&$UJ zkk*vbSrEog1!Sc$035~u@R!r22Ebhm0B;e%@=;_jl;!EE>R~`B$-o>8Y$9N97WYfksrR&RD>O5J=1iYu zk;Y6ynE}2+euI5A1@2CPe@ubxDe(QT0Dh37jRrp?LGMxLdH%;zlrug569TrD9_lm> znXn}~T!@Mo~iW3YQ{^W3!=hb8J&WZkb z3OvytmvcR^8a)!xADHNzf(pbo%w0U!J4$XB^f|V??;`Km^4=lb5uxyLi^7QE8Hj@7 zcv*kU+#Pnv;J!zl=#R;n3zf#^EW*7ur_s^qR%mo6-d(8hd;WNk=C<_gl+yU8h#zj^ zbFK!}IFSUR4ai)rU!J1;3n*u0qa2&Fm`R7}*Jh`0jH2I}l|HX!-QrH66wvl~9~zFl zmi3GtgVKN<`~lucS|n$J$cqofKteA)ywtz5AM)uPM`3;w(r#f^S-w4Xn}cO+wVQx z#a}-Mz^kM)Pp~7HvEUx26f;in%59*%gshB1_!HDs^aWKojndoRwz52h` zQ|W89;;(fX{<_ubMW2Qz7CG)pC;UirRg4lJmp9itB`$RI*s%~6;U$Nopiu+FJI08s zM)(Maxq4k2v|$I}Jk~zTY*ZgTP_Ga7g7NCZ-C+X!hXNZFc*p{M+W^oidQ~d-i=-p2dAX!?V*Tyszz7aFF$Zg8}gBccfs}E$&w$02;4y8+H73U`a3f12Kh= zYNTMKLGe`K{}_co(h!)!7n6)Z12GOJ%x_ts-s~v=>4; z)-MNrxDB`~lR6ibIcTn2`q%~d>$yyNTdTv;N&RsKb)dyb#%`V`O+bH!{J+Z3RwG`P z@hi}G-hesq(ghd@ydiH3Bq+sTv^^EV7^Wi?!Z_v4R0w0Bw*tYX7zq6>2%St({@W6U z3zSLl@7ix>3BMDH-y?6 z*aSbc!4ebv$iE)1-8n5~#qys1sm~Yna?$#qGW;iaO2cf=-=~x&*2@{lC|m=0^K)`I zjzdW=I_0v}w*~ndgqMuRgOJhoiBNcZ zD4fXEYBrcJrS9c_Kc^n%?31wgK5r?oTY={kctU|z1@2Se9tD1+z-R z0+%Z=QGp9Ba4MDU_aQ=T|4aOsQNCXn035*dJ5&G+(~E6X$q5P!w7`%v0hClLdYl52 z6u44>`3mTn!Tq?tIv0Ig$!!|-BL(hJ;MWRlQQ%1hb}68T`l&!jgUcG~csiY%~{RA!94KGaHA zoj#-^<=rl2UQ1IW$MO-h9Feyh+I;i|9EOaOQR0~VmbQg5D#>fv%S_khwd~<@N~f0X z3qQ>3pPuK#Pza$si+(|l1B3DfC`6gcZA$0~Ae(4}ns*}@f5Sp&U_^q&N+55BAp&OY z2}6)2Yh!J()P89eIUm!#*zp9l&KNCOD*~d@tV9R7+IbC_gR-fzg6KZhKT@`;WsOOh zf!3OrNpCDR*0_K>pyP1y$C>mg`Dw#NO5dJIohpyOPdT#?Lu1F1ld2FlfVu_T%(@#K zrrj+ty81a*gp>79R>aXz%hF#c(}$McV16QSFaPzNZO5Z2KHK8^Y*%2H0(%q~t3aay zHz`nQkMHj@NP*)OI7)$o73ieEKb8H@3bZP)QGxXe+^N7S1?DI)U4e-fFb()9q?6mS zd~v^v1z5GX-xLAXEbcc`0KB1GD*)b5mI2^Z(xSi*6!?h(_bIShfhQGsL4m(0@DB?N z`3yiwXT0}__(K&qMuGkc3{gP;WT4+@U~*fU7M;#7OnUXTfPbIA?l{jI_CDf<^~Uh9 zPdD6n_35O*XO^$eTNc2=9doIaA^kubv@xs&>1|BV4%rUjmq@Z?g4NU;>`qruuz#n& zxDPboD?YD2oG0KljFSh$IGfOi6AZliaK=FZPC*F3ISBzcNg)7dEddqAZ7(4$OdTe(Qf(Kf!iudN>9HajglZe_GzgFpF{3cs5azY4lO9-9 zM*0~uPpRaY&vk~2)knJTj2>jO60FKX@VXML$wH6|7lfruGZxiJ#g%~bC1C=3>R3Y{ zy>mu?kk($aXK%FB(h3_}MfB^Gdv}K1pcVwqK#9NKFNt@8@@=6U=a*sl5po)~?mha2 zs~u*OnihphlSx)v%2XyGZwE^@YLG(N3%@|N^cmnzl|4Cr_GKK)8$%q=e*s5IHch)P zgEHE*$Cnt3n0$stjX@z$;uuSrstC~%7w(rtcI^L_Au*~nXsM_6i<0H`@qVRFnI|5f z-4~+E>(VddV0oRStr+K+zER*oWG4`i5pbHZogn<9(JkcjZy=@{N--D|!G}4}9^((; zY@3;(ADHqS(w~)1zv}vcKCfjt1GShR3Os`x^V2!jxg2XEIsQGJV}r}FK9b{wbdFY+ zV^btY;fd+_b~)N2InGGu*zI!cisYD;&aubkXwTqaIVBB8sGJycS7Z$B`;424G^nQh z@>*Kuttng{`S6;c#B=82jmgk=BK>ik#|?9wn$A&_&QTD-aYZ^u_jHaf5gb2E=YUY7 zRcC6&PV?9Zrf1TbAlGHWC_IX(pnqEad!{q3k7ha}ovC*^)23)9ez;FrnysgnOnYR> ztWBqPN(S_iC9^Y~!zmeZM3qc#X_`(>$&e$eWQL@3I3EdJwu~+L`JXB=naw4_h@uGqh%nOuG~{fu#NyQp?dAy*Dy?mPYT2jJ{Q)cV|TNz_)bNw8g!3g641YZ6EF{*`;5wLv@wodp^!sgiw zS!F>4KS*K*Ym+mBHDHYAU1li#3Y5f=EqkwrrDF1sTgp-c`>Lfb>(tV8J+wa2uSdVi z`Nk9d2F*#J#dOGCnN^DojutHJUsy`{%pJTyo|3W!M+1MfM9!OuKM-DFhjz98kaYS( z@OdEUqwElFjOCEQT?WU(E=gX1UZ95RGc4jJ5KaJ-bx zAwxLj*kQ(6p`0HyC@tqQgi{U~+-0OOJe@;^aLOTrJ8}eW`i)2U|IGpdFJvQ;Ggyov z&0ESr7}Hy0(59uw4AvMlWT_ZVHP0|H=`*c5!+IvySeeS6;^NEMDu*lTHJWem_XK?8d|O^@X};)fnTEX2 zku^@|ssa_W8S({(%h59MOuj7!rT-Aj)(fA{(X@4z%?fN}(Tur)vAGB0;H`k=HL#Qk z%XqizrKAa8C@EdU+(1N{W>`&sGh%WIM4SFp$GKFc-BEOy&v6B=$0g|QOiIxJPt z=)Ixnro|@98ozw8th&(Ps>QPELW64-%c_gdt$o%lHmff7+3@k(-BZOD?nOqH~^E1hdJkx-db;58|qe!6{~0b&(32 z#Lu&d-?L~C_GpX>s7=!2L*a`;;fsSX*BR6Xni<)3d0ZNu+tO+?_NyRXK2V=CT-Tjk zE@Dz^WHh|dmd?#KUf0arV#8{&I|4O=_J%FyFNR60EyFp!m&rgE6LD?>^i#A3FJ;hS z){t*&X&<9G!Att0bidgTI$G;58FW#0rL|&k-gHke)}j-q%tgwf zSt&OzN{#&3zoz&&-!R4S;RBL8RN{F3yH=w3j9ACa5k=3wZg#2jTI{%(v$c31$e2;2 z{P)s1?6}x)*pYN-zP3+=v4&`r*J8)ThQp4eLmU^UbJ%gQ;jkm=5JxhdW3M|iYDdx` zj^CtnEMLTTXPKwQ=XwS*W-Z@N=UC%%tjgeEDIHKLIS-W*V~&Z8p?$}g7+KG1hmuw+ znM%*lYDqf1Gsg$|$T`0I(m9+tK5|6O@x77G;mq-oBWjMX_XwxVa~3&seBeNZkDTMX zAe||g<2!&~5f*z<(}GLWS$b+2b|wp_R-%~hPiJyw_{h|BeI!#y8WYFa>MeUC=U$IE z%eBDuPPu?SvRp=`b2#Nfj;L~3oX+8t3pt|7Wn(&rQ!bm3b#O)5>y30Kr(D1gT`s+= z(sJ*V3(6#0I%CqAoN_^#L@}`@U1^vy=M|GSqD7`R(>f|nORm>wGwry@XO{2}U zV>tS+8ZDEW8N%~NO7?SG%(P>eJFe046+VM|q(*a*I%Wt_G^y5~%1u)OnfJ|GoVK=} z1UD;z%!+~_JSqF{N+1ixGL83`#;>Oe85;gYqvaERMs(iUHv6)OAS3!Xjg}<@Y0;dP zI#;4OAuX-AVwiOT6NblNGqGs)ML2<-&w zKQxaZI^Y~;9_piVL=tg2_Cz5XFP-u@Y3sRjC_S|cj3Aj#9Ft`(_Hrx7<>g%LFD%~9 z#jfTIW^2=sad^XRO8X;9b#_X(N8l=e%g?%v_4e zF5eG=fp?JIV45Lg2Kk6;lXtPdDM7J=S)m9?GMSso38#;%EKfKmD4i4`XOGt!LOXkW z56d>z!iU^aWou@SX?V;Yue7tr`PX=Od63EDkPe(YZcEdFtj3xdmGo_g5Z_HO3?*r3 z?)b=aT@R3HW;+wSoAep;p%=LHb^xmLp)xeh;J6{3qt%T2%>1+rO*1%tna&{tP)$RI zrWqW6N$1$@rXfSq42~Y>Ir+u{SeHYFrWqX7=^QcuRjp)bn!&L!okIqo${|Bjas&?g z-#qpEUz^Sep1n9-d|r*%67LOeFUXjw8iUq2RY4fDSYyxxr(!sr_#YCJZDKM-%NfZn zMvB`BP{apcC<3)0_dp!%7A(D_B)khkiibBR91XLcM^_o}Cn@l!1S?2z(4InWc^Zjs zypqS+RGv;OMxQv%>hwIWA*|#aPx!_eu1dLa#-~zloQX3KCkiV$J!l0D$opv^C}1+_ zcYaZ}Sv|{mVUyG(_?q%yO7%s+2b}|noeFrbfM~CC*BnkAva1ZU3cpB72@#%yaOf0x z=%AIWA)|6r4sOhI<+FLX8=`BwkcbO0g~;ZjGLIVx!S?7+xaMq)8-TCKYWKQo#lBb)?B++GpD9BFFC>Y9Ps*EK+o##jr{KyfR z1hZnT+jI!6%UK3f1fsFH9Y+2*0__L=apz#Wl7L}~?etk(d}Vb!wD8DW4sVQpVl0sr zK!&%ln`|m#Rdq})MB=jKF-07E-RE$|E%fVse(81iQYnabBgow4tUUI~VJacb>JY+? zfju1iAlY_kM2*a2@BGz7NnTRM>U@)UM+w>jrz|8GXpf3iiui-bWfETPvr7&q%y{F< z0tGi?+Rj%=O8j;enG(-Vvf?kvV{>N2f3Yj&HTYEX1(M5+hv^J)&zj|KJ0Pdf;uH6j7gl3fnI8iQS}HymKHv~Pw`*Y`qkRMm4hm?8fUL6( z=2suWAT}}^NtoR9{pv%=(d8Jg`jC?mc~D;f(6KLci{+(BEh+(D?}M-+GQj7P_h2gz z;&C2apozqFN74mQ%O-tjo}be4utOkkSTC$9wnWGZ_3K%RWSNzIy^19}z4DC^$?D+< z1a>%LpcCkg0Min(Bdw79e!Z#>F=CFgoTBH^R;C1sEvE#Iu`(raY@v^HTT%+Ctazzk zeh3S!qUY@+deI7f^1bpya!`DI1?Y+C;DN*dC=mcr$1|!ovUo!BMjs_#F1A1MmK%L9 zr)SFuke-AHj}e$tFZGA_lB`M|(h@68Y0o52PUVd0;cz{}S1k&ar!qw1Q&O2F@c<_l z!GWnD6&>UdgLm+eP;IO~zDI3gos{pXg$Us2H0BVKzYNhBr)mB??{u69Ci_fgzx)u%K2F-x!X4g=u2GS0gkfUp%6#$B5pJA`IV(WHd8!XF$f{D$H6+E3 zL{7c4!^EtQ_BzKteQ&&i+XB>u3hKEmc^(b|S*yI^XFOmWr{FGl6))eLsGuurTvl1< z89p$MQSjM7)&vDzS!4D?)>xJGVjydhg08Hw`ys1VWhrB>hrOv*?Tm*K``#1<9lksF zgRd@vZ+%w253L3{URg$7k~Z#k`}DoB3i3o%Ub)_!%C#E!k-_17!}&KZUaO%E7F*!FF@N!V?PgQl?~U700bnSth~S~Q zTWv9g-W*TP&Y9yKu~w1)ZX06aZqXBi1UrT6J(XF*wcxrws+{0bu0>#Y zteynCN4$pBmpXa#D~Z>zmc^mrvN%q}YP7o1p<&w_GL2T_9U8W^A=79z%8}WOV#vdJ z-|ZVrA;)L9(2^?HJy%f|DtMGdMYp6XD>slePQgP1Sr;kj%F1j>RaUP+)))nk2xLuE z(3O?hl4i=9qOz0`t=~Nvtx+c5Smo;x=rBP+6-C=*w!oRPYE@RRK-Q%Sy0U6BTEI+M zbt>x^Ypxvc8c*TdEKXW@4Lt7Xgy&$~jUQ4X=``q8SZunVEvSaI$kDP&3#wtYQkE7}qXVl%x1buWx&4YwW3jE5)j|CnuizTa5aIH@NebR! zQPEAK$~rZWHBQ0X1Js2Iy0S8xMwK-wU>u|1k1Yx{c+FxBxY9Bk$4qHsRaz{_3Labik1lp=+I`exjRs8Jm9GLb zY`lDLoPrNyp2DCmRB)ITMeiNARzuS**7q({aDE!0hEx*}8quk{>hY^U|1k=d9B9aV z?;-_z*T^m+gNR2M3d#2*Gs`#}%3>jWz6>PL9s$IKQ zLn*sVObIqnruaG)e{#w$Z3{JUXUy<*_RKb%;l0-?$~9SCl*!r_MwzTG%4BT|qfAy8 zWwN$~U!JEany1m2{NUBedoTBcZvEG4SS^Jj_~-Q+c!?7g4zY$mvv?uaz`fR+RkDW9 z>0^Wlu?EIjq!MfRa+g>Gn?uAJe$ge?K!L46Dp|u?mqZN>sEw6a!|IqqtO0c*B{tbg z-7`JuvPi2R+&}P3HK>RLKGK%JzkZxa=41PeVOE2yJk55mGml!U5eLK#q3<;uZLL#g zpJ_akD~?*L5koD3?_I9oh-^HO)|GU#D#55T-eUUg>6in+E zK&PN1zdgfU)>^p_rQ|C(ETwE~qXsn5X!)%f^4IUD{0L<;h5g!&x70sivGzs{sIy?` zq@XG-bP*|JnqwP2!t**T7*S1AP}701JrRjci%4`&V??5RGHm2w%@VJr_IbF6vkX_~ zmG}N=;79$QFW0ER-71 zLKI33sAYswEz)S4b3tlA+j!rbuHf0W+ZKr$csGSyvrq%tDF~$obo3<2Xh6FPq13?U zKE|p-seu<$$h#XgP>^bG-fz@^mIT<>WDHP6X1Zf}-WQZYq{W;R-jAxU?`PE4Z(AFy zEq(7Y1^=9~;LD8~P)mYNLA4`t@N#2>{a$X2FxAUZR(Ux?z5g{sw{XeaeyqVfVxOoV z3hF3QiurJBL^YNmb#UoeZq33-FUgkdT{c;WYh74r$F=9#XCaXo-{KAB*tLorYa^kk zf{QF_fcHUnI~X3`zZmf0PS~D_aQHD;^9Gqg&|`{9sTGIVN~`IyRd$E$2Po zL?va`0{CVsY0159OYoaX3m5s}I zbd=u2N9j#`l-|Tg=}ml;-o!`gP5l1o?cv0tD7}e~(wq1wy@`*~oA@ZbiI38o`2EqF z|KJ^^H}O$=6Cb5F@lkpcAEh_(QF;@|N^jz$^d>$^Z{qhy zZ}r8n_WdJ`X|H}O$=6Cb5F@%yLudQYy1)_ZbA zwBC~|!g_DC-JQM-``$DKwFksVeOF^RAA;0OXy*cE1+|?Nx^Sih8P1U)!`TsJI4^<> zXC(Y8fv#O&Aasm&(pMSXWsK|weXWsQFomyUv#+Lva_O+~Zm zaM^xMCBg+C_FztVt7Gbf0CCgouwT95)UVm2GRucapL);Iphdd#EtiFRjw` zKn%85=an5#?@`y(hO*F`?QyTEL@H9%aTyh-FFnkzI*zus#DyDM;=*k$apCGnTsU)X z`d_!+g=I3Gu&kH68;o{2IbKD?n?gkf-}mhtZ)!@hpkl2~!ltGafedSb3GD3R<_GHw zCMcNFtfi;InyT+j2&V~DI7Ohs*;e(h3DUb%K}`eiX+;XB(z`S>y-PFGyEI#Ry4C{@ zy(8mOsVb%a$%H9(1#9i+K`zHs_~z6n!}jaK2{zmE;h(| zT1*9XyaM6v3)NOSUJ<0ex2pjKsV3BVB}lDET8RXCxt%B1S|rGb`SSLtsqywN%?iY| zEsDrYQ`FaprpAcOG&M$Krl~O^Gfj;VnQ4le{BO!=>7Zv*#x%DUo)!X@Dus&xsIbn? z$|p5HP+@(63Tq2g*ziDQx_-toDE%JcxWM*|RXNDv_NX$H+ z2o-+1$S5KbHL(6*gJg55@Cz0vgc=wZtP;7#Q+Sib384nGmQiB3nh_VSXT)iN{nD<} zB2M9+so~D5g&NQrNy2cIBraShi3?Xs;=(0KT=@EvwcES)i9%BF>~4k_FO>>v_F30b zvxXC}=_wn1KKhf(8XcNG2p2l9-ExixLH_V~Aqqv9wj8iq^ixOIx+pYY-H$ z)~dCNs4d=6yrLqlxA(X9UTbD&WsdfF-uL}~{zqr#obOqC?X}ll`#NXB)?`g&YqBP? zHCYqcnyiUz?a@T8;u>>1E^O$Im(Z5Uh%J13(l0njUPnlBy&Lk9>)qh7LAxREKhx_w zE4f}@(v5cL?6J|F++LrDy*>|negBcYBS-$>%w+Zw*P|i%o%ny|hxVj;|2rG^+7Eu; zs2)2A#I&8nylj*PlWbZl?Pd!%CTV1H{SxyOU#0Zgn|XVHM6)icpY{!3AH>7DaBYrc zB`PH=x!xDgsv=odq)xJud6lTpS@q%p^9=sP6Bt@#66YqlV`?V3{yx6{Q6U)~7N4#4 z_=din6^X~-DLED0Su{10M|$$kI@bKLHat``NFJOYj0q&}4~jpOeP7&!N;ws`jM^% zii8%)L;QdWlhXr<>}5`QW4yxV|C&!t88xp96PZuT7GCtjyKr+Tp z=WQ97D#QEz2_z#s{X3d;BymgcW9%(aX!ZnU=SNf;C_6uKrL=n(WX&>SB;8WfdDsT@ zfA4#2TY|)Uvi#`#cwH>AgSal4a9udziJa`2&W|2j(RmTm`4H22BzxpV>&N6GA;V#zbgBLa68VqX>z^|4$!l z@~ZXe2_t&Wo5hGZ389|b>LRplkj(?zmOZPl$F`-UlJX%mpI)ld8K^7XKwb3)dX=&7 zkHoT>a=Pt!LFdS0!1DE7Qu}9WDBYesy4t&m*H5=iS7o#Gl8ux6QP z?Tl-$lu}79m_=z-W!!A$#G6lroI2XOmdeH5tQ}ddTPhbH#!R*^VL!V#6cHPf1V zc)ZnD$f*=R#TS)w{yARnQ8`UakKlA&^qv{cm%Mi+D(iuz&#)DlaVzG_saS!Z39K_e z6Bsb`nDt328s3$oc&7BHutjqc730JOJDi_}{Btbc^NCf_xrE(#~#bjAdA7wd8 zE9XbBBjA-aOXXr7LlTtp0N0@rl#9s>Nl?!J=nBfkh{3j|NKnpy&CF<&i^=?uy3xqz zck`zalnZG)Bq(t#-Y2HJe`5L&?~8+w!OnPSIB~EuSsd()$AM&PlIuLK;R<^%QJXy* z)N_1Ox-vwRoI1AkOeejvIDKqLGA>Tk~PqIArBx|{QlC|8P%f=+-_#{~#pCrrUlVo}H zTy-YN<3O@J4kXLtK(ah~E?Sf1(Vi?;+mpp=d$L&Vx#pGXy^ot2>Z9khL4=wzZFS-) zE;(HsM>GC|s(w0|i=IyAqNkI&sOO_I+C^9*im}pR0O@cA(!rW!ZK-v`HQSTfzdf1# z9t+MN@)4GZJ?&4{=k~O}M>i~rmT(m%8(@-@aow^9dvv+itxFbS>ym}ox*zNg`}nR~ zi+_R;I@hKivtBaW$(eS)EA&miiyxoKsnl=x1iGrb-_>60e)oqe2LW(nGUwcw%sDqE zbIy%DoYT(xzQ2y|EYFhDKb&k0Lbkh?$wghRh4iPXX04a2i*Y#JNwNbXcDxyPz}a%zdYafVS55~x z>hU+w_-cIIj=6H`8hmyVgXbnQ`0OMG_ZTI`VDT3I2AZ5oF;NX0F8yJLmr1$-ar@`U zsT9rr2{O5o43bO?dYUy`)JrOuwbe5KMHov$Id#%q^&b>xev))O|1wHXJ1diSdP`XuOoc9~pA zK1uXv|DWjB#%6KXDv@OhDHjsP=q2UUDM7+RVsw(;bQpJ;_3^H=N=_vgWZ8bHTu456 zh)+(PEZZ-YN|7v0VqK`;o^HtrqwVKF{8je&-#Rk?#+m)jeDHe7qnzv5TTg|9?mn8Nix18S;lwt$XLd|FJh`7VLmdV9846(^6r%rJa4s(3d^N*m(n05?TxBbqg z{Vch@nmO$tO2$`>wAK)lbNo{$=0L67vrI0E7?Pl_sbpJ{VvH1njJa#l?U|&P!qWgQ=eNhbJy%Y}9|7N`%h$_=lo>5T7dc5I>EQlLN=!c( zlhR6ZV%pNz*cz#LYFz3Vf@{BF{CDp7zKmx|+$TTW$m9q1+xgiTc}!TIbCG9eW!qK0 zKU=`xqio|(*%+3+Xq}%tTqe&b%ahx$9d0Gx=FhbMkL)>;pBy@j^F4+?kK@mw{3#Du z|EKn54z%Tw2aNt-`SSz*4aC3rQ@+Iy@;40`{6#`0e;(jZ*~t8%derqNTdnhX``$~x z?7Hgo>v&y#8-L24!99E}+d{Wy>Xi06lY6FHM%e4S_){99G!1Ex(ww9d(OPfnMeXH= z6g*;;@>SYlcu&DOp11O)46j@`yt$^LDOxqWv7+*#iUrZ(3o0vzj~X^+*vR2^we!tY zTkh!c(W8d8)?ZZLu&91$U2T19+t3B|tt?hsUs=~$74;2oZEhOQ7sKb*wlsGmJ}0Y+ z#>VF1m9143x!J>88XD>@s%;sbJ1l$Hs8N#MhatA9rK!BRq3)7sc~isu*5;OSN>N@> zwQ%7u^W}4MOI200dRPq=q-Akqv>B)97JzFTa2oLWPyjitYO1{?+EgxbsXpH&(aM&F zZltocpkZNSU9=6UAyC;mzpkRWyrQvIu8lQKh4uCt!V4Sd{2=_3wzW+al{m#=tZZtn zZ>e1vO(b4e(Na@cQCF8R62>6`ti(UWW_vT71wQbl1VaPgICbou)EG5 zbJm$d&OGPLKL4M-R!Q%CSeU}9f50034b6#mM zbe@0I(Sf#c_Xa;h4g>rr9#K2%SHa3(%?X})&gbU^ei;aE3S?{wG>!{ol;M5*)q%Dv z0vXkTwicZHV_u+fSRms?b9>*$K-0~Ew6Z{-;LX9${iFNi|KOSJ{!{z=&+YFo?e9Ok zzkk|*;KtxzgR@Z<{y)^u&;R!ieimGB{(C8i{{n{vUn~tiio5?+6#TE?(ZMO7efC*! z%KnMLeh|Da*zdE?gHyf;{?0#RfPcz>;KRX({euVkC-n=iT;(6$&p&HmV9CB40$Y~( z7o`Me2M!Hp`lt5yM+W!@_giuKb;09;sHPKa#TIvK4{jHo+#?Hhh4; zyx)%(MArNB`v;Fb^S!Pe(ZC_q{_K9immyNH2%j^rYzVI$h@L1 zu;j%Oyz0&ip1U9#93QxAd0F5^-`?vf2 zsj2?#Bk3d1&p#Re@*7T$2(Af^3Ou|#I61J@wt%@zjo|cI^0b;QFP( zmSF2WjmyuvFwmzOuI;}gP`iCqpf-5(PcD7u-RlCwg8Krk!Nc*|?mxL-;O^AzfgP9m zN2UbsTaG)sFAcW%PwyXC7Xb3l?dKnHgg=zFHgKqaZps@g=LCLw=@o&G{M+&AuS}U8 zJf8jw^bTeQ_XqC`p6@@YfABzH$y;@^$?FJ&qXNDCGg44lhhKNo0{`It{_OsN-(Bj@ zP6<5c_n(yFKdB#*ogF;jA2J~L`I*7?nz0Mso_!}OEHJq`FuKgVPhKBgA6XEXGdU1? zFc7&pF!ZfJX?5Udmt7HfFJ(J_j>BhJpm*8N7tj2Oe{BEYmBAHnUKjjx@UFn$Q?>^` zzjflS+~7Ss=+3bVt_$9Ust*hajw9a%uJv#CpLC>u=8@O^xTr3=z+Z7x;IK7;y2)n; z7C%@J>_7IW5%@8%WLzL^GHNvC;57#a`}4^k0)64gyO!?@jPXxO3EX|DKPx4$%fAn9 zFs{p}xh8PirNME5HoRW(qndACMk0UrZ4cbCJUA?PUf?SqA|$2^^ zhXbWI2bPot&$}^jOG-;%^JUuuHv~2X*UUo`tw&1@;$-sGGlIVl^zjc)LEAi|KmPx` zoid)*-#@9pzo5TA?}*@%;BAQSx6OZ_A)-D*3)vBGlk25Tt_|+Es0h7|ZIaR^?+msE z#|MW8bMTpiW?4GGA09w8{L|nK{xeOp#J_?#_b7iQ`hx-fk^N$U#aH|+*f{2_;GXip z5&qLt{P{-&cdwnj;_}(Sp~3O9S44vSe*0N)m;c0m{)z#JkYWA(!;c7F?>~(iP0GQ+ zeFr0yh6N_937q+0u;1TTM5YAtgKKL1C-(QBh>omZuoacM3+31u80{}k@t@!CPvg;0 zC*N?szp#IBhiN>MZHqeGwy2YBi>fv)YWMvT<=?w7R0T&KhA9 zN9r%BsH?4-YVX!IVaTj)sHY(|+SFvUK|y)6zA9l$NmFgypo-eM)~0CUUbv#EY7r(I zGg~m%Fc~E7zfuP1g14ii8j?o>mmy=OPScvR2aRa zw$f2?)sWbCn7&s+?rH#OAPE{PgfENZH4i5h3L);HHKsE<}<)}k1brp?MM z$TTym%%KZgTI-|Z7ewo$O|_M{tZ1sN8ISqZ(9xqZhc3t*I(?)s+EUTYoekBf*QWfs zrf5agVtB5pCF*hY-8=z7VtlqWX&Ybzk{1 z-e#vlcJfB^w({ya-@?}NYPn2`%CGj&)^wW)0@L899F1qBV5x5n+`7+7e#qj3`J!{1+z*c)2EeB%#Rco z75b_ho6z}GBWxjE-;hu@wYIt0)U+Er-IYbAos(Z2DJ;kPtRf#4cg;TPF-_H#FQ5v9 zI_g(vR-z_HNLT8*d_hx16&g&7EZwN}`=Ru1W+>Ln8+b z`2y8K{-!9WnklC-(^ucnw6MG>JCVGkNp+VEjTjFxUZV49s%fB>R9kQBuonNQ1{%1J zOtlT8DQbrk9`4NeQrh5*Gh9O0A+dxG&DhyeFv&IsU$n8g7EQsoAiKQTXS=8J@>;mu zc&+<@Gjs96`3-f(dYdQ3Uugrq8|ZqJJ?X1NIN+uk)+*ZUuvWgX0qgJXl~^1pD4I62 zDBk0n1kG52QgvZvN;3$z?_tMJgGXmZTUrnz@p*&e-uQTdZd%5QX{IW4qw7}lRBLv4 zG_vtZY;0-rnT2v9i~Q#1XcKy?Hn*#uRll&JaYAc#b+pMg1a1TvW*0j1pu~vm3`bQ} zl?^rQv6&UktqUbyO_wX}uAyZD`aa#P@{5b77f6$;tcg}$R3QDo4?UA<8u5k-Z<_J3 zzLlnKQAC%WEjV2}kFt^aSqR>ELU#Q*6?Ltk^U-6rpvmdm@i;U!i3V9o1tC}3G2is4 zwv$D7OTCrvB5br&IK4AXM<{_ACgQbb>|+}+heU5D0e}{Ro)Y^Q31d+2v!g8$+9p{T zt(P&{sKBimG)FPQZs=0fSrts9m{qo-ORjIls9J8iWt3Zw+-wq$uL%>T*0x2ftT&3< zqLr;?N?J|LxXpH(wk=JmMRnFk7a*TRTG%*KrURfdNMa;yp}v5|DLW`p(VL*@wu^*@;x*BOeM%j=XRFSNc}?Fh1==-)h=dwd#HHXB33X zr$%N@%`Yto+X%!Ih9)%eO}0d(*y&Lz;*4fFEttZ!V9q5=qH;|3YEYB5t8c^EtNIgKS>7|pSqlbCPF1NbC-k( z82l<0Bs5Kvf%JQ~b>mAd6;D(1OG+ZsCh=$%m#V>x4q1@l@p&#;;8d`)+TmrgUX?P2 zPweU$LxQvSOzPaRjMgx7OY#XUVP4DIA zLY=wk!sGg;CLI8IBEa33oqdk*U0jY~E@27>4X?0gVxvFG6V;J+hJp@<`kh1y3;@&X z^ATK^MA7PLeP||cW--V;qiuZM=KJLhbyd=3(J(U+Gf`hfU1Lp!&-825!A)wawWXig zYSx{JJf}BCX(dy?VCG`XxEK16k25ppGEk3H(tsw9bpux4_)5YTH-TqHTq`se=)T;F zyAnMjrV_Q4wHUw(&G!NcD|kLIF^YiaNk(}y?UK;`jA`k19z!!1MZ)g}Fn?8zrvbguD8zyVr zs5bis+*r*Dppw>?X7nU?Qsbbe8)>ef)gWdw@iBrcaoQ!2yQuq+7k3F(GVnU>vv6c5 zyoE7sYDN8G>aN|1lpPR7Iz9<5_m!huSQjjz0B*vY&x9@_upaHmuKY*%hSxMKj1I49 zZbX;ax@dSawxKYw8Cr>Me0XzHrTMzdd=CV5txe^X4Rx&x>)EZ?LP%KGtrM4Mju)!{ zPHbwKRgV>}38VN8ihVn7o0(R8VQRJ!MvuZcRo+_P+8nL2vqIXLGgU{6gSzsnXeA~Y zxNC>S9@b21s%WgKt!$RC@=Yoz7@L`e-K$gV=9SOapT4WYk+NilFQqLb<(NLbf@|>Q z8-49H$lUSyUZHOmC!}Ru>z|M|@R~qTT4rbJgtV;H2NkAeUwLqTTJDNoXQwr`rnTj# zwWmx4p$)I9eCYyPz}=hAA=C80pO~G z@{u^gLXC8D9^dres#G_TRWGLC_FSZ$iO+nJUy2hr-zXz9{ga4BK*{*YY)1UPnI5D}`<766<4{=gRId8{Fn8+fL$;Zh8 z)=j?Bx~EgJ$q+KC*P9cNNjNDdMa4p>sAOc18Yv~~ZY9_JP#Ju_5aOWDd@=0vO{8Zt zzCRWwr#%}8+}$f}XJAs=ZoKy3^$A`F@M=$80zMf$AAGXIVIkbV2KS!@---LX@!36X zlr%3(!QFb?^%rm!UaH`Roq-7?dB0JJ#6e1Y4xu$B@w0(ad=>&|JN+fdY?cw)9f+_Y z7m@)!ubA)+YhJt%>mYo#G}+G@Ictj6RSYw0x?$$Kl40{PhcRGSYkghqMbWy&zF|!b zbdOT!49kgMRyMbcaxZd5LtW0nN?jgb z>*uL5lpOum63g13#JQATKIedu zE}eb@r_aPa_mKLN&j#ESTU@y=PG3C3r9EXY#BH&~l?+d3t>;-v_1zmUUH|m!;AHvt zS#8r@t14E@#yryh@>-j|Q#BNw{^uU)v%awD`7tbekwkVaSkm(U#^xmHsr>Y}O?CP2 zNTL(SSH}74z}EIZHMipCdE-| zuIYF5o3#iSbz{VLa{Mwl0UOoDooy

&&1n5nCB*tIeW{z2YYzveU=X?dFm%9-#qh~ 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≪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�j0| zNF6eAv(uM?#^J3{EN)tZgo?#YyPdvMVEMK&XH)K}g^No-rv#zx-iZhOI%`0|x?QLD zTy316tTo4ctsO3k#Z4^FVzTyt^Q{kQ9$;;kvT4;KFc&usJC#B_z~c3~wN>B;T6@^; zWGuZgi<|!F-1=Nfe%E`L^ zfrT5g;@QPAG{QKh7N0Q-Zd7^AP7w7E%(;AFf4XS+e5g*LWr4YnMQSm|p4`PYu#TLS zHd`QI`cw=XkWDPB1-kaV>@Sc?_7b9(~_?Q1zM zdSornrHRWoa@}J;hbKVU7*t?F4xY|W29De4ZBO7#zbWv9syC=X0%A}Nld5u7Aui`z zzsBkuZ&P{-s*u)YMjCJkq~}v`Omk*mq@bk%Cp}2viH+rYQBV(89FCt7N#Cf~d*~_- z3w>0phho6B$r?m(QIUP?Oawq$ZKL%J;x(#b$F>eq*k%ez3!rm|17uMf)2@we>o@7U~nDpLhRcjQ#TV5YNkvjo^#2~=P$rgT=H(H9o!YWT%l7Mhf%%XF!l*9RfOGqgFxfFYE+Yv9L3!M` z*F*b-^PomCY&G4$z94#$G0OM zH=yeEC!+dOhfk~N!b(V}rU|$Z4b`pIxL%vm=JbCaQ+ey(gmme%QA4eNX;>;otO`Q{ z^k9=I(E1PwxGS+`l%2F^G9b;qG!<%tS_bOF}OY|bJO2N4ZVIfh59)I z{g|#*bn!&ibUg+AE?u_~+fCQE=z5Z_UbxH(zigx8rV?1viwxpBjM`xhOV3H8uG4~H z4WAQ8zlL1?c#ZZ<8-2Buj#snzK_zi4z568 z`1fkr!`XB`npNSlli|mDry8uqh z0SkV_qk={%?wlj?uL1cCkmuh5Cd|GTFu4dIH8vafD4FgVYq+{c#Naxg0jdyyDkiZ^ zX)~S~m?-f*&U$%^mW;5#Sps(>TAQy-4T#U1WqBsmFhVylz zINv9R^L-FCj_$#cK~M68p(;>K1J!g;U1q52;md=Cu7qC_G5%b{0#`s8Qs z?TF9%?JYZ|>VHc}pdHn+((2|X~ z24-kz_wo9Uxpoy#p`k1`cW#l!_>DOl6y5inc&$fnT$A8Io_}xnGK6LPX6qoh^kw5O zv4FXgmY2O(8h5V#R{F?q!;uHC3-BYxs#{*GO!y@dgxl)R-s^#l7QWQ%xf9L_Z}ZRI zuQK6FbLeVK_*}WH{Px~$!si4JTw%iJ;x?ScnLc7@#)e&OCcHYlOZeDyTc{6?T$`ZZ zDkcnHfbsn}(u{cGir>IrJTQ{hv6)h4~o388Nwb ze5Aq4)jj)9_c6x=Y(7|@BzhCY{noxwXTp}Ajf@ZKD}+2C_? za9!NS1T?>_r{+5*(3XXi>ss}HOki;i&c}iPOU3YO5gvJVhP~9-01w~CFE>r3`H>I4 z#s~kU5B^&pe6tV!kPrU25B{tVzQ+gO=YzlNgBL($qv_`eADlBkqtPGbgL5hAX!Ixh z;8T3?3gGH&6vIr7WsoM0=ttxKbsxOm2X8g}Ltg?@T&mxT`^`S|xA@>A2J1L-q=M&* zl&Nz^rYNU6e56brKB7)3DN`qqlxepjQ3sZ&5c)6^20K+fpQ;0^b4|+Bi6&)=|EZ@T z>XvKgH+D7PC=q)Gi#}Jw9tnc;N%Wa7-Rd-uD_gp+ZC0+Pj!{nv;fWvWpbt8(Y?(jb z1e<>?9rKzSao97@J(1^(Xx7agJ^El1hV1O<>1tI$>9TRRck2^Q%xNd{ad=Eta1+rr zCR|6?mDhAB3lZ6FARY;+31;X+HR6KKPFWemwZu zcn>!naeA|;S^QXmV+lkCj|e;>@KXhTg22Bfa4f{g@R=!a_VsMM3xasSMS0iRIs z+XXJ=e^cO+&-g+_hAUSO#|m8X`HH|LpBVy|@?0ozX%CkQTaS^YmRa2fA= z0+)7ZyG%@%jJL>irHIRTj}o{nN2du~+W!Rtm+~|UT*kXV;2fjdbfpE(zO%*O^ugcr z!4DgYz_^$$$>-BP_~(4^<9+b6eDEqC`~n|*fe+s6gD>{MzwLv6mBxpQdYFu#)#t?m zKT+V<3j7p-|5)IXe!IX+1^s@3PZan?G)i2Qhf|AIo_`fMgIe6~IY{~m0)Jo7OL=}q zW5q>!DUTZ98$67=HgCmH#ek}M%`PT?s%KwtUsah+~RctJAF z4}pi_XX&5w!S6K+Cm%`wg21Icyee?nZ+TDPlFyNP;IHJA_-Q`4JX=Q6b8$4UTsd#? z!B_j>n|$zBeDK3q>5mfc2|oBVAN)0ev*_4-*(Y#WKj*P<;$nJbyYLGi{C@;qj4+n} zBLbJ@bgvKo85VL}jQ1q`ET0a6A20A92wd99zY4rW&_6G5Dd%A5l=EbPOa2uC zm;CDlF8f!_0+)Oi2wdv_djgmF^%H?h`i%mY^g9GD3SbVL(zaa3(1TNdt_k8dI2FAtwmGnpW;GgrszvzRXIO*;cZDc#)u&cHSg#X+QG?F74-g z0+)9F6M;+m0f9^UM+7eI`DKC2`l^sGDouK2x=I8t<*yL9l;Ax;;Nk3QMQvQB{OL@K{a7q6=fy?xUxNRyfmLtjM zB!Pb!el}lb3tY$FOm-+Ifz$Kp-1TOU~&#aU3f9_an zLF+-%mkV6dpCfQ7=K_ICd2SH6q)!T*Ma1g)#{y@SVDY;J&LNA%2L#Th(&F0%PE)n` zK7q@2=a3IRE{wpqa@(m>!wMczelHO?oi^THAAE%m{_4h#RlKskas*D1 zEuUiqPL>uwUErS<_!R<|{o#H#61bRNHZ_*dw*_7<@Lvi%EbvEs@F5@EVP%RdSI^a) z)HAr0e+e5QgUfteD{w|-<$qYd%VWxP8DK2Fg8S>Q6>y#kl z0+;cgCvd9Prt4P%r_AN)uk{7XLg$v*g*K6umzpX-BP z<%4(m;ER0li`m%Yq8?;+gXgCAKXY&dQP*99k4*2q0+;E1MBp;LNe;qsQ4cIqR-T;# z4-1@bUZm&GUbT7JW%01dutdRd+^&CWzoPZZ?gqVlXmZCX;mjFXr-s92rDvX*{a<>< z8B@aHQ%lb%FAejWJNBlk_&ND`CPJ{!!zr5CCo2)Y7BqDqkxB(u!j8-tS>6-+j4aoC z+j~!cyw>+>o!g|hLU6Ig0X{sAMM817ieot4Wd%66-Zo&9xVwY?4iTI2s4b#ygpKk8 z%5ueN4DS-GmH`@1F9(aZErTC=S#B?fph*Xy% zF>&wtzULix@RfY`ohSRAFR0kzoU^0vz~T6h?I99(K+n zsy&DmZyJbA+(~7pVu$5T_ksy;d(YJhLX3c-UT{Myn%8r&r7Xx$rlR>hl@<;PxS(gE zg%1~SaN%b(EU?2MQ{n>%q6D9aWIqy1or4^BFgmy=Kj!U<;#2F1kDQa5&`&M)y_@gc zvJ?*37F9nhGAW+-FdWS$JE~Lh4e#SwsACVZrqwyP z$?Y3t_Fy;I4>vj!qeHmA8mrjYeI&we+533e{`AB8L)-+V3B>YtL@W07RHA%L@&?_e z9TVS;rsAK5(#9=M)#g<^apMVY(*qL^_6?Zq+lK7B?Zyx&PeQIKJwUn$1 za^sE|jy?*8YTYoP)fN$xQJy-X=5mdE7{49K-zVLgD1^YKbRPWh1HX5m{DmxTS#%@#5 zhcf$)$5WHkp+cyFUU5@X@-z$Y74>C5ghI46o<7?S(qbo9wTT6H&BjOA`a~;yyL?{L zgtfzxH8q>8MLJniGsy};dR?bwlZE|?UO|FF<8V3@&cVtf33mw2L=vlLk4(Dh2btZ$ z`Zm{Lcd<(^i`GXki(VdWNKA zqwa(gn`^|lGd=RBSfvn|)6Qd~o_CW|BJhIZw!j-_|L7dW7s{)^#oHA_KJz@Mm!pSp zOj=0U4wRqO)v#SkhR6ZY-rUQbxH<6 zgv4n(;Wj6qSb8K58#Hi_9n>HtJG7on)KE8UZ-*+{I%$U(=}zn?QimM}>am44_FyW7 znZE~@-Pl;E zhk+b~?k`svunWZ4@`2kv)bl7rWum|iIJa=k`#$Hw0iXlUx4)~N>UO{nJ37}vvU*1J zXRxee&4I`+!`8G7gJ6wwe!!o*jvqu|)S4wvTw8|!*gd9>ho5bAZaEh0;Cjiq<#TXh zyD03$Q?eyn#cXO_2JdIDyIjqrXA+^5v#hsM?{!=LS9zx-Q`@jB>5y-b4snA&;wwxa#3%cx z$5rxg&BHo!ll(!Ee6`2Y#@%`Jx)eKa{2n`RJdX+(n{PbFN*LPO9BtTy8&86YM_eU4 z>;pxqoav8SHBcwmA{zhETU6!#R0|27lpeqD`un3?{4dNH3^C5C}&rSE{ zSGmzmighF~mtu^US^6l1r)L}UXjEjh+psrEF)^L9L=`SUgH|{i)h}w+bhrrh>I@^5RPDvletuz7u5JU#tzb zQr)x>o*P{+7@3v|{)il+-cxGJwjgN+0GK_vf{|$72iSUrGy!Mn&p(CG$?HR9_q(a< z+LpP=!;^tn#qOJmk{2Do(OQq^?cfu7L3!@YTN!1nBE4`I_j+Nxka3KgoYtnoIJch; zX30zDBHS~1I}y%V`XwOIR1LD$^p7giPKpa<-KIe%FrJ!FTRr%EusZKxHTYB|zdknx zX5Q}D#DP1a?|kTO{eRedANV?}D&IS40%V|*bE?tFXw@^`qXuO9rdVH7?PyZc_5@Rn zW0ljH2&oBJoC2{;H8NF#O(2IfrTX^H)N5yM?o8i1eZ76PYR7@OjHXZ^IH&L0Ku zU?U(^0W1Idet&E4{hWPHauAf6ckbtN&*yWVXFqGNz4qGw*Is+?wa1sAGIB@Zu2jbj z2anf~SM!PbTKK?DU-|?nPheyWvwf-U*VbW1&KagFJTbm-+|u`Xm;DEyKxY2F#Pfv* zfBK5{0+LQULR7Z;E;2WF@W-`Ve{xFOR~rek_~Fy<>SP3*#NEctnAMZIYg9!>Hr~c7 z*UJSy&IHnfFWHepROr}xOKWHKgKbM6Ol{A?MB%8|;{R0p(qo(N?ihPlU2eSnVp*%4 zPbqz3EIrm=yY#NM)XvGJze?>yq#8JB#qi|7mxc!Gsy6?>9b;`+W}L~5W7=3*eDL0r zf3cF<*K&;7H@{Bp^acUux9Hj4l2+$(vo0;&(vqP^JJ&>5`k5)A9u!2gU|lz2x3#z{ zkcHpbay+0-3eEbtBH9rJGBldMNkVjn=bF?}L^_%vHNv=BRJa|sdnz|RzfsNSbUi*w zCUiEcJ8@?BNX#Gc(PyU9*O_OAY-()AgDF5-Ev~4PcOCg##WzL}4cf}n zx+@xT*DrJbBQ^3?qqe2J=w#~fwd_dD&}wMag!cD=)~$~j|YFNoo+{CRy#Hg^9n>S zc)(dHrRM5u*epf*IxN68Gae*YxZYvG9=qU}4DvRB@w?s(7YS>hnFPWUOHztKkQN`+ z9y7`)tZsjuB?;=bP9HO_pkhdiC)aKP$Fd~PPA}NubY`G**`eh}l~K=AXY(_HK++Pd z2iBMfsXBtl21N@>&2W~g0GCGE4~jDwX(3eSu+^lxtm=WWG@c3Xm?ulSsE-l#y^7kU zSrNO82ff=&(e56Hh{oucgV#2Q9kqEq){(*a%UF7@_2*j`bE$?A?t$3d__--bpb_dL zH1;|guSsZhS;7Wm=AePdM=h?S7z2%@wkB0D>D-heDkrHE6PO289EgfPbeTcEyCMms zTmlVMP28oQtS`&2$mNb|nALDF?&_#HJW zq9B@6kS9!$;Dn>I#u|=IWnrqBa8z(YZZmVOd>%70#|)d%AyCM}Yi_JID+{OUS9P0* zI$H?|zJI0RbOAveK!|ojYAUZn#a80Ic2f#^0O@fSwlg)%=-P2avO`FzTT~bam44hH zd+|bOwf2S{S%@?jKbEaXt)*k|4THKaT$?1NH>AB3L;tqh#2$)ZIGc7+(%kx8lu13u z;X;#w^pdH-EA~YVk+2!ZQqrWH`D;tF6NK{Z>Em$Y4D6a?e_$vFWG?j#I zXv#4XhF}K0qYmN}1V(4l(K+O~${#Z5URN%Zm-D_Qc(+T|fe&>v<~Rbv%xiU-#d9iM zT`Z4bX=6hp6A4dBm(o&cgK@nTpp~?emz1)K-7<}$&C*=bYtrdr3A*HxoyUwkoqYNi zz8K51pjgfnV)wJ>t#zUOMzUCYo+Sr;RZuQf%*Lq5OLCGTzoD4blxRr>B*ES33_$Jy zgCCG(>~W2iwz@l46{vw$mkrGo6GM;Vzg_hM#&(3_+u`U$k-G973J4P*%&b%!-l6#; zGrO=p^K?h=uXBZeWiF|1T=Nh$?i}-#v|G2~+J;8%Ep1vc_Aw`}_$v}v^7uJavDS~) zwF$K^_XxCek2iCU^ocup!h%z^Nqbbdr=!>DC3E9j!u2?E1q+hn%kCy#*AM#`xFX@Z z#J~jRGr93)XXg(7q{ezx@7LE%&B{)!7&*aYtv0p&l|b9adrq{CucAH= zNF|KKfiAec@R)8i%oUinG6+FnAtt92fk^9>Vy}9<9zHDv#(#Hya(S#LL(~1^qz{<9z-uaEf+dFfG?;_*Y<+k3K zFPy!vV{8R;th4v?#>W2H*MituID3*;Q{n7GK(T{=_7sfr`EApL^L^N>zt2W%#+)6< zQOkk$8Wy2)Z|TlvQX_rRTquDuHrkq_mnSg`Q4sn0Z*J^kRoorA^(Lov$Su8l^FvV? zP*BY-S8@4eH(Vb1ibJXuB?E%j0$+C@9z|8KktmwWyzU8nXX%T}ksbxkWClI)dezE|?h#Rwa*_bDg4PhZrE^bq0&)zJdwJpCmu;2xd*3k0Jq$v2 zYXSJdmzZ9H+IeP{i{iVv%;LD~u+R_@6=IRUMjP|UOhYAE+MtTx{Zw~m%9J-F?Y|NW z;&9u0rMTm^E#1mxrGR$Jl!V9uT@XO;iZIf{_`}xB6EtgX z<)uB`D2Epum<{Ql2H@(#F=@1EQfRavX*+S3spbNYwp8NEv%tE7xrlUnj#%J&8lsk! zPS_I?Dmq04J+jP#KmP~`?B@SUH{HP6`PkJ(kYYSzw$K8=MD9EjAhd6{r>^QpuLX3H zuj$m*_wzLezm8WEoyb^MRgKM7t>cJw!^qc~L1a3n=bKLuvx$MxvI3%EyUbXNj*e_} z3d!G!vP~6DlPsZgix&np&S%M-DZvHI8a4dAztE$DR@{d&{6puU9as{vyQ zC>QpjkhFYOCT@BY{$vhzksrqQFfdhQGHT-+t(Fsv8+2(Fjj53|3y7hA0I4=hMO!^LAow{)a{r?pY9fm#K_cO&i89mbNs0Sv^ z$4MNndb&$FhJdGr|Acla%eiW-C2RcZj=9*ka(GJ0z1OEtxX00&hc2zi6s$ZR)B&IK@P* z(A=D=%vLHFu|B|DusQYDlc_8JzRu8mH@4_t>dGq&LMPNrzE6^_krS!yx1!1F=sko% z;+;VB!@145C|IWkMM6x&Fg`oH56g?vE)Qo90l-@!FxDvwxiWLIKGx3Wi^G{k$y%b73u-L{|~;3 zE(2ffg?qU?KzU95W?4{eJ{_VW=Kaa0dljX?h_t5{?c- zkIN(AL4E3zsjF_QwYFqB2(~EHks1^__#IANbv}N$kP0@Qwv}A~)v1wAKyxxnI>Obc z#&S~jM$idhzeDLzu{1DJ_C|fZ&z;|aA_YT1C1aSc)eJ-wiwgHVG*4t0LsxQ*F{An4 zu(7}mN(@gL@4uefIt2`_He^-gELrBYMoLA(7g-#C6?ox0Q4%hl#F%O;h{TzDVIC|+1z39~>(F=6Qb3JlKx(J+z(V4Bs zPfd;dEdyle-NP6Fwofv$rx^d5gzdqoY?D5K3W}WSdU}FUNy2u691B8&u4iFHQfH{{ zPNR;i%cAD)9-|{Xcz+FhRZdUF4Bz?q?vOn(j!=zL?`ymnN(gygA}%agaV$};mW@Du z*zrBF=#4_}=-2GB$OC@5>6&mPXR@hp0yK5t>Xj`9rcyUh`80exA(C9MLgHJ)8xpc3 zZfvSmyX32zTHvZacXRB9Ty_4>{HVc*qVXlQ{d)rMw#)-b!%H=JILVvqJ~Z~EMn1wV z9@+s!WbQ(GN8V>jXTe`vz(6ttzRN~N0#`C&!%cj*g-XbyU2n-vKK3R)R~$NwyJWIt z>&NLnzb;Juq+6?-xjNpzr9rWgUUTLFhI;Tz2j4Kn&b*9io?A~%T0>b2AdCAn-=I$C zc&;%eT3DgTt736?$rDQ4r+*Q*f;sBB^=eMkh#K=$>Z-jas5_sTr;!RTWIZyp&gaHY z9^U9X?UDyAw7Fp*HL@8+CHcd+HSlW+vFV{8cHCnQNwzA3o*KB%o<~4n9&b(s|Q~uP1 z;!lGje3~s-h6A%{8{YDZ|9t8>*xO2N`5$@P zUp9Ph)xDPu2l!Y2^LJi*^t43_-Wu){a3Qm;;SPdu_ke@Hx;42g;Cb5o?}UIq23%F6 zEw4p;=KX9=kM`3#fWuUWF|g7YSXp1!1_MgZ zLyDZ=TkClg>CKftUG>w|H7~7X;}s>*KGYyF9Kl@e-){_U%?g%PNgzhLUJ;@{a7_z&R&?mKY^_M!mr?=By{s|+6U zKfK?E|6JiGeE6ZV@FD-h`-47wa~WPn8OyJnufTtJZvrQ{Er5TV<-^A?xyx}!JxpC% z{A=;yuPyTkdf*LN9< zhCS-fJmpQEM%V%A^?M;~&R=YV zPpMM+;@?L5m~9jOBOV^($}M?iF5C527(CP(`>@BA9$xM<=Hcc1xOPxi73zJU^j97~ z69E3b+ryj7;D6`gO=a-C9`37c;`=!dZ%rW1all@)ZRy)u5v`=B41dzT>t@-IzuCjP z%J6UV@LU=E4iC?l!4G?Qe;NE94<9Up>vJv}_4ff!&w=}31^A;C;7?V6pG3o+i++6t z_=_vRUseJB>I(4JRe(2FfWNT<{H+z>T@~OPD!?~YfPbO_{F(~zJ-}si{>ZCbp_!a2 zXW8WL;>{Zd&R>7QrcD>EyQHdj^ZPIAJ^!Lh1}@mxU)9^c`TX8>8+v8g{Gtz6UAXy@ z-t#}Oab4e{i#Bashvc{6qDwB=biulfz3b1v?9xjwcHm~Dyh}C>^l!Yhe~}r6w}+X9 zFPe-u%kO3cKC&+FOv-z!@Mc1Ov7tE2@!n3nS()eB9$Az(%4ZqXvs?s5_pFv+F<$1| z6XWnQ>Mp>#L2oVIY{Tmcx~feBq;O-^h5Z{zd9dgX>LnX5KC5cehc*sWoqs;r)Ytol zs{VoX8!uSbcmBG8fsN$TMFXV}Y}EH%FtDx{33}bY1yvWX+pyu%-UOwWpg;qS4_WO~BS9=QhsweshF4-W!|2l$8 zCJOLZd*Pv+&KVIr#*b~9$MkQD@Lw0vZ}-BD=&R2S@qQqJzc_;TNAS}l_?IGhOh>n3 z>sLqNANhkcSmrEfFa%=dHAWoyb~8^(aYeH zv-JB}MCXtB4s?!3aEW*Uexa8?l&+Yckq92A>+d6YLqunP1ds9mIfCm*bcpxv2p;1< z8Np-vb*B)RpZJXNUlhS(`e#LOO~pdIXGiduPHcNUj`xxZ_=6EVj`wR3{B=>hKaSvW zyw!Ce@l(3uc`UVFZuy`y+Tv|C13s#{YZ-kLh0@!DIZJ zBX~?dw#gpbXju7smOYj~v5kfosX_27ueYlscpUFvMDUo;*cN$=|A`1c=JPdPE?2zG z{6oEZ%vMtzKXJcwiY8$Egdf8fRe-;{0({&HKcb_#dx-bT72xyK$?_}5UsM6UwF3Mf zE5M(r0RLlk`g6qF6~W_t7>nSG2^Z4!e@E~b|C3^~U{A(59HL7HD#QT;C@GTYK|5O29=k-hF={lzZ{E7%3_lFNf@J9Fz`TsRFHh!WX z!|(R9f>k&Mr^I!yz<9#fG$M`2A zcpUF3k?m0xX~>6*B6!Tta0HL(|6K%+`Pmo2WBe~i@R*;kMDV!ZxF>?gbe@Xfal9w{ zd8W!S#!pA^INtX~a8-?v50^%8WnqB-rwkwbL_hAAR%Qg94ZkpB;92-rR)Bx00{r$0 z@JA}ZU;Rf__A9T?`3N58Lr(-()`s+cID#un0{r63k}J$8`Qn1dr+LiQsYn^P>nJ zr|Y2z9>@ElCQGiRSI&^1?Gao-1Nv&I zwhW%~9z7AASkC-l1drv+k45k}-cOX#5Aj}A1`qLWi|EAh{#68zgEW5H7DvtsiO*d-KUKu{8XDrY!wIRK3c97CDS}y$UVr!v`l$b-H&cY4FsQj5> z;&XUwJaYj#!i>&MW%w%vXWWhg?Tt zDmSj<&;T1%01GLyc7OJpD$_)jX;Q;{QsLJ;#g9xxCyCE<r2{}8N@ZC*3>y!FMW?(oF+6|E92p5%Bv=^!*UEU2UfiY2EwP|+uB&25y@G>tKc$X zCM#wr4-94oW|qNBPRtCeJ5_u0>9@4gR*PvLB}FCT&g8rqPitPbsJb7N@fv>-_>aOZnri-oQqbi5-VS7KisgBYK!{6C91RS*8gRctF2aUoYgUt3Oeu7n6zN^#lub}_zAZy?8kDBA(&Was#;(3Qzm*L>Bht=;~z_WI1bS-2H!34i@M^Z@=Br)qzE-j;P0ZjIpl zq$|L``GfcfzCqvov?e0>#UAc@UbjC`@C!VAxA6FB?k4#89)3XI{L10CIq)p_eHGwO zdAQa}M8DtDZ%~KH?=5-&|FDN!M+&IL!#8<&yT{Ksc;?SNTsC|3%jiKq1^B=maLwbw z-u(|b{M<$LUG4SxmGwO<>({r}_phuUTwXs^{Q;=Ev|sP(^f>&MzWUbo`dmkS*UI|* z^7@|l3sL!gpU2ahlhShu9xBJDJp61AKexVRWqs?qg#JQ5nfr6+pX)Zt?SY>Qg($w; zJ>CZ$UhDhnb1MVu?e*(d*7vv954OWPAr$X`$Jf43@$;t^Z@0xO|DE1{mGf^ox4v~{ zeXhN}3)V8g4tW2(^Dl`{Oo_%}k5j3R%I<@HzARfqS*B|o{H=Pm*SCHYddfqc4${x2 z#_+51*+<#wv*Q)w_M}5z#`t=?4{j2H+J4pDd`ceKnsxPuMs@@LzaL<;v z!kc&wcq#*(tJKf)k>6b3nZ+c~_Nz5le( zD3VL+^Q$T(wxfPm^-7MDba2F^gCiy@IbyPsBPM^TOi)}~Jgt?ER?B(yt-Y2(`hMPL zGs+HQ{5zev$iS+a(aHSdU#tK4t5aj>?0udu$J=K^31X99L)+`i=`?&54_RlwM*o&7 zRB(^xq16Afc!NiIT&g>w`P+uqK7zT@Unzd!#=llR1Z$3gxI=iq^&#;M;lo?)JGc`h z96zm){&L*&e0bj-BqWac@Vm+~EaZPcT#KU&#k{674x=Y3KZOn9L-{QLR-X3?IqLOG z>-7=+%K6uKIqyObdq8%b;sVd-^?Urw;}>kM_^13HgYGioRIOAcem-@Y$|6KT|SAeS>h)zFm{Y-}9t+8@0 zI<3HkKfzl+ldX6w{<-kGfZN_^-uj*4UR8PCbK$Ff2tVx$KI`#6PyzoEkKbB`uQL*J z(f_!|?=Qo@ssjFYk3Ug{|JN1p|JLJAdwi4ifc--S{LguOSp(G1WI*26@>f1|d3cLF z`5ojxSGuNv3x9{l&+x6^9sK9Q{~mDR?=Qo@uLAx<9)G$F|M3d=&v^V}9^Yh4V5g8T zbMd3I2jZvScMd(^>h}i~@MU>T_|qQWWKHm8;ASrR%^p9<8)qB!s;W0vz+dk1dwgeW zauZ?RUIG74J^rM}|DdNYnS3sO`aFKKU*j;@lrWc6!2htv@AvqtJpG{x__Ft>{F(Il zYH#|t^Gg-*We>sXo5z2vW2frI3ix01_{Thc z=r``DfG_iXqMtd%5iWT8KdylPpvT|u@k9A)jc2a>dD7#j{eTwmYpLIJ;hzRv{DeKg zfuDv7_%HYPHfX|o==ZV}@Xz%4&A;ao8p`X974VmN{2`A&?ek}41^jn-{4Nbl{8qYG z)q5-8bKt`k!FsFuHmtk2cfEg|ZwEo#$&d>!?&Um)9TRb2I~LNXr+sg-Z+kj_og?Yv z;lAvMh#eHM?+e|L5j~y%x=oueuIkg%`P=uV__*wcJ2>RWg5 zy1^>2R2?<)n~6FWRrtz3Sl+? z_`n4llJ89)?h}Al!1$1;eBc6J4sz+n4}552RiC{agu~?P&4HdbB6<9w@mVfmBlXR=aQ6A%5M;F385ya%^_g8x4M0C#5<9b7a6 z{8rrh312dDfLGJ*^%Gp%lLP$q5j@_5q&wgA6TY_n2K=!IF4-->|7Qe$c?1u-648nG z3@jIbUpf5p2%f<|(Eo=B{^|&RO9WS66Yzf$!CxD}^M0I_cT)tvC4#Gu4s;%m;F|LV z_)0%MicY-N|G5a>9O1vhk6*%Q4lJ zbA~>=;o%+N z6COV5-vNHae@~Rb!~EgeGI*o7q^tb zgIu_$3?Aga_^hSk4RYR7W%xm!Yl!+c;Rm^GNf|uIXG3N1Act+dVADl?o7Zg!yN!~) zMsFzIwCZkMb@#3QpKn;an5$U%J@>svqyGql{?}^nR>s8os*?Mlrl?S3tD47B*wwN= z7khoH`U^K;n=>n~t9BAH_4%uI@EXxVuCqm-+WsiR@%W#fIC!KcS8xW-xXKArWT8do zw%))so=0i`WqhUO>cmgUCTJbs*mmE@mL+a}+G*BIS-(Rqp#qOM-l| zM;4Nh`%92n|4cA%v-&V>7{XLv8d4*&LowNJf;{qYCs``A)b{O2hpJ2yASuIDJv+z# zye>Dk$}X;{<*JNTjpXh^9A~Fxe^7oIAPs_GLY-CA&cb2p^0M5)qi#(Vno@pO+h(k-D+Y+@Y=MT8IU!s|GF> ziQc-c$7{Z-eg*7vM0M>E!eBN$k6CA~mg0l!@bm$31+3e=!~&+jst!w`?2?L-Q0!$& zLLF4773rlIxBVdjxF+@H({C6*=NYyEWNx3~iVb!Yj@SO4HbbR`N5ROk`Jpa%@PQhx zqvfuW6iR}+BrVIS^nt@Qw`kp(eOCH*ANCvoDC0mj@LGKR{w1A~L#^io_>DM;i0_n!;Ti(JZoPjqw zK6+2+W`|ntn_Ro_xJ8Yt;W&xXjg)zASXwJ7zDlWF^EB-wSLlZQ0^2rPl{>ZU|yez&WZAPi@l1=J-g-giOm4@7PLJ(%UMUV8WC*X6jZm^;68!EGaEN*`Zc zc&LM$@Cr9R_zt%>$wKcE7b#s_0#8`)ZIb0)HV;^>2E+CdV0#pAneKM>PZd$^4_rha z&+?V7{Q(wEZ@z5b=Fbww)tLzAaxT((aCPB+-C)}|^01UMRjGI0*;zP})Yj?LsvEe3 zI(5y3^hR8!3(XZ(ToB!+tCyAfv`npAZCZP>;^&j+$(vtisC8{$m?wv^>#$a;UfMng zUMuUk348d{kfIuGKGA0V+xTVA6b|102v<>$JlvLg$1yIWe%sQU2RgVWu4eJ%;>TBy z*Un!(jwSNW>YrN;n78$2RgR`wRfU7xFfE6xDhJ6CuAQMmJhSmzE2_WSw)9&Af4sW) z)^q$Ngt`y6{p(d7;|;Z))yK%BQ!O>AwaR_m3+O5~$VNSvOi*aaZ55buzEQozgmOWK z%cH%4W6QfzU#@NBy1P!L{mgrov1!lUHlgnv5Db`xuLSUcG8C*x?CjGl{FiwWnLn z=FZrTn$=BY&@`l7?Vt9904#*PFHBhAwKUlDe0?jG&~t3M?vo>6+qLlVlSw+2Ha+VN+KY2<3P!4{JH+*$ymQc70*U`0~-GuKd`u zkZ@T$Cza7sDzBjtsxfn~Tw)YXByAx1#sEM!Lb{U{`0;G|9R7R-i z)FVSwNdoP*Ev=X?H{f0a5^%rs<@yeTU#rh{b%PQ!+%;IE4yD_!B3+hEZGSxj)Z*`V z7PzXAv|;nMv%sB0I6DeISd9^Ss;EYKRn;MbKBF#kkda!laRhGHep?S2 zF29%muhpg8A;|qa*u&NXP`C1t8F}go!v&o`4H*-=IXPuLdJDVWz%X0l3*5DSd0A!u zHr4Ge`Lts(m&=2`2JAcfX7AilQlUV+sy?g%wR;mdj`<$pu#>luqqt- z8Qj8zCdE&x1i+oT=0nseaX@|XrAeWwC_4+sRu`VZtKpz-L^$172r)rq4O}yi9c3zp z0&_EP!%YHASWN*X1LTk(s$SfpEtZrJ%RYB`OwnZWBbI%V(GvMEN5AOgKY@qf$8civ z%98+d^otX`UFJ0FMJ&0(6S*2UC&-PhB=^K~uKEVN^Yg^QJde=`a~^)G;HP@{aghdJ z=tb;fib%8?XZwje^Gnb6ONUIUo9$N>{4%rsjyRe~EY(z%Jc)HTAdJ6@QG}arxgtY@ zt-y6gWM? z3}7O0bGbTv1Xwzi+IB8xpxI$K#b=4pYA5%kuAYYwni|m@p9*=ib-~4v$U5SyKEiGC zsjEM2i2oN)hiHQrz3$A2c|QS#)X1Zr&P+G9xv_Jdx>^>NQX>nKV6NMxkO=unLOMP= z#x75y9@%!ESz9RX4)^sY@txSDcyIgcR zO4(A<#hm=z%GbJSl``e~5_j3*FttEc+TT)avXQStv8)mhBq>=HNzY89kPZ10ysdt? zz4vqgk*pICqVP*$;CBhX|96c)3NOvWPWqG;i*KEIfVaNCbL_;4#V2OAaWR1AOeBrl z+!?jGv4}>XE1~^8j~bdxY~<-$NYl(e)BdBgUM2b~ZOBA_)m~I!6(mTa!WzzY%P&@7 zt?(Kw^@I3NtNYu2YliI@PK#w7Dq5Nx9{M?rR+s<6<52Lh0K=wZC`@ykO>Vq)gj$NU zKActesQy&c+Q)_z*@}_L&3`twe-dX$;o!*QA79-Gf(S2uTvN##I%?k4!Xzj+ei;{} zz%~?aS>9srtv@-QJN*f!0b38Y9-OH`?bBJ~Vs2adg-K8@|El_Q#Rg4rShK1Y&c_>C zQDoHNrF?+F9U~{W*1NS9<=D5ug3~-6sqK%mz{OE9X{P||r_@29_NF>Ma^qdcm8`>f zO0jlC@9{2FB|Gh2N!-k^ciQcXxHEDecQ`0dcG|t0z?+%GapYszF3f~c#Vv1L1vzJY zjUGWN>=0j*b6b4P&Ta9vIJd>u>fA|uUHT>>sl3%yt8ghZZuQ$G{iImh??Lm5pdk-Z zMtH1U9;D3mpdB7`QW3P*gHA4jCOqhrB4`v)s3T%ZIiDLF(l9bMKRt45YNYL@K*no7 zC551s?KUf0TF)flwDMLmgRyv?%%$8ia`r+?-)2PzvNIT|AIYB#SVYpND2=UQa zhfPr;wp?HzMm2yi(yF;C0P1eLXU)PiAV+XwMD!HamBieVU|31gHb zn&(^?wE#`1VXUNtO9kE)oFU2kOJEnN&# zOpj_DVa~KXP&FjXzHEO8Q&$Y58*OUSV_Q{*Ba_BO6~3wMvW=ydc^nV@3VH_H-7WlyX?ym=<|I+^FMu!fTNS9d~D12}yfB^50oLmy&D< zpG4a_wl6D88`FezuSXhgsdhyL?V)C-|Ufv zB;<)QNVimvgP`zx9QdvEA9xCGHPJP^h zOLE@AmX5KNrao>2k^#~n2t5+hp;&!0OMgs3Rni|fJf#ML6ha>x^e)WC&>#2snzWub z(}=aTk6O*7%vt*oOzLCvT+Sr?G2txee5|U20lt0dFE9Tmsim1x*mXlrbKL6n)7xwg z3{O)`37p2bG0^6~Zbe-&^NvTpuYxpv@r{=q*!)G9IEm~x4P?+MUw*jopst|Gn5KB- z`>A(+E5`~(SlLK)#ph^c!*s>9*`lsEm0nHKoDz5iE|x8ba>0tK&8DhGK$Wr>EMz#3 zC25XNVbv|YAyE`>Ix$O8{QbEU#T+60{VG-*=66=lPz%!3g6FI#ZeRMnS&HJ?+j8S* z+B>U{!sZ-`VpG6w$!4S|-s1#?peI%bKp>>n9W?8pA4cQ<$YiN@c*EVo>(f)ZB`Jts z&nXNkh`*#a3gRdAMnU{7RPtnyaLFM81`YA_md}jbVfs92h%>3}>ktN@`U(}Ot2>3C zOJCe9Jse8F)amE|^e%l*)LdL}k1@b)MH2bJti$4Tt}!?9+tf zHm9}IL0L>xrP^Wuv(?2+^~&_c#v&1G8c&l+UHSW{;e8uo66Kmx$cDgxrB?b1xOm*9 z(&>#`D+E?%l?66^G2tIh!rzp-@<`nrk^5A;JSDv%X30-@PhEM{vji+r5j*AZ<)$2V z@&FYDB_92dBnfU^*H~|@o-gJJe3{}{^~-c-EQ$h?_H1|duy^grPRiwVB7QE$?XDp0 z%O3Ga2eLD9d(PppDXOk?jR*n47aL26z*0v91@6XIK^ACtP+bn!H~WS*XqI} z9fdoY+P6FD%|rm;(#PpOGBx@~^vrS zVag$88%{3WC6*?|AkC~8%fW_*bJMOuO-keM@NdvMNkf)0zT+r~^9swZrTZ)oRQsWV zekrebrb&iK0dYFhZWW{PgEmR=<$pww+W|UGy1BPCf2Z)3W6}cSRH9Se56Gzuo6#Yx$zI6DhR!2~OJVd|x|stF{xXLaFe zxiD+s)x@f7rW6*qTX!HgNb^eWua`ntwMyPM@MySE5Y;_FZW82XK~%lUub4xLY&jM*#rppC|614u2uy#o&baF03s4BX~nab5j!C4Z^ zA|y=&BGr)7q)g?zMU|f@TSKlsYn1J(ozTb!?CZk}&7>R(4INiloYm4kF=<+cqurZ@ zA=L|n*2HoTl+*yXWc!mO?DO+E0@~cHJ$0^IIRy`ioF}mxxh4zWqB{5(&C4SBGis^LIKfs#cMX7`<)e=hyK?7H* zrbl3ki@BLOsyPNiq?L$VuDDDnsY#Tk@sSjVOOgd~b5%Bq(wOQF!3B4oBaWg5(Q)ZY>c6RLB4{~JR4(Iz-TdyuGttn0tR`O z-)Z?U8)>gY+UawLxCUn$b@=dsg#0T zD6wzm7gA>@wu9!vpZV2eqBR6*!Y{fhb@i8xo^}7y1)01!^D5Roc9PBE7C-?BjX48eYwF?R;TsWMs)0{l1oe zzZ05?f~YHNp^YG7maW``+*w`o5y_xO_-o|`T5V%5omuy&l4$EHmHE;7LT2^Y$_o5+ zj4h)+d4B8$(+hX7e(?g)dpRpK7w)*OpNZb{S)UmKKVF~NqVH&ZW>DYJ`pkg7qxBhG z*b%SKjOx2&edYy9-&?diP5n97Z36X$T9o1oGl`$gBWB*nJ-N>Ze=*Aw&juO&=w?Al#=jhsjg7ue0j-l$yf&;%e> zmu*Gi?X}iRj5iFSJMJ2LcNa>7+FZ@qP3_~gcY?=4N3MrBrio+uczfN6Pq+Q**IxHb zFQ)Yd?aS@Gqb>FM$=vuxRyyWCrf?{w$7`9ZrMJ|jd3M*;(FnhZM3G*0jPDI~jWnOB z3s@x`ihe>E?KE!95 z?>#nRL-PpX=1=orLcj>$DxpEs2ZU)Qpsx;T+wIb|u+5sDXt3R5LW1HtEFSvY)|2i! z7Oc;1=^XEC!6w<7mb%XIfflR;ZfMEm#=BcuNK%U?M_H6r8(M6NcSh&?OuzB^4il54lkOcnl3?(>L(rSdCypoF)Xs2r1k6{%)M2pt+%Im7 zDZPH&h}*`D8=YZyiXaK8ElB-P#7ZGA1Zdp5-Es+^nkYD~6vg*NXSa({8;Zp71hfQp zWMEQcU~9<1p*yIc-?TI)k@_sQX@z{Wp}}pd*ycj|6u`R+qnXU0D0|Tm*S@gsBc9sBp#K?#l%MLX~Wg!u<3Z5{s$IC2mJO#%Js*J3atnTuW5wR#fVgWDkv&8fZ zep^^7e@skB37RlS;g0%DaCv5VIpJgZzURs~Br-1qkcheR#!I5=2HI`97cpXUM(dRE z4b9u8A&3SHIge0bRgP2+o~}x)k$6hW$`G>?*Ohue1WBE~EMz)m(hSheFhd6c2{cdV zW+j2U`J7F&o1TIe;tcgm-LuOkk`wx(^qOp!<|CT?`P#qDcR5AYl>gFfxOvTFLPs?| z5zk46YIfiOb|OxsHgUbtL=@WX69bKe`y~4;rN^e)41tToO%p;1yM$PvoKKsH^+fJs zRLw#m81S9&YWQ6~4L04N`Y1>6s1Q-on%8KSN9HIvi6CEyg5aLLC@#$k81B^XN$@^y z^fliAe}S>kLb31k8N7h`2T3R65J$RjyUmV#O4*bDIz8NiKQI*f3#c>-@KOV`8bI6q z7Q9DEV2S|5*HM6`EwcWQBL!~hiD6vBa5W>W-Wt|5H)@q5hof#OJG@7KXuGqH)BTm~ z&Qdc){h}8d8`o&_db#0;I@>z4%?To}Akij>tPhS%aP$O6-Z|zFO>zOHRgR#Oo@s_Q z#!Zv?M9Kb*qj1mujlzv61gGSV_iF&#E!Zg|lQ<HJ92#Q<-2freBs}SOthh+useI3P(eA& zn*vZ=rmw7VR45`bK5_>rnnX&}61A~L?X}pQ-Gdvi{k&4!57#N0T<>n<(e^!bjyKpo z3wF=uYS6%f*D=2Qc*nu}YC9R8ckr#))g*wYa|eISp{Zf3r+l*&!mSSD&d9Acjhlr> zHl#auv)pOtwygqq>f9Dj z&bcj~F5EuHkP~lG)~@cIe8>0_b9RicG^%+Ep~X{obO^XnUGLx)TfcJ~)j{XB*oK_D zMPWyA=f+kvb&jtmhmLj@9_DC7N8!-e3N2`lu0*hHA6wlGL#kA2>pSf2M&qK?dJ7o4 z@L~5vZtOjM63x^Zxa|`ic#i6eEfln&bOTGlURXjY-6G^PVI@nknLWMCyj2b$uf2^< z=$2osOr>k-N_IV0DaFymQ}%qq^^?95Aw z_mU1LD<>uCGeis-hdjxgV*DkUVby{HcDUs+hh_)?Rn)M*b0c9axx!UQr81e}DcGH^^#&>?9`YDjy~ zw7cN_5Gj`jl@7p`bdz;Wnxbh8IS=-l7}v?cIX^%0iS=6SJjsnj-`No*?WxGVn=U%x zeO4^p+Bi;~Ru`97H!ZjKsKyC@srWH74e@dlNY#6mV$s0Z({aNXZh z&$u49XlWcHjI~W#(4-|YZ%h~o>(B=cNu>#~mw`S_gwmdGP&p8IkO*H!gf_Tn2y}QJ zr^|4ZCBhL?&@jJ!UUttpn_6>M+@wCQ zW9i+gksKJAxag{zx*?VcaBjh+!dp zh8}1(P*X7gYGG3gSz%j0M_Hw}T0uUpy_4?a5&ACLVw0SAXjEYiiU#CWrp{W?CCJGD z>ib9Xk z3hn*Mbz0>>v-QGTnwyD< zZD?*vI0DJfu%LKMVGz$%AjF7V#IOa!)YoQ>1bL47YRk8suhLA+jaG;WQ&xs+(d;qk z`_Y8FTW=}S*iO;(=oKdL`2kC47;wev5Hv(l*`n^>B^i}F-(mWgUqcd96gGJKHF)2C zX=bhXIjIgu)Qw~ymMn;oGT(e~xor#@esr`f=od)pVS=bL!FjK^y2D7QaNv1V$;IWT z{RDKGXr-vST?MvvDhX5SVU4K?=WBz4`s0A^hi8k~s)ey**v6+CskqUxFgoA8d|Gbl z;Y0>{FQT*2iL3~8r6Lr($MR-28zp(8gcyp&VUe@($r6)OxXjx!=NDFLcX$8ouG6^5i2%G+{Altz_Bs%k_< zQ%FXdP(L9e%}8-vZUQgJ2`!M8MtAOUdD2KjV6bFdCd(nvMbHp?U~lfB*_uX>+o0sf zWts~41tkhe0$9SzoTIZ(uLeNN1oct`A$Y3=Z?xcG!U0z#8_uR z5YUWMr|E-nqtDP- zd&K#CqjSuS4DjqUG0ldfPTd3Y2GH&vubSyGfTkBgHA9M_+b@XpGBkCU`l2ET-OvK{ ztU7H@J2*z{Xk{kof2FR`TUwgEyyu7Im{$6FX%(sum>PgJnQ6XKBcEk~Hh0CLqkNQT za7yn|D%GE$?UDVKR}uM1L|s*&RlDOao$`v+DI;h|sraf*OqW~nPHn#dYh{HgbS48t zs$WOpX{kSeNM_Mb?iP^Et)*ta3S>hmgu1{Zr6SN)v%()e=@?sKx)sX6_^3k}f{J8Y zR#x8&w-796@xPC~WBw?=;!!2l52Fe;2>KH!Ia5nWF4E9Hr1y9Zg#mTZxltrg$($R7 z0qQ~LMv=gr!MT$YAsQPC$L$CQJ)%4{-j+lqp+!L_T~Lc^%DF8nl@}~nREM40qB`Q- zNmR%5-RtsF^p5IVF^}g=_iY}PmM zCcP25l#d-X-PxwGvumaN_&S@-$NsX1Vp6fwNL*=g9g*ae=&Xnu&>&24W+XTE?)C0C zfgK!?JnT-6a1X@w5TF5ZKl?IR63wJEr^PT3h0g|SWP?*mx`YnYbQD^ z%q|z^O#4vLfx6G{{-O3BE{3sF6NaWjBp#_G*h1~X90*&TobJrWBbyD5w`*OPg>+(& zZQ{$JFicSX#Sdow7o!Wjimd!QptS6Jz6wTd6s(IeZv=Tc)@&t2R@Lf>*LuFm59ic`?m+t*W zjP{r%p_?8&+wZHg&>F1C{wXp`bMIt2Vl$K9{X7V}dPk%tx9Wzjyt8l|144{Y%Ve`7 zV+OC1V>n z^Fec+3UTIxPI(sdL5(`oriQ}=CXHuZ)|fMkLP-*@4T+}OrUalfA(1KHT5C=-)G8=LJDo}F(CHILIE z?#p$EoIw~v;00_ZgMOac{xR|=H}=I{#6m87KQbF+XFv+Mu}|&5PgD@3&#OH{{Ri91 zf7(%lNKgHqCK&pE9RV>aVCn?O7bh$fTVQD9`rhll=>!vdK^yyNvnBR|$ZIv0)RoVq zgO1WB3nh`dM3Wy(S9JQs+4{L9b>&jTCBuX)ef#V{KZK7V_)tnixEoVf-abcUA*(}5 zU8aqFanuDzG2)`dO3}tSf`?!tqaZr`5wd;k)TKX9U2~P4{b*Iry1g(uSk}asKkLYW zFJ?Yz^!0Q}cAy9X1KGyH*EM`GX|tY+nQunld%|8DraIH|0p`zzq!zG4flq(B?@z%kRN#3@eN?uOdKn;Ls zq^^;Xj``2%nSF}lPR@S-7>(lRWda$4Tca2yWrKv+FysK_*gtQj9;3 zvmZGkGfP3595W<5>)DSy*}1_KDEVIyi#zl|r0$4-ZC)lV8Il*D{>Zm4b-N){^OdXc zsHmn%f}_T{55R7EZrke-JevUd;~zMj*wLrrbq7GSa^(+zDEBZZ^lQ-|Cji@F5XTf# zSr}Ass64EP4!-2I!qt#XU9-`h0@P+1sy~8O>#p7)&u#n}*=^pMg6BH=v8Tvmf7Dej{xb4h2a zWXZ=+{Gn=RKIAX+BA(2;ln5zdj^l{~U2PGi#{i9FNm#{bG2Furcbh3Zi|83V`)onr zum$X*OzyP+o_L6ke4={UxfC*~wMc<_1P7GVbBU0$0o|46QCz$I49V|HqDD}2lu9;RJ0UB_={F52uxWmYI3KX7Lx81sv8>$Y7ox3=Fd0P?78V56GpXuPTqNfhDW+M8 zt)gxY#sl30eqsiyn`E6ER99pHQIZ8J3@5^~0tc-*j)m+CQ?+*504hi~@>*z9vAMNu z^@N1i@{McCFm6n0wiX!L?Pqi_bj0w_b>}%D)^6%)7Us(s`Z0b^ksOFv$rMx1=v`8g z%9N<;`G<(hcyYN9Y3x}4v-p&s#SMdzwocr;WHE(wbYjh5GvOjFwbyv7Y>rr*s%iw+ za9MqRSCOjuXs*ex68SZ`}~!H|SN zp9X{ZTe9-z25?f!zB0cCCzD?Lp{c(HO?#tA?4n#!Bfoz5#EG`Cg;{NY8T%JiLEGEC zk1a4AZb^-7bt<6M(g58T>-0KU9@F|)+t`Y(ux-aHj*6RPoLP~vou2+lDrHI71QBSES!6 z$4$Ay0qY$V|9y&CXF%5;lA1%^2p?qKUdKO4mc%HS-))O{@~C1kF)&{(ingD8kK5D@ zBGRKHgDJ+;0(bV<92x`_pF|yc42cDjc5D+lyC9i6{zyJ(?BydZp(FuwOo(DCpW$Oq z*dV^g^&=KS)!Q+C*t>j>gs^GH@J2hL&WkSW(aR_4a&w8Ks4Sz{?oFZfkt4hkuOu@yS%Hvcr6rus5}FxhYeF|9 z9+s4NAuGRZ|GCR9vE+oKgde&N1PICrinfQxR)xJV)nlN&|>OAMhOxp}PppE(PV! zC>S_c?Jx&4Jgd+s+u{R6ToNTHL@5~^3E27~<)P~5xmq!Ga|*W~=YB$|ZMY^AQWCZb z!AmO$T5R+}T%&~_hch=rbw>v}ZVm|Ank_BLHd<-T_ZzcJbn1xbA}MAQ6P*@F zmVsv<@l;!!=0Dpwr#i!+WI6(dsyT$P+c^GE;Agc{*grczs2w7cp=VJ><^0$YPoHm= z&KHXAi_IA45Gkp;ZWBfSe9n7b1%7#*dwxXUpG@p;UZ+d2 zqVt|FXgXhS$}CGK{TzY<3HIGctcgqftXyXj@0husH?`5}-jE{IEmH?2P#}YvKE)(e&7lQjMm+O~ z&QoYB8Ug==>@nfz%&erbyY-MM5NKrNHp|yY&T>DN91U%w+Dqz(Kp4yVOz~i|Xvs5* zDQ{1WP<~XG7&fe#)K7xTjF_)gj&rV3R8B-!Is?t z4H1G4)y~l{otz{zqkwqBZsy3X+6&+;XXeIhcLal(dC}3X1=-R}ZQtLhT?= z2fuh9fis#pMP!oAr}g$mGqvoCoAv+1m?jHA=xH3BMW)1c4U}F1pOd)Zc!8vTW=WfpFf;Y$irWm}W01z#14n*O+U zI8fCFvUR`7CZ_bt(;LoCa6d312iW9P^(*$z)=_# zauViP2;-y)!nDj4M&+G^QB5#*jvG6KX`U;LYJ;_{rZ}+bMv|T$^NDyywhdJPP>8s0LAybg7_6~ts(IDoKM;eWgq&8O|8^%QG z?H*}I3DTZYgP1QB5;a&zigs6ote$O%^duC&%X*cNg(T#OGUU-1`5})iBq8rDL)Jtt zq5MsdV}qD)WyMEze-b2Vhp2P+*n@SC0Hm8QSTdpz1 z`kf472DzM>?Qd%rv$^_~rKn(ED7%tqp=EfH;4#MG9E zg5GS2SoixjILu`s^V6jkGXI*rDR5x&uq0XwqTf${&xxngZOKEB_u?sqazG^ z(!)CRzqOsrFOX^8z-443bN)S-okrC}JBYc=fuW+YOr0c>kXjUHA`w+tfHl^{J>m>FTLSPjWk0c z?}%QTKy=+wLL_zN1-kpnShtacIKoPz0*?~7Xg!kx2t>YMWFtC*mFbjN(R4XMIz>8W zB=s8!lvvYrbev7icgOoG4l_uaJxx@eIAu>$b0X&dGN#!qW11}l_8M<=Yw_-hNkV{( z;?#nqnV_ZJb_}^@8`S(eb^?_e)Xb^klp54j?bSXCrMTRnrtPQDG>}~YCPXM#h)QP> zX_50Ekyi8uIb~N<#x(ur3KZ2+@JDtv{eB8IR~!|=4JCFp>5ZSeK}{5Rl{QsqO~V`1 zv=w7_utD1(q&ib0;7MevtWuz}1%d?VZYP_Y@`_cIBoqRmYq241mN3!I1#_E3MTN)1 zEyrqS1vfKIk_|S~POBjO#)6&(^{Mb>UB2J8K>M(7C-!oxa0CWP_+Qi5v^fVKXcTe0X z7<)Xlw?c4LH!EQ6%n)GO16~Llo2qu!ro)~LGLDs5o>ou{)kjp>QfW50!d3|*b70j6IQ4_%D%pACZw@)j0zW)1Csp|RzjRM z#naZ9hiw~AqR)c1Yix!=w0_2S+_-NwimY9j{yy1FK|{LU_C^|`Hs50}gj-vKV7rn> zS?4Wuvr)2R;J<><9Gfbx&-Pok6VNtQB!pP9P@Vn>)cnGZ+dfdbsUl6_kU8u!cQwW7 z_DN>4L!)$4Mc~S)YU6&X-9$I;um>>s6|d=Wb<#q4*M*9kWVG?NnZh}$(dELT~9Y# zN?rZg1W!5x;W0vL{b&rDtmpiD+UuMmv*)(g`7Mk&{x4y#^Byws`K;gk4E%Wg=BN6O z)^8rrceH-EyeFH$)E@TpS7Vo37Cv(naBAc> z)|>>L1lm5<5}dKI&nMXshT42CW1q>EusMuy_;{g~42XuX_hpD{WxZUn_ z)pr&_>Z%Aj}G2WLZcNXk3IT~X}X(c8tw|h2|z7waqMT@~rMekbNuW3`Yq>C^uro?@3BFk zMUn0(1pX3RluC`~C6RhvYj%=zLW2lJBhYq}26mjl!Eo4}6*ezJw1gGoU?S!~lJ8Nt zO>*xznZh;(va`qMIv;^s^38`18Avj_T>cPO9@Kp{-7IoPjwcy4WigXcn(6@$Q;{Br zZrqudiT=XY>hfVQl@m;NVGd%_a@b7DViX6OrY1YasG1^DWP#Eol*=GVRB)NhCFf${ zh~Y316S6Ysh!N-~XT(b=aKCfh6)2H9(gah?#Nl?;1vWx^fs;XFAnmH{u-_h(PRh)c zVlmut$Ii~|kfS>sjx2ApX;j1!G{j9Ys02j(lbuI_^nMJ<#3p;Zu4_qGS6p|D}iEsvs2mPGH9FM3{TK0$5UyPQR19`v3`|B2DiIyni67l zvl~rbY@UD&ZG8~kr0->RT2^aTqb`W@_dFyuRYRZppLX8Xv7rK*A|BTYaJXiKBQ)v- z4RlYGh%0_}XJuS68|a__yS@bQeplEH*iQniVkP>W*$EH+LjyNCu2@o+1Id{_);yYn zN2~V``31F&!kNEN)*y%QXkW@ely*IEYGgV09P`Qi4(-m*xQhjKdQkdkW5#|4P4mx~ zADcCt{>NYpMc~`fnG9OI@7!Y>%#IVFWP_R4 zI_c$itPHpvD^h6yMArKh&1+K*_qcf}2&vGmyHhgOW- zml}ONVLN&c<*<{{Ilin*_8(NhT)_v8vs<_`YkRij>7JO5hoGx5$yKoRYtleqBKU5G+6a_k1r#g7~J zGQ8`;EW4W^F4KIw%cO??r&_>x?Vn?KU@J|t)`fA+5@QCN@gCvS>jT8kC4Tde4%Nr;0?|z3YD!##&UM8hjYXv+q~=CyRY~hn$Dx)#<(kyYp(hzpxcFmG zDVsS%Ex?U33GAi7u6qLC+}NK8$@1VUaxI>;h`*{F^UN5=b&r>zl_6bsFTM|cv64M* z$toI8Rb!}VLwv%&8wjYgsz4f22LWPf2PBQH>w&tlbrc9*{=!@aSydYivJ=i@x0Pl) z6b3*;!AgDELFm-><$l0b^K^OOqYndaGJVb&46~VHb7+`r8TKfJi=!fZv~?t_dTQcjK*6RX7+fSP7QBUBd9RmHl~JG;;r%a zbHv~y*LeG1`P4w0_5&?ObTrh~$zV-q;c>Hv!hqXR_|?oJPpI!~a>Y&RF-XKTkOAI9gaf~gOgK~9Z)T`mQ8-li-pq}-B>!vNOowq3?dfkAH;+KyB9VBJPFzmx zmM}7uAY5^%iTI!A(D`c{I$Z}0rK*|kX!MN0D>Q&!Xx;5|9YEg?c5$3F){CR;b*u4Z z0G<0j3nfI8%uzT}8u_GyW(uzF^6_&oz-0V95r$nGKUJeNNmY4A<0o@08$78#6Oc}A zf5oZbQP!$VzHU9|dFnKKqZl_kdpBeU++=l0SI5Bzn4~&Es-ySgJ${nfk^218t_UR! zhjH^B2oULB;vRWCHS)koyu-A0iyKC#;J@IGn!57~yTUmP)$~yHnX(p+FFs*ID4Sd! zjYrW*@Jv2qU!e`7xoFN>uyUoPtuFk89d$NmRe*EG7iNm*fo#1py?NHW^@3;|MUfoF z(G^vixW30Gt`{c*sh_yMNAlcnGjV;7OI#(D*nbp2e(k;X257kAu$Tf^k^C|6$hSElcQX3!CP1)qtjj3tI)TUpg&Ojp# zHdeci`2n`s4Y1Z`>SoRssiZ$l##^V33$s(r+x*zepky;y#@>#SftA_nF5#;;vmrKx zW&u00^IsZ9Qs2jQ6h6id-xC^WHL7YlI|1vZgQXuu$(n=3;gT_Qz0`Obo^cPF+jVFR z-&25@KmVo?RsEKROhO#RsmvjesWdiESVpRAkH%ef=cVIrYI_!#=Bz5&kH0J)d;d8Z zd;bZS#@)vMkG*#RkE%M?$0rvdD$Rrz+ti|tIBGzQ38E%SHA5ya(LtktCT%2yBwRF* zm;?cDph=M3I7ls?;t?xtZKbWPv`1UvPz`sgRndCEd%P!r8nFsq`MvMD%zW9IjoQ=a zIsgCjob@EL*M8TxzV)rOzIEN}V%yy>AFp?JMx4RFc-Njg(Q7?1aiH9q!rwF+ciz*<{iMtSqn* zZV~*^^!ym!?0k}duL8VwJ4)!6PbsZkmyApaC3?hff@t@j)^cFJ`^mohfmcJ0Fj~GY zJW*3-DV@SBGu8y4R75a?m=%fPBT&S_Q7fDm&nfZW+Y?4hSjLX-AYB|Hn9)QPXl8iH z1O$_m7|OR8ZqgCu|0RTHszE(}TjXzBz|NvaI~nn}od#!oRLlkgR1|F=;#tCW^8r|l z=xjHCgIC*b7U_1gC0I~`1Iy7^M)EsLoX=H@nE^W8V&bzH=7T38Dj@6`23*xYVdu3H z=WPy5^$lcdoJ2)&zJ>9rQ*4{a$hP7LV-s6t(m{Abrp!z=PhSqAQEc{PQ_7sY&g|0 zDB?Va4;dMe>o>7~5OG@Bf}*7?bBY(?F~}+AaI*_;PH`vOMjzrBaf;bCG8vrWUI>6` zZ@e>k5$5GE(fhC2X`IqLvZ@BOtr5_xJ?#DPZr=)PRf9W%-E!^hp>Ujz_Jr2Dl zScGo`Xc+5x{88mLJAZqvj}y3NYB*B((p;Pk1>Mqmg4VDoZi{H!^_y2C;64LSoR3cq#3GnKPCkq`5&6qe(~MxgC#yto7i==Fzaju)2!;CPWL0SBl8KW(nU z8@fnq;LpGG6fOp#P+WnPuu=|`M7ZfsC6s2j}xq1k(sj-)ufVE#f;muC% zP7ZK5sarZUd2$emaU+MBdZtwj9UJw?Gd_G=g`hAP+-a&Y3aVO6c; zGT6KHQ)%BE>+nn@z==lX*ZlXed}L(va;w!QP&+Ohmrj5si$yP z!4_Zd{cjyl{!3H04;^d>c^6s(whChtt%4m6Ccekn(PW>tfz5_#8}vZZ;-g4C z{<_=nQ>-d)|{5QrpAW)>Xumza{{xP1GTf7 ztEbi1)dr?FGzD5_!cW8Ox;ZVJE{dDpbF{VVoa~dslRiE6yG7Q?zS}25GZIwX{tw|GD(v!He0_{>G=X`?d_%Pc6)%nN1afCjYiGd440 zJkCu}{OS$A`;Pqz{MHOe`{uDp`Gv?Yg8%h~-zXga@}=^vN>3XyIGK1_whY9XW@!LF zUl!L@1JVv1npC!PZCuL@zsrYx1%8O@^9PA5^MXw-O$j!`e5rh$scAP2NGhL7JM!Ca z_?`Y$_;sbG&HEC5+|5JdX99d#`hnlQU&3#N;dc}o*e~aolC~-}X?%+*P8t5Y4ZmZ* zqI??; z&G#wy+_`h=XI)-bzo4&cLHxC*xuwrb{^`Elnugh|Cw#ec8d~adQJ_z5ZmF)h9DW#V z-OQ@#P1UpOl#Ks|p8$AJRZ!>kF-7L>=j;LslO^o5;^sadMq>TU)D0Rc7_FavJQz-f ze)VOW=LrU<8c6eoD_ra54r?SF$n#(IpAWy3i_89`vEd&lLD)%4W1mIF?oPjfev2nO)6epmV*IlTm!`=0x9M!%tbhBw91i5=_CMA5&$AxQ)8@Z@ zE-?Pxbjyr?H(x8aecoXFyMpok!uWUN)}O_HYW$nxZ(-kRH1x@>)Hx=*ogKw zY4P0X>z3wYuJku)J~mGMO`6iIzWCkhHKSh$_ZZxkKkG}Yd;f6%tOyQO`eSMN{BQMQ zwJG7!sjP?k0tqN?OY`;VyefPPe%-Rr;VBRN1rPic5Bzlx{A~}MW||2P1JMAREG=kyvgCw>b@cMv(FF5)=({>Jr;( zH4SrGnr2P&&8VK++&rs#P8`4>_wx8lb4zVqQ9sFjs_+Xt8C#b2eubGPx za$S{;LvxjmwGSfIH`MrQs;4zWh}v0KQiw*KxK-6HElnT?!1Ap2BUu-%>bY|0R<(y)=0>p8OYaD+?;FA6b(?uXXuBnaFUng*;mF52*0v{sq zbpq#BHA`P^nn3dBSjOU;Jn+W^&iNBde?Z`p&taxXCVxpkR^XC;g1{yH5`jzlp9oyi zuQN?G!v?S3&!hMfuAMtVFE8QIFr?=5^QLnUAXPxMFpSZKxgH=Sl~f{ zAI?G!LwSzJzoj23a2c;!f#cZmIDLb_rJh_RaH%Kl0+)R5^1$yExa7Z1;FA9qflK~x zc;N2|T=M@!;FA9#rr*zWll+4o_$dOH{Ld7)h(SR6Sc>1`JQ&GGFEjJRs<|2wb+CF9@6}VEK0$+}&=z>B4QhxkvC}mRdd^ z37lDB@n4O=3k>s@NoetP0+;1(i@;^M+a+*G|DnJo{gEdzR2{CQKVIOH{#=1e`pE*9 z^gj`}toJfcNrWr&tHJ|+%mYu&OU~yK5BwGne3u9Qz5L|-a|ABqHBaC&UTZz@lu^m~ zpW}hIc;I(=;4gXLS!P1sU7rj!^Nhr0yk-|Bmvf~D{!b75gfo-#x!wc+FAqHBtmJ%7 z_Q1ypT*k{0xXiCr0+;j;3tZB_DsV}kIVQQBCkb5AUm$SFzs&>xiwFLW2Y%G|lgpDD zN{;7y;6L@iw|d}5k4?^hf(QN+4}84`{*1t7dLCPxJlq0-OZsU7m+8Mi;FA6~0+;lg z1TN{{5xAu19vYfd{7d{I54_z2f4~EOzC^KC{0GBo+m|8dCUADWEq;~k5| z#;Zr*GG6613HpGbUnB5i1^$S@hY5U#!4Cs=2>z{k+Jzr(99|TBjss@pd`sX#fv>35 z9@+lN@zMmM>HkJPZPMz_XdGWK354` z(*H%^l76GWCH;PZOZv3tgq)H-uO)%Yba+wVvfdbUWrAMz6NY%;Lp|`}9{A}Vc(Dh5 zo(Dch;8OlOJn$|L+&?c7U&-ep5Bz!$e6t7sfd^hXKY6&fdf=~m;3q6d&gWte-0{FS zdf*>=;J03r2v@d8e-^k*&*QI6&<9|(_1j#5%l4>4;IchhW^i|VbgK(LTnFb{Dfr0t z=+^=d3V9}9r#-scqeg+ta(A`BWw~1;a7q8)0+;l?0+;lk3S83XElkKM>CY0lq;~}V zZ4uvFuTSui_-i*L@S_C%;MU~$Y!7^;2Yz^4az1Bz;7dL5zk1+Dv?u33!2@67fp7A_ zKlQ**U7S4JIUe|LJn*ZQB>2dDS?(n86JfRGsOv`wT;|uI9m(<21HObp9?(jUkhB8(>DYz%W03m zWjW3KN%C-q30#(=u^#w%flK*+C~(Q=Y7cyoz$KsGc;MyB6Y|S)Iz!;HoL(bvnf|wX z;J?0A^QRs%&pJ!=`@sLuPhzrs{XpQdeANs56!@`vc%{H)`C8CC8EA{-_0+)Jzl)z=UC%X7+DX`w&h1+oR1s@siSpt{g7Ov17 zDW}Ze+ipwXQm;08;O_}smamilGr>oeuTcV*O+SIzD(ef&s_qS zd^UUFe-pUmv)2Q^^Y%o1W%>Gpz-9T`BygGjuX*5mR%-sXeA#^x3-3sTE6dlD0+;3M z4S~z@^{&8W`P%P+AM&$AxRQQ^z-9S5PvA1Xy99nJSlax3;LZg9qXhn{2cEeqK`-?% zDsZWX7YbbJ;gtfH;r>YAQh$Eyf!`}|$!C+mC7;(l@NR)iK7)VmQ-FWZQ<#eyWWjXy! z;4*&){7MH#{t}M}{4}`PbgmHiFo91oxI6u8T)0jDnSzf@|0aP41^@bA;{}Ft%5;0; zZUrk`>hmWa_=&3%^s>Cy30#)<%LFdV`^^HE;r>$KvYc-6z_$xr@_A97(|NcDezCxF;MJC+ua3J?4vfy?|Fv?dX*%$ExUF6)^|26xvp z6)xP?GgAd0SDR3F?)h_;8YRm_?a2syB;3LDmS>Q6#hJ~|K>m7h1dKv7OTY+*@sTxyCD z5YbEVosIwgX2~fDtVmglu-EV#zK%0G#-|r<_22w0_=?s%GkM~XBL7de;D+u<;ST>#HWp><^0(aqkD=C& z5A`qR7#gR~kBMyPJ||STA><#op}o`D*m49s?!XDwIK{_@JGd*-pT!wwzR>U|oE`a3 zhGL~(l%{MvFE(<=gxHWx`JGSnv~EZ_KQ{C`6H+$Dh8~Jzg0s@oS~sRl_g91u@rC@u z(^GLHTUO!2O>cw>pNpj*-SREmm=qh*3HG>*yds@9;->OkN?dNnbygAZ%YQO)rXlFk zcaZQOAWYv80q;oR!*jcp7o0Jf-??czO#DplSop95>fA}^nb73Wq}wJ>-1l*$bTX^=TQ;dUH z(}OsD6sJhzq6}E}E3rxn_bgl;!Ew;I!(zNTI}~?VRBk-@*`fL_S#>h2j$7hP?bgln z`y3ql5aYh?o`JZ-iZ(@_IN?aU{>i%bySs_GCGv}THKFEX=GEkhVcgJs{hs+uXMM|3 zC;0QWhzKC$f1A32iwEj9MsV2YPPhWSNnIiq#tFOgdE3|n^p3-Um0X~J^)ozxDtH;5 zH2$_*{0!$E+=d1V^z7OU{9HhE|mHS&K^ZE z!GG_YQRi(NNU5{c-_{9t!$0o8dln1)ZMP?!-m`w_6!5oQnbc{Ezinbt2YuwNzwJ9o zU2&$ozils))5y^$Fq}NRJK@wPf!3%9-pdTZQFc3-gD55u=M|**A#n3`l<`=x70X~% zNzSe9IwEoU`_wI6P+MHt75AiQK(Zb5D3fg&-@sYxIGGx+DgL&{GUJl=Nikf3caMit zp1F9lD49%GQlg2B6OgML^w!XRH{PeWq{8Fwk~9QK8J|X@arv z22?C<>Q>k=)0X+u2?FQ&s7U!G^a|(3u=DYO9TDD{$D<5cKtn8#zIFTw+fsqLNx9P- zK$~CPc;)H6I~W6aXQXjgAQ@+OABXE;)q#$d3ku{;`20RSp(w(RqL>4()BU)176t@p zRC4$GZ`g$%SQw{mu7PVw`vL#bZw;a!T)*3^JmWT8?J@Tif9v_m4Q2cRxFOB=lNU_5 zW2LG{N^oXl`@=flnZh=9Q3p4ZS8PWFaf=`t21HO%?DbcDj7YM9*4Gx| zLe3pvIC#?ceW3%-z&q$QM|}yR4F#Uirh;2Jext!6=XoZ#E?kvQAOsha@~E@RQiPqi z=?a;_UzJUjZFm7AC8BT6A>r&0uv!TDTVTgcNz6-o3geO|o3eHV`K zCn=?R?a8HNVU=kb(O5zD=#{mJVlhtaGejqVAqpm*nUHozIA43G_O z&njn8J%+-U;QcM<6}F`lQub&m3O}FwW*AO|28pnvGd$ShPNH4 z${>{j4^G1CL+9hCY+|XiLEXMgZzwoB@l>oMt()^$FbW_Gi>N3UxN5|Z=wL2Oi9+S` zkXkm0otG8&&?R&N4^{~j0jIcAJ{v&5r0y5aD;h#2+89}ZY83slYqsrIib5CV3N7eG z0P9ssL&0!4L=L-SsF)Dhk5F{>`ENXYfGR1vMO3A!H3%pS0rrDYICgbP3GUQw`winejE9Fp!$_L_Yv&q!UQYKGuKE5o4nG=y|{yb8h8LN}B zb#oxUv;74KygaxH=)$9R7ic|-Zh+ms&TRHfUdB(XenR|Xq%0`sYHykzGY6mz=6H__5*XjLGE#`QoVGu5NBpK)2}A!LTH=Nz`5&L?P8Oa*{`99XYcqnMEY-ffW;2(DtxRxlb9 z>=}=KRsRCL8l0FUs9xIC=Z|nxhE6rD`h6IHIeDkHegvQg>=UnEx-JNiQ==kA(0vg8{^{7tSiW zJFI1uvRG2|m=`Gkw0TIYp0MWBFXiHC7J7*FU(W%Qm%aXxZR+rJC2XW-KSov8==kY7 zcxL+B!+Q@LSa)v*RtvEd_Zv-w9#Z-BM{rv=3{+~`B-wn*x?lBi?KhvY?oNi$x$ZVT z)?RkNRpoQkITP_Sz0*F`e)}g#?=O!_%h;rv(lhOS$bJ=q?KuZ6$GSPRokrVC+D@Xa zg|=g9`!Q|bp>0~+wUV|pVi(i)F)FTgWwd>R*txX5Ni0m;3$&e0+Y_+G&-hQ=F{3As z3Qz~K&YI6Bt|!%%F)D^eBm$?BnAw~`nu%P|SJQGTsS{a?={(-VG@p}4U3jPQxA{?g zp;@efvkFjpUsejS25u@_BVdQ(3p8s!YU->;)?0QohAMH9CcID2T;WQc`C4b3Z5)ah zb;FqfbEvQcR)5>ALja(-6)C!eyPB+-V;v(kn+zY!0Q&FM6Mu#eHQwJ=s7TX;hHML{ zx^340glfj>d-=Y{}w09{5W~(}rxs~G1y0#_BT`)INo6Ln_VZx}= z%MCL|!<1Y9$a4OyqUi zjzp__(x?$pLB^-3piGKDl~=<%4b^&;u&*%o4j*)nVc2BsC<*{s(+3IS;&;p(0l!b+ z_>j<1lmqYq0Y*BCaOasG#U)r40ofs_IxsQO7JNz|@E4)KodWA9Dg)9b5Kv)y9CJ{8 zv2tSF0_!N63V}Hv)(g~s^JdH~vR{CU>ucV@wPkE`Rfem?L?zhi+@l&zrw4x6#pDR& zLG}btYGUER2X5QlFLUq$y`ASS(>X}HWV-W~x zP&_er382;h$byluifPP92P!%<)oJH85k>Y8)l4V!4UD2r*97OE6PzzXxVICZNswA> z0N8V31>ro;AuBtDOMtSLovCLl1H?Lk>HZ2U1U2;1`G@!_0(zp8tt*r(x~$8<8Z?UW zD$tN|pu6c!Jzfpaiy}qqu^(!Of3y$cEc_Wq5X{pY9gZ5Bc|f-|%FKlFU% zeaaUo*Q@3PeDuve=nH<~Z@mmeNW-aM%w%MF&!7bctY}5D^2}De@o0SZ#E)Y*%k_{7 zcZS}fgJm|PX)olmQ!|nMNpPCkhf~}4(4S#?Gfz1^MNb_=me3j?RI@RPuE;W4S#Fr% zY^YQwbPTzkbSwk7*^LF(AdCnQ);Y+#L7-|=4LF+Vq60gq{6*9OaJxxqCgx^wHR|>L zrGqfzi@Ywv7@STsL{lex?^P9^r@ zS?WARg^CS&ywrIt$^7AGDu<(mA9DWC^49z~iK$qjK$2BrHgS@fO(c4d*~BnEcj*r= zDik@D$YLTo*_hEZBz7thqx~Gr9&mmOm%B2#p{F+~L0FEhE=Oa7zAzI6HjDs$WXs7p zJ+Z%n_)PU}rg8(7d8JA=s^2%Xl0eg#)?yrNk#s)T%hK=FoZ2*z?etW-)iJM;;Z21o=04+T5L=T``ccDwkKNf z;b}S#R8P4J8r8<+@AIvu?7)4&ML@+=;>9*ukcMU|F+1L$icckmCS$VZYt1FDKrjoB z+kLi~$H6c)%T6fbdNucq;#Q>A#(B?|CY@2lwE96M`ifJEGpQtcs)SKx$5*wKU~gs= z5;o*@7qS@to>PiHKyt$wg$|=>^eYfw43-r~Lx>cY+u_+t;lPsrKU^EO8DMsGN;_*&m7!nx}c;^7`^qraS2eARh* z;siCRs7kk-jI5Y4x4q|mj0@fKirJP0>RUap*wuetasI?mpAlx;-Qd)dkKr7O85<0= zDr^{T+o!*{^}K|6BD663fl(tOn3JJ9Dl2V4--CTI|IN36klzMK$&U|g!1#?|`9^+z zXg>id26QTdD7;BEoBlgHkKN%s+=dSPT%bs6GX_HttYlK~BsrFyzh4*-SjsR`bkD85L5!6os+uk6=tSS1AYZft_MbOeYU6=lq23lE8wgX8futX7}u}nXew9a|+zld~*T9?K9u3)@yNwG+xc9 z2~ke0py#D$qZ8Aj@oCVFXft-A&EP**F&qWy5ur*oeF`@$^xC+C)1x4q{0~fzVh+_- zeJWkx2mBF=z9+b68!lNidZ*@a|B`s;P^#x{ysJKE&kY|b1y;3$?p&){f=W^Z61A>* z|4AV;BBQ^Q^!yD~dVXiJ^3*e#)u@)t6s*1>^khL&?}il3x*iR&A=RTDRzbS+$ex}4 zdaM%Q;O%M8UQ`76Pxd@vD-e!l;_*QtMs}i8NVV)4g7~b<#jqkV!P$QbGpCQck|435 zY~3)p?(SMQl#k~SW#$Bz)c%z}cxMg(V*O92o?|uk%BPPH0(hI<}_>)#ywf&vz~m`a*A{fZ8y{QTiV)b`!j8~)Aj;wKc?+2+HR!n zXS7{Q+X~ty(sm1NvuL}Cwkp~d(>58lM4`14?(tdmm3Cl?Y73E6da%Y?R%nZEkb2(R zb>IN5ys&F)z3)G?_ae@5--`%#zMnodZrq2NGsc`7{q&X_@8~M{>^JW}IbmAaIY?5N z_ZG(9xqsEZt!vJ{b@hzNlP5oY4*mFi_D;m!hfXdYy8E@$k2HAKId?BzecQ%MJ~;C~ zZ=+dz4!OWA)c)eXi{iGgc_-pXcpPdxA4jddFf(g$YA7?iH7)g-!!~7Rg|_mx!^@3L z-FX-_^gk#4xcD84CGk5H$Hwna9GAH!CGG5#r1#jVd;2(ETxs~7!F>*id-f9iLUDfQ zX0Ay~`)g|E18I@W&a_K1x2J_Nccg_gcjDQFXE$zg>`f~r)z8yFRRXH9pehE{rG}~! zt~GEQ2e%5ija6=I(o)|9SP3wE5VjW^k0H>Mz~eM9OT9D+nD#~r&{D9QjAsaB3-Ao% zon9%ml5)LQ+N2Fg2mPfkdLPs4?@V`?2l*v0kzUE}usk=jH$CnC)Xe?qk<3MhT#~s2 z_ggMIB%HYd&sBJ?K4fg>nnOxSweAp5m4Iq2sER>#siCTXYYp7S!7U8861X)1OML}k zCBX3qVSBOhNT&2SjUFvG38wX=7}9b)Lm*p#XBg~5@H!S=m6ZQTOR7ub4o`cFvAonp z|1aEW$V(1AE-}Ovhvvq^|Mg1@1IT)+0S5%q6q%QoM@Y2)Jwe}tGkYU$v;Rzuba5jK z45oNhI2r0qpYKoW2?l5DDox-d`IfM|!(zQdp8u-fBU?^_-B}b|ML*)dZu$A{5>e4EIaPaE!r?y*K98vO(H;Jwz}>n_`m93!`h2gt z0(8A&=+vwVf8-H7jfa|8obPf z>-P+wEyvdRVneUvVNsut^@K0UU5wQRcgJA5!QK2XH+Z>v_WenAcjFZxar2-rmfK|b zogVnFJ@ETI@Qoh$vmW@19{B4XcsFp~jn`!gzv^=WONAaMi`StZ_z(~LXb=2E51i#E zS-6EB_*f78JP&-52R_9Euk*kgJn(rQ`1KxmhX=mg1HZ!q|2=T#V}N~N7}XcX^KTyX zk9pv7z}HaQ&=S9T&^LQQ6#?ENn6K^^#I4n^s#^y0)is0p>Yl+u1>+KHIBJqJlwI9F zn7EEm5sb>~dmZ1G-tT9w2>jaj`>Cr8``k%bUw7r~>ZU5(R#=704`E9tLDu0H8eMQ zN26|5y{`#`4YR8n>zZcI#r=r08s;eFz>ocs&tg9m|Ck0+hV$Sw#5wb4@oNPx_gt^= z!0n!2wq&Mifz9VT#k}xw4E`dM{!@rgD_W}83C6z$bd(b3E`HJ@8+7;GG`$3m$ly>4#E&F7&nOFjU|t3cNtzlD@tx#>#or0KC9ZevTzAzC+;htcGrZv+r%`-x2r-fq&a{HOc=Zfe#b7 z4EGFyv)#4)KN2{{78bVy5Ax@j%Hr9k<3(KZxl`a${<{P&<-cFxSXvn8zd_&}E8B42 z5V%aY%Qzr`p*)iQ=K`1dutnhPBU}F60v|5$R8}M~^S9sd?Tk(E0P@iXvm@k<0gQsB!4P8GBCn*}b< z{OJ|A)T>9#gdD?__3G;aXVfkKZh^~k@rl4s5cDUq@qi)!@8I9^IYr={8?pFB0{^bS ze;rtq_yFGF7Tkh2eR^laq}PK!Zj7{X*D?GEBPNK zaLNBdfy;6|PvA1XU1nTK{t{OgU;uODIcC1xjbG@2U*>@?_P~GPfv@+#pYg!=dfA#kbxWdc7D{%ro%3tZB-2wc+tOyDw|*LvVjd*DZyi9e>l> zuL_)1qYd{RflGOQtCqoNd1QU~p1@`PW^s@VL-|j}zvX|bz-7E@1uprt2s~HtaRe^w z|KAB*#`oJCY{O6k`KTF_JKZ^t|!@XSKGTeCr zmwLEN;8_UY%5#svWjZ`4a2f6+0v{##oHmTH((yVKPs{&2flL0E34FAmUn+1(|6c-^ z?am6*fTn`S>4MKQ0zX6G1K60skY1L@0)fkNQ7>>dWj0=E$CHcZL(wdr<$)jNfsgRO z%RKOM4}6*jUhjdoc;MG~;Eo6W6Ayfq2mV_R{5}u-1rPj95Bwbue4hvY2pcCD>Vqur zuM3<_z0F@g8(kREOZ~h;;8GuM5%`(#WBD|(a)cotS)X?ZT-F221TM?t!v;SLdN6+v^YaQ%572z|Gbh)WzBnZ)QeqTgZtWXBeHdTF`4l^q zvLiL8lsbDN8{SL{pEVE`^hh+hqquz<8U7j6k-OQ6F(sQsV_+b!X z=SW2->{!67j>j2a-;P=AxAeQrYUUgLKCLEWzC8Mw^ogCAH z*S-LbS(@V}K-eu5!D&T_Mc5g;{~qVpF%ZkrmtKvWnLIK79g_|nmq0+fSXnP-_8-&6 z7%)AuQg%gZa8yLrc}x_XQ`u3Rjo5RS>7X4t87lsUq}aaKe<6;2$nEt1XdisSh9k;y z9-em&XbvGSteC{waQqHcd&cl?ah=rQuH4ni?Q-K~%JzCBbbpb@QGQZ~GrR(MjR z=5LYM=7HuAkg3ez?pyIy(Oa`z->Q$C=j2%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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

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 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 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 z3Wz>(cy)o5UsK@0I2JkVDe!Rm{>YvP0>~ySpm=-aaBalmJJq!6uZvjaxqRl42g4Do zd~qJ7p<}yuV*wo5Z8k-$@)<&2cf^u+#SY>ZIu>+Vd{*WS@%*JbhIo0Z9{-tDVqMW! zJCsQK-~y*5N8%3R6FOGpwB+r`!*%3c5v!dP{KO3abK?%W9y+$74rw*UhdgR;=-A3y zq%~~h(mL>IWh04osMj<{E~x|m4d%#hFmMj^oB6en3-P#(VN0$nT0J}--BF8nwwoh2 zRSzXF?7vrrNv_bbzPcgQhxk24KPTFsKj6W*7CLrG6X=nybECcuY~&IbJJ_3GO51E z@oM2m*K9+5yV(c^A7Wsxtnt7;p<^|shy49`9}GkK20i!lmDOZVex35f2YQVhCiijf z-))}hhTT`2BP-p&;TPF1=wXdH5^zEPh`VRi4)J)oztys1_Oc<2r?h{g9mFAXsmrp< z=W0L?{6eqg4@=#!O9*zWruK%8Rhu5F_xPEsVZSwqzrBIo>-`b4Oz3@S2y&o&AVhp9 zUXSuf%S>*UByt!0XMr2_hs=?})u^`$X%H#+Ea3O=z{hLZtGK`_ zA8-#jXkW_jKfH+jx5@b(U$7c_g}epAPw<{-)IWSQ%LRJWoA0&kTv$f@$^Y(;ygG#V z75OdV-UTN2ujBe^)JO4c;XgXRuQ+@ZlLqXAeS=AMjU2bO56o2etr+*m>~rLJucn_i z{wK@*Pm+7g^CLRqG-%fe%--}L_77Hk3~_83A8Utv>9p>($Hn+_AlOI9bs)xnEdLmf z-`eBaQ)#{!h9>DQ0>#;Ay-aa!r1p3iV&9m*`Tj-5#lmo`Xxzn$b0Mzn8dTz|bv(Wb z=R0_aMNA$~gZf=Zuug(~M&mUzT_T>1`{lHM z;OiH}!{+n$u#i<8>o>l>*mETNE?Q>{pP1vJc*XDwMTmz`yL0mfdE5rPn@Atnr|7ia zqty#SIJAG^dEf@&d_Tie>}RYzPYWsTN_i1K;>#zjzY1yn3BRKK)zB=qFB|oep7Fd6 zUq|MP_!stBaUtxpX1NgWLR?kqze=p{na1vi0;2kdy+|Kc+zb1yXnez6hpjb_pThz} zChh0Y-lt?7ai;Jal=Wz6FUdu5u}sp-jMm3yLM^nvl=>?c?MZN3`bK<^>l@EuKz}u(Qp>Fbn-OZH=_Nv*D){? z%BKFs_whNckIA?+=C6LN$IM(@r?@ZTYoK@DB>SheZ+{Z&NLCA5q)IwDj_}?~@oU)S zRQfC83=XOXeli3_jgMdR=X%&~++RWGfZk}oME9YbZ#W?5$Skry{KV>KPadV-Oa3zx zxYb`l&-a(iieG!i_E*_Y;UB!8a{od9DHd@G>Cc;ME=9`pyP3C?oYU0 zO#VaVgn!GpwTxFsuzPYs|D+d+Tjz^-b)oF99{2_PdX^jg75)G_Vx7wGM;$>b^nWmV z{{VhE+FvnFMEh$V`YZg6{D}JD9HjRCr=$HiPRKa*91*9s;%}I5={ou$+36(w56_eF z{z%@75RQp>1^O$iDHnRQ#tqDuWzZwyy)ur?^;jnKh!pDM`v>4g$ihVmJz9C57P_te zhw+QwS6V=N#dX}g#sf%*KfA5_Pky0i+y~<0$AU69Uq9qK2OW&!u@s;7jmD>``@;py zLW+BqVZTlO0X<>=#qST$^M4eV&gSF$uoZ_!TpIn4Kc_+7!?6D0_jM>vjd_RS)O>%5 zbpr-T*kkx8?r%fAw=P3S}EHX^c58uyW(d7g3Z42TdS!P*zz$n^ z`rCQ6{5cjdu6(~J&wDM(Q8n2y%F}x&aEvQoAYS2MuOAV&arKMu^^7Zjrzmf4zb8id zJAjc1R3AJk%G=u;%l~x@^b#8^05CDwft=1KjZ3`gyI5~hyBL2|CbY$KNAIT*xT=6Y-)%=+i|DxAGQAB zRTfc1&{F-g7>f&uyiBMZSTOmXVTsH1E^#7LIscN*sGJ-pCA~}wg4UT#?d4QiWtmv% zXgN(=4T2Z*EUF(P4e`@BHrs;u?=+7sgVQ-*wDQ$RplUXMi^oGqTB63@cNcGI?(S*p z?C_KqdlQSp4c(iX!=9ea^`+&B#SKkO-OW8c^*wEU&7MG63Hx|{TTk1@_GV9Ub4R#) zyT|ro$JlR5D@ZKnuYmLv_m=ugSjyD;`7BWHEvjH8fwBsgk>jgivvd6PQ%XO;W|x!~ zt%gpiPB&U=h{IHxdI>QgkDaTn)`TB*4n>)AkX@^RSJaZbu4Q)-u+orSj zGUPhK8EjcOPNuQT8=8vSJBxcZ^_Md}gXNYM&0ve?wD?L{ zdf6&ga$4DW_^Yt;^H|nt=d;{E(M$%jU#HLR-8h})RTOnHSD=_p3tq`?E?bz`vbmwS zyCaKDtAx|P$4Xj)|H0Ta@9iw*1E$aM`R-tu!8@3Cqo!d%TFz3Sh-sy3S&84fmTA+< z*0MsMe;u0*b24@&7TlWlxaR0%ndN0{dc~L6jM8{*t}l2VR29!sD>8J$!}2PN)@ud6 zKqf2mdyAH9Ild~5P4gG&lik`JzyBsJ$B!qmGyF5O$$w{a{AJ(QGX4L>0{-%%T4<}3 zWdzz;(%)FVzhV{BW@sh;z?bkT)!=uu#B20?f8_-zk5M|Kv`WwNYg&G(uUji9^*+za zOZ`Rb*t}9-87_9P(@MQBu>4XeYnDGao4HEw*2+uEi}JO3rT#27Cs35Am6Vn}1J)IL z^sK-$OmAg56|WhNIi;1AY@Xj+$x6$-X!qn{qrA)q;pUZjA)O!6J!9mTl|s5Dtfb7} z>R|D!jLARJQp>6xIc485;_}M^3s@onH?Zu$UL#Nzgk+f&&{<^%OWJG9E%%8nFNOqyA_W>RMG8>*D8D zlwQO#E1#HzT2Zj#Fy7@>F;|;>H(BEaM^PYvvx*`uF0~?_C45=WuK@3?05$6(R!~uX zH!Ba4K{6}$8s$M>QMQ&@apt7*pugxi#DE#{gQdUI65HbD2Fv`~jEdg?C~wnd1xvy1 z2gclBV6T>Nog+V30WpHiQ63DE9Kp0n=q^4z>D;)vmELPLcd#QN!*{7RHFzVWEz{># z`mfazJdXTI5#b!8AL?iNygm#Lg_V^RO!r}UeO1?UecschXEx0_dPxX~gapbZEKA$hhrkB-gGlMs?jPgYon0}v-g#mtg zpkB+u0G3e!`tR#$zK+R`n?sp}VDI1R&HUUtl=e0oZ%Z)CEb z5qG-JU%HMNvrj9lgCgqSU*&ZyX~&ej07gib;7F-sjsefCpdK^X#mRcyJ6h7`(T)vCj(d!f zQeQoDI9|da;IC&|{7c&8kK@vOdy0AndNw0iPeXNrkyH9> zEiTRfns#1k0CUvur{1t2vAa3EF!2ljl`;7YdROx|(o+7Ok(%sf$tN>Xf-EQbAC#o! z_=7Ajr<8um=%<{10;{#W)QSbu--~bWY;0)v6gRZDZR+rpd5Sj%J@xg|*hBSA&0U>g z-^GEl^_kO8JAZWwJFRBnmDA_@O1q{P1p{5vv&*`srHk8VbPL?HA{{+ zz#P*`gBkIkbCuxHP?rIISGsfz8oOKx`b}o4FNtMB3-NEcGPk=}+}p0i>CEwtYsU6X zEYDwbF4GsXK)0{l;Yxbf<*uAT;2C)CG$F5}=}iM~gv0v5*_D%d#;iGArIoHHef1R! z6R}_@?(JwxVN-oA@vD6Gz6C$m;`BSGyDA5#=lFt|rhe=6nZ5;?j@zc^lpRlC`T=K7 z>21?>-Ob`Jo6fE@DtcPGLs^*E(n_^@2D}Yo!k~JAK(`sE0vee42LYv;Nk*j8W~V>E9DmF3gNG9!pHQC^Ign@+Fs%6 zF=JZAPGgp@Vra&+@}Fo~_?K4t6K!t!f(zJezb{`;c+xS;?|;vc*Asr%;czF;T93vi zKbod}DM?$&5)W(I8A*-prp#uEIe~1P`n@oOcS~>_)zM|{pz6z|AXok?Im0`~Kx{lGQqI4EZxqOB`r^1hu z${9v}g%2lpG9$w~i_HzLpQSm@NJ=d|J*lLk0?!LNZcZ+#07KpQJdkpnI5L2g1Jt3IwOU4Z{DJd zjbxnsXOThN8E1oFw(l?8a?O#u=h29 zQ5DzzcfXRG%_h5qkZitoSF*qcLfEftHhgX%;1U8x0ZS20_=-UzL`Wo3t1DkkB-lu; z#g^Kw_3z_Zs#L8|pS6pM+BR*`s-@MoG}MZ%w$xTjU$y*yXYSnHyIb3aUxdAV|@iekq3`8pT@)@X#Zb!Q8l2o1y~Xn z2<0AUfnceLSjM>vHHL_#z;zkj)E}8GegLshGDa-qa~{&FBbLlrf(lUIdQHarMFqa{ z@?uv`(Ij8>@6AOXtmAxMq13$xtFY?@-J;@*$IZeu7VR0K$oF-#dO}H+&;3}D`YMan z+W^f@FSB}Z$1-STnZhon;9|EDo>Lm$mW&QdZi7YR6^!_+K_>t@eu=ulGR}WnNhTJC9xMzi zYF#%9Vuf12-=eOWU?kQWEYoVM8!S4h54C=9=AuX9U*oV?Y8I(b;}~nr5|u{NU@7!p zrcyOn#MjF(q<)WOhtpLGZH={v-B{;WS*u(%&swnNo2yk#%QX5eh@Hv><1&j%dyPMkva+K)n ztcX88EJ5soSEMy17I6zI)~m|gFJZ|&UzHKCWR6ui+|F`AAGA5#E)cJ=s%Kd=%PA%=k~H`weAyYL@J&+g1$|qSy?v0#@eQ3;G|aD?I+yg8miwz6y<9DE7>(RKHuHEmjqKes5EM z&!Kxm&_Awqc-`%)oLAHiuVQIoC8 zc~y;wBdF5+W{Un&WNXpdkJS~`PPB%lsH$8GfOT@7OH-~vYp(Ig{;)#(1|mV%7shEa ziq&sjQtI7oRvl64{yp1xgH4@ZeW#(s^dIHN2oK%m9@nkT63_RFR2EIeX3rPg zhK+^jL#g}u994-%*JZ%MtIUSE>GiSGs;jXzuhtZlc@Y9uYgNX_Y^wa~ALNuEZ?1W# zyt3N&J2J^X7_9!^3A%D$B)Qkqbc(p5wgZ; z5FQ>u$B^-PjtHs(?-3#U-|Xtex!Pl(!IS#OMOOb2L0|5rE*(MdkHDY-AB&9f%tSDV znhfXd>hI@jD|BVPyEN)Ug0&VT>bo>$AkDf7DNV~lfVsEjj&v00xk&-j?d z_!mGrr`M}bYH%x_<8F5EtggFd`3_@+pfhI8Q5#)?$?9?mWyTlOc~wvwDwX$y$Avd75{|-dTG6*RwHn|817>#_?KfO~?35YxPqz zGIJMc)IZEn-+PJGvq)nocYD60+93T5)IP0HIw`j-1MTM+?HOupl3=5(R+wOQ<4>9A ziy6jW3Heqp)~IFGKhs!UzpXP(b9w$PLucHqG8-SpSHsU0O02F&>Qu-STisumG8Rv5 zrqx#mi1!oPt#iE1xMg7O&H2GZb)%5G53}ArEu!GcMyzATP0XmQxw_h{>n>3Lc3f6p zq2_pDi?h|U!0%KEADf}a4@@+U$*-IU^VaI%kF`2sQf=k6bB#x|D$Nh|8T!fNTJ@8K zPNCk}>U8!C;>H4<6+0YrF`3WZvcA1zwxF4)(@qe|Cv;qD$ynwnYWnTi0k zw#Mi5yWVnS{zu(WY_d)Dd;EStJd?DKm8u?{tXn78TV1Cn3ujd7dX>6uJf40rIr9;G z*011lXX`xI+}{Z5_XV{|$o!Ttu60iUJb1kC3;MHph1Hz_-Q9uAR}1oeM`l+$Tj#!| z%Dfu8c2^5G&qsJ$y8!Y37{6DzVZO7v+Ua)tCe~Iy8PH@Ei?gt?qcg$+69Heqoc zuJOg{r^gint-`|^{e{XnUaeL?RsheBD)x+<%jU9rJ`PINB zo|=*@llQL^jEffNGgnU?*LurTN2~jDnyeeNV?4iAc^543KdMpRA{2F8R+l-p4)e$F z$7xT^&skkn(<}q#VZbQ~fM)f<>=2SPk z59Eyv$9{pMQ#BhuVRv7iu~Nv&y<2T<^$O#1w;GICcUawRg3XGxl+D;?P)&9DbH)XWmb8Ji7iBkorBHi|)*Mo3D9|O-QxK_-}#2)QLd$r47 zdr~Os`hIp^?cwZl=lmH$rm$qJ?{fDTpIckGw&U{PWLNFBGOUo)nV17S&*1IVMj_+- z27R-^;@KzUo)J`Qgsko2Sl{zrlW)E#ZY|HeOvu=lrQagxezeH=BZEf!e$e=eAw%sb z&zK~rR*TuM84wx&*sy4txBABhU6t4OW5YCW&5sRPS?$^I%s)1Wh2DKZ%?}o4owRAb zy3k5k)2cy%ziyObd+>X6$5!Xh%bEo>0+4lN;W+2F7G~fPb&2Y)S|*X<>oJ#5ZJJY) z3n9p?s}0k9PCQTYxyq;HJNGSw`CF+%P1Y1PLhj7L%LqId`cBV-v11HO^HGHw^OQmw z6Km(I7frx)dZ|WJG9|Mdv(=x3X*E=3p6liD>aVB^Y8Ecc`tfvY^+|(fT=kV$2%JRF z<~oTgZWPR}H-ckbpVdyQp8oty{bbLKRXUBi!5En$XwFT}dayhzuc6dq&&gbAGET@* zSJqVH?{|cnZ_JYdg8C8DQo`&qNWf-Pc zmiX_=)UV9QubEeAT$!QCSW7qEWMAbSm5z=YKz^Q)_1S6TT$jx@UY((}yWH2!Ha2Id zG?&aaJ9}mqtofi4VVq|zP8Iom(}c{YAoq0{`kaF*_3f(6hKwSALx%ctu>?20vof*>J+9`tWbGqya zg612JLeB&!eSD=xyROpe{BDD)0K0W?(vEzcsxo7uV5vQ&&HVMk2{q2!8uVA_^8EpI z_EOzA|59C1%~BlYr<@?yT&L^4i*$jD>-p%~|Mcb~#&A zKDX0(&N#!@T>E^s=Gda?czhvSa}Vrt%_2d2g)q(Od@(x5jlYPYKv%e#zF&R7h}87}AaOA1szA?v81E-JA4vJF`fyij2Lg;}c^S75FAr8#r5 zztSO8Oy~&eCTw>7bZO={botevn`iunPN!a@vsQmYr!A~`ZC>Ok&Uv)$!) z30dVDYwb~8R+;O|TFtKtC%XLqp_}Y-t;@vgiC$It!+H0m(xhf|Kx$l}MIbHWSH81O?dR+wzblwHoKhhPrK50bI@~RG= z=2e}j>85S{o~hb4Q>kl%N%LhiQ1kgJK~pN^+*+moLRD#Xes+dtrLiJISLv;($WT8a z%;|7>zgMNL^i~&Tt4>s9SK`%AbCtd{Th&vQ`-`e+UY|8v)V@^(gx^((3$Y=$S;(D` zZTtY$mRy~co2?mpndTe1;_8rFJ>lb}?s2-Tl{$4>hW_I^@la8Pr?Np1+(K53F6;1B zCj=ESd*66gF@655**66T=ngbDker9HkF0Wdginp3NmA%^M5=57JT6Im0PG45N zMWy-L=V`KC*e)Zx3wCZM$F2d zS_4+|eb=ZBHM#=uQS3#_ez2-n&nPJL82lyGd^zAY5pIUrcEVSEjN{9 zjIC)Zu^^50&J^^$Mr`b4BCbS<)-4#DN=#Por;M4J$Fbc49N-lr75cG-a#@Mq>R(cl zp>3FJ3>dW-G8pNLN>tX`hLT3Buc0IZdmJV3j}0Y-1QRurOA2Zp!_wUU>N>ayLU7X` zPeVyop*N>QZ7t3!Dn^KiXZ4u*g;=f?VJ@Otved5?75XqQeZeuUn(E3ryG~P7lJ%Kl zY)X`5X`VA?PHxVcU5elfj|JX`=VMYU@_?8sEAXvakf~lxlP0U{TLG)@m(4n}@8hct z2b;A*3BrB7yA0D;Ly11~+a(!SEig8e6qf?o>i%GyUj3Z{{U3@|cWO(jeLD3xgr>%l zEW`5)@CwxGtpMM(j*?7^Z$^Qo8e0+?)cj0q{B^O~SW#l61~Lk;xrzr( zg%=KmG6Y>}^;MK)T5BpGa(_ihktVAovjTNgWY}w63rm2OnfbnMTBx>VQCrk$(H3*8fhoG))OJ0tU^)w;gas@C1M zzR-uMsK$AzFrn5p_EO`n^{Om5%G$$%rcjmX#)}=7b4p2BSusoTVOMgp*R{W-$WtcdKHN}6 z_A}XCy&L1=s|Inbds=y=+k2oP`-^yG@=B?$!26UIc}yK&EYzE;Un$k>$;|itO*_py z{k_?Gt9$MEamB9pW@kIftTm1@m1ZAUS+hvQ7E+lXuxdwHPF1yYp4n3UUa|OaDFH3j z(`q$~)n&Dgvf}D1)Y>OYFX75{q#v@>r+q(!169_NeYE%=dtG}@**HNbZMTup` zZyF%;iZZ>XG}Ab_EH~rc&6x9iQ_9+`{wZa6Wiq7nteBBpbd+=1Lx36i+q!WoWJNa|5@Vj`6mfeJ+4WD=Hr(YIRXBbLH)E* zTP0{GA+87b`58rC%-r8C)#{gJq1-XP!teY;snzqiPNmsUYV|$19J}hJ*(myA&RF*s za~wYRy}GG>{DB8A(-qX*t1I#O%Bi-6 z-_qtjU8>5j{Y|O92BkG+nK)K49y4N@?$;h&ahdUR(}i(Y&+@3TOI;(sfyD71fiC=j z-Y|Ysl;K_IHv*}iA%99zYA^b8j_H1^mh%0^Hc+SS?gT23yD)=tl z;%oRnf{9L3-zCvOj7)mbzB7qm^)|^dc71)`ngw)LFzLE<-n{8H$E8b_SJ=E&wN)OD zkk9X`ZC41YZj-9SkSX?~0u$hLewZ$PG@Cs;C6KU&?)AC0HTqn!A^XN$5szl1CW7PI zN`Fwk9_5$fLkm>aPB2`&%y})!=JT>1sYt+v7STC3Dasz;WiRuxZ77?|=_0)BcT!p4 z6DX_WWpD5@3kjq?w@H1eR4MUtj+Z@2ceK}uq9Ev~-}zjC>_|WQ-~f!i6osxI(x19q z({6QLu6c*%vfLK+Mt!askUG{;lFu@Z`vT$4%{BEA?rsfg+M!(zf~)npHb6i#H&>jO z&AwQxhtm|(9W;B@b92poM6+94m+RP}Ysw9&)VJt!9d(QtC?KAnO}stFX}?76DS2yB zuZr>pp%Cs_jysv|O56_hz9{Y@3736B2WVF&N|70=Yolqx!rZS9rd~Xx?CqDJ2%$`DoLEEhWN>+!@Ib;=nraB z+Nu2cm{d00$mxzsbj};m4~UYDn>Jq7!s}ek$~HyEO%E?mv`6CS0bZ79|NSV7@UmOE zj&`UUbIq4hpU^XBSFWjnI%k{D9zXV9`M7}f1;1~9^9`M=*Wli{S-Q8$xy|d_aoBWy zcc)ObZguBsp=!;RE%XEL&8@3D*KF7*NWY2OwXuD}_3PVj7OFOHA~dSVRppO=bm391 z4G60@cXoAf(1wj0I-6H--n^P>Ae@aiEm++-cf-b|>#w~5Cy9lswVQ5`G8;E_uCIbi zoQfLOUeD3jw>7uocjVWTqW=ENhAK!kiD$3_fYCKxcEHjj^FKVxZmB*4LIuHxB`h=l z+!0kn?T8YX*)e4&A`zUM34z$PE}>l+PG`22S0x^(&P4h)PT!(L=D13b3lG#jAF9zJ znSTGRQ4q_>{L9dp$fS~=@EI8+{!X~cYY`p@MxVQgyEr`?Z;ArBeYu{MC`(2EB&WYp zK}%VgPQGvCutfcv?v@DIxRtW(FU>#7YXcqg^wAHQfASl2-Hw!t zl&HV{&r)d&UwTS@z6AxT+7CW2QONC3`*3wh?e7DOWRa-<{k(md9w_XR`Inz@W+-L* zEh*a10j^9h*ZUQ~sC}B3mDge3ex4GDysS6*enQxCH}8LsLjPk>BGb$LCs?BHsRHxo>8~JsPr60_Ch||uiM$hXTbTYr zuH3N4A&-MvN$rzOrfPrxgDAjt7@sy*UWx51%anP-(+Yn$uY}2YO_CO*5=}}@h`&@4 zCOC^uQ|Pq-#bk$P23N$_zd_jrosPd8u(dW&naFHi``I9CitNK z%6^K)XMmS;_z7+}EcOC?7Ke9`!twdl!eZZ>PXBi=S}c19+0G}Vk;CfEH!ttp(0&s`a7qY>Ddu${Y6-61xUPLYNO_GbAAm_)X;8FWjVV}!MdghOssvI>?q#~QCrYJ3>mrWKFQmUR6ODezV@u}-vJ;`29T zj?~ipjh!1hZ0 z2S$N3KG5C;wv~y4rRx!+*KNMGi^bjqX$^uNXWE7^gRw5j7?;JS8*ry-z zqs7EW4IedWtVP+66ox=GIM;2scJ&Q6tcG>83hTQ!bP6N}UTs|8S;4$b8(TMA+qHQ; zsf$4*{Vv6OySB}nHsXWQ$_f;tSqYX7SZ-av;o7#&EofO;w&ljnovBOWdY~kUD`;l5 zDvM(;9T;@+`Ww(M^xgaSjA5ex`$n&l#hCu6-eh;2))?Uq$Q65&d|Fx)We$W$M(;{f z^O*Jx4q7CNQTQm3{XO2Ay9U|AC>%qar(1v_--&Mk5LRO&5CecO7g>ynC0O+PfA}&f z3g^oTwwFP9+G`;ECVId%UAi&+OOXGN-ZtTCNdu>S6}qS$8)f*_!SA%ZrTXPKqLag^ zT^rBS?Pi`Q{P9R~yI)hlmHeDY13!}n{s#^xKFfKYZs&Mj=F`caKje4fQ;DxqNd<{~ zPE){@d^V+lx21vK$l=7No%2AqZUsKc)4|K=dwA#~K9%^lEAW-_JfeVK0vcJ)C)2>? z_Z-w;B|d)mh+TM&LFC0$&ok)c8#Rn=CQ-9@nSKOJe{@_P}gM~NR!gMTy)oZ?)%66-mW2L3*WlN=^7O6kvs3Vf17KrW*PTqFl2 zzK+AG-SJ4W94=G9mHp~S1Mf)#52t}2O#?rX2L4VOxQMutu0*+w<8YFXJf;b+RDn-b z`U!}OzWtdll8+L9f&yPDpH&LDQa+(H@cn7vhtt4cOap(3!>L~t#4x{3D)6aaCJujt z!>M0N{9h{YmHkp7UZ#uWIR#0!hh+-*R0aH21zahIPbuJ43jBi#xDr2-2L2rmr~Z~B z$=3-5KJ{0gUtZyG>aP<2CklLJf9;6V>7xEB`xR8cm3&^VfGhjep@1v#d(yz~;&AGh zofzQPeg!`DOSZ)W98UdG;(u9zuk6=(1zg!LFL#X8UnQTj6mVs~S`=_4es>ype;W9~ zH1Mx;ILToGl6)Oi;FBC|yx=JgCpjqbk16n#a>(WTT#5RvRKS(}s#d_2a%fV(mG~d$ zaN=L)kyvd};1mDyI&B??6aO|O`P!hsSMncGz?FJbQ-in?^_Z=IEBSXS;7b03Y2Yns z;N5B9w{tk%Di~+dpB@E1$wBr@eH>15P~zXEz*oxQeFa=82Rdg%SE3x~T!Rc(%ArF6 zSIVI$4LqC%el!jIL>l)WT zB+mdZ>*R0}pA!FO1-??A-%`Mp{GUn#$8Q3&i{!7wH|3^=JJP^sr-83b1HYET6Xmc` zfv=RqW)4r3!&U{pQVw5Jz?J%b6)lYEr;yA=3J`DnRABmR}+&ZU4)MVUMvuTa2i6!1?g;7U0^oCf}U z8u$-6ocgO=|GcKar~W#T==v##Q-78Czfj;S`)lOySCaIV1|CcUznsH~PdhQfuN4Y> z;#1baDh?+;mH4X__)0$SRlt?fmj-U)@7ogP=|}^2aX9r$xqk2~@Tp(ijD?vTPW@8i*D3Io{o1a8EBh5r13#Ju zej*M0Od7aA2bFLo${~xxslUqgfJuQ*{gu}b`5aFDRpJ*Z@Rj}L?67v}oQ&+Ru2uMf zDg~VQql@^VGl25N!wy!@9028d__V8m;LG^) zdX9e7(my=Q%Muub!qCscMGwL@Ts2t(xcF-fGgK^*QcqcQ-M#s%GWjp zejR|ae&up1md|<+OymUw*YP~vY&=hOE9>_rz-2tyPG$Uljwj1WhRf$)`V#8tCkAjG zly0m|q6zTEpG$zt=U+}Gz~%EV^8UQsu6+K*%-fOS^7)s_1h{<8Wkmv9KDR>WxagAW zkXT`pYlKgzI;9xuRRxO_gPF9F`q=Sw;_MVH*Jd_DzVpc;iM?Gx|BH?(eQ zUxyc}Rhy>SY%XuD$LH~T@J-P8_Pd&z*k7lQ!F;}2FUoAT7X5AW8yBcmT=1w}CCo-j zzRs3#A)zS1it&}D7@fgAbgEDY^50@W_HBA@HClJlUHCTrNh)ovlFNfT-Z8sgewv0@ zu=f_b7~G3LXNYK5q*IR%V40saTI-YRVJNJaj%UQ`TfQ{d`)aWFkEl~ib?$h_focs@ ztwF9f8+4AVu13WHfeK7dQuCaLRdy4{>pDq=<|k=Zp6*UP-R&P@8O*;UW>OZ*V)3_)(~mjut3_@qJ^{+%QiSH)O7 zl$yoQD&_paf*Ow$E$JvRjh=!~}$XVd~# zb35rEEPuS6_@h`e2nz`LPdbS^#i%pxvZ5@t4e3xajPVuob&#JyS$SuULUOIl9~hFy z*XzVtI=}Ta6{hoBU#3FalW}s33UwzHI^yLf3)Q%5ElC99i^?44 zkMv8UFecr?v=aR2Z;)Vgen>MOFUs$t{2Vc-QE&fmusBcehe`&{Yoc~KZj+y_%Xut3{9!H}caxgsUPx6v~!AMT+C4WI< zoUI?yj_>sAkJz;GFS`C<{_%qA5Avc+uFiP>7_;Jh=0$hfLy)TENSS<-9qDh=3i&0J z^e;pi5I@EQ_P56&r3EyFNlgE=H(EoG z58Hqs*_l}#39<{q`I^|M#Ab(%I7}zM(-PlF^7|ga75o?3EW(~~h@a9w8cmpr<=(a1OVL9cNE+>Sj@TLPXE%?mrTEa6ysmtWs&vHmU#N?6 zAlLOxDs(FQ-yU5*cO3}s*=Y|$DTL%#PR>fNv4t;B4sygmEM}xQFTgoA?rPwOhdE*} zK5Ps|5icP`G-*!}Zc|X5QY~+j_o5xQBkwbpV;y-Q9u58{lwz%MEwNf^%GMh(+^DJXjGAGTSTNsXgsFKb5IE> zvNT0R@J8Cj>N6;z{n+UEirw3>) zc3k_@#l5e>OD*pGLGbo>dY~@Ytvz_`w2qV%4EFx4k)i>bi540k`Ax9*cTBM4pn|Rs zg1x`tk5Abnpe4vp8D!zw7wVyu4mdaVo~<5>_ZHdXHSX0$$j^09H7#(I3y+=BFbByT zBdcInq!AdRapyw3)~LJvqw}cVc;^S01nTzgqK9MW#9a~Non?UR>9pH-oKZFGt$Y;) zLh#rraenVR^Iab-ta!%t`riC+5VT>h_Eq8B`SYwh?~zfOj}7XBYhMoT^-5&$%qH%~ z5rB!eKTEGV>YikOYkwy9MDE0VK1t9{|NzwCi`Cq2yMexF1d098Ho zF_Tx;Iipbn#_Mi>hZqgs{w}c;oPjlnaaT2SuTT}k4c`7-4;Ymuhq`O(nx*#%fn@*l zHEOgz`F+A?iEjrLHYxm%>MZp{p0A9%+PPDYpfSQ-5$}dpaK??h=B0Jq6)2(ceiU62 z-hLmWzi#h*Rgwx^--(I0_t|5=wW^*4gZ3S#{{}0DbwMMIYo9%KN=*hvx?u(vZU2|3 zch~E|J?-{jXUUGgLIEXA0kQc)6{&spw*QpX;EbxF-R?zoeyAblR6m2-sJq@Ic^Y?Z zEn<; zG(vtWFrI7}nO-KS6)V|5e7hU(!6kLPi)IqnJMsQ_#fAOxin#SjI)7}A&3{3fOu5>+ z*#5OGW`88C=;X-uuN^Uii(A?3B=w}-_m}psm3t}zu>%n{^aj})4n&S_rIyvDCH=rWAobw@$m_QllNo6NvRZ^u=VrLumCeR;*SHt z$?w<~IH!qEu-$Q?;G}b;!HIF_<;u9^Z}2-Qm#-x9y&fbe(YVV>NprI z-SOt%ux6FxljCXQlb^sh?|L`5XBCT2K0pdr%vRCRKd0iI=5{ zPnPmBg-B^V9*a-hgn)jrz%Z>m4Zuo0@ZV#>Nl2QZXlhG}OR(5JLGu>^F$zu`n9=Z? z^|)57RKz6|Cem)7a=rXt=wH*l5BT47zD9n(6Bp0HQ)$_2v)Jn$<@v-E@f>@9qD_i@ zyZ-L;iE;0*m3x2+9(xVZ9dbIKNUwR~{7VXcPtrI^!EYq?iV8;`nFa2ObUj4aCS^dX zc#FN43eB;Al%iERl$EwhQV)tN;AC3+X+Es$W%FU<+QH?)wXZE*-22wTW53e%euOFS z6?t03iymyNHzKIS@WEakn+jiB*!!xq?!$NqxISo%?$&`_dK{EW*B&QtByXUtw}$Ie*mu$}eNxw(7mb?5K>^GC!a@|yGa z*8TZh-Hzw1y~p7^XV)8pr|OP^JZdD-3|N=0xgZ|`*N zB2;IJt(^@to1n~_WZuSIi>VAXvDbS+rG+S?|4G0~Z3H-&>Tip9+41J6OowPFk6UP2)%a`_k#7>V( zM}-=DwRRf&Vz4)E;5(HA|0z4YX(Pycim@LZ?)7*N*aRi9_mT20J=-*GO1DdI+&N|> zV_$0@YUqz)L*HvxjQnv?f1O}wEjIKqJ?K4h+BEaU>5Z~k_WgK9$*afPebSh>@@N5&rECR=GpjnD8|1N z>^q7cf$<-RAOEp1UK;;9S?@@#X&v*#w3;!Xgnd2)<6oL0u(IH<-Pz6@ z?a}o9978Be+A9|Chh}XXzcw!8scDag<{0xQH-Db57}vh`h}f#`lKR8A6{i0vWIX7 zXeVw2lAOWa3^TarDSHU8^e*9TC_BY+p!e9Zx2>vUFa2e2eGXp$}FfUM=kfpJ$S?14-v)dFD!FH5yRn90jpV$Ol)s+Op8C;>w1SxAMF{(7)f%A7+z z=4f;L3IQCJ@>PQI?GVJ$+Aqng^QN@2N}+mruns9NS}}<11yb)%$X9%juwb}j?q2Oj zU<0$}GW!bngn%G~=HOmWK=KiEzjSZq9OEO$@yI?9d7Zj`Z~atZe(&4gq>q?AO@D&> z8~1vjsk@SlzNM*d`4x4`T`!;Sq~#nA&SF44Wv4HXXF^0zPk@AZpPlwskR@}LtRjLO z%AeyvjJ!Z_PkcA)j@nR~Y#bKSb{qVYh~h>pncHNQ?VJD>uhW8^7T3$J59+>2P1p6F zVa&1z*XvE!Y3r^8&f|6`xJkh^vEurGoyutJ{WGf?)>s-__T{~u1xl_@<4*dlW?G%1 zAcS%*f}O~c2!^ZH1Ju*lN({~f2C7_%8F$^ptvJD#g~)%U(F*%PxbW=M6+e(IJ(F{ZksWJaVO$%L*`Bg~?b zR2C$e1^Uf6!b|jnpjEs?hZfHP!f(<#j{uE(cG}P4&ewTuF&B?@-p~ItU8jy=Hzc~= zq4+Pj_W8Qyjcb2HpTzt9!eejhmM`pmerfMtu?9Te`+YtGkdos3|MLrbpOYAt{XdWP zFgQ+uE5)jC?X$fHlBa;j?IEDSc4UJY*%a^xnA?S?0P_2Pg^v=Y*&y8%V1cnJLc!P+ zpo|CqB(eGr_y26=_CLn|ix=?!J0PK`|Gzn{ulW1?e;+fg3rqo@`X^2SO)wlb0?^ri z@)S@rk}1FsK4Virl zp7(^?j=qVG?HLUF^Rcn`F?Qbb;`>ybE0sQfK_TA7_o-d~XWyq5z#Sh}#m>LryyyR_ z_o-3@Wv;$^K^k%s$J33LGMYgRHNsE5e zOu9E}2dOa48?{k?9#Hx2FzzFP!~gui|J3s~i+g`4y;b;-J`aFXG`|fVdsAJwuP}kFe7sh#jbr&i~i5l*z)bqk;W986MUZAd;J`ek@MI`8I5b-#1^u;vG>=E7NQ7&B!Cnz z73}gvwHMnfB3x>?NZ;@f6(uv;ErwdD|G3s>_;{c!H*!egh$=kFkbhEhk z)bMq486BP>-4yJg!!w^|&YtZTNq!zI*ojm28mNu*(jw~xN6LxA*sk6kyyMbx{q2v# zG2(r+3-XY|fKnuQ|%oc$A&l^!WMnZ_{pDY(AuR_&JyOb`XzHS$?W^cv&E} zp%-fhWoI&3JIJ;6OXsIL(Jd+WGJE|QYyaZz^U>;HdE|2_>~-GOxpnFiiQ z%l=(`*-%~crBkuQhtA2ihVBj{YmN8D(tVEXOEkNs`yAOn^84eo zDL&9{#T=Y^0WgB^kBh}-Y-uuj`K3GO%H(H`P4dcXA08$Iuu{yQ)P+MoV!{eD&WV7i*#UH+s>FbUm{P+&*jm8(((aCg*8&jh0@6ESDG zlx2U%De}rJ{~laV>^se$j{cq_;f3Z&^@jM56qf7C5mNc_n~?$Fp{#04+m_DFovYU% zC*N5+fqvvuQ^<}LU|+V=JItF&91 z@k6Vf8*W&SU;bU>Yi-}Oy3^gXv^h#YI?63=t2eJ-H#*w2n>KFgT)nZ=ISO*#S659( zd)F5H{Peo6wdUL*Oa-y%4I*oBZ3nq=(jPH?Lk7FL^M! z)Z5&&XiFTcBO;1%J|eQXjtrkt?w6bYs4wf6vQOxchdPuX{o&%rdNW*#xQ+~qi1)!Q z|4KL?BWv0>t-XFrbI1D4(g@km#7BryHzOm8_GV;c$}Ww7QtnrrHl<%jZstFNoLW&)Ylwmz;Rx7q;;h3{YYz(1;N~(QV^r9i21-# zS0oG9qjZD{V^+v@h%q(#8e)PSeI2q`nKVXO4VQ(hRw)ab6tJ@1pb$jDQhM0<(S8n_ zLfNa~5vA0pVq%N$Mp_3MC&A&9M|cV}}SXj6eb!5@X0p zt{M5)zj3a7^KRWAi9Y*0t&wWhNyF7L}>=}g}5WZ4!0|^s)mg(Yi=Y2 zk|KvsA?bD4_$kypTv{4c%6Wt)hv>GaJkis1A^Y)RAuDt}T>O}theJUc9tNJQ+~J_g zdL1UNq|#v_OWGU`Y6?XTgA6*13(m0QzQ9HuSL6sNV%i)TQB7tV-2WAIHg9R)w6$Y(XWM8im)0Sp z>8d}8qyRdHi~L!$u-n_19`PI6kg17hOAQQe1}4& z<<*6omRD9oU~w%ERhjHDhQyPWQbRPFtfnFAO&axzOvsD@pPI!zNAX1cpzY(hrP?JcTP50euQ@FnsD{OB<~c;eeIzA!1ATbnl|%31?(f z3TqoNVa&!xL`s%55(3$tMnof9(MX9dw7gF(c{_HrM%<+ob}|x9$v-LaNA$WYm7$EN zf;2WW(mGO`&q!;DTmFw$CW-nR0TV-q0Hb8;f)&Td0i$h!2Lq!ePc_CyA~|`IAFU~; z8hImWe~3Xif~sgd#=WFd%ovKe`$EfwQIadh;^=FLjl|K_LBnTMl(K6d9kFbGqo$Ue z{V2&LuRl8C6qYxtiYRgz)(dtyd>9sT%%+A{Iq;(vG%O0(%W&}IkYX52(-s5G!o#Xo z_B+EvmLiPd@Q|$4;k1zCI}|$%3g3}-gCWjvhDjUKaQwI(K6zB(qp2XtUkt0=l+I#! zlqo&Mut`(6iDA;F@DanKOg5#_$T@L541EA2rJop@EM`yf7C$V?s40z*HfgvI)mln_ zG9*z-&oUe;W;P@16JODckSN($MpglNAmv-7l*eA=s9{LNm@N!lsdDBR7Ixffe<)VS z%?yDf`;?*3WCt<~Jjq)Oi72^-q0o|5J5-NoNs#hzjy$we7RHp8npml*lo&J^Q`9UE zxl~2+z)Djl4WE>S(v+O4C|O>qxFQ+Fy$Yb^x?-+PSf|I{b&rThA=8o4#a!Y@C`kt+ zz?XG15)xTwBczdZIU*uS$0H$0q5Bb#D?fvlD!&#$jZm3XMBkgN5ek#wI~jax68r$Z zbwneWli+`3@Tw&E4tyJgMkq>x|BAtb{~Ck4lHeP% z2){H5{uton1((nQI=VvaFFyCDm*A&3{1iUZPghSi)l()tDE?~j)9M<*t$;|`-{uh9 zk_5jZm*Acx_&y`Stx50?8Qhx;A4B-IBzR{Y!K;(t_I!e0k_7)EgZq-;ZTP^W1`DHgpOggu5rfZ2f?o*{YXnCU zyb+?*2o*{2yBU0D68si?_*5fIPJ*9c@L5UlZiql5Oi6;1W#*X_egNZ^u4^&g^Gvbv z9>FtRFQ7cnq!`z*GrGQo@tbFgjo%2@^X(X?c_zg;1s+{nF@Ew)vGEweIH79*<0#J* z8!r)FzZ2sk&lDRMAU+$%>E25Ac4ifj#G$V)h)tIHc=6*kPV2 zW^WPh60Spic_xKj@p`IIo@Y|n59+0>2X>NYQrHRn1zm@21XtJtujiBF2_Dn0Eaxvn zk9nq;9wXdkYRg4IhHCQ^`7w$-aO4^ZCz=ONG!Gn^8^Vd^ffLOGNA3yXMDxIj=7A$i zfpDUE;6(GlktIkt(L8XXdEm&>C!A;=IMF0651eQoI2Z!# zBAjR*IMFq?1Uekj^5BnDcE&P9#54KRR>}=>*a_BnQ4EVFgkf zQV$ZzoV7_ZhcHM6BHH~|kU>BA*pK9Z41!26BAq}wgCwG_HYAdbA1R2`gtP+b1p0jv zX$6d@8>t`ZAktB!6FNaKK@Lqw-AMb9NCq~vLw47XIhABUQ_9eG2T8@*9U4$PqYCL+ zwsW-81Q4}D{82lYTG5Ud_U4V+TPA5lQO`fq{!Eaqc0wCIZ~^;3qVGRnfz*RUvOk4% z7AaBYEs%Q~l5C4-kSBYT<$e;PX@l&~B8iZ>6UonI53_?z49pJ6?pHvpB%3kF2UPf; zjv{}8Y+}CVpD7z?1+r<6j!m*P>T@?zKhl1rgGh&wUPO{*L2@{UlsGo!ad`%EFk>tR zkvfpNk-|uRvPH%C?1n%{E@PQo;(f3>!3Rq|FeWFENH!(gB7EQqfT(^+{#u~}dZqfu z0Ul5VAooC2?rasI@Ntrj#_Ip)>!B~IhyCEY2l6G^A4MX$lgvrpW+d{54x}K`3Zyoq z4y10RexxwcL8QY-2Z24KdtF;2*U_$Xz3%mg1ZqkKsEjMe+! zsZZJGbbwFtIpk}|$B5wHgk(mdzW9-vkjU3`Aax^=-d8{`El3?m-ALqX&LEkftAh%k zL;fb1!snbtn@!Lo$%=Lh0;+#q+Yf+0?Swuyx05}e<^3ez+JV%M6h=CXbQI}Dq!UOd zkRjazB!+OIeSn03$s*VY6hn9Z3C1|#NaPEGl!qKoA)P@w zi*ydDh086B`-@yoWZ&ILJxFBRN?&~xvLd-mVsa7TBN21(xCS6pj5!W-W&l|w9q~kT z6@UkZjECZV`f%ctZr+}W?$95}gxaIsrNF=3ny4pfE`AaEIe~Nr=^T;>-I$SVNTjzQ zQWFx*!97U*Nc)kFBGDXt66p-mIV2OtiJ8U4EQ``_}Rb!sP3L)Qc*F+(VyZJLAQS)3ld{`{9g>UQYSGTC8 z&pK=^7K!}KHun5RU>EK>;!hUdo^95#jD2#GWC`jgtT7|?!c+v^2F z7@jh8YCNl_ZQHmHzoHWI!GAv0+qTVO5994vWH5a6In!a&QL)%YZ`UG&iS3qAd!@n0 zR6;3v-uqemLhu;cd-42jLf+xSLin&LES8x{!J_#wive;I9Iww*+w8=TI-+6yE;i9& zc*9*5QwVqSXYxKobKv8RG2zg0v@g;j03mcdH=jIxWKeGs*qy`Ql;!X@jii^ElAO%A ze@J|&XQJOiXxB*ZPnr5mB!7kenI6@`feMm0^vdi(&-GqlfF4ZZ<(B-?3etZMl)pdJ zqldofb!(nJQjRy4LMPFZ97fsmT@0i@$*%ujcOjqJ$>VZRe^`#kmTh{2Z8BcQ_U9VHxSRF~ti8~$Oh-|SSG=Ru zD~(_Li|P-2vTz>^?j0xLcToN)`5V~r{oMcTK1qH- z`1N+KA5b4Pg?dEz2fQFZb!3|Wy9Itlk4+Q~8@_x;pAG1zwp~r-J5L?i{Q%W_YFkx4 z{8!lgnY%-W`wp89i=QO_f_`4&ko=fyr$p~tCE*p{VyhHF+J4I|Vcbpo?ln_AJ0oyD z_!koID-E-LZ!m)n@QwWWF0&1K+8McrKd(01;7@i&TKV%~Q(x!++OIX+(CwX(*&IH` zOs9-?MsmBuYC#)0)y2nO0WYsJ+i)zO+7E>efqnspFEpTj^m~ax;ycSBwRc0CG=6g{ zB|a;H65JGGmsr&(!Ha@Y|JS!j_^bWW{l@*$eM7%=uXRT0A3z=AlJ0Oe@p*MXD!UW`inpc+8OZ2s(O z`Tb@a!@tKCX62wGN5AzMGx{gh%bp)H+i?0Ta2n(!LUD;5^#;X(J4!*{Ck}v)?04&( zCaJy8+rd}AFwkJa9dr+ug0HYJ0BVdt);`0(Pn7t*UzGTG(hffF76)KI*}_3_U|%WP zjR*q|<)fZHVSq+YD0D!)qtS*FvXQ|hcJO^z7PPj$R&rh>zh?qr@Er4-i`Jm;4h!c&j2|*E(|m<&R{r0I4ll4dPwSb!y!oy`}~qzpA1O%hx|C{6NDYzVw*z#$P_w=8V-pM zHv*sf-3WTr|3m`9Yapwx@dzlKzhk%I$<+%tQMx>d_walQQ0Y zhe>|N24#9h`^+!OcqG1l%%7h>WQ)W98vFyTR~80g_wbXqbH0}Fem>(Qd>X&xK|LLHd=yo&vJT~}@U#kDXL(+J><8evf4>=_GlVnGr2Qh4Dr}(g6;`h#( zxWli&epq|^>Tw6ZlPf`o@mp01x`V>N{Z1RLD?uOX-wFQR&MqmP;|A6FoA8eQKd_CaDD-JBENBiUtdQkp=IN+VmCE5ynf=K0bwrQ#&mA_<9To;A+JHA84m6nY48QjSLzwCNK{Md+bMAl*_HuR` z+Q$FK#d}1QQ~zv`%X8wu^QDq}p2?T!9yQ_qlrUf^4Kuq~V#6JNl#CBUP=q~8^6ruB z?MXArpMxCCsHapMxGx{_gWL-Y65c+8^lZu}`j9x@g#Li90uk_h%x@;#(cbh*$sTec zWtz8US4#T1RRlizXWB{P$NFoX@+*Jil8+d+?{l-8MDNV+*>a`t^^~`ZF>(-cJ4z{!{9Jm>VR$Ly(~JeYAdfv|X}?JG!O&?ry35hNEHlvB=c0UV!ZW?6&D`s2_F)<6-UJu?o*U z7^f|ugT3XpfDZC=uL9jpabSC=jmhJtbrK)D*GbPz`)L2xb{n(L)^^F>_H|3o8@r|F z@ea~oWN@Ng8V{2VXa~<>V>?fx8UB1_B+A|bD+N#+%Yfv+EDL0 zVL;!8dT*ie*9Qj}7H6-*Gx#0P{LI;H4Xc3HE)2AC|FOnE{6+?^^GkBtSdVf%BlOAw+R*Ik^PxM{p7zLf;qAp@f(X@ zrt^4RMe#P`KNrPuh))vt2Tb9<{=PTDVy^!HiwCjKgPoMfkuhw)CvqfLl;XLG6kj8b zolWr<#j`W99!4C7_zUZgLn7is#J?>5`V$cIa_u%8D2|-+@wjUY`4`m7;$W}gu7&iB zeE_UK`}@AXS7dRq*Zi5eLC|4eBriyI(yF4?XV}r+ZVB@ZSs{ zJ_-vyD%ScBQ2P;!pY~f!SHV6{{Eh8Pts;1I|75iL9Eo--KV$zY3<*T{KVbJXPwYmc z^;(RV=za(7Yxu>>XVG}@pNj5hz<*%>?y&f`Ib`3bwt;Uu1f6Q%qLS}h;BML{?^j6M z7l)<&2kcwq@O_I73Hug#Z3kfQ!eHJ>8b89XrTrrF>li+?1QD@sK}YuU*gTAJx`6Lb z==pxdM!t`+hL0!s(JAO*zv&QqkNNXv+MmF_Rx97XC+UgvX7mSezpP87u|ooDPM|{KW-v>+ZnlrKVNQw-QoF4{=Cp8m7{h13yIs- z*^C0l2jWMzkCA1f@rnHhj_)!_>vJD}u3+)k&PeDWq7TsT?cj043h9ph6;{tBEdJdY z>1~qW6FL0yZnTenA^vCdV|YA&CGX$DCW*frMTzc{*dL(v{E}syKh#J20HOV%aNpsO z*yWJQZ?;MIOL%>gPttlp__d4V>o|3!!Xe4C$|=c_E&mb!yJ_A<|KW!*ZiKB@m}#Dk z?He?hX+6Q>0?1X^y3$O30rPiod#GnS;{Q^VH?j3Q+aJI_B*VY16!jopL%j@s1;#tk zwU&b3jQJDuH$*62N$VZJiyTtF8%jaHLL9ipB-OLhg!)l_Wj>yli31kCkAuCh?WjY% zCLi>xgn=vSRUvz69`R~B#&1`*!_>t`O7G^)>ced_c7lxj&|00Ij{s#%4^tYS#i?F`g8~}ZX zIDr1J@xHxL;&%zH*RZ~k?E>|nzQF08el!pMTls#_l3+NC_-`;t^~w6)PVo=oSXnOP zSCG6SgZoMS;XV=j5sdDWTuxYT;`!L%k`U1aPJ??80_ zvWwFLpX~=sToq>R$nEZC>%wD$+qk^Rd%yz3g?ztsKBvciBx`>=!vh~&??rsybTRw` z=?~(^`UmeHc=_C&kQewW;`(i9gPhi3ymb@*kV}WuPmEvK@iB`3uzheqyk;Gh9~+$8 zL~%r9@Cv)6C&-WKZ6%Y>vB8FAVfdrS;FY#8vjCWE3X=yTv$9KOHJG@Rl;T`DWQt4?uf}0OVHx(}4^xDC zaWO99Vr<6IJPb>wJTAxOOgXcJ?C+e@7s-HkCf}F(9#EIQx4X~j)2B~&pFiEK54C)u ztQT2N+-pBl`wI4j0v|lFV~%-a;D61npRpUNT?;#A3-T0uRrmF; ztk0;wt1q%&vcFZh_I=WhHe29ezbp_tn(w`;aLs<1{ha-(<@5Hd;%NQh>{oFwc+@Mx zZsUbKV~2f({~u+)nq0p@XWhRvx^-NE+rJbyU~j|@jeS?_f$!1ytE5ZP$6;W`Z`dd66NvW4>Fi4piMJyDUMe%8TppK)8{|D-gq_lsi5T5oyL_bPql zH1C(hMo1@K8Vp(fr+?(v2KGIcN=o@R>jV3~AR+dH^u(+L?WW$K#=g&&{Tb_6tXJb> z1D@ZO82Pf$CEg<6Z}nBSh&=*3yf^K*^fTYF-z8X=SGABKjy+D>h3%IGXu2T6Kf=3K z@WoDFFL+|7=exth+85!GzJdkwJo{f)zwR5^*H!9%QQ<3}-8&9SUB5tm^ndsdMEz4D z^|$}^Y-u0$kT^!WOTar6yy++H&&s5cn4SUE-ys z;)lY%Ykt>=**#r*7b-vYXO_71yqMTG`!m>n<&VFmUD}`D)$G2{?Girf&(v=+dwfMq z_`6^DTlZz`e|3Gu>M@-@n!daL-H@_+fX6zbKl7aiORpF}kHr3HUX9@u;i1r31I=9y zJla*i+}bfCmQ=e-se|`Sjp4Je&fss?{k87jSi%qbOI^Kd1A{krkHPt-!{=#7ulQBb z4|q3c34M_-Lm1D+1f}f*{*nRqS>RcnFg#`j2DiM*;LccVc$O}<@3Q$APWxYZ#?g15 z!*?Ng$UcVoK%cXKFX?fIq;W#h>Uen%kwnWPdAi)7#zCF8W05 zP3`nM&lhU!+2}E^z~6_KlOH{{*uN2={(iWhmX_xE^jlNq0dr#SP8u$zLBwe`?4`;Dy~!0TpzmVjU2*S>Ak3!kluk>2TLpK|hGmHHo{e^>D> z`j@-((s3Wrhxtt9NVB8wqj&RJe%hW+V}EO0msGn6^iqCX(`oH)?jF(iTN6?b`{G`q zTPC-@#oC4NU;{Q#xTMqQvUL;qf`56J(W5K@|0jjtCGG9D@u&M4O~=v~(VragS1cEL z>U*+Hj)TT_8PDf(%_+wFVlTTWF#MLOd{p}<7SOc6WSxvZ_DjlN(7nt0Z&3^FNI}0% zq}#D4H6?+|{$%+`fs8|15BjhAl>zlb|Hc^jqQ?PV#4pMZ<=cesCih|0JEgtna9 zBKb;dtRBj1z4(o4eaLn3$jgfMHN=pcAJc`#4d%aknHM9tQGLt@o|m6_gLO_lcx?1o z9fHSKJ(j;7`|p^WpYL6NiPKIdhsgR2T{IF=EG@o-}6E+Kz^{kY}fB4Q|kBT%6_&+ z-kZN};=MNJMOioJd+1?lN9=gaV-MxyCp#hji8JWc+jqWnWM;`UeL~KFb!Mho)^n+@ zoveT1%e39KSng5U<|2DE?6(FxUC$g`u^1* zk9uzIyT|`_w%Av~iBo^#>{lDY!1qoRjlOz3Ap1n$AIbNl6Q{m^ETEqz`Zg!Azl;$- zpzgP6v47=BJ7*lVa-~P?d*L4ZZujDMy$8SBy|KefeJRhnuE^{_w*5)_YsF5+`vb%1 z`&j?4ci$KKrbk}l()0ZQ{=m=HrEUK*=Fjs3n%$picJJr%j^vsoSor0XD6eutblyzq5HBIo&I{Qv!5&) z#;=6-Jn!_?f8^|7*yFSv0$s{~uf^a+_PIX}Ed8h0V`LpWKV|sNa(1%{htE-I04y}u z*-P3|){ccOR{sp($-ZYvtKk><)^ar{hS|$EFwtmum%d*bUr5PXa z-x^=+Blxd%nq5SkZ)N?AT#<3R`=n2MwY-kw-RQ+Ny{L$C9h3I8Rh{@1k&pdLiR>R% ztfM~GS58jtUbPPYqYnHei@=9<)sjxq!2&Un%dwTgz<}eK=|CI9mQy=NGkRb->ZAnH>Jdaj|Paec?dU{|LMm8Ar}P?u4Jk zUr_zYun!e@UFsLeIAK2{_6eRtu>m{?v41WPb$yF{3qD9)Uv%8TX#j8Nae8q$UFfl~ zCP04Uf8D}+_G|22!~xJhxtMaaXNmZy@qLx^2gLtD+xvr~R^RtS;P8D(5<0UUm*ubM zf0tE37wUU0X7w*~{y9rRqsy!67l=RfYRW_Z&C5geQ?}uE#J|vDbUGQMpN@Lwp9`F` z;+M`1pIx*Edp~k)<8o)m zS#10u+gEm=KK^BVN4}}w2lCDNyZ4>2cK?+d|9sbR|NXHLI%aa}zdHu~UH%_U=2LkV zz5n6ldzF*XdyH3Ox_@wd^Mm67e!=H*Q{cgmllVmZ_38 zkTmu;hu>0%$2Xju)A{SG*k4oFZ-ImVi^{>%t{;Es@jp+Jn2>-t3 z?3b^VfERQ}o-lm8-HVn7nt#O!vx_mFly6U#fY&(VXE;45@@q{U?~xPB+Rd)Fst!7+ zys>$6qtY8c-WvE;__Je@f5d<5q>K~Z_qlOV+amNq-mDJQ&-(jr9G*>DKKK;6=>Ab8 zxF4_M{h0dK>Uep*3Ha>0sgLrEM;S*vPqu=0ji0S8p*;LHYz!>^w+bJ=P`;+T&k8i# z=lHs`Bq$KNyw*fM+TGklee6H+eI1kie`5>v3LNo+<+-4hr@-$G>VIEedsZ2`$@B&2YNpO$Zzfd9`?*t8Iu zpZE~pW89xTZg>Mv`wf3j+Rc8y#Qb7crQvh%cwPO~nZI27PmWlA{EAiI(I@Mf7W`j( zX)pa8@24F6xuX}jwEwjsq%PPV!6p)>*kl0dT07;RZ)Gz0Bj<`}ogq z_5=1cncN&%hq7L0-AQ|h*KmH@8`YmalUpS71MlHyg@^J@rZE5YG;{{;l3p8sZ*+*9 z&g6cuJT$#vTDJAbhar2eNduoWWrYL%6(ffK0pU~LS4#i#T;DJ2?bP)r`|bOD!LMrK zRP~6xM|(tZ_8v=&uKX*U9*62zA`NI?VejKYIl7i~sSH!+fXTsvC^{ZLQQp`=Ujus9fe`-% zoqy8`{|Nb!S8E1=*US3r2;W(+tRDu>V&?l{-j8_M$_C@36;57?^IjVKmpHhK9Nq9| zQT!`q9tS>r$b5=FWry{DTbHyalN0nu(El`d8h*=M{c9S4Prt2FKMDHL)%&FD58@42 z7k}Ir`46-1V!Zwq{=?fuAMu<0htZd-e&#p(4~MufV1E;P0{dCrZ#GT!AGZByweIWx z{r8->n-$CP3-gjTlx=+{dr(&{IvOx+;4wQn*BN7I|8Ed$UkN* zOp5`XBje!p(OY2uB|r4{cGikMgnadK{sH+0_ZO)DU!o@9Ge-fa zpHdpVnaSZwomW!V%bZ>$KS4gRPx|7Qr16t~)sO!O=N713ZFKbqR^N?sKh*cl4jyp8 zBYh*!3k6;#HujfzK>VyA)b-VENg7aD0L17dtqG4iDeH zn;n3-`Fr*9kv>h%9~i>_K01GZc5D3lNvA)1J&IR%@#uf!^l8N|JtXS~?C%%GD0jkN znLj?l`ZYcAW63v;pZ9swoBhO3VQ=Jjoc?SN z6GdF}pP$s9;TKSE{XD{RGJbuA_*b$2Ag*2a9}oMcH$(6LIsF;u8mRuP@T%^!C4Qah zV`OUlM61AkPUdOgUUGV}cbq-dS%gRzl*zpNkstc%lz-nlIOGSNponCD%Y zIF%E=#GdlV=6$>{swtJQ8c<)_@8{wIDgrult;SYGp#?EPmuJ z9qG@AS7%)W{oH%}ZQ;}Y%r2L%aQ>*(E*<%6s{T&aKkofp=bu^c(hIw-o+aCCUwFvH zam>r|o_6B@!1Cv~KdamHQO#Lv7y3YjAL#-AB5-=JK`U3`^jE6CJD&F22Cdwd1}pbh zZhzC$WN;Tcy_|7eFjXE;4t{h-lzrPHq+ zr#ucA{yc~OnpUg#kK3)>79H>XnWet9ub|VOYrE_zrPKIt^we3o4X#}W`q7)hPYYWt z-+b^8ecej%m*>HL^r`R<+CRDw{j2V4`ZIGK9{c*OzU^tF$1a!Nk`7d_(Bsmwct4&V z^Z(h=>z{fJ?_W}nwEr35N9gghw59hASo@z(8y^8(Q1A~;!c#t)0*`f8f9A%3m5<<3 zUiAe+KHM+95|#f)2YKpeiM#7$O^fq2b@zR zy8qb~SpGXno}YQyT~)l_Nt|MZeZPqJyp*RLcvCJJ%=D(^Ww!=Ak?+7|ym-5(&p9JlXCs+pXV7K#19Au`DKssK2ebEsiK`FdD;Bg zQeQ##VAlGp^fB@`6lDL-^+WMY(nSTL|B?t+|J~bRm8^%~{w(~OJPRKK&$_&9AQ$ZdZ3$DNpF8?fCW8ut3_e!yDE7yodk1 z-PJ#p^|tPxp#Px1wLRd9&eZiDm9x;jz|!PrUx6L_Cn0o9U9Vjja6^^$3-UqFA7J}H z{J>>d*W>3BjLs4G^=qws*-vfX(zwCWmGiB9`A=+r73p7#S4Q=k-uj)(?60V=aIU?F zK40 zJwDHEUiR%So{7AyZQtm>`Q%IHWe?v5U-}7pe%T8OvX$Fu7yVSeG=Q$D>+jT%?+Wpc z+riH-puB9~;J;}V+V0{Rqe|D(8KdBnx;{N`6n!lHvDV7}`ZM$oqz}bL89%1K>SzDF zwHthRUp-?~`|FNx`Csm`XM7>^2>I&^g5Q<)6tCr3lqdWHcx0?zN*Kz-1&?0M_w%IB?r3g;W#L$dD${$bG{@q9tf zr!f2hKj<%dnsTv%Y~j>teE-tJ$080h;g z$V-vam2)XayS5`=CH=iUCXdT!S128jpCMKFFYh&Zja-DU`!kU|d}heX?PuId`4<{S ze^>jjsiH=n0|!9n>YWIv|6K0<+fM$3I1nMfrf(-7^%ia+8_zpCsRubrJt{Yw|JBM*f4D;BQ?4uI zy0p~w;eHJ8z%QO>d>?;oROQSai3f+j3m1lSME=E>3cl3q;1Yhe@L~K>>M1G6mK9jJVh6YMu~Fn=>Uw;hjZefs3>4nA-!$$Iv>ovR-~;z%(Ssn@ z%jR4CrSrju@mK#S_3$3p62jw`J!Abxew7398Kc_H`e#Dz&(ddkNG;rmSP1>fEuDlvLD&*I5<#*y|zY$TK+=r_){`b!Id zD{@HaJU~3!`QZ0u9Tw5&(D{&b?C_;4A{W(uhy*+0Kkw#M?H9()q0da7BKK!Y`y1B= z5QKi;5GdchlMR{g#Q#e*SU!BAbXzb#m?in%x@_aK-xly? z-NU&I+HcLz+V}hlqZ8&6_VAql@ihHOx%#J#U#jytf{15W*ORv+i;Xn%1YMVYM6nr9llfh`MYp`U(Vl!ETGgY{6k!1-0u=z1_sIo92M zY3iZ=cS`I%$gBK1Ao0VLJG_|k)X#cZ>%krx_fq%;v>5-_&bR(s)XFoZ`g`TyUnMC2 zDd$^>U5fQxPcX_lB9mKCW%V3L01titJ4x$z;`_A!rT2w?7h-%z9yZUhb}W$dW3WGA z_xexDm-9%ATz|~IuNexM=Q6nxjGv45*6@4CihO2;Y-|anbiMw z15fFvnTCe|7yEx%I{1?0ub%@v=ujQ-q`r4z)Js2HD&fid(k82~GT=!)Ccf}r?=t=S z`p--+zsNT^U-)T&oaWrJ!ui|3AYvhO(uKjJ4szV*(3WmmhbZ#fsM*Ui7S z0q~paf;@R&-X-g0?CqVQ&fA5VK-crjLVLdZf$%-&n4A~BVEx1fv?SK=S04$rUG?gJ zf_;6PwDVl9XIR>a{YmXP#I0X3`c@vZ_flT?XHhHYlW#$h^y9f)Stgh+_Mn$D;dFVw zWXRUFb44#!_w!S$t_nQyAK(BN%2)3d{rI_D+p#cT>`eH3vA-bB_n6R+_~{1P(_l5=^x@BLVNCzbtGxdvC(#xE*E+#{h$E+n=pP@u}RvQ$+fKuz#o6`VT1Q% zCdik4TV=-fGaF;H2Rj_HLs-yXe=Pix*sV4vfrA}uR}6dy@z+lRk9G6bkn;VEYw>SO zd)a-%A^pw%3;8Gg_jV@Ge(DqZIs21knLzimFCQ~_xnuVI@G*N|7`pP{{#EI}Q2HZv z{ZEGNdBB4n4dO>3dR6=e2c%!I&khJ*qR$?X_5xp8Px%$j9@ggUU{7|-{sz9?DfAT+*lL* zcJ{*)Utm8R!#*#;vJg;=*u}g?UKfB*dD;&={KvZ=oji;l#=n_ z_K#!F`H?(d;Ouv8bs_yte+nM7Yn6lR>brsehr%0waq#cSr z+oj)(>Ww$>9{5rHvGk1N{o^nCPrCYBtlW)z?T70fyp+{|{f&XqoBD-*H<|-$ zcVut6d;XB>&53(15jmR4)jB_*(kkSY(9zjJYoz_uLwT()I)CWlq7iwYAwK-St^H89 z55sruSJ-!;>*V?A#GlRuuP)9(&ht2*CyF!2pIzga(f%WXIX}Xkr(ISgd@kpnn;mk7 zo|m0D1z$_U<7Kf>$62}KYiy6#^3I%>`2%|UKdDRvwO*3*)f4DllZlrSffP%>Sym*^ zTYUyc;^cvYU5?R1`0ty3X^E%htE2PEnSF#0D@(juuZsFg(mbU;d7r*(*TZ^>`0{+> zXVh*tvxI(-^Vv&eJnDG{?G*UJ^U2x!hS#M1KKd)pJ18rG&loR_iZAijGERwurCn6h z-B;ZdD*xe!p-(%$eax?P?TgwqTks%`VrB_?rV{T}#mmk;I4^O~Y$nN?cn|p@BrMSL z%ch*$UhL=)ozu=@=l8rK&NKJYJQFXubJ^d#YybQ&Jh%Nf#XrL*4{7{!%j2 zUB(0W!+)%xj{653T;g-#L+|D;7azT#mw0H-sqJ&|(Q{nf?mjnuIJ-$qKz>^<@zI=9 zOWd*V?Ie!3igC9m@zRDC`Wby@&tB#WnGagwOWM6u&qtVyf1Z)D_}mrl`@9y3cf+6G zrRS!k9r*Jvw(@IR#IDkx0XX1Uf4O`sTf~loebc2YG)@=4f0tg^B6QC0+Om!Mz*CHG z;{O}PlRnX9-ceDB8ps)TZ^ z!)HprbN<$nCd$X~hi>A19Vd9Q_=JFW^_TTZfBe>)sE74D@w*x)NxZDC`(}jW-1Y1< zy2$Vq{qNJRe(=;cE%a?FVi?|^am1$Eah7nPxLF}yGX`OeVx_AIMne7?lJ@TwKecr2m7Ud`m0Id zX}_)L0?*~x%i9^x@PBnfQrCyg%LPBtfB4^RKgj+~;;C0NK4ctIPt^bLGyFFh_guSE z@FCr*_F?=_`{e*LpYs`khyABU>PLT6m(=?2#vwcVQSD-Zi=IW|of-dn{=pFT6XHkt z*}4dNmrC6Bl=^JFG4L~ud!}3z?<}3~F@G}8|GxO?MDU_`=cr!RSHDktCGL3$IA7(y z8E;y0;74-)BQ^*>k922#>OV8@|G!C}b5AV(=tdM5omVpQgCA_|N@<*A<`08P#dlz0 zgy>a%e*Q7RKXaoZAZ|D>bE!HQ@lsXZ^6IK&UW&Nb>Z%dD!NZJio(vMoL!>Z}PBg9AO>$Pzpc4&=c^U551>(LKot=1TOc;fX>%<=AoCl z{T1(sl)L>q-9I+Eoy&P{ygZCdl{BKLE z$X^A2NjZu4m3Xok>A>C#UXqUDzVTm!kG%Le;is4Yefov|o{am3-WvBE#Q}3~4Su)Y z8*>7C&Xu@a;*;g@U)tbTNL)7Q*)=ZiTjIZ`#C?O$=i?^ENx zrM;}P+n9&sS?OVCChog3WAWaxGz)O#-@S3)6~VN?V?S4)d4z84+F4s8{F!$XI(}W^ zyz6U(pYm=h^(l}1cu3;DiC5-*dg9S4i~p`weSy~>#d~vp%|odgZ!lg5U5E#l?ic%~ z#*-5dQX8Y6>3{SL4@rO5ik^Y@S}*B(^#>t-xy0UU_%rm+7ybn<^RD*$qpaVgzv(x_ zCvg2+mxK;6iSL$itnvFX%Fot#aE<%kDesB@XQ)nz`wl)I_f7fZk`MEp{6+o&V$5#E8lj$2cO#hPUq!c{!H7?_?apAJ|OO!aVFy;ha6M-Am@}H89&Q;kMop- ze=~lNUmz4riQjIO@0$Msao-Tf@-zO%k$+Rl1yT-pBA;_#6!#s)dt3Ooj32@8w~zad z&VM^2+>891;=ii<`d1>qRhdux%y-~h9p8m=E*x@b{fLZ*?)PN zI^9($@!G!kHkY<2eEetO9Q^3z{j|)_#rZWhLjOzoqI5^G#Q(Q=8=0@rqjaGs@Tg~? ztH4iFFZvZdAE4t~ZSH%>;dlJ;3;cflUN7hK1>TLaTO*W<-7I!b@ct5XxPklypUjPj z4pNvu(tf2&)PCBnbSQW2&6n{{yX_ph%+Cu|Pm=jLtG9tP@Z-0neBRB{Tfpa8cxwc| zf!NLRB=3{n+i{mhjtiW(OOxcI{lMk~Y4KknAL&9l=fh85=AJ5wr!Tk3xqHcm_ew`- z5B!9FiL4Fp75dXyR}D;XdO6)c!1*U>;0{dWc}AzG9Q$O-ciqh&zx`)I-&~vON4nn| z674bgN977{$#EHZH;Y}q{99F^OS^>LtPcw19J9KbcNIULES`YR_?P}W?yvlz)0>_Z zIfgzcy2q<h7%s;A<-0Ufuo_YR3aHRlP$ zv%*($o?kG;!p;1m-^E%=Ck_k028A6=mQna(i&)lZsx0o89BJ&UHJ^959Y0Q^Y* zAf3f;_q~47s-H3aZim4CXVD7*U-lnq*8gufKj}-(Pg?!pp;y_b_(f;%i+1|5{o)sW zapKe`&MzA82VFnEa5WIPH~;0+kOzGJDgM!!*x{dk8qgo<%$AD*@s9YRek#*!I-`Ek z0~4o?QGAU4zwv1RU!^lW>2R)hCGk?T^Rq4(uyW6R8tQlTo96qaYyg~zJ}Dd<8}!#o zzK@GW|MgR`lcY22#gCeNFAPe(6McQ3Sv!9Dspvz~nSGxbejI-wfBYR+?fb5)_P+9( zrK_)5dgBL{-u!{3i3e1=P*@z0{ZjDp!&b zaE~0}V==4eEq{dp-?$#t!fJ%+k9)voy}rBh=vcUA6cB zOgY)7?z?K=|HaYcA6>hj5k7_v#2+euMf~&bH7o!AH7l3mk9O!uE3rwusrWW z&M!cEU;-8SFYEc;^)tk-ox1)}%={ES5kG4DZ;EGAAO3uI0?P5dbT;s@r&9Qc=r8cc zRzEfTuz`dB&5S_(ISR#p8GS(YOzOjbgZ5!J!Vl5;_oRW(7T9l|Yw&8t&-z35v2(4y zeCPjLITO6_>)BNWeDR~3N&Wa!r2EAJ3jFidpLeRj^R}1$sE+*jtGwy>Vfv%ghabc{ zB^7g6C*-ykReVjJvVRr)^B)y_#lKYcx8V2p*g0wErQVPUh;xu11wZ^zpp$-o>x$V? zjeOW&V-=cj*UnJ=9(t~X&=Wn|!cgg1SuhIX_-C!Obo0LoAu~A#zZ{PE`gy3QJW9snnN7r)ITg<+ECxt%+dK~;@wVmLNd}3d| z!Scn|TD|mNiO|2tCqHyA-y-(b)b;7&4^4k>y+}Kx-SbF8pW?p-FZ`K4>L7os_)qK4 zKP3Lr_>ZdwRvF_B;OQTJBIV%DBScxzo~bWrGT-zi^)UgBpDFw+rzL z0q@GkMzvpR{|UN*|MZo-mvQ6sgfC0i@*HHJ|4-7t)_%di_@AIp5&mNT1YOV@;|Hty zf z3%0+|Z#%=0e*SfTz=m7yAsnriXYCg9^S=6N>V=PM#h)2JzQ}(T=aJzw<*T;`Dqm~2 zh#Vnad`qC?J~t7vk2#m4JU6T02ly2485h;g|5m6!ILQ9}Y0A@|v;#c6-S`u0ef3XK zA99QK==-+kt^V4%MjvRO7WnnfZ?}1_`PJgb3jOfMUPybWw|+kKqn>8x$6Nd_@F)Ic z5`EH-|5o!H$}z4B3#?x1gMP%nJZyAmc7D|L1y&#NFE#>&RcZ_E8(Z_2U0bePz!=PxxHC zcSL@GzxEe$LHn=zMd*zDZ+sEBw2N`8{8T+_RNn$`{&@fmMQkN2BQPSra9+)D8ir@jNJpxV3UW#4b1 z{nS$_{>{7>?i2m-`)TpZ%j6EEY7cjU)d9^3*gt% zg*-`fzeG~n5n}+9z<->p1zg6T_*YX;{AlU@09gRCa3<=^ykLqz+>D*>k;IN(o4KgMSj;V47J}D$@r!`@fBMC zqJ+VTe+RgSe-Lty_7sHl1LJEx z?c%BPC+n}Wc}6GXiqZ>tsQ49%KQVImlOoEc3bKplS$X`W9}&8U^Lv`}YbqayuJr4I z`9|Ne$EXK-7x#wc!biUne`3}r_%kcq`WNkq_5}aO0l%!m)`R$mYya%p1K!|U|2$9n zq5MT_PwjTTv+k*W9Q^UG7VE9d-#dEY2jDk8Mmz9RuY893k*~P{p5h1oto6&jz0i?% z6Ca>(C`}CmKJ$UfALd8xXZ)+RKY9kO+=3Ua+@U=@S!e9q!+ZSiC_lmq&d1*^A?v-q z7U)d<%zGMVz&)8-PoMZZ;}`ixGE}&nFH$D{$@uO{7x)(js1H6_&|v-lixSKCLXvh; z-x~>@8PiFH-4ZgJZ-8zHU==_#nNKg;!j9(ee@zHUC zU-cup&I_a+1=%6>zwP%gbVEPj#>J00>t`?3kgvqc&KAFD^o?h0$X|yYPtVyP&TY9{ zzk)p?%*XE)e$V7qeHuV-{Hzy;Du0%=Be#*8oF~ORf`9b70C|L+(UAt+foJ_XAbJ(zM{7oPU3@SR z==X1mo+XjciQ@ND>g|<_eeY^-WKD{P@{?N<$Q!g0~p)=>{IDcN9KPbO-I8guS%^`e%9HIa6W#8VD z1P|=`_*<9AINmYDclHm9?)FZl6&RX6)Dp6ECA z9y9t+e*Q);nC@-joUNlg;lD*k=qLORmK*`zV)l!dNkjk5mr2t<&1vWWel$N6|L4ZE z)XzAPbKZC^NDDsne>y~eiahI-@rHa_EqwKHA96+0n4c(5{LgCn{&C-mb@H8c&uUrU zs=u}N$7a^3QcsVoe`6YaNQdS??QeDPHm+tKqTIdrtW3GbwT^{pN%?W?J>HgYsrS&( zpxAE{R2a`6F*^``d}>FE?&(1(py!fsKhN)Y=u@$O{GP}CJe=J;b_Q9Xb~5zz?4Q)W zT3m!b9QXD##w=fR%+lo3^HK1#j{NV&#eR!F1MTj@-}29-{ipD^0-ek$&c9dv|KpOz zUmAPHOgTS#K>|An`Mtpxvoq3H<^IYJ3dY^JLT^^K1|%o%F^#theS7;VJK#I$TfG`S zHVq+@ne0p1N&n@@Us?Hu`^WGb z#eUQ+{=<5Ijo3dz_z!!H$nS&riGmJxqI|68(6`jtLCWs>;eQQ2!mq!C@#FhH84J_? zm(GWNrJSpk5IHaBYV8?|u!w z_I7g4lKC0?q52sY>iuinbCnSI={JjoZtzKAEYN3qhIBuawC^{5+kT(g{|LbQ?AJRS zA7HnA$a{eO8kl?8n3u5m!+afz{`_t{uda2_|E0-oNh_u8!^d$sJ}L+JmT?ADRJ z`d&Nr82;c#rY?5cTDXi~8YTwO`W(BhLSSU?Q^f-z_J86xgpzojp1#$9-gqFLNg* z70D%54|)#=^6{E z_$yas9D6ro1K9r=f0x|3UT2*gchM^aq4Af-(ESwJbJNc^n#PCv4P_( zfXDoElJEG$saH+}6r1S#8`-zezSqVA_-vwYO$+waGo0Ub+{*vucu2lDb}Xaltp8dGeTj-#;$(rZZAFhW&Z{G0XS8@lfAa9<_Wg zj*Fb+{*I&e{rPd3=h7MC5fzS^uepCcwC}9r^gZi4g}ZUc(wm1YT{dXx@$;;qGo!=il{R+|^cWiuv41aI9843sGLI{rmZnHU zzzgSD&r#o6?6+c9-Oy#v6_Nef48Z=%l-0Y?*|opz>~2e(UHdx?R&Gm;J?DzO6L|QZ zGlY)$Pch#~{T0qW9G$;bFZOc!ZGMg6GtW0T1rDBw-!bkVBOXlc3k#`V>?22H}*2!PcA3{4*L_{!v~b-H;De|k~^pU>}tx5 zvyX|{zT@o@o|h#*^3LG19!p(+w*(XlQ9H*YEA@sqXcHZ{eZ(j~|-~CpLj05&@ z0r`0f`^fnH=yJ&Vmi?=X-#_V7KI>)Q)%#nOUZCUce#@Qf^mdKyi*@%7{-=}R`2l*O zmQeRuHyhw%fl~#&*yp{{%6q=QE_PM$ypRO{3HE<@U&90bYoad#-lZ$-voHJEw*#Z! zDtygzQV$H=hZ4W^xOb2ddAI{Tnd^KJn3C7JXK(LB4a2cPs5=zj!u9z0mEQ z^VaURD(Fo8ZzZ7uc7nuRj zKlujt>=~E;ywTyb&|`x0s!xP71@8+&&nuk6E%c#$OziLdY5(mrM*nw3&jcLpC*pw@ zq%3_YMf>0*^1+|bwUzuWKKiTl5ex|af@3y5PM$FQFV(pGt>lLeZO3ff{G}To3yxa; z5C?uK-*(i#|9$j5wC~@~N@xoQIftF`|FYw28GZhrOZ&h1e6%kUF*?(Y!$ zJWq6=RPkN^9jBlC!D8Ms{@<4Stocs8%qQON73omxL2l@{J}_kZCgFbZ-#_U1_T*}- z_Z1m`($6LKjO4-LLD4gv%e}f7d_s$#m-2_)c^apict7T6Unv6q2;)uoK=F|EfVcZ) zIp>=4uZ#;F&>x~e75Vf1Cg5UsIw<z;CY<|*a-#e#$XR+ZIvp(<2(r;piqy|kD#?V(>|g3pD3Cvy92((==NLO{8{ z-RM+haEP;3cyHB_rk_S-J;QziJv8Uspr4cbM}W`R7YmrreFOzEzRTtO_|?oGdLF%( zU6TOLV$Ss`q8xJL38{yBh7JUz=^uq28zlaV`l>j$sUs-xT8TfA`&9%5`bHz&~`v@O)*+@O$-$;roMahR;bozk_>QHW@y|>oTr0Ie3phY5(vhp%>@A z)j0aPbJF9(@EPOj;4uBd_ecm+ z&k*;&tRsDtdp9yhpTaJl&_5o*R|M$)Zr%_3*+1E4?SMbD9WQ6BJw2VgFCuO-C47Qi zv^`{91pnF3U)Dvw4$eR61}@{Ga75b6yww^aC(!G5g70$9#qSEH3!iLWM}5d4?DxU| z{(*ITA4V_RO+Db(*iF78UiQ^c_%xGiR=Q$8X$f?l#C}BSN#C=48u1q$6+S^f+#+zX z-?o^1DO9?G?{eWM>^5$Fgr1l7iT+TQA1D0E78y^Rcj4;g{uHHukE^G&B~ZIf<>G+; z`?#;XC19SS{|P@phvTnzc*PeBejoR3ba<>1xh3hv0qdgs^|~k@5Z|2NZ1?7UtGpLI z2zp(Bu;G<^%XJ~jy^W?HxmT}??m<+zK6*FXr}{iizpM9cKR~am_iv-u)$i!n6LWaiBtHkGcLO1oB&kL zs;5#t>0P~UOF%u7`q7wNU;ZQR4_Es2_CxZ zzw<5OuLTUiRod_F1Y2BYr;A2!OlyhevvqXV5QAy?3y159>bS z*rIdOqj&%h0U-N_)_m_{1Z#YQ%tcuJ$B+lzMWb!K?87-9hW$O`13!1x%d*c#`@tX9FK(Yd z*X{r7BY$>ZTkX(c1iE%AlrxU@@KolXP(fpY2s8tlhG~O{}(4h#-Fa|VGnP2 zU&7=&>Q7?WWdC6LfPINj_ld|$x)VRZgwQ2*eP5F2UhKw6%CSD_OH$t+<{!i-Q;1*O zVC|tE`p4V7z^&V7=sF9!){(yjf3PI=v-2$9Nxwj6;&&k7u-{l?_+gh&J<2ROrwux7 zmGyE1_f@&}x78SawKay9!B_tcrDwE1DGh8r&{ju2){~3sD93toS5=5#te1`Wgz~z6 z?g@CW^cewt?8brg2Yy(xp5@t7MSa`k-X+72^YEb~!d2mu}-T_ zTD}?di}Zg1{2|Y|342EN6_!q4g@Asg+JEtX7zlxjADG*BZEi5UIR8iS*xbaEbqeRO z_+AzJZ^o~j|Fy*JZyFm6?vZUe|Aw|t7Jsq@ZQLVt&E)!a zhPp00wA0oH2V$15d9U=3;-&TVX!~XTX7tiiG=;3MqXUD%fx z@0`bveIGlxj7!#)=`^jU{k!B|qBQHT2H>`HzIIJGUD{XOg*^&6uyIew`Y@B57X&)~ z;Zy9_UiLTyknw+@33vm<%~u)S_c{NQXWV$9oc0&%Df*f9kjp3f4|>2qurtv1JvW(O z<;G)fJVxuz9ZfQ>xli1cr#LGB?q7(;(74l<63i>RMWf#Q)y{CP>$d65&QOnyzM1@~>x^goGGf`v7Vt?ijzzCj<7fN1 zKT!NXN*4#N&t+7jUt)uYJ#wYzeU!L9GMabfoaoy+O{nNVO+mGOD4=*Q|< zzxxJX;l^UwdXcyI-;^Z`AMC9PZ$u9;2#KFIxb*?}S^E@jX;T0`oOe(s`OoDFWqs|l z|0p3J^};)-fJ2}8>`PvPeYTDd_P@T=!@jr3@P_U>ZVMX%>ZLw~e=ZlM2VRx+H~2EY z$Fl6x`93+$^EIrUd80F+{KS2J718-q^oPbr>N-l!pHh38o*PM64)k^0GvnDh`h$P3efPIn z|AE8Z^&kDjI7uChyYrUVmx%u0V6oWGv7^yGn%4f;*y(D#Skd>w^iSmoE+2mr(%cJW zc)p1r3H>;6DxMI!$$4g!hdyntU!<+jS?-J*zZScO|I4%*-o+mmK1tRPA751`>GI4U zC|zoAKf-w6JhI!5FrK>Qd^G9r54b(+nw+1&K1tIWZ_fL2gou2PbN+wNg;(xfQ-2)dkTL6qd#M}x zvM)>~Q~&md={T{s;rRJ$V!zh>r5tvoc*kTtbS_7GbR5O?yo*UX8eO~l#^v@UddAV+ z&L4r!zb&80{Ywmisr|40%|2D{4FbOM1AL(AGNE&czmk2Xwu|kraIjyVz#l{XLPm6* z&3c@2(tpO+m5Bg*t)5HAGx5?4C)d*hr_amz6|-btfxm_FiL8ILy^MFqH^9?+IroHq zlXDTw&ja4|!m$BA8fU<45c;xz)$vTeD#3&D&=I?F4E)W{L&slwwR>(Ask{DaS) z|6e-3+5g|ue>x5}{lN8`-Y3s~D6*e_Kibb7Pon=wPoR9!{nOv-c+H&3KT7#y{>sQc zujh6i)BCb&E<$g{`AHxDGUNnwFJi)c&^}P*8v8(%Yr6RO z^U#m>gL-aY3i*q@T=svz$*wNve~SHTihcbu0zAIo?E?=w{yXdTfdqvqA6;;Ec4EgV zJ|O;sk9z&nhR$9LXotH$>&(Qdw_Kd~>8mm?c{kU%{UP>g@)JKYWcO#CJ|TQE(Kr7L z>wu!7(NVX)$&>vM_mFY!>|F1B;-$6Y_8tF6P3!g%`8h-0XW5rY+&B52X^{H*Gy7b; z@sck+ObRg7~qrrz1E+^;aR(U%P0GP*3kYly@pQ#cuTvMy7ugI z`R9X|yx-Dm{dgm7c(QmE4T9g}^8KT0?=M|Dwg^8$kDt2s_sIQO^w0CUUSH##({#Q1Te!iR!ukSfOhkV?7r1|bRyA$(3fxJH?=Wvs6UyVH}uKP#oXM7O1*%0XW zUp2{-INB(VdRD+^g{%3BXJOYSt?Fk`U_YeyTD{N&T=s?3hkZ-p#zikpTx1jYeJuBO zS-Q5u$^%^lkbkI-a(js@tq^-}>iQ>j)JHsOd4<8Xc$|Y7>%YR;VVTqqY$1SuNA6|5?PZB$p+F|L ztx-3%MQXXd{N>uxc^{pz+N!V8S#_gt@)>K z57jOim-ubIFPLHZ^X2|1`gMn#|4l#sTI?a8O8gHK7~}s}J1Iy1d?NQdLC5lfP~(f^ z3#HxMyF5TU&^Oip*y_Kt6MTuQ66*8wHLjTZDk`6*ebD)2JLQSnqCW~H9_*0Z8&15| zrJc0rBKD{jz7rR%{YU#>7@$4G)07sBD!n-0LFqZYU{vW(Tn`J`Ob)yipF94jqrWg9 z<0r2|^Dp?Z(TjL2?Fa4|(fpNfnjNb8pUnPq(jQg&QCJAze-g8N?s^V-&`+OqvF|^RJ$x^CjAOr(d$fs1BMuh5oY9{UZvTSM zd;0f{z2G;1owJ=Z@oA^`LXVW3TVVRdn;!(K7lB{CB>V;MqX9|##CNOSfPByef29ut zO+Vx0?snvx$lvF?O->)$W^#N?1TgsTPuu&0{q}wfa!~WTatHkirROIl6|;n{Z%nIT zd}VU}cL(C4zX2}e!rOgdPY}sRO*d{gd2?Vd@&vt2>C=JUdsMjJ#<3_*Li+E()qoGg z$Lt{={F=X=B+pB75A{|2kan89Dg9fEhkCacdBS%VkJ1AFQfyT5xv&R0!uxl3A}_9D zuiqQuV~!qgJ89m>-?Z`<_VAuGvE#rGed^~s{Jxj+^jD+H_wHxH51HKA665O&4Wr6W zXH)Ro8R>uYcm2cu1-a*qa=*SEX#UeFX&?7pHH<tt)2^kwePHqAK+5{D^ecY>!9#I@v2(y^aAjJFPmK)DSQGQ zIXA+^o!^Op4|11#zOH9(#Pd)u_Y#iSi z;I+^%4FSRbbhq`}ok98u_@}o~|2XF*xcsNPtzYgWSRw-FRf|rYn zFZ+?v@npC4Q|4=KEZjt|cIhL_%x1s>QR zZ}{A451|X|54nfQLyn1DH9Qp$oxdqxIRiTJe){8-L(ayFDin|6Z%_`pa}N{uS`m-C z8aTvVuX6LbiGSkHS2HglS8KlkUhv z{+Dw_6d&v-F&E#j_$;d#Rr=j=_gYPVLdG%ee}Z;!zn0N|$gll2^)X+@H(2}BKap`n zKP#O4ZvdCLV(ueQyvn}~KJYu`#e(3MeTRCXEAtNhMBLi5MmO~BhL4NyMla2Jg7_{M zmu~JS=ue9(=E!^6Nk6hak#kCT67Q|_pZ?IO&adSXpAKG6N}M{< zz17dcZ{XMX6#d0>pWL4%<(~?*{XN}0p%1vT|0e#jn|xBv-AlG>r{Tx>6z9PpOQdBncXFRvmdgq5Pwzb{iu%q<$1P-=NaS=>j&Y#7sUS%dT-vud*b6S zBuHn7HxEeD&(Xb9;HCVGpXz^-^QVw~6d(3KYJ#t{eWJI=KGZ-y-~vzkbCKRJ$i0*9 z-m3aX=@;@#=@|5@uOTh@lU7e-15fb5PN{MKz}NiEbFAD)39Fy-{K5a)-RD(4A?teL zWjm4AJT?9q{ZI90VZNmI?F|*b+J=DjJpMPhZ>4Gfk`9rl-1oFo=!CzH%#%D<`NA*k zLnL0EbuWJ2oFh@ddG3F%egr~2ksoh;VEczP0r)|$%G&|`t#RCHcRNsIe19_;&_CD@ z`h_pV|A*nv_&eDZXd3;7;zct@iUAFp8S5+{MScmu8`m{WxU$FC54+qoGY$AV5 z{?#r9ZUVj*KgK%zuZGCigunY@;I&|vyiER9{8BH&m(<@T=LmGbf9ifm|FzpX;YsIb zw_;rAfgLMkT#+BUo8q-&$kJ;*v~~#nsKQ_Mp|!j3a-jRQ%4EoX48QXe!q4Y&TQ7(7 zBlD)SGu1YP>?5InC=`T$)lUoiT4?)=#^FHYez8+QuaEnP@BgaU%@+9J*-2dRWxlV| z_EW(Tf6)+fZZ&?XACSM-$NmMrdx+Z(h5o7QJBIBE{gM*T{^YR1G51@}KN&Lo@b^=` zT{9&7g1otG&*r8;`Dj53x`KCOGEn>ld6A#=H|=Cz905){eg-ane$fbUy12*S2+v;j zl^*p$_r?ex(k7@Qew+DD|QLs*-^Exp&H=9Q2Ei&>r}# z>&IF5c=;|wD|Iyt|CBWV3bXOGAeI|BOd z=)NiLiBdf`_wTT8%AAVgo=b~NPmS(gRLDMYd4TNdYu59k(f>EGAH&~hieC9MGQ{IW zf$BXsI{h(sPcZ>L@2fnc{$+9xQ&-XVh}%+ohTbdHm3Rq%M2dI!b+pTqI8L!6qCXw% z|B(!VyZ&=QJLA;8OTLNv)h5qsJ47xamy4|a8ms3+^(O=ed8eZrS-L}~9N^AJ_koaFI-Yu@Zv6SY2Ge}uG>Ps9QV@AhP*N&KmUwb}wa&1qImAgTC zrQ5uQ5rLa2mwN|@H-rzb+#&8d^2@XEYws@q{v2 zN&cxnCb4A;s}^86nE5PJGU2ySMy8YtVh=uRfssqYo(m;TM!|Pm!75 zG)FLjUoUBSIpz0%8}aUgzkH!(np*z%d3A630VB+P*zVhR) zd~yo@^Bz$C|Fr(Sul$w&(Eul=ly7`M`B#`l`Q2Ck!^QgE>#~RP=zqum?`{8P(N;g8 z{PHLCz1KbkeiN_mEx*ke@;>-SmRR`#dno+J9#H-d3N*ug<&*!D6|9>AUxviJ@K3YK z;&)&9_I7>$IsE^7Ufo;%gbd&Zlpk8E@4d7=6#o|fe{cE!oz()r`{2ix>3cntME)oI zGoOF=mOsF%hu?kWYyM|_|GDxDcy(|2F0`!t?kk^KuJ1j;S$+clf8gJ}<>#Z#<#%8C zJ+6H8LCT9rxwrh^e?j?Mu6)y!{(JlZ<^O?qlEsEG|8c+M3j0r|D=(=!{@n}z|KR(B z%J2MN?l1o!`O^vD2bIruSoz*5@E;_9Dj>{*%D1k%zx;#b&!=Bd{`miTKmHB8y0`tm z;KhUBPy42opPK(c@~0m8^Puv+E5CC}`yW((9`ffw<@c^J_CG={V zO}8ymO^qRtm}W2~l+XfvlhD&>{t`+OAhiEyP9i@7e4mH^J&$H)XWn<(&b;%sS>=ZD zzx)1wjQ)N0f8Z;9Kg9nr`nM1C@5kD|amg?~hWDS}kI}#P{}24TM)1Eq|5znIhQA5) z@5kEzvVXYQ@6xXyqo0$De;j}A(qa7X&fkyGPqwF6`(6lJ|Mgow@-#wNL1q~J6ubTl zh>&U^|0w>-VZi;Y5I=?w`swdt58wIi`#*&lKpLj4=Ma;`}ty2{C901hS>j|@#9@--^V{@g#LXWpWHNzf6{m6AM`K$ef)+I`uBbO zllF|X53LjXvHpYp&F*3k-}zZ1^zZxlU5^d}b$w_3fd2iU{pu0=hhhBJ@1)0vANON_ zvma|8^y>%nw@LhV{{MUZcD+!ha>@%N8Z!`cV^`N8;M<^NIq{jm}Jis7ahKIqR6=6^Tx zv4jfa8-^SXPfCw8{GH4s(*OOI30VfPpNbFq^8@_Rn4h1D5Bl>1{J;PIzz6;L0sj4% zn4j7{=+6)EZvy@KsrZcl`u_a=9rWj?;Y19{8W6#KYbtn zKG2_^iqH6`@8h2f`twuq8UOQr{3Ag>ekwlWf4+}jgp8kx&-kD3hsSZVkhy>5aLI#+ zy9!AUsU6NLgt40MJRmuE`S1a7DAJ6TX*;H-1`&yh9Qm*+bo4E1sCR5F33d)_%-4|;=`a$A81+P6RZGIf$BrT2bcm^AR%}?&w|bx^HL!N$ z;BhtCg{rj*6%MSl&_+lw{jUXI4p0B-&w%9Cg5>tZ%%W6{V zPv)1@uOH`(9~&W;_Ni7){osaiMuc3yRM%?imyWYFwH{MMCJl}=ln6Nq#;5wh{&7}4 z=@G@%{WlP9Jbz;SpsFTo7M;b{w)KvkPGoI5sjpC{6Mip|Eu>ink!_aJS;}E4gQZ-S zGLcHF$_yl}8HAHY@eLd8-m&7Eb;rc{8)jdHpEG-k(@#}hin+s$BG-4ul|s)RrNT@;c3Y+8}}1311=el5Y8rdB5Tdjt5ZK) z(Lb<^6!+iC^X(++oJiRqwSCd2Bhk`a%?5ba{ z*7ObLYkGQ=EJmSlzs#pLu1hUjy{<36dLKABkF1iLBnqky;nEC$ybhlaic8 zO57)Tx|;N?U6MN5^^8`NkCIMJ>8&BORI4Gyt=&T1y1}D7wR#ff`H6&nPVQX2F||Rp z0`==P46j7_6tDJEQXJ7r`LsK>hIr`DIawJ^ynGEW+ck)vf(yS}w}!3lME56c1JEGI3R(m^8bBEor5=RXNIK!siscMa00 zsYj7Y=if+Zvk4a>QSAnv){r_qIfrZ3lnPRBBwbF{9w)S3Gan}+<;t{=xU^=DCDi)~ z*P?>9c>f7lo8ubXP_`R8oUeLOmlUnaxwKKNW^R>;q}xqtz7`Wh5e5ICJPT{YRlRX74QT`l<` z>fFisBJP!5?@liGGA(z9xGMaWxW{5VLtF*^Slr?oll|q&SQ6OHO;!~Zsi$34^)6CoRuNIB$hA61rS&vvU^EtG zCJ6cURMv_p->9lD@=^PDM4Tly;QzrBv6$Mbrgo4D?P5X)15u571M>nA^(dZ7Xj%n{ z0EvDP`%N1}6FIKv13sdb2jXsy6I;BNs-bhikN9#eNrO3#Kg>_Z5Er=)!IsH!(g7aS zM?ICNeO!yK^bkM55AgKZfUNX$9M@Td!8YsKWR7kO@KH2bpjTG%_oPw}6*am^Pq7p# z@|5cLi7jsLrKHYD%7lC^NF78dbvsB)tIAS5oaBatdPD8R_U~ke*l5B%PNgm) z#4tats@6^F49p34kV@S|C^Ne;Psp=a-g`XD^Ni>CFGGH0IxI6>0QB~7mD&W3UJ{5J zuW{0*5GL7FyGig6&M-9wm*l0lJidt8$i>WSyb1FSPq}%uw@o$gz?_SXs=3qKu9`Mp zwu?F6OPegElZe-P`G2r?EnVmJ^W(*O)w<3r-Yqq$)^6`8>rKjk94Mp{cy6+=OvE>_ z+1ySN-0kJRWSHV^FHH+{0@q@JrWe+T^mV`=QD=F}R3cWHyM2BtR;w?1`B(f+IF~rl zC&pF3SgF43ZE{#A9^v?^LMI;K{QS>>RxF0@D2l65p!s}!Ol(q}$2hSZXmOwge2ot4 z!DB!ZSr3qqMd1)m94$uFF{0q(60YeLZt_Zts1S8GQh&&oaDR#Ivf3Y7L>pZT*kK<( zC<-|L+`O;Rb;7=IipJf)^Yj2;;jZ`Fn!?s*ibl1g1F{wNk%(67qqU6cKVD)hda?{# z4okDaz&dHy6m0|X4f@01@Dc4Hbo9jl^2{FM+vpE|!#8Qlcq+aeh-g=er~^gz2NC}< z{-Wnbz<(b|Xg7;ZntCcHKF618lcUmdj^pnR0NT}5Z7Rp_7ik&SuBq%SWot978k1r^ z{;PnYsm^mWp;t%wBShLr5^5!(Z$w8qr<6prQv?C0!9vm2G~;j_-Cd>S=6piCh_s*h z>CR}KX74BB^k`JOC7RN#b4zqhSI3d+J83bn z56iVEC+-2bp%-)FE$FS6wU{tQ(I&&3M@AXj!5pB)9+EKUl{MPQuYd^dq0vdcPSS?8 zl=ty}3YeOyG|CJupc6Xm5+Hui+q)=cYgQq+W##>Uuhj!F*`f7HB> zivhZ5VmUo6DmyKd)LQy@A|b)Q8AU&Wld!Q4|Dv#0zwNG`*1fxoLGdp zwcIW)*g|ozjAt=@)Pr%i*a~J&8?M0CG48vNr(xVibQsz!X0dHsNze=XITE)=fE^6_ z!B~KT7>*U`SERyjEsJOsqVPI6rw)O)0`zVn{3~CRqup1^zXc|442?UZNRVZL7*$wN zoJfqltJD_y_%lWNR|;_d)ulqCAfAaHJAe*lKn$?|B9uX3D`;opbs|~DeVY`1PXJ!3 z^_I%k#U)s*gMkWnIajE{1^*^qinlf(%E;sC9RYs05?gbf|5<>rK<7kOJ(tV{VQ4k~e8WupQz zBfiKxx&jIte4cZXVo_`*D;#{$b5p(hAa%Ae57^gNE)%hyg zP$iDEhQW^X0fTK(jSX1F%Q-X)%=zVXIajGhal#fFjp(yug|YHi6(&soQ*~5-MU3eiG|yBl~rIS-ww6w z8m6YfLb6i7MsC#YIZ~?%Y%C+Xxl}xk!$ClC;OOlH`|${pG?Y>vJ8~h3>Sz1JEws_l zP77lSRKC=>r-a9lACUQ`AaD|TQ_$57^9WKjnKm2NLP94J@jQ+{!6Q(d!oi6Cnuq=w zaPwqPiIv5n^<1g>OjKIWal!+m#<$zsrSxETxoOqcW)z1x3w0NZ)(LE)F7Ow%IrJlv3JI$8%CNKU& z;FDaT(>Jgp;|LPE1--XSR8qrxI%{<$SxyQV=IM_3I&3^_U&>I}H zI?$?XR}eZ7;%^be83O-8us~s-4nkOR=8-mCc>(7f$j38v3Fl|v7N`3YdPyXrCwx(N zI}ulMw895rsa1DJ$odMd$pu;GALbM8i9Y_tpzsw5{@Tay4bs;!nriS_J4OeWb5YkS zDjZ7B4>$Q_&^8oC)t7TfFtWa!Yex<^4DIY1aRtP=Oj%mdSAwB54)d29r`05|^8AB= z@G8zV6s#eh*0(6;3k_fsIWA$m?~fR0^-nkf#*KCi#XXT1uOLmT@**EN!y7S%$7(zs zbPN?_HFUZc3D8xp2pGRF!Gm!kc19J6n&T zBNZ#eLZcw8#)Q#vqiJ+#IoG6VFY+Qt+VD_~C()v26o!f&jT&$S8a<%0n|X{ALfY{> zCYHSmtRbUhv}xv2Y zCBmOgC=FmHhc|N}$&l>iZ+-0;3z80!t%i0RBOKekuE9tgy`_cU?oAk%Mx(}1OIkPV z7T&M5(62DDcL|LK=z#bT*J$8u6fffG{uUnk3FD(+)c6w6*6F3ZWf=B}rF0EfX^5?j z2HFfB0rJ33hm9sv%9BgTQ@PFO8m+-~8* z3-}5f!00emTO5CYWjSJs;~!+yyv4B5U;K@L(lgso#xejY%#TQHWDl@PUBh-R)rG&? z)%uYJcQfI}5b;Q(!g$$%Bjc?;WYx0aVY_hMQNqIlr7|ADPwquzfQH55ncy?xeQWh zavWdjEMrUJE=cEZaW^aK|iSqzZyo7nHoi(@rpY_uHiy?$quRt8w=bSp_VO%yI7?62cz!S zO5m+v#C#y+d6fhtkZUDf1G3#@DpUrUZ#NaR=MT!Ddyc6X-2~J!V(t&IHt`Gr`p{9s z@N!k*SlAtMO~si=f@snO_&$w)h(V*~oER}54bo9qcYkV&ntL_+DiPlZm71T8k)5Sc z>YA#tg9jNE|JH^yRIdq~r4sn-!Z7)# zhimvh@!*g>U-fz z^Di8|BP5+HVOu_B2Tzuw<|P6SgM@h%fFHEz;hq*oD8OI;9=xO=jP^x~7>Y`@akyQZUvmhX=r z1(OcAFL0PGrg<6&MaTmVa~GwD$EC+hscl-?1w3EEiBE^3=Js-FaSQ{?)kz;kF|a2B zg@K)mzG5D#N(9HW?DKd~W3hv$$ogdo7d3AR$?9DtK%3$ehIVE=0&sLcVa`ZJ%rddS z)5c59H!2H*vyJn3ekl=u5pql$5}UY;qZh;rqvM|pvD4LjgcUhw^Em2oexpS|b?^{g z5hKfixX(_DX(31r;z?+4PKe2Brai2>09)yR@d1<8EVi4*FkNpJE6qTF|2!ys;%_k- zpp*Q>FP@S_Z-c`9zGfRloxepyQmjYj$wA=^Z;Qj;j+Q*bn{Y0a+~ zEJekUS`|b>!;yLo_WRwzMoW3FGuR5qUk0Tl$CX+yr0B+!_{VX=T|udhqky)D&qEM) z`oU<>Na(=V)yf7VmUl2tp~X@_=I`x6k*7&T-8{|#RmC~5fDiT(-vYkHRW4<-gH>xa z)-_t{0=~^vE@1%ttrNsMf=w)|(!HJKG`rfRgkMtNL9F^Z$I@88w_*Sq>ZTQ);wEU4 zP<5uz02slmT(%CZ)$t|^`QbK>6|+ug>@Wf(h&)5UIdJ`OwaGx#dZ4P%VVzaFsx$C{ zCu#)(QFk|G%*QT?0<)iF%HJ`%B}c<4#A)lxy5t>ho5jD)|=fU=p9 zY#k&U6zD#_)zSdqBuk+aTSfx(lZe2ILB4UK|BFjlE0l=U7!qdkvW^O(W&t3tyPL;~ z;;T~*w2OWD!PJK1)-I`0B2&i<92K5ky8fK$LItT^J5T{j$+e{=O7B$q<}7js;Syx@ zdcAvU%j_!k=PY||o&F?{9grG|0D0=Z>$()oDI(Bk9^=%3Cq!`HLa&XRtf){SGW(V{%46-Xsi%4 z(rN3P{k*_`TvTt_O;AnrZXksLp2@QTURET)E@`U$7~!7);{1){=Zuri;V1}-u$Z*C zn*nc6BlKdPRX@f@*8|?}Ctgnthx(iC%{iPQL67(wq25YX5$OYtgF&dC1V7;7&Ig=q z*MaVr1Y&L-_}eo%`l+A)dr`vbtc+Nf4!7?1V+zMX;qn_Q1Hvzi;m~x#jM6e2I+Q5o zpY_UiJ?DYlte%MVkjxb5`nMfM&k68fpspG#V)qvbufW1u82Kk0Wg}NmgAlRfVx6rt za_yGV+b|`n;^p2au(9l|k1AV<1emD3uio?VOsK8T;e1hRxyYJV7mm_x)y2?17Q|mq zq5OC{&Le{j8yUHWh)s6;n~?r*_Y3uYzHENI4JAF|nB9n7$z-*u;)#O;8^?;9#pS(R zQZr6pa6xu(UEk3gHmc_@@Qs7jctfu=nbd{1^hz^{T67kRT4ik}87u27Fj!!+z+!>T z0w8i*@GE&uwvU8e zcNMS7&QqDKvi?*?kd3D@9^v2N@KYJtd#0lv3Hn5_jHD1VW@efQhiB z5VjV=c880VhO94bsVJu#7Rl;Y zi=Z&k|Ckk@r*wTQ1ta#?pzvNs%ArClmlaF6v}NbAKG{J4Gl)Mcz7`bzRscxZDj;?R z1!pnf`LwO)(5eBLW}8`3{+6k?tg%`2Gj3gxt+cPp$o3W4pzK_cCDr!Z8R?3wkam3&ZLaSuy442rmx`8yN_ncFZeKua#VxNjpYGhNtKB%6dhH0~1$mEi?G* zv*D#iT%U8XZ1gtJI>ULTKCSnra%JE^azfmC&V@Reqeu4gS7$n5Dp{8yo>8FpM@{nx z`K*x1q?OH=XNWKvc9@gFhNDbp`ya@E?@eo)AI*T4JNxqBJ0Q===2GHA8P5Ao*Vz0H z5T}r|;~>Fm#rZu_+Ui0|gIX{bs&-SkB0eiCshm%cvr;O@d%o$Fl~|S{Eei2$AeBp) zpM>ay3^M2l46Vuvr)DaYlY=yt6+-hxFZAzO|0@}PExMcpW=d6uYt~XZe_GWM3g7ff zfJQYzoLZ?I5kDWPR!FPp3{*L%W?EEdS0=9X93h6kt+F?h)(TZ>sD*VFbLWRK8ex-W z?LHzLWBnG>8tNe8tG?l`3fL)tfa(6!OhQ4O+OZ47$Vi<&Wg&DB=txROHLOnb$Dh`1 zR;TX#VS!YU>vf<0Lo!||sLHkxKb-QXM7t7KZ@1q#r{BFXc4Vvj}-d@xU@El-LWvkr5#j-J%p-)e(V?w%a?Z9 z(xc5;Cd#;*JV@0$`lpN0VJIMA<8-gi#*MF*%Vt}auUFi)OIV4;gpoT()4wf*e3G!H z=9*mR!lmMz(X=TGktFS?&G0uc_Alc3hnZv|&Z-w0v(mq^gua=fFZV*V%edqY(rPIX zNANVtfK^zHg%KDB@rcJjh%L!Y0#Rof@6!%YyDwX2#ai&pxq@ziNLNR9f7W>v8 z&evu=Ulcq==p|{{YR~dC-3-xCmYP7c0Z44;2vA-A2H1^Wgk?CvQ^*9@oSjwQ`|H`sLj%SSu(b+2#Mo(@f)sD zvyXv(^jW58=^&k!X2-Me8F5JP^-MWjUnDu}7sm1l*bIGBau9i*r~KO#O;uWU7Zl>q zKC0WGmxM6!hhEwEFvHO=aE$2(koYt!p5vigu?I~IN_e0YgW`mIhCZ7S8#rNGP+}vx zwHqT6cg5lI@I@5jv~X-4)5Z$tEQ;tpB3?!5RY7EUnhYk89kNi|*~91Ac%WBmUCi@2 z=s%9*R6^Wg#=qBG@FbJLW@)=yI^r8WEB6# zXse~aUvS!yLEHSo&IQ5~iz@+zqJ;H7A^LowN+_dzo(mQVLT|(>J3>~=^0Qg6h6Ugh z5y0&6PEIV#!x^U*Z|5znfq^pFEz6gsO|2{sE1e#gRY-c%re2m0fO##;Cs_rWY@oMs zvw0Cql*<--a!%vYruA~x!#@C3g}Y<1bYza_i^DOiyaQT>Cl=EuC)Hc}Eo{Y8dy)6` zUg@rJlTkgC$oV2qIj$Qx(V_4wpS?}~}j7y@KP{jIIkuWMRK3FAunqmCA0{-3eudJ{p zixa3N>!I3VvQ9J9gM(pyZ}3*=+HMAkV|?@9xU}oiOxy#O)dI-bp<36yn3uY;{|)nXrDt^TuW{n-&>U!bG>0P_{r3JV3l(Kr1GtAiJvF8^ zaxDg=4*F0hoz*X{nZ}PRf_jc{oM#BldEt zY#fneymneP=MN_&Umg-UawIPju$rWsoZkEWwW_*5+}ftDE2i@vELE$e$;$^*NB>Wb z$V9~6TNbWUwO4XPBjp=D%aR&WrJkG@R_1+^nPOKb0enl!UY^B2+^X+ifjkG&c<9Nz z=dC<0`_My4%)LEN9vou~4y?(SRKR;o`YJE+kN4JV%4H?8Ufsu+xz9t*dGfvdY9fv9 z^Wx2n?gP<%wAVknPqs$)d4!?;VI*y2$DPrA2_$0fRSyD~k6Cv;=sOhx<>O1V38U-R z>;DK8Ynl8}@RLj(y>^8}N>nP&)Us-O=l)=|x+f#8>hqLpdosaQeR2KlJJ8KleZdMH zvg?EvSvv~8FB4mWM}fv11@d!WUr;vp^_3@#eSL9bF()~F;A)H``@Avxg(cExBFN5{ zeMPeSWnYk9malSl_oX%K-aeYv?0fr)kbasMrN8Yb)y}vXu!h~@J`Zw>`vhSn8Kv)M!o|M6fNZKU$R{|jDol7aYB5>eLm;A>EEi6Nvi-@O zyzHLT#|cWG?EV9B_q>FAGYLF&o&bHWeFfgkzCIs)?ATFynFpNl@1dX9AC6K9|Dh;m zyu491pd%Az6IhT+;k_oqQ;>M964{s+XW6oSOa#>N6S#=F8zaG*cmD{hBdvbaFUrP8{iRT6eAGWiw*KBf2wHrwzZ?Qke?7RVcl+;<-exJ~ zFa5`X@V?bw&zY6d>;1n1%Ip0*W#jdJCO%oO_YcX=>-{gY)J-e}apn^Ko-smm1T%3T zGysgD{-PhR`RxPEkjy{t5Bax4_r4t+J7sjC*wqJuAZqGBGx*1Wu>W$nqFm0#8yzm! zm$PzxU;y|=-#`%nggqO@l@f&k_4A{{mLy#@0G)w(IfJma4Vc{SWzx2RTfk^;8+ez# zyZk>7=-q>9w|CpX93-PQtR;pqpm*ht22AtP-YfOPuOm4#HW*>biGKnC14 z;Nw2w+6IlMoF-d6q$6#)+Xlv`Z4}HbgtH6boI?1++Q)sTbQoqh^vdXXkXPnklu`Q>pn&qvxwZH6*uSw3UH}v+of%p3PGp9$4%IUtV zr`DOt|DBUEjM_48T@Zcz^(NSwPKBxmPSmn>>rAe}u$Pm-of&@1beNS+f&S`OBz#vU za0-bV?;c)fYDX@W@kb)|$3zT`=kQ-D;8l0zQf^<6=?2fc8;bI#@@Cuia!~0&JJ>y! zaQ|XWO`6{5s1xR<9A)fMs*VRIdE`=ljH$?I-KBA?>vM=C#0fV!lo~XJxc?QBbhO-Z zC>ht2G4+GfmdBjCcyj5|aPTqd)d4O%_fhGU0V>;n9DsrKF=)>QxH8jxNc!VIM7IAh zP%hiQAE=Q2XP`W4H(e)<8Q`VS0|NaA;T}R4qK3_PK7upY>>1!?Wp95d-RhJN z5H2gU+U*sT`xM%(IE}e++UbrJcMU|PIQ2yBGbVbTpB%AYT_#=s2;qn7#n)0o@)5ea zBc*6di4;O*c5FHLlE2rNsZ~@cT`rLULfgklRa7c!9jLa)FAr5w&x}XY*5(KMd3sa7 z^k6@wbp!xj>4E+x>3$5%R=y33kY6I*+aK?=RrB6{$Qr0N9NO3KXjyk(f1{}t`+{FC z1VtPT=g+G9V~Ryf&0jB+Q9~L0|1QoE#Ov1pVG zGh&+z8y2iXEP&Tbn`}M$NNuN~nU6kFEF=AQQMSFY6cP;psg!*ph3oj8GpG&(ei? zKBoOUFYP_hBU^h9K%KSsfB_ZKfk4dOCLuctIe6~@D!kPfv)4W%P0mAdcK05@!Gchc zJ|%&;c63h4^zqU!4us&Aao}88Uw;7Xka}KPLrRs+^#>^IeNbE>Rf)p}&w~0$Li^@n zY5jo+O*|s&iAOxbVL9o`hs$L3%ZF)`uD!Jc=SmRTDP8;W;h1a$p`BvMjfJoRr&Teu zQ!Hn1A#5mwCltb_LinI;R6{$ZS=CS!)j}4phI&e=&b(Ae-o%oiBA4)|tP$2rXpDn~ zO4m7PsC50yhhgJK2n##HR;r_epUY1i;*#B)Y(%sIc;=Mw(rUfFE~@Hvy>Zo`xo)mlO6 z`IF$vCT<^Bs`ea8e?3Y3CDZ8nt>QpCjDp)AU*)q0JsET$ZV}PM;ub%TVZOc0|j~-Ln$f(|*rI z43ai#%0P<#7S^l(4SFtRSwy(M7xN%qIFH6+o?m4|x6}_eiLs^h!-8K2_~uJ9@L8BP zfzV4cX_Yy42p@hUFi7UZ5&rF>a`mxA0*tkX{zEtw3^yR-+SWAl>NxlfPJv48Il{k{ zq3_V}`5D;xqRwVF=*=>x4%vPe<3>j>W=;-4NWf{~MJTqIPX{x`(T~I8#D6%N#(X-q zF(V;&f8Jl8X;i?hV8N%eu**14FB0PV0w@V>C+qXjez9uAo=2Ur^|~=N3n`!V6iy)k z6KX&Ya4`g|L1bGW~;f4!``f_=bmb`q4 z6z%pz_1Ee=>$2i)?+G_$>Ekri9v}AG&>-9RgxBjluV%$R|I_nwmcEe-!g<3=F7A^- z(EY;19+>x_ecb#-miOMC3xAOn{+5@R5IwO6+C&&wM)UOkxzc+1dAka4mBd%ke|eE(ADcGZV+n8kuQzu7lz&flMdq`W1Ec`LWd z;^F07+?o?d8@qG7e?eR}9?U`3hIyaEezX@Qw4QR${v5q9k4A)ny!09b?`sm09HzqY zIp5w;r)KL9=5Q+{482B9I;@BY_vPws^Z1)HjMWv#8NfH;6?$a{MV3Qc)1b z;v5Eoe;m`KIl|%=&B={wcJ%Y|;>jG{nqzwEo^kZmEV|B|6R_)eLCT4+KUelZRl?D$ zhX*5U&Bg5PIbQ?U=qhLP7z}M5$9xk)J92cvAxwKh50uJmG{QAm{)?RW`dnde4%_&y z#RSN7c|P=9uH3E|klnpGLXXdgA5uEnFT2<0{1C&7Cd}t|h5U39cR|kIMSNr6!>tz6 zn{)iJPC|>v=$@SC{hX}t$q{_V2DhT1+=Zq|-IMdnW-CYN895pLQ=Df{E@Jzjh}@Io zpoD`d?Ub58ij(%_AZx-XEliffDb9;W?AFsl!k*l)DIG4!G4+?3qP;m;9qeaU9AHX! z44BegrlZnT=LYl|egKs~z^~ zgH4>Cx}Z|qRM<_e9DQRljQ>1+y9TG-Ygw!WW<{sJTZLYV*Tm@^7$ocG@8+Uo9DQM~ ztY3ZzN4IygY!9{+I%?j~?T74rch2)ff3PSI8TT52>(5KKiyq;wcIeZm=(@dm3hVnx zG_)}f-_)CzV2iL)Y@UQ3{I}4Olg{d+w0I)@)^0vE81na6M7_UT2cmEGpu($ zPjZPDx+i3Mdiy+o{uxq!VND-1EwfBIZb_qV4O^Ds*ZFv#7n&z@H)<3)p7;A%U!f{P zU+FvNRG4p{6Zp8#^HqOv_Y&yd>;c(+7PjLPBc50L(l#?4(_gKy9cA;=XNC{-fiK@Z z;5`8K!04q?KjA&+_j#bZ`TaM3VV;jN-HjWiOZ%v>q*b@x&&Rc|`#?!XR5zFR$?BAy z(3h}jLI6Q+*fear*5p!c6 z9(jI+x#0YO(?@^T7rc8Zb^GXGA7?C+o`tsH~MLDM$m8@0`^o(qjWJV}o4 zOXy$C@Raq@tzgrjL!;fLG3cV03QBnR2-tqbzs7`Lwr#}zNffCU)6 zVOox9M8u4yqWbx;(@xphK$h9znk%HqI|I@LQX8JQ6YObQxSK@{vNo02vd#j71uzWT zGH!y}vi;VMx=zE;-r7-|w(;PR9^#|Y>pSAoT#}St*})g$-|yh0FG-v965_8&N_t@j zmFA)R*^&6^;aIkh+VRb;U=nr+viVkrSJ>VWeyc;a-s*sV>suWOBx25mm(@ zoHV$j3w-V1j&LDdnJ@--MDbURzo5|pGBCIU{ORBhA+D9C(i&1h8F}!1LSBV&6x0ta z3*af)IDUK}W}e?AU5p&%;vG%WoE_)TGdp0-@9hTgw_TXRcV{HO26y! zLkEEz<@IaE$jb9wgR=2VS2+mZGhHwXqv#Gn@sS@IL zKel^VW$;(uWL)V}PwT)cuF@Xt2(Ry!_I7)#jlJEW-fr34+YN&-f^z$?i^prKSty1< zc(_>E+wH@PM|p+TwNpH6e6yx6?GaCK1HC(oj^7DubhlV-A%H1Zvk!K|6pR2g8W+xf zH$J>`r*yE}lhRc{+!hj!?ZzXIw|%hNFU;tce!i3U?>T{cyj$A2lU-xP?cRnSn1CC4 zU;^IIjR&}0Dh)k8;Ws;kZQa#lQC~xkH)ZGmTQnAL0%ahX4PbV5bg}AS*8cS_D39EYj!rnO74L=r zrEuiBUh#a~1+{x?HwWi9xba-I%g3!bq_F$<^$2Hl!SMU!E>QJJ6M0w;nVO2(eNprc zTHpLRpg+2*=)~^-*nCmaI#gLO`R?lxN4fiYLfg7!WnT~ONva5i?On2RLpPOBnzD?2 zJ>jEj|_uz-7Hc>t2`*5jH#4Nkicq zx=|CpybbJ>WB0RG1=tMHP_wJXF^VPVmVPz5rLXG-c}*KhRF`s5L%0C#O}lmh+Bsi@9o*Mb?dDm1lpf6*;ptu7o;y2aEsGk>%ZGTnW4G+Q(9M@y%iwHsb2o|E z|2o8f-msA^Ygt_pqac=4Uf z1OFJ^=6q{pYpjutMMHeddB2;HcRTqSDSE8C+^K-J(3yj4ny?!RY3L`dA>MZ@v#=5R zQYZkMp{Cx}4Z_E4hPPp|7<1S<6w~K)f+wakhnS0NlLix;th5jDaVNQ(#EOcJ81hW& zk?q?t0(IVyzmoWV-_zpitGeL;-a$gEuWZzHFk)BiZqyAJ3vcfBm#Mc8;T~$4wUOsA zic6U*_j}!N#6Mz)8)3uZkbYxEb|`#8x9|ldrkUNG@bezooi~If%S?szh3R4F=?@=@ zj#xcoe%J`%b`K$THtQ)b%wiFw9QTOJ;Ak`iEk836nlP@?=f{bgJbX*Y^A+~yJ>xlg z-xeI#(}#%g(R5f&kzmW)+8*?Ih9(Gaju*d%s{ZR>=(4Lpvrtkn^r57(lP!FI{=8P* zfPE02tTNmkK>)@Hmv(p6o679W?eAz9zn~J=YVU-K7vHO0z zx*?AI&$XhvS=e!{a0TQvKzN?J7J9nHJ)WrZ`fi}`o=6Y>)o$^xRrI)Pg$cNFu3ZW7 z(#Wv-%7lf4YrjmovD+(ZvxNg)6rW(Q9ahb=-5#G8Z4LL^>s||OVyM(;Xp}Kw9?y#I zsNT#N%?kC#Yo$sm(1Gqw+>b$Ty=U(Rp5&R@%`}K~0n5i6mU|}jR4RMA%dPXeIsIB} zlqR^x_W&j=s1R77V)Qt>@kl*?7>c?}nD-%E1~%;m>#ziDtS()R!gBPuQm>-081Cd= z9zret6Nhl%v6fUUV8rvbC{g3_9yZ?K5R(q@mfSz5$Z zOEa)*@ePMDEWrL{DecM41!W@~f*xx3l&ZbQN&8QT6kXcs{W6$P-#ttCq_av}HY28A z6!Og96yG>=7CD9!7j!^5-rEr|7IX-6W+lu89Z~!h<1er~RAydJLeF$nSW9M!JH|(q znb3GF5cs>gWUY9br__G2Q#`%1#5t`~{Ooo9i(skq#jMbKUHlUG0kn5DsP>8Dh3h+u zBAunm=Uw8GAlw`dbjr%aQw1{5Biwmtr>gee5*BQ?H<=<*sB z>1I}2!%6RiInNkAZrhu@V?g4@@SzEF+EnKYE-%=-Z~i6WeKR-vzQVz2%;yRJ z=keu;WA3H!$A1S#>-oI@nr?K$*~dlk9fnGKl2ESw>%=Pc;|b-O-Xm?Np78R`($jyU z(nTP=4P$%?FP(zR_-b{)1Jl)SvHhR%x9R!Ss~1Be@}>?~wob+M`-D65AM zRn`!MkDZDPP=A(Tt`3kYcU1tl;#LL1@1I_+K0L*@Zi@5|ZgjXlz`ljkAPojU{*aKh zfXKn3+#uaF1t&jC3?Tu_a)WHf#E{r`S~U3)zS&VKRs;?YR2U1ykU58%M7;k{**e4L zrSGWzF+SpowM!L_NGEuM{0-i!&8hOZmMZtXacYTmjbHLk<+zD+L%RbW|5We0qJ#^X zMe@)6qBn4yF8xn8T;h8BeIdS$aDSa$Z2d+Q+kJ8MLlUa-#ni7LLe}_nE33nSRB!(b zdut}v{HN?A1RqWf^1gEAGmIs6vA}&;C4J)!3;mbzDMI(a^W!V8@BL7@xoVp4$!VS| zrpjulmyf9pi(+~`7sa=08qM|`;WMEscR3$3#^7GnZx1cCo-PZ2A;H{?nDY&3R_*MJQup;>m3kQNdgvfqyE9Qs5sR&KK>UoWw1jNM>Wf3T z{vW<16tyl1`CCFIR)3{`Hr%(5#z3WM9@=}3G*{&Kw-QCO!SE$7Jx3avF9^zd;WcP3 z<(NQMLj%E+3p`>`$lo_6_^YMpVF!`AF7|lpTIesA2_9ZLF&YPJ@ul}?1FHwt;G@N=F3})#_1! zPQV}Q_sZ(Ae%!WuGa?@AFV!yci^pA{+gWQh?54;1E6jW6G%p*t-%t0I`X2C?*q?c& zV*=rXdW;_*KUqnn_fWO=^HQPOd@mp!^m}nnW24{uc|$@SH!fzLIZak~@kKN~MYs`~ zszJY(e>*0DVtXhey;vJ;UA^(e+6rU6zs^p!mPf4h z{+PXl1m289?KdOnv%L;|)({8}l*`Un7xUgTN~NoEoKSI`@Ow_m_Iv!>{o!?fNa)K; zgU4B#ll~`(n&sC`dqho zl)ZLwc*d8(ExtfQv2-pel`4w8-ZMq%S}!N{9Vz%G9WMRR!}%bdl@|-)H^j2!c|C{s^6(s!G8ypz=yjFxuC;9S!CX($vJighCEt;T@xPoMa; zr^GoXEbW5Mb2&cy(E{PzBSf7YalqJIoC*uq3a3*3=XKtpc$q##4b z@yvmSWj66t%<_RMf0Y#Xky=%`7xeF5Tnp`=S*~>opjO*%FO(C_;QC-rlrR>QsUHn;tBl43VaYYj`mhf~A;-6^kPv>0l4sw^3rEt@Cd!zaQ~ zm7Bka_}>RT(Bc7sR>MtiYb4y86>b_p zgiSJyII_+Hg9RoFEEd@FcR?c8=I<(z-TAw`(i42El;6ccJJKet-o+Q%wnWyO z`aLnDsUN_ltQK6}O-^KD_2fKMk%^(0Y$b-qu+-5kNZ_`>Py_TbO^DrEX&VET}KcV{S+FtiR}LGYeW-kYoXjx&IZg(MUtNVeLa9 zJQ7k1l*i4wz4Ja9sUUdd-o?AiWM}bie8@vtB*VZmlp7COe<(Mz5Y8@ya|+>iYC7*b z*~5jyp;vY%UOSrwYh?FLSa1$oFT0D*gVV3dp8mKyVmy%!OS|jw<(@6l#A`7_6R#EM ziN(kZPP|sOCtfS!BYf8;SrwY>pfK*<36XQ+wPkdAPS&2@A<)yf6fPCzq~07AGtWpF z=8WuSb2MiEafg)65pQT0q6nN}a=CJZ`5X)*ZeJli@mfE9)8F8zP{U5V)<@@s%N=NH zkrd_+`QRbQGM~ydILwIp($DkwvYYwye1v|o|lK9?S)n}oQFLAF+5l$zvlz!wnoPLk`rdva=;0@6;W8OWubF zx0}7viFps3`E=AZyJh!+L*hx*z2BV)8DPaH)BTb`O2d!8A>petGCn=i6uzIOq4h&r zHeTt#M)A!kmUZ8ZxO6gCDZP3G<`6yy>f$#z@Oq=9Z5=GGhP7cvAtP<{-j=VFie~Wm z;?#^|D%?ZIj#ic2Wa+KxA>ZV&QH89i(oR3Zvv+}T-U2B;!y~Kl8H@4hw}X@U=G7Y! zUgY{#F`Tc$V$XPC!Fc`%d@-Z_+-aUe!EpI!@KnY(zYYb!o+9t#o-*sH1yx$8BWC<; zUWFc5c&H8<>nU7w`4xKo)fxAmGkr}-@ts|cqyeS+m4cM zoerhvt2%Uz(uDbN7X(^kz z;xHo2T=?a;3zLTL#x;%197~xju+H^!(y~ct$?aG9NBvESN8a_zG!Y@Rt1--Z>&W;ZqoXFU?|;dqBlZ<)0H?IWeRexCkyNF*!eRf_|}^YsefjU=Kp zloy@RL(cH>N8c!2GSL&98j#hiC-|uu3$Wxx6aAj;J-FxL65w_c!lH2<*{e>2CFsSL}}850FBL;wd;DN(0h~rKla`Q zJgVyIA3kShGMU`w1PmmQkQrcv03l>@4+N8t0R{{h1T-L+1V{wr5+NWemvB)TAs8yP z*iwy^x21it(zmoli#8|<#Y?HxdTB+x7OWRutqNZMzqKzjdropteBb_`@Al$%LLUsWcaslv|anW}_-c^?JhKd6e|eT@~ZQ(O|q$ zuk@L!_yZL~igGsu`j!?~4e?XvSo7!M=_y=pa||lVe^yKAH7={D;2)!L9_F1Yjnd*T z`1=(7sVe>i=HSJ@!Joq7&+CVj@Wd!Da$h*ouJ5g~KdUAFbR15@A)kkGWBysy%m2?R z*GSq_Kt@O79jB@shpXr7MmcYAC0=`hl?<?S{#?+RgV7IeaG3Jz>qO= z!k|=l;o~uNeeqERcX=k(GJV~%2hm!_Jv9@L{$edd3z`DD71FJUZpBY#;?1y6X7<2e z17^k;J8c&mEra>8=G9CaEoO|<;`xzyekq=@9v1H@nu(1`TK*K*d(ax|J=oc<_c-kH zLPou(%9D3(CSJbR;K}g#8$4*3*#p)YhmPPBR^A+s+k@K({4rtD7|)2JQO8!h4KL36 z7H;t14+%GTu<_wBZoJjmyC|{A*x(uNe!Vp=+taHk+k;Ra$ChJ8jwhy9Q4R(1OV$RD z_%p%{p57z_hl;Xa!Y(h!coK&{6ogEoR(Ogw&+Y5U-QXES!C%vDFx}SB4d+icc<={; zo9A{A3v2QlJU{UiZ1AATZ}8yn0}1{5`+bT}VBSQ^7H{y}Ot*t{dzfyIbaT5VsUB}1 zYnWvll2`f~<|cpE9FBLe0YCdbY_Z)s7k^x~BQwsXV@o#goToGeH_Tx@_yf{&p1wG@ ze9mJZj6Z`;1NnH(!8P++ zme-B6Cw=9?PoZ9S-OcQ$(5vZz-jaUyjQ%M!w!hcY^r)WgDRykrjX~bz;YB6wa94!MG5=vO?bFG_7zNqo-rJxNQ}L|H>I$@ z*L847tana)zeZQ4w_oXoMX^)R%}v^?*&n~onC{hLOY4m^Ta2gZk@A?>k96bqYjK+X zk#eW=Q@j?T}Bvv018%ep$&>+M(a-wjT@+x3?o;|tx6WdgWj2O2EJ>e&E!e!nYj9FiZ3DcHF<$ooFP6Suy?6c%SaRb1ueNnKVoBDH*1paIV;cMM(^r45bF7zY%TqejYmd8bb;ADiLH>dVd`WqBdxm#Vae>W)y}p57 zJ*OzQ&~CrXgEz4xJVk^2SZ{m@UShiv#lOxI`&Szdoj>DA8tBa_&g*GM4TCZMOdsSB zgB<#MvCz26YePTtoR-j>Zg0oj`wU(no8hImx#Do^Kri-hazDf#GG4n0NVh+?5Gms? z@}&BIxyFsxw$53ZmODqs2?MB$pH3dL>Gm7>n_QJliYb#~dP`=0VP`oSZD+Z|g~Hyt z(V3Y$ubhh7xAaRr{=SXb1^#RC$9Q;aT`A6B6>QY-$JD9i?rg8|$Bi!cEteSiUVDPq zmyqvGdVRP((L1D|FcuE;VsfvhcltWt^XmPt8L+51Lrc8UJFp=CwfV-=8{^zJU6Szh zM&q(gF*$|#?~FDUdTj|u2PQ1^CY9LjW4uEO^J2A(LYE%5v~^fv5uR;c9WSYGK7!0k zUL9@Rg)VN^4G#T}9{1$YMu%66|Ki4k4sXAr=WPzxrZgy;U6kj-1h2=YzWFXa{xM3P z-dx{5?x;6Do!%yU+w5Ki&kfX``6_$alBc|mGhSmvI({&n{h6m{eCtJ?k(m$mPI#@? zh`Q_E=w-iTDz=os*D?fW zseb!7M+(#MgDgcAWpE<1cyNXhuVXaK&RAv3J7zRqtM{MbUr(2)C?l0kXJ*J+t~Kic3#Kl@}xX7MuzZCB_C-(M3ua4gH|#MD zJ^Mie``@-1cvDBLJ;OC~xD(;6`!aK<6r~pIE{e~*DYamXE$J^e4=N}?2l`3R_~%3R zp|_%9TZ$h4Q@?$1nNeA$_r?oQa2_Ql!+vJ1CqE{m5AyNj z1<0%`2ONvngXevr#gEu*-&2BDQrOV7E$^brceH0 z-M+BQ_6+8&%kc{1=^2Ad*IbkuyE`4Zd$LPPcc$ajG|E=qUat0z619=luD`(-3Nn{HdJ#r{-J z`e;-dUVi9-3h2jwp=&V<%6b-|Q!BhP!)2eI;e0?(JglYq|4Y}?GZK!Z+ie-KztI!l zDH`lA$k0aQmJTRGE6ByS(42Z%Our1*jTtyNQKjp(Y4}ms=E(5mIw0uFyXa{jR9w2G z!T@2P@k0VMbo8DgM@Ax@>@B#mykF@ZTI?%$F+}m6B7IO^5ppOg9au(e@gEf8WN+{E z`1-h*i_3cD?n{dukm`W^d`AY}Pf_%-&5@daStkB!%TD3KkI}`q#<}OISLS8rf23h=?PozNg7m!g;83+_>PMmZ=_$GU$|tH+dtj$ z#sp8%efK2b{7A<=F6^t_b5Fu$c_qUN?!L#B5bU4eE9+PETza39wPn2uK2JAt%IM9m zWtho7o1U0i^5ao)&!(fBxaS_2sc^*)WBL|en;yG(lyO&v-mBz=^tkQmaUZAW6qMYR z;htBPiAUVLGW>xdOMF*G{IlutPnF|O!>>zE+MeFm|9Dxi?E84)LY6rG`(?IUZ!vyY zme>zz<9~=Yg5SR#&cE1_AnM+-1RTA4K0WDq+t8wt&yn~gN{rJQc-1(@fG5i`^X|&P zvFx??#6J*YFCCA_xIWn*lo>zBa3$ik^#97p&&@5lN^@U4TE9u_HRe=>^Cem;57Gu+ z+(Vli*aopX}|fh)Z#^& z{nw)hEc4rX+0%7fW2yb6GF#jJ*zfC_EpbY2QTL#$Bfb9>VD*atdcSK zDP^L5b@8}yZJlxdr^x=LKdldumvK?6I_(_u3v+3Z=Rh5I{z z#oNXWDaA`_lD6sg@V9cE-ks~USstJO>4LR zJkOtBnu~WG7UXW+(BEJ1Wtr=Gym`ykt5~-ipj`Lw%JY|&PQ;5zM;-3f^!Pi&n(}TL z8+*6I_E-Bw;}&VXvKuh&|6)pS|Elet+)H9?dTbw?{uA34H2N=QC7g*VZeFxvc^y8J zvtn*z9X`F%f)Crwx<0!p>#Lc@Q0&Y;H_K+^X+1ptJPoI+vhTz;U;Yaldh{#(k;miz z>joq4_)Mc8eju80RlI>uwHS|Wz+bM90^z^PX5i`GcHFb%pMQ)oUYn5>v&f!N($}8k z-(cJ_L${53-Pm_5&XZSr3=5q%KG0%nj~U5DvG`iY47;B3reV9Ww`Y}pW1NvP27g~S zYS;#MzuaLP9FL#u;jeikHf2Lfu3s}!HrQ=DTnYbCm0mczDrWMZZFhINQ#N3DiT_EJ zZLF4P!=aM+4T(vUYt#Hiw5d48Q~1?-Y;nym^b{_@!nX*^+oBiN8~t(%tMsjJ+bT4p zu&P&iVO{AzM#CSmZDUJ7e6~ zgzE3KFde+JVq}ssw*-!qnkH2CnkIj?$2YefAM(?UXJp4%s@xa z*R-2-+^tGRFDR92^b9-wCAdupgW=+`s1ep^2d^ z=Y5xXiu;VmqBiSoM`nJX@rJD`))+n4hCfR(`pngA*QFY>-%hk=<1e+}xzq7qn(^Q; zr~BZL)VycVx%(U1F7o764c5W!#wL*erZ*D$zBff*n#!jyP1Q7EUM>V6ebhIqqSJSunvz1lQZ--k zX0q-QP^MB{N|&dm>n;Ito8{AAiPEn+D*x0v;>#;galG8 z@;V#=qT^yU!p73RIN=ldh8I*2M>)d$(Yc6fA-&Xki5yB3xu75mWKL6a>#X^eLu{&I zke{#U)ufa=TA}D-lU<|go6=b%U_~+KL*Fp#TGysenK27X`$gA}lHRZ6Ybn62@A zM#b|h0}~35OqvMpZoJ}=Owp--pvNh?Ez}<5kA)zEp(A7ce+-6>RUW_|?AMP_Ofd9V zFhn261EoG#>fpx1UX~uoxPW8)@v0)>|L6RFhLwV$SCu+Kjak;w|7$sgoAQFg&zXmZ z{gbtdt4@=zM_<3Hnlnz1hInXvRnb|JajD`-7>)$7tCx5ZPe9OP=oxT(ua0-S#wE;fyAaI6|9OPtT2Hr1ck(T^ zN_W~u`xJM@74hyg0F`dnSbUGvi|BUZ!L@^Kh}viy@6K(EXFOxTL*F9ZPjR@Csi0%r z$!)eV?zF9Tx(w((2<}!Evtax`;rQ3^)z%YjN;c3sXl;16FHsyz_z&hV%s2Y_o zm5X$qFVYsBCvBZ6ZIwuiJzjQwm$WDU2Wc~GJl~n;(b=lJwIXfZdD3=QN8~R%MA{cA z`-s0xbXPQm{Uso5( zSE3xG%VxoI7igu-)$R&-h0uj~(-6I1@ZL_m0e9}TXm1op?en;ZW9<_yg0MHc!Oohn zT<|42aKrTy-Oy~dQainc+SNwKRCm1%R-p!k8bO1CW0PGfM4CIS>@wNiflo$u@oY-F zTBJSr9nu~UX)mH3l}l#aZbt*%&+3-KpDO z)8pMGmF`^hEo0nX@bWHYOoIIj-F;M_=svDrjb|lznN6m#$%m#jT9xZEcdOn_W3Pug z?IP_J|6|?GN!`Y~Uxbb)p`+06U*Azbsz=ONTjX&=Cak0-T&Ub z-6tm4|0T;dSGD_7LjHyNgcrKG@I7$4qS`1Np4uplJJd$A1hui2x@KRZ8 z1@Fhy&MbW_Kt48&%6gl^Q;hle%Fl4@E#L>>_GzlfH9S=fhzVIK~<_v!dl zgN6Kn`+!~z4P-v`;D-F8_@Cjz4cDjSFW*##le+yCoBNbL(|txC=RT{CakrxT*o>yt zZX4&`iT@q8)$l^w1TY1_G*d8*5lrI+(-bh-uha!sjfpFua80o&(s-mDj~p8Dtc0|u zp#CXQmPGOqQ(4ARn`ic7^>G;~aN>WjEVHOAq|bLLO8{xBk#;=nFdinYhjQeL2k`xP zy1r?=$N+BA83lk{{dXNQn~kQ9t+ zb6v;irv&eJ>6=_=w{-3J=5kDfj<%abr#c(YN&xt9X}8`Ogj}-md?Ek4*m(DM&}}^Y zWUkZ=@7wQg0F$~o5?Rr|)4aAqq`lBNq8C}N7HOmUTxDLA2A&SV(?%*{TwaK|j_i*Q zin#R@7x9fQ>xwLNXr~140O~KnyU|wdP8(07ImYzd74GDTG^~5k6(D%!|M!C9*lG>7 z!8b-0w=~Sbvt=27&X?4(#zpu9dgH1VEe8j~8nm1_%a?08OIFr3HMGoGutZ~Vcn|2J z1=lt%xpWJ2+j`AgUjg7OzjB?)UX{pJTKHgcKMmm!Kcy4cTYmdIs9OVhMVMfyA*9llqH z^u?xv$^509nHK4V)<$v@Ro?hld`O`2X(@Xq&z{nPy z8-%z{iu@&?#ASLJrhdD-{7-h1e-e0QdMWolV3a?tPu1(J$iJrwf|m6rpL-QdWsOfV zmx#y8bdvXwNN?s&7U`3za+FKzFCm(@bXWefqWoD({r3Q`OfSn%n7Qsrwl|$?^dtq8 z%d9_z&AJnNbAOI{#hi)u3x?vqIe+rW?(%OR#?xPO5g3%qoWBwkMG~Jw`|q!#KoPKq zYcV?3-mNC2Uw0NJTo_jo!`pjToLKB#dMQmS2X0gJk$a)_z;)!?fhS$kZa0D^T!!Vd z131a?;z^g>KYal-@yq&_{7Enn;kArABELr9vY(OscM81O#Q&DSD@^!ER3FK2HR0bE zxNL7y{$~Q;Y2v>M&OvfYO!&hBuQB0+P*udAWy0?ec)JO=p{j^~z6qZp@Kq-Kkidf` z+&~9TayFarH3IjV@COB6Z^9{P*Tmvm#L0&}T-6zVDKla!7y#ts2A#b*)^jJ_&oIKPRa+s zd!l4sA&Tu?BDE6=cgNoYIPq7A9L9)efd&6K!7u%1z2KiOa4*^cT|4+g+ad5yfwMIu z;(jmioS9pA?-T`wal`{m?4KDV(U9Rs|8*!CJqu`Bk&H`gD#2h7x+o! zLl;|5;rW5URl#)(ZaPCjO-YKV!mg5qPo~@YxYK=>#I)S?P5S+d&yu~1-!fR zZnVI+S>Qhq@=wwN0vFp$CIa=x^yx*F}v zlLh&yz+1C8F8yZ){DboC9Kvzw4?huj{ZNjxy-|=K3A|z$$Jst8$Tav9$*CB@akhU6 za{7w+us6tx4@5&<+yA|ZwtJA9LJ@d3*pBkzhgYd+5Qs9alnzJ%QZ#tS5)&TY5)5L z-a3inQqJQ7_g>C%*)RFv-;{6tRF2F3WQ)K%rwKXyp&gR^(>c&4@c$P08G)}CxF7Sr z?)0Bvf!AB$TFc_*RZT5*xwwk`jq?~mf#C&piyM|KXj-~xUR~3ooVhFJH8eH)bLKWT zYka#QKwA)XGpqB8>gp(V`jTah*DP4x(zpzJ1r5t?nBKBr(MraU%NekXP_+X46v2ii z^AVc-z{Ua2PBzu799vjN1i0|}ow|9AD;MB6u~Z5sR~NWx#5#3k#kPnoQIO9l_DaF%_6?x$XfWo6ABTQ zQrJa`#lfkr2ceWBR1tBow8@HUx#+~trIAB)@;qGkXhdii9l!MU{?vSw+CZIzGb-*$PFvI^?S-95RWyWYrP%5cj0MYE7SPB<&T71@D^|zLI*gP4x&;gB@(YS{n@S7w*huKdpoYOSz(%uaw2@DPU_QJLLm=ff zc1g>EmK$V_RX7tn2OH}(>Tp<{k5Y&C{O7Z=w61CCGRnQ-8VumeAk{P=^Zk2_$;hwU zbD)%Wb;F7!bLWHOn+80MxTiMa7lX=W*WlbQeiEQ_q{=AS)b&)h<+-7`t^3P}($XHmoQs%sh- zFRoje_f0aXr2nfk8VLOE%UROq-@LZyVi1%4{LtZlWp3NOoNqD5Q2jeHG3cT<112>t zp4&__tge+9J*IX2Gok3UU3ZyI#nP{-OTN<2kZ0G{95r)^9um6Cp{B`V4}hjY|2GzY z$mTmN29WB4HhL(RF1GrH$*a0QBj(m}imuG8qZSL}Fqdvwuyl!r7dX*$0Ke_v9-%^v0co5YbN<}Z!nw1aycr$JibEt z=HUMq^jBQC76^WlKU~oCXcn~8vqpqbGls(&&mg3GMWa0`5*1%8bMzR3drg$4dQfs-BlOf!Ca zMByhp$ltdg6*$>J@wpsE%56t@EsQTLl*cEfs-Bb zxC-q4jKWWLkiWM-D{!)d%Kw7GuiBvpCB|hgSBV8a-2z``f!}U{@3p|6wZPxEz&{r_ z*(aZsgx~0V0$o%tVv+rzQ{ZGDl|Mn@SM77Df~)?}Y=O5~;J>iIk67SuTHx{GcWZNf z$-nO{b@Ma5qjlju1g~A5(*rf22ePrLdS>R+JmA_5lSMBqpf~)p9V}U1$fyrFnp%(af z3w)jhewzip&jNo~;AEd7{Fm2Zg`eyb6cI-SPWDmxpH}!)`}Dv$3cAeYEwR9-Tj2P3 zNVjrsx4`#W;Llp%XD#p)G7K)W{{00`k7CBbZ&?aI+085B@U;qdk=;~&zrwHDZN7r5 ze${S)-*15*v%vphfjdY+T;}o)wZLmE@Ea^}I!{6uWiQ8e3b!fzWVg)(;@U27dZ_$& zD*US5o=|YrZl^48EsvYIeVRqw z9)Xj6RQ~%Fe$_s2DY$AMmzxVTm)C27kG8;PTHtd9PURXUbf?Ecg`dh*K_IRr0;h7R z{L2-7wOo4?JO^o{KODEf-?PBuaXyDGvmFLo-~kJKp#|Po*-2%T_;H3X(p(8yS6@Jn`O~fq_IO(tQ zFH!ha{qI$9wca}|@K-GG&n$2|&Nb0R`d`8*ev214*^TtC6>)BXlm06IMGC*Ff0cr( z`qx|Ft1R$#3;ZsDlm5emj`ZkI_(^|Jd~LVDNq?39=L)~7{~-mhKpNTZUbDb;oa3U4 z>{fyQlD|>G0}8%Y!BzX*s^DW4{`(bNZ{A(0`)jrQD_(Y_U z{n#4{eyM_gY=IYvfzRAOB=_Npneo{QuGULP!K;;g_bIr_f7Akh&jRn=H=>U!XQ05z z&gwjEsKQTnuHebF9D$RaRsMX1U$yg{3a+;IHx*pfM@#1!ncGo@1wPsWZ?eFb3*21Z z8x?-FylVw+F7HN#UoGz+6g-HcNWV(R=)PRJ7WgC!e4zz?s|CK>0)N^9|HK0Kh`$ps z*Gsm*$)4(ZHecZ1MzW>ZFvZu;lrtquw+@auVJH1cA)pq(D3;YuWSN-sKCfCQ@ zPWyQxxayx16kKhm%?hsaZ??eix4{2ufxjVev){g>@T-3NzQE0X`;o%0+W87^q`azq zb}P7Au1*X5?-ux%7Wkn48oSJXl`n8}c}FVzYI!daxVgMl3cp(3+Z22fG?4A|ZwjuK zR~yj1p8YKFkrw!s7I=#V{$mUL2@CuK3;YX#lRe9LQP|x+Fj6kEXDbhCu>vQ1s{Dxx zziQ7*6O;dcO8CE^AMoa~_T z4_5e9JG3bHM5K{^wNb$@Rq*dw;D1r@fWp7$Vgztexm5gP1y{@4e-LA5{M8_(J~axi z^3S)x+br;3Ti~4nH@Dv-3cmyTRB-jQCk1Y9zsD7R)y_qOc|x-tHYvDTt_}+wGoTrKZ$3;aC`Jbp;To~oR| z7I?q{Uuc21Ti`zyIN5C^SApGkEBs`)W)ZhX;AA(I|9*vEwcBe7u9l0vqyRF^^_Vu4 zXJp0;E$~YOPWtEar0hOc;V1p)i?|5_C;e6a$qK)!|7HbO_1|ZKAGN?wSm2*q;Az9S z!e%>MEbw?lmx?mcBU|C8^41fGD_7tNpjG}tg{q2~-uln0F0yq2HUle}T4sR>C>Te^&W+;{SGW?hIyH3GX zf7_Ec(V7Wg=UoBeH)!ms+<6oH%l z?FxlowewpFu9i2ZD59t8Z)+8t`VwjXXBAwP^R9xc_Q6+7*hThK<&0NwwZ0B2xGMjI zf~)dBv%u3!BKfNPWfu4Zft&rjM&T#>*K)DiG=ZD_e1^iW+P_o5NrtrNGYYQye@`*k zlie;?_%j7gdWxY5l%`#*@ROc348{9N1WtOY{5cB0s^>Zdk7{2EuGUwF1^%IetM&LB zG3o5mz7$-ouP-d{v{Af(BuC{hwZO*-yi5C1_{nZ^o;gL}WH&K%unezI_*J{TrQm9L zU1IT1daCVfy@IR$@VtVnaz0RSwYXX2RM)bS->;O6!< zQsE~(W&65B;H0P8zN!>{RnO}bT(xt%1%AH;e#`>@i@?oxep%sH?R-MuW;?&3@T>Ye zDzH{TBE!3;Z7zxT8{Im)ZY^THrMn z_-uibeZ)}7RcBnzDEEV^XIm#XkPfK)J4)6xY_ zc2@bl3cqUSTNIo~Y3Jt@TrJlb3p{ZwSHP_Qa0`5@1zs<3b9t{(_#Mcqf@h~)D{ymp zn-zYwybmb&EXa`M{aC@(@}`aB8JWwKYk^O)z!zHJ%LQ&O?~Mw-THdt+H!bz#r3K!1eD`)Jw!o)Z;5S;}w+fu>AeM|Q!)*#b*`b81p?y!_WCxZ12MWJx zhvyW0BGSlue?!49Rq%Y#aZ$aK%S-+S1+P=^WeTp!->BfK{J$xOpD&oc_H@+Xq9y2*FA z1wK;X@kk@4ek^^Z!tVf*#!yWgEARvp|3rmf^`H3)uIjnT0`IWEk6YmHTHr1+7A~{> zueHG2E%5s-@M9MEKP+&^r0(?@YJt~S;Ijoz^(B__Oz=E~pXy7lRptvk!Nk8<;m<~# zY!~|#oLo%ek105rT;dl^MgW(&y$2M04&o*MbOl%CHz~L({}BaO+ew)g%DzuMj}7C8Bj%0E=$SN$ib;HsYUEbvwfe4hpWlm&ju0-r*gCb-P? zvdRMgz6JhY7Wkhm@OLfno>RNmC&vOGC2*=Qu~o?gj8XWhzOuMjZJfZVzEu886@In8 zwkx<=Uk5Dk=PmFLEpUT2J8+rppKXC(Zh^0|z;{^S_X?csCbkBd9(xpivYVHonzmQq zWH*)nR|>ysx7QV1wVO7b3pAHE%>pm9z%LQFxt)$x_|m1~y>i)~ugP6?OtD@45H-z~yo*~j=7D*Tg0Jmu>VG(Cui zF2dFNUCL12Kd5q630%ra72zEUu8v1{S;+Z`!cX>*c6mg>)%O0Fg`B4pev&g^gik4W zzM$#xo}kHYYPmiUIF*a!bck?IofCu?3XC490yoR?D*PnpMUkdV!KIH;l=}V(RiB9p zKgrQVxJkj){g7(~Zq}zo;U_utjVHR=6}*T;?5@5CL)Axp4+hDp7wI2Tm}iLe4mapX7K&c#eXr=Xuoka;SE0R`^Md{66ef1($sVskB|-^ibveP~j&zn??Ay z3NC)KATiqS1x^oD&S8b0IWEAf-UV6zCo zCGm=Q9^Prf<@Xk(|44rMy#-CFFN%}jTgW!ytzsaZZ^GsG7Pgx3RxxooV8Z41G@diz z@;eNtOt}2sf_(43)L(vYA(;kaToRYxTafR+m-v~UoUhfyAO3!V376kbIBUY?_Y-PG zeM3rh2g@=NlI3cOx_ZgFwso?Ap%NojE|9=Ph` z*Nv?nXVXQ81Zg^cL5_qXnPj~kp(_|!fj^&0(<-M|PMZ*Ve)`n5jE4!X4n000^wfmV z(cs2+(}JN7gP{*XCn_H#k?eMgn2tDnn$}u=B;aE$3#_h%`lQfn)uGd&6G%~cfcS%L z(|pN{Ke+L5)>X4BXI0LAjPjclI!yVMPY6AYjH*NbfPh??ORz2AbFr*~q33H@@|nT8 zkA+SU-=ULst_x(xXp`CkG03Ibmo_D$+eb!nz?b}yQ5*1ik#7ml7vavw3{Qntd1d7l zlR|&u#r`W(gBAH-6GCT#>)+i6#ZhXRNx&BjhN^w8&}v@+D;dj&YgIk>FV&&fLnkI2 zdfiTCW>M9lX}+|}+OktvsCH856qTozr}!vfBqx#mh?wV_M~_9mdN7SOu6zxl9*cY> z0b@wD&&$oogb+T@SHS3*)uDGt;CwD{#-z|0rb9S?#zh>fgpymJ4_Lr(HP+YE(jnBL6Tbxh_viULCBCf`otWK5N{vbgWCSjJ7l_Ap_TDWo&jCgX$7 zVB10887emwyXKKrPR{a8GqRpCvi`#DQ1X#6<2dT+DJt+@-%iv7MWE;e?Dp*vz$PD! z{~sB%j`5_kHu*ZZz=J-T5kLf!xXahdLkE4k8Iio-w~rA$w)$wuN9L5l4&Nq*&NvG7 z_WJfyV60L5Eabe0|Hpt%^R<9_`6B}<>0>8=s(sCXUIVntw}}aR31R#vG0?V^VXp_b zY+@whAaKUXU}%%C7Ey$=60!_t{he@{zSFZ=JWCi~z*o=m-HVWs^$g39CLonY*58P` z12l~B4AJw6t~6$`h#CY4tY8d1U^Wl|BMaUIcOxw~`F0QwRG}y)IhhEUdxtLt6!Q2p z;Df#l0dMkoL6Ou|gb-hv0O~n#(B~6CkONzNS)ADG%jQJDm&QCJxP^N~ux&r959Ub^ zwZi^98csvRBi=BA1cXomQj{l%L?gjjL_b2E68ffUJcOt%Tbch=qpzs0JV5ae(XBf4 zHDl)a&28~jlc#DB9X#~5J*s^_FU1O*K5h3nG`R z3^xgG*}-^&TUva%fzUr#1a%uob8}})VUW0~pdA2U?f<|7Mw;gHa#unN@RFVJ^idQ` zPfoc9focfqz!P#wPFfIMHiQ*0r$m>40&+z_m2hg~;huW`Z)lhgdp23Zs`k1ydrgY^aS~8GBCwm0-7ctbP z1YuKGb?DTj(3cZJM^4`dewkm}*tMbeL#H=BQEmj!9Qw*BOv@~ZO1rXh{Szb%Mn&=0 zzehCLw>G%`F`76|Sbu~+9@i>_Nnjt4Y&H`>M$~Z97DC@@Uj;WL8Hg$Yw}Wg1AtQ71 z%$yM%HOuERHeL4B*Ix%pTYSmJwwWmKBao;)NSU9$3PH1f!MWgB>))k~ksu613qI8nG# znE^uRzS?|0c2HULqrtXiE}Ha}sA}dyVi1ayNXay6o}>gqK9`7&aAI4c7a|fx>aA@2 zrKG`ck+jh&>9oP{D=T7sXcn5t)fdmh|4xfao=&yK=b1~1R*mjBy zVLn6M(xJCwggz8P>`sbpD`gW=csa zwWVlnjv|>}f0iZTqoL4hhp(08w4b?6ShuiswoLa1wv4SI<=PxQgIlIIle7RlD!3B^ z2b^~d=J7%pFESSrdbv9EHp8((Vo!`Nq?33ALZ6`nOQD|sMWC#wr0$=pRa8Ao#YSEi zz}lH$r9S57KN{N(;zx-<=qyI>(Lgl46Ktz}E$B2?Q5cP_9S<6T=`G9DapzLND*{;* zI0K;+Q%?a}y;ER*li@*sD`%f%NU}xP@zAePwbFRO>U_&CrZFY031W~4QZEH-Vbt2h zJqj@u!al;I5uL>lj0@95!NpixOE$rvmuxZt6r;fzN>)x^sL_BjjV#xSLXy#HNPC1g zBaGAm_*n%s+=t+)L!W;AHTC9<2P;or{a?5E<_EUy_H}@TRakZCg9%s`hTaK?Y71_` z6b=E-UwIW$G)qnlH8I)*v6Mn3aQk63!R>8iodS*jSUE1j0{daw&IVhHuNJc^Qu67i zsxLuwi!TjT<`SX(zO9J*r`PZh^TnXGjqmALpUm^Qz>K@GsYeWzbO)=Lz!eYz}y~Np|#=lUFbU`wv;Rt}J7CQsgunhSM&w)EMWWgMi z$E46nmJNiE+ptl3lh46OXvel4DCL!L2oIGvF{6Rl#S9Aq6F*kwSw_~Q2*R0Ikk*QraR9+m zi^;yZ6H*Xu+}wtF0+Z}Da*i1r)*^U5BXg-t22!C$Q0fpZGEtYz=DgJ1nVL5{UNm?p zwa##WEG`0jebi}_4%GUTYTz=1qS?V?Spw7qB^EW|W29OWB@;rYyRC^Cl{1A6a;Eue zuPqN;})6$LHt2cMahMvqLK9h zEcGdDc+mF+!XWmul?Kq^#1Mcx&}@1v)ep8D&VV*nPqlVft|1m^s+D@ z#v2wjLuqxvwiG)h&O%}gIFu@vFQ`$#t*q6fU&GEB;;*g)`NxlpDTj=lPUE(6toG0_ z-itI0be6OSAx(o`gO>DkF~hY4Xi@W#F_SkIw4bak4xiphBRE>SL#`h&yLbv|uzaND zB3mE=qP09}dWP(SB+=6fnlQ8yhj0`BCmVV0dUfbCQGRSVP};VG%y`w9UBQmzV6=GH z!xHoDISL;T;dT+l=(>@$eb&MF!@9{ z3~#7c1M_7-x&}>gQKd>1%27Gu74N!uG={+vnyb{vc?EX|^zjjoPy|)hAp!OrXbdL5 zh?;;<|Bve5#5@5+C;Z@~k@F^Mb~me=+L^{}A0=`hrXtedLcYv12+zzT+U^yuj!u%; zF|2caSNm!drNGbTO)sMXFs-X0DpPZ4@&a+@QcC#cpxRLa)1Na3QW)4|%S zX4ABjk8f`p+djpQb}#}T0hvD{UnA@Npq;lUOb=&~!r;LkJ<7Bh0lF|+z7Lbk!yXE} z*9&qlTOCB^@2nxB1+{{kPszc!it-?j4$_YU;XN|Abruj-$n~pz4z@|QZ4mLHVz>l8 zTM;`-0l?G=jJ^3;V_PNosDV?Pq}_^6JSz{oqRWYqQ5b;iaCcH#G!`{Rcu z#2Xt68HN>0GpdRDBwmk5w3YQA3w5}fm&WT3@Ke(TN&SfID}<91M<+NaS|i(vt^~h# zvd)A$5T&aic4oY9&)&{zY%8lsQE6NP7JX=L$;@;#YH)N1uW|K9UFg7=Xy70wr2=M$ zyJ9fOQ9}$_s5LCqzT(`Kv#0{)_;UVHgZAA~1D&W(zLdrga@@#y*0^mAjz$38$`5^_Q0vwYFGgZ0pGT>d*BA4-^K zewz`9OH&6R#g$;k)xP;yLM_75UKkoxwg|TT69ASW+LLGwY{g1HUd}+Zzh~ zD=~k32|KS~hBrapG^}jHtqrE~<44%AaaN)7q8pwxx3uy%v^ri1beJHj)z!|V6(96O z?nch*6~^}-r)dHCjA$5qwiXUhD+vdv(+dZvjk5r4ZFC}rd27T=X}pkLO8&SHVeE5A zS4CaY1gqNxHcfa+u+@Pr0I-L<5B&7TGQ>Er2g1aI4Gpq4k&UIhjSrN1uWv7MB!!Pd z0IC8#Kbjz;QTY{$@56A=7OezTwFpBwhPrSMA{4o>$WrpB{)6@4OaU<|th=nuQ9wNr&Hj{hTnCw-X*~lCFcKJ7 zvaW`E0xfT5Z1rt|U1)fQO)!{<(;G(JR9=3Oef6Ki1_EOhv}D%)5EX^B|jhsr0$Y@lN$(b#W%}g zT$MovEjvEEvKF1_!sUQ8UMn- zvZtSV3JQ8Fp;S5kU-EGAErqGS`*R68txI+{uK`wH@zlYs;xwYx3ZC%Qzl#4&22Ksb zf$)PuI7SekGW+s?%|V{akGI;Nqr7Y`cDB)`yC}kD;~|FtF*hKPwK|6*X2FscBw!(o zSjcy4Gwz&hBT`6k+ex2U7J(2(4309)7j}5|cHjs|c9WMU|L9bxUd|wt*m&s*|C2 zN%SJ1_aKl&yiOMh5uPJ_#Ob21bGmIV=8-mjWEuNj@T%Bd&jCy(n%{Ls+t}UC?hba3 zVRt9H$FjSN-Q(Cjp4}7JJ(1m$*xk+U2D>M-dk=Q+$?g}idkVYvV)x$cp33fN?B0jn z`?7mFyJxU_KX%V#cMrRJ*}Xrz4`BC!?CxXti`jh;yANjfEOsBl?nBvq7`tb)`*3#8 zVfPX2p3CljcF$w?e0DEl_hNSEUmYrC^hkCe#qMS7KAPPxVfS)&@8m{5#2=6H$7B4# zmh_tTI1jOtA=sJWp`-lqNB%g*A5ZefQ~dEXe>}qK=*RQUA_~~E;)1GQ$3A)S2upeWyj5)%H`D`Fv%0~bMNEvjW2c|KmRcvzwm7L z@r&Q!@y(g&X}joa6S=h55z7i2cL$xVce+O6-kYr4%Ml-F?=~%ycEPQUXA?iAT;`Zi z^;4GPUYVoQ^#@cH>wxwpj{YNv)X7@nCDcP`sSD<5iRDB(QWq`K5-Wa%RIb#;iv^jS zx@3tU(^8jD5TrMC`4mBBr7oW?$lTQBR|v8sbyzxD6xzY*8cOdHX{VkAn z_)W~v$urw|Uy$h0D-q>%kzLZrF1E2`F8ex!`>aBUx(X39k;U94VkRmvml170LNUuU zTMhB`Ss;kM%Nmz!w#yke4vb;$)?fdaI2)HO15KS@28U^yBgh4WaPHyyKujk&KAbu% zK}NWTGj6VZEz@K)Qjqp6B($H)V(LW<3-!4{v(00Q@x)6R%QnHnLd4QcFq`t*mMPq` z827IPHwzK>Y{tTMKdb*5e@wy^K1*6EWS_~J&agg{B(VwvCFs-G(jdtjKv3=VA-@ph zoH;~N)%RI&y#N)-f*gW&CCDFvWChUAk^C;?XJl87eopH5ou=tulfIdrYE5@C|MSdk z(RA*~p2atSfqSxN?gB|>dd6tF!I+WM=^{y9P?|nS*Pp>pJN-p~B&+lQm|_r1HvqN} z6pS&5nQs8I2B&2zD#ZYpW)So20n9dnX2u!B+QI;`4u@qzGK!?hDg&8HgIPvh>0xZ7 z54G~&5SWUVhj$;gydI}ju>XT9x==nY-+&`aHT@mD)bC)7*Op37fgWTF< zG2rteb3moZ+8iAKYYPh6Kd4xn)7&My;!+xQfZs$%8iB|O?);!qgMwx?_+X?4IV^=o zYVAQ*YaVo|4>5H;H%f{dNua@1^SZ-1&+moV>mY^GQTUDB<0{Yj>4Qu@=i#)crD27p zcg0m6mG;bCsOb+Yb+6Kgxg$2@YWm~M4KjJsSR2&`UI0uty-eQ?AE4g1jH&>`gkHuv zT@;!Tyw6n0NKaZ8=}AXN`pk0HB)61GMl2PKSZWeks_!zt zrcY*r%d$K{X@kf(@}lhN(swvtfAJc^rr`@VHU zo&urTt_)^a2H|81vXQVIv!gH_*(1tyl*YuLv|Mv}eJ<~yeBVlKfV8@Tt|649uoo|D z`Yc{DEWj}2(53IX4QHrFz__!>xO9zxBhDfNvjAFeIFL(7mmry%E)h%Sri+i_4P)QM|ntuBaSyabbuHOS>O8fS|i<0i68L)l( z=CysZPyF>;(x|2zc-$ z8Q+!j#^AT*Y+Yu-Nz-8U(r?>EoIjJf-!0?2mUPR@;4G$+esd{@$~>r@xv^R|cT?0( zP2E~lnla~3BinOSnw8z8Ax2Xl5S8W^-K0@;TZ`n>;-k`>JZ~Du_C2ee-K3%qoSLnm zXxD8{6VIEbyPTR~?*%-SeS7O#>I_}25%sqqx{XCoqsE632GPty5grn;SBf~hhKcY- zqDjPYDWZdm@OC5OeJNryX@Sk%unqS?LI&1mWQ}#WBNB?xt(I;xsIf=5Nu5H@jb3VP zbi_0nv6B~7(eqWLyU8k-h?IU6C~Xv5E-8~oC1M)UAgXHLf&{OK1ZmIuN!B%>YKD=L`Et?DnTt?{MV#sFKJ#gd3B^qFnL?$X}`^%DIv+D9!4G&Wh@T2i*-)*#Nxg529c(8qa?OT;)jA5P3&j;g88Qc8OjA<*ssLj zP#}h?$W=l0LS03zV|(j5XO~%-eY+Rkz_gg{JJ#)RfY-jgV;!56;hR@dAieCI{GU7s z(-r>4G!{u-?}x_7ib@S8iVtf-(KRyqK$mDz^kyE-6rIM}8CUo6nC=jplE*?JgcHw9)*{wmq2#if(qw%flu%Wj31GjA6Nqw$a3ouBv&>nqI*;DafNM!%7DT)T@{QP%CT4 z&Y=n4SQ~9B*|+cE?*!L_jK6{>o)Au4g{kUf8yS_Zs)lO3$90axd@+JTSYrQJjhg|&4;_fHB;3@ zJ`f4>a{cdxtCr1Q+ExC~!nWY~J@!8g9}bs?%e&C<)28$n8vc_h{i~+%$#D6quGRE6 z!Xd8ho2Kwv;qZ9cB2+K^e%qtiDjWQ&zy~)a@#w)bB{5kNRgx%{#4t(pmqaf~BuGM+ z#HTWge+r_Aaw=UW;!D>`;x<8K-3emIPjI6?ZOAVfv2R`JLn6NPaY_7H60b<&v?M;3 zgpNHxmSK`4QUx(&APCIQ)@6+V5$txoe3Sm){b44#1|A$<6^GZ!;gxfEEiG5q6L`j& zwptMC3c8iApfQ`HC>(VLv%#d9Rg zmd#pF-AuVc=%(MSKRo~?l^Z03!&@fdO_cDqN_eA1ZoPy{Dy2oXSE33?v#XuRB~n0| zQcZDefAJ<7m%d?xg%?qzILlIqsa1H3B{ERb*i2tjMplmPoy0uNvAun@GL#ZW5A0$w zjsx9sJcA)(8t32xc+B=zOa`GbM(>-5z%agp5g5i<8D==PAHPY-gl2Esxd@pUlM$X6 zQg~tv$lWF;bNxZ&A1S;r6YWez%|K~taHb14jC&@^R1p(V@rI!jpxU5`6cn>6p1D}D z=y1lfqz%sP9XEaA7#TZ0o_Y7bKCv4)lOISrRdlI461Dlf)a6cv2ESlf;iCal0foO5z4dER{r~BxVSL znR7@Ma<*?jwRT7zBhIWHQpSk0Ylj3Gfj)g2Bih%M)`OrcYY~XjRU%ZnNfPZceiy~> zk??*=bV}kWL1euMqV!D(pOM5DlHezoz{3w9A!|B8G=z@-&?dzA2gO2)>xD-0mRIb3LCl8)5_b9w^jy6WBRjotSB@ThCv}wQ z>h_=q&z5G2YQ!<*~;Mv?JPQemyBm!1nr+k z;z{jS6}8XPSDOkFE;+3%zZN3c&l>29)bWs3iv{2&%TPSVOJ)#^PMc{jt(MJ6uv)}= z^nAT>)I{nUVMhHKedMt3+M7*~bs?px6Dg~n?JH3frSrXjo{h~izOiTP(q})XC&L}S z+jQ|;_t`JPkdZ!{cA}#C>@LGlbf3-mW0q@cBfqZ1tmGlyUq{v7Vp)H$=%0P(l7;I} zO8YkT*NN;*rF3lXAV>UmV-qW9ETvFOD#|TJrY`mOwoYH%{x{d(PGn&!nd+I}rvAEk zW_ZePRWi-?%bb6E$(A9D^OQ_ZS;IYMH=fz0WTx2!8wU;{pHhA{75>?E>eR#bqY?l1 z!qy>+NMTuraRJrXeFtNz#&F+F!^cBBKbnEi(r}$JI#BdWGWtxn(Pw4!*>0oL@KYxW zBNhu?rI*O~vuvRhl{-y}7w~w#Yz>c+5qpqZhD$>Uc6673wd^QX8dHDGSo!{}X$?n% z^U3ZcVcV2`ccB3~oGmf;#DE>CG&T%|Ioa5c{vtX>;G0r(no}$}Pl||a8gB3AoN_@Z zM_hHJ4Qo$Ed!mcvt`;M{_oMizC+x~c%f&SOVjQss>oLRDh-gZ78}qF>TcS9>jfH>u zO)1F2)-#R^jkyX_sc;$!gu5dO4AUD1U^R{vEx%`O-@BRuJL#UW{S?Jy4xnDA%c9d} zKS#@&@3e!$2mkP)l&EzYt9eL{t|>7J(!M^mm~bJY=Bwft2kLC1mYi%ys%yWQWd9TX z{Nvv|{QUq~L>DD$`qHI9-3$_@>C0+$`qEvg{-f~UUaG~&;{R%?itOv)H2>zhszMf# zQgs>QzF|P`evC5>=;02o8LjUwo}a0gzXdTzgeH{bH1w(?-q@(G=*CVpuURDU**Spqiqi+Z3~-QyfrJ?CUngcAmmKSO54d0SAFh*ugdG zpiv<#q9TGM5MY8~5dsDTLr5kN$z~RaEJ^{92vJa4(W+q87F$$aU)$nV!40r3XkF0S zidHSCtteVS-^%ZM?mg$3n=>=jzW?|A{NG2Jd++x-=iGD8J@?$@d1hi799*~85^Qyk zZAr;&A)wbQ3ft1Dz`2pu4ZTE2HhUxk6-lj->?4v^rlNN^ZZWrEf=#rp!to%~Ftc^M zA|l&i$Sw(EN4`Go9K0S!Z^+SOU(5onDL-*32QcM;WR#tUkO5HjLE2QIYMhQMfm_hY z7Z|cBZA{hF1hY)QuxnlK3@eSFSkNeHrP>>i)L6+iO4S^CAmb@H=KA&Ejnx7$>5z(f`6$LBJY@UO<_Yw(hVw1Mv@T%`v+* zhlwrbB^q#;s8ggl*0<&`u|+sc)JfoYt~G~=Ey7`hv-4h z7;HWfBjqs(4+Z-aC+-%sWhpb1{7T6+jw_PC-(to5>t`-;dF1j>F^h3|*pv%jS5#YE7qk9kYuYw#7&TOb7$ z01Hgfj(8<(B36FOeifviTa;TfxOcaCaHRb*q-*(!|EDK(EvS2<68-#8zx^nPhH z${pq6YSP;{1?GcZhbk~1^yUxu`_e9?CY{CG&NYmt^R47_(r9CDC3{l1F>f0`qY$F~ zEUusN$n?H=Ad4?%ju1(4^-Rta?5H=M8W#R5-KU2Q1tfr5J0Oi>#rL!}wahz66s;Gn zOrv4OE9O@RXwtQ=Z(85s#2_s>Y)!^;MqMvw?a-ln8|?w%&*URP8df?8a4w={oT%kEtuZz!g}9!!L$kbXV0Q7tv)j9&R& zGEIYewXtGMV~-o;6poJ*ThbFi>Y40Bka~ra+WYZcKxQ_|W-@b|GkRI(Q<&M@J(Vjp zh0|D)DMaFYqG9iB|L|U-wiR}Iwtqku>eQT`w~XjTmO7)2Z$Os-x;SePGI)kcNuVdD z@#bZIc)|eF7iV9;%0bU3FzLT?a|WC$B;dS5j$lr@|uCJ$Zu`5;qh)D&2zXR=m;w2Iz=cnBHm4OcO*B2 ziSwMhARgu1MR9ceF6lYOX|?g?C;_B8%wT|g+Y+oBm=pFI-iti?;@0gMYI?r_KktsV zj&twXJL5gb_`C%tWc%2oj7>=FL}=-|#3KeysCS*lIlpg;WNR5p^BwX}+2~QKOmB~| zP_ky`0POc&B3V~fVMXM2p(BO2t$vaILS9^?wl64sqdq@~* z7NQGXE7`wT1khB;y0Q{`NM!xgW1J*ekqN6x_OBk*Ov$?96MIO+FY_3SB>SNU&5*1s zE3t<});f=Inq*zZ#2z}x4E-=|(1l?oS^HyDn4f+47KgnX{}jGH9$%qkFYusCB>OE^ zyL9<|GbAh0ss9tZQN(wT%aZI-PuAsG)qO1EaZcO%s&7uKx;ahC!hI~>ep`HV+PY`( zVQYb_JIZfb&%-GgfX7IQGn-ei{x{90A=BBPr=#KS$ z)vNExs4EGr{6*>Aan*YC{i=sit^DRRfkjkX-|U;yfNRy(DJzA@O6$IaEwr7Ev35!n zA)54IP1^cmH>bf6QXbBDi12kEK(rvD9J*j1bphSe3ToShee1R^0qw&}@F0(D^L-2u zzKIy_!H1^ZhYbT=qDV(yB`DXY60ONYOr9uMv=-YO>zi|P+E?9;!h*#s#l3aUq^eYl zYD=QflUhuOaEM7%=0fg($zBJ+7fNqVlkz;TgVrqtJMIWl)A|njPY_Wa)+L^GE0WYp zzxPa+baxwafW+R1V^QxX>Yc%1=K@YKF-Z0{4=R@IVA`YOva`9Ani%!_iX|Is=Mvg{ z{8K6&%BTJm#oxj=;Dgo zTw;}zq{<4`f~Ek;OWlyQ^cpGj#$McR`)tWP`5+)O1`n5b^azsYr_`L13%YonAd^1T1*r8yg=B$ZiO zUCVDpYv%R)CTS9Bpj4jB2-_3H6lyW{M>T!EJG`2vNcNYg0xMGq##w1w8D*2k~$2j<%)kw+wStJxJ0s_ zc+eEd9@E}((vXq7R6~_s;#DdQBudGPB9=0)gUyL1(n9p7WQ7qeSik7|`HFzKP<(#xpZsah5-xrf?ekz`%4TLuFrrDe{ zPzDeM2+f-;Xf*TC5I*cBBrEIz-?}Wxi=9EHHF%9Bz3C0MiINqG=rTN@=cbA%18QOK zv5*hq!X)ePElqH~AV;xmPW`@#k`42S#|~ApojquxWJNn@b#Sp19`HJBqGTN{4kl>P z<9}2O9dwLp+F)Hcii)YJuO)vs4?8ffTKo$xJj^Lsh)3E8P7U2Y)lJEqN50b1y@OQ; zvLq{Y!`h<%D{(7IR;&oB?`x3XS0XYege@`YxkL*8jD!0ZNuhrykMj1#!@1Bc;(${4 zHV$rGB!z{nF)F%!u@oeD0^GVt3gRh-QkxE{{~N7yM6Fu#14PZ_crT$IwMq7G@kXs! zA_Zw2=p-wxfc7hvXjT9JiF;a$-ltk*GN;g9vLA3jHXZVJE=bc1D3N)5yg#=tO4^^P zsvXYNLfnQ$qqvR;*rdjevsxu zur#(S_cI{f)Fjm>3l-YWl8tsfTL*jtHp(O^PD_|>uI7UcH!l{(jU01u%qm%tW$3<7 zSipBq5+%<0UM1T4?o6X!{1-i>Qo+BkUZSJFDv*rd{LlV4Cec*tOE#8%s4S8DNqkZN z!Q#6(C`d#=gFaaN->DUWYcsyE-~Y_e(c`?P$S`d0!|%IiM(j-iMW0rj0!px=28(W@ zRx-o15~4fg30}}V>@8kGIo{elzET1BeVkaoW{8gnBVpgi=l9LjY=FLxtF7+~L!wy) zPsM*JuV0d^thFg{*rufeN+tMtHY65jlCARUnjzV^nb+-Gq@}~b_9g6Ax3b)qIR(+4 zltgEEtIf$;I8KqY>ax&#G%k8?f@mRfG+&=g^Td6&aPDXEw6mA|OYvpo+C@@$l6{2; zCEqc=v|PJL3i;ejB9#0*&w=r~7E%zUiBKDaFnx*2!bdS|v1T-&=G4w(C!Wk)%`Es2 z;io^XlZ>D6AHols;mP<3*Zdv1L%PH}W`BY&=uLo~oeM6KeLxTHKv-zQA}QEECPHUX zG0Dnr)EGLgA?UPnx?b?bHy=)4ZBFKxoB!=*ToCuU4c>O<9v75?Q}>2sb#F-6vx=$2 z6n!`LVK{e268_TwBR$Ttp1j`B0C?;y} z!(x2hr{>#e8wD)%lulTZ|DL@|tdMNZmd=)j5RLYcWPB3YrQttxKGJ+YY(5e$`CW`8 z^NEm%Lh#zX2|Zyz-50_sfe}Rim!hQfp7=0X`Z^x^(%2treJu;t*P?z{ha?J`ktk?} zcBCXHB8h?~i=cmRdewqVSBogI(+_tFEso|dktVYxVUm~0(m0P4WVR$i$;)JE2&EwU z5ur93Vfqr&#Q73eewrr9GB1%N;Q%y|Bw=AAB-!3_=#o_WiV^-TgJ@yT@h&L76-jKo zrb*IxA(4eXmTa=F7s*Lhal-XQ{8(}8)u}X$i!PSzKD_xz7g<2^Q|Q$}T*f7&@Dm1! zQ1b8at8j);3f~-OxrtEntB$vrA(X;z86-lF@>iYY z5K7?)gG4C#vojq+DQsYn2qhmn#UYf!FBv34$)CYnNYiR5oWRFOL@4`Nk|ym+CVqQL@nSic zaeFe(O({9w;SNX=jI>(V4NE<%GHqv|z731ybXAU44Je1na=2&|CNRT5!$xDnggi??oM}(4>7-tBjAVH1@B`-0~5K2LU91%)hVw@qAf&@7t zl)S_^LnsC5J|fhY^q$awG4Z+NYwVdrcm}f34c{x^@`>>|W5Z%8%wiDxF3C=c`|9RJ zQs@%d-&Zp13o-jHg%86-0X%-^|n02b^N#CN*bpB}w*j zZ}+m+TX?cfA!^^EEN+uD2YixsTDH%7D(8&CeT$N`An~Y5s&!hhZ;=$Fdg!u0i7q8c zblIOwm;K3fNj$1}cHFrrX4gsa}(^;H-n05R{t7rVv&+aJdexF-j=4oCcS*JfYC$r(^WHwAZCz1a2 z^XS3D0LdQ5m6DS#l7iS0Hk7Q42ty}ka)xl?d80JnX~ULeqiu_4buldJNPNBKLmoIk zjdyyYI486C82lKe+;RX+9ZSD&89+7XBrCOK)wX~!QSS^kY z8niURv@pWHPcqRtm1TEt%79^SnbWW+=Z;FA(iYCT>?UafyrAB--ss zCT>qMafvJ2B;vLv6Sp;)xWwhIi2DIhK^&9_B`^MI2&EtnN`#UZ z|1^YB5Cu0N*sP9YJ6(7H|Dj4c`&FjZ6jH~m_) z6SpNpQV_o-LdlEU8bT?E-x8tZ#cd6t6r}5kP@;|WJYo8}jL(N;$wfgp zaTOW&YgvdVTT5tJ@M|r~adnZDBvy-{Q@qo@sj}I>JJYrR;ai#)7nJafR0Iw8jy5NY zplzO@$%%pz;=TxKH9mCX&=Egy10>?aFR5oFFK%hNRSM#lL@0T2OG79H@k=6mRCSF3A9Bjt}>beYxH1U9%IJt}b8@V2Q&0Vrm zC9J7y1f(kjr0Yx44N_f~Q*A?&yvQRYCwnB5QyE1x#gE*iB9Uo|Y%6)0rHtcCL0U_M z(p;Ik2$Q_bT!vW+@t#=V(}p2tZ5hJ!CEwwREAl_v(^~%jWVed!^!QT7ZO2;$!uuG+l;Y41)ls71DX2?DWI zz5^kq0wmQ{@M|@I601va8&lYM zeZsXa{?1e4=oBIej47j}t}t0d?1LB*FmZ%`yMbehd@ojqm`cAzmUFt2(_1+G8K?Jg zs`*Q7Wxg1vS97|8)76~b%qjh}E?rM@`YNaLOFiFm`8!VSPZ8sHID?i?encmW%NC7) zKEQn6SI-wr7w5D&U2iw8M}E}j5iUQ$=_{PdFH328qPLbK?YN)$w{rR{r}A@9TAsAK z57Ws{1<6nLX!Gcb^T3%az&-G+Z<^9(=T-Nlz=2t}@;Z$BBJ(s`!c0Q*Ua;o`b zjL+wE9jEe2>9<^N+rx6oyF1fQv1N(W5~d|u=@>0z``@iU%FhUR^Y>3mM*eI?C5oBd`9r?RsD zJ(o3o?^7*u0jI+`UBjuYF7M#-Bb+|Xsg@^mzNTw)nx*W{>2aKDewoaE&oiD(YOiov z(+|LJTm^i0`SGPG|F`KK@##k%-=OmO`;}JptDjp^7c1*mQd?W!ue7nOWN`m}4K+2D z^C}wp4bJYLJ!nwBit5tJ#|)7i6E)ud-scDK!l~ulT${ zeH*LiRo5)2?ps+=-PqK3PIV)bd9oVo>qKzB*%b}-zJ3nk%l7%|8tRJcYbxi*ib<>N zQfgLdMG{P>WYO9 z?DJ!#4K=Nh(#F}9CH2K6wH3C4(ng~}U2$nmRaH%OYcP+?P|4nFLkgCa6xTIYH&j%` z;$kLKGwMh?Tva6vb4yDqE91&SNvkb>E*mYeSMWmrO?7jJ6olq~82a)*HUyi7<^`L| zCk2~AnR#;qeY-t&$HkWi+kFvSFf`b%+-cKAP^cG>U@2$sR2S^fen*_wb`LhC%qKx5Xka_F>oIJ zAL_p}kkvJCX4j?o7r3x%U})E1`@nF2C@*xe_uuZ&?$Ej;q5lYs=@vMrTj125fwQ}W z=C240KQ2(!J^0{~FM_WGmLU!F_9It>+^%V%NT6@mV9&r%fAIAs%L08+N4ManK;R62 zD0W@&bvIVe zTN8@F4nWT6vSP^G??M+{wmW!TU{~1-C^R(qWPsY-CsZChurP43KX^-jtAPrRcryV-9dr#oh?!jd+9r`7! zTkxAes3^1`a7NeQe=Yuku)f`b*9O-Hzg@hH(>sywnjYxgDKzb|;GxCK0%vs%M7jlU z42Iqaly?a}eKjiZ??So@=}@Fa(_VTzG%rv9#~IxvG#&Qp@4x?0a6a_w5g6%*Q%1V( z4*nE8AKNO?gEt35ne&4C7mr15?1aIAzTJcELtEC}v5vf`AO7zWII|0UJGci5z{#Nb z7O(_g1IL!&*idoka9}{U;FDJ1S>1x$(T444gH2)5-+}6s52ptro$h}kIJP`^UQuxD z+TeL(H{Q50e@-y}jbN8Ag2#OjoVg`1qU-x})*(uuQglH!^-Sl=5a`z}xUJ3F;4a*u^y!7mLb~nK(!0>njfpcu|SLos3-Agb! zgC`?!>@pivIRy;9QTpe5Bn_W^L!z30fZtwvs z^A*?6t*L96-B|9cuZ@)!SClmk@Nxrv#rYG8D{D&Ufx5J&I%ejBQFV1Sbp<7j)unSs zS5(F(m)6wA$|g6~m7BZcVpXNJSLMaZOByS|g!#kJ=U2}!sjMjDyA^eqsw!%#X~v7y z)tPFbLUF9R%r|n%PLwlvFqP>gLuI*Tv?(XSY<k7izQ{{b;;~X@RrX77N;9m(G)AQrWn-}D{V9$R5+KatA~ao$Wi0e z`PCH-74W%*B}At@#P(nzRy8$^4Zf!0+Pa!rSj8^JGRrG#N*a6(F<771OLMtvV#Q@8 z4JE}5#cV!g9Co>A*Obuk|*(LCO8kup5 zyvEwfiqevX7!9bB(%ATts+g}~E~%pZs2C%rJXSZE+`;Pw+puvZO?k1}hPk7fN@KAy zSW*wF$x}v-95s1zoX?uIeoA%8e7FIb-`9ZNtSK+|RWwwUG!@sE8K8cluc>K3e;T-C zIj>$G-xzS^6>}QvjLnUc)|X(#OXE7OzV&nCJJ!7Lm4?Q80M_uHP}`V^;I62iGZK#5 z;5DcUE6$qIg!(5oG}&R(Upi=XenCO;xCwcqiV;IbP4ZRNRL{vYAFT!r!UW=8L&-p_ zXPb&Es;gslMmRhbQKGoKy1v+YV^dTA{t4BNgnxi6FIHb#S5b>aIHb&@h5l&rBwpkD zXn26N73Q^7h7U%Y=eg)Obo>DNfL_>YAfnhzWTWCGSWE!TXrztAdH`LTR{W`hP1RN34P*-n+ zkRdQfkv(bZqtIflTwPOFRb1!vy!4kB?rLf=4Wa`uIPfvRS5;F6$0>`ILP$OCp#Qjj z%)@B?Y#P($bul}59X~@Xy298-rc#=j3Tt?VYKT?U&c^ML5L<~!o0`=ya*PjC1NNxC zSZ#d;+AQAB*2|qVGFm(?fAYB8!jVx9@B>X9?mT4Yh=f=Hrb+ol#f8%*j#86xNoDO^ zxF;J8(|!~VT3AD$zEVs+xMRAgq{+^S#bgr!>#=F1e#6Gn#?%CT#>3{5V+{>(4R1+e zy2Z{QzVXKYZSanFS8anGxu#{g_4TnjtaO?x9Cw>iT~$&$!URt{IkK0-sP&DrVbh8^ zRTOSVmXwv1`^w5nYvy7)NB_^MD{;n(jG~%`5oS(p?XbB81rtWf04bdtE1fq|W=bC- z1bZ+~vG7|Rp_Ri6??i8|M4*s)(i^_kN7yCvtEV75c=vb^PA#cyOhnnaxWCG38cL~a zXb9LvhP8APf(yBAert<}WJvH^1&_dYG( z5MwYEdUR<1v{*wvot{+15Ko+r!L3QO1o73?%xBJJ9#5fBBS%ibG*w6Id$WkZ?72Xi;f3gHHS zY8_xr*fP7J%vMVS69DYy#>ABPGTd2Nnt29GB3d|kr+U6Iguo*z{db)ZexAyX^6%R2}s_PqTYq33a-N^w9 zDzGb1D}3x8;{GXd(p%}i|LG~Kov zhPpb;CKgJs^q@1&YZY9|nO?K@}GaKzR^f;2)r67OgsPU6W#hZxnF|MTgD)Iq$ z=fyDwOK!We_e?Uyo{RfRX)EKQJ{%ZVnmutV_2&4>ilb$|esgQ8V*TdU*TUqD3;NY# z2aa&uw-lYxufDF-9KBDrN6bF6#K1m@mQQBIFZ_ZR-_?k+E83RBksL z)oeQu30rR=d+Ni|tjsJl>D1j$f~`UYhGzv16fiz(D$67|Vyje9 zZRS$M@lifALQS)LWwH7B6p78wmA0^B>MERhjQxC~v(4ezwR+CvtI(<{v#6BO-1?-2 z{C;8$W@pSStyd;GmU}JWB^%43FxPq$>6sou7OSU<(h5wxdFC-hyz6;YG&g3Nj-VZmQ?I*y9)Zc0mWJMK%=?oQ;}u(x z&x9u9NspE24oKQz(Jb%oa#EZ-LY4@V9LjXDjEGXjvJrXmH~StR}Cl!`8@C z67jaKp|Ms5W-)mQLXdHYfeGWZ_2B129%pGixuPt_K@7VML_cXmJ~q4sqedqlOXQDl zJ^RwG+H7&Cf9icDv=)yqrfK6|6>GqR6>ovKWd(ZJt|qzC2@{J)wo+hH{+MW?kAr!= z88dOx@o|aR|KXs94zReF%q_QrXwaC5Y0kvzuas&~WuYM+$L9gFX?nrtuoy^~myp{6_1B`~KWNhPX3tP=iGXA{I zABm}=u3<_wP6b-KZhRqU7O&nS&bY^UgWw>=jn$3VM%qO=os}9ViyFtpEY%->WOL-m zA(>fNa-3;b9Q53+D?fenFP!A_H)Z&b?{G|LEglsP1ul=jYl&t=dPYlNM0(FP!Mya$ zm1()@S$;-5i$xE9k#GX9C_MN`vfjZQC` zojz-NYY|_9<#yu3)tUb48~AUe`fmu1O3!Rb8q)XGLrSA(C0K3dk9S)8FHYR-) z{;fr=8<1{FD+SFc=F0=$1khXpnh~JM$GyNZ+#CS`GjLM`6vJlHQiD)bv^7)*A#j-`mfu4N%~TMQ~E0Zxb(IDG3gupqtiF}F9jC3`PgLA^V$0G4LzM9XRmm#HU;TzfYS5;vrhqR&2rgJ4%7ppAEHV1jxX41%> zjTMpsvKy-_E9S*2ukvMM!GsBgxCdkp@QS7N4TIdmfb&oyab>aDjdO5JhD8zQOJ>im zi_LF!n+|?m@{+oe>N%LZm_tgAK(qGX+fc=qjX9i74SdKaTpl3cnjdN-70{&)EYfHztDQ`Jwj?v;ZAVFO(ddEq}(;leNJ1l*^*e$}sx z+do+GOZz3IIW8IhCg#`uC1pu#m|s=n+sgdCN4CBrdJB35GNOxMlxBo1<=iP2rT(Ya zDs}%a0;XjW$LkgJ>GS0&;hOda&}fYKdR5b>&v$6NP32S5Hsgk>!jLx2w3Pjsk@W9? ze_H=LfoYk<(%}jE#GhifiT9~#Z^fNt^>0p6e>!M&{r3aYGO4nmT>sLxREl4kb~|oq zjHn-fnm&ELbNTNz+EOCzW4Mt_|GligjB~Ody$-AOe*qZPukG~t^8j(Z&+oX}QoMfA z`AytN#{Y+XmP3y#;ggieByTzF=ZAD7$)#y2%n`=R6^~>8KAX)^#*y|5;Xz~+iMe%< z?bpPFWIuW>SNO%QWK$jch$ZVo`tLPy$K)(DzP_qVRnqa~# zX>SC5YLIyJVl4(doUE331%3RN*3_+_i98BKG3cX+kaz`s8Hpf}pc#RQw}QSdiJ+lZ zgO90##uHn!oMVcK?X~mQF+TBE;kw9Vj!Ed5k&Mf}Q0Vg+rx@&BG=KVFf7&Lvmz{Qw z@nyOo@|T(NF`ggG?OBX#%at=Obdt_vJR$(6S2Es5;r4re$M^;*d8Y*$bTL4-yeBvsXq+;UgLS z*D3G-27EI5lTzUQQs6l$@ChmKq7?X?6!_8<_)k*c_ol$N0ylPU2ZP`;`&^_?rJ&!P z0)I6H{(cJluPN}uDR4jHcd~xzkpl0Z0zWSWJ}d=3Dg~|{=HM-LI=j**ao9fLF&hrU zYqQVyWfzRZUanx|fOF|{u*q7WzsX~3hnt4(0+YoyuwbOUXQ&67{J??crXd+#6DQE>d3n)N*(iECm3m%c48&8Xh99r-6XxJ#d%0-xvLG$)Jxk2!c> z2hZU3F4ddu;HNlvKL^iFfnS&ck2!e6k@J#+_jmAj9o((=(-im^UW)4eDolaj+}b1^$JDyY1@23uCI+ z9T!J}$q`=SSgCx?^t+&*{-FDsP;4b|(2k+su>rDrzy|T3Tu!GYX zklr>!&rNAFZf$wo}cf9y%FyPYdIx_`+wS&8I ze(T__eLhHmA4h|Y>vio@;^6LhzuCcE`M*tp&!bLd`L3OtQs8$uxU0{Q0A6Nf1De%Wq z;2$`+tA9KGEd-M9`pNkY?&?{b0>9hA$+Tks_Z-}ne;of-g7)XU6!`59evTvmO$T@F z^M_0j;nH>soRSJ5M|{-Y&Pl zu1SHvkOKcK1)hFdTs}>SQg1&8Cl?mH*uly51fS>N(8C2GZ?=JspxZ#gHsm?KEc6V`gtkvt5e{sQ{cBa z_;9D*yBI$f*fIDg*UuHs%RJw&963247X5$i;JFU|xP!a(KBweMy}wtu)cdL<$F27r z2Y2h`?LPjpfUnh4BHkVuoR--0d4D4H5Ix~nkz?TE<%37RhS%jhP2nWp`j8>U2hB}% zk({u}*^mC57x_O=v%t9uf1ec}p>X*t!}$t7p4Yn*6`sZIx>VtRCWUcbZWFIv&+_^; zrtlkie^syW!>nhM!Z)y-EWhn1_T10G z_^_f6b6I}#Pw3kQtsT-h6+BP;K;cibJu?)3DUZAE3J_$IdhVujzs_FSg$$JkF+DSQdr=LUsOVEeCE_%5#Z zHidu5@nn<2*Ra3+T;YFVIa?H-$M$(h;b(FGZd3S4d_Mj=h0o*h@{GbSXFZ=+_$RFA z9)+L8<5zwgQ~WlU$ICm4ekj-bzQVs{|Jkqbja>e_!h7(z`%>ZZJm4FJAH({~3pS!> z4zFk0vOK}%w@f=I{7Lr1&IR-(|bcQ1}^a|5AmI;W%)G!p~$szf$2_ zxZI?0d5(LH!vDx}Rw{fK*W04-z3l(%6)xwnKUMhWEdOT;{}tQqK82sgcHXM+H@W;< zg*Wi{`klgWV?Cc$_)_-g7ZiRE>-oCEXRtrKuW|OP{R;n#`~B|<@5FKVONDP`zx_tx zF}A;-$AOHyAM-fssPGfno&!?g!xY}k<*33dSE(Sq!S^uU zQ_;_4{A7if5+km&6uyh?An!Sd{QVqU7@_tM^9vwcP=T-rNc;nLpe3YYelD_q)Jr*LW4)e09oysYqlaD3RO zaB1(S3YYeFXTOm4p2Ph$LE%raJug$Z*kO*s#SZlf-^O}gqi}h@aJ9lE9^ImFvBQH3 zf1BgXa|+LA|9(~BV|ac0XNB)zJp()shIwg)ifA-HGF?*i-!H zM1_n03{&{~96zThyovq0MB#GYbd|!t=Kfx%@Ls$wxhry!@M{@=SK+TPF8dX- zC&};61nzIN!Ty}#ym;mb(o5vak3Afm^y$uV^#lhedgV1IalsY)o`(w6?oWqe=!ry258x{Ru8NXBEbGaYyS9lfUk1PCd9LJt>aMDxG zO@8m-q|X;j|GI+{{RKQPyzSsb|0kZuKX7oOFJbxv4o>vvbNoEy;6&fT^qCy*#XjPKTbSbVGl?gOmQRvHrhRa^!EM zJ?YSsoE#&?=X=S)Nlp*$#{&-T(jQUsvGq`^UC;5#mzn8*)&bXXI ziyh?o=-CcE$FUSM(P${WS`|nekN$ zKf?Grg`dFl!cQEW#;?4WbGL(&h#aQh%(&6!qd6GCMo<=#xHen(ldwS)oceR`<&1E%yDp{ zm-DeY2Pb-2CoOPrqL=;8G6yI66M6k{or4p7Tdwye2PgXVOn-}m6aB3`-hbxcM1O$k zA5wS+UQhhi!AZ__Eaxc)CpmMOey4*Iy`1B{>fl7biRs^TaH5y&)!oi8YH?JqYc5tGv@IsSKcaFRch<@a!KqL*{dQyrY> z?_l~f9GvLoymX+06aAl<{(J`~dU+l*!oi9DL|!+Had4vF#_P064(`%l>flBnrkB6L zD)yPl^5-b}DLk*$Ik;&r(=TvvlD~<^<1z;~?PdDw9GvLy=Ki|L!HxdBKDfofiT)`5 z(e*P2C;BB!{|g5v`a164ha8;fH!=Nog@4NUvkp#jLOd?^I5^3f&FhBO9h~U>T<<;y zck4aCxU2`{Z$p3X&{LJOct7ILL;;udSGvqwnFjXoDC1oeF5ja&LE*n)`rZov2jkfa z@5OQXe1%V7JV)UVGM=yS*BGCq@MC#AU#9SWjF&0Ag7GSapTKskb8y$5iyWN#Wh&FJ zQ20+6Z&COgjNh;D?p*$j!Y}0VlM4S&F7HyfeDCfRg5q!Z$JAf$I_fyp-2(S&WMx?q&J`3ePyr>N7;)qZuEW0xwW_1=CMa_}z@l-xl|y zx&Z!(p39hC@a3%kZxk-?mHk8E@_eT=w@=zTB4XQhyu#)CauJ2U$@F~{p4r>VIZxpg zjLYAymU_1_p2zgkF4418;rCJy!gZ^{|HSx93V)5`+d+kY#CT`6x76E_?Kwr^6Bxfq z;TLhe+ZBEd(BFEdX4 zLZ;aoAS?TP?>e}NC#PF{zrq(X{<*^MVEh|}Z)ZHn{V)FT7sfj%yzLpbUisdo)-%HN zV$Xiuu1N}?!}w~2|C-mOH#_x`9rm#u?sjmp!(`THi^3N$F5j1wcHPGK?-ae@I~Bf% z>Gvr7E5=`Ua97XI85cVo;&%0By9$09ub*QIk1<}wxY#)}Xzjnm!O0G9ooVrv3jdDr z8x?-~S(bi-!pAdym%{59|E0oz!uWqG{1L_SUxTkp3DAHsMV_w%tJBRkJzyraTjW4yb>y0=#`H%GP zbO)z;-(mW>3g6Jr%DGbEe_?!~!l(DQ^s5y95##F=zG;A^m+>KXIBTHA?^g618UGK3 z|DEwi72f3>E9Ys2U(EOm3cr@|*A<>I$jbSYaT#ASj=ps0$3g{*x76$at2*?`C{}!oOgAh{AKvv+_qOd@bVz3jdPvDGKj%zLj&i!WS_< zN8!&iUaRm97g#xs4({6XRtG1)y^QI9rtq5?|Am8-9Qj_*!wybzK4khQ6yA9VG5LJY zDZGjCmlgg~#$R)Asy8Rii1EGW;8gFEO#cz%;#U{(zN`nwSHV{>o~!V?jk5Kr*-tKYaI(XXhS~PcQurf`S19~5#_JT``64T4k;2bqe1*bqWxUzJT|Mt* zT-x;*x9cs1?`OPQA6viBSM{>x0gRI!sJ$a{@PSL_Nx>K97}z&b(QjqEK;eI2e2T(5 z=2|(IEBs2vOBvUCu2%Rhtp5&$KgRe`g zr}6kTkBgZKzcgy~DOdP&jMpgq0OL&x?~rfh{7B*b7+>k&G#*9ITN#)3uH|;^RQT^1 z|4!k(Pqp>7<$a9UVLRKQyMvP*-eY}ER(S4MD<@0gcQ8Ic;oBJ>qVT^kj-S9c*RlAd zdP5i6a@4`e4x(on<6?(GZr28dFK7HUh2P8gJ4(L%zV@dMPWt?u_4!KSZ40cP-zj`H zwQScpV-#o zPdPZ*;ZLm3?-kx{yrq9l;TJRhrxf@{3jYz)f2Q!CGybK6r=v>IGlTcLVuzc!U3m(B zgz=>cKitmhf1ASlQODuhrSKTz-zfa2jLSY-^!YR6m!EIt2|l>JE#IW@dd8nq_=Ajp zqwv2oo;|p={C_Y$TH!sYGjYvPcpt`BDLj|)`xHKv@#hsjkMU0w-psh1ABY`(!Faz5 ztX_ieV0@CoUt#=8h5wE54GJIB(aL>F;qv{lPZi$G^j&ygEBZXl_z;D^!1zpszs7i@ z!jC2A#dWX3@9b>x7ZpCfi^bno`1On*RroI%AHe%#(f3^s2TP*C`t?=38@VMFyx2e=C@5v8S z_~|?^T&eJP*x%MD{Ial>`yUE_n%lKc;fq+GlX6-SqiV^@+O5JE;PT#bzd-{s-D|+03(h(3oWbXuHz+f6K>zd4kAL<*&te15zhEG7%sTd( zQ3ay|ey$Mp`+XOg_OdEo(({-&O^!ixe8>5E_;N4Jof<9OnL8!7Ft_>ZDU+kkPe*s` zYm?vnT3$<^3t4`r|mfuE_wli9*)q@p#p|P6t_f%nqsFF zk^Uw#<9Wk{@*q<#f(R5|uvBXzdr$#HL*gz-d>(?0hi{1w1n#gNbgEf(*7JU!s z?U4xh!&z^E1^;+)VA0hpOd8I5ANO93Hm`^r1|Pvz&uRMz%OX;w8Agqw=PAF} z(B(yH4~1vELqHA_g$xl?DPVvcnF#hFHQGe!2CASY(~X{-lB-dfI+X5{alx=6vRyLI z*i2p|i|95neG{h>DFq*q)BSCnZsl|{ryDq3%jqgkmvUOm>0C}{aau$voOZKm&`#@} zC~rmCbUpmmf~z05L@f`S9y2cZ=u(h*w}~f;+oZF;4UyYOBn8x+d65DnXpe|qV@0D1 z`sPPLOF^U{cN>vDN*{*F)>7IKDIj&XTlVOVkAj}cyNEQ?-Z0>_qYs;QtVVrW+(g_X zhJw{=xZBql1*6a*2Rc}(%b&>{@Q>U_ND0tyMgm!RR+d2@1*wl@ZAW@V8fTE~lQMZX z37sWE;olhY7-!pY$Br|&!!yW6mp_v;YQT0QhUyxy`-(KDdNQw>Fc1pJ2Afcxctx6M z+MjA68VjI62KNdi&?{_^Ld(dbh(qBApKg9?$Hyl(zvAEV$`J}qxh;9&qb*lmjh19V z@hM12+O0^bhZz3)$nqV-3s%pF6g9scj=ub44oG$!X`4IwXhS4QmZiprAKa1Ge5Cm~ zTREB-UA;a+Kj0f({jiCZ&F`{aYfQ0a&>i4{BNaw+jIDa+lGDRmKEuF)`Qbh}u-lV{ zt6+6uB=Zqk_K3LX>ctdtU-cTh2{*mMTCeb z`)Kp)9PCMz{N|m_2&xqG(GA2@u=<`z1~rMoy@~XQ_!RClZN;!?e)Ill^T*Mayhs!w zxS;v3PE7W~Jt*jAOT)81kD^t)FG>>c%V4F3$Sfz`4|ZhR%3!(5NFRy!JZo^gH`bMS zKQSKf3!QjBxK+H*f)P+DBtq_%NFPK9%CJl<1?Mhv#`{&7pSCbAs{YaQqzIipq>yPM{Vqe77nvoIzCRNk zHq|52(H| zX%3~}BXYXGnbS?2Zs2qkr%O3);TrSMl8`(EVbFp?1I4G}{5b`a}O zj>xi!WZ*XGtZzf)X53C0>5XW}pt$k_BK=mXs(ELtNWYEbXWAQPfKjC1g!1YXk)9YR z9@%b>&o+ahgAstx>qdIg5wem-dQ!-QXv(18JyPC|48`Cg5o)5>3`BaO^dkMkMCL?# zs!K)sO@K_KM-ZScTZ^)e^h9G-N0po10txi)tdasEeI8eOnlKwP%)qi1MjIdQw(T-o81SAfRGZ@XB3eahsPis4wH?*~ImVP8~# zdxU<{5~Gaf_%Fhp*4s(H+0L9%g!K^6+l`|jeD?`II5P9vSM!jqr5JB~7Hyz85R3Y( zmJ!3l`A_XQ+!o_?4eG{Yc@J#bv6j>iEX+*9ui|WxWL+^(W=_b3}q9C>jV1DVlR$#PcYAD(= zbVGhKR^vxVObB8Mn%{^vuQvf45+4mEidkdCL|fACpamGqYEFwlUI)nY=Qsa><@Gf3 z{!H>Rk^PtQSk7uAr)RWfu|Lfyw%)AWjUJF{v_p`x(yVcb1)Wc$S%g>>4HKEArNEDt zjy{16OyM!wyv9r!M#S>Pkv^qlAM!#>zkmB8Jbg3yxtT||BNM)HFwTS#d-~w^?&d8R zYjpRlUFa{`2YnIFz$OBjX!E;YglE`QHKc4m6ux|inQr#rrq!1mFD(*YF`*ZYyoN|q zwB_*#O%vQ4MrZFJ){)lge0#85^cuxie*o9$NLxs=-|RgPb0galr}h^ zqG_q1`8m5M-tkczdlwDPpu4khcWld`YtaVQ3E*ZU_|L2(Ne8%rz#Z=eqRlT+jTzou zBWIIgd5J+NLG2Hjsft20smk{rwh;k4^VOp>qs?Eho*!xH`XbdIe(+$l^wsc#*f*lL z(4s)|=ivv#{$0`4H?G2c6YC*p$G%{6L$qbkkBs;Pu|!7AT0K9rrE5D#Y)Lzh;taVG zxq(-K|JCT~@wG;jUB7wSHxcb7ROvM!Zhe-rdNCS+8dAt!J+(G(_2`MT`G=|=Wod1LR#lRh3~GOzhNtN~c8H99Q_wnIA%cuOPO zqXW_Arz|~|9CWHcm$4p^vu&}EMZwu0m@S7_3E7wkq!KIJlS+bUA%mKnI*jOeL!*-) zqj*h5!avx@9^9Dz22NGjwSYAK`Y&wbv9X&40y-b)&uJFbhN_lQ85aqmt|7~XoK`q? z0yV4oq@yEkYOnBX}W2R+@gU;4C9(V$3EHE>N7)NwkR1w zhFt`r0=ZTzdUQPOFS|f&q0FceZ!#h>iPaJn!CIbsv1{S%3bm83xgJKo3+_P?t3^2b z^KjPw@bv+lGKa7IFC6xUv!27P6Ukzb|7!O-cXRV7M%p&w z_KXPXz^<_;J|(_FLIjo0*oVxyR=WL(Ba}gdDeI4DZ*JKFr^Uw{!Of-_#ENiv-MVmRxK@b$+~%dYj~6Wqp>Vf#Y} z*Z_vJaBw&idKK>que=R6!`Vh|cxAn%XUSv%a-#Ssz&JW0!_Inwu?-*8vVq7s$g8a5 zpn8b$h#xn%9A`5gviuW&_}YSh9zANU0*mwyXMZw(JeY^E>EQ1U7r7`RM)=a5?x=$@ znD9|Gd2azGGS^LK7D4!S3f|NnNZt}z3fbYT7p;ni!cRTx4;StVPuUrsYL^FepI!WM zGnB-g*`O;TL=j$j6Uq>R5Q`#uhIQ`rxIF`PqhhO2pDN17T^LfGC96@I!aM5div%f- z6m}s)UXH1!_S!#&Z+rmSP*XvDyFDvIAJUEm)VD`g7`Ew8W9jZ&i;tX;X;gd;+63#^ zNvRe&cTz$H2;)g-Bu~%gx2(a;jIrKKPR)O&2u+d5xQ@hVsJI)7MQvwT0?a|QaKo(+ zXt5jM4gNcFXnqT-br@GPR zKO;VJ3lNBzi*`2-l}mDvNr)!}iO*>Bu-TlXAjx2o5YiMRnM~3%HA#Oa$x2OIchzd3poun`c>{!4h-&y6Je z^eUWvAbj0BcvUH!ZK8E}<)u&p?7gp zeq#xZE~c)hcC;~Z_NnmHT?c*)g-|_L@K*RbI`ff6E$d-g_)d6bIx=4CO2A`A<}T32 z$KA~RGWHH!gTMMIQcc*ShfyXJECtJf3YZ78NfhASD2re~6n@HxcXphQUkALd4sx&eo2)`YLD!~sy(Y1dR`=wTW?8)SeF)<4 zM$2OfD|WpC@zLg2jQbx7U$QHly*qsUDm>mssKYWO;~q6p$Rko5?$U+>mb=HqFj#06 z%5)+6y%Roji#c$E-Ft>l&hZWShY4#m$u>c+<(AsdqYme(0BJ)?O|%;d>^^Eu;dYLJ zcwj+2x|u+n3q`3wLy`&;sZhj)A}Y`@raQB!KtrDjSXr*HyB-w!bLG5O*#?@{$o2$e zPXe-65LynA^3HNvZwl;AOcucuw;jXp4QIpLST8n&uRl9v*XX1Yb)1sxEYqpK=`K+b zwJV$(W7k#2`UC!f8nDkYchF%I!GWy-t?&y?7J|GL{vIIl6+Zb~e1%VDXYst#!JL(o zP`RlsBX?Tv^gLREjKUh^hSTgq5-UG!P`-P;!5Vzu4V30n`8*kYt;L&NqsO zV4+>KmJMgUVxP%ja-H^8IC~$GuGkw+n?a2!G7U3N{t6=*;bliLcVP`++6oKIzyLyi zDCX&x!9xFx4T`YFn<@(zR@AQJ1}P~6nk(FP#oTyE@$a9#v;)zE2W_GzxVdENEIJiI!$S=msWA-p>`21`=&r^1-&T7YRE=3gy znnF&RJvu~f)H`+|7QWQF7{G_ktO^@qwK8LuIf{77jA}o82KVjSjy9#wVN+^0?%05A zhT8C(UHAuA2xr45F5iKrMtIq3bH^^`!r6xxTpi9f;^uc;{;fZ}ayhi5d0NoWitXWR zuLBRA2qL)v@%!qtP_JPMXRk1`e|TjCi)%~2odJc=pcxU2>-f6J?x+tu_8r>1cEBIQ*~6&8;h#Lc{OQ1f&oGpYtALLpc-Ec+ zL+#3ZOJpg?V3)o~kj~s<&I%ye-Z?N00>Nb*;6Ux)Jvj(q4F{h6nii$qh_$XGsEm+s z_Qwmbov>ZEY#3N@r#I0c;)GZBv4BT984J<(+hY41E_{L4qU{U`g%DAihIQ7i0~^0J z!feyB4~MTi<||U*+3@o3zC3#Llj~)_L$uG}c-F8};RUqfk(&@kqpJ<`qomI7=);ga z36t8K$1x9*AEBkdCohz*rS}PQn_u!CQR4Yz6S)TtrLn%XCplPq=HX=69{k8t$pQ-P zSW1Q;9Fs}!mdv7UH-)9`4QXl?H##^GM~VxqD)yZ&W`;$gB*Ebx+tL9Kn-+913mAKX5k;ljMAhi(q)Y{wY2fO~ zT-!~}_I0y=RaHSIo*j-k!El-9vvd?fD*r#`z68FC>iU1)n@1QR1SCN~Q6d5fD1oqv z3X+W_7`7xRC>map7ZSQ=1SCKX%ST0v|3!@89Ie^zm8Q*p zM{7V2TQRC=z_;Fj1tr{j9JGSx#qhM@!~6l`o`I&Whm|EPHA=3AB_@o$)3pbNupCiJ zUD*RpFyIRODrj8BK)oNJ$Dgb+BNM^j5|QaiNL~_>nS{&}q`2xCENL;M=Uu&*p46yE z==4IZJUooRL*EDJS#j{Q!=WG|>DeJhUUA@gFkUW0Im@uHz-NamNtM87ho~uA0ABup z#5Jj-;L+hVo8-CSHEHzN@Gw3x#D{x5z%$@y&tl&m2qZo~{N@&DHQ(oks)0cy&JR#J zX2Y6YlShva@d;tobI_ztsApb?o=&2Czo&+o*o@f)*GB#Zg^?U=2DLi~^DnN0Do{$h zX2vKeXrA}DP?aRrP!A8`rDV?h0F7)U5?L0x0C7E7tBUWWB*s6UOtbp%*zs^-qvkJ_I5@Ggp7qOt4nE+_h2FeBV zssY)!gqR9)fL%?o)Fu?ptH>tm<+}})fph{5oVaNh=URXiTnw|T033Q2n z0)m6Wz<2h-IEC{m5Ir2`=MTe`hB+A5fxZ|fm8uzGq6X_Vo~`2#SOCyp>5^ELpc;TM zn57;!#hp#kD323&iwv*=Iz3nut)bRuRBu@EM*vX;!J`r66$>f>Fq5Jch6Ajr%&G|( za6Yj?$yd;OC~E084;ZmDYIIOxpokSm!&;Ck)PSlNxvjSh{GbPNfPQ9We4Ld87jM#u{{o>Pz8aLNRa=8C}0Yp?8qnNm4y+rb0o;zN)LrdjZ0js~;i z@JTqiAiNMsO^Pv;l)x@EkjjPfMH^8890|iOY61dBje@Be>=e?oxPkTP>f@lfmXl0; z|M`L!;Bnk%1FjlP3l23}gUR-&c^_n5HX!F;1Fjkib(jZRl`w@ZhZo|38IpJLOUi19 z!RZf7ZXLLl`~VK#O2O$uvvwasmoh1sXba2R4A%gb?aYFT1+eU5mYu;YdzfW+Fw0(M z=?P}p&n)|bSYQ*E>y|LdeSkWYdk5pao5<4>nR)_)hs(BDPq;BzW^pcCt zs1jd$WF64PLpXdpras{T4?G;GhI+m;6qc<6M&KKQuu{bCwhwZDFyNvmz+JXrtGx3R zY)X)5T*fZIhcpm6&>0hF0BNwo=e6y<$HBVETj1vQP>7k1L0_jZp z5{wipgo$jG2vI`)3|C5+64TWkn-b~=EmC?>VxGE_LW#vH(VG&>RH9F46{xsUCHjRL zAW^Fl{X?j+`q*d6ag=CPchW;^;m$gh7!oRn#AcNk8mffEWhyZ&gun54xk{WAx*QT) zRANl%GGOaci3y=?kl3yg6DhG%C2}dTOC=^zVz)|64&k>P>`{rSq+5?loDsSj%6Wu7 zUTdZNfZAbb%1u~FW!IaLWco)~U2cc}!rBhBQXKptuoMRyu^+ZAs-1c*5Qt)P68b$s z(*fN9iT=o&;-C=r9QKhE=a+ChE$oo{ga^`vKKmT$eZvco8i=CRIYe}r&NiJRbnZpx z6gu~&vqR@TbnZ*%esoTybALLg(Rl!!2h#aCIuD|AI-S+8!wx3;csdWE^H4euqw@)L zK9SDD>3kBMPp0z-I%m@P6grQj^Qm+mMd#6U9z*9WI%m^)ES<;E`7}D8PUrD-&Y|-J zI!~l?E}bXQc`}`+(fJHIPp9*lbUusDXVZBGooCWneH-~KqI;Clf2}SLsmsIa@`$=T zsxH4#m&ep)uev<0E>Eb-lj`!6x;(8e@>atH=zEGF0XZSlBBE7S03t|oFq|QEm zpnh((_oKa@25+K|$UDxU&_u|WvCaaD=Hwacg^VbE=bBI|uqf9nL~i;vlkh2_89)z? z^eNpA*QAn_@9CbVyvxV0l< zMA{^M!(2+M4R@)seXg4VWQubZoR7!;H8|3@sLMAmN&C{hO%>C33I3cNmDsQFCsg9? zQeukpD`+YTK)X|CJ`Ti?g;wf07$K}7b=6ktEF>dC>g%o4yq`lZXGlW>lLLn|HZnPM zNJ}x3nL}EZFqu81Whs*rhqRo>ay$8x7L#3uJJPw!@{~|J#Mn@FA81p-vx9k99OmQ$*it{_7bAS#H zgjo@XjI}}zRor3X&QuAT5~DJp zMI%IJE4)`|IFvq7B~n5oAu&B;ftA{unDR0fv|6bS(M5`<4;4HwVxth7=yD6vkl4Gv**ZdQq5p|gQa4Zo?w zL+F`ZDi)-U2<5}A6bJ1x6pa?1jpnjDAw6szq%c(Qo%wXk z->^C+PKd`^tZ*rD!-uY5?siJ(W8{p*<3MBZ8?IoS<}gW(ueisDPK9}>!pCF4V$$Ot zPuxoTWu(bW$bs5ZinVW|JFEE)rG~Ay!p#WNYCIe#9hICF#Qja?rWA5tL@Y|N`$PYL zZGNTA{iuRQ4qIr22phIQh;=|9Qaxyi0Kk{xxt6ArWf>tXukYMmknBS1$j9bay;70TYWZGQR3x=ozS|WQ zNG(QWa(P~_%=dcbJg+kpd%cn(sp>6dUI#q_i5+yg+d-G3Q0$-z!VXH1lGy5X&^E7w zuJJnPdQT^f-0UgxJx{_9kURmq=Z#2NnRp6~qjnEH1K-)DoON6X@9H%6Lxo~LWD0CQ zj+|NbJw;0{)C&90xDYml>OV|VTa5Gi55v?NiWVVW{}~s;R!|Oe8hMYRPghRWVuf-@ z6@@B#1k7zhlT@dvS?!Vy9vQD~5?Eza+{yG5t!qrx=rc5Wx<=2^=ySg$U83;^(F=%Y z#Da6J&|+$=5e-&o2~pgu>NP3JG&5bQ#8l>5p*WM1tWcYlb)819CmK9s6oza$-hoby zN9cz0hQJrX&EZ?47YVUUh^P?tLbNe4W-}1sZfndnbalpDPuGECZlmkaF?Z87bIjRr zP5BJZJFFeH;LHSp7l2(4z!Ow3`CX|@aYSHppZGXf}qoI zQluZD3opkES4f79NF{t~_HA-|XVUHW;P$h0yWH;Xf!j+UHx=fIKpy-qOArk_n|yh&nB(AeEHdFP zEC%|iYrpil;1hB;GllMgn9vlk9G;fj-4C>T>?mxxu`|4;^lleGF+1gU zQZXLiX5f1+k#8cZGYKp1>ZDiTCCFjJDjlk)93Vs)pyf-ET85|PkYS>@)Tnu|#yYly zYo)Uw&*NN(lLtnsZs}~XPNvhM(Ia!mbXp^Gha1?5M*7rECNL`ra56_t91jo4j!&O9 z=45Qn7N}ThxD(Fk$ey9LfJO&dBjj0PcRO0QxCL%!KseZSTKr*y9bWxOV^=e@b|jI-I-%D>6$&}$H15J8J>4!ody`iWBKvutb9Ts z43`iAVfcI?vMMPBVYq<^2*YcLfH1sSA)B(UCIZ6nbwoVXo^>M;5Qc9BBI_2`>E1?&|Fo#4Wlfvj-N6wQJMzNfOK6#E7dV8P1s{4oD zF>>2K^vE>Cdi-73ZShTi+-ZnkgeeaZ;EF9^FCdOwsK{-O6oSlm!6CKcl~ zCx+eyiG0TpdN-*Y$^rg2LhmyW$x0!jU?ii^yUGZfFg|UI{BK6zUQ_}GU~=b3_jY{( zf`Rx_){g@*e4D>Ja$s=%j=If3bqPiDql8xaJ&@4M@DvqG=zJrH!6S6TebX#!(mGgO zoD;^?4IYzVtuxC@!Fnmr%{&R0t8QvCvWE(cbfJ+h;#IL_DSk6OJB-_ac&Phcnp6(W z3FDq9#B!CK7sjMAUEE>i~Tn6JYt-9HQ`4w{Fp>8jAD|H5fq4UW=cK4o5(<>`+og2m#InJEUr28v~ zKTl=8$jv+rR+^PzG&&yB=0nZ~*VNOi>a5Ua;4Tf5uLp5gu-b<qO{Yr#I;El3d|#!pDvE!L z%Rg-)h`ZXY_w>58kp32zwxx3#?l!3;8KMU4vd7C@gC6OnmGJ*udK#^kJ$Y)CyqC&X zWxe8R{?*d2x^+?czij%CM*c6G{db*_(y9hKaG?0V4Zmf$tRJ zJ|TW1#B)OYfr+fY00GOi&g@U*R+zs%Y@Ws~UI&La0joPY zEe7^kP#6xbxMD7+u?eRCl#wQJ3DdVu;K$!qH~6u^j70@1avgK36*&%`ni8%K$oVUs zGt#{W7a@MU{|&sKGA~UAdUd}Gqd(RW3n}f@y_J&7Bi+08K^p5h8VqmfO@?u6=^7Ci zPX?z~L(h1)7H&{NG4hka_v@u11OCE0$inEG%z)K-hSfS!b(V31nAO59)BdBCvee>I zf42oy(~~t|CLZbDrR(=T6hD)*Ypw5DWf}Gdufk}ThGc$an^IMjzOY2A{~(#)lb_&Q zDE-l7ZqM?r$KXcaznRmw+F&noA=6&yzSAJk`8KXw0WuVHfFmt^t+ViPl758J?K@5f zFUw?n^i@K9TZo?uAwSDA`g1}4F2q|x>=)uGA$o+kTZkKl*dRos5S2p6Px_255u`wf z=}f4|+q*x;zpl=#Tb4|2l$f>c)`Ui(i;y!Z@?~uye-RP>#K3j^#(l;mJN53~@Pg9@%8r2)8B4^lyaU z9kaxL9izmo(^UPBtRu$aCCqu`c0-%uuNo(X)T9>1sQnAQba${Ke}!1x)1ERD_wv-B z?%5XI@pV0Cpo60}$DJOmjlWM69xVs@#6*rH)+n&e|0q#2UlWj&&}L^Ff$=e=o6)bj z;m*gbwQ5P+Azl)ij+@1}3jurgp8b$JYrNL(VCoK>7!p5>m6%A%fuH`yRwen7?tOZW zy}miFNz8FT&#~V($AgJE4(T}#`sVn1Vvfz5w0a!&&2b8ZHLV_9dX6oaNUC{RVve19 zj_pY~u$5MGjux%;a3I;LaJzdS#5=!n;tpZhAHaaWI80;JfdGcT3&X(x2K+5xlCU>` zAy*g|G!yaTv={~eEW$LIzmbJoy51|yxB!lHU!m&Ll)IHBmxKF%wD(8jBe07<%d0r9 zNfznZjU`q>S;NkSkExC#M4I*qjVJ+Znj;J?NiRC zMk#t)Mo)cl!Q~b38gQ1uZw^C02<3s{PwL*aXysZ)VVze{vS^R{v!h1 zzJOZ+ymO4#|L62Tw76UVx>M;)|_uA?)4?p#J zoTR%?eV*VXKlORiR*!iy)nlG#>2od6)S%WrTORXF$HzR+3#YpKf~&N8-ti(;*zP{S zPkHv+>M74be#-L-@$*xjSKZ9`l;;gQ;VI9%uIcD0&lkjtk9FuNPdK8U@*Lu)Hj+MU zq&Ig?-vG~1}ZholK3kFwpDCsGb zw!5F&q>QA}$f44`U4DA#<3!})fO_bY#-;614}DY;b5FyEK9jx7^w7sktHS4T;SX$@ z2Jd>#cez#3rAE5UNSC{5e9W=b)lA7gFM_X@;*45J`)~I?ZTq|rySR$6n#AD4y~Ok* zoW6qZ%R{=ab6PC+9ajyvQ)6VQIZH(r9Kg#1c4Tn*+?B)!8x6P&NO<=K7p(Uk6DOAa zW)i1=RO<((a0dd|@#j(ChK_9!FX~{?Q<>4LOpuZl$Ha*{%HK%hBqd=__JF>%@&gEZ zztCBGbswa?`{XJ;3#`8-R+vZb$9b4ni?`w4-vaKT$p(Wzpeei>Cd)^@na9W%I|e?q zEdBy^V!3{nMSIe-LlL%?w}Xt zf+U7EVc;F}B!=$@1Miq0G?lzp7-*v-1%rjT$?&SM&{hYrn41ir3kz+lgGYNk2X*!iljW5MnY15ASx)f+=xq+Z=PI^oJ2r{bmfnA(kDI8pxtGpi+W zz>tPp%)JY=aDZc<+EGp%`!Jg3GNUmvz%$PWw7837!*YMlgr}Fd*twkf$TT!H&}!pO z>yvgq9yvlbFkVXcHrhII&4JqiMt z&GIN|PzQkpwg}#WOaHkbEycmJk`Ldc0W^KSV;kn*10_=-c`rnyq<(8d_`L@B>5M#F zs;iQy5EQMF__04Fv)>Pi9p4lVW~Yq4xog8%7=B7ZSXvG>8q)L0jJjzq5 zPltoM1L&E`S|oOlD|RMGDV{vs z1IVKjuGjPEg!>JOq8rqPXkL~!If4sBCC$yL%J1_sH9fxcY&N~8snTgy1UJKuqLVBB zD2kEGq^+POFAjN{awNj22eJ1#o!-91)sCqvna5LDv8NF`4wNQ)U~{BRC5>>n4z8$= z-PxFNGA3?inkABgn30=(b2>Kr*~p2_t_q_bsf>Mb1b6q2VwD2OX$|-xZp7e(K96=} zsO|%Q8$z)-YN>usQd3W|amvPpq0hK#5v6oXJ&s=Fq?kABnC5PY&bm2MJ?EzC)gh8y&ui7?oF!_Gc^x_dk@Q&4!dU z;MZg#tS9W79v2#&mfp`!J2CCJ(4gJ}VRjrIW%cWK4nVL{U=sW;a=c}yoz?H`e#Pl9 zat#0$7)tPh>E7ufJFQ^g)(#p#$M^%Sc(eFEI< zKL!YhrejdiNP^UX*&uaze|USt#d~4iK`al%!&v5OLiaMDXb05dq6bGpo|ea_gS;8= zp`jtn1XV%(hUP%ASe2|{#p#(;p<%t07^>_EQ?$g19-7P6AdpaWW@J&k-zy z5}7QdmsjE`EQaong*U0|NB z-G$_t-d(KLSz5Ko3&1Ov4nZT$kQ%`%9_y2vPW6HBOp<1rH4HssR${GYkcIPlk!$Ab z_tc#N7b2fgU}27>TL#4zfv(nUlNq}q9}t=82w6*od>z6j9wDntNclK>#QHF_bcec~ ze9mSlWR(kf(3Mpoq%NyGP*#~MYl)C=y0TUXsmoe&46;f^mSnUoc!Zu@6NLzK$XX_( zmapp=@-6qtw<|E;2390&O-!oG`#3v>BhxiPsgTgQ=&-GL{tih@U@&5}UDmlnQYDNI zv@Y$u)H<0?^hx0zaz|Qxpk;iZZHL?u6AVRP$Q=%+lU+i|QtEhT(fuSYy`L4g=%dMT z>%<)b7V^!IwOB~eX|$;=Wj1PXVp0`Gn^o}rIJ8?Y@z1G2^`h)#%;_Uj_ovGaRkzF@L1 zj7)g+vSP?4$qt575U##1BI)ZGcMyl2LCC|%`?#Ehqp#Jy4`8~d5byjg=KU{MdgXPI zs_P<>rZ27qcE^IfB)TKW^KQ9x2MQNND;M*0e*`D5L`5Cqu7ThGhX=%JA-@ZQkAljC zyon+ISS_-KyRyoKl#CD);mZ|_L-G~4`IZQ|-PPedAw?AGkQ|qjWtEC7$p|`ZKL#C^ z`Q+Pq4EdJ(2C>0Vv`i}?2Wl2nAP{n0Q&0;h;F3TNh-Q>6| zcf@qZ9+yqsCA63okkDe*K|+gJi-6E#RwW>`nDzORq2(@atl#rhLW`uuOb}Z3KwnGl zZGY#CpYfGLizF|BT`@c)v`D&xaWAw8@(QeF@HkRvS?!g1PVpoSG=JLc&1s6#zWRzG{F7GTw%yNq;7joG^ z#RmBfZj{9B!xe}{l<$z#vy2j}BnE5Rl<$z#S)MirH%dZORZ=CTDSmLHr?R3%@h90v zA!~_{_KAumWL+er!;lJjl9B>|F-W`^BV?5dxf&iV;Q^K1+AfLvU2skQ4oQf4u+&mN zV{L8sGghgev9`AR8LM=MkFmBU#Y!=jQ~~D9LW&Jg$7M+q+vJSP&5U4%eaBE>RPqgm zjV z*O?{pu)!?pS9NAdIPBpfS<=!2NDWCyqYAU6rCEtt64F+}tg7XmlUu9O*Xo1D@9;G+ z;J&s5yzdxQ%qe^wV8PRNc+jWb1wZHH9dcs=GlZ;FS!|u@d`-x+GVkOaawDG^LRO8C z^MmsEwjU_y+f;!2whG|B4FI^WtpWF}tf*vLSBb5qnV{{qcN{%mwNJiXf%$&M-3O;T zW+Ngd4fs#)42YRFL(H@nVy3MS6FW(z5Hsua7{_3-UD)c*7FzPbeubHBIFC6a+_7h| zke8(^W-#HFb_t^rD+v_6q{9$s7D}L5tbCGof>*4NqAcWlHz}r_%lxfgJ|SoDXbhDU zQd9v^n>!>S@?jZmYPI4^Nk73GMK^bd38WM%(v;r_+qZb^;VbktA;rFsZ)b-j#Mr&yU@#=#TC#z3MjND;{_8~sJ9}gZs zGRgbyN~&4Zr)Il5e5$eAuNv?@NyZ_z`zDuUTTb;3Nr=I~dUqvRk2rk_zRR!RyOIj- z;R5knBJlvf5i*x29ARs%CG;!qB&7SmMmI4?5zm3CIH`VmxMGhaY{nM6>&;JGwCgEvq?as?2@SA(T1{1;@=*4aHAw7 z1`?+vR`4i8G9@9AkvJtGBR+9TLgFQHNqCCMxe@IO=OELPJkRx% zN+FwEs7gp_cL)U1cw%>e#CS0_9zdoA05UZY$Mj@JlVwMfWk-``M@`ukJY|-FC~V~> z%{CA-*NcdmK@Bl8rXgmw4`SxL46&o>2EBS;yU*abuU)j07!7kxA+=z#uiey`B$_G{ zXzEO$skG0$J=va^a%To|Ap-IoZdalaFf86zj$a{;Hd4Qo+TlVnV@9 zD++EJQgG9jf}7?P{AhlFeE7?CetIMBr#Iq$dL!V&lemLAuZ^Zrd zM%+(t#QpR}+)r=B{q#osSoFqU0`$`xaX-Bg_tP73KfMw6(;IO=y%9eaz46xz{q#oM zPjAHi^hVrIZ^ZrdM%+(t#E(U9XqR*R^hVrIZ^ZrdM%+(t#QpR}+)r=Bk4^8DdH#B@ z%=6cKWuCv@EA#yIUYX~w_sTqfy;tU$davg(PgZ2Gj2BXdK?wI-I?Q?yD3$2s6#`@y zQsN}>nUx~YtPz1`bqF-;LZDd@bA}6(See(#JoNN0Gl|_Hxn!m>^()O@-3?Khd44%1 zB$v#azM>K`ACigcR}^E;p}t&Q%(nfCN|+6AdN6K0iet*e2eB@mph|lVus$B7mNf8Y zoAbp9ZcB-+lNiK`GGk3*Fzu7XV0yCOCh4)(N9s1@WMvWP6$jgY!CVO zx`8()B^HLQJfA6SUQ)h?c>~Pbf^qV8$o`(U1p}a|1?&U{i|ge+vAkGFPqXfULQAP3 z>teG^fXosBGOI1>hurd32q|U2-n}EVSKf-`@>V35w<4&#z23%T@{1#))O6nShZTpA zGZ@0XOQB=lqk%me%>w1g>q}mN$#0wp({movuB6?5VUm5$kadBOGLS+i-0io!xx3wG zBXhU=s8>1`WtW~Ly@)`cO~>8sukv1{^dsUl+kn6zqlo^%bYPPtw94%9tIQt1%Ixv0 z%$|0i%IsDS$lLkARr{9PP zy_GR4L=qBFkkNE=#7sX&%ye|b%$7vVJa)0&?%^vml92rdD`r?O2r1PEbq{Zp`u~JS z9|9$@#~qTQLOR1;c|uATr>v&!2o&RqO$aoNLZE3C0!_0JXxfE9(=c^xkZ-$&sT8s| z8wTO@6X_l5%P#tI|9F$qO6_ zZ)t4)fB0sHS-~$jzFy0{S5;*5EJ+{sth%LrI`>k|;kNhQ1$L>V*^1Iv#NnkCzijJDcaVWlSosz1GAWPo@odH{KJM)fBt~JYFuI0Oh3Nn+nQ2q6VZvx<9GSLoJfWFo-={tGyf1l$g z7jbhmM7tw?YXBWw2R#)a2%`g#L5)sdn^q!cCTd)%U;enp`;DN3dpBEl@{DHiolS7g#N*;KwI zrNdNDN-f}u03-$e(1XmfX0sr%SjegpQe8+|&2wFKD@6rqBA>_M zcd!7j!DD7wKW7$cNRT3Bh#jDHgxum4FLTP%-RX7Z8BErAko5lNJm@7RKlPx0GWmrE zMR$;fW=yNy0($>%*$5lEYAD_DexB+%S}Bv7_aBvShB z$(d!S4)mXULTR?6UwQbX)vOlkfw);S!OhAEezfxNtpu|y#LRLKGt2OMqrt2XOt9Yd zdnw`FcC!THG>ahcXmxR~=29GIuqA5&X$AiZRrdgn=_W3pHzIM|z#2Hkj zuX~E@4XpPzk12V^DB5Obsxn{`&(zgtUr%yvKUH#-$s?nw?z>E7@-|=C4P0dzOv+;- z*uYLMt}NMO%7QGQS$V6UmACp?d8?n5w+0$e%7CrEWMwZ(R`w#Z4Sn_u!y{CU@-x*{ zg--ak6;7A&6`KF>MoDZNsh9ygv{4d2VK8LHgoN)Np@T9@D~QP{vsn)*-mDBsP{O?P z$*g-WUt#QAc#67R#aDo&sfW6)AC*#9dXOhoQ6e)xQ>8(H7urSY&s^zMQY$G1<|ZnG zcfOX@>-N+XGCxzXf%GlylK3r42k_uVNepnU8x=C{wZdVaR)|U~Y~;>C@c+E#P79L7WuB z$0APL;nNt0lNuu-hpM=DPS&hSG!vtPu@7x5WQ{}(XcQr}PI}0F^43GF20%1p(A+tos!@0s$zMoEldCr3u1uksxKz(z?(bzurBgkV&;*bsSv3*6tKNrT3l=1?d?7ex7}}GN`B(In;WN}XTRir@*y)xgaStqQqwIt zp9EVm22|h$Keb-)iw`d(#Rt>xaLOw-x{XzY$%K#+uxx8h=MFc)tqOdm(Eu`A%4cdK zN{Ic#*3=y`H<7nj5HqDCW{O8lHL-VogJ)ARfctHO$atk0{{`0vyb-I`ld7o29EDrm zW!+LCzdu&7Kof25ki_(p(T%=L^pIR&$*zv0=ZpH}+j0!~Dt+=L ze+DtxUdw&*C4cBo@;#KJYJh#hv>#%oPx$qnAG0g$a$RDvke8_M-LojTGace0!G8PN zCDG5-W346hT%A!8uWO9S3d56MZfR*Hzfs_qtmv;jg@u%Cpxg~fjr20}JKVG3vxGw8 zFLmMp>uDjSbAzbE?UFcydpcwlQY-RsQg=J})r(XLdFNZlDf&-*g{Q7UiXlPOZ5@)h z!|g`ZLP{-&&y%dE#Z%OEFJEDw622_n{X8svg|^!H9JxaTN>4lzuUXEUN%e^O)MI;x zl%Xk|{DE~b%A+h4HqEMcNa9+y091ERl39q;r;I&GZu2*m9XU-^1=?Q7H`$xPZNzb< z#{nc{n&&h^N~M6yw9l>F&pwj=W<`Z8 zlEg2eiB`fcx229KtdmXkW7lM>guK&*qC)=Mg(`)7kf8wj`LmE(bv%P<)k&UtN#Om63suYc&9fw33BTQl2gWZccW;Izc%X*?ihN0#J!w~^2-l*eDfn7 zlwXX3qmR(FZY!)1QtV_~?VUR$A+~^?Vw%FI*ZGZaIULZ$3ivv$SU^M~W@>)}YbZ_v zi#n49=Wk&GorGI5CM1B6xP^f|e7&EqkMQ+(e0_zl=H)L8yvJAh(z<~m1su=UlleM} zuXFgih_7XQjq!CoU(NCamoLGSud$P_zBBo6U^()&c6&H2UuO3#r{yc`-s1G1`T7xG z5A)TOzb!=}yZCw=U*(&-9_93td^PjG%=jPpD&Ndy^;VoId`;u)1int=>ny$&@^vX+ zYxruGC%Amm*Lvpb2;jepary49n>j7t*mWWcy6k@3M9`%abS9@&c5+7Irh2|5Ls`|NlrId9UU< zemr@AuW$491HQ`JTgR&JZnmR5z5L&@=O|tZ=kiq^G!*d~e&QI_0(pG3xZSe&dbD;l z+fSC&UpqzJ36d`>Y%~919v!PWls52Hg1dxP3Gxz-WnePp-3MVdZ0&;R5DI;s)*sFx zj1-ujP}MM@r8XLmRZoaEH@8fvYO9V;pE#klsi}T-UF(GDITLfHOqo#ESXJLv9kVp< z=4jRG=&IO+RaI3JrsSNNGkHRNT_vT~O`ldVZOZtz#?_5YYa7Sc*EP1S8^5ZtjoDmT zZ7p#TJfX6#6@Gt5L-4mb;;r$DmZthOv5I(8Wm`*Y1uBO)kccME2^3r8k!mt!6HtBO6p!ORIoZ)5pQd3t!s#RV#=^KanufuhG=VTRkXg| zQx=p|+QRp#tML)o>>ev*_jPjYHS_KC8Fs^byKk$#<^zB-?KLy(zRT=B%fe&QBjND4 zA(4=CW+Xf-61p}LcHoQ9t0H0hq_^R#)FD@YXHX>k^O=ySn*s2UnqnX?neVKDL`GK$ zCJ;Zq3#j_}_WCYJ4DY(suHPF8%?5F!BcV9}4eeSB46*q@4s!;fxSIJ_Kw`*cK-M9A z{0Bf?GXHWQ2QLG1_54HMK6IV)r^}rdr_D|cuMB%;9k4`47+89-Ty_qZT^KtcH7%_zf8OB#oQu$T`T-8D{^)^H#=vq z`qoPqIQt)e-0tIC_VHXhb%xzKf9;tIZhi91o5U>kw>!h9g+Px(mV*`S>YPCdtY23r z7zOXtK;oyJXsH7hn+o#Xj@5hB!d$m>lD%HDl)-N5B#XU9_kPrgHhUd1lF@GMM62Bh z(0dlzV8=Vd&If4z<{9?D zR(suid)?dNQ%@+hn{%Af(!Qw&f`w^hYmK=eCOxk^9P5|9qhas&P#XhyfS=Rdbn_?z2SrJ*ne&h zFAO=$?S4*XczQ;7(qQMM%fHs`jBsXN;C$rVcE{OwTwpgrDVJ}$+#Ydz_|$Y~d3eI$ z@QU=WeHb2>;hcQVJ3Wub?7lVOiRsQu#m;i)ip?dPOD?|@zGpc;^XzlaUG=z~d0D-^ z;l;)9SAT(X{;HTW)4sEFnf;>G^+)IOmYMf<+i_=^J#@ZZdzn2nJT|o4z9GEV3P&R0 ziG%TCXN2d&cjNl3w%UEV?6qL`J~j5* z@c0mcT>xGHoS>^&Wq*?Yr#;ToHXP4K6&q)uD&iK)sm!~^7cZbh{=3CY6+!-F9ZlCVt zVD0U{g!YEV3<>8C={|dQeQZ@YI?V3h1+i&~z5c#JC*!Pd7lS$M4KwVt`OqSvl&+K~ z`1-7U9GK(I&b{`T&|LNl9nRbKFTx){b*TOWYrknf(eZ)3&Y5A~9fn4Fq#cZ5zhLdP zq2oCh*q>S9xgq%zk9kUT3HMu63jR$4z_fKZm=Vnw9pt%NijZeFoiR zUy-u~I>*S6^9?|+2)?67r9lFS{a7jjZ;b3sEtL^odeb;F|bCz?k!ahDc zEfmf_-Z`*s`Q=|*?u>V4F2B6kNx$tw=jHI|bdUlqmy;2maJ=)g@M%NHKF$Xz&=GU& zf-d`Z2>gCUpG_Exv^GC1mfds?_86rPoS?@Z`6^S^UpxG2MU ziF|p!a_~VAX2HRs(b4N`$ieUbG@2iM<)wf8*?ASB^aIJ>z0-B>$068%e-mcEH}rvh zN2l{5v;ZWX2ka#`Iy=LCVXlHh>FjvCDZV&f*Ay>_wl!AO&WYC5x5Z;tWwa#*6O33z zD~z}}WG`xt#iOltO^txWDi&8;g-Z)c^2;j9%Ja*Y!ewb;;q0<9D_R|GjnW`Zl38e7 zeXOjisX10%))udUoMnqj&YN8-d5ag8&n{hDlA^SsI+``ktc9oG+w_RZs`ieD^AgQKDMU924VwZyL)cvyRrdD-3vF0LYUbL~gK4vwnuc$#u7EkjU+v=Ov zk^)MXvh~p7@utSQ4KcFL+IU@SjO^3a*iyHuF;<;f2lbk}aA{^?Ce1}N$2YXLHO6MH ziZ#aKbybjz#;a;)!pwC1v?-b6S7nZ0G})@CsgJH|v0|;!YSy=?F1o55rjO)JuG2Nu zfM3M(>*KL#^?ERIyfuay_%sHv)B^{vqgt;vcT>ssr;g*K3;^cDk{#OTd{Ou zae0!DUNF0KQN`lo^A?qRMuNIF#n`MhpK`mF zrH#=w5ND#5;G-4IO)XYkYeRHhMN2hy)uy)O9t~ZrrfyZ63P9wlEm2s#U{Ch+RZfrL z2^tl9VhuL5sq|vVRM)twunCqBuHx(-4X|ivs`7HM4Hvhrvx?AFCQ2jCDK06gSg@#Q zb_K+Y*`-!}Q{$>kIwf=*2n)GTrRXGBlB}z!YlJ>yLDt!>(%|&1Z7rbk`s%ux8q~R= zuCW3v0F18DSc^jNAc!VKv6iZMT{A4Jyk4DaVNZe_bI|R00cW*b8gH#ZW8~rqV@Y)s zdJxE{sA+7esA+44SRJca8>?GY+uBlbskMP!dsVcdAqxKPYJh!6JdHYQX>3(p3n&R~ zUWwMuRTqfZO*J(wG4TAVRlLGPA=-iyld|fpsXvNNo(8VNvOklk9fs=!YsVf1f-K8X<(c){2>epIwpP)m5KiVK5RH#Nq<8Jn8f5qYF(jkh$& zPy%g?jaPxK2!e6=#pbTS$$-_^6mO`APxM4DjvE2IsToFi?80@8${*^iibjY(7S>Mn z$aqXe5v}eJPA?&$iE&#SEtwfq6JI6IDFof>YgCn53+JjXYQ>sc>YyiDt0q>oSSk!x zRdVpHsHlT>BTMoiyzZ@Fm;qwCbLU(wJ6n?i{mRZ?6ydtupZo+NSWwZJ|HwjS(-;KU2P0g9o4 zivdtf~M92h~?&dG{2-|QK8$(YhzWb3&k)N1W)Q;u70}N&?6E@ zHJT+fwk5THVi$)waSXg&O|M8n<-t6?DQ;W}eaJ158|S=eeH);aFh;;QAPv4~afONy zdON!Pf#!!08mh3}*R7iflM@&-)ZhZc48~(?HEcOXwNVLYacZ5U< zTasxjVO7!AtimgIc5h};vgmx6S(r9I*IQCRoNkW7PBvX&=*1LFzVR|eU8Y(!@z|vk zt%BL*`Br%?_SqJgs}|szYEqIvV+<>XiDxkkTo=I5DC;mNVNEMW1{vI4on{vnE^Vx8 zisM3yCUdPYP{P<@!6aj?H4i*dZ}By-o?M;CZ%!C}hiBCb>S3cIT3-xHt$3SS*12jx z8^9!(`cipQQ%N+wDyGdi61TYBOfq_bBf7JGa8L?DxJ1=w4T;T8^E{ZQ;_TJkF83Nr z&5g^UBj8N7wW1al0@!`jYPtf(;o54eHQELaqej$bT9Xwvw83D8BUEFwu}1Y>?6yX; zS@|W4=jHQ+8TRqj@Fz1yij*>^^s4Fxd2P{QHMUf1Q#5T(<_`O6dTlJ%it1X_D1lwX zqE$I962Zl*V~tQddU}w__E9S=m^A2f6wg-NDOLBI1N#ru`&wfS%^1Z@??{a4(6RL~ zmg6JYlvGpJwa_J7Hc*jsX!fDT0<;u4R+2J{>sqU7IWSA`aHpT=K?la_)BF<+tW(SQL8$(rns@D$)a$K zad~dMPQ}ZzHky)o8uR8`He z%{1NlVGGR2>Zq^he02cT!$%!ai^gfChgD}mG&}csjlY93ILa=?LzA4`ub4K z=pppap{XOR37TNEULAu6p$SW9w>Vg+$6MQ)c|%GqHE6a+O_^)aC3#3q?Awi`7;mh( zjiL1B^jZ^EyRapOyMf%tRP|df^;nA4=q&fqYzx$0in}nWuqvR+url0$ zJt7V#$WG%`U3HA7`dmD1fh~kJI&Fi2uQx(}SDT8^m#fMN>aRWP@LyJ}q4?4mZL=fZZEooaK7o~MYR={;3Tsz=e3|ITbFi>-WTAH>Ut~(=9;9dmSQo!Z{Rsh)Cv|aGG z8|Xc7?TM@gp4ptQ2zV9&&l2D%0G?vF7cPODi$Fj=3!TRTmVuG~-UY1UYT%j8`HHw2OV|#@a4%d4Hw#$60!=^(3s{RxrG9mL z0NU;a_``#~Ps*zsm>{X$=7U!DCA~mw3D|Wmn63m&7rqAIS_X^Qp4E_^tu;`7+Fr2p zzDNb|h97{N*fJ;~A0!m>%}em+O1>Gs5r`t-5|L3X{lyTV7XexZ*L>vP2*e^5hOQ7s z+l7k&MF*J=MIhRWncoZdO`Ber)WXI6?9;-)uj@M(RGJI=wqd?Zyo(Bl7ZCySqd+V~ zoEsV`+Hd5AjPZ*wB0s% z*#(ae(%|H^pI^TNgV?s+D{YUxAgu@daIamGw$HAD^n6G|6Tx*K;QQh4fPE=&gZTjO zf$Lttyn8E9QbP#tHo{%-z&&s~QN(-@(Fu3~GVCEakf#^sIS-*O%Cp-phpVnmD~Mi7 z_q<~3u()UZTdxRztSqNxeFLoT;My8j*IJ?CvHEBZZ58CugTI_gm`o9n)7DsDw>nn8 z-pYZw73@u6?%bSQH(AxvnmbKTO@Rc8tBzH+t%3~=7?YWfR#wJiYb1qR^g3rW9&KC& zBOWtLN|Qyi7QPKySUE6vzzqp2r?IIumIHBkJnX>06j6zd)mGHR@rlTP=Ewvw7zX$q zPgiIjQ|8OL{7a%PAHK^6FIpuX%Z91oa(INgH54w0$g97Y#slBIL!4b1Av&($j{!(p zu4Y;IQZdT>s`>F9JHPq|m9J_g+E`W|=jS)m-5ZjBqKha(^2>D*=O1M7^>F<&4P5Lm z*NX!3AK?7vCnm&xa&6-LMlsfm=?eLzB=L^uE$Agch%CYIy7)}R*>%1`vAz4ipV|H! z0W)P{j!9SI*|M%O8rI~!8hA_$^X=J!XUpnWrmk`}dA|oYObmG+ensU7YCm-sVlWVX z^*?}V4+*tY`>Mvi1xzwr6s@E58p92T%2+Y zO;MVDMk()P>W^ttcjVsQUvUoP0`X@X@%>`6eC(5ck``#s}k&-er5 zqlA&KZ!=zG;OawC4)qWH7RE)a?_wPU`V&e!mN6}`{>gZ`!5^l8;aJNI9Fw+{o`gEU zZ`uHv(gX030r>a;yf^^AAOK$-fNu!EFAusO_jE8=99v6U5 z4Zw>7@CyR)l>zvw0K6#xZv`CvN4^t*_70$bT^qpvg8=-N0Q`v0HUoB(bLv{+(VS_p z@P!Ac@UWvfXFBat>g?L9Pl|nt#tzT4b4m*7GzqEccsc`5xkTokiS#7J0E>LUCljL1 zu(pT0loK9|<6V`P_$g~H-GmJRxTmCwFk8Pnxgx5uetRP5B(F~n=oITN-S2MH}Gz}`*s=E-Fp+6l$SS@4CU3sPrWvY zZx@oYsfVK8D_03h`oEIMO6f2Ea3TILgP@MyGY|FysK4p#&;`^>XPV0J<~hAF$*+sc z+kf;bQ{cmLR*>Jvb|2rnZ<;~Bm+>9K9jy=k`Gq>xKpGFk@zeQ&?*tkT6W^s^!f|dR z{D0DLUCw(b01mTU8{&!|K2pOc0)xoU55OoB?T=%yZHC&gopBElxxql15Q+X+e{Ca&) z3&7`Txb8o34cGnc8V%R^cLv~ZXt?fg)B1vfaG;*Lzg?r@y1(6^;d*=B8i3y&fIl37 z&)}sl>T@RiiT|9h;kv(Fs^PkwH)*)8=QRQN3|>lOxo2zTUaH}^))Rep1>mm-;G=Q# z4i1#B%c&2*9|*vErIDB84#4{k@buB;3=hBy0`Qdq z_=W)d8v*#P0Q|uK{P!BJ`_IIIUcGdDV*vjB0Q_YQpQ+jT6AhoJ;s4g~b2NMiZuG%{ z{xcZ-_)F zaJ^loAQK#D2fZE3H5^SS^0#Za-i|M5_*9MmjR5=|4Ii%YJ6L%*P@j|FPs+Ve!)I%F zy@uoWfC&HH8a_wEdtsx(f%50VpYUgE_&g2&p@tW0_yZa~U&Hg!Ip9F~dfcecaD9Ba zECBy;0KPW>|7!rAhE53w>Z6yN8-SMw;86|N<6fhN>+QZV0N)aTUmt+q9Dx5U0Dm9= ze>?zxF#vxn06!FfXJBWB1MQ3~;#b2pT<`DGG#tBv@SmgM=)!_8)o^^|E_jWGV;2{E zjfQ7x_!R+ow}$KGeow>EKYPeo*rH1SL-5NehlXJg@kJj)%Xn3ZE_r@Rv2kNhvn;C%Lq~ZE__oRmF@pdQ% zH8@cIx$q};xL(8cdBT4M;P(dLPX^$x1>pbI@B&Sr6Zkz4)KjllnTG59ts1WLe^bNt z`aZAW`ndDHhU;>^kqL^xf%@qD_hcfPjQ=^4;3R$rofQt0Qv!ct{~Qh1>os4)b$yyO ze2gaNb`96%JgwonoYaw!84lEEI{b+~b2MD9Z!dIuIFMhL^D7P4`QHt|Q%8{)k~0-Z zDR)=^J|+O49DwHq;58b)Kr8pZG(1nk|DxeV8a^01GaRNJMrybof97a7_Uq4i->lTY z#ST|%coy&re!YfgYxr#%uJb>k;bS%a40IYeP=7rRS88|x+>>(mX}E5OURW3$C`b3h z!5Y30?unc!8a__L%QSqXhF_%Nx|}8rM;8or`DpI2(Q&VQYT>-@K9xX%BChU@wq z&~Tl<4>l4UXdj*bBn@8#_eB4ZjN^8R1AlU488}ZDtOw;fs8h*Bh>-s#S z;b&|7z0hgkK>cUHpDRBAZwSDD5`e!IfDc9IfWs_zVF3R10Q@Zt*XtEJ&6A%4q|_@t z03RKIpAmo;2jCY5;GF^Z4FUMw0r+nM@V5i-e+1xzPxtJjuRCf3@Sg|ZzYoBVAMeSZ zso6hA!}W5fY4|xB{{juy`){R&&(ipJXn3B6SE3WZf&P{cf8r09Xt*x_CJo0ajPT!T z;1cJ5so@10e`EqA;J|XRDMU_&hU?{y55T8t_()Aom4@r(uGVm!{}v6`=L5HExUSFh z8m`YXKiBX=O`l9`R5-A{x;}SkxX%A-0Df4*_4Yb7m&B9&G9bmCXK8r3hA-0ar5e6L z!*S{)a<*u=KE8Ja;5#)OyMoAhO~Z9NgeL(L9HxD;0`OTHuG=3zB<0G{?RmY1>vp>< z0Dnfq^>%;Vz{SrGYPjAnW=;0=$pcdCP@~~W7<@>QwuEZ)GS+$owI zz1(v&TrW3;1!BEYMYu*IejNN|V@2RV8V|u=O;hQ?>f+)H2PuHJ8|h747C|?7dPw{O zIWJzY&*CBS-(+KBAH+lOp57{rF#!+3$EK?^)*TPQ|IGOL3U={ZSn-Pu{6tnZX5ed> zzuv$Lxe_e~e!~C-tTXVTT(8Rw{L~=|=rZu3tp5%Jzp9S{ZZYt-UJCe`fj6_9`we^< z*Y`04pNE@ru9Rz_Zyu<;MU-&pVj^Duch8 z+eLmPK=^;b{5uW)vE1+OF!0;i4)+=k^d^=gAM%q?9U?&d_1@NL<7G`iM7r!@S#``9EAp6#s0a-z{j&6UTEM~ zvOh@si~a}tDfxF9{PVfrJ!IhhxE&ui@H03*JY(Ql96w(+@ax%64jA}E_P0M7_Zap!2izr z>@o1SS|H$pK&%ht$IQ*i4<9ph8ylUX5aJg?9_{}Wmpn<>7 z>Gur0j{Dt527U|2=g$oMJhroZ6ieFczqwv1EKl(FxSysP_-E`tgADvU<{xU{qd0Dy zY~UZUoktmX8^@_}20o4be4>G$iweUr&A>llJI^riOty1@frojVnrGlQvA-=e@Ygwh zE;aC6POmWV-?Khd27ZB3-@3%WQ`w%E8u*_$z0SaAa(j0g_&q%Dy~@D(@8)(7alJ)P`KV!k1Amk4 zoEw18H1K_#o@d~N?6)fn9QP*hs5J2FIX=`H_%qz^8V!6Qw|lFBpUCBIFz|KUFE$(a zAGx1iW#D(SowpizE9?1f1DE%4zh~h4SfASs{95*h|6|~jIW6x|OM6|zc9~K(;7i|A>1K-L0?g9g!!1}K=@R2O1#=uKuAHcvb<9HG` za9RJaGw=xz=yLwxdvX&`V<&=HTRc=2L1!qquszyP!?#$UK?eRJ$H~(T{4n#MY2X>$E^`e0O7^#M1Amg!H3t45 z>%G~)FJym{{R7cIjr-|dgZ~}Y^K}D%lKtT?20oeV`-y>n%G0Xpnfq$LlzhvMC zI1V4r^%nUDxxG#?@EZ2p(+zw(+w%+q7kd^M_`{rDVBl}Ep34mUJof)81HX{*Z3aGv z?Z3moJ6ZlO47`QoMvsASWP3ho;P0{hUo`M$_S-iNd^X!%;ng5W1i(kopgVb02 zYB;y2;5V{8rx^GJ9QS1YF8od0?#m4RKd^n84ZMf#-)`W=9EZ0U_?*aEU+X8MwrssDVrTsW)(mKWhzK;?HFUF7ao(fv2*c-)i8}zP~VV zY2UvZco+9O`P`P+N7{D=_dmg;^Jvj0%6NmpKb!SmZ{Q`2UuobgI4*tLz+YqhE(71d_3bh6 z=XiX1o^jmXhaSQ|HSiKb;l$-&eUX1V<9!VLZN>)~_;B7|J<-6+8IKzH zR>tM|o|Lohlew{LdNq2F6o3&WU}*&Sx38*samPW!-(Pf%oQiyxqX1|L!yJ+06fufmbp8~mR$|4jzInEmH@1OGAO5uVqG{JR-H&cGjLT=tuVe;?zg z8T_v?E#XA&5$BwPxJ5@Cd(k^>OJB?(DjA_)YN zaEXR6nUFv(lL-V5ZqzYEiN}h&DtIp5?CP$7u7U#Mf%hujc;n5ABI~Mr?^V6mQ}dE> z_q%?-@9Q6QSN-eNt5>hi?wRxmekt)k3jPT3I|Tol_`d}A(tS-GeKCpc^bubncnrRCm4_8J`Z^rQRymke&oe<1Xi zQ@uVH@<&MjF@vN2Ye;`AeL;%rdpYUpMjZWyBv zUN3kf@f!tSL;Oy`uOa?d;%EkLZ5C5M`G=6__s#L&IifuF=Z<9m*M=VCi;4d%_#i6R zLx0c6df3ix21k3YqWQUx!BKw!Jr_9D;3)qX4jMR;436?4k{@Ppl;2JBd%D3ug;Cz4ey}_|w{5peQj{m&+8ojeKhw6ejfXakpGg`r$opM2g_ei{ikc9 z!iuAQelKZvgQJ}bFcFUaiR$9Wv;NZ!j`IJceZ|=ZM-^jfUPv=I%JXyP@dihEo?kKz zj`IAxI?v!J&*N~0!BKu1tqb!Fj`9o1U!?{|`A^9|RR%ZZFETjFe@XI}8XV>OQGB(* zQ9hg6eS^VK{#A;%8XV=PlKjmENBOHxhZ7vPCE_{UzKbytj{6hU#c8ie19ki_21os; z(|CW};HaX@AUMIXI}y)e{aG}QUPx5f(I0b$j=wGV>%>1WIF`GY=Kn7Zj^&;=7*23} zm#8jEKgu7a{r1lWM|u7`=Mx4;`EM~1j?UyK?Ei>LhaiRZjW{m%IAy$vncz6ZkVg|& z(R^~2!BNjmq-Uhyhlx)Rd`=Q-x2(y6?s1T?RM({HVdv&##jFtAZaR{*K^(O4j9m zBKRXIntw02ovQhN1osk;p>f0gCpJyXcN6?e;wK57HA2e|5Ikw5=4pa=J4f@ef(M9a z3H}e_d4l&IrS%jG&hi&V;fn;XCOvh6?`_r|a`wXu77465i8yxjtPV&12e~I{Wf^Q$A^}i;#Jy!E~1@{vFkhl$E zF8JS-=JN!aUzu;Cc{oFG{{Hh~!B3+7*!6D|V zXj*^Ro`b}*1^<-zY{9*>k1rvP^9J_Aj2P|5T7zRh>@!aDCc!TyzFP3FiC-o7{P9}P zb%H-m{AR%uCusS*1izH{gMuF<{;1%;65l2Gz;m_!X9b@^{8hoLh`%FvEAdYR=l%aT zg8xYJ#{};;QQLDu@QK7dv~Ke}-$=Z>;6D-XC;0gpTK@opo9$j~aBTNSN&Z5?8#1+? zMS{OXyiV|ble9d)cZKc$koX!Qza&e`Hw*qQ@#_U&nyuw;5xi%P=64Hz$z;tR5&Q$< z{}4Q8ik9Ce_;}(k3EoWnpx_BUt>;6*>xh46aBLU;KKd^P$9B1%L$h|duGTHZjL1m8ydH^E;ao<#dr z8;ZkqIbonKcc$Ry5x-RMPl)q-U|3HlT3-(d`KO8ZpnWULA0eJCcml1LGXy`C_=SR> zNqmXmqlq^Oo=tqc;C*R4Tq`)=H*FUDMUsC|@Qkk7pZf)GB+mPH_QU&poFZ9QF5_s>G~9gQGlu?s=iXQ9h64{RT&Q{yel^aQ-}Wwcz}D=tjZ$ z`NL+xi?o^6PQm%}(7gsnJJ*q&2MvyP^5>o(8yw|dCi%k#M|u9-(~}MnIM|>3xo2;| z`E$=igQNb{c`CaFKfR?6Zm%80CkTFazLr~HaI}X%_pB3~Kli*waQ=LAv)~CGwEp)5 z=g&P48yxLlM)rJTaI}X%_xx3G{@l|uMwi3&eUtQbGdSwu&prDX9Lt?JP3IpX_y*#W z1wTT(Sn!hRT2DamFNiNTINHPS=~!!Uv}aU-)^nx7QJz2Ny3XJzA3HzbyFY#9ilVe{;G0&(!geg3lygE_gHXO@e<;e4F60 zXKDSf2!1m0?*$)9yywJ7JI4_pDR@5dV!=y@FBN<(@f!u-LVTy-JBhz1_;bX65&SUm zQ!^s%|CRW;g7-aJ%LN3lB7U{teMOVVaN>?}GnG`~$(W=sALCQlvfQ#D@$16!Ce2^XHiB1Yb<|b*+LwNPMs0e1G_@;3Mch zCpRn7&H~~=!Jj35li;5ae^zjt?oa+BxR>~_?8tJb5HAsYG4WQxTZlg`_%`A{3jP}L zA@m&GhT>iD|2^WFf`3VTn&3YXKVR?|nhz@k?@qi?@Y9H|5&SIT*9e|Y{AR&(h~Fdl zOyYkRyoC7Ef>#lLS@7k=-xIti?X$l(IBp&K=w`J}2u|2an5;wWfByV>h~Vj@f4sp> z{kej({&@z+_MJp}YKV6R)*b%OqJ7SC!LN(an$`(^SYzuN!RMZ&6YdrKkuI8lCwK-H z4o9yk`pWk7rgF0c$KS=qW3}K5NzXySf9t5_&+zFh>;F1I$HxhN3CV{9zox5}zf17P zJ8Axs;Co1ZU~Xi&={VeUI#)AoyRXzf}mHNdCV{@bf!sy*CT~2-*L#;Md_z2^^ma z&VM&S?ojvdU9_k=Q@6HIEsl3S{zm0Uo=5^HYs&v3IR8D-LDIwW{5}6oZk>TSKX)pt zEL~Jn+YqeuHze1lBqpYf95Z6ni1ZPo&Vg|1=+WVG>Znm86BEx#9iEz=h{uA?D|7ND zyKE{D)Z5m%s$OK8b2(D)1g#7e%z)p_I@!vcojJ=FI-XgaSyWu;Tm7{MRQdcF9$)Bj z?^clWHBa<4j}EQ#?!gFLx8t>C-M&>S{)l%Q@%?c9YUswywg0x`*M-3K5OT$@i-oq% zS^sSO`l%4`HLoM3>Atnkq#lMWCTpJJS-&fOeK(X`{S2l$5~`Hu$Eoy~?b*#>;X3ao zkb!b~HC~CTl~7B}aZI^Vsm{pog`R2ld3~X5Z=KH%hWA?KYl$iF?LHLab5BWcu3ONO z9e3iUpMG0^3^Lsbnc}aW1wT6z+PC}Asd>#eD?Op7b3?zReKYjvGwZyoKm?D_&E8#b z+1UJu%9R;<(bqiRTbDWX+t88b!bJPhVsseso*2%76>U+AUyhmQI}McxXZ{ie_V8p;;r`QOO1 zU(O3%@7)Muc`dzG=Yd3GX6Wtg_=jH0_dk~nN$W7l-k%2+&dCqGl-JUAwa%lPO&1;ES4Ru-tigTN%dVIS- za^*qQQ*+XG#Xs~kR%hhu9oUssvu8GEdlPfOsa{CWZ0_pKYtHlfe9f176Tr%Y@el1i zuccQDNL!il4?S=1&u!kIS_>T1uBxn-UQ0ofD!}J|wIwFQXTKW%(BWJt2l~c0Po4puN@4AEbe-{gcBQNxUN=JLrQ3w-4Z-?H`3%#Wi{EsMxX-9Mh z-|o*`Dy?PYpAa7h?DHQ3RZfbp8Cwc;E=W73M9~XLzO`R_azkH)-Y&psMnUGRJh-NU zKgRn)Ro((LyH4fug}znUz*0R8teOZxej&7P?ViNK6G3lMX6RtrG4)zwU&{vX4`9cP zLKyy``@O3mT#T>}G{lT-C}Mu-EmU2h^g?*;o{W~P6>E1-JQ|;%7Ld>ZABZJ}-j4Sj zeli2{@BXy|nQ`I~;vq z(IHT-Q)yLOYDIGYo^-?a+2D=wXkvkC1z&TJoSz-~w<;;KC3^tODgS}KU4Sl!N$qOY zJr3rioydl^>i^S0Y_fu!&@Y*xeIS+&J^E;_e_tLn+`iz0-6l-=XX1B zLBe6EJ?O52uIvs%$q>rO9(SoXH8>=5?K5_!{ciS{GH?GCtCdaYJuoTZi)AoV#t&Ep zN-S_k|2*usm%B5@d zOkqPeSOuv*e}~A~dvf}irUmg=Z~ga)6FB)_2{O&oyv_G}w?HVbc|k^Q=;<#m`1!;M zbgEh!U^-W;7fclUQTlPUsO2|bjxNt_DSCfFZcFfe-OqfX4{}@jzn|N3>2J^zmt^?- zjb5KWC(#%G&=DA|vC7Wo9B*#((mr}hX}+YcrPp52WW_&JoCq_Fy6pa6X4I2U@5_V_=E>b z54{(H2_4s?rqBj&BIe!TT?Hw-KXzwBFbN1=uLMMM82$*{Guhq+c+CUNsUU(^JxuDYclF&u2_+JrWOh71^PBlAKZ>fA5@GxA_u z7i5RN%nN;%7rN8i1m@xOAQa?7KSLa^hajF8`UzCwH|$h)su`L5h z$p^c%(A({1$PMj@U$+yslkI6rh+nrUN>ZQrbxR>ByJh@0P+M#Fr!ko=Q|xT}SJ-KU zp2~(DZ~Q~gz#hPU+2=2UPJGzca=-Tw%!l?t*gb^4gSloV>0=*C; zhtQ8Oy;{nj>%52Y3JsEkiQ6HfYLa%0Hxj;<$Gvr+K)vr;djbf)Rox`ew;=1231BUD zU+f)lpvnvgvjcNO`{LIvgLPL4GWCV_#;?QIGCr9OdBPNy0BD!^b(0|pd`x9)jd`Je zWj-_6gM~bkgM(VnA2_rYK!JHJtI!@_OH+a`lw;+FI^g!8LZ!gmfY;DIoEAJvD|Sc^ zR&)-e_(ET(xdvW0?@dVlW5>LOpN*kxI@L`i?Sy5NE)kt_~EO z0Bb*v`)nB2FM}$)z7AJdZegE(5JGTy7cS_{U3i5|eRh92z^(Hk-mkEpz+U;;*HTf@ z64PV-t6)uH{M9&uU<%Dvr2i8g#Om|otU-J!HeuJso z-vbtw8=&Cpy<0UdfO!lsRjF6FN*Hv6w{ZzDP^q3!!0-A2HUxR0k3t{t7KyS2y?xqZ zT-+1m*C&H5$Ml?O$FEOPjS5}=e%-!s#ny6)&u~J0oKTVzO4lLUJi{D7#)49AGz_v7 zq+%b3YH|UOdkX;2z880Y*p)aMR;v|2Ps7nlr9ZCvdHWIU%uWjSUlqZrG66R%sWf>( z(2MH^4mLdULf^y2M9YRgVxPj4OdEf0m%NakzSqL-7j!|m`MnA1g!42{iAi}dam@F% zzxoY~5}yP`pZWaJ__=7peD53x9f44lcYb`* z>kxV6X#D&);*-@Am}Tc-UikkiB)tA4)?z!>21MRI8h_zINZEBXe%{k$04+?b_auSc z5u1zn0gz`dmU+N<_^G9W)-nHHAJkrL1kBs* z=4`tHZo%PxWdZgung_OkvuF(9lz<0Jo4GC7>(yYzF+=9kFmU=6ib8v#0z8xyu0ZV! zf-eUDdg6p0zfQYc3cHZEVWCv{zW5f#>W-D5iuP6Et&~a+Z=`(9-LN#}+@1LRNo~G` z(2hTYrmr5^fe&K8dSJ)q_Vl*dqV2#n_lrwmMDDm2^Bvf61%f3A)?%m>LYDcg$Zws| zFgEdwvayLZwTb2Rfk0w)pt`nxd1p(;bL#7B>l3HdRyG7u@@vZiRf%T=QqBm#^ETE{ zS+lgXsLAar+bZHw+x=`vrMqhr;M?>K6 zywM|;T0+!#R#84ZL=OQm?ut>OE)Oh6mIcEX(Cd@&?12pdD2j=#tO;5!8&+RA0MhI6?0`roMLfzNkLP(H zoz*kKn4vb#_Fdt>l^#3sKX6ek9-G+@8bJpqVPTfX^8%cyGE@U)Osx61Cn0v~bU^CCWo$kMDLf6s9?*$7w3g`@RJoF3=kJEF&UGPHlENi*5Y(?fj9q}- zqh%H%@2bP)$P;w90>k?C!LgOapt6sZkQ6)bD+qOi6f3r2ffEddX``yIsu2Drk*d%T zvT`X});%?LZ7obSx|H=;3WYB>EW84{2w8X~hRLcBhWqHeSKSF&6Lq*5^Cs!=1`MBV zC8Wl#z6lD_*0ywTf>(#>HR^Gwvi2TGSJtMGwHwh&SAuGF<>e0QM-F?$bUytZNa+cM z(Am~GI;MUGT(!bM&#^_2QQ719DO^X-FLy>`$kC7}oXj{Dxs&FV6`KC*n*8t!!f? zNk?oIN!1aTis;|*^0-wbL#M>5h)+j4s7Qg1bX1W!I?~DB3??qnkuEmA>8nCVy4h%P zosM)@5&eFCPfvRpq%`T2lkG}~tkRJ_b`eC@=ty7tKG3#SM^3dLgUFRSGT2@NVjFcN z#l90Fn{*^qMYiZjnu=`Ik>M(`O-Dx9_|28=I&zM(ZI_OWws%80dn~oSdH$r@p^xV_ ztfX#-+c8MtJFvRc4u8hlCct>ALFU0mR6PRCRy_hKHm9PmFzpExbE;41VmjUYeuv0T zp*o05o!#mjqt3DF+(Df?sfB46 zd#m$F>U^>~>vstDRpEZ>e2O~vSLajJ`80JNpw5ZvtluOsP=yDnvsazZQ0Kwwtp6%A zNrm+<0G_GBXQ}hq>a5>PlB~ih>YS?1`b~wyRd|Ftk5cE+>YT35`mKdyRan1if4mA$ zQ0H^id7?V+(&KTrzC5KbPwUGbec7uo`}E}*ec7)s&+5x_`trQKyr3@!^yNi;c}ZVh z)|V6dvhhU=J|BT2rqd92XIOlcP5NxBa6~`QKN8YuZ|s}Um()tq>ABc}kj>-4->>U~ zntH@^nOWbSCf%3Jo}+t9JN-mYOI@Qw(m46?p!sI z_N^?l;wEA+rf*f16_;@q=IvWuO~Hh|H8m9M)3+g)f{A?_W>7GxZ$mK!Q~Nf|qF{R8 z`cf-y5-Q8+TNdzB*r(=>xGc1xpl_hbip$Q0;2e!7qZteO)>&~=a9*f@@=~q1^H510 z6v*KwD3Qafphym1*|(g78?{1}b(0a^VuZJXi_V7vJ)S$^tlBDWY8sTK9Mv^$8h*O1 z<)AX;~s>p9;qT%QaR_i-vZC8a?;~&aW8es zxz#LZv#ENE8@EBJq~JDpCgf2H65{T1-|Hy2+dTpIul->6ZpHmsY4ce9u|kLJxcgM1 zHuZitb~>uw16ok0Jm`+c4VsQTq#}L#HIzZm-sYYT$`c`yX2tzQMX0U+s(eKie^^Bh zpNjh99&zJVd5f)Me{&au26!yv@u1D{4@9=Q@pW|`PZlJ3Jm?I~|E}s4y9_Y?S8F%s z#9kYF2TV3CVH(>T#?3889NYMTaNOg;O6h)YKRLM@4J> zT-hOjS=L$1I>KfdkEdX_jy4@)o6bgRe}wkq zF&LfyNjSFHvOndR7X6H)!?0tUhkZ18AED%dY9zzoW8n`cBzL7)vdw{GEcg`%KH|Vz z9N^z{Nd5_Y_%lZh4a4f{WHb-l4w{Bet-P9f#PW={A*Xz`n z*5hbRk*fjAQRkWHp|CKjx|LOJ6A908!geepzQG#qZD2Epd&5e44zjF&afw^dV?}l< z6d2RGb%ilRMt~UJ^@JT;q#d9Qi*0@PG8}VrVp^3C=f<=ixy;C34cRL>`(aftRf?Sk zZK71nQRlf@QP{kfAmbLD(dEfNcPO{(Qe#>Vp{ml3KMae(M|9zAKSH**xjgctClmdM z=ZF%O607~jng#P(f7lgZbIa15Qc>SZqlGVpOoe8qPOW=ZTpu%N;;6w+L%J>lopU0x zp^HK~TTH9zB4~{WgQ4!>iekGw!?{$4FN$g1s5-v(5tQ-!&GHlHrZS;LW?Ao0JgOs`4m!|;rbtl;PVkw znHI+VVLcnKa1s9=Hp}pLK2X|0TER{3ngW;~$^l@+(MTzd#UDkL%EVZ+pnvbus zVCF|rJSO1Do@?XA+TFUUDKfrg`MD~6o+Gd03mre{xL@6Iq3C!&G*7@0AMb}I3P-i{ zB6Sz0r0dEpI~msr=-t946id6D0?uG5fS!%7lh@Njc(jxQpXzY6 z!h{o7Lt>>)bhmE26jO~U1XHiz)cu!<)J>3jt4_5u!YUEt>*#Tm34B0f7yPxeGx{F{ zaiBTl|cwgk>@+qL?LV$X(RUs4=a`EU&of7TXH z2K-w)T9BUvjhp@D$BqOsx!ixCRm&cdz>0h~D$x4)^bRNOpA%gW7^$mqo^Z zSvI~j6~fU4@w$GKEv|<#U(IROhw?UnvKLhC7gv zvm4wyJxMvyj3PTdtQHgBjYu^1pqLnQs?yx8b1_S4)jih*_arJ zmWYG+GAA+Yc;q(_H|G@O4@8mQ8%6%TDDppPuBN@nlHuzRRF_CTRCAGI6;yB@<)E># z4LzxL&#*h3h~35P@tC1GGQ;-vGDJ)#&S=89<*#hWp=HJ$2&TNiDWEZIJf?icDKKy& zQ}E#qG+j*VK{Ewi-AAWH%&*8(iC<=n#@nD)3p5Xp72KK6P<^6Jk3uh8Lwc0klvs49 zK^6W?;wy||v1(5eUn%&T#7*a;`QNpp2YJWSb-T+RH-NZVF3OK?M;@zHLHvK}4SOL2 zzQ|d%fEt24QM5Y{aa;bh5|6em4>vAu!{64b<<7nXY>Q!_hUZmir*Nd*wK>y*qvKZm zOjZZ)2?y`R;3O1C*kzZ4?kwDeW~opWM(go}+cA{@dwP88=<(nKM~?>|z-43R_#<8L z`4`@UW#I=nS>urEq=5;Tgm*<*cLL1XiziIa!q0R`=(Fx9gij;d6UsUX60IEgZ^d`B zQBtYywj)M;a0d68S!j^9T~z=d7?SE$Juw5`jB3>?UD-DY6()QTgSWR?SQmX#0_vm~ zKD#tDV}jP)Wk?5n?6?PWqgS%<6F_|mt56{&qE6vxIHXF!D0uWaP}XS}1hs7=Y_gE!Su zO6kWFNqrxKBhZiv8{m|!MdPyEAD}3nz%}^sPd0?C_~l)(Me+7mwJ0fm*2W>GTSD~) zN3qV%{7~Ill;fFBR6ja~&Fq-%YLj1HOONG9}=$)AC z@q7v4PA{n7j}Y`B#{IfZ&qX14UWs>J=`#srA|tZF6-Y|7Vej`@WU82ZprZz z-MC%Wj@LClUDX)(%GwXw{-PM1#kKv4bpOc49jCTNm68y`Qg$K4fYJM$j$!yI?n2iv zkixBHSX?_jZm~)_)=pB##mYL|uw>~*RYrGzO*tL+f_lCQPfBqeTxbpM%yc<;yPt)3 z^;!6|AuDps>$XW~!YsH<=t?L-RVO@==T^9IISt#MH4KvUAen+v32RUomkuqX3JQ1B z^OVfRN~_ZEBq?$ADCUf~o?YB=r^I!)dvr{I??t%0R+ld40)ktSlPq`K*e>I`bc*q1D3NI+!X)q)}{{!z@T}VqTlCCG)?zk>pP$??t z*3Bp(u4hDAd^jx~bSIo_hx2vsX+(O&7?GYyps{~9`0SIiL$R2>H`fL3=VBttIoXNE zqDk-t9CzG!xOeUg3Ou^@r~qEprJ|(o6vtSVtG|lIg1CyFY7`y|pXMb)iKuEw-$YgK z0kMv5_)HBVj+;*pi{|#!!UIPd>4P9bR#}$U6vhV3=yT(wzF3Ph!a7~hsDs0~Fgm1* zU3@Z%C56`5@Hj3_wdT;P*1%UQ`?OAiZ*`o>3Ggvc7m(=D5yj6ATY_qaoeDW;f~q-P z=7NWkW39BF$Wt%?yF)AmJ_{L1csK@N=~QtjVGAx z@Jtei-aoM~_MSI7&Tr~+xwRZP<_4h&s(7f4UA0uT8=Wd%7%oVM@Q-8g zxXGojXIo3*gJE!2he3(g$I!!#;a>Hzv3}AqC=Sw(tmrVX!@$itPx#E*R>2g8hi%Q* zf$*6{jJkvqb%;eSA)9P#CI{I}w{^)Xn^?t;(Roc6rL z^E<7#)@i+{-Q`Cz1B&0+rg;1Y6dqlv{XpnYo4Ssm#L-S8&*dODm2J)A;634nTD5{B z?1NJ+^~yh!C1VP zhXWQ*4)X6BgA=wc=LrA3QH*ueE{>|L25wKZwjvI)0?@XtU2R2CwQ*M4n!!QSj2Tfg zCOBrym72MkV_b$3)dJx&`;85KHrFvMmCOY}4i+0Vn7f%{Y)LzNzHiA7XTm)i9_{swW^!K__&yvS zuyS&+HzALuKd|v`W)d#5%`(gSzI2-90uFL6@b#WHJ}aiaxyb3G1sr4tLB4~_IFjJ_ zr+|Zo{)27wUrT!L3G3$|s|5WoFJou_iS#STk-Nf$YHx+l?Bf~aYZ&4j+(HOct!mXi%KuWeLpm0Gt^aK_2a=17S8r9k6RA+v;du&_A5r6dVfnQU{Z-|y(J2;0AZ*;N$bm)*Cc{|?gsx3K*j zj3bA`Eep>NTqGd&eb_hiImkA&Q{3lN%psb=U^H=%yEb^{$i*Dt0z2RwI>Nh|OJ&bN ze(nG8Sr&1&p5^ogaMWol?FQ?#mDWn@v=OHbTnSq$*lE!BF^78xZkE=Jjm!(?<(9 z_y1tc09K4qhjP0$5TV8c?E#zGNo#}6(%(acA z4?DTaILI}DnjT-qs=@}8Nmc4S2>{=4RF!kkQ1y$WD$%j3oP(?#iu|R`_K20e?dbQP zMJiGpRSP-Ds%*sC0f$oxUj0Up`^5~65^2PZ)~Wo!VLb}>gw8> zNSICWEKY;R5LnTY`o@}IWpyB|rcf!tD(ISlLAt#03mYq|g2O6n&czh?AdBcPCep?e3ZEuHNrH zdHh@Mi`P8s?z+ZZGRNIJIM==S4R@dM?&aTmMjbu6^ob3RuXuCW%6FzL^S|X8?z!DH z@FZ7iuX*xv&(=Q+tU-*d;>E7I34o3-xGP3{dK{NoBR;wsy-bKPps+#lAB^UOWJ z#C>sa{rb_%YS+*I<1xz>HlF5r7d_HgLEo+|xHJ~!f^GV}<6Xtr}oVMzL2Sy*5 zhdSrFPeGp?U*WlRK9q3#%3D2~-K*?d>*_p@xtnfvUwo^(G5AMUXP8*ZD{IP18XBwX z>TAmy{Xx~$)eC(J>MLvO^GX|Q{1uZ+E2|po1J=UQh5*bZff5wMAvL`&P+uCXtgQh; zBMw3(4HdQZ!G(?G7U&%w)=0`Ks{)1o+PXklVPkzce9@wCdfu#@nI(lq_>$<7+-XHQ zGpA+dmE_EvIelh0GZj^{sBn2hFi>rkh|_l+-LMtqcb08*qxjzv{#SpbjO0nzA-VPMcm- zQdnG2FnwlGPIlNvUuk{WGMJhRgD^8ICx+89%gX8l4Gq)FvubPLg-1{%PAW=hTJ6+8 zeNCV$w;E>i$+h*_fu)sxT_crF_RkD7)Hc@p1HRIlvMM84yLEbFaC$k5G(dgeg-eTS zR3Tbl;c{rn`r4Yxivy}|%j%)Js&X2CuSw8NdWO-Z?O zQ`q2w09fMpLrao9IW?sVtH2&?P2IyX8^F`hi(q*R=gY0B3|2yuT&#wR=o`8Pi(ql7 zZG?eTA6RPDRo3aDX3AGI($7+qCcnqS(K9RQc*H2DL8GHf}V z6~JrkM(9Y;qf4twswx|TZJMgEI4disu+T6}b@+zjn$o2()1W^sXovdx(wap9SfeWf z^?@3HWuRer8&z%lS;NJS@uk6FeMu!a9xSVF48of@U}&R<9gon+f+1O6xu{W3Wgxt) zbV;DGuB0Jw5k&O-z*`3Be(2XZ0QU7-*#0$G=pL)Ow!A!SyAOUR06Hg6TnHTATvLSv!-ajnAH zu>@D>Hfpk9?tng#9cb{^S3>L8Tfx$XB^5X{of6y3vbA-vLgRpexwxbTn#)=k2$ov- zB0!akR#P={i)v}fDgzOJMaiPN#**^-+UgSR1)P8NES6mZQ*y&nu(PqeJWyYVK7myU zSB6M$XU@#>mE`9Z=4TdV`E>WP>X+5lF9};rW4Ca5^}^b!b|!|RnYnXHislyNltix9 zu+n2~oSJi2tPM_r!D>$AI2wv-@r;wJzp)CY&JyDliAJ^bnv6{gy%UUqp%N&qE`gk| z1xkZ;23~w9s+gCXl{2j{hn7gVuTb5@FpMfVt+u|pFc5^rXpz-W7x0%t)=Ds$NhB1KX)zWs@$K$3aaK4`ytDmtM9%L?^>;1I7b4Grk>Dui;@kprUgF z!QAS)sz7xB)>qwARed38Chm=(KD13JsjF?Uf>2g{eI?xIwizDta%N61Daf5Q9jeO> z!2?Qf4#GUYvMEretDn;p@HeW>v)q0~4p`KwCy1$VdjYC=_`^yI>#&}%7Q!YOR$1M3 zOO|LsYG1NtYIt9a-K!K{3ZyQ>^<{*-p4p;;gDX#RUa>^t<5W~Uj#0J6|Y)}L{8m$ ze&bHS>i~2qXUhs*ExfhE9UabdoFRN$VeD+x2%x=y)A8{}3L9JBL6*R6TZPewLFb}{ zdJZgGR00K-s~uaBdKT0GHwlyQZV$>;8++b`@unnL!F@fv7Gilddgf@LZCcoDu4i^- zgWkWZdscM^q_$^j;Vyv=6og$YZUo^`5sbX1M5`FFuRAp1z=C^GD!2w`X4r8pg5I5`&bX?Ul}=Xcv7S)TGvdyI>?o)V`YUM4iz10D?J7b7na}d z(yDtdPk>?zpPf^bndO6t$nmtkkHdmWaH9=7KUl_%x>C#1V}(L^77RCFG(>0;h2>;= zO(t}`r2**fc+aXfpz6l7?LCK_gLuPB`xFcAD9S9|)M@3g^RFu7nGr|$WY}d}rB!tm zuzhf5d%f9Hj?r_D3Kll1`)Aq~hi})^EgDP%xWO%jZ4*>FvOZL093BPmhyXgOm~!Q; z?%Y$UU76FfaLS6jf7PuXoZNan zoxNGaZj1+Hut|-U5_Makc5}upBneRglVD}U&0pm561l0xM-I*mXjqT477PU#-P(HY zer089*+t1hy;X%uXEP{BReh zW^S6Xq5l@CJ`DYV?4|_! zl&&2;8{s3OuJFIV`Ur*IZ#O9};TqSZxIS0AbK(+PVkX5UZHUc`OTD~9W?cH(j#J`x zx!gCnxo3k+9mq@pnb{yS zg=DfpCKF^Pfy|`1E8UrK8{M-&)|DX?W|P86q_7y$vkW6H!b}L!ieh0!Hf5RyLhi@x z&T;8kajBVcNt5Cdp{`KpNpT*08K?TohqJ*ac&N7uh5Z4tZTxXw+#!#9lO1>1GcE3j z$8|jfpz-80$m*`M^psF-kZQhgBU1IV=SWlASQv=wT zpmX{&vt21)U(4{6{N(QT}Wc-W|W^XIGrZyNzhF&)ex;E@2wE=+&cv6ASLH{2)52bVwHgb+qqJouVHVVOTe|NXCp`ptvu&-qb5hNb^6 zqWorF40Ar__#Gf*Qanh2z8=8hPFt}b>nOh?K!H`1|Fa>HDYE<}kj%PF$s;W~ww=D1 zf$fdoF6H`j`@_2>+SCbimXF1=WqmCgR+fJi$Vi6yX3_`QOM7E1d`Q_pLXu=Cy`R1e zof^)nLQ?hypzYOv>(e?T=g0cuS9N9m?*S%tV$S1K{wk3uqSoV4@;4Qu7Ry>9jFb5v z1sNft@*O1mxt;KeV_(|89~kQ7dO}bgAJ5g-`jZIIh0EvbYrxyfUtFg%$bQZF`1&s8 zC-u%{5#@IP1vnq`e?b75^L+kf2KorO8=+Kz&-SyV?VO)T(-vFh#pvV0tq?Ulb}rOv-!I7XJQrP`iRF2?%T ziMApk*P(X;f+syu2#%=X#E3D+jqzDYxVeFmK(`OM^ z7vrSIJN!V8!8quqA5q{qUeAI52_~TMq$qr56kZX9H$>srMBzB^w`b3TQTRWi@K>Yo zkE8H^N8t%DB-*p*lqftU3eSncr$yoAQFwI}-WY{n47>-7BmQ$KeuF696SY?_{9a{y z{J|(3e^#tL`In>cucPo^qHs6NbM5Kx5rv-~g`XLPr$pi7qj3D|(Dv*pi^7*i;a5iC zo1*aBqVRj8@V`XiyQ6UN1KSsodH&VT+$ar*wB}L@bA3>4<@Fg%a zpYvoW`iF|@F?zI5ujvb6{1pQINviXt#(3V1&u{RK6+VP*@5?LlLn(f6qP``8k02wz zHz6Ly$Y+%FWfJkgnsw3_nVe@`>T`QeR9~=a=c5x&R|ZjfJ0Ekl{d`z{iO~6ug7Ykw zJ_VIu6rtyY{71&n&DFl@7x*zkJoxWDB>w{fIPijdKbHS63O|`PWm3Ms!EsN=dd3?Z z-pA_jVuRzoI?G>Va8tfH3cuOl_+%-{nJehO_ZrTqm4H|u-Y;AVaKHy}{YAVYsW?aPsS4ZgwP zW2i_{+Pj&41Rz({>>k`1VWbe zs==u$ioa#(8485U{n+4V8XRA1hzHgepZ&0WSrmSG6u!sc!wfxc`eQ}Z4}T8e=pSKl zv)uCyZpv2}+?3y7a8v#!gPZbyjl%J*nDDtII7G~_2job~)_aMS*2bh9MK-JMbR{ZaUHQTV$CH~svf z!A(EA@WCD&XiolRH_PR{1p1AZ6jcOG&uSgP zCQ87DfP>UHda z{cCA|gJTL0=GDY=HFo$*D z5j>jo#o-r_{93_}lOwkX{w>-6kl@ohX|P-Hk4XOk!HcQE--^ON6}*n@IU@L**r0I4 z(3R_3NS&yQ;45e|i_husV0mt*vjm??_Z6cBKMym)ktO)QsGrOdd@v1+Qo%D$(t7YY z9Ug4YpQzoJ3l48iRmXb4dEV#0cV<0zQ+wSagP>@vwzkK&i=VtaP|*Ar!)O?64~>p;Kk&J#|7U^^Uq6yKSF-^r{K$| zpS&x$pT^zif@e{C9TmKW;wJ=up5})x^qhg)E@{g0A6v5}B^WtizTg4sC;U8$?KzFwy+X)ur~1|kj&JwI;}XHWG|yilcs}*R>jZy; z^c)oYDf0g(g4a+xeku4b^!M9Pn@S8=sUguH2Jy*zoL;WOM@Y~7% z=Lx=r#t}b1Wc}l4zM3WEo2fs{7yLDnUnuz7y1Q8wf|rn=YXnat{XxNxlOHY?d^F8V zs|3d%rpDuP!CT2c8wBr5uYEeh!RBXTe`1em`HLeP1?T&m8wJ0c`v0wh zcO?JZB{+W%^?t!OPWQUhpTVUriEx4vk-*;Qc5*P4FwJUPXfY$j|cx=l0{zx!4a=$#3OC{$8qYmEcRL zz72vuM1H$m@c)n>xIc2a9cX>IR>;q${&|z&{G8}c!Aodd|4r~pUXKL#)BLbs@Xu)8 zeo^o$>TjEZzl;pp4xqg;AK?b;erRrzDEV0L+jd$g0H6d04j&=e1PI-3EqMH zHd^om;st__rgl7E@EYp3{2pd5_XhGqgOL9t@s)y~Lj7=q;Ac}i-Y0kx*~9nWTy75a z|NTOq=a*xG|DEFe`7G;sf%@Ai2;gA;9{E2>@NdcfJi)h+o%MqAdzM!Ueg?()^IVLg z>SHvY+%4pJzxA--x6wSlOYlEazj{&dE65LT3;quEf4=X*WK~rfFFy$R_o+YFl#k`( z$v<&|H&A~#MesthGfD6s zf0^|2TV~l0In=N26Y~80WryH#K3K7#YFR16cG=VAWbpY;r-bv9qfe?k3u zq2L!&e1qU8sNX&y_-2Z~Ao#`9E^i8+LHm}E1ka=X{EguE(z?v=N9B5PJG!ZznLn+& zp%o{159-hT1iyy%XNi|^9g@GgXPkYQu2j{yva)i$L}=Yago8P>WcFV zoLPUJqL#(4R%Sc<(D-_Z^e~@5oS!!^FC!j9{$`GUuY;1>}0&^Y6Ef0KB3!TGs%Kf(EV>mb2b z)4asruVDQj6F*1D-$MOAOYoz(!1{eOFRc;sONie@ocre=sXsg~I6s&8 zx8SExyCl>6&3Y1vj}ttD_*}u~62DyVM&g?VzlQkpf^Q-Iz2I*W=jYaJC;wE^Vp<27 zUr6nKo8W&Y{*K_=h#waGS>mVCI>!3>^OYjO`E&NG1TUg~vR`n1K6_kn{*|dAwElCs zOKJbcpUW}l=WY)Rd4Avc(}J(Y$qJ5_1;0aM>pj8wdB>N6^XJ7jJwIc6-lKkglEKMz zRl*>HlWP>8U~ufpJT7Jv=XpDk#($aMyl<})oY&Kv1RqL9-7Prld06nlbpQ1Pajq}_ z-P8MqJkER9&^Y~C@b`%SEI8l)drnsFQ1`Q_hri!^vf$5;5 z^87vB>jZz5^z0TqllGDO437FQrg{GrgQNaCN&ao(Jnr~+6FxHJvB+KUACK=1j(QeT z|NM{OFA=xs3xC)jOg)_pj(Vn(ojnA?A5sJ_Bc3NXf3CV%aDJc0m4ZJ& zdNvFGH1WR+{uc2U1^=G-JA(VL(s0D|(^sySj)AWP50LzD!C8Ki;8V%}vki_zeK2KM zVsKm+c%QURaQ>Z(4aB(}6VYHeZZ@OqI!`=L1E+>T8&UriN!4e?6B`F{Hf!TI-BHW?hp z#VBQkW!+(L92fc2Ub_UZApV})Lba1dew-7&D@V^t!75o+A<$_mZBg3&m@IMj%gWz`%e^Bt<#Pc#VOn&A9gtW@wZG+s6e&ijY0g6ER_F2QXa#Bdx;R2RpdnZ%C> zzLmJ=^ho(_#1jR7k$Ae`pAxSWocGC_1RqT6-~$H7@lNivb_%YogWsVtIL-t7dr1CkL&`zR7s2{c_g^E3P5 z6&kNR|1eLa05%gfl_IGm&zzsn;JYv2tsTj=DT(Sx8#!jgs1fNSMvY2LOiLX-I($wY z1wR>-IDFKI(U1hkg3c>*@+P}%QVgZo*15_~GQqj{d`{5HP{9oABzPy1uXzH-e9urH zfn6oW%i{;E!>g59oLQ6~I#68b3%!>1sxNdPKeWfU_UrWKZ0~~5rQR4{3!FkhujdJD zq)@iE>tW2cepj%!Z|$D+^DoGppLxO4<;koMM(mB<)NL3siZN9D&&+&~;}L z3ZP+5+Bisi5`gI)mH5T!kOD5swyZ*Q%2AaM+>#I)@AZW~KN{aX+Z#iAf?dH83Fkkp z<#iMElJ*2DY++attNg3MKA0PgnoqK-I&Of(r z=f?N}tFS4H3q!Bvq&=IHwhQ_)nCMeq`Ug8x!Nf#-qm%Vaws$05y?9lv+11xvkVVqsrkS9eOHr`x~HO2mH65z*E{TC9w9T6Y7fn0e&qG+yPF}LtA+RowN4qjNf%i zTX*zDlMP2W-LJnYOr6&6YAgX4WZ<*_-4Vu*@}b%9tHR^{8SMmT+~d%8#{IqRJK?%^ zobVk~J*pE#|Hb((H}pDmlLOhIr(x#P&hUYg{(Dbp>y%Tm3LfqqRbCJD5XUitOvl8p zzYNUO{X@^0j(@tg?H_xE-_SMAe7Uua-;DXPxm~|~aS_aD?YPbZ10bsFu>VBPe~HfA zqXrGnV8KpMuf+4A#Z`N^Gyi{}P4<8bocZ5r>ZjT@`K$Z>e=+~*sn}T|X&6Mzf9?3= z|F!uq+IsFZTpLTfLEAa&_(s*A+xEwIM1PD}zn>8O(HAoM<87E2W&Lhkhlx?wZa=Nv z3))$`r$k-5hvC}otXG+{GiPOn-bA;A_T%F9V`ltA&!ip84gHD>m@o7lYv0eG3?- z5nppJZ*!j4gA)rZCkf3#XHpBEZq5jeqrmg9ZsTl_d0}RWUcc9V?eT@a%n$twc3@5U zp~HEh53@tRg+3@yS?A3A>hX4l@oq37>M%xIt%7fgciM2bd}X?d!oJnEGay(oRLA+; z5q6h@I09F?^)-kN(!O*=M@n(Q3RGZ7?Ml*-46nWpiX8;|(sauWXMATPygS`_XF9(- z-Fd6Jdb>LOK57m8hF5aU(q#SSb9}3Ma>=~3^tP|Gt1Kulm|E`0{&y+;+tB}A4u0SU z-h*BfO#LsSY}+~FZGM&7vcj~If~gIa5eEJbqG2ol2jK`)|C>D9JDThbzcjG2ydct% zzatX1?{|dQuHopy|3OZ~gy!PdPr)P3aqV-6l z2EQW`w)}U5{tx!vJwB@H+#6qe?@6+mgqa};7$A^=3=lAcnaLdx6G&i!0mDs01w+UM zf+2~?giBC~f*vA?fVGyksYdPTY0uFkr?sc3Z4F*3Dz>y*ORH_Emx{fddTeX4_44~Z z>#}FhWP*D7{{DDB@7a*Op7pHfwx0E@%U*lW-pb+&^%PFSe4`rd!>3sB*3Y`G?LmKC zXG2F_C%(>`AFS(YZQqG++-&`%tTZ5u;H9xx8F`^@YH!Wh4yycVHFqq5&7l{fiFouv zR8iKBMrfP1Mu3+CV^}-kk>X*Fmm81~G)u?S7Vll!cW}IY*GVw_lH+L!5q&JhCQ`h2 zdxwFaj(NNTd~}?a>4OqqCimFXCeiP>9J!GuSok>)bcXe2qDa9PI9}fHiJka8zL&NQ4MT)UQK)u#4**u?{PTr<6BysI;O~crpZDeqI>8L z=%9_QGA$Q6axIyOEC(_nL+VUm-Y6XodMm6=Cx6$wEw9Jeq>&bzJiCpw_H)eG%1$Fa zgTD~$I*C7u&olTti@$UDdmDe}@wXN2ei(np@OK7(=ke!3Ys3CJ?rmzD@$vi=o#bC~4XeCUXFB|M&P`5By*2fsa1g z+lh;HM0zjZ0s6nTBL8dn|9c+r^bhq99WxWzpve{v<0nIdxEoq_i<4#MP~OD`F|3!} zqO;5dmSV1QnZRcZ*H2}G8q3`D@^Q})9p*yexeR9x%FW@XNyAwf^;y_yW>UI;jG2ip zl2nFrjrI4w!R4^wdSX8288ThxAdhLkahXU>_G4EH9=37}|Js=Xd91f)D(TJKbQPv8 z#ouYu?AqF`mJ_`#t)mRF*lLm$N~np4(GA^w3xDlc+yZwo~VF%3fmv|GMWm zp_%!iF}N50dVQgw%Ru`6M|)YSzL4|ZJX*>;{r&yDy_WyscihD2wUzf@*-lf}4x;{^tHzp{Xusa!sWs$B&sEGE z#BjyD)hQx9?&%$b3(d-AH~stZk|{MGCt~?#`)=gUJY~mz5tEHDa;DW>frlj`a>g zzmMoLs@}g_V>QFyhJF!0A+#6tqU;Wx^`^T*XgBCD=W(@v_%H=P?=f>d=|lT1O%ZmK znweN_?H^tw^8u6IhO>XTNakmoSa_2CV21!RPvWPUnOJ%4AD$`m6HT&n|L|0qA7^G_ zX}y1Vg3R~QaiG^MFBAGpOGFy*i*$C5NT+#4Ix$0}<7B*f7(94?uQ{P{(8*FxpG7;+ z@-KBP`C-%Wx2GyUq-0*qGEP>(BR5c!PHqVIV6Jzq0hq(9oEUG=j5pobra zdS+}DdYffA=(#^UJboTBK?i>g^<1(^(Ang0KcO7-Lp>W~9XFbPz%}0+PHzy2d z;lcH+FzBFXs0S(9X{ZPBM{&G!D({y=Ju{i`$08=`d%XALP59@$n!hb+sHc+&`w^#{ zZyOVORUB_dBM7--JUD(F{kOm0M8DMxnKFE7;jYgZ8;_i1dy z@VC)>@NdYRI-D)~ONv*-iQ(mTxKf6AoV1u;lR|`FA zymM6xeG^O}e=4Jt+Mf<)&Sa#|+p~y~K5x&}jP!XCkEGArvyG8_Z_j2%a=krHM)JVV z`r*KSTw?LBjO<)l`wFfq%U(ks@-SbOV4gt!){;TUD|>B1tw>dU!Am2U*^@H}J!P-m zQik$s^OUN`Z5aZ8lb`s2FN3^vc4|V-AoQ2L23|72EXol1m41{TV5e^NBEO5|h<=ji zLB8KSH4vhH)jK@F3%QWf+v~Z5_;+}aFJPx&2kKY$+U+^iZ_8dQsvX3pOxbHoYX`Ai zq3SjK>YNghC*yk&zn`fl{ll-W^nwq1ca#Xe9xvn_hP`E=FECF{-%s=D@T)~-D6e3r z5I!WqT*@$CV!d-qFZpZu)y1`#ub?lF4Wd0@5BG1`YxDi!%R&7zgnfsJ{f=Jqh74i% zO|^sAo+*26&VHn2?9?PL+9j8r%JV|bVe?e82Xyey;{A}{Yo6-$9K{fP)V#fx{DOAM z$mIHG)A(O8{OzSIlh^yE+)T93@T+QFm6wC@Vy*cj_;0Ycf5@EU&2&+HmhR8w^(_w# zqWOD=+x)0^*gUnGw=evbE7mtl%TV97=BgpjaSQ>6&AeQ(-dS8B$`9mFyY;@lv_jzf zLR8=0;k z;=Xqn?Z@j^BkM7p<_pN3a6r@taf)~zeii!f=%0qMek#UDO`bzZX(F48Mxz#}qPT`eeH)KRZj{_qFCJCjFP1F6~e44&+1G2q0>mG_LwtoVxA9~APn+W>zH=m&|iJ{Lj||svk|E>)6bUZvro`^T}N75P%yIY)+8$ z(`mfH{s`h2T`cs?nHIg5$8)NrTPpQ4EP!eLo$DF=km6;&7vorkp6XFF%ocm+G5?+oeliFv^q7h~e@6Ytwt@kd(zfrFfe zg~o`ZOk$p;US&Io-hR+MoOt|BiF`b!FqWTwG7_VLi$9Ik5gomffoMn<4Gwfoyd&f&ylyJ8znLEV}Z|N`QIIdU%g|I?nTi-{B(CWmj5nM zK0^N>uD0mdAQH~+0UXOOzg01U|GeaWa7q0Kz(iHY~ZLiBObmrAJHg-03b=7sX^fY;k3g)qoX0&v* zY;JAx<~6m2J9m3WefB8Fna3Xx^X7Hu2WPRQadk6TVVysB9?NQ;$2=1P^Vp@`B+>OJ zGw7V{+~dsaY}(?@>*#E1^v>v5n7@eL{B$j=oYA}x@3!)69>_Bcl(=KPnItv##@m=AL zye&;7Z0(pC9f8~#?9XYz1$(&GoI%F>N_} zq7j7KyIb>iwlp^FXW5MfE7(n-g=6!&=CPT9+(I_4V4`MR&h%AUSpe0T$(zb`=|!znERIz=;RF z@SyHLv#re_?+PDa`aZ@cvq=RTSsMOk1?RVEp`u)`e=*DGUd*yPi}Kms1@oPqP2mO3 z>w66&tt46R72lU`4X5ZU>3Q0HhIa4~=ooe9e82k(^dIQLo{jvYwzX!b73cliWF| z#Fq`XAMcm>5+%u5K|jmP%E!+UGYg(EGK-!u<^_tMHPX@ohYfeSmNIwaf>M^{FGZ!E zHngmK09nCO#xQn+U=ssx7?%d?3O|>W zm|pk=Bc;bkD_Y6ipG(s8LN>AJRpx#rF*!R>^zW>)HJsP=FGw26VQCb{otV^V{c zIg;OX_yUE?S#U;Y!DpF%o8Evp^*8%9rafwSg1=94H#qcpfx<>;4>mf?2?dRgqUP=O zb4*`h?mSaVU}=R{GV=j8uJErc=^~b0l$*#h13B7EbOScdf3Y?`A72hT;&2}}6VeLb zVOfO_IMQ!Y4@N$jc!PXe`5}jU9&@@Lb)aWWbUjW77LBWp_v}A`dE#cz)+#zP1Ptwwu{sUwBnws5?DfvH6 zm|R+y;NGS?UE>n7N-kv??u!z&tfDdiQvnnM$jUFnp#Kq@m{mXk(?DC&pobPl%MHLdO>sClQ0Lr!enU}JF@&m%weo@P7GY0nB~t*LQ5}CLUaU! z$z$})#MD3vn;0x%iJwoNxI2R-98E@VbbKLs^6o9n7tCG3^aU8P0);0NQf^AlES-!V z{9;mL3A%n;BR-Z?!?Y6S>3G&PVcOpkO7iQ9+gySCy1*Y2F2{NPX)Qtjl9pMrFe$C* zPBt}PGsg#d5+?->b5=p_YIboTKQk${d!@s1w>!0OxFQ?4f+BthHo3A-i#$-2LTa@H0z_^lG0HX>gv$Yr}+B@+fH>ai*v*{g$ z+u_6QT0&Y70R9lVMP`22*4Ft6?iOu~mdU0RT{C7@G3q!kP;@S3%%y?ijaf{0T&`VO zd~v}vW{l6D#^#l5e8U`57bw0s5S-4835C;HRuO>lIL!} zwd_FgX*^e^FYVr(*LBjR-)W31E*vwy@CVu?{H5jpK$}%Ke;rE?Rwqx)KbD*xeBEgb zJB-(y6uV9*cH{W<2G5WJJQ|4rA@iXPA}4d!p0#vJMXY0Ukgo=_MlEX@XMHcJ?fllz-` zLQ$Zw*oO-FfWpcBZH_7g^i7sHp}@yxVSv&c$C4o_H%oJ8GA;Y!f~g3qsZ38TnabuB zmIBHS1hXJGi(y;|ntj<5%dz z0e?|peqnwvP;`T?r50YN>xU8(2k?nj%~7CdZN&B4jFQ}TgWhH`qd2#N12c7<&m{F3RREcS? zBrSpEv!vAtX(gM}$9&+J7byL}VWj4tbx$q*jizIrv63bKEb$^>=LJeWh5ui1=*G<$ z(7>4Bcr`H#Y2u8cV)z`08AU}LNSs+jj^-G68UQf)()B@((2aW?Sw$xt+W6oHjx4xa zH#R%w6%_+;z@{SZUIO4=fhizAf01S73xB)QO)blhN||p@njHi{nX!6P8UkjvgBjgP zS^1R?&FDb-fkSs(BhzM{8jgCIZjkAfGF_cytV+`U3&ss3C-{Oz52Poyr)38VI+HU3 zj*OXwB?%d`i%KT3@uh1s^?%iU-J4%_&hTTbo>*AGW)%er*yNJz^jSs0YnXNkn_U#R zglUH}#up9iNt2kqHa+n@J@Fu$fms5D`33Bvq5_s#bdX(Cd=1l6n31a)UJ8kGC@!4D zW|ZbW$8w5_uSInDKTJ;{SX2{Da*B4#RbrMgiS1dpN%X2 zE8-Sp*NLgx~eQR3H%O8ihcu^1Wyq_Md4THt5v6ALCm`X)W2 z@G(|cg2fn{T@r*sRCaQ~W9URh5K*L!d7o1|uVfdMY|1*ikER-jjI_Wjn3V!x%g<)XSF?65fMK`bdV3Sn#kj4guNLZ~f<+S!5N(Zr>JU`Z~i4His+ z-=TKQ7mVqF{Eb)&oz@+*@&nK6`pu3xf&4UWR(>#(C7;%F^7Aic&R-hE`2`R&Cm(YZ z>T#>#{*lho^4IGb1wYm&BZKYm0CUHzd?+j_3@rFz`t-uw zNs}}4k7wxlzKp_mvs3nEYu6ZaJA*?Lv}Pk=Q}VoEaY?r3FvsQwig*MZNX{zR%@VX6 z&y?Vtv9t62yIEH0Zibodv9YrQ#pjdLO3o)|1`jd)8g#BhY>f7ruFYY|hgb%XSjqVG zwETWoP7r1KGi++n=QU8y%l98*&g}eLPC2pQ5OaSc2ZY~q%?_3xViSS~GMqvrggEup z6Q|@K!rBu83xPHcx@Q+afa9OXW>OxSA;FnlEX0lZmOd}PnCQNR&U)Xp^!%?*OnFW> zKn4j;ZIeDF|2a4h65P+}dV0Y>jh_fLj_34D;<|>gPVH#I+)j{9EO<_zR}cW+v37!^ zbmDNay6SK`X{$%Q8>69{Em>Dl?rrMqZ10@!ozdl;Kcfq2qciH~MlnAY!KLWvKa(2u zIu&Z8gsP=)^l$Zu3h#e-jLzriv`zfPavOkcc!9;!p}uGFbIL`(PwDIttx3X@qYjV|FWMAa?k0qOz(w@eM zW7(1=^SwFCR<4`vEyyd)%a;VW5yua$XEhd@9&K_;l6e>cXpf5T93z#lJ;mdM{AMAi zpF~O)yF3H>Vpm4LQQ`95;wX3J>`S=ZwP}mfl~eBWmb)@Qu-Ii@hP@*%$XhulW7kny z?(*!@SGv6Goi0zg%UlMU9H0+Nnj0m}Ejq;PGgi6Eu5h|Czy#V#N&6j1`-N!QYFAmE zMO!Xuxz|8P4-t`WDHqo2ofa_+^`n@6^Fs%JO$(~fPd3~_b=>Dz3x=zlymF9&$}LUi zehEpQTuS5nr9;0a#xbb07v%dTuiY>H4Z<%K(*CbWnj1lHvp=q1UX-*S=ND>Ewc(mb zJn`+`1aC&Q=Mvbo7UT=no}A=8M6qz2QR(syI2OBd`V*G8{I?{QyGr&Y)xd2xIb9{? zEQu8d{G15SUp%d?!Ay_GzB z<;P{0x=fOjXZ;#E+!?OlZ2y&KXHzToeDG^zme*Jxu4j3hySnJ7y>-p?;ms{=jF(}@ zrnTjorqy|aCe79wY0T_>*_l@>q!F9wCz}0A70$j zwx(&zcI?D4{1k7y0NUEaO?l|5b0MMO8cE)?wXPYz)@-86{NslgBBm`FzPf9(>%I6a16)nDoJSqfomL#&k5YxR6W%KM?KpyC zX-`O8Z2b>Q{&O(}%6=u6=H7VqKQ8O9?st*@>3)^sSM?`cY}*6Hb}gebW6z1iI>*{i z`4|&9?a38_g??j52e#4qSQV?EVltlo;nl>22Z~}U7pq@TjXV>bgZ=kMBTGnMd?`j; zONc(k2r1OycdVi{h&W1!Mq2c-y&RiMD8oBLEPA!Z+W-vq@2t2~e%m4ODzDN(iC5!; z!oM!@IY{YHcv?4-d@sjF@arUA#fPFNGQ;|%uVj*mVM&d}Q|m?_hag|)Bay(X@$C(+ zmwKo;l>Wa-dlX*TGY3IO_Ls;4g{N%1-**dqM80I>3IC0$9U)@CO|Ww2z)$$-Z=`M zzO@>U{;5&$EWEvAS97?|kE0k{)*PVB+=9BuwSQxMb5kI%p`(L|C3FQXtLr{f9V`;B zvsl-L8^tYM;igWkf9pGUuM4-d?%)J|L4cLXWefw_l z>XN$7rskR*m4$T#5LH2O9d1zVXlZDw>k4;ouCu?nBJQ?8^ovd^^Xuxkocz)ea^k<} zgI=#=aTUPC8118OXNb*-2rDWc!#c|AE##|l6lZlYOzjG{kaOrwA*-ch%UfIPV9#jH zRadN7lj7mmrl_;$-n^Pow#Osw9VVAVGHMeZ(?rb7AaE$QV zn%%IZv%L*ZUU2NPrf_wASGbb)#1w*O)`UA-+P1K+a3l81SaSz*;bzvft0m0Iq*ir5 zAMc*Eqxs!&!qt7|Q-v+)KYhRw)29tp;`zt=WmI)#KjydjJ6nx>Jm#4 z7-2jm!|HLd57*Lx;g}yZEn&#RPgKOAE<+VwqWLuo!E{YQ^nS|VSR)d?CZ$K7-ltte z9KEC9zdQ>5kx}qJ9R+`W6ujEQBKz(3Oq25j)h|=>Qj#O_s=jJnqvkWJubn<%qsNU& ztG*j-c)Q--QSe_L1^-ovCp&R>(>flo(UbqZvgAJ`p6s;K|EG=KZs#v-c)OhmB;_S${3_Sx5**<)f(ZK5>NKq>G4B5eo$h!zd&{{!e`@8^^5g3yxpG7HoVV9 zzr}{1VZ$G`;q7uB7zO{I5+55^-?h=(X|wYs8-A7z|GLDJ zJ@j0vI^K%GlYTnXH!#VcIrvllJa3af+lF64mBA6~{|z?0-EU1cybpXz{x%!l?*Gr( z@OC-h7zO{R#K-oR$8Gcks{ZnQiI43s&)Mkh{(sSixBLHP8{Y2!wG8$_Ic+48$J2Wi$6O4L*mJ9JN7(E;mUyz$i$8S)ZS-WP>UTvFPj-40G13J#db^$1+VFOJ4v&I=U=;kbqu^hV zc(UIfZ$G!u+vD?>5+56%ui5DB_D_}zo>+gDje_4e3chC){O3o(KQs#d^eFgWNIdz$ zz8-tsMo)f_S$0kWGwc%5T5RJ zDSFD&L3-7mDdlBYaeBHZqv+`#2OUJO#!kwU4Rpk|Bk|HfdZ-=M^++ipaVWf!U&gT! ze1XhY+VFI*U(v6Wc;z>0OFFiRgxh29U+of4dhK#{+33mti)8)|8{R8nO4NQ08rj8#=fNX6-n5nfJlyvQpFNRC~; z^0&e(x{et7G7^YG`A5+YOZ+e|6|$*J?VtWW2Cw!{GbEpq!-EC1)iHRrFL@vaul6PH zkHM>b$ulu{wJ+%)g*cR6wJ%u~gID{KM{Rh!pYi;r*M5B5H*^Z|a9asy_~hTv>F5!* z(z%R-Wem?MGJ1#n;(-d+NAwo?^1i>WTT{{ZT&UsY(2c)805W|BUXtV9 zA4Eyz%^$gMqX@;j;ZtrNcHj1Wz}0xcytgv&hY;RkpHtEIhrn=U-v_Cr}{3-4E6o2e4y;3%D(UKdrAxKd%|0_?@6<7$?v(nm8E~#d$ha{ z#;o;uLkC-ZCFKK^_(IQy$IZw)`9lY zyt!^ob>C}MCtgpi>iY{?d@BMJeiPEE-JWvqzY;y)hO6p(+P(i_j{C3j<1{LV`rhK- zKs5Gkvx=leODONPR9_l+m@gP2s5gMrUfXEv~Bq^ky1}ZZ^krR(X z_;2}u;|sqTBX?^hgOHLw)= zE{}FvsPFq!g&IZS1%)R{iKM7i6gi-XtrnNDNm0b*-x@;SLlshz6j{b)11YnjR842Wg!UHmLDf?5et{g&F4ldlessm2l~0El=k&nNxA_4&Qzt-~%OJ$VQF} z(!Vl^-`DntzW)D8$%Y6QWOw#4oF*GH?8{O;nZ> zzYJj=#wW7k>hn-pN&K=7Dl3a$W>Q(mRs-3ALkAc7L}mK^$kXaR@x3aH@-(_ejq(pC zq2=XJ9~tFG4B%=qpjk3P#-&F_c}Y~>H~jG2D7q2i9!^I2?+(asiqU)!YT&bfRo_SW zh|GyM6EPFu-3{pCh&viL!%LdXfrT!^Hd<~E3 z^3~`pessg~)zt{65*~gkba`J?kz0X^l_A5sFjC%wMbqtj(hu7s9h7PsQ$5*1D}271 z^0f$dR4KM9wjQZ;u2K4Uj}&7DI9K$&C3=-jNxZ&@)r{5q_-MrGm2h6w^406m?Y8M_ z9wPzet7+jxhD6n}s_$pKqKgL>egG=^a2KY=pP^HhA4gT$BXp9{D;EPYni0hm8PQRE zCCEQMiXUzP+3$X8i_=CRBJn&TPQ|)TtXFt^A~bxxx!b5en1i4A>-eRI#aNavbVv_ zXQ{6dAS~sx6gsi{=G9QoJHaN=33Bb7fK11VdL3xW4=yvy7c5KZdm6Y6?n8%Ch|g09 znN*f};!OkV_gYwk-c0Lix*JnXU$Lsd61EnP)#Ecyw2qVhKDal1@j&kLf_iM-kzE@Q;1xLEfH$itq|h zJ5|4sQFZVl4MP8btDxVOM3^fuVw(FLKC^t^39Yj9@=W)kKYZ}fN7aZEv0VQ`HH}mV zVOIXvy_txv^uQkm7T!#;()Utdx{nVWu=K=l^iadQp%cGxgtT9B?;K3=-TcfAmsG=^ zzCYr+0>MV>3S8}7pI(i{0L9Z!%lAF4op=?jKMtLE6|6t!tQ89u`nu0l_5JZ+t`Apb zb~FheR$$c-TmNS$b=D<=O%ogG>H+ygK+^i zTfUR=vn&&WXOM+R=ZbV(qtCo~7@arQ=e`YZcNIOCTBJtx*JbIpS+IO9p9UcSLjxAf zGz;LeS1dyjIOVjMEGe%!aaIo<1=U(#N$A8`2cQi;Kh9*?K#IXFqG`cegsagp-gsDZ z5ZeJH*e_ zRAkXPkd9wGKed|H&gE-S>D8g5vc9Of5J~j~gqlmSdr2%VWhe-EL{NOa$J_ULYTsWY zLZ1np_%$L_%%jx4?n9j)%C52xai+l~{_@gg9`~Uyq6>YBD1#|{>_&PC3_!)k75H$% zr#NpYZaysH{kJG}0E^GJnjar}&y5JZsMy9N5t#*OhA6;&mdFgD;>c};R}@MfSX;X|;{l<+jK0Gg=!3Csvd+F!(sa8B%f z@W}vylc^?ziqezrLw`Xq-#pA`1ov$zST?9|rFba`{6R%6AE9iatH@M2ig2CB!`g3+ z!?tlOG8B}r6_?N$#MJmzYahQt*7&sv!qMwwf5zhf|F==hwpyogg&RvlA0KzN>3r=S zHF{w&hyhj_2v`|M9Wye9eTuP53be*98oMJxKm7=Xb}l{VKC}a4m}uS-_ib}WSt!c( zvW*7^X}8J5U?tay)VqIz=)wZ(S+O>JfnVlpC#w#+X;4TxVU4xr7IWX{saR2BLs+DIA>- zC@P_fCKJ(}(Gn+z4v! z`w`nD4=)Gp1+G0GzMMpUgVuX|f8=r6FQ+f;{vDT~yI?1-wP@jlOUy9#4{_~9TUS7e z7z^Dz%(p3uaV-}4>^iVS;&<=A8+T$WXyZxk1OJ*2&RBU?^!=4vT!m{!e0{#lz7vcx zxrtmWX|Ul88vE{fgdewQP=?hJUE6W-sth+tP$GALu_jW5p@wrXZ*6G6@dvPAZzq@E z7{`9FgY4cUm8Rr^TV59`R%LT8*=*khj@Z1_q9L1Q%@x0_R|ltgYz?$?h!&EEk)dTX zF0fCRKWrdZ!_SXY^Mafc->U*Zlo?pCJ$3~V&^7md-%$=41d=p~D@4DDBQ^SkcbvT?hcc3E(F!5=E97|5Mm z)$mlP?{T_{sT!zG&lp$(g9f=aco%_Ejhjzcc2^CwI!eU)3Onc}f#HW!VIR87pF;me zS1Ad&QD2QNQnsS+H*&uYdk-GmBiQ#eZcTmPOS!s}IB!^Xmf)3V;ieZTfB6~8Z#muU zrW-hX8QITw-#FQM%)G)YlwJBZb}df7jd6A zeCHt(&GKXJJ(TdpHmMQRnyf^*|tv=YX{sWcOlhwzi*iPGw*5lO}Aq8 z4U}B`uYVdw;{Gcd0njS6Z{F+fTW2EX;C)(d?e|ekD+aXJPw;mV-dl zHC*{a@VyL~jkH6x;c?Nx>z{YeUWn_7^-pn6fF8vtTIfU5{xJ0PeqSxTLy0L{ax9ZOHQ&tSMh7T5}&ZHPNfQxOs^iY#bNW!O(0a zbhYzch*6!a`7rf|B-OoOIIg|Wo0gs<{MB#M`SLSBQy=p*pQkeFb60*JCPzBdGWZ`( zILm8@o<&@rsDHf#zL9+^g|7e9hWr1xV@>~mreo1_yH@WQ=8fP%7ed#03L%az_9h=T zc!$COnfGJ&t*>E7qK<*4%|*=r@pXig>RIbCdey#)Mn1=p)Q@Pa1nKiYpo>XH43z7k z06mL3-Sa%+xIF4J)Q2`9OzA);5q$$PAp%WH{f89rJ_MnS^o#{v&UUB;4|pK;&z5L^)g(I$G9qIL!5!VKz@`_5^Y8xyJqHaK$7 zAKjjf=kYiNrn{%S$gD$e%fUP~ZUgx}=UG7M;*J{%fx|f+-Q{Yu+12QB<2C{qw~kMa z0MQXYoq%#K0q1pR@hFG;=I!W2bWdd$A6oFBhg{n7)g11jRQLVHw!T&?F1lG*8ydI_ zPon(QRxDi?_H9L(f0VKglwq}G!dd*PLdjIFCcpZHq#K{4rD@a>8vfgWv3k`{Da_%o zr`@;GuVE0e!|+r;pF2djug7hRS~TzmF;l!X4Xb*re?c{uVphgBpHMcSBdmIZW+F&@ z%6%&}B!tjJW=W);gbAzmO(6O7Me^5U@}c#FoP2~Ppr7XUM^w@5Fk(%#a+3s!bY-;juZ`$78x!Y;stIKQ3ms}>^JjGu) zMQu6qc>B_5+}n@E(H%VDyhcAgfhfQM0;QM)GtngfNjZt; z*O5u$7zZ~X(R>sb6RT|)KU+JA1Pgq8usGq;-yz}8%_fxN?a~rlOu|{B`5m04@lq96 zp0JSWkU|P3foJRh0K96?!*#hF=CmSU=|NF>)`ZQ8{&L_wgg%q>?&F9n{*YkOUsn)H z!0+D)&=l|o1bYPhA;B2}{)k{NT}j|cT#Yh0BBSw)Uu1NiDG?ciXUaq-foDP@lgKkQ zB9p{3wIVY{`x{K$Br=) z+cqpR#abrxJjv2f&XT^w+aV+A9;&49>%9a@_&%zxY=?iL+IkQU&!9Zfq(~j}oB&x?@dSvAWJqEp2MeF#7J`*OGj1ze8MaaKA75zCP!}_f^zNO= zbv=6xuQ|R6!?VwkYC0x0v@q>?Wu7Rsvh>KhES=OA-a0yACK1x{ayDZ>L2sR+ts~u$ z9}E093jP!+n4+!K@VafmgqHN7!7(_2&C$j12G^*PSyl<&_G*<(2hVy@qF=WQ0!iT% zZPgfAQIfV=@7fgMXa+|I=ZI3?3pBl%Z-mWvqToKM`^ecF@V;ziFO|^M57~RJFS*!4()2`+-ZnLTWmUauz4_}|Xd@an#(eC8=-s`g0MRLQ}WnY1J2wR2M zXRl$}Z0)O@^zA)D(ZyDMMc!}at>&X}>%r^?Z|T?Be$hhs+vDJ00e)EGyQoF<>v3MC zl~9E309dQx<+{qnL`@I0lXOXKDr(1(^F4`i=)IVrZS>v^HeoodmmrfBQ6V5Cz}*P^fZzzt)MoY`}S!}R)0@5}<*fW975=V8T2o-&tksVk$T zCLDPMj1a8vwRKuDX0u}ZWX6!DI~5Cb!2W1WREhP*k>Tqi6(FAHNAf6}M7=p*lyx*1 z{9Uq%!GOk#RayZJ9Tdm4T1i9+QP0J#4_mPC@j}qcNFKpa7tO&(48_4?c?8FiXbwJR zC=MRYBRHOj=HR1-;^6T-g5xjI9DLkR96XpuaLgJTRdYUaC=MRTBRJMabMUc4aquXP z;-Fc(Q*apQKoPf7!L3r%4qIpu{$7c~VaQ%t?>DbGPY=ak#Lgz#Y z9}GthiPZZ?42j9O2;oB_jcoCU#0o(&+K_l!42gVJiNp$-vr{p~9TZ2HLj&BuDwa_P zMXY=xT8QP>BUuuT2&3#{Vwa7LM!9i_4B^Fm9}U1_R1EMXI|^#;eTBN=Y(XEjTqAtB z#M`?qbszsORtxq?%kiD69JNB9w7kQfTi?L6 zMOIlo(=MfsPlu4XSe4C0CtIQx=i*YbLaohb^0j%TTAI(q(p+YSubW9Lb5(TxOf1Zo zs&%>05Q?tE3YDED%wX(lOc-=r53Q3LI+<3lNUZ)xkyDIq2Dv;LvCvv`fwJ}slpVZ4 z*}q(%?1>AM{ptc`e~&K95fx|bUl2J~Wxt8coODrH-ZAK+BdTN2IJ}rs&=pC9lklI3 zgC~jmC7$~KD&Fl@^Q)O!z!3>Q1LdI59VPj1z&{j6GK~bUaD2SATSP96OK$9m&;sWZ zt{h4962&s=xHNLHcFhPLH6BIGA%_fbI(110M@PAsD84NzKHg_!M?^Qp zvN2)wSkAgY*@6p{U3r1Bt_zeMxaNdJ|w1NL+ew9#1)F=Q-s6~ zBY5J6#1N+&EhMPnzQZZ_QZs6aNo8j)Q1<5wlzA}j$xDL~GIK9b7P>%LV|1A{S&^MK zU6H7y70wqq)4HVV*baPJ#J0$x!f%MesL_J(&&I)1Z~s8zKVfetQC>_Kd{K0N+(i*h z`z3+|`zsOC311`e_R)~=+XUW$l}XIHf4?fhb;yrkhO!wmhxdBiyP~ zP07=+Nq5j%nTrK;BtMNd1n;y&XJhO59*tBQM~+>W9l~1V9<4`YZ^#ZY?OyFxo;!Pe z_LWS#Px~Uzy}c*uCM|742Vn*=f1k_Z3SCS`7C8vqm=ru(5cZ$_GVW(*LvN zk7;e#`TlAoOB><#EYto)Q){GYUG`k|GRlcn(9rdw?#C@b*8EMO{3^E4(WnmEcnFTE8ydPS<}% zQ(OCt`gQU4x!#JVwssjYlF%kxyj`wIQ=%$sZ8Rp(;1iEwc{GDWa5lJC{&$LrRk3G_FjdS)}AA`_}57vRC!7gKp))>j+_A`7gj$zR65hA%|Yh$c= z`%>6U9gvKsbq$*LHw zBq}*6RuW>m$MzttvZyAs-zfIouCwu^dNVmczZ_)Efwh)>C;3%K}~ z$`UmG|Io$nDiJX*uJZDL(9QpIE)H{%aPirmk8|@J$vDd<#SpXo#x^%up}{@c=*R+p)Hm- zg{`rRn>5Vj=tZOKfJTd$!)ZsXkFL0|2n<@{h~4v~#B&2Q&C>#e=S{K4NlOrW zo|YYoelA2lqVkxqNkih-=(fKAza9bqmB5RMz?{mL2y`Pn_jiO4*f|_a(*eigQ@W~X z%#Zlv7j&OHmma-fq?VcIh+-)ruYu@X4y~f>9aNlXRs&A@4hOFUm`7;3^DWASjQ2Y8@nl37@Yfl#F*|tmh2+UBeQ6gk;|E* zPqG$s3>tuJjmD`gXGA<~mq}AZT$w?tECn!)`#k(|zlB`x0O3h8OKGEoW`bO@PJ9WA zIA}Z*94n9$9QlCQ*e??YSBL9`aoP-5TC(Aq>>8(~CV6mquKQSW@*<%4_Ke}0pS&Qs z3j1{qiO1S5!3`4hHA<)`X(pq@M9tZl35@e35XO0tX1J1*NrZ-M@>o&EnNl>ZDvhvX zr-7Spe7YQ3gccvDH(Z%x@w8D|l#Fo^871IJm075xY?{;#FdZePFrG@r=V_Nt6t$T( zu_}#fTbA+p#S_Vb2?-Xd4i8*}tgJR=GYTSET+C0jWl=f_S*eDICr3&#Q}^AZ!NUPw z%_+Cuq%l3?$YRKtstWLsie*>A2)n$}E>wHsB+a+DrF3wr!!H4ity7?S_{?BO zPT!#DV$)?7-&zG8w@`Ho%*RkqhsHK4@J@+xus>;Q?^ZdbQ)6|4W8@h#1wS-KQyD)L z7c7186Ii-hS!&}u^9lHBP#OA>6&;{c zpshYIX75pQK)7aGt z93K&{z&R47vrhA33Df(F=m4hxBIhL%RNh5}6lgQ_?N4RsWJ}!@3RIRDY{%ZaRpyci zodP44Zj9I{AVs1+luNFOrFmOQOxHyTI-&wQlN4%=0^gG;>OJd5m6c)1TC2e49ZJ?l z1zu;9wf1h6yU$X(QGxG75vrUr1$}$8Iw|GovI^X7iN9KbO0$V5>`63BGA&yoDnR^lLBfjcCMhO8cy z8MJtJG6i=f3VEDRW!j||bv(VMVAXbmi4%T}s3o3bfgNW{e|uy&*)U8Xb~NU$8w#?gUzQs5UQ zD*kYwWX-l@tySQM5(OJh?@^h>Hfd|`Ryn1ElL`xLXDLU?MNe0tjql`$YWgMLBFnTI z1=2%}bf5z$6P8)18U-rppc3SfbCHx8dE>qRZUv`~5p;-H1un3JZ)lNVhlQ$9pwbsF zlKQ2>yCRiQYzHHf6nMx&jfkXwmI5A#2vVSO1zO>iJ+XFjWW)gi8j^axW9eI`z%VXc z=s+YXaHoZ;QQ*H>s4Eosr3{NvfrAnyLQv(VU?oaNob}ufaiu}WuVsp+P~htpYMlcA zOQLkP*HrK-sghUwGcvQqGHsm#l`(PMql7u-{Vr-)1+P>0z}R{PPL$4otOI*gCdcy7 z`YjSv&LeROK5UWIDezuP+}^!+tBmq8oc15G{D&6n6$<>d#fo)_%KSY-H`6WrJ6EPU z3*4>X)e?tZqrgUq;>#SB*%qPeli(vVrHUyyh{vDkfH(zKNE8>RGHW7q{Sy3lnUZx1 zem+8`z+Xue7pF3BMCf$>^CHHk%ap8BaI3^=Y`p^MSs6NbE2_+Vi3FVj_em6qQ}Cbh zfCe3?tO64!2o$$YWhP1_=oFYQQ6x^m*I8ui6}Tu8*9Z2fOtlr)>xHt&`AymAs_{v@ zf@>|u;kMcanQ68hH)8qmV>wE^W{nm#3cTI2V2$cg_gJVJ1x9-Onuvu0`mL1t*NAKd zs$PgiI;Ru5Bj-dZ z*XD#jN)d`rv%bk^v2BuE12Roy^`A92f7VoaWtx*=Z~wC<9{2yO87tOaW(k&nlK-fb zPc5(Drz8&kQ=n3Z8V>DIdM3yrns^l)snUo5`MeZ+UqrkDzhR+jmGC5M{I5}9vW2RR ztR3h|bHX!`^HtJ7dgg`>#Hs?%WGmDL1^!B+aP@`<7I1y2 z5XvPQ+n~U^EjQLEP*ojNLUiQZCH3DKu|t6wQV{I#-J>$Wh;#+E+Ua6jjx)#F!I2Rg z1hiXPpy~``6{u=EqWa1z(5l!{)&Lc23sA9^02OOTfeh^RR)E(iP^p9c=X;dG%1HIk z_o!HoSWy#e1!s;`nk1EFMwE8pSqeHHktrhNl$T>z@-&eGrCjr_6*M(de4;cYQ&63i z;Oi08e@XD22CShcx8=o~4pJtct}JY(tiVf#jB1=zgQw7J4f6`}O7rsPwYF^Lxm_hibwvepyW6g5Yv0*6x3#6Md)M48 zZQYWrrLCd0yRnJQ>+b5DCo}Ulw}iW*Ff1?bdplqUK5*UG)SQQh3Kh|#ey%rjn`FEzSq4d?4C)_=#m zm+218yfe`}3;?&tQxlKWtTJ{j+*n#|>|DM&w8Yp|TLBTf%jX-rmX{bQ_b)O!Ye8!m zY5EM@Q*#)vZMf&|dyVzxLi6r>%twt`X4niaz1J)=mzcpD)|)FzpV6nM;s2vI>b^8R zJ58UPrst-a-2zMJS!u>tbGgxV(=!An>oYa|inQ+?S#X(owO*U9uSwUjbB*1q2crHx zcx2yx3T)FetEin z#dv*sI#{nV7aFOxX7`ud!S&en@0g2q-vo0POsO@-EH`$(WQ@7V*a@OBwZ_i-jWOo^ zdR|)7CEG~cHRLvZZhE5WB^MUu@C-*^BGB8-{buzQ-=WOi9P<-8%fc2jJ(m~h#aya>l~7zWo`~QE zo?OWjV~2T?vW9aZPj;AIW9REemYJvIqv5E;JVa+p!=sebX9~fUBAFjADHF(YdxE+z zr0~&V;meNPYV0!aKdLWCL+mXS=6r_B7R~%+4o~MwMP$X;BUpG1^p7{^nWoFC zwwTinUH0^4=1TKj!&z%|FXwh0hFz(~u9t45F^WvwRclT?VvISgFBq?9PcZ&`!#l=N z&CD~#-o;J2G^*W-J$IUyn9uMr;otWS8H>&3=K7~EtGdiMz84)oH|?X#jCFdYhT*_` z#Hhm`#s%ZMdtc(|FK&1VBgSEkhK$eeMWY(tyNqw_y~~&x)}5Gi>gw8?o9n`?uIlnS z8qDiD!kw(HVQYO$Tf8?q$)UWiJ{<0>YiVlfSl83v(Do^SKS}jZO2qIve;bwx+!mZ;I+-bzNKAJHwm1n^{Bq_Kw!3 zUEap_?#-=DZ1t6wuf7Z-Nd=42+}P9=?rh)9tp;P==K8KC*4o~-#mi6KZJ4Lpni{?I zex{Dj_H9iK;X0%Zo!wpGrmhhcs}mDha~o^g74EDj;;oR_#ptb7(VD|HH6&dsg2Os3 z!fowvUPJw6RIRzCwW+P0;l(b2h>M%r8if>YUVAsZ-`rZi1>|T1styt0U0DqsyBYTl zuZMI>lpng9!i`Pg`i8A=f4GTJTLQ%z>%;YI`|i4)txagmx{mtB09&?lop*_suOYm1 zw}-pinl9PW)YjD5(tvDzXT#P@u%eh-RN$Sv#XI-%d?s8{N22QRYOneoTk1$;Lqj{P zqUPt;g7X^az@&~UN4dSZqZ8F@78Q*u>8wZfsH!_VTf$8|;97UKcV2@)pde^$t>50T zRaQKTu}Z`PIJ@wUG12ICt?doqXzWVwz6*dUhqj(Uw6+HZSzYhy#c6<*Fz*`gECDwWAyZl?9D zX4)=~HaR89Jd71{HOewM1%94F%lgGG&mn!WE8{j}naexiSnSH_PpEMDZ%Hh7mF!De z;TqPAZJN_nvc%;tcjYX0c|i-F#V)g)w)W2AuN8lE@V5&^p0TkgypW2NKYqJxz6AZ@2~{b4$xGBW;tko z60SV$Sm`>0^sHmK>m1Uzk)C%fb@jr6VMixuV3ngBG|NC!0h&tCT#vG5l)*Ca2>!*A zf3d}X60%M^;4!2T`NX$H%B%#w9DK_lvjsA@k5<98s2jZznhyR;^?3Cww_;)$?AIS~ z9e`U8CoFXxMLGm`izX%4qH_4_O)!capjrm1#h}^*stQT96{XE6t3+9al(2Y&gaeQ& zcthZ&@?~he9pGz}c+yPx3gBBv<4!|rtcKK;HjQncP~!?Id6`XPCuDAjqp_VdKBSN4 zR_1|U58#564*uJ_3;UHK*0^4S1M6kXRv|VK0dOYuiF1J8#@~7T^(L+&>Wg$qU5QAp z_!w&ayCBwEA^S?%U)Do5q7$+q>n^4F6UjPCvThnp*1yFcF&1OQ$m`m@9TNi5aHmMI zL=aGCQ)_)*m`3kBJ`m<@?&@NBo$Zt%@<3jI@$$Uxw$_$wnp$_OQkCX4bcIXonLrV8 zB(||>bN3dk3o#){xPJ5I&ZZqIM{|;$vL0)JEm&vSaoEXdQuHu~FiqlcRbFT)v-IDRj8Mpa&=vASpzie&yz`2*l3{rs75r0P)m zD-=SeL`#QC%Oo7@KR;D0&Y}2J(d7ULXKn7t0>$rFgfdnBQ|K!J5Wl=}tqmHe^ot@0|RTVSzuB3*|ifA=iPMUGh7P87wqDN!H0NAMh#aX|H;d%?~C=f!zO#?|uqF|AW3CbE)vDFs)VxiTyrB`WdD_SoJcNHyo zMXVOJR=^vA7rf^GeP*6#cQVPgZ~vdq@8pwnX1?>xGtWHp%=OHkk%Y9j9W9C3QT+Gk z>j*g5$d|Zm+ARp`DE%dzzGR=C zROWX2Ex?GUozI_F1H_e;s-JR~^yRr10UgDErcTGO?ONi=^JR`N{N?#~jxTU=1+8QKEnz~^AMFN__@YUV5g%Krqv-F$>+kVUns)lMxviu0o!fN$`>X^luO{l?PIM{GC#@X>A-#^d_&b&a zXh#|*xh>=3#Qg1gGd*wdAzj(xCSOjXD1LmGxueS;*H+t5zr$Ms+rr?(yH(oZoh?Lr z{SJPJ)4}CeHNAa+Uw>S#s@t@!4A2D1QC@;5Hy$i{KLLNCaUXh^u{mM<_hd zF2*RvsjaXtzy6kU8+a^y-3@z??fA2Rcf^DGi;nO+li>Fz!5>V5{~-y!D+&H=68wcE z_`xLjKY)`SWN*KGQTKQf`mdAVsi;~V>8C4jrDrZ;HQxk}^ODdHOoCsW1jiD4hv{Ak zoYIwTAM&k`sY&Q(B*Et;!E2J>bb?1mdRUbNr((eaYa{&*1Z>ft%@`Mj6FL53{_^Pl8V| z@DSoizHUx}Z%BeanFRkl3ErCrT@-AlJ1z;nI0=4768yJG@aL1@$CBVZQy?HNTh9C> z`1~aJok{S28u+3u z4E$UJf6l;7d5&7R$aCDnMV?MPPow-2ACc#D12^RvWZ-7Hmsz-^dzFPtx|1zj(!IgJ z2N?O4`J`Ul_QV?iPzbqNwZ77B1;NX7Dl7-C^Kny3ZPT5S*o6 z9I*K7R2}CH3%`&9{%PFzOb)1D6{!9Puc z_rWU?x+q=JfyjS{ft&XEkb#@&zGmQNy_m#?LHy0~jU~bNB*Fc>Bx3U!n*?8z1b;RO z9-u{3TsHsn4Ll3sqMr%_Ki|NAYT)M?_!~*^{_)?!;jEv7Z|vypUD<3`nkcvrM}c!xY*Sj25y%7AX*gX@-^+}W&=0r>08)BZ{oiA z?-qBL2#D*eq-_1sp8!b|Hw-8Ky{w30OM3%X1d)C+)Q_(ft&s5yd?M!4cxT1 zN0Q(#Cc!^Wf)C{P@+7~>|H>rz4N363lHm6l_#mVqdiGQ1aZ$S2_!m6Oz)k)9)4-_- z5&G`D*^Ky*EeJl?z)kvm10QVA7aF)pe}jRW{MQ(Gj=`rB|2m1}G4oq(;AVb*V&K%I ziTuwRxG7IJ{*4gv&o$`l4V;=T;qxm4r}kX%KUjDQ!svR!!eyNQoQ0E2biHigW;=Yu zz%K%wq`R0JFI;wc-EZJ#x$H1-Q~z%nxT*hHLB(G2H}${Az)k&MN0kg(7a2J5Ve@k?w{WV%bd5G}bKEk?z|C^0HgHm{ zl-Di;A7bF0&Y@Ja-b{Rqfe%Hv@F_QNvz@sm34UJ^{Le}7Ck)(_e|Hi*#ESAkMzh}C zZs2D9y34@L`uLWCUu@*}<0SaEN$_+U^xz_SOg?8Cco1Qt&vO}<{s=+py1>Gz&d@d3 z;6tV=d`1{J6}8}D0}mSbc#A)R)HTV%rGKO^tKzj~DLUyw zE}FB@Mf5!7QhsXF=n_7^=5&W?IF8f%O}a+vM;y;*f1!n!bK)YG@XupDGcEc*`!z7f z!Z$H}m4)BO{A(>d!uS#kXRp&>;a8++V4a13#(XwfcqgX+gN0wm@;_nW=dk=wC&6E~ z@Sn5)kcD5v{!c9&N9(EUYYTst{mKyF7XAN*H-b;%7vLERVi+%~0?~g6~JH{Wh@F%r6 z&L1qC-q+J5?;j=IKQfd=K%|!&G@?(zJ&F5+`^^)w_5m2_IKv=MV?*k?``38 zng0L_53@Y-Jwf5Khw1Yy`Ub5AXS{_sv;SHPm-nypEL`^E)LHlvmS>fPuVDO63%`x= z|FQ5GRSUnJ{crJ;%#TTR%A5|#(}iWBM;8kp z&S7U-_^%nyvhcSVA7tRvZ;JfG44l$EgX_gu3!lXJRR&IcF5yg+T6i`4D-4|YOZy`4 zqecH8G2LQ|{wSxr(!#IebXOZV$6)q?!sq*p%f4N~@G^>s zOFr@`d=@c&hea>rnFlTWR;GW_!lm7R(ZI~GKrI|Z4?CF$PD_$UkC!g#fXzrgrL3;&eyzgf8S z6DiytNV?L#Uuxl1T)x#7F5eD*z`_qR{VNvUh3)f83t!B5PI_BC>}0&c!p~s4lyBCF z{MC#ujW%><_i=HoFdH!PI@S=?4lZa0;{k;ZG^ioe>w{WSa?;1Gq z$)!lRjv6>YGQRrMz==M_^j|VAb}nD(mHiA-UI&@J4|fnkKb8Bl;TC>3y(mx%t@XlP0-&pt{#!sP_1h^z$@{#WgEnGg}J;uW2>)h8_ zxO}2}wS~*)x9_)b`3Co&E&NQ@zwE~lId5P*gBP*{e~j@03-8ADv&zEd``ve1xO~KW ztA)!4y7yVQe5L!ag)d|~OW}PQBIhp|4_f$JjE}VNGr9aqEF51`Q&*jZZ(w|#g}=f0 zZ!Nqx>wS-fzs2}53(w_zp2Pb?Bwx!JpJw4NGQPsX<&)w+v+!Fuy(cXE5yoG$@J|^3 z%)&2Ze>dJQA#yHYe2|6zn(;ylf1mL=7Jfeams|K=ZqMZm0ZI3tjQ`1^KgsyB7JetU z|KC~opBV4Q`z3^b71NEd@H)n2Ux(0(+&5VCYnXnug+I#pPYm4b-+yW0+BzNQ4+c(M zmwXC+kA=U>{NFHelmAf*7yh3bIPsUSbf@#a4#{t4rVARlIer*q;llrN11J9So$iT@ zQ}I#L<55Dmrd#y#d%|S~JvHSaZkJXWIPqD9<<=a>k!9aN=_+DGApe11COd+@8E@;p-UB<^3?C9~lpfG;re6i`$ue1E+L< z%j2Ah7QTw}Rc_(i8Lu*M;vZxF-?#94xLvx#!uxW2vfjd1F#Zb*FXi^(Q3EIWWgN1@ zz)7C_nEq+T#XhI7eZFJiQa``7@E5VSa=SX>ro58 zn(;Gv|Bs|w&G^+8{zJyUZ{hbd{zD6w?`8kX!UuBxPvLz#lI~cRZ-j-fWqg^1r!(Js zEL=Xy{ho!(H;=pXejZ8pHKrS9;U6+yW#K<#{{{)FjCWzVT!`Vre+%R1TKKb!kFs$2ruFp}{%%O;>n007!FZ#EyXR{9 zyDi*fT)r+Yat>kqPX>J|Sa7rIG+Vgl?l}7m+y_v;p?%oGXE6V74V?I2$c#>9eULna z%av*1#9uzgeW8UnGXFdSCq5$ocncT)(+r&W%a^_X%DD6+<0ufa>eR<9sd)0y$jg3YA}y z%e>Z&PWbYaGY`L!?B(Q7%b%J*CBL{Z`puNd;fjxq-G4M3o8H~G|8OKW-SK&S z^PGlnU9h>BD35#HE`ht-ac}y*<6h}Y%V&gRDSg5EnZj7+j&SrC(Ss-)2sbomfa00( zh<)`RfnZ<5w>~6KF<)GM-c@a5&Gq^gDCXgeHu1s@ErE#pO2pkA8S%8Yx)z^+IC45} zZF2QK*dAV9ja-E*zKulxMHvmZyyuU^iYY_iMPi+Il69;@J|JZ-qy))c@KCyhv{x8e zdU<(ouWuve75RYp4b1^i1i~Y>)qhOlgx&8VBlg$-NtMy+=y$EHt3UMaT!m+BZ0lU_ z<9~)*-t~`Em9>{`#YHz3i7r zo*Pd;?oHeVU$b||aa1c{&mw1gBGE&+FDU&qY@>1@v05OK6Lt@ecyYzi!iF9A#Qz8N zcOUuY+t$`_^!dlgBBTEZM>~hKSu}MHF&3#f41zcsUjHO1=J;qYWBQSk;by$RS^p z4?V16N1|WoWLGyAMt5?wYY+_qiD(IuT};xMI5+9UAeFLZlp~Y_)9%(Q94iWJrTi{G zd+s`D|LJhUHb{TL9_X3%H2UO-!}b3}+=5VG_3McEB1|wP1l8{I?z|CkBTdsoS%tAF z88FH>sNIUhijG|!t2@mJk9Z}|`{C~J>Sph!+fI44;p%Pp^@R1!=pxQ_r`4>o7=j&xo!vqA?e?bMBhA~?{%Bu zU4gVM_w7&?C!;=NrEoB3U|Ia(JAfV+RLm-%7- z{`c|tXMV$W7pCrxj}@dkV`4+kER2mzi@5s`i6&mo_*iBdqGja1P#CLAMJq8k-^)rN zS-yl}&Gn}5L}HyC{CAq`mF@s=((9Iz25_X0DrLclgNyez6@)VK-Q6Rg%eu=8D|SOL zu0$CrE&1+0NZ@5VVWOO&k-HR8DH@CE2Pv=^fq3-Uh33G!^K-Py5tvX`Ec47r^uf@2 z$hkWuM2&Q~;+bf1D5s#QGL#d6Dq};Rqp~6E>P0z^khMmuLOJ2sl8o`O>8S{+3kAas zAHg>=EgWqLN0)_$M^F)3-WwT?eid#xI%>qh=&Rn9Uq`=cXmt^~B#b0xMGubc-F*I6 z`7jxHhsGv-R{-l?i^TRHjonOi0k<_j`V}0@BGJEv{mVkL6u-Cqg|TI@5(Lyh*{73Z zenIrrNKFE@<3zd$Rg<@6PLCaN*d*jDqNU=l=utg($&1@BZC|Bc34} z`zk!5E;Kyf`)PCGh;4b^Pj}~^a3uq-sx^%`k|bhrswh@dQcMWDfO1d1z)Anzq4 z`TWA@mk~ddTj-uFgraj>uZ~rvI^*4Ag)MLSuZ|UWf%}t4Q&p&j)Rz}&ste^&3+2F| zGEz=&*zLkmiiK$MckTvhv@@kU?5c%iK_kB{V+8bi(_SPvbVM-}3w~i@RYWqB)QxSr zM=7rWQU*uIM)nU^wC20}+=JsQn!^<@pnhZ&C{rpecv)Fg!M6^9DZ41kDX*wJf7g%_ za+Q4*y4#vYhQjWyunRN&d&EWl=+^79WuY8z^=vfXktS__5G1GJLpORL-1N9=y1a*W zM&MkDqEt;qe)M2CHt<7gIU~`JsHe&Q;^5fa=J5LQ6)%jBbsmMZGm1x@ER1#DRoL>5 zzp&+PzwY0F=buD{dxm-jG$kpJxHP2`werjR&RvJf1o2htUYgQ^I){d#n=kBsHDddU zcM8!F6*e3}clc(+-Cu|{Z3_uVQmIxm68)H;x}iPt(HE_)P$Jp!k-s2+g|VuO(FjCP ze6f^EV6RM==EJmj1UctU$;u?f26NL zBFdl9Cv@cK$=24#%0c)PQJW*5r(o1t4Uf%$>-{$s#bc#HrZkfv;`G85+b{$}jgGd^ zKrHM%v?sq9BeCdb7?I_E9*%xY-B`m1;qmCPVwul@8!9{OQFU>O=h)S)%jbp3M)PCS zS|ic-Be7*K1|qS6YFs7hG<-lksy}}!?y4=Gl0S8P^sW42B&&L_g6Jm&(c@1RBY~$T zP$~`E1LNHfR1)Ea9XXK^U)MiMwKhZbl^UmB811mvNPDF_4(#PMo{eUL0)y>hqThap zVnVvH(N7Aahp%sCIr2#kx`kr}p}eB}sjbu~sXJU@5i1Y<;%eFme!RdNh-O-)P+0Me zDPES>NLvXLiR2d(GbE(>A=}#VB&A2(Rl;qONC$(lt3O{}O^VJ4qq~N-C!oTj1);$N z>R2RJxhL@c5E)16w`0`BE1QA|k40TYyZ%LC!_GjV`wzu`{D^n!&ZNpZ7N(b-iu9eE zwqp>{{Qh&QUXai2tzSosudY zg+`*SIuyLJgf84>$o|-y#FM1y*u++|CD^yL0Pf6Y^jJ~N#H3Btz4;_F;=A+FgyF=FzL5C3=_y%G_xw`eiP)XGg zukkh59Y<{wG~8?aJM~=4LgCe|-krnX9vw^hjF_Mx)5gU{{#m)B(?dmLVtp=&L@PsC z(QQaEGQOhKdx%o5IGXRi8K%yEtowN(8sOapA+_E)z5?EV`NQjJgg}DiMWTlbF(CII z+MNJ1FjUn9DIuD#P)7(;zH=Y!EQ0YyFid?wZZma`f&A!B>AxeuxbQk*ZaP$bKQTb|1Gfy1y8+Mub9FKgG0yy-l*wCQn7aN~(b!_O!HlaEM zf(62{@yMtVG2(t$7#o@*OL5-W zsa_01T0^4i+}faySHB1+;^E`;l|f>-sCwao@~R*mBO{bVeQZ>>qk~r6_v7zfPQJu- zY?O8%8x_U?n+gZNnuqLq?S`K5>m+YpA;1?azLOvH0 zI8eI*_3$ZbpWYqpOHq_6u7B-N5NKzNoW6hxObw(3HYf&)Pg;N$5IZR(#T0KeC_GtlEgLI(@lBm zw5NxA6GE0~PfvFNJS(-QmrH|*TeYXROP`T#(4Mp0U&FITdj`0-f=q8NOC9Xq1J8OL zlA}DEv?o`2HfzsC%Ckj#hPam@&Q|TYSjpC`J;U5BNN0zmRs~c4N0mdb)Spo%wO)Tt zPIf;)+2wNhCFM55Nej#XmKLBw>`VntVt0wbK`0fcLYoEc0dx?;d>N|r)jl8hJmMwS zQk5?3dP60Q$s6ss=TU6BXleR8a@uRXelkatf>5+8eg>6!OmYGj(WN5u9GO* zd$)LdR4j1ZA4u9gDi#{POy=S=vO~xHk;K)%j&@Fm+3S!CB&0*{g%YaL9M~q!xbJjH zvw@U&8u>1U!ZgE45=VO{xAhhae;qodt#_)Zut&`j$DNk&<+kyXI_3%A9u?OcKF#)q zwrs1!%kP$9!w{U_1oU#J_-b)4_TS{B^>Qb=xlWvBD`@EJmH&?B0BGnJHlRtld&>&2 zD0YV@SWxg-X0e=-u>)r(3p|0C%Y^Z!6=?~c6mkzoyTxgX|2EDpfv2KPKA`bal;Klx zL6n3iLE8tkA8T4u6U3q)3K+Re#A?F=s`CX(<3Eyih!Lp4B!Ois%t#lJOm>refrs?j z2#))dlr9O|mBn^9oP(Bws^wE+8Qy4^LfofoT0E|*q;VeyN=#+nEs~5jn`GWIZgSjh zl(rvbtVy@U`Ak7r+%wtqOf5EiakDCd8t6wXWL7s)LAW_kMB3f!R-_FHC5mDp$bQCT zfe9e<1&T?_WJZ(Jy~NG!<<3t3bdYK}ssB#ICc|siu~UdMC1cs;KmL92cM=Tavts`h zq76l&l!HLN5)Jn@qN$vfLd>=zP1CUg%bY+k6bKIJfN|`Qzg%x9l#Mo?BaH?%l z9b#4wqv9mEQ$5?HY3&NT_rGBAPRA@fm^^ZtObXr^gKwEY3eZt)?@UeY5X9q-SnW!2 z&r|(A)n&To$LsTKsI$+t=t*6vL478WPpV~~@uu%Aifo47+$JV91qnuHPjcK#<805K z*v3a4NI^W6vlrEv-gseCCGul*LWO+`?(M2jFj?UFFv$rVsJa(bA11m(sqQWVE7aXY zArkeV&P?ly?$X zw^FK)O9)OiY%UXV8$~rN_3q!UxF-c!O}+c%a&nZ2!>(#6cdw+@#i(p%BSw0;huLc_ zk|gM(NsH8UmbgO`!VvW?lR6EGTSgg^7*J}v7+;t%M0FAYI&BxBcNeV`5=hatp`i9* zh^A0^ONd7y<0WLhS>H+3icGP_Ir7E7XnFjmX`KyHU*W?TmLE8O;a9@v9`jNxwPq$91edF=iN+>z{ zQIn!v9DcV7r%K&nc;H1fZJ~1NM^hGcbIyUw3B2v9ArQ@eG_#cJRqH`I==T7?5EGv5 zqfw5LYD%RdyoykD)6{8!XAtXL;zvxWBK0GOmMliNO1aZ|AN98VXnLb>Dr)7jn|muKprB3z9C`$L zxc;u`=@h|*_mnR2*uz16Dtl84dyqF>c%!#+-NGIeqf0;V8H~WL+3u*`I_dLz7xp00 z@_PMvbZ;W^QXPLl3+02jSmPBL$=fX*S?b=22)hq$6Vd}-BVMK^#KbAxozj!&Pmki) z@!i~9ID5hgKDkP&UUvZZ^}2gB_I~%40Q?lu;D?Yi<07ZF6S)tIjI5K4XR*laJ)0S^ zw{Jr1QN2f^dIsa1(~0v)rGZC`9|#&(%0F84Avn=K*a=K_u6u z>~Nha;w*|o)5TfIP>OTHow8AUcg6{)i1SjlR0V@`ri)Yd1|bD;{v$yp&SJD5bm)aZDmxEisuvz$mjw~aSmlj#};3F z$-ud$JZhvMF6~5dO7>i5vN+A0PK@UiGY56K&O~wEABQH3Q*uml zOx!5G>Ud@+ic_+Obj3M6p3{lqlx%^D(`xtN#aCFuBypMzgK=pmiBsewX(x3c z?Ic@TO(+7-jSDoMtTp$&vh$E!`S)dYIAj(NeT%1*L$xFm(Dz+r9SP|BBO2jF0YL4O)Q9&q9ky;bl zGH++#qqry)^oEZv5yf>XG?2L8$j$`GoJ62@35i&Y&&k8{+5*_X4<#zw6 zD0QU7U0OI{>pGR<#7IhA)DTJnqF=&n{)E|=eEMvEJa0wf6jl&x(+c4w8dacp7RBwg zNSvZ^MQ0}}C3L!9hd}4zl+G3)BL1sk4Me5ZeCMbv*;*s6O z{KR;nGaFV{eiE6u`!fb^i`aRqK_>3s#K|U#^C5=(&iCAnqM*l&xZ?ge9=AxGC*pBy z+;;8qx149m9kt!!p3Au+bNwE>W%!B_uQ=b2ljUr*$FUSus+yQ_M@@zl%LnlysE}Mr z^bYzoImS#16CJ-WkbEJQlt^A&HM8aWN(tWOcGXzPrY1-3Aj9H(D{h6;#d#-Ij_9Hu zdrCVc3$Mgymx{&Nl_O#REzX_>ui}m3lN3OxlS=g94ZoLW<152;*>O0 z8zG)Vf-@6IX_q9}6z4ukoUg^9GI0tc(#@o#x)Jd(3=*d(7wwHWOQQtmJ zjzki7Q9QlrQV@NZ6OoAwSH!tbFJ-4Bj9H>MMOz)^`CU%DZ#=;waZ184hs`VO6cyM$ z&32g_$NaBN=v15r|1~SbBe{g4#VO(=$u%oGQnS*!V>Y;#pRdsP@s4g z#nsqO{tD)MQ-Z%Z4gTxn{BLJg;JVT&c-g91SKiX$FS~4 zmu_FgCp&CFsDl%CVgH*r`(knaF%C@@=N0i3XNpsJQ;NmyYMQWrAjNJ zk3sYUx3cqIeiA!zKNKgMDb8*zt>0O`VxxG@if3V_orR7X%7G9^kI8xbpagUr(JOo7 z51C-g{9f-7!%K4RoZ^J+5V{%6UWQP1JQ>K_@&5-STA$j^?z&%h?c%t&K!bmGaK*yG zwe!mt?-Q(kdH`Mf!U=T%e;9-2KOd&uCb1+$fJ$?!|cE*Uzge$fq!s&85}sA|EY z`Xz(rEvjd-1&b=G>MQ3sgX?P-4`$Ec*$e7w+aPr6OS_GVkOHSoW3yrQd=iJY7}mlG zWddGM4JSm07~))5UN^s@ysCGK>3b~3;XH2Tl`sejZ@ z{lU3`C;VlBk&OczeHWcR;;)Z8z7&7pYP#P|xA64HrHz%1alZ!rH~!PVe#I+@^pNko zjKBVsf;ZCb(MSB|kLSLYVjPn);k#p?n;_YFJUcTTTQ7bm26G#=%y@$&~huBTfK-Bg}R$rYyC1iC4it`7X<@>SPeT6^uh5r{rNuo@w*@1UFE zyS7JQux~&HQToRBpohxI9CDTVF3PC)4eSv(%Xj^0zR>9#S1ARkR0694XZyRAJl)`* zzsEPAN1$II?7wMUPU))B`uXD~6qfpLUgz(+&L0Vk^q=7ixq;IAY66%0-*p1rZy$Ee z*as%pw^pH>p+@j#&7zx1aykRfpSeg2)!I)7%M#9vk7&m8Hmy6P%_ z=AOV_Vi$NY%{QStxzc=B^hgVk)1Rhg2@E7pgl_Yu4WheJ zg-NRv6xfU~pTF)g|7pJd?#502?*+Oe^9y~W-N5BP3zYg!MP(#|uEuHcOUo8imM>he z$ibf7@8vj^)%CNh<~R!$-8h%RD(B6iBi73buPLi7zj034oJEzYzBsiDZk|(C=imhR z`Wm$XSnt=ZRRL6^oU-bphc7FBZ zx*+YdEn8T=_=Y))b@<{r<&`QyEyu!z&a`VLT@5+Q>qyu+%C(@{DXXYoTvkzCRlg9K zsH)bX->Y@1su#@*svEJcDzB}(vAoJDL#}U7SyM;9UpZk)a7<9OJi$Q=>*^QHxqRN7 zMROJ}sDQV8amD=0(GCs5Z>9&$3l6$wh*Pr|%;v`BCwgB`EJ9$qIzLX)tn_kRz^a1q}wtQ z{F0%tw6eJis^%=Jc51)3xXu|ozk1=E!SictU)rU;3 zuDVf~60sl#P|+msMbMm`pfdZ6>F}iCY zKi%02KJ%HE+UPizc!lG<#u2alIIl@vSGvA2UDvqdx~_8zyRLUfcirTU?YbGyE$&uRK|=ZB5;1P z>^N(Xm*F%T!KF5ODcy{X=f~2`RHW|zkqg$>A^p)v|4O7^&~*>~_d$+Lu5W*mv`RQq zeg~0s`c8K3(uJ@vJnI(g=X`N3o>NtxT}O2+8&#~_$(~(X>tw@9;34wd?A*5A;f^BD zu3uEO;D$M{Qz5RXtwR{cgeUhB_=tPuoZ0pBV03e`3^#pf<{=goio1-P5Q!iZ+%!h|Meq? z7*w>p92$?G$1Zz(oDrwc4%!UJt9@-k4vg2{lf10h8(vC>hsO?WA)spj{t4#=U4qm# z+)jvbJ1rX56VLy@@v*;ArGF9gmxrW3S^$1>HBfN~%ELclcKnqre+XgbCHWUV*C3GG zw*DVXl7EROJZV*rFtvqQKs+~de4CA=FV7p2#BWX#U--$Bri!*qB*s1_hzKEpxNO>` z!0a+5x_wjV?l`m5PZ9K%b6Ud@xy63&0A}-~czycO9eRmRGPK{OT@TpCh%);eO@D`U z7~0#8b~Esf;%^vFyblsgi~E#(0)B!e@c?R$nh3v zI*R}6Q7U@7dLi-Td6eU8>cstVj-NoZOX3OM3I}19nK{iIzuAf)`Iq=#!O@ZamazW9 zOi21W9sd$v^h!FlRHS08%vTTR6f1qp+o9|fv#3zVZ)WCaz}pl5cKWm&(oy;u-2S&{ zHu1ZiemuZ(5M7G%38}Z;3Ffo?@&wD{_;D#j%Nk=9bRHd!mEBS~<2 zP1g}UZG7kmpPdAsmjqvw1g`~7`IWa!@`I_>N$A%m!S79iZ%l%322Sb9Qo8)GV`~!n z_H*Lw$e0`lx1n?!-qD(ep4-awKQRep7tNdXa*JBGP*Yf5NKk77?I(XcEsd{yXa_CD zkV~yx*t2d5P%~2Gi>^!bL$%}7w#pf&d5lwyHCv0Z25yes$d2fu{(#zS;WM8=T!dq* zY#e`xKwLI{1viv7j@`o@(pL~6E_zT~E$J>ba2l%%{;+{lT@?IS61)dDtrTq14>fQz zU)OV!OY}5^68`edM8Zw_LT)+;Kg*z(vzTnWGztB712_2xxhW<-XB+%4Wn9Xa`ct~( zEGEG69eEI^Ed=(jZ(7>lB;Xl{Hh5ro(ACrH*ft&oFGVuOJx}O`ksfYgD^^hLU zG3YNfaI<`;CBgNN(Y3vq^p6_!A%p*G25!>#Ne2-wlEY(;S?5)3rBS>B68+=TA7-HZ- zP)dGBS$xjdY@7lMXV|&g;A8TiV&EqKYKxD^UuWUMf2qO8^n|BTLEz$Lia7lfjMEr^ zE{e{O@=LuWJ}jETbJRU?2DKhPI}T6hThisxO@Y97l#Y|3f9B37A|K&OFBYN9~Y%d(h>Y74Lcw4Q*b%6n$`j668xvy z>!dM1!TYd2yIc5l_RBZ5g?<6^kuzciU(b4zb$!8UJ&&%j7JnI!${D~yA7**VEc#EF zf0cy~((Ik37EbdNy5x*=;r}_){m`PP@66D($--rx^g9bb%KpbJ{29&0ksa}p?ggB$ zmn{0%*#DM=pJYBCS$H|)Ut2hRg^#WP=R?xHj{T=u_-yv~xA31bpTQQsfc?@=2!Gia zUTD#O%Y3F;_-59F{4h@VJkRvkTl8buf1`!hF#W9-ehuSKTX>ZH&s+F7tv2TX;}o5p zD&z};UfOU9_{PAg-5$aDO5-Qxi|C73o=gkBl;ig{aN_fK{L^)gffFCG!z=?Qdg(td zGH{}Qh5f?}oakvCgRXo7C;E9jzZ%E5=%G?kJI-|mJ@I*zaXF(}@_Qx?LUCPh(bF7> zt~vv!bSEquIdC z{61sg#AhIP_Ga*9@HK>2uX|y=mY?FZwxb;6(p3rvKc+Uu9f>z)ZLEMBm6u zil=aSihe?;AP|>qj;HdX{N@mdE6DUxANR1`USQzF|8eF&*uY8tT(;*C22S*HR_YZ7 zPV`qX|FD4*{b@Yk9&g}8e+TD(nt>Dj514+sffIcb^Pg+rME?oX*IBsCpBpUvW!Br> z7M@Fr!nM)D=kuWNK?5gwqWBvcj!sYi< zat4*qZ)JMDpUJIA~jK8z+t60z544j_YV&-$q!sX2CRIWFaFOz?F3m5+V4V?JPI`JhIUc?cuG;nsRkYWR8(G)(1 zajAEAv7Y5DYSF)}+umu=lPRoXd)Q#%&oM6Thwztv>NghsK%VzLYT@@Y{-lAEJ#Sai zJI)>hrzDRs{VNuJF*mNS8#wW~fbH#F11CNUnf|DO6a6gK&!+}X^nYjiG;W_HzYimf zt_%Yw`f{Emon_%Ij9*~k{U~utN3qQ(ucdmbu?x&0owD4~j zFR<`5DjZxhEqoB;wFYk1U#TCIu35kCFzCtDhB2R?S@<=K|HZ(G&-wVLOUl#c|C~Wj z{N*<=|FrO1nE#gsZpxp=qF4rpTBQqA~ZEP6TXb%%v_qQxX! zFIxCK#^1K^J&eD{xY*~5TrMeWkAi>1cs~o@%kA4W7JiiRRTfTPOrqSojjgYc2c_ zjK5&v&olm>ft&T>Qw!JHL>=dRh@1zQPag~ai1AzlH~C*-;llqK1E;Db`_?KLCo>}Z zcey`WWZ^$!e5r-ka6N6ba2b!?XW>(s&!ZO3&A#)Rg^T>3TKJP3fX^bSOXMF$3dAKp ze-Zo!#s^#U@-du=7T!tpVBx>f?48Fg{2a!+ayumHe#iI)7CwskbGNV7wJ45UOx|u? z^fM+9I}!RIJE%CgNEA|K;nJ_rce?2kdTCb&=jQ06yn_0QpZzr~aq5>o0ZgC$m76mn zXV^tV`+qx#z_^f>feSE(c4re+{9AjfzTOST>f@=yt@gUOi z(L>%t+u6ep`uKZEbX&{OzR~?|%l+$?_ztDS zpfMvpeLtR}AT~80_{Kk2X({QHDJ```Wll=xS^~-~#5?*ZnK6BIp3iNd(;!4OVdKa( zb#{a<-&TFR+bdMQU*n*HR61H@FLIrQ(;$i6N!F>gCdk38H_8lVaISog>)80{?z=a(qFg&^&=cT5pMYx>c?JE8|ugS z==0$iqEodXec)J%fYzjDtyFlZ00*?S*CIp6Y*wUct&YAwdQj|HvB`qL2kDF?Nd309 zv`5Lr)J-J;t)S&n7Ou3D$Um&*6T>(YS}1`T*}QOcNhmA7n3B@w1f9cJsVHFYB`>HV z=w-wzjP6rHM53V(2(~G4dnu7X8wvG6F{z$c#RMbRDWDe6;-HJR)Buprw${56xm4{_ zL39tjHVl0ltS@pqiw54I3Z`BM^D(p@0gL83cBwDATvC*Rn0IpX= zkx(9v^VtyE2hsxXp%uZ^FBG8l8V>M5b)KA8xGjQ0TnTDAgJ}((Ehp1(1lI<{D8zA3 zEr(Lk)D_^UEF7qUW3EIj^dm7I4mHC^r}^kJCIax$kuTg#(lK&GP#C)@ zr7cA``bjwYaER9J!?Btaa#Lrd(@DgVLob*w$D*?m5G#XXWh7#0-5{;b5$K7;l%q|I zw3$UO#p0$+2uoao!lkg{3sNpc){$@u(VyW#9*2hF`E_5VbGS0Xv8jP@^h+w0EHF?R ze<2*bCzJ!9`j`m16wVTBs;P5``682)3JDOK+BqES9HX_ULYzqH?xE9;aMlQ#b{wg8 zPbg2=(HVgB%7}S_h=grMJYo8jt%HNJ|aJ>wV=ZBkoQ(2psI za{Q)FTqO#&aibuzaIDrgrC3-W`9jvlBslhkv?(0Iv2eiVlG%==Gn1^Ln&45?Wr z*p3EAtkiaH%c~s?3ZYhxVF^I4M5YpHp*yeb(;=S?AySXmP#+%muHC#2*+*0N+t496yt`;q1vTAtP!x`T8hQeUPewO{=EL$(KRyKw zj*Dtk4P6*V(BT}!M%5I8Rr3jNCR*igjiF;8)43%R-0n3V$NoGzx+WNoJs#SuZ2BYC z)Z?KomVc||Z?^oKw4V-4;56Tan z7PTH(Se5AA!x$J)u@grkqf9mx24k6*fMqP@6r~lW5!PY%g>X}0jbfE=vf_piY;;*1 zZ5ycC2?XlgTY*&FNC2%09cUCKix6qLM~Pby{k#eNBqSZ%G`c96oJ|vzkex?|qX$`k zndne-iJ{3e#N&Y=m=2}W%}6a&k{Da4v2^UFQL9@qYbg9e&l(&phnxe?T@lR#+{Mt`Gh2Uk!>}AslrKM^ACUU8J*C$Aw*U~d8K#ds_J9{ zplC{W2xw+@iY+SQCh(z%nmxK`N*3LiE`HQv6@No0sPGMHFi7-T-4Qw?^q$a4ViH}e zCRKFIXiWq{4cAkmI$cRAKC6?%}a*E z-sfu-9RBdr)>dS<0Jhk$BdZXD_u~91Xm-J=WU|4IN4CUflVYNWMUUVZR73&&?#56i zvP&1l)Z|JVBnhcTR|jQR;pkBviPg1M5lAAMHweIz$_gXwQ3iLUDB>klP&E0k)Vf0t z^YOogtZ5DWrWT68N)4%@1yO3raixVE4CI$kLh&ge)Xd3J(a=S#s62^9z{rb{k%YWY zney_aXl;mw%rfa*8_I!CM-IpCQ3{A_gMw&qM-ecE8z04)&Cr-0d7wb{4J!?CH&LUMJ*_~cCW(hZT2ZUoIm0hFX#KE6SC}U(U zoNp8s#A9Lw>9NE_YUXi7+>40M(J;Wve$LB!#k*r9RySR;Y}i;eFRzJp^ZjBMbjTjJnWJFZ3zN#IA~{2N2PP)2&;2BRy(eoL+e^JeNvs1(6jPtLup=d_3?ylp#OuXv9)WxF?3 zkA0|$vF&5#f>>;^qELPMxOdxb=nzcwY#t@U#og`U5bY}?S#chx>V(h;Qa6Fe=EQ>< z1>MzP76=#(YzZpa<47G`8xs>gC3FfXkOu1ogFO-oEoKlURRbAuT;yGcArR#j5grfy zlfn_WL}eQ-;5~}*wKyTWo1@Nt@0O=W{R!zvNXXn)PL%`nv^a~|64(+pam;9tngC)J zeKQi%?Ub?~X(%D&(-3XG$`i6mN=6SyV^cB(s&f<@BUf}fu^MkRWod&N-(r;dpc<}v zg~xD=eam+#(SuKAsH~y7qUzEBHe;QfW9`kOa-=NBf9ID+y)}u;{3c$<$tdTbsNKXuYq^TvnIEs&68Zf9#k_;vZ;F*LZ$Rxdz zljJZ-R&tU&CK;Zbq=-qv$w_80Nl7viJ>Q7e$St6Yd$yXMX4A9D^c*lf>rBsj({s%9 z95Ov*j`2kIn4W#6he|h2w#D=uh+@^WId3yb|+?K<- zP|>Li+vjB+^0M}Lx95IEqp=Ldz{`HeyY&|c2cX-2bbhy2f~l9SID5BEgUkq3z9XmV zxTg_IB(7LfH9FGrC0gceIcpa5nIW8qEyiW1 zgK9hp2c`k&ecnU0)XWPiA+&j zDVe(U=XeR}W&ho~?LIg_rgJwCaZr=>!d{}D&Nzg+x{2!Qk!!GP6_`8E?uyZ|XBgN)V*GmoI3@iRStw zq{AZvhJ^>!gT#YCYP!jUsb42QwIl)cyhqJ9jFI@K@6tiGx~ZfQxinFF0EBiX9Nn)> z{De1gkC*+lcgF?Lw{Es|Z$q;(WLI}V&=ZZ;=z>ny@+|6FG6LNtp&W2{_PBTv;03@g z@B9Ztn-D|26>|6OKN;N?o9=JL^2HjU4b98((g2IJm`pXouh(YR!bSB+HLld(Q#+CS zd4o=0&I)eS(<{s$NM$qw3DSD~X63=$jGkE0Eh0;y)_^=E_G8t5h{wd2o+LWgt{z+P#Kc&#-QJxz}%wHs%v#G!#lyL~ZhCS@mp86+tcr_B1_IA+UlP#8fXQ z8bfu;2}bd$2#wwnQxVF1Vk)u{)XaXZ8s-uEqIQX^)G}t2AS*S9kMYo*BQVZ~A)?vA zL@JAt4@chspy$-UdXXTAh-2Hmtgq4d;UAvxkrm+J^<7C(Kwn7{h{{mdYdD0?9wcj3 zZW5zWKvOtib=&pma*W6!@#pJDn#xJu-1wsN}q=h?-Ovx+AD^LnCtozL3mS_7LbkWf&XhDeAMNy3k zqpuZ44BlT2hgrxH>-{U* zzlT-oGRQ76m> z$NVFZFeY8$axvSViAfAX*M^oL(^ezV#dUb3Bbugs{vQ@o^&+bDSiDR_HR~AIp&6gA zG+lLg;~_N$dpxuQwuBU+`jb!@YLZS9?q%qD8+PPSWRits(!I78mt{``ZWY;ec$`m) znmNN@GpNk;aMzoLrFCV+=o_e}Xsu4)qgILZE^-&Ybux)?op9T_P{h? zAYL{sWo8Qoa^9^m%~nUt#!!73054nBpBvK~zIMIaeg;YBt`jWK*zB#o3q+{QG;-pm zVflGTT*dOT8`UVvyRF+7t*s4s5dbQ`cc&jd%Y9I~D~70Zy}kv6 z$*NbW?gyb;?KrZuRT0sc%5l^J=8@m2)ftLMg{X!{P(0a1$9;>oi~k41(iyM+x5LsQ z5E7Z4u6AL_z!O=e@;$QgJJCn(>t6Qdlwt2j+Z(p~j{Fr(fHEe;rM@j|-;w@$-eE8F z%+L!x(4P)DG6GByOO^GJKYbAAgH?!?NZ`o5pVRz6O|%ww0ijMd`>UH~aN&Em-hx;N zxftZI5-cxNX&_Eo;${5{(}B0su=wE>@8uc6I7LMbkcdtVLe`!m-u%m-^aAd-XHpP4B${;PZ90CP(lGrtZ9V6H*0#7A&f~M*s)>n;&=~(5tYM8^rZ1FH{8&iQV>NTUexPi z4`aC;U>Ms}cmmULG_9JY3@8aHt>HDhQUvYiqyg{ z^}&kYxM2UrW{DlH_%@93N)A)UhCV@Y!_kqUNVHYOj6m60VHXnyTIr6}V6|KA;Mf8> zC7^zDsALH@b;e7a1iL0gjl@Q(of_d-QRk~-Lr2lesyz}{ZL?@rDZZjoV>arICMsKD zY#P6(S4q+K40k{Fe_)3OUc(_?VN`7(IRGb1D;r1*7W`tKSrmn1DtZm_ky~bbTJ$e{fYGV{Zud#{nXiEgPYQDmMj~QV*P+{!Gw%ZNf!ww7CgrURR z?m3`v*@2cn|x_6#v55gp|UVwcA2RL=c3wc3Z@QIwQ!+bX;JHX5}^F z>h!LTb-n_jR&L`lkxOf*1vH^!(hE)G(b{Pd55`W5OtsU38XfehuVdkPJ=IGzK+{7- z-d!JH)dRMliLj@^s#0yUh=i7*Bv%HA_5pyo12cmv9f7{1>SP%C~?{)DxJ^4B@V4@3vuSx z@&Liaf|{~_LEVwt1Bq-h1ZmkBl4TM;kTB8C2dx#NC2Lw9EQtQg z5QoH#i_G!xi;t?g9o7h2Tw!NZfES1%+;2wqQ>pV7d~hHLTXBT(m&;!|dn zvaFn}<(z1xN}!<|b1jmJjo9L60J{Am(^a`ORYDXhn3_7R8?x}|$atwft_s1UHA2kk z^)B(%&DWNCt7$%gY!>h&2FAJiw;F0x^&`Jwr(1B@Z|7K8g4Lv?}K{hLMJug&g=hEL9@XcJXhDo`{G8sf##w$Vufg z!!TVo!o=#4DlW?=^Ay{*g{2BW(xNxp86dLChk_|C1yfy7ycLa96pdq22EnivhWlB% zLX}G8+osf)ijZ?DWTsc#;!SzBOtmXB_j%cGd$+xgsivO(|A)rEDpXLwzmbqCX7>b0 zM>n6e)#l^FD`E092BUQiTEfC!9_R)GD{5G=$ngdyekFSFf||DydE`g*2pq$;pB@F# zw<)Q0a7vLEL_b1m1)-X!@{lpD&xRdAQW`NDA3bTQQw{hELd%ql)Lv5$xdbS-TCP=b z)V_sG*|$KW#lmA40v3nzunh-07SKb=mIc}?q|7rCny!jF-T@>8(De=7kyJR+0@5Wx zAVdgTc=3)SPi<4ksG>@60SdBIaV)`7+O;oXr@~j>ZP!9`q(K@RKw(?;Xckc`z3gwj z+XjPW81L?~!Wd=^k8e*<#vI?CfY%(xmIAdmLFNs5iyMuVRzi^m6*_7*Q{Jx#5=uJ> z$z}wpmyXFvwlc|<Pwr-BQ)m``3*ZY5b^zZb*4@4K&EiS9A#(6Pii>qhX*Vf@kp~~{I^2&t^vn!l) zYcCC+TX|`4LUoWn-y6iYd8-#Mm7jjn=joopS13l)U?}T`MHBF(u}&VoZ|9Bd~WYvU@NZx4EmK=5Zg3r$dEt5@8mx*?6!-0KC*xKgEhAxY*yah$Jbs6n!Mj% zxoQ;FR#$+=yjGf?@Fe-xdI0Ko9Cb4Zb`4MXeELg0{*+WYHwX&CZ%JPoNB4mGEw9fF zD9opQDGcHz97ud>z~c+3hl=Z8TY~`YjFFSsqz2Lg8_4Ubn2I1J=l4Jq!vm?G)0e$e zQmJ1I1mR9AU&w8y7B!?nk+JbBua zsyt!sNmCyEncLJ(%0ut9@yqU0T>6;JEbU2m7s4}Nd%CzJagFwLQ=U5Q>EY4`@s?;$ zPq!Ez{mb3dUhZsoZq*^Z-CA&K(4Mp026)zJ&j9yUkge06!7fc+)@x6W@@&$cT;oz(wP<^5>>I-Nq z%uo+3k-?tQHoOF12D$a!Rc@cU`_(-~-BZ;)P2D@GdqCY!QTJ2TJzd>9t9uu9*T44m zlsiM+yQzD3b?>3>nd*L;x}UD@J=MLJy7yN1Gt~V|b?>9@XQ}(y>fTq~gX-Q--TSNi zIqFV7xWspq)E!6P;W|&zfidcs{0^y&sO)r>Yk(Sx$1tAx(`wJOVoXs zy6aP4Mkx2C>VBEJk5u=|)%^-}AEoZiTIntNu}wd=>&FiL*r^}8^kcVv?9q>>_2U`+ zcve65>c?~X@w|TgO+WVON2`9Ud!E0yl#+IyI?F)Gq{BL}1AP`o+RoJHU`^`t8i7vT z-5j)0nM)kF29a(vmx?n*(|2leJA*{oTwn}Qt1o{9`nexN%V=gb}4JyGc8S9 zR|j^bd7m<4vLtiL4}TA5THs;aPbZ;N;-`+;q0napO$$7QjHv3lCH?aE(0=xu;G|zc z^~dSCpwdYnMb4C-RaH)U9__{s^u(!n+L_UF(IR&C>RDUJ&S1~liR{ekSv!TDIX!Eq zvU7OP#pO==XkwPvvvN)ayTht2NFPHo6!o05#7Qss5Y7?}k0UW=^{jEyuY3>!&qsPW zPWn~Eqy`C!dkIn$_evxw?lnE>0w-WG# zwWR5eYF3@hdMDj=RS9I?D9((`o5a~mQ~MNkP<#B!6Us!1rYL8Y##7x(kxq{Gq`7(U z49^_zq<2z8d70zuob-Tl>n|0hpP~{DXHG!CsVbl-v!Z&wlit}KhsY(GQ&$x-OMAMh zD7p_z@9s_pSxx5r*|VMWOyyak$xd^rI9F;kL5J=Q}g5%C6 zwD%G?6ml~=bZmvO^MN?e*^k0W#{@WT1*HV3sg`RwwzwB#7^mZGA>M|Q(opB57tqPb|5^hLu{iTbPhy#Z8(G!ggy^)`GAs?dpew#2pM{?$QCSX}+g=o8aOyaMA8I_{|;T zCe6QhFIN0Djdlm?%Ih7sJ#NyN?qJ3Bj$5mWUBjmP`!1PLusp|cm#KcRzovOoccRWY zN)eY);kYBaNKLtn3PRhda#E$y4j7Dfz+Ns>H2~(K9k3SV;CRHcH_Gy|MD@^4Rg#yf z>Y++cp&ThuDK4X;>r;x78JVaPmnZ7P6=d^$P$jhkJ&&7DNpcgg&>h}Yvam3b8|9?j zXa{nm9mvf>$&Gd(H_D*}Tc~oQtX&5qV+#`*(@vGKh4Ff?L6t@2WKM=KiQFzsGy&C# z?A9c*`#s98ZUSy34%)edoT@eWKDoFwUnF7ON{n=Lk}oEbR8Gpjb|C-Sf&9NH`PUBQ zUpbgXBL60;qCgcdCRCxFN)<20RiQzp3gu*`iTuBq$p1@;oW4x??^ElzuO@V&oPDUc zUniOR%)LQe3*)ZE^Q#%uYEqQ#oNrKkjlP+19!fahN;nTEoS*&|&d(CG9XmfKf;lF#!imaZDWc3%vAaLe(49gIFLaB2w*Zyku?YT52mgP}y$O62McVk^GaX$U&qsd}F7ny0%J^?iSze=9Rp-+t<;r=B{yre+e3WWn8%+NN5opL&^_ z)<1L5I;($XU%~o__<(g1O!9z8>cA1hVCSw^%Gg2uhJ@yaYr&-a@H%*17;ZEyx(qDp z3C}KQ@cz&O5Vc!-w|6c61Kgja?}t}_RH4!YLU%*q7KSoi$#+2#fBz6Pc_s71O_;Tm zZwr=h^pk%fP(G=3Q&W491B^Y^mpVmK>t?(?A8G^q5E^9-dk=b3y{8V^ ztKQQGHNkuPukp2Q&|S}JT49DsRBMeKsN}H4=7TbyR&o?nG`3r89rIOF^=Tux%;jsfGlq3I0_y()x|PO1 zsF#M{1;Z;1za6Fmw{zfr4m?VMK|3ITZR7>@9vSq8dha&qef8dZ(C6wsb$o+LB(drbgDnJikOC)-8R%C24N&>HT0mF6 za>;xcAPT{S?OHcBZAilEL0>MiTj38wI_+i;B(0^S#(1Gb^Fx^+FUNozz0x*xW@orE z<-5+ctQWlU)T^a-l>U!L+OcA&_M2s?Az&EBLhq@iE#G)UtzQ9Oa}Ml*XRLyS!29ZH z0R=K(F}l}{!5ZcRHwNpJ58W87fj)8+XTU<}V<$EoRfRrLacBY~RPZq)4Or4{xkLH9&U~3=>8BchQh7gbo4+77ewLK-a7gP*^;WGUs5z(N52ZKg~#r2 z>xfkKoDP4$1JY(HtG`vte+-T+sj));Wl4S43XjHHaE-#3P&kb1IH>7$BbP0Mm3LAY zS8#Ug=59*sWU{$H3~bOBkyIuQ4+qWpAZPMHj=}^F5#6(H zCd`UVm^b z|9SD-owDfkKQ(^8NdHsg4~q0pi}*86`NqIQ#HLjV^rgL|p~EYnlzR{HTA7)^n36Cse!Klhwo$oGpla3%-p zIlzD0Z7`m%wqb5dzlv{N&w(u*_#+3NeUpkZq#Zjak?G|8(6&fDbzDPC3L1rXL`r=)f}(9p0E>^_h`r}l0;+XtA~Px zYN}3L(xK!{resoUb0ee3>*XvCO@o5=5DmeNKrThvEZ}kaSDyDm@;=&d^Hrvk^G|?gnFFgaS=0pq>nc z02b{zEf7$&e5#g(KUzfGkLi(Wv;_9fe6Cqi0jd(bgbiec@fZ+?x|u?Xu=!B?aDBB9 zdyMM_^j4i$UVAKGHvw}&hs(tqyz;L8gykY?-{qBd>%(cMxU@?f%`eYf7l@H8YV3qh;yKv17}b` z58$&QAG>vZ)8JE8prvVWR0TFR4PLGS@Px8j1>gzg0tn!h-pYY%IdB^X9^k+e9QZQ_ zUgf~M9Qcd^N0o_~lbV5*$&kNi=gG|;%ehIX!tSG{Z_f25>21oZJ2Fq>-yGhOKL~UZ0c_ir;0`xtPHS4!XLEQ~Mpj*uuklL+# zOmWALrC8r6QoQm5DO}y?+W&ea{at%py7q`Jr&u*Qm4F)Olcd`b{53Wll17}^Z}w>P zs}1zjUk^zV>+L^>lsoDia$Im*@UO2WKb8XPYbcLmxX1L1B9B0E90i_;F^s(cf^TWt zM4Nvf?i>!a=q!q$4dX(4erO!-w&@-E(QeB@`J6=gmbH$2QtNt*EQI|br)6M@`ot8Q z%oH1aQ{0-E;%+m=X5SRACZ^bCrr7G6qSKJXa+@i3_@ckZL%oKY) zDX^Y?ty744!aFzm-a-HV&CEp&sJi}=TDPgECRrctpw&1v9Vsv}y3lV*RPM1M<61bWRxunPA}6d9IK{@#g+ zHv1=-mc`^&->oKm~Y?hdwUDt-ix=@ zLNd{~zp}t46ljloJ?4ivX#eLdve^+CX?)IuBHc%j2byuY=k-t~-{0qJq?aI1W@(5R+W%84|v*0Jrbx#q@!E6gRcfmQg7rttbXFJ-LFA1eP{USe9Zs_EAz6F)pah3!y<}AfBJcxA*umOr>|l+W zZ&}N8JvDj|dumzx*5L#v>r-NMkgMEMl>c)*#XXbCr;QmDn9 zCq-^z3blme6l!tjNwG39g<8UK3bnZNq_{0Hg<8UK3bnZNq+C0RSqu<@iSUh0^|9W1Hu z-suzxCI*ncTNU4Kg(C>+n8m%eK4!6-j#)gQthys=-33u_lh{2gjfSd75Mf+al5%m z7n$#`Z&If&INH*rPF-+xW0N{{@jdxxQZc?W%IC?jos<2>TqA#6SNX6~e zN9a_Bj-!P3XxNcgr!MRyn6}iVPhE6O(uZKS>QfgwiucFhsf)30Vs+}mjqA*3Qs!IM zje+lI%yy)8)(b@Z6cInwiQ_qgg^p!f_o5^`8f~|3qk=u?+*cn^Pn*Q&PRf)T_lyk3 z@2A@N72W4EGZ#^uJ@#A23}?K-5%Zq$dbY;J4?dTvg)VHwnFG)tU^IB$BL};N__UVr zVKnaWwSx~~aOLA_^pnKAhj}DnQ7`hw(Yl!A@E9D7*4x#`(J)%C$CV&Wdo8edbcaE- z<5G`yKRZzJkB20QAJOjdNGgl*0HvGxXY=7pZxanCzyzaA_LeR!A5LN@Q7Fzdx8C*0`S}&S)fF0VVoT88!ba@63nfnsl)QhP zyYj>N)lG-{4N;vAK0q>;O1NIXoHVL7qr9W{i2Qe7pD?A9T4`O3yS4B<;Mq|`{reMB z(7ITspp~@PuT9I+-cS}LwbHs+r=XRzNO5Xn3R)NI6tt2SDSn-p;-I-RN-JrR;^D*; z>(}A4v)ZSIkLxkw*|q#6F~vqRMT;i|)>4OTRdP{Fc;{H(JLuo}`i?r!N=r$yN^PZk ztU51I-q_;ILL|>t%Lg3Zq^yg`73t;@hoy+fk+-$hXy}@SfDUd>ap`!ww--jXV0IaiuJv_I;C@ z63*76z^_<9?TUhc+?2hQ1=K+?uk)Vf`u8& z3Ab@uYKFRv8`5eNSLGMmkSKPx5}V(-$@;+^*^Q3a4{pak%9-xgo2acZ7) zzv-$K&`hSX-~@W3A#v%7C+g-0C&2?p+6dQ6xPoe+B<`x4*IHYQwHB@iZ}gW;Sn=b2 z&TAy6_fm9m@%g}6css>)wi!G-$bIs3*_How4JtqA6N;coZRWZq!R_NIltz{WYR47C z-QzYbNV~^dux`7y;+uML>sId`qvK)s_!8PZZnx5chX=KJENsBd;~fb$U{dW(PbdAv z5QNW7U>S;{V(<9q8Da`Eow9+da&ht4(SX#vV9)Y8|ntD<+B&JXc zP)?zirkKKMp#R5PzyG`W9B*Q`!X!ZU=~xW^-3aEB{fz2J^dwtB&xI32=` z!ff0gBt@f={@^Hb66yRqzNWrr^%CA!$y9DC`cF;+TdJ=?xXU!q*gk@kBf zL!2sQQk7n5a+RnwUEoKjAPqLq>` zAWnMPKv71wj#YU&##I*hA%|}kSQPSZa|(JFcNugQD2r9IgOxx01lqO3;1w6!*%;7? zy1o#?>cU4>qlI=l+Q#LLwof@r>2g9kZKuQ4mx{2f8m?ACs>xy+UBzAB4&idftn{@Z z{G``N=jNcSn~aG&=Op2rETxoaV1rP}VPOvqyNmkTp;ncfgtPNcQ>Dt{I;-P_s&%Bp zSYXtJiaORqN6LWvT`-l-ctZ%MWSKGE*G*Z4oEPodj#Vlr{Orm%C;aUsQolF}hcm7I zhnrGTNeIn+6~)yH{)k>K7)J1jyMIa<8FH0sez?)+YltYkZfNggHn{(%8yYOP zq4hnXT!iI>j~fmTr6s3yvXlEHA0F!7q1zGgamYZcQ>Tf5U=2#a2n@5mEIaw6PA7ND zgJosAt{?&<6~1A-LrTa_?$ilyhaej1;>eBzv5pjNmzRQ3_#(5`1e=?oP+khA=zOe| zmogk~b{PZ#uyGLB)-@?XYqxYr*S8Cth;(>6OxvCGfqM8oIL8uouLoou0kbIUo=G9s z%3dj;Z~T#Ps+feJC}>~r49FIXGWf_;Rh%Q+X(g(tM?u8#L!Z$`q%TA;FC}&~DQP$O z$h;J-rXQtLb{<1IRV0;Cs>rdVQ$_lB3c>HTxCXJVff=F96s)kU-d9gP61|Yt&dN*) zL-h?(f!?qkJQ@iE(p3O%9f!C2LJ^Pu^|>La`Ap^eAs3w+vckPvCxhrvxMAT9*i+94 zO*&E)Rh1zjGx(!a>{iTjxLY#J4}M%vr^{0YWx54I;gN1(QFxSb7owxxC>lD(5Qem4 zj|S87Lko^r*lCff-xE4P1b#XVO9-347(-{A#O1TBlQ9Vz&q?4@5|o|L2^`QJhU|%0 z{LBg;x^jZQZ2={q?Ng2t1VqT!Sb%XB2F&4rR$Q>8HW zN4lobd&pYA!Dk#)z(M|9mLv;)2&9eE%J+dz!a@%2hE|1F$U1|Armn(3T{Cq$NI0K^ zFFCqS=b));{vqhfXI-y3x{5ex>dHR^T?<(kC$ug2ZYr7Wd1K*_73H9j?wUhLx5y{m z=D>7ck_BOFgr_XOrh8Y@d&tV?ApW8%UbYp_ZsW-Hn!)cI)}Pzd#<9DJcC*fII@{`} zUepiOwDBEoJ>4wvBQ$M%=Q&45eN(;FPrYaze21OX8(UsxUwG-~1hgcnaNf9d8;EqW z;+FdDoVeAejp40yGi1%BrOpWjydBqUqFz4z!6>z5eoP^$?`AoXkK<^=IF$d2fWLs;p+c<*1l#N%C6^Egj zW35EJLo?su=I9-o`3@K5&<9?&eI@1I$y#Bp!JmR!0|PI^i|Hl|yc}ekJOi)#9VySi z%i?UPXW;ey`KdM*=gt#2f~w@sfae5gkC(IsJ112RGsl5nLgwh=ac+Y&roX^1<2tg0t*u)W2n|E;ZYRhMBU7b>%&OuX`caZbyTFAQK zun>7{2?tGG-a*c*YZ2?Z%F%TO2TfgzJOidzSCn;eLhzV(8O!OuB)9r4N7>6yI<*ny z;C!-E)kYIV&U1{9a?r2=esV%wn@Ij|PPzpg#BU(u1^SY!Xor#!y@oGj!&e`n#o$4fgN=mH?1gO{bNzCLt5}D; z>KC#4;jUkHG;rjat~y;uOMeY#Qr|X4rpp%jxok&+pUW2cxok&+pUW2cxok(nkC!RR zWts<@A9%I5_S;_^KCF%7>?x>%Z`X3AhHZqFf1 zJ29Vh2R-SYqUqri>UKEQ%HD;HD}MV?x_ro_uaQYVBa=Qx#{OkzAd@~kp`9jG^bV^& z4&FqA9Qbfs0}pFFb}7giaF0&h z`&lK3aFA7kO&rXop+aeoQiO9+Q0hOwgCD`W3~90v{kdp*sijr2b1 z>gV8i*Vt{19N|p;^>6d&-+YMreT?-g`y;KlxP5@f_BL{a8w)I*ILJmTIiDQ7?sx`1 zgvYCwv!W{HAm;XYfdMxRXgd3o_oHXgs0^HAkNU6@=eeF)A=c2=IH3tnZG_Un3a`RfBA~(b7 zw#1A5{1N%vwjw2M95M1Ze=SEiFD1zlqk_-(%lLdx#z#}pPH-xC9tXE});2-|>Pbz1 z7`;Pl=OAb4vqGOutG8F_9o)u29*v>#9BAYS56b{=YUBu4u#)5mSE7>S2zy3JvPT+e zI9HG(JjREtat=R;RsI^N|GZ_q-n1zNshec!kZd7 z5^=|y1C1Qvnt*6Op|^SB2Al0^U{O#viz|Bjy6 zcqI_FGM(if&-uvYHU}~}g&>nN2r}6lkd@QC%g+hOe%!WJOD;~rR;$Ifb)qQ}7ET=G zfg8oV=C)d1b6YL1xviGh+*WC=<#pvKA6Jg@apfo_6txhs>oPnM+?Xm;Pifohp5ba`+dg{p?2WXE$;`yOI0Zjoi;}_+ZqH*!C_ zkspfP_*cjM>_+ZqH*!C_k^9+=+|O?0es&{26uY5bCi>Zp+|O?0es&}Gvm3de-N^my zMt*2^FU|6|duf)x-Al9l?OvMYZ}-wHf4i4v`P;oTOWJ)uO?Uh>9I{F|$TJ|U)OR<^ zawy1!;K>CNbCAbLB_|7|pe&JsvN#ILvM4AEA|EASYUc+CUZV~DDXTlr%5KchTG=^U z_`EjzX-04=Ue^3fBX}vqPXs?xja)wcw6e&${h3C{ikB@Ix4gMw@`eDiQd;(NYdGPj zv<6(Df6;r`@sa-?^3%_fsZZc$Z z%!JJNp$Ci04TnctWOA@YCdXQ2vO6M^g){U2x&2P+#C)W#H+pEoQ%=~*@_ACo@}&C> z?eV%>ijk}~-h_3x6ah*rU;{f?UA=R@;B*eUmbLcgFy|VwPM3KCk~spB#b)zYI{7W( zAm;(kY27&N=C{O~-x6#_vmVf(pZ9!J%0}_uWP&Yr4lbmR2k|&2hkxaMWVm;Y z%3h`DxoWR|;}R z;!dPs8SRsEFH+EFzkILX*7)8ZEefdHvCgM3&3>OoG&lMbrn%9lFwKoVg=uc|DNM89 z=6|ziqysyfJ=@$|d0Yu_R7q9=AZfF4@`>FKNZJcXS_?=z9+0=`dyYZz=MhE|*yTBu z10~$$SBKv71g!2_x6SVJR5S}c?|(s*e0h}}JShKSRb9b|0P7LdN0V?sBK4fg{`_QBZbCWU@~plbsTotVv|@`WN}_ zb9#qL;^5T7wJoxad4G`N;zmu5}O;O?^Gu2Jobr# z(kTi`uP7+pqM-DPg3>XUl0*LNIA$3KCzE3!-6p5NTqFghvN=?A7J^e^QiPi#p9{o& zMU-K$Bs-`4H0L;XXp}$fl>y^B5r|35sjCJXp6jjwl6@6`o_jYf)(ay#a> zhFXdk4Q`v$;EYzY&9BvLbLJJJ)og3%dPiY%dPiY%dPiY%dPjU<+$~5q1YK5Kin-f(?!-(jhO=%Z0Jtlrp=2HUHE3-4>(X>wh({4o3i}%ZVK3--IR4` zdaX(RdM)3LcF^hBX!p0*3b5A-u-7^ydq<7BZ=#>Q$mMK^en)<2erWd9`%^aVr60o9 zXwMD;GHEBWARD>E_$^Dh-#kT~i8~p`Um+f{ih0z2g0=@xH2tiqIp0yd9Zl=Zr8%4x ztK_UW{<)J?)GsSk$64{ZidD$0I?#l90=>ft3@I{=a^u`#X~wzU>+F9-Im0f_YR&Tr zeKRRykAbH+$hy;UX~ai*vL^LYKh_3^iu&@w`Tnqgg!_G+L)i~FCKPj!-416q6O?^% z#4sm0o6A)Jws4S3aOk#3k8!Eqaq4N=Ml$nZ$J}LX0#}ib>-UlZx58sGua8NDD-yJD z9_$CGFb*Dcv*$JCHBN;syOn}3yNG-LWaq0-%cfDV+C_B~T(m?j>H`OT4;08^f$S z+4X>T3zmiYxI*5|O%_Ubg}fVMEHuUy@@|Z=&}3K0yZy*Q?C@WDtSPA0=ekCC*UfCi z>8_A>Tb+fT>8taAZA)+UdA2P%6_*d1=B@^rPC;4m3d*WiFsO`&AI+8(aPXO98J)t% zfcfJ)U;7KB6(ALqol&G6enr~RAeDjjJNynCdp9_x+hCJYIt`H24@f!;khK3>4njrIEE>>S!rcjW>w-QqtIWmI|sVj{XzKh{3DYW zd!HQy8SHkNhS3go`?Z7JPCMXiRdSifWmI7wxV7o+P%qF;9?GCaanP{Mn~q0ib_ROL zZ}r^bw|Z`Al(xzSimQ0`(I~f(ZT1<{=CPfI+cx{TZL^=-HV5_A0fZbnsw&2h}=PgL*J#W$vH|lG5qEkI>#c;1ST92S9Hg z0KItt{IPPt^Owv4kkk)IYWI0?llDXPdv*IY60dGgyOFw~-u5&|?FvfU6_hzB_+!2S zp}l^2?Dgxpd;NN@_p#BZ9Q*w8*yoqWKEFJ?Pn|w_9Q4cMpkE#b{qpcWwEE=H?AKPC z{n~1?Ut9G)^K$h*K-~=L!@F!?p?IpUPB_KI!PO4RKZL5@<>#VZelFVO=OXXX8R;T; zL}X)mzyKthDiq=pS`AslB zW!%{KkY~tk-RRfEHu^QOjXydZcD7nKz@H!log0$+v~gy;DbvKeO#el9;mc&?-P6dC%5fUv zPg5;uB^>N)sE6M`qu2z;jwKv44W92~@De|R=ldA!nI+j^_7?mG z8V9+UPz{^b{7~qjANXE&%(fK)*@EVslcr5feny_;gOJoM!NNf zP@H8x>Ab&;;G(zftcs()Fg(=H_wmF$F-Z;+bmGd|_H(h4}zdgi3HU(7eZsZ7e ze;6t`Xk@wD(*RO`ewN&aeo%kcj)Lft+_lL2G(dvEwvcIUT_)ZaMMpPBb> z<2;I}j)Ns-9%p(A7WFCEC2KhcqZF6;mEsal*JIbsqMYFIv80V7>@O()W{>lfq)*11 z{W9L{$(ZN1%bhx#$HDd^v>)Ipk89#KXResXL9QI1t$-Uc$dNQ^!su>M@GkrlE^tAS zHmv2y(*!||gGSM}HF7Dr0wM9XMvfRh-{2|b4_IR4u)(hay~;LI8UErFtBixMI4H`& z*BrErgYOaQYSB-zv~F1kM>zVl@r@6iG*J$IHPUg|wnmP8M-VnSIcOBeb(rBB?~kDI zoYse`TYu)#Jc;8M5yu@w&UpPOZ4ELG!=E})7u3q#jU34(h=Q`F{I(?77%m1HbLX^k zCUMnsNto*_-B-W#aMw3IayO+8ae6L1PT(BA#qstM4zfRNYi&~-N4U(;BV>_%I?2T} zesPKM3u9bbPL51k`X*V!6_1@uTtguqR*Zhvz&$;lK zS>AT#&(F%}bChT3oi~PgFIwiuhs*ekGN0Ujv%8jjm)?{AAK7yRogC^y`SzjrW9WSt zz4HO2MMY4evsaIBlE}VQPv-CwJxLW z`ylnhW4r043J>7Dls?xr|z3*DG1Q|fpsjZ8O;)bTs%ojW3T4epTKop=zDTJg6l z!`8+S9I*<0liUSfLlB1ZR#s?u@$%ud<GDyoVr>q}zR@cP>LaEc6HR#8`*KrADx zXiZJ+@Z$QC=(rKX>#D0O&#I^!J}zTK#^}+U-hwGMUKcN_t*%@VD~eYytFNsq!W2c( zlI6=Y)Q8WtbtNUS(u{H}NZsn1SS$;qDx?rx0oVzDicODa5VAkELXlCqH{KdqePNEhI&v<)WZZe$iIXM{ zop{Q`=OQD=ogT^CZ}(WZKRh7iyi>~}m%p;8?9}rjdFR!Z*_{?f%G)B@k<4jVo;Yd2 z^@}lai+X9WSC>Ly{KV6y&7L(B{<||WC$i|XvdE*6S#9>`>muDFBd1=MRTvq#I6V3& zd)4^+Bl{tTp5e4!6$`G96kmUOWa23Y7TdqKBU|ilTkM+gcDIG_y5k~y)wy=JQhQY$ z1pcwquF0^wy{vA3ev=))&Q4xvcaB^aIS?M(1OAUpYz`maBYbL)aAA+|{2t*sJtH?o z{u)^XWugBEriAJL9+CZ#Huc{t5%|yU5_!2W@)+FxO>X2jk)t9r_wU~ynfc|^ND2tH zMpE`4h|K&Va!)wFXLx4M$Rm+Q!u@-Pr=>*BzaTt3CA^@wz2@_)?57&TD?^b*cBe>c zcy^C)Ue9p)lUSv_^{N`EBv(7sk{s2XM!pSpdmhH4t&#koAygU#7RW6R4 zS{92;u`kqoUGV`++97vuACjEQt@VBcAeic5cN94lDdH-sSJQums{(ERgdnexMSJoQ26RO@G z8X1qivo8yVi!&L-uRo&%q7Kb=}G@vu|D-8E>zGf3Jt3ny+g_ zk-u9z>>HXQ8Ii^IHx{%M`#gw1B747muDxyDj>sc+;dS?hXku&=VW zL|T?Y7p;Px8iBx!i}EA)+MUDwL(n%*=mGy9XvU1^^axMu5zgrm&gvCe6S*1M_q*!9 z{m`QJLl4>M^vO2vlN%yC&&q{SM|Vl?lXpbwBNHOSBbo4?3Ei@=XLx!~tl=vnSA{34 zZV7(}amkV4Vi*s4hDW9R+FpI`)sdPLCq?!a*}cMJLgDOQkv$t0op;Wn$gs$SMd#&3 zQf}WLc`cll5{~wS7Lw5;JiJ%r%J2~EG@9xC-UD93JlYan)(zDLo=PRp*(ZdsKJbqXz39RjPVa z+gH)-=k17dzuR zs-(Jb2bhqTMQdX)uf~e%)a)@E=85WJD^@hG#L8JPc~*8oQ9)sL;R1MDkdu>JP+&z% zqIFRvn;VZ;$7epon>r!an0%}vC?RLr8Yt-&Z}Axt*j`St?yREVbZLquEI$+7LO}!pr9yL zRpQz*FJ9po6s@SNkH_45)1&c{m9X3>sDssp%D}ysT~ZQ{)z;1}&8e=ct&3LGS*wbw z;>%ruIn^^`@v2y5-f~!{Os$SjiLIz8Hk4~8%&o7RTbduMt*(z3$7&%p&f+=XmDNQn zMpCq(q`FSWN}^^lIob4RRY_&cTE4od6cbvs0I904tX`??(ODO)2B*iXt18ySlq*)o zE9zp(8TD1Q6=hYilGF+)#<5{p25$z^# zPGN5Toa|Xex%v5X^If0Li-G21a8xbn&#j6ss|5X}tBPvk)iqGlu(YzuK|obTNvyIi zT4Yp8UR6b11vHU0QEV8xN~>y1XyvAtLVEdzE4QN3N^5z2(FlCW#Fvp!$(WB(pgGlb z^J4Mk6?I%pC=S$mQE62zik_*KfL5_ys->1zR!8fsI`EmLmuE%gy7DkiFtx>}$$;_YO7t!q}=hY`@E**w{-eqh;8jNbi*Tn#u}D7{ksM zEso8JE{|DtjFv!kn~#A@owb5|9|VkO|@qN3{3QtI={R^6^@7gR-8 zK%HVuSVc9}wbkZ4vKIkc>6=n5tZ9Vqw+9<4-b$8^|s&34X3s|P=uoP=k+BOdo zRaBMbRKvQ}v5(roay=Y5C8yly)veMc9br{gSCyrzmxPuN-53U+y68x2d304#MHMu9 zYjsIQX({&WmsI%Ij*2&bHQ2qbrLpUmk^)=vXkly0(s+h3aqlv9gL< zFcNBO89FdizrjcfRaYB>rdM2MGbc13Nis%f+BBr40B zb4vECyeUQSx**qr$Gch!M@&_9MP*olpbp2?f?}x2kvx=|E-#BmOQ6Hl@xvSTeybKn zN^I_+5PXIMbWvTAa;R$_H?Cr6IMp@OXwVl>E$DA-Z_)$!#;@eywFd2umZ zR@cCM2=gTjKJoHu>`4_>x(+Mg&uTyix6oAGG2$^jozQfr=9j{1W1gWBf*stJU_euL z*5ypooxzIL)K)-Ou*yah)mnOZDk`b~mn*L&PB;atmoKZXRMzV}vHcZR;Yj z2TjCTHyG}9<_j36d9IkFszP?Ugqp`%)6=7pbt{i*c zX3fpvE>&C}D?TfS$A1e(Ce<~ZjtbsX^J7&#F5P0#y7X$n7}{x;&8u1fjn_#?u0JJO zSr2p>jF@%M<>YOr9jcmy4q1!^L09TIUyZ1GkcHtEM=R?r*lLLyqcbi?FoBvToLW=% zkqxkhgwc*00Q49bDPbSOH3sT^QLHWxH%gYrs(6l8D&ST=u2Gb**>thhX%$3cu&S(w zA-Sp^X4N7!EJL~F%uOcI{OX!8yJA(WM0+E5RjjyPElEqU8?VyCrtV8KE1){7Vr7tz zn-**=;N<{No+M!=t;4YZ=P5lYVbSBUviizsoEA6AbIV}iT3@3FA6Y>&-Q_7v?KRPo zO#KFxgt1ZjZM1$HW0l5ZXOFNZ=N4vLh2=P;)xx@GGOo@c!HPPZ{dtmca>~ugSx{A6 z9mj>ZS{~HF=ngZH1xuur)^wi&rbRgLtKr_sd$#XnNs9&=>hwx=q;+-?Oo^`9)WGOzyZ}BHh|!{U zlwQri;Dlq8n*t__xmDTF5Ld)tgurJ>wa`-!jx>tuj+*dnP*h!6!owR*NmF6fYDFt+ z%A=MV1932%7O&95SwX#eMs@R?TNA^lo~p8f)v!8WZb3fADxS)Kqu0D@7#`pm1D@33 z0}RVCfmTsegSc);d^B@h38N=0J1UARV6vN{K1Fb?ptZx)7<3L=kQCM8jtcJOs8K<$ zd2p3uD1^zcqJ+{;hDi#STDV{kO@7YYLfG$_H*ao!A*|J zkH_F2=#<)Kp5IDJ5@&t$*+MTyVQ`S^;s`B;r-H0n9t#~~Z7HTs2mDotqB#rQ0$W?FP7b|mcPd9_h|a)gzSGoMf;&RGF+ zXC+RLj!WRF2fU7ZGSsqNZ^8UGJ6g3Ghjw$(r6*68b{5P`c5ZU&rAd>M(=TZ^C3(b!?X#1|o!4Q0a!q~ms_f+E(6r?B@UJB_J9#6#ZwfUg z*Mt`&ubK?E!rPO_WhalwPEMbkoC>Ny<>X}e*uN6gucy2w{&-&Vl9xu4%V#9lOiNxh z*T``WmcQ~`p5HwvK1$5Sx6 zi?L*D{c?)H2Z2jbKU{Ad3ok$H!-C{}Np@-^`C!ttZ zho>QUuZ!#Vat_5~mC+3KWS5~nG09j4s}lt>>Z>X%&Wcs8wldZsgR|^-XwsS7u{B@~Z zld>p19n*1caC$lk=WtHX?{g_Vc5(B{qVg{_A%wa7{C+wx)FsnzqV%b7&%C()_`Mo# zvMr|EHcCG$-=tpIGvPMdVoJ`NOIjCeiuK(Q{>l2sZwdS5-}w@q?s8GFQufyY>3@H@ zPJgNBC^G$T1Jb8|q0`fGEFJNY-2lwD{I}_xI6amhf8$h^f3uHHK-NOaUp8!M|HDD) z526;!x)IKQnU_reAl&q`znSdkev0qdW~BYk07IQzP6(>&oB!(ft9Zdkob%`R7lHew zZ|$KaWV_~c{Qf$nC-u(Tjg;O26yS8s_dx)ea|VlUY^L-r!Z}=iPX7@EgowJek^Hxc z1kryz;UA}GzvA&s(Zp4XaYt%Pzg98#McVIYJ4$a&)sj1?{*MHPbtLo0KIxZ#bGlA{ ztGFrihvdE&$|YH!Q7cFkv6aUY8OC6i_d><}JdE;i`c7PUY%^@=SI;8Aa2P|rhYFyc z8-V?acq(PB*7v~hvOTjRbJ3|Z26^@_0f(cPy_aou@~QvjxkG&Pqvb{^Q$*s;w&Q^~0mYWsN;aT$bz^;yAXL*De+5U=1}PgmJvEtPY;> zW;gNC>fL?$2k~((QlWR$YsBNB4@rKYkjHTX^UYH4?#q`d*uk3OA{F|U_;|s?s{M6v zo|XA_L~(EXNhWz7FIZj=PaT}+app&nd@2KaPbDsMNF$yuUe7Z0Q`^@AU`dD&kNv719&WeuM6Op1@N{2{#XEiDS&?wz>{En z_w(m~06r>!PYmF51NedfetG~e3E&mLv3~j6zG_bZ>i1Ux^1lh-*9CCnGY_8=_qYdX zZr9?+N#=HKMn!ErnlTn0UE$+KIHy&UF-~nTnrYNAGSaOM@~bqXj5L`eJc+!Sj4>0y z1N*F;k@zwSU&i6fiTIMKUPq3=@JKDC#74qXJ=|5VBgd$_@MI4%Dt)Gs$y91GRVpwE z<<;F2R8S{U;gL#kl+HwDI7-_xN|l2Sa{Bqs=Of2N2lI#K>eGHcw?QA_@VO6EtUvEl z+3C;nL`4OBzL}UAIT^I5>=KVl=I~hq_mex!*sK`*P>ha=Ij1J&{ylu&op1;s@f3~D zIq*P{{vgq8Oo@lORQdJCo$eu!gzWJAp!XygWosS_p-gaMS(?^oWJ}aV^XCR2zJP z!EX-WuLtmBY2y*~n|f9S@Ld7?XnGWpdd>>q^#(V~)e^uTGPr5yn*p331*JU$B2ZAc zB%c<*&kEp|2k`p>_)7u&-vRtEdSsP$rUmdR0lYYXpBuoh58x?>xqdV2YqY`5{xv0l z7Y6W>0KPhaUl+h158$r`@DBs{*8%)6+A_d;G0S^Q03RN}XBylb$F2?FdjohodZ0&p zOg;Sr_>=&CvB4*TE*@uYGPtRKi@_%u^4krLU-f1^uNyqe;4jmbiuC8+0KSR-I33Do z8+tYy+|2hKgPZegJKC~AJ?1z+$>65^Ukq-_r*s1aaG@S^UKnlg$#9STaAp9H8{C{1 zzBD-QL9m`2`d&HaYw9mFIPN*J{OJbAuW~d0jluD=B<9x!@Y@XD*O334!A(0~Gq_o< zFAQ$hm)#wd!iD8BCuY;QtQb-v;oG*h%2RdKnA<*#2Gtd_Vw)?>MVV>X{J0XBgZZ=T8mbB?0_=gPY^b zB?dRglUodqO^M5Ur@_r}=6?)+tRerh!7<2s-ZOZAgC7XsAu))oIUo^NWAHu@Gh2@?e%Kk{LOV_QNd`CN7a80v?{x+r4Z66zj~Lw4vlN{O7v^iq zw*>HQ0sK9KoA!6c$r3JEu5^Q&_RKeUI;3O!FEY5P|8|3$dOkGxC_~RcoTTBB<$ciL zW;;nvRpLti1PHP{gAG2?;KL1W_TLo-H~oBt!N(YS+6-=v|Jw|1mbVE9LAYeO?h4?~ z8{E|YW&r;%fPWRhllr^%nEH=5_zVzXe{M7QWP^Vczz-VS^mAVvOyLT{KOBF)q5V2O z7sZ_OEi&Yf19|4v20z~5=Na6TzuDkJ4Ec`@ex$)C;~)+f+GDoo?FOF=_t>8A3~u&| zE;xw8g?eVfKb9YA@Sz5uX>fBMSzvHe&zT0tAnUI)xH--@8{Cw?%;2Vco54-_9R@e; z`G>(x`Hnbg!6p6B+u*a{KHJ})I6n4w#DsJW7MzAPE5pz;8)R9}ID^kI_!NVi`Q{1z zobMdLIo|?9kD2chgPZwQ7~J$*dz`%CLjR11e{BC4gHJGcWdOf1fWH;Mhht-f3-dMg zuL$6O3gG_<;5K$bxMaEd1n|iQH|<##z|RffHw5sf0{Ghj+{VF$>^JQh7{Dh7@Ff9! zRRF&-fZrRy{~W+S4&a@J_?PP}gPZN}5`&xb?==B@O8|c)fbR<6Zw2u7IB3F!?aH*L ze*ixzfX@lw(Ewf-z%L2lZ2|mYgPZG$uESKZRJlxkY5-pwz^@MA_XO~N8+;;|z~fIO z!{4561~;EWk2APAk7OG>%g}%Ea94k}!LKy9spnaPryBAv3eM|__YH2&<3q90z=iDx zi_Ya8Z*Vi;1p$1C!Oi*lVuPFcUTJVsewV?`_3%puH|_bx;71$w56FZFTv#r19Xs9N zrahk++?4N&i)Of_p0Nfu>nm#Td616%xys=44SuP?^9}y6!EtHEdUhJzTu;6bz~3@B zHa*tU51k7amdo@59GFm-!s0fNu=oHwW;0 z1Nh?sd{+Q}HGsbt!1o#4oJYP6;7Q|Db*Xk`$`3U-E_JzHmKuDf!Rrh@%iybt9|o)= z{Nrn#;MnK!I?vEE8{(Yrr3Rm4@C^nx^Sw^!N8NbcC^+Z)2SbmU@BIch^KGxySy-+v z6vnsC6h_`1{=FH};Bdh|N!FmJ;GcKZ;7H<_9Q!<9w5(S7vQE55)0g6y@2eej3S4vI z#rC{UhUaPQ@VB~YfO8FAEMGyHoT{lh& z2-Vj|g1<$2z7qWJR4+;7Z?3N=xxEQqK>q0`_#a8HK9B9zdnGD_Yn+h3rIY5lg6Ae_ zo-g~)-$8z^6#O)5pZq-uF4wapf1!}yPxW%8;4hN=CczJ<4tj^+b=rLEA;Iq? z{dm3_FSh5tb~^r&;4hMY-V*#(;-3n>hxj*wzfT;$vyB($`zi6Bf@e`X!E@TC{0yr1 z5rS7}G3x}uPo{d$5qu@J&$)s>OYQJ9!TV5qxI^$WNdNtUZzK691s_B8xKr>wRF8iV z{5cvwUlY79*||sXVrp;i3BH{AHJ{UC|KCG$p9}djDBpvEKSh52Uhpr;empmh7waF- z?LhEjX};(t_+au|FTs0~pZf~_B~8Np1#cz&>4NX2de0Di8ug3Og7+r7Yo@Cs^gn*?7;?d=x9A0<6o1iym( zbFbj{Q+wDdco!$*MhIp?y*AD9=Sh0O6|Fe;J1>WGXprEV`n`hNN&22 z|CsDNU2r-kVJ#Cpo%~rY_{C&rmEh;lcvvSmpIcoccn|6q+%MVw(WK`>AzwiCc!}U0 z$j&PSpGxD^Zv}6q{(F<)6Um+j1TQ83KO{JR58*k%S5m)wS@2J(T>PBD<^3I%D^B;B zFQakbDZ#I%a{X2C1=Oxy7rco00l`zKJ^xp7@=qsfziiJd)Q*o3{0-s*1b>bDo=!~Qvm z`WJr>g835aFC&C}1-085fVb}xuhpuaJKU# z!4FWqOcgwv+UI=1lgU577CcJrrcLnW)L;G~INSe$;B5Z^!Lzvk3eM#^jOu~?wv)=m z&u7fPBflRnPo)G+L^7Ees-%0h( z-%r8Y=z@hL_pXqCi|XYw!KYGuEVVC;q8_fV$%1ozEff58^8XsaxjkPfc$nm`6TE`z zo!9$pPgfcbpA_<+(D?8d!MT246Z{2gPwxrd3(Abw=Ys#0{CpVoOSb2J8n=51{vFxL z-=ksq6R93g6!MP}&lQ~8`4YhgQu{ee@a@#j8wBTe%j-C{huiIALY~|0e!;mtd@ne+ zhZM4l^|Sr`1m}90Ab3AohvW&)_7@7y_Fp16+t1$*VtW=-y}l*nxm+I$&gJ?_a2}V! z)J|DHm$!@HT;AS-^LRBta2~Ht6#Q;#=XruV{t?{qkKpW|8w4Lf{qZfq9sdaK_(yOq zZ5#K8Kv!v%K!FivQzemRY{EW)=s*wMZ_&b90{-90yvmXB5Z&$&$ zkUf0_uc7fOUGSHP&k%eP#ZM7@9vT2w1##TQA=4E<-;k%ORs2%HXDMn~R|&q6IQtv3 zLp|>h-z?-u&_2>Vf|nD2NO1oAqZ_qT*1wzN`wRXp@gaiqe)?#^d0!@%I3`E`aCu7v zzlX~8rQo}W_oDvIdcGu{CHQeWK1uKlnwO^wUP^qv;P(<=Eco}tw+UWL?SH4>R}<&Y&Dj0| zny+6M@+~wj{hK)Z=P2qN$+U08{0ic?2!1Q^{}Fs2@q>c5qjgRn`rw@PcO^bd@KoZ{ ziF3JP7=UZO;P((;EckcCiv{QXt4hJIrg^+x@P83sC-`t$uWT2*jQBqV=kGIrNSyt! zl*-$Q7K+TTAwE^`r-;`H{w4971n1B7UKIQkns>ey{Cwh(BecKKjo8($pm9HiINQ02 z3=Jsiglh!T&-1;kO3I zET_}Be4W8j53dt$GdRjWM&l!YACUdV>!Lpi`Qs_y#|)18+t9Ia?KC**Uq7i#-*`>ccF23s^HV8Kb|7^Hza?r;JiQf zsKL>mZqz=WG&tJRNd4tS!Bc78eAD2l=K|96w!u-)PMW7aF*wTiB7c1=IRCx*KJ@)c z_Slk=}>vw{WI9BsEgQK2%@CB~h z1?PR92Mvz;FCje-8yr>ee$mqgNBQ*r@B-I!21j}ReCTC^qx>r*|GL3Z{@2tm-ZQwV z|09E=JnvtBX>gRU9RM$IePeKx=Y6pDRG-}5h745VRwsj_JnyIVGC1o07s>ZAILdEC zg>VfrILiM%O<`+@!BIY!*Q*9Mg!BM``V1=#I z4345tl6Q>tchW{8AdX zuQE8wPZ)yImUWZhi-&1`o54{}l={*A21h;5ll-3yj`BTeeZJk`DE~kPyuh{7;3&@r zQF)x;b`=_qH{o0=$@98vJK6KDp@&?fUV!TBB}-c9iG5lX#v zq~Mnl?=SeP#D@s}J@L_k^Y=F<2>vN8Af^h=-h&6fV|`_g(Kr7n_yXeF1pj)hmftCO$~etm5uDG3 z?-88ObAKRs$%$Ie=Yr>*r1>|3-*B?#NpxVF+tsheYu-!n$_bhuBlvdW>4NvgFKNM* zDR>?6lLTk^oB%#c@Li;5f#3rt>3q)+ypedB;5&#{8XW7rMys(_8yxHXW0F5t@KdvN zf{O)zk~se!0{U6CD|(K&R>;3g^0x?{kqrsqx=ZjQCTqS`@TJ6`5d1viPZQ_yY8pKs zeIfXZUA6wSe)^r|kD$&mSMYJfR|{T6{AR)bLi_{4$EiUVzFTxmV!pM+Ckp-=jkiUD z^ZtIb;G?iI;o2qmxx{;>s@w1jzIy-dCgQn*?{-&bKjpG*7;A^#8J-wS?Hq1MxZ zo)=lqb;P>~9$uj3j}*L=cz?m4CC<;UtUu!vt!K25FCaca@Lv<3D)=YF=L(*8s@A_y z@H>bvF}UgfYJ;QyJ1*3ERtY|g_&I{N62D0B8H==@jl{XVJw)x{Pl9(()$*YZpYPQiH` z+bwuES}$6(55eUfO8j`iFC(5W_+7*s1^<-zErO?0dwX8+?bIH=7W}+pwO@wP{s-H6 zAMrB;e~I{6g1<$4mEfNezd-PBh+iRigyx$z!4IeQw?*(p#Q!AtF5)``&u*v7%fD9> zf++U8TH@W0*Y}yd>+|We-3?_c2;4_Ky@3*j?YT_>m`74Qcr2R0K z|0D5kpsO^4H@Z*RlWh9nsI`I<)FC$(h_=Uu86#Qo5y9GbuFrD9L zf{!EKZMd+9_;A5_KY5PeA^1Ou4;q=W`ox2%b&y9|%5&xIH$}&!-VTTJUn>CkWn|_KTJao=Ln_ z@OOycEqDg4H(nB)_Y1n6kZ9+2k{>PjzlkptoS$c!1;3G=2OboB2l3Yg??UU^_XQtG z{9A+Lltar^t0V2-W51)RNEPTPgX8*VJn4ZyR;4cPr>7F1YH(A3f#5o|Wi2r{>W`8h z{@obXznu6*B#+ZXclbBb)|xICoWKA7JE5Qdj@iS8d@`uGlb*|V3O;zT*8hs&cM*SA z@TI3|`Tc@#Bp#yAH_(2}_bcM<4UXli>7n)X6r9ibq#7Lc3_o4Vrx_ge@Hw9m21ofa zk{@Gml;?9kSq4Y>Hj>XZILh-mpF+X;oKLafe9k8>IG^)5M{qvp(;_%cf7b1S^Esaf z4UYD|LiX=8INHzWd|om*$`4oqFL1qTaFpkBKK~J%&-t`JNhNcBH-OLi9By#be*x(~ zM({_8X9zyx46S#9;4Q>w3BHf`X@buy(t6Gm{959d8XU{Z=YOshoX`I}A~>J(*(G=l zJqM+qtn=so%jbVa865rd9oaL^;Ajt@|Cug0pZ_T^IO>Tng%`LM8XWcT`JZBgqx{QD z;RUX^;Nzm2w+em_@lAqvS*GRh7W``B4;UQn*@S~1Tz@e*+Vc*{|IOgaej4}wVQ`ec zuNbw!xetS*{Crvmerj-(&niJ_%i3>nl>dg-z26%g<(o;q^LR|9e;)(+HsXgH9QE+` zDUKDK&mjyoIO-W0gBQ3m4c-mncawikGC1mAOV6P*1n-3t2V9E<|0qfGGX=k)z2<8K z??QgNz~E@-nPlfh#Ce|L=Z$*>f1aKz-Vl5+4P@;n==@pFEaDl0-$A@Y@Xv@}CV1*d zt#_;7ClP;3@EGyV6BF%eB%UsKEAc6U_owxGrQp+uZxFna_#=WpO#D5;UnAaaQlg#z zA%2qJB{UyT7yPinI{h-iPbA(Vcn$F<1;3T}0m0uTJ}4{E&Qa+)zEJQr#4i&3QR0sX z{vGj81@Ca2rLIV}e&=>|4DtSgPbHo&cq#Go1;3E^gM!~ee6Qfo5I=fyqCM{spC@>S zXW|FL=XPt$e?=)?WLZIdc-RU;oeZpXW(t?Y-B1uf6u=TqaQew5jh5 z)bBI($5f`vjh-syNrCzsO#Qb4^(#&N9fA5*Q~#?#{YF#Y9;k0K^=}30cbfXVs&qZy zF!d(}>iY~9yz>I}Cz<-I1NEa!{qjKlBvZdWP=A%Fe>70Pz|_APs9$O7bI(iH{}-k{ z9;pALsUH`pKir)6%?Z?BV(RY-)Zc3A9}3j}#MHkQsK3wDN6t^z^CMF~I8fhjsNmHG z>I+T%%s_pWssC-D{w`DhPM|*5oW~uPNY|&q)K3c3f6vq}3e@j4_3Hxlt>)ZhccA`J zQ{Ne=?`!t|1FO^ZIoZ@#1?pFt`qn`GJ*NJ#Kz-co+usY+k2dwkj!l<)rKztC)Gssj zw*~6goBBK``veQVPFw$s!X z1?pck^(O}E-!}E92kJjI^)-R|FHQZ7Kz+olD;fj!-!=6=^wsx<%EyS>&d&q&rrO!y ztItB^D#PdDK>fX@ewVM_&tLBoalwP?^|RlHPy9Cfa31mzw&cCrG>M|HPqE9v!Hk zZ|Wxp>SrAy_`e9$7aIPr1nMs)$KsQJfqbjn!AAeeK>gpNf;Tr%ug_`zG*BNi{{KUu zesr$zc_vW*pQc|r1NA>LdLCMvu4n(lrCb)Mzum|k8>nAr>SqV)pE3IUCQ!e@$bB$S ze}Tb25U8JKf^_)AbUpPwAlC%yPc-`fAW%O(D)bKp>Mu0$))A;b+{pbfP_NGmpEN05 zpVLg7oE@lt+4#qZ;q#wN^bm7lC?xzID23 zS3de2rM|CH>-Ajpw9;Y2W4=%6@Dan#7*;myjIvm)wB*b)z5gX=oB_%zYTiriREjZ`WiNE)m6 zDJ4(~s8movL_(>J<LXJSpFrLa8FJSLK8$ywRBAmZHk_nK+NHPfc% z0Chsk@c$t*$Ge|YZ+#=XCiQ+I`@u$3bW&|i_S~tcn$R-%zo=-M#=GxSZ+$(xVriZ$ zSgE(tiM|CQt*TgB5K8#bb$7T|7ULS`;ryq-x@2%~iFP&DX_KJ5rVL;^wMYW%M_l(ce@}MRD-CEtNwL z-1X^~fFp@%I})jFiLI~pO0l*!otMAEhYT z^g4pMnMCx9FKa80-o2x0*}Ks@KS8jSH|>u$y#t`Sr7~VSKJ`kXxgnmYD&1eYzpC^A zNhq&My|eeo8Qf2mBNoMrqj&xlf||z{SEk+r(@15@;Jm64?<|Q_raG!pZ=Ad@m7Qp= zDydldsM}l@A6%2_7f+eK4%@03?l{H{rj8<|KwPZ6HT zu1Y3S7ta1Yv%mG><@g5o)&z8f-^c7Epa?*Fb#p}m-(Bn^XkG)iM86k9Cswqae_(v- zPcT2xGVqZIIEdZVAT2N5Kel;f35jLG>Jcy9_!jglh9_Do59O}fw(PO!o%e%`y-93R zgX6}d2P#s3C4a(ApOm)ky*7GWxs!UlBK6eC+frk4NV)u))Pd^kMR5f7ym$nGoH`s% z5qnZ&5~M_gj7^Q1$r_B9MV+yk>}<}7C&c9HrH@9M6UCJyK8W7=2AV2U`_600DXtjN zzIattYDZ=2?Y%3T(TgpWQK)qsM{IqIiONWBl@b)Ld0e`WKN(u@5Vv z^_B6m8PWG2YZAUK9jyKQm#nnq6QNO=<<6C%gKhez++4g)JwdnlAJC|&=cY?ndVQuri!1f5wzeI27gis4t2YM{`PLFzu0XzBM{ zb@qk%sku2k*cO(fEgxf)$6RV$3_}u8&!ch&MMg0VUi5zv5;F}&3{)D97_8oid~{4X ziRJ-22HEg==?aNj9)-{$`n^(gEDZ*Vvi;*ze=pr#(=xl91}3Hs42znU!8|bi@lk#2 zj^$vTXdYRVSo*Mp2?Dq-9znz=e?&7)&4|6AN6n2-kl|$_99gqE^-mM+1DW}qkl<%Y?X=G*D3oLZPyGe+S)!)-EQ=x@%anQY(~p2;eBD(hE0t-GnLB-(VR4uYE**dM*~+W-m zl3uA!B#V>VD^fY}>gMxHya6$g`s2GRsq3dbgKSluy|BC*Y$HG`t3Wo!xIl;(*Q5r* z(s}V53N=E=hr#6-GEdWxsX#?hHRkphMn^^S1;x?d@CZQz=6hlglQBX#nCH$Bf4FAMwVGPt*7apZl3 zZ5;FB7${}NE)CRQ(m=%=+fluAy9?BKoLN?mcw-4%k2Mn)!M6=cBIxr6$A6FTAa3J| z{&*}w712{3p?*I+#59!qVM|)IXO!v!q~eJ0R$0fQuVw@9!*iqgIWg9nYMLiIsAkY&F==%-Udy9RPoyn9k!1X9&+62UXcN7)AJI^Z zz>oNro`vW)UV8QgU=~Y=-`zDdpN^+>D-A5+2Jqck5?jzHRp-uVq9xreM2o7kYwbd`EOR0HR(19Q zOp4pT)(Yu8 z1u2xn)~7mqNjWb9kqIkOld*IgFH6(vR27z{=L2UCz?rO*tV~bhxdsWcS`q{~N|&Z| zN`$3pWlK&rEluyl(lqtL-aFCVSef>v6_Z(+o(``de_>V3oH6ZU*AGuewY+DY8Km$(bhv2Q?x&% zp|Rh+#WUqG%`ZqUx4yFr0n^;8vUy2SW%Jb|>6qeCob@DdOi=;TE4IF#HGbNG@zY*k z+UZuOzO3H*Rt|RBIoRv*6h$lkev=Z-Q)p#(2MuxfBY{~3GfThm?iYy>pG2E(0>g^N z#n|S*QGrEeb@O>870s7oow+IA4(Tl;dncx0v-*z&EoZmBiW85wauV*gg!?GE`E>l< z9I0r&s0_e2@HZvWGID_XwEGt%(CP7f_c3=jk>ki@+HO?U(FBfZVXIqBYgrtiRJ&~n zcPq^+#q=+UX@3`Hf6P(&y$J{36&O*UiFi72d5u*NIviD$CESOhbn%Ck(fp%oQcZE% zw0&3=&Bp?2b$lZVV_QbXsPz>3H!P7_5pN?DR=8jmuLf_3jBQyI%LhV1L%f8(rpL?a zYeRfF;Q~U2hXDiQ_=klCX0<;9LiPo;1MT^v>E9IQ6C2x78DlPs;c|?7 z-)jk1bOOI2;NYNwRptoZz>ZSiPUG|fgt~YFNd^=r3I%v3nI}0e2LlOSf`Y|=N2PR# zvJeBA1=hM`c>&==9nz$dd#A?h3TsteyxbUyMKa?rV0<`{I#Ah*zNiyxK|ocUFeJZn@2|^n`uu&|K00EV3C4o~Hk0CF`kfPpY`!V3^Xjmre;93-50Da|j(wJKv zZv+yk7Ujc4Y*ryFe_nn*dNKJJz*+e?CX$Sd()4^vXbsx{)^UJ0AbS$yTHrUlfb3V_ zhK(ySZo@wq2Yyc$!h`kLur|=#jif`YyO;_R>-VGKZEk?$lAm&YIqKg<{i{@;PxbGi zv=gPu{4&@!8||;71_`778MMBEZ^%ueLz9>QlF+X{r*(XTDo+iMVkQ_Dv22}XhX22M9;8UproFzaI_Jzi# z5Sa}oQI;@KF{__c7uXcmKsnw_fz(^Vemc+hfhiEu5g2wB)hUoH7AmZatSp?tu&lHx zkeYGijjn$d(7{cC2p>#=wB(X0ungtyra&>W92gLfWYgmmNIin3kz+txR8a1gimSUJb6 zz(r2C+It%>HYo@y+FoGP19}b+R@*cjo&<=U1c46ptmPsf;p zE^B$Gt_cH&Q{cx#4-^Z4w!&;hFya$vsxhZj0N3fJ97$yXb^JT1Y=Cx12D_LG2}te% zsJkOt5t-iPizyxb6tEVI}iXZZo#pJ zvl#9G0K8V+i`3=G?ZNV?%wVSC8i8aj_KP%!vcb%p_mM0NuI&&dR>$dH81NwPka4=@(|fyEh$#{!R>WfrAY?c`NK8B9nmum9$8Q~vDyXtL-YWcbkE_&Bo2Y?el@zt1 zpUnBSK7Q&7^w4bM9VG)J6G%;_SD_g7s@f)&vM!CR3lGI6b?%CI36NQj<)RSQ1xmXi zPFA5Wn%G?sjh!$w?qE!aE*DyH8=BHZuZxorEV>mG#FdMJcae#`ZnO=}|JeBy5HxC- z$vyxQJs7MK4XlU3T*rL?naOtOBN@06BqlU$z^pLC9PZ$*fLh9|U<3EL2%(f|!r!MK z+eH)-I?%5bGFZPZ09HSV2xtQgMulGRAb55VK1y`fX`;irh}M|1(O6)~O_uOfY8P>Q zo&z=7xQKZRodBnhj{u0*lxTy^p^JEo(?Bzlp+^_vH!>lji?|RWX`zR+m}iN-=&oGX zYL*dI?7mcW3U>_!PMgRw6zrn)s6t2m74bSH7cj*&9Zs&$RIKd5l`;@T;VuNEBtam` zf*jtW5veNTRt8QmZOmQIIN>!mklZBQ2{EQSF-<`YnIcMjoMzD)0o=*Iz7)QKlQAgh z6wi(bUP@TijmD}IZjfVju@RN?QRYiF0Z?;;nn8k`q0+b&PV6=^R*YANs7YXDBh-u_ zEZo?O_-elN8XqnPx3f-hBT@IDWAP!qlC6uZW zf*{%_kuHDOqo zR{b;8TVKgW=G#`2`g0=t52Qj3?$jWu^^emqFzGT{|AHt&p5R_%8(9|``-G|^5UH>g zRn#m05F*J^^;ClH(l;1jL`Ya43YK(eJ(Yk(5ePxDO2_nsJsNbPjo#A$5RPr227;_X zxPubSlW`l5>{#Fc5g@!Nc5t^QYK37OI=$-~{Y zYWJDM8qkSRiPWQnFVcCtv9!%ixX;l&Vwj-{7Di}yQ57vX93i$g9Yg)6Y&HoRG&iFN-o)|0`U3t#>Q}8%qPgDg)YwB%oX!^Ug{`l$ zU8nm_TE&Q5x$siMyS#u0ar(pEg8f!Gb>}}JWNo~W^oPg}l(1kXkXAT+%?BfBy)+k? zBW&Mow`)VjjB0gNc%`04`#R zGNte?s!|@I&|(3Gjo%)RA^{e4e5p#VaruJJ*x}(O85g|_-O&hxi&4X;iGIZ!*!9X3 zRsoSn`AipxqA$+@ocVFHEqsm!IFatF1nRu_%$k;wjZ{se7Cj=Tj~n8RY*o^30qG0? z&1$ZRE-=z_$>btF=i~!uoDLS_t_GeMfbu6$)<2G0xsiN7G_wR%Q8LEbTCC*k4hvv5 z<_a3oaGA^@5^1w&@-L8O3C^O~T-g^(-fq&99-?GBYb9fYkS@X{k;1EyT-jE-qLrMD zd(9|eM(AdSzj2lZV@lC<)*17qXH?)a1m)G)xSs-dtUz$E478*v}1oT%-&>K(fKobLO;U5-#Z?P?p{Z-V2# zDMiia^JyQwKe}Sd)>pGCE}~m3>A8^$=_Czz9>1DXo%(|ANXY^#C{Fi@u8wzrBaQ>B zQ*C=sF!wiAv{bn@+1NsomvBQ9Zua1_z`?{DH$34q&^%)zH{I()CXZG&msI1ShLYx) zxcLML5cFeO2A)ybvS@G6kIl zmbjg68_ojP;E1m}^`V^X(H$&r!k7_e4(sWg`B4g(^2!COF>)1hd7FB%d{=y>!jB4T!QRA}Zew1TRO^FjUv6z5EVR$hpq z)!T4wi8qqcOhK~}L3+*v7skY*)$z>&G?{ZX%Fc#vlPpLfgz`=PRJa9q>(DuF8{9(t zHpvkQb8a(K8~8*6$EA$1)*SJAholb1DEf-B<04kQM`Lx_7HWgPz-=+Bbu@ixc_AAj-p<^3B{2d%a0p4o8b% zG0)hzQUgeK=9I~x?Fu=@fg|KhmYlPJmu1SAH&Z<+04}(3O3^(vw!~YZDhGqv|t};on_*&w62wBIWzJ{Fv)ms_x6qOwk zF{DE{AndXXDeB1;;$~*EUfJwZHqy_C`LZ-RGFIHcbX`hE&%vVJ_Rb3K6AcQSwFx3l zs~Hjct7UmbN*z;b6AeCjG^cL_86~oqJw4qi{WmLuM2p>OD~fPA z9j(^CNC0l&Ne)C?w?~^q0Zker`9P8r$~CMUxEc3tV7Wk=6`?#*|02?RayL4RkBetT zimCElDkF)3Qw(LkI|=#_D`3A!t)vT%45$iS*CZ!jzXBN z@i{Dgc9Z-GNgaZ#M@%55tLGF`CF%y#G! zQb#RMpW+Tm3mRFhCDgH_fo|;~H$a~@?stbBoiU#Rvc75I!r=YaHMZA?2Wt&irr_b*wT?cZ$^{!6w1x zd;$ELz!!43M`HI8Z&M_18A3TZrKI>eROGvDF?(@9f{5-o#;k2L6Czazi(YY;y~^7Z zpRHlGK*Fq&kF;=XagXq9RJ!F#mte^|)95ng?b8lfsdSjepz$sr-CAAH zSjU=`4Rz!irK4?=SnAnloZMt4bD*~>dO4$$Spe4h=VDkR)j(e_=vIRjW|~G^lnE(L zs#KnE0XqpA9=2pZ2i! JG&z-#$VEWach(+!o2dsI%SYO=vj(CKYutgz-!LMhm1@ zg$uitxlqSY!yXDD>2V@^jsoU$YA{FD?0=yAgZ1a$X9DprD7j;K>~~O~q48Bl&1b`a z5F-ExCMN*lLq;f4gkpnW4+N9XFhYr`SD%7cxm3Y+=`hT;;z`8)Mrnf&Z0~GjFY~ER zQWhR%ghU1>0FarVk!l-suVBoqRjjGP(~MXU?u2GdCP72^Oh&}WmUu-!wQ}I{Wj~qd zxx4LPjzJ?YF4nVtpxVV${s`ltNwPL_6D(eyMk=ORfglw}AUJ7^u$t?Yw9!ZE6Sj{L zLD8iY#R9C*=^D3_c%+cwh`o01$F@f)HY*P@k^65a#Cx``(du@!RS+TGl6GUOkzuan z(yS+lASI`CZ!3;tGo*@BK-#FJX-0brmndn6DlCW~ZIdQRsw;TwSAZd`GHm@Y#HJI9liNwG)1TL$(dvS}f`pEFxa~ECyzA z)(;+%z(>AY&wbyp6)PY*VUiJvxkE1Nqjo!LH}H8N>qU#zb*vYey$cG1hS+LXFQPT2 z+|hgvcuPRWST6dSm7;*87=cv~Ae*3-J{I%%D9cFkA0SLsHq^nQTfR>>Iy-w3e{_Df ziA_|cX6cbk+6Kr*E{!MXp-~t<6;dE_7*dEzpOFC8wN@;JHgZVU_NwUCAhz%DE0u0b*pVR5awjfd*lA- zn$$jE!V`RfhXRIFNVr{i#S3mNrrV0qz2%@Q_%7xYGjm`Yc2k~8UOynwtp6KCl; zXTsyEIa<%g-z5<=0w^G$yX*4y&_*_Z-WY-EE1?jCn&Tq9bB5G3p_;!=mkxUE-uEN} zA{B$991H^6C`6|m2Ka&=E|VTem&UWLT%`k-Hqpnwzl}l#V#8|g1Ynr^PBjhR?)G4^ zI)biV`a1@E$g$WE2y5#Kvr8tQ&}xsuXs;P;8#q!u$E=Zf6H?zN! zr>Ra;e!`s*S+I15Iu@Engip6lN|R%q)W$+VPE5I{@`X=eqiT-=WEn$q#EGCFCt?4k zOuCdwQkm2OASbX0h7qLY!YQ{zhe?UP8)PZXY>`V-+1tBhh z1Bd}y%;AIHelZoM$h{Dl$=EVvKJtocJ)I{fb9FA7oX#udS(E{=&XJx-&Y}dWi!%`; zS6zU_w_DJ{jOyXrbtVB}sxd>HuA(BDuhUzHEyhz|Dhsa;)(l)oE7}|WU0AK*`hpF5 zLlc-pSiU(nmfi;)^%c?Kf*9s0nr@LHgsC1S8xrXWJP3pipamRvr9G#X?m_DU%F}f7 z1RXxJTib-4IWk78cx5LV&1pB?`5>bS7Xzn)F%HZ4+?j8=z>GH&xVTWMuBYXrSvjJS z^wG{m>PK+I6w%nP&Na~|9V%k@nQMgCa#j+wMM~=zq8Sm0;#p{u&!X(*J(}c6rv$W- zm59s+P@55x9$&IXUrB&QD4#@4CEqjRQ=U13SAdrFM% z_U2{NpAyp^!5d{7L5RCe=mwRoukn(J?@r;jUdi%c;*bZEzsQ&Z{*1R2Xt6ka1-uM!BcK)Q_*uuCDx0@32fTHO-&U6cqTfgG zuO{_oW$FM&I~IKaD>hRb?f{8!JFrOzrM;RIy$BZfhkyl+08kS~mhUH{EF@$xLq-`a zjI78{Mmb){iVYbhEGEO-o6c()`3IeAY`kgesWgfjn$#>Y`@x*4`IJxEKlCdrQN{j-`r_tj>n+o8S`=%t3TREswKbO70$*-Dd9wCP4}mAj;k zaDJhujU~ffxG6@<+=!?sYPBR}Q<%V@alqC?i>0IGcf+kX9ZeL}ut$d+uHXqlVt z9~RyX$08p`bVsZkw32i*nMxoCH`E!QtmPm~WTPv5xT%*7Ls-NHgQdgv@7FSF28uvVv!h_q<4X z#VA~&7~G2BA8v$UqJe}mpRG0STX+wW6@p#5Nt@hvhzKepIZvn8kgenM=H^RkDSq4G z93(m1i%ecZz_#K$^&(zOig)A5xfzl;3#QlV8kbL_mv?51Iuhp{Ad5ErtjKZ3wJiD{ z+y>}x#`{-sGYMmS3mCWw>|J;%ZJey<)-h;JOvCwAIz2XN+?<1E6W7ZQ+%gh&QF4M- zrMs7Qx`~#-jhLab_ZI5=>S)N3SiCfn$1z!FYPi@7! z+3*(4%6JT~gT`Alu`;E%wY5~9jQ65_1`zLqM$Qg>A2eQ~dEt@BoY1}aAsV-yZ?O7^ zHXV;fdiV2wG|_9%==tflN%t1qvjPoqqa@x2Nw3{2DQ&C7ivrO(HFQhbp7E(Ss!|{G zn|7bvi!&a&U$b%qZUehz9#S^F->xYYQ&fH3ce5VD!Y@nWkc@-y?=WP-)kxF ziai`Jf91E3Rp2pf^V(v1nmo015ng##&CgnoX`~m=;oW;=XtNp`*O*$Doks1 zATmDnPR5&qk>aqkf;Ifg=JZzwzwW(BvI0sM(;J)L!B0x!z6_Wx+i_BpmXo}fDs4kp zBG+Sm`jqx(bK&hMZe8}wErpa*or}mlArYE~^mwiFB=@y{ipsAdebTks69#$)&D=Sok0G>*(vcoiL?yI4} zo&XZvW14yoFTmqbz8Ta2@SFgE&b7b9n|2I=MQY;9)}4*ihIp){5T&G0lX_dYV3fd{ zozedRLh2=5n2lqw>xh9K&u5XWnV@3~x|PB_ z{wbS73KSygi3PuZajMt-Z=r|4r=FFw{9>K+*BwAdBAT$-faDlD6QgbDtI<0?$wtm! z_CEfj4p$BGPlQN#u3-n-aZY~IVW^vBwkPE<1hHsMMUjsV&;=!cal-yp#$$65V}?+K zZ&&5eK#kED%3;tV^uRAk_qfq@6_(L}mr&PJ1hzvtL?ey%oAaVgtD(9_U(fLiOx*5O zZ_aZr!);&5zk=#-psM3+)E>R#HO!{bWiQH{%F^M0=kbSA$^Ep54+W-^szblHiF#{09a1MLU}}jD#*sNV1JHO3O+3*Te0C z@Xo{j3&R(yaxkJvgqADI9s{dJ+K@-TcNE-4^^GhDNAdX(KuLEqYhFMRG+Wodn4L^0 zEi&&ZAWRy&rkd0tH8l1+S0lt#tQCrVrb#Hv+HM(YK(o-8)tE~w4Xiw9iq$2Cu*d4+ ztwam%r-F(`pv=OyK=`&m^N=l2ayJF&XR9M#HEtrYg2?Lgr7RNJv=Mv8w7 zM(?-;>y_xT$@mW)a$z1NAOvdhF8uA>v*ZhIFv<;md;T;+IaqC@8AitMl=+ zukpuiFW@JlPI?vlYs|Pj7@QO^al_( z^p)rj+M2e_J5p;=)p1jA5Inu73kIXeHfXaDbubZLiZ4DpM?`P?b1qzprxPc`+>4&x zvl*?le4=qc!yMyGW@`y>Ow%(lvgAeg{`rMwJDprG%YDe9hKz#rg8ywFMkj**(8u6v zgo7pAtaU&r3r`SC2LtFE9RME;7qjITW%TKs;vt+{009*;L zhqhv-jy8R(yH@-X1u}Yft<|aR(Wb@SwbCTM6N*Ngu3!|opGi~m^J!Cg(3x0_fYNg& zUM)@sIos$}&*hnZ?Sx-(vIb`@CxLf7i{E?FHG_W+U`oisy3Ff1kwqO{ZUxrhnJGCr zqFm_{=<|N+eMB1;K6r_1+O)!PhbbAM$0MGF$)Od`7qY+*RgeM6P+D5F;alYw;SA7N zyTB7(gr28lzU-Ma!T522nfP5Qh@-`o;fT^KKHXguk3Cw+=U;>&mqu&c4pzC0x~LIy z!4CXyoLkR zGNU+F7&vsm!V0^K$FNi*1YM(nAGM}HMQv~T+kzp1Ri(NI6u!Y#1}=Oy&O@vNq40Y2 zjsfBk2?!wYPzQUuu>lwKw{g$VRE!nIWV|fwq9NIrU@@_jx>an) z_7bx-t>s2EafrYedJ>!2|arb4KQ3~*NOlGLA=cu#NbO? zYw7@4wKw|xHsmSvx}5Cb7bi^6$T!|VFJ-KtC_$RS2H6V1g=W8_8B+IfymN#Uj$M3f zgg_B2aSa#$vJi3)U1L@U8@YQV2V-byCn0Pa{P`=*(TxQwXv`P^JOuuk#SMd8?iJ|W500` z1&rLl0BZC%c;>_VSa72w+cDEeZ|lNz3NdEz5B)6q-8WGKBo=T};Zp4R-RSKR-femL zPFf>PZP8`NBI_ZgcEBZOXhpyGIC%J`?=3t-`xed%{`E!k<#%y+Z8B?dS+ql6^;?0g z-EY7DV=V2>2`-lSF=$k=IRYgqNRz*(&9)vZqPc_>mV0gJ{F4wF$Mon5zEn$nSO&~+ zj9SAZ(JjVQgOThOW46JV*)2w+!C25O#!7>+9N#`mjJ13I^CMdO(Rm*FeF*HtcfGES zbwzv>{jh+}Z#t0Rl=k!!67a=rT1Niea}3s{{5xZ_ zXd}3ZN3Tp9nFBX$F^Qu@jM`&<^8d7yoB}t%g6X{9m-QnOm)yy-XAJV?%F;Gi_I~t7 zZA;s-C@bx`iWk2~Zgz$Sh(Xq|=ZP+kK3OEdZ)SabDbC?ojJdgM&-1c!=h3_8j(xn^ z<}ove4kvg)51AiCzf1EWAWl@#+ol53*S;@$`vjqVCHmdl8I$fg-1dAPre%5|7tIX# zo{X6StMcQ4%kc{PFfOPEjzZ61UbSbPXu%oTo{RR99LnehU-EKs`<_qmHc#l&5&iC8 zI8KJ{h%S8$(4Hx@`H|lXfzX|M?t9+|L#chwPtf@fw9<>P9pPgU<-tS2~a1J8F zYc=SZKS?zPXnUV~fPNNs?>!G3elVOj0BO%6sQJJz37SJtHL+RwzyF>HI_Hn_^uY0uR}xiG!%Le%{cRnG5k1oQA8ntR|nl3PdAR|E2K>`!%5sr3wk z&Ln6sL8Sy84`|N3IhW79Vb)DoT|VcAD{q`T@A50AUw7TmY0imr&x)Nm{jAu88)7r& zTy<6Kx~s0cVa|8@*!t@O^`xSF_OZHJa-5FQ9!$`_Kv~;6l&)Ox39s?M^e3OJpz>Ab z+i^A;`oi*sh4-HS%FUZb%Xic0|G0}j5196y=DSnNe-yqSkLF)dn(;SNA1iNp_rBIo zcWfLrWX!<5uby%AXyRIU@B5~`UwOgXLvFq6$KU2L2Of>W$8q33`Yh4%qc=DXdKw=( z!Eos0BK`C6T(6uJVbtuL+-t6%=VV2AByv^Ov*Ur56~Q_gUoLuzz9RgU#b{Ys(w4(t z!e_g^h)ya$8hyO-hBZK+mjwG%766eD~)2{`OgdmV}zCwDx72A)801U!jC zxLiOWQx)en{2H)OT{fJE1-^VXz#M_6Qhh|=E2+Mpz%!^mU*MSp7YcmMD6lMYqQ$uv zKMP1Kn_F$cd0y>}Y%d%1I@;NwZx}6aBF&;XxeE@(B1D+n?12keS*D~#3R7kq%0=&k zGAo~r&ClsGs0s)L(9isL1ud_HZhdDr?CQ1uX)JHBub~%6KLRHkh!KG{+q1LM?mjFm*qvM6FCe>Z4LvfrS9a~v$ghc)DmT0 zWC;GVxQ&r>Xw6+X!HJBbCa3V4=}u%cfjNbd1PUKu-R$e&$s%ZvJu$_@Hq(g1tRSTR*<>dgAQhgq& zF|}~E6FHx3ml^3~+|38HPS^4M}s=AW@ z-^>4LK{-Am4hM_V|3)Nwm;0grO$z4spRZt%U}rIQObXdti1$ao<}g?+ z^||f_=ujesJomRKl=UC)M0zn&dH?bAoJfS>O2O&Pf)o8GprH>p)b^it!%QdA&)p2- zq+lJ&EmNgXz$7vu#0xuk$wjro*#N|E#_&-M5ST=X5Pr5F+J<+)77QZum?hwyPP zBV<(MlKKLl>x0KIJYf24$F0hxej6|Y^3V5SRQuqu8SsEvGv_+)IN>tKgGG|5zs_-M z*i@*vjM z+A%+BI4|0NKR-UmwPAyMcd~MM?>m`+PY6#|Lj^bzuzU1PPmAIo!p6z*QFN zVkweo!*aYX7PyEzGqYZd8=gZQ_*F0oQydO%xNCTh=dR&7CKL8;wkXUAF z6APw?)eFvW9ARwA z$1_#J&-N**!pB|vJFq>0L5mOv-)agj`oxLxIO;n3oJjw=EuKWJ^qD)$acm*wFJ!bh z-yj0_4?Ob|xZObR+86RM=O8j=nxM8~j!3V3GFAH95nX@aq)rXmN)bk%L6lVfD3bvd zc`$C}d5fA)p?|JdW|#VkG%&XZupjw~@{Vlvqi}Bz;6D~9Kg$ygDRS!n@Kiydi41(Z z$K{kp9cS=%(>8dQ0#7Nxw{8NJ1}A^+hWm%Qby;)qf1+DD)SZ;Gs&Pr)P&et8I?ic| z_6X1>gZEH(@}aBNELoisiNzzalOoHH^>B6rXBOksx#eEk&Y@vzUj&T;iOEaKr0bRpCeYtQt^EZA4TA0+4;UMdBuRN4jw^SZfPYIvb z(Nwffq%i#qyLEV_?_4J&Mk$U?!Xl0XV0~sVro|EU`8k5@?{f;0DO?>4FB1Q4)3Vq( zBkn0i!|WHf<@7kqU&Hcw(DC@AA!kjYP<@aoCZ;L%AnO0pP5lR4Zw7N373tg?VF1UZ z)l<}clk4lciQmw0ley>~+aKH~HZ$Lb({0Mk_t*|9M!?;}n;XKE%zFz=FICWQrP`Afc?9#C~x958wr>lXB8Ako|uOr>9BvY=2n0zHIK z;C$?b+-|g-EiLW@L})H&dYTtb_mC)Ju4km~B8GB$pNHHY0%sMU@{srGGf%#ds9pwN z2@Vfq5PJ9fwzs(NSKH39tBC0JU~>8eeGDsnHJJ3~u6?Emb7F&O8azXRISMQ?z_8T{ z+^xVp3f!;2HU*wk;CTh!P~Zav{;5E3vs@a!95rtEkSj%BuPR06J;W^ZhGJ^_60tL=Y5PE49__F z_(@@d*!LomGUXdwKQW^|7k$sU7Lz#wIivj;Zs+=MYV42(c2v_itPm#taY&coy z2@Jqx8CP6)CDwiJKfvKk?C6i9DgI2VVqls9 zoH8=b9pWC3-4@{uQ9J>F$ADQ!+`n3Vf$uM|%chUJb}Z7qtQIT+Z3Z^$EY8b;-wzjb zYz?rFwk6pbbXOcP#&I7c_9MX7cxZo zsZw?=7RucDjuP&?MG1EilulSWpa+p{ma;vQ@>-<~5xL$Hc4QK+Q^H2UA}a;@Dl1=7 z%9WOKjiv0-%p&xHc-B&SGJ|GnZ#v{zrV5i6w9z?-MxFoTzA(6mk24SfQ3di0kk;|{ zt9qSQy?xfOR)NAhexFckcf*&aJK)QWmXKp7z2i?g#@H4nvddC({H0S)6UtDLn=K)S za5~{nRix=ED9aH!)P=G=TKSw(hJ{RzW)>iea2JNj44RqQ@nQ0UHloY>(h2Fs5M{0% z0z$H=OIz2>;ETVMIDdkyDeD>!gVr^Z)7Ldq?7C(U1Ta zBufKQ236434nhvCw<0--4&o$wvt8T{;w-w*D{~rMWlpAz zwjASZdMlURD@SD{(;MBp2OWGi?HAKA|8=wJd6rVbIc-$6+YslFN)PcyOBo{53!FCM zkxu!0OBo{53=^b)j!eprm2$bITq)33<&9g~JEw_|j(DZ2LG^Gra88kDq>oetLnCLiW zfqJ(4Foe+p^=w_B3V;PF1LhPwk#-9H1r9LiQ%kP@weUhSN#aM4MsDd~>4;xjYe%&b+|)E?~m@yodBR4mU5>@Nl-fF9Hk7C*={L2GATDG3b(9X>M50e+P(a9YD z^RreV2_5wKR~=1X!#Z#7GG0di?+odGtVh`|#u89&v>r9O(|nmpIY%i?lz?)*rR-7W z2BkE9puVt_J<5DjDZ}hEaR_a>r_!S}25)w2r-=km@~BCtyjUs2>pX+;n8;MQ7@TKb5WwYBp>lbexO?6q_|FILJhJA*Qz zpM2~IQkacFc_H~fm9+|M*n`ghs-qbL7>k|OqZ_St9E<7Jl_8GUFD8)>R*sgz-MuC#Wp$<#ImZ+2^^iCfD5nUohR zWtg2onVJ0`W@AvEcNa{?jW1Z($tR=5Y=MSm`w!`VgMA@=+_i6?uUjAwgSJ4Y*?UcR z3sjn|r{}x{@`{*Iz^3~$ZGleDwp$ zls{6+Oqssj@A6E-ok|!cvQdOX+15qU6!&(GZ!mm zrpyd8Glxi+xS)w4OQdA>CS0pK*e%i^#5ucV{X*_9>O+Cxmi1yL9LlLVz4tFs%3YSS z!%}Xvlsu@@DSxPxVKO&c%8<1DXQd32*=8wu`bn4B8$Mw>TP3z(=Xx9EJ=!@&DK}cm z&6cu9I~OZun9QJ^nf)IoFKA;(|If_cbmrIS|1)~%|CM4V4XDh8?j=eY))#9e5Am=8r3{s6wPn8Pl)d4TZu`GSJI5$xSby|r=VGM{>;Isg2iN~= z0yc&$k*?4F?TCZV{)5VlUmEQwUT#Nmk7@A|rQB#KJ1u38X>qkuX3ES+i&)vnhgObH zDPfq%U}{XGjKC$_{rjx4T~^s1%{*5r8^!fV9Lojzs!494QfA7`Ff+6F!^8zm3>p15 zWdHW;gCG6J;j)%wf61LN&Md-VT!%P_Mn4&xKA!58(quqTn&lhK8LFtq~P)d^l zLCKk(7sx%zd{QY*76hf)Xnd8-Uhqk`cA9yBa#g0C6-pUqr^!N~`WI&Z_MC$s{l|?oelZyk9$hcn1$cC&?Zwv2<$t|Wwh9&~yX>%Nx0E4U zjvpwc$(^9wXDNG>`J|;3d{CMV6}0V9W-s`J`#;RiHmfafThe>6LMg-S+-xaBHb@JV zGR)4P%*_5bnLf?NpuCX&UzGh@JokOD8|33gWsHlRwl8?>^4dRa{41pw-5r+F8~+^V z=^p)oQhFn5m!<4c=95b4jg3xA*`v%}@JV3&dv@-!y||IMrE6QElwo#uSjw>RuaseS z24!aUf0&Iyc_I1#*6fWHUnBpQ8^4&oL$rAwU1QhN`!fBKR7%e}6Cv`%1n`%e5|q|Ta`SxguCB!s#)?Qar9{BXr=T9u}K79Vdi&~Fid37 z%*@`;6qjjY$nofpvNw)F?;qsx=rQLSw=_x|qxY8!l$QNjm-McfqLlkAWtXLF6NuIg z1f^51Qp$~%veQzow3Hz-pHRw18S|vA9sNDZ%!N<5`@`&P_he>9`DmpKvvaehT$xGv z9irR=clM(;3Xm!%BJ zr>m55m!;e(5Mr)JnNKL?K1pXDI%p0_NAPW)fOt_J`Nd2uoc&QI=^1;nMc=gwWf9S*S4*n7248Se2cd_FJ zj31|QnaJY@aTo{>1?2tFnj@KLC%230FIn6{$Zz{BrnG(# zD1T_nqv&d>|Km31ExiB>jQyq_2Xc-x%*8CiKwkxpP+*V(rz-GG1oPE8SPMKULsY3f!l_Rt26^;5h?eO^t^HFNB7#Y18`(eD25j-b73uk+DE5 z#6S8BJ5GU96ga~Gqv;Jo{*QZq?qH|4)C@jCfj$cCH|XNa<4yIjAqF^2S<$2D*Fq6m zbkQTI`r!FZzV}NIj&09bwQ~_ahPjwojLOifHTRTBh=qTXZh7EkWlKP29i(nysy+H| zRP7gj)pAyK@gVI)Jf^6tq}EDKrv5kQshXa8&8?JK3&MDsz56}{!Xt&u|`?%6R&A$*a$A$ zN^j0tZCb@H^))dg#gnYK^$|VBY}Lxa#VHyo+@bK|ss5pY_kHlk-Jf^$Esvxr$60~iUD90)wsMBV6w{4zjX8HD56+7R zqug}xvYyk?+EW6TnGbIjS`_ZPS8`6McbxNS>id73lK<&f%}jee_$|Ftc?EI?G#U0A z6O$qRT$uakf0M4i0w3!}&WilDn}4;BSLe*U_}}A1?6!T1Y3lAa_|U7ItygmI+DU+3 ziS=FKgx6;fPTJcwsQH&YH$T#I^Xok~XJ5irgvqA4ZkRO7S|A6v!;=x+ypM?RukhpR z3g1p={b4%DiyWk(|9h&ieoyJlIxqliZoSkVn+!s9J(mCz-W7#E@ ze`!O!j&6{CmE{jDaME9|L}n(^aD3|;p0)jE(h^`s8g@|kJJMP+?W1MF9|gxO-scM$W zBfwGGuUI}^4ZhZsUp>>Nuaa%-&`HmK3#Xh_X$kUOjF7(?8NP(&mQl{7rFqhS>_gbS zu1Jf)HeQ!AKHh{#g$#-j=@dKt6z!oD-%6+G@KfvxrDzq3+*KbhTAd?XNvsgU7<(HTUoexfxQMAT#TLKNsRYMC3_LcR0LfFLu4 zQg`BYl%WRg&LD}<98Dy+B)K^61Qat2%Ac0*g{--;bfWwaqN)s{;&h^-5TeByL|`rI z^(OUj$6?;_A#}gXpaXZI>qT^noG_|A8C0~YU}rGZEumC{rpuV1iM%fNBe(7Zc+aon znuVb{PW02sq-ArLdUndboSSP)ePT}hzq6q7ufmivg&$`Li zJ)r2WdoD;P^7Rdf!uzH%oy2xb?jk3=TYi^LVEd%kB4<@dj~sr3<2+$Wyea==)OXr? zUTJ!bG#n!{*a3a;0|5Na+F`!ea#H&@q`miI=k)ku=lEcAg*4|Zc7iR_rR5OZnW?Aq zew0cmwvbV<`1MqxHUEDYp51H-9_YDwSI^C#cHew0R66BqlN(5D+%?em<00m_ci(6X zi(KrVSLb-@(sAGgLShbscnzC==0-1=T_8xhhz&nAq($QQ6%pGFPY$(g&=#D1rMK+R z7OV%-TROA_D=e>t6zV;b4GmT3B*%Q3;gQp{@G~k1RW@F};w?@|IZM)W!7?MX#Xc20 zG1T&7ZNYd-Z~0$sK^F8{=m4}sTlkFE9JU)j2M$4L`_=0jV?UoHhHk*9b4wJBm#E#G z_L9nD6)~gVrfW+^zulrO8U40KTQd4>t8Jmvrfw9X+4Lhp2s&B}%cGkS4l9!Aqs28=#nyPQeQzrPvuNBb83E%P)hMN*OX9OsC-0kjmH>D&yUB ziY~tlULf|o~W6tw@j)llgE$9I}Q+taiw5fsn& z8hkzplig1vs9n>E)-=E<8RX9-Irh0F0#{9CBFczOTBppA}p&{^8q6*#6%4WeourdKvIo0z^6~ zEk&=6Am@TvH(c!;avi~Rki$44I)@TPjU7_KZh!&D}j$QGi600%eZTsEDyS; zM+_0~oR>w{sf0gk#W(#3?P2`OF_glko$ae7g{e;{c-ha=*h2NJ*C9V8MwI6KhK$Oh zg_Gb~CHU8zxK8}q=UDCwT1t(5ofxtreNN$8TE}p$pWt=2?{aFQfF4T_`tB-x^`f6t z6Dy0dXnyqxXH%&^jD$VRhUQ)WxERE8X<&*r6Nw=2TCN@Y3&0a_H&20k;-QE4%|3L% zp#|^FKGef5Lu>R%*W;9*<0}*tgYCdW z9Op1Y0og=xxT=dE6hliyG^ZB)!Uq=(;t^JGE-7^6Q0M{V+;1EeW4()VEjoT<1qE&; z1CRDls}C3C0mFUB15vQZ$Mkja0~3AlC=95dRHNu{3_gp7F%*WS^EOo?`gH zlhk_ZG2lhy$-edM%fY!$DYp!PBm^2s8hQtWW)-bE7iOKN)#QN^Dg5x165zzaubgx*}bqf0ER(4DG4@);u zK_4A9Z-I}>JIv}iNkJdo`h%cLDqW*x)u7;QwyIviUuBCX5r=;KCQ>`_ZmqchYXbUE z_b2bx(p9F`bs7{zHuE8BN!13TG<*NI8|UE1V))}a7b&P>vYh#j!YT)7e>Kuo2%#zL z3B1S?B~YcYTaBhzRd}=Y$>j=OV)V*(mMpnj3+fivxm>|#4IZ&m_*2g&1&=UFbLeY9 zje}7Ns-4764Sm_V`yvI8Fz$Ap%M?7xR$Zi^dIeZPZ`@#1yxFI|GXKcPBOQWtJ$dmi z>%~hIe9l(YW%}^ao_zS0^$*h?8da_{Q9-rKbuLx# za$^pYYC-LWD>Rt)TC*=zv%N66R6(Bs__>zA=U$`uCeHu`%WOE!brinX*3EHV)?T$* zOmq$C>)n7nyfWNK`lcsQ!3#{E!S2mVv~Z2>$XW$`7H!Vxl6Oq6YCeEp71V)(6sE5B zyqe)J5C)b1*zo?$iyQ@gI<3vnX_T=w;n}R9_7ZGfzC;UH0r3ZYt)RMt@q&8&%=ldc z14x-uUJkOVW3fpe z%*6O58=8r=M)f8uCf=>3YGVbsDmdF`?!>#bq-uxE#H)?6rIy!4%HE^Om`>j^RH`{m z77AZ#4ZB>y7fcmTr&>_kFrBJIwIXcp`P}e0+&C!=rzf?BTD2}!aIUSIq)I7w4CRAS z>K@~Vdu^B3Dwu1TU#OsRM1XEuq6M`X%Weg=+Zay^UT<6|QR4mIVWdCiX`x`A7w9Xk zNM!~7nd{dvM(=8?f31Sgn<`Afo^S)bVr2i*(?LPC6yi2p9n@ANECtn4#?yl83cR39 z`q^jtXyBpG6x3irz^inX0<28l8F~BnhW8&ma}-pKpytMm6sCeTj`}$NX?g%BCnj>o znQt&yzC;V&Xqfi2Y5&-dr}?bNuse+U4v))zQZVFL`AMGR@76Xoxxc}YpXae?;x+I8 zbwQ2l*tO}P1CSBByk%27cE_#fmBu9j3Aa1}!D|7xTA^@b2xEtBeU z4_}~n43$*~c~s$LR^Lk%%<3a#+0No6cWYswsYGBac#^HERj|ZXU8LaEmfNZoOH|pL zjs9Wj^#nsO+I$nvRs-v=%~74gADOn0X{RT{M_I!!Q*ec?nxtl*Y?)tnFuLo{(f+TE zv04TDS>~51s2mZlYpiR$-p|-jJz==&&)8xJDLBOF305nXXyIB@SwQ?(cRV>79w0`-% z4f$FHeQIUq$omZclP&*C71RjBo>)Qk2f|xHbqyP>Dyc}|1?+JiG6Ekq-{>6$=X$-f z)*7U&z&~?k9t?PYhS$LFvfiEVDEx(~bDgUcR6FPlL*c2$nTMbb>3y;D>F`g747gjX zuQknwI`ZQy+8#7HVeqEgzPTcZve7hcwL1$}gx z6PePrSh`6H=HW^TeUJ+kjM%EAf~B}LL?3Slpf!6;3;BQ2-C8;M~R~T2Mg|ZK|@J zTbA6dg%3PD1wXe{m#f>ZF-DODg}>(+s^CUb#Rqg+P=jHpf@(XlQ{(p;Cjd>sp{5V8 z2vBg8t-44-bri6~jo$x{)ncFe$~Nmd+ z8?6s(75t6Wb#acm@Og`NnS$y{Bw+=^yxM8KdYQVg%)0RZvUeVER#eHq@3{?Q148U3{2qbu59JLmH-&;56IU)B%EgX1 zlX09B1xUOh(+!o{D^|IqIr%h@xq##JyD?Vk%!Ze@#Hwu!Cq?b*M(5msP(2tA87K2l zXH@RB7xBi$v5FkcNl~K234{5RqFH!|pti}$f5Zxz$)S!%b&8Hxz}%n&w#(zbb5i(} zI1N|_{mt>?IJrNDrgD-S3x%ONU?82!j;6;W!$}TWz-{S2qmOXoA?u0fzyPJh6CNwQ z8SizRd^d*Z6SoI1eAKg03A$fbjS_tpE7^IRoJ$Xa%|a#P1Z^WtSWpj?N8#J=%^B|L)v7CIavE3DM=EwDsItJGvi198P08p zIhn~x;iP#Ue9*G7somTOndB63vKIo^+?hqM0ncz+(cE#F|63>aw~tyKzk*ewP(9XH zU67g_pvyYtaossxS_kVME+Y9H-Z=>z7ojsT zcGx&hio+7eML4%1=42)(g_E!ukHWeWZ2DcuBxgD&`ygrujf-}~@qgKkIsvQP1l5Db z#e;QOXLVdxjEe{B>Wcd{yYaHv)R!np?O3JfagyET5yc-vZMXJ+u0fP2?xh?<57tfe zGF(@CS%-rg^D^2TN$sUSi0(XS++&plF9UQ#y$sORUY-D#&W?GR%Sm>DV<|@5g~r!k zi)DarsFwk{+RIbno%!7&vm`m$oJ*W*4!C^YK;-FT)pbq=yTKbCdARvC(6hDS#}SLke+|8F}!>p1Jt@AdzG`gr?& zjPxeuiMAZ{{v0Pi>da^YCw~G8p1>aq8*IRws5m_`W`8m#FNmShoaB50?qjj>HnF3o za+321I>XI}`p8aPm`t09uNOiko_WF@%BD~`8IsPEoD`WSoVhlq+FaOjSR+2%dZ{NJ zTE2%{@0n8$E#Jef_w{Lqmha)#>(lPg@;%&ow{$qPd=Iyt)5YdH6p{uDPTqyv2h?*p zxf;k07QFR9OflhxgQv`!@q_J;k7YT9lZ7!fnv>VXj8Ea@Z80=jyAU*yW*?4?8|TB; z#4T}>3llW*EP;h)BW%zD?(kd(mAA*U$^@H;uWLi)=$Joa*dNZEZcWDZZR(>lt>hqt z4LpZiZ&LR|%lB~WeRTGr<$JjGM)o+`Z54YaKgAOgxDM z;6uyzaO>^BnDJZv_i*d|IO@>Kd${$|F@pV8c@MYVS`6ubjScPRak9}w8w&GiV?J** zDzng@(s-*WsIST!`wvpJhsD>N@sSwt>G2gOugS1#^o)p;cLKRi`H&U7!MPh>#;@Ql z&Yq6u#JdwSYJ7&OKO}w_lhklzF2U%E^{_n?vSX<_!)REA#fkc(7J2 z;Ej8fW!~lp`<8iwBg~fnii1CsmR-b@1Mgel4Guh8;B5}Qugn`<53Fnh6JIA*#k9bx zXxwf^VAP3En_E*Zyz_D>B{{h(P)lne6OKDFt;Gu7`2>`b zoP17h{phcIAAxvYQ9jFolaJl;5{tVaiyb>Y0g zvXXK@Sr^U=EGsDoly%{}z_OBZKv@?iynlH#PIKqP&itb}c@w4_bfw5iZf6wZy;Z!y zCscZ|gp(q?dlSOj{`Y6eP54*s4^FprlmSV8agtpo|EuS-u}|Zjqinjs1eT zRr2|E56#Rt`A`f^;N+fo$Jk!Q9fI3DIlH|oprLQCs*BhQ_qN9ds$ubp*1Esa7i$(+0!%{KdW-#{RSGhUqi1Vfut@7(35?8>W3rh9B`f zJH_(kGk(PB6tmA!GK^bq+%OUsa^2mL=u-mbL`SZS@{CQtc{=6@qvrPr% zzh>|UbDOOBA~1j67nDEm3(BAO9ZLRIXM14&ye}w!-WQZV@3S(#4l2Lhdt_Qr`{RA# z`5&1Up8t_)@tkd{0GC@uko0K+PV#U`L;Dr;1Fo#DnBNPREIhdRn(Q@CsC^i4MRdh{ zJ3J-BR8rZc~;x7d8i$(rTPwC_|rF{QiYuE9XK|jPV|0V1A4y>j^58f zy=j=!#Lwl4bA^*ISLg{zb30Co9=0Xn zI)i${mU-GaI+*?Bd4R9S{fiy;p&l`OsH<}T+?o`3kCVc^T~#~)ut#KUS5-h?-&Gah z%dXheFfBd{+*QRJ98xMBCpn^iHrnTG_&PW4Gbe@5-v#;nU69Y;1^N74g3rP3eOIO3 zy9{ovio3^2;ojb$8s9rFAf$WeaY(scQdIWN;|;F!VE6VWTsiV2`asOREKc5wK27#l z&gTvD9oJ@|_hdQAec#v9o!^H%5`2yEli;R#yitN7(!eU-;Qs1s@I=MB+(o=tHx?|0?N;tzF@_`%$p3)rhfqXz`H0vRz*UNjx|Mnb+%cASx zkX_O(p_7`HP)pzz%(Fv6Is$~W2ZXe3T~pM76KzjRv^_1+_B3t#S4M4dQdt=r2gE41 zGN{Y14C?YLV_p7N2Q>DDGd5Ep9)CDr!}Ph#Fg?&1#^VfUYM3s6G0sAK3Zn4;1BlN? zgkl#oc!Wf#Wr;Oa3>i>wmn%aif40OUwPBo|hyk6t^S*i9YQ#9QZyt}Bzg8m7l}l95 zDK%g1-?5FE9W&&z)peG6*VVH7*tHEP|?UMNV>aLwL|4Ux;IC(>?N+)yo;^CAI&!@6VSZB0EVW}ja&gz=v^fZK@$0ONlu+nv<8r z;!U@2IJjByE3=z|p7GpNrE_=!mfw!$Fou&yA%_T^0`+nR)ceK`8_h|6S%wO@xDq}_+S5${%`wa-7I;;UmmPvB(JShJbR*5if}9)i_|fjTim zqd6%Gzc#4wYlE82+Ms5$HmKRG4XV_&36;t(GR%%U7)xGY!$Lt@8RX!~AO}|lIT(Kh zLbU6ZF$cLqsGI4E*o$b#M~BdfH8*T>_dk7WhTY^E_HAn4^emc`Lhb_U`IE27rzdXB zJ`Bis%e=6Rc|!94=O+Ib%uTp?+W7_#ay-6Ss@sXBx}jLATZ*M&O=WLvhBAed>=3oP zeL;6x_XXWs+ZVgjDw^%SD(#GKGt3u}@$b3CD>D8rDC5NYxd9#Z_uLN`8Se_pcvr&U z${D{Ai~dwjiXiL_ivHf9=e&98B)FN54wZBKj@a~{@5*5 zF--1H7_E4E#8*Xh{mx0g&ZBHsRR!FJURA|cu)X55l~q-IxyqNS{^9yM(}%hVHpeAZ zyV#{w7AH$%D36oEy-hLqnnbNpvcIW{H%P*W8bI`W7$TzP`*BVUj|@U0G#*Y}G8H zHLZ-brjHQ@Uo)UdtPE-rD?+MY=|0OIFG|FI{m_V+C{4Nh4Zv;J`CveiSLjeil-yv zo^G!S@N|2uEecP!2MxU2gWBTupy6qILIZ7!GvJ8WnGkNp1OEVa@d+wkMQda4{P1~7 z?$eVdxld1>U8_B)amxnI_04-=k1t+9^0DYd>il2j>GJ9?4x~4!<5%dQkx7o<5s1~lYm$$vhvQHARhP{7;9FN%p9A^t=F7oegM6tdoP6ka z79}2fo8oA9j@JT|XP%;QI;zpDsxZ-uT4e zjSC3guGoAx9=vfJyfv}rIgXPYFLG>G!mOA+niCMb_{}AbmwzyCaKz|fPKx0D#!H!X z<@=Wb%`EXI6i14SOTO%%#|0Ku{@-&qs0=41z9~UpfxfotT5sC5vAYM{O_o6OPRL{@ zD^M^00(%74^>6hV?iK%!caX%e{CaQT_D-#mlmFK@eU8C@dn=!XHu(q0@K(MQ8rMUH zwem->|NpmgM=l(ebqEiAe+DN-rT_OlW?)}L#{WHI>JupZR><(y_Bu3nLWZ?Ao>Ilf z%5j_&gTj`10VlusBTzoy`KR=NGu?l|G+~^h7A}bJdVi4D@rn{&@1Gao^b!7%N+ zVcKWIw6BI~9}NrhjbHS$kC%Ci-}U?7@!B7^bL*pyprty7mg*>4s^e&>j-;hJmX?M^ zlRc#&abJ~o)Q8$nA8I#!sJ--|cG8F1M<4!{ixGNZS$k-hcF-{GpJCcP!?bsXY3B_4 zE#IgB-vxy)ex4Vj_;*1U^S|*ivbyoBtoSPe=@L!tFP@ zF_Gx{1dh}g3{6z2oQ(UK@c!vG__rtSCnw{j(6er_O;my)` z2)~3gO>o1Oio!{D%8Y+O^YmS@SC&M_-xV}Z-!(7MlZ4kx`IhmY3#@nX8K~nh+lYN; zxUAxhTk*yr!F5%*7Fxsp!Mg7KgLUks3%U|~EAcqmR6e4}{*uy7^Dey5 z?tB)K-r(WfFOA)A$Ibk>!LcZ+FtxIZH#4C>OaRUgWj04+ryzLe8y?z3x_s z(ZYvuFKmi^;oy?e%7AQhINLSxY*$w4j^`iD0T8~djQJv(Q{wA29D;u$KJ?gwlbaA= z%3)_!9r(Z@AiaQ0cg8ZE5gRTO9^P@5@5Qp5#z`(QWx1}Z2eQn7q{mpC6m4%^P(`eZ z`5_9wF5%S;u86{zC%K$l9B&eHDF6%SaR5YV z7tRZadi;yvL}?RWsd*0dnHn!z;!E>Qv9mQ~l9R(p;alQy+*L(9ZVDREZVDREZc2Ew zl52+JVy@#jJu2@1rhslUn*9`Ei+_|~^pH(K^|dLXYS4PJF-(tg zhUwAZzv6M8&Vx?||3!18uP_W#>K(zP5g z(_`Blkwc!t>0)a`G*J9IH~6%+#l$EkO!?S0N0AEu z?_6~1!Uvf9&&2XboJCHCq*$&BzT0^wG|q+$i{)t;v3W$JhNVZDfVQTO@|%c0&WAc6 z-H_ivNGidW;1Z00##qR(67Xb=OF-G)6g!2*r)#+D8~2CLReW4PF%wl1E@iYzV4>eY z5kC3NLVpg8AAh6Je4>gwKlyo_v-l)JpLf+YLMX8dBwS!5+MbYTJLpm@(RM=j(zg9@ zY3n}JzWY$;5E$(@IS`Q4#MT8ju`i(U4P;moI|dh}JTazh_s-)JGdt=-KKF~^V{g!O zB=LKR0`k}u^o12Xq2WjxG3;>&PIkCUQy6C+Q0`m)~7H_TuD(j9uv z%1PnJmY^_g2@2DepeB|0Nka}nP`?;tn%YrqeJwDEXD zOMZl`*T`=3Pg&<5W4HNNitSc>-L0`rp9?t`l79<07rz}m3$hAwG2|a1?}m(9wyEE1 zXL3_}d_Lqn$VHG#AeTel1X%-l2PFS^_-1Txf!qekKimBow)w}jzsL43khKs;{?Y5k z*lr4WJftrFUDzb2{{CE4CHx`7eHPJxe)R) zNNX*={;$fX;$)l0e8{Dce}cRd@=3_;kRL#P3i&1E50DK`v36TRo((w?vIz1b$n}u? zQiI!s^kAplJ zvOQ!MNUI%R^G|%P>0sI8kQ*U4L%smH19CTH+>%ZGUOR0%+2gxG4uu>Ec^l+CkoQCC z{OfkJqzPm*$TJ{2LiUBsg)HoDk1K_o2YD&vHIO$#-Ug}j|GjqZ?P)D-fP5D6CCE1* zKZX1WvccK*xR#KUAhRKhAZI~d3V9>sU67AM@-Iz(gYBOolX~fr+U=vjTR?V%CNQOobO^Y6y-V#M(>$Uj1^fLsH)4stW(3y>c{ za$IL2UKc?w4vC-E`viJlL4FH)0MZ+11H`|G`P?A8{Wj!`!FGEtU$)$U z?QM{6L;eC;dx*8y0J0h6sgPYE`#|0X`7q>@klP`xwfOpQm17P3eF!qFT)G@*z%N~n z!QjIor$FXI&W2nJc|ByjWHzPa_#e>w0P<7F4~JR(k0I*}x7!UMkB96BIUI5fCC{?-$|4bHFW z@XG^y*Hq_s-voIJuZO%F@?J>0H@^N8IO{!v?VXUHLw*J6sD)quC zaKn8&bjd+TOiQ%>B@AN%YzJ zq#x@aMe8I=(+BOHqz>6d9m?{vN~1X)W@Keg&&rE-$ji>|(6xP!_FXy@7EJTE=5+6t z*{y54isI?TC9{j$6&4g%%xRZbTmiL$;_SkToT$^GqO7z7HabizC@(vRIPD#$w7fL4 ztfX*OG_$m1T18oTW>!v7QTuG;Z&`UxPBgcDzVWKOaz?a_k`clMc?HF}C3a&*d46d$ zD@X1w&nYlFUB@A3IXR%DsHmiva4m#u_a!U=N@i=@^^tHLaqcu)JMCv3~_r zlvSReomE%}9~`F+d14oLZLOrHPNbF_8QsJer==L-Z4m26-8$9oALcEZb9(F3(}+is zy~fFFyeoRTbB7J|UL4+M;jEd%C+!)2Yld4|?xrkq|CYSPYtqvjkao_66_;-GzNcf7 z+`5sIl9D5>8yf@O?j6Z}UpA@B%&LP~xfR2FQOAmgR+Rc;xY;w( zznhny>=|`8*-iCK`CRYpDf=TGOk5+g%*yw5l1aH0TZ|ZBR(`SlT@%cvZ#%=J`~vx# zZiVsLO|D7yUi^7Y&yH@B>f77{Rgn%!?&Y<-EkpX;;#TZ78s1`meYV@*TkUOhXV-Wu z-8<)c=~NGQ*=nHuNp2nQt7Omjz^h4q=VfA|2K<7S7zV7Ui=F5)&17yyEV?J zEHo=$d9;6Y{_bS2J*7oApm)lf1LY10^CnqxNo(!|5$9%)NlS#5T@ z93j8penq+niEYz)Tp6BWAWg=-aVcE z)Xlv*;Jj~Nh7WM(u5L%GUYCw^=dK^V>@qh$-90hVF3CH4^8QHQrn5#)ax2yk7(Q~s@Zo0< zr(+^*Qoiu}Px(qW#pHBqO62qucj1D_=}GQY5!;m}d)3|^?`(Hqybpa>wZm)PcKC3& zv58p3EshLKT0VQ^kd4cSj2P1AiI;nHUeLK+kF+Iy%;wHM3zzm8GIHq9v-=G18yS$| zzPiBl)Q6J1MH%ka1(8#dB9|wnEM4Y3@pWWS%F@N&6Zc1Yr$mQ8;dO}Sx%DFLliZ3$ zZoRL_U$@>Ox5CSH>#dJWNjb0~GBd?%cYmZ`iZ^jjq+bK~fuvmbjRosV3*6O7<&?^G z*Cf?I=DM%b_5(@jh4ZJmuPsP-A0g#e2$7uZ&Z#Cg%zZp*wTb6;Kbh;kNNSIh+FXks zqvT7pi{zjY1%rp@FC5{fdtbSy^>WkKyX|t_PaGOret%pZns4IrYQ)54?R*oLD>yFa z_;J~A2yvNe;&LIyWdq`p*1&xwlIuP;e|=8A`;wo^bzdg==zI!5q#81}$i095BKO5e zx%|(fuZ(otm?7|ZcVdm(K9^Fpyk}^goNUrZ8uj|QHOcO?H10HS+nbt} zIoLFIi0&xxpDj6N`E+se$#T23F(ND|aiZ z-FiFxHFc=fZoL|6Y}Dp*Ba>6SrC!TOIfa$PIBKjfEOR&3ii}HgpRQVTt@~uH>Isx8 zcQ@6lE*n0Pk~QvAChLpc%~it;bvM!8r)pJK+)6u#`8$_AK|9l}Ug=76klonhuB}?+ zzFKS83QBm5-A3tdNwwQ3*DaxDXbQHL6t|PfwN*8~FOkz4c^x7dDN8fFGAi3BN-Q0| z^lK_vx-A)%hDv7F)o#TOw_bH5GbOt0;$@~tL#ark{gSya(TJMlK1>d7tyM!Zcg4!6 zpVfm^y{=h-)G$&THf6|&T@zQj50dJ3Qe9hB?QW}8vw}c%hWiSUM`(|giGZx9XAE7j zJi~i?!@}OT=Jg$)(c$b4OMBl+qs8EYUsk?({w-bxRn6m7>5<#03uG+K_|i*>^hvpO z#nQKz4u5OK3(F!!7QgLgMFuCiCsw;zG}Pt#>)meFj!4&(kM_`D^8^(xE#-*~?kBY( zJ(KQmmsa(fo$Y>EE7z^2FF&qWx;(mK>9V}2`$etXA?|hL^Cwicv(BBdJUV37kb)sE zZphE@7B5@ayDYDr@iE#vNA2OnJ#0xL=8RF%87n?R5X_qpbN;!Ff zmvZ*Gmt5ry_j-H#BR3{BJKue!(p&95m|Ww&O5@W*bb@!cRi?WSC-0_cSDKM~9yRWX zwsBvW5-E%|?upa{XuzbVOih3)pVl>zMM){%#T1!&DId}L+!Siu?&{>oc}ebTmDJ)M zNT%ViGS^+3Ts?8b1WHyD@=}*Y-PbAiH74l|zb_+7d(w@PyS=i;Kj`(!-Tv+sv}YKO z$+*~T(a=kWQDPA#hLQ8@O(_~v!b^=@OzznJWja=50*&Y7P)TG$lG|v#TS6K+RGzn} zkx!yhQro7~L{r}3)R{X_JD*I2q=Kc>w9jArh3uU|ds{|ESTQvvGPQyC(k&}*aW^OL zcAu(Dztw$~;`tYo*qof6;XX-)d722xWd>D~(rzMN>I#3~W>Wx*%&r=*gLk4kqsDDi zO-jS0iq|W4*f26ghuu#he1k0BPhqjLIx^HcmXkuI^F1Bgz&pu1fgComIL|UDjc5=# z^Jw>7vT#S`Ft5YX<+m<#kECqM%U4{T;r^ApzLPBfM=~`MQZR{BBCBc7?g}F5Ox6?e zQVoLAn#wKi>*W2K%4%v29u0`pv%H}jA{4JF4cr}Mc{Rl&`!?V0)s?x++?~n8N=FQu z(qo8wFID?a(pXiQyT^Tt68DkWx01_AR@0%oNgK#tdE_`Rlsk!R`VJm^i_Kn>&8WlNX&t(O`|WI_Y)0g6|d z-(1WPb{|2rwIMRB0Tt9tVkj)jmS-=&b$QgCR%3>Vcc`#;ap~_Q{|L9JcHfJKFm)mw zmFow}*L;r(ayfw?=ty$_#gzDZI>bcM#NEWyy_8gbB$Z1E{Ft0;wrLRZCK``>(g;MW zD4mlVxCbae7gweiOrX(hd9)&$m$%gYnGU#w3|xG~u#)S~x$Yi+@R>CvZ^N1lZ`u7l zdzVeh$;dG0xgmYdJHPh}Ik)v#VMfSEt7h(h)bh%=mTbBFEN|=l>C`CNN7^=toW=j6 zO(Ol9On52Mw@GApQ*Rqx>THR$N-^_1`~M71oSx!sFnjF(Hb{8iUUu2S==`)xmb-Hn zxpi~DUg9zcNvHMj$FFkTn zGw)OH)5uB3dQW-B=M_v_cCGu;{B-xR$PSX#lf1V1m)sv|)5QClF6&Ao9h0&@UgjP} z)5DT<_f>}mmxnG|?QV0b-I2S!748>vye)6Pl{d*L;aNlxPQyVVzcDvssMb0u8W3?hD9c{#QX5}64ugx`aEw`z; zB&p>Vnk%JRZWA;Ap)GSMXSRxHDoSN~als<@(WGIMGFGg3$;+dCN$$~JFRqBU3D9-+ z`7fGf#fvYwUsuuWy`|UcZMR^{rS65^qUrO!KHscx8_<=ZSK(bf(mi3zPPdGv)mx|% z-D@M1#Sinn-H}dB+}k6Ov?TY&SyVx9-mW zxku6z%e}vfVtiko*U^2qR*hHa-dg1?dizVWP3Kp4S=APjJ2Kp>N$qQ|UEY58C&HJ| z+~KEMJKW}xsY&kX>m!|y_O`nZ&-GTneKBSFM(tep{<$=-uT7UfJKPf@y^`EZ=6XBG z>?HRZ>Na1|Aar%|qObSRQF~}U|EHvM$YGG>!=o#P=DBxYG|YY1S?}&%P~%=nQ~3j= z{QX>Sy>IC%%BF6v`x)hQ73D-%RI@3+NZX`4BHfy~bG^oe?%bL}uaoIO?taHxw3rrm zf900QNh$6n<}Qxg(4)B!orYG{-QhOOrDRRUeR*HrANAYUqjlH2FVoIPDU+A!a%N54 zMP5~;RgABZj6ge%){ice#gS)KuSCNyNxQi>i-F^lBShwcQ{A=9L=k9h3y&Ctr z+LXi2c}9Qs7H^Gv`~1j=BzKcz4!tEpx&GaK#;ta5Sg^x=v?|@3x{xJ$}*m`>pSV0@qC&hmE~Ky&}OHIce>Z?e3g zFj_+2?Kv&0EK0W;qnYJSezdHNZ{E>nR!&xVmXleQUs75=ts>Vcn-R^fcXI zZC6xYQ5@}^7cGvK7G%?AR%v#AZ@RnMu3OjCc6q7o&h6q9&B@FvEzPQoWqM&rL9tU* znVFqkGNaPES22SE5Y3z&Ey&BKyR4aHXFA>Ej2)6qcS*~mPVSt{8KosNOwlMZ=B}w@ zZ=mKDXZpn~EXgaYD2mxBs>tl<(5jPJb#}_56l8N7v?SZfD=e9oRhVhwo@s94#%-q7PyfUXK>epL&zA>DciJP&R)&vz}MoGK~ev}J}3(8HMl~j~F zxu!DwK$m6F{bO5o*(DVeVltOmTw3JVYNJpUviH ztYEI`W2TAuCHMU`O*b<$tB@LU%$%(#R7%N=z{vPL$MijOqdwDgVW+sHv?#N*BlX%M zlKCa3Mk(OF>jh46h2MdSqjRW^OQTVzv@C~WZ|a@0C?7C5q3xAB#o77vEW(M-C@Uam zoxF~j)VRofY6P7{ty2fC$f5Qcf7D>Rj|n9e&d<_#(UF|Ur>^8sODJ`6OhcfKkXckh z&oqjnw$Yb6qssHAb&L%#&M3cZMbUEV$)f-JG*f7VgJ~1`|3~oLn-w zfzF_Te46dq)Brm<=r9KdfqW{eD9p+3;`beTm{n1lO}!`6Hdh;lSoNDh${$x8zlN=k zrfw)uIniSJB8Q?Gh0!^w@X7SXm_?L2KIHmG6<5$OR~ns3P1f&Kq@_+5exJ{Y(uu32 z(lMvfvbl~KepO}q4#mbHzx5ZC(@>r5kJo;#wzFWUqhZlM|JZ26y>fCghvYaN@=J=M z9rDYl1xG7pcPOLBh}2lxWm73Tl$B=t4-)Yp&dDmAk)K7U1=E`SHmnBEfrG~P8!%+h zK#X;!O%zv@nbVsc{JKC=^YG6wX87{YZGQD0)B>`~i>A?_SeV02#WeV_wo0fumRet& z^6WhOR8oy}G$5JDfWOWoH~j44t%@o#T3SqJMZfdKGWG|N%rY}CF%7{^3(P=(Hcu&k z?4VKJjLW8<`(vRq#B}JK9NR~uWt|-J;1kaJO}wIrPS~@p)19p7>|~V|6&!L5E2R~+p%NOD z{Zo}aotug=v3D}FD@rr7OA0HBXr4ko&mlWzRP3n2$&HfF`+iHM(I$t=ote>thX564DnjK}S9ns_?YUVYxS2bll8#0yMtt<6+XYhalJyTDk8CV-T0rMZ>(z9@X zNt!#>Nt)9z>9`}0@T%#dv-#}L@!0nsq|ej(HIe@Hn_cbpub;Z4R=@hE zT~>QQ{f>*1BkSv*HlThg?IM-_^*zTKLF)WI!L#&y+8n#o9D9v9_Uc;w>z}ry_Q3ib zFH7!Mzx(1N2G*}ma_N~^{qDrbF6qRK4XH9@772StvGF1jlBWDa#Q0+X;KWB!}3OWvPz1yOQg7r>aw-wsCHa=l!`y z9Gb0WM^Ic6jSXkt%3<%PltZw$$#wsh5@gQ$wh6W#XhpWhnl?4aw5dU{HWe@Dxcb$} zZec63^$ZpCd1_RzP^;QXZFCz|#H;na(PW~bse4)mwxE@2Q-e$!joTYve_t*4&H905 z9c;?#FYU`Ji|7RHw5M~BmDIUysx(@d)t;`Q%ANN9P}F`JUDx@bJzYW+Opg{;I_*nK z%sz8;r}mwAlV$ttvhwb7qfOZ}tk_FSG zO>>q@8aDKY8O``5m*Z%|bS=F!+-f_@y3Tgbu_O$kA?!v>Ns8f&K=u~ zC3IeGX^*k_dynr##|D)@EPq?7m^)4yjz=+LJNS4rX2^w)XKCIYHfeOXnCe25pU(@t zgX72B6DeViZ_EI4EskHSI3Le4hxP{@e?f&kFjXDT_0MuPj#p~VLLC2c$-#TL+)T|o zg}P47zAXo==E7N4)3l$u^v8a4`&mp(+cZZE-(%MFE|@Z|b#EXY&CGFs`NXUpXWLCy z@>=UIqa7*>Uvuu)OLhHg-P=evDF03KZ2o+_$-e>p z>HHrhrfm{(HX#39P0Ut&(Yjk`mu5!$Pd8e+<~NRihIE7c-v|G>o{j&ejcfnkBxZ8r za#GS?hOV~x4>gTOEbJ@G_i10y@yj2x8oFQcaV+=Zc#(ylyc5T3&c`wTnG%NS{=W*x zU!VpyEbQ;?>~N^-G)*)@#PxgR!Mi^7H@TO@_k97b_k82?#Q6LOeN=DEkpD_H#hLpb z@38&cb5a>V9_6<`&$&SH6!3J#n}g>oej@k`#ZLx5P4P3phbZ0&e4*lJfnT9`Z}2&a z4*>73_)wqM@vqb6u5+QU&ok$Nr%7c>pASA-@htFa#q+>#QoIO!mEvXKD-}1Ju5)w( ztt9=13Dj|xgz(!!_^J^8KnQ;_gufiZw}|3!ygCVV7E^h4rcZqw0|?!f4+3^ULV;zwFmF8w{O+$!Gl$Pb*Fkf>Stl@ zo6{|T;<9q{XtjM;<-LDfz5R#*XF9a%)Wu)h^jRj|neJ!My0ck#v1^}n@in`2_4oSe z&c1#Zy17A`HtpN#?613Y^fj%%uhW@sijbyV_b}N+gD-wa6n|_)QH?)qj6F?>e=;cGsT~=y zPaoI||JJepAo3uhZYJ|S@!XOu;o-2cX&>~)Z-*Oa{bz^=9~pS#njxN;m6-=qcw%J7 ziy<^VnT5K8KfnIzfL)3OH|IF!^948aapudex$N>) zFi)KbZrIsEzglqfeGzQ`XKjb}nWZtN;@VEJ;JrzY?aUI~+`UyMzXq){RE_i>z#|oY%_+(|DcKJ(|;?2Nw zg&mpi48di-ZwW5j!&A7=!S&A96+6IneRaU4ijLt(*+X@Xx4Zn8nqDMj;N#W|kW3Oh2Mw+b%f`Geqt$S(Wy2V9C8|7D!l2rkFZ z$IMMaS~zYFu9M#eH+E#)n$`E!6XJgpc+8Gfb5;v|X(!p-9Hd3Zxsl+eh+MAbigW)u zP4IJs{@H>XJA8#cKyki;K38zrA14Yf`(35rQvYJXO(@y_I|P^dPYW)`lT=LhO?+hg z93Xg_us;%9x3{s1ccf#?k|FHK_LePpf6`8eg&KC$fGT$o%m-%kS z4N((^!6M&(hVXv4zoYd_Likf5{PPgr2@OWupA^Ec7Ccq>zg}=zt`7y5`UeD;`kis< zZTyk?!vvT5If6_5s|A<(8$Jg zdP1GbT%44B_9O z60G0#)L?#d2!At#w>>S`PF4tiJcNG{!ke{;+fSt=$8(b4GM+O9m-=@KF7-DEF7-bb zTvA>`w^cYXwiGq7Xy zLU_u#@qDHIQG!eR`GQORn*^8ouZ8f2qvQEX{kDQj`+0&({fh;c`VR^&^>>Hx6UGGl zbJN&h{&WccIfS5yp0BijTL|A3!p}$#w$oj3S*{5o{7S*4KWjty+adgy5Z)#u*w4`+ ze0B)GErdTA!gqym`hr1!G3Q@d-qs;}WC$+_;mbn!Lm|BG`Eh^Dses#C6T!`?fO)Fm zJp}I?!Y>Hn7lrWG1vjS@&iBO0v_XrBo6NVD;5}&{>%ReB7su=(X_mTEtTcAcra#sn zAh?;rGaoCsnR+vyBX}#pHw!NH-xS=On%K^M!KIzZR8peF_-Rfltlva%S>Cfk_{0!C zJA|(kT>5jD;O11r{+yOc8?+cd&5+N$qu|oc7{Sd{j`iuw5B$a0=_~kL!Oe5D<9#j1 zxm5Ai;8zPS?Y}Iz^yi!l?14JpQG%O%YgsiXLvi*$NANzR%e++Z0fJu@!XFLc+d}xa zg12zu`F6^p4O&dO%&lDZr$lh+&lMqjrQjyUY-g3=Ckwt;@KXfe7s7uM{8XWT$~4-b z#rSV-k#fF$1efI6?PcsE$Kl=-Arc}(&6CaZdTZ()?6ug7r`vjN%H_9a?T1*@|(jWWZTW}fYae_-b z7l!as!KIxgf=fHE3qC;j^H~T_&m$#TjQ?h8&;DO1xIE8Z5yI~d;m?Nf4?}pPeB+Jv zM~+J;3NFhvAcW5sT-v!xaB1g}5dN;L%P)=Nd?&aZKfMC0VB#qO!nCS>jdvA_!ol9c6-81b59uQpS+q}%? zWa269v;f!TJxTFq_HgGkVMpfMS#X(ew&1e9W-0qH?^G$y`Tjw1)?cbP>))(6$MX)w z+0MP-CO+mCEZ5^Bg3EGk39++ParWmmoLG&0IbOZpz*;&P|av;z+@dqx3dx zH&T44F-XhNR`_;|{=-LD&>TF8BvgIU2HdoVW05w3<7c?BGrf+Xj$_VoW?{b2wcBRQ zF$?n<;G->$@$TqkxNRKcJz#&T(tiy4xV(tzzk`5bIuPS$f}>hu{At*oqwM2!<1ALZ zuPF#E)mFspbbvoA6rYRweo*lnP_D-m?~nLw4&hwS9M5}T=N+YgCG7lD@gB7;*su7@ zsQm90=XTq~G!R-i-x`!RRq=nJyloZF1@Ei)5Y)>s#s7}&35wr{e5Wbi2LAKsx!BLv zh{If^&)>Ijnd1L}_$*g^5B#rDyfgG4R(uiqm-$?;j2qY2i;8o7?NPi6<^4`^w&SAQ zTwb*iVc5A@@s|+KyA^*J@!@iFJZqZ-Et{17G>e_Biu32q-c@`n+R4X?ACK+* zirdfc(|4YreC$sN^y(`<72C%tejDoLG{w(F9L`jHE%bRD;(S{o{^u(F(-4QrinoFN z0>yh`dzRv#BR-22{|1~t|H%IQ1b(a1cj4!~iq{2yT=C1%PWXF6V)hY-*OmS`DDOLp zH-JBT6yJ~SFBN|l@sXe3H0M+9=R@EZ`}1e`zg2M_A9pL>3fuDYmBv1Qp0gkPW&52` zu91qHXIf_A&p)#M_wZ+?(vO1AS9~$}m5N`6_I$nKV=>O$t~kQxJgxYru)kCBxv0n8 zicd#{l7+W{yxduHAlW{6u%JtI3B5r?Id>OWnLH%$Q}HVhw+h84Vf$*uv(O%HR@_6}?o#}H#O*=FzlQ&RQT!dm=Y7SCz&}%b zBewsg_+I3DoCypq9G}&&(@F6wteVqX@y)P1T57 zzS|VP5&plcIG;cMu6QG~pRW}E5b^&-aUM_Vnwzb(nBC_5#r^#R#b?4!f5lmUlHxqy z%~gCG{47;`7ux3n#oHooe^C4?w9gw9KbGUHIM>T7iVs8lcPq~Qai8L!BMu&J5OCa1 zK|4%Ud<@!2TgBHvzq{hoVSk|F$tc%o#Xo`mWW`HSu5!hffG<>>`{@$Jx5NKC6h8v( zbFJbtQNK?r{s;K~BDkq;R3W7tXP44{6ZUHzeI+AYt2nks%hGD!vJ ze0RatSxTSt?I*a&w>9)fh49IW&wzfG;L^|Of*bpz4pD8{He4pT^ygY9FD7dl1 z_YvyUw+8|+kX#c$hH}-kl9wNB0e>&Rj zM8&6p=PEu4{bIV{#y*e#vjjKx?}C1n;6|U%3zrFQ^mEZ4uM*to^ZDWi!Hxb-=>JJ@ zqtEA&y9GD;-7${cC%Dn)^UGs`8~y8`zd>-L&-2OW1vmPyK>t;6Q+=kYZbLjjQ~GT& z9`0A1zu)9%#b-gEzjuP$)zjehaNn5oeG2(@Ry+mmXM*D0z>5?=AMNT7iu3nr{z>uc zp#PBKP2tb0ic^UE<$cBZdx*bRoa5QNp*@bv`xfkXQT#}>!(oaK0?!oOgtDVG>l7*u z?93P3gzQ|{xlVDOKi?y`vCsEWA5)y|zaY4=Uk>~4f%Euw73zJD(&z6jt%U~w+)nmD zzmeiYaGq@{xM}A+E~N@?+WA80w^#g3lMyYw6`u~?UvOie$E6X18~cBQ{v^dCnBU|p zUItzyxUtXU(rm$v{qLbaPjI6j8c--l|g-?}G8LkKz--(-qGHFID_n@H-V>557_H*TCOYydK*BSBf_WuZw}5%asd$ zqTmK`|81+d9~4@36WsLQ8L%@9+%#`fUniK1XgN>mFNA)D&^PsXG0q2z6@LQ!3c-zi z?!VUxZtQ#s{o56vk8$=P#UBQLTySHb`|oDKjr}Z)N3SWq8hn@HC!n3}5!~44{<}|b zW4{Xe-w1B>x&QK43Y$0^{YI$o+D&MK7LGsn--d!4{UOjlT5w~Z`){h?Mt>FbPZr$h zbN}rqxY2J8zt0rh=r_Z-nkKl>&xQVI#gD}Hd63+0|AzK6OL6)QM}N6i@oJL^Eq5qB z4*L8(KWzVA@VAsce_z?(75^FfKP!GD#^Gb|K$`9QKdM9i^iupr=#LRx_Q$D;`-P%a ziQuL`-Ud61!Hs(+{`?y`*C>51*Ii1VufNs`ZbI1s<$N-PZ&kbs`mcj?9DaeHyA|(< z^Th#W=Uv$G@N$4Dm+_}D^cpG7->-D6;)|evlHzA!yRG8)V*5o|c z0oN;jHuy7&&jjBB&iOuve7{iq8nlNL#GCaW0zX}GzV02YIDcU3L#+f|WS)%wN@Y@8JXp}>GSwDN^nyZ*Q0%&AHoY1{{Z^46|a4QE!jN5O&8+(W0whT^6drvs{}XteBbGM z!Hs?y^xshYUhtiQBS}X&&c}iqJ0C#*Tg5ML;j25fF|P1ah_K-N4sPDQ*nMAAHpYtn|d^HTS)J~xE$yA=N(`j04nLMz`L=Lx}0 z)8O&sS;0-dt_s!rZ2=2EZ6F^$p32y9s5B=VXzkHIf z?u=A?W^2o*D1OYzmKQ1h7x4LtcR$7IU#&MgB3pq3ka^Y=jVcrrR-?XH79x1Spjho=?a0sfNWO;59SURC)%4S(vOeI=2a$?^z{ zBLfs43qD`*Yrr2<{7G>Bo=LXe92LK9QZBZCJx5+VP+CU%d?74Ji_u< zl^ve1{$1(wIP<08rhQI8+zy2B`sim|FSkSg1jP@4w-Vg=&*Mxx!A-v1+F1WP3vTpz zoarsN(a(VX)r!vpzX6=%{B^3eUnBHQS4nd%Un97&|1j)4uK0W4n-q6Xw|1Tb=X@_| zY54)g*Mj%JIK=wNXisH|cLu*-@de-?D}FC{ON=*ce?R#7il?H!ZwTQv;HKV9-1s^2 z8o^E6&OjUZU-1gW{RYK3jy2#Mw_UYtdsrj5iQ63QvK%Gm?$ zm-(&WWs2_vzhCi=$nRstqu?#!7u(+ee!k*ggWsTdZP={=uS>Bp?X9{t;wHF>+i_=D z{RJ zelm`~LGeEA?C~|=b!pSYZDq3MYXmoOyB2mHS9}BbCdEGje-51eoQHf5DE<<74@lN; z+1~n7ruZcA`xV~?{;}dmcd&NY9@{SjKOe_2zXkjT#n*t>fOFjTaohwqajS=NKCXBd z@J)(y{XGZH{(ORb4=COYH&A+DKkE+$FH^h{{C>sn1OHg@x4~ONvVEtMjpzA_w*g0#$tva-#(*lCOQ_gBTogYQv%9{8u=obR8IZ)?;i^OwM(7mfj{$#8@jJomb+N~>{dd3zDSmQytG7V$N#IW?UI_j)cx)U%++Gv>7&3h);_$BG zuYiA~_h&p<1rq+tN1|hj}+(ed@nf1EerX!Li=L=Aoyg(lX_c! zZcuy-_;$ss!Rw-ZvHe%U2P$5_kF~!*@sZ$9gz%@qIc}TK-(M5_7&3hw$4&8#95=;x zgYN}r{};iZR%mz39|xbTxYyT~>w3j|gTJcywcvHo9@+jD@BxaSkY?@AQ+zD= zrOoE_x9KR$w+e3J_7B8ihvH2ze|lf>Uf{dIlgLJd{+f)m_FG{5U_KQ*UGW>imn*&l z{1wI9jk5NWF%Gf)`QT}aZv>yK_>bU^hVZ|DbKF+exA|@r+{7&haoC~w5{{eVk8s?; z+5fTUB5oL;nBM}PuJ~Kv%N1`j+S++V@#)~n7!TO~qu^^V;_nZwtN~oc+ILthL_);}G-bz|$2^8fW#FDc%vB$1}E* z0bU#95c5UgeHFhKe2(I8fj^-5&%>=h8x`mN`=a9O##{XzidUUy`DfrJ{>RbZl}A|q zFU9$N#bbup{jAUL8Fo{g-w*7kIKKxtLUDfIZ=&M-Uf(pu`Te~j#rboia}*z4$NGJV z;&V{mM-~6hv-&S9&T;raasHg+u^7KOK4VM+pyd?BXGJXM-yLB6i)vZkN9q3pK1^}` z{Nn`0`SWP}`vq*j6Y?!q`uzEnxr%#;!)1!|d%4RMZwdVx#VZkqdlg>*{+Qy6!8a?; zpFiELcw5)TZJ**@!1=t&arhX#&MuZ{7kE-}M*qCGTGJPZ6p!A-d);d;2W;KmO3mkxp({XJ*Vik7Z|`}v}N`wDLK zr{MZ(px{RTZexU&a|Jj0UqFAn;70#Kj2oGP8~yiieU&4)(XUg7RtJ5V zoIh9FL~v8yftXJ>7u>{+KcCxLaHD@G^xFt-^zXs=)>Uw$Un`APwDb_%=&wcp9Voc8 zKU8p|-v<43yx>MZCyiFLWC(8b`E$oPf*bwaX|$rHKyahapI3v#c3sx8O$qfq{0%CxRRO#-^dtvR`ncpLULq zo$m!V`uzF*TDbl&hZ}vqKIQ8h<_(5eJI#f@vBUF?)`A;5&p^M8;6|V48(jrA`h310 zpm@?S+DXe0!Hu1NqCJlj+}J5aKTj9j==109vjsQ$qY;;U!HxbV41tepdjH=SnrQ5Yw9e9H!Js`zE#Cn|nX zwzYGb;!Sca@1%HE)bgH+*UPn>$3OPxQScE;e<%2PikIhEJ5v>Jm~T1%P8r)@4L&18 zzcPd`QhZ2(wSSf3yTPwheBp&w|5nBC1ixKyQ|~JqTKxwEH}&3Zy5;K>Ukd)T;>m?p z|9QcUeg3@nPQi`+Hqie-@q3G`olg}{Ew=n?#Rq`@1kU5eIJD1NIM0}RG5&0CWbHH* z-1yU|#Pa5fzX;w+@#L9S{|v=DmRjCXaG7rp!A-tTLchP_d%*{TbGgb;uIY*|0l!Z1 z{0pojpN5SsUoE(d!=s8fEVKGgDt>CYL#fpdFELmUPRF5@s-@o(WzhT=!fwth@g{ATbx!A%@E z-x-3Nd>hZPb}AL`1-<~B%lpIyR{u`LUkBf!_%QJA6z6twJmyDizfP95KS1%zF>cRP z{BH0iiaR(TJ*ap~@V_eF7W@ar&jW8Z-ulIU&H_J2@nzsSia!8;z2YCi|ECn+g7d;MQSpYCS>8kO^TGQo{xSGy#mg?Yb}|(IJNPuk zXIx?Rrz`#gc%|Y~ueAD$6kiN}mEs$~uT#A4AFQ2Q6~7+*ZgB2LM`K>MS?PZR{g)Nb zxXRjpGlc(#;#;8q;s2rSOW>obuK(XGz!<<8ghEi1QAZ8Pl1S9BC`z3065 z=FSBC{r>&UN9Ue%?mf#r_uTEgmx1Y~-p`EoBW~C7tt)jYadHLIdoW$(oXPeNGw7Ev zeTG55iRo9nWzhe|^ji%2nN7MsjV`)d?*Eeg=+^;7L`Yxs)GU#71{fJ9G*%R)s?FJKvz6&-C>MJ?(qC{f}Mp$(};? z!!{S4>DKeb=O^ni=*mfz1sr}l4g`;!fN-yfn9pP@{b{=UHWPd4Zu zGkuXkU&80227`V((|=*mf5r4C40=1$-!bU_V7j04Mq;PzJDCPu*5|nf{WV^%ZZ+t# zjy+(|KjQiRoI&ry5T?m;;PM{Ke$+r<39}g zb4>5Uc|Nh{Fw-Lj{R^f~FzDXvwfrK3p2GCS27M6IZ!zf2-`8^PFz7kUG`-cJf1l~U zHt5eYUH%Q?~&r)x3Rv#$0;m(R;Cx);P(Sh|Le`dv)VHRxY6eVRd^bBC65Inzb&IQDbBL7&a^2Mqd+On=ZNpVkfey{*Sx zbXqrLzkbG`?_+yjH0aAWXggms=pQisEf?Lb_X8K5^cLQ!<^0>A-@x=Qm@a;}m-`jw z`>W6&WqPz5dLh$kyvUyB0a|aVi%#}zVZDnCdf-R8z5Kh_GVZ&WexuQT$d7gVS{L1| zH|C;~-db+|LxcV+rvI4f(%%=kzkf97A2IzMgWjL#qo-8cCH6nh@>2|YpN+a*|8D3* z4EiK)e>&5}PSJaT(S9YjpJ>p3&h%VE&gpD_xj`St^mPVZo=vz^j0#x(4gPR^qUO&LrmXk&`;!Z!!rgw zmFWi!dW7jGa9&F6nauPwgFcVxlMH$t)8`xXmzciNpr`UW`xApM``N<={c3LivO&L* z=|>FuFPVNWpYNn!@|>~IpfBP0_<=!}c)G)&|B2iG+Mu^HeZN6}k?C(3^beSx!skb^ zGn3KB+e1rZq)7KdEl%-nET?YMhraxxTmoohm zgWkgQv%jb1iJcEJz1X0?$n@I``e#gk#GntZ&~oKoDDuZMJi|v%Q51ATi;y$e{D`W>p#Vhsj`kZZqgV zV|uGWmw%V>34>nFdS5l@O)Td#gFcSw<9Xglzj`r!ra}LL`@7JfSN72MHyHFQxV=0# z7WvQmb^Aw*_FGxbs|NjErk}<0Qsk^)`8fvt-kw_ie1k6kuHjmPzKZ>JpFzJs>@?^r z*`AjT`ViLpkwKS#Utj>wSF!(V_VaLq-g$zyKhL1IaDV0BxfVI4-2Qf>{ZmZ8-=JU6 zOUvJ7&>PtPKZbQZ$^Q=ie`v7A-wEqd=yx%_@9FXM`1z$T=skFL zy!<1lY20&8a{4fX-ofp2x}h&J=x=iSo4TR@(xCf>YCHCHLqBNHKNS1Z;_VmuFoQ1r z&FO~z#ISfdb0Ql5$e`yiec4U5UF^BSpi9428+5Vf=iSi%+n|ddJ~ilK&uJs#?G$^a7<92`i9r{8CXR}iGcQBq z3k`ZM)7KmH%b4!|{T7<1OPT(<(Y}P~y+$Y3JKCU&oGT6bJuGL9K^Oaf(GC4sgD(C0 z%%J~}J+eRN_-=(B=2!Z#U@jyY8WxW0VsSAQb>j7I< zPTdop{JMSF`JRt+$BqfEI~~#sV(&$LGqMU|ozbQn0@3}-ozeZ1vWOvC_wJGCurHb} z&E9j+_pj&!jZ^)(o_|Km_P~TZ)zIpX{=XWXuL45 zv7~fTQ*P?fyK+AJGAH&}tR27?(X#gZ#+>qK*?@+oKL1v_8Yf4iWlt6~<$Iz{{sWgb z4fr#W4^Ih(2DBdzhN?m#e6I`z7UTufo>~x0d*X1gUsWV@I5$}G%;8|kUewMCrQ*9F zlm=pPpZ3P@`VR+-kL-EF2f=8)GLjl?`hiD@38P8Ke*|(qRyAo0a)N1XAoi<|bdla* z$p>0$@!uiD4-@*$ili$g>FEB~$~ZD81Q~w;eQ_wfAUBxy85%wk>C_@#Aj;Qd;wOqf zi@>h{=0>9U-WKUVJJP%dI-WvhaaAaQih@WsnNk%gB-%>Vt*Q{T_uCR_Cl-kP2-wcZ z0aApf8zMV21hawO9@$E0Ff=cehJjMjRz`{s=LC!2fosW*(8=^QH*jfFQ5Ia=Z(gLF z2wpPhRf4{!s=}8x9#gPEHn@pXBR>4qFggELW4T=bHG&4ZYji|AWgrxaS-2{ei<2J>I z$ZYO9{C1E~?HJ`_HVTT!@S;eVOxYG`)tYujHUbdonhQn^9(O>wXrLfSR#!%#s)h2yKL+v#GG+dsJod8p%&yoe0g|Hw>{Upy|n3V%)&{~yS`#}m-fKjZG$%OcW(WpsZ5WMmt zw6-AZc8(M1;5aFY6k5p0{Hjf0riQ0ERWlQ?GBn?HE5_O5ag&ah70X+(H&AQYLQTMqwSl<(Z>pm|F){g|R_Q~MdCGcRh z(GO`^(YpP5S!BHNlEB)w;M(!j;wI$;?TALDZSh*GahlbXbENKwXT{x7O`NsQDro#E zEgJb{{zy(^RV1R$S55xH=$=l0z7Mw5t&C)P8f!{}>z+e99Iie@q}O0N{2TFsaPC(g zIQYGzM~`wmD9O_re|7+kAEn{WZ`!u6O_iJWwW8G4qS!$L#wKFEaqt8FJH~%x}uw2j1p3C25E9VB;>5t0W!l{Dv&aB{kIT#@~}2`Hhb#6Lgb5qRE0$ zavofJG`Q{@sAM0{@3q+V;EhZnP=jZ?F8gZYo)tP%QoC)yBjZwG*!Jx6>wO-1Kp z;J=CbDpUUz45_f%^QI4=+BkB8+n7?|#v`y_nsY(zHl`E5B?&B&Tk_hpflE#>uVE4^fsQ z)(2b~-SZ~K8UD{5djrP!Lr9G_{-GI#3WvPd`v{xHBJ@3mO){@(ME{)FKXYS8^M<{Q zv4@U4REBfLQZztUZm{5~#+ik|?Ks`yg#NC#?(-9ZYhQ=UX-PU(MGk;<|3Xl zb7D`bMPYXft_Xi3_KY^A!C3%Y_nX*@xZ*URQR5`sPH^yUydfNoRYk&$%ObJI5mS9~ zVqcS2qfIL#2$$_2p>hE3328^~`s~YS*(+$GS5eg<<5S(4rU`#1%^IN<5@&*7{bo!i z+!?OMZKMu<_q(gpgZ1<%A#d%6!F5pp=t>_f73(GdEF%^@_`vtM1`E~mP(T%aGg$PbssiMBT;@H%pXzbRiw}A({p2;Fb#p|@0mAtUvS;2XcRk~7u$RA6t&jCa`+>sY0A-p#(9{>O|$j~ z)cnqGoDj+QG{0`I9+CX9uP*;U4U2X(aoJEh_R9ts;p~>Yfv8B@eJ`q1M^pqUE+>9C z+;-m|*C-*(?ENA>-s(Q6$!)wejDSNpT^gaN!z^DJNvF6Asi5;Czye3}Vt?is^Q)^K zVh62`5n`#q`n!%1VqqPDD#Y@1i1{hR#;OoQFkn5UC=2JtzR76Ii+!9E`*#6sN;~)z zIvN~m<;K3qi+z7t=jM)G9PzJ?P;g~NbZqSp zt{Y~@7Hu&0{)mMjrzuaxSK9I8OSX7$+3^*|B308(a9s+AA2+z@A3Ap$B?i|5XMc3X z*G(VLj@@_(5;VPHPsHjYdkMXjJQUo1EBPi5X`A}UCXj>cetQCbNZ=49WA=1n9zPP< z3UYLReT1@W0dnL>SNNQG_}j>3NOHnq#v?Xc8u(*A7`k8 zUoUw9LMgqZ^JF1E6Xys zc66C8)S>G-anvh*hltl5z&DPPrBtlc`M9fhLDW2z8Y`omNvNz%5yI3aH&RTw6iV0O zJQ_s>-Sa^~J`n^GKcfp@Qx5^}CP@d@Ep|F?lcW)mP~58fk5oY`pf3DR!{j8-g78s; zB0AXwBP$jRC1mN)0~Q@r`8u7Y(>loVr`UWbLVu71D5lOyiZ3pWLMgXeZB;^+u2s_w zd!XFO(s9lS!=?OcJ3(cskQ>vUrQApwncy%wPD0v*TKy%?IT!;94Hi#%lWcSZ`8m~{ zux<-W>;^an)4X{@c^92lIXm5P^P}9 zcm=(HVdMr09CRub6I*BNwnCD0%ARjJ%{CHMumm#DA;^e=N$2KTRfTD*h|yC}nNks# ztt(byY7$LJnyS-%r0$c;I+P))_`W?-tJPy+ho}N)P_X6e8s!7kLCj3W(jY8h#iEbS z0Jo`W20(RLbuU-t!tHTL(BewfRVbqZ@QY5>K{)&ridW&};IQHhh+y66hi}rT7EtmU zE$OtKAO?_`EWny}L}Oa{;z+0fyQqZ)EjoUa+OE(NDYKAXZfPkSL~;iyv2ZiQq!M)| zq9rEuAR6Mq=OsS+D(+2C@8H^}C}D>McPs3x z(wopBO7wLAjk^FZppTs|i8~LH3W)9`NjQJ# zse<`HI>EuAO2s05zQ_#wu#P@lu)YJebW<;^>H(l@j6XV!4 z;P1*hT)8UfTtGtuXDPeTC>OHkMPq+Myg@Q~U0;=vdlL{RcZ3lRgb)k(`NP5C;5i(; zWYzQiVhAEM#Ip#eCIH!5Pbs6|O`*k{Hq9)0@oNSAy#b!K8)* z%?o8HWN6@CSV|Gw_3?*C)jh9*+zq-&E{MIU{E*XlA}s4f)23Vxl0KiWeKIG8n;Mipoyv`UfBd(fOLnEc*4cz3zgc$Nt(vRw=79c8$=00P^z6ZMgo1n`U?leDfl>3P0}gEk?>_ ztXft_;1Yfajg{ra+jwU#-l=p3*Pn>X`v!H3$e{;b1!;Kc#M!QzBSbiY3pDy)O=VQ_Zu1SjY4oD7R{kdx!vO)q@E|MKoO zDxTl{1Qk!?ltHW8+UJAy-+@6>nnqw=>d9}<%PH=8sU3^isb?kDWZIl$T4Kas$CY*? zI;P?QrHVqmNIVveJrVWQ>#f9ROx4(ana}^L*?PQrN{1Yprd=qznWjgPpFemd;_vug z(5sW`8m~^OyW$3N!T)`ilPypbAkoBCO7|XJr;szcuQMz-_vz(|A{A~5uK$$W5&iPp zxZ_&~h4L6oP~Q5#_fULL{0Dt=kbE=Z3p>B+oKJIOPtz-AIC^2`W}`rFFQSQxQoXWJ zfQ2xs=s4INKn0y)524J(gxjJUo{9o=oOduMgWEeWCiQl1k57eL^i=?-*;cj0?Bo*= zW@H#Ml1{c;QK3$E;NPV4exL?G&25VOoph**be_OFP@xEPa-~oXX~Ir*crC!nTLHUq zE~dSb&cy)fd_y2?-w7&;0VrtUDmwb6qdH4-HGm6Pr*520mC#WP zDmCij#!-}duc7lX=x|W5{=pEOh@%UvcAcJaI9Iiq{cvWQaLS*+inBH!XEl3=YH$J? z&_l@B>)0irv>8eZ3*Z>yr_(6;v;Y;PjS^YZ!-CjBZ3vEv1fbbQMgA!EUO{YIB!IIO zmF3FvO_30AO8-_64j#Je$hn*7X@29h|&R9Tte(VW6!$B+MVnb~-Sih>9D%@SJ zMiu-<{!+|MBlf{^M9nrejiP1y@K%O;PN{vVFFjeo`cud#95K=qn^UQ;W5N1QPVBCJ z2g$GhGuMYzkKFvbRQ=CUj}fDwBQ;qxX5Xl0Hg#vD2DNwr6q9Nzn9SN*yOvZ&o3=#? z5rWc+CP%bsXC$45wF&bbb;0dAMsW4b?E3qwI7D%{!bR<9kLBkP$)xHm0%mU}1Rzvs z*5iT21` zc&ys>o3~(YlV7b!bTSLj-SLoWk0S$}M>!nP3AKaZ4v9Bqa%1l*kErXT2I)jfQ@ayA z2K)-n_?aj_r86%w$HO3HMx;p^EfVKPbg*j5~=60 ze(=-^Lxbzy#e*jdCb_x*3pdT`X|d1XhP|k!4=wmE^tc!Cejiev^i zx1HY6P#<|3tzbKiZe1tcJD+g~d%A|=;6zo=9iclG+HFBC?p|ou28cGdFs`3rz!)h1 zTAX7L18@cK=GsS;bahe91_1}{-}Z(!JPOTt1*h?Kzk^?=#lDb5{{0{DeYXgA9Z1f` z3nn8vavKXe{8d$$S+;BNszjg1^2>0SKyG;>1S7;-QNUmAaC(XVF+g};Q1TdZmLvAi zS7(l=J;(D*-rBd~v#Nt+Xv7k)( zz#pURqgr|3QKWD2_7yEN@C70(hpx%xG^=oc!#faqtB*DomQ%RZLJUPrR>{u!6VD)bMzR1G#9oVfUMlcBPlPB{VvLJ*qd!Bs zHMMC#jNUA$p=II#1m`!6m>%8p4s5|X3H)&IsuBN9_j^1S-Knx^|Dxv@YC+X})PXO_ctZDc*2PC!CUE!ChO^iD|A1XzeCnjqyjP3W)e!vjoe0sBf(%8T=~ zsFPXbhT`1VmdFNF7Bp>9YiAU%aFrKOphA8|JWm4p2n{jd%k( zi`k;_*t^M>Y*mZFMn(xXBRcgXNty=8Lm)b6Y9SLh>RDQMYe^(xho)rkmb39!4wM5x zIs>OIn2NP_Pkc@T$>TofLHsL9GIa-{o(|sf3S42c(!JQl`m|1m5bH^la;9CE)(2V; zX)P&$i%X-P|BiaF@PvBeW&^|E5iIUNLU{iagfKh-f>*_DSxRGJsft?t%GV2s#|Dh@ zvpk!W6QYfI<&87Z3=dMGW&Cm&wY-U2fnmRH8MlgbUHdm$HuN(=*GdqLy=S*}7K5$~ zw1$0a_yq;P=wt`oH4pfv49{OnU^m zNd+3l>64C=$UmNCYbtwOE!WrzOXQ)`h%U^hR+5i5xHdpO#W0;i5KQb5_cF>$E5cSy zEKCC01Yti`V?`-@g;u)hFCS|W?Pk5C#g)Arx2Q#%{6A3xqL!nk0h8&)!YEP;vZ2D(oqta5ri#QP429I8@N z9CJ!9P5ShLl_&G%j3;F-tGTO}om_+@d>^G*NDB_es134%8}`)g2|%HmtB6w)4i~Dm z7EL!YUWh3`th9E4m6lUrySP@bWRM74jf_1)v5#F{1W0P=qgv6lCZkFb1+HmLlR0?H zZ_v`7f!$BuUgR2@fpl=o;xU3eOfF~IsiA1Ryi`w{;PwrAt~O=XQDjn>V0nFh5OuszcoF?8yWs!?k`;sAM$x#=Lt>wT~^%Z17fQ;teFV6D3FY#Zu8XretHAZIym8T${|_#g8vhbQGrun zWY<+;8BpdOoTc6s&ZYx<*pB!wwCOncW*U!l!~W!PoZM(?nzYqarebkJhXcpiUe^9@9^!ufLb@~K97o9nAaI8!MuF)Z zm`d+sSD*n$_KO&I={DtPQ&9E})_c&}NGPObfkYlvO|(5?WjM7>0d>OhN^X4JEXw8}>6aEcZmmK2ZZQ z9dG_Ye=*xdmdiN-ZQ6;KR|xzSE~k!=*usLtNr$l0v&XN^K|bg`+`wtxgp9O|KN76p z-%TB6YOwyN-BiMXs>dr)Ux4T6G&)LlTqZC#f;`vdwg_5t9LsPI3f7P6rV_bLCBGNy z;V<%&8W$vI_o2eV!^tg?S^)WFA4oQb6LM+6`lkZi+^8xI)^F{msytYK3svENjEF0n znH$0D0`ow|xKKoq8`E-QZ(^UNZJ-NtwF^8>_0C8!nxO@?EH&#&ow{;5Gt!|$wV~@+ zh=#BU8xM%N9P{YuHWd(EXFf&qL&qiN5-$Cu7Uv$aO6@=cz>jF59f(8`sAgjq$pw*; zykSKIcN+jqgd?EoRRMsqM07wV^^m3}cu6*WST=#$Zd{~euhG8mdybAGx%4hh1b+^- zQ%5<{Fc65-EoG}}6bp=p$>irZRenmRh7s=a2oWchw`sDrp&8&;C`I)yec|n@YxXK@ zJHQV>-_)o*5DYIg0XBFpp;E+E3wvW;8&S3Qou2lp@)Ku_mt~ zXOxLL>NCAlO`n|L=DoO6+}W0M`d(OA3sP>wyvP`xZK?+fD#^g>5qLuy@5duYr`B;K zZ^q!*^djmo^Qj5F8c_@mdc?S{2h`K)mI4BQHa{(fxW;|t;4e|H`02n&?nSx-SH?7q z3Y;NV#y{JS!PTt>H&47%hZlHra4Ul|&=e<53qy!Y9Gx+1A(cFaHw)+rz&ACDZ`{;$ zHV+pp9@vMUp@CKJWT62CoBd8oe%)SA!T5RTVtp>8t3D!?S9JneT0Et3L@Ub7T=<06 zG~BArrCDKTZ1O2xR^rz;s|KbdJ93d>(9a>Ts{!Y^6F}cwGfJmWGW!vCDebZJ90()Q zFN(hcLm~lNIjKNJnySFt)`$^Yrv~i7B?_AeQITh%2RblwAEd(|N;AbiQ-5Vb7d>^k zDnttm1w|<=ELH7sC8hSjYZwp1g@iZg)*GQ5*hXMH0?t_A>gnnTcTef(djMRAzfOeP z5QO?QGRlrbamfC2@Ydn#-3h#Gi3j$3i0fdsq96;0gB;BerI1ztFg#v;I)vz_qd4nH z6HEfv%|*|#64;7s3(Z-&wor*{3(fc7+T}O^su<+XOi(8WwXEy=0NpFv!NK*ehY(cL z>eNVt5Sex>X2AAfL!~xUZi}O{ghC4N_e>SsQ7nFnrVi7cXn{2o^V6@5L%-!HreE9W zP&X94hU3kRPPNX_b|70y0dGyRmSaoffr4u!4hhtjs0>#CQH7zGe&F>W5L`EuR0nm5 z)%|LMXRy6%1byo07NO*l-7L*UWDcuGE1=S+>$kMy@kRn4VEiaDY4O4PCM*QUJ6K!paI2<%aTuPyk$Cw+NDdapnr@E{0Ydc&0&{OS7u;<$`|mA%KOQ!uxcCwqKG zA{hSa*8l^W{x!iN4Sz#$s)mme9L57MXtz9sq-n&XkaUfB6_TY9pF*-V(nBFpjr3GV zp+v)M#XorwB-`Mp8XhTjb4yirvqfTnS-cKOIKc(*SIQ)Wo8?pj{X+#UqAoifKC9~D2 z6P1QjQFjQnKd27@=}T<`0TSYlq@MfiNa{6#;u6o@Ei0?nL=U11&-g8?SB?WtqQ+W5 zKh!I=7wS0dmG>I7sP2S%P0c~A>Q1j|1TuUX!2wt?1SOHJUf(59ai#T|^&nu)b&V5@ zy{@GnU-fHxDbckax_%Fwt_S3PG*w2AW}}c;Q)u%2Zmkm81HkTs9Y$ z>Gt1sf+t16I=oW~6+iRN$Uu9=dm!b?C$KRLylk$OQtV%)kr_ZqW930lRWRCS;e68xd^QR3aQb}zT+Xk)@o$1=Mb9d%`s(&hX$@0 z%PKx8Lp`6OO8pG*u9T6!3fn!SeB^ZY)o6wIwX_T$EwBNNoTm`(@)#ejVr+G$LK;}l z`Mv=Vt@LE2+~cFaQP%?Qb?e#e7TxNj^)#IY+~=blq!JKH`I+w>TfonKDj)~pfN7=t zLP-nY=MM4d^rSqXDz&Cqh7MY;$!6{Kt-e134g^-CDi9!NX!=8{ zUp*)qe}^iyA9$=Dj{&Ct8ONy*96erwuR9!Sj}L%%xo{fuBMz=ycPM3u{~PpEclA_1 zU74*xSS!x(6Kh~liIsAu0#gTFWu-(E$UQzwab^v|dULj-BcQF6bCirinhR+P7Pd(kCk@J-HHjRw&w`1%M zXykl9#de1q>D0&tN>7*7gBF7mae8B4YQJCTCr1SW6b3`k!1Ao|bfM9y6@X|oPOTB( zdH`hdsY@6*O}Bpl#n5?Ze-n+=&?QynmgiP#7*_QP99p)Bi9<2tJ-0d4jY+}VDUdXO z)5$PLgX_pL4K_P$Ee?1Gf#}Wa2!PWG^o;U&e%lxS94*WnrPhx#&>x1-p;2mqIRib? z(3#9ceTq<@(mm9vi0ae@s_|K>iH0N8Ee5GyV_hxAbpwz0P~Wta2_EX4hA&cn*YHHe zYn?sk`ni_pOwY^c!Krfq(b9M-krYtI01q6xiZd{^8zt>DfOZP7&QYdkQRn3IBqY+u zv+327u(>MGZZM3xL_Vj_FzT!Pa}L1ZJsTTN4Z1~#PPRM~Nc_-CGHmEPfr@NekXrjWj$MtXlP!~mfAI_wcFm@pptGHzc+M|{Gnvj^w$8Rho#(?Rs-K5!YrAY~kJ-h zY6t_^pdk!{a!&j(+$@|2Y|eI@v)SfUffUdAh;Sy;xzXlSff3L7scQTa+j|3Rd!)himM$w)v}(|wVrH; ziOBy*PX2}!1+wg4BDY4Px!r{vAQRvP>Oy4*`p}V4p6MrGi_)J_p3A+}mZkoBi73Rd zL^JgGmuKdgso?2JK&vl~&rz`!96#0^Zm~t43@~^mTK16t8VzBG2V)lq{0P3kfD}Ui z160ZfXFd9OHr+i?2F__QrfQyrKG*}d5a>)VwWQA&Iw^au@K$Klx{%8MM!sMSHo8(T z?ez4?qA$-}yV)4p`R2RO?^&LOHmX~f8~7rxxLDUaxKi2%rFyHs!J7$fwhtavKA6+% zlT7MoHs+ZIDI-h`)n|AC3L9$#7Keyt>mJk`g(h;UE68?%oDP@JXjkHUy1!-Dnx1-~ zM|q09D?uwk$l5~x?bd+%w==dQf!v}+xyLp>etan{UdZjyvUAM_zth#Y;iiM>*f752 zvyi8*XVW)0sg4-IRU^(9ppb!amjLMb1$}QGcd>%$d-M3K@lBuc^9867;6?$~3b0;) zyBV19GXOqp_TzU*)gJ_SLV)K4c$ER$^|Ums9Hd-NLL86L+Hkj1K{MR7IM7tyscZd} z)|iOHM>QR~=EO=XBT{LdaUOkTy4uj(> z*|7-Z@mf;6`D;)yQ&;$LPU`6_3{$B4`-vZHG%E~_KHQngWlEewp*3M`9asU4SC3~T&gP%Qn!sYnt_ zlnww}l30r9nq$V&6}pXgb$79Z<--{r3Z?56M?xswq6NiUPXW`aE0ToL!%~wFN*`(+ z-G`Dd1;0pWdyittk3ptI(1@iA6}jkGlJK*I_S)=Pu=GvpZx6^2q4cI`#Iq?aEH^2; z0o|ltiMvVtT{aR$DxU>7Caqy~7-#eM(VUZpl7m!Qs>&Gr>zqjOQv5YE$E337&tPU6oCXTwUlO4VwJ z&Y2pbllYAZoPAC`hR$}Ia~DDJI;RL{GM!yE=b=Q-TH$Qft#F>+s3BO}daQmvESx)R z&aF1*G5h(6a3<4f_R|ht@~s+?WctiLS{El=r3QMw?OkdE;zVg`(EW!`YBdC(wCO%# z1eJH<$Kk()^N8(}2HPhGY)%zP@tlF7$I#hfbE;^J=bR**$#izwoT@MJoGWckibZuJ zsWsLTjOeZ8DX->=v&tLe^}!Wo`~#|<`?CTe6yF!4!vScBHIs(M?}CzUuz)}%>S3Fd2Q<7?tA z>M;V@I*D?ATC1<6-8Pu|=W97&;2j3uY2aN3e#pR&7J-nzLsVK z-)P{i2ENs!?m-RqaE&R$J;;q-7l&5GS5JnMW8&m7$lBfJ2txYR!E(v7o<1> z?;0S}X=>%s)uxXMrj|5B%$P_XU3_}9)UU$DeIx}YeFB%^$*UHj&GxtKSOrPb`>KWr zyFmDC_ucg@F#L=eNLi7;rGXe+@p$M_@W)*zrhw^jH&a~g9Mm3GwQ~j|sO1&8mNq(R zoYJC>2&XwFw<1T8jzfFWUQ@cDm+Cb-OnC&e!({&g&8@4%YwiwU22ICwjMup(UZ={o zC{KSYov)fYiQlc0oaS$7Ugf%MSHsq?G@rjEOsfJVc^s=+P(K9EdFWP{te$RV7fAh) zl`hFtQz$hFIxD0miB95d(DiY)P%J*J=@N^MhrV4b($IXvqE%=?L86bmt6d(Tz#4+h z1s+hLUldnMwq|mz+ixR5FIDvJ?u$t;iakbR!qkIL<)u&Hr;?9(J@0A1EHdzz&sq004w^EJnT|U4rwe z>t%bm)Tzsb;#l=JJ0|E{^@`IJ=PC!&=cre`6iL38&gFvZb5?V8Jl@Ip-@W?WwPS^@ z7yLktKAya0YtwkAZOifSF*Qq(jWC+OVc>5X;{T`kTXy_ubN*q-f7eT|5z%K%*)q#> z&`a+QX%tU@-m|rjDML2JLA9`d8p017!oTv;izrH3#RALYLQCS&tKv{C+-Ys%ldCZ1 zN4@lb1mk!k(1n)A@=*re*K+6wy7@vMy&OUxjo<2%Th~Rj8o58775?`}A+^)ObeW+R zJy@WBH<}ho_fklSzAF?xF43p*e~IK@Uxq)6N+N~m_b|QMwLDYm`5{eL`9k|C#G_!a z5w%Q9FA@-EyQ3pk00%tk13lSFIAWa@SD6{ld8w{Up@h3W){unroJP;VrnM}AHXhnb zuqXRkQq}Vm(pcd6}(w&4ic1J_CSk&C)P|D?4ZiX`3m z;8>L>q#uLx>|<5t=}Lb~XC=Cj;JmqUyoU4E9INtP5!|H(8@zu!cJ-^ruKrT06Rjq% z561&WIMUy8MCsP&-{gL$=2uDct}60@uO$n`^zPfQllF&Hdp{Bf#L%OqtCEgm31dr7 zt$(9=Xg(_yhkoD$3QfZ&bUl>D(>p~M+!$YTq#KTtuwSXp$8pd}Y`U)DGm#2LU(1da zaw(|V;CEjN;*Pp>BlrQct7(860mVEWPODso(P?cPx1*{4MQNnth;_3#Dbpv0Bol z<38QeK4?Ooaw?zy;Z(kJ#Jxa3etL;H)vBeI6_=mr`VPZjs59zxvZt5)WT|sC@5JQ< zm#(w;oV|} zW#1}7p8TIFBR{>tEN;)z9PN!nnp)_NIh)DcDY~UxK{=wom2+;?tomqSPEwvg3A1j; zw_w83<4gmJxHqIb-zG%KFQ2oPYS-Wpy&n#QG(p_{BKk0(f@ZSw7HeExqov}LW7N|bvCmlDb$2#^=x1xXp^<8`r6z6=spcxF ziCclFrb=qIE_aO~)vS{m90;A!rJAi$gVBwzc}!}s(!|%iDm6#ku|m>5m6|S-Cp3;H z1;e%s~80{8;=}s@zsa#YD*MF6Z@>@FNv`+nffHc3pkA^&(Zova% zdd|1bFYg=Bb3PY_jFSsP#>s^t%Q&s~$7P(>`&}6) z;nW!?!8qfzUi~2iX%w~MO_gz4uX@87CqZ<^Nn=jN$&sWoPFjzXaS~iIPJ&CuNpQ(H z2`(8Y!6oA)xMZ9JmyDBRj@dD{Kb?$|bVo8yf=k9paLG6c&Kaj4+QX$XPA(MLhdb<; zpp4T7rztW{4yH3kclve4=>TVt?)2-7Q*Di||B+v3m?Wd5aT-9Z=7^gg+tM|Dm#zDF zxXLh@&F?nYe`1I~9=^$rFRgyFA+OaRmr=UkAD2=3siEyxhO`F^X}|Z!rI1`GrI1`G zrH~v{o7O$9Qb@b}@hPO;{`eG9xJP^nX`K37eHxR*6w*UIMj;B5r;u*WmWA0_1SwZ^ zm!c=6kai@JDOJ>-L<-S6neJYtDTVa8rvHahNK?@}eQ3hwVm_rSkE=8?8BMzK*eRrT zmY`Eey!Gi65?zy#LaJSXt7x(m(!b+asdYE02}&Uim?Yaj-Odv_GUnLTmmjm1^;eKuK8cuxX*wqJFw0;t5@b;Xnhc0R5na8Tk=1O%&bq~6_0qROz8LP>aI`4-= zb&>=RHQy}F+f;K&B6ZvTZ_>V9wU;DPA4usu*?+*zxVXffTO|-TE)CH7}*lX*&L5$L&J8e10W0a{1hV zg+|TyxPYVUXBw_{SX{e#@7*zzuAdWC+k{u=mmO0CCZ#Xst>k~}gNcdD`d0dZAPxS~ z0e?jx0+{|xpZ=ha{_-I@5~%TIp|2R1)6`3I^j8p3`i&T-4p&tAGdo3%R#01D_<@4) zzW(S^RDCHjepl!O=Ii^M`W+$oWH*1~eRpZ=;Qs(#l)S=OhEIOh|Q{&+j8pbA1C zAg(~*I>3GDZ@dG6YJgLTOn(EX>#@%8j}WNtlZ&IjhVfByhW;W3ZE)Yyyz~cQx(lk$ zC*GoN5cC&dQMwwKUu2tVuD_5wx1!FM<4U^IN9lJIqV(elE)Mlaa9Y(SrHWQYQ;X7^ zOx3qlpoe3!kN!eU4Tm1Plq;1=`U5p>l5)Fhd}5pDEt{;MgM@xeUvy7V?cs9jQy@T{ z3IxbZ&HSW?{%p(1;2951jHq&py7nj+_~)ru%BL#zMZZop4U!**Uv)HmPHvzJ zN0fd-AsX%pRPpvXUGdUn*3qdf>HDUK-ZIp&pxrz~^_h<6TD`Kt`7+$B(2+E($-Weu zGlYHrqGFm311hFw-8;A%=f*tMCmb5GLMhUY(x2zMNQrCM& zylRT1ML*A3C-(2{JMokg`*}|46&i$B%Oh6r-WP*}w=@RgZH;lg$M??fkCl+=0p#aA zp8WnE(0ZP3<@X=m8*e2B!GuVrf*POD-{U*6cW>gQb}4;rL0M?lo6#Qm{R!J(%I-~f4_?|APtN|`r@ay`U99S7{BlYQ32g0Qay-K zwj`A;0g4~8vHQ@2`f##Bdw^G=r?~onXd5yL7L(j_28UIbPwinVvaHj9*p3{U>8c3> zv1?9eAw5LM87xHEb0!N>2!0J&^+O?Nu@Hrv4I!$BBIF#$wEX@w%Gs$uo-~-cl;)V_ z#mEnH3?p>7V;Z67p6bcyf4OJopyJ*os)r+bSQ-7PW@PMU4+1Geg~X`aZpM!zrnXGN zQDwAL;GI@CH-5|1>1mur=Q({O$zz;e5}FBl7AeA?d27*kP8zJ$Uf>8cI(cDl*i606 za=b*s#*TuZIOXG1hhX>kOzznP1%Psq%X~Hb7rP1c_K|1eU^S2!JHvo%XAntmj;o81 zJjqoHbh4|*KyzI-1I;7nX7pFiox)X^ZxaVoXkTL6fFR88KS2#~w1=8d`F5SMPuE@Q zCTh~u?|6up+*M%L_Fi(LBXAnburAYeM9qY5YW`ijIcuP0hd(R0^jI?m{5=*s`gp7v z0?IGE_$~aE{$^e6e5W%g0=!oGW`RH8I*)aYfGsTFXRThdSrE~Lc8*{Tox_A|EXd4({`X1hW_Ve?t|V|bf^fzP@8hRYMJWmbiC zqXkQEv`#r?v(S69Q~IG^{Y|~oM5<2WCLXI$z*B9~PJ*~r9vGM(%ZucC= z!twO}r`sLi`Sq4Pk7o)<|E>dlFtr8T%_K~r^38%g%tSrk1$*7$)?%D*(fN_Bvq-@6 zY_2N=6hS1uC}F&$RgTSADBy2x(iMqDUE!Fcp(wYWVRID<_@GUiC7|@(V_j|v>g3j6 z*<3RPd^JTk_F5|}ffwTRLLW@f+BJfF%|vP_a7`aqLqWDMks1p8TX#c2I$aI59OwHe zTlyC^&tuINa3ix}r4?|SO`2^A2csjEtvx}PAuch01r(E#E=@n?_AlFfvu{>22g#Z>n+0+FaINhC224MY_+zP@Ypc9kr2ok# z%@(lI9{H)1M&uyKtbVi(#8C=OBW1aU@a;s2lY3;t);FA(cxFkw~e8bQQl zkF`KRF<7+|#Ib&XX}wI{qxP816!4OuZVXW!$T7x$gs&H_a`eIZMnD%|XSaOk*nHCk z{0R43E$RvZzhn~a7}GZkCJH=O!rFf$i~S{+G+hLK(N=klfR!v?ok<0G#qA+k`>^+igoL{bIrEcC%Rmer}Tr1r%eFtUEWe8*a6|Tqt0(O`0K~ z44hhb1Q91=^b;fT4i+MfF_{I-w_UTs5?BTfvJR{kL=@2B+x&#Nw`beEo-UxvUnZBe zo6UOM?qi{Vq7^1|*(NNrN4iizu}ZZw6?CxN_Z|5Hy5t{Py^7`D>Btw*B|mX3x{c+E zFFjU?fW2)`%ofmz(`!Y$NKbN=*ul2s;*}HnV4V~2cc*BQzJhAl`F_`6q?i}Zg)9u- z+OcL8i@BFe1cf6-L%X?s`T&h5n?Db6n`dl8rwiE0r2bY*1Ad#($p&N^P@L48dthcTfq=X*h;FEk( zinLT)T9JSvfaYXUX^CkdktVDjYr24LGo~k;yM-~^Hsf*ug^|oyrMu#M3!iB5Sj7Um z_}*UWaA+uzuOtcIBj18=P7*%67-xJE_02W;dRqVT{rjYm-MU8Fd#o}6rHhKS|F>bC zC#)e}=HS%>D+PI&iAvspk(&hg^s|EWnZzY6;R4@vx~_x5 zPgdT?$ZxpBdLf3r?)2i5m4i8L@6!57f>zORlS`|>r#V^=tsKqBq+@D5luYZP1g&Qd z)%|JYlKT34hKs_w4*6XHS2D?GeSghnL7ueRO_%PsbN5M{^gfNfK(rYQf5jzK2zX{h zYgH-)8O20Q+ZhagVUuPEcv7chqSp2^0W}!(>`&#bbT1uGl zxkI$bsazu91r$k0cnc^(F<0tWnO6OaMf5#Y*FcnjuDR2^%Fhwf+m4h%0bTN&6XbWW z{P!GF1$4=8SS85Y9I>!NK$rZ61o;Em?li}K0YxPGePpE=dy-?nfG+t*R+|3FWV!Pl z`2xD+Z(SwAWR}5x0bTO9t}^A1W4W^IxEHy5oaLr=l_0X%L4km>?xCZ#t7PiCtgBs> z#FW|uQ}*zRm&@x8rlo*|O!8S3H*OY0)|&vb!}uAw^M}~mfy^mLFf}`kO;227cmj8B zaf^DWEs2=*<4R=~mbNL(u+yT_UP0?Nqa8H*W7AUTRO zB&d$6*c4irHh!e1f6A-N{ z1v#IIBu-$NkHl+y{%7N}P{wB^4+X|&>qw*lXYo=tE@^lcdyX#~n6t}wTmUD

;`}WsdF|cku%7K2Z>@{lr8#m+2rWju_u+QPKIiMxHv=1rGx+NqChxXu3EZoVBw}M%Qs$n1-?nlq8qkep;fkQ9oQI!OPmP`8!p$;H}-b*;P1$9 zBt`xApAblpY9@XMI}aE=wT1)Mn%w{BRbEW>Ga%H__H_-*qyO9yk)e4M4b1JBs*U(Z zaNSG@#Gmd7^BQpa&UWz=L4#;c9N(*tUt5f<;}(NdG-!T(tB!RUm+MnBSnY3VlKp zf7bzRAm6uIm4DLtQ~Yc~W1>!|f9;bR)|zjliGOMz>Q zH|Tx|Fq)sv<>Kd{K7Uy;5OqUuMtNTmtQhqPeS9&D>c%(&{1e>UPBH+1`J{fyXE5<%2@=xuFyc2ToFnvOBu3Oeu{#7~mQc zvziloCGOEqgbjhdSOhox=U@1|tDnBNNHBYup>;79v8G@1XB59%}ERF}9XquPm& z#K!3t_wc`WTny0bQ~f=8>cW>5CSiAhPeb;_o!0>PeGKJF9Zuuu;de2V zAJO44eJdH~@SQsR9UV@7g`Rr=pGrR8(D7ZmAL92j;Q!zx_%EFV{*F%Ps7{C9-9X0z zK4`s$pW^p3!0UAQPjtiKcQ3%_>hL~te0cah3*`kmoPOIHJ^W6Ea=Q-aznubYet$xF z3*b|&_ftClKHU`G0WLj1*Wm>^?D6pX5=sx&L;Ua4O^)A{0FUbMK7EqyGziZ+9sZ6E zH|*hnflmhtoI-ggE8~UBC!#m40M8ke|0zTDx_n!p*FDHSs zfh+oU^bB;x@MPD=61+lZ0EBx8O$sH*Fi(*pHjTy8dgmsWwsl*&H*Z7_#>Ttw;6Bs6 z{faBD>Eb_&*ww$Wr}L^MsV=GzRuV|v8?V~5VPn^}f$i(NPL+<^)Y-En(baXbaYkZs z{0n82SnTZSp?~88)rk|T*~>TfZ`ruH%lHbIHf(tp#x3s}ADEjsF#bWQN={&T*ZAbA zNt2&O+EmMpjuS~{CNlB?IZmC8&TUxI_;Ru*rE-#t>ZYmbBq!2|8;Y1B!>g^uV4@=n zd2AclM0P?}cAjoy3pQ`=N+!EbX1iUPaog>J&8l6SHg#p=J?l4i$NqO|=NB4B6@~GQ zwo*~sApVIWI8D0r{4p9~$V1Gm`dkn;C8=#FUVV4<8ZAG7ujWCsRbmRsI7?wYOgS2~cVCq#ef z*Ij)>rLMtjA)D*%=_*OB(a9laL3(~jd;jsZGCXMSm3#E&Qf@fhdxNxQhYPRtV^2%q zs-7%`Zg0ho*Xoea{EzSHulc9N@ME8zHBN6qQx?Y>9RW4e_vCZg!NIJYZ+)5G(R?|B zG-$OeVh;reh6;W8+;FKEhbxfu__oV)yZ+KpLH_%gdIu#giG5w?F!o>H*~>02A3xN)dN{U&xZ;4wP!NVy z?6PW1bGv95K(Nd852b7S=sv-b7O}^5j8U@NdPZv<8f=82fnmi+_mrISW%&hwOjf=K zloLQN7sEZKT!JnA{*PWJEpff92;XI38{aiR--b70kEE-h-zwXSc-kb^?Qndr0z*3q zHq?r`#^o*QxBG~)a7zS)+2q#d`d15DcqoSzVn;JQQBjO|?S6TKa>UDe+;_vEua?E)Z70tD6di8QMNkjx zCmsE?!*QPri|d(p_*Lb|!;Y|L`m#uRfXTpCx7 zJh1)@={XNqiE|z>&juFf0rMJ+a~_JsIS*qFpLF=N!)G1-qjH2%o4OWWe<3~cqun?& zuN*7ZUm-o`r}oygQsiN;EH-YxM4a<>fjH-3hB)VKjyUTV9A0_bvOlb^b9j@(I~|@^ zj(qM&n+mT*(j%WuT2@w$^s)XJ={cVt6KDVD94`M8I2iKJ`pV5~^E!t=?(i2Ko>Ly@ zp+I`hLrHm@hY`|q9?lczxMm$b?{Ish6z8W(zxR)My~A4_-tX{X<;YK6dMLu{8>B~m z>^ks-a^#2gZ<3z#vq1+m_|NBE1M%HbX6NJc#P<<@i#X@`1BZX^@UN63UcUbMmh_0X zUKZH*$`LQ?eT{g5Bo`vxU?H*9#xLGSl>)~ zj_VY0j%(84(+;0?_`Jh2n5ZNc=iz4Mh?lPiDoKxc?fT&k<%pN{_mH0BRd+(Wm?vZ7 zRgT7iJ;dQ34E|sapqbuwgnFJ3X!YpFLeN`V*vHQAIIf4ZLbQXpTJ`Sc$GN!lOg5sr%4v8XH+@r(bNX=Ht|Hau{x*QaXs%TH-B36eV9@& z+u%Gte5&PfJzpv}f8=Mog3S}J(QT}*=yqJsU&^f>%lqvWX#qHYUfiubt|uWq@?6w< z<@Z2?p&rXUK(kYi{I*aq=uywv^lhe(IA7Pj=G0RrJ$yBLo%GED?f6BPICAy8ex&Fq8o6h>F=_gcYc`|PEU&f<)Cg6bBRJsanVxJ~${IiJL{Fk|i+x!>% ze%{)(`7c%4j&Ym+vMb^?&*gB$ZQcszalxz}o40Z);x=#P&xqT+l| - 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; ci_gogsI)xlD#c?hJEB9BynL@<@E3CI-bvi?41GMG_UC+~05Qy{lFi zb)0;2@BjP$^?g-!&Tp^1_S$Q&z1Q07oH}(!W9G6!;c&>k!lCbkif0ZDg{qbmj~nE$ zAygBp4yAG@H7@Citaun$rgchXZd`pb-*haKyJA)*H_aQHAN;vgcyBsC>2u+5&xHKQ z$Gz4)-s*II$m8DD7c0H3y_K2p)0yzT$Qj+j^++UoqPKWwmA80n49DqSZ*dO}rtZc& z*>@yTlkUX_kD`|y#fOJeDQsXN&uVyN3e$&OO_0{`tR~4yrlu=wV4=sthNIr7ry@%h z9y@kyM9=of8L3bxIVE{@+J7oJIXP+aRo>z^JTLbR&tDX;_599wEZ5kS$~DIQ?S3jg zGS?VOMegf~+}GHW!g&?mb(iK-6~})1-iJ6I;@OgVm`~i{c|A zD|Ucc%6}0IhNSZGaO0>~@2E=oha3GrpR?Pq@N$iH`5}+6#aw5+&dV)|$C8V8g}v4% zGJbQs%Jauofu=tEsFxr7M;6!m^DEO8S4B}0%{4Z7{)Cu6wsvdppxnen(cZ4H4Q$x+pMF5T@7%sjPRFhl z6+qMsx>gmrcLdb#p$GLdBX9Lg^q(KG-^0awL+KQOt~bX~84xPra$3NW8^6ylcLnY)*bZd2MpqcD8x& z4_@vP%DDMt=v}1!4Nph(NWBLEhU94G%;C;njeQ&|*hhUP_pKz%qA@ok?r+5~)#8kH z(VL`U02m6J_$qATeH;(=TDJz+L?Y7N&(UwaE%BR5*RZvhXr%iH${Lrv99i)a%AmAb zPQpLpIGrEY)HveZ#u57``tQ?d5)=KmXfLrw|D6%k>%qa$V`@};y!Q5aeqSyVADkan z<^MK0>J8e=ft3HsIom2AXU2aoU2(GvaLhl|jbFwe98WHODD0VWOk=M4ix*+&(z$VU zFqbY4A?;>gI+yhDeN6biRfcy{98VrP1{g1l&Y zB1qBj9MN3^h(6UmuNKoBapM66%Y6oNEuRI$x@JU=RoM2KD>>hi z{wM5I+W*7ktMWs~c>WR3f5*G?jeQs?{{_#_#1~3Svf#c61S^uv|Bg()?Sv2xGx1qz zf7itDTN(d=kRC}{;{h-;6-o4G{8#-Kq*BYw#>kY{rBXH(IcHM50mL=!@h01w*F7I? z2q{;YOuWt(d{8V%NI}dNyqfV}w>4@}FxZ$|#{Zz$C{{3ViWX$+#5%H>%K(h6E#9%2 zHVHABJGs_HN5(1h?gtD(*351&KM?;*ifSx5j*inly?IXKKrMiM(&@@MuR zUBZ-j6fLicuSEmEvVR6OJOc z%)zR7mpf>32fc>js`y5SG`NGPJ2-5bhn^FVBHA2}u{RudDDq`?bU%P8dquFL!Z7+} zd~s}K0Ybr=_)5oawK-T5Z*j;hhtxTw-XS4}RPg}nTo+$y!~siAQeqe$X{}~?e7!m7 zj&F3x{X78C@_3Iu>W=Tm(GADEe0O{qj+$<$%;eWe)#RiMKz5iqM+b%(-oJ_B3`Qb%bOk&iHQ&31Tstz9{@E9LNYHuf~xq#y1SY z(PzmcIq9mj-|s(@ydN!wHuK{*G=7oNu)1?c6<`$&xOKL^oRG!k7=BdNBP>P}{T&h? zX45%14F*~MB)UOFT3nFoB~qLn_jke*Crm`pX9I#^2ndEFAov7DRbDHCtFl3&7wLW% z;&==QI6VI7xVQDSL8b>Lj;xJzZvY7#a00$|Nl!!M?{_vXITTs(4%FAs{cfcD4IHHN zsW>9a7a1IQ2o)GCNJQDt=)ZCB3o|9|Wn7tvtoQ>Og1=4qZ-Qx6DnIPRMiVCVHX=+o zY(h(hlDSFoVGIr6@FkSYkN$jq$UCSVqHnJEZ>>a06agoqNkocXe&kagLXTLw;=9n& zd{{sdvdd@a~Y^B6UF!XfAqBtBaR&Y30|5PZ& z^v56TQ3wR(PooR7L;?hB7>s42&B}No^x9RG^Af31Z%0M z@?Sr=l2KDW6@kddF#i6w$dVK~54&Zo;~Cq58Pqa#!Ei(=4-EF1zEwvNj4$4SDE@`w zyfCqh*%$e5dbwfo#wG9GzKNk6b|y}dQeM=fY!0j*pIh+S!6k+I{zC^JbTjSsiZaVV zai%%4mj;jt*CG-1?B*jTqyHrLYnj}|b{1j)lQ{&(xw-MA%tM6c;~1LvAv7N+q4`*u ziAkA?gVTXiu`bfR0`p9!)-7`IZe+!eI63oUn=-V*5r<$BX@CESJv4-<-flseIojXqQ`;z{f=j=}Um2t+_#SPrhBgshg z?v=EL)+aDP+=iuTZy>8@Y>s^#1~ZZIhoYyQ@k;KFNDxJb8?eQDoLjpjfZ8v(G~>J%9U3Y(vXUzdp}Dm_rRz zOtI!fAKW1;j=uI!)5e8}8xX9F~ zSMGr77ugVy8x9DG7%w_3{s7E_+$PjM_!YzD8xL_D|DNLr{HhqX4Sfi}9unz<68nqY zH=WEw6n=j<=A#FKt;Gq+ZR;_#KRSMJnVTZN#1b!k4%)lU#H+A>&3z#`30Hyu5OC!-r6WbEW zO%4HRu8f)bi1r#AAYx;~qW@(g`R%%WH(%5K!^VGlxpU}0J^D{9=14{Sg{~9MCCGI) zM7k$n;V8P!MOa>y?@aqUBi(~Qf%%uf%&AhYa|p&wT<2$SU>AGX@BR#gj7{|62S4Ey z$AoF7<7o8Xn&>}!@QZGLl^TU*C$i#E406GbM&U>68b`gw?e@_||Kp+`-D&+O<}%iP z>qyU2j>v}FCSPcPo({OAki{pc=ehpZb!za#cbJklPIQpZpPau+H8W9BYi zo9I83_Mi2iJlJ9P_tvi_eHtl*!&Wj~afkTT7*@ihKRJ~2CmPq9_8Z|^$AP9FhuX9L z0s@F%PMeex73Ey(Gbn>=P30>q=vr66wffH-T!G<+XZ;L3>xb5}o(HX9{eoAQ8JufO zVCG6Nif;);u?po!F9AOjZd<48Ww3$eI?;E=+s5f4?kUc3=X6beWTO8O)sE2OEf338 zv~kI?+jC|Blml)+p3KEDCH}Yndl0iY|2022rHyVCIRb0{lvTu$9UMR={SNfU*r225 zxsy`4J8Dz8AB<+kYS)RJmQyN|n2pJ;FAtj7dTe6r%ZvNMY5$Y-*4HYLIjBUeC@z8# z#^5Wx+?1+Je%L*nVkuO{s*aU?@Wk*(-l%sX-8X_^vg~ERXPG99l#($l4~J!w~`l9Q__8ILz`Z6K?^&F22?_onm5w z2?U-q4`DO~rmN8te91}oEl zDoNCX{GkK}2+Gicq;cTIZRg9hXw-qA4y>>qifk=TS2jstyOr&gQVx~j-bN)t03%xV zMVv_EHEnUT2m)x17yD~!Ir-QY+LgAvT*2DfMEvG>)OP1)m_c?Q-j9PG12!`<=&H9Y~cP*jTNDpx+NlHcs8A(T8=oLC|JBl5J&@U8vD3Jr` z=#9L4{WKF+yS@h%X7&7vr}m^EGOj7Awd?*ZH%t@UzdMaEfMlnYujlJa1g zz-Kd7Pcm)-ee*NOxJ~WBg*>dd%^wI2^qw684NgVu+k8Zz4zfNfwb0^JTp?;k&KCTj zxa@m^LmyKQ0mlV^=%gs|H*vNXXQ^mC`qqK+mw{lxnEC`tU%@xp%|abnIDjnVn_7ts zKeiF0-eGf>HQ=-6vWCc#d)|lRzH`Subb3Tj(RI6gwYhj$&i@_zf9?N%{r{o=t3BTT z#r}Wi|DqVr0sfEHS#zBK8_5ZX%Z@S!==F^M11Qiu z7Mtj&xj>Exc0i&LudEM*KK_$F5UhtF!=ij2wUm*d0d1lf$wPGMQ=K0}MD!WTMFmtg z0%xUiVHC=GKk5iq^`wKfLLYMj{}+0{fvrFU0ASeyW(CtRfiS%voeM3;H3GLZ!1;01 z>tSZ_mmpkZ;?E&{K#&9g(kX6li}QkQ@O<=w6hgZ`tR8fY@O|(%;;U@Dnu!|jP4Iou z%NNoEK7rwH-pvK)1b7*_-Udfyz27XjCiqpz1I>!_6DP;v0fxrKv57;3uZ=Xrof#+F zOdDK8XE^nc=l7*6=mndP;`9k#9Hh&G+s*F8H+(H!qPRkY(Ze6Vu@#>OZn_Ph+c2`v z-1Ipdy@aD3H$jSNkK6WYV2)EJG?RcTw&NY&UNjy#zVhou`IETVKRZ^VLDf zc3~eku@x6T4$01K>#LPWgdO~f$s;ClktCcBKZ;BM5@8QDj5-|2FGKBIV`VDb>#mYF z7O#>&n#$GZvPeQSB5So=E=G>a4;eGD^@)l8lj+6Vutj_c+t~w^sIeoPf7LTMqO3&uT(p-)dUpX3i(oOL!v;`7ehXF|h z?{V0v{?UWzvPp*MC~b>3NuxEq77Sy=P~-D#+|g9$h4*q;H%Syul-omu!d*019J$>S z+-?gvO_FV(EFoCzdkhi1DQX(`ikw9|a*)DErYZg+1j4djT{$52hEr zczk|R9jqR*tS8?{RAT-!33E4F2YW{cVkkHr?<-k{?*@EulLDjS_5!mWM}Nc-qHRux zM{tOp1P{045P4pFwqedevNf_(cMArRL*OS<4>cBoY`NWvV8(kW%GfLs4%fL&ITn?L z9QZc?;;J*aazh0!Y#`f@L$^D^_Jyec)2?hzA;AX8a1c%uPZJqy2L;Cj13F4?2%D%q+Qcd=-u)(EjY9+Lx@j7eUgncZY+c8BKv z3CC}p%qroxR%i---qnWKtPPTakVb6qGd{mb$>&r7^XstMan-=c8dA3%RS3pWb+VYA ziY#3si%i9Cr`+XnbJGOmSY`x5T;S>!?h5G^b(y|`vR5e4bc4}}Q5)JA*z~$MJE7Dd zUF)_+hw7dwKCWr3Q;6r@f~UlYm;uO|YPfgktv$Rz(w4y5f_<@A0nR%Un3z+a<#c}) z<=LT_HQ4Hsff+c^T0=?fH@v4}O_I&)Dl??Q1=T>gmmLI`=0p@`J@rwtpsvWqf{0MQRwgYm>FJ09RJ%9VAHhaWCw(J1dTvg=^R%e>VxgR57Of8 zEn5aGduyC|6;eNH{LB=DL98~Wvs~T!g2`&ELFBNMg*LOhA&*&syHIz4)r&MVK^)kj zfZUE*%?25BXQJiglYX&=F&)Ua)^J-c^U_q6#l&g=6hev>3mU{S+)H|#G1qCWqo5+L zfF0DNF(@9H>P3-uo8}~+12KB!6n+bX0Hqu@rABM6(uTqsx=7|YQckXA$Z-^BZc_>C zA{SO<+b%f^KTGyTcE@#%TV;$WjzvMH@~WZX7@0Bax=cHuT#q9R)E{84Q%=A!?V8ES z_N*Fh*r1{2D!?qpUET97H6ar5jXUJ#sn-_cx{do5=sZzZ8Mx`#ttX^H^eY5 zqE2yYXN&+&)UR5?&7h+Ovv`FWA)!P;bStrw2{+iFUy@ff`BCA^KH&uWp+r+7PUNAs|yXuuoqh7WonEp47P`5Sm1{A;v8$3*t%K@uxT?^ zO8L{Ryhvna1-WtMu&Om8VkSSh%dT2epGa?gu>vdKwhV4@uK3?wXeNU@CD`iJ-_oe; zav7QWj1h#IkWrI9iY!M7fz-`FEQMK z7KnySln(cVJsM=XwtZ3 zCTbLg!o}QLl?ort;M#fzZ=gIi+>;JJA@@?7PI>+=(wk`|+l|FNVK4kgxpfyw)?2H% zyJ(EmoQkGwb{XBlE+xy&CMsIJ)c~rw372@C+UeiESH(sL;jl&yYX+en_85^YRl$Rr zf+-e)025*ta~^2pevSy+-%Aa<$cHT(Lq-p*ha=HS5@GGR0N-rK8D^K9VpVPeFGRQ; zsy7Ssy$7XkT5O7U;f#vu#3Bo~`xI)}Bo=yqxF8EivfLmucY&P+KDo=laH1Z+)L^2_ zt(Hzkv(z}R2zy)0R?9`sb{8Nc%WkuKXW^SN(nUhJwLD|@m&t(y&sLS_=Npt}xlxT< z;d+Fy#`cS2Nj*CqtWz$bI2f?gwWiu~H!4Max`ElK*laRnklfB9XcOswrwfRWrQb=7 zGy3|%PE7^}f-|>>FKm5_(yZRkztzCu{1}C6;$7r` zCVPbq$1BSSN3Qu`#b0lVYitVJ-L@xHIR;``Xa<|qSyC;EPhK%|Ht1xvEm2miX)woy z_*C0cOas)570C^EYb8>H^;8#nsfFmidxto)_~BMuRe9ZcY^25}5Hdl)sR z{uZ}_a&@*|f)E#D{@@@bnLDoT=mEM7U%?p-l^GvmEUjHGZJMwIxoGLGO9FG8Pb1lDq3i5-P{@NC9ArHrG#eh+MLD+s_vV<4 zTK|kUc=syr`@mg>5PZPZHr}^}bl;TP+&IbhtvM6-f=t{xK7{5VxW*-Gr5bxxl(`U$_Ukz|~0crE$xUWcGLm%d6LI_ciI3NgELsZVD&f$4}TYb9A)x< z@*|q|IDSMk^kUFR&ImDy%m`eXI3Ji>^}&Xg6rEpnIf!w84Y-|2;F}=Y;(iIi#SqvJ zInI$a4xp1=wB`O)*M{UW!|U zhMZ33W9j2|K0=t^eY* z>~@KhD`@9Z1Ems(p|rT++G2i+A~nQe;K=2fph6R&f&PBeHM4h-mT2T`X_Lu9pb@S& z(H5u$ck3{Z+XJ;2iiD+IXMS0OS{o#zfpn=*tg$KHVv;n3If}8u(uCWIs3v4cL(Gtd z>QFF@S3zh)r3q5*XBcXm%UC8s8f6$`5~Q+mFbUEGsM?CxQJN*^%7i9$w^r6tC~UVJ zV9A(;#;^{%CExmf^tb4=O>(prJr<9RD>XPWJ9DmNV6fbzanOB}lcjPt$QIf0mCe+r z2>>p*amtYw#n>V;b3*Q5vq$-{mHf99_O~`yO$Jn}@f&6rfqb%K`RR&KyQF1MjjL1$ zg{(Go#|6jMbtHh`!co#R_UfCqpLTK(MdCkXBDs2tTaHV6)z_IqcY&afWd9 z1M01G^%|t!QCBa-#e#!kM`yW=iBSvNV|Szme-MQOY;j$U0WFD4Ua=mfHvN{`wXSx| z)jqyG`t*&c#)T|a$e!*s%5Ja15`o?7TAV`VOs!6Rjt97bCp#2nfvHW_fF#XTSlVoc za_rX36xFk? z`tef~YIb|Ngv{#}*XAr&X(jX|qrqLCQ`kW7X4X%%!~p}XN%BagqlHRX>9;sZ*A?>F zM@52K%(EA9BzI-ltH_uWTkNV|v(AnLI8&UW6xzl8MN)8PiIfn>I4%q}Z|$KP;Np64 z=&mQ|T(V4m+XA)`5CVjO?w$h5+G;iK@~arRTG*?Tf1SIc0y`!KQ9cq;xM)e@p9+y& zfs+XwsyH5cC>#6|VAj$2**vEi`79l9U$GMyew=S4*d5>>iW5%hg-v z>Uq-g-U0QFZ||@hkd>}plhoU%>aB4uv!GZT*>JI2?driz+nyHtXO-M-ojJ(YI`Re~ zXSZ;$M*Cv)#=sz7Z^#Q0EVphNc2RGtB2(qsGhD#V7>!I@_FoKcqieXde=_|JlMF}J zcVXbRnDrM}Vfcy;4f$`1=z<~4Uyd6CqyvVFs8zUdU|7&k6EY*tYJa4`e8>Wusuul& zEE35im~95fO8}tJTirA!;0TH4^4vxqT1>CICgW0;7$P>2JaJ zW833u-0yg>i!y$D(R{J5s~x=C_0E$=bNrNhza% zMaL|SGWV%qE_S@13N7vvQqE>Mu(w^vph>;mhSeIgui)Y#1B1GoY@CkEjWRO;A!H&s zDsFN&grRE^s%){=3Q;IKCE)4{367eE?cE#fmo!`@J#@>S+r^kcwM!VrjrX@=bMjkg zZ<5>syUOgOv94t#zbtp8nhm~mUlfkgPPuj>Kj1Li1}RM#@oN^5m;4q3UR>mZhK%83 zy<0D1-@FwO5Hn0>M#6WP%lbff0Nr380K>Qor|U#6c6%QLMjf(sL%m2Y1;`!Emw>l1 z$e6T?u@<3dkhBr>3JsX6_bU~PPDy3iR&)ZP#j)W48`+{tHs;PA!JGNBP0~dtRJ%*b z4A=k_ESK|1rb$sMv-P3H7#_re9;>LZ$nA(tjIu_8x zGE?O8kuV;vg;rqb)Kmqbm?2Hnj2RmcS({8xA&t2t0dPYxslgj9*#|eNZRN7fiRp5P zm@nu6A0+ogI$W};Hx_I(AGO;*Ufbz|k>PG9L-#Nr=#U@Mg;W9`uu_3aQ-6y)T;;+F zOvElK(h$cEf|(0`m_iAhQ=M<460~3j#avRC>#Z0{A%Tx)J`f}_Tyil7vx`%M-Q)!r zsEyrF6VhlAmwaR%cA2J;q=2ePh21zV@$>pT_dM&3x#IFxx&mosobi|P?wXd#j;HY> zWDc_ts^py|@joNgPyRJ+zt*OE$){0s|vllRGpM@_9Wo*w} zG%wjk!8%PcKp8z$W=3FOYvOIqRXWJiBdnqCqc|vvKsF+#5y5W6*9r>jvwVa0U?w_( z#Kq!(9laVKW#`v5>UN@Cj7XfOhN%IAT;MQ*aMbQXv&!!WjApH_+IBNnJ4Eun3c^ z5yo6GppxDwD5*|$YF%;f$9^g$IXS?z*6=cmQ>Jh`fT>$l5BZ9EdWDls=9pfC3s(^o zr%En`V9-e~V{JJcClm(?Hn&XZprI}tn07J0SGldC-F-nZl6x0>B9*XiFs)k?OOtr!mlMa8rjGAo~R45))?wXlpzFsI1?B{uos zZog=eDzi{vbbte+71vgqI465`E|P7NNV%O(5^;_hiEKL;P#2t;X3SL=5XoH~49ti_ zx9jWz1Xp9HIIp5&F<<9y9WGGd>3TLP-77i=7fQvw;j@O-YBLra+zm}&QYG!%w6Pg| z)Z}<*y zrA8}zMYU!oB1|*02^Rxfz!=Fg$vev}7g$_547gC~RL}6yMvf@dgNvmf#u0Obw*OAK z*80qVnr7rT*D$YTA|&e8y1Kd%=Nv$2Gz)2>(p2H{9-Ta8@}M2<4Is78 z6v8sIK+HWON_gQ2UR6R81Bv~8c&*eghlVNkS&MJ4K#4Wx@}MPGB1us!`pd!utx1J|%LI=oWE4Q~)Ex5Lo>TkqIDCU6cGK zT(%*&1BZ5qUN&ppl6GSjHjrX@?$1uK; z!y4QsQ8q|P=0?VOyG;*zz>8b={e;N!uZ%xsVP8c(@N>H+m$NY9A8Z zgohq|0kpYwQpdv$oKe)pGaNmCYw~_REA)XXeBcqcDgU+0@+1E{P7H^Pe-x0R!Tldq zmJW|9#`PxfkegR}aCZnExMTuHX}F|XR9E9#X{yC+H7;c3s@3XhT)IrPgssM9PpToB z&-m{{PQ>&NQFJRWB;ri>@Dn@s*9$++TYJ_L%JYHNxL_hopCZh~Pudff95tznQDohs z67oYIsS*xNwdwreGdy2v(`8v{vCGDZH1$_Y>2#E)TR$FH8<*^*Fpfgp zX-3n<^8zn|c6kVyM(khQ7sex?yD&{F4o2j;gj=LNJTxcag0NFA=%_M7AH_I z^7!nxM4Sws&Xmn?Bj)9CFEILxm8M;MPbVK!^`_kSSL3!dHJeQGbNSEFH(Q}VWPEI2 z2g~r>DbLHB{1TocY`DN(U>f(`_s<~Ubbe1nK!cZ1DnPiFj0fkvheymE#o-=!P~6Oy z-Dwn;+aTGz}{!Nc<$AL$*QXAG8dA=x#-NBXt!QLpSEo~y;%``EPueJ}m; z*fBTIIXnM+{uJa~5os5^zdNWm2WH#P<5Zr%NQD=wX~*-w@b>}!L$pH*!^I%H_usjC z5$eHr#_!Ewk=sWoycf^2Z}9xzv9Om29+uT)dWHuS$`fRr?5;167Kg5fu9QB6sx%Lz zrDy5^J7^pb1WOgd4Y*a~)}w5z1@(l2*9z_Ln_^bYn?#~dV-X{ZY;pWw!ytjrR1vPg zueNXI-yeez)||{w9}eS}(XU7DdA0)PzvL-=VvHXG%~_Bdrq{d^>ORNKkl^D@d3}?bG%bPfU3-hDs(oXY1 zAP|zbjyFFF0Ux{N#7Ote5WdJ^s@MzYa7V*btRpPD7I!ve|A0DA(XLSs--+Fkdv;>| zi!7o4b@hQijW<#!-`|HHjN{IT;i8bp(yKs)voDilWa(_vrsa?RR2iG$^CLMc%!EM8 z2#jfzGNNd_2iDFBC36dwUWBwlj=+P|&2^Gb59C&5T^lROn%dQb?7FzR|?a$kM&0|4wh1vsh)8 znFVN*MC${rj8Cj%3-j6%Qm^~krSXy{Jv%~x_R~E z{rgeMaGKW6xr_zd_+w+naKvB#CpzM1N*oA{$9WF~&qu_VpkTBA=s6PkF8B}M6S-#; zj6}^ZGifnY<~OCgx+Nn3X0QfKSCez7sWbP*!`JRc3I9U_GjFk>HGioP5D;yTbiW2& zmo0_ojdVX5RO+$xXF;WDe@CQyaZo88^j-)W>Fzp(jhpN7Zq!k!SY#9LIgBs;x%yzCVnVs+yozm_OXYx zy8*y4kK_HM>3Nm=U2?nPN7?`~U z*MK*iUPu3XsAhi`0hJ;n5m~$t3n9GNek#b*&Jdf^2K4?Ju)qq2Ex*dQ)Ul^6bbkE1 z4G=gDKn4(E2u#_lx~$X)a8bi41U1xTLI8uXgN^rKO7%k0mm~L_Y}A0OqNb}C;EUbk z10i2H64@qW0WUiV;lgBi+N*zue)BOm4@VZ?&u6NYu@`g;?U@Z3LF0 zz6*XVUfEplbBirzlp7vcOLHGO>Oxx&d%_-!p)1GU#@zIWb_y+EuTx-P%#>8U9Qxum z$ql>-TkP;}u}%x43T2!4huSmtz(+)yEf21=*j_fnH6|GKKBgxteKIEL)ifCQBLI#! z_*P*Au(h~$H(Yw;u6M90A_e7R_ z9yoGCRj4xxF>&PHhj64kwCRtEfd4O|-;2%O&PD1b`i3|KM{k1grAPjZ0DXF^q&|97XMFB1|0_CVqX{We(@7LuiMQZDA-)2j4a*! z?y+Nwx5!{uL{|J0&iZRWYMu*5Z^`6C|JM;jO1=u$M5s1yXpECMe!i9q75S%Z&-Z=2 z_WvqSya*aV587qBuAVrukrGb861kFphBo|@$lvrV?is|zsDF-xwph^${uqatSH1nu z;V(bH%&BRr)!X;ookUJ=FPeB<0`~sL4olE2(`a5bs$0i8{Oy|-pk-N`B6s~Gs8Hsn z-_gj@pO|_t-ZTeo(*ZwyBAhQD6hkL~@5yiqh_&Z}PD~VwlX>n=e-a~N)@fT{*4xDH z{;z%@!t_S&9)ih%=66OGpMsS zdFGpAd|Ipdi@;!_pMvP%rp-U**RM9``Spv3!u!SCI25tHOZrD5t zMg6y+Gn+3Tzz_|MW6?AeiO|xq&6o(Ar=Vj8yElIyU#8{+9(9n%cX>1uN9z9|-1GA? zt*^@?^f(LoFAN_4SOb?J>X6|*k$=IF@(1IcijT%nz|ZpO7Fis+%W(2JcCgyz;=9;nMuj|IL1j-24Lon4aP;3e!k2r*@2>4HcF8(Ls!5X^{-XgvZ*8y=I;VpX|Av5ZbxsI@xEjKJbc1*4x z;Qtnk8u%Ay*wXId42*(gK*6p^_urQ(kWKJ{vIU5BBi$FE2z&-ysE|J#`3wpWgF??H zXv&So6-pD$i8MHA!~P83yw1y;`<4(gVvemdH;}U!(GyM&vo}}z%t~M}XEuWUlgkIB z$31_2tA55(1ksG)S&9%1e>cb&bQEGkM8DR3qSdodY_0>iHQP8gL{q&8LgilyvB2!W z8$UPOCTl!1BnZ(=a#Gqf5iDa1H$r(>l!m zc7jfRer#O^Q`yWEUwae<(jbC7T*7dH{XJO9fY@Ns^uP%1GPw4?6`3EaiJNQAj+NPs z?dOHx=1SZJ$t^*4yBh;(jiV4_gbKA4sH8$4wD6G7n)Yx_QUU;IzYu;692CamC;U_# zqVUGwe?SrueZVjXf`5f{Viq~#e?%;AtgH?4$B{@fW1LOCQw~i)j{n$LG8$S14|m0a z&^ZP|S{|FmC^wB*w=q0XbOz6Qb$?@Mvd>d@ejCd?#@A|Bo=<_hCYgnIn~2)WC?dqL77!(Z1eiz$^uHfo% zzboe3@v%8eX36%s+ztcxd9kdcT$#Dghf8G_1S(HK_e+YEffVa4#o9m$`c(4R7)Y_( zQuN^a_#XC8K{P4K!&KVcKMT?J<|pB@{^Q_p^F>vf>>&P!TNb~~BL?8YTg9Z`98h}j z=bO(O2&~+E#sHvy5duCFXhu*F5NMu+qW>_`4TGrvYm6j6Ko1W-Hm`HuwE1(}Z=5l0 z-rVaJ%gJ?;#Dh4*B)C7jM}##(Z~= z`PonT+HA}B+V4Jh{-9~=&tv$fYBOg(W${=;{?Na!eQ)Q+@0>e!=)nUQo<4?Lhy402 zTRym<=Jj*${OM1ABqJOOMH|j(zIWR@iGsXchHoeY?ecYpI}eQ;JNDvOV%+7E&xws3 zK5F>+wg5LH$SZuym7(y$X!uKmPpVppX-X$M)Rys22Kmb1T_;DO&}s0?p-Zcy_YS(W zx^_iHV|6TFd1-ZG`3cGDx}_&3tLqn^l&bCuS3Dd(rMezivN~~Tbqutqb7^%|66yU= z3*KFL^Yvf;5`G;Og9aIPjRV^UU`wcQkKC`}E0fsM_(|-LAXSClO8a{6>%UFBF@Dc9ReGhM)-&wCuMqC&g z-a2>o>=|>wbk5w)8N;z+o;$yDdg~8C#`b2+nl^LZ^w~40u>S|I7(`5Cl!JZ7h zU881Q*2tG|V%4qT(C$^{v}LsUj^K;!!1c!rYqx^ewi=#JrBpMUn=KDTfbB;((>iLZ&k-qU_Z7M zBL@u^>N#*F4#=xif4gn{wIv4Q`t9oHa8N=bMTy4^1-A@*(S8Tx?dm)GtpQeQdw@c- zy7?Ij{mgt7K3%^ZP)>H1dA^}&unkh*jG0Q?$1MS6+h6uqrvA4}gez^|P#ynCy2{Yo z$Br*yd*!)Ql$C_^ErSByHUJ@7LdpO?Y6Z<^T zP)K|-z8Sa*f2$XX`+#{CUu#8@SPd9o6(Ks|fiKQ$^69V(r^IK#xGoESIwQO!PQv$G zyos-@CmITg@5J|Acne-l)W$*h{u{iB$G%L|#sT>L7rcr0(D3k*I0xT*@h0A2C!EAI zz+ScZ$~X@268~=w(JyCB7AWAfs^ec3A={~vq6(QA1Ht#@^mzWC2!1^O3Nfjw<0LxS z;<173LlKK}Ug+0p7SC!B$6vB|Qwjc6i?@{EXIp$$34X4{-TZgO=UcqN0rq>8#TS;~ zV=UfPf;U>cqXeH|@wyWHyMmuwSdU%t6ic6Qfc^fy#jP0@N?R@N6%cc9y~UT6;Bzg$ zvIJjX@wFxRtrlNhg5PQJ^(FY-7T;Kc^Ryy#{JuI5`e+&2RRQ=v1mN7?l;iWS0r-{x zd`AGjCjj3YfIky}^EQBTa=sgY^IyQr(VrTC4-LT23c!a4;Cv24xpqef;Ee(J<-kA3 z_3aun$g)?!;miQ~xdHf{0r-Cnz&8fq{~mzv2*4i+z+Vc$j|Skpg}IzQE8)+|;hzCc zz3rWE^d@^E9DXf;{+j{#`2l#SO!BOA*1Q?h+h)$0HgnGWX*1`yPH&$PlEcuz5(Bo* zU4YHbz!HP8PRG2tHyFBEGqAfFNG6bQUeJME){JR4&A9#tvpP%3KDDrY?hocKm|dtT z+pbCJ|F$a!*X5L1esV`l6~sWs9Z3+ItSIZ}9zyf8F-dVBla)=-BLcUmik zp>syz^oF_D&j~S`8JgeOh74tBX2-ni=XA~t%~*JSX9%Ojy0dSdc75CQ+0z&DsEuP+ zs6TrF_PWzR^urPen_Ysa(AqY)li#=eU`G3V0b1vF+-#BAvqN?y<}3(7?NTl+Oq(`) zL3>;4`O28)%xzuJHr*yO39LPNYnLh(P!)6&UZzdEe*T>4 zbL9K_IWx_Cycp{>UzpYs-{T$?d7VoDFM2v2x;XX{i}PzF-j05`!g(u;!|zl0g$jRM z;TI`5xdcNA?rFy=m1b2FF zQGB$XZ&0|_^N$p++r71fKg5++R|)Rgy<73o?f#X*b-Pt|KSVunujR&fxWd1q@Jke~ z=_e~()2~+e7{zCM0RFJTPgnFGDO~e8%^E%B*ZA-N{IURiS^!>Uw?5?0e1g+Yv;?!u5PPlLmzs`8VM0_>WMywukRnywncImf&N-i?8vDkG6*^6|U#Y0);10 z-^p{Q!l?(o`MReBXFhRL7Lnoa2O7L%5 z{%(d*t_3fJR!n^g$ev5w0d{11y0)9YTZ!u7g0QEq-H`Vmx< z*L4;zv2rj`~>MW|FH_!`kZKS%CGf#WeM)|IaTq|`fOFW*5?fh*X?$e@Hf2; zg>Ejv-THRB;-lMLrf}Wv>k8N7_z@F?c)9U%d7Ek*l$Yvxn!>d{|H0ypkIVD?FGa7% z{bZX5Djkdo>3AI|!QH&;SA6t%{Yl|^ygpF4ZnyID z5Du@>c26$Bot;!$oO;vk)+$`Ldu6S#m$mRR96J5n7Jxq*yux+A<|$m$->Yy>{q^86ANCbpZaG z0Q?l203U>AH2(_&@U8%SLjb;0;abmMI47WA3fJ^=6t3l5rf^OF8-;87eG1p~)!!&D z&-eg*X#oCE03NmurBu)Uu=4Hh2*A%dw>*7Y0KO#ve>(slH@rOmI|A_a3fKC4UEx~) z;cpfBYxb{J-Uc% zy;^*Yqba zIc3|`^j}f9roUR@ntnw9zB2&-Bmlp#LBu&u&%aC-aV^gwg==|Ey0kp~B?{N{QxvZG z=M}E$|A)dg{c{S}^h0gpYY@cJ_%#9e&jRqL1Mq5YdhsgdKQ;hg6@c#vz|Y`jvJC%8 z0r)=#;Liu(7gH%^_|FT#e;I%u2*6JtSDycs0r))u__hH2zopCbe=Y#89bcaQssMaN z0KO*xubfbx|Cj*$rU3lD0Q|K8{ESTbcBcg3%LDKo0r*D&_#KxQ+tu^&K84dX+!;kw-i6t3xCSGcDC+EvANHGW+He!Ido|IG^5e4bRerayU7`F6bk{O$n! z9~7?nKd*2t&ryYI`o!e&@=OoF*9PEQ6t4Ndr*JJ#&DF(rHGNXyn!Ymt|5X5<$QJo) zKHm+%^8xsl0Q{8zyn1T+b}tOTrw8Dv?-lvz`Et@VMO@F9dlh~@NZdSnT;ZO=tD1{^ zG(IK(zcB#c6M#p)U!MPE0r-*t{O1a%D|7w&Cxz1mIsD%guJ!O#0RCP8KIGb>{F={o z3a1Nl?JiaL#R~t&0DMmX-XDOUGOZ{NQ?st!(-cnEvkVhINOZl&DU>RU7Pe$Q-e0_y~4RxG;{#N0%9f$u;;b$p)Xd6!OV!IdO?dZQAfY$}!mjvKz70yu0 z@&9LqGo*6(g9_L3JRE>O9e}?YfFBOPhs=Nwcu@~u$J@#Cb%pEktylQjivF?y{3?Z? zspzj!xVGn86t3;(ZxpWO`8$O()$Qc}y~1M(e^%kz{zEfCgctRo<@}<;wLE7lTwgC3 zt?+Ze%gHld;adJ5D*PLY{wE68dfuXN&1Z+gHU0An*YxoppfSAIU%f6}5`eE)I75Ed zuLl&a{o}p>{M`V2$Sg`@jYf(S46OOJ0n03RNJe>(tQt8fo(xPEO5z+Vo)Pwb$SMo!Hq5rB^mz}o}x6#@7< zg`a`;o%}l#K3w5v{D@K-IknwRRJgXcnF06~g)^n(+I?2x-%|L83fF$WY96IA@|=Tj z$LDhj*Y+7xxTYVja7{l$;a4jDw+G-41mKS;T;Ct{dyAg}-Y4PhUQd_c-?RnKD?atW zTz~r%K1$(#R=95W{SyAJ-A_vJQ*A-Td~?8gq}%kQeJgxfmuENJFe9($vwo0RCYBKIEpN{F=|V0`Myp{zWCv6oqFL{)oaSD*O)$*L)5r{47OZ zxezCK(cXwVKRZ<6dOaVhaLpe-0FxK_)Zy*s%L0XK`g;TLpD0|f=l3gI%hRK9O}}5^ zI{th~;aZ-PZZ67yxsv}3h3j_5D4a1DV;a83m*6grouqKhzggkrn;lEb+6ovm$;W~ahr0|5I58r|lyl4+2@OJH`*xA-F&IO6(@MHzZc@|`uq6+{3`+Y*8}jn z0DP^&uTbs&v%;Gc{-DCOoDT=!PY2+y2H+v*J>Q0Z7RQ(4<9;7K)XH1HzijB*OK|u5p!-X4 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$ - */ - -#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 */ From affc6697b36f3d67f73f90dd6deab2b14ceaf395 Mon Sep 17 00:00:00 2001 From: Faruk D Date: Thu, 16 Nov 2017 16:09:19 +0100 Subject: [PATCH 4/8] Update and rename README to README.md --- README => README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) rename README => README.md (97%) diff --git a/README b/README.md similarity index 97% rename from README rename to README.md index 1d7d620..b7d80f3 100644 --- a/README +++ b/README.md @@ -1,15 +1,16 @@ -SAGECAL +# SAGECAL ======= Read INSTALL for installation. This file gives a brief guide to use SAGECal. Warning: this file may be obsolete. use sagecal -h to see up-to-date options. -Step by Step Introduction: -####################################################################### +## Step by Step Introduction: + 1a)Calibrate data in the standard way using BBS/CASA or anything else. Use NDPP to average the data in your MS to a few channels (also average in time to about 10sec). Also flag any spikes in the data. 1b)For subtraction of the ATeam from raw data (CasA,CygA,...), no initial calibration is necessary. Just run sagecal on raw data, but it is better to scale the sky model to match the apparent flux of the sources that are being subtracted. -####################################################################### + + 2) Sky Model: 3a)Make an image of your MS (using ExCon/casapy). Use Duchamp to create a mask for the image. Use buildsky to create a sky model. (see the README file on top level directory). Also create a proper cluster file. From 79f3b46651cd8f8bba23fae75b2dbe7bb6ef1e83 Mon Sep 17 00:00:00 2001 From: Faruk D Date: Thu, 16 Nov 2017 16:10:10 +0100 Subject: [PATCH 5/8] Update README.md --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index b7d80f3..2abe2e5 100644 --- a/README.md +++ b/README.md @@ -40,15 +40,15 @@ e.g. P1C1 0 12 42.996 85 43 21.514 0.030498 0 0 0 -5.713060 0 0 0 0 115039062.0 P5C1 1 18 5.864 85 58 39.755 0.041839 0 0 0 -6.672879 0 0 0 0 115039062.0 -# A Gaussian mjor,minor 0.1375,0.0917 deg diameter -> radius(rad), PA 43.4772 deg (-> rad) -# Position Angle: "West from North (counter-clockwise)" (0 deg = North, 90 deg = West). -# Note: PyBDSM and BBS use "North from East (counter-clockwise)" (0 deg = East, 90 deg = North). +#A Gaussian mjor,minor 0.1375,0.0917 deg diameter -> radius(rad), PA 43.4772 deg (-> rad) +#Position Angle: "West from North (counter-clockwise)" (0 deg = North, 90 deg = West). +#Note: PyBDSM and BBS use "North from East (counter-clockwise)" (0 deg = East, 90 deg = North). G0 5 34 31.75 22 00 52.86 100 0 0 0 0.00 0 0.0012 0.0008 -2.329615801 130.0e6 -# A Disk radius=0.041 deg +#A Disk radius=0.041 deg D01 23 23 25.67 58 48 58 80 0 0 0 0 0 0.000715 0.000715 0 130e6 -# A Ring radius=0.031 deg +#A Ring radius=0.031 deg R01 23 23 25.416 58 48 57 70 0 0 0 0 0 0.00052 0.00052 0 130e6 -# A shapelet ('S3C61MD.fits.modes' file must be in the current directory) +#A shapelet ('S3C61MD.fits.modes' file must be in the current directory) S3C61MD 2 22 49.796414 86 18 55.913266 0.135 0 0 0 -6.6 0 1 1 0.0 115000000.0 From a98d6893f73f34fc98aa9703ceb3593730336eec Mon Sep 17 00:00:00 2001 From: Faruk D Date: Thu, 16 Nov 2017 16:10:31 +0100 Subject: [PATCH 6/8] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2abe2e5..8e76c92 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # SAGECAL -======= + + Read INSTALL for installation. This file gives a brief guide to use SAGECal. Warning: this file may be obsolete. use sagecal -h to see up-to-date options. From 24abfc6b6c84c7541390d35b9a1a6cc3ff2ecf93 Mon Sep 17 00:00:00 2001 From: Faruk D Date: Thu, 16 Nov 2017 16:10:57 +0100 Subject: [PATCH 7/8] Update and rename INSTALL to INSTALL.md --- INSTALL => INSTALL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename INSTALL => INSTALL.md (98%) diff --git a/INSTALL b/INSTALL.md similarity index 98% rename from INSTALL rename to INSTALL.md index 41bfa99..84a6c08 100644 --- a/INSTALL +++ b/INSTALL.md @@ -1,5 +1,5 @@ vr 2 dec 2016 23:07:19 CET -SAGECal Installation +# SAGECal Installation ==================== 0) Prerequsites: - CASACORE http://casacore.googlecode.com/ From ab2b1d66f8514e5e618511a2ca3aef23e26a6263 Mon Sep 17 00:00:00 2001 From: Faruk D Date: Thu, 16 Nov 2017 16:13:07 +0100 Subject: [PATCH 8/8] Update INSTALL.md --- INSTALL.md | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 84a6c08..6cba268 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -1,7 +1,8 @@ vr 2 dec 2016 23:07:19 CET + # SAGECal Installation -==================== -0) Prerequsites: + +## 1 Prerequsites: - CASACORE http://casacore.googlecode.com/ - glib http://developer.gnome.org/glib - BLAS/LAPACK @@ -15,12 +16,13 @@ vr 2 dec 2016 23:07:19 CET -- Intel MKL and other libraries - Get the source for SAGECal : git clone git://git.code.sf.net/p/sagecal/code sagecal-code -1) The basic way to build is +## 2 The basic way to build is 1.a) go to ./src/lib and run make (which will create libsagecal.a) 1.b) go to ./src/MS and run make (which will create the executable) -2) In ./src/lib and ./src/MS you MUST edit the Makefiles to suit your system. Some common items to edit are: +## 3 Build settings +In ./src/lib and ./src/MS you MUST edit the Makefiles to suit your system. Some common items to edit are: - LAPACK: directory where LAPACK/OpenBLAS is installed - GLIBI/GLIBL: include/lib files for glib - CASA_LIBDIR/CASA_INCDIR/CASA_LIBS : casacore include/library location and files: @@ -39,23 +41,23 @@ vr 2 dec 2016 23:07:19 CET -SAGECAL-MPI Installation -======================== -0) Prerequsites: +# SAGECAL-MPI Installation + +## 1 Prerequsites: - Same as above - MPI (e.g. OpenMPI) -1) Build ./src/lib as above (using mpicc -DMPI_BUILD) +## 2 Build ./src/lib as above (using mpicc -DMPI_BUILD) -2) Build ./src/MPI using mpicc++ +## 3 Build ./src/MPI using mpicc++ -BUILDSKY Installation -===================== -1) See INSTALL in ./src/buildsky +## BUILDSKY Installation + + - See INSTALL in ./src/buildsky -RESTORE Installation -===================== -1) See INSTALL in ./src/restore +## RESTORE Installation + + - See INSTALL in ./src/restore