10#include "CLHEP/GenericFunctions/CumulativeChiSquare.hh"
11#include "GaudiKernel/MsgStream.h"
26#include "TGraphErrors.h"
39 init(0.5 * CLHEP::mm, 1, 5,
true,
true,
true,
true, 100,
false,
false);
43 const unsigned int &ord,
const bool &
split,
const bool &full_matrix,
const bool &fix_min,
44 const bool &fix_max,
const int &max_it,
bool do_smoothing,
bool do_parabolic_extrapolation) :
46 init(rt_accuracy, func_type, ord,
split, full_matrix, fix_min, fix_max, max_it, do_smoothing, do_parabolic_extrapolation);
57 const bool &full_matrix,
const bool &fix_min,
const bool &fix_max,
const int &max_it,
bool do_smoothing,
58 bool do_parabolic_extrapolation) {
87 throw std::runtime_error(
88 Form(
"File: %s, Line: %d\nRtCalibrationAnalytic::init - Order of the correction polynomial must be >0!", __FILE__, __LINE__));
102 if (func_type < 1 || func_type > 3) {
104 throw std::runtime_error(
105 Form(
"File: %s, Line: %d\nRtCalibrationAnalytic::init - Illegal correction function type!", __FILE__, __LINE__));
108 case 1:
m_base_function = std::make_unique<LegendrePolynomial>();
break;
109 case 2:
m_base_function = std::make_unique<ChebyshevPolynomial>();
break;
112 throw std::runtime_error(
113 Form(
"File: %s, Line: %d\nRtCalibrationAnalytic::init - Order must be >2 for polygons! It is set to %i by the user.",
117 double bin_width = 2.0 /
static_cast<double>(
m_order - 1);
118 for (
unsigned int k = 0; k <
m_order; k++) {
x[k] = -1 + k * bin_width; }
147 double precision(0.001);
154 while (t_max - t_min > 0.1 && std::abs(
m_rt->radius(0.5 * (t_min + t_max)) -
r) > precision) {
155 if (
m_rt->radius(0.5 * (t_min + t_max)) >
r) {
156 t_max = 0.5 * (t_min + t_max);
158 t_min = 0.5 * (t_min + t_max);
162 return 0.5 * (t_min + t_max);
174 double y_min, y_max, z_min, z_max;
182 y_min = (segment->
mdtHOT()[0])->localPosition().y();
184 z_min = (segment->
mdtHOT()[0])->localPosition().z();
187 if ((segment->
mdtHOT()[k])->localPosition().y() < y_min) { y_min = (segment->
mdtHOT()[k])->localPosition().y(); }
188 if ((segment->
mdtHOT()[k])->localPosition().y() > y_max) { y_max = (segment->
mdtHOT()[k])->localPosition().y(); }
189 if ((segment->
mdtHOT()[k])->localPosition().z() < z_min) { z_min = (segment->
mdtHOT()[k])->localPosition().z(); }
190 if ((segment->
mdtHOT()[k])->localPosition().z() > z_max) { z_max = (segment->
mdtHOT()[k])->localPosition().z(); }
192 for (
unsigned int k = 0; k < segment->
mdtCloseHits(); k++) {
193 if ((segment->
mdtClose()[k])->localPosition().y() < y_min) { y_min = (segment->
mdtClose()[k])->localPosition().y(); }
194 if ((segment->
mdtClose()[k])->localPosition().y() > y_max) { y_max = (segment->
mdtClose()[k])->localPosition().y(); }
195 if ((segment->
mdtClose()[k])->localPosition().z() < z_min) { z_min = (segment->
mdtClose()[k])->localPosition().z(); }
196 if ((segment->
mdtClose()[k])->localPosition().z() > z_max) { z_max = (segment->
mdtClose()[k])->localPosition().z(); }
200 if (y_max - y_min > z_max - z_min) {
201 outfile <<
"nullptr " << y_min - 30.0 <<
" " << y_max + 30.0 <<
" " << 0.5 * (z_min + z_max) - 0.5 * (y_max - y_min) - 30.0 <<
" "
202 << 0.5 * (z_min + z_max) + 0.5 * (y_max - y_min) + 30.0 <<
"\n";
204 outfile <<
"nullptr " << 0.5 * (y_min + y_max) - 0.5 * (z_max - z_min) - 30.0 <<
" "
205 << 0.5 * (y_min + y_max) + 0.5 * (z_max - z_min) + 30.0 <<
" " << z_min - 30.0 <<
" " << z_max + 30.0 <<
"\n";
211 outfile <<
"SET PLCI 1\n"
212 <<
"ARC " << (segment->
mdtHOT()[k])->localPosition().y() <<
" " << (segment->
mdtHOT()[k])->localPosition().z() <<
" 15.0\n";
215 outfile <<
"SET PLCI 3\n"
216 <<
"ARC " << (segment->
mdtHOT()[k])->localPosition().y() <<
" " << (segment->
mdtHOT()[k])->localPosition().z() <<
" "
217 << (segment->
mdtHOT()[k])->driftRadius() <<
"\n";
221 for (
unsigned int k = 0; k < segment->
mdtCloseHits(); k++) {
223 outfile <<
"SET PLCI 1\n"
224 <<
"ARC " << (segment->
mdtClose()[k])->localPosition().y() <<
" " << (segment->
mdtClose()[k])->localPosition().z()
228 outfile <<
"SET PLCI 2\n"
229 <<
"ARC " << (segment->
mdtClose()[k])->localPosition().y() <<
" " << (segment->
mdtClose()[k])->localPosition().z() <<
" "
230 << (segment->
mdtClose()[k])->driftRadius() <<
"\n";
235 outfile <<
"SET PLCI 4\n"
236 <<
"LINE " << aux_track.
a_x2() * (z_min - 30.0) + aux_track.
b_x2() <<
" " << z_min - 30.0 <<
" "
237 << aux_track.
a_x2() * (z_max + 30.0) + aux_track.
b_x2() <<
" " << z_max + 30.0 <<
"\n";
312 m_tfile = std::make_unique<TFile>(file_name.c_str(),
"RECREATE");
314 m_cut_evolution = std::make_unique<TH1F>(
"m_cut_evolution",
"CUT EVOLUTION", 11, -0.5, 10.5);
316 m_nb_segment_hits = std::make_unique<TH1F>(
"m_nb_segment_hits",
"NUMBER OF HITS ON THE REFITTED SEGMENTS", 11, -0.5, 10.5);
318 m_CL = std::make_unique<TH1F>(
"m_CL",
"CONFIDENCE LEVELS OF THE REFITTED SEGMENTS", 100, 0.0, 1.0);
320 m_residuals = std::make_unique<TH2F>(
"m_residuals",
"RESIDUALS OF THE REFITTED SEGMENTS", 100, -0.5, 15.0, 300, -1.5, 1.5);
340 std::shared_ptr<const IRtRelation> tmp_rt;
341 std::shared_ptr<const IRtRelation> conv_rt;
350 log << MSG::WARNING <<
"analyseSegments() - analyse failed, segments:" <<
endmsg;
351 for (
unsigned int i = 0; i < seg.size(); i++) {
352 log << MSG::WARNING << i <<
" " << seg[i]->direction() <<
" " << seg[i]->position() <<
endmsg;
360 tmp_rt = rtOut->
rt();
362 std::vector<double> params;
363 params.push_back(tmp_rt->tLower());
364 params.push_back(0.01 * (tmp_rt->tUpper() - tmp_rt->tLower()));
365 for (
double t = tmp_rt->tLower(); t <= tmp_rt->tUpper(); t = t + params[1]) { params.push_back(tmp_rt->radius(t)); }
366 conv_rt = std::make_shared<RtRelationLookUp>(params);
372 m_output = std::make_shared<RtCalibrationOutput>(
383 int max_smoothing_iterations(
static_cast<int>(
m_max_it));
384 if (max_smoothing_iterations == 0) { max_smoothing_iterations = 1; }
387 double convergence_RMS(0.002);
396 while (it < max_smoothing_iterations && RMS > convergence_RMS) {
401 unsigned int counter(0);
404 for (
const auto & k : seg) {
405 if (k->mdtHitsOnTrack() < 3) {
continue; }
407 for (
unsigned int h = 0;
h < k->mdtHitsOnTrack();
h++) {
408 k->mdtHOT()[
h]->setDriftRadius(tmp_rt->radius(k->mdtHOT()[
h]->driftTime()),
409 k->mdtHOT()[
h]->sigmaDriftRadius());
410 if (k->mdtHOT()[
h]->sigmaDriftRadius() < 0.5 *
m_r_max) {
411 avres = avres + k->mdtHOT()[
h]->sigma2DriftRadius();
416 avres = avres /
static_cast<double>(k->mdtHitsOnTrack());
417 avres = std::sqrt(avres);
418 if (k->mdtHitsOnTrack() > 3) {
419 if (
smoothing.addResidualsFromSegment(*k,
true, 7.0 * avres)) { counter++; }
421 if (
smoothing.addResidualsFromSegment(*k,
false, 7.0 * avres)) { counter++; }
426 if (counter < 1000) {
428 log << MSG::WARNING <<
"analyseSegments() - no smoothing applied due to too small number of reconstructed segments" <<
endmsg;
437 double bin_width(0.01 * (smooth_rt.
tUpper() - smooth_rt.
tLower()));
438 for (
double t = smooth_rt.
tLower(); t <= smooth_rt.
tUpper(); t = t + bin_width) {
439 RMS = RMS + std::pow(smooth_rt.
radius(t) - tmp_rt->radius(t), 2);
441 RMS = std::sqrt(0.01 * RMS);
447 tmp_rt = std::make_shared<RtRelationLookUp>(smooth_rt);
454 m_output = std::make_shared<RtCalibrationOutput>(
482 if (std::abs(seg.
direction().y()) > 100)
return true;
491 double chi2_scale_factor;
497 unsigned int nb_hits_in_ml[2];
499 std::vector<double> d_track;
500 std::vector<double> residual_value;
501 std::vector<MTStraightLine> w;
507 std::vector<double> zeta;
519 nb_hits_in_ml[0] = 0;
520 nb_hits_in_ml[1] = 0;
523 aux_res = seg.
mdtHOT()[k]->sigmaDriftRadius();
524 if (
m_rt->radius(seg.
mdtHOT()[k]->driftTime()) < 0.5) {
525 if (aux_res < 0.5 - m_rt->radius(seg.
mdtHOT()[k]->driftTime())) { aux_res = 0.5 -
m_rt->radius(seg.
mdtHOT()[k]->driftTime()); }
529 seg.
mdtHOT()[k]->setDriftRadius(
m_rt->radius(seg.
mdtHOT()[k]->driftTime()), aux_res);
536 av_res = av_res + std::pow(seg.
mdtHOT()[k]->sigmaDriftRadius(), 2);
538 av_res = av_res + 0.01;
542 (hit_selection[0])[k] = 0;
553 (hit_selection[0])[k] = 1;
554 (hit_selection[1])[k] = 1;
558 nb_hits_in_ml[0] = nb_hits_in_ml[0] + (1 - (hit_selection[0])[k]);
559 nb_hits_in_ml[1] = nb_hits_in_ml[1] + (1 - (hit_selection[1])[k]);
567 av_res = std::sqrt(av_res /
static_cast<double>(seg.
mdtHitsOnTrack()));
583 if (nb_hits_in_ml[k] < 3) {
continue; }
588 if (!
m_tracker.fit(seg, hit_selection[k], track)) {
continue; }
590 if (std::isnan(track.a_x1()) || std::isnan(track.a_x2()) || std::isnan(track.b_x1()) || std::isnan(track.b_x2())) {
continue; }
593 if (track.chi2PerDegreesOfFreedom() > 5 * chi2_scale_factor) {
continue; }
597 if (track.numberOfTrackHits() < 3) {
continue; }
600 if (std::abs(track.a_x2()) > 8e8)
continue;
614 d_track = std::vector<double>(track.numberOfTrackHits());
615 residual_value = std::vector<double>(track.numberOfTrackHits());
616 w = std::vector<MTStraightLine>(track.numberOfTrackHits());
617 G = CLHEP::HepVector(track.numberOfTrackHits());
618 zeta = std::vector<double>(track.numberOfTrackHits());
621 for (
unsigned int l = 0; l <
m_order; l++) {
622 m_U[l] = CLHEP::HepVector(track.numberOfTrackHits());
623 for (
unsigned int m = 0; m < track.numberOfTrackHits(); m++) {
624 x = (track.trackHits()[m]->driftRadius() - 0.5 *
m_r_max) / (0.5 *
m_r_max);
630 for (
unsigned int l = 0; l < track.numberOfTrackHits(); l++) {
634 d_track[l] = track.signDistFrom(w[l]);
635 residual_value[l] = track.trackHits()[l]->driftRadius() - std::abs(d_track[l]);
640 for (
unsigned int l = 0; l < track.numberOfTrackHits(); l++) {
642 for (
unsigned int m = 0; m < track.numberOfTrackHits(); m++) {
643 zeta[m] = std::sqrt(1.0 + std::pow(track.a_x2(), 2)) * (w[m].positionVector()).z() - track.a_x2() * d_track[m];
645 for (
unsigned int m = 0; m < track.numberOfTrackHits(); m++) {
646 double sum1(0.0), sum2(0.0), sum3(0.0), sum4(0.0);
647 for (
unsigned int ll = 0; ll < track.numberOfTrackHits(); ll++) {
648 sum1 = sum1 + (zeta[l] - zeta[ll]) * (zeta[ll] - zeta[m]) /
649 (track.trackHits()[m]->sigma2DriftRadius() * track.trackHits()[ll]->sigma2DriftRadius());
650 sum2 = sum2 + zeta[ll] / track.trackHits()[ll]->sigma2DriftRadius();
651 sum3 = sum3 + 1.0 / track.trackHits()[ll]->sigma2DriftRadius();
652 sum4 = sum4 + std::pow(zeta[ll] / track.trackHits()[ll]->sigmaDriftRadius(), 2);
654 if (d_track[m] * d_track[l] >= 0.0) {
655 G[m] = (l == m) -
m_full_matrix * sum1 / (sum2 * sum2 - sum3 * sum4);
657 G[m] = (l == m) +
m_full_matrix * sum1 / (sum2 * sum2 - sum3 * sum4);
660 CLHEP::HepSymMatrix A_tmp(
m_A);
662 for (
unsigned int p = 0; p <
m_order; p++) {
663 for (
unsigned int pp = p; pp <
m_order; pp++) {
664 A_tmp[p][pp] =
m_A[p][pp] + (
dot(
G,
m_U[p]) *
dot(
G,
m_U[pp])) / track.trackHits()[l]->sigma2DriftRadius();
665 if (std::isnan(A_tmp[p][pp]))
return true;
669 CLHEP::HepVector b_tmp(
m_b);
670 for (
unsigned int p = 0; p <
m_order; p++) {
671 b_tmp[p] =
m_b[p] - residual_value[l] *
dot(
G,
m_U[p]) / track.trackHits()[l]->sigma2DriftRadius();
672 if (std::isnan(b_tmp[p]))
return true;
697 if (input ==
nullptr) {
698 throw std::runtime_error(
699 Form(
"File: %s, Line: %d\nRtCalibrationAnalytic::setInput - Calibration input class not supported.", __FILE__, __LINE__));
722 if (!rt_Chebyshev && !rt_LookUp) {
723 throw std::runtime_error(
724 Form(
"File: %s, Line: %d\nRtCalibrationAnalytic::setInput - r-t class not supported.", __FILE__, __LINE__));
729 m_t_length = rt_Chebyshev->tUpper() - rt_Chebyshev->tLower();
730 m_t_mean = 0.5 * (rt_Chebyshev->tLower() + rt_Chebyshev->tUpper());
735 m_t_length = rt_LookUp->par(1) * (rt_LookUp->nPar() - 2) - rt_LookUp->par(0);
736 m_t_mean = 0.5 * (rt_LookUp->par(1) * (rt_LookUp->nPar() - 2) + rt_LookUp->par(0));
747 unsigned int nb_points(30);
752 std::vector<double> rt_param(
m_rt->nPar());
761 log << MSG::WARNING <<
"analyse() - Could not solve the autocalibration equation!" <<
endmsg;
772 nb_points = std::max(rt_Chebyshev->nDoF(), 30u);
775 step =
m_r_max /
static_cast<double>(nb_points);
778 std::vector<SamplePoint> x_r(nb_points + 1);
783 for (
unsigned int k = 0; k < nb_points + 1; k++) {
785 x_r[k].set_x2(rt_Chebyshev->radius(x_r[k].x1()));
786 x_r[k].set_x1(rt_Chebyshev->getReducedTime(x_r[k].x1()));
787 x_r[k].set_error(1.0);
790 for (
unsigned int l = 0; l <
m_order; l++) {
797 if (((k == 0 || x_r[k].x2() < 0.5) &&
m_fix_min) || ((k == nb_points || x_r[k].x2() > 14.1) &&
m_fix_max)) {
799 x_r[k].set_error(0.01);
801 x_r[k].set_x2(x_r[k].x2() + r_corr);
806 for (
unsigned int k = 0; k < nb_points; k++) {
807 if (x_r[k].x2() > x_r[k + 1].x2()) { x_r[k + 1].set_x2(x_r[k].x2()); }
812 std::unique_ptr<TGraphErrors> gre = std::make_unique<TGraphErrors>(x_r.size());
813 for (
unsigned int i = 0; i < 1; i++) {
814 gre->SetPoint(i, x_r[i].x1(), x_r[i].x2());
815 gre->SetPointError(i, 0, x_r[i].
error());
817 std::ostringstream str_str;
819 gre->Write(str_str.str().c_str());
823 fitter.fit_parameters(x_r, 1, nb_points + 1, chebyshev);
824 rt_param[0] = rt_Chebyshev->tLower();
825 rt_param[1] = rt_Chebyshev->tUpper();
826 for (
unsigned int k = 0; k < rt_Chebyshev->nDoF(); k++) { rt_param[k + 2] = fitter.coefficients()[k]; }
828 m_rt_new = std::make_unique<RtChebyshev>(rt_param);
833 rt_param = rt_LookUp->parameters();
834 unsigned int min_k(2), max_k(rt_param.size());
836 if (
m_fix_max) { max_k = rt_param.size() - 1; }
838 std::unique_ptr<TGraph>
gr;
841 for (
unsigned int k = min_k; k < max_k; k++) {
845 rt_param[k] = rt_param[k] + r_corr;
848 if (rt_param[k] < rt_param[k - 1]) { rt_param[k] = rt_param[k - 1]; }
852 m_rt_new = std::make_unique<RtRelationLookUp>(rt_param);
854 std::ostringstream str_str;
856 gr->Write(str_str.str().c_str());
867 double r_corr_max = 0.0;
869 for (
unsigned int k = 0; k < 100; k++) {
872 if (std::abs(r_corr) > r_corr_max) { r_corr_max = std::abs(r_corr); }
900 m_output = std::make_shared<RtCalibrationOutput>(
913 std::shared_ptr<RtRelationLookUp> rt_low, rt_high;
916 std::vector<SamplePoint> add_fit_point;
923 add_fit_point.clear();
930 rt_high = std::make_shared<RtRelationLookUp>(
939 add_fit_point.clear();
953 if (
min &&
max) {
return rt_low; }
954 if (
min) {
return rt_low; }
This class performs a fit of a linear combination of base functions to a set of sample points.
This class class provides a Chebyshev polynomial of order k.
Interface to pass calibration output during calibration.
IMdtCalibration(const std::string &name)
constructor, string used to identify the instance
virtual std::string name() const
returns name (region) of instance
std::shared_ptr< IMdtCalibrationOutput > MdtCalibOutputPtr
std::vector< std::shared_ptr< MuonCalibSegment > > MuonSegVec
std::vector< unsigned int > HitSelection
generic interface for a rt-relation
virtual double radius(double t) const =0
returns drift radius for a given time
virtual double tUpper() const =0
Returns the upper time covered by the r-t.
double b_x2() const
get the intercept of the straight line in the x2-x3 plane
double a_x2() const
get the slope of the straight line in the x2-x3 plane
A MuonCalibSegment is a reconstructed three dimensional track segment in the MuonSpectrometer.
unsigned int mdtHitsOnTrack() const
retrieve the number of MdtCalibHitBase s assigned to this segment
const MdtHitVec & mdtClose() const
retrieve the full set of nearby mdt hits of this segment.
const Amg::Vector3D & direction() const
retrieve local direction of segment (on station level) retrieve the transformation from local chamber...
unsigned int mdtCloseHits() const
retrieve the number of nearby mdt hits.
const MdtHitVec & mdtHOT() const
retrieve the full set of MdtCalibHitBase s assigned to this segment
const Amg::Vector3D & position() const
retrieve local position of segment (on station level)
std::unique_ptr< TH1F > m_cut_evolution
std::vector< CLHEP::HepVector > m_U
MdtCalibOutputPtr getResults() const
returns the final r-t relationship
std::unique_ptr< TFile > m_tfile
void doNotForceMonotony()
do not force r(t) to be monotonically increasing
void setEstimateRtAccuracy(const double acc)
set the estimated r-t accuracy =acc
double t_from_r(const double r)
bool m_control_histograms
int numberOfSegmentsUsed() const
get the number of segments which are used in the autocalibration
bool splitIntoMultilayers() const
returns true, if segments are internally restricted to single multilayers; returns false,...
MdtCalibOutputPtr analyseSegments(const MuonSegVec &seg)
perform the full autocalibration including iterations (required since MdtCalibInterfaces-00-01-06)
std::shared_ptr< IRtRelation > m_rt_new
std::unique_ptr< TH1F > m_CL
bool m_do_parabolic_extrapolation
double reliability() const
get the reliability of the r-t: 0: no convergence yet 1: convergence, r-t is reliable 2: convergence,...
void display_segment(MuonCalibSegment *segment, std::ofstream &outfile)
int numberOfSegments() const
get the number of segments which were passed to the algorithm
void doSmoothing()
requires that the r-t relationship will be smoothened using the conventional autocalibration after co...
void noParabolicExtrapolation()
no parabolic extrapolation is done
double m_rt_accuracy_previous
void switch_off_control_histograms()
the algorithm does not produce controll histograms (this is the default)
double estimatedRtAccuracy() const
get the estimated r-t quality (CLHEP::mm), the accuracy of the input r-t is computed at the end of th...
std::shared_ptr< const IRtRelation > m_rt
std::unique_ptr< BaseFunction > m_base_function
void doParabolicExtrapolation()
requires that parabolic extrapolation will be used for small and large radii
void init(const double rt_accuracy, const unsigned int &func_type, const unsigned int &ord, const bool &split, const bool &full_matrix, const bool &fix_min, const bool &fix_max, const int &max_it, bool do_smoothing, bool do_parabolic_extrapolation)
bool converged() const
returns true, if the autocalibration has converged
QuasianalyticLineReconstruction m_tracker
bool fullMatrix() const
returns true, if the full matrix relating the errors in the r-t relationship to the residuals should ...
void switch_on_control_histograms(const std::string &file_name)
this methods requests control histograms from the algorithms; the algorithm will write them to ROOT f...
RtCalibrationAnalytic(const std::string &name)
Default constructor: r-t accuracy is set to 0.5 mm.
std::shared_ptr< RtCalibrationOutput > m_output
std::array< bool, 2 > m_multilayer
int iteration() const
get the number of the current iteration
void setInput(const IMdtCalibrationOutput *rt_input)
set the r-t relationship, the internal autocalibration objects are reset
std::unique_ptr< TH2F > m_residuals
void noSmoothing()
do not smoothen the r-t relationship after convergence
bool analyse()
perform the autocalibration with the segments acquired so far
std::shared_ptr< RtRelationLookUp > performParabolicExtrapolation(const bool &min, const bool &max, const IRtRelation &in_rt)
bool handleSegment(MuonCalibSegment &seg)
analyse the segment "seg" (this method was required before MdtCalibInterfaces-00-01-06)
bool smoothing() const
returns true, if the r-t relationship will be smoothened using the conventional autocalibration after...
void forceMonotony()
force r(t) to be monotonically increasing
std::unique_ptr< TH1F > m_nb_segment_hits
Class for communication between event loop and rt calibration algorithm contains only a rt relation f...
std::shared_ptr< const IRtRelation > rt() const
access to private attributes
This class contains the implementation of an r(t) relationship parametrized by a linear combination o...
Equidistant look up table for rt-relations with the time as key.
virtual double radius(double t) const override final
returns drift radius for a given time
virtual double tLower() const override final
return rt range
virtual double tUpper() const override final
Returns the upper time covered by the r-t.
This class provides a sample point for the BaseFunctionFitter.
singleton-like access to IMessageSvc via open function and helper
std::vector< std::string > split(const std::string &s, const std::string &t=":")
Eigen::Matrix< double, 3, 1 > Vector3D
IMessageSvc * getMessageSvc(bool quiet=false)
CscCalcPed - algorithm that finds the Cathode Strip Chamber pedestals from an RDO.