libs/math/example/color_maps_sf_example.cpp
// (C) Copyright John Maddock 2022.
// Use, modification and distribution are subject to the
// Boost Software License, Version 1.0. (See accompanying file
// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// This program plots various special functions as color maps,
// run with the "help" option to see the various command line options.
//
#include <cmath>
#include <cstdint>
#include <array>
#include <complex>
#include <tuple>
#include <iostream>
#include <vector>
#include <limits>
#include <boost/math/tools/color_maps.hpp>
#include <boost/math/special_functions.hpp>
#if !__has_include("lodepng.h")
#error "lodepng.h is required to run this example."
#endif
#include "lodepng.h"
#include <iostream>
#include <string>
#include <vector>
#include <functional>
// In lodepng, the vector is expected to be row major, with the top row
// specified first. Note that this is a bit confusing sometimes as it's more
// natural to let y increase moving *up*.
unsigned write_png(const std::string& filename,
const std::vector<std::uint8_t>& img, std::size_t width,
std::size_t height) {
unsigned error = lodepng::encode(filename, img, width, height,
LodePNGColorType::LCT_RGBA, 8);
if (error) {
std::cerr << "Error encoding png: " << lodepng_error_text(error) << "\n";
}
return error;
}
double hypergeometric_1F1_at_half(double x, double y)
{
try
{
return boost::math::hypergeometric_1F1(x, y, -3.5);
}
catch (const std::domain_error&)
{
return 0;
}
}
void show_help()
{
std::cout <<
"The following command line options are supported:\n"
" gamma_p|gamma_q|gamma_p_inv|gamma_q_inv|cyl_bessel_j|cyl_neumann|cyl_bessel_i|cyl_bessel_k\n"
" |cyl_bessel_d|ellint_1|ellint_2|ellint_3|jacobi_zeta|heuman_lambda|jacobi_theta1|1F1\n"
" Sets the function to be plotted.\n"
" Note that the defaults for the options below change depending on the function selected here,\n"
" so set this option first, and then fine tune with the following options:\n"
" smooth_cool_warm|plasma|black_body|inferno|kindlmann|extended_kindlmann\n"
" Sets the color map used.\n"
" width=XX\n"
" height=XX\n"
" Sets the width and height of the bitmap.\n"
" x_min=XX\n"
" x_max=XX\n"
" y_min=XX\n"
" y_max=XX\n"
" Sets the extent of the x and y variables passed to the function.\n"
" log=false|true|0|1\n"
" Turns logarithmic scale on or off (default off)\n";
}
int main(int argc, char** argv)
{
using Real = double;
using boost::math::tools::viridis;
using std::sqrt;
std::function<std::array<Real, 3>(Real)> color_map = viridis<Real>;
std::string requested_color_map = "viridis";
std::string function_name = "gamma_p";
int64_t image_width = 1024;
int64_t image_height = 1024;
double x_min{ 0.001 }, x_max{ 20 };
double y_min{ 0.001 }, y_max{ 20 };
Real(*the_function)(Real, Real) = boost::math::gamma_p;
bool log_scale = false;
bool debug = false;
for(unsigned i = 1; i < argc; ++i)
{
std::string arg = std::string(argv[i]);
if (arg == "smooth_cool_warm") {
requested_color_map = arg;
color_map = boost::math::tools::smooth_cool_warm<Real>;
}
else if (arg == "plasma") {
requested_color_map = arg;
color_map = boost::math::tools::plasma<Real>;
}
else if (arg == "black_body") {
requested_color_map = arg;
color_map = boost::math::tools::black_body<Real>;
}
else if (arg == "inferno") {
requested_color_map = arg;
color_map = boost::math::tools::inferno<Real>;
}
else if (arg == "kindlmann") {
requested_color_map = arg;
color_map = boost::math::tools::kindlmann<Real>;
}
else if (arg == "extended_kindlmann") {
requested_color_map = arg;
color_map = boost::math::tools::extended_kindlmann<Real>;
}
else if (arg.compare(0, 6, "width=") == 0)
{
image_width = std::strtol(arg.c_str() + 6, nullptr, 10);
}
else if (arg.compare(0, 7, "height=") == 0)
{
image_height = std::strtol(arg.c_str() + 7, nullptr, 10);
}
else if (arg.compare(0, 6, "x_min=") == 0)
{
x_min = std::strtod(arg.c_str() + 6, nullptr);
}
else if (arg.compare(0, 6, "x_max=") == 0)
{
x_max = std::strtod(arg.c_str() + 6, nullptr);
}
else if (arg.compare(0, 6, "y_min=") == 0)
{
y_min = std::strtod(arg.c_str() + 6, nullptr);
}
else if (arg.compare(0, 6, "y_max=") == 0)
{
y_max = std::strtod(arg.c_str() + 6, nullptr);
}
else if (arg == "log=1")
{
log_scale = true;
}
else if (arg == "log=0")
{
log_scale = false;
}
else if (arg == "log=true")
{
log_scale = true;
}
else if (arg == "log=false")
{
log_scale = false;
}
else if (arg == "debug")
{
debug = true;
}
else if (arg == "gamma_p")
{
the_function = boost::math::gamma_p;
function_name = arg;
if (requested_color_map == "viridis")
{
std::cout << "Setting default gamma_p color map to extended_kindlmann" << std::endl;
requested_color_map = "extended_kindlmann";
color_map = boost::math::tools::extended_kindlmann<Real>;
}
}
else if (arg == "gamma_q")
{
the_function = boost::math::gamma_q;
function_name = arg;
if (requested_color_map == "viridis")
{
std::cout << "Setting default gamma_p color map to extended_kindlmann" << std::endl;
requested_color_map = "extended_kindlmann";
color_map = boost::math::tools::extended_kindlmann<Real>;
}
}
else if (arg == "gamma_p_inv")
{
the_function = boost::math::gamma_p_inv;
function_name = arg;
if (y_max > 1)
{
std::cout << "Setting y range to [0.01, 0.99] for gamma_p_inv" << std::endl;
y_min = 0.01;
y_max = 0.99;
}
if (requested_color_map == "viridis")
{
std::cout << "Setting default gamma_p_inv color map to inferno" << std::endl;
requested_color_map = "inferno";
color_map = boost::math::tools::inferno<Real>;
}
}
else if (arg == "gamma_q_inv")
{
the_function = boost::math::gamma_q_inv;
function_name = arg;
if (y_max > 1)
{
std::cout << "Setting y range to [0.01, 0.99] for gamma_p_inv" << std::endl;
y_min = 0.01;
y_max = 0.99;
}
if (requested_color_map == "viridis")
{
std::cout << "Setting default gamma_p_inv color map to inferno" << std::endl;
requested_color_map = "inferno";
color_map = boost::math::tools::inferno<Real>;
}
}
else if (arg == "beta")
{
the_function = boost::math::beta;
function_name = arg;
if (log_scale == false)
{
std::cout << "Setting log scale to true for beta" << std::endl;
log_scale = true;
}
if (requested_color_map == "viridis")
{
std::cout << "Setting default beta color map to smooth_cool_warm" << std::endl;
requested_color_map = "smooth_cool_warm";
color_map = boost::math::tools::smooth_cool_warm<Real>;
}
}
else if (arg == "cyl_bessel_j")
{
the_function = boost::math::cyl_bessel_j;
function_name = arg;
if (requested_color_map == "viridis")
{
std::cout << "Setting default beta color map to smooth_cool_warm" << std::endl;
requested_color_map = "smooth_cool_warm";
color_map = boost::math::tools::smooth_cool_warm<Real>;
}
}
else if (arg == "cyl_neumann")
{
the_function = boost::math::cyl_neumann;
function_name = arg;
if (requested_color_map == "viridis")
{
std::cout << "Setting default cyl_neumann color map to black_body" << std::endl;
requested_color_map = "black_body";
color_map = boost::math::tools::black_body<Real>;
}
if (x_max > 1.5)
{
std::cout << "Setting cyl_neumann default x range to [0.5,1.5]" << std::endl;
x_min = 0.5;
x_max = 1.5;
}
if (log_scale == false)
{
std::cout << "Turning on log scale for cyl_neumann" << std::endl;
log_scale = true;
}
}
else if (arg == "cyl_bessel_i")
{
the_function = boost::math::cyl_bessel_i;
function_name = arg;
if (requested_color_map == "viridis")
{
std::cout << "Setting default cyl_bessel_i color map to black_body" << std::endl;
requested_color_map = "black_body";
color_map = boost::math::tools::black_body<Real>;
}
if (x_max > 1.5)
{
std::cout << "Setting cyl_bessel_i default x range to [0.5,1.5]" << std::endl;
x_min = 0.5;
x_max = 1.5;
}
if (log_scale == false)
{
std::cout << "Turning on log scale for cyl_bessel_i" << std::endl;
log_scale = true;
}
}
else if (arg == "cyl_bessel_k")
{
the_function = boost::math::cyl_bessel_k;
function_name = arg;
if (requested_color_map == "viridis")
{
std::cout << "Setting default cyl_bessel_k color map to plasma" << std::endl;
requested_color_map = "plasma";
color_map = boost::math::tools::plasma<Real>;
}
if (x_max > 1.5)
{
std::cout << "Setting cyl_bessel_k default x range to [0,5]" << std::endl;
x_min = 0.01;
x_max = 5;
}
if (log_scale == false)
{
std::cout << "Turning on log scale for cyl_bessel_k" << std::endl;
log_scale = true;
}
}
else if (arg == "cyl_bessel_d")
{
the_function = boost::math::cyl_bessel_k;
function_name = arg;
if (log_scale == false)
{
std::cout << "Turning on log scale for cyl_bessel_d" << std::endl;
log_scale = true;
}
}
else if (arg == "ellint_1")
{
the_function = boost::math::ellint_1;
function_name = arg;
// x_max=1 y_max=1.5 kindlmann log=true
if (x_max >= 20)
{
std::cout << "Setting ellint_1 x range to [0, 1]" << std::endl;
x_max = 1;
}
if (y_max >= 20)
{
std::cout << "Setting ellint_1 y range to [0, 1.5]" << std::endl;
x_max = 1.5;
}
if (log_scale == false)
{
std::cout << "Turning on log scale for ellint_1" << std::endl;
log_scale = true;
}
if (requested_color_map == "viridis")
{
std::cout << "Setting default ellint_1 color map to kindlmann" << std::endl;
requested_color_map = "kindlmann";
color_map = boost::math::tools::kindlmann<Real>;
}
}
else if (arg == "ellint_2")
{
the_function = boost::math::ellint_2;
function_name = arg;
// x_max=1 y_max=1.5 kindlmann log=true
if (x_max >= 20)
{
std::cout << "Setting ellint_2 x range to [-1, 1]" << std::endl;
x_max = 1;
x_min = -1;
}
if (y_max >= 20)
{
std::cout << "Setting ellint_2 y range to [0, 1.5]" << std::endl;
y_max = 1.5;
}
if (log_scale == false)
{
std::cout << "Turning on log scale for ellint_2" << std::endl;
log_scale = true;
}
if (requested_color_map == "viridis")
{
std::cout << "Setting default ellint_1 color map to kindlmann" << std::endl;
requested_color_map = "kindlmann";
color_map = boost::math::tools::kindlmann<Real>;
}
}
else if (arg == "ellint_3")
{
the_function = boost::math::ellint_3;
function_name = arg;
// x_max=1 y_max=1.5 kindlmann log=true
if (x_max >= 20)
{
std::cout << "Setting ellint_3 x range to [-0.99, 0.99]" << std::endl;
x_max = 0.99;
x_min = -0.99;
}
if (y_max >= 20)
{
std::cout << "Setting ellint_3 y range to [0, 1]" << std::endl;
y_max = 1;
}
if (log_scale == false)
{
std::cout << "Turning on log scale for ellint_3" << std::endl;
log_scale = true;
}
if (requested_color_map == "viridis")
{
std::cout << "Setting default ellint_3 color map to kindlmann" << std::endl;
requested_color_map = "kindlmann";
color_map = boost::math::tools::kindlmann<Real>;
}
}
else if (arg == "jacobi_zeta")
{
the_function = boost::math::jacobi_zeta;
function_name = arg;
// x_max=1 y_max=1.5 kindlmann log=true
if (x_max >= 20)
{
std::cout << "Setting jacobi_zeta x range to [-0.99, 0.99]" << std::endl;
x_max = 0.99;
x_min = -0.99;
}
if (y_max >= 20)
{
std::cout << "Setting jacobi_zeta y range to [0, 1]" << std::endl;
y_max = 0.99;
}
if (requested_color_map == "viridis")
{
std::cout << "Setting default jacobi_zeta color map to kindlmann" << std::endl;
requested_color_map = "kindlmann";
color_map = boost::math::tools::kindlmann<Real>;
}
}
else if (arg == "heuman_lambda")
{
the_function = boost::math::heuman_lambda;
function_name = arg;
// x_max=1 y_max=1.5 kindlmann log=true
if (x_max >= 20)
{
std::cout << "Setting heuman_lambda x range to [-0.99, 0.99]" << std::endl;
x_max = 0.99;
x_min = -0.99;
}
if (y_max >= 20)
{
std::cout << "Setting heuman_lambda y range to [0, 1]" << std::endl;
y_max = 0.99;
}
if (requested_color_map == "viridis")
{
std::cout << "Setting default heuman_lambda color map to kindlmann" << std::endl;
requested_color_map = "kindlmann";
color_map = boost::math::tools::kindlmann<Real>;
}
}
else if (arg == "jacobi_theta1")
{
the_function = boost::math::jacobi_theta1;
function_name = arg;
if (y_max >= 20)
{
std::cout << "Setting jacobi_theta1 y range to [0, 1]" << std::endl;
y_max = 0.99;
}
if (requested_color_map == "viridis")
{
std::cout << "Setting default jacobi_theta1 color map to kindlmann" << std::endl;
requested_color_map = "kindlmann";
color_map = boost::math::tools::kindlmann<Real>;
}
}
else if (arg == "1F1")
{
the_function = hypergeometric_1F1_at_half;
function_name = arg;
if (x_min >= 0)
{
std::cout << "Setting 1F1 x range to [-20,20]" << std::endl;
x_min = -20;
x_max = 20;
}
if (y_min >= 0)
{
std::cout << "Setting 1F1 y range to [-20,20]" << std::endl;
y_min = -20;
y_max = 20;
}
if (requested_color_map == "viridis")
{
std::cout << "Setting default 1F1 color map to extended_kindlmann" << std::endl;
requested_color_map = "extended_kindlmann";
color_map = boost::math::tools::extended_kindlmann<Real>;
}
if (!log_scale)
{
std::cout << "Turning on logarithmic scale for 1F1" << std::endl;
log_scale = true;
}
}
else if (arg == "help")
{
show_help();
return 0;
}
else
{
std::cerr << "Could not recognize argument " << argv[i] << ".\n\n";
show_help();
return 1;
}
}
std::vector<std::uint8_t> img(4*image_width*image_height, 0);
std::vector<Real> points(image_width*image_height, 0);
Real min_value{ std::numeric_limits<Real>::infinity() }, max_value{ -std::numeric_limits<Real>::infinity() };
//
// Get a matrix of points:
//
for (int64_t i = 0; i < image_width; ++i)
{
for (int64_t j = 0; j < image_height; ++j)
{
double x = x_max - (image_width - i) * (x_max - x_min) / image_width;
double y = y_max - (image_height - j) * (y_max - y_min) / image_height;
Real p = the_function(x, y);
if (std::isnan(p))
std::cerr << "Ooops, got a NaN" << std::endl;
if (p < min_value)
min_value = p;
if (p > max_value)
max_value = p;
points[i + image_width * (image_height - j - 1)] = p;
}
}
std::cout << "Function range is: [" << std::setprecision(3) << min_value << "," << max_value << "]\n";
//
// Handle log scale, the formula differs depending on whether we have found negative values or not:
//
if (log_scale)
{
Real new_max = -std::numeric_limits<Real>::infinity();
Real new_min = std::numeric_limits<Real>::infinity();
for (int64_t i = 0; i < points.size(); ++i)
{
Real p = points[i];
if (min_value <= 0)
p = boost::math::sign(p) * log10(1 + std::fabs(p * boost::math::constants::ln_ten<Real>()));
else
p = log(p);
if (std::isnan(p))
std::cerr << "Ooops, got a NaN" << std::endl;
if (p < new_min)
new_min = p;
if (p > new_max)
new_max = p;
points[i] = p;
}
max_value = new_max;
min_value = new_min;
std::cout << "Function range is: [" << std::setprecision(3) << min_value << "," << max_value << "]\n";
}
//
// Normalize the points so they are all in [0,1]
//
for (int64_t i = 0; i < points.size(); ++i)
{
double p = points[i];
p -= min_value;
p /= (max_value - min_value);
points[i] = p;
}
//
// debugging, adds an alternating 0 and 1 row on the second half of the zeroth row:
//
if (debug)
{
for (int64_t i = image_width / 2; i < image_width; ++i)
points[image_width * (image_height - 1) + i] = i & 1 ? 1 : 0;
}
//
// Now calculate the RGB bitmap from the points:
//
for (int64_t i = 0; i < image_width; ++i)
{
for (int64_t j = 0; j < image_height; ++j)
{
double p = points[i + image_width * j];
auto c = boost::math::tools::to_8bit_rgba(color_map(p));
int64_t idx = 4 * (image_width * j + i);
img[idx + 0] = c[0];
img[idx + 1] = c[1];
img[idx + 2] = c[2];
img[idx + 3] = c[3];
}
}
// Requires lodepng.h
// See: https://github.com/lvandeve/lodepng for download and compilation instructions
write_png(requested_color_map + "_" + function_name + ".png", img, image_width, image_height);
}