Commit 3e91edf7053fab1ac35fce25923ce18596bf8cae

Authored by Jürgen Knödlseder
1 parent 75b5e302

Implement response computation for extended sources

inst/com/include/GCOMResponse.hpp
... ... @@ -111,6 +111,13 @@ private:
111 111 const GObservation& obs,
112 112 GMatrix* gradients = NULL) const;
113 113  
  114 + // Support methods
  115 + GVector irf_extended(const GModelSky& model,
  116 + const GObservation& obs,
  117 + const GSkyDir& model_dir,
  118 + const double& theta_max,
  119 + GMatrix* gradients = NULL) const;
  120 +
114 121 // Private data members
115 122 GCaldb m_caldb; //!< Calibration database
116 123 std::string m_rspname; //!< Response name
... ...
inst/com/src/GCOMResponse.cpp
... ... @@ -32,6 +32,8 @@
32 32 #include <typeinfo>
33 33 #include "GTools.hpp"
34 34 #include "GMath.hpp"
  35 +#include "GVector.hpp"
  36 +#include "GMatrix.hpp"
35 37 #include "GFits.hpp"
36 38 #include "GCaldb.hpp"
37 39 #include "GEvent.hpp"
... ... @@ -44,6 +46,8 @@
44 46 #include "GFitsImageFloat.hpp"
45 47 #include "GModelSky.hpp"
46 48 #include "GModelSpatialPointSource.hpp"
  49 +#include "GModelSpatialRadial.hpp"
  50 +#include "GModelSpatialElliptical.hpp"
47 51 #include "GModelSpectralConst.hpp"
48 52 #include "GCOMResponse.hpp"
49 53 #include "GCOMObservation.hpp"
... ... @@ -66,6 +70,8 @@
66 70 " GObservation&, GMatrix*)"
67 71 #define G_IRF_DIFFUSE "GCOMResponse::irf_diffuse(GModelSky&, GObservation&, "\
68 72 "GMatrix*)"
  73 +#define G_IRF_EXTENDED "GCOMResponse::irf_extended(GModelSky&, "\
  74 + "GObservation&, GSkyDir&, double&, GMatrix*)"
