From 6b01695aa09fd4b211eeedc9c03ea2108a287eb7 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 11 Jun 2020 13:20:58 +0000 Subject: [PATCH] Revert "Removed station response packages, which are not used in LOFAR and externally available as https://github.com/lofar-astron/LOFARBeam". Was still needed by BBSKernel, oops. This reverts commit 34770298013115c02c2140cd266a08a55ef6f0cb. --- CEP/Calibration/CMakeLists.txt | 4 + .../ElementResponse/CMakeLists.txt | 10 + .../include/ElementResponse/CMakeLists.txt | 11 + .../include/ElementResponse/ElementResponse.h | 101 + .../ElementResponse/src/CMakeLists.txt | 7 + .../ElementResponse/src/DefaultCoeffHBA.cc | 74 + .../ElementResponse/src/DefaultCoeffLBA.cc | 74 + .../ElementResponse/src/ElementResponse.cc | 157 + .../ElementResponse/src/convert_coeff.py | 176 ++ .../src/element_beam_HBA.coeff | 101 + .../src/element_beam_LBA.coeff | 101 + CEP/Calibration/ExpIon/CMakeLists.txt | 9 + CEP/Calibration/ExpIon/src/CMakeLists.txt | 43 + CEP/Calibration/ExpIon/src/MMionosphere.py | 674 +++++ CEP/Calibration/ExpIon/src/PosTools.py | 272 ++ CEP/Calibration/ExpIon/src/__init__.py | 26 + CEP/Calibration/ExpIon/src/acalc.py | 234 ++ CEP/Calibration/ExpIon/src/baselinefitting.cc | 228 ++ CEP/Calibration/ExpIon/src/baselinefitting.py | 35 + CEP/Calibration/ExpIon/src/calibrate-ion | 231 ++ CEP/Calibration/ExpIon/src/client.py | 41 + CEP/Calibration/ExpIon/src/error.py | 6 + CEP/Calibration/ExpIon/src/fitClockTEC.py | 938 ++++++ CEP/Calibration/ExpIon/src/format.py | 486 +++ CEP/Calibration/ExpIon/src/io.py | 308 ++ CEP/Calibration/ExpIon/src/ionosphere.py | 1380 +++++++++ CEP/Calibration/ExpIon/src/mpfit.py | 2593 +++++++++++++++++ CEP/Calibration/ExpIon/src/parmdbmain.py | 52 + CEP/Calibration/ExpIon/src/parmdbwriter.py | 44 + CEP/Calibration/ExpIon/src/read_sagecal.py | 223 ++ CEP/Calibration/ExpIon/src/readms-part.py | 34 + CEP/Calibration/ExpIon/src/readms-part.sh | 8 + CEP/Calibration/ExpIon/src/readms.py | 105 + CEP/Calibration/ExpIon/src/repairGlobaldb.py | 361 +++ CEP/Calibration/ExpIon/src/sphere.py | 467 +++ .../StationResponse/CMakeLists.txt | 14 + .../include/StationResponse/AntennaField.h | 261 ++ .../include/StationResponse/AntennaFieldHBA.h | 73 + .../include/StationResponse/AntennaFieldLBA.h | 64 + .../include/StationResponse/AntennaModelHBA.h | 69 + .../include/StationResponse/AntennaModelLBA.h | 57 + .../include/StationResponse/CMakeLists.txt | 23 + .../include/StationResponse/Constants.h | 60 + .../StationResponse/DualDipoleAntenna.h | 55 + .../include/StationResponse/ITRFDirection.h | 65 + .../StationResponse/LofarMetaDataUtil.h | 65 + .../include/StationResponse/MathUtil.h | 172 ++ .../include/StationResponse/Station.h | 361 +++ .../include/StationResponse/TileAntenna.h | 68 + .../include/StationResponse/Types.h | 234 ++ .../StationResponse/src/AntennaField.cc | 233 ++ .../StationResponse/src/AntennaFieldHBA.cc | 77 + .../StationResponse/src/AntennaFieldLBA.cc | 54 + .../StationResponse/src/AntennaModelHBA.cc | 64 + .../StationResponse/src/AntennaModelLBA.cc | 36 + .../StationResponse/src/CMakeLists.txt | 20 + .../StationResponse/src/DualDipoleAntenna.cc | 51 + .../StationResponse/src/ITRFDirection.cc | 77 + .../StationResponse/src/LofarMetaDataUtil.cc | 335 +++ .../StationResponse/src/MathUtil.cc | 32 + .../StationResponse/src/Station.cc | 176 ++ .../StationResponse/src/TileAntenna.cc | 100 + CEP/Calibration/StationResponse/src/Types.cc | 32 + .../StationResponse/src/makeresponseimage.cc | 636 ++++ .../pystationresponse/CMakeLists.txt | 11 + .../pystationresponse/src/CMakeLists.txt | 28 + .../pystationresponse/src/__init__.py | 210 ++ .../src/pystationresponse.cc | 769 +++++ .../pystationresponse/test/CMakeLists.txt | 15 + .../tStationBeamNCP.in.MS/ANTENNA/table.dat | Bin 0 -> 3626 bytes .../tStationBeamNCP.in.MS/ANTENNA/table.f0 | Bin 0 -> 17424 bytes .../tStationBeamNCP.in.MS/ANTENNA/table.info | 3 + .../tStationBeamNCP.in.MS/ANTENNA/table.lock | Bin 0 -> 325 bytes .../DATA_DESCRIPTION/table.dat | Bin 0 -> 997 bytes .../DATA_DESCRIPTION/table.f0 | Bin 0 -> 1032 bytes .../DATA_DESCRIPTION/table.info | 3 + .../DATA_DESCRIPTION/table.lock | Bin 0 -> 325 bytes .../test/tStationBeamNCP.in.MS/FEED/table.dat | Bin 0 -> 4311 bytes .../test/tStationBeamNCP.in.MS/FEED/table.f0 | Bin 0 -> 12800 bytes .../test/tStationBeamNCP.in.MS/FEED/table.f0i | Bin 0 -> 4576 bytes .../tStationBeamNCP.in.MS/FEED/table.info | 3 + .../tStationBeamNCP.in.MS/FEED/table.lock | Bin 0 -> 325 bytes .../tStationBeamNCP.in.MS/FIELD/table.dat | Bin 0 -> 4006 bytes .../test/tStationBeamNCP.in.MS/FIELD/table.f0 | Bin 0 -> 5128 bytes .../tStationBeamNCP.in.MS/FIELD/table.f0i | Bin 0 -> 136 bytes .../tStationBeamNCP.in.MS/FIELD/table.info | 3 + .../tStationBeamNCP.in.MS/FIELD/table.lock | Bin 0 -> 325 bytes .../tStationBeamNCP.in.MS/FLAG_CMD/table.dat | Bin 0 -> 2423 bytes .../tStationBeamNCP.in.MS/FLAG_CMD/table.f0 | Bin 0 -> 2436 bytes .../tStationBeamNCP.in.MS/FLAG_CMD/table.info | 3 + .../tStationBeamNCP.in.MS/FLAG_CMD/table.lock | Bin 0 -> 325 bytes .../tStationBeamNCP.in.MS/HISTORY/table.dat | Bin 0 -> 2592 bytes .../tStationBeamNCP.in.MS/HISTORY/table.f0 | Bin 0 -> 614400 bytes .../tStationBeamNCP.in.MS/HISTORY/table.info | 3 + .../tStationBeamNCP.in.MS/HISTORY/table.lock | Bin 0 -> 325 bytes .../LOFAR_ANTENNA_FIELD/table.dat | Bin 0 -> 3864 bytes .../LOFAR_ANTENNA_FIELD/table.f0 | Bin 0 -> 15104 bytes .../LOFAR_ANTENNA_FIELD/table.f0i | Bin 0 -> 75712 bytes .../LOFAR_ANTENNA_FIELD/table.info | 3 + .../LOFAR_ANTENNA_FIELD/table.lock | Bin 0 -> 325 bytes .../LOFAR_ELEMENT_FAILURE/table.dat | Bin 0 -> 1242 bytes .../LOFAR_ELEMENT_FAILURE/table.f0 | Bin 0 -> 1024 bytes .../LOFAR_ELEMENT_FAILURE/table.info | 3 + .../LOFAR_ELEMENT_FAILURE/table.lock | Bin 0 -> 325 bytes .../LOFAR_STATION/table.dat | Bin 0 -> 923 bytes .../LOFAR_STATION/table.f0 | Bin 0 -> 2060 bytes .../LOFAR_STATION/table.info | 3 + .../LOFAR_STATION/table.lock | Bin 0 -> 325 bytes .../OBSERVATION/table.dat | Bin 0 -> 9335 bytes .../OBSERVATION/table.f0 | Bin 0 -> 30860 bytes .../OBSERVATION/table.info | 3 + .../OBSERVATION/table.lock | Bin 0 -> 325 bytes .../tStationBeamNCP.in.MS/POINTING/table.dat | Bin 0 -> 3428 bytes .../tStationBeamNCP.in.MS/POINTING/table.f0 | Bin 0 -> 2436 bytes .../tStationBeamNCP.in.MS/POINTING/table.f0i | Bin 0 -> 16 bytes .../tStationBeamNCP.in.MS/POINTING/table.info | 3 + .../tStationBeamNCP.in.MS/POINTING/table.lock | Bin 0 -> 325 bytes .../POLARIZATION/table.dat | Bin 0 -> 1306 bytes .../POLARIZATION/table.f0 | Bin 0 -> 1800 bytes .../POLARIZATION/table.f0i | Bin 0 -> 84 bytes .../POLARIZATION/table.info | 3 + .../POLARIZATION/table.lock | Bin 0 -> 325 bytes .../tStationBeamNCP.in.MS/PROCESSOR/table.dat | Bin 0 -> 1344 bytes .../tStationBeamNCP.in.MS/PROCESSOR/table.f0 | Bin 0 -> 1540 bytes .../PROCESSOR/table.info | 3 + .../PROCESSOR/table.lock | Bin 0 -> 325 bytes .../SPECTRAL_WINDOW/table.dat | Bin 0 -> 5224 bytes .../SPECTRAL_WINDOW/table.f0 | Bin 0 -> 6408 bytes .../SPECTRAL_WINDOW/table.f0i | Bin 0 -> 6320 bytes .../SPECTRAL_WINDOW/table.info | 3 + .../SPECTRAL_WINDOW/table.lock | Bin 0 -> 325 bytes .../tStationBeamNCP.in.MS/STATE/table.dat | Bin 0 -> 2016 bytes .../test/tStationBeamNCP.in.MS/STATE/table.f0 | Bin 0 -> 1548 bytes .../tStationBeamNCP.in.MS/STATE/table.info | 3 + .../tStationBeamNCP.in.MS/STATE/table.lock | Bin 0 -> 325 bytes .../test/tStationBeamNCP.in.MS/table.dat | Bin 0 -> 8122 bytes .../test/tStationBeamNCP.in.MS/table.f0 | Bin 0 -> 66048 bytes .../test/tStationBeamNCP.in.MS/table.f1 | Bin 0 -> 33362 bytes .../test/tStationBeamNCP.in.MS/table.f1i | Bin 0 -> 16 bytes .../test/tStationBeamNCP.in.MS/table.f2 | Bin 0 -> 276 bytes .../test/tStationBeamNCP.in.MS/table.f2_TSM0 | Bin 0 -> 24576 bytes .../test/tStationBeamNCP.in.MS/table.f3 | Bin 0 -> 289 bytes .../test/tStationBeamNCP.in.MS/table.f3_TSM0 | Bin 0 -> 1048576 bytes .../test/tStationBeamNCP.in.MS/table.f4 | Bin 0 -> 289 bytes .../test/tStationBeamNCP.in.MS/table.f4_TSM0 | Bin 0 -> 131072 bytes .../test/tStationBeamNCP.in.MS/table.f5 | Bin 0 -> 292 bytes .../test/tStationBeamNCP.in.MS/table.f5_TSM0 | Bin 0 -> 32768 bytes .../test/tStationBeamNCP.in.MS/table.f6 | Bin 0 -> 299 bytes .../test/tStationBeamNCP.in.MS/table.f6_TSM0 | Bin 0 -> 524288 bytes .../test/tStationBeamNCP.in.MS/table.info | 5 + .../test/tStationBeamNCP.in.MS/table.lock | Bin 0 -> 349 bytes .../pystationresponse/test/tStationBeamNCP.py | 25 + .../pystationresponse/test/tStationBeamNCP.sh | 2 + .../test/tpystationresponse.py | 5 + .../test/tpystationresponse.sh | 2 + CMake/LofarPackageList.cmake | 4 + 156 files changed, 14299 insertions(+) create mode 100644 CEP/Calibration/ElementResponse/CMakeLists.txt create mode 100644 CEP/Calibration/ElementResponse/include/ElementResponse/CMakeLists.txt create mode 100644 CEP/Calibration/ElementResponse/include/ElementResponse/ElementResponse.h create mode 100644 CEP/Calibration/ElementResponse/src/CMakeLists.txt create mode 100644 CEP/Calibration/ElementResponse/src/DefaultCoeffHBA.cc create mode 100644 CEP/Calibration/ElementResponse/src/DefaultCoeffLBA.cc create mode 100644 CEP/Calibration/ElementResponse/src/ElementResponse.cc create mode 100755 CEP/Calibration/ElementResponse/src/convert_coeff.py create mode 100644 CEP/Calibration/ElementResponse/src/element_beam_HBA.coeff create mode 100644 CEP/Calibration/ElementResponse/src/element_beam_LBA.coeff create mode 100644 CEP/Calibration/ExpIon/CMakeLists.txt create mode 100644 CEP/Calibration/ExpIon/src/CMakeLists.txt create mode 100755 CEP/Calibration/ExpIon/src/MMionosphere.py create mode 100644 CEP/Calibration/ExpIon/src/PosTools.py create mode 100755 CEP/Calibration/ExpIon/src/__init__.py create mode 100644 CEP/Calibration/ExpIon/src/acalc.py create mode 100644 CEP/Calibration/ExpIon/src/baselinefitting.cc create mode 100644 CEP/Calibration/ExpIon/src/baselinefitting.py create mode 100755 CEP/Calibration/ExpIon/src/calibrate-ion create mode 100644 CEP/Calibration/ExpIon/src/client.py create mode 100644 CEP/Calibration/ExpIon/src/error.py create mode 100644 CEP/Calibration/ExpIon/src/fitClockTEC.py create mode 100644 CEP/Calibration/ExpIon/src/format.py create mode 100644 CEP/Calibration/ExpIon/src/io.py create mode 100755 CEP/Calibration/ExpIon/src/ionosphere.py create mode 100644 CEP/Calibration/ExpIon/src/mpfit.py create mode 100644 CEP/Calibration/ExpIon/src/parmdbmain.py create mode 100755 CEP/Calibration/ExpIon/src/parmdbwriter.py create mode 100644 CEP/Calibration/ExpIon/src/read_sagecal.py create mode 100755 CEP/Calibration/ExpIon/src/readms-part.py create mode 100755 CEP/Calibration/ExpIon/src/readms-part.sh create mode 100755 CEP/Calibration/ExpIon/src/readms.py create mode 100644 CEP/Calibration/ExpIon/src/repairGlobaldb.py create mode 100644 CEP/Calibration/ExpIon/src/sphere.py create mode 100644 CEP/Calibration/StationResponse/CMakeLists.txt create mode 100644 CEP/Calibration/StationResponse/include/StationResponse/AntennaField.h create mode 100644 CEP/Calibration/StationResponse/include/StationResponse/AntennaFieldHBA.h create mode 100644 CEP/Calibration/StationResponse/include/StationResponse/AntennaFieldLBA.h create mode 100644 CEP/Calibration/StationResponse/include/StationResponse/AntennaModelHBA.h create mode 100644 CEP/Calibration/StationResponse/include/StationResponse/AntennaModelLBA.h create mode 100644 CEP/Calibration/StationResponse/include/StationResponse/CMakeLists.txt create mode 100644 CEP/Calibration/StationResponse/include/StationResponse/Constants.h create mode 100644 CEP/Calibration/StationResponse/include/StationResponse/DualDipoleAntenna.h create mode 100644 CEP/Calibration/StationResponse/include/StationResponse/ITRFDirection.h create mode 100644 CEP/Calibration/StationResponse/include/StationResponse/LofarMetaDataUtil.h create mode 100644 CEP/Calibration/StationResponse/include/StationResponse/MathUtil.h create mode 100644 CEP/Calibration/StationResponse/include/StationResponse/Station.h create mode 100644 CEP/Calibration/StationResponse/include/StationResponse/TileAntenna.h create mode 100644 CEP/Calibration/StationResponse/include/StationResponse/Types.h create mode 100644 CEP/Calibration/StationResponse/src/AntennaField.cc create mode 100644 CEP/Calibration/StationResponse/src/AntennaFieldHBA.cc create mode 100644 CEP/Calibration/StationResponse/src/AntennaFieldLBA.cc create mode 100644 CEP/Calibration/StationResponse/src/AntennaModelHBA.cc create mode 100644 CEP/Calibration/StationResponse/src/AntennaModelLBA.cc create mode 100644 CEP/Calibration/StationResponse/src/CMakeLists.txt create mode 100644 CEP/Calibration/StationResponse/src/DualDipoleAntenna.cc create mode 100644 CEP/Calibration/StationResponse/src/ITRFDirection.cc create mode 100644 CEP/Calibration/StationResponse/src/LofarMetaDataUtil.cc create mode 100644 CEP/Calibration/StationResponse/src/MathUtil.cc create mode 100644 CEP/Calibration/StationResponse/src/Station.cc create mode 100644 CEP/Calibration/StationResponse/src/TileAntenna.cc create mode 100644 CEP/Calibration/StationResponse/src/Types.cc create mode 100644 CEP/Calibration/StationResponse/src/makeresponseimage.cc create mode 100644 CEP/Calibration/pystationresponse/CMakeLists.txt create mode 100644 CEP/Calibration/pystationresponse/src/CMakeLists.txt create mode 100755 CEP/Calibration/pystationresponse/src/__init__.py create mode 100755 CEP/Calibration/pystationresponse/src/pystationresponse.cc create mode 100644 CEP/Calibration/pystationresponse/test/CMakeLists.txt create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/ANTENNA/table.dat create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/ANTENNA/table.f0 create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/ANTENNA/table.info create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/ANTENNA/table.lock create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/DATA_DESCRIPTION/table.dat create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/DATA_DESCRIPTION/table.f0 create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/DATA_DESCRIPTION/table.info create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/DATA_DESCRIPTION/table.lock create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FEED/table.dat create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FEED/table.f0 create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FEED/table.f0i create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FEED/table.info create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FEED/table.lock create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FIELD/table.dat create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FIELD/table.f0 create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FIELD/table.f0i create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FIELD/table.info create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FIELD/table.lock create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FLAG_CMD/table.dat create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FLAG_CMD/table.f0 create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FLAG_CMD/table.info create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/FLAG_CMD/table.lock create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/HISTORY/table.dat create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/HISTORY/table.f0 create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/HISTORY/table.info create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/HISTORY/table.lock create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_ANTENNA_FIELD/table.dat create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_ANTENNA_FIELD/table.f0 create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_ANTENNA_FIELD/table.f0i create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_ANTENNA_FIELD/table.info create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_ANTENNA_FIELD/table.lock create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_ELEMENT_FAILURE/table.dat create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_ELEMENT_FAILURE/table.f0 create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_ELEMENT_FAILURE/table.info create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_ELEMENT_FAILURE/table.lock create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_STATION/table.dat create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_STATION/table.f0 create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_STATION/table.info create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/LOFAR_STATION/table.lock create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/OBSERVATION/table.dat create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/OBSERVATION/table.f0 create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/OBSERVATION/table.info create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/OBSERVATION/table.lock create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/POINTING/table.dat create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/POINTING/table.f0 create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/POINTING/table.f0i create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/POINTING/table.info create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/POINTING/table.lock create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/POLARIZATION/table.dat create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/POLARIZATION/table.f0 create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/POLARIZATION/table.f0i create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/POLARIZATION/table.info create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/POLARIZATION/table.lock create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/PROCESSOR/table.dat create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/PROCESSOR/table.f0 create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/PROCESSOR/table.info create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/PROCESSOR/table.lock create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/SPECTRAL_WINDOW/table.dat create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/SPECTRAL_WINDOW/table.f0 create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/SPECTRAL_WINDOW/table.f0i create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/SPECTRAL_WINDOW/table.info create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/SPECTRAL_WINDOW/table.lock create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/STATE/table.dat create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/STATE/table.f0 create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/STATE/table.info create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/STATE/table.lock create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.dat create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f0 create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f1 create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f1i create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f2 create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f2_TSM0 create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f3 create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f3_TSM0 create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f4 create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f4_TSM0 create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f5 create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f5_TSM0 create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f6 create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.f6_TSM0 create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.info create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.in.MS/table.lock create mode 100644 CEP/Calibration/pystationresponse/test/tStationBeamNCP.py create mode 100755 CEP/Calibration/pystationresponse/test/tStationBeamNCP.sh create mode 100644 CEP/Calibration/pystationresponse/test/tpystationresponse.py create mode 100755 CEP/Calibration/pystationresponse/test/tpystationresponse.sh diff --git a/CEP/Calibration/CMakeLists.txt b/CEP/Calibration/CMakeLists.txt index a3c04936e25..c957fbcc8da 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 00000000000..a5bc6b53164 --- /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 00000000000..a97059ef372 --- /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 00000000000..f95ba672544 --- /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 00000000000..fbbf6cf0cc5 --- /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 00000000000..c37da7e5e27 --- /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 00000000000..d196d2356d1 --- /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 00000000000..1324bfd4be5 --- /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 00000000000..03618ec0e31 --- /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 00000000000..f2d39e70295 --- /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 00000000000..7259a7d7cec --- /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 00000000000..3c3ce32fcb3 --- /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 00000000000..7629de3ffc9 --- /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 00000000000..ced85cbfadf --- /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 00000000000..e883734b163 --- /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 00000000000..21bc2dc8a36 --- /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 00000000000..f888fc74daa --- /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 00000000000..792caea105a --- /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 00000000000..4708eea1265 --- /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 00000000000..6811a1d0e0f --- /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 00000000000..2d94ba48702 --- /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 00000000000..2e9ec7edc9e --- /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 00000000000..5b753e87c4d --- /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 00000000000..81b149105be --- /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 00000000000..06b09f7eab8 --- /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 00000000000..429a0e597fc --- /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 00000000000..2cf1ae18796 --- /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 00000000000..05eb57b3ef4 --- /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 00000000000..43a90cf0126 --- /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 00000000000..4a0fbb73bec --- /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 00000000000..0e20fa37483 --- /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 00000000000..e4f916f81a2 --- /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 00000000000..4385626bd71 --- /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 00000000000..1ee4a8d8898 --- /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 00000000000..9a26e4563a2 --- /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 00000000000..16350a206a8 --- /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 00000000000..934e0b22472 --- /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 00000000000..6caafee4964 --- /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 00000000000..f4c08254291 --- /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 00000000000..9ef452c0d5b --- /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 00000000000..7b6bfa543c6 --- /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 00000000000..b4dc8dff3c3 --- /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 00000000000..c77be6d22cd --- /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 00000000000..4ac6439fc6e --- /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 00000000000..280a6111a99 --- /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 00000000000..57327b0ed0e --- /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 00000000000..cb16fb898c2 --- /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 00000000000..0019c33dcdf --- /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 00000000000..3695aa3e816 --- /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 00000000000..8565e127528 --- /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 00000000000..d3ca5879353 --- /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 00000000000..ff099fb04a2 --- /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 00000000000..90838c135e6 --- /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 00000000000..52ca0accc54 --- /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 00000000000..87d02b94501 --- /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 00000000000..e31a7a54574 --- /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 00000000000..659d9f45ddb --- /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 00000000000..f7f9c49e025 --- /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 00000000000..4d96af8dfe4 --- /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 00000000000..1ce4c5af2ee --- /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 00000000000..7c4e21045a4 --- /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 00000000000..2813563ec96 --- /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 00000000000..5267ae75691 --- /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 00000000000..36a4276ffc9 --- /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 00000000000..e7c432c3b58 --- /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 00000000000..0d7aa5176a1 --- /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 00000000000..87961234ba1 --- /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 00000000000..828098ec170 --- /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 00000000000..64287aff04e --- /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 GIT binary patch literal 3626 zcmeHK&2G~`5Kd`A)0QBRiquvDRzj*;lvb#~0jVH4iA^mvKg20eF42-~8Vkn`_C`=7 zBn~_RCk{MH9)<@2zH#i-O&o%9Ksj`zv3F-?*E2gmJMQ=U{Y2vWRw9u|)%aUWU~mPv z23!EHR4i`V0cZ6mD9iq&AiM@9BpQXsn5_B&Rnc(VCZ@E@CU9jy8Cgi&nHH@CTdymw z%g^$T)$7=__d(O?!3@R%{?)Rk8EOr)tnyKK%xSeeA@O1zr)Nth9j2*jfmKyat*WKG z%DRqeOUP#o$?Yb0n}#fMJC2YBF$E+eZUdL~isPBmbV!<rgo{AH!XrFlj^;DS<3~g1 zdhJDz+p^a=woU0_z7CB4%5MOe1nHux7<x%7W4r;JeDWks`jK84{}2xh>6-TGGZ0AW zP<emcA;R#{#w^B`Cm_e$l9Xp%3awQj!D3ak@Cv8&sdV@J?2>w|s)0sy|1r(W=}Otq zYkIi^`7Onk!nQekoju9EVcj<O1an18xWaA-xOeEHOAik`Jr5q<FdE$AZX}Th(a2a3 z0?FD*MaB4T=w1pbwk`Hf_n1*p^6G9*G1S5iM5g;S3_J+?FjEQgqZ7;NJ+A&p9m*7R z<FH=Pm7-cxtF)rhKJ!c%&6+0f2q|1DN=?(F<eF{))t)GP!z3mpsSb>%BxyWTd7*{( zffEY`3Ha#O3QR&E;`kxu9LHh+Fi9Y-9VyT2)$%KhZ)z4lWi7{LvTb_Ibv}SN{ahe) z0cQ7Xyv8F@nJSi#OEr9PyEwQ`C@B4y<d)p!pR!E6?{Vq4Cbuw_jXlUtx8gs<=ZGQ6 z5+W12-3imnMo>ayO0Snk%Km<q<&_5y=T!S8qoxGqe-qa>UBPS{x2Jy(ba``!kva3C zypFyj67@HZ%9>KG2PV|@0`%O_3(U0H1rC!zgEl>WQrIF2vu6Qzp+uN=0pA*ES~)}w zt`B=DEWDQ!yn`(6puSyR#q-^4p^f|nm#qapC%=z{8fbpkf8JM<m>(nY%_*4FUr<LI z?b7~zRe16HD}tTIZqq0;dW%Hq!&)6BhIKDWi&T>+IWAA5{E0c>3(@1RC-eiqV)z$J zd<QNXMlm#X^f%-w7^$4)G~QuAyV9=^fKK@;aOn$9n(!-FNpuBxXYkJ8otU&3tSRsZ DyY-&z literal 0 HcmV?d00001 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 GIT binary patch literal 17424 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 Y1%v`Z0il3UKqw#-5DEwd-dP3y0YjJH=>Px# literal 0 HcmV?d00001 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 00000000000..a1a976ed494 --- /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 GIT binary patch literal 325 ycmZQz7zMx(u-yj)K-v<BS&A$3l7TcM5Q_jY7f1}smju$RPC5C>*-!zHA{GEdQ3qoH literal 0 HcmV?d00001 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 GIT binary patch literal 997 zcmc&z!AiqG5M3JsS`opUg1CN!H!o>pCD1k@2?V@^Z4x!O?v`{V;MwoWNB9T+h;b%e zCE7?K7Y82Mee-shx0|V|sv^XACxn<GTks4P1Hg{P(Bmu@Ix~0!cE8!!*(%Xe_<YAD ztPUE1pCz(LVT+Yc0mC)(>mX)(9Wr$3lOSQ76=TVtp9B`a8cE41gcj^grq)gDyAkXU zC!F0=^^mV<Axk6z#YD=AG*B}px(MKYM6YJiWpfdm-8w>Hvs4R?Ja=mO&aD+X?mTvE zpwB#+3zZdANxGEaHGg3$mx3z&UfWHWP^KOIO+Xbiv(?9O;9ZYH-<rmeGq>GnZRNaW zWwp!_RWM%WB9-O8n5pafLmNU<h>g1ht{I5-Z-hb&9F&d+Yp1*K;Y)D`(+A^6762m% fX1eA^6Bm#$M_eY4u)xFk_hak=UIBj!P((ffg$}-V literal 0 HcmV?d00001 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 GIT binary patch literal 1032 zcmdnDZ{I#Q1_lORAPz1`%u7iuN(nCUP0V8k3NWxRGB7X!X%Gg1|3Cl~0s{^xzYa*l z#2_-G<j@HLP>g~AFd6Kt0y-NMtHHs(o_Q&$6`)uJVFe%tB?k>4769Vl%-n*URR5xs p)S}cB-^2ps*ayi=0x>HPJLTjjXTuBu^07;R6b!8GAg7IV002LhJ5m4u literal 0 HcmV?d00001 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 00000000000..a1a976ed494 --- /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 GIT binary patch literal 325 wcmZQz7zMx(u-yj)K-v<BS&A$3l7TcM6f*;9Rw!E%#CFQbPtJx4fE2L;06?$@O8@`> literal 0 HcmV?d00001 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 GIT binary patch literal 4311 zcmd5=-HzKt6i&Hh!CtCk9BkPs?Rs%2?~D%wik#6d<IC&XSVxQQnAWMkI$SQDq| zRzl(-5D&oxkFYPn`vBi@?BFC$6Rpa&BOQC@%$f1|KQq3(yu2)x-utUmDphUX_XRrd z06qbf0lOWadto|rLjkjs3>sqOU_hk#_7H=;grF-Nsay<cmk$8DQ_R9bn!VDZ{dDVF zno|6tF8y&3(%z3<IffXFrSr8;+cIpUZDRbOBS&8B$&kqb8wlYtC43%x&jpKRh8PGX zLPxMDrNz1fE9Rlgm21T<9u^^LKvn@H&WC^<Bl}c}q=^aut^5Fw0ww(d^s<sj!~5Tl zc^Hp_)6k1Y7~cXEf8`%EOj4`0bj>o%W*hz6fYml2YT}RhI!M&{*ou`Go;?MEn9i6_ z`T-$kk0C|{vOEI1SeC8$A|fZ-15m4U#Q?8tNS~_ZRKI1|-6n8^Paf;+y?3l@tzNs? zv~(LwzBv{=nAs9`AB;rI?!pB|=NGI$j+qxSC|86bS0c0hK=D9)ishSGo_HfWk(!q* zPm^fn6<+?vt^O65TWNKYilCx6#d7-k8Qs@y^c~kz!lB^A=GE_x4i68}S#9>IhccED z-R!haw65`!Mxoxbf9&X(_}YfAQLIPO=gNDDP>10W>XV`83>jx<2>p<C^}61%+g)UX zQ6xi`LTtU^^B(&u!(T1Etrom4gUA;zGQsyDc(1ElowjL_%%3;D2sonm1@rEU`^*>o zoSzBS7x6?u)_5WzrX!+Qs?Frw!KT#KIr!_Ab0Lr4$gI@U%x_Of(vR(-fGk<e^6(64 ze<0PIwL!Pul$5=xk<wpj%HKgy;P{kcm$M!1cBRZs3-19|MkNstq~S>2DesoU;pn+y zXoNANmJ;l0rmdT%)-xKw+%Xyy9BKBWL>Ldf5#v-)F}+@IRZ!*IiWIlH@??V0Ce**Z zPJrzsyC>Y=G~C46gkgbng^vy1N}ij#-k559yGe%*k5cJd!RbPb)eN{t_Xq6+{5^#0 z2=$!%iIS%YGjR{9pOjKL)ku(x82OCjj1`Z?MuKJG2I8dshUfJ})o1w9+Ndg1UQA*t z;ci3EJTOmN$ivf0VqxWxIS%?lk*;P5mr?nl7)uVHcOxbHICEdHFB#I-(PjsZ>kqp3 zt&U!|yV^<bnPE2C&!)co5wZtm+o@ATsND+AGVwxJP8ghI9Feluo}8~ioCPo6vW=Dw z-k!8PH<BJQBuo^0!L)e%pPpW(TUCLlzwtBR=5ax(t3ZNBKrdH7Ix=i<-mi*CI&_h! zoZ5A~X0eCM%-<MicTvJGITUco(IkJ*OE#cK@g)fpbdw?O7H$i(FVkG%k#de=@$5A> zxqM)mo1QPl*%W;@SL5c;|CNzEI-j%i_@#W5pDd2(JVh2H8SYQ~C%}(_(jS0|Wwr1k zUYE>4fmJ>BrE`uBEv9=q8Yz4WQ2n)3dKa(_pgK^)n@*U!1n4KH>Q$S7X2bR`T~<e- literal 0 HcmV?d00001 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 GIT binary patch literal 12800 zcmeI0J!n%=6vuBr)c760<5zutR5OTVaHt@X7Lh6xgIVGrB$z<NHil3nlN2eEp+g6U z;8dJ~I0RfWg-nu3I!OkH4h|U{LZ{-Q|C9HBa+DS+)IOWu1E;_LyYKbgd(J(1@4j2F z*XwtTG5g{1=~OPADx~An6RF$)8jRUz%plO;Juv16tdDwdr!jKTE4bIjQx;n$12Usx zD1&+dqYl91@rk>+bf)Ods)IHZf(gTe@c5Id{AA{SA)P5?(i5q?cO6c3&mRMKz{e(^ zJ$~w)T(wdD!<x;2&4A5-&46DSkZ1o(bpGeFU*7r-d_B+pv$+1ZXTRL!9DEqQ>PdM) zc<T#DYc>Nm12zLT12zLT12zNyI0N!MD^_yxYb(S1`0eofHOnQ+5}9!KN6aEQF54uq z200>&AXt?QW4A<Xfeh_(kLSsNEWThf<O-SJ?Y6hc<R169MaK5J$4xRSi#1q{jL60g ztU@{m+~YFI;hW=V7A^GjRi($2PAHvIdRFN-N>`QssPq@5mz546=iV5Gl)kR?J*AUM zKUaEA>9<PPl>VgjSEauz9mJe_<GHN#O{E_wEw$oZ|3c~4O24CyB5tvo3ll-VVinTC zbz)^Q$O$WX6UTZhk{vS3x?3U>th+^WoORbAM_6xFGR%5gAVaLTc{0Fyn;}=oJnODa zCRukaGRC@Vl6f~g(<Wsu#ad*HaW}~*<F1hr##<pB##<(XjJHG@##<yiWR`I+kqO4V zNRBh^206lbt7Mq*E|4L{J5L4}?+m#@N~{v9geM_M7&3Gjt_)3vBSVmGr7O9;+*1Di zAY7hbhv0|dN8m@{vehQ_cLE-QpM;-+pN5}-%V*dSTt3U(-9P{SVhFz4+=r>r+2u#k z?MkUTKi$8|b9*kuZe1zeSRUQ3^!3MQJGU9|IRim?iNZ=`_l1h%B0mf|WL93XJ!rD# zXy66ltye!Q{Y~k9T<BdBQu-Qo52j{QLW;8PYGj1<Rv{hMUAaf=FVDE!WRh{W$Qa{p z_GtVLs_Z=c0{kNU5_}lGS+>^tYqR?IyPx&0`~4p8M)!|L{Hp8!jqYcy`}tje>s|M+ z701?YAGnR3hW9cTh{M~C5cbJlU*AL9YV**o3tJEF@8fTIzz@cn&4A5-&4A5-&4A5- J&A|VefuFZx>qr0q literal 0 HcmV?d00001 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 GIT binary patch literal 4576 zcmeH|u?@md3`7kZ(9tpgGte*s1F;#a&@uxJ`3fih7oDgnE>dFoboz;&&Q4X;^RPU= z*M6zGrTwb%j#_oUcyH^w#W1|b^Y!*Vy&kGO$DcB*^!NF*Xa1hNcjZlPq~$vL!+B-Y z9FnW#%FWGI%N%Wqj6QRu|DP&z;+RpRzj@NtdiGWJ6|>>H<jQf$9BEIfGAE81HTs(; jU9D$dWnVEHzDuqgm&}p&lqz%Lm{Fs@dD7MTzwN6J^bt?H literal 0 HcmV?d00001 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 00000000000..a1a976ed494 --- /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 GIT binary patch literal 325 zcmZQz7zMx(u-yj)K-v<BS&A$3l7TcM5UT+(4-hj!`I11I)hQ=GIU6bfQp5xRM*|0Q literal 0 HcmV?d00001 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 GIT binary patch literal 4006 zcmeHKOK;Oa5OzocNlSzTmlg?W#R0Xbswxl%P^si34h_l+I~C#-ZJkYGaJ-T2sLBC} z1HXa;2mS?@{50Sj$1YBsC{QFQQb!u^?#w>sVb6Y^PN!2vx%EX+lx&-y+5(+x;8e&P zEt^|Ts40AfG#?Dg!ZX9EXwK;gdYi!nU6|7CVn{pP1#iqG3kykmp+y_v)?2FU@<~bB z<Do-)A9m#!ZZH<=^JT53zUh>!Is`{$%N3??Nr%ZE>k6Asm?<1zxNJx4_xG8u?y<5~ z+T|V_NqgeRp~Y=xIn1|)Vjnvc44*q)?sg5o&Yfj~9E3y^#cgoD+LE3{XJSC^hLl?2 z_Iv1)v7+5>z8Z7K9}iC)%l9ze0^b8CPDp(RK|z$OYpPLgRGR3MIjd7av&p{4;6f19 zL&JA1r+)-NI#iM!#A%mPa>M2=x=Ygj6yoKsWc<m9UbRgyQCb&0G|`aE$akY$VUT_K z=-}W0ogjepSey6Ov1({x-goEnJ{oXOEYJH@if8HlFH&64wTh-|jZ&26(p;L)wJTD6 z^^H^Iyv*PDwqbIcyRqUvD@crml^|1UmNoRB86#m*r8WoL<tFO6<@uKBQI+pm!tU;| zpu87RyGPvf81}VJUG7U4tJl}`s4ii0Kr2f3>e@;bD=1xIoAj(GrOiO$gj}VjzUb)9 z*BIw?dB!R>@1wuoR!o|si`z;DCk<UpK|`%m3T`*}P*A-6f#uDD4(WPx|3YZ1j!%(g zu!mgAHBRcCR<rg7eb)MU%LA?e2fzcmG?*D63c6<3`;f(~I9)vC*kyUt7ktPY%@ZAU zuUf{6ydlS~DVRk!@};nL0fS}Q)!-eu-v)|e(zW_PR6Xgk^DCa=!({j#2@|aMAc9z! zOq6<n#}fGQ&ymza*tHT#QJw>)cZ@Q(#DhNqP+2jO<^UU~PuivMFf;+t!Rrvzom#V^ z>YaAArge&%S`V?*-f>X{90tTb6?_;=;Z6cEpY7t9Urc8RSqY*h@GN9RSBM!wi0b8U zAZCR28-*{%iKIXm;QG1WC0Y>>!NGhSP|s-+IBZTE-O-Vd_KM$bad{@ZOyaV5xsJ={ zN?=?ORhzhM#k(9$Q9k2082`b_Cve&@>S)N$M3|mKm@V4Ue1{ILrr%Hybpy<dHuC}e VQBkgEz-Vuv&4HCwi=kRWKL9?8@)7_5 literal 0 HcmV?d00001 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 GIT binary patch literal 5128 zcmdnDZ{I#Q1_lORAPz1`%u7iuN(nCUP0V8k3NWy6GB7X!X%Gg1|3JV9VKTI_K=^e) zK1>WMHA)Sc5CFv}2mq78z9OKjIe<7g*w-^JCA9(;s|r9dE+EzbVgVoy&de>yN%b#E zNi9lE@l7m%sRK%Z<wb!sD-b*7<R@ptB(VyDbc}-05Eu=CksSh{(x(a-1n8v?sPIq# zVp2*UNuYX^(g#)oVU>VM0yT|-(GVC7fe{!2pf=4&1_mS9^FY4`jBx*tdVk<Uz{%Co qHy)S~*#_Pzqb?YL5CHYT2f$h+D;tzMEI&CS35+Oa!HDuLre^^oQbiL0 literal 0 HcmV?d00001 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 GIT binary patch literal 136 zcmZQzU|{F~Vi;foGC-ISh$sJCrRv+ma^QX`+tUl`Z|y;9kmW&q5RlMy3Hhz~6GI-G Levr8!GtlJ#*~A>$ literal 0 HcmV?d00001 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 00000000000..a1a976ed494 --- /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 GIT binary patch literal 325 wcmZQz7zMx(u-yj)K-v<BS&A$3l7TcM6mtP-CMcE!(yUH7`N`Q(NsuBY06|*^O#lD@ literal 0 HcmV?d00001 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 GIT binary patch literal 2423 zcmcguPixyS6i=3<O_~*U8LVRkokrVHXb(FK${<aXVTjW#b_+eKw$g|>R<M<f9Cq7j zhn@C$@@e)tw*8)Cw{Dcd7HA(lOZq+id4DC(=ks}?P>TzNLdE6Rfk5X8n5YQe>IGbe zsbupfkXkZ06S0RuHWl53-r`$NCiI1;A|FHA<t2D)fh;W~=~|1b>DD`r#{(WURWKQa zwE4-YQUL>{6E+*YUdL*kVfe<4CW6TU8wC7@4SCG2g$NlRj{_-urWG5(Sf*1yY&)8V zK977y+dRa20|0i9*0O|Lka)>7#nF)rZ|+QKp4)b#OQt+)!-N|WdTKO+n!pV844_U& z<!F6<u?gvIs_d%?vglUjs?~K{{mVuNlMh@ug8M@)qA3s9Kt=5HP<lhAhcaHo1T94J z->jh&$kPuDRwUm!U^Dqh`==i#Jk*oXMJP2@zzgs$xMuZKEVWb=bedTF4MIyq;{)(^ zBZ~O!WD%nG07|JaSwMZ-k_H)3-*lK2YRXmyAx)@sT9+;I)+>rI3&d0e?2tubDq|>= z0+1WQHGm3c<>>*Uy_BFE{Z_*{@8ZyUqJ1QIoH)_Y%>W)DHJ3bHb>Qi~<z7ME=MMZF zNj*b3`GV~o9UdR;GYAYl4d4;Hyi`+V*RosC?bw$7SfQSn8a!NC6<_116=n4Wuvu30 zAgonZRU4^eb=y>}JK(=R84&(w5z=+C!h&~zS6filvoXDdSyGnzVvrjqbt=Zn8v-m{ zxJ@*KWkh|m(3fhZ>|XUKin}h(q6j9B(q6mM_`q;8UUaFqj|oP`89=6HcK-T>j%)5$ zx(gXWY@9}N3UlsZ&5TdryGJSy=O2PRetX~L!Ml4TkLC+Dk7xC8A6s}g!@lD`2R}N6 wZ{U*S*r^SMapbznX`sAM=+NTqktt*N6^4UL=$Fw}&~AW}ADESkAX`m;04h|(+5i9m literal 0 HcmV?d00001 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 GIT binary patch literal 2436 zcmdnDZ{I#Q1_lORAPz1`%u7iuN(nCUP0V8k3NW;=GcYg$X(kW{2>t^Bhz|lBKn&uS z0cn^RNPH9wr4RtcAP4}H!M-A(!$Gkc9PI0vmy%imi&X`vxCW3G0Lf?O7UZP*7p0^Y xrKb2M7Qoa2rNHu{K$;bZopSP%vtg201wlGS!DtAKhQMeDjE2By2#m}S005EsDv1C9 literal 0 HcmV?d00001 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 00000000000..a1a976ed494 --- /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 GIT binary patch literal 325 wcmZQz7zMx(u-yj)K-v<BS&A$3l7TcM5Q6{*5QEqtED6M{PC5C>*+_zn06@40M*si- literal 0 HcmV?d00001 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 GIT binary patch literal 2592 zcmd5;OK;Oa5Ozq@=0Q;pEou=+dqOSTka|EuNNyS`qq+{Ui^QqgI@@$1UK@J@^nk=~ z;K0wxPXoSj?ACcHP^nTz+RX0E>||!=<>7ERG>rOV!!RlyKko}{?iogD3%G<;Z}+)W z8K>%jQcDkqBJoF}_`QLBY#XUSNBCjf#gVi;!m5ub6Bmhlt4p;^^|l$u{HhuD2Z18p zuUs8V)nhE<*UfhOtZjBo+XdyJ8AW~Rb1g%~A|CTVXb~sith<`4E|0sewz--`Xn;sS zChuU?tag}4(v%T_Qn-~rq%~E&$e~8_*g9)Ewr!pq;>sqD%nySA*0V%>9th=A5SPZE z9oOf59?y?Z0}(p5nx<ztCop`Y7$SuL{g*QkPT}@3!1Xl3i_BiLTdr#!wIJU(4tp#R zNy2+eW38n$BgovTIR|fBP4CP~bH<5fPbyRa3dK49D14pL7w1d`>WXj&&pcuH1LweP zb>64hF|z)-bDoI!%czEBm+eZ`72lXttRTuXHM2d~x)e@LG^JIiV;xzP?JsuSa1i?f zCEOKzEKCE#F5)m?UoWM9nWt68;aX5nUF=idtny&pL7*mls<t~8-10u)FE{K_F|a6> za7kY+i_P6|fV8--iWSeYsem^<iSpGvh*)@$Klwk|T!mBtN>6c^F#x}VJUsw0XpQ^t z23+YuaH^!HIkb)S1Z!%#l>Pv~AgJ0c)3r|CIM~xDnm=Djn%dbjto3wMW&`py2<41P z0_h?HD8FWS@Dk+N9<1oAh}vGBa^;S=C>%*~MMR;0iB0*`YXT811-SGZ$1NOkBc^hj z1nH&5C;7DsVol7%ao+fN$$50G(87!4v0hnZO!uN9_ok?e)M8l_>3Qnqv|soT!<U%x y6RYgHHV~OO^%@k1%0WN$KVd_c=|c!m`Z0><3h*k<tH5i(Rp2_XG4CSr=EGmX5#;6o literal 0 HcmV?d00001 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 GIT binary patch literal 614400 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 F{|_8V<qrS= literal 0 HcmV?d00001 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 00000000000..a1a976ed494 --- /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 GIT binary patch literal 325 zcmZQz7zMx(u-yj)K-v<BS&A$3l7TcM5Qze5P9QA?;WJ1AX;!D4{N!w?07#J-07lCP AiU0rr literal 0 HcmV?d00001 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 GIT binary patch literal 3864 zcmeHK%Wl&^6b-bM`lP%TJcKlCfJ#-%0u~5V$!#4l%8S@YWmOHa(^@!oWjjSIkoX1` zd``cKF96P&*shy#Kn)-^T<O{~_ud)b_l!?YPEKmI)$6rdZO)Nz0}q3Xz<a<8z{O4= z{V?M!e+6YhO?JJchY3Z?@EDUNCD8SHQS4&MU2XvvN0hOJ<W99{A=~<<-IIZgccNf8 z2)X$)D@+9;6Ie7_j@fD%`_?Wfw~a9M!cYopSNNgGcp}YAU$Ijex-xd{v?)W#YlJxv z%P^Jlw3@)TZh)P}et1wAFxxVkCIqkMs`q^_a78Ew43?&#Mp%MW8Gc*i8%7+<4<(Pg z(GZzaC_LY3+m>UsTllKj@!;@SM12wX2Zt#KG7EvS12US%8etw%bUgia2e@E$qQp=A zh#?}8I0FP+c!Wnq4t@k#&p{^e@~a_K8V+8Eewtu@16ci)?*K3f^G(yRtyaB_@lD|5 z6sI)xqh2e@b<ai--U5M2=gO<`fP~_4h*^a#Pe87=Wj6hA#I(qRKGXI3r71r(e{|}* zS<C6xK_gu~eihr*op!ssYqbo=+&A`28_TvD5lT>uva%PU>itlrULs^7I_t02yW&Al z#vu4ITobMzdp$-@9OE6ubhs98jgTh*!*v&^B>)-pU!DXHF|KgjKTm>9=ru73Xaw;z z{(iL?zZL(5A_Um{+zD{WY?w{6<?OfXb=!2X#nZeNQQQ5*OB2?(7kC3yej?s)#2e{` z=oM`sNu>(^`Klk^QLbUBX~8EG@W1l^XyuQcqf2?<yF0Jp?E83$VdA->AH^a)^iD(m zfRsJ7c~`tygQ!-`8iBo?z~ui~gr~bIOm1%5Q52M}3d^~P^@i~rs%$vQIJ7f#tmDE$ zfez*!v3H5{n@TeYf7w+1Rxx%+uQ*o2-0!v>BRi=#yHQq|DBl;`hDd#!=~?;aMO$j` zutAHXMquAA!xu~{+A<GO{XApbraGIsa2+_Y(Ncgo6U+RHikSkEe{~36Q@+cSrD%jN zs~Vv@#M$WjM-iM^G(|s5eAg?yoH<!JeAFnRgyu_)pq!gsNq$x}|NG)peG#7hhB)MZ zLQD)T1fZ!(KKqkFQ37i`zrB}f<qNW+^HF`{1cO%^?o@I0Di+@bUpp4LD8CbNtRLNK zJveIru?ByJH{Kb~Ftt{abNSH;wUUd}zTgKGf1tI`z!}?aX3A)u=9On|JBWJkFyLzb c01`fpwC53W3AhTp47>uo3am|BNajTN0h6WU?f?J) literal 0 HcmV?d00001 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 GIT binary patch literal 15104 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 literal 0 HcmV?d00001 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 GIT binary patch literal 75712 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 G@_zuRqT?$7 literal 0 HcmV?d00001 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 00000000000..a1a976ed494 --- /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 GIT binary patch literal 325 zcmZQz7zMx(u-yj)K-v<BS&A$3l7TcM5UT+(ClE71`I11I)hQ=GIU6bfQp5xRMx6(6 literal 0 HcmV?d00001 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 GIT binary patch literal 1242 zcmd5+O-sW-5KV0hwOa6|7KC`vgLe;t2({fpph;^J!CP6Atp?NGl5W5r1b<oof;WGT z`X(k)ZKUAI2a}zTx5LiPY!-{f!Z0eYhGA4aej5oW8xTSXQXfP-lv$+w0?%e@bi~Yu zkyD|jIK_7>B@BhH;s7RPX+r9so*x@2`ln6hEcL$Y^N7c76(ti%$<HmRO+rT4X*nKq zoYtsov0i6nb>MFf#aP5b`oh%8{6;{u;c6}e9tW<rxh!&2;atX_DxlH{w63k9!FtSQ zjyJNL4!gmQBNmB?K&hbR0D&ggf$F7r$t$9xXrzo;fq31;i7Cl$}K8P2xrg%e?_ zu_>}*#ypIYn1*ouv1?#0p>zzRVD_HiUY$J}-0iD`OPx%vWvFMc_aFz5O>3ZLp$--G zp1M<d2e1-qHfy<-)9pjwhx|U;#HQURkVcETaqj9kly?_!<l~#BCn6nyo=ZUmXK)u+ zP|@=#UDg(aP{v$i$xTXL6;tKkQ|#JnV<BG(l=POV`5{*BoZ@s-MQ<oiMDdmBTn^^V r&V6J2a`4YFZXspYwXvi*e!jXx1ge*j@*kkkX1X)Nb5_N#gp~CGunprq literal 0 HcmV?d00001 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 GIT binary patch literal 1024 zcmdnDZ{I#Q1_lORAPz1`%u7iuN(nCUP0V8k3NSD*F)%PfC@}Xw5P<j~zyZV{ei@L4 ziGjpN!B7eTPz-_qFd6JC0y-QNtHHs(o_Q&$6|h)U0E&T<g9Z=_0C8|;Zb43}e^E+m lQEG~BVgXDYPzo$B3Zz+q*eNGJIU6R4RS=|O6bxht007HPDf<8b literal 0 HcmV?d00001 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 00000000000..a1a976ed494 --- /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 GIT binary patch literal 325 wcmZQz7zMx(u-yj)K-v<BS&A$3l7TcM5Q6|S5QEqtED6M{PC5C>*+_zn06-20LI3~& literal 0 HcmV?d00001 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 GIT binary patch literal 923 zcmcIj%}&EG49-?6jSY?*;1n*r0XHsN2dH9Ys72y9W&IfySt4~saO5pG@hW|^mCvP> z0tHQ~ge5EX7u)%g*hNtkj9KrDG5cODWelSaJ%je3!#gQbRqymafb3V!G~R@8DAbg5 zIHM}Sk3*eBaH+}(bohn*NTl6PNmgC@I0%IlnWN?NT2b+fAkR{@;=B&nyB&L%un&S< z<f&F%i8bBw5Wb2;76rK{)NjBDpedbr(gjIqZF6;1&PnQkfis&sHw$+P|HxHQ{K|F0 zH;*EVBOXc}K4D`waT`&l`~&fc*3u+?H5*?q{P_cp9r*f%CsM3n@8)XqB)cQmvMjgA zZrYY!yLC2|r-p4hqWK!a%$NU$8y9Qv1T%x7VN0kL1RfSD(K_!GpgpO7jgqNnA-%VH KaQ+IYqSz<rO{CTU literal 0 HcmV?d00001 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 GIT binary patch literal 2060 zcmeHIOH0E*5MH&`w|Ma=_9A%PN22zsAb1Eu<`)E$gIHq;J$Uxu&&prZZ%QXSm0o(V zD0JXszn$04>`qAAw(Y$!<_MrkQI|zi(j+VDEhtQH(-<aLrt$^ZnDIUc??15@-ORLJ z{>=^W3$kDg+6Gnc0z}zET~_b%ssreF3NC>YfSzB+qw29KtEMWmVk~|zP3@;xw}IQy z+tZ5}7b-d8F9>)4fkJ+E&vQQjmg)Ws{&U@Pl5^k!SZL%ck}nK&<#I>XI-JT?Gxv%I z#S_I-#jg~<j@+r7PUUgR$15MN@_Uuj-^853Dz0Kftl6nvoQ>ytr4MsF#wJRic04C% zrJkI;mEM_4ZQxF7%PA<IpkjlS-r>{vqorRJnJ=E=9>DM12X+9Sc$S<JRcE5=OzaSL jj>#$Q?vr`!J?(rr*IJgpfjxxm0|&q%!2jc8V3qO>+X!kZ literal 0 HcmV?d00001 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 00000000000..a1a976ed494 --- /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 GIT binary patch literal 325 zcmZQz7zMx(u-yj)K-v<BS&A$3l7TcM5UT+(GY~UD`I11I)hQ=GIU6bfQp5xRMbQUp literal 0 HcmV?d00001 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 GIT binary patch literal 9335 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 Ok1$q-Z3v)u(EbJh7Y|<m literal 0 HcmV?d00001 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 GIT binary patch literal 30860 zcmeI)U2D@&7zgld;?y}|AP9=!fgx_9B}w;U3&PszaM04ZTnHkR)8x#>rb$WDI(H$0 zUicLRFXo*eLVD?i-$C#*crEDj^kt+_z06|!KkzIkIZ4hrzjK<k*_v**+uhbQ?Gl9* zN#Bv7V?|}@pP>!fyIY#ZnOR<;^qbO5KdXJc+~5A1)`!~$MOged0z9IeXc}~nsMY5w zEUUcZJL-@}l|xY6pjf6@q_EsZ(^HkuQK53mvKb$JF271~mZIndc71pfS|48!Kbm(! zf&c^{009U<00Izz00bZa0SG_<0uX=z1V#mL_ioe!YY>0{1Rwwb2tWV=5P$##AOHaf zKmY;|NRz;urT4j?h3_A~t?)8UEkj!%FzEur|8BlM=^agR|Jr`t4_^A(xfHi?lAFe_ z0iOR0libM^_igdhug}L%ruTP$sj^+zqw(SI4Im{i0RQC8<2Ca2m%m?w>u+2oxjg%? zQs6yca^~@x*ngJ-^FOYVT%P@(QDFWjXCAM~$Qim}YT8_UBk~Cf^zXqXSYfKFqR$_< zr~TgJ@p`bCszFJ(F+U0~rn>)|U%aXAf5Ovkq&j|Xz-gvO#X|Mz!@UYW{{Sg@0e=5^ zNO3Xws!`*KfV+8;ow5wizqb@urmWv-=`Q<!pcr|WOj;U;c0d3E6D`2kp9}Qk+C=v< zy`5`!`fcNJKspV9>AwHf7oT0yw40Cld3dxwd~zKx7wjMmE&D(@ZBLoHk?sAxv{JO< z`Ca$uD7YgYgc-3H)KnM+Vmp++ty*?4I3HIt^RNB*bEs+PRdY=r95QUiTy{&Cxiu>a zLit?fMMs4#H}J)MVPuRAvF!R{t1RoP<hG(#COVArRG_cSi-7i&o+m=paeGJ5A?Ajj zy*DqK(yr6VMvK-rvN^Hr$yOxn1L;#SoqDh*ovzk)y+}~S-hr>{hN!&~Ua&7i-tGt) ziHaQw(-bTt^jzM^=1udYs+MTCl#>_pLhSgF3iqY0?)E!C?aTDqMU{xMRpOJ5E9w>W zo>4ijw9ssNTS21{_~S~gC{(gBuH<jWl=^q(t5#uG*Y&DZ)Q#0@yOSwfJeEihfB*y_ z009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_KmvaNI12gh literal 0 HcmV?d00001 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 00000000000..a1a976ed494 --- /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 GIT binary patch literal 325 wcmZQz7zMx(u-yj)K-v<BS&A$3l7TcM6w3o?HYi&XNV7WS<R@oC1we|}07QoeX#fBK literal 0 HcmV?d00001 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 GIT binary patch literal 3428 zcmeHKUvJYe5Kn14`e%Jb8xoQwq&*-ZRUjUaCZwz@Q(#R?k|FUlOq!N#sZ+Tr(jJib z3<&WF_#FLc!0)nnOPiFIv5A+RboTkqcFuR_J7+eV%`~m?UemO)!#@Nbh&w=n0<hK# zxGxe({RcSJc(CtHTnyAykTE<%TaPDLo*T+OhP2ClVC|f<u#luzT2xK8-qNMyXRR=p zjs)$!+YhHW24ji5x^GzRmSc2HFq*oEJRvw6hrWn>F=TyTdT!*0g7FEs!I=n0J`b2L zSmck$8Vx(*qR(aDjyhcAF)CmZkTJG_RihV9eA*NPI$#2YB?tWsdDc3#+vb}o7twTd zB>ZTC@fPp_F!P1NF8~Atl}=l?4fACeG^w-P_%xgJdkSpCcHFfi3A4NaqnHRu55l74 z<j$l!0h#0I=V0d_r4*fw$((f{LDBLC=+uzZs9rb&gYv5<&1MrsY(Q$ZZI=gJrd(}^ zh!G^SYP#vPO;bNM_Q9zsUmK+MYA=>CD-BDWwcMJdW2|h_XZ2kO&58kit+)Ci7Bn10 z8=@}q<R=~wW9_iR<QTr8&2b5ZLDnUt=#`?27Cr(ljTdEx0tKwH2gsPt+T=Xo7)vIU zOuf?veXpl(=s3AcOz<`DqlzhP6T=O7av}06=27Rk*FAiP?`_(i^t}l~_VXmhs~vgm zk+&)*ZaBJi&~~8QPQ>NVi>@yXCVA*K4d&cA%Az-s1{cyw$uZ8a&|FGkBflS4y|qG8 zl{8EyDD0rg$u0lAa;=|<s0ieo;GYUs*&B!MM{Ha?a$002paP<^*>1sAb#m2l*D?+a zdY){@9t)*E#Peei%FAmB;p#kc>$G*Q*WWvBFARg!Go#{Idh4}e9>6IN9m(BOdPoie zK4g!XBc~|i1BM5sDqL&%4wL5KiZu)A_h+&X1t4K2Zb(k4Xzpcv5xOLqe@H12NzzZ2 zG+&kNG%sBg)BMZoqqwm;Ds3qyeQx1?q(x~jDE@c+%;0ZE`wA@Db_X5plCc0?`tn{7 ix~Cv$G5t&ch%9`;B47!034Iy30lW*;mMw(n5AYjPzj|!| literal 0 HcmV?d00001 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 GIT binary patch literal 2436 zcmdnDZ{I#Q1_lORAPz1`%u7iuN(nCUP0V8k3NW;=GcYg$X(kW{2>t^Bhz|lBKn&uS z0cn^RNPH9wr4RtcAP4}H!M-A(!$Gkc9PI0vmy%imi&X`n7$*>G0I>iN2WRFM<fQr+ zrKA?6ruZfnz|;Yy!1AI%niYtha`Kb2VUkz{K{`gkXb6mkz-S1JhQMeDjLZ-K0GU-P AiU0rr literal 0 HcmV?d00001 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 GIT binary patch literal 16 PcmZQzU|<jcVi*7b0MGyt literal 0 HcmV?d00001 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 00000000000..a1a976ed494 --- /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 GIT binary patch literal 325 wcmZQz7zMx(u-yj)K-v<BS&A$3l7TcM5Q6|G5QEqtED6M{PC5C>*+_zn06^LYNB{r; literal 0 HcmV?d00001 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 GIT binary patch literal 1306 zcmcIkOHaZ;5UwH#pk9r5FnjT4ym`_KFAa%6)0P-dWNEjsQg+F98-qWt|H(ff&Xm@a zhml~MWV*B8Ja%Shx^W!GhEY5-;N#F?z(6<zK1jJ@29%3bQ}_V1oNSsbbW>AUzUdaE z<HW#Xu2dduG13KaWr?ggqINH*qcrq+O({BW$RL^ujD6o0njoe6a-(Ni1LwtT0nl>B zjLc*}6@RCiOF{H}#)vN!VbmQHNLjI9shKJ1MXtUkG$fSRT25F<n24q~5a`s_RCrW* zw(d}|4JZPLgauZC%WYGJ9M4QaSQ6T+dW@Qc{;m9zq<qurHTw+*eD2!9<1SR<!Cfgn z<YGh=bJ<Kw6%y$apD~Y&B;s6XnZFWn-@R@eqJONrYP%HLnCp98${^SG(!*2jabWeH zA&-(J--sX3(M}bB(F*;oL7R!L4QQ#`?+ma@aOh<>nhu%5K5QAq5G;0YKvu*4=`R+G zPl$rEmhr~%cnY?9<u^;FGt;b3;EFQerA&e0@|Tj~0-h>(tBhCRf^BzTLDP+tt^h37 Y1L;mcKxcd^fKWt-L)`yl1gez200`CqL;wH) literal 0 HcmV?d00001 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 GIT binary patch literal 1800 zcmdnDZ{I#Q1_lORAPz1`%u7iuN(nCUP0V8k3NW-VF)#oT7=!r#fq)Uj00K841H`Wb zvSDIiiBVz*g#ai9K>(Nx_7wr$%>l&0!M>h(DXA5(SXBUuu>i3K5DNftaAs~nPO5)V zN@`JRif>{8OdU`PEH4VAS%KIoCqFqGCW%!Lq+>V$Q0P?wJ%S#3AkQlRF)5)Z2~>{~ edaw}0Dgl!OY8tWtl%_^90OhNZ4nR3%AOQfDsXROY literal 0 HcmV?d00001 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 GIT binary patch literal 84 rcmZQzU|<LVVi;fqGFX6^6NtHhm>Y<BfS3u2L2}3dq#lGp0LBIYCI|qK literal 0 HcmV?d00001 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 00000000000..a1a976ed494 --- /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 GIT binary patch literal 325 wcmZQz7zMx(u-yj)K-v<BS&A$3l7TcM6te(nCMa7HNV7WS<R@oC1we|J06=vIMgRZ+ literal 0 HcmV?d00001 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 GIT binary patch literal 1344 zcmd5+O-sW-5RJ8=u|+{|ddk%w;H?*Hs}w8^*@}27+a#9aHY=M{@$T=+PqV&>8?!b_ zAs{+1yE|`YH!p8LvMkFi%kEleCVVC$3>t8<9eCRld=cwRvk7D`5BgCOASg;P%<T+q zCok}$K&lWSt#Sfye;^wZDO+ojt(QJ#0T*0dNU>VRw0O5CrEp=oz`49Wznc2vTf~kp zpWS=G=NRv^l*b`gAxlR*{tf{4#cJ`SAIvqV5*@}V@pN@TYc%c;ro%qMLr=*dN)oBu zr3@o?K|*dPOq!ThpD<P-;#JNpRf~B3$+<bX^9Cq9C<&)8k93H(v+bbDv|nJmsW7pv zC`^R^ufh(y=}ooDN2SWiYUcid((Cp5P45|<5Aw=JQ=Z-h^Wsq<8R5n`H{s$anOLjb yFcUU4DexyeWO&Ie1>a?Cq(4?+cZ!7F6Eb+ffF>80hTgVUFht)#Z~a(^Txsuy&hm@^ literal 0 HcmV?d00001 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 GIT binary patch literal 1540 zcmdnDZ{I#Q1_lORAPz1`%u7iuN(nCUP0V8k3NWy+FfcFzX(kW{2>t^Bhz|lBKn&uS z0cn^RNPH9wr4RtcAP4}H!M-A(!$Gkc9PI0vmy%imi&X`n7%LEK0I>iN2WRFM<fQr+ srKA?6ruZfnz|;Yy!1AI%8l>JSCqFqGCW%!Lq+=9}hQMeD3}grZ0GUoI0{{R3 literal 0 HcmV?d00001 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 00000000000..a1a976ed494 --- /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 GIT binary patch literal 325 wcmZQz7zMx(u-yj)K-v<BS&A$3l7TcM5Q6|K5QEqtED7Q}<>V)4BMCAB06<a)L;wH) literal 0 HcmV?d00001 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 GIT binary patch literal 5224 zcmeHLUvJ|?5Kr6mnxxHDh?BVE#Azj-&`G?((+SCO?bIT1dbZOOyjX6$ZY&(T_|KIh zA@RUhKzszg0}uUZz;9gpy0H@yB2J1#SDMUvW@ocAJOAw4+uPel<B8j7H1;j$BH)<p z0(J`MoCS^_7Mu?}P}}*#2~Q@)QlNL^3(OAl1qPo)F>OmbeGKT_Q7VGO-AL$hvGp@^ z;sj1CM?p3VY4cZFm@*p0g6>d}Oj|XS*S4XkAU;=P{(Z*7$qgHFC&^;YV(#%66w<zH zrcUTOv1_J7CtO6>2T>dwou3q^LAb7i8)dkQdmV7{T!pcs%iloaBWo(0nt7#0V68rX z3#){&`z;<8T-r|{*DsBn<LGtfglRS#hklyi`6=KNKwCSD5<m4LG8-wj^A`p%jTYU5 zrmG{&p8=Np1g$3Nmw>Jm$Ii_wGmZW5@+%Pb3TD1N=DVjqV)bvBEl}Zikn1WmL2aft zS9IHl02=LcCpNeTH^M_nq#Z%Duyk!T3mG|bbD0mZ-AdDtF73HHtorN~RxC9|o5TT4 zLoqGF1Jn4rz$b)zl0i<h*H_5+O>1;U_@FNhB-x_rqe0&ojn8O}JnRAW0f%>{&k#?d z;md!+iu{(yA?Q-bsuUTI00#i_GE%G@G_N~)FNy*oSX=5q>f6TX4HmbI=$d(fbBXzC zr!N1d#G41@;)7PQN+(Y!rPYq_Q_5u=W%+GwLmIgY8^&N1((3sB(6%lO6_B1(PR>GS zMscy);AN0taXvOTIK<boc~?_yd8D7)@~Nchka|H`P*d3?3g7ZLLEd4LDasWKv)KiY zv0}9v<g(&;4{B6yp;?;VyyBBIb^><ohc0}5i%ox|E0%3)CrVG!DWx3gJY|XR@(a{T zJa2`aD<Rs_$3t=z@|5R#?h!2VSW;RYt`dekpv=A%$FJ1<V{2qd1Dg!~Mmw=iam-K2 z-_t0C%aGl#eK(ylFN#?@^^?Mj*3zcZ0W^Mv)PnL!rJ-IvDONY?07&p9U}+(v1q69b zuN9A{c|8&uy^6uwbk}*8ge|_5d6@F}U1#L;B)1fICR0ihbn^_EB85d=Cw17&L5uMN zoo;2~ChJkPqoXda?FAF)p}B$)9-6E7)?ASft_qA!9vEd3c_A0xf1f_Ypum3rZ3Jv_ za#_cCM~bQ{vZb9Xc8|K!&lJx?+4kRZ7Dj%;g-nd%@?gp3`MHDFlhI&oX(Ou0pHnU4 zOKnM2?SE-ZyfFU>^N?$nP>V<K@{csNPa7mbgWk!y)o%tOQ+&@7>GQ8OB4@$7gUGJ+ z<|4D~ctu*ZqZD~!=O)q@v_v9%t^vpy^z)AunhZ6c9@m$vW`^}8=ObY~Iee$1FLjMS z0DGo61j=u@+N}^{zZXQ4?=YdQ9|JIUeg>W37rdgQdpQ7Z0k?rWz#ZTQ_`85TKoihd Kwi#Iea{LV_4cRjQ literal 0 HcmV?d00001 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 GIT binary patch literal 6408 zcmdnDZ{I#Q1_lORAPz1`%u7iuN(nCUP0V8k3NW;AGcYg#X%Gg1|3JV9VKQ()`E@`V zCI*!nrG`uhfMOH`fXQHA70}h7SPc&L^~_62tpLR;2rB?F9}sH*u>cSUXXX~<r1}@7 zq!y*7_$C%0$39425{OxW*eNGJIU8mOkdIvgSvN@0C>RZa(GVC7fzc2kJ_JTfZ{ovh zROM(0jE2By2n?PO0JW6|5)?=br0s*kZl)IrD;)=gO9sFZ4I@1MCya3aFBsweAAl@3 nYUOAMpoBoMldho&P-LLn0w|6e<<Knz2E!<5%XRh<+XeyvduT&@ literal 0 HcmV?d00001 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 GIT binary patch literal 6320 zcmeI$%PT}-7zXeU6AGCrAq^=OZpFeaNrYI8a$C&8WyYoCItxXN63Qq^xin!hxeFP? zViprqENDi}Lawu*Tnj0aD8+n_TUP#n^Pa`|owIotU(Y$;a}HyS=>wgoufxS@I<7k< z_oWLNjEQbVWn7I}3ca=QaIi@R{c@_HHbf1j&qGkRw+O>$w&1+_6vh-kL4CAWIiDNv z76y$cDKOPg2+e)9FiY75caq1TOl*L*>s?s9c@B?8Utvk3M8W5tWk<rQ;0$=<ki&Yb z3O-J0U~{_;z7!gvI?4u_>kaHVeuti`0ZKl%UlR+(iX1qcR1O1uTj0di01Vxjh0`M& zFrwiAE@eHyIH`LDpSS)L43jMh&@`C`)7z@yc0mWsjU0hiwhZ%+w&DJY9m;zh&|c<U z$>%B(!{LQbDy+FIf_J7ms2c8uj=FK!nz;twf-Fv5esaKA&*2;*K?-jjC<l~({@haz zC<lLYK>dLF0g58^0|ECV+81bF_`5IAdI<SD>Gyp={eb!b^#i^F^gf^*P!7KTD+~L1 apd2v9I|5^A{&weob$<PM&vW7N@BIfgyfJeC literal 0 HcmV?d00001 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 00000000000..a1a976ed494 --- /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 GIT binary patch literal 325 xcmZQz7zMx(u-yj)K-v<BS&A$3l7TcM6!QUTR**OlOM=)=Ir+)iKn6?!D*!@t2U!3B literal 0 HcmV?d00001 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 GIT binary patch literal 2016 zcmd^A&x_MQ6i&_7?e4nZQH8?bNu(@z@U{s3xfI(??X-9-lVn<g>5NX26|W-V!J~gq z{}TTe^?PZG(`Kd8li-7wy!qbuChyJ5%&b<cmE#<LcO0iW5q>OT+<}r*pr@l)M5^%I zeFx+uA9Q3AV6dUW%;h<rXE|Xk18qVKX_iOO(>1aaNZx~jj*F=u@<7DGv~`><6-|B_ z>I`)-RuJ5Fa{#k`t)oOTkR#uSG}4Nta`{%m&J0z3&Qqa6VM3k`g}M!>0kq{C2DcRJ z0WKFyh2DFW36*BcsfyAB;|I_>^rS!1NklneK*Y*V2;L+#ehNKr8Y4bj^3+6X@dAKb zIP(CqUlssfxsap0JT0wb27!|Zwcen4H5+@c$sX(XY#xgR?ET9D(v}?5MoJ;iqnzkY zACCrJvjg(|ffnI^+Pii0fA`L7@!8Ppbg?XtJwK7=2LA?I>QFYAyjWZ`nAhvg_^8{S zjHg4e_ILFREL#rmDrTYXmK(eC2fE{K4?H$8nPhWqm|#ZErI9L-Oy^bQ`xTu1>4UT- zho}E{x%2)NzE`($PjqJf!NmJ2rCraEEmc<I)7Ff)o7b?P@XTjPAQV&C(wC-z^^dXS zH<G2=c7VIT168nWIlKcn1Ai1VQOcyH&y$}nq2iP$4hcc_$!6o5gT%&a)=skV;@{kY z)-AUodoefoXI!kfS)Gqimvh>DlvJsJX8^0MSO;%lP%vF=5Hc%A2!gtwpkJV00sbj~ Hg39s><ehwe literal 0 HcmV?d00001 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 GIT binary patch literal 1548 zcmdnDZ{I#Q1_lORAPz1`%u7iuN(nCUP0V8k3NY}nFfcFzX(kW{2>t^Bhz|lBKn&uS z0cn^RNPH9wr4RtcAP4}H!M-A(!$Gkc9PI0vmy%imi&X`n7&{Pa0I>iN2WRFM<fQr+ srKA?6ruZfnz|;Yy!1AI%niYtha`Kb2VUkz{K{`gkXb6mkz+eag03f_74FCWD literal 0 HcmV?d00001 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 00000000000..a1a976ed494 --- /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 GIT binary patch literal 325 wcmZQz7zMx(u-yj)K-v<BS&A$3l7TcM5Q6|a5QEqtED6M{PC5C>*+_zn06>-pMgRZ+ literal 0 HcmV?d00001 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 GIT binary patch literal 8122 zcmeHMTT|Oc6gDv?c7Q@lGD(_VmWQNmhCoU$Go8tFB5au`^@U0Tn!b1zTgIBmk}Jtj zI@4d!*Z!71@+0!x-_rIwE6Em;01ur4?R00>+C67)=elQ)kB^U!bGZ+H%jI$fo$px& z8aGhipiZIQ+OoK5hnNTN08$d2is7}<p`o#oUqWYA3{Z`><95)+DpSAXdn<s%CN2`R zuC;b#Ra2S`^lqZQSJ&8q;d-WHGke(IGu#zst{ST=Y<=}9jgcC<+-OK>&Y?zSu_EbG zt0HS<RoT*m<>L71sw`J9I1VdTYtm+`Tu;IaRYk5Pfj3_$n%-1jCDxd2ZfLT)BV}w^ z+-fQfU1@A4rp#?MYm%ybpEaknr8dj5rZv^Xqy=qDF6*jPYwaqHN^>`XE@-->%L(9o ztyz`S7NOD%SqhLLH(OaG-wB4uHMuS~^j1|;YTIgZld0#}6=imHZFOxdo7>dQkV%o? zkt{@eEQ6BhhrAc-jd{L6xCE~W6yxz?&_BW3c>#;@bcq0IUW%<F9==UulDxY+1}1rS ze~e1=>I>pc$|Q2{0-QDpVkDW}z33~sE<Y+e*067r<vi;+Ll`MzAwIvoLz?i>j?wlV zmmQjZj|~qFS=)i}nKt(gk9};~tZ(=*sZTI;Q8z846RfEDb#4Pz6tG~aBKieXI>q~_ zC1uO;Odr-50tt~vMVTUd%%JUOfYY>*ubBIG$Zdbv-?mNPL!ao(-sMjeAgL7VvZN`E zY7_1IsHd}zM$`E|N1Yd}d#3rWY43jx03A9yylD3cF!~{1l|jo_0B6%O?H>)uUCyFX z$*V>epQtOgf-C?kyj*|!lz2rj@pCUJ*A2(97)pqr4?Gi0dNXi2YkJRMU5oFtHiWwG zxJN6jv0ZPKC0&3$)`k2H-tMsu2ZOQBqGGpQqi@(gw<1a3sySWmMm-D?g}6bCP02by z=W@TGk-?j908aCUh%Dk<(a|T8F->}idaBfb6XkRkb}fhdV;0Wr%F5;o9g`mJ8s>h_ zA9HBnSll&#;-uBg8^Ctl2{}d=)VbC%9h@|GA<LhbzJjJ~)+KE9O!j=U53T4hx9>6A zF+HdXw4vSOwryCUSAfMGu{~~g2Hf+QYj^|4_DFxv*ON=6`o7lY5KheFDK-g}F`Sfm zQw*K&r)WP^D$MD!uIYL{gI_Uhn=^Pj6HT{kxQ=jp7&x!_Ae_6MNb_ejt-ixuph%kW za&e(y6*}O=6s%+LqksYr!IItiH<6&K(yNw2G1A?D1Wp$fgVuS&J<$g0e9gLi?$3>4 zlHmA^94lNVXC2(Ht~n4+!KY9ddekv~ye@PkZvLYD-Bwcz!m$sC2BX`BBQp=^*tj9w zz`MaSOlISB@5jrp=w!Hjx`VS_{)Mh~GMLAxr@DO_ASy)*)QAeT&o#9I1)Ah39*FDH zfQu9|749+SNUo!zinEiCIOFVuzK&-viq}M7U=I-M{V;@9b_Cb`p6RiH106+Jc3qC2 zh`xe%p(-9(E2?uC1YS>q54WtqU*Mt<#JCt>cA4%DC$lsVeWG0a#>uvUq@%zalpd%x zY<h>xz~lXa1?C6W<N>#UIA4kWlm6kY*n=|A*3WR8QS2ln>+E@kdmtPsau9SG;Frbb zFmX>z#J?-J=|-u9fV6cbu)sauer+Jy7`RT`@H~g|D-ms@Dc72^S*<KJT6q3!$SRos zSTo>Y;83{5yf%;0My!hfkU91fC_A+N$n%ZKo<)~0s-nm|g#I$Efn=<nj|l&|Xj;O> zOzTQr#@UwYPRHz;xMg1sEfa#M@S};2oNz8vk1#7E__*@C!sMaKKaou+7z3x>18efz zdKsVd!=Z$n=1ESiGIDupQ^fB73ygR;MX}{o2rQKaDm%EEam$3lou;=6+6$vMN?T+C z0m@3_l|&k^g0~C?lu#)VL0PwD>L>GfgrEV$0V;nl3`voa2&twip@m((9FaoFEN_K{ z@5V0MLk7$XautYB;IMmqU@-29U<)%(r?`rhhys+Wq<C2Fad(`k2=nXJ?OLs+%33RA zz_Os5;t0nz_mQHtSa*n!5(yc|Hq5hT(Hg+27^@myNOB~@GlfjfH4-xMDZ{Z=08YQ^ zXw_1q`~r8av_ry+F2)l6)qo>838XEaqtaJwM?WdGNF?bNf+SrlNXT#3G3EAQkgXdS z$Y43HyGCg&z_WO=Bs4xq?`h*a-c1a@3{60a`VVaN29>s?n#wc*mKj|Wcd|$#rf0z^ zN1FvFI<w#;t1S2o;Iv2h&jB6d2K{7b;b|a^Kux8jX%<z19cNnw_{<d}(Tp;flTV3f z|HR(m_7R*O#3$k7M3vYj9G}br#c}b>N*q2)CZvGTu7n^~#h<_N!SQ$H33%*@7Kf*w zCvoiDC|C~y$2cw#A;(eiSThcfg19&=6)?n+(d|UAJpC-Ao_Wm>Z=#EUCQMH}>l9~7 z^9yG#5;;HGI>Xt}+?g{MAZ4WiyZECV|CHx6)I74s_|Tf;L^2srVZ(CTuhF0xghqKz aKc^uHms<{(ra^f{EKNq2{a+1D5almAf(Tv! literal 0 HcmV?d00001 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 GIT binary patch literal 66048 zcmeI)O-|cT7zI$%(n9G!9d!p?2Lq%IA_iVTRA*2XQWP;j463fod+3VbT@tIP3dPu# zs!HdRu1xIb7dQ#S>$Yv%<EpA&m(&;YWiwwj^~G$yd|FOa)z7MWQVvPxy39^FCi|P$ zKQ^z+!Th%Z`9w20cAs|l^ZKjs`&wS_W^tLjYjW<{?b+?;pFePIugCR#>oVKkH~VLg zta(wA>9g)&y|nk*mjl+n8hB6sFEf4rj{I(UuiW>hBs28hx9i(S-T!W)J>E~a&*8*- zec0^hK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB=E90_lf$9r5mI59#>dO}hPmCSCt+Ne}<Nq@Vw{q`Uu6 z((Uh)^!ocIz4^x^9s9>7o%|t5kAG;=;UAOq_s1sP{V9^({!~e4f6A2Y3;_ZJ2oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjX@30!ZcEpt_NO8365XP=kN;<9@#Knmb_F2JXfbiwPdXXmGjlU1`=Et=W< zJnOp2^^YZaF2K?0_b=bNO@7^Xp`14a2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs P0RjXF5FkL{9}C<7&8s@> literal 0 HcmV?d00001 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 GIT binary patch literal 33362 zcmeI*O-oc!9Dwm_U`U`yZITEB7aF#xMW7Z`Qo<}oFmd6cLTnb%czv77@}()?Mtj)^ zu5{PBzQKk12z`?13+Qu*Lq~|*Dm-U+xPQ)E=Fa`i>~B#N#my|s+NqRJ&Q+!=d2OOP zS}RZF&Fp1aJ<FOn#KP7AD;!;?USQ|Q9n|med;h0DC-*-4a<%u;FV0KWKR0=Qx3$sL z=>L5$9-H_l9rybCvEg4ARtv@XVlww6^R;BYp3Hs8d^?%%GIvo}XXzZZQX8G83ly%^ zL7jAkx+z>UeEtm@pj-5ShG>Ky(qno;RhptjTA~m1k@jexzS1}PPCw{Ah4XWorWu;0 zIhv;$y`Y!$iWcZKy`gxGW#$!nOLclj?`f6RXq`4_lNz)|+Z4asC+5!-y~Dl5xtz@5 zUgQ2)GLI+oqhziyyA)7B0R<FLKmi35P(T3%6i`3`1r$&~0R<FLKmi35P(T3%6i`3` z1r$&~0R<FLKmi35P(T3%6i`3`1r$&~0R<FLKmi35P(T3%6i`3`1r$&~0R<FLKmi35 zP(T3%6i`3`1r$&~0R<FLKmi35P(T3%6i`3`1r$&~0R<FLKmi35P(T3%6i`3`1r$&~ z0R<FLKmi35P(XnbD^Re8`F1T-8ZDRdr<H~9Q1{SfYNo;J^yKrfafZVBNgZ_L&G;zz EL-1%zo&W#< literal 0 HcmV?d00001 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 GIT binary patch literal 16 PcmZQ%U|<jcVi*7b0N?-; literal 0 HcmV?d00001 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 GIT binary patch literal 276 zcmdnDZ{I!!21Wq}1_ptU%$(E|=lq<~+`Qlt-^4ti7$XoX0x_p&Kz?y%NoGDwkO?Tr z%)r0`q!=y(F&9`fOdCj+0YqE^VvrdeAPiO#8Ws*@g8)dL5hMx1K#%|;V0;50n=L3c tIlm|cDk=q}xga7gsl~}a9@rL;EJzcGqX@*fTncsyJ_TT}01aSZ006D)B{Tp4 literal 0 HcmV?d00001 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 GIT binary patch literal 24576 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 literal 0 HcmV?d00001 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 GIT binary patch literal 289 zcmdnDZ{I!!21Z#11_ptU%$(E|=lq<~+`Qlt-^4ti7$Xp?0x_p&Kz?y%NoGDwkQpe* z0#VQaq!{i1F&EeXm{ul`Fi6=YAO@Mk3Bq6{E{P?HKn@6i6flBhK^W);FbU%u0C{Xd ssmb|8DL@uTR0@c>AR;cQ#mPV(*cy;5NE&1sA?JeKLr75r&>#o^0K`8gga7~l literal 0 HcmV?d00001 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 GIT binary patch literal 1048576 zcmeI!Z>*MO9l&v*i3uGAaS^pZ0y}^<QJ8q*JU2)PMlM3*uaz}|0mIlz%uP5Y5D)?Z zx*+0DAQN2tRfYlQxexYd4saCNWHgM91Z?O~&Ka9XWUsC__iuN-YQ6cKchC2Fe%E!~ zpXWShr%jvx{By&j)AAh+SyS;{Wxe>mv-fpuIzInzSy^v9)sPQb)GzE`){Fc6zh5tT zq{EQ8n>UB&OMT?8Pvl3QJrvecUsPMk-`W3uUVM(SUVPtwd%f!2k=dY@4#79|x+%@s z>Xm!K{ZfCjays95WL{V=>&5#F|H9#Fmjx%n^Og1D{LlNeY08=phWXS_jJuNMH*O2- zsb6WV$xhGiJfyg9SueitujAV_Ej=@`TGr1F?UvPb|4TT(tQYtB#}_*dd12+EFrWJG zzB!_DVN*@Gf9lifKFUuG>=@?Ddhz`GvY%$1Upo@!Q}0z>o%Nc$DJ#yUzW1>)`G9lB z!g^UR?$hJgw!G)i9l<yCb%Sdv$FHmj_e*`^x@p<??{o;~m-XWLGoG26@BGcdN?E^s z<M?XV$3GL!Pkrm>24<6A?-SNj|KPfRRu62xHLREQ;yt=wm|i*ca>wxbr#^Yt)x5PK ze*USSsa>88A9X04U)GD~zrS=zw*8^qSy?|m^FY4%=`V%#)Nh)$Fu%HLeK<e$aXmkl zkGjwt=2IX4L``*c-X+YJ_2ND6-QFdCW%e!gWqs}ZN2`5n>%#u2?>qU6%HRi<h552x zJm>O^-qpI6i{bYx^(}Aa)xHNdHx}noU)FwS<74-q4BwB`*KQbF-P2)mxL@k8pYEQ0 zw^#2lpZfCWdQ=|1XJ@!ySuftRb<C*j#RcnvU+Qm<=$rMpeP#H3Q*Zdm@_a*eW;nmB z7tcS~+O<0Uqi4eTskbyv&DOjWzu&1Zoch+mi~T2r`=wq}yD%HM|4{Ht{gDBcO1FU% z!+h#XxAn@$eEYNEeyPtrP+Qsi#!X?qtQVhu#jV|keC5wm!+h$en$G9FpL#p2r`~?j zSM%2T_Tm47vR*v@&QG^ypLpQ4;FJ1-Uo|y;@WiEXe(JURuI2A6ULQXH)Sq0pH9OSq zr7)lRu6b?q-@dRWoS*u|pWTstZ~W!Fc<<B?ynL~8-H14!`r2;{$p+lNBb;B>i|0Jw zscp9L!T9;7K6K#?*$)OT3;U;j_1dcZ=81E|--ohZJpY~}^XuEb)h|4M>K)Ea%`Pw5 z8TL>8?tV4ZhxW`0_e=e7-I2<B&t4Axsek?ZpUk&B-!+_{`iE_Pm=C)2tuUYZsofK* zds}}Q)>EI;?w0K2%?rYM>VMpR+rcxh{wthc){B1cF5FugFy)RgpZdk~cV-POi-S+< zTlznjPx}0vN^#$^UVPt)(}Sv;R{T9YU+NQv&CVWf-a5EAm-@^u+v=M(j1Bio{YSgo zXU#Q-!~UsnU(q+eFz2rD{Hc%bHYDFS^6oI7`YX$JR?mKId6-YV)6ApwAHKRO%%|SC z<N9jrnDb#i^(!sMvLAPSFZ}+N^`g`MUS0EX4M!_w{r4|6SBH)IcsM`xh0A(ot7gRe zm-XT~!_Rf7&YW;9oS*uzNq?$7^<ZuANxgph^2Wygox=T6?|knY)xLLqD(s*7`lnke zH?C<H=2QRj*wvMDTaJhSFH%2w`uc47<Kx2pQlB+?Om+I3@%x$jp(#fzgDdmG{ZhX$ z>$b+4OWm@fTk8A&u(Wa4^?ky6SugJ6H30$y2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly I@V^)MHw%PAtN;K2 literal 0 HcmV?d00001 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 GIT binary patch literal 289 zcmdnDZ{I!!21Z#11_ptU%$(E|=lq<~+`Qlt-^4ti7$Xp?0x_p&Kz?y%NoGDwkQpe* z0#*Pb85r&WF&EeXm{ul`Fi7$e5QEGC0Zy<Ix17Xu2%iCD4kJhwq5`Z5%m(odfS4^P qH95a11*$>{NOM6%TvCgZfjo!~s39QJ2sjtw9s-IWegYcCzyJV7P$hN% literal 0 HcmV?d00001 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 GIT binary patch literal 131072 zcmeIu!41G53<R*-p!;vx4QL4gsT=fRgy0g$$4SP>om{owFpJ84RJoenIoEpXi8J*H z`^#)v1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 I2>e^%0nt7Q_W%F@ literal 0 HcmV?d00001 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 GIT binary patch literal 292 zcmdnDZ{I!!21W%21_ptU%$(E|=lq<~+`Qlt-^4ti7$XoX0x_p&Kz?y%NoGDwkO?Tr z!N9-*q!=CpF&9`fOdCj+ffa}^0Wru7kO0Jdx6+)PpwwcwoWyjX7zi)}F(XJ32m?U_ zh=B18fNZv))a3l46sV{akmiDjxTF>*19@QEK(ZiBAk$QV7{kRN380I?CWD+sNRa?g H4FmuH+*2sm literal 0 HcmV?d00001 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 GIT binary patch literal 32768 zcmeIzF%Ezr3<XfR|Dkn2Mj}C=9Ucn_(BI}Ql&h{XxZfYEm($96_w9W2-R%3nb*t)E z=j_VcoqH<(bf2hx(S5V@&o)Qad9uy*enU0_1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF L5FkK+K$yS-N)@0V literal 0 HcmV?d00001 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 GIT binary patch literal 299 zcmdnDZ{I!!21a!T1_ptU%$(E|=lq<~+`Qlt-^4ti7$Xp?0x_p&Kz?y%NoGDwkQpe* z0#VQaq!^w7F&EeXm{ul`FdGnG0%DLk>_99GRuZ0?nVwM+T#%YvQdF7?6b1p1iHsn1 zAk4tP0VZL510atrC^b31C<Vv@iAn)67evG*wKy5b16v4^1xbTUBjj+f%LplI02%}V E0HV<<+yDRo literal 0 HcmV?d00001 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 GIT binary patch literal 524288 zcmeI!Jq~~%3<l7i&2xEpCI>r^5JmZUCJrRnetkF)aURk2jfYQbzTB?$`@H|{dSBQ7 zyWeR2N6#}`zS;AxmVfnmcAIbadCgzHpY^_<WdGULeCq#CK2KhEuk2w>fB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N g0t5&UAV7cs0RjXF5FkK+009C72oNAZfWReC0K@n+F8}}l literal 0 HcmV?d00001 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 00000000000..105af65c138 --- /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 GIT binary patch literal 349 zcmZQz7zMx(2;Bz+KspeJS&A$3l7TcM5MKgf2_R;IVnZO!>Xeh8oDF1v6tF{S5E}yn E06Oyse*gdg literal 0 HcmV?d00001 diff --git a/CEP/Calibration/pystationresponse/test/tStationBeamNCP.py b/CEP/Calibration/pystationresponse/test/tStationBeamNCP.py new file mode 100644 index 00000000000..bed4602c339 --- /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 00000000000..28e06978533 --- /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 00000000000..f69e21ff7d1 --- /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 00000000000..f967776d2c3 --- /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 2320c39a326..a7b83510990 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) -- GitLab