Commit 3e91edf7053fab1ac35fce25923ce18596bf8cae
1 parent
75b5e302
Implement response computation for extended sources
Showing
2 changed files
with
311 additions
and
21 deletions
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& 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& 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& 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 | +} | ... | ... |