diff --git a/CEP/Calibration/CMakeLists.txt b/CEP/Calibration/CMakeLists.txt index a3c04936e25cd3d4d3bfe04905c4e4733f06b4f2..c957fbcc8dabae852e589ded2558bd9148b4d4cc 100644 --- a/CEP/Calibration/CMakeLists.txt +++ b/CEP/Calibration/CMakeLists.txt @@ -2,3 +2,7 @@ lofar_add_package(BBSKernel) lofar_add_package(BBSControl) +lofar_add_package(ExpIon) +lofar_add_package(pystationresponse) +lofar_add_package(ElementResponse) +lofar_add_package(StationResponse) diff --git a/CEP/Calibration/ElementResponse/CMakeLists.txt b/CEP/Calibration/ElementResponse/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..a5bc6b53164ea50c6088698dec00a2c047517a0b --- /dev/null +++ b/CEP/Calibration/ElementResponse/CMakeLists.txt @@ -0,0 +1,10 @@ +# $Id$ + +lofar_package(ElementResponse 0.1 DEPENDS Common) + +# Uncomment to check for unsafe conversions (gcc), for example conversion of +# size_t to unsigned int (truncation). +#add_definitions(-Wconversion) + +add_subdirectory(include/ElementResponse) +add_subdirectory(src) diff --git a/CEP/Calibration/ElementResponse/include/ElementResponse/CMakeLists.txt b/CEP/Calibration/ElementResponse/include/ElementResponse/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..a97059ef3720c45a058d22dad5e7024dda6488ac --- /dev/null +++ b/CEP/Calibration/ElementResponse/include/ElementResponse/CMakeLists.txt @@ -0,0 +1,11 @@ +# $Id$ + +# Create symbolic link to include directory. +execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_BINARY_DIR}/include/${PACKAGE_NAME}) + +# Install header files. +install(FILES + ElementResponse.h + DESTINATION include/${PACKAGE_NAME}) diff --git a/CEP/Calibration/ElementResponse/include/ElementResponse/ElementResponse.h b/CEP/Calibration/ElementResponse/include/ElementResponse/ElementResponse.h new file mode 100644 index 0000000000000000000000000000000000000000..f95ba672544e94f30f6046d752f3cf73ffa1c853 --- /dev/null +++ b/CEP/Calibration/ElementResponse/include/ElementResponse/ElementResponse.h @@ -0,0 +1,101 @@ +//# ElementResponse.h: Functions to compute the (idealized) response of a LOFAR +//# LBA or HBA dual dipole antenna. +//# +//# Copyright (C) 2011 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR software suite. +//# The LOFAR software suite 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 3 of the License, or +//# (at your option) any later version. +//# +//# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id$ + +#ifndef LOFAR_ELEMENTRESPONSE_H +#define LOFAR_ELEMENTRESPONSE_H + +// \file +// Functions to compute the (idealized) response of a LOFAR LBA or HBA dual +// dipole antenna. + +#include <complex> + +namespace LOFAR +{ + +// \addtogroup ElementResponse +// @{ + +// Compute the response of an idealized LOFAR LBA dual dipole antenna to +// radiation at frequency freq (Hz) arriving from the direction given by theta, +// phi (rad). The antenna model is described in a spherical coordinate system +// with coordinates theta (zenith angle) and phi (azimith). The +X dipole is at +// azimuth zero, the +Y dipole is at azimuth PI / 2.0. +// +// Preconditions: +// -------------- +// freq: Frequency in Hz in the range [10 MHz, 100 MHz]. +// theta: Zenith angle in rad in the range [0.0, PI / 2.0]. +// phi: Azimuth in rad in the range [0.0, 2.0 * PI]. +// +void element_response_lba(double freq, double theta, double phi, + std::complex<double> (&response)[2][2]); + +// Compute the response of an idealized LOFAR HBA dual dipole antenna to +// radiation at frequency freq (Hz) arriving from the direction given by theta, +// phi (rad). The antenna model is described in a spherical coordinate system +// with coordinates theta (zenith angle) and phi (azimith). The +X dipole is at +// azimuth zero, the +Y dipole is at azimuth PI / 2.0. +// +// Preconditions: +// -------------- +// freq: Frequency in Hz in the range [120 MHz, 240 MHz]. +// theta: Zenith angle in rad in the range [0.0, PI / 2.0]. +// phi: Azimuth in rad in the range [0.0, 2.0 * PI]. +// +void element_response_hba(double freq, double theta, double phi, + std::complex<double> (&response)[2][2]); + +// Compute the response of an idealized LOFAR dual dipole antenna to radiation +// at frequency freq (Hz) arriving from the direction given by theta, phi (rad). +// The antenna model is described in a spherical coordinate system with +// coordinates theta (zenith angle) and phi (azimith). The +X dipole is at +// azimuth zero, the +Y dipole is at azimuth PI / 2.0. +// +// This function uses a set of user defined coefficients to evaluate the beam +// model. The coeff_shape parameter defines the shape of the coefficient array +// as no. of harmonics x degree in theta x degree in frequency x 2. The last +// dimension is implicit and always equal to 2. The coeff parameter points to an +// array of coefficients of the proper size, stored in row-major order +// ("C"-order). The freq_center and freq_range parameters define the frequency +// range covered by the model described by the set of coefficients. +// +// Preconditions: +// -------------- +// freq: Frequency in Hz in the range [freq_center - freq_range, +// freq_center + freq_range]. +// theta: Zenith angle in rad in the range [0.0, PI / 2.0]. +// phi: Azimuth in rad in the range [0.0, 2.0 * PI]. +// freq_range, freq_center: Frequency center and range in Hz, should be > 0. +// coeff_shape: Shape of the coefficient array, all dimensions should be > 0. +// +void element_response(double freq, double theta, double phi, + std::complex<double> (&response)[2][2], double freq_center, + double freq_range, const unsigned int (&coeff_shape)[3], + const std::complex<double> coeff[]); + +// @} + +} //# namespace LOFAR + +#endif diff --git a/CEP/Calibration/ElementResponse/src/CMakeLists.txt b/CEP/Calibration/ElementResponse/src/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..fbbf6cf0cc590054491f91aa4ef16c2b2732ccf5 --- /dev/null +++ b/CEP/Calibration/ElementResponse/src/CMakeLists.txt @@ -0,0 +1,7 @@ +# $Id: CMakeLists.txt 18775 2011-09-06 13:36:45Z zwieten $ + +include(LofarPackageVersion) + +lofar_add_library(elementresponse + Package__Version.cc + ElementResponse.cc) diff --git a/CEP/Calibration/ElementResponse/src/DefaultCoeffHBA.cc b/CEP/Calibration/ElementResponse/src/DefaultCoeffHBA.cc new file mode 100644 index 0000000000000000000000000000000000000000..c37da7e5e27a609311dcc132013ba358ab9d3eb7 --- /dev/null +++ b/CEP/Calibration/ElementResponse/src/DefaultCoeffHBA.cc @@ -0,0 +1,74 @@ +// Beam model coefficients converted by convert_coeff.py. +// Conversion performed on 2011/09/14/08:23:16 UTC using: +// convert_coeff.py element_beam_HBA.coeff DefaultCoeffHBA.cc default_hba + +#include <complex> + +// Center frequency, and frequency range for which the beam model coefficients +// are valid. The beam model is parameterized in terms of a normalized +// frequency f' in the range [-1.0, 1.0]. The appropriate conversion is: +// +// f' = (f - center) / range +// +const double default_hba_freq_center = 180e6; +const double default_hba_freq_range = 60e6; + +// Shape of the coefficient array: 2x5x5x2 (the size of the last dimension is +// implied, and always equal to 2). +// +const unsigned int default_hba_coeff_shape[3] = {2, 5, 5}; + +// The array of coefficients in row-major order ("C"-order). +// +const std::complex<double> default_hba_coeff[100] = { + std::complex<double>(0.9989322499459223, 0.0003305895124867), std::complex<double>(1.0030546028872600, 0.0002157249025076), + std::complex<double>(0.0003002209532403, 0.0007909077657054), std::complex<double>(0.0022051270911392, 0.0003834815341981), + std::complex<double>(-0.0003856663268042, 0.0008435910525861), std::complex<double>(0.0004887765294093, 0.0002777796480946), + std::complex<double>(-0.0000699366665322, 0.0005136144371953), std::complex<double>(0.0001520602842105, 0.0001303481681886), + std::complex<double>(0.0000512381993616, 0.0001550785137302), std::complex<double>(0.0000819244737818, 0.0000466470412396), + std::complex<double>(0.0249658150445263, -0.0122024663463393), std::complex<double>(-0.0917825091832822, -0.0062606338208358), + std::complex<double>(-0.0083709499453879, -0.0289759752488368), std::complex<double>(-0.0689260153643395, -0.0111348626546314), + std::complex<double>(0.0116296166994115, -0.0307342946951178), std::complex<double>(-0.0171249717275797, -0.0080642275561593), + std::complex<double>(0.0012408055399100, -0.0191295543986957), std::complex<double>(-0.0051031652662961, -0.0037143632875100), + std::complex<double>(-0.0022414352263751, -0.0060474723525871), std::complex<double>(-0.0024377933436567, -0.0012852163337395), + std::complex<double>(-0.6730977722052307, 0.0940030437973656), std::complex<double>(0.3711597596859299, 0.0557089394867947), + std::complex<double>(0.2119250520015808, 0.2155514942677135), std::complex<double>(0.6727380529527980, 0.0989550572104158), + std::complex<double>(-0.0419944347289523, 0.2355624543349744), std::complex<double>(0.1917656461134636, 0.0732470381581913), + std::complex<double>(0.0048918921441903, 0.1588912409502319), std::complex<double>(0.0575369727210951, 0.0344677222786687), + std::complex<double>(0.0241014578366618, 0.0547046570516960), std::complex<double>(0.0219986510834463, 0.0112189146988984), + std::complex<double>(0.0665319393516388, -0.1418009730472832), std::complex<double>(-0.7576728614553603, -0.0472040122949963), + std::complex<double>(-0.1017024786435272, -0.3302620837788515), std::complex<double>(-0.5600906156274197, -0.0797555201430585), + std::complex<double>(0.0889729243872774, -0.3406964719938829), std::complex<double>(-0.1342560801672904, -0.0515926960946038), + std::complex<double>(-0.0149335262655201, -0.2084962323582034), std::complex<double>(-0.0327252678958813, -0.0172371907472848), + std::complex<double>(-0.0362395089905272, -0.0661322227928722), std::complex<double>(-0.0141568558526096, -0.0042676979206835), + std::complex<double>(0.1121669548152054, 0.0504713119323919), std::complex<double>(0.1882531376700409, 0.0088411256350159), + std::complex<double>(0.0066968933526899, 0.1181452711088882), std::complex<double>(0.0981630367567397, 0.0129921405004959), + std::complex<double>(-0.0347327225501659, 0.1186585563636635), std::complex<double>(0.0102831315790362, 0.0046275244914932), + std::complex<double>(0.0070209144233666, 0.0689639468490938), std::complex<double>(-0.0020239346031291, -0.0025499069613344), + std::complex<double>(0.0132702874173192, 0.0207916487187541), std::complex<double>(0.0004387107229914, -0.0017223838914815), + std::complex<double>(-0.0004916757488397, 0.0000266213616248), std::complex<double>(0.0006516553273188, -0.0000433166563288), + std::complex<double>(-0.0004357897643121, 0.0000320567996700), std::complex<double>(0.0005818285824826, -0.0001021069650381), + std::complex<double>(-0.0001047488648808, -0.0000302146563592), std::complex<double>(0.0001593350153828, -0.0000879125663990), + std::complex<double>(-0.0000141882506567, -0.0000941521783975), std::complex<double>(-0.0000004226298134, -0.0000245060763932), + std::complex<double>(-0.0000177429496833, -0.0000561890408003), std::complex<double>(-0.0000018388829279, 0.0000032387726477), + std::complex<double>(0.0162495046881796, -0.0010736997976255), std::complex<double>(-0.0175635905033026, 0.0012997068962173), + std::complex<double>(0.0138897851110661, -0.0014876219938565), std::complex<double>(-0.0150211436594772, 0.0029712291209158), + std::complex<double>(0.0031705620225488, 0.0004838463688512), std::complex<double>(-0.0034418973689263, 0.0024603729467258), + std::complex<double>(0.0003028387544878, 0.0026905629457281), std::complex<double>(0.0006768121359769, 0.0005901486396051), + std::complex<double>(0.0004634797107989, 0.0016976603895716), std::complex<double>(0.0003344773954073, -0.0001499932789294), + std::complex<double>(-0.1492097398080444, 0.0123735410547393), std::complex<double>(0.1393121453502456, -0.0121117146246749), + std::complex<double>(-0.1217628319418324, 0.0222643129255504), std::complex<double>(0.1108579917761457, -0.0262986164183475), + std::complex<double>(-0.0273147374272124, 0.0098595182007132), std::complex<double>(0.0208992817013466, -0.0205929453727953), + std::complex<double>(-0.0002152227668601, -0.0089220757225133), std::complex<double>(-0.0074792188817697, -0.0043562231368076), + std::complex<double>(-0.0012019994038721, -0.0079939660050373), std::complex<double>(-0.0035807498769946, 0.0014801422733613), + std::complex<double>(0.1567990061437258, -0.0143275575385193), std::complex<double>(-0.1043118778001582, 0.0106756004832779), + std::complex<double>(0.1151024257152241, -0.0225518489392044), std::complex<double>(-0.0593437249231851, 0.0216080058910987), + std::complex<double>(0.0142781186223020, -0.0057037138045721), std::complex<double>(0.0151043140114779, 0.0141435752121475), + std::complex<double>(-0.0057143555179676, 0.0141142700941743), std::complex<double>(0.0251435557201315, -0.0005753615445942), + std::complex<double>(0.0004475745352473, 0.0102135659618127), std::complex<double>(0.0090474375150397, -0.0032177128650026), + std::complex<double>(-0.0459124372023251, 0.0044990718645418), std::complex<double>(0.0135433541303599, -0.0021789296923529), + std::complex<double>(-0.0306136798186735, 0.0064963361606382), std::complex<double>(-0.0046440676338940, -0.0037281688158807), + std::complex<double>(-0.0006372791846825, 0.0008894047150233), std::complex<double>(-0.0181611528840412, -0.0011106177431486), + std::complex<double>(0.0032325387394458, -0.0048123509184894), std::complex<double>(-0.0136340313457176, 0.0021185000810664), + std::complex<double>(0.0001287985092565, -0.0032079544559908), std::complex<double>(-0.0045503800737417, 0.0015366231416036) +}; diff --git a/CEP/Calibration/ElementResponse/src/DefaultCoeffLBA.cc b/CEP/Calibration/ElementResponse/src/DefaultCoeffLBA.cc new file mode 100644 index 0000000000000000000000000000000000000000..d196d2356d16a71b0f153b5364ac153bb5829f81 --- /dev/null +++ b/CEP/Calibration/ElementResponse/src/DefaultCoeffLBA.cc @@ -0,0 +1,74 @@ +// Beam model coefficients converted by convert_coeff.py. +// Conversion performed on 2011/09/14/08:23:09 UTC using: +// convert_coeff.py element_beam_LBA.coeff DefaultCoeffLBA.cc default_lba + +#include <complex> + +// Center frequency, and frequency range for which the beam model coefficients +// are valid. The beam model is parameterized in terms of a normalized +// frequency f' in the range [-1.0, 1.0]. The appropriate conversion is: +// +// f' = (f - center) / range +// +const double default_lba_freq_center = 55e6; +const double default_lba_freq_range = 45e6; + +// Shape of the coefficient array: 2x5x5x2 (the size of the last dimension is +// implied, and always equal to 2). +// +const unsigned int default_lba_coeff_shape[3] = {2, 5, 5}; + +// The array of coefficients in row-major order ("C"-order). +// +const std::complex<double> default_lba_coeff[100] = { + std::complex<double>(0.9982445079290715, 0.0000650863154389), std::complex<double>(1.0006230902158257, -0.0022053287681416), + std::complex<double>(0.0001002692200362, 0.0006838211278268), std::complex<double>(-0.0003660049052840, -0.0008418920419220), + std::complex<double>(-0.0010581424498791, 0.0015237878543047), std::complex<double>(0.0007398729642721, 0.0028468649470433), + std::complex<double>(-0.0039458389254656, -0.0007048354913730), std::complex<double>(0.0007040177887611, 0.0007856369612188), + std::complex<double>(-0.0031701591723043, -0.0010521154166512), std::complex<double>(-0.0007213036752903, -0.0007227764008022), + std::complex<double>(0.0550606068782634, 0.0011958385659938), std::complex<double>(-0.0160912944232080, 0.0703645376267940), + std::complex<double>(0.0033849565901213, -0.0244636379385135), std::complex<double>(0.0234264238829944, 0.0084068836453700), + std::complex<double>(0.0557107413978542, -0.0634701730653090), std::complex<double>(-0.0139549526991330, -0.1175401658864208), + std::complex<double>(0.1336911750356096, 0.0202651327657687), std::complex<double>(-0.0113385668361727, -0.0339262369086247), + std::complex<double>(0.0962263571740972, 0.0440074333288440), std::complex<double>(0.0313595045238824, 0.0230763038515351), + std::complex<double>(-0.8624889445327827, -0.1522883072804402), std::complex<double>(-0.0386800869486029, -0.7569350701887934), + std::complex<double>(0.0891332399420108, 0.1876527151756476), std::complex<double>(-0.1012363483900640, -0.1975118891151966), + std::complex<double>(-0.6404795825927633, 0.7568775384981410), std::complex<double>(0.0767245154665722, 1.3441875993523555), + std::complex<double>(-0.8758406699506004, 0.3350237639226141), std::complex<double>(0.2824832769101577, 0.6821307442669313), + std::complex<double>(-0.3144282315609649, -0.2763869580286276), std::complex<double>(-0.1705959031354030, -0.0712085950559831), + std::complex<double>(0.4039567648146965, 0.0810473144253429), std::complex<double>(-0.0350803390479135, 0.5214591717801087), + std::complex<double>(0.2232030356124932, -0.2248154851829713), std::complex<double>(0.4704343293662089, -0.3552101485419532), + std::complex<double>(0.9646419509627557, -0.8095088593139815), std::complex<double>(0.1635280638865702, -1.4854352979459096), + std::complex<double>(1.0331569921006993, 0.0509705885336283), std::complex<double>(0.1501121326521990, -0.5193414816770609), + std::complex<double>(0.4715775965513117, 0.5077361528286819), std::complex<double>(0.3847391427972284, 0.1136717951238837), + std::complex<double>(-0.0756250564248881, 0.0056622911723172), std::complex<double>(-0.1267444401630109, -0.0349676272376008), + std::complex<double>(-0.1793752883639813, 0.0720222655359702), std::complex<double>(-0.2678542619793421, 0.3152115802895427), + std::complex<double>(-0.3718069213271066, 0.2275266747872172), std::complex<double>(-0.1372223722572021, 0.4314989948093362), + std::complex<double>(-0.3316657641578328, -0.1655909947939444), std::complex<double>(-0.2158100484836540, 0.0614504774034524), + std::complex<double>(-0.1901597954359592, -0.2294955549701665), std::complex<double>(-0.1864961465389693, -0.0486276177310768), + std::complex<double>(-0.0000762326746410, 0.0000118155774181), std::complex<double>(0.0000118903581604, -0.0000251324432498), + std::complex<double>(-0.0002204197663391, -0.0000213776348027), std::complex<double>(0.0001477083861977, 0.0000599750510518), + std::complex<double>(-0.0003281057522772, -0.0000770207588466), std::complex<double>(0.0003478997686964, 0.0001481982639746), + std::complex<double>(-0.0000625695757282, 0.0000249138990722), std::complex<double>(-0.0000960097542525, 0.0002521364065803), + std::complex<double>(0.0001275344578325, 0.0000652362392482), std::complex<double>(-0.0003113309221942, 0.0001956734476566), + std::complex<double>(0.0029807707669629, -0.0003262084082071), std::complex<double>(0.0001639620574332, -0.0000266272685197), + std::complex<double>(0.0076282580587895, 0.0026614359017468), std::complex<double>(-0.0044850263974801, -0.0058337192660638), + std::complex<double>(0.0124258438959177, 0.0067985224235178), std::complex<double>(-0.0126349778957970, -0.0100656881493938), + std::complex<double>(0.0059031372522229, 0.0008660479915339), std::complex<double>(0.0039660364524413, -0.0100356333791398), + std::complex<double>(-0.0020520685193773, -0.0028564379463666), std::complex<double>(0.0121039958869239, -0.0059701468961263), + std::complex<double>(-0.0229975846564195, 0.0010565261888195), std::complex<double>(0.0019573207027441, 0.0050550600926414), + std::complex<double>(-0.0682274156850413, -0.0758159820140411), std::complex<double>(0.0497303968865466, 0.1019681987654797), + std::complex<double>(-0.1757936183439326, -0.1363710820472197), std::complex<double>(0.1765450269056824, 0.1555919358121995), + std::complex<double>(-0.1541299429420569, -0.0281422177614844), std::complex<double>(0.0816399676454817, 0.0691599035109852), + std::complex<double>(-0.0235110916473515, 0.0306385386726702), std::complex<double>(-0.0474273292450285, 0.0116831908947225), + std::complex<double>(0.0333560984394624, -0.0009767086536162), std::complex<double>(0.0141704479374002, -0.0205386534626779), + std::complex<double>(0.0562541280098909, 0.0743149092143081), std::complex<double>(-0.0226634801339250, -0.1439026188572270), + std::complex<double>(0.1238595124159999, 0.1766108700786397), std::complex<double>(-0.1307647072780430, -0.2090615438301942), + std::complex<double>(0.1557916917691289, 0.0646351862895731), std::complex<double>(0.0170294191358757, -0.1027926845803498), + std::complex<double>(0.0543537332385954, -0.0366524906364179), std::complex<double>(0.1127180664279469, -0.0176607923511174), + std::complex<double>(-0.0126732549319889, 0.0002042370658763), std::complex<double>(-0.0101360135082899, 0.0114084024114141), + std::complex<double>(-0.0102147881225462, -0.0176848554302252), std::complex<double>(-0.0051268936720694, 0.0527621533959941), + std::complex<double>(-0.0110701836450407, -0.0593085026046026), std::complex<double>(0.0140598301629874, 0.0738668439833535), + std::complex<double>(-0.0389912915621699, -0.0301364165752433), std::complex<double>(-0.0462331759359031, 0.0405864871628086), + std::complex<double>(-0.0251598701859194, 0.0115712688652445), std::complex<double>(-0.0563476280247398, 0.0079787883434624) +}; diff --git a/CEP/Calibration/ElementResponse/src/ElementResponse.cc b/CEP/Calibration/ElementResponse/src/ElementResponse.cc new file mode 100644 index 0000000000000000000000000000000000000000..1324bfd4be5479bbadbd685bad2362e4d4c260fc --- /dev/null +++ b/CEP/Calibration/ElementResponse/src/ElementResponse.cc @@ -0,0 +1,157 @@ +//# ElementResponse.cc: Functions to compute the (idealized) response of a LOFAR +//# LBA or HBA dual dipole antenna. +//# +//# Copyright (C) 2011 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR software suite. +//# The LOFAR software suite 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 3 of the License, or +//# (at your option) any later version. +//# +//# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id$ + +#include <lofar_config.h> +#include <ElementResponse/ElementResponse.h> +#include <cmath> + +// The coefficients are kept in an unnamed namespace which effectively makes +// them invisible outside this translation unit. +namespace +{ +// PI / 2.0 +const double pi_2 = 1.570796326794896619231322; + +#include "DefaultCoeffLBA.cc" +#include "DefaultCoeffHBA.cc" +} + +namespace LOFAR +{ + +void element_response_lba(double freq, double theta, double phi, + std::complex<double> (&response)[2][2]) +{ + element_response(freq, theta, phi, response, default_lba_freq_center, + default_lba_freq_range, default_lba_coeff_shape, default_lba_coeff); +} + +void element_response_hba(double freq, double theta, double phi, + std::complex<double> (&response)[2][2]) +{ + element_response(freq, theta, phi, response, default_hba_freq_center, + default_hba_freq_range, default_hba_coeff_shape, default_hba_coeff); +} + +void element_response(double freq, double theta, double phi, + std::complex<double> (&response)[2][2], double freq_center, + double freq_range, const unsigned int (&coeff_shape)[3], + const std::complex<double> coeff[]) +{ + // Initialize the response to zero. + response[0][0] = 0.0; + response[0][1] = 0.0; + response[1][0] = 0.0; + response[1][1] = 0.0; + + // Clip directions below the horizon. + if(theta >= pi_2) + { + return; + } + + const unsigned int nHarmonics = coeff_shape[0]; + const unsigned int nPowerTheta = coeff_shape[1]; + const unsigned int nPowerFreq = coeff_shape[2]; + + // The model is parameterized in terms of a normalized frequency in the + // range [-1, 1]. The appropriate conversion is taken care of below. + freq = (freq - freq_center) / freq_range; + + // The variables sign and kappa are used to compute the value of kappa + // mentioned in the description of the beam model [kappa = (-1)^k * (2 * k + //+ 1)] incrementally. + int sign = 1, kappa = 1; + + std::complex<double> P[2], Pj[2]; + for(unsigned int k = 0; k < nHarmonics; ++k) + { + // Compute the (diagonal) projection matrix P for the current harmonic. + // This requires the evaluation of two polynomials in theta and freq (of + // degree nPowerTheta in theta and nPowerFreq in freq), one for each + // element of P. The polynomials are evaluated using Horner's rule. + + // Horner's rule requires backward iteration of the coefficients, so + // move the iterator to the first location past the end of the block of + // coefficients for the current harmonic (k). + coeff += nPowerTheta * nPowerFreq * 2; + + // Evaluate the polynomial. Note that the iterator is always decremented + // before it is dereferenced, using the prefix operator--. After + // evaluation of the polynomial, the iterator points exactly to the + // beginning of the block of coefficients for the current harmonic (k), + // that is, all the decrements together exactly cancel the increment + // aplied above. + + // Evaluate the highest order term. Note that the order of the + // assigments is important because of the iterator decrement, i.e. P[1] + // should be assigned first. + P[1] = *--coeff; + P[0] = *--coeff; + + for(unsigned int i = 0; i < nPowerFreq - 1; ++i) + { + P[1] = P[1] * freq + *--coeff; + P[0] = P[0] * freq + *--coeff; + } + + // Evaluate the remaining terms. + for(unsigned int j = 0; j < nPowerTheta - 1; ++j) + { + Pj[1] = *--coeff; + Pj[0] = *--coeff; + + for(unsigned int i = 0; i < nPowerFreq - 1; ++i) + { + Pj[1] = Pj[1] * freq + *--coeff; + Pj[0] = Pj[0] * freq + *--coeff; + } + + P[1] = P[1] * theta + Pj[1]; + P[0] = P[0] * theta + Pj[0]; + } + + // Because the increment and decrements cancel, the iterator points to + // the same location as at the beginning of this iteration of the outer + // loop. The next iteration should use the coefficients for the next + // harmonic (k), so we move the iterator to the start of that block. + coeff += nPowerTheta * nPowerFreq * 2; + + // Compute the Jones matrix for the current harmonic, by rotating P over + // kappa * az, and add it to the result. + const double angle = sign * kappa * phi; + const double caz = std::cos(angle); + const double saz = std::sin(angle); + + response[0][0] += caz * P[0]; + response[0][1] += -saz * P[1]; + response[1][0] += saz * P[0]; + response[1][1] += caz * P[1]; + + // Update sign and kappa. + sign = -sign; + kappa += 2; + } +} + +} //# namespace LOFAR diff --git a/CEP/Calibration/ElementResponse/src/convert_coeff.py b/CEP/Calibration/ElementResponse/src/convert_coeff.py new file mode 100755 index 0000000000000000000000000000000000000000..03618ec0e31c0758365ad459748fb3bfcb6a40f3 --- /dev/null +++ b/CEP/Calibration/ElementResponse/src/convert_coeff.py @@ -0,0 +1,176 @@ +#!/usr/bin/env python3 + +# Script to convert an ASCII beam model coefficient file to a .cc file for +# inclusion in the library. Whenever the beam model coefficients file are +# updated, this script can be run to automatically update the corresponding .cc +# files. +# +# $Id$ + +import sys +import time +import re +from functools import reduce + +def flat_index(shape, index): + """ + Compute the flat index of the element with the provided (N-dimensional) + index of an N-dimensional array with the provided shape. Row-major order + (or "C"-order) is assumed in the computation of the flattened index. The + index is range checked against the provided shape. + """ + + assert len(shape) == len(index) + + if len(shape) == 0: + return 0 + + assert index[0] < shape[0] + flat = index[0] + + for i in range(1, len(shape)): + assert index[i] < shape[i] + flat *= shape[i] + flat += index[i] + + return flat + +def regex(name, type, signed = True): + """ + Return a regular expression to match a (possibly signed) int or float, using + the named group syntax. The matching group will by assigned the provided + name. + """ + + expr = None + if type == "int": + expr = "\d+" + elif type == "float": + expr = "(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?" + + assert expr, "unknown type: %s" % type + + if signed: + expr = "[-+]?" + expr + + return "(?P<%s>%s)" % (name, expr) + +def main(args): + print("converting %s -> %s (variable name: %s)" % (args[0], args[1], args[2])) + + HEADER, COEFF = list(range(2)) + state = HEADER + + shape = None + coeff = None + freqAvg = None + freqRange = None + + line_no = 0 + count = 0 + + fin = open(args[0], "r") + for line in fin: + # Remove leading and trailing whitespace. + line = line.strip() + + # Skip empty lines and comments. + if len(line) == 0 or line[0] == "#": + line_no += 1 + continue + + # Parse header information. + if state == HEADER: + match = re.match("^d\s+%s\s+k\s+%s\s+pwrT\s+%s\s+pwrF\s+%s\s+" + "freqAvg\s+%s\s+freqRange\s+%s$" % (regex("d", "int", False), + regex("k", "int", False), regex("pwrT", "int", False), + regex("pwrF", "int", False), regex("freqAvg", "float", False), + regex("freqRange", "float", False)), line) + assert match, "unable to parse header: \"%s\"" % line + + shape = (int(match.group("k")), int(match.group("pwrT")), + int(match.group("pwrF")), int(match.group("d"))) + assert shape[3] == 2, "unsupported array shape, expected d == 2" + + size = reduce(lambda x, y: x * y, shape) + print("coefficient array shape:", shape, "(%d total)" % size) + + freqAvg = match.group("freqAvg") + freqRange = match.group("freqRange") + + coeff = [None for x in range(size)] + state = COEFF + + # Parse coefficients. + elif state == COEFF: + match = re.match("^%s\s+%s\s+%s\s+%s\s+%s\s+%s$" + % (regex("d", "int", False), regex("k", "int", False), + regex("pwrT", "int", False), regex("pwrF", "int", False), + regex("re", "float"), regex("im", "float")), line) + assert match, "unable to parse line #%d" % line_no + + d = int(match.group("d")) + k = int(match.group("k")) + pwrT = int(match.group("pwrT")) + pwrF = int(match.group("pwrF")) + + index = flat_index(shape, (k, pwrT, pwrF, d)) + coeff[index] = "std::complex<double>(%s, %s)" % (match.group("re"), match.group("im")) + + count += 1 + + line_no += 1 + + fin.close() + assert not (coeff is None) and all(coeff) + + # Write the output. + fout = open(args[1], "w") + + print("// Beam model coefficients converted by convert_coeff.py.", file=fout) + print("// Conversion performed on %s UTC using: " % time.strftime("%Y/%m/%d/%H:%M:%S", time.gmtime()), file=fout) + print("// convert_coeff.py %s %s %s" % (args[0], args[1], args[2]), file=fout) + print(file=fout) + print("#include <complex>", file=fout) + print(file=fout) + print("// Center frequency, and frequency range for which the beam model coefficients", file=fout) + print("// are valid. The beam model is parameterized in terms of a normalized", file=fout) + print("// frequency f' in the range [-1.0, 1.0]. The appropriate conversion is:", file=fout) + print("//", file=fout) + print("// f' = (f - center) / range", file=fout) + print("//", file=fout) + print("const double %s_freq_center = %s;" % (args[2], freqAvg), file=fout) + print("const double %s_freq_range = %s;" % (args[2], freqRange), file=fout) + print(file=fout) + print("// Shape of the coefficient array: %dx%dx%dx2 (the size of the last dimension is" % (shape[0], shape[1], shape[2]), file=fout) + print("// implied, and always equal to 2).", file=fout) + print("//", file=fout) + print("const unsigned int %s_coeff_shape[3] = {%d, %d, %d};" % (args[2], shape[0], shape[1], shape[2]), file=fout) + print(file=fout) + print("// The array of coefficients in row-major order (\"C\"-order).", file=fout) + print("//", file=fout) + print("const std::complex<double> %s_coeff[%d] = {" % (args[2], len(coeff)), file=fout) + + i = 0 + while i < len(coeff): + if i + 2 < len(coeff): + print(" %s, %s," % (coeff[i], coeff[i + 1]), file=fout) + i += 2 + elif i + 2 == len(coeff): + print(" %s, %s" % (coeff[i], coeff[i + 1]), file=fout) + i += 2 + else: + print(" %s" % coeff[i], file=fout) + i += 1 + + print("};", file=fout) + + fout.close() + +if __name__ == "__main__": + if len(sys.argv) != 4: + print("convert a beam model coefficient (.coeff) file to a C++ (.cc) file.") + print("usage: convert_coeff.py <input-file> <output-file> <variable-name>") + sys.exit(1) + + main(sys.argv[1:]) diff --git a/CEP/Calibration/ElementResponse/src/element_beam_HBA.coeff b/CEP/Calibration/ElementResponse/src/element_beam_HBA.coeff new file mode 100644 index 0000000000000000000000000000000000000000..f2d39e70295da200b3f65c26328ab30f34740a02 --- /dev/null +++ b/CEP/Calibration/ElementResponse/src/element_beam_HBA.coeff @@ -0,0 +1,101 @@ +d 2 k 2 pwrT 5 pwrF 5 freqAvg 180e6 freqRange 60e6 +0 0 0 0 0.9989322499459223 0.0003305895124867 +1 0 0 0 1.0030546028872600 0.0002157249025076 +0 1 0 0 -0.0004916757488397 0.0000266213616248 +1 1 0 0 0.0006516553273188 -0.0000433166563288 +0 0 1 0 0.0249658150445263 -0.0122024663463393 +1 0 1 0 -0.0917825091832822 -0.0062606338208358 +0 1 1 0 0.0162495046881796 -0.0010736997976255 +1 1 1 0 -0.0175635905033026 0.0012997068962173 +0 0 2 0 -0.6730977722052307 0.0940030437973656 +1 0 2 0 0.3711597596859299 0.0557089394867947 +0 1 2 0 -0.1492097398080444 0.0123735410547393 +1 1 2 0 0.1393121453502456 -0.0121117146246749 +0 0 3 0 0.0665319393516388 -0.1418009730472832 +1 0 3 0 -0.7576728614553603 -0.0472040122949963 +0 1 3 0 0.1567990061437258 -0.0143275575385193 +1 1 3 0 -0.1043118778001582 0.0106756004832779 +0 0 4 0 0.1121669548152054 0.0504713119323919 +1 0 4 0 0.1882531376700409 0.0088411256350159 +0 1 4 0 -0.0459124372023251 0.0044990718645418 +1 1 4 0 0.0135433541303599 -0.0021789296923529 +0 0 0 1 0.0003002209532403 0.0007909077657054 +1 0 0 1 0.0022051270911392 0.0003834815341981 +0 1 0 1 -0.0004357897643121 0.0000320567996700 +1 1 0 1 0.0005818285824826 -0.0001021069650381 +0 0 1 1 -0.0083709499453879 -0.0289759752488368 +1 0 1 1 -0.0689260153643395 -0.0111348626546314 +0 1 1 1 0.0138897851110661 -0.0014876219938565 +1 1 1 1 -0.0150211436594772 0.0029712291209158 +0 0 2 1 0.2119250520015808 0.2155514942677135 +1 0 2 1 0.6727380529527980 0.0989550572104158 +0 1 2 1 -0.1217628319418324 0.0222643129255504 +1 1 2 1 0.1108579917761457 -0.0262986164183475 +0 0 3 1 -0.1017024786435272 -0.3302620837788515 +1 0 3 1 -0.5600906156274197 -0.0797555201430585 +0 1 3 1 0.1151024257152241 -0.0225518489392044 +1 1 3 1 -0.0593437249231851 0.0216080058910987 +0 0 4 1 0.0066968933526899 0.1181452711088882 +1 0 4 1 0.0981630367567397 0.0129921405004959 +0 1 4 1 -0.0306136798186735 0.0064963361606382 +1 1 4 1 -0.0046440676338940 -0.0037281688158807 +0 0 0 2 -0.0003856663268042 0.0008435910525861 +1 0 0 2 0.0004887765294093 0.0002777796480946 +0 1 0 2 -0.0001047488648808 -0.0000302146563592 +1 1 0 2 0.0001593350153828 -0.0000879125663990 +0 0 1 2 0.0116296166994115 -0.0307342946951178 +1 0 1 2 -0.0171249717275797 -0.0080642275561593 +0 1 1 2 0.0031705620225488 0.0004838463688512 +1 1 1 2 -0.0034418973689263 0.0024603729467258 +0 0 2 2 -0.0419944347289523 0.2355624543349744 +1 0 2 2 0.1917656461134636 0.0732470381581913 +0 1 2 2 -0.0273147374272124 0.0098595182007132 +1 1 2 2 0.0208992817013466 -0.0205929453727953 +0 0 3 2 0.0889729243872774 -0.3406964719938829 +1 0 3 2 -0.1342560801672904 -0.0515926960946038 +0 1 3 2 0.0142781186223020 -0.0057037138045721 +1 1 3 2 0.0151043140114779 0.0141435752121475 +0 0 4 2 -0.0347327225501659 0.1186585563636635 +1 0 4 2 0.0102831315790362 0.0046275244914932 +0 1 4 2 -0.0006372791846825 0.0008894047150233 +1 1 4 2 -0.0181611528840412 -0.0011106177431486 +0 0 0 3 -0.0000699366665322 0.0005136144371953 +1 0 0 3 0.0001520602842105 0.0001303481681886 +0 1 0 3 -0.0000141882506567 -0.0000941521783975 +1 1 0 3 -0.0000004226298134 -0.0000245060763932 +0 0 1 3 0.0012408055399100 -0.0191295543986957 +1 0 1 3 -0.0051031652662961 -0.0037143632875100 +0 1 1 3 0.0003028387544878 0.0026905629457281 +1 1 1 3 0.0006768121359769 0.0005901486396051 +0 0 2 3 0.0048918921441903 0.1588912409502319 +1 0 2 3 0.0575369727210951 0.0344677222786687 +0 1 2 3 -0.0002152227668601 -0.0089220757225133 +1 1 2 3 -0.0074792188817697 -0.0043562231368076 +0 0 3 3 -0.0149335262655201 -0.2084962323582034 +1 0 3 3 -0.0327252678958813 -0.0172371907472848 +0 1 3 3 -0.0057143555179676 0.0141142700941743 +1 1 3 3 0.0251435557201315 -0.0005753615445942 +0 0 4 3 0.0070209144233666 0.0689639468490938 +1 0 4 3 -0.0020239346031291 -0.0025499069613344 +0 1 4 3 0.0032325387394458 -0.0048123509184894 +1 1 4 3 -0.0136340313457176 0.0021185000810664 +0 0 0 4 0.0000512381993616 0.0001550785137302 +1 0 0 4 0.0000819244737818 0.0000466470412396 +0 1 0 4 -0.0000177429496833 -0.0000561890408003 +1 1 0 4 -0.0000018388829279 0.0000032387726477 +0 0 1 4 -0.0022414352263751 -0.0060474723525871 +1 0 1 4 -0.0024377933436567 -0.0012852163337395 +0 1 1 4 0.0004634797107989 0.0016976603895716 +1 1 1 4 0.0003344773954073 -0.0001499932789294 +0 0 2 4 0.0241014578366618 0.0547046570516960 +1 0 2 4 0.0219986510834463 0.0112189146988984 +0 1 2 4 -0.0012019994038721 -0.0079939660050373 +1 1 2 4 -0.0035807498769946 0.0014801422733613 +0 0 3 4 -0.0362395089905272 -0.0661322227928722 +1 0 3 4 -0.0141568558526096 -0.0042676979206835 +0 1 3 4 0.0004475745352473 0.0102135659618127 +1 1 3 4 0.0090474375150397 -0.0032177128650026 +0 0 4 4 0.0132702874173192 0.0207916487187541 +1 0 4 4 0.0004387107229914 -0.0017223838914815 +0 1 4 4 0.0001287985092565 -0.0032079544559908 +1 1 4 4 -0.0045503800737417 0.0015366231416036 diff --git a/CEP/Calibration/ElementResponse/src/element_beam_LBA.coeff b/CEP/Calibration/ElementResponse/src/element_beam_LBA.coeff new file mode 100644 index 0000000000000000000000000000000000000000..7259a7d7cecfe9c908a07b25ac42b6c85d9f3d45 --- /dev/null +++ b/CEP/Calibration/ElementResponse/src/element_beam_LBA.coeff @@ -0,0 +1,101 @@ +d 2 k 2 pwrT 5 pwrF 5 freqAvg 55e6 freqRange 45e6 + 0 0 0 0 0.9982445079290715 0.0000650863154389 + 1 0 0 0 1.0006230902158257 -0.0022053287681416 + 0 1 0 0 -0.0000762326746410 0.0000118155774181 + 1 1 0 0 0.0000118903581604 -0.0000251324432498 + 0 0 1 0 0.0550606068782634 0.0011958385659938 + 1 0 1 0 -0.0160912944232080 0.0703645376267940 + 0 1 1 0 0.0029807707669629 -0.0003262084082071 + 1 1 1 0 0.0001639620574332 -0.0000266272685197 + 0 0 2 0 -0.8624889445327827 -0.1522883072804402 + 1 0 2 0 -0.0386800869486029 -0.7569350701887934 + 0 1 2 0 -0.0229975846564195 0.0010565261888195 + 1 1 2 0 0.0019573207027441 0.0050550600926414 + 0 0 3 0 0.4039567648146965 0.0810473144253429 + 1 0 3 0 -0.0350803390479135 0.5214591717801087 + 0 1 3 0 0.0333560984394624 -0.0009767086536162 + 1 1 3 0 0.0141704479374002 -0.0205386534626779 + 0 0 4 0 -0.0756250564248881 0.0056622911723172 + 1 0 4 0 -0.1267444401630109 -0.0349676272376008 + 0 1 4 0 -0.0126732549319889 0.0002042370658763 + 1 1 4 0 -0.0101360135082899 0.0114084024114141 + 0 0 0 1 0.0001002692200362 0.0006838211278268 + 1 0 0 1 -0.0003660049052840 -0.0008418920419220 + 0 1 0 1 -0.0002204197663391 -0.0000213776348027 + 1 1 0 1 0.0001477083861977 0.0000599750510518 + 0 0 1 1 0.0033849565901213 -0.0244636379385135 + 1 0 1 1 0.0234264238829944 0.0084068836453700 + 0 1 1 1 0.0076282580587895 0.0026614359017468 + 1 1 1 1 -0.0044850263974801 -0.0058337192660638 + 0 0 2 1 0.0891332399420108 0.1876527151756476 + 1 0 2 1 -0.1012363483900640 -0.1975118891151966 + 0 1 2 1 -0.0682274156850413 -0.0758159820140411 + 1 1 2 1 0.0497303968865466 0.1019681987654797 + 0 0 3 1 0.2232030356124932 -0.2248154851829713 + 1 0 3 1 0.4704343293662089 -0.3552101485419532 + 0 1 3 1 0.0562541280098909 0.0743149092143081 + 1 1 3 1 -0.0226634801339250 -0.1439026188572270 + 0 0 4 1 -0.1793752883639813 0.0720222655359702 + 1 0 4 1 -0.2678542619793421 0.3152115802895427 + 0 1 4 1 -0.0102147881225462 -0.0176848554302252 + 1 1 4 1 -0.0051268936720694 0.0527621533959941 + 0 0 0 2 -0.0010581424498791 0.0015237878543047 + 1 0 0 2 0.0007398729642721 0.0028468649470433 + 0 1 0 2 -0.0003281057522772 -0.0000770207588466 + 1 1 0 2 0.0003478997686964 0.0001481982639746 + 0 0 1 2 0.0557107413978542 -0.0634701730653090 + 1 0 1 2 -0.0139549526991330 -0.1175401658864208 + 0 1 1 2 0.0124258438959177 0.0067985224235178 + 1 1 1 2 -0.0126349778957970 -0.0100656881493938 + 0 0 2 2 -0.6404795825927633 0.7568775384981410 + 1 0 2 2 0.0767245154665722 1.3441875993523555 + 0 1 2 2 -0.1757936183439326 -0.1363710820472197 + 1 1 2 2 0.1765450269056824 0.1555919358121995 + 0 0 3 2 0.9646419509627557 -0.8095088593139815 + 1 0 3 2 0.1635280638865702 -1.4854352979459096 + 0 1 3 2 0.1238595124159999 0.1766108700786397 + 1 1 3 2 -0.1307647072780430 -0.2090615438301942 + 0 0 4 2 -0.3718069213271066 0.2275266747872172 + 1 0 4 2 -0.1372223722572021 0.4314989948093362 + 0 1 4 2 -0.0110701836450407 -0.0593085026046026 + 1 1 4 2 0.0140598301629874 0.0738668439833535 + 0 0 0 3 -0.0039458389254656 -0.0007048354913730 + 1 0 0 3 0.0007040177887611 0.0007856369612188 + 0 1 0 3 -0.0000625695757282 0.0000249138990722 + 1 1 0 3 -0.0000960097542525 0.0002521364065803 + 0 0 1 3 0.1336911750356096 0.0202651327657687 + 1 0 1 3 -0.0113385668361727 -0.0339262369086247 + 0 1 1 3 0.0059031372522229 0.0008660479915339 + 1 1 1 3 0.0039660364524413 -0.0100356333791398 + 0 0 2 3 -0.8758406699506004 0.3350237639226141 + 1 0 2 3 0.2824832769101577 0.6821307442669313 + 0 1 2 3 -0.1541299429420569 -0.0281422177614844 + 1 1 2 3 0.0816399676454817 0.0691599035109852 + 0 0 3 3 1.0331569921006993 0.0509705885336283 + 1 0 3 3 0.1501121326521990 -0.5193414816770609 + 0 1 3 3 0.1557916917691289 0.0646351862895731 + 1 1 3 3 0.0170294191358757 -0.1027926845803498 + 0 0 4 3 -0.3316657641578328 -0.1655909947939444 + 1 0 4 3 -0.2158100484836540 0.0614504774034524 + 0 1 4 3 -0.0389912915621699 -0.0301364165752433 + 1 1 4 3 -0.0462331759359031 0.0405864871628086 + 0 0 0 4 -0.0031701591723043 -0.0010521154166512 + 1 0 0 4 -0.0007213036752903 -0.0007227764008022 + 0 1 0 4 0.0001275344578325 0.0000652362392482 + 1 1 0 4 -0.0003113309221942 0.0001956734476566 + 0 0 1 4 0.0962263571740972 0.0440074333288440 + 1 0 1 4 0.0313595045238824 0.0230763038515351 + 0 1 1 4 -0.0020520685193773 -0.0028564379463666 + 1 1 1 4 0.0121039958869239 -0.0059701468961263 + 0 0 2 4 -0.3144282315609649 -0.2763869580286276 + 1 0 2 4 -0.1705959031354030 -0.0712085950559831 + 0 1 2 4 -0.0235110916473515 0.0306385386726702 + 1 1 2 4 -0.0474273292450285 0.0116831908947225 + 0 0 3 4 0.4715775965513117 0.5077361528286819 + 1 0 3 4 0.3847391427972284 0.1136717951238837 + 0 1 3 4 0.0543537332385954 -0.0366524906364179 + 1 1 3 4 0.1127180664279469 -0.0176607923511174 + 0 0 4 4 -0.1901597954359592 -0.2294955549701665 + 1 0 4 4 -0.1864961465389693 -0.0486276177310768 + 0 1 4 4 -0.0251598701859194 0.0115712688652445 + 1 1 4 4 -0.0563476280247398 0.0079787883434624 diff --git a/CEP/Calibration/ExpIon/CMakeLists.txt b/CEP/Calibration/ExpIon/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..3c3ce32fcb31ba8fc15c850ebef103eb4194c7b8 --- /dev/null +++ b/CEP/Calibration/ExpIon/CMakeLists.txt @@ -0,0 +1,9 @@ +# $Id: CMakeLists.txt 14609 2009-12-07 09:10:48Z loose $ + +# Do not split the following line, otherwise makeversion will fail! +lofar_package(ExpIon 1.0 DEPENDS pyparameterset pyparmdb) + +include(LofarFindPackage) +lofar_find_package(Boost REQUIRED COMPONENTS python thread) +lofar_find_package(Casacore REQUIRED COMPONENTS python scimath) +add_subdirectory(src) diff --git a/CEP/Calibration/ExpIon/src/CMakeLists.txt b/CEP/Calibration/ExpIon/src/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..7629de3ffc9090cb95112fe97534081129da3725 --- /dev/null +++ b/CEP/Calibration/ExpIon/src/CMakeLists.txt @@ -0,0 +1,43 @@ +# $Id: CMakeLists.txt 14508 2010-03-24 13:18:18Z vdtol $ + +include(PythonInstall) + +lofar_add_bin_scripts( + calibrate-ion + readms-part.sh + readms-part.py + parmdbwriter.py) + +lofar_add_library(_baselinefitting MODULE baselinefitting.cc) + +set_target_properties(_baselinefitting PROPERTIES + PREFIX "" + LIBRARY_OUTPUT_DIRECTORY ${PYTHON_BUILD_DIR}/lofar/expion) + +# This is a quick-and-dirty fix to install the Python binding module in the +# right place. It will now be installed twice, because lofar_add_library() +# will install it in $prefix/$libdir +install(TARGETS _baselinefitting + DESTINATION ${PYTHON_INSTALL_DIR}/lofar/expion) + +# Python modules. +python_install( + __init__.py + io.py + format.py + ionosphere.py + acalc.py + client.py + sphere.py + error.py + mpfit.py + readms.py + parmdbmain.py + baselinefitting.py + repairGlobaldb.py + MMionosphere.py + fitClockTEC.py + PosTools.py + read_sagecal.py + DESTINATION lofar/expion) + diff --git a/CEP/Calibration/ExpIon/src/MMionosphere.py b/CEP/Calibration/ExpIon/src/MMionosphere.py new file mode 100755 index 0000000000000000000000000000000000000000..ced85cbfadf7b55fd5665ac4d8438b2912b2a06a --- /dev/null +++ b/CEP/Calibration/ExpIon/src/MMionosphere.py @@ -0,0 +1,674 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# Copyright (C) 2007 +# ASTRON (Netherlands Institute for Radio Astronomy) +# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +# +# This file is part of the LOFAR software suite. +# The LOFAR software suite 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 3 of the License, or +# (at your option) any later version. +# +# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +# +# $Id$ +############################################################################### + +# import Python modules +import sys +import os +from datetime import * +from math import * +import time +import re +import atexit + +# import 3rd party modules +#from IPython.parallel import client +from numpy import * +#from pylab import * +from numpy.linalg.linalg import inv +from numpy import linalg +import scipy.optimize + +# import user modules +#from files import * +from . import client +from .acalc import * +from . import sphere +from .error import * +import tables +from . import PosTools + +############################################################################### + +class IonosphericModel: + """IonosphericModel class is the main interface to the functions impelmented in the ionosphere module""" + + def __init__( self, ionmodel): + + """ + ionmodel is the hdf5 file + """ + + self.load_globaldb(ionmodel) + + def load_globaldb ( self, ionmodel) : + self.hdf5 = tables.openFile(ionmodel, 'r+') + for varname in self.hdf5.root: + #print varname,"name:",varname.name + self.__dict__.update( [(varname.name, self.hdf5.getNode(self.hdf5.root, varname.name))] ) + + self.stations = self.hdf5.root.stations.cols.name + self.station_positions = self.hdf5.root.stations.cols.position + self.array_center = array( self.station_positions ).mean(axis=0).tolist() + if not 'array_center' in self.hdf5.root: + a_center=self.hdf5.createArray(self.hdf5.root, 'array_center', self.array_center) + a_center.flush() + self.array_center = self.hdf5.root.array_center + self.N_stations = len(self.stations) + self.pointing = self.hdf5.root.pointing + if not 'sources' in self.hdf5.root: + source_table = self.hdf5.createTable(self.hdf5.root, 'sources', {'name': tables.StringCol(40), 'position':tables.Float64Col(2)}) + row = source_table.row + row['name'] = "Pointing" + row['position'] = list(self.pointing) + row.append() + source_table.flush() + if not 'timewidths' in self.hdf5.root: + self.timewidths=(self.times[1]-self.times[0])*ones(self.times.shape) + self.sources = self.hdf5.root.sources[:]['name'] + self.source_positions = self.hdf5.root.sources[:]['position'] + self.N_sources = len(self.sources) + self.N_piercepoints = self.N_sources * self.N_stations + + + + self.freqs = self.hdf5.root.freqs + self.polarizations = self.hdf5.root.polarizations + self.N_pol = len(self.polarizations) + + self.phases = self.hdf5.root.phases + self.flags = self.hdf5.root.flags + + # for varname in ['amplitudes', 'Clock', 'TEC', 'TECfit', 'TECfit_white', 'offsets', \ + # 'rotation','times', 'timewidths', 'piercepoints', 'facets', 'facet_piercepoints', 'n_list', \ + # 'STEC_facets'] : + # if varname in self.hdf5.root: + # self.__dict__.update( [(varname, self.hdf5.getNode(self.hdf5.root, varname))] ) + + + + def calculate_piercepoints(self, time_steps = [], station_select=[],height = 200.e3): + if ( len( time_steps ) == 0 ): + n_list = list(range( self.times[:].shape[0])) + else: + n_list = time_steps + self.n_list = n_list + if 'n_list' in self.hdf5.root: self.hdf5.root.n_list.remove() + self.hdf5.createArray(self.hdf5.root, 'n_list', self.n_list) + if ( len( station_select ) == 0 ): + stat_select = list(range( self.stations[:].shape[0])) + else: + stat_select = station_select + self.stat_select = stat_select + if 'stat_select' in self.hdf5.root: self.hdf5.root.stat_select.remove() + self.hdf5.createArray(self.hdf5.root, 'stat_select', self.stat_select) + + + self.height = height + + N_stations=len(stat_select) + if 'piercepoints' in self.hdf5.root: self.hdf5.root.piercepoints.remove() + description = {'positions':tables.Float64Col((self.N_sources, N_stations,2)), \ + 'positions_xyz':tables.Float64Col((self.N_sources, N_stations,3)), \ + 'zenith_angles':tables.Float64Col((self.N_sources, N_stations))} + self.piercepoints = self.hdf5.createTable(self.hdf5.root, 'piercepoints', description) + self.piercepoints.attrs.height = self.height + piercepoints_row = self.piercepoints.row + p = ProgressBar(len(n_list), "Calculating piercepoints: ") + for (n, counter) in zip(n_list, list(range(len(n_list)))): + p.update(counter) + + piercepoints=PosTools.getPiercePoints(self.times[n],self.source_positions,self.station_positions[:][self.stat_select[:]],height=self.height) + #piercepoints = PiercePoints( self.times[ n ], self.pointing, self.array_center, self.source_positions, self.station_positions[:][self.stat_select[:]], height = self.height ) + piercepoints_row['positions'] = piercepoints.positions + piercepoints_row['positions_xyz'] = piercepoints.positions_xyz + piercepoints_row['zenith_angles'] = piercepoints.zenith_angles + piercepoints_row.append() + self.piercepoints.flush() + p.finished() + + + def calculate_Sage_piercepoints(self, sage_group=0,time_steps = [], station_select=[],height = 200.e3): + if ( len( time_steps ) == 0 ): + n_list = list(range( self.times[:].shape[0])) + else: + n_list = time_steps + self.sage_n_list = n_list + if 'sage%d_n_list'%sage_group in self.hdf5.root: self.hdf5.removeNode('\sage%d_n_list'%sage_group) + self.hdf5.createArray(self.hdf5.root, 'sage%d_n_list'%sage_group, self.sage_n_list) + if ( len( station_select ) == 0 ): + stat_select = list(range( self.stations[:].shape[0])) + else: + stat_select = station_select + self.sage_stat_select = stat_select + if 'sage%d_stat_select'%sage_group in self.hdf5.root: self.hdf5.removeNode('\sage%d_stat_select'%sage_group) + self.hdf5.createArray(self.hdf5.root,'sage%d_stat_select'%sage_group, self.sage_stat_select) + + + self.sage_height = height + + N_stations=len(stat_select) + if 'sage%d_piercepoints'%sage_group in self.hdf5.root: self.hdf5.removeNode('\sage%d_piercepoints'%sage_group) + description = {'positions':tables.Float64Col((self.N_sources, N_stations,2)), \ + 'positions_xyz':tables.Float64Col((self.N_sources, N_stations,3)), \ + 'zenith_angles':tables.Float64Col((self.N_sources, N_stations))} + self.sage_piercepoints = self.hdf5.createTable(self.hdf5.root,'sage%d_piercepoints'%sage_group, description) + self.sage_piercepoints.attrs.height = self.sage_height + piercepoints_row = self.sage_piercepoints.row + source_positions=self.hdf5.getNode('sage_radec%d'%sage_group)[:] + p = ProgressBar(len(n_list), "Calculating piercepoints: ") + for (n, counter) in zip(n_list, list(range(len(n_list)))): + p.update(counter) + + piercepoints=PosTools.getPiercePoints(self.times[n],source_positions,self.station_positions[:][self.stat_select[:]],height=self.sage_height) + #piercepoints = PiercePoints( self.times[ n ], self.pointing, self.array_center, self.source_positions, self.station_positions[:][self.stat_select[:]], height = self.height ) + piercepoints_row['positions'] = piercepoints.positions + piercepoints_row['positions_xyz'] = piercepoints.positions_xyz + piercepoints_row['zenith_angles'] = piercepoints.zenith_angles + piercepoints_row.append() + self.sage_piercepoints.flush() + p.finished() + + def calculate_basevectors(self, order = 15, beta = 5. / 3., r_0 = 1.): + self.order = order + self.beta = beta + self.r_0 = r_0 + #N_stations = len(self.stations) + N_stations = len(self.stat_select[:]) + N_sources = len(self.sources) + + N_piercepoints = N_stations * N_sources + P = eye(N_piercepoints) - ones((N_piercepoints, N_piercepoints)) / N_piercepoints + + self.C_list = [] + self.U_list = [] + self.S_list = [] + p = ProgressBar(len(self.piercepoints), "Calculating base vectors: ") + for (piercepoints, counter) in zip(self.piercepoints, list(range(len(self.piercepoints)))): + p.update( counter ) + Xp_table = reshape(piercepoints['positions_xyz'], (N_piercepoints, 3) ) + + # calculate structure matrix + D = resize( Xp_table, ( N_piercepoints, N_piercepoints, 3 ) ) + D = transpose( D, ( 1, 0, 2 ) ) - D + D2 = sum( D**2, 2 ) + C = -(D2 / ( r_0**2 ) )**( beta / 2.0 )/2.0 + self.C_list.append(C) + + # calculate covariance matrix C + # calculate partial product for interpolation B + # reforce symmetry + + C = dot(dot(P, C ), P) + + # eigenvalue decomposition + # reforce symmetry + # select subset of base vectors + [ U, S, V ] = linalg.svd( C ) + U = U[ :, 0 : order ] + S = S[ 0 : order ] + self.U_list.append(U) + self.S_list.append(S) + p.finished() + + def fit_model ( self) : + #N_stations = len(self.stations) + N_stations = len(self.stat_select[:]) + N_times = len(self.times) + N_sources = len(self.sources) + N_pol = min(len(self.polarizations),2) + G = kron(eye( N_sources ), ( eye( N_stations ) - ones((N_stations, N_stations)) / N_stations)) + + if 'TECfit' in self.hdf5.root: self.hdf5.root.TECfit.remove() + self.TECfit = self.hdf5.createArray(self.hdf5.root, 'TECfit', zeros(self.TEC[:].shape)) + + if 'TECfit_white' in self.hdf5.root: self.hdf5.root.TECfit_white.remove() + self.TECfit_white = self.hdf5.createArray(self.hdf5.root, 'TECfit_white', zeros(self.TEC[:].shape)) + + self.offsets = zeros((len(self.n_list),N_pol)) + p = ProgressBar(len(self.n_list), "Fitting phase screen: ") + za=self.piercepoints.cols.zenith_angles[:] + for i in range(len(self.n_list)) : + p.update( i ) + U = self.U_list[i] + S = self.S_list[i] + for pol in range(N_pol) : + #print self.TEC[ self.n_list[i], :, :,pol].shape + TEC = multiply(self.TEC[:][ self.n_list[i], self.stat_select[:], :,pol].swapaxes(0,1), + cos(za[i,:,:])).reshape( (N_sources * N_stations, 1) ) + TECfit = dot(U, dot(inv(dot(U.T, dot(G, U))), dot(U.T, dot(G, TEC)))) + TECfit_white = dot(U, dot(diag(1/S), dot(U.T, TECfit))) + self.offsets[i,pol] = TECfit[0] - dot(self.C_list[i][0,:], TECfit_white) + TECfit=reshape( TECfit, (N_sources,N_stations) ).swapaxes(0,1) + TECfit_white= reshape( TECfit_white,(N_sources,N_stations) ).swapaxes(0,1) + for istat,stat in enumerate(self.stat_select[:]): + self.TECfit[i, stat, : ,pol] = TECfit[istat,:] + self.TECfit_white[i,stat,: ,pol ] = TECfit_white[istat,:] + p.finished() + + self.TECfit_white.attrs.r_0 = self.r_0 + self.TECfit_white.attrs.beta = self.beta + + if 'offsets' in self.hdf5.root: self.hdf5.root.offsets.remove() + self.hdf5.createArray(self.hdf5.root, 'offsets', self.offsets) + + def get_TEC_pp(self,radec,pol=0,new_stat_select=None): + TEC_out=[] + N_stations = len(self.stat_select[:]) + N_sources = len(self.sources) + N_piercepoints = N_stations * N_sources + h=self.piercepoints.attrs.height + r_0 = self.TECfit_white.attrs.r_0 + beta = self.TECfit_white.attrs.beta + pp_out=[] + am_out=[] + TEC_out=[] + stations=self.station_positions[:] + if not (new_stat_select is None): + stations=stations[new_stat_select] + for i in range(len(self.n_list)) : + if i%10==0: + sys.stdout.write(str(i)+'...') + sys.stdout.flush() + time=self.times[i] + piercepoints=PosTools.getPiercePoints(time,radec.reshape((1,-1)),stations,height=h) + pp=piercepoints.positions_xyz + am=piercepoints.zenith_angles + pp_out.append(pp) + am_out.append(am) + Xp_table=reshape(self.piercepoints[i]['positions_xyz'], (N_piercepoints, 3)) + #v=self.TECfit_white[ i, :, : ,pol][self.stat_select[:]].reshape((N_piercepoints,1)) + v=self.TECfit_white[ i, :, : ,pol][self.stat_select[:]].reshape((N_piercepoints,1)) + tecs=[] + for ist in range(stations[:].shape[0]): + tecs.append(get_interpolated_TEC_white(Xp_table,v,beta,r_0,pp[0,ist])) + TEC_out.append(tecs) + return array(pp_out),array(am_out),array(TEC_out) + + def get_TEC_frame(self,time=0,pol=0,scale=1.1,steps=10): + N_stations = len(self.stat_select[:]) + N_sources = len(self.sources) + N_piercepoints = N_stations * N_sources + + h=self.piercepoints.attrs.height + r_0 = self.TECfit_white.attrs.r_0 + beta = self.TECfit_white.attrs.beta + pp=self.piercepoints[time]['positions'].reshape(N_piercepoints,2) + Xp_table=reshape(self.piercepoints[time]['positions_xyz'], (N_piercepoints, 3)) + v=self.TECfit_white[ time, :, : ,pol][self.stat_select[:]].reshape((N_piercepoints,1)) + myxlim=[min(pp[:,0]),max(pp[:,0])] + myylim=[min(pp[:,1]),max(pp[:,1])] + diff=(myxlim[1]-myxlim[0])*(scale-1.)*0.5 + myxlim[0]-=diff + myxlim[1]+=diff + xsize=(myxlim[1]-myxlim[0])*scale/(steps+1) + myxlim[1]+=xsize + diff=(myylim[1]-myylim[0])*(scale-1.)*0.5 + myylim[0]-=diff + myylim[1]+=diff + ysize=(myylim[1]-myylim[0])*scale/(steps+1) + myylim[1]+=ysize + length=sqrt(dot(Xp_table[0],Xp_table[0].T)) + #print "length",length,myxlim,myylim,xsize,ysize + iy=0 + ix=0 + phi=zeros((steps,steps),dtype=float) + for lat in arange(myylim[0],myylim[1],ysize): + for lon in arange(myxlim[0],myxlim[1],xsize): + xyzpp=[cos(lat)*cos(lon)*length,cos(lat)*sin(lon)*length,sin(lat)*length] + #print "my",ix,iy,lon,lat,xyzpp + #print Xp_table[0] + phi[iy,ix]=get_interpolated_TEC_white(Xp_table,v,beta,r_0,xyzpp) + ix+=1 + ix=0 + iy+=1 + return phi,arange(myxlim[0],myxlim[1],xsize),arange(myylim[0],myylim[1],ysize) + + def make_movie( self, extent = 0, npixels = 100, vmin = 0, vmax = 0 ): + """ + """ + + multiengine_furl = os.environ['HOME'] + '/ipcluster/multiengine.furl' +# mec = client.MultiEngineClient( multiengine_furl ) + mec = client.MultiEngineClient( ) + task_furl = os.environ['HOME'] + '/ipcluster/task.furl' + #tc = client.TaskClient( task_furl ) + tc = client.TaskClient( ) + N_stations = len(self.stations) + N_times = self.TECfit[:].shape[1] + N_sources = len(self.sources) + N_piercepoints = N_stations * N_sources + N_pol = len(self.polarizations) + R = 6378137 + taskids = [] + + print("Making movie...") + p = ProgressBar( len( self.n_list ), 'Submitting jobs: ' ) + for i in range(len(self.n_list)) : + p.update(i) + Xp_table = reshape(self.piercepoints[i]['positions'], (N_piercepoints, 2) ) + if extent > 0 : + w = extent/R/2 + else : + w = 1.1*abs(Xp_table).max() + for pol in range(N_pol) : + v = self.TECfit[ i, :, : ,pol].reshape((N_piercepoints,1)) + maptask = client.MapTask(calculate_frame, (Xp_table, v, self.beta, self.r_0, npixels, w) ) + taskids.append(tc.run(maptask)) + p.finished() + + if vmin == 0 and vmax == 0 : + vmin = self.TECfit.min() + vmax = self.TECfit.max() + vdiff = vmax - vmin + vmin = vmin - 0.1*vdiff + vmax = vmax + 0.1*vdiff + + p = ProgressBar( len( self.n_list ), 'Fetch results: ' ) + for i in range(len(self.n_list)) : + p.update(i) + clf() + for pol in range(N_pol) : + (phi,w) = tc.get_task_result(taskids.pop(0), block = True) + phi = phi + self.offsets[pol, i] + subplot(1, N_pol, pol+1) + w = w*R*1e-3 + h_im = imshow(phi, interpolation = 'nearest', origin = 'lower', extent = (-w, w, -w, w), vmin = vmin, vmax = vmax ) + h_axes = gca() + cl = h_im.get_clim() + TEC = reshape( self.TEC[ pol, i, :, : ], N_piercepoints ) + for j in range(N_piercepoints): + color = h_im.cmap(int(round((TEC[j]-cl[0])/(cl[1]-cl[0])*(h_im.cmap.N-1)))) + plot(R*1e-3*Xp_table[j,0], R*1e-3*Xp_table[j,1], marker = 'o', markeredgecolor = 'k', markerfacecolor = color) + colorbar() + xlim(-w, w) + ylim(-w, w) + savefig('tmpfig%4.4i.png' % i) + p.finished() + os.system("mencoder mf://tmpfig????.png -o movie.mpeg -mf type=png:fps=3 -ovc lavc -ffourcc DX50 -noskip -oac copy") + + def interpolate( self, facetlistfile ) : + + """ + """ + + #facetdbname = os.path.join(self.globaldb, 'facets') + #os.system( 'makesourcedb in=%s out=%s append=False' % (facetlistfile, facetdbname) ) + + #patch_table = pt.table( os.path.join(facetdbname, 'SOURCES', 'PATCHES' ) ) + + #if 'facets' in self.hdf5.root: self.hdf5.root.facets.remove() + #description = {'name': tables.StringCol(40), 'position':tables.Float64Col(2)} + #self.facets = self.hdf5.createTable(self.hdf5.root, 'facets', description) + + #facet = self.facets.row + #for patch in patch_table : + #facet['name'] = patch['PATCHNAME'] + #facet['position'] = array([patch['RA'], patch['DEC']]) + #facet.append() + #self.facets.flush() + self.N_facets = len(self.facets) + + self.facet_names = self.facets[:]['name'] + self.facet_positions = self.facets[:]['position'] + + print(self.n_list) + if 'STEC_facets' in self.hdf5.root: self.hdf5.root.STEC_facets.remove() + self.STEC_facets = self.hdf5.createCArray(self.hdf5.root, 'STEC_facets', tables.Float32Atom(), shape = (self.N_pol, self.n_list[:].shape[0], self.N_facets, self.N_stations)) + + #if 'facet_piercepoints' in self.hdf5.root: self.hdf5.root.facet_piercepoints.remove() + #description = {'positions':tables.Float64Col((self.N_facets, self.N_stations,2)), \ + #'positions_xyz':tables.Float64Col((self.N_facets, self.N_stations,3)), \ + #'zenith_angles':tables.Float64Col((self.N_facets, self.N_stations))} + #self.facet_piercepoints = self.hdf5.createTable(self.hdf5.root, 'facet_piercepoints', description) + #height = self.piercepoints.attrs.height + #facet_piercepoints_row = self.facet_piercepoints.row + #print "Calculating facet piercepoints..." + #for n in self.n_list: + #piercepoints = PiercePoints( self.times[ n ], self.pointing, self.array_center, self.facet_positions, self.station_positions, height = height ) + #facet_piercepoints_row['positions'] = piercepoints.positions + #facet_piercepoints_row['positions_xyz'] = piercepoints.positions_xyz + #facet_piercepoints_row['zenith_angles'] = piercepoints.zenith_angles + #facet_piercepoints_row.append() + #self.facet_piercepoints.flush() + + r_0 = self.TECfit_white.attrs.r_0 + beta = self.TECfit_white.attrs.beta + + for facet_idx in range(self.N_facets) : + for station_idx in range(self.N_stations): + for pol_idx in range(self.N_pol) : + TEC_list = [] + for n in range(len(self.n_list)): + p = self.facet_piercepoints[n]['positions_xyz'][facet_idx, station_idx,:] + za = self.facet_piercepoints[n]['zenith_angles'][facet_idx, station_idx] + Xp_table = reshape(self.piercepoints[n]['positions_xyz'], (self.N_piercepoints, 3) ) + v = self.TECfit_white[ pol_idx, n, :, : ].reshape((self.N_piercepoints,1)) + D2 = sum((Xp_table - p)**2,1) + C = (D2 / ( r_0**2 ) )**( beta / 2. ) / -2. + self.STEC_facets[pol_idx, n, facet_idx, station_idx] = dot(C, v)/cos(za) + +def product(*args, **kwds): + # product('ABCD', 'xy') --> Ax Ay Bx By Cx Cy Dx Dy + # product(range(2), repeat=3) --> 000 001 010 011 100 101 110 111 + pools = list(map(tuple, args)) * kwds.get('repeat', 1) + result = [[]] + for pool in pools: + result = [x+[y] for x in result for y in pool] + for prod in result: + yield tuple(prod) + +def fillarray( a, v ) : + print(a.shape, a.chunkshape) + for idx in product(*[range(0, s, c) for s, c in zip(a.shape, a.chunkshape)]) : + s = tuple([slice(i,min(i+c,s)) for i,s,c in zip(idx, a.shape, a.chunkshape)]) + a[s] = v + + + +def calculate_frame(Xp_table, v, beta, r_0, npixels, w): + import numpy + phi = zeros((npixels,npixels)) + N_piercepoints = Xp_table.shape[0] + P = eye(N_piercepoints) - ones((N_piercepoints, N_piercepoints)) / N_piercepoints + # calculate structure matrix + D = resize( Xp_table, ( N_piercepoints, N_piercepoints, 2 ) ) + D = transpose( D, ( 1, 0, 2 ) ) - D + D2 = sum( D**2, 2 ) + C = -(D2 / ( r_0**2 ) )**( beta / 2.0 )/2.0 + C = dot(dot(P, C ), P) + v = dot(linalg.pinv(C), v) + + for x_idx in range(0, npixels): + x = -w + 2*x_idx*w/( npixels-1 ) + for y_idx in range(0, npixels): + y = -w + 2*y_idx*w/(npixels-1) + #D2 = sum((6378452*(Xp_table - array([ x, y ])))**2,1) + D2 = sum(((Xp_table - array([ x, y ])))**2,1) + C = (D2 / ( r_0**2 ) )**( beta / 2. ) / -2. + phi[y_idx, x_idx] = dot(C, v) + return phi, w + + + + +def get_interpolated_TEC(Xp_table,v,beta,r_0,pp): + N_piercepoints = Xp_table.shape[0] + n_axes=Xp_table.shape[1] + if n_axes!=pp.shape[0]: + print("wrong number of axes, original:",n_axes,"requested:",pp.shape[0]) + return -1 + P = eye(N_piercepoints) - ones((N_piercepoints, N_piercepoints)) / N_piercepoints + # calculate structure matrix + D = resize( Xp_table, ( N_piercepoints, N_piercepoints, n_axes ) ) + D = transpose( D, ( 1, 0, 2 ) ) - D + D2 = sum( D**2, 2 ) + C = -(D2 / ( r_0**2 ) )**( beta / 2.0 )/2.0 + C = dot(dot(P, C ), P) + v = dot(linalg.pinv(C), v) + D2 = sum((Xp_table - pp)**2,axis=1) + C = (D2 / ( r_0**2 ) )**( beta / 2. ) / -2. + return dot(C, v) + + +def get_interpolated_TEC_white(Xp_table,v,beta,r_0,pp): + D2 = sum((Xp_table - pp)**2,axis=1) + C = (D2 / ( r_0**2 ) )**( beta / 2. ) / -2. + return dot(C, v) + + + + +def fit_phi_klmap_model( P, U_table = None, pza_table = None, phase_table = None, dojac = None): +# TODO: calculate penalty terms of MAP estimator + + # handle input parameters + if ( ( U_table == None ) or + ( pza_table == None ) or + ( phase_table == None ) ): + return - 1, None, None + + (antenna_count, source_count) = phase_table.shape + + # calculate phases at puncture points + phi_table = dot( U_table, P ) / cos( aradians( pza_table ) ) + phi_table = phi_table.reshape( (antenna_count, source_count) ) + + # calculate chi2 terms + chi_list = [] + for source in range(source_count): + for i in range(antenna_count): + for j in range(i, antenna_count): + chi_list.append( mod( phi_table[i, source] - phi_table[j, source] - phase_table[i, source] + phase_table[j, source] + pi, 2*pi) - pi ) + + # make (normalized) chi2 array + chi_array = array( chi_list ) + + return 0, chi_array, None + +############################################################################### + +# gradient + +def phi_gradient_model( X, p ): + phi = dot( X, p ) + return phi + +############################################################################### + +def phi_klmap_model( X, Xp_table, B_table, F_table, beta = 5. / 3., r_0 = 1. ): +# B_table = (1/m)(1T)(C_table)(A_table) +# F_table = ( Ci_table )( U_table )( P_table ) + + # input check + if ( len( shape( X ) ) == 1 ): + X_table = array( [ X ] ) + elif ( len( shape( X ) ) == 2 ): + X_table = X + + # calculate structure matrix + x_count = len( X_table ) + p_count = len( Xp_table ) + D_table = transpose( resize( X_table, ( p_count, x_count, 2 ) ), ( 1, 0, 2 ) ) + D_table = D_table - resize( Xp_table, ( x_count, p_count, 2 ) ) + D_table = add.reduce( D_table**2, 2 ) + D_table = ( D_table / ( r_0**2 ) )**( beta / 2. ) + + # calculate covariance matrix + C_table = - D_table / 2. + C_table = transpose( transpose( C_table ) - ( add.reduce( C_table, 1 ) / float( p_count ) ) ) + C_table = C_table - B_table + + phi = dot( C_table, F_table ) + phi = reshape( phi, ( x_count ) ) + if ( len( phi ) == 1 ): + phi = phi[ 0 ] + + return phi + + +############################################################################### + +class PiercePoints: + + def __init__( self, time, pointing, array_center, source_positions, antenna_positions, height = 400.e3 ): + # source table radecs at observing epoch + + # calculate Earth referenced coordinates of puncture points of array center towards pointing center + [ center_pxyz, center_pza ] = sphere.calculate_puncture_point_mevius( array_center, pointing, time, height = height ) + self.center_p_geo_llh = sphere.xyz_to_geo_llh( center_pxyz, time ) + + # loop over sources + positions = [] + positions_xyz = [] + zenith_angles = [] + + for k in range( len( source_positions ) ): + positions_xyz1 = [] + positions1 = [] + zenith_angles1 = [] + + # loop over antennas + for i in range( len( antenna_positions ) ): + # calculate Earth referenced coordinates of puncture points of antenna towards peeled source + [ pxyz, pza ] = sphere.calculate_puncture_point_mevius( antenna_positions[ i ], source_positions[ k ], time, height = height ) + p_geo_llh = sphere.xyz_to_geo_llh( pxyz, time ) + + # calculate local angular coordinates of antenna puncture point ( x = East, y = North ) + [ separation, angle ] = sphere.calculate_angular_separation( self.center_p_geo_llh[ 0 : 2 ], p_geo_llh[ 0 : 2 ] ) + X = [ separation * sin( angle ), separation * cos( angle ) ] + + # store model fit input data + positions1.append(X) + positions_xyz1.append( pxyz ) + zenith_angles1.append( pza ) + positions.append(positions1) + positions_xyz.append(positions_xyz1) + zenith_angles.append( zenith_angles1 ) + + self.positions = array( positions ) + self.positions_xyz = array( positions_xyz ) + self.zenith_angles = array( zenith_angles ) + + +class ProgressBar: + + def __init__(self, length, message = ''): + self.length = length + self.current = 0 + sys.stdout.write(message + '0%') + sys.stdout.flush() + + def update(self, value): + while self.current < 2*int(50*value/self.length): + self.current += 2 + if self.current % 10 == 0 : + sys.stdout.write(str(self.current) + '%') + else: + sys.stdout.write('.') + sys.stdout.flush() + + def finished(self): + self.update(self.length) + sys.stdout.write('\n') + sys.stdout.flush() + diff --git a/CEP/Calibration/ExpIon/src/PosTools.py b/CEP/Calibration/ExpIon/src/PosTools.py new file mode 100644 index 0000000000000000000000000000000000000000..e883734b163fc9eb0f2605a1f878c1c59c27b066 --- /dev/null +++ b/CEP/Calibration/ExpIon/src/PosTools.py @@ -0,0 +1,272 @@ +from pyrap.measures import measures +from math import * +import pyrap.quanta as qa; +import os +from pyrap import tables as tab +import numpy as np + +R_earth=6364.62e3; +earth_ellipsoid_a = 6378137.0; +earth_ellipsoid_a2 = earth_ellipsoid_a*earth_ellipsoid_a; +earth_ellipsoid_b = 6356752.3142; +earth_ellipsoid_b2 = earth_ellipsoid_b*earth_ellipsoid_b; +earth_ellipsoid_e2 = (earth_ellipsoid_a2 - earth_ellipsoid_b2) / earth_ellipsoid_a2; +posCS002=[3826577.1095 ,461022.900196, 5064892.758] + +def getMSinfo(MS=None): + print("getting info for",MS) + if MS is None: + print("No measurement set given") + return + if os.path.isdir(MS): + myMS=tab.table(MS) + else: + print("Do not understand the format of MS",MS,"bailing out") + return; + print("opened table",MS) + timerange=[np.amin(myMS.getcol('TIME_CENTROID')),np.amax(myMS.getcol('TIME_CENTROID'))] + timestep=myMS.getcell('INTERVAL',0) + + pointing= tab.table(myMS.getkeyword('FIELD')).getcell('PHASE_DIR',0); + stations = tab.table(myMS.getkeyword('ANTENNA')).getcol('NAME') + station_pos = tab.table(myMS.getkeyword('ANTENNA')).getcol('POSITION') + + return (timerange,timestep,pointing.flatten(),stations,station_pos) + +def getPPsimple(height=[450.e3,],mPosition=[0.,0.,0.],direction=[0.,0.,0.]): + '''get piercepoints for antenna position mPosition in m, direction ITRF in m on unit sphere and for array of heights, assuming a spherical Earth''' + height=np.array(height) + stX=mPosition[0] + stY=mPosition[1] + stZ=mPosition[2] + x=np.divide(stX,(R_earth+height)) + y=np.divide(stY,(R_earth+height)) + z=np.divide(stZ,(R_earth+height)) + c = x*x + y*y + z*z - 1.0; + + dx=np.divide(direction[0],(R_earth+height)) + dy=np.divide(direction[1],(R_earth+height)) + dz=np.divide(direction[2],(R_earth+height)) + + a = dx*dx + dy*dy + dz*dz; + b = x*dx + y*dy + z*dz; + + alpha = (-b + np.sqrt(b*b - a*c))/a; + + pp=np.zeros(height.shape+(3,)) + pp[:,0]=stX+alpha*direction[0] + pp[:,1]=stY+alpha*direction[1] + pp[:,2]=stZ+alpha*direction[2] + + am=np.divide(1.,pp[:,0]*dx+pp[:,1]*dy+pp[:,2]*dz) + return pp,am + +def getPPsimpleAngle(height=[450.e3,],mPosition=[0.,0.,0.],direction=[0.,0.,0.]): + '''get (lon,lat,h values) of piercepoints for antenna position mPosition in m, direction ITRF in m on unit sphere and for array of heights, assuming a spherical Earth''' + height=np.array(height) + stX=mPosition[0] + stY=mPosition[1] + stZ=mPosition[2] + x=np.divide(stX,(R_earth+height)) + y=np.divide(stY,(R_earth+height)) + z=np.divide(stZ,(R_earth+height)) + + c = x*x + y*y + z*z - 1.0; + + dx=np.divide(direction[0],(R_earth+height)) + dy=np.divide(direction[1],(R_earth+height)) + dz=np.divide(direction[2],(R_earth+height)) + + a = dx*dx + dy*dy + dz*dz; + b = x*dx + y*dy + z*dz; + + alpha = (-b + np.sqrt(b*b - a*c))/a; + + + pp=np.zeros(height.shape+(3,)) + pp[:,0]=stX+alpha*direction[0] + pp[:,1]=stY+alpha*direction[1] + pp[:,2]=stZ+alpha*direction[2] + + am=np.divide(1.,pp[:,0]*dx+pp[:,1]*dy+pp[:,2]*dz) + + ppl=np.zeros(height.shape+(3,)) + ppl[:,0]=np.atan2(pp[:,1],pp[:0]) + ppl[:,1]=np.atan2(pp[:,2],np.sqrt(pp[:0]*pp[:,0]+pp[:1]*pp[:,1])) + ppl[:,2]=heigth + + return ppl,am + +def getPP(h=450e3,mPosition=[0.,0.,0.],direction=[0.,0.,0.]): + stationX = mPosition[0]; + stationY = mPosition[1]; + stationZ = mPosition[2]; + + ion_ellipsoid_a = earth_ellipsoid_a + h; + ion_ellipsoid_a2_inv = 1.0 / (ion_ellipsoid_a * ion_ellipsoid_a); + ion_ellipsoid_b = earth_ellipsoid_b + h; + ion_ellipsoid_b2_inv = 1.0 / (ion_ellipsoid_b * ion_ellipsoid_b); + + x = stationX/ion_ellipsoid_a; + y = stationY/ion_ellipsoid_a; + z = stationZ/ion_ellipsoid_b; + c = x*x + y*y + z*z - 1.0; + + dx = direction [0]/ ion_ellipsoid_a; + dy = direction [1] / ion_ellipsoid_a; + dz = direction [2] / ion_ellipsoid_b; + + a = dx*dx + dy*dy + dz*dz; + b = x*dx + y*dy + z*dz; + alpha = (-b + sqrt(b*b - a*c))/a; + pp_x = stationX + alpha*direction[0]; + pp_y = stationY + alpha*direction[1] + pp_z = stationZ + alpha*direction[2]; + + normal_x = pp_x * ion_ellipsoid_a2_inv; + normal_y = pp_y * ion_ellipsoid_a2_inv; + normal_z = pp_z * ion_ellipsoid_b2_inv; + norm_normal2 = normal_x*normal_x + normal_y*normal_y + normal_z*normal_z; + norm_normal = sqrt(norm_normal2); + sin_lat2 = normal_z*normal_z / norm_normal2; + + + g = 1.0 - earth_ellipsoid_e2*sin_lat2; + sqrt_g = sqrt(g); + + M = earth_ellipsoid_b2 / ( earth_ellipsoid_a * g * sqrt_g ); + N = earth_ellipsoid_a / sqrt_g; + + local_ion_ellipsoid_e2 = (M-N) / ((M+h)*sin_lat2 - N - h); + local_ion_ellipsoid_a = (N+h) * sqrt(1.0 - local_ion_ellipsoid_e2*sin_lat2); + local_ion_ellipsoid_b = local_ion_ellipsoid_a*sqrt(1.0 - local_ion_ellipsoid_e2); + + z_offset = ((1.0-earth_ellipsoid_e2)*N + h - (1.0-local_ion_ellipsoid_e2)*(N+h)) * sqrt(sin_lat2); + + x1 = stationX/local_ion_ellipsoid_a; + y1 = stationY/local_ion_ellipsoid_a; + z1 = (stationZ-z_offset)/local_ion_ellipsoid_b; + c1 = x1*x1 + y1*y1 + z1*z1 - 1.0; + + dx = direction[0] / local_ion_ellipsoid_a; + dy = direction[1] / local_ion_ellipsoid_a; + dz = direction[2] / local_ion_ellipsoid_b; + a = dx*dx + dy*dy + dz*dz; + b = x1*dx + y1*dy + z1*dz; + alpha = (-b + sqrt(b*b - a*c1))/a; + + pp_x = stationX + alpha*direction[0]; + pp_y = stationY + alpha*direction[1] + pp_z = stationZ + alpha*direction[2]; + + normal_x = pp_x / (local_ion_ellipsoid_a * local_ion_ellipsoid_a); + normal_y = pp_y / (local_ion_ellipsoid_a * local_ion_ellipsoid_a); + normal_z = (pp_z-z_offset) / (local_ion_ellipsoid_b * local_ion_ellipsoid_b); + + norm_normal2 = normal_x*normal_x + normal_y*normal_y + normal_z*normal_z; + norm_normal = sqrt(norm_normal2); + + pp_airmass = norm_normal / (direction[0]*normal_x + direction[1]*normal_y + direction[2]*normal_z); + + return (np.array([[pp_x,pp_y,pp_z]]),pp_airmass) + +class PPdummy: + pass + +def getPiercePoints(time,source_positions,station_positions,height=450.e3): + me=measures() + piercepoints=PPdummy() + piercepoints.positions_xyz=np.zeros((source_positions.shape[0],station_positions.shape[0],3),dtype=np.float64) + piercepoints.positions=np.zeros((source_positions.shape[0],station_positions.shape[0],2),dtype=np.float64) + piercepoints.zenith_angles=np.zeros((source_positions.shape[0],station_positions.shape[0]),dtype=np.float64) + for isrc in range(source_positions.shape[0]) : + radec=source_positions[isrc] + for istat in range(station_positions.shape[0]): + stat_pos=station_positions[istat] + azel=radec2azel(radec[0],radec[1],time=str(time)+'s',pos=stat_pos) + az=azel['m0']['value']; + el=azel['m1']['value']; + lonlat=getLonLatStation(az,el,pos=stat_pos); + + lon=lonlat['m0']['value']; + lat=lonlat['m1']['value']; + + # convert to itrf coordinates on sphere with radius 1 + diritrf=[cos(lat)*cos(lon),cos(lat)*sin(lon),sin(lat)] + (pp,am)=getPP(h=height,mPosition=stat_pos,direction=diritrf) + piercepoints.positions_xyz[isrc,istat]=np.array(pp[0]) + piercepoints.zenith_angles[isrc,istat]=np.arccos(1./am) + pp1position=me.position("ITRF",str(pp[0,0])+'m',str(pp[0,1])+'m',str(pp[0,2])+'m') + # print "pp", degrees(pp1position['m0']['value']),degrees(pp1position['m1']['value']) + piercepoints.positions[isrc,istat,0] = pp1position['m0']['value']; + piercepoints.positions[isrc,istat,1] = pp1position['m1']['value']; + return piercepoints + +def getLonLat(pos): + #converts ITRF pos in xyz to lon lat + me=measures() + a=me.measure(me.position('ITRF',str(pos[0])+'m',str(pos[1])+'m',str(pos[2])+'m'),"ITRF"); + return (a['m0']['value'],a['m1']['value']) + +def getLonLatStation(az=0,el=0,pos=posCS002): + #gets converts local station direction to ITRF lon/lat + if not isinstance(az,str): + az=str(az)+'rad'; + if not isinstance(el,str): + el=str(el)+'rad'; + me=measures() + me.do_frame(me.position('ITRF',str(pos[0])+'m',str(pos[1])+'m',str(pos[2])+'m')) + #me.do_frame(me.epoch('utc', 'today')) + direction=me.direction("AZEL",az,el) + return me.measure(direction,"ITRF"); + + +def radec2azel(ra,dec,time, pos): + me=measures(); + if type(ra)!=str: + ra=str(ra)+'rad'; + if type(dec)!=str: + dec=str(dec)+'rad'; + phasedir=me.direction('J2000',ra,dec) + t=me.epoch("UTC",qa.quantity(time)); + me.do_frame(t); + + p = me.position('ITRF',str(pos[0])+'m',str(pos[1])+'m',str(pos[2])+'m') + me.do_frame(p); + + azel = me.measure(phasedir,'azel'); + return azel; + +def azel2radec(az,el,time, pos): + me=measures(); + if type(az)!=str: + az=str(az)+'rad'; + if type(el)!=str: + el=str(el)+'rad'; + phasedir=me.direction('AZEL',az,el) + t=me.epoch("UTC",qa.quantity(time)); + me.do_frame(t); + + p = me.position('ITRF',str(pos[0])+'m',str(pos[1])+'m',str(pos[2])+'m') + me.do_frame(p); + + radec = me.measure(phasedir,'RADEC'); + return radec; + + + +def getStatPos(stations,AFPath='/opt/lofar/etc/StaticMetaData/',Field='LBA'): + StatPos=[]; + for st in stations: + antFile=open(AFPath+st+"-AntennaField.conf") + line=antFile.readline() + while len(line)>0 and line[:len(Field)]!=Field: + + line=antFile.readline() + if line[:len(Field)]==Field: + StatPos.append([float(stp) for stp in antFile.readline().split()[2:5]]) + else: + print("Field",Field,"for",st,"not found,putting zeros") + StatPos.append([0,0,0]) + antFile.close() + return StatPos diff --git a/CEP/Calibration/ExpIon/src/__init__.py b/CEP/Calibration/ExpIon/src/__init__.py new file mode 100755 index 0000000000000000000000000000000000000000..21bc2dc8a367fcbf4e108a15cbecd7bec1c375a3 --- /dev/null +++ b/CEP/Calibration/ExpIon/src/__init__.py @@ -0,0 +1,26 @@ +# -*- coding: iso-8859-1 -*- +# __init__.py: Top level .py file for python solution analysis tools. +# +# Copyright (C) 2010 +# ASTRON (Netherlands Institute for Radio Astronomy) +# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +# +# This file is part of the LOFAR software suite. +# The LOFAR software suite 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 3 of the License, or +# (at your option) any later version. +# +# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +# +# $Id: __init__.py 12729 2010-03-24 13:39:59Z vdtol $ + +#from ionosphere import * + +__all__ = ['ionosphere', 'parmdbmain'] \ No newline at end of file diff --git a/CEP/Calibration/ExpIon/src/acalc.py b/CEP/Calibration/ExpIon/src/acalc.py new file mode 100644 index 0000000000000000000000000000000000000000..f888fc74daa919ee47f7d9f06a6842826ee62abc --- /dev/null +++ b/CEP/Calibration/ExpIon/src/acalc.py @@ -0,0 +1,234 @@ +############################################################################### + +# import Python modules +from math import pi, atan2, degrees, radians + +# import 3rd party modules +from numpy import * + +# import user modules +from .error import * +try: + import _acalc +except: + __acalc = False +else: + __acalc = True + +############################################################################### +# NOTE: there are several cases where one needs to preserve the sign of zero, +# e.g. +0 and -0. +############################################################################### + +def factorial( n ): + nn = int( n ) + if ( nn < 0 ): + fac = None + else: + if __acalc: + fac = _acalc.factorial( nn ) + else: + fac = int( 1 ) + while ( nn > 0 ): + fac = fac * int( nn ) + nn = nn - 1 + return fac + +############################################################################### + +def binomial( n, k ): + nn = int( n ) + kk = int( k ) + if ( kk < 0 ) or ( kk > nn ): + binom = None + else: + if __acalc: + binom = _acalc.binomial( nn, kk ) + else: + binom = factorial( nn ) / ( factorial( kk ) * factorial( nn - kk ) ) + return binom + +############################################################################### + +def complex_to_r_phi( c ): + if __acalc: + [ r, phi ] = _acalc.complex_to_r_phi( [ c.real, c.imag ] ) + else: + r = abs( c ) + phi = degrees( log( c ).imag ) + return [ r, phi ] + +############################################################################### + +def r_phi_to_complex( rp ): + if __acalc: + [ cr, ci ] = _acalc.r_phi_to_complex( rp ) + c = complex( cr, ci ) + else: + [ r, phi ] = rp + c = r * exp( complex( 0., 1. ) * radians( phi ) ) + return c + +############################################################################### + +def is_array( a ): + return isinstance( a, type( array( [ 1 ] ) ) ) + +############################################################################### + +def azeros( x ): + if ( len( shape( x ) ) == 0 ): + zero = 0. + else: + zero = zeros( shape = x.shape, dtype = x.dtype ) + return zero + +############################################################################### + +def aones( x ): + if ( len( x.shape ) == 0 ): + one = 1. + else: + one = ones( shape = x.shape, dtype = x.dtype ) + return one + +############################################################################### + +def aatan2( y, x ): + if ( shape( x ) != shape( y ) ): + raise error( 'x and y have different shapes' ) + if ( len( shape( x ) ) == 0 ): + z = atan2( y, x ) + else: + xx = x.ravel() + yy = y.ravel() + zz = array( [ atan2( yy[ i ], xx[ i ] ) for i in range( len( xx ) ) ], dtype = x.dtype ) + z = zz.reshape( x.shape ) + return z + +############################################################################### +def asign( x ): +# this function also separates between -0 and +0 + if ( not is_array( x ) ): + s = ( - 2. * float( aatan2( x, x ) < 0. ) + 1. ) + else: + s = ( - 2. * array( aatan2( x, x ) < 0., dtype = x.dtype ) + 1. ) + return s + +############################################################################### + +def amodulo( x, y ): + if ( not is_array( x ) ): + if ( not is_array( y ) ): + m = x - y * floor( x / ( y + float( y == 0. ) ) ) + else: + xx = x * aones( y ) + m = xx - y * floor( x / ( y + array( y == 0., dtype = y.dtype ) ) ) + else: + if ( not is_array( y ) ): + yy = y * aones( x ) + m = x - yy * floor( x / ( yy + array( yy == 0., dtype = yy.dtype ) ) ) + else: + m = x - y * floor( x / ( y + array( y == 0., dtype = y.dtype ) ) ) + return m + +############################################################################### + +def aradians( x ): + r = x * ( pi / 180. ) + return r + +############################################################################### + +def adegrees( x ): + r = x * ( 180. / pi ) + return r + +############################################################################### + +def awhere( a ): + return transpose( array( where( a ) ) ) + +############################################################################### + +def aput( data, sel, sub ): +# data, sel and sub must be arrays + + # check input dimensions + if ( len( sel ) == 0 ): + return data.copy() + if ( len( sel.ravel() ) == 0 ): + return data.copy() + if ( sel.shape[ 1 ] > len( data.shape ) ): + raise error( 'number of dimensions of index array is higher than that of data array' ) + asub = array( sub ) + if ( len( asub.shape ) == len( data.shape ) - sel.shape[ 1 ] ): + if ( asub.shape != data.shape[ sel.shape[ 1 ] : ] ): + raise error( 'shape of subarray does not match selected data' ) + asub = resize( asub, [ sel.shape[ 0 ] ] + list( data.shape[ sel.shape[ 1 ] : ] ) ) + elif ( len( asub.shape ) == len( data.shape ) - sel.shape[ 1 ] + 1 ): + if ( list( asub.shape ) != [ sel.shape[ 0 ] ] + list( data.shape[ sel.shape[ 1 ] : ] ) ): + raise error( 'shape of subarray does not match selected data' ) + + # collapse and write data + coffset = [ int( product( data.shape[ i : sel.shape[ 1 ] ] ) ) for i in range( 1, 1 + sel.shape[ 1 ] ) ] + coffset[ - 1 ] = 1 + csel = add.reduce( sel * array( coffset ), 1 ) + subsize = int( product( data.shape[ sel.shape[ 1 ] : ] ) ) + suboffset = resize( arange( subsize ), [ sel.shape[ 0 ], subsize ] ) + csel = ( transpose( resize( csel * subsize, [ subsize, sel.shape[ 0 ] ] ) ) + suboffset ).ravel() + cdata = data.copy().ravel() + csub = asub.ravel() + put( cdata, csel, csub ) + + return cdata.reshape( data.shape ) + +############################################################################### + +def aget( data, sel ): +# data and sel must be arrays + + # check input dimensions + if ( len( sel ) == 0 ): + return array( [], dtype = data.dtype ) + if ( len( sel.ravel() ) == 0 ): + return array( [], dtype = data.dtype ) + if ( sel.shape[ 1 ] > len( data.shape ) ): + raise error( 'number of dimensions of index array is higher than that of data array' ) + + # collapse data along sel axes + cdata_len = int( product( data.shape[ 0 : sel.shape[ 1 ] ] ) ) + cdata_shape = [ cdata_len ] + list( data.shape[ sel.shape[ 1 ] : ] ) + cdata = data.reshape( cdata_shape ) + coffset = [ int( product( data.shape[ i : sel.shape[ 1 ] ] ) ) for i in range( 1, 1 + sel.shape[ 1 ] ) ] + coffset[ - 1 ] = 1 + csel = add.reduce( sel * array( coffset ), 1 ) + + return take( cdata, csel, axis = 0 ) + +############################################################################### + +def amean_phase( data ): + # determine phase average (with trick to get around possible phase wrap problems) + phases = array( [ data ], dtype = float64 ).ravel() + offsets = [ 0., 90., 180., 270. ] + mean_phases = [ ( amodulo( ( phases + offsets[ j ] ) + 180., 360. ) - 180. ).mean() + for j in range( len( offsets ) ) ] + var_phases = [ ( ( ( amodulo( ( phases + offsets[ j ] ) + 180., 360. ) - 180. ) - + mean_phases[ j ] )**2 ).mean() for j in range( len( offsets ) ) ] + j = var_phases.index( min( var_phases ) ) + mean_phase = mean_phases[ j ] - offsets[ j ] + return float( amodulo( mean_phase + 180, 360. ) - 180. ) + +############################################################################### + +def amedian_phase( data ): + # determine phase average (with trick to get around possible phase wrap problems) + mean_phase = amean_phase( data ) + phases = array( [ data ], dtype = float64 ).ravel() + median_phase = median( amodulo( ( phases - mean_phase ) + 180., 360. ) - 180. ) + median_phase = amodulo( ( median_phase + mean_phase ) + 180., 360. ) - 180. + return float( median_phase ) + +############################################################################### + diff --git a/CEP/Calibration/ExpIon/src/baselinefitting.cc b/CEP/Calibration/ExpIon/src/baselinefitting.cc new file mode 100644 index 0000000000000000000000000000000000000000..792caea105a3dba5cf7bf79970070f53a0050db4 --- /dev/null +++ b/CEP/Calibration/ExpIon/src/baselinefitting.cc @@ -0,0 +1,228 @@ +//# fitting.cc: Clock and TEC fitting using cascore +//# +//# +//# Copyright (C) 2012 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR software suite. +//# The LOFAR software suite 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 3 of the License, or +//# (at your option) any later version. +//# +//# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id: $ + +#include <lofar_config.h> +#include <Common/OpenMP.h> + +#include <casacore/casa/Containers/ValueHolder.h> +#include <casacore/casa/Containers/Record.h> +#include <casacore/casa/Utilities/CountedPtr.h> +#include <casacore/scimath/Fitting/LSQFit.h> + +#if defined(HAVE_CASACORE) +#include <casacore/python/Converters/PycExcp.h> +#include <casacore/python/Converters/PycBasicData.h> +#include <casacore/python/Converters/PycValueHolder.h> +#include <casacore/python/Converters/PycRecord.h> +#define pyrap python +#else +#include <pyrap/Converters/PycExcp.h> +#include <pyrap/Converters/PycBasicData.h> +#include <pyrap/Converters/PycValueHolder.h> +#include <pyrap/Converters/PycRecord.h> +#endif + +#include <boost/python.hpp> +#include <boost/python/args.hpp> + +using namespace casacore; +using namespace boost::python; + +namespace LOFAR +{ +namespace ExpIon +{ + +ValueHolder fit(const ValueHolder &phases_vh, const ValueHolder &A_vh, const ValueHolder &init_vh, + const ValueHolder &flags_vh, const ValueHolder &constant_parm_vh ) +{ + // Arrays are passed as ValueHolder + // They are Array is extracted by the asArray<Type> methods + // They pointer to the actual data is obtained by the Array.data() method. + + // Get the phases + Array<Float> phases = phases_vh.asArrayFloat(); + Float *phases_data = phases.data(); + + // Get the flags + Array<Bool> flags(flags_vh.asArrayBool()); + Bool noflags = (flags.ndim() == 0); + Bool *flags_data; + if (!noflags) + { + flags_data = flags.data(); + } + + IPosition s = phases.shape(); + int N_station = s[0]; + int N_freq = s[1]; + + // Get the matrix with basis functions + Array<Float> A = A_vh.asArrayFloat(); + Float *A_data = A.data(); + + IPosition s1 = A.shape(); + + int N_coeff = s1[0]; + int N_parm = N_station * N_coeff; + Float sol[N_parm]; + + Array<Float> init(init_vh.asArrayFloat()); + if (init.ndim() == 0) + { + for(int i = 0; i < N_parm; i++) sol[i] = 0.0; + } + else + { + for(int i = 0; i < N_parm; i++) sol[i] = init.data()[i]; + } + + // Get the flags indicating which parameters are constant_parm + // i.e. are not a free parameter in the minimization problem + Array<Bool> constant_parm(constant_parm_vh.asArrayBool()); + Bool no_constant_parm = (constant_parm.ndim() == 0); + Bool *constant_parm_data; + if (!no_constant_parm) + { + constant_parm_data = constant_parm.data(); + } + + Float cEq[N_parm]; + for(int i=0; i<N_parm; ++i) cEq[i] = 0.0; + + int N_thread = OpenMP::maxThreads(); + std::vector<LSQFit> lnl(N_thread); + + uInt nr = 0; + + for (int iter = 0; iter<1000; iter++) + { + for(int i = 0; i<N_thread; i++) { + lnl[i] = LSQFit(N_parm); + } + #pragma omp parallel + { + int threadNum = OpenMP::threadNum(); + Float derivatives_re[2*N_coeff]; + Float derivatives_im[2*N_coeff]; + uInt idx[2*N_coeff]; + #pragma omp for + for(int k = 0; k<N_freq; k++) + { + Float *A_data_k = A_data + k*N_coeff; + Float *phases_data_k = phases_data + k*N_station; + Bool *flags_data_k = flags_data + k*N_station; + for(int i = 1; i<N_station; i++) + { + Float phases_data_k_i = phases_data_k[i]; + Bool flags_data_k_i = flags_data_k[i]; + for(int j = 0; j<i; j++) + { + if (noflags || !(flags_data_k_i || flags_data_k[j])) + { + Float phase_ij_obs = phases_data_k_i - phases_data_k[j]; + Float phase_ij_model = 0.0; + + for (int l = 0; l<N_coeff; l++) + { + Float coeff = A_data_k[l]; + phase_ij_model += (sol[i + l*N_station] - sol[j + l*N_station]) * coeff ; + } + Float sin_dphase, cos_dphase; +#if defined(_LIBCPP_VERSION) +#define sincosf __sincosf +#endif + sincosf(phase_ij_obs - phase_ij_model, &sin_dphase, &cos_dphase); + Float residual_re = cos_dphase - 1.0; + Float residual_im = sin_dphase; + Float derivative_re = -sin_dphase; + Float derivative_im = cos_dphase; + + + for (int l = 0; l<N_coeff; l++) + { + Float coeff = A_data_k[l]; + Float a = derivative_re * coeff; + Float b = derivative_im * coeff; + derivatives_re[l] = a; + derivatives_re[N_coeff + l] = -a; + derivatives_im[l] = b; + derivatives_im[N_coeff + l] = -b; + idx[l] = i+l*N_station; + idx[N_coeff + l] = j+l*N_station; + } + lnl[threadNum].makeNorm(uInt(2*N_coeff), (uInt*) idx, (Float*) derivatives_re, Float(1.0), residual_re); + lnl[threadNum].makeNorm(uInt(2*N_coeff), (uInt*) idx, (Float*) derivatives_im, Float(1.0), residual_im); + } + } + } + } + } + + for(int i = 1; i<N_thread; i++) + { + lnl[0].merge(lnl[i]); + } + + if ((!no_constant_parm) ) + { + for (int i = 0; i<N_parm; i++) + { + if (constant_parm_data[i]) + { + cEq[i] = 1.0; + lnl[0].addConstraint( (Float*) cEq, 0.0); + cEq[i] = 0.0; + } + } + } + + if (!lnl[0].solveLoop(nr, sol, True)) + { + cout << "Error in loop: " << nr << endl; + break; + } + if (lnl[0].isReady()) + { + break; + } + } + + Array<Float> solutions(IPosition(2, N_station, N_coeff), sol); + ValueHolder result(solutions); + return result; +}; + + +} // namespace ExpIon +} // namespace LOFAR + +BOOST_PYTHON_MODULE(_baselinefitting) +{ + casacore::pyrap::register_convert_excp(); + casacore::pyrap::register_convert_basicdata(); + casacore::pyrap::register_convert_casa_valueholder(); + casacore::pyrap::register_convert_casa_record(); + + def("fit", LOFAR::ExpIon::fit); +} diff --git a/CEP/Calibration/ExpIon/src/baselinefitting.py b/CEP/Calibration/ExpIon/src/baselinefitting.py new file mode 100644 index 0000000000000000000000000000000000000000..4708eea126511836feeeec40920344c173cebd05 --- /dev/null +++ b/CEP/Calibration/ExpIon/src/baselinefitting.py @@ -0,0 +1,35 @@ +"""Fit basefunctions to phases using all baselines + +Application: Clock-TEC separation + + +The phase can be described by a linear model + +.. math:: + phases = A p + +where the columns of matrix A contain the basefunctions and p are the parameters + +The same equation holds for the phases of multiple stations +The dimension of phases is then N_freq x N_station and +the dimension of p is N_freq x N_param + +The cost function that will be minimized is + +.. math:: + \\sum_{i=1}^{N_{station}-1}\\sum_{j=0}^{i-1} \\sum_{k=0}^{N_{freq}} \\| \\exp(\imath(\\Delta phase_{ijk} - \\Delta modelphase_{ijk})) \\|^2 +where +.. math:: + \\Delta phase_{ijk} = + \\Delta modelphase_{ijk} = + + + +""" + + +import _baselinefitting + +def fit(phases, A, p_0 = None, flags = None, constant_parms = None): + """see module description for detailed info""" + return _baselinefitting.fit(phases, A, p_0, flags, constant_parms) \ No newline at end of file diff --git a/CEP/Calibration/ExpIon/src/calibrate-ion b/CEP/Calibration/ExpIon/src/calibrate-ion new file mode 100755 index 0000000000000000000000000000000000000000..6811a1d0e0fa6d9c0fb9a21d5cde40df95cbacb5 --- /dev/null +++ b/CEP/Calibration/ExpIon/src/calibrate-ion @@ -0,0 +1,231 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# Copyright (C) 2007 +# ASTRON (Netherlands Institute for Radio Astronomy) +# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +# +# This file is part of the LOFAR software suite. +# The LOFAR software suite 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 3 of the License, or +# (at your option) any later version. +# +# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +# +# $Id$ + +import sys +import os +import re +import lofar.expion.ionosphere as ionosphere +import lofar.parameterset + +from pylab import * + +arguments = sys.argv.__iter__() +scriptname = os.path.basename(arguments.next()) + +def print_error(msg): + print "%s: %s" % (scriptname, msg) + +sky_name = 'sky' +instrument_name = 'instrument' +clusterdescfile = "~/CEP.clusterdesc" +globaldb = 'globaldb' + +def display_help_and_exit(dummy = None): + print '''usage: + %s [options] <parsetfile> <gdsfiles> ''' % scriptname + print + print '''arguments: + <gdsfiles> gds file(s)''' + print + print '''options: + -h, --help display this help text + --cluster-desc <cluster description file> + Define cluster description file + (default: ~/CEP.clusterdesc) + --sky-name <skyname> + Define basename for the sky model parmdb + (default: sky) + --instrument-name <instrumentname> + Define basename of the instrument parmdb + (default: instrument) + ''' + exit() + +def set_clusterdesc(arguments): + global clusterdescfile + try: + clusterdescfile = arguments.next() + if clusterdescfile[0] == '-': + raise ValueError + except (KeyError, ValueError): + print_error( "--cluster-desc should be followed by the name of the cluster description file") + exit() + +def set_instrument_name(arguments): + global instrument_name + try: + instrument_name = arguments.next() + if instrument_name[0] == '-': + raise ValueError + except (KeyError, ValueError): + print_error( "--instrument-name should be followed by the basename of the instrument parmdb") + exit() + +def set_sky_name(arguments): + global sky_name + try: + sky_name = arguments.next() + if sky_name[0] == '-': + raise ValueError + except (KeyError, ValueError): + print_error( "--sky-name should be followed by the basename of the sky parmdb") + exit() + +def set_globaldb(arguments): + global globaldb + try: + globaldb = arguments.next() + if globaldb[0] == '-': + raise ValueError + except (KeyError, ValueError): + print_error( "--globaldb should be followed by the global") + exit() + +options = { '-h': display_help_and_exit, + '--help': display_help_and_exit, + '--cluster-desc': set_clusterdesc, + '--sky': set_sky_name, + '--instrument-name': set_instrument_name, + '--globaldb': set_globaldb} + +while True: + try: + argument = arguments.next() + except StopIteration: + print_error( "No parset file and no gds file(s) specified" ) + display_help_and_exit() + if argument[0] == '-': + try: + options[argument](arguments) + except KeyError: + print_error( "Unknown option: " + argument ) + display_help_and_exit() + else: + break + +parsetfile = argument +parset = lofar.parameterset.parameterset( parsetfile ) + +print clusterdescfile +clusterdescfile = os.path.expanduser( clusterdescfile ) +print clusterdescfile + +clusterdesc = lofar.parameterset.parameterset( clusterdescfile ) + +gdsfiles = [] +gdsfiles.extend(arguments) + +if len(gdsfiles) == 0 : + print_error( "No gds file(s) or globaldb specified" ) + display_help_and_exit() + + +print "parset-file: " + repr(parsetfile) +print "gds-files: " + repr(gdsfiles) +print "instrument-name: " + repr(instrument_name) +print "sky-name: " + repr(sky_name) + +stations = parset.getStringVector("ExpIon.Stations", []) +sources = parset.getStringVector("ExpIon.Sources", []) + +d = {'XX' : 0, 'YY' : 1} +l = parset.getStringVector("ExpIon.Polarizations", ['XX', 'YY']) +polarizations = [d[key] for key in l] + +DirectionalGainEnable = parset.getBool( "ExpIon.DirectionalGain.Enable", False ) +GainEnable = parset.getBool( "ExpIon.Gain.Enable", False ) +PhasorsEnable = parset.getBool( "ExpIon.Phasors.Enable", False ) +RotationEnable = parset.getBool( "ExpIon.Rotation.Enable", False ) +print "RotationEnable:", RotationEnable + +print repr(stations) +print repr(sources) +print repr(DirectionalGainEnable) + +ion_model = ionosphere.IonosphericModel(gdsfiles, clusterdescfile, + GainEnable = GainEnable, + DirectionalGainEnable = DirectionalGainEnable, + RotationEnable = RotationEnable, + PhasorsEnable = PhasorsEnable, + stations = stations, + sources = sources, + polarizations = polarizations, + instrument_name = instrument_name, + globaldb = globaldb) + +def operation_clocktec ( step ): + ClockEnable = parset.getBool('.'.join(["ExpIon.Steps", step, "Clock.Enable"]), True ) + TECEnable = parset.getBool('.'.join(["ExpIon.Steps", step, "TEC.Enable"]), True ) + print '.'.join(["ExpIon.Steps", step, "Stations"]) + stations = parset.getStringVector('.'.join(["ExpIon.Steps", step, "Stations"]), []) + print "stations: ", stations + if ClockEnable or TECEnable : + ion_model.ClockTEC( ClockEnable = ClockEnable, TECEnable = TECEnable, stations = stations ) + + +def operation_fitmodel( step ) : + height = parset.getFloat('.'.join(["ExpIon.Steps", step, "height"]), 200.0e3 ) + ion_model.calculate_piercepoints(height = height) + + order = parset.getInt('.'.join(["ExpIon.Steps", step, "order"]), 2 ) + ion_model.calculate_basevectors( order = order ) + + ion_model.fit_model() + +def operation_makemovie( step ) : + npixels = parset.getInt('.'.join(["ExpIon.Steps", step, "npixels"]), 100 ) + extent = parset.getFloat('.'.join(["ExpIon.Steps", step, "extent"]), 0 ) + clim = parset.getFloatVector('.'.join(["ExpIon.Steps", step, "clim"]), [] ) + if len(clim) == 2: + vmin = clim[0] + vmax = clim[1] + else: + vmin = 0 + vmax = 0 + ion_model.make_movie(extent = extent, npixels = npixels, vmin = vmin, vmax = vmax) + +def operation_store ( step ): + ClockTEC_parmdbname = parset.getString('.'.join(["ExpIon.Steps", step, "ClockTEC.parmdb"]), "ClockTEC.parmdb") + phases_parmdbname = parset.getString('.'.join(["ExpIon.Steps", step, "Phases.parmdb"]), "ionosphere") + ion_model.write_to_parmdb( ) + #ion_model.write_phases_to_parmdb( ClockTEC_parmdbname, phases_parmdbname ) + +def operation_plot ( step ): + print ion_model.TEC.shape + figure(1) + plot(ion_model.Clock[0,:,:]) + figure(2) + plot(ion_model.TEC[0,:,:]) + show() + +Operations = { "CLOCKTEC": operation_clocktec , + "FITMODEL": operation_fitmodel, + "MAKEMOVIE": operation_makemovie, + "STORE": operation_store, + "PLOT": operation_plot } + +steps = parset.getStringVector("ExpIon.Steps", [] ) + +for step in steps: + operation = parset.getString( '.'.join( [ "ExpIon.Steps", step, "Operation" ] ) ) + Operations[ operation ] ( step ) + diff --git a/CEP/Calibration/ExpIon/src/client.py b/CEP/Calibration/ExpIon/src/client.py new file mode 100644 index 0000000000000000000000000000000000000000..2d94ba48702e20f23a3757178ceaebe7b4529900 --- /dev/null +++ b/CEP/Calibration/ExpIon/src/client.py @@ -0,0 +1,41 @@ + # Compatability layer for IPython.client 0.10 and IPython.parallel >= 0.11 + +import IPython + +if [int(v) for v in IPython.__version__.split('.')] < [0,11] : + from IPython.kernel import client + import atexit +# Without the following statement python sometimes throws an exception on exit + atexit.register(client.rit.stop) + MultiEngineClient = client.MultiEngineClient + TaskClient = client.TaskClient + MapTask = client.MapTask +else: + from IPython.parallel import Client + + def MultiEngineClient() : + rc = Client() + dview = rc[:] + return dview + + class TaskClient : + def __init__(self) : + self.rc = Client() + self.dview = self.rc[:] + self.lbview = self.rc.load_balanced_view() + + def run(self, maptask) : + return self.lbview.apply(maptask.func, *maptask.args) + + def get_task_result(self, task, block = True) : + return task.get() + + def clear(self): + pass + + class MapTask : + def __init__(self, func, args) : + self.func = func + self.args = args + pass + \ No newline at end of file diff --git a/CEP/Calibration/ExpIon/src/error.py b/CEP/Calibration/ExpIon/src/error.py new file mode 100644 index 0000000000000000000000000000000000000000..2e9ec7edc9e6d2e6651f59600d927dab129eebb7 --- /dev/null +++ b/CEP/Calibration/ExpIon/src/error.py @@ -0,0 +1,6 @@ +############################################################################### + +def error( string ): + raise RuntimeError( string ) + +############################################################################### diff --git a/CEP/Calibration/ExpIon/src/fitClockTEC.py b/CEP/Calibration/ExpIon/src/fitClockTEC.py new file mode 100644 index 0000000000000000000000000000000000000000..5b753e87c4d5ce8c17b3333f9bb4522fb2494249 --- /dev/null +++ b/CEP/Calibration/ExpIon/src/fitClockTEC.py @@ -0,0 +1,938 @@ +import numpy as np; +import lofar.expion.parmdbmain as parmdbmain +import lofar.expion.baselinefitting as fitting +#import lofar.expion.fitting as fitting +from scipy import optimize as opt +import tables as tab +import sys +#from pylab import * +from numpy import * +light_speed=299792458. + +clockarray=0 +tecarray=0 +offsetarray=0 +residualarray=0 + + +def ClockTECfunc(xarray,par): + delay=np.array([-1*par[1]*1e-9]).flatten() #in ns, array has dimension 1, even if scalar + delayfact=2*np.pi*delay[:,np.newaxis]*xarray + TEC=np.array([par[0]]).flatten(); # dTEC in TECU + drefract=-8.4479745e9*TEC[:,np.newaxis]/xarray; + return drefract[:,np.newaxis,:]+delayfact+par[2]; #returns nTEC x nClock x nFreq + +def getRM(ion,station,refStationIdx=0,starttime=0,SBselect='all'): + '''get Rotation Measure vs. time and residual rotation''' + freqs=ion.freqs[:] + nF=freqs.shape[0] + if SBselect=='all': + SBselect=np.ones(nF,dtype=bool) + if isinstance(SBselect,list) and len(SBselect)==2: + SBselect=np.logical_and(freqs>SBselect[0],freqs<SBselect[1]) + ant1 = list(ion.stations[:]).index(station) + ant2 = refStationIdx + A = (light_speed/ freqs[SBselect])**2 + A = np.reshape(A, (-1,1)) + rot=ion.rotation[starttime:,:,ant1][:,SBselect]-ion.rotation[starttime:,:,ant2][:,SBselect] + #remove nans + rot[np.isnan(rot)]=0. + while len(rot.shape)>2: + rot=rot[:,:,0] + + RM=np.remainder(rot+np.pi,2*np.pi)-np.pi + + RMtime= np.dot(1./(np.dot(A.T,A)), np.dot(A.T,RM.T)).flatten() + residuals=RM-RMtime[:,np.newaxis]*A.T + return RMtime,residuals + +def getInitPar(data,dTECArray, dClockArray,freqs,ff=ClockTECfunc): + '''initialize paramaters and unwraps data for fit''' + if np.min(np.absolute(dTECArray))<1e-5: + dTECArray+=0.0001 #just to prevent 0 to be there, since otherwise the fit might get stuck in 0 + nT=dTECArray.shape[0] + nD=dClockArray.shape[0] + par=[dTECArray,dClockArray,0] + # first check all unwrapping possibilities + bigdata=ff(freqs,par) # returns array of shape nT,nD,nF + wraps=np.around(np.divide(bigdata-data,2*np.pi)); + difference= bigdata-data-wraps*2*np.pi + offset=np.average(difference,axis=2); + index=np.unravel_index( + np.argmin( + np.sum(np.absolute((difference.T-offset.T).T),axis=2)) + ,(nT,nD)); + OffsetIn=-1*offset[index]; + par=[dTECArray[index[0]],dClockArray[index[1]],OffsetIn]; + estimate=ff(freqs,par).flatten() + wraps=np.around(np.divide(estimate-data,2*np.pi)); + data[:]=np.add(2*np.pi*wraps,data) + return par + +def getClockTECAll(ph,amp,freqs,stationname,stIdx,polIdx): + global tecarray + global clockarray + global offsetarray + + maxTimesteps=500 + #first unwrap data and get initial guess + nT=ph[:].shape[0] + nF=freqs[:].shape[0] + + stepDelay=.03 + if 'CS' in stationname: + iTEC1=-0.1 + iTEC2=0.1 + iD1=-2 + iD2=2 + else: + iTEC1=-0.4 + iTEC2=0.4 + iD1=-200 + iD2=200 + + if "HBA" in stationname: + stepdTEC=0.0005 + else: + stepdTEC=0.0001 # faster wrapping for LBA + + tmsteps=nT/maxTimesteps + for istep in range(tmsteps): + bigshape=[0,2*min(maxTimesteps,nT-maxTimesteps*istep)+1] + alldata=np.array([]) + allfreqs=[] + for itm in range(istep*maxTimesteps,min(nT,(istep+1)*maxTimesteps)): + + if itm%100==0 and itm>0: + sys.stdout.write(str(itm)+'... '+str(par[0])+' '+str(par[1])+' '+str(par[2])+' ') + sys.stdout.flush() + if itm>0: + iTEC1=par[0]-0.1 + iTEC2=par[0]+0.1 + iD1=par[1]-10 + iD2=par[1]+10 + dTECArray=np.arange(iTEC1,iTEC2,stepdTEC) + dClockArray=np.arange(iD1,iD2,stepDelay) + + flags=(amp[itm,:]!=1); + nrFlags=np.sum(np.logical_not(flags)) + data=ph[itm,:][flags] + tmpfreqs=freqs[flags] + par = getInitPar(data,dTECArray, dClockArray,tmpfreqs,ClockTECfunc,useOffset=False,plot_debug=(itm%100==0)) + + alldata=np.append(alldata,data) + bigshape[0]+=tmpfreqs.shape[0] + allfreqs.append(tmpfreqs) + + + print("got bigmatrix",bigshape) + bigmatrix=np.zeros(bigshape) + idx=0 + + for itm in range(min(nT-istep*maxTimesteps,maxTimesteps)): + nextidx=allfreqs[itm].shape[0] + bigmatrix[idx:idx+nextidx,2*itm]=-8.4479745e9/allfreqs[itm] + bigmatrix[idx:idx+nextidx,2*itm+1]=-2.e-9*np.pi*allfreqs[itm] + idx+=nextidx + bigmatrix[:,-1]+=1 + print("fitting",bigmatrix.shape,alldata.shape) + sol=np.linalg.lstsq(bigmatrix,alldata) + finalpar=sol[0] + print("result",finalpar[0],finalpar[1],finalpar[-1]) + offsetarray[istep*maxTimesteps:(istep+1)*maxTimesteps,stIdx,polIdx]=finalpar[-1] + tecarray[istep*maxTimesteps:(istep+1)*maxTimesteps,stIdx,polIdx]=finalpar[:-1].reshape(-1,2)[:,0] + clockarray[istep*maxTimesteps:(istep+1)*maxTimesteps,stIdx,polIdx]=finalpar[:-1].reshape(-1,2)[:,1] + +def getClockTEC(ph,amp,freqs,SBselect,stationname,stIdx,polIdx,fixedOffset=False): + global tecarray + global clockarray + global offsetarray + global residualarray + + errorf= lambda par,x,data: (-1*par[0]*x*2*np.pi*1e-9+par[1]-data).flatten() #delay function+ offset + #remove nans + ph[np.isnan(ph)]=0. + ph=np.unwrap(np.remainder(ph,2*np.pi)) # unwrap last (=freq) axis + par=[0.,0.] + avg_delay=0. + if ("CS" in stationname) or ("HBA" in stationname): + #do not do this step for RS stations LBA,because ionospheric fluctuations + result=opt.leastsq(errorf,par,args=(freqs,ph)) #get average delay + avg_delay=result[0][0] + + print("avg_delay",stationname,polIdx,avg_delay) + # define the function we want to fit, for core stations keep delay fixed + stepDelay=.3 + if 'CS' in stationname: + fixedDelay=True + if fixedOffset: + ff=lambda x,par:ClockTECfunc(x,[par[0],avg_delay,0.]) + else: + ff=lambda x,par:ClockTECfunc(x,[par[0],avg_delay,par[1]]) + initTEC1=-0.5 + initTEC2=0.5 + initD1=avg_delay + initD2=avg_delay+stepDelay + else: + fixedDelay=False + if fixedOffset: + ff=lambda x,par:ClockTECfunc(x,[par[0],par[1],0.]) + else: + ff=lambda x,par:ClockTECfunc(x,par) + par=[0.,0.,0.] + initTEC1=-2 + initTEC2=2 + if "HBA" in stationname: + initD1=avg_delay-30 + initD2=avg_delay+30 + else: + initD1=-200 + initD2=200 + + if "HBA" in stationname: + stepdTEC=0.01 + else: + stepdTEC=0.008 # faster wrapping for LBA + + errorf = lambda par,x,y: (ff(x,par)-y).flatten() + + nTimes=ph.shape[0] + nF=ph.shape[1] + + success=False + iTEC1=initTEC1 + iTEC2=initTEC2 + iD1=initD1 + iD2=initD2 + finalpar=[0]*nTimes + print(stationname,polIdx,"tm:", end=' ') + for tm in range(0,nTimes): + if tm%100==0: + sys.stdout.write(str(tm)+'...') + sys.stdout.flush() + if tm>0 and success: + iTEC1=finalpar[tm-1][0]-5*stepdTEC + iTEC2=finalpar[tm-1][0]+6*stepdTEC + if not fixedDelay: + iD1=finalpar[tm-1][1]-1*stepDelay + iD2=finalpar[tm-1][1]+2*stepDelay + else: + iTEC1=max(iTEC1-5*stepdTEC,min(initTEC1,iTEC2-5)) + iTEC2=min(iTEC2+6*stepdTEC,max(initTEC2,iTEC2+5)) + if not fixedDelay: + iD1=max(iD1-1*stepDelay,min(initD1,iD2-60)) + iD2=min(iD2+2*stepDelay,max(initD2,iD1+60)) + itm=tm + dTECArray=np.arange(iTEC1,iTEC2,stepdTEC) + dClockArray=np.arange(iD1,iD2,stepDelay) + + flags=(amp[itm,:]!=1); + nrFlags=np.sum(np.logical_not(flags)) + data=ph[itm,:][flags] + tmpfreqs=freqs[flags] + + if nrFlags>0.5*nF: + print("TOO many data points flagged:",tm,tmpfreqs.shape[0],"remaining") + if tm>0: + finalpar[tm]=np.array(finalpar[tm-1]) + else: + finalpar[tm]=np.array(par) + success=False + continue + par = getInitPar(data,dTECArray, dClockArray,tmpfreqs,ClockTECfunc) + if fixedDelay: + par=par[:1]+par[2:] + if fixedOffset: + par=par[:-1] + (finalpar[tm],success)=opt.leastsq(errorf,par,args=(tmpfreqs,data)) + #print "fitted",tm,(finalpar[tm],success) + if not hasattr(finalpar[tm],'__len__'): + finalpar[tm]=[finalpar[tm]] + chi2 = np.average(np.power(errorf(par,tmpfreqs,data), 2)) + if chi2>10: + print("got a Fail",stationname,itm,chi2,finalpar[tm]) + success=False + else: + residualarray[itm,SBselect,stIdx,polIdx][flags]=errorf(finalpar[tm],tmpfreqs,data) + + print('finished') + #acquire lock?, store data + finalpar=np.array(finalpar) + tecarray[:,stIdx,polIdx]=np.array(finalpar)[:,0] + if fixedDelay: + clockarray[:,stIdx,polIdx]=avg_delay*np.ones(clockarray.shape[0]) + if not fixedOffset: + offsetarray[:,stIdx,polIdx]=np.array(finalpar)[:,1] + else: + clockarray[:,stIdx,polIdx]=np.array(finalpar)[:,1] + if not fixedOffset: + offsetarray[:,stIdx,polIdx]=np.array(finalpar)[:,2] + + +def getResidualPhaseWraps(avgResiduals,freqs): + flags=avgResiduals!=0. + nSt=avgResiduals.shape[1] + nF=freqs.shape[0] + wraps=np.zeros((nSt,),dtype=np.float) + for ist in range(nSt): + print(ist) + tmpfreqs=freqs[flags[:,ist]] + nF=tmpfreqs.shape[0] + if nF<10: + print("too many flagged",ist) + continue + basef,steps=getPhaseWrapBase(tmpfreqs) + + data=avgResiduals[flags[:,ist],ist] + wraps[ist]=np.dot(1./(np.dot(basef.T,basef)), np.dot(basef,data)) + return wraps,steps + +def getPhaseWrapBase(freqs): + nF=freqs.shape[0] + A=np.zeros((nF,2),dtype=np.float) + A[:,1] = freqs*2*np.pi*(-1e-9) + A[:,0] = -8.44797245e9/freqs + steps=np.dot(np.dot(np.linalg.inv(np.dot(A.T,A)),A.T),2*np.pi*np.ones((nF,),dtype=np.float)) + basef=np.dot(A,steps)-2*np.pi + return basef,steps + +def getResidualPhaseWraps2(avgResiduals,freqs): + flags=avgResiduals[:,10]==0. + nSt=avgResiduals.shape[1] + nF=freqs.shape[0] + wraps=np.zeros((nSt,),dtype=np.float) + #tmpflags=np.sum(flags[:,np.sum(flags,axis=0)<(nF*0.5)],axis=1) + tmpflags=flags + tmpfreqs=freqs[np.logical_not(tmpflags)] + tmpbasef,steps=getPhaseWrapBase(tmpfreqs) + basef=np.zeros(freqs.shape) + basef[np.logical_not(tmpflags)]=tmpbasef + basef=basef.reshape((-1,1)) + + data=avgResiduals[:,:] + + + wraps=fitting.fit(data,basef,wraps,flags).flatten() + return wraps,steps + + +def getTECBaselineFit(ph,amp,freqs,SBselect,polIdx,stIdx,useOffset=False,stations=[],initSol=[],chi2cut=300.,timeIdx=0): + global tecarray + global offsetarray + global residualarray + amp[np.isnan(ph)]=1 + ph[np.isnan(ph)]=0. + ph=np.unwrap(ph,axis=0) + #first unwrap data and get initial guess + nT=ph.shape[0] + nF=freqs.shape[0] + nSt=ph.shape[2] + nparms=1+(useOffset>0) + sol = np.zeros((nSt,nparms),dtype=np.float) + A=np.zeros((nF,nparms),dtype=np.float) + A[:,0] = -8.44797245e9/freqs + if useOffset: + A[:,1] = np.ones((nF,)) + # init first sol + big_array=(np.arange(-0.1,0.1,0.005)*A[:,0][:,np.newaxis]).T + diff=np.sum(np.absolute(np.remainder(big_array[:,:,np.newaxis]-(ph[0,:,:]-ph[0,:,:][:,[0]])+np.pi,2*np.pi)-np.pi),axis=1) + init_idx=np.argmin(diff,axis=0) + sol[:,0]=init_idx*0.005-0.1 + print("Initializing with",sol[:,0]) + for itm in range(nT): + + if itm%100==0 and itm>0: + sys.stdout.write(str(itm)+'... '+str(sol[-1,0]-sol[0,0])+' '+str(sol[-1,-1]-sol[0,-1])+' ') + sys.stdout.flush() + flags=(amp[itm,:]==1); + nrFlags=np.sum(flags,axis=0) + sol=fitting.fit(ph[itm],A,sol.T,flags).T + tecarray[itm+timeIdx,stIdx,polIdx]=sol[:,0] + if useOffset: + offsetarray[itm+timeIdx,stIdx,polIdx]=sol[:,1] + residual = ph[itm] - np.dot(A, sol.T) + residual = residual - residual[:, 0][:,np.newaxis] + residual = np.remainder(residual+np.pi, 2*np.pi) - np.pi + residual[flags]=0 + residualarray[np.ix_([itm+timeIdx],SBselect,stIdx,[polIdx])]=residual.reshape((1,nF,nSt,1)) + + +def getClockTECBaselineFit(ph,amp,freqs,SBselect,polIdx,stIdx,useOffset=False,stations=[],initSol=[],chi2cut=300.,fixedClockforCS=False,timeIdx=0): + global tecarray + global clockarray + global offsetarray + global residualarray + amp[np.isnan(ph)]=1 + ph[np.isnan(ph)]=0. + ph=np.unwrap(ph,axis=0) + #first unwrap data and get initial guess + nT=ph.shape[0] + nF=freqs.shape[0] + nSt=ph.shape[2] + nparms=2+(useOffset>0) + #sol = np.zeros((nSt,nparms),dtype=np.float) + sol = np.zeros((nSt,nparms),dtype=np.float) + print(sol.shape,nparms,nSt) + A=np.zeros((nF,nparms),dtype=np.float) + A[:,1] = freqs*2*np.pi*(-1e-9) + A[:,0] = -8.44797245e9/freqs + if useOffset: + A[:,2] = np.ones((nF,)) + + constant_parms=np.zeros(sol.shape,dtype=bool) + if fixedClockforCS: + for ist,st in enumerate(stations): + if 'CS' in st: + constant_parms[ist,1]=True + stepDelay=1 + + if "HBA" in stations[0]: + stepdTEC=0.005 + else: + stepdTEC=0.001 # faster wrapping for LBA + stepDelay=3 + + succes=False + initprevsol=False + nrFail=0 + for itm in range(nT): + + if itm%100==0 and itm>0: + sys.stdout.write(str(itm)+'... '+str(sol[-1,0]-sol[0,0])+' '+str(sol[-1,1]-sol[0,1])+' '+str(sol[-1,-1]-sol[0,-1])+' ') + sys.stdout.flush() + + flags=(amp[itm,:]==1); + nrFlags=np.sum(flags,axis=0) + if itm==0 or not succes: + for ist in range(1,nSt): + if (nF-nrFlags[ist])<10: + print("Too many data points flagged",itm,ist) + continue; + if itm==0 or not initprevsol: + if hasattr(initSol,'__len__') and len(initSol)>ist: + iTEC1=initSol[ist,0] + iTEC2=initSol[ist,0]+stepdTEC + iD1=initSol[ist,1] + iD2=initSol[ist,1]+stepDelay + else: + if 'CS' in stations[ist]: + iTEC1=-0.2 + iTEC2=0.2 + iD1=-4 + iD2=4 + else: + iTEC1=-1.5 + iTEC2=1.5 + iD1=-50 + iD2=300 + print("First",iTEC1,iTEC2,iD1,iD2) + + + else: + + iTEC1=prevsol[ist,0]-stepdTEC*nrFail + iTEC2=prevsol[ist,0]+stepdTEC*(nrFail+1) + if not fixedClockforCS or not 'CS' in stations[ist]: + iD1=prevsol[ist,1]-stepDelay*nrFail + iD2=prevsol[ist,1]+stepDelay*(nrFail+1) + else: + iD1=sol[ist,1] + iD2=sol[ist,1]+stepDelay + + print("Failure",iTEC1,iTEC2,iD1,iD2,nrFail) + + dTECArray=np.arange(iTEC1,iTEC2,stepdTEC) + dClockArray=np.arange(iD1,iD2,stepDelay) + data=ph[itm,:,ist][np.logical_not(np.logical_or(flags[:,ist],flags[:,0]))]-ph[itm,:,0][np.logical_not(np.logical_or(flags[:,ist],flags[:,0]))] + tmpfreqs=freqs[np.logical_not(np.logical_or(flags[:,ist],flags[:,0]))] + print("getting init",ist, end=' ') + par = getInitPar(data,dTECArray, dClockArray,tmpfreqs,ClockTECfunc) + print(par) + sol[ist,:]=par[:nparms] + if not succes: + #reset first station + sol[0,:] = np.zeros(nparms) + #sol=fitting.fit(ph[itm],A,sol.T,flags).T + #sol=fitting.fit(ph[itm],A,sol,flags) + sol=fitting.fit(ph[itm],A,sol.T,flags,constant_parms.T).T + tecarray[itm+timeIdx,stIdx,polIdx]=sol[:,0] + clockarray[itm+timeIdx,stIdx,polIdx]=sol[:,1] + if useOffset: + offsetarray[itm+timeIdx,stIdx,polIdx]+=sol[:,2] + residual = ph[itm] - np.dot(A, sol.T) + residual = residual - residual[:, 0][:,np.newaxis] + residual = np.remainder(residual+np.pi, 2*np.pi) - np.pi + residual[flags]=0 + residualarray[np.ix_([itm+timeIdx],SBselect,stIdx,[polIdx])]=residual.reshape((1,nF,nSt,1)) + chi2=np.sum(np.square(np.degrees(residual)))/(nSt*nF) + if chi2>chi2cut: + print("failure",chi2,sol) + succes=False + nrFail=0 + else: + prevsol=np.copy(sol) +# print "succes",chi2,dTECArray.shape,dClockArray.shape + succes=True + initprevsol=True + nrFail+=1 + +def add_to_h5_func(h5file,data,name='test'): + if name in h5file.root: + h5file.removeNode('/'+name) + myarray=h5file.createCArray(h5file.root,name,tab.Float32Atom(),shape=data.shape) + myarray[:]=data + myarray.flush() + + + +def getAll(ionmodel,refstIdx=0,doClockTEC=True,doRM=False,add_to_h5=True,stationSelect='BA',label='fit',SBselect='all',allBaselines=True,useOffset=False,initFromPrevious=False,flagBadChannels=False,flagcut=1.5,chi2cut=30000.,removePhaseWraps=False,combine_pol=False,fixedClockforCS=False,timerange='all',CStec0=False,ignore_stations=["NOTHING_TO_IGNORE",]): + global tecarray + global clockarray + global offsetarray + global residualarray + if allBaselines and not doClockTEC: + doClockTEC=True + + polshape=ionmodel.phases[:].shape[-1] + nT=ionmodel.times[:].shape[0] + if timerange=='all': + timerange=[0,nT] + nT=timerange[1]-timerange[0] + nF=ionmodel.freqs[:].shape[0] + freqs=ionmodel.freqs[:] + if SBselect=='all': + SBselect=np.ones(nF,dtype=bool) + if isinstance(SBselect,list) and len(SBselect)==2: + SBselect=np.logical_and(freqs>SBselect[0],freqs<SBselect[1]) + if flagBadChannels: + rms=lambda x,y: np.sqrt(np.mean(np.square(x-np.mean(x,axis=y)),axis=y)) + ph=ionmodel.phases[timerange[0]:timerange[1],:,1,0,0]-ionmodel.phases[timerange[0]:timerange[1],:,0,0,0] + #myrms1=rms(ph[:,SBselect],0) + myrms1=rms(ph[:],0) + freqselect=myrms1[SBselect]<flagcut*np.average(myrms1[SBselect]) + cutlevel=flagcut*np.average(rms(ph[:,SBselect][:,freqselect],0)) + SBselect=np.logical_and(SBselect,myrms1<cutlevel) + print("flagging",np.sum(np.logical_not(SBselect)),"channels") + freqs=freqs[SBselect] + if isinstance(stationSelect,str): + stations=[st for st in list(ionmodel.stations[:]) if stationSelect] + else: + stations=list(ionmodel.stations[:][stationSelect]) + for ignore in ignore_stations: + stations=[st for st in stations if not ignore in st] + print("stations",stations) + if doClockTEC: + clockarray=np.zeros(ionmodel.times[:].shape+ionmodel.stations[:].shape+(2,)) + tecarray=np.zeros(ionmodel.times[:].shape+ionmodel.stations[:].shape+(2,)) + offsetarray=np.zeros(ionmodel.times[:].shape+ionmodel.stations[:].shape+(2,)) + residualarray=np.zeros((len(ionmodel.times),len(ionmodel.freqs),len(ionmodel.stations),2)) + ph=ionmodel.phases + amp=ionmodel.amplitudes + if doRM: + rmarray=np.zeros((len(ionmodel.times),len(ionmodel.stations))) + rotation_resarray=np.zeros((len(ionmodel.times),len(freqs),len(ionmodel.stations))) + if allBaselines: + #stationIndices=[list(ionmodel.stations[:]).index(st) for st in stations] + stationIndices=np.array([idxst in stations for idxst in ionmodel.stations[:]]) + CSstations=np.array(['CS' in idxst for idxst in ionmodel.stations[:] if idxst in stations]) + print('selected CS',CSstations) + for pol in range(2): + if combine_pol: + #phdata=ph[timerange[0]:timerange[1],:,:,0,(polshape-1)][:,SBselect][:,:,stationIndices]+ph[timerange[0]:timerange[1],:,:,0,0][:,SBselect][:,:,stationIndices] + ampdata=np.logical_or(amp[timerange[0]:timerange[1],:,:,0,(polshape-1)][:,SBselect][:,:,stationIndices]==1,amp[timerange[0]:timerange[1],:,:,0,0][:,SBselect][:,:,stationIndices]==1) + + cdata1=1.*np.exp(1j*ph[timerange[0]:timerange[1],:,:,0,(polshape-1)][:,SBselect][:,:,stationIndices]) + cdata2=1.*np.exp(1j*ph[timerange[0]:timerange[1],:,:,0,0][:,SBselect][:,:,stationIndices]) + phdata=np.angle((cdata1+cdata2)/2.) + + #return phdata + else: + phdata=ph[timerange[0]:timerange[1],:,:,0,pol*(polshape-1)][:,SBselect][:,:,stationIndices] + ampdata=amp[timerange[0]:timerange[1],:,:,0,pol*(polshape-1)][:,SBselect][:,:,stationIndices] + if hasattr(ionmodel,'TEC') and initFromPrevious: + initSol=np.zeros((len(stations),2),dtype=np.float) + if len(ionmodel.TEC[:].shape)>3: + initSol[:,0]=ionmodel.TEC[:][timerange[0],stationIndices,0,pol] + else: + initSol[:,0]=ionmodel.TEC[:][timerange[0],stationIndices,pol] + initSol[:,1]=ionmodel.Clock[:][timerange[0],stationIndices,pol] + phdata+=ionmodel.clock_tec_offsets[:][timerange[0],stationIndices,pol] + offsetarray[:,stationIndices,pol]=ionmodel.clock_tec_offsets[:][timerange[0],stationIndices,pol] + else: + initSol=False + + kwargs={'ph':phdata, + 'amp':ampdata, + 'freqs':freqs, + 'SBselect':SBselect, + 'polIdx':pol, + 'stIdx':stationIndices, + 'stations':stations, + 'useOffset':useOffset, + 'initSol':initSol, + 'chi2cut':chi2cut, + 'timeIdx':timerange[0]} + getClockTECBaselineFit(**kwargs) + if removePhaseWraps: + avgResiduals=np.average(residualarray[timerange[0]:timerange[1],:,:,pol],axis=0) + wraps,steps=getResidualPhaseWraps(avgResiduals,ionmodel.freqs[:]) + + #halfwraps=np.remainder(np.round(np.absolute(wraps[stationIndices]*2)),2)==1 + #print "found halfwraps for",np.array(stations)[halfwraps] + if CStec0: + pos=ionmodel.station_positions[:] + lats=np.degrees(np.arctan2(pos[:,2],np.sqrt(pos[:,0]*pos[:,0]+pos[:,1]*pos[:,1]))) + lats=lats[stationIndices] + lats-=lats[0] + lons=np.degrees(np.arctan2(pos[:,1],pos[:,0])) + lons=lons[stationIndices] + lons-=lons[0] + TEC=tecarray[timerange[0]:timerange[1],stationIndices,pol]-tecarray[timerange[0]:timerange[1],[0],pol]+steps[0]*(np.round(wraps[stationIndices])-np.round(wraps[0])) + lonlat=np.concatenate((lons,lats)).reshape((2,)+lons.shape) + + slope=np.dot(np.diag(1./np.diag(np.dot(lonlat,lonlat.T))),np.dot(lonlat,TEC.T)) + chi2=np.sum(np.square(TEC-np.dot(lonlat.T,slope).T),axis=1) + + + #slope=np.dot(1./(np.dot(lats.T,lats)), np.dot(lats.T,TEC.T)) + #chi2=np.sum(np.square(TEC-lats*slope[:,np.newaxis]),axis=1) + chi2select=chi2<np.average(chi2) + chi2select=chi2<np.average(chi2[chi2select]) + #return chi2,slope,TEC,lats + print("wraps",wraps) + print("slope",slope[:,chi2select][:,0]) + #offsets=-1*(np.average(TEC[chi2select]-lats*slope[chi2select][:,np.newaxis],axis=0))*2.*np.pi/steps[0] + offsets=-1*(np.average(TEC[chi2select]-np.dot(slope.T,lonlat)[chi2select],axis=0))*2.*np.pi/steps[0] + print("step",steps[0]) + print(offsets) + remainingwraps=np.round(offsets/(2*np.pi))#-np.round(wraps[stationIndices]) + print(remainingwraps) + wraps[stationIndices]+=remainingwraps + + #one more iteration + if np.sum(np.absolute(remainingwraps))>0: + TEC=tecarray[timerange[0]:timerange[1],stationIndices,pol]-tecarray[timerange[0]:timerange[1],[0],pol]+steps[0]*(np.round(wraps[stationIndices])-np.round(wraps[0])) + slope=np.dot(np.diag(1./np.diag(np.dot(lonlat,lonlat.T))),np.dot(lonlat,TEC.T)) + chi2=np.sum(np.square(TEC-np.dot(lonlat.T,slope).T),axis=1) + #slope=np.dot(1./(np.dot(lats.T,lats)), np.dot(lats.T,TEC.T)) + #chi2=np.sum(np.square(TEC-lats*slope[:,np.newaxis]),axis=1) + chi2select=chi2<np.average(chi2) + chi2select=chi2<np.average(chi2[chi2select]) + offsets=-1*(np.average(TEC[chi2select]-np.dot(slope.T,lonlat)[chi2select],axis=0))*2.*np.pi/steps[0] + #offsets=-1*(np.average(TEC[chi2select]-lats*slope[chi2select][:,np.newaxis],axis=0))*2.*np.pi/steps[0] + print("offsets itereation2:",offsets) + remainingwraps=np.round(offsets/(2*np.pi))#-np.round(wraps[stationIndices]) + print("remaining wraps iteration 2",remainingwraps) + wraps[stationIndices]+=remainingwraps + #phdata[:,:,:]+=offsets + phdata[:,:,CSstations]+=offsets[CSstations] + offsetarray[:,stationIndices,pol]+=offsets + #clockarray[:,stationIndices,pol]+=(np.remainder(offsets+np.pi,2*np.pi)-np.pi)*steps[1]/(2*np.pi) + #!!!!!!!!!!!!!!!!TESTESTESTSETTE!!! + #phdata[:,:,np.arange(1,46,2)]+=0.01*np.arange(1,46,2) + initSol=np.zeros((len(stations),2),dtype=np.float) + #if combine_pol: + # initSol[:,0]=tecarray[timerange[0],stationIndices,pol]+steps[0]*2*np.round(wraps[stationIndices]) + # initSol[:,1]=clockarray[timerange[0],stationIndices,pol]+steps[1]*2*np.round(wraps[stationIndices]) + #else: + initSol[:,0]=tecarray[timerange[0],stationIndices,pol]+steps[0]*np.round(wraps[stationIndices]) + initSol[:,1]=clockarray[timerange[0],stationIndices,pol]+steps[1]*np.round(wraps[stationIndices]) + #initSol[:,1]=np.average(clockarray[:,stationIndices,pol]-clockarray[:,[0],pol],axis=0)+steps[1]*np.round(wraps[stationIndices]) + print("final wraps",np.round(wraps[stationIndices])) + print("prev solutions", clockarray[timerange[0],stationIndices,pol]) + print("init Clock with", initSol[:,1]) + print("prev solutions TEC", tecarray[timerange[0],stationIndices,pol]) + print("init TEC with", initSol[:,0]) + if not(CStec0) and np.all(np.round(wraps[stationIndices])==0): + print("No need for phase unwrapping") + continue; + kwargs={'ph':phdata, + 'amp':ampdata, + 'freqs':freqs, + 'SBselect':SBselect, + 'polIdx':pol, + 'stIdx':stationIndices, + 'stations':stations, + 'useOffset':useOffset, + 'initSol':initSol, + 'chi2cut':chi2cut, + 'timeIdx':timerange[0], + 'fixedClockforCS':fixedClockforCS} + getClockTECBaselineFit(**kwargs) + if combine_pol: + #tecarray/=2. + #clockarray/=2. + break; + + else: + for ist,st in enumerate(stations): + print("getting values for station",st) + if doClockTEC: + if ist==refstIdx: + continue + for pol in range(2): + kwargs={'ph':ph[:,:,ist,0,pol*(polshape-1)][:,SBselect]-ph[:,:,refstIdx,0,pol*(polshape-1)][:,SBselect], + 'amp':amp[:,:,ist,0,pol*(polshape-1)][:,SBselect], + 'freqs':freqs, + 'SBselect':SBselect, + 'stationname':st, + 'stIdx':ist, + 'polIdx':pol} + getClockTEC(**kwargs) + if doRM: + if st==refstIdx: + continue; + rmarray[:,ist],rotation_resarray[:,:,ist]=getRM(ionmodel,st,refstIdx,SBselect=SBselect) + + + if add_to_h5: + if hasattr(ionmodel,'hdf5'): + h5file=ionmodel.hdf5 + else: + h5file=ionmodel + if doClockTEC: + add_to_h5_func(h5file,clockarray,name='Clock') + add_to_h5_func(h5file,tecarray,name='TEC') + add_to_h5_func(h5file,offsetarray,name='clock_tec_offsets') + add_to_h5_func(h5file,residualarray,name='clock_tec_residuals') + if doRM: + add_to_h5_func(h5file,rmarray,name='rmtime') + add_to_h5_func(h5file,rotation_resarray,name='rm_residuals') + else: + if doClockTEC: + np.save('dclock_%s.npy'%(label), clockarray) + np.save('dTEC_%s.npy'%(label), tecarray) + np.save('offset_%s.npy'%(label), offsetarray) + np.save('residual_%s.npy'%(label), residualarray) + if doRM: + np.save('rm_%s.npy'%(label), rmarray) + np.save('rm_residuals_%s.npy'%(label), rotation_resarray) + + + +def SwapClockTECAxes(ionmodel): + print("swap axes will reshape your Clock and TEC solutions. The order of Clock is now times x stations x polarizations and of TEC: times x stations x sources x polarizations") + TEC =ionmodel.TEC; + TECshape=TEC[:].shape + Clock =ionmodel.Clock; + Clockshape=Clock[:].shape + nT=ionmodel.times[:].shape[0] + nst=ionmodel.stations[:].shape[0] + nsources=ionmodel.N_sources + newshape=(nT,nsources,nst,2) + if TECshape==newshape: + print("nothing to be done for TEC") + else: + TEC=TEC[:] + indices=list(range(4)) #nT,st,nsources,pol + tmaxis=TECshape.index(nT) + indices[tmaxis]=0 + staxis=TECshape.index(nst) + indices[staxis]=1 + if len(TECshape)==3 or (nsources!=2): + polaxis=TECshape.index(2) + indices[polaxis]=3 + if len(TECshape)>3: + nsaxis=TECshape.index(nsources) + else: + TEC=TEC.reshape(TECshape+(1,)) + nsaxis=3 + + indices[nsaxis]=2 + else: + print("ambigous shape of TEC, try swapping by hand") + while tmaxis>0: + TEC=TEC.swapaxes(tmaxis,tmaxis-1) + indices[tmaxis]=indices[tmaxis-1] + tmaxis-=1 + indices[tmaxis]=0 + staxis=indices.index(1) + + while staxis>1: + TEC=TEC.swapaxes(staxis,staxis-1) + indices[staxis]=indices[staxis-1] + staxis-=1 + indices[staxis]=1 + srcaxis=indices.index(2) + + while srcaxis>2: + TEC=TEC.swapaxes(srcaxis,srcaxis-1) + indices[srcaxis]=indices[srcaxis-1] + srcaxis-=1 + indices[srcaxis]=2 + add_to_h5_func(ionmodel.hdf5,TEC,name='TEC') + newshape=(nT,nst,2) + if Clockshape==newshape: + print("nothing to be done for Clock") + else: + Clock=Clock[:] + indices=list(range(3)) #nT,st,pol + tmaxis=Clockshape.index(nT) + indices[tmaxis]=0 + staxis=Clockshape.index(nst) + indices[staxis]=1 + polaxis=Clockshape.index(2) + indices[polaxis]=2 + while tmaxis>0: + Clock=Clock.swapaxes(tmaxis,tmaxis-1) + indices[tmaxis]=indices[tmaxis-1] + tmaxis-=1 + indices[tmaxis]=0 + staxis=indices.index(1) + + while staxis>1: + Clock=Clock.swapaxes(staxis,staxis-1) + indices[staxis]=indices[staxis-1] + staxis-=1 + indices[staxis]=1 + + add_to_h5_func(ionmodel.hdf5,Clock,name='Clock') + + +def writeClocktoParmdb(ionmodel,average=False,create_new = True): + '''if average the average of both polarizations is used, snice BBS can handle only on value at the moment''' + if not hasattr(ionmodel,'Clock'): + print("No Clock solutions found, maybe you forgot to run the fit?") + return + Clock=ionmodel.Clock[:] # times x stations x pol + parms = {} + parm = {} + parm[ 'freqs' ] = np.array( [ .5e9 ] ) + parm[ 'freqwidths' ] = np.array( [ 1.0e9 ] ) + parm[ 'times' ] = ionmodel.times[:].ravel() + parm[ 'timewidths' ] = ionmodel.timewidths[:].ravel() + + stations=list(ionmodel.stations[:]) + pol=ionmodel.polarizations[:] + for ist,st in enumerate(stations): + if average: + Clock_parm = parm.copy() + parmname = ':'.join(['Clock', st]) + value=0.5*ionmodel.Clock[:, ist,0]+0.5*ionmodel.Clock[:, ist] + value*=1.e-9 + Clock_parm[ 'values' ] = value + parms[ parmname ] = Clock_parm + else: + for n_pol,ipol in enumerate(pol): + Clock_parm = parm.copy() + parmname = ':'.join(['Clock', str(ipol), st]) + Clock_parm[ 'values' ] = 1.e-9*ionmodel.Clock[:, ist,n_pol] + parms[ parmname ] = Clock_parm + #return parms + parmdbmain.store_parms( ionmodel.globaldb + '/ionosphere', parms, create_new = create_new) + +def writePhaseScreentoParmdb(ionmodel,create_new = True): + N_sources=ionmodel.N_sources + N_pol = min(len(ionmodel.polarizations),2) + if not hasattr(ionmodel,'globaldb'): + ionmodel.globaldb='./' + if not hasattr(ionmodel,'DirectionalGainEnable'): + ionmodel.DirectionalGainEnable=False + parms = {} + parm = {} + parm[ 'freqs' ] = np.array( [ .5e9 ] ) + parm[ 'freqwidths' ] = np.array( [ 1.0e9 ] ) + parm[ 'times' ] = ionmodel.times[:].ravel() + parm[ 'timewidths' ] = ionmodel.timewidths[:].ravel() + + for n_pol in range(N_pol): + + for n_station in range(len(ionmodel.stations[:])): + station = ionmodel.stations[n_station] + for n_source in range(N_sources): + if ionmodel.DirectionalGainEnable: + source = ionmodel.sources[n_source] + identifier = ':'.join([str(n_pol), station, source]) + else: + identifier = ':'.join([str(n_pol), station]) + + # TEC + TEC_parm = parm.copy() + parmname = ':'.join(['TEC', identifier]) + TEC_parm[ 'values' ] = ionmodel.TEC[:,n_station,n_source,n_pol] + parms[ parmname ] = TEC_parm + + #TECfit + TECfit_parm = parm.copy() + parmname = ':'.join(['TECfit', identifier]) + TECfit_parm[ 'values' ] = ionmodel.TECfit[:,n_station,n_source,n_pol] + parms[ parmname ] = TECfit_parm + + #TECfit_white + TECfit_white_parm = parm.copy() + parmname = ':'.join(['TECfit_white', identifier]) + TECfit_white_parm[ 'values' ] = ionmodel.TECfit_white[:,n_station,n_source,n_pol] + parms[ parmname ] = TECfit_white_parm + + # Piercepoints + + for n_station in range(len(ionmodel.stations)): + station = ionmodel.stations[n_station] + for n_source in range(N_sources): + if ionmodel.DirectionalGainEnable: + source = ionmodel.sources[n_source] + identifier = ':'.join([station, source]) + else: + identifier = station + PiercepointX_parm = parm.copy() + parmname = ':'.join(['Piercepoint', 'X', identifier]) + print(n_source, n_station) + x = ionmodel.piercepoints[:]['positions_xyz'][:,n_source, n_station,0] + PiercepointX_parm['values'] = x + parms[ parmname ] = PiercepointX_parm + + PiercepointY_parm = parm.copy() + parmname = ':'.join(['Piercepoint', 'Y', identifier]) + y = ionmodel.piercepoints[:]['positions_xyz'][:,n_source, n_station,1] + PiercepointY_parm['values'] = array(y) + parms[ parmname ] = PiercepointY_parm + + PiercepointZ_parm = parm.copy() + parmname = ':'.join(['Piercepoint', 'Z', identifier]) + z = ionmodel.piercepoints[:]['positions_xyz'][:,n_source, n_station,2] + PiercepointZ_parm['values'] = z + parms[ parmname ] = PiercepointZ_parm + + Piercepoint_zenithangle_parm = parm.copy() + parmname = ':'.join(['Piercepoint', 'zenithangle', identifier]) + za = ionmodel.piercepoints[:]['zenith_angles'][:,n_source, n_station] + Piercepoint_zenithangle_parm['values'] = za + parms[ parmname ] = Piercepoint_zenithangle_parm + + time_start = ionmodel.times[0] - ionmodel.timewidths[0]/2 + time_end = ionmodel.times[-1] + ionmodel.timewidths[-1]/2 + + parm[ 'times' ] = array([(time_start + time_end) / 2]) + parm[ 'timewidths' ] = array([time_end - time_start]) + + height_parm = parm.copy() + height_parm[ 'values' ] = array( ionmodel.piercepoints.attrs.height ) + parms[ 'height' ] = height_parm + + beta_parm = parm.copy() + beta_parm[ 'values' ] = array( ionmodel.TECfit_white.attrs.beta ) + parms[ 'beta' ] = beta_parm + + r_0_parm = parm.copy() + r_0_parm[ 'values' ] = array( ionmodel.TECfit_white.attrs.r_0 ) + parms[ 'r_0' ] = r_0_parm + + parmdbmain.store_parms( ionmodel.globaldb + '/ionosphere', parms, create_new = create_new) + + +def writePhaseScreenInfo(ionmodel,filename="clocktec.xmmlss.send"): + if not hasattr(ionmodel,'TEC'): + print('no fitted TEC information in you model, maybe you forgot to fit?') + return + + + np.savez(filename, + clock=ionmodel.Clock[:], + tec=ionmodel.TEC[:], + offset=ionmodel.hdf5.root.clock_tec_offsets[:], + freqs=ionmodel.freqs[:], + radec=ionmodel.pointing[:], + antenna_names =ionmodel.stations[:], + antenna_positions=ionmodel.station_positions[:], + radeccal=ionmodel.source_positions[:]) + diff --git a/CEP/Calibration/ExpIon/src/format.py b/CEP/Calibration/ExpIon/src/format.py new file mode 100644 index 0000000000000000000000000000000000000000..81b149105be23fda4c4117aceefd86e21e68f947 --- /dev/null +++ b/CEP/Calibration/ExpIon/src/format.py @@ -0,0 +1,486 @@ +""" +Define a simple format for saving numpy arrays to disk with the full +information about them. + +WARNING: Due to limitations in the interpretation of structured dtypes, dtypes +with fields with empty names will have the names replaced by 'f0', 'f1', etc. +Such arrays will not round-trip through the format entirely accurately. The +data is intact; only the field names will differ. We are working on a fix for +this. This fix will not require a change in the file format. The arrays with +such structures can still be saved and restored, and the correct dtype may be +restored by using the `loadedarray.view(correct_dtype)` method. + +Format Version 1.0 +------------------ + +The first 6 bytes are a magic string: exactly "\\\\x93NUMPY". + +The next 1 byte is an unsigned byte: the major version number of the file +format, e.g. \\\\x01. + +The next 1 byte is an unsigned byte: the minor version number of the file +format, e.g. \\\\x00. Note: the version of the file format is not tied to the +version of the numpy package. + +The next 2 bytes form a little-endian unsigned short int: the length of the +header data HEADER_LEN. + +The next HEADER_LEN bytes form the header data describing the array's format. +It is an ASCII string which contains a Python literal expression of a +dictionary. It is terminated by a newline ('\\\\n') and padded with spaces +('\\\\x20') to make the total length of the magic string + 4 + HEADER_LEN be +evenly divisible by 16 for alignment purposes. + +The dictionary contains three keys: + + "descr" : dtype.descr + An object that can be passed as an argument to the numpy.dtype() + constructor to create the array's dtype. + "fortran_order" : bool + Whether the array data is Fortran-contiguous or not. Since + Fortran-contiguous arrays are a common form of non-C-contiguity, we + allow them to be written directly to disk for efficiency. + "shape" : tuple of int + The shape of the array. + +For repeatability and readability, the dictionary keys are sorted in alphabetic +order. This is for convenience only. A writer SHOULD implement this if +possible. A reader MUST NOT depend on this. + +Following the header comes the array data. If the dtype contains Python objects +(i.e. dtype.hasobject is True), then the data is a Python pickle of the array. +Otherwise the data is the contiguous (either C- or Fortran-, depending on +fortran_order) bytes of the array. Consumers can figure out the number of bytes +by multiplying the number of elements given by the shape (noting that shape=() +means there is 1 element) by dtype.itemsize. + +""" + +import pickle + +import numpy +from numpy.lib.utils import safe_eval + + +MAGIC_PREFIX = '\x93NUMPY' +MAGIC_LEN = len(MAGIC_PREFIX) + 2 + +def magic(major, minor): + """ Return the magic string for the given file format version. + + Parameters + ---------- + major : int in [0, 255] + minor : int in [0, 255] + + Returns + ------- + magic : str + + Raises + ------ + ValueError if the version cannot be formatted. + """ + if major < 0 or major > 255: + raise ValueError("major version must be 0 <= major < 256") + if minor < 0 or minor > 255: + raise ValueError("minor version must be 0 <= minor < 256") + return '%s%s%s' % (MAGIC_PREFIX, chr(major), chr(minor)) + +def read_magic(fp): + """ Read the magic string to get the version of the file format. + + Parameters + ---------- + fp : filelike object + + Returns + ------- + major : int + minor : int + """ + magic_str = fp.read(MAGIC_LEN) + if len(magic_str) != MAGIC_LEN: + msg = "could not read %d characters for the magic string; got %r" + raise ValueError(msg % (MAGIC_LEN, magic_str)) + if magic_str[:-2] != MAGIC_PREFIX: + msg = "the magic string is not correct; expected %r, got %r" + raise ValueError(msg % (MAGIC_PREFIX, magic_str[:-2])) + major, minor = list(map(ord, magic_str[-2:])) + return major, minor + +def dtype_to_descr(dtype): + """ + Get a serializable descriptor from the dtype. + + The .descr attribute of a dtype object cannot be round-tripped through + the dtype() constructor. Simple types, like dtype('float32'), have + a descr which looks like a record array with one field with '' as + a name. The dtype() constructor interprets this as a request to give + a default name. Instead, we construct descriptor that can be passed to + dtype(). + + Parameters + ---------- + dtype : dtype + The dtype of the array that will be written to disk. + + Returns + ------- + descr : object + An object that can be passed to `numpy.dtype()` in order to + replicate the input dtype. + + """ + if dtype.names is not None: + # This is a record array. The .descr is fine. + # XXX: parts of the record array with an empty name, like padding bytes, + # still get fiddled with. This needs to be fixed in the C implementation + # of dtype(). + return dtype.descr + else: + return dtype.str + +def header_data_from_array_1_0(array): + """ Get the dictionary of header metadata from a numpy.ndarray. + + Parameters + ---------- + array : numpy.ndarray + + Returns + ------- + d : dict + This has the appropriate entries for writing its string representation + to the header of the file. + """ + d = {} + d['shape'] = array.shape + if array.flags.c_contiguous: + d['fortran_order'] = False + elif array.flags.f_contiguous: + d['fortran_order'] = True + else: + # Totally non-contiguous data. We will have to make it C-contiguous + # before writing. Note that we need to test for C_CONTIGUOUS first + # because a 1-D array is both C_CONTIGUOUS and F_CONTIGUOUS. + d['fortran_order'] = False + + d['descr'] = dtype_to_descr(array.dtype) + return d + +def write_array_header_1_0(fp, d): + """ Write the header for an array using the 1.0 format. + + Parameters + ---------- + fp : filelike object + d : dict + This has the appropriate entries for writing its string representation + to the header of the file. + """ + import struct + header = ["{"] + for key, value in sorted(d.items()): + # Need to use repr here, since we eval these when reading + header.append("'%s': %s, " % (key, repr(value))) + header.append("}") + header = "".join(header) + # Pad the header with spaces and a final newline such that the magic + # string, the header-length short and the header are aligned on a 16-byte + # boundary. Hopefully, some system, possibly memory-mapping, can take + # advantage of our premature optimization. + current_header_len = MAGIC_LEN + 2 + len(header) + 1 # 1 for the newline + topad = 16 - (current_header_len % 16) + header = '%s%s\n' % (header, ' '*topad) + if len(header) >= (256*256): + raise ValueError("header does not fit inside %s bytes" % (256*256)) + header_len_str = struct.pack('<H', len(header)) + fp.write(header_len_str) + fp.write(header) + +def read_array_header_1_0(fp): + """ + Read an array header from a filelike object using the 1.0 file format + version. + + This will leave the file object located just after the header. + + Parameters + ---------- + fp : filelike object + A file object or something with a `.read()` method like a file. + + Returns + ------- + shape : tuple of int + The shape of the array. + fortran_order : bool + The array data will be written out directly if it is either C-contiguous + or Fortran-contiguous. Otherwise, it will be made contiguous before + writing it out. + dtype : dtype + The dtype of the file's data. + + Raises + ------ + ValueError : + If the data is invalid. + + """ + # Read an unsigned, little-endian short int which has the length of the + # header. + import struct + hlength_str = fp.read(2) + if len(hlength_str) != 2: + msg = "EOF at %s before reading array header length" + raise ValueError(msg % fp.tell()) + header_length = struct.unpack('<H', hlength_str)[0] + header = fp.read(header_length) + if len(header) != header_length: + raise ValueError("EOF at %s before reading array header" % fp.tell()) + + # The header is a pretty-printed string representation of a literal Python + # dictionary with trailing newlines padded to a 16-byte boundary. The keys + # are strings. + # "shape" : tuple of int + # "fortran_order" : bool + # "descr" : dtype.descr + try: + d = safe_eval(header) + except SyntaxError as e: + msg = "Cannot parse header: %r\nException: %r" + raise ValueError(msg % (header, e)) + if not isinstance(d, dict): + msg = "Header is not a dictionary: %r" + raise ValueError(msg % d) + keys = list(d.keys()) + keys.sort() + if keys != ['descr', 'fortran_order', 'shape']: + msg = "Header does not contain the correct keys: %r" + raise ValueError(msg % (keys,)) + + # Sanity-check the values. + if (not isinstance(d['shape'], tuple) or + not numpy.all([isinstance(x, int) for x in d['shape']])): + msg = "shape is not valid: %r" + raise ValueError(msg % (d['shape'],)) + if not isinstance(d['fortran_order'], bool): + msg = "fortran_order is not a valid bool: %r" + raise ValueError(msg % (d['fortran_order'],)) + try: + dtype = numpy.dtype(d['descr']) + except TypeError as e: + msg = "descr is not a valid dtype descriptor: %r" + raise ValueError(msg % (d['descr'],)) + + return d['shape'], d['fortran_order'], dtype + +def write_array(fp, array, version=(1,0)): + """ + Write an array to an NPY file, including a header. + + If the array is neither C-contiguous or Fortran-contiguous AND if the + filelike object is not a real file object, then this function will have + to copy data in memory. + + Parameters + ---------- + fp : filelike object + An open, writable file object or similar object with a `.write()` + method. + array : numpy.ndarray + The array to write to disk. + version : (int, int), optional + The version number of the format. + + Raises + ------ + ValueError + If the array cannot be persisted. + Various other errors + If the array contains Python objects as part of its dtype, the + process of pickling them may raise arbitrary errors if the objects + are not picklable. + + """ + if version != (1, 0): + msg = "we only support format version (1,0), not %s" + raise ValueError(msg % (version,)) + fp.write(magic(*version)) + write_array_header_1_0(fp, header_data_from_array_1_0(array)) + if array.dtype.hasobject: + # We contain Python objects so we cannot write out the data directly. + # Instead, we will pickle it out with version 2 of the pickle protocol. + pickle.dump(array, fp, protocol=2) + elif array.flags.f_contiguous and not array.flags.c_contiguous: + # Use a suboptimal, possibly memory-intensive, but correct way to + # handle Fortran-contiguous arrays. + fp.write(array.data) + else: + if isinstance(fp, file): + array.tofile(fp) + else: + # XXX: We could probably chunk this using something like + # arrayterator. + fp.write(array.tostring('C')) + +def read_array(fp): + """ + Read an array from an NPY file. + + Parameters + ---------- + fp : filelike object + If this is not a real file object, then this may take extra memory and + time. + + Returns + ------- + array : numpy.ndarray + The array from the data on disk. + + Raises + ------ + ValueError + If the data is invalid. + + """ + version = read_magic(fp) + if version != (1, 0): + msg = "only support version (1,0) of file format, not %r" + raise ValueError(msg % (version,)) + shape, fortran_order, dtype = read_array_header_1_0(fp) + if len(shape) == 0: + count = 1 + else: + count = numpy.multiply.reduce(shape) + + # Now read the actual data. + if dtype.hasobject: + # The array contained Python objects. We need to unpickle the data. + array = pickle.load(fp) + else: + if isinstance(fp, file): + # We can use the fast fromfile() function. + array = numpy.fromfile(fp, dtype=dtype, count=count) + else: + # This is not a real file. We have to read it the memory-intensive + # way. + # XXX: we can probably chunk this to avoid the memory hit. + data = fp.read(count * dtype.itemsize) + array = numpy.fromstring(data, dtype=dtype, count=count) + + if fortran_order: + array.shape = shape[::-1] + array = array.transpose() + else: + array.shape = shape + + return array + + +def open_memmap(filename, mode='r+', dtype=None, shape=None, + fortran_order=False, version=(1,0)): + """ + Open a .npy file as a memory-mapped array. + + This may be used to read an existing file or create a new one. + + Parameters + ---------- + filename : str + The name of the file on disk. This may not be a file-like object. + mode : str, optional + The mode to open the file with. In addition to the standard file modes, + 'c' is also accepted to mean "copy on write". See `numpy.memmap` for + the available mode strings. + dtype : dtype, optional + The data type of the array if we are creating a new file in "write" + mode. + shape : tuple of int, optional + The shape of the array if we are creating a new file in "write" + mode. + fortran_order : bool, optional + Whether the array should be Fortran-contiguous (True) or + C-contiguous (False) if we are creating a new file in "write" mode. + version : tuple of int (major, minor) + If the mode is a "write" mode, then this is the version of the file + format used to create the file. + + Returns + ------- + marray : numpy.memmap + The memory-mapped array. + + Raises + ------ + ValueError + If the data or the mode is invalid. + IOError + If the file is not found or cannot be opened correctly. + + See Also + -------- + numpy.memmap + + """ + if not isinstance(filename, str): + raise ValueError("Filename must be a string. Memmap cannot use" \ + " existing file handles.") + + if 'w' in mode: + # We are creating the file, not reading it. + # Check if we ought to create the file. + if version != (1, 0): + msg = "only support version (1,0) of file format, not %r" + raise ValueError(msg % (version,)) + # Ensure that the given dtype is an authentic dtype object rather than + # just something that can be interpreted as a dtype object. + dtype = numpy.dtype(dtype) + if dtype.hasobject: + msg = "Array can't be memory-mapped: Python objects in dtype." + raise ValueError(msg) + d = dict( + descr=dtype_to_descr(dtype), + fortran_order=fortran_order, + shape=shape, + ) + # If we got here, then it should be safe to create the file. + fp = open(filename, mode+'b') + try: + fp.write(magic(*version)) + write_array_header_1_0(fp, d) + offset = fp.tell() + finally: + fp.close() + else: + # Read the header of the file first. + fp = open(filename, 'rb') + try: + version = read_magic(fp) + if version != (1, 0): + msg = "only support version (1,0) of file format, not %r" + raise ValueError(msg % (version,)) + shape, fortran_order, dtype = read_array_header_1_0(fp) + if dtype.hasobject: + msg = "Array can't be memory-mapped: Python objects in dtype." + raise ValueError(msg) + offset = fp.tell() + finally: + fp.close() + + if fortran_order: + order = 'F' + else: + order = 'C' + + # We need to change a write-only mode to a read-write mode since we've + # already written data to the file. + if mode == 'w+': + mode = 'r+' + + marray = numpy.memmap(filename, dtype=dtype, shape=shape, order=order, + mode=mode, offset=offset) + + return marray diff --git a/CEP/Calibration/ExpIon/src/io.py b/CEP/Calibration/ExpIon/src/io.py new file mode 100644 index 0000000000000000000000000000000000000000..06b09f7eab83fa494dfc9b8141c2381f68e46630 --- /dev/null +++ b/CEP/Calibration/ExpIon/src/io.py @@ -0,0 +1,308 @@ +import numpy as np +from . import format +import io +import os +import itertools +import sys + +from pickle import load as _cload, loads + +_file = file + +def seek_gzip_factory(f): + """Use this factory to produce the class so that we can do a lazy + import on gzip. + + """ + import gzip, new + + def seek(self, offset, whence=0): + # figure out new position (we can only seek forwards) + if whence == 1: + offset = self.offset + offset + + if whence not in [0, 1]: + raise IOError("Illegal argument") + + if offset < self.offset: + # for negative seek, rewind and do positive seek + self.rewind() + count = offset - self.offset + for i in range(count // 1024): + self.read(1024) + self.read(count % 1024) + + def tell(self): + return self.offset + + if isinstance(f, str): + f = gzip.GzipFile(f) + + f.seek = new.instancemethod(seek, f) + f.tell = new.instancemethod(tell, f) + + return f + +def zipfile_factory(*args, **kwargs): + """Allow the Zip64 extension, which is supported in Python >= 2.5. + + """ + from zipfile import ZipFile + if sys.version_info > (2, 4): + kwargs['allowZip64'] = True + return ZipFile(*args, **kwargs) + +class BagObj(object): + """A simple class that converts attribute lookups to + getitems on the class passed in. + """ + def __init__(self, obj): + self._obj = obj + def __getattribute__(self, key): + try: + return object.__getattribute__(self, '_obj')[key] + except KeyError: + raise AttributeError(key) + +class NpzFile(object): + """A dictionary-like object with lazy-loading of files in the zipped + archive provided on construction. + + The arrays and file strings are lazily loaded on either + getitem access using obj['key'] or attribute lookup using obj.f.key + + A list of all files (without .npy) extensions can be obtained + with .files and the ZipFile object itself using .zip + """ + def __init__(self, fid): + # Import is postponed to here since zipfile depends on gzip, an optional + # component of the so-called standard library. + import zipfile + _zip = zipfile_factory(fid) + self._files = _zip.namelist() + self.files = [] + for x in self._files: + if x.endswith('.npy'): + self.files.append(x[:-4]) + else: + self.files.append(x) + self.zip = _zip + self.f = BagObj(self) + + def __getitem__(self, key): + # FIXME: This seems like it will copy strings around + # more than is strictly necessary. The zipfile + # will read the string and then + # the format.read_array will copy the string + # to another place in memory. + # It would be better if the zipfile could read + # (or at least uncompress) the data + # directly into the array memory. + member = 0 + if key in self._files: + member = 1 + elif key in self.files: + member = 1 + key += '.npy' + if member: + bytes = self.zip.read(key) + if bytes.startswith(format.MAGIC_PREFIX): + value = io.StringIO(bytes) + return format.read_array(value) + else: + return bytes + else: + raise KeyError("%s is not a file in the archive" % key) + +def load(file, mmap_mode=None): + """ + Load a pickled, ``.npy``, or ``.npz`` binary file. + + Parameters + ---------- + file : file-like object or string + The file to read. It must support ``seek()`` and ``read()`` methods. + mmap_mode: {None, 'r+', 'r', 'w+', 'c'}, optional + If not None, then memory-map the file, using the given mode + (see `numpy.memmap`). The mode has no effect for pickled or + zipped files. + A memory-mapped array is stored on disk, and not directly loaded + into memory. However, it can be accessed and sliced like any + ndarray. Memory mapping is especially useful for accessing + small fragments of large files without reading the entire file + into memory. + + Returns + ------- + result : array, tuple, dict, etc. + Data stored in the file. + + Raises + ------ + IOError + If the input file does not exist or cannot be read. + + Notes + ----- + - If the file contains pickle data, then whatever is stored in the + pickle is returned. + - If the file is a ``.npy`` file, then an array is returned. + - If the file is a ``.npz`` file, then a dictionary-like object is + returned, containing ``{filename: array}`` key-value pairs, one for + each file in the archive. + + Examples + -------- + Store data to disk, and load it again: + + >>> np.save('/tmp/123', np.array([[1, 2, 3], [4, 5, 6]])) + >>> np.load('/tmp/123.npy') + array([[1, 2, 3], + [4, 5, 6]]) + + Mem-map the stored array, and then access the second row + directly from disk: + + >>> X = np.load('/tmp/123.npy', mmap_mode='r') + >>> X[1, :] + memmap([4, 5, 6]) + + """ + import gzip + + if isinstance(file, str): + fid = _file(file,"rb") + elif isinstance(file, gzip.GzipFile): + fid = seek_gzip_factory(file) + else: + fid = file + + # Code to distinguish from NumPy binary files and pickles. + _ZIP_PREFIX = 'PK\x03\x04' + N = len(format.MAGIC_PREFIX) + magic = fid.read(N) + fid.seek(-N,1) # back-up + if magic.startswith(_ZIP_PREFIX): # zip-file (assume .npz) + return NpzFile(fid) + elif magic == format.MAGIC_PREFIX: # .npy file + if mmap_mode: + return format.open_memmap(file, mode=mmap_mode) + else: + return format.read_array(fid) + else: # Try a pickle + try: + return _cload(fid) + except: + raise IOError("Failed to interpret file %s as a pickle" % repr(file)) + +def save(file, arr): + """ + Save an array to a binary file in NumPy format. + + Parameters + ---------- + f : file or string + File or filename to which the data is saved. If the filename + does not already have a ``.npy`` extension, it is added. + x : array_like + Array data. + + See Also + -------- + savez : Save several arrays into an .npz compressed archive + savetxt : Save an array to a file as plain text + + Examples + -------- + >>> from tempfile import TemporaryFile + >>> outfile = TemporaryFile() + + >>> x = np.arange(10) + >>> np.save(outfile, x) + + >>> outfile.seek(0) + >>> np.load(outfile) + array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + + """ + if isinstance(file, str): + if not file.endswith('.npy'): + file = file + '.npy' + fid = open(file, "wb") + else: + fid = file + + arr = np.asanyarray(arr) + format.write_array(fid, arr) + +def savez(file, *args, **kwds): + """ + Save several arrays into a single, compressed file with extension ".npz" + + If keyword arguments are given, the names for variables assigned to the + keywords are the keyword names (not the variable names in the caller). + If arguments are passed in with no keywords, the corresponding variable + names are arr_0, arr_1, etc. + + Parameters + ---------- + file : Either the filename (string) or an open file (file-like object) + If file is a string, it names the output file. ".npz" will be appended + if it is not already there. + args : Arguments + Any function arguments other than the file name are variables to save. + Since it is not possible for Python to know their names outside the + savez function, they will be saved with names "arr_0", "arr_1", and so + on. These arguments can be any expression. + kwds : Keyword arguments + All keyword=value pairs cause the value to be saved with the name of + the keyword. + + See Also + -------- + save : Save a single array to a binary file in NumPy format + savetxt : Save an array to a file as plain text + + Notes + ----- + The .npz file format is a zipped archive of files named after the variables + they contain. Each file contains one variable in .npy format. + + """ + + # Import is postponed to here since zipfile depends on gzip, an optional + # component of the so-called standard library. + import zipfile + + if isinstance(file, str): + if not file.endswith('.npz'): + file = file + '.npz' + + namedict = kwds + for i, val in enumerate(args): + key = 'arr_%d' % i + if key in list(namedict.keys()): + raise ValueError("Cannot use un-named variables and keyword %s" % key) + namedict[key] = val + + zip = zipfile_factory(file, mode="w") + + # Place to write temporary .npy files + # before storing them in the zip + import tempfile + direc = tempfile.gettempdir() + todel = [] + + for key, val in namedict.items(): + fname = key + '.npy' + filename = os.path.join(direc, fname) + todel.append(filename) + fid = open(filename,'wb') + format.write_array(fid, np.asanyarray(val)) + fid.close() + zip.write(filename, arcname=fname) + + zip.close() + for name in todel: + os.remove(name) + diff --git a/CEP/Calibration/ExpIon/src/ionosphere.py b/CEP/Calibration/ExpIon/src/ionosphere.py new file mode 100755 index 0000000000000000000000000000000000000000..429a0e597fcf44f01bba1070aeabd78d2f21295c --- /dev/null +++ b/CEP/Calibration/ExpIon/src/ionosphere.py @@ -0,0 +1,1380 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# Copyright (C) 2007 +# ASTRON (Netherlands Institute for Radio Astronomy) +# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +# +# This file is part of the LOFAR software suite. +# The LOFAR software suite 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 3 of the License, or +# (at your option) any later version. +# +# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +# +# $Id$ +############################################################################### + +# import Python modules +import sys +import os +from datetime import * +from math import * +import time +import re + +# import 3rd party modules + +import numpy +from pylab import * +import scipy.optimize + +# import user modules +#from files import * +from . import client +from .acalc import * +from . import sphere +import lofar.parmdb +import lofar.parameterset +import pyrap.tables as pt +from .mpfit import * +from .error import * +from . import readms +from . import io + +from . import parmdbmain +import tables + + +############################################################################### + +class IonosphericModel: + """IonosphericModel class is the main interface to the functions impelmented in the ionosphere module""" + + def __init__( self, gdsfiles, clusterdesc = '', sky_name = 'sky', instrument_name = 'instrument', + sources = [], stations = [], polarizations = [], GainEnable = False, DirectionalGainEnable = False, + PhasorsEnable = False, RotationEnable = False, estimate_gradient = True, + remove_gradient = True, equal_weights = False, normalize_weights = True, save_pierce_points = True, + globaldb = 'globaldb', movie_file_name = '', format = 'mpg', plot_gradient = True, xy_range = [ - 0.5, 0.5 ], + e_steps = 4, print_info = False, estimate_offsets = False, + include_airmass = True, solution_version = 0 ): + + """ + gdsfiles can be either a list of gdsfiles or a single string that will be interpreted as the name + of a file that was created by the save method + clusterdesc is the name of the cluster description file + sky_name + instrument_name + sources + stations + + """ + + # Check whether to open a list of parmdbs or a previously stored IonosphericModel object + self.DirectionalGainEnable = DirectionalGainEnable + + if len(gdsfiles) == 1 and os.path.isdir( gdsfiles[0] ): + self.load_globaldb( gdsfiles[0] ) + else: + self.GainEnable = GainEnable + self.DirectionalGainEnable = DirectionalGainEnable + self.PhasorsEnable = PhasorsEnable + self.RotationEnable = RotationEnable + self.polarizations = polarizations + self.N_pol = len(polarizations) + print("RotationEnable:", self.RotationEnable) + self.load_gds(gdsfiles, clusterdesc, globaldb, sky_name, instrument_name, stations, sources) + + def load_globaldb ( self, globaldb ) : + self.globaldb = globaldb + self.hdf5 = tables.openFile(os.path.join(globaldb , 'ionmodel.hdf5'), 'r+') + + self.stations = self.hdf5.root.stations.cols.name + self.station_positions = self.hdf5.root.stations.cols.position + self.array_center = self.hdf5.root.array_center + self.N_stations = len(self.stations) + + + #ionospheredbname = os.path.join(globaldb, 'ionosphere') + #ionospheredb = lofar.parmdb.parmdb( ionospheredbname ) + + #self.stations = get_station_list_from_ionospheredb( ionospheredb ) + + #antenna_table = pt.table( globaldb + "/ANTENNA") + #name_col = antenna_table.getcol('NAME') + #position_col = antenna_table.getcol( 'POSITION' ) + #self.station_positions = [position_col[name_col.index(station_name)] for station_name in self.stations] + #antenna_table.close() + + #field_table = pt.table( globaldb + "/FIELD") + #phase_dir_col = field_table.getcol('PHASE_DIR') + #self.pointing = phase_dir_col[0,0,:] + #field_table.close() + + #self.sources = get_source_list_from_ionospheredb( ionospheredb ) # source positions + + self.sources = self.hdf5.root.sources[:]['name'] + self.source_positions = self.hdf5.root.sources[:]['position'] + self.N_sources = len(self.sources) + self.N_piercepoints = self.N_sources * self.N_stations + + self.pointing = self.hdf5.root.pointing + + self.freqs = self.hdf5.root.freqs + self.polarizations = self.hdf5.root.polarizations + self.N_pol = len(self.polarizations) + + self.phases = self.hdf5.root.phases + self.flags = self.hdf5.root.flags + + for varname in ['amplitudes', 'rotation', 'Clock', 'TEC', 'TECfit', 'TECfit_white', 'offsets', \ + 'times', 'timewidths', 'piercepoints', 'facets', 'facet_piercepoints', 'n_list', \ + 'STEC_facets'] : + if varname in self.hdf5.root: + self.__dict__.update( [(varname, self.hdf5.getNode(self.hdf5.root, varname))] ) + + #if 'Clock' in self.hdf5.root : self.Clock = self.hdf5.root.Clock + #if 'TEC' in self.hdf5.root: self.TEC = self.hdf5.root.TEC + #if 'TECfit' in self.hdf5.root: self.TEC = self.hdf5.root.TECfit + #if 'TECfit_white' in self.hdf5.root: self.TEC = self.hdf5.root.TECfit_white + #if 'offsets' in self.hdf5.root: self.TEC = self.hdf5.root.offsets + #if 'piercepoints' in self.hdf5.root: self.TEC = self.hdf5.root.piercepoints + + self.N_stations = len(self.stations) + self.N_sources = len(self.sources) + + def load_gds( self, gdsfiles, clusterdesc, globaldb, sky_name, instrument_name, stations, sources ): + + self.gdsfiles = gdsfiles + self.instrument_name = instrument_name + self.globaldb = globaldb + + try: + os.mkdir(globaldb) + except OSError: + pass + + self.instrumentdb_name_list = [] + for gdsfile in gdsfiles: + instrumentdb_name = os.path.splitext(gdsfile)[0] + os.path.extsep + instrument_name + if not os.path.exists(instrumentdb_name): + instrumentdb_name = make_instrumentdb( gdsfile, instrument_name, globaldb ) + self.instrumentdb_name_list.append(instrumentdb_name) + + gdsfiles = [] + for (idx, gdsfile) in zip(list(range(len(self.gdsfiles))), self.gdsfiles): + gdsfiles.extend( splitgds( gdsfile, wd = self.globaldb, id = 'part-%i' % idx) ) + self.gdsfiles = gdsfiles + + instrumentdb_name_list = [] + for (idx, instrumentdb_name) in zip(list(range(len(self.instrumentdb_name_list))), self.instrumentdb_name_list): + instrumentdb_name_list.extend( splitgds( instrumentdb_name, wd = self.globaldb, id = 'instrument-%i' % idx) ) + self.instrumentdb_name_list = instrumentdb_name_list + + instrumentdb_name_list = [] + for instrumentdb_name in self.instrumentdb_name_list : + instrumentdb_parset = lofar.parameterset.parameterset( instrumentdb_name ) + instrumentdbfilename = instrumentdb_parset.getString( "Part0.FileName" ) + instrumentdbhostname = instrumentdb_parset.getString( "Part0.FileSys" ).split(':')[0] + instrumentdb_name = os.path.splitext( instrumentdb_name )[0] + if not os.path.exists(instrumentdb_name) : + os.system( "scp -r %s:%s %s" % ( instrumentdbhostname, instrumentdbfilename, instrumentdb_name ) ) + instrumentdb_name_list.append(instrumentdb_name) + self.instrumentdb_name_list = instrumentdb_name_list + + gdsfile = gdsfiles[0] + instrumentdb = lofar.parmdb.parmdb( self.instrumentdb_name_list[0] ) + + self.hdf5 = tables.openFile(os.path.join(globaldb , 'ionmodel.hdf5'), 'w') + + gdsparset = lofar.parameterset.parameterset( gdsfile ) + + skydbfilename = os.path.join(gdsparset.getString( "Part0.FileName" ), sky_name) + skydbhostname = gdsparset.getString( "Part0.FileSys" ).split(':')[0] + skydbname = globaldb + "/sky" + if not os.path.exists(skydbname) : + os.system( "scp -r %s:%s %s" % ( skydbhostname, skydbfilename, skydbname ) ) + skydb = lofar.parmdb.parmdb( skydbname ) + + gdsparset = lofar.parameterset.parameterset( gdsfile ) + msname = gdsparset.getString( "Part0.FileName" ) + mshostname = gdsparset.getString( "Part0.FileSys" ).split(':')[0] + antenna_table_name = os.path.join( globaldb, "ANTENNA") + if not os.path.exists(antenna_table_name) : + os.system( "scp -r %s:%s/ANTENNA %s" % ( mshostname, msname, antenna_table_name ) ) + field_table_name = os.path.join( globaldb, "FIELD" ) + if not os.path.exists( field_table_name ) : + os.system( "scp -r %s:%s/FIELD %s" % ( mshostname, msname, field_table_name ) ) + + if len( stations ) == 0 : + stations = ["*"] + self.stations = get_station_list( instrumentdb, stations, self.DirectionalGainEnable ) + self.N_stations = len(self.stations) + + antenna_table = pt.table( globaldb + "/ANTENNA") + name_col = antenna_table.getcol('NAME') + position_col = antenna_table.getcol( 'POSITION' ) + self.station_positions = [position_col[name_col.index(station_name)] for station_name in self.stations] + antenna_table.close() + + station_table = self.hdf5.createTable(self.hdf5.root, 'stations', {'name': tables.StringCol(40), 'position':tables.Float64Col(3)}) + row = station_table.row + for (station, position) in zip(self.stations, self.station_positions) : + row['name'] = station + row['position'] = position + row.append() + station_table.flush() + + self.array_center = array( self.station_positions ).mean(axis=0).tolist() + self.hdf5.createArray(self.hdf5.root, 'array_center', self.array_center) + + field_table = pt.table( globaldb + "/FIELD") + phase_dir_col = field_table.getcol('PHASE_DIR') + self.pointing = phase_dir_col[0,0,:] + field_table.close() + self.hdf5.createArray(self.hdf5.root, 'pointing', self.pointing) + + if self.DirectionalGainEnable or self.RotationEnable: + if len( sources ) == 0: + sources = ["*"] + self.sources = get_source_list( instrumentdb, sources ) + self.source_positions = [] + for source in self.sources : + try: + RA = skydb.getDefValues( 'Ra:' + source )['Ra:' + source][0][0] + dec = skydb.getDefValues( 'Dec:' + source )['Dec:' + source][0][0] + except KeyError: + # Source not found in skymodel parmdb, try to find components + RA = numpy.array(list(skydb.getDefValues( 'Ra:' + source + '.*' ).values())).mean() + dec = numpy.array(list(skydb.getDefValues( 'Dec:' + source + '.*' ).values())).mean() + self.source_positions.append([RA, dec]) + else: + self.sources = ["Pointing"] + self.source_positions = [list(self.pointing)] + self.N_sources = len(self.sources) + + source_table = self.hdf5.createTable(self.hdf5.root, 'sources', {'name': tables.StringCol(40), 'position':tables.Float64Col(2)}) + row = source_table.row + for (source, position) in zip(self.sources, self.source_positions) : + row['name'] = source + row['position'] = position + row.append() + source_table.flush() + + if self.PhasorsEnable: + infix = ('Ampl', 'Phase') + else: + infix = ('Real', 'Imag') + + if self.GainEnable : + parmname0 = ':'.join(['Gain', str(self.polarizations[0]), str(self.polarizations[0]), infix[1], self.stations[0]]) + v0 = instrumentdb.getValuesGrid( parmname0 )[ parmname0 ] + if self.DirectionalGainEnable : + parmname0 = ':'.join(['DirectionalGain', str(self.polarizations[0]), str(self.polarizations[0]), infix[1], self.stations[0], self.sources[0]]) + v0 = instrumentdb.getValuesGrid( parmname0 )[ parmname0 ] + + self.freqs = [] + self.freqwidths = [] + + # First collect all frequencies + # We need them beforehand to sort the frequencies (frequencies are not necessarily given in sorted order) + for instrumentdb_name in self.instrumentdb_name_list: + try: + instrumentdb = lofar.parmdb.parmdb( instrumentdb_name ) + v0 = instrumentdb.getValuesGrid( parmname0 )[ parmname0 ] + freqs = v0['freqs'] + self.freqs = numpy.concatenate([self.freqs, freqs]) + self.freqwidths = numpy.concatenate([self.freqwidths, v0['freqwidths']]) + except: + print("Error opening " + instrumentdb_name) + exit() + # Sort frequencies, find both the forward and inverse mapping + # Mappings are such that + # sorted_freqs = unsorted_freqs[sorted_freq_idx] + # sorted_freqs[inverse_sorted_freq_idx] = unsorted_freqs + # We will use the following form + # sorted_freqs[inverse_sorted_freq_idx[selection]] = unsorted_freqs[selection] + # to process chunks (=selections) of unsorted data and store them in sorted order + sorted_freq_idx = sorted(list(range(len(self.freqs))), key = lambda idx: self.freqs[idx]) + inverse_sorted_freq_idx = sorted(list(range(len(self.freqs))), key = lambda idx: sorted_freq_idx[idx]) + + self.freqs = self.freqs[sorted_freq_idx] + self.freqwidths = self.freqwidths[sorted_freq_idx] + self.hdf5.createArray(self.hdf5.root, 'freqs', self.freqs) + self.N_freqs = len(self.freqs) + + self.times = v0['times'] + self.timewidths = v0['timewidths'] + self.hdf5.createArray(self.hdf5.root, 'times', self.times) + self.hdf5.createArray(self.hdf5.root, 'timewidths', self.timewidths) + self.N_times = len( self.times ) + + self.hdf5.createArray(self.hdf5.root, 'polarizations', self.polarizations) + + chunkshape = (1024 , 32, 1, 1, 1) + self.phases = self.hdf5.createCArray(self.hdf5.root, 'phases', tables.Float32Atom(), shape=(self.N_times, self.N_freqs, self.N_stations, self.N_sources, self.N_pol), chunkshape = chunkshape) + self.amplitudes = self.hdf5.createCArray(self.hdf5.root, 'amplitudes', tables.Float32Atom(), shape=(self.N_times, self.N_freqs, self.N_stations, self.N_sources, self.N_pol), chunkshape = chunkshape) + if self.RotationEnable: + self.rotation = self.hdf5.createCArray(self.hdf5.root, 'rotation', tables.Float32Atom(), shape=(self.N_times, self.N_freqs, self.N_stations, self.N_sources), chunkshape = chunkshape[:-1]) + fillarray(self.amplitudes, 1.0) + self.flags = self.hdf5.createCArray(self.hdf5.root, 'flags', tables.Float32Atom(), shape=(self.N_times, self.N_freqs)) + + freq_idx = 0 + for gdsfile, instrumentdb_name, gdsfile_idx in zip(gdsfiles, self.instrumentdb_name_list, list(range(len(gdsfiles)))) : + print(('-Reading %s (%i/%i)' % (gdsfile, gdsfile_idx+1, len(gdsfiles))), end=' ') + shapemismatch = False + + instrumentdb = lofar.parmdb.parmdb( instrumentdb_name ) + v0 = instrumentdb.getValuesGrid( parmname0 )[ parmname0 ] + freqs = v0['freqs'] + N_freqs = len(freqs) + sorted_freq_selection = inverse_sorted_freq_idx[freq_idx:freq_idx+N_freqs] + + try: + self.flags[:, sorted_freq_selection] = instrumentdb.getValuesGrid('flags')['flags']['values'] + except KeyError: + pass + + for pol, pol_idx in zip(self.polarizations, list(range(len(self.polarizations)))): + for station, station_idx in zip(self.stations, list(range(len(self.stations)))): + if self.GainEnable: + parmname0 = ':'.join(['Gain', str(pol), str(pol), infix[0], station]) + parmname1 = ':'.join(['Gain', str(pol), str(pol), infix[1], station]) + if self.PhasorsEnable: + gain_phase = instrumentdb.getValuesGrid( parmname1 )[ parmname1 ]['values'] + self.phases[:, sorted_freq_selection, station_idx, :, pol_idx] = resize(gain_phase.T, (self.N_sources, N_freqs, self.N_times)).T + try: + gain_amplitude = instrumentdb.getValuesGrid( parmname0 )[ parmname0 ]['values'] + except KeyError: + #self.amplitudes[:, sorted_freq_selection, station_idx, :, pol_idx] = numpy.ones((self.N_times, N_freqs, self.N_sources)) + pass + else: + self.amplitudes[:, sorted_freq_selection, station_idx, :, pol_idx] = abs(numpy.resize(gain_amplitudes.T, (self.N_sources, N_freqs, self.N_times)).T) + self.phases[:, sorted_freq_selection, station_idx, :, pol_idx] += angle(numpy.resize(gain_amplitudes.T, (self.N_sources, N_freqs, self.N_times)).T) + else: + gain_real = instrumentdb.getValuesGrid( parmname0 )[ parmname0 ]['values'] + gain_imag = instrumentdb.getValuesGrid( parmname1 )[ parmname1 ]['values'] + self.phases[:, sorted_freq_selection, station_idx, :, pol_idx] = numpy.resize(numpy.arctan2(gain_imag.T, gain_real.T),(self.N_sources, N_freqs, self.N_times)).T + self.amplitudes[:, sorted_freq_selection, station_idx, :, pol_idx] = numpy.resize(numpy.sqrt(gain_imag.T**2 + gain_real.T**2),(self.N_sources, N_freqs, self.N_times)).T + if self.DirectionalGainEnable: + for source, source_idx in zip(self.sources, list(range(len(self.sources)))) : + parmname0 = ':'.join(['DirectionalGain', str(pol), str(pol), infix[0], station, source]) + parmname1 = ':'.join(['DirectionalGain', str(pol), str(pol), infix[1], station, source]) + if self.PhasorsEnable: + gain_phase = instrumentdb.getValuesGrid( parmname1 )[ parmname1 ]['values'] + self.phases[:, sorted_freq_selection, station_idx, source_idx, pol_idx] += gain_phase + try: + gain_amplitude = instrumentdb.getValuesGrid( parmname0 )[ parmname0 ]['values'] + except KeyError: + pass + else: + self.amplitudes[:, sorted_freq_selection, station_idx, source_idx, pol_idx] *= abs(gain_amplitude) + self.phases[:, sorted_freq_selection, station_idx, source_idx, pol_idx] += angle(gain_amplitude) + else: + gain_real = instrumentdb.getValuesGrid( parmname0 )[ parmname0 ]['values'] + gain_imag = instrumentdb.getValuesGrid( parmname1 )[ parmname1 ]['values'] + l = min(gain_real.shape[0], gain_imag.shape[0], self.phases.shape[0]) + if l != self.phases.shape[0]: + shapemismatch = True + gain_real = gain_real[0:l,:] + gain_imag = gain_imag[0:l,:] + self.phases[0:l, sorted_freq_selection, station_idx, source_idx, pol_idx] += numpy.arctan2(gain_imag, gain_real) + self.amplitudes[0:l, sorted_freq_selection, station_idx, source_idx, pol_idx] *= numpy.sqrt(gain_real**2 + gain_imag**2) + if self.RotationEnable: + for station, station_idx in zip(self.stations, list(range(len(self.stations)))): + for source, source_idx in zip(self.sources, list(range(len(self.sources)))) : + parmname = ':'.join(['RotationAngle', station, source]) + rotation = instrumentdb.getValuesGrid( parmname )[ parmname ]['values'] + l = min(rotation.shape[0], self.rotation.shape[0]) + if l != self.rotation.shape[0] : + shapemismatch = True + self.rotation[:l, sorted_freq_selection, station_idx, source_idx] = rotation[:l,:] + freq_idx += N_freqs + print(["","*"][shapemismatch]) + + if self.flags.shape != self.phases.shape[0:2] : + self.flags = numpy.zeros(self.phases.shape[0:2]) + + def calculate_piercepoints(self, time_steps = [], height = 200.e3): + if ( len( time_steps ) == 0 ): + n_list = list(range( self.times.shape[0])) + else: + n_list = time_steps + self.n_list = n_list + if 'n_list' in self.hdf5.root: self.hdf5.root.n_list.remove() + self.hdf5.createArray(self.hdf5.root, 'n_list', self.n_list) + + self.height = height + + if 'piercepoints' in self.hdf5.root: self.hdf5.root.piercepoints.remove() + description = {'positions':tables.Float64Col((self.N_sources, self.N_stations,2)), \ + 'positions_xyz':tables.Float64Col((self.N_sources, self.N_stations,3)), \ + 'zenith_angles':tables.Float64Col((self.N_sources, self.N_stations))} + self.piercepoints = self.hdf5.createTable(self.hdf5.root, 'piercepoints', description) + self.piercepoints.attrs.height = self.height + piercepoints_row = self.piercepoints.row + p = ProgressBar(len(n_list), "Calculating piercepoints: ") + for (n, counter) in zip(n_list, list(range(len(n_list)))): + p.update(counter) + piercepoints = PiercePoints( self.times[ n ], self.pointing, self.array_center, self.source_positions, self.station_positions, height = self.height ) + piercepoints_row['positions'] = piercepoints.positions + piercepoints_row['positions_xyz'] = piercepoints.positions_xyz + piercepoints_row['zenith_angles'] = piercepoints.zenith_angles + piercepoints_row.append() + self.piercepoints.flush() + p.finished() + + def calculate_basevectors(self, order = 15, beta = 5. / 3., r_0 = 1.): + self.order = order + self.beta = beta + self.r_0 = r_0 + + N_stations = len(self.stations) + N_sources = len(self.sources) + + N_piercepoints = N_stations * N_sources + P = eye(N_piercepoints) - ones((N_piercepoints, N_piercepoints)) / N_piercepoints + + self.C_list = [] + self.U_list = [] + self.S_list = [] + p = ProgressBar(len(self.piercepoints), "Calculating base vectors: ") + for (piercepoints, counter) in zip(self.piercepoints, list(range(len(self.piercepoints)))): + p.update( counter ) + Xp_table = reshape(piercepoints['positions_xyz'], (N_piercepoints, 3) ) + + # calculate structure matrix + D = resize( Xp_table, ( N_piercepoints, N_piercepoints, 3 ) ) + D = transpose( D, ( 1, 0, 2 ) ) - D + D2 = sum( D**2, 2 ) + C = -(D2 / ( r_0**2 ) )**( beta / 2.0 )/2.0 + self.C_list.append(C) + + # calculate covariance matrix C + # calculate partial product for interpolation B + # reforce symmetry + + C = dot(dot(P, C ), P) + + # eigenvalue decomposition + # reforce symmetry + # select subset of base vectors + [ U, S, V ] = linalg.svd( C ) + U = U[ :, 0 : order ] + S = S[ 0 : order ] + self.U_list.append(U) + self.S_list.append(S) + p.finished() + + def fit_model ( self ) : + N_stations = len(self.stations) + N_times = len(self.times) + N_sources = len(self.sources) + N_pol = len(self.polarizations) + G = kron(eye( N_sources ), ( eye( N_stations ) - ones((N_stations, N_stations)) / N_stations)) + + if 'TECfit' in self.hdf5.root: self.hdf5.root.TECfit.remove() + self.TECfit = self.hdf5.createArray(self.hdf5.root, 'TECfit', zeros(self.TEC.shape)) + + if 'TECfit_white' in self.hdf5.root: self.hdf5.root.TECfit_white.remove() + self.TECfit_white = self.hdf5.createArray(self.hdf5.root, 'TECfit_white', zeros(self.TEC.shape)) + + self.offsets = zeros((N_pol, len(self.n_list))) + p = ProgressBar(len(self.n_list), "Fitting phase screen: ") + for i in range(len(self.n_list)) : + p.update( i ) + U = self.U_list[i] + S = self.S_list[i] + for pol in range(1) : # range(N_pol) : + TEC = self.TEC[ pol, self.n_list[i], :, :].reshape( (N_sources * N_stations, 1) ) + TECfit = dot(U, dot(inv(dot(U.T, dot(G, U))), dot(U.T, dot(G, TEC)))) + TECfit_white = dot(U, dot(diag(1/S), dot(U.T, TECfit))) + self.offsets[pol, i] = TECfit[0] - dot(self.C_list[i][0,:], TECfit_white) + self.TECfit[ pol, i, :, : ] = reshape( TECfit, (N_sources, N_stations) ) + self.TECfit_white[ pol, i, :, : ] = reshape( TECfit_white, (N_sources, N_stations) ) + p.finished() + + self.TECfit.attrs.r_0 = self.r_0 + self.TECfit.attrs.beta = self.beta + + self.TECfit_white.attrs.r_0 = self.r_0 + self.TECfit_white.attrs.beta = self.beta + + if 'offsets' in self.hdf5.root: self.hdf5.root.offsets.remove() + self.hdf5.createArray(self.hdf5.root, 'offsets', self.offsets) + + def make_movie( self, extent = 0, npixels = 100, vmin = 0, vmax = 0 ): + """ + """ + + multiengine_furl = os.environ['HOME'] + '/ipcluster/multiengine.furl' +# mec = client.MultiEngineClient( multiengine_furl ) + mec = client.MultiEngineClient( ) + task_furl = os.environ['HOME'] + '/ipcluster/task.furl' + #tc = client.TaskClient( task_furl ) + tc = client.TaskClient( ) + N_stations = len(self.stations) + N_times = self.TECfit.shape[1] + N_sources = len(self.sources) + N_piercepoints = N_stations * N_sources + N_pol = self.TECfit_white.shape[0] + R = 6378137.0 + taskids = [] + + beta = self.TECfit.attrs.beta + r_0 = self.TECfit.attrs.r_0 + + print("Making movie...") + p = ProgressBar( len( self.n_list ), 'Submitting jobs: ' ) + for i in range(len(self.n_list)) : + p.update(i) + Xp_table = reshape(self.piercepoints[i]['positions'], (N_piercepoints, 2) ) + if extent > 0 : + w = extent/R/2 + else : + w = 1.1*abs(Xp_table).max() + for pol in range(N_pol) : + v = self.TECfit[ pol, i, :, : ].reshape((N_piercepoints,1)) + maptask = client.MapTask(calculate_frame, (Xp_table, v, beta, r_0, npixels, w) ) + taskids.append(tc.run(maptask)) + p.finished() + + if vmin == 0 and vmax == 0 : + vmin = self.TECfit.min() + vmax = self.TECfit.max() + vdiff = vmax - vmin + vmin = vmin - 0.1*vdiff + vmax = vmax + 0.1*vdiff + + p = ProgressBar( len( self.n_list ), 'Fetch results: ' ) + for i in range(len(self.n_list)) : + p.update(i) + clf() + for pol in range(N_pol) : + (phi,w) = tc.get_task_result(taskids.pop(0), block = True) + phi = phi + self.offsets[pol, i] + subplot(1, N_pol, pol+1) + w = w*R*1e-3 + h_im = imshow(phi, interpolation = 'nearest', origin = 'lower', extent = (-w, w, -w, w), vmin = vmin, vmax = vmax ) + h_axes = gca() + cl = h_im.get_clim() + TEC = reshape( self.TEC[ pol, i, :, : ], N_piercepoints ) + for j in range(N_piercepoints): + color = h_im.cmap(int(round((TEC[j]-cl[0])/(cl[1]-cl[0])*(h_im.cmap.N-1)))) + plot(R*1e-3*Xp_table[j,0], R*1e-3*Xp_table[j,1], marker = 'o', markeredgecolor = 'k', markerfacecolor = color) + colorbar() + xlim(-w, w) + ylim(-w, w) + savefig('tmpfig%4.4i.png' % i) + p.finished() + os.system("mencoder mf://tmpfig????.png -o movie.mpeg -mf type=png:fps=3 -ovc lavc -ffourcc DX50 -noskip -oac copy") + + def interpolate( self, facetlistfile ) : + + """ + """ + + #facetdbname = os.path.join(self.globaldb, 'facets') + #os.system( 'makesourcedb in=%s out=%s append=False' % (facetlistfile, facetdbname) ) + + #patch_table = pt.table( os.path.join(facetdbname, 'SOURCES', 'PATCHES' ) ) + + #if 'facets' in self.hdf5.root: self.hdf5.root.facets.remove() + #description = {'name': tables.StringCol(40), 'position':tables.Float64Col(2)} + #self.facets = self.hdf5.createTable(self.hdf5.root, 'facets', description) + + #facet = self.facets.row + #for patch in patch_table : + #facet['name'] = patch['PATCHNAME'] + #facet['position'] = array([patch['RA'], patch['DEC']]) + #facet.append() + #self.facets.flush() + self.N_facets = len(self.facets) + + self.facet_names = self.facets[:]['name'] + self.facet_positions = self.facets[:]['position'] + + print(self.n_list) + if 'STEC_facets' in self.hdf5.root: self.hdf5.root.STEC_facets.remove() + self.STEC_facets = self.hdf5.createCArray(self.hdf5.root, 'STEC_facets', tables.Float32Atom(), shape = (self.N_pol, self.n_list.shape[0], self.N_facets, self.N_stations)) + + #if 'facet_piercepoints' in self.hdf5.root: self.hdf5.root.facet_piercepoints.remove() + #description = {'positions':tables.Float64Col((self.N_facets, self.N_stations,2)), \ + #'positions_xyz':tables.Float64Col((self.N_facets, self.N_stations,3)), \ + #'zenith_angles':tables.Float64Col((self.N_facets, self.N_stations))} + #self.facet_piercepoints = self.hdf5.createTable(self.hdf5.root, 'facet_piercepoints', description) + #height = self.piercepoints.attrs.height + #facet_piercepoints_row = self.facet_piercepoints.row + #print "Calculating facet piercepoints..." + #for n in self.n_list: + #piercepoints = PiercePoints( self.times[ n ], self.pointing, self.array_center, self.facet_positions, self.station_positions, height = height ) + #facet_piercepoints_row['positions'] = piercepoints.positions + #facet_piercepoints_row['positions_xyz'] = piercepoints.positions_xyz + #facet_piercepoints_row['zenith_angles'] = piercepoints.zenith_angles + #facet_piercepoints_row.append() + #self.facet_piercepoints.flush() + + r_0 = self.TECfit_white.attrs.r_0 + beta = self.TECfit_white.attrs.beta + + for facet_idx in range(self.N_facets) : + for station_idx in range(self.N_stations): + for pol_idx in range(self.N_pol) : + TEC_list = [] + for n in range(len(self.n_list)): + p = self.facet_piercepoints[n]['positions_xyz'][facet_idx, station_idx,:] + za = self.facet_piercepoints[n]['zenith_angles'][facet_idx, station_idx] + Xp_table = reshape(self.piercepoints[n]['positions_xyz'], (self.N_piercepoints, 3) ) + v = self.TECfit_white[ pol_idx, n, :, : ].reshape((self.N_piercepoints,1)) + D2 = sum((Xp_table - p)**2,1) + C = (D2 / ( r_0**2 ) )**( beta / 2. ) / -2. + self.STEC_facets[pol_idx, n, facet_idx, station_idx] = dot(C, v)/cos(za) + + def ClockTEC( self, ClockEnable = True, TECEnable = True, stations = []) : + """ + Estimate Clock and ionospheric TEC from phase information + """ + + if not ClockEnable and not TECEnable: return + + if len(stations) == 0: + stations = self.stations + + station_list = list(self.stations[:]) + station_idx = [station_list.index(station) for station in stations] + N_stations = len(stations) + N_baselines = N_stations * (N_stations - 1) / 2 + time_start = 3000 + time_end = 6000 + N_times = time_end - time_start + + N_times = len(self.times) + time_start = 0 + time_end = N_times + + N_sources = len(self.sources) + N_pol = len(self.polarizations) + + if N_sources == 0: + N_sources = 1 + + if ClockEnable: + if 'Clock' in self.hdf5.root: self.hdf5.root.Clock.remove() + self.Clock = self.hdf5.createCArray(self.hdf5.root, 'Clock', tables.Float32Atom(), shape = (N_pol, N_times, N_stations)) + if TECEnable: + if 'TEC' in self.hdf5.root: self.hdf5.root.TEC.remove() + self.TEC = self.hdf5.createCArray(self.hdf5.root, 'TEC', tables.Float32Atom(), shape = (N_pol, N_times, N_sources, N_stations)) + + + for pol in range(N_pol): + for source_no in range(N_sources): + if ClockEnable and TECEnable: + (Clock, TEC) = fit_Clock_and_TEC( squeeze( self.phases[time_start:time_end, :, station_idx, source_no, pol] ), + self.freqs[:], self.flags[time_start:time_end, :] ) + self.Clock[ pol, :, 1: ] = Clock + self.TEC[ pol, :, source_no, 1: ] = TEC + else : + v = fit_Clock_or_TEC( squeeze( self.phases[:, :, station_idx, source_no, pol] ), self.freqs[:], self.flags[:, :], ClockEnable ) + if ClockEnable : + self.Clock[ pol, :, source_no, 1: ] = v + else: + self.TEC[ pol, :, source_no, 1: ] = v + + def write_to_parmdb( self ) : + """ + Write Clock and TEC to a parmdb + """ + + N_sources = len(self.sources) + + if N_sources == 0: + N_sources = 1 + + parms = {} + parm = {} + parm[ 'freqs' ] = numpy.array( [ .5e9 ] ) + parm[ 'freqwidths' ] = numpy.array( [ 1.0e9 ] ) + parm[ 'times' ] = self.times[:].ravel() + parm[ 'timewidths' ] = self.timewidths[:].ravel() + + for n_pol in range(len(self.polarizations)): + pol = self.polarizations[n_pol] + + for n_station in range(len(self.stations)): + station = self.stations[n_station] + + # Clock + if 'Clock' in self.__dict__ : + Clock_parm = parm.copy() + parmname = ':'.join(['Clock', str(pol), station]) + Clock_parm[ 'values' ] = self.Clock[n_pol, :, n_station] + parms[ parmname ] = Clock_parm + + for n_source in range(N_sources): + if self.DirectionalGainEnable: + source = self.sources[n_source] + identifier = ':'.join([str(pol), station, source]) + else: + identifier = ':'.join([str(pol), station]) + + # TEC + TEC_parm = parm.copy() + parmname = ':'.join(['TEC', identifier]) + TEC_parm[ 'values' ] = self.TEC[n_pol, :, n_source, n_station] + parms[ parmname ] = TEC_parm + + #TECfit + TECfit_parm = parm.copy() + parmname = ':'.join(['TECfit', identifier]) + TECfit_parm[ 'values' ] = self.TECfit[n_pol, :, n_source, n_station] + parms[ parmname ] = TECfit_parm + + #TECfit_white + TECfit_white_parm = parm.copy() + parmname = ':'.join(['TECfit_white', identifier]) + TECfit_white_parm[ 'values' ] = self.TECfit_white[n_pol, :, n_source, n_station] + parms[ parmname ] = TECfit_white_parm + + #Piercepoints + + for n_station in range(len(self.stations)): + station = self.stations[n_station] + for n_source in range(N_sources): + if self.DirectionalGainEnable: + source = self.sources[n_source] + identifier = ':'.join([station, source]) + else: + identifier = station + PiercepointX_parm = parm.copy() + parmname = ':'.join(['Piercepoint', 'X', identifier]) + print(n_source, n_station) + x = self.piercepoints[:]['positions_xyz'][:,n_source, n_station,0] + PiercepointX_parm['values'] = x + parms[ parmname ] = PiercepointX_parm + + PiercepointY_parm = parm.copy() + parmname = ':'.join(['Piercepoint', 'Y', identifier]) + y = self.piercepoints[:]['positions_xyz'][:,n_source, n_station,1] + PiercepointY_parm['values'] = array(y) + parms[ parmname ] = PiercepointY_parm + + PiercepointZ_parm = parm.copy() + parmname = ':'.join(['Piercepoint', 'Z', identifier]) + z = self.piercepoints[:]['positions_xyz'][:,n_source, n_station,2] + PiercepointZ_parm['values'] = z + parms[ parmname ] = PiercepointZ_parm + + Piercepoint_zenithangle_parm = parm.copy() + parmname = ':'.join(['Piercepoint', 'zenithangle', identifier]) + za = self.piercepoints[:]['zenith_angles'][:,n_source, n_station] + Piercepoint_zenithangle_parm['values'] = za + parms[ parmname ] = Piercepoint_zenithangle_parm + + time_start = self.times[0] - self.timewidths[0]/2 + time_end = self.times[-1] + self.timewidths[-1]/2 + + + parm[ 'times' ] = numpy.array([(time_start + time_end) / 2]) + parm[ 'timewidths' ] = numpy.array([time_end - time_start]) + + height_parm = parm.copy() + height_parm[ 'values' ] = numpy.array( self.piercepoints.attrs.height ) + parms[ 'height' ] = height_parm + + beta_parm = parm.copy() + beta_parm[ 'values' ] = numpy.array( self.TECfit_white.attrs.beta ) + parms[ 'beta' ] = beta_parm + + r_0_parm = parm.copy() + r_0_parm[ 'values' ] = numpy.array( self.TECfit_white.attrs.r_0 ) + parms[ 'r_0' ] = r_0_parm + + parmdbmain.store_parms( self.globaldb + '/ionosphere', parms, create_new = True) + + def write_phases_to_parmdb( self, gdsfile, phases_name = 'ionosphere') : + gdsparset = lofar.parameterset.parameterset( gdsfile ) + N_parts = gdsparset.getInt("NParts") + parm = {} + N_times = len(self.times) + parm[ 'times' ] = self.times[:].ravel() + parm[ 'timewidths' ] = self.timewidths[:].ravel() + for i in [0]: # range(N_parts): + parms = {} + msname = gdsparset.getString( "Part%i.FileName" % i ) + mshostname = gdsparset.getString( "Part%i.FileSys" % i).split(':')[0] + spectral_table_name = self.globaldb + "/SPECTRAL_WINDOW" + if not os.path.exists( spectral_window_name ) : + os.system("scp -r %s:%s/SPECTRAL_WINDOW %s" % ( mshostname, msname, spectral_window_name )) + spectral_table = pt.table( spectral_table_name ) + freqs = spectral_table[0]['CHAN_FREQ'] + N_freqs = len(freqs) + parm[ 'freqs' ] = freqs + parm[ 'freqwidths' ] = spectral_table[0]['CHAN_WIDTH'] + spectral_table.close() + + for (pol, pol_idx) in zip(self.polarizations, list(range(len(self.polarizations)))): + for n_station in range(len(self.stations)): + station = self.stations[n_station] + + ## Clock + #if 'Clock' in self.__dict__ : + #Clock_parm = parm.copy() + #parmname = ':'.join(['Clock', str(pol), station]) + #Clock_parm[ 'values' ] = self.Clock[n_pol, :, n_station] + #parms[ parmname ] = Clock_parm + + for (facet, facet_idx) in zip(self.facets[:]['name'], list(range(len(self.facets)))): + v = exp(1j * self.STEC_facets[pol_idx, :, facet_idx, n_station].reshape((N_times,1)) * \ + 8.44797245e9 / freqs.reshape((1, N_freqs))) + identifier = ':'.join([str(pol), str(pol), 'Real', station, facet]) + DirectionalGain_parm = parm.copy() + parmname = ':'.join(['DirectionalGain', identifier]) + DirectionalGain_parm[ 'values' ] = real(v) + parms[ parmname ] = DirectionalGain_parm + + identifier = ':'.join([str(pol), str(pol), 'Imag', station, facet]) + DirectionalGain_parm = parm.copy() + parmname = ':'.join(['DirectionalGain', identifier]) + DirectionalGain_parm[ 'values' ] = imag(v) + parms[ parmname ] = DirectionalGain_parm + + parmdbmain.store_parms( self.globaldb + '/facetgains', parms, create_new = True) + + #def write_phases_to_parmdb( self, gdsfiles = [], phases_name = 'ionosphere') : + #if len(gdsfiles) == 0: + #gdsfiles = self.gdsfiles + #for gdsfile in gdsfiles : + #instrument_parmdbname = os.path.splitext(gdsfile)[0] + os.path.extsep + str(self.instrument_name) + #phases_parmdbname = os.path.splitext(gdsfile)[0] + os.path.extsep + phases_name + #os.system( "rundist -wd %s parmdbwriter.py %s %s %s" % (os.environ['PWD'], instrument_parmdbname, self.globaldb, phases_name) ) + + #p = re.compile('(^Part\\d*.FileName\\s*=\\s*\\S*)(%s$)' % str(self.instrument_name)) + #print repr(instrument_parmdbname) + #file_instrument_parmdb = open(instrument_parmdbname) + #file_phases_parmdb = open(phases_parmdbname, 'w') + #file_phases_parmdb.writelines([p.sub('\\1%s' % phases_name, l) for l in file_instrument_parmdb.readlines()]) + #file_instrument_parmdb.close() + #file_phases_parmdb.close() + +def product(*args, **kwds): + # product('ABCD', 'xy') --> Ax Ay Bx By Cx Cy Dx Dy + # product(range(2), repeat=3) --> 000 001 010 011 100 101 110 111 + pools = list(map(tuple, args)) * kwds.get('repeat', 1) + result = [[]] + for pool in pools: + result = [x+[y] for x in result for y in pool] + for prod in result: + yield tuple(prod) + +def fillarray( a, v ) : + print(a.shape, a.chunkshape) + for idx in product(*[range(0, s, c) for s, c in zip(a.shape, a.chunkshape)]) : + s = tuple([slice(i,min(i+c,s)) for i,s,c in zip(idx, a.shape, a.chunkshape)]) + a[s] = v + + + +def calculate_frame(Xp_table, v, beta, r_0, npixels, w): + import numpy + + N_piercepoints = Xp_table.shape[0] + P = numpy.eye(N_piercepoints) - numpy.ones((N_piercepoints, N_piercepoints)) / N_piercepoints + # calculate structure matrix + D = numpy.resize( Xp_table, ( N_piercepoints, N_piercepoints, 2 ) ) + D = numpy.transpose( D, ( 1, 0, 2 ) ) - D + D2 = numpy.sum( D**2, 2 ) + C = -(D2 / ( r_0**2 ) )**( beta / 2.0 )/2.0 + C = numpy.dot(numpy.dot(P, C ), P) + v = numpy.dot(numpy.linalg.pinv(C), v) + + phi = numpy.zeros((npixels,npixels)) + for x_idx in range(0, npixels): + x = -w + 2*x_idx*w/( npixels-1 ) + for y_idx in range(0, npixels): + y = -w + 2*y_idx*w/(npixels-1) + D2 = numpy.sum((Xp_table - numpy.array([ x, y ]))**2,1) + C = (D2 / ( r_0**2 ) )**( beta / 2.0 ) / -2.0 + phi[y_idx, x_idx] = numpy.dot(C, v) + return phi, w + +def fit_Clock_or_TEC( phase, freqs, flags, ClockEnable ): + + if ClockEnable : + v = freqs.reshape((freqs.size,1))*2*pi + else : + v = 8.44797245e9/freqs.reshape((freqs.size,1)) + + A1 = numpy.concatenate([v, 2*pi*numpy.ones(v.shape)], axis=1) + A2 = v + + p22 = [] + rr = [] + + multiengine_furl = os.environ['HOME'] + '/ipcluster/multiengine.furl' + #mec = client.MultiEngineClient( multiengine_furl ) + mec = client.MultiEngineClient( ) + mec.execute('import numpy, scipy.optimize') + + task_furl = os.environ['HOME'] + '/ipcluster/task.furl' + #tc = client.TaskClient( task_furl ) + tc = client.TaskClient( ) + + taskids = [] + for i in range(0,phase.shape[0]): + print(i+1, '/', phase.shape[0]) + maptask = client.MapTask(fit_Clock_or_TEC_worker, ( phase[i, :, :], flags[i, :], A1,A2)) + taskids.append(tc.run(maptask)) + i = 0 + for taskid in taskids : + i += 1 + print(i, '/', len(taskids)) + (residual_std, p) = tc.get_task_result(taskid, block = True) + rr.append(residual_std) + p22.append(p) + + Clock_or_TEC = numpy.array(p22) + + tc.clear() + del tc + del mec + + return Clock_or_TEC + + +#################################################################### +def fit_Clock_or_TEC_worker( phase, flags, A1, A2 ): + + def costfunction(p, A, y, flags = 0) : + N_stations = y.shape[1] + TEC = numpy.concatenate( ( numpy.zeros(1), p ) ) + e = [] + for station0 in range(0, N_stations): + for station1 in range(station0 + 1, N_stations): + p1 = TEC[station1] - TEC[station0] + dphase = y[:, [station1]] - y[:, [station0]] + e.append( (numpy.mod( numpy.dot(A, p1) - dphase + numpy.pi, 2*numpy.pi ) - numpy.pi) ) + e = numpy.concatenate(e, axis=0) + e = e[:,0] * (1-flags) + return e + + N_stations = phase.shape[1] + + rr = [] + A = [] + dphase = [] + flags1 = [] + not_flagged_idx, = numpy.nonzero(1-flags) + for station0 in range(0, N_stations): + for station1 in range(station0 + 1, N_stations): + v = numpy.zeros(N_stations) + v[station1] = 1 + v[station0] = -1 + A.append(v) + dphase1 = numpy.zeros(phase.shape[0]) + dphase1[not_flagged_idx] = numpy.unwrap(phase[not_flagged_idx, station1] - phase[not_flagged_idx, station0]) + dphase.append(dphase1) + flags1.append(flags) + A = numpy.array(A) + dphase = numpy.concatenate(dphase) + flags1 = numpy.concatenate(flags1) + + A3 = numpy.kron(A[:,1:], A1) + S3 = numpy.dot(numpy.linalg.inv(numpy.dot(A3.T, A3)), A3.T) + p = numpy.dot(S3, dphase) + + p[0::2] = 0 + p[1::2] = numpy.round(p[1::2]) + dphase = dphase - numpy.dot(A3, p) + + A4 = numpy.kron(A[:,1:], A2) + S4 = numpy.dot(numpy.linalg.inv(numpy.dot(A4.T, A4)), A4.T) + p = numpy.dot(S4, dphase) + + p, returncode = scipy.optimize.leastsq(costfunction, p, (A2, phase, flags1)) + + while True: + dphase_fit = numpy.dot(A4, p) + residual = (numpy.mod(dphase - dphase_fit + numpy.pi,2*numpy.pi) - numpy.pi) + residual_std = numpy.sqrt(numpy.mean(residual[flags1==0]**2)) + new_outlier_idx, = numpy.nonzero( (numpy.abs(residual) > (3*residual_std)) & (flags1 == 0)) + if len(new_outlier_idx) == 0: + break + flags1[new_outlier_idx] = 1 + p, returncode = scipy.optimize.leastsq(costfunction, p, (A2, phase, flags1)) + p_init = p + + return residual_std, p + +#################################################################### + +def fit_Clock_and_TEC( phase, freqs, flags ): + + A1 = zeros((len(freqs),3)) + A1[:,0] = freqs*2*pi + A1[:,1] = 8.44797245e9/freqs + A1[:,2] = 2*pi*numpy.ones(freqs.shape) + + A2 = A1[:, 0:2] + S2 = numpy.dot(numpy.linalg.inv(numpy.dot(A2.T, A2)), A2.T) + + dp = 2*pi*numpy.dot(S2, ones(phase.shape[1])) + + p22 = [] + residual_std1 = [] + + rr = [] + multiengine_furl = os.environ['HOME'] + '/ipcluster/multiengine.furl' + #mec = client.MultiEngineClient( multiengine_furl ) + mec = client.MultiEngineClient( ) + mec.execute('import numpy, scipy.optimize') + + + task_furl = os.environ['HOME'] + '/ipcluster/task.furl' + #tc = client.TaskClient( task_furl ) + tc = client.TaskClient( ) + + taskids = [] + for i in range(0,phase.shape[0]): + print(i+1, '/', phase.shape[0]) + sys.stdout.flush() + maptask = client.MapTask(fit_Clock_and_TEC_worker, ( phase[i, :, :], flags[i, :], A1,A2,dp)) + taskids.append(tc.run(maptask)) + i = 0 + for taskid in taskids : + i += 1 + print(i, '/', len(taskids)) + sys.stdout.flush() + (residual_std, p) = tc.get_task_result(taskid, block = True) + rr.append(residual_std) + p22.append(p.copy()) + rr = numpy.array(rr) + p22 = numpy.array(p22) + + tc.clear() + del tc + del mec + + Clock = p22[:,0::2] + TEC = p22[:,1::2] + + return (Clock, TEC) + +#################################################################### + +def fit_Clock_and_TEC1( phase, freqs, flags ): + + A1 = zeros((len(freqs),3)) + A1[:,0] = freqs*2*pi + A1[:,1] = 8.44797245e9/freqs + A1[:,2] = 2*pi*numpy.ones(freqs.shape) + + A2 = A1[:, 0:2] + S2 = numpy.dot(numpy.linalg.inv(numpy.dot(A2.T, A2)), A2.T) + + dp = 2*pi*numpy.dot(S2, ones(phase.shape[1])) + + p22 = [] + residual_std1 = [] + + rr = [] + for i in range(0,phase.shape[0]): + print(i+1, '/', phase.shape[0]) + sys.stdout.flush() + residual_std, p = fit_Clock_and_TEC_worker ( phase[i, :, :], flags[i, :], A1,A2,dp) + rr.append(residual_std) + p22.append(p.copy()) + rr = numpy.array(rr) + p22 = numpy.array(p22) + + Clock = p22[:,0::2] + TEC = p22[:,1::2] + + return (Clock, TEC) + +############################################################################### + +def fit_Clock_and_TEC_worker( phase, flags, A1, A2, dp ): + #import numpy + #import scipy.optimize + + def costfunction(p, A, y, flags = 0) : + N_stations = y.shape[1] + Clock = numpy.concatenate( ( numpy.zeros(1), p[0::2] ) ) + TEC = numpy.concatenate( ( numpy.zeros(1), p[1::2] ) ) + e = [] + for station0 in range(0, N_stations): + for station1 in range(station0 + 1, N_stations): + dClock = Clock[station1] - Clock[station0] + dTEC = TEC[station1] - TEC[station0] + p1 = numpy.array( [ dClock, dTEC ] ) + dphase = y[:, station1] - y[:, station0] + e.append( (numpy.mod( numpy.dot(A, p1) - dphase + numpy.pi, 2*numpy.pi ) - numpy.pi) ) + e = numpy.concatenate(e) * (1-flags) + return e + + N_stations = phase.shape[1] + + p_init = numpy.zeros( 2*N_stations -2 ) + A = [] + dphase = [] + flags1 = [] + not_flagged_idx, = numpy.nonzero(1-flags) + for station0 in range(0, N_stations): + for station1 in range(station0 + 1, N_stations): + v = numpy.zeros(N_stations) + v[ station1 ] = 1 + v[ station0 ] = -1 + A.append(v) + dphase1 = numpy.zeros(phase.shape[0]) + dphase1[not_flagged_idx] = numpy.unwrap(phase[not_flagged_idx, station1] - phase[not_flagged_idx, station0]) + dphase.append(dphase1) + flags1.append(flags) + A = numpy.array(A) + dphase = numpy.concatenate(dphase) + flags1 = numpy.concatenate(flags1) + + A3 = numpy.kron(A[:,1:], A1) + S3 = numpy.dot(numpy.linalg.inv(numpy.dot(A3.T, A3)), A3.T) + p = numpy.dot(S3, dphase) + + p[0::3] = 0 + p[1::3] = 0 + p[2::3] = numpy.round(p[2::3]) + dphase = dphase - numpy.dot(A3, p) + + + A4 = numpy.kron(A[:,1:], A2) + S4 = numpy.dot(numpy.linalg.inv(numpy.dot(A4.T, A4)), A4.T) + p = numpy.dot(S4, dphase) + p = p - numpy.kron(numpy.round(p[1::2] / dp[1]), dp) + + p, returncode = scipy.optimize.leastsq(costfunction, p, (A2, phase, flags1)) + p_init = p - numpy.kron(numpy.round(p[1::2] / dp[1]), dp) + + while True: + dphase_fit = numpy.dot(A4, p) + residual = numpy.mod(dphase - dphase_fit + numpy.pi,2*numpy.pi) - numpy.pi + residual_std = numpy.sqrt( numpy.mean ( residual[flags1==0]**2 ) ) + new_outlier_idx, = numpy.nonzero( (numpy.abs(residual) > (3*residual_std)) & (flags1 == 0)) + if len( new_outlier_idx ) == 0: + break + flags1[new_outlier_idx] = 1 + p, returncode = scipy.optimize.leastsq(costfunction, p_init, (A2, phase, flags1)) + p_init = p - numpy.kron(numpy.round(p[1::2] / dp[1]), dp) + + return (residual_std, p) + +##################################################################### + + +def fit_phi_klmap_model( P, U_table = None, pza_table = None, phase_table = None, dojac = None): +# TODO: calculate penalty terms of MAP estimator + + # handle input parameters + if ( ( U_table == None ) or + ( pza_table == None ) or + ( phase_table == None ) ): + return - 1, None, None + + (antenna_count, source_count) = phase_table.shape + + # calculate phases at puncture points + phi_table = dot( U_table, P ) / cos( aradians( pza_table ) ) + phi_table = phi_table.reshape( (antenna_count, source_count) ) + + # calculate chi2 terms + chi_list = [] + for source in range(source_count): + for i in range(antenna_count): + for j in range(i, antenna_count): + chi_list.append( mod( phi_table[i, source] - phi_table[j, source] - phase_table[i, source] + phase_table[j, source] + pi, 2*pi) - pi ) + + # make (normalized) chi2 array + chi_array = array( chi_list ) + + return 0, chi_array, None + +############################################################################### + +# gradient + +def phi_gradient_model( X, p ): + phi = dot( X, p ) + return phi + +############################################################################### + +def phi_klmap_model( X, Xp_table, B_table, F_table, beta = 5. / 3., r_0 = 1. ): +# B_table = (1/m)(1T)(C_table)(A_table) +# F_table = ( Ci_table )( U_table )( P_table ) + + # input check + if ( len( shape( X ) ) == 1 ): + X_table = array( [ X ] ) + elif ( len( shape( X ) ) == 2 ): + X_table = X + + # calculate structure matrix + x_count = len( X_table ) + p_count = len( Xp_table ) + D_table = transpose( resize( X_table, ( p_count, x_count, 2 ) ), ( 1, 0, 2 ) ) + D_table = D_table - resize( Xp_table, ( x_count, p_count, 2 ) ) + D_table = add.reduce( D_table**2, 2 ) + D_table = ( D_table / ( r_0**2 ) )**( beta / 2. ) + + # calculate covariance matrix + C_table = - D_table / 2. + C_table = transpose( transpose( C_table ) - ( add.reduce( C_table, 1 ) / float( p_count ) ) ) + C_table = C_table - B_table + + phi = dot( C_table, F_table ) + phi = reshape( phi, ( x_count ) ) + if ( len( phi ) == 1 ): + phi = phi[ 0 ] + + return phi + +def get_source_list( pdb, source_pattern_list ): + source_list = [] + for pattern in source_pattern_list : + parmname_list = pdb.getNames( 'DirectionalGain:?:?:*:*:' + pattern ) + source_list.extend([n.split(':')[-1] for n in parmname_list]) + parmname_list = pdb.getNames( 'RotationAngle:*:' + pattern ) + source_list.extend([n.split(':')[-1] for n in parmname_list]) + print(source_list) + return sorted(set(source_list)) + +def get_source_list_from_ionospheredb( pdb ): + parmname_list = pdb.getNames( 'TEC:?:*:*:*' ) + source_list = [n.split(':')[-1] for n in parmname_list] + return sorted(set(source_list)) + +def get_station_list( pdb, station_pattern_list, DirectionalGainEnable ): + station_list = [] + for pattern in station_pattern_list : + parmname_list = pdb.getNames( { True : 'DirectionalGain:?:?:*:'+pattern+':*', False: 'Gain:?:?:*:' + pattern}[DirectionalGainEnable] ) + station_list.extend(sorted(set([n.split(':')[{True : -2, False : -1}[DirectionalGainEnable]] for n in parmname_list]))) + return station_list + +def get_station_list_from_ionospheredb( pdb ): + parmname_list = pdb.getNames( 'TEC:?:*:*:*' ) + station_list = [n.split(':')[-2] for n in parmname_list] + return sorted(set(station_list)) + +def get_time_list_from_ionospheredb( pdb, pattern = '*' ): + parameter_names = pdb.getNames( pattern ) + parm0 = pdb.getValuesGrid(parameter_names[0])[parameter_names[0]] + time_list = parm0['times'] + time_width_list = parm0['timewidths'] + return (time_list, time_width_list) + +############################################################################### + +class PiercePoints: + + def __init__( self, time, pointing, array_center, source_positions, antenna_positions, height = 400.e3 ): + # source table radecs at observing epoch + + # calculate Earth referenced coordinates of puncture points of array center towards pointing center + [ center_pxyz, center_pza ] = sphere.calculate_puncture_point_mevius( array_center, pointing, time, height = height ) + self.center_p_geo_llh = sphere.xyz_to_geo_llh( center_pxyz, time ) + + # loop over sources + positions = [] + positions_xyz = [] + zenith_angles = [] + + for k in range( len( source_positions ) ): + positions_xyz1 = [] + positions1 = [] + zenith_angles1 = [] + + # loop over antennas + for i in range( len( antenna_positions ) ): + # calculate Earth referenced coordinates of puncture points of antenna towards peeled source + [ pxyz, pza ] = sphere.calculate_puncture_point_mevius( antenna_positions[ i ], source_positions[ k ], time, height = height ) + p_geo_llh = sphere.xyz_to_geo_llh( pxyz, time ) + + # calculate local angular coordinates of antenna puncture point ( x = East, y = North ) + [ separation, angle ] = sphere.calculate_angular_separation( self.center_p_geo_llh[ 0 : 2 ], p_geo_llh[ 0 : 2 ] ) + X = [ separation * sin( angle ), separation * cos( angle ) ] + + # store model fit input data + positions1.append(X) + positions_xyz1.append( pxyz ) + zenith_angles1.append( pza ) + positions.append(positions1) + positions_xyz.append(positions_xyz1) + zenith_angles.append( zenith_angles1 ) + + self.positions = array( positions ) + self.positions_xyz = array( positions_xyz ) + self.zenith_angles = array( zenith_angles ) + +def make_instrumentdb( gdsfilename, instrument_name, globaldb ): + instrumentdb_name = os.path.join( globaldb, os.path.splitext(os.path.basename(gdsfilename))[0] + os.path.extsep + instrument_name) + p = re.compile('(^Part\\d*.FileName\\s*=\\s*\\S*)') + gdsfile = open(gdsfilename) + instrumentdb_file = open(instrumentdb_name, 'w') + instrumentdb_file.writelines([p.sub('\\1%s%s' % (os.path.sep, instrument_name), l) for l in gdsfile.readlines()]) + gdsfile.close() + instrumentdb_file.close() + return instrumentdb_name + +def splitgds( gdsname, wd = '', id = 'part' ): + ps = lofar.parameterset.parameterset( gdsname ) + clusterdesc = ps.getString("ClusterDesc") + starttime = ps.getString("StartTime") + endtime = ps.getString("EndTime") + steptime = ps.getString("StepTime") + N = ps.getInt("NParts") + gdslist = [] + for i in range(N): + partname = os.path.join( wd, "%s-%i.gds" % (id, i) ) + ps_part = ps.makeSubset( 'Part%i.' %i, 'Part0.') + NChan = ps_part.getString("Part0.NChan") + StartFreqs = ps_part.getString("Part0.StartFreqs") + EndFreqs = ps_part.getString("Part0.EndFreqs") + + ps_part.add("Name", os.path.basename(partname)) + ps_part.add("ClusterDesc", clusterdesc) + ps_part.add("StartTime", starttime) + ps_part.add("EndTime", endtime) + ps_part.add("StepTime", steptime) + ps_part.add("NChan", NChan) + ps_part.add("StartFreqs", StartFreqs) + ps_part.add("EndFreqs", EndFreqs) + ps_part.add("NParts", "1") + ps_part.writeFile( partname ) + gdslist.append( partname ) + return gdslist + + +class ProgressBar: + + def __init__(self, length, message = ''): + self.length = length + self.current = 0 + sys.stdout.write(message + '0%') + sys.stdout.flush() + + def update(self, value): + while self.current < 2*int(50*value/self.length): + self.current += 2 + if self.current % 10 == 0 : + sys.stdout.write(str(self.current) + '%') + else: + sys.stdout.write('.') + sys.stdout.flush() + + def finished(self): + self.update(self.length) + sys.stdout.write('\n') + sys.stdout.flush() + diff --git a/CEP/Calibration/ExpIon/src/mpfit.py b/CEP/Calibration/ExpIon/src/mpfit.py new file mode 100644 index 0000000000000000000000000000000000000000..2cf1ae187968bcb45706cbe24b8b7708b4026881 --- /dev/null +++ b/CEP/Calibration/ExpIon/src/mpfit.py @@ -0,0 +1,2593 @@ +############################################################################### + +# import Python modules + +# import 3rd party modules +from numpy import * +#from pylab import * + +# import user modules +from .acalc import * + +############################################################################### + +""" +20070202 HI: This documentation is not up-to-date. + +Perform Levenberg-Marquardt least-squares minimization, based on MINPACK-1. + + AUTHORS + The original version of this software, called LMFIT, was written in FORTRAN + as part of the MINPACK-1 package by XXX. + + Craig Markwardt converted the FORTRAN code to IDL. The information for the + IDL version is: + Craig B. Markwardt, NASA/GSFC Code 662, Greenbelt, MD 20770 + craigm@lheamail.gsfc.nasa.gov + UPDATED VERSIONs can be found on my WEB PAGE: + http://cow.physics.wisc.edu/~craigm/idl/idl.html + + Mark Rivers created this Python version from Craig's IDL version. + Mark Rivers, University of Chicago + Building 434A, Argonne National Laboratory + 9700 South Cass Avenue, Argonne, IL 60439 + rivers@cars.uchicago.edu + Updated versions can be found at http://cars.uchicago.edu/software + + + DESCRIPTION + + MPFIT uses the Levenberg-Marquardt technique to solve the + least-squares problem. In its typical use, MPFIT will be used to + fit a user-supplied function (the "model") to user-supplied data + points (the "data") by adjusting a set of parameters. MPFIT is + based upon MINPACK-1 (LMDIF.F) by More' and collaborators. + + For example, a researcher may think that a set of observed data + points is best modelled with a Gaussian curve. A Gaussian curve is + parameterized by its mean, standard deviation and normalization. + MPFIT will, within certain constraints, find the set of parameters + which best fits the data. The fit is "best" in the least-squares + sense; that is, the sum of the weighted squared differences between + the model and data is minimized. + + The Levenberg-Marquardt technique is a particular strategy for + iteratively searching for the best fit. This particular + implementation is drawn from MINPACK-1 (see NETLIB), and is much faster + and more accurate than the version provided in the Scientific Python package + in Scientific.Functions.LeastSquares. + This version allows upper and lower bounding constraints to be placed on each + parameter, or the parameter can be held fixed. + + The user-supplied Python function should return an array of weighted + deviations between model and data. In a typical scientific problem + the residuals should be weighted so that each deviate has a + gaussian sigma of 1.0. If X represents values of the independent + variable, Y represents a measurement for each value of X, and ERR + represents the error in the measurements, then the deviates could + be calculated as follows: + + DEVIATES = (Y - F(X)) / ERR + + where F is the analytical function representing the model. You are + recommended to use the convenience functions MPFITFUN and + MPFITEXPR, which are driver functions that calculate the deviates + for you. If ERR are the 1-sigma uncertainties in Y, then + + TOTAL( DEVIATES^2 ) + + will be the total chi-squared value. MPFIT will minimize the + chi-square value. The values of X, Y and ERR are passed through + MPFIT to the user-supplied function via the FUNCTKW keyword. + + Simple constraints can be placed on parameter values by using the + PARINFO keyword to MPFIT. See below for a description of this + keyword. + + MPFIT does not perform more general optimization tasks. See TNMIN + instead. MPFIT is customized, based on MINPACK-1, to the + least-squares minimization problem. + + + USER FUNCTION + + The user must define a function which returns the appropriate + values as specified above. The function should return the weighted + deviations between the model and the data. It should also return a status + flag and an optional partial derivative array. For applications which + use finite-difference derivatives -- the default -- the user + function should be declared in the following way: + + def myfunct(p, fjac=None, x=None, y=None, err=None) + # Parameter values are passed in "p" + # If fjac==None then partial derivatives should not be + # computed. It will always be None if MPFIT is called with default + # flag. + model = F(x, p) + # Non-negative status value means MPFIT should continue, negative means + # stop the calculation. + status = 0 + return([status, (y-model)/err] + + See below for applications with analytical derivatives. + + The keyword parameters X, Y, and ERR in the example above are + suggestive but not required. Any parameters can be passed to + MYFUNCT by using the functkw keyword to MPFIT. Use MPFITFUN and + MPFITEXPR if you need ideas on how to do that. The function *must* + accept a parameter list, P. + + In general there are no restrictions on the number of dimensions in + X, Y or ERR. However the deviates *must* be returned in a + one-dimensional Numeric array of type Float. + + User functions may also indicate a fatal error condition using the + status return described above. If status is set to a number between + -15 and -1 then MPFIT will stop the calculation and return to the caller. + + + ANALYTIC DERIVATIVES + + In the search for the best-fit solution, MPFIT by default + calculates derivatives numerically via a finite difference + approximation. The user-supplied function need not calculate the + derivatives explicitly. However, if you desire to compute them + analytically, then the AUTODERIVATIVE=0 keyword must be passed to MPFIT. + As a practical matter, it is often sufficient and even faster to allow + MPFIT to calculate the derivatives numerically, and so + AUTODERIVATIVE=0 is not necessary. + + If AUTODERIVATIVE=0 is used then the user function must check the parameter + FJAC, and if FJAC!=None then return the partial derivative array in the + return list. + def myfunct(p, fjac=None, x=None, y=None, err=None) + # Parameter values are passed in "p" + # If FJAC!=None then partial derivatives must be computed. + # FJAC contains an array of len(p), where each entry + # is 1 if that parameter is free and 0 if it is fixed. + model = F(x, p) + Non-negative status value means MPFIT should continue, negative means + # stop the calculation. + status = 0 + if (dojac): + pderiv = Numeric.zeros([len(x), len(p)], typecode=Numeric.Float) + for j in range(len(p)): + pderiv[:,j] = FGRAD(x, p, j) + else: + pderiv = None + return([status, (y-model)/err, pderiv] + + where FGRAD(x, p, i) is a user function which must compute the + derivative of the model with respect to parameter P[i] at X. When + finite differencing is used for computing derivatives (ie, when + AUTODERIVATIVE=1), or when MPFIT needs only the errors but not the + derivatives the parameter FJAC=None. + + Derivatives should be returned in the PDERIV array. PDERIV should be an m x + n array, where m is the number of data points and n is the number + of parameters. dp[i,j] is the derivative at the ith point with + respect to the jth parameter. + + The derivatives with respect to fixed parameters are ignored; zero + is an appropriate value to insert for those derivatives. Upon + input to the user function, FJAC is set to a vector with the same + length as P, with a value of 1 for a parameter which is free, and a + value of zero for a parameter which is fixed (and hence no + derivative needs to be calculated). + + If the data is higher than one dimensional, then the *last* + dimension should be the parameter dimension. Example: fitting a + 50x50 image, "dp" should be 50x50xNPAR. + + + CONSTRAINING PARAMETER VALUES WITH THE PARINFO KEYWORD + + The behavior of MPFIT can be modified with respect to each + parameter to be fitted. A parameter value can be fixed; simple + boundary constraints can be imposed; limitations on the parameter + changes can be imposed; properties of the automatic derivative can + be modified; and parameters can be tied to one another. + + These properties are governed by the PARINFO structure, which is + passed as a keyword parameter to MPFIT. + + PARINFO should be a list of dictionaries, one list entry for each parameter. + Each parameter is associated with one element of the array, in + numerical order. The dictionary can have the following keys + (none are required, keys are case insensitive): + + 'value' - the starting parameter value (but see the START_PARAMS + parameter for more information). + + 'fixed' - a boolean value, whether the parameter is to be held + fixed or not. Fixed parameters are not varied by + MPFIT, but are passed on to MYFUNCT for evaluation. + + 'limited' - a two-element boolean array. If the first/second + element is set, then the parameter is bounded on the + lower/upper side. A parameter can be bounded on both + sides. Both LIMITED and LIMITS must be given + together. + + 'limits' - a two-element float array. Gives the + parameter limits on the lower and upper sides, + respectively. Zero, one or two of these values can be + set, depending on the values of LIMITED. Both LIMITED + and LIMITS must be given together. + + 'parname' - a string, giving the name of the parameter. The + fitting code of MPFIT does not use this tag in any + way. However, the default iterfunct will print the + parameter name if available. + + 'step' - the step size to be used in calculating the numerical + derivatives. If set to zero, then the step size is + computed automatically. Ignored when AUTODERIVATIVE=0. + + 'mpside' - the sidedness of the finite difference when computing + numerical derivatives. This field can take four + values: + + 0 - one-sided derivative computed automatically + 1 - one-sided derivative (f(x+h) - f(x) )/h + - 1 - one-sided derivative (f(x) - f(x-h))/h + 2 - two-sided derivative (f(x+h) - f(x-h))/(2*h) + + Where H is the STEP parameter described above. The + "automatic" one-sided derivative method will chose a + direction for the finite difference which does not + violate any constraints. The other methods do not + perform this check. The two-sided method is in + principle more precise, but requires twice as many + function evaluations. Default: 0. + + 'mpmaxstep' - the maximum change to be made in the parameter + value. During the fitting process, the parameter + will never be changed by more than this value in + one iteration. + + A value of 0 indicates no maximum. Default: 0. + + 'tied' - a string expression which "ties" the parameter to other + free or fixed parameters. Any expression involving + constants and the parameter array P are permitted. + Example: if parameter 2 is always to be twice parameter + 1 then use the following: parinfo(2).tied = '2 * p(1)'. + Since they are totally constrained, tied parameters are + considered to be fixed; no errors are computed for them. + [ NOTE: the PARNAME can't be used in expressions. ] + + 'mpprint' - if set to 1, then the default iterfunct will print the + parameter value. If set to 0, the parameter value + will not be printed. This tag can be used to + selectively print only a few parameter values out of + many. Default: 1 (all parameters printed) + + + Future modifications to the PARINFO structure, if any, will involve + adding dictionary tags beginning with the two letters "MP". + Therefore programmers are urged to avoid using tags starting with + the same letters; otherwise they are free to include their own + fields within the PARINFO structure, and they will be ignored. + + PARINFO Example: + parinfo = [{'value':0., 'fixed':0, 'limited':[0,0], 'limits':[0.,0.]}]*5 + parinfo[0]['fixed'] = 1 + parinfo[4]['limited'][0] = 1 + parinfo[4]['limits'][0] = 50. + values = [5.7, 2.2, 500., 1.5, 2000.] + for i in range(5): parinfo[i]['value']=values[i] + + A total of 5 parameters, with starting values of 5.7, + 2.2, 500, 1.5, and 2000 are given. The first parameter + is fixed at a value of 5.7, and the last parameter is + constrained to be above 50. + + + EXAMPLE + + import mpfit + import Numeric + x = Numeric.arange(100, Numeric.float) + p0 = [5.7, 2.2, 500., 1.5, 2000.] + y = ( p[0] + p[1]*[x] + p[2]*[x**2] + p[3]*Numeric.sqrt(x) + + p[4]*Numeric.log(x)) + fa = {'x':x, 'y':y, 'err':err} + m = mpfit('myfunct', p0, functkw=fa) + print 'status = ', m.status + if (m.status <= 0): print 'error message = ', m.errmsg + print 'parameters = ', m.params + + Minimizes sum of squares of MYFUNCT. MYFUNCT is called with the X, + Y, and ERR keyword parameters that are given by FUNCTKW. The + results can be obtained from the returned object m. + + + THEORY OF OPERATION + + There are many specific strategies for function minimization. One + very popular technique is to use function gradient information to + realize the local structure of the function. Near a local minimum + the function value can be taylor expanded about x0 as follows: + + f(x) = f(x0) + f'(x0) . (x-x0) + (1/2) (x-x0) . f''(x0) . (x-x0) + ----- --------------- ------------------------------- (1) + Order 0th 1st 2nd + + Here f'(x) is the gradient vector of f at x, and f''(x) is the + Hessian matrix of second derivatives of f at x. The vector x is + the set of function parameters, not the measured data vector. One + can find the minimum of f, f(xm) using Newton's method, and + arrives at the following linear equation: + + f''(x0) . (xm-x0) = - f'(x0) (2) + + If an inverse can be found for f''(x0) then one can solve for + (xm-x0), the step vector from the current position x0 to the new + projected minimum. Here the problem has been linearized (ie, the + gradient information is known to first order). f''(x0) is + symmetric n x n matrix, and should be positive definite. + + The Levenberg - Marquardt technique is a variation on this theme. + It adds an additional diagonal term to the equation which may aid the + convergence properties: + + (f''(x0) + nu I) . (xm-x0) = -f'(x0) (2a) + + where I is the identity matrix. When nu is large, the overall + matrix is diagonally dominant, and the iterations follow steepest + descent. When nu is small, the iterations are quadratically + convergent. + + In principle, if f''(x0) and f'(x0) are known then xm-x0 can be + determined. However the Hessian matrix is often difficult or + impossible to compute. The gradient f'(x0) may be easier to + compute, if even by finite difference techniques. So-called + quasi-Newton techniques attempt to successively estimate f''(x0) + by building up gradient information as the iterations proceed. + + In the least squares problem there are further simplifications + which assist in solving eqn (2). The function to be minimized is + a sum of squares: + + f = Sum(hi^2) (3) + + where hi is the ith residual out of m residuals as described + above. This can be substituted back into eqn (2) after computing + the derivatives: + + f' = 2 Sum(hi hi') + f'' = 2 Sum(hi' hj') + 2 Sum(hi hi'') (4) + + If one assumes that the parameters are already close enough to a + minimum, then one typically finds that the second term in f'' is + negligible [or, in any case, is too difficult to compute]. Thus, + equation (2) can be solved, at least approximately, using only + gradient information. + + In matrix notation, the combination of eqns (2) and (4) becomes: + + hT' . h' . dx = - hT' . h (5) + + Where h is the residual vector (length m), hT is its transpose, h' + is the Jacobian matrix (dimensions n x m), and dx is (xm-x0). The + user function supplies the residual vector h, and in some cases h' + when it is not found by finite differences (see MPFIT_FDJAC2, + which finds h and hT'). Even if dx is not the best absolute step + to take, it does provide a good estimate of the best *direction*, + so often a line minimization will occur along the dx vector + direction. + + The method of solution employed by MINPACK is to form the Q . R + factorization of h', where Q is an orthogonal matrix such that QT . + Q = I, and R is upper right triangular. Using h' = Q . R and the + ortogonality of Q, eqn (5) becomes + + (RT . QT) . (Q . R) . dx = - (RT . QT) . h + RT . R . dx = - RT . QT . h (6) + R . dx = - QT . h + + where the last statement follows because R is upper triangular. + Here, R, QT and h are known so this is a matter of solving for dx. + The routine MPFIT_QRFAC provides the QR factorization of h, with + pivoting, and MPFIT_QRSOLV provides the solution for dx. + + + REFERENCES + + MINPACK-1, Jorge More', available from netlib (www.netlib.org). + "Optimization Software Guide," Jorge More' and Stephen Wright, + SIAM, *Frontiers in Applied Mathematics*, Number 14. + More', Jorge J., "The Levenberg-Marquardt Algorithm: + Implementation and Theory," in *Numerical Analysis*, ed. Watson, + G. A., Lecture Notes in Mathematics 630, Springer-Verlag, 1977. + + + MODIFICATION HISTORY + + Translated from MINPACK-1 in FORTRAN, Apr-Jul 1998, CM + Copyright (C) 1997-2002, Craig Markwardt + This software is provided as is without any warranty whatsoever. + Permission to use, copy, modify, and distribute modified or + unmodified copies is granted, provided this copyright and disclaimer + are included unchanged. + + Translated from MPFIT (Craig Markwardt's IDL package) to Python, + August, 2002. Mark Rivers +""" + +############################################################################### + +# Original FORTRAN documentation +# ********** +# +# subroutine lmdif +# +# the purpose of lmdif is to minimize the sum of the squares of +# m nonlinear functions in n variables by a modification of +# the levenberg-marquardt algorithm. the user must provide a +# subroutine which calculates the functions. the jacobian is +# then calculated by a forward-difference approximation. +# +# the subroutine statement is +# +# subroutine lmdif(fcn,m,n,x,fvec,ftol,xtol,gtol,maxfev,epsfcn, +# diag,mode,factor,nprint,info,nfev,fjac, +# ldfjac,ipvt,qtf,wa1,wa2,wa3,wa4) +# +# where +# +# fcn is the name of the user-supplied subroutine which +# calculates the functions. fcn must be declared +# in an external statement in the user calling +# program, and should be written as follows. +# +# subroutine fcn(m,n,x,fvec,iflag) +# integer m,n,iflag +# double precision x(n),fvec(m) +# ---------- +# calculate the functions at x and +# return this vector in fvec. +# ---------- +# return +# end +# +# the value of iflag should not be changed by fcn unless +# the user wants to terminate execution of lmdif. +# in this case set iflag to a negative integer. +# +# m is a positive integer input variable set to the number +# of functions. +# +# n is a positive integer input variable set to the number +# of variables. n must not exceed m. +# +# x is an array of length n. on input x must contain +# an initial estimate of the solution vector. on output x +# contains the final estimate of the solution vector. +# +# fvec is an output array of length m which contains +# the functions evaluated at the output x. +# +# ftol is a nonnegative input variable. termination +# occurs when both the actual and predicted relative +# reductions in the sum of squares are at most ftol. +# therefore, ftol measures the relative error desired +# in the sum of squares. +# +# xtol is a nonnegative input variable. termination +# occurs when the relative error between two consecutive +# iterates is at most xtol. therefore, xtol measures the +# relative error desired in the approximate solution. +# +# gtol is a nonnegative input variable. termination +# occurs when the cosine of the angle between fvec and +# any column of the jacobian is at most gtol in absolute +# value. therefore, gtol measures the orthogonality +# desired between the function vector and the columns +# of the jacobian. +# +# maxfev is a positive integer input variable. termination +# occurs when the number of calls to fcn is at least +# maxfev by the end of an iteration. +# +# epsfcn is an input variable used in determining a suitable +# step length for the forward-difference approximation. this +# approximation assumes that the relative errors in the +# functions are of the order of epsfcn. if epsfcn is less +# than the machine precision, it is assumed that the relative +# errors in the functions are of the order of the machine +# precision. +# +# diag is an array of length n. if mode = 1 (see +# below), diag is internally set. if mode = 2, diag +# must contain positive entries that serve as +# multiplicative scale factors for the variables. +# +# mode is an integer input variable. if mode = 1, the +# variables will be scaled internally. if mode = 2, +# the scaling is specified by the input diag. other +# values of mode are equivalent to mode = 1. +# +# factor is a positive input variable used in determining the +# initial step bound. this bound is set to the product of +# factor and the euclidean norm of diag*x if nonzero, or else +# to factor itself. in most cases factor should lie in the +# interval (.1,100.). 100. is a generally recommended value. +# +# nprint is an integer input variable that enables controlled +# printing of iterates if it is positive. in this case, +# fcn is called with iflag = 0 at the beginning of the first +# iteration and every nprint iterations thereafter and +# immediately prior to return, with x and fvec available +# for printing. if nprint is not positive, no special calls +# of fcn with iflag = 0 are made. +# +# info is an integer output variable. if the user has +# terminated execution, info is set to the (negative) +# value of iflag. see description of fcn. otherwise, +# info is set as follows. +# +# info = 0 improper input parameters. +# +# info = 1 both actual and predicted relative reductions +# in the sum of squares are at most ftol. +# +# info = 2 relative error between two consecutive iterates +# is at most xtol. +# +# info = 3 conditions for info = 1 and info = 2 both hold. +# +# info = 4 the cosine of the angle between fvec and any +# column of the jacobian is at most gtol in +# absolute value. +# +# info = 5 number of calls to fcn has reached or +# exceeded maxfev. +# +# info = 6 ftol is too small. no further reduction in +# the sum of squares is possible. +# +# info = 7 xtol is too small. no further improvement in +# the approximate solution x is possible. +# +# info = 8 gtol is too small. fvec is orthogonal to the +# columns of the jacobian to machine precision. +# +# nfev is an integer output variable set to the number of +# calls to fcn. +# +# fjac is an output m by n array. the upper n by n submatrix +# of fjac contains an upper triangular matrix r with +# diagonal elements of nonincreasing magnitude such that +# +# t t t +# p *(jac *jac)*p = r *r, +# +# where p is a permutation matrix and jac is the final +# calculated jacobian. column j of p is column ipvt(j) +# (see below) of the identity matrix. the lower trapezoidal +# part of fjac contains information generated during +# the computation of r. +# +# ldfjac is a positive integer input variable not less than m +# which specifies the leading dimension of the array fjac. +# +# ipvt is an integer output array of length n. ipvt +# defines a permutation matrix p such that jac*p = q*r, +# where jac is the final calculated jacobian, q is +# orthogonal (not stored), and r is upper triangular +# with diagonal elements of nonincreasing magnitude. +# column j of p is column ipvt(j) of the identity matrix. +# +# qtf is an output array of length n which contains +# the first n elements of the vector (q transpose)*fvec. +# +# wa1, wa2, and wa3 are work arrays of length n. +# +# wa4 is a work array of length m. +# +# subprograms called +# +# user-supplied ...... fcn +# +# minpack-supplied ... dpmpar,enorm,fdjac2,,qrfac +# +# fortran-supplied ... dabs,dmax1,dmin1,dsqrt,mod +# +# argonne national laboratory. minpack project. march 1980. +# burton s. garbow, kenneth e. hillstrom, jorge j. more +# +# ********** + +############################################################################### + +class mpfit: + + def __init__( self, fcn, xall = None, functkw = {}, parinfo = None, + ftol = 1.e-10, xtol = 1.e-10, gtol = 1.e-10, double = True, + damp = 0., maxiter = 200, factor = 100., nprint = 1, + iterfunct = 'default', iterkw = {}, nocovar = False, + fastnorm = False, rescale = False, autoderivative = True, + quiet = False, diag = None, epsfcn = None, debug = False ): + + """ +Inputs: + fcn: + The function to be minimized. The function should return the weighted + deviations between the model and the data, as described above. + + xall: + An array of starting values for each of the parameters of the model. + The number of parameters should be fewer than the number of measurements. + + This parameter is optional if the parinfo keyword is used (but see + parinfo). The parinfo keyword provides a mechanism to fix or constrain + individual parameters. + +Keywords: + + autoderivative: + If this is set, derivatives of the function will be computed + automatically via a finite differencing procedure. If not set, then + fcn must provide the (analytical) derivatives. + Default: set (=1) + NOTE: to supply your own analytical derivatives, + explicitly pass autoderivative=0 + + fastnorm: + Set this keyword to select a faster algorithm to compute sum-of-square + values internally. For systems with large numbers of data points, the + standard algorithm can become prohibitively slow because it cannot be + vectorized well. By setting this keyword, MPFIT will run faster, but + it will be more prone to floating point overflows and underflows. Thus, setting + this keyword may sacrifice some stability in the fitting process. + Default: clear (=0) + + ftol: + A nonnegative input variable. Termination occurs when both the actual + and predicted relative reductions in the sum of squares are at most + ftol (and status is accordingly set to 1 or 3). Therefore, ftol + measures the relative error desired in the sum of squares. + Default: 1E-10 + + functkw: + A dictionary which contains the parameters to be passed to the + user-supplied function specified by fcn via the standard Python + keyword dictionary mechanism. This is the way you can pass additional + data to your user-supplied function without using global variables. + + Consider the following example: + if functkw = {'xval':[1.,2.,3.], 'yval':[1.,4.,9.], + 'errval':[1.,1.,1.] } + then the user supplied function should be declared like this: + def myfunct(p, fjac=None, xval=None, yval=None, errval=None): + + Default: {} No extra parameters are passed to the user-supplied + function. + + gtol: + A nonnegative input variable. Termination occurs when the cosine of + the angle between fvec and any column of the jacobian is at most gtol + in absolute value (and status is accordingly set to 4). Therefore, + gtol measures the orthogonality desired between the function vector + and the columns of the jacobian. + Default: 1e-10 + + iterkw: + The keyword arguments to be passed to iterfunct via the dictionary + keyword mechanism. This should be a dictionary and is similar in + operation to FUNCTKW. + Default: {} No arguments are passed. + + iterfunct: + The name of a function to be called upon each NPRINT iteration of the + MPFIT routine. It should be declared in the following way: + def iterfunct(myfunct, p, iter, fnorm, functkw=None, + parinfo=None, quiet=0, dof=None, [iterkw keywords here]) + # perform custom iteration update + + iterfunct must accept all three keyword parameters (FUNCTKW, PARINFO + and QUIET). + + myfunct: The user-supplied function to be minimized, + p: The current set of model parameters + iter: The iteration number + functkw: The arguments to be passed to myfunct. + fnorm: The chi-squared value. + quiet: Set when no textual output should be printed. + dof: The number of degrees of freedom, normally the number of points + less the number of free parameters. + See below for documentation of parinfo. + + In implementation, iterfunct can perform updates to the terminal or + graphical user interface, to provide feedback while the fit proceeds. + If the fit is to be stopped for any reason, then iterfunct should return a + a status value between - 15 and - 1. Otherwise it should return 0. + In principle, iterfunct should probably not modify the parameter values, + because it may interfere with the algorithm's stability. In practice it + is allowed. + + Default: an internal routine is used to print the parameter values. + + Set iterfunct=None if there is no user-defined routine and you don't + want the internal default routine be called. + + maxiter: + The maximum number of iterations to perform. If the number is exceeded, + then the status value is set to 5 and MPFIT returns. + Default: 200 iterations + + nocovar: + Set this keyword to prevent the calculation of the covariance matrix + before returning (see COVAR) + Default: clear (=0) The covariance matrix is returned + + nprint: + The frequency with which iterfunct is called. A value of 1 indicates + that iterfunct is called with every iteration, while 2 indicates every + other iteration, etc. Note that several Levenberg-Marquardt attempts + can be made in a single iteration. + Default value: 1 + + parinfo + Provides a mechanism for more sophisticated constraints to be placed on + parameter values. When parinfo is not passed, then it is assumed that + all parameters are free and unconstrained. Values in parinfo are never + modified during a call to MPFIT. + + See description above for the structure of PARINFO. + + Default value: None All parameters are free and unconstrained. + + quiet: + Set this keyword when no textual output should be printed by MPFIT + + damp: + A scalar number, indicating the cut-off value of residuals where + "damping" will occur. Residuals with magnitudes greater than this + number will be replaced by their hyperbolic tangent. This partially + mitigates the so-called large residual problem inherent in + least-squares solvers (as for the test problem CURVI, + http://www.maxthis.com/curviex.htm). + A value of 0 indicates no damping. + Default: 0 + + Note: DAMP doesn't work with autoderivative=0 + + xtol: + A nonnegative input variable. Termination occurs when the relative error + between two consecutive iterates is at most xtol (and status is + accordingly set to 2 or 3). Therefore, xtol measures the relative error + desired in the approximate solution. + Default: 1E-10 + + Outputs: + + Returns an object of type mpfit. The results are attributes of this class, + e.g. mpfit.status, mpfit.errmsg, mpfit.params, npfit.niter, mpfit.covar. + + .status + An integer status code is returned. All values greater than zero can + represent success (however .status == 5 may indicate failure to + converge). It can have one of the following values: + + - 16 + A parameter or function value has become infinite or an undefined + number. This is usually a consequence of numerical overflow in the + user's model function, which must be avoided. + + - 15 to - 1 + These are error codes that either MYFUNCT or iterfunct may return to + terminate the fitting process. Values from - 15 to - 1 are reserved + for the user functions and will not clash with MPFIT. + + 0 Improper input parameters. + + 1 Both actual and predicted relative reductions in the sum of squares + are at most ftol. + + 2 Relative error between two consecutive iterates is at most xtol + + 3 Conditions for status = 1 and status = 2 both hold. + + 4 The cosine of the angle between fvec and any column of the jacobian + is at most gtol in absolute value. + + 5 The maximum number of iterations has been reached. + + 6 ftol is too small. No further reduction in the sum of squares is + possible. + + 7 xtol is too small. No further improvement in the approximate solution + x is possible. + + 8 gtol is too small. fvec is orthogonal to the columns of the jacobian + to machine precision. + + .fnorm + The value of the summed squared residuals for the returned parameter + values. + + .covar + The covariance matrix for the set of parameters returned by MPFIT. + The matrix is NxN where N is the number of parameters. The square root + of the diagonal elements gives the formal 1-sigma statistical errors on + the parameters if errors were treated "properly" in fcn. + Parameter errors are also returned in .perror. + + To compute the correlation matrix, pcor, use this example: + cov = mpfit.covar + pcor = cov * 0. + for i in range(n): + for j in range(n): + pcor[i,j] = cov[i,j]/Numeric.sqrt(cov[i,i]*cov[j,j]) + + If nocovar is set or MPFIT terminated abnormally, then .covar is set to + a scalar with value None. + + .errmsg + A string error or warning message is returned. + + .nfev + The number of calls to MYFUNCT performed. + + .niter + The number of iterations completed. + + .perror + The formal 1-sigma errors in each parameter, computed from the + covariance matrix. If a parameter is held fixed, or if it touches a + boundary, then the error is reported as zero. + + If the fit is unweighted (i.e. no errors were given, or the weights + were uniformly set to unity), then .perror will probably not represent + the true parameter uncertainties. + + *If* you can assume that the true reduced chi-squared value is unity -- + meaning that the fit is implicitly assumed to be of good quality -- + then the estimated parameter uncertainties can be computed by scaling + .perror by the measured chi-squared value. + + dof = len(x) - len(mpfit.params) # deg of freedom + # scaled uncertainties + pcerror = mpfit.perror * Numeric.sqrt(mpfit.fnorm / dof) + + """ + + self.niter = 0 + self.params = None + self.covar = None + self.perror = None + self.status = 0 # Invalid input flag set while we check inputs + self.debug = debug + self.errmsg = '' + self.fastnorm = fastnorm + self.nfev = 0 + self.damp = damp + self.machar = machar( double = double ) + machep = self.machar.machep + self.diag = diag + if double: + self.dtype = float64 + else: + self.dtype = float32 + try: + import _mpfit + except: + self.__mpfit = False + else: + self.__mpfit = True + + if ( fcn == None ): + self.errmsg = "Usage: parms = mpfit('myfunt', ... )" + return + + if ( iterfunct == 'default' ): + iterfunct = self.defiter + + ## Parameter damping doesn't work when user is providing their own + ## gradients. + if ( ( self.damp != 0. ) and ( autoderivative == False ) ): + self.errmsg = 'ERROR: keywords DAMP and AUTODERIVATIVE are mutually exclusive' + return + +# 20070130 HI: TODO - iterstop and iterkeystop + + ## Parameters can either be stored in parinfo, or x. x takes precedence if it exists + if ( ( xall == None ) and ( parinfo == None ) ): + self.errmsg = 'ERROR: must pass parameters in P or PARINFO' + return + + ## Be sure that PARINFO is of the right type + if ( parinfo != None ): + if ( type( parinfo ) != type( [] ) ): + self.errmsg = 'ERROR: PARINFO must be a list of dictionaries.' + return + if ( len( parinfo ) == 0 ): + self.errmsg = 'ERROR: PARINFO must be a non-empty list of dictionaries.' + return + if ( type( parinfo[ 0 ] ) != type( {} ) ): + self.errmsg = 'ERROR: PARINFO must be a list of dictionaries.' + return + if ( ( xall != None ) and ( len( xall ) != len( parinfo ) ) ): + self.errmsg = 'ERROR: number of elements in PARINFO and P must agree' + return + + ## If the parameters were not specified at the command line, then + ## extract them from PARINFO + if ( xall == None ): + xall = self.parinfo( parinfo = parinfo, key = 'value' ) + if ( xall == None ): + self.errmsg = 'ERROR: either P or PARINFO(*)["value"] must be supplied.' + return + + ## Make sure parameters are of the specified internal precision + xall = array( xall, dtype = self.dtype ) + + npar = len( xall ) + self.fnorm = - 1. + fnorm1 = - 1. + + ## TIED parameters? + ptied = self.parinfo( parinfo = parinfo, key = 'tied', default = '', n = npar ) + self.ptied = [ ptied[ i ].strip() for i in range( npar ) ] + ptied = array( [ ( self.ptied[ i ] != '' ) for i in range( npar ) ], dtype = bool ) + self.qanytied = sometrue( ptied ) + + ## FIXED parameters ? +# pfixed = self.parinfo( parinfo = parinfo, key = 'fixed', default = False, n = npar ) +# pfixed = ( pfixed == True ) +# pfixed = ( pfixed | ptied ) ## Tied parameters are also effectively fixed + pfixed = ptied.copy() + + ## Finite differencing step, absolute and relative, and sidedness of deriv. + step = self.parinfo( parinfo = parinfo, key = 'step', default = 0., n = npar ) + dstep = self.parinfo( parinfo = parinfo, key = 'relstep', default = 0., n = npar ) + dside = self.parinfo( parinfo = parinfo, key = 'mpside', default = 0, n = npar ) + + ## Maximum and minimum steps allowed to be taken in one iteration + maxstep = self.parinfo( parinfo = parinfo, key = 'mpmaxstep', default = 0., n = npar ) + minstep = self.parinfo( parinfo = parinfo, key = 'mpminstep', default = 0., n = npar ) + qmin = zeros( shape = minstep.shape, dtype = bool ) ## Remove minstep for now!! + qmax = ( maxstep != 0. ) + if sometrue( qmin & qmax & ( maxstep < minstep ) ): + self.errmsg = 'ERROR: MPMINSTEP is greater than MPMAXSTEP' + return + qminmax = sometrue( qmin | qmax ) + +# 20070130 HI: TODO - isext + + ## LIMITED parameters ? + limits = self.parinfo( parinfo = parinfo, key = 'limits', default = [ None, None ], n = npar ) + if ( limits != None ): + + ## Error checking on limits in parinfo + for i in range( npar ): + limit = limits[ i ] # do not remove + if ( limit[ 0 ] != None ): + if ( xall[ i ] < limit[ 0 ] ): + self.errmsg = 'ERROR: parameters are not within PARINFO limits' + return + if ( limit[ 1 ] != None ): + if ( xall[ i ] > limit[ 1 ] ): + self.errmsg = 'ERROR: parameters are not within PARINFO limits' + return + if ( limit[ 0 ] != None ) and ( limit[ 1 ] != None ) and ( pfixed[ i ] == False ): + if ( limit[ 0 ] > limit[ 1 ] ): + self.errmsg = 'ERROR: parameters are not within PARINFO limits' + return + if ( limit[ 0 ] == limit[ 1 ] ): + pfixed[ i ] = True + + ## Update the free parameters + ifree = awhere( pfixed == False ) + nfree = len( ifree ) + if ( nfree == 0 ): + self.errmsg = 'ERROR: no free parameters' + return + + ## Compose only VARYING parameters + self.params = xall ## self.params is the set of parameters to be returned + x = aget( self.params, ifree ) ## x is the set of free parameters + + if ( limits != None ): + + ## Transfer structure values to local variables + qulim = zeros( shape = x.shape, dtype = bool ) + ulim = zeros( shape = x.shape, dtype = self.dtype ) + qllim = zeros( shape = x.shape, dtype = bool ) + llim = zeros( shape = x.shape, dtype = self.dtype ) + for i in range( nfree ): + limit = limits[ ifree[ i, 0 ] ] # do not remove + if ( limit[ 1 ] != None ): + qulim[ i ] = True + ulim[ i ] = limit[ 1 ] + if ( limit[ 0 ] != None ): + qllim[ i ] = True + llim[ i ] = limit[ 0 ] + qanylim = sometrue( qulim | qllim ) + + else: + + ## Fill in local variables with dummy values + qulim = zeros( shape = x.shape, dtype = bool ) + ulim = zeros( shape = x.shape, dtype = self.dtype ) + qllim = zeros( shape = x.shape, dtype = bool ) + llim = zeros( shape = x.shape, dtype = self.dtype ) + qanylim = False + + ## CYCLIC parameters ? + pcyclic = self.parinfo( parinfo = parinfo, key = 'cyclic', default = False, n = npar ) + qcyclic = aget( pcyclic, ifree ) + if sometrue( qcyclic & ( ( qulim == False ) | ( qllim == False ) ) ): + self.errmsg = 'ERROR: PARINFO cyclic parameter needs both upper and lower limits set' + return + + wh = awhere( qcyclic ) + ncyclic = len( wh ) + qanycyclic = ( ncyclic > 0 ) + + ## Initialize the number of parameters pegged at a hard limit value + wh = awhere( ( ( qulim & ( x == ulim ) ) | ( qllim & ( x == llim ) ) ) & + ( qcyclic == False ) ) + npegged = len( wh ) + + n = len( x ) + + ## Check input parameters for errors + if ( ( n < 0 ) or ( ftol <= 0. ) or ( xtol <= 0. ) or ( gtol <= 0. ) + or ( maxiter <= 0 ) or ( factor <= 0. ) ): + self.errmsg = 'ERROR: input keywords are inconsistent' + return + + if rescale: + self.errmsg = 'ERROR: DIAG parameter scales are inconsistent' + if ( diag == None ): + return + if ( len( diag ) < n ): + return + self.diag = array( diag, dtype = self.dtype ) + if sometrue( self.diag <= 0. ): + return + self.errmsg = '' + + # Make sure x is of the specified internal type + x = array( x, dtype = self.dtype ) + +# 20070130 HI: TODO - external iteration + + self.status, fvec, dummy = self.call( fcn, self.params, functkw ) + if ( self.status < 0 ): + self.errmsg = 'ERROR: first call to "' + str( fcn ) + '" failed' + return + +# 20070130 HI: TODO - data type check + + m = len( fvec ) + if ( m < n ): + self.errmsg = 'ERROR: number of parameters must not exceed data' + return + + self.fnorm = self.enorm( fvec ) + + ## Initialize Levelberg-Marquardt parameter and iteration counter + + par = 0. + self.niter = 1 + qtf = zeros( shape = x.shape, dtype = self.dtype ) + self.status = 0 + + + ## Beginning of the outer loop + + while True: + + ## If requested, call fcn to enable printing of iterates + self.params = aput( self.params, ifree, x ) + if ( self.qanytied ): + self.params = self.tie( self.params, ptied = ptied ) + dof = max( [ len( fvec ) - nfree, 1 ] ) + + if ( nprint > 0 ) and ( iterfunct != None ): + if ( amodulo( ( self.niter - 1 ), nprint ) == 0 ): + xnew0 = self.params.copy() + + status = iterfunct( fcn, self.params, self.niter, self.fnorm**2, + functkw = functkw, parinfo = parinfo, quiet = quiet, + dof = dof, **iterkw) + if ( status != None ): + self.status = status + + ## Check for user termination + if ( self.status < 0 ): + self.errmsg = 'WARNING: premature termination by ' + str( iterfunct ) + return + + ## If parameters were changed (grrr..) then re-tie + if ( ( abs( self.params - xnew0 ) ).sum() > 0. ): + if self.qanytied: + self.params = self.tie( self.params, ptied = ptied ) + x = aget( self.params, ifree ) + + + ## Calculate the jacobian matrix + self.status = 2 + catch_msg = 'calling MPFIT_FDJAC2' + fjac = self.fdjac2( fcn, x, fvec, step = step, ulimited = qulim, + ulimit = ulim, dside = dside, epsfcn = epsfcn, + autoderiv = autoderivative, functkw = functkw, + xall = self.params, ifree = ifree, dstep = dstep ) + if ( fjac == None ): + self.errmsg = 'WARNING: premature termination by FDJAC2' + return + +# 20070130 HI: TODO - put in external option + + ## Determine if any of the parameters are pegged at the limits + npegged = 0 + if qanylim: + catch_msg = 'zeroing derivatives of pegged parameters' + whlpeg = awhere( qllim & ( x == llim ) & ( qcyclic == False ) ) + nlpeg = len( whlpeg ) + whupeg = awhere( qulim & ( x == ulim ) & ( qcyclic == False ) ) + nupeg = len( whupeg ) + npegged = nlpeg + nupeg + + ## See if any "pegged" values should keep their derivatives + if ( nlpeg > 0 ): + ## Total derivative of sum wrt lower pegged parameters + for i in range( nlpeg ): + sum = ( fvec * fjac[ : , whlpeg [ i, 0 ] ] ).sum() + if ( sum > 0 ): + fjac[ : , whlpeg [ i, 0 ] ] = 0 + if ( nupeg > 0 ): + ## Total derivative of sum wrt upper pegged parameters + for i in range( nupeg ): + sum = ( fvec * fjac[ : , whupeg[ i, 0 ] ] ).sum() + if ( sum < 0 ): + fjac[ : , whupeg[ i, 0 ] ] = 0 + + ## Compute the QR factorization of the jacobian + fjac, ipvt, wa1, wa2 = self.qrfac( fjac, pivot = True ) + + ## On the first iteration if "diag" is unspecified, scale + ## according to the norms of the columns of the initial jacobian + catch_msg = 'rescaling diagonal elements' + if ( self.niter == 1 ): + + if ( not rescale ) or ( len( self.diag ) < n ): + self.diag = wa2.copy() + wh = awhere( self.diag == 0. ) + self.diag = aput( self.diag, wh, 1. ) + + ## On the first iteration, calculate the norm of the scaled x + ## and initialize the step bound delta + wa3 = self.diag * x + xnorm = self.enorm( wa3 ) + delta = factor * xnorm + if ( delta == 0. ): + delta = factor + + ## Form (q transpose)*fvec and store the first n components in qtf + catch_msg = 'forming (q transpose)*fvec' + wa4 = fvec.copy() +# mpvt = ( transpose( repeat( [ ipvt ], n ) ) == repeat( [ range( n ) ], n ) ) +# fjac * mpvt != 0. + for j in range( n ): + lj = ipvt[ j, 0 ] + temp3 = fjac[ j, lj ] + if ( temp3 != 0. ): + fj = fjac[ j : , lj ] + wj = wa4[ j : ] + ## *** optimization wa4(j:*) + wa4[ j : ] = wj - fj * ( fj * wj ).sum() / temp3 + fjac[ j, lj ] = wa1[ j ] + qtf[ j ] = wa4[ j ] + ## From this point on, only the square matrix, consisting of the + ## triangle of R, is needed. + fjac = fjac[ 0 : n, 0 : n ] + fjac = reshape( fjac, ( n, n ) ) + temp = fjac.copy() + for i in range( n ): + temp[ : , i ] = fjac[ : , ipvt[ i, 0 ] ] + fjac = temp.copy() +# dummy = ( repeat( [ ipvt[ 0 : n ] ], n ) == transpose( repeat( [ range( n ) ], n ) ) +# fjac = matrixmultiply( fjac, dummy ) + + ## Check for overflow. This should be a cheap test here since FJAC + ## has been reduced to a (small) square matrix, and the test is + ## O(N^2). + #wh = where(finite(fjac) EQ 0, ct) + #if ct GT 0 then goto, FAIL_OVERFLOW + +# 20070130 HI: TODO - Find out if overflow check is necessary + + ## Compute the norm of the scaled gradient + catch_msg = 'computing the scaled gradient' + gnorm = 0. + if ( self.fnorm != 0. ): + for j in range( n ): + l = ipvt[ j, 0 ] + if ( wa2[ l ] != 0. ): + sum = ( fjac[ 0 : j + 1, j ] * qtf[ 0 : j + 1 ] ).sum() / self.fnorm + gnorm = max( [ gnorm, abs( sum / wa2[ l ] ) ] ) + + ## Test for convergence of the gradient norm + if ( gnorm <= gtol ): + print('gnorm = ', gnorm) + self.status = 4 + return + + if ( maxiter == 0 ): + return + + ## Rescale if necessary + if ( not rescale ): + wh = awhere( self.diag < wa2 ) + self.diag = aput( self.diag, wh, aget( wa2, wh ) ) + + ## Beginning of the inner loop + while True: + + ## Determine the levenberg-marquardt parameter + catch_msg = 'calculating LM parameter (MPFIT_)' + fjac, par, wa1, wa2 = self.lmpar( fjac, ipvt, self.diag, qtf, + wa1, wa2, delta, par ) + + ## Store the direction p and x+p. Calculate the norm of p + wa1 = -wa1 + + if ( not qanylim ) and ( not qminmax ) and ( not qanycyclic ): + ## No parameter limits, so just move to new position WA2 + alpha = 1. + wa2 = x + wa1 + + else: + + ## Respect the limits. If a step were to go out of bounds, then + ## we should take a step in the same direction but shorter distance. + ## The step should take us right to the limit in that case. + alpha = 1. + + if qanylim: + ## Do not allow any steps out of bounds + catch_msg = 'checking for a step out of bounds' + if ( nlpeg > 0 ): + temp = aget( wa1, whlpeg ) + wa1 = aput( wa1, whlpeg, aput( temp, awhere( temp < 0. ), 0. ) ) + if ( nupeg > 0 ): + temp = aget( wa1, whupeg ) + wa1 = aput( wa1, whupeg, aput( temp, awhere( temp > 0. ), 0. ) ) + + dwa1 = ( abs( wa1 ) > machep ) + whl = awhere( dwa1 & qllim & ( ( x + wa1 ) < llim ) ) + if ( len( whl ) > 0 ): + t = ( ( aget( llim, whl ) - aget( x, whl ) ) / + aget( wa1, whl ) ) + alpha = min( [ alpha, t.min() ] ) + whu = awhere( dwa1 & qulim & ( ( x + wa1 ) > ulim ) ) + if ( len( whu ) > 0 ): + t = ( ( aget( ulim, whu ) - aget( x, whu ) ) / + aget( wa1, whu ) ) + alpha = min( [ alpha, t.min() ] ) + + ## Obey any max step values. + if qminmax: + nwa1 = wa1 * alpha + +# 20070107 HI: Bug fix + qmax2 = aget( qmax, ifree ) + maxstep2 = aget( maxstep, ifree ) + + whmax = awhere( qmax2 & ( maxstep2 > 0. ) ) + if ( len( whmax ) > 0 ): + mrat = ( abs( aget( nwa1, whmax ) ) / + abs( aget( maxstep2, whmax ) ) ).max() + if ( mrat > 1. ): + alpha = alpha / mrat + + ## Scale the resulting vector + wa1 = wa1 * alpha + wa2 = x + wa1 + + ## Cycle around any cyclic parameter + if qanycyclic: + wh = awhere( qcyclic & qulim & ( wa2 > ulim ) ) + if ( len( wh ) > 0 ): + wa2 = aput( wa2, wh, aget( amodulo( wa2 - ulim, ulim - llim ) + llim, wh ) ) + wh = awhere( qcyclic & qulim & ( wa2 < llim ) ) + if ( len( wh ) > 0 ): + wa2 = aput( wa2, wh, aget( amodulo( wa2 - llim, ulim - llim ) + llim, wh ) ) + + ## Adjust the final output values. If the step put us exactly + ## on a boundary, make sure it is exact. + sgnu = array( ( ulim >= 0. ) * 2, dtype = self.dtype ) - 1. + sgnl = array( ( llim >= 0. ) * 2, dtype = self.dtype ) - 1. + wh = awhere( ( qcyclic == False ) & qulim & ( wa2 >= ulim * ( - sgnu * machep + 1. ) ) ) + if ( len( wh ) > 0 ): + wa2 = aput( wa2, wh, aget( ulim, wh ) ) + wh = awhere( ( qcyclic == False ) & qllim & ( wa2 <= llim * ( - sgnl * machep + 1. ) ) ) + if ( len( wh ) > 0 ): + wa2 = aput( wa2, wh, aget( llim, wh ) ) + + wa3 = self.diag * wa1 + pnorm = self.enorm( wa3 ) + + ## On the first iteration, adjust the initial step bound + if ( self.niter == 1 ): + delta = min( [ delta, pnorm ] ) + + self.params = aput( self.params, ifree, wa2 ) + +# 20070131 HI: TODO - external + + ## Evaluate the function at x+p and calculate its norm + catch_msg = 'calling ' + str( fcn ) + self.status, wa4, dummy = self.call( fcn, self.params, functkw ) + if ( self.status < 0 ): + self.errmsg = 'WARNING: premature termination by "' + fcn + '"' + return + fnorm1 = self.enorm( wa4 ) + + ## Compute the scaled actual reduction + catch_msg = 'computing convergence criteria' + actred = - 1. + if ( ( 0.1 * fnorm1 ) < self.fnorm ): + actred = - ( fnorm1 / self.fnorm )**2 + 1. + + ## Compute the scaled predicted reduction and the scaled directional + ## derivative + for j in range( n ): + wa3[ j ] = 0. + wa3[ 0 : j + 1 ] = wa3[ 0 : j + 1 ] + fjac[ 0 : j + 1, j ] * wa1[ ipvt[ j, 0 ] ] + + ## Remember, alpha is the fraction of the full LM step actually + ## taken + temp1 = self.enorm( alpha * wa3 ) / self.fnorm + temp2 = ( sqrt( alpha * par ) * pnorm ) / self.fnorm + prered = temp1**2 + ( temp2**2 / 0.5 ) + dirder = - ( temp1**2 + temp2**2 ) + + ## Compute the ratio of the actual to the predicted reduction. + ratio = 0. + if ( prered != 0. ): + ratio = actred / prered + + ## Update the step bound + if ( ratio <= 0.25 ): + if ( actred >= 0. ): + temp = 0.5 + else: + temp = ( 0.5 * dirder ) / ( dirder + 0.5 * actred ) + if ( ( 0.1 * fnorm1 ) >= self.fnorm ) or ( temp < 0.1 ): + temp = 0.1 + delta = temp * min( [ delta, pnorm / 0.1 ] ) + par = par / temp + else: + if ( par == 0. ) or ( ratio >= 0.75 ): + delta = pnorm / 0.5 + par = 0.5 * par + + ## Test for successful iteration + if ( ratio >= 0.0001 ): + ## Successful iteration. Update x, fvec, and their norms + x = wa2 + wa2 = self.diag * x + + fvec = wa4 + xnorm = self.enorm( wa2 ) + self.fnorm = fnorm1 + self.niter = self.niter + 1 + + ## Tests for convergence + if ( ( abs( actred ) <= ftol ) and ( prered <= ftol ) + and ( 0.5 * ratio <= 1. ) ): + self.status = 1 + if ( delta <= xtol * xnorm ): + self.status = 2 + if ( ( abs( actred ) <= ftol ) and ( prered <= ftol ) + and ( 0.5 * ratio <= 1. ) and ( self.status == 2 ) ): + self.status = 3 + if ( self.status != 0 ): + break + + ## Tests for termination and stringent tolerances + if ( self.niter >= maxiter ): + self.status = 5 + if ( ( abs( actred ) <= machep ) and ( prered <= machep ) + and ( 0.5 * ratio <= 1. ) ): + self.status = 6 + if ( delta <= machep * xnorm ): + self.status = 7 + if ( gnorm <= machep ): + self.status = 8 + if ( self.status != 0 ): + break + + ## End of inner loop. Repeat if iteration unsuccessful + if ( ratio >= 0.0001 ): + break + +# 20070131 HI: TODO - overflow checks?? + + ## Check for over/underflow - SKIP FOR NOW + ##wh = where(finite(wa1) EQ 0 OR finite(wa2) EQ 0 OR finite(x) EQ 0, ct) + ##if ct GT 0 OR finite(ratio) EQ 0 then begin + ## errmsg = ('ERROR: parameter or function value(s) have become '+$ + ## 'infinite# check model function for over- '+$ + ## 'and underflow') + ## self.status = - 16 + ## break + if ( self.status != 0 ): + break; + ## End of outer loop. + + catch_msg = 'in the termination phase' + ## Termination, either normal or user imposed. + if ( len( self.params ) == 0 ): + return + if ( nfree == 0 ): + self.params = xall.copy() + else: + self.params = aput( self.params, ifree, x ) + dof = len( fvec ) - nfree + + + ## Call the ITERPROC at the end of the fit, if the fit status is + ## okay. Don't call it if the fit failed for some reason. + if ( self.status > 0 ): + + xnew0 = self.params.copy() + + status = iterfunct( fcn, self.params, self.niter, self.fnorm**2, + functkw = functkw, parinfo = parinfo, quiet = quiet, + dof = dof, **iterkw ) + if ( status != None ): + self.status = status + + if ( self.status < 0 ): + self.errmsg = 'WARNING: premature termination by ' + str( iterfunct ) + return + ## If parameters were changed (grrr..) then re-tie + if ( ( abs( self.params - xnew0 ) ).sum() > 0. ): + if self.qanytied: + self.params = self.tie( self.params, ptied = ptied ) + x = aget( self.params, ifree ) + + ## Initialize the number of parameters pegged at a hard limit value + npegged = 0 + if qanylim: + wh = awhere( ( qulim & ( x == ulim ) ) | + ( qllim & ( x == llim ) ) ) + npegged = len( wh ) + +# 20070131 HI: TODO - Add external to check + + if ( nprint > 0 ) and ( self.status > 0 ): + catch_msg = 'calling ' + str( fcn ) + status, fvec, dummy = self.call( fcn, self.params, functkw ) + catch_msg = 'in the termination phase' + self.fnorm = self.enorm( fvec ) + + if ( self.fnorm != None ) and ( fnorm1 != None ): + self.fnorm = ( max( [ self.fnorm, fnorm1 ] ) )**2 + + self.covar = None + self.perror = None + ## (very carefully) set the covariance matrix COVAR + if ( ( self.status > 0 ) and ( not nocovar ) and ( n != None ) + and ( fjac != None ) and ( ipvt != None ) ): + sz = fjac.shape + if ( n > 0 ) and ( len( sz ) > 1 ): + if ( sz[ 0 ] >= n ) and ( sz[ 1 ] >= n ) and ( len( ipvt ) >= n ): + catch_msg = 'computing the covariance matrix' + cv = self.calc_covar( fjac[ 0 : n, 0 : n ], ipvt[ 0 : n, 0 ] ) + cv = reshape( cv, ( n, n ) ) + nn = len( xall ) + + ## Fill in actual covariance matrix, accounting for fixed + ## parameters. + self.covar = zeros( shape = ( nn, nn ), dtype = self.dtype ) + for i in range( n ): +# indices = ifree + ifree[ i ] * nn +# print self.covar.shape, ifree, indices, cv + + self.covar[ : , ifree[ i, 0 ] ] = aput( self.covar[ : , ifree[ i, 0 ] ], ifree, cv[ : , i ] ) + + ## Compute errors in parameters + catch_msg = 'computing parameter errors' + self.perror = zeros( shape = ( nn ), dtype = Float64 ) + d = self.covar.diagonal() + wh = awhere( d >= 0. ) + if ( len( wh ) > 0 ): + self.perror = aput( self.perror, wh, sqrt( aget( d, wh ) ) ) + + return + +############################################################################### + + ## Default procedure to be called every iteration. It simply prints + ## the parameter values. + def defiter( self, fcn, x, iter, fnorm = None, functkw = None, + quiet = False, iterstop = None, parinfo = None, + format = None, pformat = '%.10g', dof = 1 ): + + if self.debug: + print('Entering defiter...') + + if not quiet: + + if ( fnorm == None ): + status, fvec, dummy = self.call( fcn, x, functkw ) + fnorm = ( self.enorm( fvec ) )**2 + + ## Determine which parameters to print + nprint = len( x ) + + print("Iter %6i CHI-SQUARE = %.10g DOF = %i" % ( iter, fnorm, dof )) + for i in range( nprint ): + if ( parinfo != None ): + if ( 'parname' in parinfo[ i ] ): + p = ' ' + parinfo[i]['parname'] + ' = ' + else: + p = ' P' + str( i ) + ' = ' + if ( 'mpprint' in parinfo[ i ] ): + iprint = parinfo[ i ][ 'mpprint' ] + else: + iprint = 1 + else: + p = ' P' + str( i ) + ' = ' + iprint = 1 + if ( iprint > 0 ): + print(p + ( pformat % x[ i ] ) + ' ') + + return 0 + +############################################################################### + + ## DO_ITERSTOP: + ## if keyword_set(iterstop) then begin + ## k = get_kbrd(0) + ## if k EQ string(byte(7)) then begin + ## message, 'WARNING: minimization not complete', /info + ## print, 'Do you want to terminate this procedure? (y/n)', $ + ## format='(A,$)' + ## k = '' + ## read, k + ## if strupcase(strmid(k,0,1)) EQ 'Y' then begin + ## message, 'WARNING: Procedure is terminating.', /info + ## mperr = - 1 + ## endif + ## endif + ## endif + +############################################################################### + + ## Procedure to parse the parameter values in PARINFO, which is a list of dictionaries + def parinfo( self, parinfo = None, key = 'a', default = None, n = 0 ): + + if self.debug: + print('Entering parinfo...') + + if ( ( n == 0 ) and ( parinfo != None ) ): + n = len( parinfo ) + + if ( n == 0 ): + values = default + return values + + values = [] + for i in range( n ): + if ( parinfo != None ): + if ( key in parinfo[ i ] ): + values.append( parinfo[ i ][ key ] ) + else: + values.append( default ) + else: + values.append( default ) + + # Convert to numpy arrays if possible + test = default + if ( type( default ) == type( [] ) ): + test = default[ 0 ] + if ( type( test ) == type( True ) ): + values = array( values, dtype = bool ) + elif ( type( test ) == type( 1 ) ): + values = array( values, dtype = int32 ) + elif ( type( test ) == type( 1. ) ): + values = array( values, dtype = self.dtype ) + + return values + +############################################################################### + + ## Call user function or procedure, with _EXTRA or not, with + ## derivatives or not. + def call( self, fcn, x, functkw, dojac = None ): + + if self.debug: + print('Entering call...') + + if self.qanytied: + x = self.tie( x, ptied = self.ptied ) + + self.nfev = self.nfev + 1 + + status, f, temp = fcn( x, dojac = dojac, **functkw ) + + if ( dojac == None ): + fjac = None + if ( self.damp > 0. ): + ## Apply the damping if requested. This replaces the residuals + ## with their hyperbolic tangent. Thus residuals larger than + ## DAMP are essentially clipped. + f = tanh( f / self.damp ) + else: + if sometrue( dojac ): + fjac = array( temp, dtype = self.dtype ) + if ( fjac.shape != ( len( f ), len( x ) ) ): + status = - 1 + else: + fjac = array( [[]], dtype = self.dtype ) + + return status, f, fjac + +############################################################################### + + def enorm( self, vec ): + + if self.debug: + print('Entering enorm...') + + if self.__mpfit: + import _mpfit + ans = _mpfit.enorm( vec.tolist(), self.machar.rgiant, self.machar.rdwarf ) + return ans + + + ## NOTE: it turns out that, for systems that have a lot of data + ## points, this routine is a big computing bottleneck. The extended + ## computations that need to be done cannot be effectively + ## vectorized. The introduction of the FASTNORM configuration + ## parameter allows the user to select a faster routine, which is + ## based on TOTAL() alone. + + # Very simple-minded sum-of-squares + if self.fastnorm: + ans = sqrt( ( vec**2 ).sum() ) + else: + agiant = self.machar.rgiant / len( vec ) + adwarf = self.machar.rdwarf * len( vec ) + + ## This is hopefully a compromise between speed and robustness. + ## Need to do this because of the possibility of over- or underflow. + mx = vec.max() + mn = vec.min() + mx = max( [ abs( mx ), abs( mn ) ] ) + if ( mx == 0. ): + return 0. + + if ( mx > agiant ) or ( mx < adwarf ): + ans = mx * sqrt( ( vec / mx )**2 ) + else: + ans = sqrt( ( vec**2).sum() ) + + return ans + +############################################################################### + + def fdjac2( self, fcn, x, fvec, step = None, ulimited = None, + ulimit = None, dside = None, + epsfcn = None, autoderiv = True, + functkw = None, xall = None, ifree = None, dstep = None ): + + if self.debug: + print('Entering fdjac2...') + + machep = self.machar.machep + + if ( epsfcn == None ): + epsfcn = machep + if ( xall == None ): + xall = x + if ( ifree == None ): + ifree = arange( len( xall ), dtype = int32 ) + if ( step == None ): + step = azeros( x ) + nall = len( xall ) + + eps = sqrt( max( [ epsfcn, machep ] ) ) + m = len( fvec ) + n = len( x ) + + ## Compute analytical derivative if requested + if ( not autoderiv ): + dojac = zeros( shape = ( nall ), dtype = bool ) + dojac = aput( dojac, ifree, 1 ) ## Specify which parameters need derivatives + status, fp, fjac = self.call( fcn, xall, functkw, dojac = dojac ) + + if ( ( status < 0 ) or ( len( fjac.getflat() ) != m * nall ) ): + print('ERROR: Derivative matrix was not computed properly.') + return None + + ## This definition is consistent with CURVEFIT + ## Sign error found (thanks Jesus Fernandez <fernande@irm.chu-caen.fr>) + fjac = reshape( - fjac, ( m, nall ) ) + + ## Select only the free parameters + if ( len( ifree ) < nall ): + temp = array( shape = ( m, n ), dtype = self.dtype ) + for i in range( n ): + temp[ : , i ] = fjac[ : , ifree[ i, 0 ] ] + fjac = temp.copy() + + return fjac + + fjac = zeros( shape = ( m, n ), dtype = self.dtype ) + + h = eps * abs( x ) + + ## if STEP is given, use that + if ( step != None ): + stepi = aget( step, ifree ) + wh = awhere( stepi > 0. ) + if ( len( wh ) > 0 ): + h = aput( h, wh, aget( stepi, wh ) ) + + ## if relative step is given, use that + if ( len( dstep ) > 0 ): + dstepi = aget( dstep, ifree ) + wh = awhere( dstepi > 0. ) + if ( len( wh ) > 0 ): + h = aput( h, wh, aget( abs( dstepi * x ), wh ) ) + + ## In case any of the step values are zero + wh = awhere( h == 0. ) + if ( len( wh ) > 0 ): + h = aput( h, wh, eps ) + + ## Reverse the sign of the step if we are up against the parameter + ## limit, or if the user requested it. + +# 20070201 HI: Bug fix + dside2 = aget( dside, ifree ) +# mask = ( dside == - 1 ) + mask = ( dside2 == - 1 ) + + if ( len( ulimited ) > 0 ) and ( len( ulimit ) > 0 ): + wh = awhere( mask | ( ulimited & ( x > ( ulimit - h ) ) ) ) +# h = h * ( - mask * 2. + 1. ) + if ( len( wh ) > 0 ): + h = aput( h, wh, aget( - h, wh ) ) + + ## Loop through parameters, computing the derivative for each + for j in range( n ): + xp = xall.copy() + xp[ ifree[ j, 0 ] ] = xp[ ifree[ j, 0 ] ] + h[ j ] + + status, fp, dummy = self.call( fcn, xp, functkw ) + if ( status < 0 ): + return None + +# if ( abs( dside[ j ] ) <= 1 ): + if ( abs( dside2[ j ] ) <= 1 ): + ## COMPUTE THE ONE-SIDED DERIVATIVE + ## Note optimization fjac(0:*,j) + fjac[ 0 : , j ] = ( fp - fvec ) / h[ j ] +# fjac[ 0, j ] = ( fp - fvec ) / h[ j ] + + else: + ## COMPUTE THE TWO-SIDED DERIVATIVE + xp[ ifree[ j, 0 ] ] = xall[ ifree[ j, 0 ] ] - h[ j ] + + status, fm, dummy = self.call( fcn, xp, functkw ) + if ( status < 0 ): + return None + + ## Note optimization fjac(0:*,j) + fjac[ 0 : , j ] = ( fp - fm ) / ( 2 * h[ j ] ) +# fjac[ 0, j ] = ( fp - fm ) / ( 2 * h[ j ] ) + + return fjac + +############################################################################### + + # Original FORTRAN documentation + # ********** + # + # subroutine qrfac + # + # this subroutine uses householder transformations with column + # pivoting (optional) to compute a qr factorization of the + # m by n matrix a. that is, qrfac determines an orthogonal + # matrix q, a permutation matrix p, and an upper trapezoidal + # matrix r with diagonal elements of nonincreasing magnitude, + # such that a*p = q*r. the householder transformation for + # column k, k = 1,2,...,min(m,n), is of the form + # + # t + # i - (1/u(k))*u*u + # + # where u has zeros in the first k - 1 positions. the form of + # this transformation and the method of pivoting first + # appeared in the corresponding linpack subroutine. + # + # the subroutine statement is + # + # subroutine qrfac(m,n,a,lda,pivot,ipvt,lipvt,rdiag,acnorm,wa) + # + # where + # + # m is a positive integer input variable set to the number + # of rows of a. + # + # n is a positive integer input variable set to the number + # of columns of a. + # + # a is an m by n array. on input a contains the matrix for + # which the qr factorization is to be computed. on output + # the strict upper trapezoidal part of a contains the strict + # upper trapezoidal part of r, and the lower trapezoidal + # part of a contains a factored form of q (the non-trivial + # elements of the u vectors described above). + # + # lda is a positive integer input variable not less than m + # which specifies the leading dimension of the array a. + # + # pivot is a logical input variable. if pivot is set true, + # then column pivoting is enforced. if pivot is set false, + # then no column pivoting is done. + # + # ipvt is an integer output array of length lipvt. ipvt + # defines the permutation matrix p such that a*p = q*r. + # column j of p is column ipvt(j) of the identity matrix. + # if pivot is false, ipvt is not referenced. + # + # lipvt is a positive integer input variable. if pivot is false, + # then lipvt may be as small as 1. if pivot is true, then + # lipvt must be at least n. + # + # rdiag is an output array of length n which contains the + # diagonal elements of r. + # + # acnorm is an output array of length n which contains the + # norms of the corresponding columns of the input matrix a. + # if this information is not needed, then acnorm can coincide + # with rdiag. + # + # wa is a work array of length n. if pivot is false, then wa + # can coincide with rdiag. + # + # subprograms called + # + # minpack-supplied ... dpmpar,enorm + # + # fortran-supplied ... dmax1,dsqrt,min0 + # + # argonne national laboratory. minpack project. march 1980. + # burton s. garbow, kenneth e. hillstrom, jorge j. more + # + # ********** + + # NOTE: in IDL the factors appear slightly differently than described + # above. The matrix A is still m x n where m >= n. + # + # The "upper" triangular matrix R is actually stored in the strict + # lower left triangle of A under the standard notation of IDL. + # + # The reflectors that generate Q are in the upper trapezoid of A upon + # output. + # + # EXAMPLE: decompose the matrix [[9.,2.,6.],[4.,8.,7.]] + # aa = [[9.,2.,6.],[4.,8.,7.]] + # mpfit_qrfac, aa, aapvt, rdiag, aanorm + # IDL> print, aa + # 1.81818* 0.181818* 0.545455* + # -8.54545+ 1.90160* 0.432573* + # IDL> print, rdiag + # -11.0000+ -7.48166+ + # + # The components marked with a * are the components of the + # reflectors, and those marked with a + are components of R. + # + # To reconstruct Q and R we proceed as follows. First R. + # r = fltarr(m, n) + # for i = 0, n - 1 do r(0:i,i) = aa(0:i,i) # fill in lower diag + # r(lindgen(n)*(m+1)) = rdiag + # + # Next, Q, which are composed from the reflectors. Each reflector v + # is taken from the upper trapezoid of aa, and converted to a matrix + # via (I - 2 vT . v / (v . vT)). + # + # hh = ident ## identity matrix + # for i = 0, n-1 do begin + # v = aa(*,i) & if i GT 0 then v(0:i-1) = 0 ## extract reflector + # hh = hh ## (ident - 2*(v # v)/total(v * v)) ## generate matrix + # endfor + # + # Test the result: + # IDL> print, hh ## transpose(r) + # 9.00000 4.00000 + # 2.00000 8.00000 + # 6.00000 7.00000 + # + # Note that it is usually never necessary to form the Q matrix + # explicitly, and MPFIT does not. + +############################################################################### + + def qrfac( self, a, pivot = False ): + + if self.debug: + print('Entering qrfac...') + + if self.__mpfit: + import _mpfit + result = _mpfit.qrfac( a.tolist(), pivot, self.machar.machep, self.machar.rgiant, self.machar.rdwarf ) + result = array( result ) + m = result.shape[ 0 ] - 3 + n = result.shape[ 1 ] + a = result[ 0 : m, : ] + ipvt = array( result[ m, : ] + 0.5, dtype = Int ) # only works cause ipvt >= 0 + rdiag = result[ m + 1, : ] + acnorm = result[ m + 2, : ] + return a, ipvt.reshape( [ len( ipvt ), 1 ] ), rdiag, acnorm + + machep = self.machar.machep + sz = a.shape + m = sz[ 0 ] + n = sz[ 1 ] + + ## Compute the initial column norms and initialize arrays + acnorm = zeros( shape = ( n ), dtype = self.dtype ) + for j in range( n ): + acnorm[ j ] = self.enorm( a[ : , j ] ) + rdiag = acnorm.copy() + wa = rdiag.copy() + ipvt = arange( n ).reshape( [ n, 1 ] ) + + ## Reduce a to r with householder transformations + minmn = min( [ m, n ] ) + for j in range( minmn ): + if pivot: + + ## Bring the column of largest norm into the pivot position + rmax = ( rdiag[ j : ] ).max() + kmax = awhere( rdiag[ j : ] == rmax ) + j + if ( len( kmax ) > 0 ): + kmax = kmax[ 0, 0 ] + + ## Exchange rows via the pivot only. Avoid actually exchanging + ## the rows, in case there is lots of memory transfer. The + ## exchange occurs later, within the body of MPFIT, after the + ## extraneous columns of the matrix have been shed. + if ( kmax != j ): + temp = ipvt[ j, 0 ] + ipvt[ j, 0 ] = ipvt[ kmax, 0 ] + ipvt[ kmax, 0 ] = temp + rdiag[ kmax ] = rdiag[ j ] + wa[ kmax ] = wa[ j ] + + ## Compute the householder transformation to reduce the jth + ## column of A to a multiple of the jth unit vector + lj = ipvt[ j, 0 ] + ajj = a[ j :, lj ] + ajnorm = self.enorm( ajj ) + if ( ajnorm == 0. ): + break + if ( a[ j, lj ] < 0. ): + ajnorm = - ajnorm + + ajj = ajj / ajnorm + ajj[ 0 ] = ajj[ 0 ] + 1 + ## *** Note optimization a(j:*,j) + a[ j : , lj ] = ajj +# a[ j, lj ] = ajj + + ## Apply the transformation to the remaining columns + ## and update the norms + + ## NOTE to SELF: tried to optimize this by removing the loop, + ## but it actually got slower. Reverted to "for" loop to keep + ## it simple. + if ( j + 1 < n ): + for k in range( j + 1, n ): + lk = ipvt[ k, 0 ] + ajk = a[ j : , lk ] + ## *** Note optimization a(j:*,lk) + ## (corrected 20 Jul 2000) + if ( a[ j, lj ] != 0. ): + a[ j : , lk ] = ajk - ajj * ( ajk * ajj ).sum() / a[ j, lj ] +# a[ j, lk ] = ajk - ajj * ( ajk * ajj ).sum() / a[ j, lj ] + + if pivot and ( rdiag[ k ] != 0. ): + temp = a[ j, lk ] / rdiag[ k ] + rdiag[ k ] = rdiag[ k ] * sqrt( max( [ 1. - temp**2, 0. ] ) ) + temp = rdiag[ k ] / wa[ k ] + if ( ( 0.05 * temp**2 ) <= machep ): + rdiag[ k ] = self.enorm( a[ j + 1 : , lk ] ) + wa[ k ] = rdiag[ k ] + rdiag[ j ] = - ajnorm + + return a, ipvt, rdiag, acnorm + +############################################################################### + + # Original FORTRAN documentation + # ********** + # + # subroutine qrsolv + # + # given an m by n matrix a, an n by n diagonal matrix d, + # and an m-vector b, the problem is to determine an x which + # solves the system + # + # a*x = b , d*x = 0 , + # + # in the least squares sense. + # + # this subroutine completes the solution of the problem + # if it is provided with the necessary information from the + # factorization, with column pivoting, of a. that is, if + # a*p = q*r, where p is a permutation matrix, q has orthogonal + # columns, and r is an upper triangular matrix with diagonal + # elements of nonincreasing magnitude, then qrsolv expects + # the full upper triangle of r, the permutation matrix p, + # and the first n components of (q transpose)*b. the system + # a*x = b, d*x = 0, is then equivalent to + # + # t t + # r*z = q *b , p *d*p*z = 0 , + # + # where x = p*z. if this system does not have full rank, + # then a least squares solution is obtained. on output qrsolv + # also provides an upper triangular matrix s such that + # + # t t t + # p *(a *a + d*d)*p = s *s . + # + # s is computed within qrsolv and may be of separate interest. + # + # the subroutine statement is + # + # subroutine qrsolv(n,r,ldr,ipvt,diag,qtb,x,sdiag,wa) + # + # where + # + # n is a positive integer input variable set to the order of r. + # + # r is an n by n array. on input the full upper triangle + # must contain the full upper triangle of the matrix r. + # on output the full upper triangle is unaltered, and the + # strict lower triangle contains the strict upper triangle + # (transposed) of the upper triangular matrix s. + # + # ldr is a positive integer input variable not less than n + # which specifies the leading dimension of the array r. + # + # ipvt is an integer input array of length n which defines the + # permutation matrix p such that a*p = q*r. column j of p + # is column ipvt(j) of the identity matrix. + # + # diag is an input array of length n which must contain the + # diagonal elements of the matrix d. + # + # qtb is an input array of length n which must contain the first + # n elements of the vector (q transpose)*b. + # + # x is an output array of length n which contains the least + # squares solution of the system a*x = b, d*x = 0. + # + # sdiag is an output array of length n which contains the + # diagonal elements of the upper triangular matrix s. + # + # wa is a work array of length n. + # + # subprograms called + # + # fortran-supplied ... dabs,dsqrt + # + # argonne national laboratory. minpack project. march 1980. + # burton s. garbow, kenneth e. hillstrom, jorge j. more + # + +############################################################################### + + def qrsolv( self, r, ipvt, diag, qtb, sdiag ): + + if self.debug: + print('Entering qrsolv...') + + if self.__mpfit: + import _mpfit + result = _mpfit.qrsolv( r.tolist(), ipvt.ravel().tolist(), diag.tolist(), qtb.tolist(), sdiag.tolist() ) + result = array( result ) + m = result.shape[ 0 ] - 2 + n = result.shape[ 1 ] + r = result[ 0 : m, : ] + x = result[ m, : ] + sdiag = result[ m + 1, : ] + return r, x, sdiag + + sz = r.shape + m = sz[ 0 ] + n = sz[ 1 ] + + ## copy r and (q transpose)*b to preserve input and initialize s. + ## in particular, save the diagonal elements of r in x. + + for j in range( n ): + r[ j : n, j ] = r[ j, j : n ] + x = r.diagonal() + wa = qtb.copy() + + ## Eliminate the diagonal matrix d using a givens rotation + for j in range( n ): + l = ipvt[ j, 0 ] + if ( diag[ l ] == 0. ): + break + sdiag[ j : ] = 0. + sdiag[ j ] = diag[ l ] + + ## The transformations to eliminate the row of d modify only a + ## single element of (q transpose)*b beyond the first n, which + ## is initially zero. + + qtbpj = 0. + for k in range( j, n ): + if ( sdiag[ k ] == 0. ): + break + if ( abs( r[ k, k ] ) < abs( sdiag[ k ] ) ): + cotan = r[ k, k ] / sdiag[ k ] + sine = 0.5 / sqrt( 0.25 + 0.25 * cotan**2 ) + cosine = sine * cotan + else: + tang = sdiag[ k ] / r[ k, k ] + cosine = 0.5 / sqrt( 0.25 + 0.25 * tang**2 ) + sine = cosine * tang + + ## Compute the modified diagonal element of r and the + ## modified element of ((q transpose)*b,0). + r[ k, k ] = cosine * r[ k, k ] + sine * sdiag[ k ] + temp = cosine * wa[ k ] + sine * qtbpj + qtbpj = - sine * wa[ k ] + cosine * qtbpj + wa[ k ] = temp + + ## Accumulate the transformation in the row of s + if ( n > k + 1 ): + temp = cosine * r[ k + 1 : n, k ] + sine * sdiag[ k + 1 : n ] + sdiag[ k + 1 : n ] = - sine * r[ k + 1 : n, k ] + cosine * sdiag[ k + 1 : n ] + r[ k + 1 : n, k ] = temp + + sdiag[ j ] = r[ j, j ] + r[ j, j ] = x[ j ] + + ## Solve the triangular system for z. If the system is singular + ## then obtain a least squares solution + nsing = n + wh = awhere( sdiag == 0. ) + if ( len( wh ) > 0 ): + nsing = wh[ 0 ] + wa[ nsing : ] = 0 + + if ( nsing >= 1 ): + wa[ nsing - 1 ] = wa[ nsing - 1 ] / sdiag[ nsing - 1 ] ## Degenerate case + ## *** Reverse loop *** + for j in range( nsing - 2, - 1, - 1 ): + sum = ( r[ j + 1 : nsing, j ] * wa[ j + 1 : nsing ]).sum() + wa[ j ] = ( wa[ j ] - sum ) / sdiag[ j ] + + ## Permute the components of z back to components of x + x = aput( x, ipvt, wa ) + + return r, x, sdiag + +############################################################################### + + # Original FORTRAN documentation + # + # subroutine lmpar + # + # given an m by n matrix a, an n by n nonsingular diagonal + # matrix d, an m-vector b, and a positive number delta, + # the problem is to determine a value for the parameter + # par such that if x solves the system + # + # a*x = b , sqrt(par)*d*x = 0 , + # + # in the least squares sense, and dxnorm is the euclidean + # norm of d*x, then either par is zero and + # + # (dxnorm-delta) .le. 0.1*delta , + # + # or par is positive and + # + # abs(dxnorm-delta) .le. 0.1*delta . + # + # this subroutine completes the solution of the problem + # if it is provided with the necessary information from the + # qr factorization, with column pivoting, of a. that is, if + # a*p = q*r, where p is a permutation matrix, q has orthogonal + # columns, and r is an upper triangular matrix with diagonal + # elements of nonincreasing magnitude, then lmpar expects + # the full upper triangle of r, the permutation matrix p, + # and the first n components of (q transpose)*b. on output + # lmpar also provides an upper triangular matrix s such that + # + # t t t + # p *(a *a + par*d*d)*p = s *s . + # + # s is employed within lmpar and may be of separate interest. + # + # only a few iterations are generally needed for convergence + # of the algorithm. if, however, the limit of 10 iterations + # is reached, then the output par will contain the best + # value obtained so far. + # + # the subroutine statement is + # + # subroutine lmpar(n,r,ldr,ipvt,diag,qtb,delta,par,x,sdiag, + # wa1,wa2) + # + # where + # + # n is a positive integer input variable set to the order of r. + # + # r is an n by n array. on input the full upper triangle + # must contain the full upper triangle of the matrix r. + # on output the full upper triangle is unaltered, and the + # strict lower triangle contains the strict upper triangle + # (transposed) of the upper triangular matrix s. + # + # ldr is a positive integer input variable not less than n + # which specifies the leading dimension of the array r. + # + # ipvt is an integer input array of length n which defines the + # permutation matrix p such that a*p = q*r. column j of p + # is column ipvt(j) of the identity matrix. + # + # diag is an input array of length n which must contain the + # diagonal elements of the matrix d. + # + # qtb is an input array of length n which must contain the first + # n elements of the vector (q transpose)*b. + # + # delta is a positive input variable which specifies an upper + # bound on the euclidean norm of d*x. + # + # par is a nonnegative variable. on input par contains an + # initial estimate of the levenberg-marquardt parameter. + # on output par contains the final estimate. + # + # x is an output array of length n which contains the least + # squares solution of the system a*x = b, sqrt(par)*d*x = 0, + # for the output par. + # + # sdiag is an output array of length n which contains the + # diagonal elements of the upper triangular matrix s. + # + # wa1 and wa2 are work arrays of length n. + # + # subprograms called + # + # minpack-supplied ... dpmpar,enorm,qrsolv + # + # fortran-supplied ... dabs,dmax1,dmin1,dsqrt + # + # argonne national laboratory. minpack project. march 1980. + # burton s. garbow, kenneth e. hillstrom, jorge j. more + # + +############################################################################### + + def lmpar( self, r, ipvt, diag, qtb, x, sdiag, delta, par ): + + if self.debug: + print('Entering lmpar...') + + if self.__mpfit: + import _mpfit + result = _mpfit.lmpar( r.tolist(), ipvt.ravel().tolist(), diag.tolist(), qtb.tolist(), x.tolist(), + sdiag.tolist(), delta, par, self.machar.minnum, self.machar.rgiant, self.machar.rdwarf ) + result = array( result ) + m = result.shape[ 0 ] - 3 + n = result.shape[ 1 ] + r = result[ 0 : m, : ] + par = result[ m, 0 ] # par is single float + x = result[ m + 1, : ] + sdiag = result[ m + 2, : ] + return r, par, x, sdiag + + dwarf = self.machar.minnum + sz = r.shape + m = sz[ 0 ] + n = sz[ 1 ] + + ## Compute and store in x the gauss-newton direction. If the + ## jacobian is rank-deficient, obtain a least-squares solution + nsing = n + wa1 = qtb.copy() + wh = awhere( r.diagonal() == 0. ) + if ( len( wh ) > 0 ): + nsing = wh[ 0, 0 ] + wa1[ nsing : ] = 0 + + if ( nsing >= 1 ): + ## *** Reverse loop *** + for j in range( nsing - 1, - 1, - 1 ): + wa1[ j ] = wa1[ j ] / r[ j, j ] + if ( j - 1 >= 0 ): + wa1[ 0 : j ] = wa1[ 0 : j ] - r[ 0 : j, j ] * wa1[ j ] + + ## Note: ipvt here is a permutation array + x = aput( x, ipvt, wa1 ) + + ## Initialize the iteration counter. Evaluate the function at the + ## origin, and test for acceptance of the gauss-newton direction + iter = 0 + wa2 = diag * x + dxnorm = self.enorm( wa2 ) + fp = dxnorm - delta + if ( fp <= 0.1 * delta ): + return r, 0., x, sdiag + + ## If the jacobian is not rank deficient, the newton step provides a + ## lower bound, parl, for the zero of the function. Otherwise set + ## this bound to zero. + + parl = 0. + if ( nsing >= n ): + wa1 = aget( diag * wa2 / dxnorm, ipvt ) + + wa1[ 0 ] = wa1[ 0 ] / r[ 0, 0 ] ## Degenerate case + for j in range( 1, n ): ## Note "1" here, not zero + sum = ( r[ 0 : j, j ] * wa1[ 0 : j ] ).sum() + wa1[ j ] = ( wa1[ j ] - sum ) / r[ j, j ] + + temp = self.enorm( wa1 ) + parl = ( ( fp / delta ) / temp ) / temp + + ## Calculate an upper bound, paru, for the zero of the function + for j in range( n ): + sum = ( r[ 0 : j + 1, j ] * qtb[ 0 : j + 1 ] ).sum() + wa1[ j ] = sum / diag[ ipvt[ j, 0 ] ] + gnorm = self.enorm( wa1 ) + paru = gnorm / delta + if ( paru == 0. ): + paru = dwarf / min( [ delta, 0.1 ] ) + + ## If the input par lies outside of the interval (parl,paru), set + ## par to the closer endpoint + + par = max( [ par, parl ] ) + par = min( [ par, paru ] ) + if ( par == 0. ): + par = gnorm / dxnorm + + ## Beginning of an interation + while( True ): + iter = iter + 1 + + ## Evaluate the function at the current value of par + if ( par == 0. ): + par = max( [ dwarf, paru * 0.001 ] ) + temp = sqrt( par ) + wa1 = temp * diag + r, x, sdiag = self.qrsolv( r, ipvt, wa1, qtb, sdiag ) + wa2 = diag * x + dxnorm = self.enorm( wa2 ) + temp = fp + fp = dxnorm - delta + + if ( ( abs( fp ) <= 0.1 * delta ) or + ( ( parl == 0. ) and ( fp <= temp ) and ( temp < 0 ) ) or + ( iter == 10 ) ): + break; + + ## Compute the newton correction + wa1 = aget( diag * wa2 / dxnorm, ipvt ) + + for j in range( n - 1 ): + wa1[ j ] = wa1[ j ] / sdiag[ j ] + wa1[ j + 1 : n ] = wa1[ j + 1 : n ] - r[ j + 1 : n, j ] * wa1[ j ] + wa1[ n - 1 ] = wa1[ n - 1 ] / sdiag[ n - 1 ] ## Degenerate case + + temp = self.enorm( wa1 ) + parc = ( fp / delta ) / ( temp**2 ) + + ## Depending on the sign of the function, update parl or paru + if ( fp > 0. ): + parl = max( [ parl, par ] ) + if ( fp < 0. ): + paru = min( [ paru, par ] ) + + ## Compute an improved estimate for par + par = max( [ parl, par + parc ] ) + + ## End of an iteration + + ## Termination + return r, par, x, sdiag + +############################################################################### + + ## Procedure to tie one parameter to another. + def tie( self, p, ptied = None ): + + if self.debug: + print('Entering tie...') + + pp = p.copy() + if ( ptied != None ): + for i in range( len( ptied ) ): + if ( ptied[ i ] != '' ): + cmd = 'pp[' + str( i ) + '] = ' + ptied[ i ] + exec( cmd ) + + return pp + +############################################################################### + + # Original FORTRAN documentation + # ********** + # + # subroutine covar + # + # given an m by n matrix a, the problem is to determine + # the covariance matrix corresponding to a, defined as + # + # t + # inverse(a *a) . + # + # this subroutine completes the solution of the problem + # if it is provided with the necessary information from the + # qr factorization, with column pivoting, of a. that is, if + # a*p = q*r, where p is a permutation matrix, q has orthogonal + # columns, and r is an upper triangular matrix with diagonal + # elements of nonincreasing magnitude, then covar expects + # the full upper triangle of r and the permutation matrix p. + # the covariance matrix is then computed as + # + # t t + # p*inverse(r *r)*p . + # + # if a is nearly rank deficient, it may be desirable to compute + # the covariance matrix corresponding to the linearly independent + # columns of a. to define the numerical rank of a, covar uses + # the tolerance tol. if l is the largest integer such that + # + # abs(r(l,l)) .gt. tol*abs(r(1,1)) , + # + # then covar computes the covariance matrix corresponding to + # the first l columns of r. for k greater than l, column + # and row ipvt(k) of the covariance matrix are set to zero. + # + # the subroutine statement is + # + # subroutine covar(n,r,ldr,ipvt,tol,wa) + # + # where + # + # n is a positive integer input variable set to the order of r. + # + # r is an n by n array. on input the full upper triangle must + # contain the full upper triangle of the matrix r. on output + # r contains the square symmetric covariance matrix. + # + # ldr is a positive integer input variable not less than n + # which specifies the leading dimension of the array r. + # + # ipvt is an integer input array of length n which defines the + # permutation matrix p such that a*p = q*r. column j of p + # is column ipvt(j) of the identity matrix. + # + # tol is a nonnegative input variable used to define the + # numerical rank of a in the manner described above. + # + # wa is a work array of length n. + # + # subprograms called + # + # fortran-supplied ... dabs + # + # argonne national laboratory. minpack project. august 1980. + # burton s. garbow, kenneth e. hillstrom, jorge j. more + # + # ********** + +############################################################################### + + def calc_covar( self, rr, ipvt = None, tol = 1.e-14 ): + + if self.debug: + print('Entering calc_covar...') + + if self.__mpfit: + import _mpfit + r = _mpfit.calc_covar( rr.tolist(), ipvt.ravel().tolist(), tol ) + return array( r ) + + s = rr.shape + if ( rank( rr ) != 2 ): + print('ERROR: r must be a two-dimensional matrix') + return - 1 + m = s[ 0 ] + n = s[ 1 ] + if ( m != n ): + print('ERROR: r must be a square matrix') + return - 1 + + if ( ipvt == None ): + ipvt = arange( n ).reshape( [ n, 1 ] ) + r = rr.copy() + r = reshape( r, ( n, n ) ) + + ## Form the inverse of r in the full upper triangle of r + l = - 1 + tolr = tol * abs( r[ 0, 0 ] ) # BUG!! because r[ 0, 0 ] might be 0. + for k in range( n ): + if ( abs( r[ k, k ] ) <= tolr ): + break + r[ k, k ] = 1. / r[ k, k ] + for j in range( k ): + temp = r[ k, k ] * r[ j, k ] + r[ j, k ] = 0. + r[ 0 : j + 1, k ] = r[ 0 : j + 1, k ] - temp * r[ 0 : j + 1, j ] + l = k + return r + + ## Form the full upper triangle of the inverse of (r transpose)*r + ## in the full upper triangle of r + if l >= 0: + for k in range( l + 1 ): + for j in range( k ): + temp = r[ j, k ] + r[ 0 : j + 1 , j ] = r[ 0 : j + 1, j ] + temp * r[ 0 : j + 1, k ] + temp = r[ k, k ] + r[ 0 : k + 1, k ] = temp * r[ 0 : k + 1, k ] + + ## Form the full lower triangle of the covariance matrix + ## in the strict lower triangle or and in wa + wa = repeat( [ r[ 0, 0 ] ], n ) + for j in range( n ): + jj = ipvt[ j, 0 ] + sing = ( j > l ) + for i in range( j + 1 ): + if sing: + r[ i, j ] = 0. + ii = ipvt[ i, 0 ] + if ( ii > jj ): + r[ ii, jj ] = r[ i, j ] + if ( ii < jj ): + r[ jj, ii ] = r[ i, j ] + wa[ jj ] = r[ j, j ] + + ## Symmetrize the covariance matrix in r + for j in range( n ): + r[ 0 : j + 1, j ] = r[ j, 0 : j + 1 ] + r[ j, j ] = wa[ j ] + + return r + +############################################################################### + +class machar: + + def __init__( self, double = True ): + + if not double: + self.machep = 1.19209e-007 + self.maxnum = 3.40282e+038 + self.minnum = 1.17549e-038 + else: + self.machep = 2.2204460e-016 + self.maxnum = 1.7976931e+308 + self.minnum = 2.2250739e-308 + self.maxgam = 171.624376956302725 + self.maxlog = log( self.maxnum ) + self.minlog = log( self.minnum ) + self.rdwarf = sqrt( self.minnum * 1.5 ) * 10. + self.rgiant = sqrt( self.maxnum ) * 0.1 + +############################################################################### diff --git a/CEP/Calibration/ExpIon/src/parmdbmain.py b/CEP/Calibration/ExpIon/src/parmdbmain.py new file mode 100644 index 0000000000000000000000000000000000000000..05eb57b3ef452600db9b5c61b93289252283b8b6 --- /dev/null +++ b/CEP/Calibration/ExpIon/src/parmdbmain.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2007 +# ASTRON (Netherlands Institute for Radio Astronomy) +# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +# +# This file is part of the LOFAR software suite. +# The LOFAR software suite 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 3 of the License, or +# (at your option) any later version. +# +# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +# +# $Id$ + +import subprocess + +def store_parms( pdbname, parms, create_new = False) : + + FNULL = open( '/dev/null', 'w' ) + process = subprocess.Popen( ['parmdbm'], shell = False, stdin = subprocess.PIPE, stdout = FNULL, stderr = FNULL ) + if create_new : + process.stdin.write( "create tablename='" + pdbname + "'\n" ) + else : + process.stdin.write( "open tablename='" + pdbname + "'\n" ) + + parmnames = list(parms.keys()) + for parmname in parmnames: + v = parms[parmname] + times = v['times'] + nt = len(times) + freqs = v['freqs'] + nf = len(freqs) + timewidths = v['timewidths'] + freqwidths = v['freqwidths'] + values = v['values'] + repr_values = '[' + ', '.join([repr(v1) for v1 in values.flat]) + ']' + freq_start = freqs[0]-freqwidths[0]/2 + freq_end = freqs[-1]+freqwidths[-1]/2 + time_start = times[0] - timewidths[0]/2 + time_end = times[-1] + timewidths[-1]/2 + domain = "[%s,%s,%s,%s]" % ( freq_start, freq_end, time_start, time_end ) + process.stdin.write("add %s shape=[%i,%i], values=%s, domain=%s\n" % (parmname, nf, nt, repr_values, domain)) + + process.stdin.write('quit\n') + process.wait() diff --git a/CEP/Calibration/ExpIon/src/parmdbwriter.py b/CEP/Calibration/ExpIon/src/parmdbwriter.py new file mode 100755 index 0000000000000000000000000000000000000000..43a90cf0126caa1271bbf8b7af2e43267ce54be1 --- /dev/null +++ b/CEP/Calibration/ExpIon/src/parmdbwriter.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 + +import lofar.parmdb +import lofar.expion.parmdbmain +import sys +import numpy + +rank = sys.argv[1] +instrument_pdbname = sys.argv[2] +globaldbname = sys.argv[3] +ion_pdbname = sys.argv[4] + +ion_pdbname = '/'.join(instrument_pdbname.split('/')[0:-1]) + '/' + ion_pdbname + +instrument_pdb = lofar.parmdb.parmdb( instrument_pdbname ) +ClockTEC_pdb = lofar.parmdb.parmdb( globaldbname + "/ionosphere") + +instrument_parms = instrument_pdb.getValuesGrid('Gain:{0:0,1:1}:*') +Clock_parms = ClockTEC_pdb.getValuesGrid('Clock*') +TEC_parms = ClockTEC_pdb.getValuesGrid('TEC*') + +ionosphere_parms = instrument_parms +for parm_name in list(Clock_parms.keys()) : + parm_name_split = parm_name.split(':') + pol = parm_name_split[1] + station = parm_name_split[2] + try : + Clock = Clock_parms[ ':'.join( [ 'Clock', pol, station ] ) ][ 'values' ] + TEC = TEC_parms[ ':'.join( [ 'TEC', pol, station ] ) ][ 'values' ] + except KeyError : + pass + else: + parm_real = ionosphere_parms[ ':'.join( [ 'Gain', pol, pol, 'Real', station] ) ] + parm_imag = ionosphere_parms[ ':'.join( [ 'Gain', pol, pol, 'Imag', station] ) ] + freqs = parm_real['freqs'] + phases = Clock*freqs*2*numpy.pi/1e9 + TEC*8e9/freqs + phasors = numpy.sqrt(parm_real['values']**2 + parm_imag['values']**2) * numpy.exp(1j * phases) + parm_real[ 'values' ] = phasors.real + parm_imag[ 'values' ] = phasors.imag + +lofar.expion.parmdbmain.store_parms( ion_pdbname, ionosphere_parms, create_new = True ) + + + diff --git a/CEP/Calibration/ExpIon/src/read_sagecal.py b/CEP/Calibration/ExpIon/src/read_sagecal.py new file mode 100644 index 0000000000000000000000000000000000000000..4a0fbb73bec59ba1e35ad0daa2a9c4fbb34f9ccc --- /dev/null +++ b/CEP/Calibration/ExpIon/src/read_sagecal.py @@ -0,0 +1,223 @@ +import numpy as np +import tables as tab +import os +from subprocess import call + +def getClusters(clusterf,skymodel,max_nr_clusters=1000): + #get clusters + sky=open(skymodel) + sources={} + for line in sky: + if line.strip()[0]=='#': + continue; + splitted=line.split() + sources[splitted[0].strip()]={} + sources[splitted[0].strip()]['Ra']=np.pi/12.*(float(splitted[1]) + +float(splitted[2])/60. + +float(splitted[3])/3600.) + sources[splitted[0].strip()]['Dec']=np.pi/180.*(float(splitted[4]) + +float(splitted[5])/60. + +float(splitted[6])/3600.) + sources[splitted[0].strip()]['I']=float(splitted[7]) + sources[splitted[0].strip()]['sp']=float(splitted[11]) + sources[splitted[0].strip()]['freq0']=float(splitted[-1]) + + clusterfile=open(clusterf) + clusters=[] + count=0 + tot_nr_sol=0 + nrSB=0 + for line in clusterfile: + print("adding cluster",line) + if line.strip()[0]=='#': + continue; + splitted=line.split() + if count>=max_nr_clusters: + tot_nr_sol+=int(splitted[1]) + continue + clusters.append({}) + clusters[count]['id']=int(splitted[0]) + clusters[count]['nrsol']=int(splitted[1]) + clusters[count]['real']=[] + clusters[count]['imag']=[] + clusters[count]['sources']={} + clusters[count]['store_data']=True + avg_ra=0 + avg_dec=0 + sum_weight=0 + for src in splitted[2:]: + clusters[count]['sources'][src.strip()]=sources[src.strip()] + weight=sources[src.strip()]['I'] + avg_ra+=sources[src.strip()]['Ra']*weight + avg_dec+=sources[src.strip()]['Dec']*weight + sum_weight+=weight + clusters[count]['Ra']=avg_ra/sum_weight + clusters[count]['Dec']=avg_dec/sum_weight + tot_nr_sol+=clusters[count]['nrsol'] + count+=1 + + return clusters,tot_nr_sol + +def get_freq_data(sol,clusters,tot_nr_sol): + data=[] + indices=[] + for line in sol: + splitted=line.split() + indices.append(int(splitted[0])) + data.append(np.array([float(i) for i in splitted[1:]])) + data=np.array(data); + nrStations=(max(indices)+1)/8 + nrTimes=data.shape[0]/(8*nrStations) + print(data.shape,nrTimes,nrStations,8) + if data.shape[0]!=nrTimes*nrStations*8: + print("wrong shape") + return -1 + data=data.reshape(nrTimes,nrStations,8,tot_nr_sol) + start=0 + for icl,cluster in enumerate(clusters): + if cluster['store_data']==True: + cluster['real'].append(data[:,:,0:8:2,start:start+cluster['nrsol']]) + cluster['imag'].append(data[:,:,1:8:2,start:start+cluster['nrsol']]) + start+=cluster['nrsol'] + return 1 + +def remove_unitary(clusters,freqs,store_intermediate=False): + for idxc,cluster in enumerate(clusters): + if cluster['store_data']: + if store_intermediate: + first=True + for isb in freqs: + data=np.load('tmp_store_real_%d_%d.npy'%(idxc,isb)) + if first: + cluster['real']=data + else: + cluster['real']=np.concatenate((cluster['real'],data)) + data=np.load('tmp_store_imag_%d_%d.npy'%(idxc,isb)) + if first: + cluster['imag']=data + first=False + else: + cluster['imag']=np.concatenate((cluster['imag'],data)) + call("rm tmp_store_real_%d_%d.npy"%(idxc,isb),shell=True) + call("rm tmp_store_imag_%d_%d.npy"%(idxc,isb),shell=True) + + + else: + cluster['real']=np.array(cluster['real']) + cluster['imag']=np.array(cluster['imag']) + nrTimes=cluster['real'].shape[1] + nrSB=cluster['real'].shape[0] + nrStations=cluster['real'].shape[2] + cdata=cluster['real']+1.j*cluster['imag'] + cluster['real']=[] + cluster['imag']=[] + print(cdata.shape) + cdata=np.swapaxes(cdata,0,1) + print(cdata.shape) + cdata=np.swapaxes(cdata,3,4) + print(cdata.shape) + cdata=np.swapaxes(cdata,2,3) + print(cdata.shape) + cdata=np.swapaxes(cdata,1,2) + print(cdata.shape,nrTimes*cluster['nrsol'],nrSB,nrStations,4) + cdata=cdata.reshape(nrTimes*cluster['nrsol'],nrSB,nrStations,4) + + + #multiply with unitary matrix to get something more constant in time/freq + + J0=cdata[0,0].reshape(nrStations*2,2) + for ntime in range(nrTimes*cluster['nrsol']): + for nfreq in range(nrSB): + if ntime==0 and nfreq==0: + continue; + J1=cdata[ntime,nfreq].reshape(nrStations*2,2) + u,s,v=np.linalg.svd(np.dot(np.conjugate(J1.T),J0)) + U1=np.dot(u,v) + J0=np.dot(J1,U1) + cdata[ntime,nfreq]=J0.reshape(nrStations,4) + J0=cdata[ntime,0].reshape(nrStations*2,2) + if store_intermediate: + np.save('tmp_store_cdata_%d.npy'%(idxc),cdata) + else: + cluster['cdata']=cdata + +def fill_sb(clusters,solpath,solpath_end,subbandlist,tot_nr_sol,store_intermediate=False): + freqs=[] + for isb,sb in enumerate(subbandlist): + print("opening",solpath+str(sb)+solpath_end) + if not os.path.isfile(solpath+str(sb)+solpath_end): + print("skipping",sb) + continue; + sol=open(solpath+str(sb)+solpath_end) + if get_freq_data(sol,clusters,tot_nr_sol)>0: + freqs.append(isb) + # store intermediate results....to save memory? + if store_intermediate: + for idxc,cluster in enumerate(clusters): + if cluster['store_data']: + np.save('tmp_store_real_%d_%d.npy'%(idxc,isb),np.array(cluster['real'])) + np.save('tmp_store_imag_%d_%d.npy'%(idxc,isb),np.array(cluster['imag'])) + cluster['real']=[] + cluster['imag']=[] + + return freqs + +def addToH5File(h5file,clusters,freqs,store_intermediate=False): + #group into nrsolutions + poss_nr_sol=[] + groups=[] + for clusteridx,cluster in enumerate(clusters): + if not cluster['nrsol'] in poss_nr_sol: + poss_nr_sol.append(cluster['nrsol']) + groups.append([]) + idx=poss_nr_sol.index(cluster['nrsol']) + groups[idx].append(clusteridx) + + if 'sagefreqIdx' in h5file.root: + h5file.removeNode('/sagefreqIdx') + + h5file.createArray(h5file.root, 'sagefreqIdx',freqs) + for igrp,grp in enumerate(groups): + # create arrays: + if store_intermediate: + cdata=np.load('tmp_store_cdata_%d.npy'%(grp[0])) + else: + cdata=clusters[grp[0]]['cdata'] + + arrayshape=cdata.shape[:-1]+(len(grp),4) + for name in ['sageradec%d'%igrp,'sagephases%d'%igrp,'sageamplitudes%d'%igrp]: + if name in h5file.root: + h5file.removeNode('/'+name) + + srcarray = h5file.createCArray(h5file.root, 'sageradec%d'%igrp, tab.Float32Atom(), shape=(len(grp),2)) + pharray = h5file.createCArray(h5file.root, 'sagephases%d'%igrp, tab.Float32Atom(), shape=arrayshape) + amparray = h5file.createCArray(h5file.root, 'sageamplitudes%d'%igrp, tab.Float32Atom(), shape=arrayshape) + for idx,clusteridx in enumerate(grp): + if store_intermediate: + cdata=np.load('tmp_store_cdata_%d.npy'%(clusteridx)) + else: + cdata=clusters[clusteridx]['cdata'] + pharray[:,:,:,idx,:]=np.angle(cdata) + amparray[:,:,:,idx,:]=np.absolute(cdata) + srcarray[idx,:]=np.array([clusters[clusteridx]['Ra'],clusters[clusteridx]['Dec']]) + clusters[clusteridx]['cdata']=[] + if store_intermediate: + call("rm tmp_store_cdata_%d.npy"%(clusteridx),shell=True) + pharray.flush(); + amparray.flush(); + srcarray.flush(); + +def UpdateIonmodel(h5filename,clusterf,skymodel,solpath,subbands,max_nr_clusters=1000,store_intermediate=True): + '''Add sagecal solutions to your ionmodel.hdf5.Use store_intermediate if you are trying to store many solutions, since otherwise the program will run out of memory. subbands is a list with subband indices. The sagecal solutions are stored per group with the same timestep. THe list of valid subband indices are also stored in the hdf5 file.''' + + file_example=os.listdir(solpath)[0] + pos=file_example.find('SB') + start_name=solpath+'/'+file_example[:pos+2] + end_name=file_example[pos+5:] + subbandlist=['%03d'%(sb) for sb in subbands] + clusters,solshape=getClusters(clusterf=clusterf,skymodel=skymodel,max_nr_clusters=max_nr_clusters) + freqs=fill_sb(clusters,solpath=start_name,solpath_end=end_name,subbandlist=subbandlist,tot_nr_sol=solshape,store_intermediate=store_intermediate) + remove_unitary(clusters,freqs,store_intermediate) + h5file = tab.openFile(h5filename, mode = "r+") + addToH5File(h5file,clusters,np.array(subbands)[freqs],store_intermediate=store_intermediate) + diff --git a/CEP/Calibration/ExpIon/src/readms-part.py b/CEP/Calibration/ExpIon/src/readms-part.py new file mode 100755 index 0000000000000000000000000000000000000000..0e20fa37483093595840bbb6f4e1d336aa17d7a6 --- /dev/null +++ b/CEP/Calibration/ExpIon/src/readms-part.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 + +import os +import socket +import sys +import pyrap.tables as pt + + +masterhost = sys.argv[2] +port = int( sys.argv[3] ) +partnr = sys.argv[5] +msname = sys.argv[6] + +antenna_table = pt.table( msname + "/ANTENNA") +name_col = antenna_table.getcol('NAME') +position_col = antenna_table.getcol( 'POSITION' ) +antenna_table.close() + +field_table = pt.table( msname + "/FIELD") +phase_dir_col = field_table.getcol('PHASE_DIR') +field_table.close() + +s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +s.connect((masterhost, port)) +s.sendall(partnr.encode("ascii")) +s.recv(1024) +s.sendall(repr(name_col).encode("ascii")) +s.recv(1024) +s.sendall(repr(position_col).encode("ascii")) +s.recv(1024) +s.sendall(repr(phase_dir_col).encode("ascii")) +s.recv(1024) + +s.close() diff --git a/CEP/Calibration/ExpIon/src/readms-part.sh b/CEP/Calibration/ExpIon/src/readms-part.sh new file mode 100755 index 0000000000000000000000000000000000000000..e4f916f81a29dc7ea3b1773d0571c4e1d26be11f --- /dev/null +++ b/CEP/Calibration/ExpIon/src/readms-part.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +source $9 + +cd ${10} + +exec `dirname $0`/readms-part.py $@ + diff --git a/CEP/Calibration/ExpIon/src/readms.py b/CEP/Calibration/ExpIon/src/readms.py new file mode 100755 index 0000000000000000000000000000000000000000..4385626bd7153208f18114ace91179a10df085b1 --- /dev/null +++ b/CEP/Calibration/ExpIon/src/readms.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# Copyright (C) 2007 +# ASTRON (Netherlands Institute for Radio Astronomy) +# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +# +# This file is part of the LOFAR software suite. +# The LOFAR software suite 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 3 of the License, or +# (at your option) any later version. +# +# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +# +# $Id$ + + +import os +import socket +import sys +import time +import numpy + +from threading import Thread + +class Server( Thread ): + + def __init__ ( self ): + Thread.__init__( self ) + self.socket = socket.socket ( socket.AF_INET, socket.SOCK_STREAM ) + self.port = 2727 + while True: + try: + self.socket.bind ( ( '', self.port ) ) + break + except socket.error: + self.port += 1 + + self.socket.listen ( 3 ) + self.socket.settimeout(1) + + def run(self): + self.connectionserverlist = [] + self.stop_flag = False + while not self.stop_flag: + try: + connection, details = self.socket.accept() + except socket.timeout: + pass + else: + connection.settimeout( None ) + connectionserver = ConnectionServer(connection) + connectionserver.start() + self.connectionserverlist.append( connectionserver ) + + def get_results( self ): + + self.stop_flag = True + self.join() + results = [] + for connectionserver in self.connectionserverlist: + connectionserver.join() + results.append( connectionserver.result ) + return results + + +class ConnectionServer( Thread ): + + def __init__( self, connection ): + Thread.__init__( self ) + self.connection = connection + + def run( self ): + self.result = [] + while True: + data = self.connection.recv(1024) + if not data: + break + #Always explicitely send an aknowledgement, this speeds up communication + self.connection.send('OK') + self.result.append( data ) + self.connection.close() + + +def readms(gdsfile, clusterdesc): + + host = socket.gethostname() + + server = Server() + server.start() + os.system("startdistproc -useenv -wait -cdn %s -dsn %s -mode %i -masterhost %s -nostartmaster `which readms-part.sh` $PWD" % (clusterdesc, gdsfile, server.port, host)) + + results = server.get_results() + antennas = eval(results[0][1], {}) + positions = eval(results[0][2], {'array': numpy.array}) + pointing = eval(results[0][3], {'array': numpy.array}) + + return (antennas, positions, pointing) + diff --git a/CEP/Calibration/ExpIon/src/repairGlobaldb.py b/CEP/Calibration/ExpIon/src/repairGlobaldb.py new file mode 100644 index 0000000000000000000000000000000000000000..1ee4a8d8898b9285ff75f079f00b89da1ee8ddac --- /dev/null +++ b/CEP/Calibration/ExpIon/src/repairGlobaldb.py @@ -0,0 +1,361 @@ +import tables +import lofar.parmdb +import pyrap.tables as pt +import lofar.expion.ionosphere as iono +import os +import numpy as np +class dummyion: + pass + +def get_source_list( pdb, source_pattern_list ): + source_list = [] + for pattern in source_pattern_list : + parmname_list = pdb.getNames( 'DirectionalGain:?:?:*:*:' + pattern ) + source_list.extend([n.split(':')[-1] for n in parmname_list]) + parmname_list = pdb.getNames( 'RotationAngle:*:' + pattern ) + source_list.extend([n.split(':')[-1] for n in parmname_list]) + parmname_list = pdb.getNames( 'ScalarPhase:*:' + pattern ) + source_list.extend([n.split(':')[-1] for n in parmname_list]) + print(set(source_list)) + return sorted(set(source_list)) + +def get_station_list( pdb, station_pattern_list, DirectionalGainEnable ): + station_list = [] + for pattern in station_pattern_list : + parmname_list = pdb.getNames( { True : 'DirectionalGain:?:?:*:'+pattern+':*', False: 'Gain:?:?:*:' + pattern}[DirectionalGainEnable] ) + station_list.extend(sorted(set([n.split(':')[{True : -2, False : -1}[DirectionalGainEnable]] for n in parmname_list]))) + return station_list + +def repair_station_table(myion,globaldbpath,instrumentdb): + stations = ["*"] + myion.stations = get_station_list( instrumentdb, stations, myion.DirectionalGainEnable ) + myion.N_stations = len(myion.stations) + + antenna_table_name = os.path.join( globaldbpath, "ANTENNA") + if not os.path.exists(antenna_table_name) : + print("ANTENNA table not existing, please copy to globaldb") + return + antenna_table = pt.table(antenna_table_name) + name_col = antenna_table.getcol('NAME') + position_col = antenna_table.getcol( 'POSITION' ) + myion.station_positions = [position_col[name_col.index(station_name)] for station_name in myion.stations] + antenna_table.close() + station_table = myion.hdf5.createTable(myion.hdf5.root, 'stations', {'name': tables.StringCol(40), 'position':tables.Float64Col(3)}) + row = station_table.row + for (station, position) in zip(myion.stations, myion.station_positions) : + row['name'] = station + row['position'] = position + row.append() + station_table.flush() + myion.array_center = np.array( myion.station_positions ).mean(axis=0).tolist() + myion.hdf5.createArray(myion.hdf5.root, 'array_center', myion.array_center) + + + +def repair_pointing(myion,globaldbpath): + field_table_name = os.path.join( globaldbpath, "FIELD" ) + if not os.path.exists(field_table_name) : + print("FIELD table not existing, please copy to globaldb") + return + field_table = pt.table( field_table_name) + field_table = pt.table( globaldbpath + "/FIELD") + phase_dir_col = field_table.getcol('PHASE_DIR') + myion.pointing = phase_dir_col[0,0,:] + field_table.close() + myion.hdf5.createArray(myion.hdf5.root, 'pointing', myion.pointing) + + +def repair_sources(myion,globaldb,instrumentdb): + skydbname = globaldb + "/sky" + if not os.path.exists(skydbname) : + print("No skydb found, copy first to globaldb") + return + skydb = lofar.parmdb.parmdb( skydbname ) + sources = ["*"] + myion.sources = get_source_list( instrumentdb, sources ) + myion.source_positions = [] + for source in myion.sources : + try: + RA = skydb.getDefValues( 'Ra:' + source )['Ra:' + source][0][0] + dec = skydb.getDefValues( 'Dec:' + source )['Dec:' + source][0][0] + except KeyError: + # Source not found in skymodel parmdb, try to find components + RA = np.array(list(skydb.getDefValues( 'Ra:' + source + '.*' ).values())).mean() + dec = np.array(list(skydb.getDefValues( 'Dec:' + source + '.*' ).values())).mean() + myion.source_positions.append([RA, dec]) + +def add_to_h5_func(h5file,data,name='test',dtype=None): + atom = tables.Atom.from_dtype(data.dtype) + if name in h5file.root: + h5file.removeNode('/'+name) + myarray=h5file.createCArray(h5file.root,name,atom,shape=data.shape) + myarray[:]=data + myarray.flush() + +def doRepair(globaldbpath, + GainEnable = False, DirectionalGainEnable = False, + PhasorsEnable = False, RotationEnable = False, CommonRotationEnable = False,ScalarPhaseEnable = False, CommonScalarPhaseEnable = False,polarizations=[0,1],tablename='instrument-0'): + if not os.path.isdir(globaldbpath): + print("error:",globaldbpath,"does not exist") + return + if os.path.isfile(globaldbpath+'/ionmodel.hdf5'): + try: + myion=iono.IonosphericModel([globaldbpath]) + except (RuntimeError, TypeError, NameError): + myion=dummyion() + myion.hdf5=tables.openFile(globaldbpath+'/ionmodel.hdf5','w') + + else: + myion=dummyion() + myion.hdf5=tables.openFile(globaldbpath+'/ionmodel.hdf5','w') + + myion.GainEnable = GainEnable + myion.DirectionalGainEnable = DirectionalGainEnable + myion.PhasorsEnable =PhasorsEnable + myion.RotationEnable =RotationEnable + myion.CommonRotationEnable =CommonRotationEnable + myion.ScalarPhaseEnable =ScalarPhaseEnable + myion.CommonScalarPhaseEnable =CommonScalarPhaseEnable + myion.polarizations = polarizations + myion.N_pol = len(polarizations) + myion.instrument_db_list=[globaldbpath+'/%s-%i'%(tablename,idx) for idx in range(500) if os.path.isdir(globaldbpath+'/%s-%i'%(tablename,idx))] + + instrumentdb=lofar.parmdb.parmdb(myion.instrument_db_list[0]) + + if hasattr(myion,'stations'): + stations=myion.stations + else: + repair_station_table(myion,globaldbpath,instrumentdb) + + + if not hasattr(myion,'pointing'): + repair_pointing(myion,globaldbpath) + + + if not hasattr(myion,'sources'): + if DirectionalGainEnable or myion.RotationEnable or ScalarPhaseEnable: + print("getting source names from instrumentdb") + repair_sources(myion,globaldbpath,instrumentdb) + + else: + myion.sources = ["Pointing"] + myion.source_positions = [list(myion.pointing)] + + source_table = myion.hdf5.createTable(myion.hdf5.root, 'sources', {'name': tables.StringCol(40), 'position':tables.Float64Col(2)}) + row = source_table.row + for (source, position) in zip(myion.sources, myion.source_positions) : + row['name'] = source + row['position'] = position + row.append() + source_table.flush() + myion.N_sources = len(myion.sources) + + # First collect all frequencies + # We need them beforehand to sort the frequencies (frequencies are not necessarily given in sorted order) + if PhasorsEnable: + infix = ('Ampl', 'Phase') + else: + infix = ('Real', 'Imag') + if myion.GainEnable : + parmname0 = ':'.join(['Gain', str(myion.polarizations[0]), str(myion.polarizations[0]), infix[1], myion.stations[0]]) + else: + if myion.DirectionalGainEnable : + parmname0 = ':'.join(['DirectionalGain', str(myion.polarizations[0]), str(myion.polarizations[0]), infix[1], myion.stations[0], myion.sources[0]]) + else: + if myion.ScalarPhaseEnable : + parmname0 = ':'.join(['ScalarPhase', myion.stations[0], myion.sources[0]]) + else: + if myion.CommonScalarPhaseEnable: + parmname0 = ':'.join(['CommonScalarPhase', myion.stations[0]]) + + parmname_check=parmname0 + myion.freqs = [] + myion.freqwidths = [] + newdblist=[] + for instrumentdb_name in myion.instrument_db_list: + print("opening",instrumentdb_name,parmname_check) + try: + instrumentdb = lofar.parmdb.parmdb( instrumentdb_name ) + v0 = instrumentdb.getValuesGrid( parmname_check )[ parmname_check ] + freqs = v0['freqs'] + except: + print("Error opening " + instrumentdb_name,"removing from list") + else: + myion.freqs = np.concatenate([myion.freqs, freqs]) + myion.freqwidths = np.concatenate([myion.freqwidths, v0['freqwidths']]) + newdblist.append(instrumentdb_name) + myion.instrument_db_list=newdblist + # Sort frequencies, find both the forward and inverse mapping + # Mappings are such that + # sorted_freqs = unsorted_freqs[sorted_freq_idx] + # sorted_freqs[inverse_sorted_freq_idx] = unsorted_freqs + # We will use the following form + # sorted_freqs[inverse_sorted_freq_idx[selection]] = unsorted_freqs[selection] + # to process chunks (=selections) of unsorted data and store them in sorted order + sorted_freq_idx = sorted(list(range(len(myion.freqs))), key = lambda idx: myion.freqs[idx]) + inverse_sorted_freq_idx = sorted(list(range(len(myion.freqs))), key = lambda idx: sorted_freq_idx[idx]) + + myion.freqs = myion.freqs[sorted_freq_idx] + myion.freqwidths = myion.freqwidths[sorted_freq_idx] + add_to_h5_func(myion.hdf5,np.array(myion.freqs),name='freqs',dtype=tables.Float64Atom()) + add_to_h5_func(myion.hdf5,np.array(myion.freqwidths),name='freqwidths',dtype=tables.Float64Atom()) + myion.N_freqs = len(myion.freqs) + + myion.times = v0['times'] + myion.timewidths = v0['timewidths'] + add_to_h5_func(myion.hdf5,myion.times,name='times',dtype=tables.Float64Atom()) + add_to_h5_func(myion.hdf5,myion.timewidths,name='timewidths',dtype=tables.Float64Atom()) + + myion.N_times = len( myion.times ) + add_to_h5_func(myion.hdf5, np.array(myion.polarizations),name='polarizations',dtype=tables.Float32Atom()) + + + if GainEnable or DirectionalGainEnable: + if hasattr(myion,'phases'): + myion.hdf5.removeNode('/phases') + chunkshape = (1024 , 32, 1, 1, 1) + ph=myion.hdf5.createCArray(myion.hdf5.root, 'phases', tables.Float64Atom(), shape=(myion.N_times, myion.N_freqs, myion.N_stations, myion.N_sources, myion.N_pol), chunkshape = chunkshape) + + if hasattr(myion,'amplitudes'): + myion.hdf5.removeNode('/amplitudes') + chunkshape = (1024 , 32, 1, 1, 1) + amp = myion.hdf5.createCArray(myion.hdf5.root, 'amplitudes', tables.Float64Atom(), shape=(myion.N_times, myion.N_freqs, myion.N_stations, myion.N_sources, myion.N_pol), chunkshape = chunkshape) + if not hasattr(myion,'flags'): + myion.flags = myion.hdf5.createCArray(myion.hdf5.root, 'flags', tables.Float32Atom(), shape=(myion.N_times, myion.N_freqs)) + + else: + if ScalarPhaseEnable or CommonScalarPhaseEnable: + if hasattr(myion,'scalarphases'): + myion.hdf5.removeNode('/scalarphases') + chunkshape = (1024 , 32, 1, 1) + scalarph=myion.hdf5.createCArray(myion.hdf5.root, 'scalarphases', tables.Float64Atom(), shape=(myion.N_times, myion.N_freqs, myion.N_stations, myion.N_sources), chunkshape = chunkshape) + + + if RotationEnable or CommonRotationEnable: + if not hasattr(myion,'rotation'): + chunkshape = (1024 , 32, 1, 1) + rotation = myion.hdf5.createCArray(myion.hdf5.root, 'rotation', tables.Float64Atom(), shape=ph.shape[:4], chunkshape = chunkshape) + else: + rotation = myion.rotation + + freq_idx = 0 + + for instrumentdb_name in myion.instrument_db_list: + print("processing",instrumentdb_name) + instrumentdb = lofar.parmdb.parmdb( instrumentdb_name ) + v0 = instrumentdb.getValuesGrid( parmname_check )[ parmname_check ] + freqs = v0['freqs'] + N_freqs = len(freqs) + sorted_freq_selection = inverse_sorted_freq_idx[freq_idx:freq_idx+N_freqs] + for station_idx,station in enumerate(list(myion.stations[:])): + for pol_idx,pol in enumerate(myion.polarizations[:]): + + if GainEnable: + parmname0 = ':'.join(['Gain', str(pol), str(pol), infix[0], station]) + parmname1 = ':'.join(['Gain', str(pol), str(pol), infix[1], station]) + hasAmpl=False + hasPhase=False + #print " checking",parmname0,parmname0 in instrumentdb.getNames() + if parmname0 in instrumentdb.getNames(): + hasAmpl=True + if parmname1 in instrumentdb.getNames(): + hasPhase=True + if PhasorsEnable: + if hasPhase: + gain_phase = instrumentdb.getValuesGrid( parmname1 )[ parmname1 ]['values'] + if gain_phase.shape != ph[:, sorted_freq_selection[0]:sorted_freq_selection[-1]+1, station_idx, 0, pol_idx].shape: + print("wrong shape",gain_phase.shape,parmname1) + continue; + + ph[:, sorted_freq_selection[0]:sorted_freq_selection[-1]+1, station_idx, 0, pol_idx] = gain_phase + if hasAmpl: + + gain_amplitude = instrumentdb.getValuesGrid( parmname0 )[ parmname0 ]['values'] + amp[:,sorted_freq_selection[0]:sorted_freq_selection[-1]+1, station_idx, 0, pol_idx] = gain_amplitude + else: + gain_real = instrumentdb.getValuesGrid( parmname0 )[ parmname0 ]['values'] + gain_imag = instrumentdb.getValuesGrid( parmname1 )[ parmname1 ]['values'] + + cdata=gain_real+1.j*gain_imag + if cdata.shape != ph[:, sorted_freq_selection[0]:sorted_freq_selection[-1]+1, station_idx, 0, pol_idx].shape: + print("wrong shape",cdata.shape,parmname1) + continue; + + ph[:, sorted_freq_selection[0]:sorted_freq_selection[-1]+1, station_idx, 0, pol_idx] =np.angle(cdata) + + amp[:,sorted_freq_selection[0]:sorted_freq_selection[-1]+1 , station_idx, 0, pol_idx] = np.absolute(cdata) + + if myion.DirectionalGainEnable: + for source_idx,source in enumerate(myion.sources): + parmname0 = ':'.join(['DirectionalGain', str(pol), str(pol), infix[0], station, source]) + parmname1 = ':'.join(['DirectionalGain', str(pol), str(pol), infix[1], station, source]) + hasAmpl=False + hasPhase=False + if parmname0 in instrumentdb.getNames(): + hasAmpl=True + if parmname1 in instrumentdb.getNames(): + hasPhase=True + + if myion.PhasorsEnable: + + if hasPhase: + gain_phase = instrumentdb.getValuesGrid( parmname1 )[ parmname1 ]['values'] + if gain_phase.shape != ph[:, sorted_freq_selection[0]:sorted_freq_selection[-1]+1, station_idx, source_idx, pol_idx].shape: + print("wrong shape",gain_phase.shape,parmname1) + continue; + + ph[:, sorted_freq_selection[0]:sorted_freq_selection[-1]+1, station_idx, source_idx, pol_idx] = gain_phase + if hasAmpl: + gain_amplitude = instrumentdb.getValuesGrid( parmname0 )[ parmname0 ]['values'] + amp[:,sorted_freq_selection[0]:sorted_freq_selection[-1]+1 , station_idx, source_idx, pol_idx] = gain_amplitude + else: + gain_real = instrumentdb.getValuesGrid( parmname0 )[ parmname0 ]['values'] + gain_imag = instrumentdb.getValuesGrid( parmname1 )[ parmname1 ]['values'] + + cdata=gain_real+1.j*gain_imag + if cdata.shape != ph[:, sorted_freq_selection[0]:sorted_freq_selection[-1]+1, station_idx, source_idx, pol_idx].shape: + print("wrong shape",cdata.shape,parmname1) + continue; + + ph[:, sorted_freq_selection[0]:sorted_freq_selection[-1]+1, station_idx, source_idx, pol_idx] =np.angle(cdata) + + amp[:, sorted_freq_selection[0]:sorted_freq_selection[-1]+1, station_idx, source_idx, pol_idx] = np.absolute(cdata) + + if CommonScalarPhaseEnable: + parmname1 = ':'.join(['CommonScalarPhase', station]) + gain_phase = instrumentdb.getValuesGrid( parmname1 )[ parmname1 ]['values'] + if gain_phase.shape != scalarph[:, sorted_freq_selection[0]:sorted_freq_selection[-1]+1, station_idx, 0].shape: + print("wrong shape",gain_phase.shape,parmname1) + continue; + + scalarph[:, sorted_freq_selection[0]:sorted_freq_selection[-1]+1, station_idx, 0] = gain_phase + if myion.ScalarPhaseEnable: + for source_idx,source in enumerate(myion.sources): + parmname1 = ':'.join(['ScalarPhase', station,source]) + gain_phase = instrumentdb.getValuesGrid( parmname1 )[ parmname1 ]['values'] + if gain_phase.shape != scalarph[:, sorted_freq_selection[0]:sorted_freq_selection[-1]+1, station_idx, source_idx].shape: + print("wrong shape",gain_phase.shape,parmname1) + continue; + + scalarph[:, sorted_freq_selection[0]:sorted_freq_selection[-1]+1, station_idx, source_idx] = gain_phase + + if myion.CommonRotationEnable: + for station_idx,station in enumerate(myion.stations): + parmname = ':'.join(['CommonRotationAngle', station]) + rot = instrumentdb.getValuesGrid( parmname )[ parmname ]['values'] + rotation[:, sorted_freq_selection[0]:sorted_freq_selection[-1]+1, station_idx,0] = rot + + if myion.RotationEnable: + for station_idx,station in enumerate(myion.stations): + for source_idx,source in enumerate(myion.sources): + parmname = ':'.join(['RotationAngle', station,source]) + rot = instrumentdb.getValuesGrid( parmname )[ parmname ]['values'] + if rot.shape != rotation[:, sorted_freq_selection[0]:sorted_freq_selection[-1]+1, station_idx, source_idx].shape: + print("wrong shape",rot.shape,parmname) + continue; + rotation[:, sorted_freq_selection[0]:sorted_freq_selection[-1]+1, station_idx,source_idx] = rot + + + + freq_idx += N_freqs + myion.hdf5.close() diff --git a/CEP/Calibration/ExpIon/src/sphere.py b/CEP/Calibration/ExpIon/src/sphere.py new file mode 100644 index 0000000000000000000000000000000000000000..9a26e4563a218ceafafebde50449be3362c769e1 --- /dev/null +++ b/CEP/Calibration/ExpIon/src/sphere.py @@ -0,0 +1,467 @@ +# -*- coding: utf-8 -*- +############################################################################### + +# import Python modules +from math import * + +# import 3rd patry modules +from numpy import * +import pyrap.measures +import pyrap.quanta + +# import user modules +from .acalc import * + +############################################################################### +# be aware of the special cases -0 and .. 60 .. +# TODO: implement special cases in C-code +############################################################################### + +def radec_to_name( radec ): + rd = degdeg_to_hmsdms( radec ) + name = '%02d%02d' % ( rd[ 0 ], rd[ 1 ] ) + if ( radec[ 1 ] >= 0. ): + name = name + '+%02d' % ( rd[ 3 ] ) + else: + name = name + '%03d' % ( rd[ 3 ] ) + name = name + '%02d' % ( rd[ 4 ] ) + return name + +############################################################################### + +def convert_radec_from_j2000( radec, epoch, m = 3.075, n = 1.336 ): + # TODO: make more accurate + # TODO: special cases for dec close to poles + [ ra, dec ] = radec + depoch = epoch - 2000. + mm = depoch * m * 15. / 3600. + nn = depoch * n * 15. / 3600. + dra = mm + nn * sin( radians( ra ) ) * tan( radians( dec ) ) + ddec = nn * cos( radians( ra ) ) + return [ amodulo( ra + dra, 360. ), dec + ddec ] + +############################################################################### + +def convert_b1950_to_j2000( radec ): +# based on Lieske (1976) + [ ra1, dec1 ] = [ aradians( radec[ 0 ] ), aradians( radec[ 1 ] ) ] + xyz1 = array( [ cos( ra1 ) * cos( dec1 ), sin( ra1 ) * cos( dec1 ), sin( dec1 ) ], dtype = float64 ) + rot = array( [ [ 0.9999257079523629, - 0.0111789381377700, - 0.0048590038153592 ], + [ 0.0111789381264276, 0.9999375133499888, - 0.0000271625947142 ], + [ 0.0048590038414544, - 0.0000271579262585, 0.9999881946023742 ] ], dtype = float64 ) + xyz2 = dot( rot, xyz1 ).tolist() + ra2 = amodulo( adegrees( atan2( xyz2[ 1 ], xyz2[ 0 ] ) ), 360. ) + dec2 = adegrees( max( - 1., min( 1., asin( xyz2[ 2 ] ) ) ) ) + return [ ra2, dec2 ] + +############################################################################### + +def convert_j2000_to_b1950( radec ): +# based on Lieske (1976) + [ ra1, dec1 ] = [ aradians( radec[ 0 ] ), aradians( radec[ 1 ] ) ] + xyz1 = array( [ cos( ra1 ) * cos( dec1 ), sin( ra1 ) * cos( dec1 ), sin( dec1 ) ], dtype = float64 ) + rot = array( [ [ 0.9999257079523629, 0.0111789381264276, 0.0048590038414544 ], + [ - 0.0111789381377700, 0.9999375133499888, - 0.0000271579262585 ], + [ - 0.0048590038153592, - 0.0000271625947142, 0.9999881946023742 ] ], dtype = float64 ) + xyz2 = dot( rot, xyz1 ).tolist() + ra2 = amodulo( adegrees( atan2( xyz2[ 1 ], xyz2[ 0 ] ) ), 360. ) + dec2 = adegrees( asin( max( - 1., min( 1., xyz2[ 2 ] ) ) ) ) + return [ ra2, dec2 ] + +############################################################################### + +def hmsdms_to_degdeg( hmsdms ): +# if __sphere: +# return _sphere.hmsdms_to_degdeg( [ float( x ) for x in hmsdms ] ) + ra_h = amodulo( hmsdms[ 0 ], 24. ) + ra = 15. * ( ra_h + ( hmsdms[ 1 ] / 60. ) + ( hmsdms[ 2 ] / 3600. ) ) + dec_d = asign( hmsdms[ 3 ] ) * degrees( asin( + max( - 1., min( 1., sin( radians( amodulo( fabs( hmsdms[ 3 ] ), 360. ) ) ) ) ) ) ) + dec = dec_d + asign( dec_d ) * ( ( hmsdms[ 4 ] / 60. ) + ( hmsdms[ 5 ] / 3600. ) ) + if ( dec > 90. ): + dec = 90. + elif ( dec < - 90. ): + dec = - 90. + return [ ra, dec ] + +############################################################################### + +def degdeg_to_hmsdms( degdeg, precision = None ): +# if __sphere: +# return _sphere.degdeg_to_hmsdms( [ float( x ) for x in degdeg ] ) + ra_deg = amodulo( degdeg[ 0 ], 360. ) + ra_h = floor( ra_deg / 15. ) + ra_m = floor( 60. * ( ( ra_deg / 15. ) - ra_h ) ) + ra_s = 3600. * ( ( ra_deg / 15. ) - ra_h - ( ra_m / 60. ) ) + dec_deg = asign( degdeg[ 1 ] ) * degrees( asin( + max( - 1., min( 1., sin( radians( amodulo( fabs( degdeg[ 1 ] ), 360. ) ) ) ) ) ) ) + dec_d = asign( dec_deg ) * floor( abs( dec_deg ) ) + dec_m = floor( 60. * abs( dec_deg - dec_d ) ) + dec_s = 3600. * ( abs( dec_deg - dec_d ) - ( dec_m / 60. ) ) + if ( precision != None ): + if ( len( shape( precision ) ) == 0 ): + prec1 = int( precision ) + prec2 = int( precision ) + elif ( len( precision ) == 1 ): + prec1 = int( precision[ 0 ] ) + prec2 = int( precision[ 0 ] ) + else: + prec1 = int( precision[ 0 ] ) + prec2 = int( precision[ 1 ] ) + ra_s = around( ra_s, decimals = prec1 ) + dec_s = around( dec_s, decimals = prec2 ) + if ( ra_s >= 60. ): + ra_s = ra_s - 60. + ra_m = ra_m + 1. + if ( ra_m >= 60. ): + ra_m = ra_m - 60. + ra_h = ra_h + 1. + if ( ra_h >= 24. ): + ra_h = ra_h - 24. + if ( dec_s >= 60. ): + dec_s = dec_s - 60. + dec_m = dec_m + 1. + if ( dec_m >= 60. ): + dec_m = dec_m - 60. + if ( asign( dec_deg ) > 0. ): + dec_d = dec_d + 1. + if ( dec_d == 90. ): + dec_s = 0. + dec_m = 0. + else: + dec_d = dec_d - 1. + if ( dec_d == - 90. ): + dec_s = 0. + dec_m = 0. + return [ ra_h, ra_m, ra_s, dec_d, dec_m, dec_s ] + +############################################################################### + +def degdeg_to_dmsdms( degdeg, precision = None ): +# if __sphere: +# return _sphere.degdeg_to_dmsdms( [ float( x ) for x in degdeg ] ) + lon_deg = amodulo( degdeg[ 0 ] + 180., 360. ) - 180. + lon_d = asign( lon_deg ) * floor( abs( lon_deg ) ) + lon_m = floor( 60. * abs( lon_deg - lon_d ) ) + lon_s = 3600. * ( abs( lon_deg - lon_d ) - ( lon_m / 60. ) ) + lat_deg = degrees( asin( + max( - 1., min( 1., sin( radians( amodulo( degdeg[ 1 ], 360. ) ) ) ) ) ) ) + lat_d = asign( lat_deg ) * floor( abs( lat_deg ) ) + lat_m = floor( 60. * abs( lat_deg - lat_d ) ) + lat_s = 3600. * ( abs( lat_deg - lat_d ) - ( lat_m / 60. ) ) + if ( precision != None ): + if ( len( shape( precision ) ) == 0 ): + prec1 = int( precision ) + prec2 = int( precision ) + elif ( len( precision ) == 1 ): + prec1 = int( precision[ 0 ] ) + prec2 = int( precision[ 0 ] ) + else: + prec1 = int( precision[ 0 ] ) + prec2 = int( precision[ 1 ] ) + lon_s = around( lon_s, decimals = prec1 ) + lat_s = around( lat_s, decimals = prec2 ) + if ( lon_s >= 60. ): + lon_s = lon_s - 60. + lon_m = lon_m + 1. + if ( lon_m >= 60. ): + lon_m = lon_m - 60. + lon_d = lon_d + 1. + if ( lon_d >= 360. ): + lon_d = lon_d - 360. + if ( lat_s >= 60. ): + lat_s = lat_s - 60. + lat_m = lat_m + 1. + if ( lat_m >= 60. ): + lat_m = lat_m - 60. + if ( asign( lat_deg ) > 0. ): + lat_d = lat_d + 1. + if ( lat_d == 90. ): + lat_s = 0. + lat_m = 0. + else: + lat_d = dec_d - 1. + if ( lat_d == - 90. ): + lat_s = 0. + lat_m = 0. + return( [ lon_d, lon_m, lon_s, lat_d, lat_m, lat_s ] ) + +############################################################################### + +def calculate_angular_separation( lonlat0, lonlat1 ): + me = pyrap.measures.measures() + lon0 = pyrap.quanta.quantity( lonlat0[ 0 ], 'rad' ) + lat0 = pyrap.quanta.quantity( lonlat0[ 1 ], 'rad' ) + lon1 = pyrap.quanta.quantity( lonlat1[ 0 ], 'rad' ) + lat1 = pyrap.quanta.quantity( lonlat1[ 1 ], 'rad' ) + direction1 = me.direction('', lon0, lat0 ) + direction2 = me.direction('', lon1, lat1 ) + separation = me.separation(direction1, direction2).get_value('rad') + angle = me.posangle(direction1, direction2).get_value('rad') + + return [ separation, angle ] + +############################################################################### + +def calculate_offset_position( degdeg, radius, angle ): +# 0. <= radius <= 180. + if __sphere: + return _sphere.calculate_offset_position( [ float( x ) for x in degdeg ], + float( radius ), float( angle ) ) + ra = degdeg[ 0 ] + dec = degdeg[ 1 ] + if ( radius <= 0. ): + new_ra = ra + new_dec = dec + else: + a = radians( radius ) + c = radians( 90. - dec ) + B = radians( - angle ) + b = acos( max( - 1., min( 1., sin( a ) * cos( B ) * sin( c ) + cos( a ) * cos( c ) ) ) ) + if ( b == 0. ): + A = 0. + else: + A = asin( max( - 1., min( 1., sin( a ) * sin( B ) / sin( b ) ) ) ) + if ( ( ( cos( a ) * sin( c ) - sin( a ) * cos( B ) * cos( c ) ) / sin( b ) ) < 0. ): + A = pi - A + new_ra = amodulo( ra - degrees( A ), 360. ) + new_dec = 90. - degrees( b ) + return [ new_ra, new_dec ] + +############################################################################### + +def xyz_to_llr( xyz ): + if __sphere: + return _sphere.xyz_to_llr( [ float( x ) for x in xyz ] ) + x = xyz[ 0 ] + y = xyz[ 1 ] + z = xyz[ 2 ] + lon = amodulo( degrees( atan2( y, x ) ) + 180., 360. ) - 180. + lat = degrees( atan2( z, sqrt( x**2 + y**2 ) ) ) + rad = sqrt( x**2 + y**2 + z**2 ) + return [ lon, lat, rad ] + +############################################################################### + +def xyz_to_geo_llh( xyz, time ): +# default Earth ellipticity definition (a,f) is WGS (1984) +# Note that longitude is defined as positive towards east, just like RA + + [ x, y, z ] = xyz + me = pyrap.measures.measures() + x = pyrap.quanta.quantity(x, 'm') + y = pyrap.quanta.quantity(y, 'm') + z = pyrap.quanta.quantity(z, 'm') + pos_itrf = me.position( 'itrf', x, y, z ) + + t = pyrap.quanta.quantity(time, 's') + t1 = me.epoch('utc', t) + me.doframe(t1) + + pos_wgs84 = me.measure(pos_itrf, 'wgs84') + glon = pos_wgs84['m0']['value'] + glat = pos_wgs84['m1']['value'] + gh = pos_wgs84['m2']['value'] + + #[ x, y, z ] = xyz + #glon = atan2( y, x ) + #glat = atan2( z, sqrt( x**2 + y**2 ) ) + #gh = sqrt( x**2 + y**2 + z**2 ) - a * sqrt( 1. - f ) + #if ( iterations > 0 ): + #phi = glat + #for i in range( iterations ): + #n = a / sqrt( 1. - e2 * ( sin( phi )**2 ) ) + #gh = ( sqrt( x**2 + y**2 ) / cos( phi ) ) - n + #phi = atan( z / ( sqrt( x**2 + y**2 ) * ( 1. - e2 * ( n / ( n + gh ) ) ) ) ) + #glat = phi + + return [ glon, glat, gh ] + +############################################################################### + +def geo_llh_to_xyz( geo_llh, a = 6378137., f = 1. / 298.257, e2 = 6.6943799013e-3 ): +# default Earth ellipticity definition (a,f) is WGS (1984) +# Note that longitude is defined as positive towards east, just like RA + if __sphere: + return _sphere.geo_llh_to_xyz( [ float( x ) for x in geo_llh ], float( a ), + float( f ), float( e2 ) ) + [ glon, glat, gh ] = geo_llh + lamda = radians( glon ) + phi = radians( glat ) + n = a / sqrt( 1. - e2 * ( sin( phi )**2 ) ) + x = ( n + gh ) * cos( phi ) * cos( lamda ) + y = ( n + gh ) * cos( phi ) * sin( lamda ) + z = ( n * ( 1. - e2 ) + gh ) * sin( phi ) + return [ x, y, z ] + +############################################################################### + +def calculate_hour_angles_at_elevation_limit( lat, dec, elevation_limit = 0. ): + if __sphere: + return _sphere.calculate_hour_angles_at_elevation_limit( float( lat ), float( dec ), + float( elevation_limit ) ) + if ( ( dec + lat >= 90. ) or ( dec + lat <= - 90. ) ): # check for circumpolar sources + ha = 180. + elif ( ( dec - lat >= 90. ) or ( dec - lat <= - 90. ) ): # check for non-visible sources + ha = 0. + else: + a = radians( 90. - elevation_limit ) + b = radians( 90. - dec ) # 0 < b < 180 + c = radians( 90. - lat ) # 0 < c < 180 + A = acos( max( - 1., min( 1., ( cos( a ) - cos( b ) * cos( c ) ) / ( sin( b ) * sin( c ) ) ) ) ) + # 0 < A < 180 degrees + ha = degrees( A ) + return [ - ha, ha ] + +############################################################################### + +def time_to_dhms( time ): + if __sphere: + return _sphere.time_to_dhms( float( time ) ) + res = abs( time ) + day = sign( time ) * floor( res ) + res = 24. ( res - day ) + hour = floor( res ) + res = 60. * ( res - hour ) + mins = floor( res ) + sec = 60. * ( res - mins ) + return [ day, hour, mins, sec ] + +############################################################################### + +def dhms_to_time( dhms ): +# if __sphere: +# return _sphere.dhms_to_time( [ float( x ) for x in dhms ] ) + [ day, hour, mins, sec ] = dhms + time = float( day ) + ( float( hour ) / 24. ) + ( float( mins ) / 1440. ) + ( float( sec ) / 86400. ) + return time + +############################################################################### + +def calculate_enu( ref_xyz, xyz ): + rot_xyz = array( xyz, dtype = float64 ) + ref_geo_llh = xyz_to_geo_llh( ref_xyz ) + ref_lon = radians( ref_geo_llh[ 0 ] ) + ref_lat = radians( ref_geo_llh[ 1 ] ) + rot = array( [ [ - sin( ref_lon ) , cos( ref_lon ) , 0. ], + [ - cos( ref_lon ) * sin( ref_lat ), - sin( ref_lon ) * sin( ref_lat ), cos( ref_lat ) ], + [ cos( ref_lon ) * cos( ref_lat ), sin( ref_lon ) * cos( ref_lat ), sin( ref_lat ) ] ], + dtype = float64 ) + rot_xyz = dot( rot, rot_xyz ) + return rot_xyz.tolist() + +############################################################################### + +def calculate_local_sky_position( geo_xyz, radec, time ): + me = pyrap.measures.measures() + x = pyrap.quanta.quantity(geo_xyz[0], 'm') + y = pyrap.quanta.quantity(geo_xyz[1], 'm') + z = pyrap.quanta.quantity(geo_xyz[2], 'm') + position = me.position( 'itrf', x, y, z ) + me.doframe( position ) + RA = pyrap.quanta.quantity( radec[0], 'rad' ) + dec = pyrap.quanta.quantity( radec[1], 'rad' ) + direction = me.direction( 'j2000', RA, dec ) + t = pyrap.quanta.quantity(time, 's') + t1 = me.epoch('utc', t) + me.doframe(t1) + a = me.measure(direction, 'azelgeo') + azimuth = a['m0']['value'] + elevation = a['m1']['value'] + zenith_angle = pi/2 - elevation + + return [ zenith_angle, azimuth ] + +############################################################################### + +def calculate_puncture_point( xyz, radec, time, height = 400.e3, iterations = 4 ): +# height in meters +# radec at J2000 + + # initialize some variables + ant_xyz = array( xyz, dtype = float64 ) + ant_geo_llh = xyz_to_geo_llh( xyz ) + ant_lon = ant_geo_llh[ 0 ] + ant_lat = ant_geo_llh[ 1 ] + ant_lh = ant_geo_llh[ 2 ] + if ( ant_lh > height ): + raise error( 'specified location has a height larger than the puncture layer' ) + rot = array( [ [ - sin( ant_lon ) , cos( ant_lon ) , 0. ], + [ - cos( ant_lon ) * sin( ant_lat ), - sin( ant_lon ) * sin( ant_lat ), cos( ant_lat ) ], + [ cos( ant_lon ) * cos( ant_lat ), sin( ant_lon ) * cos( ant_lat ), sin( ant_lat ) ] ], + dtype = float64 ) + ant_za_az = calculate_local_sky_position( ant_xyz, radec, time ) + ant_za = ant_za_az[ 0 ] + ant_az = ant_za_az[ 1 ] + len2_ant_xyz = ( ant_xyz**2 ).sum() + local_src_dxyz = array( [ sin( ant_za ) * sin( ant_az ), sin( ant_za ) * cos( ant_az ), cos( ant_za ) ], + dtype = float64 ) + src_dxyz = dot( local_src_dxyz, rot ) + + # determine xyz coordinates of puncture point through vector algebra + B = 2. * ( ant_xyz * src_dxyz ).sum() + len2_pp_xyz = ( sqrt( len2_ant_xyz ) + ( height - ant_lh ) )**2 + for i in range( iterations ): + C = len2_ant_xyz - len2_pp_xyz # always < 0 + len_src_xyz = ( sqrt( B**2 - 4. * C ) - B ) / 2. # always > 0 + src_xyz = len_src_xyz * src_dxyz + pp_xyz = ant_xyz + src_xyz + len_pp_xyz = sqrt( ( pp_xyz**2 ).sum() ) + pp_geo_llh = xyz_to_geo_llh( pp_xyz.tolist() ) + dlen_pp_xyz = height - pp_geo_llh[ 2 ] + len2_pp_xyz = ( len_pp_xyz + dlen_pp_xyz )**2 + C = len2_ant_xyz - len2_pp_xyz # always < 0 + len_src_xyz = ( sqrt( B**2 - 4. * C ) - B ) / 2. # always > 0 + src_xyz = len_src_xyz * src_dxyz + pp_xyz = ant_xyz + src_xyz + + # determine zenith angle at puncture point + pp_geo_llh = xyz_to_geo_llh( pp_xyz.tolist() ) + [ separation, angle ] = calculate_angular_separation( ant_geo_llh[ 0 : 2 ], pp_geo_llh[ 0 : 2 ] ) + pp_za = ant_za_az[ 0 ] - separation + + return [ pp_xyz.tolist(), float( pp_za ) ] + +############################################################################### +def calculate_puncture_point_mevius( xyz, radec, time, height = 400.e3): +# height in meters +# radec at J2000 + + # initialize some variables + ant_xyz = array( xyz, dtype = float64 ) + ant_geo_llh = xyz_to_geo_llh( xyz, time ) + ant_lon = ant_geo_llh[ 0 ] + ant_lat = ant_geo_llh[ 1 ] + ant_lh = ant_geo_llh[ 2 ] + if ( ant_lh > height ): + raise error( 'specified location has a height larger than the puncture layer' ) + + rot = array( [ [ - sin( ant_lon ) , cos( ant_lon ) , 0. ], + [ - cos( ant_lon ) * sin( ant_lat ), - sin( ant_lon ) * sin( ant_lat ), cos( ant_lat ) ], + [ cos( ant_lon ) * cos( ant_lat ), sin( ant_lon ) * cos( ant_lat ), sin( ant_lat ) ] ], + dtype = float64 ) + ant_za_az = calculate_local_sky_position( ant_xyz, radec, time ) + ant_za = ant_za_az[ 0 ] + ant_az = ant_za_az[ 1 ] + len_ant_xyz = sqrt(( ant_xyz**2 ).sum()) + + # This expression gives some sort of local earth radius, but the result is + # inconisistent with the local curvature of the earth + R_earth = len_ant_xyz - ant_lh + + R_pp = R_earth + height + + pp_za = arcsin(sin(ant_za)*len_ant_xyz / R_pp) + + len_src_xyz = R_pp*sin(ant_za - pp_za)/sin(ant_za) + + local_src_dxyz = array( [ sin( ant_za ) * sin( ant_az ), sin( ant_za ) * cos( ant_az ), cos( ant_za ) ], + dtype = float64 ) + src_dxyz = dot( local_src_dxyz, rot ) + src_xyz = len_src_xyz * src_dxyz + pp_xyz = ant_xyz + src_xyz + + return [ pp_xyz.tolist(), float( pp_za ) ] + +############################################################################### + diff --git a/CEP/Calibration/StationResponse/CMakeLists.txt b/CEP/Calibration/StationResponse/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..16350a206a86c1e877f491e6a2e865ddebdf9386 --- /dev/null +++ b/CEP/Calibration/StationResponse/CMakeLists.txt @@ -0,0 +1,14 @@ +# $Id$ + +lofar_package(StationResponse 0.1 DEPENDS Common ElementResponse) + +include(LofarFindPackage) +lofar_find_package(Boost REQUIRED) +lofar_find_package(Casacore REQUIRED COMPONENTS casa measures ms tables images coordinates) + +# Uncomment to check for unsafe conversions (gcc), for example conversion of +# size_t to unsigned int (truncation). +#add_definitions(-Wconversion) + +add_subdirectory(include/StationResponse) +add_subdirectory(src) diff --git a/CEP/Calibration/StationResponse/include/StationResponse/AntennaField.h b/CEP/Calibration/StationResponse/include/StationResponse/AntennaField.h new file mode 100644 index 0000000000000000000000000000000000000000..934e0b2247251b8628449391c62277ffb526a319 --- /dev/null +++ b/CEP/Calibration/StationResponse/include/StationResponse/AntennaField.h @@ -0,0 +1,261 @@ +//# AntennaField.h: Representation of a LOFAR antenna field, with methods to +//# compute its response to incoming radiation. +//# +//# Copyright (C) 2013 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR software suite. +//# The LOFAR software suite 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 3 of the License, or +//# (at your option) any later version. +//# +//# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id$ + +#ifndef LOFAR_STATIONRESPONSE_ARRAYFIELD_H +#define LOFAR_STATIONRESPONSE_ARRAYFIELD_H + +// \file +// Representation of a LOFAR antenna field, with methods to compute its response +// to incoming radiation. + +#include <Common/lofar_smartptr.h> +#include <Common/lofar_string.h> +#include <Common/lofar_vector.h> +#include <StationResponse/Constants.h> +#include <StationResponse/Types.h> +#include <StationResponse/ITRFDirection.h> + +namespace LOFAR +{ +namespace StationResponse +{ + +// \addtogroup StationResponse +// @{ + +/** + * \brief Base class that represents a generic LOFAR antenna field. + */ +class AntennaField +{ +public: + typedef shared_ptr<AntennaField> Ptr; + typedef shared_ptr<const AntennaField> ConstPtr; + + /** + * \brief Antenna field coordinate system. + * + * A right handed, cartesian, local coordinate system with coordinate axes + * \p p, \p q, and \p r is associated with each antenna field. + * + * The r-axis is orthogonal to the antenna field, and points towards the + * local pseudo zenith. + * + * The q-axis is the northern bisector of the \p X and \p Y dipoles, i.e. + * it is the reference direction from which the orientation of the dual + * dipole antennae is determined. The q-axis points towards the North at + * the core. At remote sites it is defined as the intersection of the + * antenna field plane and a plane parallel to the meridian plane at the + * core. This ensures the reference directions at all sites are similar. + * + * The p-axis is orthogonal to both other axes, and points towards the East + * at the core. + * + * The axes and origin of the anntena field coordinate system are expressed + * as vectors in the geocentric, cartesian, ITRF coordinate system, in + * meters. + * + * \sa "LOFAR Reference Plane and Reference Direction", M.A. Brentjens, + * LOFAR-ASTRON-MEM-248. + */ + struct CoordinateSystem + { + struct Axes + { + vector3r_t p; + vector3r_t q; + vector3r_t r; + }; + + vector3r_t origin; + Axes axes; + }; + + /** A single antenna. */ + struct Antenna + { + /** + * \brief Position of the antenna relative to the antenna field center + * (origin). This is a vector expressed in the geocentric, cartesian, + * ITRF coordinate system, in meters. + */ + vector3r_t position; + + /** + * \brief Status of the \p X and \p Y signal paths of the antenna, + * respectively. + */ + bool enabled[2]; + }; + + typedef vector<Antenna> AntennaList; + + + AntennaField(const string &name, const CoordinateSystem &coordinates); + + virtual ~AntennaField(); + + /** Return the name of the antenna field. */ + const string &name() const; + + /** Return the phase reference position of the antenna field. */ + const vector3r_t &position() const; + + /** Return the antenna field coordinate system. */ + const CoordinateSystem &coordinates() const; + + /** Add an antenna to the antenna field. */ + void addAntenna(const Antenna &antenna); + + /** Return the number of antennae in the antenna field. */ + size_t nAntennae() const; + + /*! + * \name Antennae accessors + * These member functions provide access to the antennae that are part of + * the antenna field. + */ + // @{ + + /** Return a read-only reference to the antenna with the requested index. */ + const Antenna &antenna(size_t n) const; + + /** Return a writeable reference to the antenna with the requested index. */ + Antenna &antenna(size_t n); + + /** Return a read-only iterator that points to the first antenna of the + * antenna field. + */ + AntennaList::const_iterator beginAntennae() const; + + /** Return a read-only iterator that points one position past the last + * antenna of the antenna field. + */ + AntennaList::const_iterator endAntennae() const; + + // @} + + /*! + * \brief Compute the response of the antenna field for a plane wave of + * frequency \p freq, arriving from direction \p direction, with the analog + * %tile beam former steered towards \p direction0. For LBA antenna fields, + * \p direction0 has no effect. + * + * \param time Time, modified Julian date, UTC, in seconds (MJD(UTC), s). + * \param freq Frequency of the plane wave (Hz). + * \param direction Direction of arrival (ITRF, m). + * \param direction0 Tile beam former reference direction (ITRF, m). + * \return Jones matrix that represents the response of the antenna field. + * + * The directions \p direction, and \p direction0 are vectors that + * represent a direction of \e arrival. These vectors have unit length and + * point \e from the ground \e towards the direction from which the plane + * wave arrives. + */ + virtual matrix22c_t response(real_t time, real_t freq, + const vector3r_t &direction, const vector3r_t &direction0) const; + + /*! + * \brief Compute the array factor of the antenna field for a plane wave of + * frequency \p freq, arriving from direction \p direction, analog %tile + * beam former steered towards \p direction0. For LBA antenna fields, + * \p direction0 has no effect. + * + * \param time Time, modified Julian date, UTC, in seconds (MJD(UTC), s). + * \param freq Frequency of the plane wave (Hz). + * \param direction Direction of arrival (ITRF, m). + * \param direction0 Tile beam former reference direction (ITRF, m). + * \return A diagonal matrix with the array factor of the X and Y antennae. + * + * The directions \p direction, and \p direction0 are vectors that + * represent a direction of \e arrival. These vectors have unit length and + * point \e from the ground \e towards the direction from which the plane + * wave arrives. + */ + virtual diag22c_t arrayFactor(real_t time, real_t freq, + const vector3r_t &direction, const vector3r_t &direction0) const; + + /*! + * \brief Compute the response of the antenna field for a plane wave of + * frequency \p freq, arriving from direction \p direction, with the analog + * %tile beam former steered towards \p direction0. For LBA antenna fields, + * \p direction0 has no effect. + * + * This method returns the non-normalized (raw) response. This allows the + * response of several antenna fields to be summed together, followed by + * normalization of the sum. + * + * \see response(real_t time, real_t freq, const vector3r_t &direction, + * const vector3r_t &direction0) const + */ + virtual raw_response_t rawResponse(real_t time, real_t freq, + const vector3r_t &direction, const vector3r_t &direction0) const; + + /*! + * \brief Compute the array factor of the antenna field for a plane wave of + * frequency \p freq, arriving from direction \p direction, analog %tile + * beam former steered towards \p direction0. For LBA antenna fields, + * \p direction0 has no effect. + * + * This method returns the non-normalized (raw) array factor. This allows + * the array factor of several antenna fields to be summed together, + * followed by normalization of the sum. + * + * \see diag22c_t arrayFactor(real_t time, real_t freq, + * const vector3r_t &direction, const vector3r_t &direction0) const + */ + virtual raw_array_factor_t rawArrayFactor(real_t time, real_t freq, + const vector3r_t &direction, const vector3r_t &direction0) const = 0; + + /*! + * \brief Compute the response of a single antenna for a plane wave of + * frequency \p freq, arriving from direction \p direction. + * + */ + virtual matrix22c_t elementResponse(real_t time, real_t freq, + const vector3r_t &direction) const = 0; + +protected: + /** Compute the parallactic rotation. */ + matrix22r_t rotation(real_t time, const vector3r_t &direction) const; + + /** Transform a vector from ITRF to antenna field coordinates. */ + vector3r_t itrf2field(const vector3r_t &itrf) const; + +private: + vector3r_t ncp(real_t time) const; + + string itsName; + CoordinateSystem itsCoordinateSystem; + AntennaList itsAntennae; + ITRFDirection::Ptr itsNCP; + mutable real_t itsNCPCacheTime; + mutable vector3r_t itsNCPCacheDirection; +}; + +// @} + +} //# namespace StationResponse +} //# namespace LOFAR + +#endif diff --git a/CEP/Calibration/StationResponse/include/StationResponse/AntennaFieldHBA.h b/CEP/Calibration/StationResponse/include/StationResponse/AntennaFieldHBA.h new file mode 100644 index 0000000000000000000000000000000000000000..6caafee496441b05685dafa17c97a54e51bdd7a4 --- /dev/null +++ b/CEP/Calibration/StationResponse/include/StationResponse/AntennaFieldHBA.h @@ -0,0 +1,73 @@ +//# AntennaFieldHBA.h: Representation of an HBA antenna field. +//# +//# Copyright (C) 2013 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR software suite. +//# The LOFAR software suite 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 3 of the License, or +//# (at your option) any later version. +//# +//# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id$ + +#ifndef LOFAR_STATIONRESPONSE_ANTENNAFIELDHBA_H +#define LOFAR_STATIONRESPONSE_ANTENNAFIELDHBA_H + +// \file +// Representation of an HBA antenna field. + +#include <StationResponse/AntennaField.h> +#include <StationResponse/AntennaModelHBA.h> + +namespace LOFAR +{ +namespace StationResponse +{ + +// \addtogroup StationResponse +// @{ + +class AntennaFieldHBA: public AntennaField +{ +public: + typedef shared_ptr<AntennaFieldHBA> Ptr; + typedef shared_ptr<const AntennaFieldHBA> ConstPtr; + + AntennaFieldHBA(const string &name, const CoordinateSystem &coordinates, + const AntennaModelHBA::ConstPtr &model); + + virtual matrix22c_t response(real_t time, real_t freq, + const vector3r_t &direction, const vector3r_t &direction0) const; + + virtual diag22c_t arrayFactor(real_t time, real_t freq, + const vector3r_t &direction, const vector3r_t &direction0) const; + + virtual raw_response_t rawResponse(real_t time, real_t freq, + const vector3r_t &direction, const vector3r_t &direction0) const; + + virtual raw_array_factor_t rawArrayFactor(real_t time, real_t freq, + const vector3r_t &direction, const vector3r_t &direction0) const; + + virtual matrix22c_t elementResponse(real_t time, real_t freq, + const vector3r_t &direction) const; + +private: + AntennaModelHBA::ConstPtr itsAntennaModel; +}; + +// @} + +} //# namespace StationResponse +} //# namespace LOFAR + +#endif diff --git a/CEP/Calibration/StationResponse/include/StationResponse/AntennaFieldLBA.h b/CEP/Calibration/StationResponse/include/StationResponse/AntennaFieldLBA.h new file mode 100644 index 0000000000000000000000000000000000000000..f4c082542910e7bf94109bed6ab3ff3c828d8c94 --- /dev/null +++ b/CEP/Calibration/StationResponse/include/StationResponse/AntennaFieldLBA.h @@ -0,0 +1,64 @@ +//# AntennaFieldLBA.h: Representation of an LBA antenna field. +//# +//# Copyright (C) 2013 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR software suite. +//# The LOFAR software suite 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 3 of the License, or +//# (at your option) any later version. +//# +//# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id$ + +#ifndef LOFAR_STATIONRESPONSE_ANTENNAFIELDLBA_H +#define LOFAR_STATIONRESPONSE_ANTENNAFIELDLBA_H + +// \file +// Representation of an LBA antenna field. + +#include <StationResponse/AntennaField.h> +#include <StationResponse/AntennaModelLBA.h> + +namespace LOFAR +{ +namespace StationResponse +{ + +// \addtogroup StationResponse +// @{ + +class AntennaFieldLBA: public AntennaField +{ +public: + typedef shared_ptr<AntennaFieldLBA> Ptr; + typedef shared_ptr<const AntennaFieldLBA> ConstPtr; + + AntennaFieldLBA(const string &name, const CoordinateSystem &coordinates, + const AntennaModelLBA::ConstPtr &model); + + virtual raw_array_factor_t rawArrayFactor(real_t time, real_t freq, + const vector3r_t &direction, const vector3r_t &direction0) const; + + virtual matrix22c_t elementResponse(real_t time, real_t freq, + const vector3r_t &direction) const; + +private: + AntennaModelLBA::ConstPtr itsAntennaModel; +}; + +// @} + +} //# namespace StationResponse +} //# namespace LOFAR + +#endif diff --git a/CEP/Calibration/StationResponse/include/StationResponse/AntennaModelHBA.h b/CEP/Calibration/StationResponse/include/StationResponse/AntennaModelHBA.h new file mode 100644 index 0000000000000000000000000000000000000000..9ef452c0d5be39b56da9b69004a737018dfdca8d --- /dev/null +++ b/CEP/Calibration/StationResponse/include/StationResponse/AntennaModelHBA.h @@ -0,0 +1,69 @@ +//# AntennaModelHBA.h: HBA antenna model interface definitions. +//# +//# Copyright (C) 2013 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR software suite. +//# The LOFAR software suite 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 3 of the License, or +//# (at your option) any later version. +//# +//# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id$ + +#ifndef LOFAR_STATIONRESPONSE_ANTENNAMODELHBA_H +#define LOFAR_STATIONRESPONSE_ANTENNAMODELHBA_H + +// \file +// HBA antenna model interface definitions. + +#include <StationResponse/Types.h> +#include <Common/lofar_smartptr.h> + +namespace LOFAR +{ +namespace StationResponse +{ + +// \addtogroup StationResponse +// @{ + +class AntennaModelHBA +{ +public: + typedef shared_ptr<AntennaModelHBA> Ptr; + typedef shared_ptr<const AntennaModelHBA> ConstPtr; + + virtual ~AntennaModelHBA(); + + virtual matrix22c_t response(real_t freq, const vector3r_t &direction, + const vector3r_t &direction0) const; + + virtual diag22c_t arrayFactor(real_t freq, const vector3r_t &direction, + const vector3r_t &direction0) const; + + virtual raw_response_t rawResponse(real_t freq, const vector3r_t &direction, + const vector3r_t &direction0) const; + + virtual raw_array_factor_t rawArrayFactor(real_t freq, + const vector3r_t &direction, const vector3r_t &direction0) const = 0; + + virtual matrix22c_t elementResponse(real_t freq, + const vector3r_t &direction) const = 0; +}; + +// @} + +} //# namespace StationResponse +} //# namespace LOFAR + +#endif diff --git a/CEP/Calibration/StationResponse/include/StationResponse/AntennaModelLBA.h b/CEP/Calibration/StationResponse/include/StationResponse/AntennaModelLBA.h new file mode 100644 index 0000000000000000000000000000000000000000..7b6bfa543c633284dd52813866bbe6fa9c2ac9d4 --- /dev/null +++ b/CEP/Calibration/StationResponse/include/StationResponse/AntennaModelLBA.h @@ -0,0 +1,57 @@ +//# AntennaModelLBA.h: LBA antenna model interface definitions. +//# +//# Copyright (C) 2013 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR software suite. +//# The LOFAR software suite 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 3 of the License, or +//# (at your option) any later version. +//# +//# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id$ + +#ifndef LOFAR_STATIONRESPONSE_ANTENNAMODELLBA_H +#define LOFAR_STATIONRESPONSE_ANTENNAMODELLBA_H + +// \file +// LBA antenna model interface definitions. + +#include <StationResponse/Types.h> +#include <Common/lofar_smartptr.h> + +namespace LOFAR +{ +namespace StationResponse +{ + +// \addtogroup StationResponse +// @{ + +class AntennaModelLBA +{ +public: + typedef shared_ptr<AntennaModelLBA> Ptr; + typedef shared_ptr<const AntennaModelLBA> ConstPtr; + + virtual ~AntennaModelLBA(); + + virtual matrix22c_t response(real_t freq, const vector3r_t &direction) + const = 0; +}; + +// @} + +} //# namespace StationResponse +} //# namespace LOFAR + +#endif diff --git a/CEP/Calibration/StationResponse/include/StationResponse/CMakeLists.txt b/CEP/Calibration/StationResponse/include/StationResponse/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..b4dc8dff3c3c708ec21a53a78bc8c087148742f7 --- /dev/null +++ b/CEP/Calibration/StationResponse/include/StationResponse/CMakeLists.txt @@ -0,0 +1,23 @@ +# $Id$ + +# Create symbolic link to include directory. +execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_BINARY_DIR}/include/${PACKAGE_NAME}) + +# Install header files. +install(FILES + AntennaField.h + AntennaFieldHBA.h + AntennaFieldLBA.h + AntennaModelHBA.h + AntennaModelLBA.h + Constants.h + DualDipoleAntenna.h + ITRFDirection.h + LofarMetaDataUtil.h + MathUtil.h + Station.h + TileAntenna.h + Types.h + DESTINATION include/${PACKAGE_NAME}) diff --git a/CEP/Calibration/StationResponse/include/StationResponse/Constants.h b/CEP/Calibration/StationResponse/include/StationResponse/Constants.h new file mode 100644 index 0000000000000000000000000000000000000000..c77be6d22cdbe63369c4f9cc3a6c9d45745d62a4 --- /dev/null +++ b/CEP/Calibration/StationResponse/include/StationResponse/Constants.h @@ -0,0 +1,60 @@ +//# Constants.h: %Constants used in this library. +//# +//# Copyright (C) 2013 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR software suite. +//# The LOFAR software suite 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 3 of the License, or +//# (at your option) any later version. +//# +//# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id$ + +#ifndef LOFAR_STATIONRESPONSE_CONSTANTS_H +#define LOFAR_STATIONRESPONSE_CONSTANTS_H + +// \file +// %Constants used in this library. + +#include <StationResponse/Types.h> + +namespace LOFAR +{ +namespace StationResponse +{ + +// \addtogroup StationResponse +// @{ + +/** %Constants used in this library. */ +namespace Constants +{ +/** 2.0 * pi */ +const real_t _2pi = 6.283185307179586476925286; + +/** pi / 2.0 */ +const real_t pi_2 = 1.570796326794896619231322; + +/** pi / 4.0 */ +const real_t pi_4 = 0.7853981633974483096156608; + +/** Speed of light (m/s) */ +const real_t c = 2.99792458e+08; +} //# namespace Constants + +// @} + +} //# namespace StationResponse +} //# namespace LOFAR + +#endif diff --git a/CEP/Calibration/StationResponse/include/StationResponse/DualDipoleAntenna.h b/CEP/Calibration/StationResponse/include/StationResponse/DualDipoleAntenna.h new file mode 100644 index 0000000000000000000000000000000000000000..4ac6439fc6e7572464a4f29cb72362b2d3d5df96 --- /dev/null +++ b/CEP/Calibration/StationResponse/include/StationResponse/DualDipoleAntenna.h @@ -0,0 +1,55 @@ +//# DualDipoleAntenna.h: Semi-analytical model of a LOFAR LBA dual dipole +//# antenna. +//# +//# Copyright (C) 2013 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR software suite. +//# The LOFAR software suite 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 3 of the License, or +//# (at your option) any later version. +//# +//# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id$ + +#ifndef LOFAR_STATIONRESPONSE_DUALDIPOLEANTENNA_H +#define LOFAR_STATIONRESPONSE_DUALDIPOLEANTENNA_H + +// \file +// Semi-analytical model of a LOFAR LBA dual dipole antenna. + +#include <StationResponse/AntennaModelLBA.h> + +namespace LOFAR +{ +namespace StationResponse +{ + +// \addtogroup StationResponse +// @{ + +class DualDipoleAntenna: public AntennaModelLBA +{ +public: + typedef shared_ptr<DualDipoleAntenna> Ptr; + typedef shared_ptr<const DualDipoleAntenna> ConstPtr; + + virtual matrix22c_t response(real_t freq, const vector3r_t &direction) + const; +}; + +// @} + +} //# namespace StationResponse +} //# namespace LOFAR + +#endif diff --git a/CEP/Calibration/StationResponse/include/StationResponse/ITRFDirection.h b/CEP/Calibration/StationResponse/include/StationResponse/ITRFDirection.h new file mode 100644 index 0000000000000000000000000000000000000000..280a6111a99443ddbe180c8eb45bdd5a72906d45 --- /dev/null +++ b/CEP/Calibration/StationResponse/include/StationResponse/ITRFDirection.h @@ -0,0 +1,65 @@ +//# ITRFDirection.h: Functor that maps time to an ITRF direction. +//# +//# Copyright (C) 2013 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR software suite. +//# The LOFAR software suite 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 3 of the License, or +//# (at your option) any later version. +//# +//# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id$ + +#ifndef LOFAR_STATIONRESPONSE_ITRFDIRECTION_H +#define LOFAR_STATIONRESPONSE_ITRFDIRECTION_H + +// \file +// Functor that maps time to an ITRF direction. + +#include <StationResponse/Types.h> +#include <Common/lofar_smartptr.h> + +#include <casacore/measures/Measures/MeasFrame.h> +#include <casacore/measures/Measures/MeasConvert.h> +#include <casacore/measures/Measures/MCDirection.h> + +namespace LOFAR +{ +namespace StationResponse +{ + +// \addtogroup StationResponse +// @{ + +class ITRFDirection +{ +public: + typedef shared_ptr<ITRFDirection> Ptr; + typedef shared_ptr<const ITRFDirection> ConstPtr; + + ITRFDirection(const vector3r_t &position, const vector2r_t &direction); + ITRFDirection(const vector3r_t &position, const vector3r_t &direction); + + vector3r_t at(real_t time) const; + +private: + mutable casacore::MeasFrame itsFrame; + mutable casacore::MDirection::Convert itsConverter; +}; + +// @} + +} //# namespace BBS +} //# namespace LOFAR + +#endif diff --git a/CEP/Calibration/StationResponse/include/StationResponse/LofarMetaDataUtil.h b/CEP/Calibration/StationResponse/include/StationResponse/LofarMetaDataUtil.h new file mode 100644 index 0000000000000000000000000000000000000000..57327b0ed0eb3c0bfbdd7857ef71b4335822a401 --- /dev/null +++ b/CEP/Calibration/StationResponse/include/StationResponse/LofarMetaDataUtil.h @@ -0,0 +1,65 @@ +//# LofarMetaDataUtil.h: Utility functions to read the meta data relevant for +//# simulating the beam from LOFAR observations stored in MS format. +//# +//# Copyright (C) 2013 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR software suite. +//# The LOFAR software suite 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 3 of the License, or +//# (at your option) any later version. +//# +//# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id$ + +#ifndef LOFAR_STATIONRESPONSE_LOFARMETADATAUTIL_H +#define LOFAR_STATIONRESPONSE_LOFARMETADATAUTIL_H + +// \file +// Utility functions to read the meta data relevant for simulating the beam from +// LOFAR observations stored in MS format. + +#include <StationResponse/Station.h> +#include <casacore/ms/MeasurementSets/MeasurementSet.h> +#include <casacore/ms/MeasurementSets/MSAntennaColumns.h> +#include <casacore/measures/Measures/MDirection.h> + +namespace LOFAR +{ +namespace StationResponse +{ + +// \addtogroup StationResponse +// @{ + +Station::Ptr readStation(const casacore::MeasurementSet &ms, unsigned int id); + +template <typename T> +void readStations(const casacore::MeasurementSet &ms, T out_it) +{ + casacore::ROMSAntennaColumns antenna(ms.antenna()); + for(unsigned int i = 0; i < antenna.nrow(); ++i) + { + *out_it++ = readStation(ms, i); + } +} + +// Read the tile beam direction from a LOFAR MS. If it is not defined, +// this function returns the delay center. +casacore::MDirection readTileBeamDirection(const casacore::MeasurementSet &ms); + +// @} + +} //# namespace StationResponse +} //# namespace LOFAR + +#endif diff --git a/CEP/Calibration/StationResponse/include/StationResponse/MathUtil.h b/CEP/Calibration/StationResponse/include/StationResponse/MathUtil.h new file mode 100644 index 0000000000000000000000000000000000000000..cb16fb898c2e0f9e229b7253823d53525fcb502a --- /dev/null +++ b/CEP/Calibration/StationResponse/include/StationResponse/MathUtil.h @@ -0,0 +1,172 @@ +//# MathUtil.h: Various mathematical operations on vectors and matrices. +//# +//# Copyright (C) 2013 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR software suite. +//# The LOFAR software suite 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 3 of the License, or +//# (at your option) any later version. +//# +//# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id$ + +#ifndef LOFAR_STATIONRESPONSE_MATHUTIL_H +#define LOFAR_STATIONRESPONSE_MATHUTIL_H + +// \file +// Various mathematical operations on vectors and matrices. + +#include <StationResponse/Constants.h> +#include <StationResponse/Types.h> +#include <Common/lofar_math.h> + +namespace LOFAR +{ +namespace StationResponse +{ + +// \addtogroup StationResponse +// @{ + +inline real_t dot(const vector3r_t &arg0, const vector3r_t &arg1) +{ + return arg0[0] * arg1[0] + arg0[1] * arg1[1] + arg0[2] * arg1[2]; +} + +inline double norm(const vector3r_t &arg0) +{ + return sqrt(dot(arg0, arg0)); +} + +inline vector3r_t operator*(real_t arg0, const vector3r_t arg1) +{ + vector3r_t result = {{arg0 * arg1[0], arg0 * arg1[1], arg0 * arg1[2]}}; + return result; +} + +inline vector3r_t normalize(const vector3r_t &arg0) +{ + return 1.0 / norm(arg0) * arg0; +} + +inline vector2r_t cart2thetaphi(const vector3r_t &cart) +{ + real_t r = sqrt(cart[0] * cart[0] + cart[1] * cart[1]); + vector2r_t thetaphi = {{Constants::pi_2 - atan2(cart[2], r), atan2(cart[1], + cart[0])}}; + return thetaphi; +} + +inline vector3r_t thetaphi2cart(const vector2r_t &thetaphi) +{ + real_t r = sin(thetaphi[0]); + vector3r_t cart = {{r * cos(thetaphi[1]), r * sin(thetaphi[1]), + cos(thetaphi[0])}}; + return cart; +} + +// returns az, el, r. +inline vector3r_t cart2sph(const vector3r_t &cart) +{ + real_t r = sqrt(cart[0] * cart[0] + cart[1] * cart[1]); + + vector3r_t sph; + sph[0] = atan2(cart[1], cart[0]); + sph[1] = atan2(cart[2], r); + sph[2] = norm(cart); + return sph; +} + +// expects az, el, r. +inline vector3r_t sph2cart(const vector3r_t &sph) +{ + vector3r_t cart = {{sph[2] * cos(sph[1]) * cos(sph[0]), sph[2] * cos(sph[1]) + * sin(sph[0]), sph[2] * sin(sph[1])}}; + return cart; +} + +inline matrix22c_t operator*(const matrix22c_t &arg0, const matrix22r_t &arg1) +{ + matrix22c_t result; + result[0][0] = arg0[0][0] * arg1[0][0] + arg0[0][1] * arg1[1][0]; + result[0][1] = arg0[0][0] * arg1[0][1] + arg0[0][1] * arg1[1][1]; + result[1][0] = arg0[1][0] * arg1[0][0] + arg0[1][1] * arg1[1][0]; + result[1][1] = arg0[1][0] * arg1[0][1] + arg0[1][1] * arg1[1][1]; + return result; +} + +inline vector3r_t cross(const vector3r_t &arg0, const vector3r_t &arg1) +{ + vector3r_t result; + result[0] = arg0[1] * arg1[2] - arg0[2] * arg1[1]; + result[1] = arg0[2] * arg1[0] - arg0[0] * arg1[2]; + result[2] = arg0[0] * arg1[1] - arg0[1] * arg1[0]; + return result; +} + +inline vector3r_t operator+(const vector3r_t &arg0, const vector3r_t &arg1) +{ + vector3r_t result = {{arg0[0] + arg1[0], arg0[1] + arg1[1], + arg0[2] + arg1[2]}}; + return result; +} + +inline vector3r_t operator-(const vector3r_t &arg0, const vector3r_t &arg1) +{ + vector3r_t result = {{arg0[0] - arg1[0], arg0[1] - arg1[1], + arg0[2] - arg1[2]}}; + return result; +} + +inline matrix22c_t normalize(const raw_response_t &raw) +{ + matrix22c_t response = {{ {{}}, {{}} }}; + + if(raw.weight[0] != 0.0) + { + response[0][0] = raw.response[0][0] / raw.weight[0]; + response[0][1] = raw.response[0][1] / raw.weight[0]; + } + + if(raw.weight[1] != 0.0) + { + response[1][0] = raw.response[1][0] / raw.weight[1]; + response[1][1] = raw.response[1][1] / raw.weight[1]; + } + + return response; +} + +inline diag22c_t normalize(const raw_array_factor_t &raw) +{ + diag22c_t af = {{}}; + + if(raw.weight[0] != 0.0) + { + af[0] = raw.factor[0] / raw.weight[0]; + } + + if(raw.weight[1] != 0.0) + { + af[1] = raw.factor[1] / raw.weight[1]; + } + + return af; +} + +// @} + +} //# namespace StationResponse +} //# namespace LOFAR + +#endif diff --git a/CEP/Calibration/StationResponse/include/StationResponse/Station.h b/CEP/Calibration/StationResponse/include/StationResponse/Station.h new file mode 100644 index 0000000000000000000000000000000000000000..0019c33dcdf0c9577e4b0e9ec9184096812ab710 --- /dev/null +++ b/CEP/Calibration/StationResponse/include/StationResponse/Station.h @@ -0,0 +1,361 @@ +//# Station.h: Representation of the station beam former. +//# +//# Copyright (C) 2013 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR software suite. +//# The LOFAR software suite 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 3 of the License, or +//# (at your option) any later version. +//# +//# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id$ + +#ifndef LOFAR_STATIONRESPONSE_STATION_H +#define LOFAR_STATIONRESPONSE_STATION_H + +// \file +// Representation of the station beam former. + +#include <Common/lofar_smartptr.h> +#include <Common/lofar_string.h> +#include <Common/lofar_vector.h> +#include <StationResponse/AntennaField.h> +#include <StationResponse/Types.h> + +namespace LOFAR +{ +namespace StationResponse +{ + +// \addtogroup StationResponse +// @{ + +class Station +{ +public: + typedef shared_ptr<Station> Ptr; + typedef shared_ptr<const Station> ConstPtr; + typedef vector<AntennaField::ConstPtr> FieldList; + + /*! + * \brief Construct a new Station instance. + * + * \param name Name of the station. + * \param position Position of the station (ITRF, m). + */ + Station(const string &name, const vector3r_t &position); + + /*! + * \brief Return the name of the station. + */ + const string &name() const; + + /*! + * \brief Return the position of the station (ITRF, m). + */ + const vector3r_t &position() const; + + /*! + * \brief Set the phase reference position. This is the position where the + * delay of the incoming plane wave is assumed to be zero. + * + * \param reference Phase reference position (ITRF, m). + * + * By default, it is assumed the position of the station is also the phase + * reference position. Use this method to set the phase reference position + * explicitly when this assumption is false. + */ + void setPhaseReference(const vector3r_t &reference); + + /*! + * \brief Return the phase reference position (ITRF, m). + * + * \see Station::setPhaseReference() + */ + const vector3r_t &phaseReference() const; + + /*! + * \brief Add an antenna field to the station. + * + * Physical %LOFAR stations consist of an LBA field, and either one (remote + * and international stations) or two (core stations) HBA fields. Virtual + * %LOFAR stations can consist of a combination of the antenna fields of + * several physical stations. + * + * Use this method to add the appropriate antenna fields to the station. + */ + void addField(const AntennaField::ConstPtr &field); + + + /*! + * \brief Return the number of available antenna fields. + */ + size_t nFields() const; + + /*! + * \brief Return the requested antenna field. + * + * \param i Antenna field number (0-based). + * \return An AntennaField::ConstPtr to the requested AntennaField + * instance, or an empty AntennaField::ConstPtr if \p i is out of bounds. + */ + AntennaField::ConstPtr field(size_t i) const; + + /*! + * \brief Return an iterator that points to the beginning of the list of + * antenna fields. + */ + FieldList::const_iterator beginFields() const; + + /*! + * \brief Return an iterator that points to the end of the list of antenna + * fields. + */ + FieldList::const_iterator endFields() const; + + /*! + * \brief Compute the response of the station for a plane wave of frequency + * \p freq, arriving from direction \p direction, with the %station beam + * former steered towards \p station0, and, for HBA stations, the analog + * %tile beam former steered towards \p tile0. For LBA stations, \p tile0 + * has no effect. + * + * \param time Time, modified Julian date, UTC, in seconds (MJD(UTC), s). + * \param freq Frequency of the plane wave (Hz). + * \param direction Direction of arrival (ITRF, m). + * \param freq0 %Station beam former reference frequency (Hz). + * \param station0 %Station beam former reference direction (ITRF, m). + * \param tile0 Tile beam former reference direction (ITRF, m). + * \return Jones matrix that represents the %station response. + * + * For any given sub-band, the %LOFAR station beam former computes weights + * for a single reference frequency. Usually, this reference frequency is + * the center frequency of the sub-band. For any frequency except the + * reference frequency, these weights are an approximation. This aspect of + * the system is taken into account in the computation of the response. + * Therefore, both the frequency of interest \p freq and the reference + * frequency \p freq0 need to be specified. + * + * The directions \p direction, \p station0, and \p tile0 are vectors that + * represent a direction of \e arrival. These vectors have unit length and + * point \e from the ground \e towards the direction from which the plane + * wave arrives. + */ + matrix22c_t response(real_t time, real_t freq, const vector3r_t &direction, + real_t freq0, const vector3r_t &station0, const vector3r_t &tile0) + const; + + /*! + * \brief Compute the array factor of the station for a plane wave of + * frequency \p freq, arriving from direction \p direction, with the + * %station beam former steered towards \p station0, and, for HBA stations + * the analog %tile beam former steered towards \p tile0. For LBA stations, + * \p tile0 has no effect. + * + * \param time Time, modified Julian date, UTC, in seconds (MJD(UTC), s). + * \param freq Frequency of the plane wave (Hz). + * \param direction Direction of arrival (ITRF, m). + * \param freq0 %Station beam former reference frequency (Hz). + * \param station0 %Station beam former reference direction (ITRF, m). + * \param tile0 Tile beam former reference direction (ITRF, m). + * \return A diagonal matrix with the array factor of the X and Y antennae. + * + * For any given sub-band, the %LOFAR station beam former computes weights + * for a single reference frequency. Usually, this reference frequency is + * the center frequency of the sub-band. For any frequency except the + * reference frequency, these weights are an approximation. This aspect of + * the system is taken into account in the computation of the response. + * Therefore, both the frequency of interest \p freq and the reference + * frequency \p freq0 need to be specified. + * + * The directions \p direction, \p station0, and \p tile0 are vectors that + * represent a direction of \e arrival. These vectors have unit length and + * point \e from the ground \e towards the direction from which the plane + * wave arrives. + */ + diag22c_t arrayFactor(real_t time, real_t freq, const vector3r_t &direction, + real_t freq0, const vector3r_t &station0, const vector3r_t &tile0) + const; + + /*! + * \name Convenience member functions + * These member functions perform the same function as the corresponding + * non-template member functions, for a list of frequencies or (frequency, + * reference frequency) pairs. + */ + // @{ + + /*! + * \brief Convenience method to compute the response of the station for a + * list of frequencies, and a fixed reference frequency. + * + * \param count Number of frequencies. + * \param time Time, modified Julian date, UTC, in seconds (MJD(UTC), s). + * \param freq Input iterator for a list of frequencies (Hz) of length + * \p count. + * \param direction Direction of arrival (ITRF, m). + * \param freq0 %Station beam former reference frequency (Hz). + * \param station0 %Station beam former reference direction (ITRF, m). + * \param tile0 Tile beam former reference direction (ITRF, m). + * \param buffer Output iterator with room for \p count instances of type + * ::matrix22c_t. + * + * \see response(real_t time, real_t freq, const vector3r_t &direction, + * real_t freq0, const vector3r_t &station0, const vector3r_t &tile0) const + */ + template <typename T, typename U> + void response(unsigned int count, real_t time, T freq, + const vector3r_t &direction, real_t freq0, const vector3r_t &station0, + const vector3r_t &tile0, U buffer) const; + + /*! + * \brief Convenience method to compute the array factor of the station for + * a list of frequencies, and a fixed reference frequency. + * + * \param count Number of frequencies. + * \param time Time, modified Julian date, UTC, in seconds (MJD(UTC), s). + * \param freq Input iterator for a list of frequencies (Hz) of length + * \p count. + * \param direction Direction of arrival (ITRF, m). + * \param freq0 %Station beam former reference frequency (Hz). + * \param station0 %Station beam former reference direction (ITRF, m). + * \param tile0 Tile beam former reference direction (ITRF, m). + * \param buffer Output iterator with room for \p count instances of type + * ::diag22c_t. + * + * \see arrayFactor(real_t time, real_t freq, const vector3r_t &direction, + * real_t freq0, const vector3r_t &station0, const vector3r_t &tile0) const + */ + template <typename T, typename U> + void arrayFactor(unsigned int count, real_t time, T freq, + const vector3r_t &direction, real_t freq0, const vector3r_t &station0, + const vector3r_t &tile0, U buffer) const; + + /*! + * \brief Convenience method to compute the response of the station for a + * list of (frequency, reference frequency) pairs. + * + * \param count Number of frequencies. + * \param time Time, modified Julian date, UTC, in seconds (MJD(UTC), s). + * \param freq Input iterator for a list of frequencies (Hz) of length + * \p count. + * \param direction Direction of arrival (ITRF, m). + * \param freq0 Input iterator for a list of %Station beam former reference + * frequencies (Hz) of length \p count. + * \param station0 %Station beam former reference direction (ITRF, m). + * \param tile0 Tile beam former reference direction (ITRF, m). + * \param buffer Output iterator with room for \p count instances of type + * ::matrix22c_t. + * + * \see response(real_t time, real_t freq, const vector3r_t &direction, + * real_t freq0, const vector3r_t &station0, const vector3r_t &tile0) const + */ + template <typename T, typename U> + void response(unsigned int count, real_t time, T freq, + const vector3r_t &direction, T freq0, const vector3r_t &station0, + const vector3r_t &tile0, U buffer) const; + + /*! + * \brief Convenience method to compute the array factor of the station for + * list of (frequency, reference frequency) pairs. + * + * \param count Number of frequencies. + * \param time Time, modified Julian date, UTC, in seconds (MJD(UTC), s). + * \param freq Input iterator for a list of frequencies (Hz) of length + * \p count. + * \param direction Direction of arrival (ITRF, m). + * \param freq0 %Station beam former reference frequency (Hz). + * \param station0 %Station beam former reference direction (ITRF, m). + * \param tile0 Tile beam former reference direction (ITRF, m). + * \param buffer Output iterator with room for \p count instances of type + * ::diag22c_t. + * + * \see arrayFactor(real_t time, real_t freq, const vector3r_t &direction, + * real_t freq0, const vector3r_t &station0, const vector3r_t &tile0) const + */ + template <typename T, typename U> + void arrayFactor(unsigned int count, real_t time, T freq, + const vector3r_t &direction, T freq0, const vector3r_t &station0, + const vector3r_t &tile0, U buffer) const; + + // @} + +private: + raw_array_factor_t fieldArrayFactor(const AntennaField::ConstPtr &field, + real_t time, real_t freq, const vector3r_t &direction, real_t freq0, + const vector3r_t &position0, const vector3r_t &direction0) const; + +private: + string itsName; + vector3r_t itsPosition; + vector3r_t itsPhaseReference; + FieldList itsFields; +}; + +// @} + +//# ------------------------------------------------------------------------- // +//# - Implementation: Station - // +//# ------------------------------------------------------------------------- // + +template <typename T, typename U> +void Station::response(unsigned int count, real_t time, T freq, + const vector3r_t &direction, real_t freq0, const vector3r_t &station0, + const vector3r_t &tile0, U buffer) const +{ + for(unsigned int i = 0; i < count; ++i) + { + *buffer++ = response(time, *freq++, direction, freq0, station0, tile0); + } +} + +template <typename T, typename U> +void Station::arrayFactor(unsigned int count, real_t time, T freq, + const vector3r_t &direction, real_t freq0, const vector3r_t &station0, + const vector3r_t &tile0, U buffer) const +{ + for(unsigned int i = 0; i < count; ++i) + { + *buffer++ = arrayFactor(time, *freq++, direction, freq0, station0, + tile0); + } +} + +template <typename T, typename U> +void Station::response(unsigned int count, real_t time, T freq, + const vector3r_t &direction, T freq0, const vector3r_t &station0, + const vector3r_t &tile0, U buffer) const +{ + for(unsigned int i = 0; i < count; ++i) + { + *buffer++ = response(time, *freq++, direction, *freq0++, station0, + tile0); + } +} + +template <typename T, typename U> +void Station::arrayFactor(unsigned int count, real_t time, T freq, + const vector3r_t &direction, T freq0, const vector3r_t &station0, + const vector3r_t &tile0, U buffer) const +{ + for(unsigned int i = 0; i < count; ++i) + { + *buffer++ = arrayFactor(time, *freq++, direction, *freq0++, station0, + tile0); + } +} + +} //# namespace StationResponse +} //# namespace LOFAR + +#endif diff --git a/CEP/Calibration/StationResponse/include/StationResponse/TileAntenna.h b/CEP/Calibration/StationResponse/include/StationResponse/TileAntenna.h new file mode 100644 index 0000000000000000000000000000000000000000..3695aa3e81657a3bcdbe5bfb91726287d5cf9040 --- /dev/null +++ b/CEP/Calibration/StationResponse/include/StationResponse/TileAntenna.h @@ -0,0 +1,68 @@ +//# TileAntenna.h: Semi-analytical model of a LOFAR HBA tile. +//# +//# Copyright (C) 2013 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR software suite. +//# The LOFAR software suite 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 3 of the License, or +//# (at your option) any later version. +//# +//# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id$ + +#ifndef LOFAR_STATIONRESPONSE_TILEANTENNA_H +#define LOFAR_STATIONRESPONSE_TILEANTENNA_H + +// \file +// Semi-analytical model of a LOFAR HBA tile. + +#include <StationResponse/AntennaModelHBA.h> + +namespace LOFAR +{ +namespace StationResponse +{ + +// \addtogroup StationResponse +// @{ + +class TileAntenna: public AntennaModelHBA +{ +public: + typedef shared_ptr<TileAntenna> Ptr; + typedef shared_ptr<const TileAntenna> ConstPtr; + + typedef static_array<vector3r_t, 16> TileConfig; + + explicit TileAntenna(const TileConfig &config); + + void setConfig(const TileConfig &config); + + const TileConfig &config() const; + + virtual raw_array_factor_t rawArrayFactor(real_t freq, + const vector3r_t &direction, const vector3r_t &direction0) const; + + virtual matrix22c_t elementResponse(real_t freq, + const vector3r_t &direction) const; + +private: + TileConfig itsConfig; +}; + +// @} + +} //# namespace StationResponse +} //# namespace LOFAR + +#endif diff --git a/CEP/Calibration/StationResponse/include/StationResponse/Types.h b/CEP/Calibration/StationResponse/include/StationResponse/Types.h new file mode 100644 index 0000000000000000000000000000000000000000..8565e127528dc692ae7750e4b9c4ee09c7cad037 --- /dev/null +++ b/CEP/Calibration/StationResponse/include/StationResponse/Types.h @@ -0,0 +1,234 @@ +//# Types.h: Types used in this library. +//# +//# Copyright (C) 2013 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR software suite. +//# The LOFAR software suite 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 3 of the License, or +//# (at your option) any later version. +//# +//# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id$ + +#ifndef LOFAR_STATIONRESPONSE_TYPES_H +#define LOFAR_STATIONRESPONSE_TYPES_H + +// \file +// Types used in this library. + +#include <Common/lofar_complex.h> +#include <Common/StreamUtil.h> + +namespace LOFAR +{ +namespace StationResponse +{ + +// \addtogroup StationResponse +// @{ + +/** + * \brief Array of static size. + * + * This struct wraps a (unidimensional) C array. Like C arrays, it is possible + * to use initializers, for example: + * \code + * int foo[3] = {1, 2, 3}; + * static_array<int, 3> bar = {{1, 2, 3}}; + * \endcode + * Note the \e double curly braces. + * + * Unlike C arrays, it is possible to return instances of this struct. + * \code + * static_array<int, 3> foo() + * { + * static_array<int, 3> bar = {{1, 2, 3}}; + * return bar; + * } + * \endcode + * \see boost::array + */ +template <typename T, size_t N> +struct static_array +{ + typedef T *iterator; + typedef const T *const_iterator; + + T __data[N]; + + /** Returns the size of the array. */ + static size_t size(); + + /** + * \brief Read-only access to a specific array element. + * \param n Index (0-based) of the element to access. + * \return A constant reference to the element at index \p n. + * + * This operator provides array-style element access. No range check is + * performed on the index \p n. The result of this operator is undefined + * for out of range indices. + */ + const T &operator[](size_t n) const; + + /** + * \brief Access to a specific array element. + * \param n Index (0-based) of the element to access. + * \return A non-constant reference to the element at index \p n. + * + * This operator provides array-style element access. No range check is + * performed on the index \p n. The result of this operator is undefined + * for out of range indices. + */ + T &operator[](size_t n); + + /** Returns an iterator that points to the first element in the array. */ + iterator begin(); + + /** Returns an iterator that points one past the last element in the + * array. + */ + iterator end(); + + /** Returns a read-only iterator that points to the first element in the + * array. + */ + const_iterator begin() const; + + /** Returns a read-only iterator that points one past the last element in + * the array. + */ + const_iterator end() const; +}; + +/** Print the contents of a static array. */ +template <typename T, size_t N> +ostream &operator<<(ostream &out, const static_array<T,N> &obj); + +/** Type used for real scalars. */ +typedef double real_t; + +/** Type used for complex scalars. */ +typedef dcomplex complex_t; + +/** Type used for 2-dimensional real vectors. */ +typedef static_array<real_t, 2> vector2r_t; + +/** Type used for 3-dimensional real vectors. */ +typedef static_array<real_t, 3> vector3r_t; + +/** Type used for 2x2 real diagonal matrices. */ +typedef static_array<real_t, 2> diag22r_t; + +/** Type used for 2x2 complex diagonal matrices. */ +typedef static_array<complex_t, 2> diag22c_t; + +/** Type used for 2x2 real matrices. */ +typedef static_array<static_array<real_t, 2>, 2> matrix22r_t; + +/** Type used for 2x2 complex matrices. */ +typedef static_array<static_array<complex_t, 2>, 2> matrix22c_t; + +/** Response of an array of antenna elements. */ +struct raw_response_t +{ + /** Combined response of all (enabled) antenna elements in the array. */ + matrix22c_t response; + + /** Number of antenna elements contributing to the combined response, per + * polarization. + */ + diag22r_t weight; +}; + +/** Array factor of an array of antenna elements. A wave front of an incident + * plane wave will arrive at each antenna element at a potentially different + * time. The time of arrival depends on the location of the antenna element and + * the direction of arrival of the plane wave. With respect to a pre-defined + * phase reference location, there is a (possibly negative) delay between the + * arrival of a wave front at a given antenna element and the arrival of the + * same wave front at the phase reference location. The array factor is the sum + * of the phase shifts due to these delays. It describes the "sensitivity" of + * the array as a function of direction. + */ +struct raw_array_factor_t +{ + /** Array factor due to all (enabled) antenna elements in the array. */ + diag22c_t factor; + + /** Number of antenna elements contributing to the array factor, per + * polarization. + */ + diag22r_t weight; +}; + +// @} + +//# ------------------------------------------------------------------------- // +//# - Implementation: static_array - // +//# ------------------------------------------------------------------------- // + +template <typename T, size_t N> +inline const T &static_array<T, N>::operator[](size_t n) const +{ + return __data[n]; +} + +template <typename T, size_t N> +inline T &static_array<T, N>::operator[](size_t n) +{ + return __data[n]; +} + +template <typename T, size_t N> +inline typename static_array<T, N>::iterator static_array<T, N>::begin() +{ + return __data; +} + +template <typename T, size_t N> +inline typename static_array<T, N>::iterator static_array<T, N>::end() +{ + return __data + N; +} + +template <typename T, size_t N> +inline typename static_array<T, N>::const_iterator static_array<T, N>::begin() + const +{ + return __data; +} + +template <typename T, size_t N> +inline typename static_array<T, N>::const_iterator static_array<T, N>::end() + const +{ + return __data + N; +} + +template <typename T, size_t N> +inline size_t static_array<T, N>::size() +{ + return N; +} + +template <typename T, size_t N> +ostream &operator<<(ostream &out, const static_array<T,N> &obj) +{ + print(out, obj.begin(), obj.end()); + return out; +} + +} //# namespace StationResponse +} //# namespace LOFAR + +#endif diff --git a/CEP/Calibration/StationResponse/src/AntennaField.cc b/CEP/Calibration/StationResponse/src/AntennaField.cc new file mode 100644 index 0000000000000000000000000000000000000000..d3ca5879353d523a70109c631d1046055f08b713 --- /dev/null +++ b/CEP/Calibration/StationResponse/src/AntennaField.cc @@ -0,0 +1,233 @@ +//# AntennaField.cc: Representation of a LOFAR antenna field, with methods to +//# compute its response to incoming radiation. +//# +//# Copyright (C) 2013 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR software suite. +//# The LOFAR software suite 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 3 of the License, or +//# (at your option) any later version. +//# +//# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id$ + +#include <lofar_config.h> +#include <StationResponse/AntennaField.h> +#include <StationResponse/Constants.h> +#include <StationResponse/MathUtil.h> +#include <ElementResponse/ElementResponse.h> +#include <casacore/measures/Measures/MeasFrame.h> + +namespace LOFAR +{ +namespace StationResponse +{ + +AntennaField::AntennaField(const string &name, + const CoordinateSystem &coordinates) + : itsName(name), + itsCoordinateSystem(coordinates), + itsNCPCacheTime(-1) +{ + vector3r_t ncp = {{0.0, 0.0, 1.0}}; + itsNCP.reset(new ITRFDirection(position(), ncp)); +} + +AntennaField::~AntennaField() +{ +} + +const string &AntennaField::name() const +{ + return itsName; +} + +const vector3r_t &AntennaField::position() const +{ + return itsCoordinateSystem.origin; +} + +const AntennaField::CoordinateSystem &AntennaField::coordinates() const +{ + return itsCoordinateSystem; +} + +void AntennaField::addAntenna(const Antenna &antenna) +{ + itsAntennae.push_back(antenna); +} + +size_t AntennaField::nAntennae() const +{ + return itsAntennae.size(); +} + +const AntennaField::Antenna &AntennaField::antenna(size_t n) const +{ + return itsAntennae[n]; +} + +AntennaField::Antenna &AntennaField::antenna(size_t n) +{ + return itsAntennae[n]; +} + +AntennaField::AntennaList::const_iterator AntennaField::beginAntennae() const +{ + return itsAntennae.begin(); +} + +AntennaField::AntennaList::const_iterator AntennaField::endAntennae() const +{ + return itsAntennae.end(); +} + +vector3r_t AntennaField::ncp(real_t time) const +{ + if(time != itsNCPCacheTime) + { + itsNCPCacheDirection = itsNCP->at(time); + itsNCPCacheTime = time; + } + + return itsNCPCacheDirection; +} + +vector3r_t AntennaField::itrf2field(const vector3r_t &itrf) const +{ + const CoordinateSystem::Axes &axes = itsCoordinateSystem.axes; + vector3r_t station = {{dot(axes.p, itrf), dot(axes.q, itrf), + dot(axes.r, itrf)}}; + return station; +} + +matrix22r_t AntennaField::rotation(real_t time, const vector3r_t &direction) + const +{ + // Compute the cross product of the NCP and the target direction. This + // yields a vector tangent to the celestial sphere at the target + // direction, pointing towards the East (the direction of +Y in the IAU + // definition, or positive right ascension). + // Test if the direction is equal to the NCP. If it is, take a random + // vector orthogonal to v1 (the east is not defined here). + vector3r_t v1; + if (abs(ncp(time)[0]-direction[0])<1e-9 && + abs(ncp(time)[1]-direction[1])<1e-9 && + abs(ncp(time)[2]-direction[2])<1e-9) { + // Make sure v1 is orthogonal to ncp(time). The first two components + // of v1 are arbitrary. + v1[0]=1.; + v1[1]=0.; + v1[2]=-(ncp(time)[0]*v1[0]+ncp(time)[1]*v1[1])/ncp(time)[2]; + v1=normalize(v1); + } else { + v1 = normalize(cross(ncp(time), direction)); + } + + // Compute the cross product of the antenna field normal (R) and the + // target direction. This yields a vector tangent to the topocentric + // spherical coordinate system at the target direction, pointing towards + // the direction of positive phi (which runs East over North around the + // pseudo zenith). + // Test if the normal is equal to the target direction. If it is, take + // a random vector orthogonal to the normal. + vector3r_t v2; + if (abs(itsCoordinateSystem.axes.r[0]-direction[0])<1e-9 && + abs(itsCoordinateSystem.axes.r[1]-direction[1])<1e-9 && + abs(itsCoordinateSystem.axes.r[2]-direction[2])<1e-9) { + // Make sure v2 is orthogonal to r. The first two components + // of v2 are arbitrary. + v2[0]=1.; + v2[1]=0.; + v2[2]=-(itsCoordinateSystem.axes.r[0]*v2[0]+ + itsCoordinateSystem.axes.r[1]*v2[1])/ + itsCoordinateSystem.axes.r[2]; + v2=normalize(v2); + } else { + v2 = normalize(cross(itsCoordinateSystem.axes.r, direction)); + } + + // Compute the cosine and sine of the parallactic angle, i.e. the angle + // between v1 and v2, both tangent to a latitude circle of their + // respective spherical coordinate systems. + real_t coschi = dot(v1, v2); + real_t sinchi = dot(cross(v1, v2), direction); + + // The input coordinate system is a right handed system with its third + // axis along the direction of propagation (IAU +Z). The output + // coordinate system is right handed as well, but its third axis points + // in the direction of arrival (i.e. exactly opposite). + // + // Because the electromagnetic field is always perpendicular to the + // direction of propagation, we only need to relate the (X, Y) axes of + // the input system to the corresponding (theta, phi) axes of the output + // system. + // + // To this end, we first rotate the input system around its third axis + // to align the Y axis with the phi axis. The X and theta axis are + // parallel after this rotation, but point in opposite directions. To + // align the X axis with the theta axis, we flip it. + // + // The Jones matrix to align the Y axis with the phi axis when these are + // separated by an angle phi (measured counter-clockwise around the + // direction of propagation, looking towards the origin), is given by: + // + // [ cos(phi) sin(phi)] + // [-sin(phi) cos(phi)] + // + // Here, cos(phi) and sin(phi) can be computed directly, without having + // to compute phi first (see the computation of coschi and sinchi + // above). + // + // Now, sinchi as computed above is opposite to sin(phi), because the + // direction used in the computation is the direction of arrival instead + // of the direction of propagation. Therefore, the sign of sinchi needs + // to be reversed. Furthermore, as explained above, the X axis has to be + // flipped to align with the theta axis. The Jones matrix returned from + // this function is therefore given by: + // + // [-coschi sinchi] + // [ sinchi coschi] + matrix22r_t rotation = {{{{-coschi, sinchi}}, {{sinchi, coschi}}}}; + return rotation; +} + +matrix22c_t AntennaField::response(real_t time, real_t freq, + const vector3r_t &direction, const vector3r_t &direction0) const +{ + return normalize(rawResponse(time, freq, direction, direction0)); +} + +diag22c_t AntennaField::arrayFactor(real_t time, real_t freq, + const vector3r_t &direction, const vector3r_t &direction0) const +{ + return normalize(rawArrayFactor(time, freq, direction, direction0)); +} + +raw_response_t AntennaField::rawResponse(real_t time, real_t freq, + const vector3r_t &direction, const vector3r_t &direction0) const +{ + raw_array_factor_t af = rawArrayFactor(time, freq, direction, direction0); + + raw_response_t result; + result.response = elementResponse(time, freq, direction); + result.response[0][0] *= af.factor[0]; + result.response[0][1] *= af.factor[0]; + result.response[1][0] *= af.factor[1]; + result.response[1][1] *= af.factor[1]; + result.weight = af.weight; + return result; +} + +} //# namespace StationResponse +} //# namespace LOFAR diff --git a/CEP/Calibration/StationResponse/src/AntennaFieldHBA.cc b/CEP/Calibration/StationResponse/src/AntennaFieldHBA.cc new file mode 100644 index 0000000000000000000000000000000000000000..ff099fb04a22ea2987f0f8c397f353bb69b66e6a --- /dev/null +++ b/CEP/Calibration/StationResponse/src/AntennaFieldHBA.cc @@ -0,0 +1,77 @@ +//# AntennaFieldHBA.cc: Representation of an HBA antenna field. +//# +//# Copyright (C) 2013 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR software suite. +//# The LOFAR software suite 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 3 of the License, or +//# (at your option) any later version. +//# +//# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id$ + +#include <lofar_config.h> +#include <StationResponse/AntennaFieldHBA.h> +#include <StationResponse/MathUtil.h> + +namespace LOFAR +{ +namespace StationResponse +{ + +AntennaFieldHBA::AntennaFieldHBA(const string &name, + const CoordinateSystem &coordinates, const AntennaModelHBA::ConstPtr &model) + : AntennaField(name, coordinates), + itsAntennaModel(model) +{ +} + +matrix22c_t AntennaFieldHBA::response(real_t time, real_t freq, + const vector3r_t &direction, const vector3r_t &direction0) const +{ + return itsAntennaModel->response(freq, itrf2field(direction), + itrf2field(direction0)) * rotation(time, direction); +} + +diag22c_t AntennaFieldHBA::arrayFactor(real_t, real_t freq, + const vector3r_t &direction, const vector3r_t &direction0) const +{ + return itsAntennaModel->arrayFactor(freq, itrf2field(direction), + itrf2field(direction0)); +} + +raw_response_t AntennaFieldHBA::rawResponse(real_t time, real_t freq, + const vector3r_t &direction, const vector3r_t &direction0) const +{ + raw_response_t result = itsAntennaModel->rawResponse(freq, + itrf2field(direction), itrf2field(direction0)); + result.response = result.response * rotation(time, direction); + return result; +} + +raw_array_factor_t AntennaFieldHBA::rawArrayFactor(real_t, real_t freq, + const vector3r_t &direction, const vector3r_t &direction0) const +{ + return itsAntennaModel->rawArrayFactor(freq, itrf2field(direction), + itrf2field(direction0)); +} + +matrix22c_t AntennaFieldHBA::elementResponse(real_t time, real_t freq, + const vector3r_t &direction) const +{ + return itsAntennaModel->elementResponse(freq, itrf2field(direction)) + * rotation(time, direction); +} + +} //# namespace StationResponse +} //# namespace LOFAR diff --git a/CEP/Calibration/StationResponse/src/AntennaFieldLBA.cc b/CEP/Calibration/StationResponse/src/AntennaFieldLBA.cc new file mode 100644 index 0000000000000000000000000000000000000000..90838c135e6790e2a9d757bf6a108a24d3933059 --- /dev/null +++ b/CEP/Calibration/StationResponse/src/AntennaFieldLBA.cc @@ -0,0 +1,54 @@ +//# AntennaFieldLBA.cc: Representation of an LBA antenna field. +//# +//# Copyright (C) 2013 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR software suite. +//# The LOFAR software suite 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 3 of the License, or +//# (at your option) any later version. +//# +//# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id$ + +#include <lofar_config.h> +#include <StationResponse/AntennaFieldLBA.h> +#include <StationResponse/MathUtil.h> + +namespace LOFAR +{ +namespace StationResponse +{ + +AntennaFieldLBA::AntennaFieldLBA(const string &name, + const CoordinateSystem &coordinates, const AntennaModelLBA::ConstPtr &model) + : AntennaField(name, coordinates), + itsAntennaModel(model) +{ +} + +raw_array_factor_t AntennaFieldLBA::rawArrayFactor(real_t, real_t, + const vector3r_t&, const vector3r_t&) const +{ + raw_array_factor_t af = {{{1.0, 1.0}}, {{1.0, 1.0}}}; + return af; +} + +matrix22c_t AntennaFieldLBA::elementResponse(real_t time, real_t freq, + const vector3r_t &direction) const +{ + return itsAntennaModel->response(freq, itrf2field(direction)) + * rotation(time, direction); +} + +} //# namespace StationResponse +} //# namespace LOFAR diff --git a/CEP/Calibration/StationResponse/src/AntennaModelHBA.cc b/CEP/Calibration/StationResponse/src/AntennaModelHBA.cc new file mode 100644 index 0000000000000000000000000000000000000000..52ca0accc5428c38aa73964bd74046d36dcda223 --- /dev/null +++ b/CEP/Calibration/StationResponse/src/AntennaModelHBA.cc @@ -0,0 +1,64 @@ +//# AntennaModelHBA.cc: HBA antenna model interface definitions. +//# +//# Copyright (C) 2013 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR software suite. +//# The LOFAR software suite 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 3 of the License, or +//# (at your option) any later version. +//# +//# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id$ + +#include <lofar_config.h> +#include <StationResponse/AntennaModelHBA.h> +#include <StationResponse/MathUtil.h> + +namespace LOFAR +{ +namespace StationResponse +{ + +AntennaModelHBA::~AntennaModelHBA() +{ +} + +matrix22c_t AntennaModelHBA::response(real_t freq, const vector3r_t &direction, + const vector3r_t &direction0) const +{ + return normalize(rawResponse(freq, direction, direction0)); +} + +diag22c_t AntennaModelHBA::arrayFactor(real_t freq, const vector3r_t &direction, + const vector3r_t &direction0) const +{ + return normalize(rawArrayFactor(freq, direction, direction0)); +} + +raw_response_t AntennaModelHBA::rawResponse(real_t freq, + const vector3r_t &direction, const vector3r_t &direction0) const +{ + raw_array_factor_t af = rawArrayFactor(freq, direction, direction0); + + raw_response_t result; + result.response = elementResponse(freq, direction); + result.response[0][0] *= af.factor[0]; + result.response[0][1] *= af.factor[0]; + result.response[1][0] *= af.factor[1]; + result.response[1][1] *= af.factor[1]; + result.weight = af.weight; + return result; +} + +} //# namespace StationResponse +} //# namespace LOFAR diff --git a/CEP/Calibration/StationResponse/src/AntennaModelLBA.cc b/CEP/Calibration/StationResponse/src/AntennaModelLBA.cc new file mode 100644 index 0000000000000000000000000000000000000000..87d02b945013406efe05d18c4481dc98a2651d42 --- /dev/null +++ b/CEP/Calibration/StationResponse/src/AntennaModelLBA.cc @@ -0,0 +1,36 @@ +//# AntennaModelLBA.cc: LBA antenna model interface definitions. +//# +//# Copyright (C) 2013 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR software suite. +//# The LOFAR software suite 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 3 of the License, or +//# (at your option) any later version. +//# +//# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id$ + +#include <lofar_config.h> +#include <StationResponse/AntennaModelLBA.h> + +namespace LOFAR +{ +namespace StationResponse +{ + +AntennaModelLBA::~AntennaModelLBA() +{ +} + +} //# namespace StationResponse +} //# namespace LOFAR diff --git a/CEP/Calibration/StationResponse/src/CMakeLists.txt b/CEP/Calibration/StationResponse/src/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..e31a7a54574b0e908c6ba2760cf49626696f1850 --- /dev/null +++ b/CEP/Calibration/StationResponse/src/CMakeLists.txt @@ -0,0 +1,20 @@ +# $Id$ + +include(LofarPackageVersion) + +lofar_add_library(stationresponse + Package__Version.cc + AntennaField.cc + AntennaFieldHBA.cc + AntennaFieldLBA.cc + AntennaModelHBA.cc + AntennaModelLBA.cc + DualDipoleAntenna.cc + ITRFDirection.cc + LofarMetaDataUtil.cc + MathUtil.cc + Station.cc + TileAntenna.cc + Types.cc) + +lofar_add_bin_program(makeresponseimage makeresponseimage.cc) diff --git a/CEP/Calibration/StationResponse/src/DualDipoleAntenna.cc b/CEP/Calibration/StationResponse/src/DualDipoleAntenna.cc new file mode 100644 index 0000000000000000000000000000000000000000..659d9f45ddb16ad04a66678d36fad174439fa152 --- /dev/null +++ b/CEP/Calibration/StationResponse/src/DualDipoleAntenna.cc @@ -0,0 +1,51 @@ +//# DualDipoleAntenna.cc: Semi-analytical model of a LOFAR LBA dual dipole +//# antenna. +//# +//# Copyright (C) 2013 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR software suite. +//# The LOFAR software suite 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 3 of the License, or +//# (at your option) any later version. +//# +//# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id$ + +#include <lofar_config.h> +#include <StationResponse/DualDipoleAntenna.h> +#include <StationResponse/Constants.h> +#include <StationResponse/MathUtil.h> +#include <ElementResponse/ElementResponse.h> + +namespace LOFAR +{ +namespace StationResponse +{ + +matrix22c_t DualDipoleAntenna::response(real_t freq, + const vector3r_t &direction) const +{ + // The positive X dipole direction is SW of the reference orientation, + // which translates to a phi coordinate of 5/4*pi in the topocentric + // spherical coordinate system. The phi coordinate is corrected for this + // offset before evaluating the antenna model. + vector2r_t thetaphi = cart2thetaphi(direction); + thetaphi[1] -= 5.0 * Constants::pi_4; + matrix22c_t response; + element_response_lba(freq, thetaphi[0], thetaphi[1], + reinterpret_cast<std::complex<double> (&)[2][2]>(response)); + return response; +} + +} //# namespace StationResponse +} //# namespace LOFAR diff --git a/CEP/Calibration/StationResponse/src/ITRFDirection.cc b/CEP/Calibration/StationResponse/src/ITRFDirection.cc new file mode 100644 index 0000000000000000000000000000000000000000..f7f9c49e02551bfac1b6afbdeb0c14acbeaa449d --- /dev/null +++ b/CEP/Calibration/StationResponse/src/ITRFDirection.cc @@ -0,0 +1,77 @@ +//# ITRFDirection.cc: Functor that maps time to an ITRF direction. +//# +//# Copyright (C) 2013 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR software suite. +//# The LOFAR software suite 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 3 of the License, or +//# (at your option) any later version. +//# +//# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id$ + +#include <lofar_config.h> +#include <StationResponse/ITRFDirection.h> + +#include <casacore/measures/Measures/MPosition.h> +#include <casacore/measures/Measures/MDirection.h> +#include <casacore/measures/Measures/MEpoch.h> + +namespace LOFAR +{ +namespace StationResponse +{ + +ITRFDirection::ITRFDirection(const vector3r_t &position, + const vector2r_t &direction) +{ + casacore::MVPosition mvPosition(position[0], position[1], position[2]); + casacore::MPosition mPosition(mvPosition, casacore::MPosition::ITRF); + itsFrame = casacore::MeasFrame(casacore::MEpoch(), mPosition); + + // Order of angles seems to be longitude (along the equator), lattitude + // (towards the pole). + casacore::MVDirection mvDirection(direction[0], direction[1]); + casacore::MDirection mDirection(mvDirection, casacore::MDirection::J2000); + itsConverter = casacore::MDirection::Convert(mDirection, + casacore::MDirection::Ref(casacore::MDirection::ITRF, itsFrame)); +} + +ITRFDirection::ITRFDirection(const vector3r_t &position, + const vector3r_t &direction) +{ + casacore::MVPosition mvPosition(position[0], position[1], position[2]); + casacore::MPosition mPosition(mvPosition, casacore::MPosition::ITRF); + itsFrame = casacore::MeasFrame(casacore::MEpoch(), mPosition); + + casacore::MVDirection mvDirection(direction[0], direction[1], direction[2]); + casacore::MDirection mDirection(mvDirection, casacore::MDirection::J2000); + itsConverter = casacore::MDirection::Convert(mDirection, + casacore::MDirection::Ref(casacore::MDirection::ITRF, itsFrame)); +} + +vector3r_t ITRFDirection::at(real_t time) const +{ + // Cannot use MeasFrame::resetEpoch(Double), because that assumes the + // argument is UTC in (fractional) days (MJD). + itsFrame.resetEpoch(casacore::Quantity(time, "s")); + + const casacore::MDirection &mITRF = itsConverter(); + const casacore::MVDirection &mvITRF = mITRF.getValue(); + + vector3r_t itrf = {{mvITRF(0), mvITRF(1), mvITRF(2)}}; + return itrf; +} + +} //# namespace StationResponse +} //# namespace LOFAR diff --git a/CEP/Calibration/StationResponse/src/LofarMetaDataUtil.cc b/CEP/Calibration/StationResponse/src/LofarMetaDataUtil.cc new file mode 100644 index 0000000000000000000000000000000000000000..4d96af8dfe4f3f0c1b7b945d625e66bc5d977282 --- /dev/null +++ b/CEP/Calibration/StationResponse/src/LofarMetaDataUtil.cc @@ -0,0 +1,335 @@ +//# LofarMetaDataUtil.cc: Utility functions to read the meta data relevant for +//# simulating the beam from LOFAR observations stored in MS format. +//# +//# Copyright (C) 2013 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR software suite. +//# The LOFAR software suite 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 3 of the License, or +//# (at your option) any later version. +//# +//# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id$ + +#include <lofar_config.h> +#include <StationResponse/LofarMetaDataUtil.h> +#include <StationResponse/AntennaFieldLBA.h> +#include <StationResponse/AntennaFieldHBA.h> +#include <StationResponse/MathUtil.h> +#include <StationResponse/TileAntenna.h> +#include <StationResponse/DualDipoleAntenna.h> + +#include <casacore/measures/Measures/MDirection.h> +#include <casacore/measures/Measures/MPosition.h> +#include <casacore/measures/Measures/MCDirection.h> +#include <casacore/measures/Measures/MCPosition.h> +#include <casacore/measures/Measures/MeasTable.h> +#include <casacore/measures/Measures/MeasConvert.h> +#include <casacore/measures/TableMeasures/ScalarMeasColumn.h> + +#include <stdexcept> + +#include <casacore/ms/MeasurementSets/MSAntenna.h> +#include <casacore/ms/MSSel/MSSelection.h> +#include <casacore/ms/MSSel/MSAntennaParse.h> +#include <casacore/ms/MeasurementSets/MSAntennaColumns.h> +#include <casacore/ms/MeasurementSets/MSDataDescription.h> +#include <casacore/ms/MeasurementSets/MSDataDescColumns.h> +#include <casacore/ms/MeasurementSets/MSField.h> +#include <casacore/ms/MeasurementSets/MSFieldColumns.h> +#include <casacore/ms/MeasurementSets/MSObservation.h> +#include <casacore/ms/MeasurementSets/MSObsColumns.h> +#include <casacore/ms/MeasurementSets/MSPolarization.h> +#include <casacore/ms/MeasurementSets/MSPolColumns.h> +#include <casacore/ms/MeasurementSets/MSSpectralWindow.h> +#include <casacore/ms/MeasurementSets/MSSpWindowColumns.h> + +namespace LOFAR +{ +namespace StationResponse +{ + +using namespace casacore; + +bool hasColumn(const Table &table, const string &column) +{ + return table.tableDesc().isColumn(column); +} + +bool hasSubTable(const Table &table, const string &name) +{ + return table.keywordSet().isDefined(name); +} + +Table getSubTable(const Table &table, const string &name) +{ + return table.keywordSet().asTable(name); +} + +TileAntenna::TileConfig readTileConfig(const Table &table, unsigned int row) +{ + ROArrayQuantColumn<Double> c_tile_offset(table, "TILE_ELEMENT_OFFSET", "m"); + + // Read tile configuration for HBA antenna fields. + Matrix<Quantity> aips_offset = c_tile_offset(row); + assert(aips_offset.ncolumn() == TileAntenna::TileConfig::size()); + + TileAntenna::TileConfig config; + for(unsigned int i = 0; i < config.size(); ++i) + { + config[i][0] = aips_offset(0, i).getValue(); + config[i][1] = aips_offset(1, i).getValue(); + config[i][2] = aips_offset(2, i).getValue(); + } + return config; +} + +void transformToFieldCoordinates(TileAntenna::TileConfig &config, + const AntennaField::CoordinateSystem::Axes &axes) +{ + for(unsigned int i = 0; i < config.size(); ++i) + { + const vector3r_t position = config[i]; + config[i][0] = dot(position, axes.p); + config[i][1] = dot(position, axes.q); + config[i][2] = dot(position, axes.r); + } +} + +AntennaField::CoordinateSystem readCoordinateSystemAartfaac( + const Table &table, unsigned int id) +{ + ROArrayQuantColumn<Double> c_position(table, "POSITION", "m"); + + // Read antenna field center (ITRF). + Vector<Quantity> aips_position = c_position(id); + assert(aips_position.size() == 3); + + vector3r_t position = {{aips_position(0).getValue(), + aips_position(1).getValue(), aips_position(2).getValue()}}; + + TableRecord keywordset = table.keywordSet(); + Matrix<double> aips_axes; + keywordset.get("AARTFAAC_COORDINATE_AXES", aips_axes); + assert(aips_axes.shape().isEqual(IPosition(2, 3, 3))); + + vector3r_t p = {{aips_axes(0, 0), aips_axes(1, 0), aips_axes(2, 0)}}; + vector3r_t q = {{aips_axes(0, 1), aips_axes(1, 1), aips_axes(2, 1)}}; + vector3r_t r = {{aips_axes(0, 2), aips_axes(1, 2), aips_axes(2, 2)}}; + + AntennaField::CoordinateSystem system = {position, {p, q, r}}; + + return system; +} + +AntennaField::CoordinateSystem readCoordinateSystem(const Table &table, + unsigned int id) +{ + ROArrayQuantColumn<Double> c_position(table, "POSITION", "m"); + ROArrayQuantColumn<Double> c_axes(table, "COORDINATE_AXES", "m"); + + // Read antenna field center (ITRF). + Vector<Quantity> aips_position = c_position(id); + assert(aips_position.size() == 3); + + vector3r_t position = {{aips_position(0).getValue(), + aips_position(1).getValue(), aips_position(2).getValue()}}; + + // Read antenna field coordinate axes (ITRF). + Matrix<Quantity> aips_axes = c_axes(id); + assert(aips_axes.shape().isEqual(IPosition(2, 3, 3))); + + vector3r_t p = {{aips_axes(0, 0).getValue(), aips_axes(1, 0).getValue(), + aips_axes(2, 0).getValue()}}; + vector3r_t q = {{aips_axes(0, 1).getValue(), aips_axes(1, 1).getValue(), + aips_axes(2, 1).getValue()}}; + vector3r_t r = {{aips_axes(0, 2).getValue(), aips_axes(1, 2).getValue(), + aips_axes(2, 2).getValue()}}; + + AntennaField::CoordinateSystem system = {position, {p, q, r}}; + return system; +} + +void readAntennae(const Table &table, unsigned int id, + const AntennaField::Ptr &field) +{ + ROArrayQuantColumn<Double> c_offset(table, "ELEMENT_OFFSET", "m"); + ROArrayColumn<Bool> c_flag(table, "ELEMENT_FLAG"); + + // Read element offsets and flags. + Matrix<Quantity> aips_offset = c_offset(id); + assert(aips_offset.shape().isEqual(IPosition(2, 3, aips_offset.ncolumn()))); + + Matrix<Bool> aips_flag = c_flag(id); + assert(aips_flag.shape().isEqual(IPosition(2, 2, aips_offset.ncolumn()))); + + for(size_t i = 0; i < aips_offset.ncolumn(); ++i) + { + AntennaField::Antenna antenna; + antenna.position[0] = aips_offset(0, i).getValue(); + antenna.position[1] = aips_offset(1, i).getValue(); + antenna.position[2] = aips_offset(2, i).getValue(); + antenna.enabled[0] = !aips_flag(0, i); + antenna.enabled[1] = !aips_flag(1, i); + field->addAntenna(antenna); + } +} + +AntennaField::Ptr readAntennaField(const Table &table, unsigned int id) +{ + AntennaField::Ptr field; + AntennaField::CoordinateSystem system = readCoordinateSystem(table, id); + + ROScalarColumn<String> c_name(table, "NAME"); + const string &name = c_name(id); + + if(name == "LBA") + { + DualDipoleAntenna::Ptr model(new DualDipoleAntenna()); + field = AntennaField::Ptr(new AntennaFieldLBA(name, system, model)); + } + else // HBA, HBA0, HBA1 + { + TileAntenna::TileConfig config = readTileConfig(table, id); + transformToFieldCoordinates(config, system.axes); + + TileAntenna::Ptr model(new TileAntenna(config)); + field = AntennaField::Ptr(new AntennaFieldHBA(name, system, model)); + } + + readAntennae(table, id, field); + return field; +} + +AntennaField::Ptr readAntennaFieldAartfaac(const Table &table, const string &ant_type, + unsigned int id) +{ + AntennaField::Ptr field; + AntennaField::CoordinateSystem system = readCoordinateSystemAartfaac(table, id); + + if (ant_type == "LBA") + { + DualDipoleAntenna::Ptr model(new DualDipoleAntenna()); + field = AntennaField::Ptr(new AntennaFieldLBA(ant_type, system, model)); + } + else // HBA + { + // TODO: implement this + throw std::runtime_error("HBA for Aartfaac is not implemented yet."); + } + + // Add only one antenna to the field (no offset, always enabled) + AntennaField::Antenna antenna; + antenna.position[0] = 0.; + antenna.position[1] = 0.; + antenna.position[2] = 0.; + antenna.enabled[0] = true; + antenna.enabled[1] = true; + + field->addAntenna(antenna); + + return field; +} + +void readStationPhaseReference(const Table &table, unsigned int id, + const Station::Ptr &station) +{ + const string columnName("LOFAR_PHASE_REFERENCE"); + if(hasColumn(table, columnName)) + { + ROScalarMeasColumn<MPosition> c_reference(table, columnName); + MPosition mReference = MPosition::Convert(c_reference(id), + MPosition::ITRF)(); + MVPosition mvReference = mReference.getValue(); + vector3r_t reference = {{mvReference(0), mvReference(1), + mvReference(2)}}; + + station->setPhaseReference(reference); + } +} + +Station::Ptr readStation(const MeasurementSet &ms, unsigned int id) +{ + ROMSAntennaColumns antenna(ms.antenna()); + assert(antenna.nrow() > id && !antenna.flagRow()(id)); + + // Get station name. + const string name(antenna.name()(id)); + + // Get station position (ITRF). + MPosition mPosition = MPosition::Convert(antenna.positionMeas()(id), + MPosition::ITRF)(); + MVPosition mvPosition = mPosition.getValue(); + const vector3r_t position = {{mvPosition(0), mvPosition(1), mvPosition(2)}}; + + // Create station. + Station::Ptr station(new Station(name, position)); + + // Read phase reference position (if available). + readStationPhaseReference(ms.antenna(), id, station); + + // Read antenna field information. + ROScalarColumn<String> telescope_name_col(getSubTable(ms, "OBSERVATION"), + "TELESCOPE_NAME"); + string telescope_name = telescope_name_col(0); + + if (telescope_name == "LOFAR") + { + Table tab_field = getSubTable(ms, "LOFAR_ANTENNA_FIELD"); + tab_field = tab_field(tab_field.col("ANTENNA_ID") == static_cast<Int>(id)); + + for(size_t i = 0; i < tab_field.nrow(); ++i) + { + station->addField(readAntennaField(tab_field, i)); + } + } + else if (telescope_name == "AARTFAAC") + { + ROScalarColumn<String> ant_type_col(getSubTable(ms, "OBSERVATION"), + "AARTFAAC_ANTENNA_TYPE"); + string ant_type = ant_type_col(0); + + Table tab_field = getSubTable(ms, "ANTENNA"); + station -> addField(readAntennaFieldAartfaac(tab_field, ant_type, id)); + } + + return station; +} + +MDirection readTileBeamDirection(const casacore::MeasurementSet &ms) { + MDirection tileBeamDir; + + Table fieldTable = getSubTable(ms, "FIELD"); + + if (fieldTable.nrow() != 1) { + throw std::runtime_error("MS has multiple fields, this does not work with the LOFAR beam library."); + } + + if (hasColumn(fieldTable, "LOFAR_TILE_BEAM_DIR")) + { + ROArrayMeasColumn<MDirection> tileBeamCol(fieldTable, + "LOFAR_TILE_BEAM_DIR"); + tileBeamDir = *(tileBeamCol(0).data()); + } + else + { + ROArrayMeasColumn<MDirection> tileBeamCol(fieldTable, + "DELAY_DIR"); + tileBeamDir = *(tileBeamCol(0).data()); + } + + return tileBeamDir; +} + +} //# namespace StationResponse +} //# namespace LOFAR diff --git a/CEP/Calibration/StationResponse/src/MathUtil.cc b/CEP/Calibration/StationResponse/src/MathUtil.cc new file mode 100644 index 0000000000000000000000000000000000000000..1ce4c5af2ee7bf1169d138edacbd2beb6657c881 --- /dev/null +++ b/CEP/Calibration/StationResponse/src/MathUtil.cc @@ -0,0 +1,32 @@ +//# MathUtil.cc: Various mathematical operations on vectors and matrices. +//# +//# Copyright (C) 2013 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR software suite. +//# The LOFAR software suite 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 3 of the License, or +//# (at your option) any later version. +//# +//# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id$ + +#include <lofar_config.h> +#include <StationResponse/MathUtil.h> + +namespace LOFAR +{ +namespace StationResponse +{ + +} //# namespace StationResponse +} //# namespace LOFAR diff --git a/CEP/Calibration/StationResponse/src/Station.cc b/CEP/Calibration/StationResponse/src/Station.cc new file mode 100644 index 0000000000000000000000000000000000000000..7c4e21045a43e83bd37767c818b8f980fcd93c5c --- /dev/null +++ b/CEP/Calibration/StationResponse/src/Station.cc @@ -0,0 +1,176 @@ +//# Station.cc: Representation of the station beam former. +//# +//# Copyright (C) 2013 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR software suite. +//# The LOFAR software suite 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 3 of the License, or +//# (at your option) any later version. +//# +//# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id$ + +#include <lofar_config.h> +#include <StationResponse/Station.h> +#include <StationResponse/MathUtil.h> + +namespace LOFAR +{ +namespace StationResponse +{ + +Station::Station(const string &name, const vector3r_t &position) + : itsName(name), + itsPosition(position), + itsPhaseReference(position) +{ +} + +const string &Station::name() const +{ + return itsName; +} + +const vector3r_t &Station::position() const +{ + return itsPosition; +} + +void Station::setPhaseReference(const vector3r_t &reference) +{ + itsPhaseReference = reference; +} + +const vector3r_t &Station::phaseReference() const +{ + return itsPhaseReference; +} + +void Station::addField(const AntennaField::ConstPtr &field) +{ + itsFields.push_back(field); +} + +size_t Station::nFields() const +{ + return itsFields.size(); +} + +AntennaField::ConstPtr Station::field(size_t i) const +{ + return (i < itsFields.size() ? itsFields[i] : AntennaField::ConstPtr()); +} + +Station::FieldList::const_iterator Station::beginFields() const +{ + return itsFields.begin(); +} + +Station::FieldList::const_iterator Station::endFields() const +{ + return itsFields.end(); +} + +matrix22c_t Station::response(real_t time, real_t freq, + const vector3r_t &direction, real_t freq0, const vector3r_t &station0, + const vector3r_t &tile0) const +{ + raw_response_t result = {{{{{}}, {{}}}}, {{}}}; + for(FieldList::const_iterator field_it = beginFields(), + field_end = endFields(); field_it != field_end; ++field_it) + { + raw_array_factor_t field = fieldArrayFactor(*field_it, time, freq, + direction, freq0, phaseReference(), station0); + + raw_response_t antenna = (*field_it)->rawResponse(time, freq, + direction, tile0); + + result.response[0][0] += field.factor[0] * antenna.response[0][0]; + result.response[0][1] += field.factor[0] * antenna.response[0][1]; + result.response[1][0] += field.factor[1] * antenna.response[1][0]; + result.response[1][1] += field.factor[1] * antenna.response[1][1]; + + result.weight[0] += field.weight[0] * antenna.weight[0]; + result.weight[1] += field.weight[1] * antenna.weight[1]; + } + + return normalize(result); +} + +diag22c_t Station::arrayFactor(real_t time, real_t freq, + const vector3r_t &direction, real_t freq0, const vector3r_t &station0, + const vector3r_t &tile0) const +{ + raw_array_factor_t af = {{{}}, {{}}}; + for(FieldList::const_iterator field_it = beginFields(), + field_end = endFields(); field_it != field_end; ++field_it) + { + raw_array_factor_t field = fieldArrayFactor(*field_it, time, freq, + direction, freq0, phaseReference(), station0); + + raw_array_factor_t antenna = (*field_it)->rawArrayFactor(time, freq, + direction, tile0); + + af.factor[0] += field.factor[0] * antenna.factor[0]; + af.factor[1] += field.factor[1] * antenna.factor[1]; + af.weight[0] += field.weight[0] * antenna.weight[0]; + af.weight[1] += field.weight[1] * antenna.weight[1]; + } + + return normalize(af); +} + +raw_array_factor_t +Station::fieldArrayFactor(const AntennaField::ConstPtr &field, + real_t, real_t freq, const vector3r_t &direction, real_t freq0, + const vector3r_t &position0, const vector3r_t &direction0) const +{ + real_t k = Constants::_2pi * freq / Constants::c; + real_t k0 = Constants::_2pi * freq0 / Constants::c; + + vector3r_t offset = field->position() - position0; + + raw_array_factor_t af = {{{}}, {{}}}; + typedef AntennaField::AntennaList AntennaList; + for(AntennaList::const_iterator antenna_it = field->beginAntennae(), + antenna_end = field->endAntennae(); antenna_it != antenna_end; + ++antenna_it) + { + if(!antenna_it->enabled[0] && !antenna_it->enabled[1]) + { + continue; + } + + vector3r_t position = offset + antenna_it->position; + real_t phase = k * dot(position, direction) - k0 * dot(position, + direction0); + complex_t shift = complex_t(cos(phase), sin(phase)); + + if(antenna_it->enabled[0]) + { + af.factor[0] += shift; + ++af.weight[0]; + } + + if(antenna_it->enabled[1]) + { + af.factor[1] += shift; + ++af.weight[1]; + } + } + + return af; +} + +} //# namespace StationResponse +} //# namespace LOFAR diff --git a/CEP/Calibration/StationResponse/src/TileAntenna.cc b/CEP/Calibration/StationResponse/src/TileAntenna.cc new file mode 100644 index 0000000000000000000000000000000000000000..2813563ec96cad64914c102168e2470aef1854b6 --- /dev/null +++ b/CEP/Calibration/StationResponse/src/TileAntenna.cc @@ -0,0 +1,100 @@ +//# TileAntenna.cc: Semi-analytical model of a LOFAR HBA tile. +//# +//# Copyright (C) 2013 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR software suite. +//# The LOFAR software suite 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 3 of the License, or +//# (at your option) any later version. +//# +//# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id$ + +#include <lofar_config.h> +#include <StationResponse/TileAntenna.h> +#include <StationResponse/Constants.h> +#include <StationResponse/MathUtil.h> +#include <ElementResponse/ElementResponse.h> + +namespace LOFAR +{ +namespace StationResponse +{ + +TileAntenna::TileAntenna(const TileConfig &config) + : itsConfig(config) +{ +} + +void TileAntenna::setConfig(const TileConfig &config) +{ + itsConfig = config; +} + +const TileAntenna::TileConfig &TileAntenna::config() const +{ + return itsConfig; +} + +raw_array_factor_t TileAntenna::rawArrayFactor(real_t freq, + const vector3r_t &direction, const vector3r_t &direction0) const +{ + // Angular wave number. + real_t k = Constants::_2pi * freq / Constants::c; + + // We need to compute the phase difference between a signal arriving from + // the target direction and a signal arriving from the reference direction, + // both relative to the center of the tile. + // + // This phase difference can be computed using a single dot product per + // dipole element by exploiting the fact that the dot product is + // distributive over vector addition: + // + // a . b + a . c = a . (b + c) + // + vector3r_t difference = direction - direction0; + + complex_t af(0.0, 0.0); + for(TileConfig::const_iterator element_it = itsConfig.begin(), + element_end = itsConfig.end(); element_it != element_end; ++element_it) + { + // Compute the effective delay for a plane wave approaching from the + // direction of interest with respect to the position of element i + // when beam forming in the reference direction using time delays. + real_t shift = k * dot(difference, *element_it); + af += complex_t(cos(shift), sin(shift)); + } + + real_t size = itsConfig.size(); + raw_array_factor_t result = {{{af, af}}, {{size, size}}}; + return result; +} + +matrix22c_t TileAntenna::elementResponse(real_t freq, + const vector3r_t &direction) const +{ + // The positive X dipole direction is SW of the reference orientation, + // which translates to a phi coordinate of 5/4*pi in the topocentric + // spherical coordinate system. The phi coordinate is corrected for this + // offset before evaluating the antenna model. + vector2r_t thetaphi = cart2thetaphi(direction); + thetaphi[1] -= 5.0 * Constants::pi_4; + + matrix22c_t response; + element_response_hba(freq, thetaphi[0], thetaphi[1], + reinterpret_cast<std::complex<double> (&)[2][2]>(response)); + return response; +} + +} //# namespace StationResponse +} //# namespace LOFAR diff --git a/CEP/Calibration/StationResponse/src/Types.cc b/CEP/Calibration/StationResponse/src/Types.cc new file mode 100644 index 0000000000000000000000000000000000000000..5267ae75691117365e166f513d915a879f48ee8a --- /dev/null +++ b/CEP/Calibration/StationResponse/src/Types.cc @@ -0,0 +1,32 @@ +//# Types.cc: Declaration of types used in this library. +//# +//# Copyright (C) 2013 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR software suite. +//# The LOFAR software suite 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 3 of the License, or +//# (at your option) any later version. +//# +//# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id$ + +#include <lofar_config.h> +#include <StationResponse/Types.h> + +namespace LOFAR +{ +namespace StationResponse +{ + +} //# namespace StationResponse +} //# namespace LOFAR diff --git a/CEP/Calibration/StationResponse/src/makeresponseimage.cc b/CEP/Calibration/StationResponse/src/makeresponseimage.cc new file mode 100644 index 0000000000000000000000000000000000000000..36a4276ffc95ecc460f1433b4a1f86000c1f80a3 --- /dev/null +++ b/CEP/Calibration/StationResponse/src/makeresponseimage.cc @@ -0,0 +1,636 @@ +//# makeresponseimage.cc: Generate images of the station response for a given +//# MeasurementSet. +//# +//# Copyright (C) 2013 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR software suite. +//# The LOFAR software suite 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 3 of the License, or +//# (at your option) any later version. +//# +//# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id$ + +#include <lofar_config.h> + +#include <StationResponse/Package__Version.h> +#include <StationResponse/LofarMetaDataUtil.h> +#include <Common/InputParSet.h> +#include <Common/lofar_sstream.h> +#include <Common/LofarLogger.h> +#include <Common/SystemUtil.h> +#include <Common/Version.h> +#include <casacore/coordinates/Coordinates/CoordinateSystem.h> +#include <casacore/coordinates/Coordinates/SpectralCoordinate.h> +#include <casacore/coordinates/Coordinates/StokesCoordinate.h> +#include <casacore/coordinates/Coordinates/DirectionCoordinate.h> +#include <casacore/images/Images/PagedImage.h> +#include <casacore/measures/Measures/MCDirection.h> +#include <casacore/measures/Measures/MCPosition.h> +#include <casacore/measures/Measures/MDirection.h> +#include <casacore/measures/Measures/MeasConvert.h> +#include <casacore/measures/Measures/MeasTable.h> +#include <casacore/measures/Measures/MEpoch.h> +#include <casacore/measures/Measures/MPosition.h> +#include <casacore/ms/MeasurementSets/MeasurementSet.h> +#include <casacore/ms/MeasurementSets/MSDataDescription.h> +#include <casacore/ms/MeasurementSets/MSDataDescColumns.h> +#include <casacore/ms/MeasurementSets/MSField.h> +#include <casacore/ms/MeasurementSets/MSFieldColumns.h> +#include <casacore/ms/MeasurementSets/MSObservation.h> +#include <casacore/ms/MeasurementSets/MSObsColumns.h> +#include <casacore/ms/MeasurementSets/MSSpectralWindow.h> +#include <casacore/ms/MeasurementSets/MSSpWindowColumns.h> +#include <casacore/tables/TaQL/ExprNode.h> + +// There is no wrapped include file lofar_iterator.h. +#include <iterator> + +using namespace casacore; +using namespace LOFAR; +using namespace LOFAR::StationResponse; +using LOFAR::operator<<; + +namespace +{ + /*! + * \brief Map of ITRF directions required to compute an image of the + * station beam. + * + * The station beam library uses the ITRF coordinate system to express + * station positions and source directions. Since the Earth moves with + * respect to the sky, the ITRF coordinates of a source vary with time. + * This structure stores the ITRF coordinates for the station and tile beam + * former reference directions, as well as for a grid of points on the sky, + * along with the time for which these ITRF coordinates are valid. + */ + struct ITRFDirectionMap + { + /*! + * \brief The time for which this ITRF direction map is valid (MJD(UTC) + * in seconds). + */ + double_t time0; + + /*! + * \brief Station beam former reference direction expressed in ITRF + * coordinates. + */ + vector3r_t station0; + + /*! + * \brief Tile beam former reference direction expressed in ITRF + * coordinates. + */ + vector3r_t tile0; + + /*! + * \brief ITRF coordinates for a grid of points on the sky. + */ + Matrix<vector3r_t> directions; + }; + + /*! + * \brief Create an ITRFDirectionMap. + * + * \param coordinates Sky coordinate system definition. + * \param shape Number of points along the RA and DEC axis. + * \param epoch Time for which to compute the ITRF coordinates. + * \param position0 Station beam former reference position (phase + * reference). + * \param station0 Station beam former reference direction (pointing). + * \param tile0 Tile beam former reference direction (pointing). + */ + ITRFDirectionMap makeDirectionMap(const DirectionCoordinate &coordinates, + const IPosition &shape, const MEpoch &epoch, const MPosition &position0, + const MDirection &station0, const MDirection &tile0); + + /*! + * \brief Create a DirectionCoordinate instance that defines an image + * coordinate system on the sky (J2000). + * + * \param reference Direction that corresponds to the origin of the + * coordinate system. + * \param size Number of points along each axis (RA, DEC). The index of the + * origin of the coordinate system is (size / 2, size / 2). + * \param delta Angular step size in radians (assumed to be the same for + * both axes). + */ + DirectionCoordinate makeCoordinates(const MDirection &reference, + unsigned int size, double delta); + + /*! + * \brief Convert an ITRF position given as a StationResponse::vector3r_t + * instance to a casacore::MPosition. + */ + MPosition toMPositionITRF(const vector3r_t &position); + + /*! + * \brief Convert a casacore::MPosition instance to a + * StationResponse::vector3r_t instance. + */ + vector3r_t fromMPosition(const MPosition &position); + + /*! + * \brief Convert a casacore::MDirection instance to a + * StationResponse::vector3r_t instance. + */ + vector3r_t fromMDirection(const MDirection &direction); + + /*! + * \brief Check if the specified column exists as a column of the specified + * table. + * + * \param table The Table instance to check. + * \param column The name of the column. + */ + bool hasColumn(const Table &table, const string &column); + + /*! + * \brief Check if the specified sub-table exists as a sub-table of the + * specified table. + * + * \param table The Table instance to check. + * \param name The name of the sub-table. + */ + bool hasSubTable(const Table &table, const string &name); + + /*! + * \brief Provide access to a sub-table by name. + * + * \param table The Table instance to which the sub-table is associated. + * \param name The name of the sub-table. + */ + Table getSubTable(const Table &table, const string &name); + + /*! + * \brief Attempt to read the position of the observatory. If the + * observatory position is unknown, the specified default position is + * returned. + * + * \param ms MeasurementSet to read the observatory position from. + * \param idObservation Identifier that determines of which observation the + * observatory position should be read. + * \param defaultPosition The position that will be returned if the + * observatory position is unknown. + */ + MPosition readObservatoryPosition(const MeasurementSet &ms, + unsigned int idObservation, const MPosition &defaultPosition); + + /*! + * \brief Read the list of unique timestamps. + * + * \param ms MeasurementSet to read the list of unique timestamps from. + */ + Vector<Double> readUniqueTimes(const MeasurementSet &ms); + + /*! + * \brief Read the reference frequency of the subband associated to the + * specified data description identifier. + * + * \param ms MeasurementSet to read the reference frequency from. + * \param idDataDescription Identifier that determines of which subband the + * reference frequency should be read. + */ + double readFreqReference(const MeasurementSet &ms, + unsigned int idDataDescription); + + /*! + * \brief Read the phase reference direction. + * + * \param ms MeasurementSet to read the phase reference direction from. + * \param idField Identifier of the field of which the phase reference + * direction should be read. + */ + MDirection readPhaseReference(const MeasurementSet &ms, + unsigned int idField); + + /*! + * \brief Read the station beam former reference direction. + * + * \param ms MeasurementSet to read the station beam former reference + * direction from. + * \param idField Identifier of the field of which the station beam former + * reference direction should be read. + */ + MDirection readDelayReference(const MeasurementSet &ms, + unsigned int idField); + + /*! + * \brief Read the station beam former reference direction. + * + * \param ms MeasurementSet to read the tile beam former reference + * direction from. + * \param idField Identifier of the field of which the tile beam former + * reference direction should be read. + */ + MDirection readTileReference(const MeasurementSet &ms, + unsigned int idField); + + /*! + * \brief Store the specified cube of pixel data as a CASA image. + * + * \param data Pixel data. The third dimension is assumed to be of length + * 4, referring to the correlation products XX, XY, YX, YY (in this order). + * \param coordinates Sky coordinate system definition. + * \param frequency Frequency for which the pixel data is valid (Hz). + * \param name File name of the output image. + */ + template <class T> + void store(const Cube<T> &data, const DirectionCoordinate &coordinates, + double frequency, const string &name); + + /*! + * \brief Convert a string to a CASA Quantity (value with unit). + */ + Quantity readQuantity(const String &in); + + /*! + * \brief Remove all elements from the range [first, last) that fall + * outside the interval [min, max]. + * + * This function returns an iterator new_last such that the range [first, + * new_last) contains no elements that fall outside the interval [min, + * max]. The iterators in the range [new_last, last) are all still + * dereferenceable, but the elements that they point to are unspecified. + * The order of the elements that are not removed is unchanged. + */ + template <typename T> + T filter(T first, T last, int min, int max); +} //# unnamed namespace + + +int main(int argc, char *argv[]) +{ + TEST_SHOW_VERSION(argc, argv, StationResponse); + INIT_LOGGER(basename(string(argv[0]))); + Version::show<StationResponseVersion>(cout); + + // Parse inputs. + LOFAR::InputParSet inputs; + inputs.create("ms", "", "Name of input MeasurementSet", "string"); + inputs.create("stations", "0", "IDs of stations to process", "int vector"); + inputs.create("cellsize", "60arcsec", "Angular pixel size", + "quantity string"); + inputs.create("size", "256", "Number of pixels along each axis", "int"); + inputs.create("offset", "0s", "Time offset from the start of the MS", + "quantity string"); + inputs.create("frames", "0", "Number of images that will be generated for" + " each station (equally spaced over the duration of the MS)", "int"); + inputs.create("abs", "false", "If set to true, store the absolute value of" + " the beam response instead of the complex value (intended for use with" + " older versions of casaviewer)", "bool"); + inputs.readArguments(argc, argv); + + vector<int> stationIDs(inputs.getIntVector("stations")); + Quantity cellsize = readQuantity(inputs.getString("cellsize")); + unsigned int size = max(inputs.getInt("size"), 1); + Quantity offset = readQuantity(inputs.getString("offset")); + unsigned int nFrames = max(inputs.getInt("frames"), 1); + Bool abs = inputs.getBool("abs"); + + // Open MS. + MeasurementSet ms(inputs.getString("ms")); + unsigned int idObservation = 0, idField = 0, idDataDescription = 0; + + // Read station meta-data. + vector<Station::Ptr> stations; + readStations(ms, std::back_inserter(stations)); + + // Remove illegal station indices. + stationIDs.erase(filter(stationIDs.begin(), stationIDs.end(), 0, + static_cast<int>(stations.size()) - 1), stationIDs.end()); + + // Read unique timestamps + Table selection = + ms(ms.col("OBSERVATION_ID") == static_cast<Int>(idObservation) + && ms.col("FIELD_ID") == static_cast<Int>(idField) + && ms.col("DATA_DESC_ID") == static_cast<Int>(idDataDescription)); + Vector<Double> time = readUniqueTimes(selection); + + // Read reference frequency. + double refFrequency = readFreqReference(ms, idDataDescription); + + // Use the position of the first selected station as the array reference + // position if the observatory position cannot be found. + MPosition refPosition = readObservatoryPosition(ms, idField, + toMPositionITRF(stations.front()->position())); + + // Read phase reference direction. + MDirection refPhase = readPhaseReference(ms, idField); + + // Read delay reference direction. + MDirection refDelay = readDelayReference(ms, idField); + + // Read tile reference direction. + MDirection refTile = readTileReference(ms, idField); + + // Create image coordinate system. + IPosition shape(2, size, size); + DirectionCoordinate coordinates = makeCoordinates(refPhase, size, + cellsize.getValue("rad")); + + // Compute station response images. + Cube<Complex> response(size, size, 4); + + MEpoch refEpoch; + Quantity refTime(time(0), "s"); + refTime = refTime + offset; + Quantity deltaTime((time(time.size() - 1) - time(0) - offset.getValue("s")) + / (nFrames - 1), "s"); + + for(size_t j = 0; j < nFrames; ++j) + { + cout << "[ Frame: " << (j + 1) << "/" << nFrames << " Offset: +" + << refTime.getValue() - time(0) << " s ]" << endl; + + // Update reference epoch. + refEpoch.set(refTime); + + cout << "Creating ITRF direction map... " << flush; + ITRFDirectionMap directionMap = makeDirectionMap(coordinates, shape, + refEpoch, refPosition, refDelay, refTile); + cout << "done." << endl; + + cout << "Computing response images... " << flush; + for(vector<int>::const_iterator it = stationIDs.begin(), + end = stationIDs.end(); it != end; ++it) + { + Station::ConstPtr station = stations[*it]; + cout << *it << ":" << station->name() << " " << flush; + + for(size_t y = 0; y < size; ++y) + { + for(size_t x = 0; x < size; ++x) + { + matrix22c_t E = station->response(directionMap.time0, + refFrequency, directionMap.directions(x, y), + refFrequency, directionMap.station0, + directionMap.tile0); + + response(x, y, 0) = E[0][0]; + response(x, y, 1) = E[0][1]; + response(x, y, 2) = E[1][0]; + response(x, y, 3) = E[1][1]; + } + } + + std::ostringstream oss; + oss << "response-" << station->name() << "-frame-" << (j + 1) + << ".img"; + + if(abs) + { + store(Cube<Float>(amplitude(response)), coordinates, + refFrequency, oss.str()); + } + else + { + store(response, coordinates, refFrequency, oss.str()); + } + } + cout << endl; + + refTime = refTime + deltaTime; + } + + return 0; +} + +namespace +{ + ITRFDirectionMap makeDirectionMap(const DirectionCoordinate &coordinates, + const IPosition &shape, const MEpoch &epoch, const MPosition &position0, + const MDirection &station0, const MDirection &tile0) + { + ITRFDirectionMap map; + + // Convert from MEpoch to a time in MJD(UTC) in seconds. + MEpoch mEpochUTC = MEpoch::Convert(epoch, MEpoch::Ref(MEpoch::UTC))(); + MVEpoch mvEpochUTC = mEpochUTC.getValue(); + Quantity qEpochUTC = mvEpochUTC.getTime(); + map.time0 = qEpochUTC.getValue("s"); + + // Create conversion engine J2000 => ITRF at the specified epoch. + MDirection::Convert convertor = MDirection::Convert(MDirection::J2000, + MDirection::Ref(MDirection::ITRF, MeasFrame(epoch, position0))); + + // Compute station and tile beam former reference directions in ITRF at + // the specified epoch. + map.station0 = fromMDirection(convertor(station0)); + map.tile0 = fromMDirection(convertor(tile0)); + + // Pre-allocate space for the grid of ITRF directions. + map.directions.resize(shape); + + // Compute ITRF directions. + MDirection world; + Vector<Double> pixel = coordinates.referencePixel(); + for(pixel(1) = 0.0; pixel(1) < shape(1); ++pixel(1)) + { + for(pixel(0) = 0.0; pixel(0) < shape(0); ++pixel(0)) + { + // CoordinateSystem::toWorld(): RA range [-pi,pi], DEC range + // [-pi/2,pi/2]. + if(coordinates.toWorld(world, pixel)) + { + map.directions(pixel(0), pixel(1)) = + fromMDirection(convertor(world)); + } + } + } + + return map; + } + + DirectionCoordinate makeCoordinates(const MDirection &reference, + unsigned int size, double delta) + { + MDirection referenceJ2000 = MDirection::Convert(reference, + MDirection::J2000)(); + Quantum<Vector<Double> > referenceAngles = referenceJ2000.getAngle(); + double ra = referenceAngles.getBaseValue()(0); + double dec = referenceAngles.getBaseValue()(1); + + Matrix<Double> xform(2,2); + xform = 0.0; xform.diagonal() = 1.0; + return DirectionCoordinate(MDirection::J2000, + Projection(Projection::SIN), ra, dec, -delta, delta, xform, + size / 2, size / 2); + } + + MPosition toMPositionITRF(const vector3r_t &position) + { + MVPosition mvITRF(position[0], position[1], position[2]); + return MPosition(mvITRF, MPosition::ITRF); + } + + vector3r_t fromMPosition(const MPosition &position) + { + MVPosition mvPosition = position.getValue(); + vector3r_t result = {{mvPosition(0), mvPosition(1), mvPosition(2)}}; + return result; + } + + vector3r_t fromMDirection(const MDirection &direction) + { + MVDirection mvDirection = direction.getValue(); + vector3r_t result = {{mvDirection(0), mvDirection(1), mvDirection(2)}}; + return result; + } + + bool hasColumn(const Table &table, const string &column) + { + return table.tableDesc().isColumn(column); + } + + bool hasSubTable(const Table &table, const string &name) + { + return table.keywordSet().isDefined(name); + } + + Table getSubTable(const Table &table, const string &name) + { + return table.keywordSet().asTable(name); + } + + MPosition readObservatoryPosition(const MeasurementSet &ms, + unsigned int idObservation, const MPosition &defaultPosition) + { + // Get the instrument position in ITRF coordinates, or use the centroid + // of the station positions if the instrument position is unknown. + ROMSObservationColumns observation(ms.observation()); + ASSERT(observation.nrow() > idObservation); + ASSERT(!observation.flagRow()(idObservation)); + + // Read observatory name and try to look-up its position. + const string observatory = observation.telescopeName()(idObservation); + + // Look-up observatory position, default to specified default position. + MPosition position(defaultPosition); + MeasTable::Observatory(position, observatory); + return position; + } + + Vector<Double> readUniqueTimes(const MeasurementSet &ms) + { + Table tab_sorted = ms.sort("TIME", Sort::Ascending, Sort::HeapSort + | Sort::NoDuplicates); + + ROScalarColumn<Double> c_time(tab_sorted, "TIME"); + return c_time.getColumn(); + } + + double readFreqReference(const MeasurementSet &ms, + unsigned int idDataDescription) + { + ROMSDataDescColumns desc(ms.dataDescription()); + ASSERT(desc.nrow() > idDataDescription); + ASSERT(!desc.flagRow()(idDataDescription)); + uInt idWindow = desc.spectralWindowId()(idDataDescription); + + ROMSSpWindowColumns window(ms.spectralWindow()); + ASSERT(window.nrow() > idWindow); + ASSERT(!window.flagRow()(idWindow)); + + return window.refFrequency()(idWindow); + } + + MDirection readPhaseReference(const MeasurementSet &ms, + unsigned int idField) + { + ROMSFieldColumns field(ms.field()); + ASSERT(field.nrow() > idField); + ASSERT(!field.flagRow()(idField)); + + return field.phaseDirMeas(idField); + } + + MDirection readDelayReference(const MeasurementSet &ms, + unsigned int idField) + { + ROMSFieldColumns field(ms.field()); + ASSERT(field.nrow() > idField); + ASSERT(!field.flagRow()(idField)); + + return field.delayDirMeas(idField); + } + + MDirection readTileReference(const MeasurementSet &ms, unsigned int idField) + { + // The MeasurementSet class does not support LOFAR specific columns, so + // we use ROArrayMeasColumn to read the tile beam reference direction. + Table tab_field = getSubTable(ms, "FIELD"); + + static const String columnName = "LOFAR_TILE_BEAM_DIR"; + if(hasColumn(tab_field, columnName)) + { + ROArrayMeasColumn<MDirection> c_direction(tab_field, columnName); + if(c_direction.isDefined(idField)) + { + return c_direction(idField)(IPosition(1, 0)); + } + } + + // By default, the tile beam reference direction is assumed to be equal + // to the station beam reference direction (for backward compatibility, + // and for non-HBA measurements). + return readDelayReference(ms, idField); + } + + template <class T> + void store(const Cube<T> &data, const DirectionCoordinate &coordinates, + double frequency, const string &name) + { + ASSERT(data.shape()(2) == 4); + + Vector<Int> stokes(4); + stokes(0) = Stokes::XX; + stokes(1) = Stokes::XY; + stokes(2) = Stokes::YX; + stokes(3) = Stokes::YY; + + CoordinateSystem csys; + csys.addCoordinate(coordinates); + csys.addCoordinate(StokesCoordinate(stokes)); + csys.addCoordinate(SpectralCoordinate(MFrequency::TOPO, frequency, 0.0, + 0.0)); + + PagedImage<T> im(TiledShape(IPosition(4, data.shape()(0), + data.shape()(1), 4, 1)), csys, name); + im.putSlice(data, IPosition(4, 0, 0, 0, 0)); + } + + Quantity readQuantity(const String &in) + { + Quantity result; + bool status = Quantity::read(result, in); + ASSERT(status); + return result; + } + + template <typename T> + T filter(T first, T last, int min, int max) + { + T new_last = first; + for(; first != last; ++first) + { + if(*first >= min && *first <= max) + { + *new_last++ = *first; + } + } + + return new_last; + } +} // unnamed namespace. diff --git a/CEP/Calibration/pystationresponse/CMakeLists.txt b/CEP/Calibration/pystationresponse/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..e7c432c3b586d385eb41e00de6ebc164cdc3416f --- /dev/null +++ b/CEP/Calibration/pystationresponse/CMakeLists.txt @@ -0,0 +1,11 @@ +# $Id$ + +lofar_package(pystationresponse 1.0 DEPENDS StationResponse) + +include(LofarFindPackage) +lofar_find_package(Python 3.4 REQUIRED) +lofar_find_package(Boost REQUIRED COMPONENTS python3) +lofar_find_package(Casacore REQUIRED COMPONENTS python) + +add_subdirectory(src) +add_subdirectory(test) diff --git a/CEP/Calibration/pystationresponse/src/CMakeLists.txt b/CEP/Calibration/pystationresponse/src/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..0d7aa5176a1ef06464b012765ae4dab74a1193d7 --- /dev/null +++ b/CEP/Calibration/pystationresponse/src/CMakeLists.txt @@ -0,0 +1,28 @@ +# $Id$ + +include(LofarPackageVersion) + +# Add current build directory to the include path. This is needed, because +# pystationresponse.cc #include's Package__Version.cc (yucky!). +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +lofar_add_library(_stationresponse MODULE pystationresponse.cc) +set_target_properties(_stationresponse PROPERTIES + PREFIX "" + LIBRARY_OUTPUT_DIRECTORY ${PYTHON_BUILD_DIR}/lofar/stationresponse) + +# This is a quick-and-dirty fix to install the Python binding module in the +# right place. It will now be installed twice, because lofar_add_library() +# will install it in $prefix/$libdir +install(TARGETS _stationresponse + DESTINATION ${PYTHON_INSTALL_DIR}/lofar/stationresponse) + +# Dummy library, needed because lofar_add_executable() takes its dependencies +# from libraries added with lofar_add_library() (see bug #1430) +lofar_add_library(lofar_pystationresponse Package__Version.cc) + +lofar_add_bin_program(versionpystationresponse versionpystationresponse.cc) + +# Install Python modules +include(PythonInstall) +python_install(__init__.py DESTINATION lofar/stationresponse) diff --git a/CEP/Calibration/pystationresponse/src/__init__.py b/CEP/Calibration/pystationresponse/src/__init__.py new file mode 100755 index 0000000000000000000000000000000000000000..87961234ba1774f50361024fa1c918deebf67484 --- /dev/null +++ b/CEP/Calibration/pystationresponse/src/__init__.py @@ -0,0 +1,210 @@ +# __init__.py: Top level .py file for python stationresponse interface +# Copyright (C) 2011 +# ASTRON (Netherlands Institute for Radio Astronomy) +# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +# +# This file is part of the LOFAR software suite. +# The LOFAR software suite 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 3 of the License, or +# (at your option) any later version. +# +# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +# +# $Id$ + +from ._stationresponse import StationResponse + +class stationresponse(object): + """ + The Python interface to the LOFAR station beam model. + """ + + def __init__ (self, msname, inverse = False, useElementResponse = True, + useArrayFactor = True, useChanFreq = False): + """Create a stationresponse object that can be used to evaluate the + LOFAR beam for the given Measurement Set. + + The Measurement Set defines the station and dipole positions, the phase + center, and the channel frequencies (and reference frequency) for which + the LOFAR beam will be evaluated. + + `msname` + Name of the Measurement Set. + `inverse` + Compute the inverse of the LOFAR beam (default False). + `useElementResponse` + Include the effect of the dual dipole (element) beam (default True). + `useArrayFactor` + Include the effect of the station and tile array factor (default + True). + `useChanFreq` + Compute the phase shift for the station beamformer using the channel + frequency instead of the subband reference frequency. This option + should be enabled for Measurement Sets that contain multiple subbands + compressed to single channels inside a single spectral window + (default: False). + + For example:: + + import pyrap.tables + import lofar.stationresponse + response = lofar.stationresponse.stationresponse('test.MS') + + # Iterate over all time stamps in the Measurement Set and compute the + # beam Jones matrix for station 0, channel 0. + ms = pyrap.tables.table('test.MS') + for subtable in ms.iter('TIME'): + time = subtable.getcell("TIME", 0) + print time, response.evaluateChannel(time, 0, 0) + """ + self._response = StationResponse(msname, inverse, + useElementResponse, useArrayFactor, useChanFreq) + + def version (self, type='other'): + """Show the software version.""" + return self._response.version (type) + + def setRefDelay (self, ra, dec): + """Set the reference direction used by the station beamformer. By + default, DELAY_DIR of field 0 is used. + + `ra` + Right ascension (in radians, J2000) + `dec` + Declination (in radians, J2000) + """ + self._response.setRefDelay(ra, dec) + + def getRefDelay (self, time): + """Get the reference direction used by the station beamformer. + Returns an ITRF vector in meters (numpy array of 3 floats). + + `time` + Time at which to evaluate the direction + """ + return self._response.getRefDelay(time) + + def setRefTile (self, ra, dec): + """Set the reference direction used by the analog tile beamformer + (relevant for HBA observations only). By default, LOFAR_TILE_BEAM_DIR + of field 0 is used. If not present, DELAY_DIR of field 0 is used + instead. + + `ra` + Right ascension (in radians, J2000) + `dec` + Declination (in radians, J2000) + """ + self._response.setRefTile(ra, dec) + + def getRefTile (self, time): + """Get the reference direction used by the analog tile beamformer + (relevant for HBA observations only). + Returns an ITRF vector in meters (numpy array of 3 floats). + + `time` + Time at which to evaluate the direction + """ + return self._response.getRefTile(time) + + def setDirection (self, ra, dec): + """Set the direction of interest (can be and often will be different + from the pointing). By default, PHASE_DIR of field 0 is used. + + `ra` + Right ascension (in radians, J2000) + `dec` + Declination (in radians, J2000) + """ + self._response.setDirection(ra, dec) + + def getDirection (self, time): + """Get the direction of interest. + Returns an ITRF vector in meters (numpy array of 3 floats). + + `time` + Time at which to evaluate the direction + """ + return self._response.getDirection(time) + + def evaluate (self, time): + """Compute the beam Jones matrix for all stations and channels at the + given time. The result is returned as a 4-dim complex numpy array with + shape: no. of stations x no. of channels x 2 x 2. + + `time` + Time (MJD in seconds) + """ + return self._response.evaluate0(time) + + def evaluateStation (self, time, station): + """Compute the beam Jones matrix for all channels at the given time for + the given station. The result is returned as a 3-dim complex numpy array + with shape: no. of channels x 2 x 2. + + `time` + Time (MJD in seconds). + `station` + Station number (as in the ANTENNA table of the Measurement Set). + """ + return self._response.evaluate1(time, station) + + def evaluateChannel (self, time, station, channel): + """Compute the beam Jones matrix for the given time, station, and + channel. The result is returned as a 2-dim complex numpy array with + shape: 2 x 2. + + `time` + Time (MJD in seconds). + `station` + Station number (as defined in the ANTENNA table of the Measurement + Set). + `channel` + Channel number (as in the SPECTRAL_WINDOW table of the Measurement + Set). + """ + return self._response.evaluate2(time, station, channel) + + def evaluateFreq (self, time, station, freq): + """Compute the beam Jones matrix for the given time, station, and + frequency. The result is returned as a 2-dim complex numpy array with + shape: 2 x 2. + + `time` + Time (MJD in seconds). + `station` + Station number (as defined in the ANTENNA table of the Measurement + Set). + `frequency` + Frequency to compute beam at (in Hz) + """ + return self._response.evaluate3(time, station, freq) + + def evaluateFreqITRF (self, time, station, freq, direction, station0, tile0): + """Compute the beam Jones matrix for the given time, station, and + frequency, with the given ITRF directions. + The result is returned as a 2-dim complex numpy array with + shape: 2 x 2. + + `time` + Time (MJD in seconds). + `station` + Station number (as defined in the ANTENNA table of the Measurement + Set). + `frequency` + Frequency to compute beam at (in Hz) + `direction` + ITRF direction to compute beam at (numpy array with 3 floats) + `station0` + ITRF direction of the station beamformer (numpy array with 3 floats) + `tile0` + ITRF direction of the tile beamformer (numpy array with 3 floats) + """ + return self._response.evaluate4(time, station, freq, direction, station0, tile0) diff --git a/CEP/Calibration/pystationresponse/src/pystationresponse.cc b/CEP/Calibration/pystationresponse/src/pystationresponse.cc new file mode 100755 index 0000000000000000000000000000000000000000..828098ec170792e9841f754b17d9ff4751bf5b6e --- /dev/null +++ b/CEP/Calibration/pystationresponse/src/pystationresponse.cc @@ -0,0 +1,769 @@ +//# pystationresponse.cc: python module for StationResponse object. +//# Copyright (C) 2007 +//# ASTRON (Netherlands Institute for Radio Astronomy) +//# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands +//# +//# This file is part of the LOFAR software suite. +//# The LOFAR software suite 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 3 of the License, or +//# (at your option) any later version. +//# +//# The LOFAR software suite 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 the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. +//# +//# $Id$ + +#include <lofar_config.h> + +#include <Common/LofarLogger.h> + +#include <StationResponse/ITRFDirection.h> +#include <StationResponse/LofarMetaDataUtil.h> +#include <StationResponse/Station.h> + +#include <casacore/casa/Arrays/Array.h> +#include <casacore/casa/Arrays/Matrix.h> +#include <casacore/casa/Arrays/Cube.h> +#include <casacore/casa/Containers/ValueHolder.h> +#include <casacore/ms/MeasurementSets/MeasurementSet.h> +#include <casacore/ms/MeasurementSets/MSColumns.h> +#include <casacore/measures/Measures/MeasTable.h> +#include <casacore/measures/Measures/MPosition.h> +#include <casacore/measures/Measures/MDirection.h> +#include <casacore/measures/Measures/MeasConvert.h> +#include <casacore/measures/Measures/MCPosition.h> +#include <casacore/measures/Measures/MCDirection.h> + +#if defined(HAVE_CASACORE) +#include <casacore/python/Converters/PycExcp.h> +#include <casacore/python/Converters/PycBasicData.h> +#include <casacore/python/Converters/PycValueHolder.h> +#define pyrap python +#else +#include <pyrap/Converters/PycExcp.h> +#include <pyrap/Converters/PycBasicData.h> +#include <pyrap/Converters/PycValueHolder.h> +#endif + +#include <boost/python.hpp> +#include <boost/python/args.hpp> + +#include "Package__Version.cc" + +using namespace casacore; +using namespace boost::python; +using namespace LOFAR::StationResponse; + +namespace LOFAR +{ +namespace BBS +{ + namespace + { + /*! + * \brief Convert an ITRF position given as a StationResponse::vector3r_t + * instance to a casacore::MPosition. + */ + MPosition toMPositionITRF(const vector3r_t &position); + + /*! + * \brief Convert a casacore::MPosition instance to a + # StationResponse::vector3r_t instance. + */ + vector3r_t fromMPosition(const MPosition &position); + + /*! + * \brief Convert a casacore::MDirection instance to a + * StationResponse::vector3r_t instance. + */ + vector3r_t fromMDirection(const MDirection &direction); + + /*! + * \brief Check if the specified column exists as a column of the specified + * table. + * + * \param table The Table instance to check. + * \param column The name of the column. + */ + bool hasColumn(const Table &table, const string &column); + + /*! + * \brief Check if the specified sub-table exists as a sub-table of the + * specified table. + * + * \param table The Table instance to check. + * \param name The name of the sub-table. + */ + bool hasSubTable(const Table &table, const string &name); + + /*! + * \brief Provide access to a sub-table by name. + * + * \param table The Table instance to which the sub-table is associated. + * \param name The name of the sub-table. + */ + Table getSubTable(const Table &table, const string &name); + + /*! + * \brief Attempt to read the position of the observatory. If the + * observatory position is unknown, the specified default position is + * returned. + * + * \param ms MeasurementSet to read the observatory position from. + * \param idObservation Identifier that determines of which observation the + * observatory position should be read. + * \param defaultPosition The position that will be returned if the + * observatory position is unknown. + */ + MPosition readObservatoryPosition(const MeasurementSet &ms, + unsigned int idObservation, const MPosition &defaultPosition); + + /*! + * \brief Read the phase reference direction. + * + * \param ms MeasurementSet to read the phase reference direction from. + * \param idField Identifier of the field of which the phase reference + * direction should be read. + */ + MDirection readPhaseReference(const MeasurementSet &ms, + unsigned int idField); + + /*! + * \brief Read the station beam former reference direction. + * + * \param ms MeasurementSet to read the station beam former reference + * direction from. + * \param idField Identifier of the field of which the station beam former + * reference direction should be read. + */ + MDirection readDelayReference(const MeasurementSet &ms, + unsigned int idField); + + /*! + * \brief Read the station beam former reference direction. + * + * \param ms MeasurementSet to read the tile beam former reference direction + * from. + * \param idField Identifier of the field of which the tile beam former + * reference direction should be read. + */ + MDirection readTileReference(const MeasurementSet &ms, + unsigned int idField); + } //# unnamed namespace + + class PyStationResponse + { + public: + PyStationResponse(const string& msName, bool inverse = false, + bool useElementResponse = true, bool useArrayFactor = true, + bool useChanFreq = false); + + // Get the software version. + string version(const string& type) const; + + // Set the delay reference direction in radians, J2000. The delay reference + // direction is the direction used by the station beamformer. + void setRefDelay(double ra, double dec); + + // Get the delay reference direction in meters, ITRF. The delay reference + // direction is the direction used by the station beamformer. + ValueHolder getRefDelay(real_t time); + + // Set the tile reference direction in radians, J2000. The tile reference + // direction is the direction used by the analog tile beamformer and is + // relevant only for HBA observations. + void setRefTile(double ra, double dec); + + // Get the tile reference direction in meters, ITRF. The delay reference + // direction is the direction used by the analog tile beamformer and is + // relevant only for HBA observations. + ValueHolder getRefTile(real_t time); + + // Set the direction of interest in radians, J2000. Can and often will be + // different than the delay and/or tile reference direction. + void setDirection(double ra, double dec); + + // Get the direction of intereset in meters, ITRF. + ValueHolder getDirection(real_t time); + + // Compute the LOFAR beam Jones matrices for the given time, station, and/or + // channel. + ValueHolder evaluate0(double time); + ValueHolder evaluate1(double time, int station); + ValueHolder evaluate2(double time, int station, int channel); + ValueHolder evaluate3(double time, int station, double freq); + ValueHolder evaluate4(double time, int station, double freq, const ValueHolder& direction, const ValueHolder& station0, const ValueHolder& tile0); + + private: + Matrix<DComplex> evaluate_itrf( + const Station::ConstPtr &station, double time, double freq, double freq0, + const vector3r_t &direction, const vector3r_t &station0, + const vector3r_t &tile0) const; + + Matrix<DComplex> evaluate(const Station::ConstPtr &station, double time, + double freq, double freq0) const; + + Cube<DComplex> evaluate(const Station::ConstPtr &station, double time, + const Vector<Double> &freq, const Vector<Double> &freq0) const; + + void invert(matrix22c_t &in) const; + void invert(diag22c_t &in) const; + + //# Data members. + bool itsInverse; + bool itsUseElementResponse; + bool itsUseArrayFactor; + bool itsUseChanFreq; + Vector<Station::Ptr> itsStations; + Vector<Double> itsChanFreq; + Vector<Double> itsRefFreq; + + vector3r_t itsRefPosition; + ITRFDirection::Ptr itsRefDelay; + ITRFDirection::Ptr itsRefTile; + + ITRFDirection::Ptr itsDirection; + }; + + PyStationResponse::PyStationResponse(const string &name, bool inverse, + bool useElementResponse, bool useArrayFactor, bool useChanFreq) + : itsInverse(inverse), + itsUseElementResponse(useElementResponse), + itsUseArrayFactor(useArrayFactor), + itsUseChanFreq(useChanFreq) + { + MeasurementSet ms(name); + + // Read spectral window id. + const unsigned int idDataDescription = 0; + ROMSDataDescColumns desc(ms.dataDescription()); + ASSERT(desc.nrow() > idDataDescription); + ASSERT(!desc.flagRow()(idDataDescription)); + + // Read the spectral information. + const unsigned int idWindow = desc.spectralWindowId()(idDataDescription); + ROMSSpWindowColumns window(ms.spectralWindow()); + ASSERT(window.nrow() > idWindow); + ASSERT(!window.flagRow()(idWindow)); + + itsChanFreq = window.chanFreq()(idWindow); + itsRefFreq = Vector<Double>(itsChanFreq.size(), + window.refFrequency()(idWindow)); + + // Read the station information. + ROMSAntennaColumns antenna(ms.antenna()); + itsStations.resize(antenna.nrow()); + for(unsigned int i = 0; i < antenna.nrow(); ++i) + { + itsStations(i) = readStation(ms, i); + } + + // Read observatory position. If unknown, default to the position of the + // first station. + unsigned int idObservation = 0; + MPosition refPosition = readObservatoryPosition(ms, idObservation, + toMPositionITRF(itsStations(0)->position())); + itsRefPosition = fromMPosition(MPosition::Convert(refPosition, + MPosition::ITRF)()); + + // Read the reference directions. + unsigned int idField = 0; + itsRefDelay.reset(new ITRFDirection(itsRefPosition, + fromMDirection(MDirection::Convert(readDelayReference(ms, idField), + MDirection::J2000)()))); + + itsRefTile.reset(new ITRFDirection(itsRefPosition, + fromMDirection(MDirection::Convert(readTileReference(ms, idField), + MDirection::J2000)()))); + + itsDirection.reset(new ITRFDirection(itsRefPosition, + fromMDirection(MDirection::Convert(readPhaseReference(ms, idField), + MDirection::J2000)()))); + } + + string PyStationResponse::version(const string& type) const + { + return Version::getInfo<pystationresponseVersion>("stationresponse", type); + } + + void PyStationResponse::setRefDelay(double ra, double dec) + { + vector2r_t direction = {{ra, dec}}; + itsRefDelay.reset(new ITRFDirection(itsRefPosition, direction)); + } + + ValueHolder PyStationResponse::getRefDelay(real_t time) + { + vector3r_t refDelay=itsRefDelay->at(time); + Vector<Double> result(3); + result(0)=refDelay[0]; result(1)=refDelay[1]; result(2)=refDelay[2]; + + return ValueHolder(result); + } + + void PyStationResponse::setRefTile(double ra, double dec) + { + vector2r_t direction = {{ra, dec}}; + itsRefTile.reset(new ITRFDirection(itsRefPosition, direction)); + } + + ValueHolder PyStationResponse::getRefTile(real_t time) + { + vector3r_t refTile=itsRefTile->at(time); + Vector<Double> result(3); + result(0)=refTile[0]; result(1)=refTile[1]; result(2)=refTile[2]; + + return ValueHolder(result); + } + + void PyStationResponse::setDirection(double ra, double dec) + { + vector2r_t direction = {{ra, dec}}; + itsDirection.reset(new ITRFDirection(itsRefPosition, direction)); + } + + ValueHolder PyStationResponse::getDirection(real_t time) + { + vector3r_t direction=itsDirection->at(time); + Vector<Double> result(3); + result(0)=direction[0]; result(1)=direction[1]; result(2)=direction[2]; + + return ValueHolder(result); + } + + ValueHolder PyStationResponse::evaluate0(double time) + { + Array<DComplex> result(IPosition(4, 2, 2, itsChanFreq.size(), + itsStations.size())); + + for(unsigned int i = 0; i < itsStations.size(); ++i) + { + IPosition start(4, 0, 0, 0, i); + IPosition end(4, 1, 1, itsChanFreq.size() - 1, i); + Cube<DComplex> slice = result(start, end).nonDegenerate(); + if(itsUseChanFreq) + { + slice = evaluate(itsStations(i), time, itsChanFreq, itsChanFreq); + } + else + { + slice = evaluate(itsStations(i), time, itsChanFreq, itsRefFreq); + } + } + + return ValueHolder(result); + } + + ValueHolder PyStationResponse::evaluate1(double time, int station) + { + ASSERTSTR(station >= 0 && static_cast<size_t>(station) + < itsStations.size(), "invalid station number: " << station); + + if(itsUseChanFreq) + { + return ValueHolder(evaluate(itsStations(station), time, itsChanFreq, + itsChanFreq)); + } + + return ValueHolder(evaluate(itsStations(station), time, itsChanFreq, + itsRefFreq)); + } + + ValueHolder PyStationResponse::evaluate2(double time, int station, + int channel) + { + ASSERTSTR(station >= 0 && static_cast<size_t>(station) + < itsStations.size(), "invalid station number: " << station); + ASSERTSTR(channel >= 0 && static_cast<size_t>(channel) + < itsChanFreq.size(), "invalid channel number: " << channel); + + + double freq = itsChanFreq(channel); + if(itsUseChanFreq) + { + return ValueHolder(evaluate(itsStations(station), time, freq, freq)); + } + + double freq0 = itsRefFreq(channel); + return ValueHolder(evaluate(itsStations(station), time, freq, freq0)); + } + + ValueHolder PyStationResponse::evaluate3(double time, int station, + double freq) + { + ASSERTSTR(station >= 0 && static_cast<size_t>(station) + < itsStations.size(), "invalid station number: " << station); + + if(itsUseChanFreq) + { + return ValueHolder(evaluate(itsStations(station), time, freq, freq)); + } + + double freq0 = itsRefFreq(0); + return ValueHolder(evaluate(itsStations(station), time, freq, freq0)); + } + + ValueHolder PyStationResponse::evaluate4(double time, int station, double freq, const ValueHolder& vh_direction, const ValueHolder& vh_station0, const ValueHolder& vh_tile0) + { + ASSERT (vh_direction.dataType() == TpArrayDouble); + ASSERT (vh_station0.dataType() == TpArrayDouble); + ASSERT (vh_tile0.dataType() == TpArrayDouble); + Array<Double> arr_dir(vh_direction.asArrayDouble()); + Array<Double> st0_dir(vh_station0.asArrayDouble()); + Array<Double> tile_dir(vh_tile0.asArrayDouble()); + vector3r_t direction={{arr_dir.data()[0],arr_dir.data()[1],arr_dir.data()[2]}}; + vector3r_t station0 ={{st0_dir.data()[0],st0_dir.data()[1],st0_dir.data()[2]}}; + vector3r_t tile0 ={{tile_dir.data()[0],tile_dir.data()[1],tile_dir.data()[2]}}; + + if(itsUseChanFreq) + { + return ValueHolder(evaluate_itrf(itsStations(station), time, freq, freq, + direction, station0, tile0)); + } + + double freq0 = itsRefFreq(0); + return ValueHolder(evaluate_itrf(itsStations(station), time, freq, freq0, + direction, station0, tile0)); + } + + Cube<DComplex> PyStationResponse::evaluate(const Station::ConstPtr &station, + double time, const Vector<Double> &freq, const Vector<Double> &freq0) const + { + Cube<DComplex> result(2, 2, freq.size(), 0.0); + if(itsUseArrayFactor) + { + vector3r_t direction = itsDirection->at(time); + vector3r_t station0 = itsRefDelay->at(time); + vector3r_t tile0 = itsRefTile->at(time); + + if(itsUseElementResponse) + { + for(unsigned int i = 0; i < freq.size(); ++i) + { + matrix22c_t response = station->response(time, freq(i), direction, + freq0(i), station0, tile0); + + if(itsInverse) + { + invert(response); + } + + result(0, 0, i) = response[0][0]; + result(1, 0, i) = response[0][1]; + result(0, 1, i) = response[1][0]; + result(1, 1, i) = response[1][1]; + } + } + else + { + for(unsigned int i = 0; i < freq.size(); ++i) + { + diag22c_t af = station->arrayFactor(time, freq(i), direction, + freq0(i), station0, tile0); + + if(itsInverse) + { + invert(af); + } + + result(0, 0, i) = af[0]; + result(1, 1, i) = af[1]; + } + } + } + else if(itsUseElementResponse) + { + // For a station with multiple antenna fields, need to select for which + // field the element response will be evaluated. Here the first field of the + // station is always selected. + AntennaField::ConstPtr field = *station->beginFields(); + + vector3r_t direction = itsDirection->at(time); + for(unsigned int i = 0; i < freq.size(); ++i) + { + matrix22c_t response = field->elementResponse(time, freq(i), + direction); + + if(itsInverse) + { + invert(response); + } + + result(0, 0, i) = response[0][0]; + result(1, 0, i) = response[0][1]; + result(0, 1, i) = response[1][0]; + result(1, 1, i) = response[1][1]; + } + } + else + { + for(unsigned int i = 0; i < freq.size(); ++i) + { + result(0, 0, i) = 1.0; + result(1, 1, i) = 1.0; + } + } + + return result; + } + + Matrix<DComplex> PyStationResponse::evaluate_itrf( + const Station::ConstPtr &station, double time, double freq, double freq0, + const vector3r_t &direction, const vector3r_t &station0, + const vector3r_t &tile0) const + { + Matrix<DComplex> result(2, 2, 0.0); + if(itsUseArrayFactor) + { + if(itsUseElementResponse) + { + matrix22c_t response = station->response(time, freq, direction, freq0, + station0, tile0); + + if(itsInverse) + { + invert(response); + } + + result(0, 0) = response[0][0]; + result(1, 0) = response[0][1]; + result(0, 1) = response[1][0]; + result(1, 1) = response[1][1]; + } + else + { + diag22c_t af = station->arrayFactor(time, freq, direction, freq0, + station0, tile0); + + if(itsInverse) + { + invert(af); + } + + result(0, 0) = af[0]; + result(1, 1) = af[1]; + } + } + else if(itsUseElementResponse) + { + // For a station with multiple antenna fields, need to select for which + // field the element response will be evaluated. Here the first field of + // the station is always selected. + AntennaField::ConstPtr field = *station->beginFields(); + + matrix22c_t response = field->elementResponse(time, freq, + direction); + + if(itsInverse) + { + invert(response); + } + + result(0, 0) = response[0][0]; + result(1, 0) = response[0][1]; + result(0, 1) = response[1][0]; + result(1, 1) = response[1][1]; + } + else + { + result(0, 0) = 1.0; + result(1, 1) = 1.0; + } + + return result; + } + + Matrix<DComplex> PyStationResponse::evaluate(const Station::ConstPtr &station, + double time, double freq, double freq0) const + { + vector3r_t direction; + vector3r_t station0; + vector3r_t tile0; + if (itsUseArrayFactor) { + direction = itsDirection->at(time); + station0 = itsRefDelay->at(time); + tile0 = itsRefTile->at(time); + } else if (itsUseElementResponse) { + direction = itsDirection->at(time); + } + return evaluate_itrf(station, time, freq, freq0, direction, station0, tile0); + } + + void PyStationResponse::invert(matrix22c_t &in) const + { + complex_t invDet = 1.0 / (in[0][0] * in[1][1] - in[0][1] * in[1][0]); + + complex_t tmp = in[1][1]; + in[1][1] = in[0][0]; + in[0][0] = tmp; + + in[0][0] *= invDet; + in[0][1] *= -invDet; + in[1][0] *= -invDet; + in[1][1] *= invDet; + } + + void PyStationResponse::invert(diag22c_t &in) const + { + DComplex invDet = 1.0 / (in[0] * in[1]); + DComplex tmp = in[1]; + in[1] = in[0]; + in[0] = tmp; + + in[0] *= invDet; + in[1] *= invDet; + } + + // Now define the interface in Boost-Python. + void pystationresponse() + { + class_<PyStationResponse> ("StationResponse", + init<std::string, bool, bool, bool, bool>()) + .def ("version", &PyStationResponse::version, + (boost::python::arg("type")="other")) + .def ("setRefDelay", &PyStationResponse::setRefDelay, + (boost::python::arg("ra"), boost::python::arg("dec"))) + .def ("getRefDelay", &PyStationResponse::getRefDelay, + (boost::python::arg("time"))) + .def ("setRefTile", &PyStationResponse::setRefTile, + (boost::python::arg("ra"), boost::python::arg("dec"))) + .def ("getRefTile", &PyStationResponse::getRefTile, + (boost::python::arg("time"))) + .def ("setDirection", &PyStationResponse::setDirection, + (boost::python::arg("ra"), boost::python::arg("dec"))) + .def ("getDirection", &PyStationResponse::getDirection, + (boost::python::arg("time"))) + .def ("evaluate0", &PyStationResponse::evaluate0, + (boost::python::arg("time"))) + .def ("evaluate1", &PyStationResponse::evaluate1, + (boost::python::arg("time"), boost::python::arg("station"))) + .def ("evaluate2", &PyStationResponse::evaluate2, + (boost::python::arg("time"), boost::python::arg("station"), + boost::python::arg("channel"))) + .def ("evaluate3", &PyStationResponse::evaluate3, + (boost::python::arg("time"), boost::python::arg("station"), + boost::python::arg("freq"))) + .def ("evaluate4", &PyStationResponse::evaluate4, + (boost::python::arg("time"), boost::python::arg("station"), + boost::python::arg("freq"), boost::python::arg("direction"), + boost::python::arg("station0"), boost::python::arg("tile0"))) + ; + } + + namespace + { + MPosition toMPositionITRF(const vector3r_t &position) + { + MVPosition mvITRF(position[0], position[1], position[2]); + return MPosition(mvITRF, MPosition::ITRF); + } + + vector3r_t fromMPosition(const MPosition &position) + { + MVPosition mvPosition = position.getValue(); + vector3r_t result = {{mvPosition(0), mvPosition(1), mvPosition(2)}}; + return result; + } + + vector3r_t fromMDirection(const MDirection &direction) + { + MVDirection mvDirection = direction.getValue(); + vector3r_t result = {{mvDirection(0), mvDirection(1), mvDirection(2)}}; + return result; + } + + bool hasColumn(const Table &table, const string &column) + { + return table.tableDesc().isColumn(column); + } + + bool hasSubTable(const Table &table, const string &name) + { + return table.keywordSet().isDefined(name); + } + + Table getSubTable(const Table &table, const string &name) + { + return table.keywordSet().asTable(name); + } + + MPosition readObservatoryPosition(const MeasurementSet &ms, + unsigned int idObservation, const MPosition &defaultPosition) + { + // Get the instrument position in ITRF coordinates, or use the centroid + // of the station positions if the instrument position is unknown. + ROMSObservationColumns observation(ms.observation()); + ASSERT(observation.nrow() > idObservation); + ASSERT(!observation.flagRow()(idObservation)); + + // Read observatory name and try to look-up its position. + const string observatory = observation.telescopeName()(idObservation); + + // Look-up observatory position, default to specified default position. + MPosition position(defaultPosition); + MeasTable::Observatory(position, observatory); + return position; + } + + MDirection readPhaseReference(const MeasurementSet &ms, + unsigned int idField) + { + ROMSFieldColumns field(ms.field()); + ASSERT(field.nrow() > idField); + ASSERT(!field.flagRow()(idField)); + + return field.phaseDirMeas(idField); + } + + MDirection readDelayReference(const MeasurementSet &ms, + unsigned int idField) + { + ROMSFieldColumns field(ms.field()); + ASSERT(field.nrow() > idField); + ASSERT(!field.flagRow()(idField)); + + return field.delayDirMeas(idField); + } + + MDirection readTileReference(const MeasurementSet &ms, + unsigned int idField) + { + // The MeasurementSet class does not support LOFAR specific columns, so we + // use ROArrayMeasColumn to read the tile beam reference direction. + Table tab_field = getSubTable(ms, "FIELD"); + + static const String columnName = "LOFAR_TILE_BEAM_DIR"; + if(hasColumn(tab_field, columnName)) + { + ROArrayMeasColumn<MDirection> c_direction(tab_field, columnName); + if(c_direction.isDefined(idField)) + { + return c_direction(idField)(IPosition(1, 0)); + } + } + + // By default, the tile beam reference direction is assumed to be equal + // to the station beam reference direction (for backward compatibility, + // and for non-HBA measurements). + return readDelayReference(ms, idField); + } + } //# unnamed namespace + +} //# namespace BBS +} //# namespace LOFAR + +// Define the python module itself. +BOOST_PYTHON_MODULE(_stationresponse) +{ + casacore::pyrap::register_convert_excp(); + casacore::pyrap::register_convert_basicdata(); + casacore::pyrap::register_convert_casa_valueholder(); + + LOFAR::BBS::pystationresponse(); +} diff --git a/CEP/Calibration/pystationresponse/test/CMakeLists.txt b/CEP/Calibration/pystationresponse/test/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..64287aff04e0cb41e5cd9c12f7fc3290a4302eb5 --- /dev/null +++ b/CEP/Calibration/pystationresponse/test/CMakeLists.txt @@ -0,0 +1,15 @@ +# $Id$ + +include(LofarCTest) + +include(LofarFindPackage) + +lofar_find_package(Casacore REQUIRED COMPONENTS python) +if(CASA_PYTHON3_LIBRARY) + #This test is disabled due to boost-python linking problems on CEP3 + #lofar_add_test(tStationBeamNCP) +else(CASA_PYTHON3_LIBRARY) + message(WARNING "Python-casacore was not found, disabling tStationBeamNCP") +endif(CASA_PYTHON3_LIBRARY) + +lofar_add_test(tpystationresponse) diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/ANTENNA/table.dat b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/ANTENNA/table.dat new file mode 100644 index 0000000000000000000000000000000000000000..b095e0ebb3d0255ac0e25b70ca2ea04b978befcf Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/ANTENNA/table.dat differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/ANTENNA/table.f0 b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/ANTENNA/table.f0 new file mode 100644 index 0000000000000000000000000000000000000000..b4c0b8a5f7ab40e29381631342f0047f8c70d5f6 Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/ANTENNA/table.f0 differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/ANTENNA/table.info b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/ANTENNA/table.info new file mode 100644 index 0000000000000000000000000000000000000000..a1a976ed49439abda34d2c64cfd0360cfcdef651 --- /dev/null +++ b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/ANTENNA/table.info @@ -0,0 +1,3 @@ +Type = +SubType = + diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/ANTENNA/table.lock b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/ANTENNA/table.lock new file mode 100644 index 0000000000000000000000000000000000000000..5e5472154b432f2e0131195718fbac5ec52d6728 Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/ANTENNA/table.lock differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/DATA_DESCRIPTION/table.dat b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/DATA_DESCRIPTION/table.dat new file mode 100644 index 0000000000000000000000000000000000000000..20ab416e6b2089d0bb7ed946963b87aa3a535c04 Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/DATA_DESCRIPTION/table.dat differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/DATA_DESCRIPTION/table.f0 b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/DATA_DESCRIPTION/table.f0 new file mode 100644 index 0000000000000000000000000000000000000000..bc76518f7b4e25a64bb5a1e2570ddf4df13bfffa Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/DATA_DESCRIPTION/table.f0 differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/DATA_DESCRIPTION/table.info b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/DATA_DESCRIPTION/table.info new file mode 100644 index 0000000000000000000000000000000000000000..a1a976ed49439abda34d2c64cfd0360cfcdef651 --- /dev/null +++ b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/DATA_DESCRIPTION/table.info @@ -0,0 +1,3 @@ +Type = +SubType = + diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/DATA_DESCRIPTION/table.lock b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/DATA_DESCRIPTION/table.lock new file mode 100644 index 0000000000000000000000000000000000000000..c985340dbb53f0a213d568ce9ccdc2911fc9b2f0 Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/DATA_DESCRIPTION/table.lock differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FEED/table.dat b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FEED/table.dat new file mode 100644 index 0000000000000000000000000000000000000000..be5d92ea9a98d4c6f864d6d6bee56e4ded5392c3 Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FEED/table.dat differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FEED/table.f0 b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FEED/table.f0 new file mode 100644 index 0000000000000000000000000000000000000000..82dce6ce873ae5a5f52f504f12e8d5a19af459eb Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FEED/table.f0 differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FEED/table.f0i b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FEED/table.f0i new file mode 100644 index 0000000000000000000000000000000000000000..b3032435e45263162440d053edccfc677ce9d36d Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FEED/table.f0i differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FEED/table.info b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FEED/table.info new file mode 100644 index 0000000000000000000000000000000000000000..a1a976ed49439abda34d2c64cfd0360cfcdef651 --- /dev/null +++ b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FEED/table.info @@ -0,0 +1,3 @@ +Type = +SubType = + diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FEED/table.lock b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FEED/table.lock new file mode 100644 index 0000000000000000000000000000000000000000..9f801dacd75bea58e64925ea82f4964e11374fc1 Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FEED/table.lock differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FIELD/table.dat b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FIELD/table.dat new file mode 100644 index 0000000000000000000000000000000000000000..51bf40ea2f1d0e463019ed6e3f3a111f05309ce4 Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FIELD/table.dat differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FIELD/table.f0 b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FIELD/table.f0 new file mode 100644 index 0000000000000000000000000000000000000000..bb5818d375d65a5c60cef9c576cf15552c07d47c Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FIELD/table.f0 differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FIELD/table.f0i b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FIELD/table.f0i new file mode 100644 index 0000000000000000000000000000000000000000..b6248a9d8abb5ed5e19cce8a510c83d7745f0de1 Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FIELD/table.f0i differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FIELD/table.info b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FIELD/table.info new file mode 100644 index 0000000000000000000000000000000000000000..a1a976ed49439abda34d2c64cfd0360cfcdef651 --- /dev/null +++ b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FIELD/table.info @@ -0,0 +1,3 @@ +Type = +SubType = + diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FIELD/table.lock b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FIELD/table.lock new file mode 100644 index 0000000000000000000000000000000000000000..053e78092f1c8750ef25123f700a4e0a6583606c Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FIELD/table.lock differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FLAG_CMD/table.dat b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FLAG_CMD/table.dat new file mode 100644 index 0000000000000000000000000000000000000000..360359641eafdad08c4c32e80d43b7af36c1328a Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FLAG_CMD/table.dat differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FLAG_CMD/table.f0 b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FLAG_CMD/table.f0 new file mode 100644 index 0000000000000000000000000000000000000000..acf64ff0cee834d15e3054ae8c3bab48645789f0 Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FLAG_CMD/table.f0 differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FLAG_CMD/table.info b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FLAG_CMD/table.info new file mode 100644 index 0000000000000000000000000000000000000000..a1a976ed49439abda34d2c64cfd0360cfcdef651 --- /dev/null +++ b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FLAG_CMD/table.info @@ -0,0 +1,3 @@ +Type = +SubType = + diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FLAG_CMD/table.lock b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FLAG_CMD/table.lock new file mode 100644 index 0000000000000000000000000000000000000000..6075ab6a530b38d4863db8006c50250ed8f94665 Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FLAG_CMD/table.lock differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/HISTORY/table.dat b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/HISTORY/table.dat new file mode 100644 index 0000000000000000000000000000000000000000..a1e994dbee2e354294c462ae3a48df687490ea08 Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/HISTORY/table.dat differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/HISTORY/table.f0 b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/HISTORY/table.f0 new file mode 100644 index 0000000000000000000000000000000000000000..9c5498713f1bb7d8f9450c22fe7af84cba01619f Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/HISTORY/table.f0 differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/HISTORY/table.info b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/HISTORY/table.info new file mode 100644 index 0000000000000000000000000000000000000000..a1a976ed49439abda34d2c64cfd0360cfcdef651 --- /dev/null +++ b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/HISTORY/table.info @@ -0,0 +1,3 @@ +Type = +SubType = + diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/HISTORY/table.lock b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/HISTORY/table.lock new file mode 100644 index 0000000000000000000000000000000000000000..9ecc6597675fd98c9ba1acc22a7181bcceef23ce Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/HISTORY/table.lock differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_ANTENNA_FIELD/table.dat b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_ANTENNA_FIELD/table.dat new file mode 100644 index 0000000000000000000000000000000000000000..7a5778d435bd5cc2d50ec36ec0f4cb366d04d889 Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_ANTENNA_FIELD/table.dat differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_ANTENNA_FIELD/table.f0 b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_ANTENNA_FIELD/table.f0 new file mode 100644 index 0000000000000000000000000000000000000000..9de9acfef02ea9bdde35ac04f1b104878a109a10 Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_ANTENNA_FIELD/table.f0 differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_ANTENNA_FIELD/table.f0i b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_ANTENNA_FIELD/table.f0i new file mode 100644 index 0000000000000000000000000000000000000000..5c0f9a2a88a5e1772a7c868db8687e4458488920 Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_ANTENNA_FIELD/table.f0i differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_ANTENNA_FIELD/table.info b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_ANTENNA_FIELD/table.info new file mode 100644 index 0000000000000000000000000000000000000000..a1a976ed49439abda34d2c64cfd0360cfcdef651 --- /dev/null +++ b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_ANTENNA_FIELD/table.info @@ -0,0 +1,3 @@ +Type = +SubType = + diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_ANTENNA_FIELD/table.lock b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_ANTENNA_FIELD/table.lock new file mode 100644 index 0000000000000000000000000000000000000000..4e5c1b43733f5aac86d4014f55083d1aedcff429 Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_ANTENNA_FIELD/table.lock differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_ELEMENT_FAILURE/table.dat b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_ELEMENT_FAILURE/table.dat new file mode 100644 index 0000000000000000000000000000000000000000..9ba0c7141eed0faa82b9afc1ea616ffea109bdf9 Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_ELEMENT_FAILURE/table.dat differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_ELEMENT_FAILURE/table.f0 b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_ELEMENT_FAILURE/table.f0 new file mode 100644 index 0000000000000000000000000000000000000000..a890c6a767ea4d04d226092c34457cccd9f04000 Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_ELEMENT_FAILURE/table.f0 differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_ELEMENT_FAILURE/table.info b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_ELEMENT_FAILURE/table.info new file mode 100644 index 0000000000000000000000000000000000000000..a1a976ed49439abda34d2c64cfd0360cfcdef651 --- /dev/null +++ b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_ELEMENT_FAILURE/table.info @@ -0,0 +1,3 @@ +Type = +SubType = + diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_ELEMENT_FAILURE/table.lock b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_ELEMENT_FAILURE/table.lock new file mode 100644 index 0000000000000000000000000000000000000000..f4914d9f8865ca727492994e62f19d07935280e2 Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_ELEMENT_FAILURE/table.lock differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_STATION/table.dat b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_STATION/table.dat new file mode 100644 index 0000000000000000000000000000000000000000..c05c4861c9cc9b1b64985e0cc90e5f7d99ea2adf Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_STATION/table.dat differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_STATION/table.f0 b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_STATION/table.f0 new file mode 100644 index 0000000000000000000000000000000000000000..1cfcc6106ee6149221b8fcef3d1c3540110ee95b Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_STATION/table.f0 differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_STATION/table.info b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_STATION/table.info new file mode 100644 index 0000000000000000000000000000000000000000..a1a976ed49439abda34d2c64cfd0360cfcdef651 --- /dev/null +++ b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_STATION/table.info @@ -0,0 +1,3 @@ +Type = +SubType = + diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_STATION/table.lock b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_STATION/table.lock new file mode 100644 index 0000000000000000000000000000000000000000..1c8629632ec05736163cbbd0c96acae378961301 Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_STATION/table.lock differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/OBSERVATION/table.dat b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/OBSERVATION/table.dat new file mode 100644 index 0000000000000000000000000000000000000000..e51b8f283d20f6d0e00166c2689297f87865e40d Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/OBSERVATION/table.dat differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/OBSERVATION/table.f0 b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/OBSERVATION/table.f0 new file mode 100644 index 0000000000000000000000000000000000000000..d7262f7342b1b5a50c58f9fe060a4fccd703f6ec Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/OBSERVATION/table.f0 differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/OBSERVATION/table.info b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/OBSERVATION/table.info new file mode 100644 index 0000000000000000000000000000000000000000..a1a976ed49439abda34d2c64cfd0360cfcdef651 --- /dev/null +++ b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/OBSERVATION/table.info @@ -0,0 +1,3 @@ +Type = +SubType = + diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/OBSERVATION/table.lock b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/OBSERVATION/table.lock new file mode 100644 index 0000000000000000000000000000000000000000..27823d906cf5fd010896ba8ad05a589705305a6e Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/OBSERVATION/table.lock differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/POINTING/table.dat b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/POINTING/table.dat new file mode 100644 index 0000000000000000000000000000000000000000..51f34d5a8a890e1f3d922d1247ce0328fb44d5e8 Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/POINTING/table.dat differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/POINTING/table.f0 b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/POINTING/table.f0 new file mode 100644 index 0000000000000000000000000000000000000000..a6ee43a66924940443a9f8b1d61186599c928e70 Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/POINTING/table.f0 differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/POINTING/table.f0i b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/POINTING/table.f0i new file mode 100644 index 0000000000000000000000000000000000000000..c09f05b309fbcc6ff12c9eb91aff39455347c41e Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/POINTING/table.f0i differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/POINTING/table.info b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/POINTING/table.info new file mode 100644 index 0000000000000000000000000000000000000000..a1a976ed49439abda34d2c64cfd0360cfcdef651 --- /dev/null +++ b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/POINTING/table.info @@ -0,0 +1,3 @@ +Type = +SubType = + diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/POINTING/table.lock b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/POINTING/table.lock new file mode 100644 index 0000000000000000000000000000000000000000..47dddc91a4a9c1eadf54ec7882fa8e13beb6ea5c Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/POINTING/table.lock differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/POLARIZATION/table.dat b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/POLARIZATION/table.dat new file mode 100644 index 0000000000000000000000000000000000000000..15d239d1be0e401e157d39b9a204533cbd948ad9 Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/POLARIZATION/table.dat differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/POLARIZATION/table.f0 b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/POLARIZATION/table.f0 new file mode 100644 index 0000000000000000000000000000000000000000..d487c40cb0089a9062007bdf010a13075950f19d Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/POLARIZATION/table.f0 differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/POLARIZATION/table.f0i b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/POLARIZATION/table.f0i new file mode 100644 index 0000000000000000000000000000000000000000..247a4aff500069af7c07dbcd0073a7299984dba3 Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/POLARIZATION/table.f0i differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/POLARIZATION/table.info b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/POLARIZATION/table.info new file mode 100644 index 0000000000000000000000000000000000000000..a1a976ed49439abda34d2c64cfd0360cfcdef651 --- /dev/null +++ b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/POLARIZATION/table.info @@ -0,0 +1,3 @@ +Type = +SubType = + diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/POLARIZATION/table.lock b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/POLARIZATION/table.lock new file mode 100644 index 0000000000000000000000000000000000000000..ecc3f973aab244303020d8132becf7db22bad38e Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/POLARIZATION/table.lock differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/PROCESSOR/table.dat b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/PROCESSOR/table.dat new file mode 100644 index 0000000000000000000000000000000000000000..53616aa517a311fdc9c7880121036b145abe1591 Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/PROCESSOR/table.dat differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/PROCESSOR/table.f0 b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/PROCESSOR/table.f0 new file mode 100644 index 0000000000000000000000000000000000000000..db03d9ca6c2db7f53db2f0e93656a9b2041f7d66 Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/PROCESSOR/table.f0 differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/PROCESSOR/table.info b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/PROCESSOR/table.info new file mode 100644 index 0000000000000000000000000000000000000000..a1a976ed49439abda34d2c64cfd0360cfcdef651 --- /dev/null +++ b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/PROCESSOR/table.info @@ -0,0 +1,3 @@ +Type = +SubType = + diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/PROCESSOR/table.lock b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/PROCESSOR/table.lock new file mode 100644 index 0000000000000000000000000000000000000000..9c961a98add6c1121e90197e8addec5732d3127e Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/PROCESSOR/table.lock differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/SPECTRAL_WINDOW/table.dat b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/SPECTRAL_WINDOW/table.dat new file mode 100644 index 0000000000000000000000000000000000000000..df3191ed563dd7ef5a9a432c36cf410fdee946cb Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/SPECTRAL_WINDOW/table.dat differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/SPECTRAL_WINDOW/table.f0 b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/SPECTRAL_WINDOW/table.f0 new file mode 100644 index 0000000000000000000000000000000000000000..f4af5e96200de834778618267c16a76e7aea6d03 Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/SPECTRAL_WINDOW/table.f0 differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/SPECTRAL_WINDOW/table.f0i b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/SPECTRAL_WINDOW/table.f0i new file mode 100644 index 0000000000000000000000000000000000000000..3d1e0e4020734dcf0c2bbe992804fb117e2cd285 Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/SPECTRAL_WINDOW/table.f0i differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/SPECTRAL_WINDOW/table.info b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/SPECTRAL_WINDOW/table.info new file mode 100644 index 0000000000000000000000000000000000000000..a1a976ed49439abda34d2c64cfd0360cfcdef651 --- /dev/null +++ b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/SPECTRAL_WINDOW/table.info @@ -0,0 +1,3 @@ +Type = +SubType = + diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/SPECTRAL_WINDOW/table.lock b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/SPECTRAL_WINDOW/table.lock new file mode 100644 index 0000000000000000000000000000000000000000..5bbad139d9b1a15ea7c702c6e84484afb99990ad Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/SPECTRAL_WINDOW/table.lock differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/STATE/table.dat b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/STATE/table.dat new file mode 100644 index 0000000000000000000000000000000000000000..943b7b45ecfdbac71d0acddb195a11faf4cf0098 Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/STATE/table.dat differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/STATE/table.f0 b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/STATE/table.f0 new file mode 100644 index 0000000000000000000000000000000000000000..f0f9754587b65cf2ca1a7aa5829e4e5f643b492b Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/STATE/table.f0 differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/STATE/table.info b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/STATE/table.info new file mode 100644 index 0000000000000000000000000000000000000000..a1a976ed49439abda34d2c64cfd0360cfcdef651 --- /dev/null +++ b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/STATE/table.info @@ -0,0 +1,3 @@ +Type = +SubType = + diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/STATE/table.lock b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/STATE/table.lock new file mode 100644 index 0000000000000000000000000000000000000000..7c926cb146a2f4fbcc57d1b4829c182797332869 Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/STATE/table.lock differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.dat b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.dat new file mode 100644 index 0000000000000000000000000000000000000000..8bf109977b5c4824f5bd56dfa2e73f5d9a4c49ef Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.dat differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f0 b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f0 new file mode 100644 index 0000000000000000000000000000000000000000..82aa58d25804a10c8d21471f7b2b8db954446b42 Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f0 differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f1 b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f1 new file mode 100644 index 0000000000000000000000000000000000000000..980491dbbbb03e5d108d452e1525c765cd68fb9d Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f1 differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f1i b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f1i new file mode 100644 index 0000000000000000000000000000000000000000..2308328019d48cc87144699973b89461338d1a0b Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f1i differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f2 b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f2 new file mode 100644 index 0000000000000000000000000000000000000000..c2f9d67a5e6c106521419ada064547cb309d3888 Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f2 differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f2_TSM0 b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f2_TSM0 new file mode 100644 index 0000000000000000000000000000000000000000..548f6fc26a7832950aa1097322beb2c683756356 Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f2_TSM0 differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f3 b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f3 new file mode 100644 index 0000000000000000000000000000000000000000..b047086288f3e36a1c29991eec5b5ac539642030 Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f3 differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f3_TSM0 b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f3_TSM0 new file mode 100644 index 0000000000000000000000000000000000000000..10c971e1e574064accc23cc538c64c308d564882 Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f3_TSM0 differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f4 b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f4 new file mode 100644 index 0000000000000000000000000000000000000000..cf4efd44704f82e24355969a544d1eb3abc40bdc Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f4 differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f4_TSM0 b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f4_TSM0 new file mode 100644 index 0000000000000000000000000000000000000000..7d46f8a944e0571e183a29d5695fbbebf3e4f24c Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f4_TSM0 differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f5 b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f5 new file mode 100644 index 0000000000000000000000000000000000000000..ae90f9c3dda47e82404bbd5e63d99d0523aa9879 Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f5 differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f5_TSM0 b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f5_TSM0 new file mode 100644 index 0000000000000000000000000000000000000000..7df6a9895eb03ecc7df8419fc8148a1abb96ff21 Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f5_TSM0 differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f6 b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f6 new file mode 100644 index 0000000000000000000000000000000000000000..980b553c69975adc1f879e34dbeec0d9b10ace4e Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f6 differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f6_TSM0 b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f6_TSM0 new file mode 100644 index 0000000000000000000000000000000000000000..3e414bb6fc1c2b199c0781a972f742ee10d496f7 Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f6_TSM0 differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.info b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.info new file mode 100644 index 0000000000000000000000000000000000000000..105af65c13868a224843eb0cb3596828614e8fd0 --- /dev/null +++ b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.info @@ -0,0 +1,5 @@ +Type = Measurement Set +SubType = + +This is a MeasurementSet Table holding measurements from a Telescope +This is a LOFAR MeasurementSet Table diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.lock b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.lock new file mode 100644 index 0000000000000000000000000000000000000000..1cafbeea7d739556b7e3bd23d2865635f5154258 Binary files /dev/null and b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.lock differ diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.py b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.py new file mode 100644 index 0000000000000000000000000000000000000000..bed4602c3395bfc69c8d824a9e02135c7b9c13b2 --- /dev/null +++ b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.py @@ -0,0 +1,25 @@ +"Test the Station Beam at the NCP. Rationale: when pointing at the NCP all stations should have (almost) the same beam" + + +import sys + +import lofar.stationresponse as st +import pyrap.tables as tab +import numpy as np + +MSNAME='tStationBeamNCP.in.MS' + +myt=tab.table(MSNAME,ack=False) +mys=st.stationresponse( msname=MSNAME, inverse=False, useElementResponse=True, useArrayFactor=False, useChanFreq=False) +times=myt.getcol('TIME') +mys.setDirection(0.01,0.5*np.pi) + +a=[mys.evaluateStation(time=times[0],station=st) for st in range(20)] + +for a1 in a: + for a2 in a: + if np.linalg.norm(a1-a2)>1.e-3: + print("a1=",a1,"\na2=",a2,"\nnorm=",np.linalg.norm(a1-a2)) + sys.exit(1) + +sys.exit(0) diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.sh b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.sh new file mode 100755 index 0000000000000000000000000000000000000000..28e06978533ca6b511e00e32c1906da96c4db66f --- /dev/null +++ b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.sh @@ -0,0 +1,2 @@ +#!/bin/sh +./runctest.sh tStationBeamNCP diff --git a/CEP/Calibration/pystationresponse/test/tpystationresponse.py b/CEP/Calibration/pystationresponse/test/tpystationresponse.py new file mode 100644 index 0000000000000000000000000000000000000000..f69e21ff7d12d104e4cdefa8b988983b3d6ddb5a --- /dev/null +++ b/CEP/Calibration/pystationresponse/test/tpystationresponse.py @@ -0,0 +1,5 @@ + + +import lofar.stationresponse + + diff --git a/CEP/Calibration/pystationresponse/test/tpystationresponse.sh b/CEP/Calibration/pystationresponse/test/tpystationresponse.sh new file mode 100755 index 0000000000000000000000000000000000000000..f967776d2c39cf2a894a13444bb6bd9a53d5fc6c --- /dev/null +++ b/CEP/Calibration/pystationresponse/test/tpystationresponse.sh @@ -0,0 +1,2 @@ +#!/bin/sh +./runctest.sh tpystationresponse diff --git a/CMake/LofarPackageList.cmake b/CMake/LofarPackageList.cmake index 2320c39a326fe8770ae89ac84e902d4038841ed2..a7b835109903f83ffd08c7369215c379f1d785d8 100644 --- a/CMake/LofarPackageList.cmake +++ b/CMake/LofarPackageList.cmake @@ -27,6 +27,10 @@ if(NOT DEFINED LOFAR_PACKAGE_LIST_INCLUDED) set(pyparmdb_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/pyparmdb) set(BBSKernel_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/Calibration/BBSKernel) set(BBSControl_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/Calibration/BBSControl) + set(ExpIon_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/Calibration/ExpIon) + set(pystationresponse_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/Calibration/pystationresponse) + set(ElementResponse_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/Calibration/ElementResponse) + set(StationResponse_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/Calibration/StationResponse) set(DPPP_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/DP3/DPPP) set(TestDynDPPP_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/DP3/TestDynDPPP) set(PythonDPPP_SOURCE_DIR ${CMAKE_SOURCE_DIR}/CEP/DP3/PythonDPPP)