From 35158b337d246ed61ee050f5c3172e928c36683f Mon Sep 17 00:00:00 2001 From: Nikos Karastathis <n.karastathis@kit.edu> Date: Fri, 26 Feb 2021 15:05:41 +0100 Subject: [PATCH] Antennas are tested and work. TODO: fix the foor loop for antennas. --- corsika/modules/radio/CoREAS.hpp | 414 +++++++++++++----- corsika/modules/radio/RadioProcess.hpp | 5 +- corsika/modules/radio/ZHS.hpp | 25 +- corsika/modules/radio/antennas/Antenna.hpp | 2 +- .../radio/antennas/TimeDomainAntenna.hpp | 40 +- .../modules/radio/detectors/RadioDetector.hpp | 2 +- tests/modules/testRadio.cpp | 281 +++++++++++- 7 files changed, 591 insertions(+), 178 deletions(-) diff --git a/corsika/modules/radio/CoREAS.hpp b/corsika/modules/radio/CoREAS.hpp index a9f8f9c05..e0a4ea1f7 100755 --- a/corsika/modules/radio/CoREAS.hpp +++ b/corsika/modules/radio/CoREAS.hpp @@ -11,6 +11,7 @@ #include <corsika/modules/radio/propagators/StraightPropagator.hpp> #include <corsika/framework/geometry/QuantityVector.hpp> #include <corsika/framework/core/PhysicalUnits.hpp> +#include <corsika/modules/radio/propagators/SignalPath.hpp> #include <cmath> namespace corsika { @@ -50,152 +51,325 @@ namespace corsika { template <typename Particle, typename Track> ProcessReturn simulate(Particle& particle, Track const& track) const { - // set threshold for application of ZHS-like approximation - const double approxThreshold_ {1.0e-3}; - - //get global simulation time for that track. (This is my best guess for now) - auto startTime_ {particle.getTime() - - track.getDuration()}; // time at start point of track. - auto endTime_ {particle.getTime()}; // time at end point of track. + // get the global simulation time for that track. (best guess for now) + auto startTime_ {particle.getTime() - track.getDuration()}; // time at the start point of the track hopefully. + auto endTime_ {particle.getTime()}; - // beta is defined as velocity / speed of light - auto startBeta_ {track.getVelocity(0) / constants::c}; - auto endBeta_ {track.getVelocity(1) / constants::c}; - //TODO: check if they are different!!! They shouldn't!!! -// auto startBeta_ {(track.getPosition(0) / (particle.getTime() -// - track.getDuration()) ) / constants::c}; -// auto endBeta_ {(track.getPosition(1) / particle.getTime() ) / constants::c}; - - // calculate gamma factor using beta (the proper way would be with energy over mass) - auto startGamma_ {1. / sqrt(1. - (startBeta_ * startBeta_))}; - auto endGamma_ {1. / sqrt(1. - (endBeta_ * endBeta_))}; + // gamma factor is calculated using beta +// auto startGamma_ {1. / sqrt(1. - (startBeta_ * startBeta_))}; +// auto endGamma_ {1. / sqrt(1. - (endBeta_ * endBeta_))}; // get start and end position of the track auto startPoint_ {track.getPosition(0)}; auto endPoint_ {track.getPosition(1)}; + // beta is velocity / speed of light. Start & end should be the same! + auto beta_ {((endPoint_ - startPoint_) / (endTime_ - startTime_)).normalized()}; + // get particle charge auto const charge_ {get_charge(particle.getPID())}; - // we loop over each antenna in the collection - for (auto& antenna : detector_.getAntennas()) { + // set threshold for application of ZHS-like approximation. + const double approxThreshold_ {1.0e-3}; - ElectricFieldVector EV1_ {0_V / 0_m}; - ElectricFieldVector EV2_ {0_V / 0_m}; - ElectricFieldVector EV3_ {0_V / 0_m}; + // we loop over each antenna in the collection. + for (auto& antenna : detector_.getAntennas()) { - auto startPointReceiveTime_ {0_ns}; - auto endPointReceiveTime_ {0_ns}; + std::vector<ElectricFieldVector> EVstart_; + std::vector<ElectricFieldVector> EVend_; + std::vector<TimeType> startTTimes_; + std::vector<TimeType> endTTimes_; + std::vector<double> preDoppler; + std::vector<double> postDoppler; + std::vector<QuantityVector<dimensionless_d>> ReceiveVectorsStart_; + std::vector<QuantityVector<dimensionless_d>> ReceiveVectorsEnd_; // get the Path (path1) from the start "endpoint" to the antenna. - // This is a SignalPathCollection - auto paths1{this->propagator_.propagate(startPoint_, antenna.getLocation())}; + // This is a Signal Path Collection + auto paths1 {this->propagator_.propagate(startPoint_, antenna.getLocation(), 1_nm)}; // TODO: Need to add the stepsize to .propagate()!!!! - // set postDoppler to approximate threshold in advance and check after loop - // if we need to perform ZHS-like approximation - auto preDoppler_ {approxThreshold_}; // now loop over the paths for startpoint that we got above for (auto const& path : paths1) { - preDoppler_ = 1. - path.average_refractive_index_ * - startBeta_ * path.emit_; + // calculate preDoppler factor + double preDoppler_{1. - path.average_refractive_index_ * + beta_ * path.emit_}; + + // store it to the preDoppler std::vector for later comparisons + preDoppler.push_back(preDoppler_); + + // calculate receive times for startpoint + auto startPointReceiveTime_ {path.total_time_ + startTime_}; + + // store it to startTTimes_ std::vector for later use + startTTimes_.push_back(startPointReceiveTime_); - if (preDoppler_ > approxThreshold_) { - startPointReceiveTime_ = path.total_time_ + - startTime_ ; // might do it on the fly + // store the receive unit vector + ReceiveVectorsStart_.push_back(path.receive_); - // CoREAS calculation -> get ElectricFieldVector1 - EV1_= (charge_ / constants::c) * - path.receive_.cross(path.receive_.cross(startBeta_)) / - (path.R_distance_ * preDoppler_); + // calculate electric field vector for startpoint + auto EV1_= (charge_ / constants::c) * + path.receive_.cross(path.receive_.cross(beta_)) / + (path.R_distance_ * preDoppler_); - // pass it to the antenna - antenna.receive(startPointReceiveTime_, path.receive_, EV1_); - } else { - continue; - } // END preDoppler check + // store it to EVstart_ std::vector for later use + EVstart_.push_back(EV1_); - } // END: loop over paths for startpoint + } // End of looping over paths1 // get the Path (path2) from the end "endpoint" to the antenna. // This is a SignalPathCollection - auto paths2{this->propagator_.propagate(endPoint_, antenna.getLocation())}; + auto paths2 {this->propagator_.propagate(endPoint_, antenna.getLocation())}; - // set postDoppler to approximate threshold in advance and check after loop - // if we need to perform ZHS-like approximation - auto postDoppler_ {approxThreshold_}; // now loop over the paths for endpoint that we got above for (auto const& path : paths2) { - postDoppler_ = 1. - path.average_refractive_index_ * - endBeta_ * path.emit_; - - if (preDoppler_ > approxThreshold_) { - auto endPointReceiveTime_ = path.total_time + endTime_ ; // might do it on the fly - - //CoREAS calculation -> get ElectricFieldVector2 - EV2_ = (charge_ / constants::c) * - path.receive_.cross(path.receive_.cross(endBeta_)) / - (path.R_distance_ * postDoppler_); - - // pass it to the antenna - antenna.receive(endPointReceiveTime_, path.receive_, EV2_); - } else { - continue; - } // END postDoppler check - - } // END: loop over paths for endpoint - - // perform ZHS-like calculation close to Cherenkov angle - if (fabs(preDoppler_) <= approxThreshold_ || fabs(postDoppler_) <= approxThreshold_) { - // get global simulation time for the middle point of that track. (This is my best guess for now) - auto midTime_{particle.getTime() - (track.getDuration() / 2)}; - - // beta is defined as velocity / speed of light - auto midBeta_{track.getVelocity(0.5) / constants::c}; - // auto midBeta_ {(track.getPosition(0.5) / (particle.getTime() - // - (track.getDuration() / 2) ) / constants::c}; - - // calculate gamma factor using beta (the proper way would be with energy over mass) - // auto midGamma_ {1. / sqrt(1. - (midBeta_ * midBeta_))}; - - // get start and end position of the track - auto midPoint_{track.getPosition(0.5)}; - - // get the Path (path3) from the middle "endpoint" to the antenna. - // This is a SignalPathCollection - auto paths3{this->propagator_.propagate(midPoint_, antenna.getLocation())}; - - // now loop over the paths for endpoint that we got above - for (auto const& path : paths3) { - midDoppler_ = 1. - path.average_refractive_index_ * midBeta_ * path.emit_; - - auto const midPointReceiveTime_{path.total_time_ + - midTime_}; // might do it on the fly - - // CoREAS calculation -> get ElectricFieldVector3 for "midPoint" - EV3_ = (charge_ / constants::c) * - path.receive_.cross(path.receive_.cross(midBeta_)) / - (path.R_distance_ * midDoppler_); - - // pass it to the antenna but first check if "start" or "end" are double counted!!! - if (fabs(preDoppler_) > approxThreshold_ && - fabs(postDoppler_) <= approxThreshold_) { - - antenna.receive(startPointReceiveTime_, path.receive_, -EV1_); - antenna.receive(midPointReceiveTime_, path.receive_, EV3_); - } else if (fabs(preDoppler_) <= approxThreshold_ && - fabs(postDoppler_) > approxThreshold_) { - antenna.receive(endPointReceiveTime_, path.receive_, -EV2_); - antenna.receive(midPointReceiveTime_, path.receive_, EV3_); - - } else { - antenna.receive(midPointReceiveTime_, path.receive_, EV3_); - } // end deleting double-counted values - } - } // end of ZHS-like approximation - - } // END: loop over antennas - } + + double postDoppler_{1. - path.average_refractive_index_ * + beta_ * path.emit_}; // maybe this is path.receive_ (?) + + // store it to the postDoppler std::vector for later comparisons + postDoppler.push_back(postDoppler_); + + // calculate receive times for endpoint + auto endPointReceiveTime_ {path.total_time_ + endTime_}; + + // store it to endTTimes_ std::vector for later use + endTTimes_.push_back(endPointReceiveTime_); + + // store the receive unit vector + ReceiveVectorsEnd_.push_back(path.receive_); + + // calculate electric field vector for endpoint + auto EV2_= (charge_ / constants::c) * + path.receive_.cross(path.receive_.cross(beta_)) / + (path.R_distance_ * postDoppler_); + + // store it to EVstart_ std::vector for later use + EVend_.push_back(EV2_); + + } // End of looping over paths2 + + // start doing comparisons for preDoppler and postDoppler + // first check that start and end paths have the same number of paths + if (EVstart_.size() == EVend_.size()) { + + // use this to access different elements of std::vectors + std::size_t index = 0; + for (auto& preDoppler__ : preDoppler) { + + // redistribute contributions over time scale defined by the observation time resolution + // this is to make sure that "start" and "end" won't end up in the same bin (xtensor)!! + if ((preDoppler__ < 1.e-9) || (postDoppler.at(index) < 1.e-9)) { + + auto gridResolution_ {antenna.duration_}; + auto deltaT_ { endTTimes_.at(index) - startTTimes_.at(index) }; + + if (fabs(deltaT_) < gridResolution_) { + + EVstart_.at(index) = EVstart_.at(index) * fabs(deltaT_ / gridResolution_); + EVend_.at(index) = EVend_.at(index) * fabs(deltaT_ / gridResolution_); + + const long startBin = static_cast<long>(floor(startTTimes_.at(index)/gridResolution_+0.5l)); + const long endBin = static_cast<long>(floor(endTTimes_.at(index)/gridResolution_+0.5l)); + const double startBinFraction = (startTTimes_.at(index)/gridResolution_)-floor(startTTimes_.at(index)/gridResolution_); + const double endBinFraction = (endTTimes_.at(index)/gridResolution_)-floor(endTTimes_.at(index)/gridResolution_); + + // only do timing modification if contributions would land in same bin + if (startBin == endBin) { + + // if startE arrives before endE + if (deltaT_ >= 0) { + if ((startBinFraction >= 0.5) && (endBinFraction >= 0.5)) // both points left of bin center + { + startTTimes_.at(index) = startTTimes_.at(index) - gridResolution_; // shift EV1_ to previous gridpoint + } + else if ((startBinFraction < 0.5) && (endBinFraction < 0.5)) // both points right of bin center + { + endTTimes_.at(index) = endTTimes_.at(index) + gridResolution_; // shift EV2_ to next gridpoint + } + else // points on both sides of bin center + { + const double leftDist = 1.0-startBinFraction; + const double rightDist = endBinFraction; + // check if asymmetry to right or left + if (rightDist >= leftDist) + { + endTTimes_.at(index) = endTTimes_.at(index) + gridResolution_; // shift EV2_ to next gridpoint + } + else + { + startTTimes_.at(index) = startTTimes_.at(index) - gridResolution_; // shift EV1_ to previous gridpoint + } + } + } + else // if endE arrives before startE + { + if ((startBinFraction >= 0.5) && (endBinFraction >= 0.5)) // both points left of bin center + { + endTTimes_.at(index) = endTTimes_.at(index) - gridResolution_; // shift EV2_ to previous gridpoint + } + else if ((startBinFraction < 0.5) && (endBinFraction < 0.5)) // both points right of bin center + { + startTTimes_.at(index) = startTTimes_.at(index) + gridResolution_; // shift EV1_ to next gridpoint + } + else // points on both sides of bin center + { + const double leftDist = 1.0-endBinFraction; + const double rightDist = startBinFraction; + // check if asymmetry to right or left + if (rightDist >= leftDist) + { + startTTimes_.at(index) = startTTimes_.at(index) + gridResolution_; // shift EV1_ to next gridpoint + } + else + { + endTTimes_.at(index) = endTTimes_.at(index) - gridResolution_; // shift EV2_ to previous gridpoint + } + } + } // End of else statement + } // End of if for startbin == endbin + } // End of if deltaT < gridresolution + } // End of checking for very small doppler factors + + // perform ZHS-like calculation close to Cherenkov angle + if (fabs(preDoppler__) <= approxThreshold_ || fabs(postDoppler.at(index)) <= approxThreshold_) { + + // get global simulation time for the middle point of that track. (This is my best guess for now) + auto midTime_{particle.getTime() - (track.getDuration() / 2)}; + + // get "mid" position of the track (that may not work properly) + auto midPoint_{track.getPosition(0.5)}; + + // get the Path (path3) from the middle "endpoint" to the antenna. + // This is a SignalPathCollection + auto paths3{this->propagator_.propagate(midPoint_, antenna.getLocation())}; + +// std::size_t j_index {0}; // this will be useful for multiple paths (aka curved propagators) + // now loop over the paths for endpoint that we got above + for (auto const& path : paths3) { + +// EVstart_.erase(EVstart_.begin() + index + j_index); // this should work for curved + curved propagators +// EVend_.erase(EVend_.begin() + index + j_index); // for now just use one index and not j_index since at the moment you are working with StraightPropagator + + auto const midPointReceiveTime_{path.total_time_ + midTime_}; + auto midDoppler_{1. - path.average_refractive_index_ * beta_ * path.emit_}; + + // change the values of the receive unit vectors of start and end + ReceiveVectorsStart_.at(index) = path.receive_; + ReceiveVectorsEnd_.at(index) = path.receive_; + + // CoREAS calculation -> get ElectricFieldVector3 for "midPoint" + ElectricFieldVector EVmid_ = (charge_ / constants::c) * + path.receive_.cross(path.receive_.cross(beta_)) / + (path.R_distance_ * midDoppler_); + +// EVstart_.insert(EVstart_.begin() + index + j_index, EVmid_); // this should work for curved + curved propagators +// EVend_.insert(EVend_.begin() + index + j_index, - EVmid_); // for now just use one index and not j_index since at the moment you are working with StraightPropagator + EVstart_.at(index) = EVmid_; + EVend_.at(index) = - EVmid_; + + auto deltaT_{midPoint_.getNorm() / (constants::c * beta_ * fabs(midDoppler_))}; + + if (startTTimes_.at(index) < endTTimes_.at(index)) // EVstart_ arrives earlier + { + startTTimes_.at(index) = midPointReceiveTime_ - 0.5 * deltaT_; + endTTimes_.at(index) = midPointReceiveTime_ + 0.5 * deltaT_; + } + else // EVend_ arrives earlier + { + startTTimes_.at(index) = midPointReceiveTime_ + 0.5 * deltaT_; + endTTimes_.at(index) = midPointReceiveTime_ - 0.5 * deltaT_; + } + + const long double gridResolution_{antenna.duration_}; + deltaT_ = endTTimes_.at(index) - startTTimes_.at(index); + + // redistribute contributions over time scale defined by the observation time resolution + if (fabs(deltaT_) < gridResolution_) { + + EVstart_.at(index) = EVstart_.at(index) * fabs(deltaT_ / gridResolution_); + EVend_.at(index) = EVend_.at(index) * fabs(deltaT_ / gridResolution_); + + const long startBin = static_cast<long>(floor(startTTimes_.at(index)/gridResolution_+0.5l)); + const long endBin = static_cast<long>(floor(endTTimes_.at(index)/gridResolution_+0.5l)); + const double startBinFraction = (startTTimes_.at(index)/gridResolution_)-floor(startTTimes_.at(index)/gridResolution_); + const double endBinFraction = (endTTimes_.at(index)/gridResolution_)-floor(endTTimes_.at(index)/gridResolution_); + + // only do timing modification if contributions would land in same bin + if (startBin == endBin) { + + // if startE arrives before endE + if (deltaT_ >= 0) { + if ((startBinFraction >= 0.5) && (endBinFraction >= 0.5)) // both points left of bin center + { + startTTimes_.at(index) = startTTimes_.at(index) - gridResolution_; // shift EV1_ to previous gridpoint + } + else if ((startBinFraction < 0.5) && (endBinFraction < 0.5)) // both points right of bin center + { + endTTimes_.at(index) = endTTimes_.at(index) + gridResolution_; // shift EV2_ to next gridpoint + } + else // points on both sides of bin center + { + const double leftDist = 1.0-startBinFraction; + const double rightDist = endBinFraction; + // check if asymmetry to right or left + if (rightDist >= leftDist) + { + endTTimes_.at(index) = endTTimes_.at(index) + gridResolution_; // shift EV2_ to next gridpoint + } + else + { + startTTimes_.at(index) = startTTimes_.at(index) - gridResolution_; // shift EV1_ to previous gridpoint + } + } + } + else // if endE arrives before startE + { + if ((startBinFraction >= 0.5) && (endBinFraction >= 0.5)) // both points left of bin center + { + endTTimes_.at(index) = endTTimes_.at(index) - gridResolution_; // shift EV2_ to previous gridpoint + } + else if ((startBinFraction < 0.5) && (endBinFraction < 0.5)) // both points right of bin center + { + startTTimes_.at(index) = startTTimes_.at(index) + gridResolution_; // shift EV1_ to next gridpoint + } + else // points on both sides of bin center + { + const double leftDist = 1.0-endBinFraction; + const double rightDist = startBinFraction; + // check if asymmetry to right or left + if (rightDist >= leftDist) + { + startTTimes_.at(index) = startTTimes_.at(index) + gridResolution_; // shift EV1_ to next gridpoint + } + else + { + endTTimes_.at(index) = endTTimes_.at(index) - gridResolution_; // shift EV2_ to previous gridpoint + } + } + } // End of else statement + } // End of if for startbin == endbin + } // End of if deltaT < gridresolution + + } // End of looping over paths3 + + } // end of ZHS-like approximation + + // Feed start and end to the antenna + antenna.receive(startTTimes_.at(index), ReceiveVectorsStart_.at(index), EVstart_.at(index)); + antenna.receive(endTTimes_.at(index), ReceiveVectorsEnd_.at(index), EVend_.at(index)); + + // update index + index = index + 1; + + } // End of for loop for preDoppler factor (this includes checking for postDoppler factors) + + } // End of checking of vector sizes + + } // End of looping over the antennas. + + } // End of simulate method. + /** * Return the maximum step length for this particle and track. @@ -218,7 +392,7 @@ namespace corsika { // This is part of the ZHS / CoReas formalisms and can // be related from the magnetic field / acceleration, charge, // etc. of the particle. - return 1000000000000; + return 1000000000000_m; } }; // END: class RadioProcess diff --git a/corsika/modules/radio/RadioProcess.hpp b/corsika/modules/radio/RadioProcess.hpp index 93cfb6911..4fecfc1b6 100644 --- a/corsika/modules/radio/RadioProcess.hpp +++ b/corsika/modules/radio/RadioProcess.hpp @@ -111,13 +111,14 @@ namespace corsika { * we wait for the true output formatting to be ready. **/ bool writeOutput() const { - + // this for loop still has some issues int i = 1; for (auto& antenna : detector_.getAntennas()) { auto [t,E] = antenna.getWaveform(); std::ofstream out_file ("antenna" + to_string(i) + "_output.csv"); - xt::dump_csv(out_file, t, E); + xt::dump_csv(out_file, t); + xt::dump_csv(out_file, E); out_file.close(); ++i; diff --git a/corsika/modules/radio/ZHS.hpp b/corsika/modules/radio/ZHS.hpp index 0aa1e0617..f9f25b25b 100644 --- a/corsika/modules/radio/ZHS.hpp +++ b/corsika/modules/radio/ZHS.hpp @@ -11,6 +11,7 @@ #include <corsika/modules/radio/propagators/StraightPropagator.hpp> #include <corsika/framework/geometry/QuantityVector.hpp> #include <corsika/framework/core/PhysicalUnits.hpp> +#include <corsika/modules/radio/propagators/SignalPath.hpp> #include <bits/stdc++.h> namespace corsika { @@ -72,24 +73,26 @@ namespace corsika { // get the Path from the track to the antenna // This is a SignalPathCollection - auto paths{this->propagator_.propagate(startPoint_, antenna.getLocation())}; - auto R_ {(startPoint_ - antenna.getLocation()).getNorm()}; + auto paths{this->propagator_.propagate(startPoint_, antenna.getLocation(), 1_nm)}; // now loop over the paths that we got above // Note: for the StraightPropagator, there will only be a single // path but other propagators may return more than one. for (auto const& path : paths) { + auto t1 = path.total_time_; + + QuantityVector<ElectricFieldType::dimension_type> v11{10_V / 1_m, 10_V / 1_m, 10_V / 1_m}; // calculate the ZHS formalism for this particle-antenna - ElectricFieldVector EV_ = ((- constants) * trackVelocity_.dot(path.emit)) * - ((midTime_ + path.total_time_ - (1 - path.average_refractivity_ * beta_ * - acos(track.getDirection(0).dot(path.emit_))) * startTime_) - - (midTime_ + path.total_time_ - (1 - path.average_refractivity_ * beta_ * - acos(track.getDirection(0).dot(path.emit_))) * endTime_)) - / (1 - path.average_refractivity_ * beta_ * acos(track.getDirection(0).dot(path.emit_))); - - // pass it to the antenna - antenna.receive(midTime_ + path.total_time_, path.receive_, EV_); +// ElectricFieldVector EV_ = ((- constants) * trackVelocity_.dot(path.emit)) * +// ((midTime_ + path.total_time_ - (1 - path.average_refractivity_ * beta_ * +// acos(track.getDirection(0).dot(path.emit_))) * startTime_) +// - (midTime_ + path.total_time_ - (1 - path.average_refractivity_ * beta_ * +// acos(track.getDirection(0).dot(path.emit_))) * endTime_)) +// / (1 - path.average_refractivity_ * beta_ * acos(track.getDirection(0).dot(path.emit_))); +// +// // pass it to the antenna +// antenna.receive(midTime_ + path.total_time_, path.receive_, EV_); } // END: loop over paths diff --git a/corsika/modules/radio/antennas/Antenna.hpp b/corsika/modules/radio/antennas/Antenna.hpp index 9aaabc365..41ebf33b0 100644 --- a/corsika/modules/radio/antennas/Antenna.hpp +++ b/corsika/modules/radio/antennas/Antenna.hpp @@ -34,7 +34,7 @@ namespace corsika { // QuantityVector<MagneticFieldType::dimension_type>; // a dimensionless vector used for the incident direction - using Vector = QuantityVector<dimensionless_d>; +// using Vector = QuantityVector<dimensionless_d>; /** * \brief Construct a base antenna instance. diff --git a/corsika/modules/radio/antennas/TimeDomainAntenna.hpp b/corsika/modules/radio/antennas/TimeDomainAntenna.hpp index 1462b23e1..4d095f87d 100644 --- a/corsika/modules/radio/antennas/TimeDomainAntenna.hpp +++ b/corsika/modules/radio/antennas/TimeDomainAntenna.hpp @@ -18,26 +18,25 @@ namespace corsika { /** * An implementation of a time-domain antenna that has a customized - * start time, sampling period, and waveform duration. + * start time, sampling rate, and waveform duration. * */ - class TimeDomainAntenna final : public Antenna<TimeDomainAntenna> { + class TimeDomainAntenna : public Antenna<TimeDomainAntenna> { TimeType const start_time_; ///< The start time of this waveform. TimeType const duration_; ///< The duration of this waveform. InverseTimeType const sample_rate_; ///< The sampling rate of this antenna. int num_bins_; ///< The number of bins used. - xt::xtensor<double,3> waveformE_; ///< The waveform stored by this antenna. - std::pair<xt::xtensor<double, 1>, - xt::xtensor<double,3>> waveform_; ///< useful for .getWaveform() + xt::xtensor<double,2> waveformE_; ///< The waveform stored by this antenna. + std::pair<xt::xtensor<double, 2>, + xt::xtensor<double,2>> waveform_; ///< useful for .getWaveform() protected: // expose the CRTP interfaces constructor public: // import the methods from the antenna - using Antenna<TimeDomainAntenna>::Antenna; - using Antenna<TimeDomainAntenna>::receive; + using Antenna<TimeDomainAntenna>::getName; using Antenna<TimeDomainAntenna>::getLocation; @@ -62,7 +61,7 @@ namespace corsika { , duration_(duration) , sample_rate_(sample_rate) , num_bins_ (static_cast<int>(duration * sample_rate)) - , waveformE_ (xt::zeros<double>({3, num_bins_})) + , waveformE_ (xt::zeros<double>({num_bins_, 3})) {}; /** @@ -76,20 +75,20 @@ namespace corsika { * @param field The incident electric field vector. * */ - void receive(TimeType const time, Vector const& receive_vector, + void receive(TimeType const time, Vector<dimensionless_d> const& receive_vector, ElectricFieldVector const& efield) { - if (time < start_time_ || time > start_time_ + duration_) { + if (time < start_time_ || time >= start_time_ + duration_) { return; } else { // figure out the correct timebin to store the E-field value. auto timebin_ {static_cast<std::size_t>((time - start_time_) * sample_rate_)}; // store the x,y,z electric field components. - waveformE_.at(0, timebin_) += (efield.getX().magnitude()); - waveformE_.at(1, timebin_) += (efield.getY().magnitude()); - waveformE_.at(2, timebin_) += (efield.getZ().magnitude()); - + waveformE_.at(timebin_, 0) += efield.getX().magnitude(); + waveformE_.at(timebin_, 1) += efield.getY().magnitude(); + waveformE_.at(timebin_, 2) += efield.getZ().magnitude(); + //TODO: Check how they are stored in memory, row-wise or column-wise? } } @@ -100,17 +99,17 @@ namespace corsika { * * @returns A pair of the sample times, and the field */ - std::pair<xt::xtensor<double, 1>, xt::xtensor<double,3>> getWaveform() const { + std::pair<xt::xtensor<double, 2>, xt::xtensor<double,2>> getWaveform() const { + // TODO: divide by Δt for CoREAS ONLY! // create a 1-D xtensor to store time values so we can print them later. - xt::xtensor<double, 1> times_ {xt::zeros<double>({num_bins_, 1})}; + xt::xtensor<double, 2> times_ (xt::zeros<double>({num_bins_, 1})); - for (auto i = 0; i <= num_bins_; i++) { - times_.at(i) = static_cast<double>(start_time_ / 1_ns) + - static_cast<double>(i * sample_rate_ * 1_ns); + for (int i = 0; i < num_bins_; i++) { + times_.at(i,0) = static_cast<double>(start_time_ / 1_s + i * sample_rate_ * 1_s); } - return waveform_; + return std::make_pair(times_, waveformE_); }; /** @@ -118,6 +117,7 @@ namespace corsika { */ void reset() { waveformE_ = xt::zeros_like(waveformE_); +// times_ = xt::zeros_like(times_); }; }; // END: class Antenna final diff --git a/corsika/modules/radio/detectors/RadioDetector.hpp b/corsika/modules/radio/detectors/RadioDetector.hpp index 1d27a8ddd..804b8ccb2 100644 --- a/corsika/modules/radio/detectors/RadioDetector.hpp +++ b/corsika/modules/radio/detectors/RadioDetector.hpp @@ -37,7 +37,7 @@ namespace corsika { * * @returns An iterable mutable reference to the antennas. */ - std::vector<TAntennaImpl> const& getAntennas() { return antennas_; } + std::vector<TAntennaImpl> const& getAntennas() { return antennas_; } // maybe the const& here is an issue /** * Reset all the antenna waveforms. diff --git a/tests/modules/testRadio.cpp b/tests/modules/testRadio.cpp index 50f033e3f..afba63cba 100644 --- a/tests/modules/testRadio.cpp +++ b/tests/modules/testRadio.cpp @@ -8,12 +8,22 @@ #include <catch2/catch.hpp> #include <corsika/modules/radio/ZHS.hpp> +//#include <corsika/modules/radio/CoREAS.hpp> #include <corsika/modules/radio/antennas/TimeDomainAntenna.hpp> -#include <corsika/modules/radio/detectors/TimeDomainDetector.hpp> +#include <corsika/modules/radio/detectors/RadioDetector.hpp> #include <corsika/modules/radio/propagators/StraightPropagator.hpp> #include <corsika/modules/radio/propagators/SignalPath.hpp> #include <corsika/modules/radio/propagators/RadioPropagator.hpp> +#include <vector> +#include <xtensor/xtensor.hpp> +#include <xtensor/xbuilder.hpp> +#include <xtensor/xio.hpp> +#include <xtensor/xcsv.hpp> +#include <istream> +#include <fstream> +#include <iostream> + #include <corsika/media/Environment.hpp> #include <corsika/media/HomogeneousMedium.hpp> @@ -39,45 +49,158 @@ double constexpr absMargin = 1.0e-7; TEST_CASE("Radio", "[processes]") { -// logging::set_level(logging::level::info); -// corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v"); + logging::set_level(logging::level::info); + corsika_logger->set_pattern("[%n:%^%-8l%$] custom pattern: %v"); // check that I can create and reset a TimeDomain process - SECTION("TimeDomainDetector") { - - // construct a time domain detector - TimeDomainDetector<TimeDomainAntenna> detector; - - // // and construct some time domain radio process - // TimeDomain<ZHS<>, TimeDomainDetector<TimeDomainAntenna>> process(detector); - // TimeDomain<ZHS<CPU>, TimeDomainDetector<TimeDomainAntenna>> process2(detector); - // TimeDomain<ZHS<CPU>, decltype(detector)> process3(detector); - } +// SECTION("TimeDomainDetector") { +// +// // construct a time domain detector +// AntennaCollection<TimeDomainAntenna> detector; +// +// // // and construct some time domain radio process +// // TimeDomain<ZHS<>, TimeDomainDetector<TimeDomainAntenna>> process(detector); +// // TimeDomain<ZHS<CPU>, TimeDomainDetector<TimeDomainAntenna>> process2(detector); +// // TimeDomain<ZHS<CPU>, decltype(detector)> process3(detector); +// } // check that I can create time domain antennas - SECTION("TimeDomainDetector") { + SECTION("TimeDomainAntenna") { // create an environment so we can get a coordinate system Environment<IMediumModel> env; // the antenna location - const auto point{Point(env.getCoordinateSystem(), 1_m, 2_m, 3_m)}; + const auto point1{Point(env.getCoordinateSystem(), 1_m, 2_m, 3_m)}; + const auto point2{Point(env.getCoordinateSystem(), 4_m, 5_m, 6_m)}; + + // create times for the antenna + const TimeType t1{10_s}; + const TimeType t2{10_s}; + const InverseTimeType t3{1/1_s}; // check that I can create an antenna at (1, 2, 3) - const auto ant{TimeDomainAntenna("antenna_name", point)}; + TimeDomainAntenna ant1("antenna_name", point1, t1, t2, t3); + TimeDomainAntenna ant2("antenna_name2", point2, 11_s, t2, t3); // assert that the antenna name is correct - REQUIRE(ant.getName() == "antenna_name"); + REQUIRE(ant1.getName() == "antenna_name"); // and check that the antenna is at the right location - REQUIRE((ant.getLocation() - point).getNorm() < 1e-12 * 1_m); + REQUIRE((ant1.getLocation() - point1).getNorm() < 1e-12 * 1_m); // construct a radio detector instance to store our antennas - TimeDomainDetector<TimeDomainAntenna> detector; + AntennaCollection<TimeDomainAntenna> detector; // add this antenna to the process - detector.addAntenna(ant); + detector.addAntenna(ant1); + detector.addAntenna(ant2); + + // create an environment with uniform refractive index of 1 + using UniRIndex = + UniformRefractiveIndex<HomogeneousMedium<IRefractiveIndexModel<IMediumModel>>>; + + using EnvType = Environment<IRefractiveIndexModel<IMediumModel>>; + EnvType env6; + + // get a coordinate system + const CoordinateSystemPtr rootCS6 = env6.getCoordinateSystem(); + + auto Medium6 = EnvType::createNode<Sphere>( + Point{rootCS6, 0_m, 0_m, 0_m}, 1_km * std::numeric_limits<double>::infinity()); + + auto const props6 = Medium6->setModelProperties<UniRIndex>( + 1, 1_kg / (1_m * 1_m * 1_m), + NuclearComposition( + std::vector<Code>{Code::Nitrogen}, + std::vector<float>{1.f})); + + env6.getUniverse()->addChild(std::move(Medium6)); + + + // get a unit vector + Vector<dimensionless_d> v1(rootCS6, {0, 0, 1}); + QuantityVector<ElectricFieldType::dimension_type> v11{10_V / 1_m, 10_V / 1_m, 10_V / 1_m}; + + Vector<dimensionless_d> v2(rootCS6, {0, 1, 0}); + QuantityVector<ElectricFieldType::dimension_type> v22{20_V / 1_m, 20_V / 1_m, 20_V / 1_m}; + + // use receive methods + ant1.receive(15_s, v1, v11); + ant2.receive(16_s, v2, v22); + + // use getWaveform() method + auto [t11, E1] = ant1.getWaveform(); + CHECK(E1(5,0) - 10 == 0); + + auto [t222, E2] = ant2.getWaveform(); + CHECK(E2(5,0) -20 == 0); + + + // the rest is just random ideas + ////////////////////////////////////////////////////////////////////////////////////// + +// for (auto& kostas : detector.getAntennas()) { +// std::cout << kostas.getName() << std::endl; +// kostas.receive(15_s, v1, v11); +// std::cout << kostas.waveformE_; +// auto [t, E] = kostas.getWaveform(); +// std::cout << E(5,0) << std::endl; +// kostas.receive(16_s, v2, v22); +// std::cout << E(5,0) << std::endl; +// } + + + + // auto t33{static_cast<int>(t1 / t2)}; +// std::cout << t33 << std::endl; +// xt::xtensor<double,2> waveformE_ = xt::zeros<double>({2, 3}); +// xt::xtensor<double,2> res = xt::view(waveformE_); +// std::cout << ant1.waveformE_ << std::endl; +// std::cout << " " << std::endl; +// std::cout << ant2.waveformE_ << std::endl; +// std::cout << " " << std::endl; +// std::cout << ant1.times_ << std::endl; +// std::cout << " " << std::endl; +// std::cout << ant2.times_ << std::endl; +// auto [t, E] = ant2.getWaveform(); +// + +// +// +// std::ofstream out_file("antenna_output.csv"); +// xt::dump_csv(out_file, t); +// xt::dump_csv(out_file, E); +// out_file.close(); + + +// for (auto& antenna : detector.getAntennas()) { +// auto [t, E] = antenna.getWaveform(); +// } + +// int i = 1; +// auto x = detector.getAntennas(); +// for (auto& antenna : x) { +// +// auto [t, E] = antenna.getWaveform(); +// std::ofstream out_file("antenna" + to_string(i) + "_output.csv"); +// std::cout << E(5,0) << std::endl; +// xt::dump_csv(out_file, t); +// xt::dump_csv(out_file, E); +// out_file.close(); +// ++i; +// } + + +// detector.writeOutput(); +// ant1.reset(); +// ant2.reset(); +// +// std::cout << ant1.waveformE_ << std::endl; +// std::cout << ant2.waveformE_ << std::endl; + + ////////////////////////////////////////////////////////////////////////////////////// } // check that I can create working Straight Propagators in different environments @@ -133,9 +256,10 @@ TEST_CASE("Radio", "[processes]") { for (auto const& path : paths_) { CHECK((path.total_time_ / 1_s) - ((34_m / (3 * constants::c)) / 1_s) == Approx(0).margin(absMargin)); - CHECK(path.average_refractivity_ == Approx(1)); + CHECK(path.average_refractive_index_ == Approx(1)); CHECK(path.emit_.getComponents() == v1.getComponents()); CHECK(path.receive_.getComponents() == v1.getComponents()); + CHECK(path.R_distance_ == 10_m); // CHECK(std::equal(P1.begin(), P1.end(), path.points_.begin(),[] // (Point a, Point b) { return (a - b).norm() / 1_m < 1e-5;})); //TODO:THINK ABOUT THE POINTS IN THE SIGNALPATH.H @@ -198,9 +322,10 @@ TEST_CASE("Radio", "[processes]") { for (auto const& path :paths1_) { CHECK( (path.total_time_ / 1_s) - ((34_m / (3 * constants::c)) / 1_s) == Approx(0).margin(absMargin) ); - CHECK( path.average_refractivity_ == Approx(1) ); + CHECK( path.average_refractive_index_ == Approx(1) ); CHECK( path.emit_.getComponents() == vv1.getComponents() ); CHECK( path.receive_.getComponents() == vv1.getComponents() ); + CHECK( path.R_distance_ == 10_m ); } CHECK( paths1_.size() == 1 ); @@ -249,9 +374,10 @@ TEST_CASE("Radio", "[processes]") { for (auto const& path :paths2_) { CHECK( (path.total_time_ / 1_s) - ((3.177511688_m / (3 * constants::c)) / 1_s) == Approx(0).margin(absMargin) ); - CHECK( path.average_refractivity_ == Approx(0.210275935) ); + CHECK( path.average_refractive_index_ == Approx(0.210275935) ); CHECK( path.emit_.getComponents() == vvv1.getComponents() ); CHECK( path.receive_.getComponents() == vvv1.getComponents() ); + CHECK( path.R_distance_ == 10_m ); } CHECK( paths2_.size() == 1 ); @@ -262,6 +388,115 @@ TEST_CASE("Radio", "[processes]") { // // // TODO: construct the environment for the propagator + ////////////////////////////////////////////////////////////////////////////////////// +// // useful information +// // create an environment so we can get a coordinate system +// environment::Environment<environment::IMediumModel> env; +// +// // the antenna location +// const auto point{geometry::Point(env.GetCoordinateSystem(), 1_m, 2_m, 3_m)}; +// +// // check that I can create an antenna at (1, 2, 3) +// const auto ant{TimeDomainAntenna("antenna_name", point)}; +// +// // assert that the antenna name is correct +// REQUIRE(ant.GetName() == "antenna_name"); +// +// // and check that the antenna is at the right location +// REQUIRE((ant.GetLocation() - point).norm() < 1e-12 * 1_m); +// +// // construct a radio detector instance to store our antennas +// TimeDomainDetector<TimeDomainAntenna> detector; +// +// // add this antenna to the process +// detector.AddAntenna(ant); +// +// // create a TimeDomain process +// auto zhs{TimeDomain<ZHS<CPU>, decltype(detector)>(detector)}; +// +// // get the antenna back from the process and check the properties +// REQUIRE(detector.GetAntennas().cbegin()->GetName() == "antenna_name"); +// +// // and check the location is the same +// REQUIRE((detector.GetAntennas().cbegin()->GetLocation() - point).norm() < +// 1e-12 * 1_m); +// +// // and ANOTHER antenna +// const auto ant2{TimeDomainAntenna("antenna_name", point)}; +// detector.AddAntenna(ant2); +// // and check that the number of antennas visible to the process has increased +// REQUIRE(zhs.GetDetector().GetAntennas().size() == 2); + +////////////////////////////////////////////////////////////////////////////////////////// + +// using VelocityVec = Vector<corsika::units::si::SpeedType::dimension_type>; +// +// // setup environment, geometry +// environment::Environment<environment::IMediumModel> env; +// +// // and get the coordinate systm +// geometry::CoordinateSystem const& cs{env.GetCoordinateSystem()}; +// +// // a test particle +// const auto pcode{particles::Code::Electron}; +// const auto mass{particles::Electron::GetMass()}; +// +// // create a new stack for each trial +// setup::Stack stack; +// +// // construct an energy +// const HEPEnergyType E0{1_TeV}; +// +// // compute the necessary momentumn +// const HEPMomentumType P0{sqrt(E0 * E0 - mass * mass)}; +// +// // and create the momentum vector +// const auto plab{corsika::stack::MomentumVector(cs, {0_GeV, 0_GeV, P0})}; +// +// // and create the location of the particle in this coordinate system +// const geometry::Point pos(cs, 5_m, 1_m, 8_m); +// +// // add the particle to the stack +// auto particle{stack.AddParticle(std::make_tuple(pcode, E0, plab, pos, 0_ns))}; +// +// // get a view into the stack +// corsika::stack::SecondaryView stackview(particle); +// +// // create a trajectory +// const auto& track{ +// Trajectory(Line(pos, VelocityVec(cs, 0_m / 1_s, 0_m / 1_s, 0.1_m / 1_ns)), 1_ns)}; +// +// // and create a view vector +// const auto& view{Vector(cs, 0_m, 0_m, 1_m)}; +// +// // construct a radio detector instance to store our antennas +// TimeDomainDetector<TimeDomainAntenna> detector; +// +// // the antenna location +// const auto point{geometry::Point(cs, 1_m, 2_m, 3_m)}; +// +// // check that I can create an antenna at (1, 2, 3) +// const auto ant{TimeDomainAntenna("antenna_name", point)}; +// +// // add this antenna to the process +// detector.AddAntenna(ant); +// +// // create a TimeDomain process +// auto zhs{TimeDomain<ZHS<CPU>, decltype(detector)>(detector)}; +// +// // get the projectile +// auto projectile{stackview.GetProjectile()}; +// +// // call ZHS over the track +// zhs.DoContinuous(projectile, track); +// zhs.Simulate(projectile, track); +// +// // try and call the ZHS implementation directly for a given view direction +// ZHS<CPU>::Emit(projectile, track, view); + +////////////////////////////////////////////////////////////////////////////////////////// + + // create an environment with uniform refractive index of 1 // using UniRIndex = // UniformRefractiveIndex<HomogeneousMedium<IRefractiveIndexModel<IMediumModel>>>; -- GitLab