File indexing completed on 2025-01-18 09:40:42
0001
0002
0003
0004
0005 #ifndef BOOST_MATH_TOOLS_ULP_PLOT_HPP
0006 #define BOOST_MATH_TOOLS_ULP_PLOT_HPP
0007 #include <algorithm>
0008 #include <iostream>
0009 #include <iomanip>
0010 #include <cassert>
0011 #include <vector>
0012 #include <utility>
0013 #include <fstream>
0014 #include <string>
0015 #include <list>
0016 #include <random>
0017 #include <limits>
0018 #include <stdexcept>
0019 #include <boost/math/tools/is_standalone.hpp>
0020 #include <boost/math/tools/condition_numbers.hpp>
0021
0022 #ifndef BOOST_MATH_STANDALONE
0023 #include <boost/random/uniform_real_distribution.hpp>
0024 #endif
0025
0026
0027
0028
0029
0030
0031 namespace boost::math::tools {
0032
0033 namespace detail {
0034 template<class F1, class F2, class CoarseReal, class PreciseReal>
0035 void write_gridlines(std::ostream& fs, int horizontal_lines, int vertical_lines,
0036 F1 x_scale, F2 y_scale, CoarseReal min_x, CoarseReal max_x, PreciseReal min_y, PreciseReal max_y,
0037 int graph_width, int graph_height, int margin_left, std::string const & font_color)
0038 {
0039
0040 for (int i = 1; i <= horizontal_lines; ++i) {
0041 PreciseReal y_cord_dataspace = min_y + ((max_y - min_y)*i)/horizontal_lines;
0042 auto y = y_scale(y_cord_dataspace);
0043 fs << "<line x1='0' y1='" << y << "' x2='" << graph_width
0044 << "' y2='" << y
0045 << "' stroke='gray' stroke-width='1' opacity='0.5' stroke-dasharray='4' />\n";
0046
0047 fs << "<text x='" << -margin_left/4 + 5 << "' y='" << y - 3
0048 << "' font-family='times' font-size='10' fill='" << font_color << "' transform='rotate(-90 "
0049 << -margin_left/4 + 8 << " " << y + 5 << ")'>"
0050 << std::setprecision(4) << y_cord_dataspace << "</text>\n";
0051 }
0052
0053 for (int i = 1; i <= vertical_lines; ++i) {
0054 CoarseReal x_cord_dataspace = min_x + ((max_x - min_x)*i)/vertical_lines;
0055 CoarseReal x = x_scale(x_cord_dataspace);
0056 fs << "<line x1='" << x << "' y1='0' x2='" << x
0057 << "' y2='" << graph_height
0058 << "' stroke='gray' stroke-width='1' opacity='0.5' stroke-dasharray='4' />\n";
0059
0060 fs << "<text x='" << x - 10 << "' y='" << graph_height + 10
0061 << "' font-family='times' font-size='10' fill='" << font_color << "'>"
0062 << std::setprecision(4) << x_cord_dataspace << "</text>\n";
0063 }
0064 }
0065 }
0066
0067 template<class F, typename PreciseReal, typename CoarseReal>
0068 class ulps_plot {
0069 public:
0070 ulps_plot(F hi_acc_impl, CoarseReal a, CoarseReal b,
0071 size_t samples = 1000, bool perturb_abscissas = false, int random_seed = -1);
0072
0073 ulps_plot& clip(PreciseReal clip);
0074
0075 ulps_plot& width(int width);
0076
0077 ulps_plot& envelope_color(std::string const & color);
0078
0079 ulps_plot& title(std::string const & title);
0080
0081 ulps_plot& background_color(std::string const & background_color);
0082
0083 ulps_plot& font_color(std::string const & font_color);
0084
0085 ulps_plot& crop_color(std::string const & color);
0086
0087 ulps_plot& nan_color(std::string const & color);
0088
0089 ulps_plot& ulp_envelope(bool write_ulp);
0090
0091 template<class G>
0092 ulps_plot& add_fn(G g, std::string const & color = "steelblue");
0093
0094 ulps_plot& horizontal_lines(int horizontal_lines);
0095
0096 ulps_plot& vertical_lines(int vertical_lines);
0097
0098 void write(std::string const & filename) const;
0099
0100 friend std::ostream& operator<<(std::ostream& fs, ulps_plot const & plot)
0101 {
0102 using std::abs;
0103 using std::floor;
0104 using std::isnan;
0105 if (plot.ulp_list_.size() == 0)
0106 {
0107 throw std::domain_error("No functions added for comparison.");
0108 }
0109 if (plot.width_ <= 1)
0110 {
0111 throw std::domain_error("Width = " + std::to_string(plot.width_) + ", which is too small.");
0112 }
0113
0114 PreciseReal worst_ulp_distance = 0;
0115 PreciseReal min_y = (std::numeric_limits<PreciseReal>::max)();
0116 PreciseReal max_y = std::numeric_limits<PreciseReal>::lowest();
0117 for (auto const & ulp_vec : plot.ulp_list_)
0118 {
0119 for (auto const & ulp : ulp_vec)
0120 {
0121 if (static_cast<PreciseReal>(abs(ulp)) > worst_ulp_distance)
0122 {
0123 worst_ulp_distance = static_cast<PreciseReal>(abs(ulp));
0124 }
0125 if (static_cast<PreciseReal>(ulp) < min_y)
0126 {
0127 min_y = static_cast<PreciseReal>(ulp);
0128 }
0129 if (static_cast<PreciseReal>(ulp) > max_y)
0130 {
0131 max_y = static_cast<PreciseReal>(ulp);
0132 }
0133 }
0134 }
0135
0136
0137
0138 if (max_y < 0.5) {
0139 max_y = 0.5;
0140 }
0141 if (min_y > -0.5) {
0142 min_y = -0.5;
0143 }
0144
0145 if (plot.clip_ > 0)
0146 {
0147 if (max_y > plot.clip_)
0148 {
0149 max_y = plot.clip_;
0150 }
0151 if (min_y < -plot.clip_)
0152 {
0153 min_y = -plot.clip_;
0154 }
0155 }
0156
0157 int height = static_cast<int>(floor(static_cast<double>(plot.width_)/1.61803));
0158 int margin_top = 40;
0159 int margin_left = 25;
0160 if (plot.title_.size() == 0)
0161 {
0162 margin_top = 10;
0163 margin_left = 15;
0164 }
0165 int margin_bottom = 20;
0166 int margin_right = 20;
0167 int graph_height = height - margin_bottom - margin_top;
0168 int graph_width = plot.width_ - margin_left - margin_right;
0169
0170
0171 auto x_scale = [&](CoarseReal x)->CoarseReal
0172 {
0173 return ((x-plot.a_)/(plot.b_ - plot.a_))*static_cast<CoarseReal>(graph_width);
0174 };
0175
0176 auto y_scale = [&](PreciseReal y)->PreciseReal
0177 {
0178 return ((max_y - y)/(max_y - min_y) )*static_cast<PreciseReal>(graph_height);
0179 };
0180
0181 fs << "<?xml version=\"1.0\" encoding='UTF-8' ?>\n"
0182 << "<svg xmlns='http://www.w3.org/2000/svg' width='"
0183 << plot.width_ << "' height='"
0184 << height << "'>\n"
0185 << "<style>\nsvg { background-color:" << plot.background_color_ << "; }\n"
0186 << "</style>\n";
0187 if (plot.title_.size() > 0)
0188 {
0189 fs << "<text x='" << floor(plot.width_/2)
0190 << "' y='" << floor(margin_top/2)
0191 << "' font-family='Palatino' font-size='25' fill='"
0192 << plot.font_color_ << "' alignment-baseline='middle' text-anchor='middle'>"
0193 << plot.title_
0194 << "</text>\n";
0195 }
0196
0197
0198 fs << "<g transform='translate(" << margin_left << ", " << margin_top << ")'>\n";
0199
0200 fs << "<line x1='0' y1='0' x2='0' y2='" << graph_height
0201 << "' stroke='gray' stroke-width='1'/>\n";
0202 PreciseReal x_axis_loc = y_scale(static_cast<PreciseReal>(0));
0203 fs << "<line x1='0' y1='" << x_axis_loc
0204 << "' x2='" << graph_width << "' y2='" << x_axis_loc
0205 << "' stroke='gray' stroke-width='1'/>\n";
0206
0207 if (worst_ulp_distance > 3)
0208 {
0209 detail::write_gridlines(fs, plot.horizontal_lines_, plot.vertical_lines_, x_scale, y_scale, plot.a_, plot.b_,
0210 min_y, max_y, graph_width, graph_height, margin_left, plot.font_color_);
0211 }
0212 else
0213 {
0214 std::vector<double> ys{-3.0, -2.5, -2.0, -1.5, -1.0, -0.5, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0};
0215 for (double & i : ys)
0216 {
0217 if (min_y <= i && i <= max_y)
0218 {
0219 PreciseReal y_cord_dataspace = i;
0220 PreciseReal y = y_scale(y_cord_dataspace);
0221 fs << "<line x1='0' y1='" << y << "' x2='" << graph_width
0222 << "' y2='" << y
0223 << "' stroke='gray' stroke-width='1' opacity='0.5' stroke-dasharray='4' />\n";
0224
0225 fs << "<text x='" << -margin_left/2 << "' y='" << y - 3
0226 << "' font-family='times' font-size='10' fill='" << plot.font_color_ << "' transform='rotate(-90 "
0227 << -margin_left/2 + 7 << " " << y << ")'>"
0228 << std::setprecision(4) << y_cord_dataspace << "</text>\n";
0229 }
0230 }
0231 for (int i = 1; i <= plot.vertical_lines_; ++i)
0232 {
0233 CoarseReal x_cord_dataspace = plot.a_ + ((plot.b_ - plot.a_)*i)/plot.vertical_lines_;
0234 CoarseReal x = x_scale(x_cord_dataspace);
0235 fs << "<line x1='" << x << "' y1='0' x2='" << x
0236 << "' y2='" << graph_height
0237 << "' stroke='gray' stroke-width='1' opacity='0.5' stroke-dasharray='4' />\n";
0238
0239 fs << "<text x='" << x - 10 << "' y='" << graph_height + 10
0240 << "' font-family='times' font-size='10' fill='" << plot.font_color_ << "'>"
0241 << std::setprecision(4) << x_cord_dataspace << "</text>\n";
0242 }
0243 }
0244
0245 int color_idx = 0;
0246 for (auto const & ulp : plot.ulp_list_)
0247 {
0248 std::string color = plot.colors_[color_idx++];
0249 for (size_t j = 0; j < ulp.size(); ++j)
0250 {
0251 if (isnan(ulp[j]))
0252 {
0253 if(plot.nan_color_ == "")
0254 continue;
0255 CoarseReal x = x_scale(plot.coarse_abscissas_[j]);
0256 PreciseReal y = y_scale(static_cast<PreciseReal>(plot.clip_));
0257 fs << "<circle cx='" << x << "' cy='" << y << "' r='1' fill='" << plot.nan_color_ << "'/>\n";
0258 y = y_scale(static_cast<PreciseReal>(-plot.clip_));
0259 fs << "<circle cx='" << x << "' cy='" << y << "' r='1' fill='" << plot.nan_color_ << "'/>\n";
0260 }
0261 if (plot.clip_ > 0 && static_cast<PreciseReal>(abs(ulp[j])) > plot.clip_)
0262 {
0263 if (plot.crop_color_ == "")
0264 continue;
0265 CoarseReal x = x_scale(plot.coarse_abscissas_[j]);
0266 PreciseReal y = y_scale(static_cast<PreciseReal>(ulp[j] < 0 ? -plot.clip_ : plot.clip_));
0267 fs << "<circle cx='" << x << "' cy='" << y << "' r='1' fill='" << plot.crop_color_ << "'/>\n";
0268 }
0269 else
0270 {
0271 CoarseReal x = x_scale(plot.coarse_abscissas_[j]);
0272 PreciseReal y = y_scale(static_cast<PreciseReal>(ulp[j]));
0273 fs << "<circle cx='" << x << "' cy='" << y << "' r='1' fill='" << color << "'/>\n";
0274 }
0275 }
0276 }
0277
0278 if (plot.ulp_envelope_)
0279 {
0280 std::string close_path = "' stroke='" + plot.envelope_color_ + "' stroke-width='1' fill='none'></path>\n";
0281 size_t jstart = 0;
0282 while (plot.cond_[jstart] > max_y)
0283 {
0284 ++jstart;
0285 if (jstart >= plot.cond_.size())
0286 {
0287 goto done;
0288 }
0289 }
0290
0291 size_t jmin = jstart;
0292 new_top_path:
0293 if (jmin >= plot.cond_.size())
0294 {
0295 goto start_bottom_paths;
0296 }
0297 fs << "<path d='M" << x_scale(plot.coarse_abscissas_[jmin]) << " " << y_scale(plot.cond_[jmin]);
0298
0299 for (size_t j = jmin + 1; j < plot.coarse_abscissas_.size(); ++j)
0300 {
0301 bool bad = isnan(plot.cond_[j]) || (plot.cond_[j] > max_y);
0302 if (bad)
0303 {
0304 ++j;
0305 while ( (j < plot.coarse_abscissas_.size() - 2) && bad)
0306 {
0307 bad = isnan(plot.cond_[j]) || (plot.cond_[j] > max_y);
0308 ++j;
0309 }
0310 jmin = j;
0311 fs << close_path;
0312 goto new_top_path;
0313 }
0314
0315 CoarseReal t = x_scale(plot.coarse_abscissas_[j]);
0316 PreciseReal y = y_scale(plot.cond_[j]);
0317 fs << " L" << t << " " << y;
0318 }
0319 fs << close_path;
0320 start_bottom_paths:
0321 jmin = jstart;
0322 new_bottom_path:
0323 if (jmin >= plot.cond_.size())
0324 {
0325 goto done;
0326 }
0327 fs << "<path d='M" << x_scale(plot.coarse_abscissas_[jmin]) << " " << y_scale(-plot.cond_[jmin]);
0328
0329 for (size_t j = jmin + 1; j < plot.coarse_abscissas_.size(); ++j)
0330 {
0331 bool bad = isnan(plot.cond_[j]) || (-plot.cond_[j] < min_y);
0332 if (bad)
0333 {
0334 ++j;
0335 while ( (j < plot.coarse_abscissas_.size() - 2) && bad)
0336 {
0337 bad = isnan(plot.cond_[j]) || (-plot.cond_[j] < min_y);
0338 ++j;
0339 }
0340 jmin = j;
0341 fs << close_path;
0342 goto new_bottom_path;
0343 }
0344 CoarseReal t = x_scale(plot.coarse_abscissas_[j]);
0345 PreciseReal y = y_scale(-plot.cond_[j]);
0346 fs << " L" << t << " " << y;
0347 }
0348 fs << close_path;
0349 }
0350 done:
0351 fs << "</g>\n"
0352 << "</svg>\n";
0353 return fs;
0354 }
0355
0356 private:
0357 std::vector<PreciseReal> precise_abscissas_;
0358 std::vector<CoarseReal> coarse_abscissas_;
0359 std::vector<PreciseReal> precise_ordinates_;
0360 std::vector<PreciseReal> cond_;
0361 std::list<std::vector<CoarseReal>> ulp_list_;
0362 std::vector<std::string> colors_;
0363 CoarseReal a_;
0364 CoarseReal b_;
0365 PreciseReal clip_;
0366 int width_;
0367 std::string envelope_color_;
0368 bool ulp_envelope_;
0369 int horizontal_lines_;
0370 int vertical_lines_;
0371 std::string title_;
0372 std::string background_color_;
0373 std::string font_color_;
0374 std::string crop_color_;
0375 std::string nan_color_;
0376 };
0377
0378 template<class F, typename PreciseReal, typename CoarseReal>
0379 ulps_plot<F, PreciseReal, CoarseReal>& ulps_plot<F, PreciseReal, CoarseReal>::envelope_color(std::string const & color)
0380 {
0381 envelope_color_ = color;
0382 return *this;
0383 }
0384
0385 template<class F, typename PreciseReal, typename CoarseReal>
0386 ulps_plot<F, PreciseReal, CoarseReal>& ulps_plot<F, PreciseReal, CoarseReal>::clip(PreciseReal clip)
0387 {
0388 clip_ = clip;
0389 return *this;
0390 }
0391
0392 template<class F, typename PreciseReal, typename CoarseReal>
0393 ulps_plot<F, PreciseReal, CoarseReal>& ulps_plot<F, PreciseReal, CoarseReal>::width(int width)
0394 {
0395 width_ = width;
0396 return *this;
0397 }
0398
0399 template<class F, typename PreciseReal, typename CoarseReal>
0400 ulps_plot<F, PreciseReal, CoarseReal>& ulps_plot<F, PreciseReal, CoarseReal>::horizontal_lines(int horizontal_lines)
0401 {
0402 horizontal_lines_ = horizontal_lines;
0403 return *this;
0404 }
0405
0406 template<class F, typename PreciseReal, typename CoarseReal>
0407 ulps_plot<F, PreciseReal, CoarseReal>& ulps_plot<F, PreciseReal, CoarseReal>::vertical_lines(int vertical_lines)
0408 {
0409 vertical_lines_ = vertical_lines;
0410 return *this;
0411 }
0412
0413 template<class F, typename PreciseReal, typename CoarseReal>
0414 ulps_plot<F, PreciseReal, CoarseReal>& ulps_plot<F, PreciseReal, CoarseReal>::title(std::string const & title)
0415 {
0416 title_ = title;
0417 return *this;
0418 }
0419
0420 template<class F, typename PreciseReal, typename CoarseReal>
0421 ulps_plot<F, PreciseReal, CoarseReal>& ulps_plot<F, PreciseReal, CoarseReal>::background_color(std::string const & background_color)
0422 {
0423 background_color_ = background_color;
0424 return *this;
0425 }
0426
0427 template<class F, typename PreciseReal, typename CoarseReal>
0428 ulps_plot<F, PreciseReal, CoarseReal>& ulps_plot<F, PreciseReal, CoarseReal>::font_color(std::string const & font_color)
0429 {
0430 font_color_ = font_color;
0431 return *this;
0432 }
0433
0434 template<class F, typename PreciseReal, typename CoarseReal>
0435 ulps_plot<F, PreciseReal, CoarseReal>& ulps_plot<F, PreciseReal, CoarseReal>::crop_color(std::string const & color)
0436 {
0437 crop_color_ = color;
0438 return *this;
0439 }
0440
0441 template<class F, typename PreciseReal, typename CoarseReal>
0442 ulps_plot<F, PreciseReal, CoarseReal>& ulps_plot<F, PreciseReal, CoarseReal>::nan_color(std::string const & color)
0443 {
0444 nan_color_ = color;
0445 return *this;
0446 }
0447
0448 template<class F, typename PreciseReal, typename CoarseReal>
0449 ulps_plot<F, PreciseReal, CoarseReal>& ulps_plot<F, PreciseReal, CoarseReal>::ulp_envelope(bool write_ulp_envelope)
0450 {
0451 ulp_envelope_ = write_ulp_envelope;
0452 return *this;
0453 }
0454
0455 namespace detail{
0456 bool ends_with(std::string const& filename, std::string const& suffix)
0457 {
0458 if(filename.size() < suffix.size())
0459 {
0460 return false;
0461 }
0462
0463 return std::equal(std::begin(suffix), std::end(suffix), std::end(filename) - suffix.size());
0464 }
0465 }
0466
0467 template<class F, typename PreciseReal, typename CoarseReal>
0468 void ulps_plot<F, PreciseReal, CoarseReal>::write(std::string const & filename) const
0469 {
0470 if(!boost::math::tools::detail::ends_with(filename, ".svg"))
0471 {
0472 throw std::logic_error("Only svg files are supported at this time.");
0473 }
0474 std::ofstream fs(filename);
0475 fs << *this;
0476 fs.close();
0477 }
0478
0479
0480 template<class F, typename PreciseReal, typename CoarseReal>
0481 ulps_plot<F, PreciseReal, CoarseReal>::ulps_plot(F hi_acc_impl, CoarseReal a, CoarseReal b,
0482 size_t samples, bool perturb_abscissas, int random_seed) : crop_color_("red")
0483 {
0484
0485 static_assert(std::numeric_limits<PreciseReal>::digits10 >= std::numeric_limits<CoarseReal>::digits10, "PreciseReal must have higher precision that CoarseReal");
0486 if (samples < 10)
0487 {
0488 throw std::domain_error("Must have at least 10 samples, samples = " + std::to_string(samples));
0489 }
0490 if (b <= a)
0491 {
0492 throw std::domain_error("On interval [a,b], b > a is required.");
0493 }
0494 a_ = a;
0495 b_ = b;
0496
0497 std::mt19937_64 gen;
0498 if (random_seed == -1)
0499 {
0500 std::random_device rd;
0501 gen.seed(rd());
0502 }
0503
0504
0505 #ifndef BOOST_MATH_STANDALONE
0506 boost::random::uniform_real_distribution<PreciseReal> dis(static_cast<PreciseReal>(a), static_cast<PreciseReal>(b));
0507 #else
0508
0509 static_assert(std::numeric_limits<PreciseReal>::digits10 <= std::numeric_limits<long double>::digits10, "Standalone mode does not support types with precision that exceeds long double");
0510 std::uniform_real_distribution<PreciseReal> dis(static_cast<PreciseReal>(a), static_cast<PreciseReal>(b));
0511 #endif
0512
0513 precise_abscissas_.resize(samples);
0514 coarse_abscissas_.resize(samples);
0515
0516 if (perturb_abscissas)
0517 {
0518 for(size_t i = 0; i < samples; ++i)
0519 {
0520 precise_abscissas_[i] = dis(gen);
0521 }
0522 std::sort(precise_abscissas_.begin(), precise_abscissas_.end());
0523 for (size_t i = 0; i < samples; ++i)
0524 {
0525 coarse_abscissas_[i] = static_cast<CoarseReal>(precise_abscissas_[i]);
0526 }
0527 }
0528 else
0529 {
0530 for(size_t i = 0; i < samples; ++i)
0531 {
0532 coarse_abscissas_[i] = static_cast<CoarseReal>(dis(gen));
0533 }
0534 std::sort(coarse_abscissas_.begin(), coarse_abscissas_.end());
0535 for (size_t i = 0; i < samples; ++i)
0536 {
0537 precise_abscissas_[i] = static_cast<PreciseReal>(coarse_abscissas_[i]);
0538 }
0539 }
0540
0541 precise_ordinates_.resize(samples);
0542 for (size_t i = 0; i < samples; ++i)
0543 {
0544 precise_ordinates_[i] = hi_acc_impl(precise_abscissas_[i]);
0545 }
0546
0547 cond_.resize(samples, std::numeric_limits<PreciseReal>::quiet_NaN());
0548 for (size_t i = 0 ; i < samples; ++i)
0549 {
0550 PreciseReal y = precise_ordinates_[i];
0551 if (y != 0)
0552 {
0553
0554 cond_[i] = boost::math::tools::evaluation_condition_number(hi_acc_impl, precise_abscissas_[i])/2;
0555
0556 if (cond_[i] < 0.5)
0557 {
0558 cond_[i] = 0.5;
0559 }
0560 }
0561
0562 }
0563 clip_ = -1;
0564 width_ = 1100;
0565 envelope_color_ = "chartreuse";
0566 ulp_envelope_ = true;
0567 horizontal_lines_ = 8;
0568 vertical_lines_ = 10;
0569 title_ = "";
0570 background_color_ = "black";
0571 font_color_ = "white";
0572 }
0573
0574 template<class F, typename PreciseReal, typename CoarseReal>
0575 template<class G>
0576 ulps_plot<F, PreciseReal, CoarseReal>& ulps_plot<F, PreciseReal, CoarseReal>::add_fn(G g, std::string const & color)
0577 {
0578 using std::abs;
0579 size_t samples = precise_abscissas_.size();
0580 std::vector<CoarseReal> ulps(samples);
0581 for (size_t i = 0; i < samples; ++i)
0582 {
0583 PreciseReal y_hi_acc = precise_ordinates_[i];
0584 PreciseReal y_lo_acc = static_cast<PreciseReal>(g(coarse_abscissas_[i]));
0585 PreciseReal absy = abs(y_hi_acc);
0586 PreciseReal dist = static_cast<PreciseReal>(nextafter(static_cast<CoarseReal>(absy), (std::numeric_limits<CoarseReal>::max)()) - static_cast<CoarseReal>(absy));
0587 ulps[i] = static_cast<CoarseReal>((y_lo_acc - y_hi_acc)/dist);
0588 }
0589 ulp_list_.emplace_back(ulps);
0590 colors_.emplace_back(color);
0591 return *this;
0592 }
0593
0594
0595
0596
0597 }
0598 #endif