17 #include "gaussian_process_guider.h"
26 #include "ekos_guide_debug.h"
28 #define DLOG if (false) qCDebug
30 #define SAVE_FFT_DATA_ 0
31 #define PRINT_TIMINGS_ 0
33 #define CIRCULAR_BUFFER_SIZE 8192 // for the raw data storage
34 #define REGULAR_BUFFER_SIZE 2048 // for the regularized data storage
35 #define FFT_SIZE 4096 // for zero-padding the FFT, >= REGULAR_BUFFER_SIZE!
36 #define GRID_INTERVAL 5.0
37 #define MAX_DITHER_STEPS 10 // for our fallback dithering
39 #define DEFAULT_LEARNING_RATE 0.01 // for a smooth parameter adaptation
41 #define HYSTERESIS 0.1 // for the hybrid mode
43 GaussianProcessGuider::GaussianProcessGuider(guide_parameters parameters) :
44 start_time_(
std::chrono::system_clock::now()),
45 last_time_(
std::chrono::system_clock::now()),
48 last_prediction_end_(0),
50 dithering_active_(false),
52 circular_buffer_data_(CIRCULAR_BUFFER_SIZE),
53 covariance_function_(),
54 output_covariance_function_(),
55 gp_(covariance_function_),
56 learning_rate_(DEFAULT_LEARNING_RATE),
57 parameters(parameters)
59 circular_buffer_data_.push_front(data_point());
60 circular_buffer_data_[0].control = 0;
61 gp_.enableExplicitTrend();
62 gp_.enableOutputProjection(output_covariance_function_);
64 std::vector<double> hyperparameters(NumParameters);
65 hyperparameters[SE0KLengthScale] = parameters.SE0KLengthScale_;
66 hyperparameters[SE0KSignalVariance] = parameters.SE0KSignalVariance_;
67 hyperparameters[PKLengthScale] = parameters.PKLengthScale_;
68 hyperparameters[PKSignalVariance] = parameters.PKSignalVariance_;
69 hyperparameters[SE1KLengthScale] = parameters.SE1KLengthScale_;
70 hyperparameters[SE1KSignalVariance] = parameters.SE1KSignalVariance_;
71 hyperparameters[PKPeriodLength] = parameters.PKPeriodLength_;
72 SetGPHyperparameters(hyperparameters);
74 qCDebug(KSTARS_EKOS_GUIDE) <<
75 QString(
"GPG Parameters: control_gain %1 min_move %2 pred_gain %3 min_for_inf %4 min_for_period %5 pts %6 cpd %7 -- se0L %8 se0V %9 PL %10 PV %11 Se1L %12 se1V %13 ppd %14")
76 .
arg(parameters.control_gain_, 6,
'f', 3)
77 .
arg(parameters.min_move_, 6,
'f', 3)
78 .
arg(parameters.prediction_gain_, 6,
'f', 3)
79 .
arg(parameters.min_periods_for_inference_, 6,
'f', 3)
80 .
arg(parameters.min_periods_for_period_estimation_, 6,
'f', 3)
81 .
arg(parameters.points_for_approximation_)
82 .
arg(parameters.compute_period_)
83 .
arg(parameters.SE0KLengthScale_, 6,
'f', 3)
84 .
arg(parameters.SE0KSignalVariance_, 6,
'f', 3)
85 .
arg(parameters.PKLengthScale_, 6,
'f', 3)
86 .
arg(parameters.PKSignalVariance_, 6,
'f', 3)
87 .
arg(parameters.SE1KLengthScale_, 6,
'f', 3)
88 .
arg(parameters.SE1KSignalVariance_, 6,
'f', 3)
89 .
arg(parameters.PKPeriodLength_, 6,
'f', 3);
92 GaussianProcessGuider::~GaussianProcessGuider()
96 void GaussianProcessGuider::SetTimestamp()
98 auto current_time = std::chrono::system_clock::now();
99 double delta_measurement_time = std::chrono::duration<double>(current_time - last_time_).count();
100 last_time_ = current_time;
101 get_last_point().timestamp = std::chrono::duration<double>(current_time - start_time_).count()
102 - (delta_measurement_time / 2.0)
107 void GaussianProcessGuider::HandleGuiding(
double input,
double SNR)
110 get_last_point().measurement = input;
111 get_last_point().variance = CalculateVariance(SNR);
115 last_prediction_end_ = get_last_point().timestamp;
118 void GaussianProcessGuider::HandleDarkGuiding()
121 get_last_point().measurement = 0;
122 get_last_point().variance = 1e4;
125 void GaussianProcessGuider::HandleControls(
double control_input)
127 get_last_point().control = control_input;
130 double GaussianProcessGuider::CalculateVariance(
double SNR)
132 SNR = std::max(SNR, 3.4);
135 double standard_deviation = 2.1752 * 1 / (SNR - 3.3) + 0.5;
137 return standard_deviation * standard_deviation;
143 clock_t begin = std::clock();
146 size_t N = get_number_of_measurements();
149 Eigen::VectorXd timestamps(N - 1);
150 Eigen::VectorXd measurements(N - 1);
151 Eigen::VectorXd variances(N - 1);
152 Eigen::VectorXd sum_controls(N - 1);
154 double sum_control = 0;
157 for (
size_t i = 0; i < N - 1; i++)
159 sum_control += circular_buffer_data_[i].control;
160 timestamps(i) = circular_buffer_data_[i].timestamp;
161 measurements(i) = circular_buffer_data_[i].measurement;
162 variances(i) = circular_buffer_data_[i].variance;
163 sum_controls(i) = sum_control;
166 Eigen::VectorXd gear_error(N - 1);
167 Eigen::VectorXd linear_fit(N - 1);
170 gear_error = sum_controls + measurements;
173 clock_t end = std::clock();
174 double time_init = double(end - begin) / CLOCKS_PER_SEC;
175 begin = std::clock();
182 timestamps =
result.row(0);
183 gear_error =
result.row(1);
184 variances =
result.row(2);
188 double time_regularize = double(end - begin) / CLOCKS_PER_SEC;
189 begin = std::clock();
193 Eigen::MatrixXd feature_matrix(2, timestamps.rows());
194 feature_matrix.row(0) = Eigen::MatrixXd::Ones(1, timestamps.rows());
195 feature_matrix.row(1) = timestamps.array();
198 Eigen::VectorXd weights = (feature_matrix * feature_matrix.transpose()
199 + 1e-3 * Eigen::Matrix<double, 2, 2>::Identity()).ldlt().solve(feature_matrix * gear_error);
202 linear_fit = weights.transpose() * feature_matrix;
205 Eigen::VectorXd gear_error_detrend = gear_error - linear_fit;
209 double time_detrend = double(end - begin) / CLOCKS_PER_SEC;
210 begin = std::clock();
215 double period_length = GetGPHyperparameters()[PKPeriodLength];
216 if (GetBoolComputePeriod() && get_last_point().timestamp > parameters.min_periods_for_period_estimation_ * period_length)
219 period_length = EstimatePeriodLength(timestamps, gear_error_detrend);
224 time_fft = double(end - begin) / CLOCKS_PER_SEC;
229 begin = std::clock();
233 gp_.inferSD(timestamps, gear_error, parameters.points_for_approximation_, variances, prediction_point);
237 double time_gp = double(end - begin) / CLOCKS_PER_SEC;
239 printf(
"timings: init: %f, regularize: %f, detrend: %f, fft: %f, gp: %f, total: %f\n",
240 time_init, time_regularize, time_detrend, time_fft, time_gp,
241 time_init + time_regularize + time_detrend + time_fft + time_gp);
245 double GaussianProcessGuider::PredictGearError(
double prediction_location)
248 if (last_prediction_end_ < 0.0)
250 last_prediction_end_ = std::chrono::duration<double>(std::chrono::system_clock::now() - start_time_).count();
254 Eigen::VectorXd next_location(2);
255 next_location << last_prediction_end_, prediction_location + dither_offset_;
256 Eigen::VectorXd prediction = gp_.predictProjected(next_location);
258 double p1 = prediction(1);
259 double p0 = prediction(0);
261 assert(!math_tools::isNaN(p1 - p0));
263 last_prediction_end_ = next_location(1);
277 double hyst_percentage = 0.0;
278 double period_length;
280 if (dithering_active_)
282 DLOG(KSTARS_EKOS_GUIDE) <<
QString(
"GPG::result(in=%1,snr=%2,ts=%3,ppt=%4) dithering active")
283 .
arg(input, 6,
'f', 2).
arg(SNR, 5,
'f', 1).
arg(time_step, 6,
'f', 2).
arg(prediction_point, 5,
'f', 1);
284 if (--dither_steps_ <= 0)
286 dithering_active_ =
false;
290 GPDebug->Log(
"PPEC rslt(dithering): input = %.2f, final = %.2f",
291 input, parameters.control_gain_ * input);
293 DLOG(KSTARS_EKOS_GUIDE)
294 <<
QString(
"PPEC rslt(dithering): input = %1, final = %2")
295 .
arg(input, 5,
'f', 2)
296 .
arg(parameters.control_gain_ * input, 5,
'f', 2);
298 return parameters.control_gain_ * input;
301 DLOG(KSTARS_EKOS_GUIDE) <<
QString(
"GPG::result(in=%1,snr=%2,ts=%3,ppt=%4)")
302 .
arg(input, 6,
'f', 2).
arg(SNR, 5,
'f', 1).
arg(time_step, 6,
'f', 2).
arg(prediction_point, 5,
'f', 1);
304 if (get_number_of_measurements() == 1)
306 start_time_ = std::chrono::system_clock::now();
307 last_time_ = start_time_;
311 HandleGuiding(input, SNR);
314 double last_control = 0.0;
315 if (get_number_of_measurements() > 1)
317 last_control = get_second_last_point().control;
319 double hysteresis_control = (1.0 - HYSTERESIS) * input + HYSTERESIS * last_control;
320 hysteresis_control *= parameters.control_gain_;
322 control_signal_ = parameters.control_gain_ * input;
323 if (std::abs(input) < parameters.min_move_)
325 control_signal_ = 0.0;
326 hysteresis_control = 0.0;
328 assert(std::abs(control_signal_) == 0.0 || std::abs(input) >= parameters.min_move_);
331 if (get_number_of_measurements() > 10)
333 if (prediction_point < 0.0)
335 prediction_point = std::chrono::duration<double>(std::chrono::system_clock::now() - start_time_).count();
338 UpdateGP(prediction_point + 0.5 * time_step);
341 prediction_ = PredictGearError(prediction_point + time_step);
342 control_signal_ += parameters.prediction_gain_ * prediction_;
345 period_length = GetGPHyperparameters()[PKPeriodLength];
346 if (get_last_point().timestamp < parameters.min_periods_for_inference_ * period_length)
348 double percentage = get_last_point().timestamp / (parameters.min_periods_for_inference_ * period_length);
349 percentage = std::min(percentage, 1.0);
350 hyst_percentage = 1.0 - percentage;
351 control_signal_ = percentage * control_signal_ + (1.0 - percentage) * hysteresis_control;
356 period_length = GetGPHyperparameters()[PKPeriodLength];
360 assert(!math_tools::isNaN(control_signal_));
363 if (math_tools::isNaN(control_signal_))
365 control_signal_ = hysteresis_control;
369 HandleControls(control_signal_);
371 GPDebug->Log(
"PPEC rslt: input = %.2f, final = %.2f, react = %.2f, pred = %.2f, hyst = %.2f, hyst_pct = %.2f, period_length = %.2f",
372 input, control_signal_, parameters.control_gain_ * input, parameters.prediction_gain_ * prediction_, hysteresis_control,
373 hyst_percentage, period_length);
375 DLOG(KSTARS_EKOS_GUIDE) <<
376 QString(
"PPEC rslt: input = %1, final = %2, react = %3, pred = %4, hyst = %5, hyst_pct = %6, period_length = %7")
377 .
arg(input, 5,
'f', 2)
378 .
arg(control_signal_, 5,
'f', 2)
379 .
arg(parameters.control_gain_ * input, 5,
'f', 2)
380 .
arg(parameters.prediction_gain_ * prediction_, 5,
'f', 2)
381 .
arg(hysteresis_control, 5,
'f', 2)
382 .
arg(hyst_percentage, 5,
'f', 2)
383 .
arg(period_length, 5,
'f', 2);
384 return control_signal_;
393 if (get_number_of_measurements() > 10
394 && get_last_point().timestamp > parameters.min_periods_for_inference_ * GetGPHyperparameters()[PKPeriodLength])
396 if (prediction_point < 0.0)
398 prediction_point = std::chrono::duration<double>(std::chrono::system_clock::now() - start_time_).count();
401 UpdateGP(prediction_point + 0.5 * time_step);
404 prediction_ = PredictGearError(prediction_point + time_step);
405 control_signal_ += prediction_;
409 HandleControls(control_signal_);
412 assert(!math_tools::isNaN(control_signal_));
415 if (math_tools::isNaN(control_signal_))
417 control_signal_ = 0.0;
420 return control_signal_;
425 qCDebug(KSTARS_EKOS_GUIDE) <<
QString(
"GPG::reset()");
426 circular_buffer_data_.clear();
431 circular_buffer_data_.push_front(data_point());
432 circular_buffer_data_[0].control = 0;
434 last_prediction_end_ = -1.0;
435 start_time_ = std::chrono::system_clock::now();
436 last_time_ = std::chrono::system_clock::now();
438 dither_offset_ = 0.0;
440 dithering_active_ =
false;
445 DLOG(KSTARS_EKOS_GUIDE) <<
QString(
"GPG::GuidingDithered(amt=%1,rate=%2)")
446 .
arg(amt, 6,
'f', 2).
arg(rate, 5,
'f', 1);
449 dither_offset_ += amt / rate;
451 dithering_active_ =
true;
452 dither_steps_ = MAX_DITHER_STEPS;
457 DLOG(KSTARS_EKOS_GUIDE) <<
QString(
"GPG::GuidingDitherSettleDone(%1)")
458 .
arg(success ?
"true" :
"false");
475 double GaussianProcessGuider::GetControlGain(
void)
const
477 return parameters.control_gain_;
480 bool GaussianProcessGuider::SetControlGain(
double control_gain)
482 parameters.control_gain_ = control_gain;
486 bool GaussianProcessGuider::GetBoolComputePeriod()
const
488 return parameters.compute_period_;
491 bool GaussianProcessGuider::SetBoolComputePeriod(
bool active)
493 parameters.compute_period_ = active;
497 std::vector<double> GaussianProcessGuider::GetGPHyperparameters()
const
500 Eigen::VectorXd hyperparameters_full = gp_.getHyperParameters().array().exp();
502 Eigen::VectorXd hyperparameters = hyperparameters_full.tail(NumParameters);
505 hyperparameters(PKLengthScale) = std::asin(hyperparameters(PKLengthScale) / 4.0) * hyperparameters(PKPeriodLength) / M_PI;
508 return std::vector<double>(hyperparameters.data(),
509 hyperparameters.data() + NumParameters);
512 bool GaussianProcessGuider::SetGPHyperparameters(std::vector<double>
const &hyperparameters)
514 Eigen::VectorXd hyperparameters_eig = Eigen::VectorXd::Map(&hyperparameters[0], hyperparameters.size());
517 hyperparameters_eig(SE0KLengthScale) = std::max(hyperparameters_eig(SE0KLengthScale), 1.0);
518 hyperparameters_eig(PKLengthScale) = std::max(hyperparameters_eig(PKLengthScale), 1.0);
519 hyperparameters_eig(SE1KLengthScale) = std::max(hyperparameters_eig(SE1KLengthScale), 1.0);
522 hyperparameters_eig(PKLengthScale) = 4 * std::sin(hyperparameters_eig(PKLengthScale)
523 * M_PI / hyperparameters_eig(PKPeriodLength));
526 hyperparameters_eig = hyperparameters_eig.array().max(1e-10);
529 Eigen::VectorXd hyperparameters_full(NumParameters + 1);
530 hyperparameters_full << 1.0, hyperparameters_eig;
533 gp_.setHyperParameters(hyperparameters_full.array().log());
537 double GaussianProcessGuider::GetMinMove()
const
539 return parameters.min_move_;
542 bool GaussianProcessGuider::SetMinMove(
double min_move)
544 parameters.min_move_ = min_move;
548 int GaussianProcessGuider::GetNumPointsForApproximation()
const
550 return parameters.points_for_approximation_;
553 bool GaussianProcessGuider::SetNumPointsForApproximation(
int num_points)
555 parameters.points_for_approximation_ = num_points;
559 double GaussianProcessGuider::GetPeriodLengthsInference()
const
561 return parameters.min_periods_for_inference_;
564 bool GaussianProcessGuider::SetPeriodLengthsInference(
double num_periods)
566 parameters.min_periods_for_inference_ = num_periods;
570 double GaussianProcessGuider::GetPeriodLengthsPeriodEstimation()
const
572 return parameters.min_periods_for_period_estimation_;
575 bool GaussianProcessGuider::SetPeriodLengthsPeriodEstimation(
double num_periods)
577 parameters.min_periods_for_period_estimation_ = num_periods;
581 double GaussianProcessGuider::GetPredictionGain()
const
583 return parameters.prediction_gain_;
586 bool GaussianProcessGuider::SetPredictionGain(
double prediction_gain)
588 parameters.prediction_gain_ = prediction_gain;
595 HandleGuiding(input, SNR);
596 last_prediction_end_ = timestamp;
597 get_last_point().timestamp = timestamp;
599 start_time_ = std::chrono::system_clock::now() - std::chrono::seconds((
int) timestamp);
602 HandleControls(control);
605 double GaussianProcessGuider::EstimatePeriodLength(
const Eigen::VectorXd &time,
const Eigen::VectorXd &data)
608 Eigen::VectorXd windowed_data = data.array() * math_tools::hamming_window(data.rows()).array();
611 std::pair<Eigen::VectorXd, Eigen::VectorXd>
result = math_tools::compute_spectrum(windowed_data, FFT_SIZE);
613 Eigen::ArrayXd amplitudes =
result.first;
614 Eigen::ArrayXd frequencies =
result.second;
616 double dt = (time(time.rows() - 1) - time(0)) / (time.rows() - 1);
620 Eigen::ArrayXd periods = 1 / frequencies.array();
621 amplitudes = (periods > 1500.0).select(0, amplitudes);
623 assert(amplitudes.size() == frequencies.size());
625 Eigen::VectorXd::Index maxIndex;
626 amplitudes.maxCoeff(&maxIndex);
628 double max_frequency = frequencies(maxIndex);
632 if (maxIndex < frequencies.size() - 1 && maxIndex > 0)
634 double spread = std::abs(frequencies(maxIndex - 1) - frequencies(maxIndex + 1));
636 Eigen::VectorXd interp_loc(3);
637 interp_loc << frequencies(maxIndex - 1), frequencies(maxIndex), frequencies(maxIndex + 1);
638 interp_loc = interp_loc.array() - max_frequency;
639 interp_loc = interp_loc.array() / spread;
641 Eigen::VectorXd interp_dat(3);
642 interp_dat << amplitudes(maxIndex - 1), amplitudes(maxIndex), amplitudes(maxIndex + 1);
643 interp_dat = interp_dat.array() / amplitudes(maxIndex);
647 if (interp_dat.maxCoeff() - interp_dat.minCoeff() < 1e-10)
649 return 1 / max_frequency;
654 Eigen::MatrixXd phi(3, 3);
655 phi.row(0) = interp_loc.array().pow(2);
656 phi.row(1) = interp_loc.array().pow(1);
657 phi.row(2) = interp_loc.array().pow(0);
660 Eigen::VectorXd w = (phi * phi.transpose()).ldlt().solve(phi * interp_dat);
663 max_frequency = max_frequency - w(1) / (2 * w(0)) * spread;
668 std::ofstream outfile;
669 outfile.open(
"spectrum_data.csv", std::ios_base::out);
672 outfile <<
"period, amplitude\n";
673 for (
int i = 0; i < amplitudes.size(); ++i)
675 outfile << std::setw(8) << periods[i] <<
"," << std::setw(8) << amplitudes[i] <<
"\n";
680 std::cout <<
"unable to write to file" << std::endl;
686 double period_length = 1 / max_frequency;
687 return period_length;
692 std::vector<double> hypers = GetGPHyperparameters();
695 assert(!math_tools::isNaN(period_length));
698 if (math_tools::isNaN(period_length))
700 period_length = hypers[PKPeriodLength];
704 hypers[PKPeriodLength] = (1 - learning_rate_) * hypers[PKPeriodLength] + learning_rate_ * period_length;
706 SetGPHyperparameters(hypers);
710 const Eigen::VectorXd &gear_error,
const Eigen::VectorXd &variances)
712 size_t N = get_number_of_measurements();
713 double grid_interval = GRID_INTERVAL;
714 double last_cell_end = -grid_interval;
715 double last_timestamp = -grid_interval;
716 double last_gear_error = 0.0;
717 double last_variance = 0.0;
718 double gear_error_sum = 0.0;
719 double variance_sum = 0.0;
720 int grid_size =
static_cast<int>(std::ceil(timestamps(timestamps.size() - 1) / grid_interval)) + 1;
721 assert(grid_size > 0);
722 Eigen::VectorXd reg_timestamps(grid_size);
723 Eigen::VectorXd reg_gear_error(grid_size);
724 Eigen::VectorXd reg_variances(grid_size);
726 for (
size_t i = 0; i < N - 1; ++i)
728 if (timestamps(i) < last_cell_end + grid_interval)
730 gear_error_sum += (timestamps(i) - last_timestamp) * 0.5 * (last_gear_error + gear_error(i));
731 variance_sum += (timestamps(i) - last_timestamp) * 0.5 * (last_variance + variances(i));
732 last_timestamp = timestamps(i);
736 while (timestamps(i) >= last_cell_end + grid_interval)
738 double inter_timestamp = last_cell_end + grid_interval;
740 double proportion = (inter_timestamp - last_timestamp) / (timestamps(i) - last_timestamp);
741 double inter_gear_error = proportion * gear_error(i) + (1 - proportion) * last_gear_error;
742 double inter_variance = proportion * variances(i) + (1 - proportion) * last_variance;
744 gear_error_sum += (inter_timestamp - last_timestamp) * 0.5 * (last_gear_error + inter_gear_error);
745 variance_sum += (inter_timestamp - last_timestamp) * 0.5 * (last_variance + inter_variance);
747 reg_timestamps(j) = last_cell_end + 0.5 * grid_interval;
748 reg_gear_error(j) = gear_error_sum / grid_interval;
749 reg_variances(j) = variance_sum / grid_interval;
751 last_timestamp = inter_timestamp;
752 last_gear_error = inter_gear_error;
753 last_variance = inter_variance;
754 last_cell_end = inter_timestamp;
756 gear_error_sum = 0.0;
762 if (j > REGULAR_BUFFER_SIZE)
764 j = REGULAR_BUFFER_SIZE;
768 Eigen::MatrixXd
result(3, j);
769 result.row(0) = reg_timestamps.head(j);
770 result.row(1) = reg_gear_error.head(j);
771 result.row(2) = reg_variances.head(j);
779 size_t N = get_number_of_measurements();
787 Eigen::VectorXd timestamps(N - 1);
788 Eigen::VectorXd measurements(N - 1);
789 Eigen::VectorXd variances(N - 1);
790 Eigen::VectorXd controls(N - 1);
791 Eigen::VectorXd sum_controls(N - 1);
792 Eigen::VectorXd gear_error(N - 1);
793 Eigen::VectorXd linear_fit(N - 1);
796 for (
size_t i = 0; i < N - 1; i++)
798 timestamps(i) = circular_buffer_data_[i].timestamp;
799 measurements(i) = circular_buffer_data_[i].measurement;
800 variances(i) = circular_buffer_data_[i].variance;
801 controls(i) = circular_buffer_data_[i].control;
802 sum_controls(i) = circular_buffer_data_[i].control;
805 sum_controls(i) += sum_controls(i - 1);
808 gear_error = sum_controls + measurements;
811 Eigen::VectorXd locations = Eigen::VectorXd::LinSpaced(M, 0, get_second_last_point().timestamp + 1500);
813 Eigen::VectorXd vars(locations.size());
814 Eigen::VectorXd means = gp_.predictProjected(locations, &vars);
815 Eigen::VectorXd stds = vars.array().sqrt();
818 std::ofstream outfile;
819 outfile.open(
"measurement_data.csv", std::ios_base::out);
822 outfile <<
"location, output\n";
823 for(
int i = 0; i < timestamps.size(); ++i)
825 outfile << std::setw(8) << timestamps[i] <<
"," << std::setw(8) << gear_error[i] <<
"\n";
830 std::cout <<
"unable to write to file" << std::endl;
834 outfile.open(
"gp_data.csv", std::ios_base::out);
837 outfile <<
"location, mean, std\n";
838 for(
int i = 0; i < locations.size(); ++i)
840 outfile << std::setw(8) << locations[i] <<
"," << std::setw(8) << means[i] <<
"," << std::setw(8) << stds[i] <<
"\n";
845 std::cout <<
"unable to write to file" << std::endl;
853 learning_rate_ = learning_rate;
859 class NullDebugLog :
public GPDebug
861 void Log(
const char *fmt, ...)
override
867 class GPDebug *GPDebug =
new NullDebugLog();
872 struct GPDebugCleanup
876 GPDebug::SetGPDebug(
nullptr);
881 void GPDebug::SetGPDebug(GPDebug *logger)