69 75  
70 76 /* __ Macros _____________________________________________________________ */
71 77  
... ... @@ -972,25 +978,25 @@ GVector GCOMResponse::irf_ptsrc(const GModelSky&amp; model,
972 978 * @return Instrument response to radial source for all events in
973 979 * observation (\f$cm^2\f$).
974 980 *
975   - * @todo Implement method.
  981 + * Returns the instrument response to a radial source for all events in
  982 + * the observations. See GCOMResponse::irf_extended for more information.
976 983 ***************************************************************************/
977 984 GVector GCOMResponse::irf_radial(const GModelSky& model,
978 985 const GObservation& obs,
979 986 GMatrix* gradients) const
980 987 {
981   - // Throw exception
982   - std::string msg = "Response computation not yet implemented "
983   - "for spatial model type \""+
984   - model.spatial()->type()+"\".";
985   - throw GException::feature_not_implemented(G_IRF_RADIAL, msg);
  988 + // Get pointer to radial model
  989 + const GModelSpatialRadial* radial =
  990 + static_cast<const GModelSpatialRadial*>(model.spatial());
986 991  
987   - // Get number of events
988   - int nevents = obs.events()->size();
  992 + // Get references to model direction and maximum model radius
  993 + const GSkyDir& model_dir = radial->dir();
  994 + const double& theta_max = radial->theta_max();
989 995  
990   - // Initialise result
991   - GVector irfs(nevents);
  996 + // Call support method
  997 + GVector irfs = irf_extended(model, obs, model_dir, theta_max, gradients);
992 998  
993   - // Return IRF value
  999 + // Return IRF vector
994 1000 return irfs;
995 1001 }
996 1002  
... ... @@ -1004,23 +1010,23 @@ GVector GCOMResponse::irf_radial(const GModelSky&amp; model,
1004 1010 * @return Instrument response to elliptical source for all events in
1005 1011 * observation (\f$cm^2\f$).
1006 1012 *
1007   - * @todo Implement method.
  1013 + * Returns the instrument response to an elliptical source for all events in
  1014 + * the observations. See GCOMResponse::irf_extended for more information.
1008 1015 ***************************************************************************/
1009 1016 GVector GCOMResponse::irf_elliptical(const GModelSky& model,
1010 1017 const GObservation& obs,
1011 1018 GMatrix* gradients) const
1012 1019 {
1013   - // Throw exception
1014   - std::string msg = "Response computation not yet implemented "
1015   - "for spatial model type \""+
1016   - model.spatial()->type()+"\".";
1017   - throw GException::feature_not_implemented(G_IRF_ELLIPTICAL, msg);
  1020 + // Get pointer to elliptical model
  1021 + const GModelSpatialElliptical* elliptical =
  1022 + static_cast<const GModelSpatialElliptical*>(model.spatial());
1018 1023  
1019   - // Get number of events
1020   - int nevents = obs.events()->size();
  1024 + // Get references to model direction and maximum model radius
  1025 + const GSkyDir& model_dir = elliptical->dir();
  1026 + const double& theta_max = elliptical->theta_max();
1021 1027  
1022   - // Initialise result
1023   - GVector irfs(nevents);
  1028 + // Call support method
  1029 + GVector irfs = irf_extended(model, obs, model_dir, theta_max, gradients);
1024 1030  
1025 1031 // Return IRF value
1026 1032 return irfs;
... ... @@ -1209,3 +1215,280 @@ GVector GCOMResponse::irf_diffuse(const GModelSky&amp; model,
1209 1215 // Return IRF vector
1210 1216 return irfs;
1211 1217 }
  1218 +
  1219 +
  1220 +/***********************************************************************//**
  1221 + * @brief Return instrument response to extended source
  1222 + *
  1223 + * @param[in] model Sky model.
  1224 + * @param[in] obs Observation.
  1225 + * @param[in] model_dir Centre direction of extended model.
  1226 + * @param[in] theta_max Maximum theta angle (radians).
  1227 + * @param[out] gradients Gradients matrix.
  1228 + * @return Instrument response to radial source for all events in
  1229 + * observation (\f$cm^2\f$).
  1230 + *
  1231 + * Returns the instrument response to an extended source for all events in
  1232 + * the observations.
  1233 + *
  1234 + * The computation is done by integrating the extended model for each pixel
  1235 + * in Chi and Psi over a circular region centred on the Chi/Psi pixel with
  1236 + * a radius equal to the maximum Phigeo value. The integration is done in
  1237 + * a spherical system, spanned by Phigeo and azimuth angle. Only coordiates
  1238 + * are considered that plausibly overlap with the extended model. The
  1239 + * integration is done by numerical summation. The integration step is by
  1240 + * default set to 1/10 of the model size, with the constraint that it never
  1241 + * becomes larger than the IAQ Phigeo binsize. This should allow to capture
  1242 + * all extended models with sufficient precision in an acceptable amount of
  1243 + * computing time.
  1244 + *
  1245 + * @p gradients is an optional matrix where the number of rows corresponds
  1246 + * to the number of events in the observation and the number of columns
  1247 + * corresponds to the number of spatial model parameters. Since no gradients
  1248 + * are computed in this method the content of @p gradients if not altered.
  1249 + ***************************************************************************/
  1250 +GVector GCOMResponse::irf_extended(const GModelSky& model,
  1251 + const GObservation& obs,
  1252 + const GSkyDir& model_dir,
  1253 + const double& theta_max,
  1254 + GMatrix* gradients) const
  1255 +{
  1256 + // Extract COMPTEL observation
  1257 + const GCOMObservation* obs_ptr = dynamic_cast<const GCOMObservation*>(&obs);
  1258 + if (obs_ptr == NULL) {
  1259 + std::string cls = std::string(typeid(&obs).name());
  1260 + std::string msg = "Observation of type \""+cls+"\" is not a COMPTEL "
  1261 + "observations. Please specify a COMPTEL observation "
  1262 + "as argument.";
  1263 + throw GException::invalid_argument(G_IRF_EXTENDED, msg);
  1264 + }
  1265 +
  1266 + // Extract COMPTEL event cube
  1267 + const GCOMEventCube* cube = dynamic_cast<const GCOMEventCube*>(obs_ptr->events());
  1268 + if (cube == NULL) {
  1269 + std::string msg = "Observation \""+obs.name()+"\" ("+obs.id()+") does "
  1270 + "not contain a COMPTEL event cube. Please specify "
  1271 + "a COMPTEL observation containing and event cube.";
  1272 + throw GException::invalid_argument(G_IRF_EXTENDED, msg);
  1273 + }
  1274 +
  1275 + // Throw an exception if COMPTEL response is not set or if
  1276 + if (m_iaq.empty()) {
  1277 + std::string msg = "COMPTEL response is empty. Please initialise the "
  1278 + "response with an \"IAQ\".";
  1279 + throw GException::invalid_value(G_IRF_EXTENDED, msg);
  1280 + }
  1281 + else if (m_phigeo_bin_size == 0.0) {
  1282 + std::string msg = "COMPTEL response has a zero Phigeo bin size. "
  1283 + "Please initialise the response with a valid "
  1284 + "\"IAQ\".";
  1285 + throw GException::invalid_value(G_IRF_EXTENDED, msg);
  1286 + }
  1287 +
  1288 + // Get number of Chi/Psi pixels, Phibar layers and event bins
  1289 + int npix = cube->naxis(0) * cube->naxis(1);
  1290 + int nphibar = cube->naxis(2);
  1291 + int nevents = cube->size();
  1292 +
  1293 + // Initialise result
  1294 + GVector irfs(nevents);
  1295 +
  1296 + // Initialise some variables
  1297 + double phigeo_min = m_phigeo_min * gammalib::deg2rad;
  1298 + double phigeo_bin = m_phigeo_bin_size * gammalib::deg2rad;
  1299 + const GSkyMap& drx = obs_ptr->drx().map();
  1300 + const double* drg = obs_ptr->drg().map().pixels();
  1301 + double cos_theta_max = std::cos(theta_max);
  1302 +
  1303 + // Compute IAQ normalisation (1/s): DEADC / ONTIME (s)
  1304 + double iaq_norm = obs_ptr->deadc() /
  1305 + (obs_ptr->ontime() * cube->dre().tof_correction());
  1306 +
  1307 + // Loop over Chi and Psi
  1308 + for (int ipix = 0; ipix < npix; ++ipix) {
  1309 +
  1310 + // Get pointer to event bin
  1311 + const GCOMEventBin* bin = (*cube)[ipix];
  1312 +
  1313 + // Get reference to instrument direction
  1314 + const GCOMInstDir& obsDir = bin->dir();
  1315 +
  1316 + // Get reference to Phigeo sky direction
  1317 + const GSkyDir& phigeoDir = obsDir.dir();
  1318 +
  1319 + // Compute angle between model centre and Phigeo sky direction (radians)
  1320 + // and position angle of model with respect to Phigeo sky direction (radians)
  1321 + //double zeta = phigeoDir.dist(radial->dir());
  1322 + double zeta = model_dir.dist(phigeoDir);
  1323 + double phi0 = phigeoDir.posang(model_dir);
  1324 +
  1325 + // Setup rotation matrix for faster conversion towards sky coordinates
  1326 + GMatrix ry;
  1327 + GMatrix rz;
  1328 + ry.eulery(phigeoDir.dec_deg() - 90.0);
  1329 + rz.eulerz(-phigeoDir.ra_deg());
  1330 + GMatrix rot = (ry * rz).transpose();
  1331 +
  1332 + // Setup azimuth angle range computation
  1333 + double sin_zeta = std::sin(zeta);
  1334 + double cos_zeta = std::cos(zeta);
  1335 +
  1336 + // Setup Phigeo integration range
  1337 + double phigeo_low = (zeta > theta_max) ? zeta - theta_max : 0.0;
  1338 + double phigeo_up = zeta + theta_max;
  1339 +
  1340 + // Setup Phigeo integration step-size and number of integration steps. By
  1341 + // default the integration step size is one tenth of the model diameter, but
  1342 + // not larger than the Phigeo binning of the IAQ. We also make sure that there
  1343 + // are at least two Phigeo integration steps.
  1344 + double phigeo_length = phigeo_up - phigeo_low;
  1345 + double phigeo_step = 0.1 * phigeo_length;
  1346 + if (phigeo_step > phigeo_bin) {
  1347 + phigeo_step = phigeo_bin;
  1348 + }
  1349 + int nphigeo = int(phigeo_length/phigeo_step + 0.5);
  1350 + if (nphigeo < 2) {
  1351 + nphigeo = 2;
  1352 + }
  1353 + phigeo_step = phigeo_length / double(nphigeo);
  1354 +
  1355 + // Compute solid-angle for selected Phigeo integration step size
  1356 + double omega0 = 2.0 * std::sin(0.5 * phigeo_step);
  1357 +
  1358 + // Integrate over Phigeo
  1359 + double phigeo = phigeo_low;
  1360 + for (int iphigeo = 0; iphigeo <= nphigeo; ++iphigeo, phigeo += phigeo_step) {
  1361 +
  1362 + // Compute half length of the arc (in radians) from a circle with
  1363 + // radius phigeo that intersects with the model, defined as a circle
  1364 + // with maximum radius m_theta_max
  1365 + double dphi = 0.5 * gammalib::roi_arclength(phigeo,
  1366 + zeta,
  1367 + cos_zeta,
  1368 + sin_zeta,
  1369 + theta_max,
  1370 + cos_theta_max);
  1371 +
  1372 + // If arc length is not positive then skip this Phigeo value
  1373 + if (dphi <= 0.0) {
  1374 + continue;
  1375 + }
  1376 +
  1377 + // Compute sine and cosine of Phigeo
  1378 + double sin_phigeo = std::sin(phigeo);
  1379 + double cos_phigeo = std::cos(phigeo);
  1380 +
  1381 + // Determine number of azimuthal integration steps and step size
  1382 + // in radians. By default the integration step size is one tenth of
  1383 + // the arclength, but larger than the Phigeo binning of the IAQ. We
  1384 + // also make sure that there are at least two integration steps.
  1385 + double azimuth_length = 2.0 * dphi * sin_phigeo;
  1386 + double az_step = 0.1 * azimuth_length;
  1387 + if (az_step > phigeo_bin) {
  1388 + az_step = phigeo_bin;
  1389 + }
  1390 + int naz = int(azimuth_length/az_step + 0.5);
  1391 + if (naz < 2) {
  1392 + naz = 2;
  1393 + }
  1394 + az_step = 2.0 * dphi / double(naz);
  1395 +
  1396 + // Computes solid angle of integration bin multiplied by Jaccobian
  1397 + double omega = omega0 * az_step * sin_phigeo;
  1398 +
  1399 + // Loop over azimuth angle
  1400 + double az = phi0 - dphi;
  1401 + for (int iaz = 0; iaz <= naz; ++iaz, az += az_step) {
  1402 +
  1403 + // Get sky direction
  1404 + double sin_az = std::sin(az);
  1405 + double cos_az = std::cos(az);
  1406 + GVector native(-cos_az*sin_phigeo, sin_az*sin_phigeo, cos_phigeo);
  1407 + GVector dir = rot * native;
  1408 + GSkyDir skyDir;
  1409 + skyDir.celvector(dir);
  1410 +
  1411 + // Fall through if sky direction is not contained in DRX
  1412 + if (!drx.contains(skyDir)) {
  1413 + continue;
  1414 + }
  1415 +
  1416 + // Set photon
  1417 + GPhoton photon(skyDir, bin->energy(), bin->time());
  1418 +
  1419 + // Get model sky intensity for photon (unit: sr^-1)
  1420 + double intensity = model.spatial()->eval(photon);
  1421 +
  1422 + // Fall through if intensity is zero
  1423 + if (intensity == 0.0) {
  1424 + continue;
  1425 + }
  1426 +
  1427 + // Multiply intensity by DRX value (unit: cm^2 s sr^-1)
  1428 + intensity *= drx(skyDir);
  1429 +
  1430 + // Multiply intensity by solid angle (unit: cm^2 s)
  1431 + intensity *= omega;
  1432 +
  1433 + // Extract IAQ value by linear inter/extrapolation in Phigeo
  1434 + double phirat = phigeo / phigeo_bin; // 0.5 at bin centre
  1435 + int i_phigeo = int(phirat); // index into which Phigeo falls
  1436 + double eps = phirat - i_phigeo - 0.5; // 0.0 at bin centre [-0.5, 0.5[
  1437 + double iaq = 0.0;
  1438 +
  1439 + // Continue only if Phigeo index is valid
  1440 + if (i_phigeo < m_phigeo_bins) {
  1441 +
  1442 + // Loop over Phibar
  1443 + for (int iphibar = 0; iphibar < nphibar; ++iphibar) {
  1444 +
  1445 + // Get IAQ index
  1446 + int i = iphibar * m_phigeo_bins + i_phigeo;
  1447 +
  1448 + // Get interpolated IAQ value
  1449 + if (eps < 0.0) { // interpolate towards left
  1450 + if (i_phigeo > 0) {
  1451 + iaq = (1.0 + eps) * m_iaq[i] - eps * m_iaq[i-1];
  1452 + }
  1453 + else {
  1454 + iaq = (1.0 - eps) * m_iaq[i] + eps * m_iaq[i+1];
  1455 + }
  1456 + }
  1457 + else { // interpolate towards right
  1458 + if (i_phigeo < m_phigeo_bins-1) {
  1459 + iaq = (1.0 - eps) * m_iaq[i] + eps * m_iaq[i+1];
  1460 + }
  1461 + else {
  1462 + iaq = (1.0 + eps) * m_iaq[i] - eps * m_iaq[i-1];
  1463 + }
  1464 + }
  1465 +
  1466 + // Fall through if IAQ is not positive
  1467 + if (iaq <= 0.0) {
  1468 + continue;
  1469 + }
  1470 +
  1471 + // Get DRI index
  1472 + int idri = ipix + iphibar * npix;
  1473 +
  1474 + // Compute IRF value (unit: cm^2)
  1475 + double irf = iaq * drg[idri] * iaq_norm * intensity;
  1476 +
  1477 + // Add IRF value if it is positive
  1478 + if (irf > 0.0) {
  1479 + irfs[idri] += irf;
  1480 + }
  1481 +
  1482 + } // endfor: looped over Phibar
  1483 +
  1484 + } // endif: Phigeo bin was valid
  1485 +
  1486 + } // endfor: looped over azimuth angle around locus of IAQ
  1487 +
  1488 + } // endfor: looped over Phigeo angles
  1489 +
  1490 + } // endfor: looped over Chi and Psi pixels
  1491 +
  1492 + // Return IRF vector
  1493 + return irfs;
  1494 +}
... ...