Commit 40a4860d authored by Jia's avatar Jia Committed by Commit Bot

[On-device adaptive brightness] Add constraints check by Trainer.

This cl contains the following changes
- Check whether initial personal curve satisfies min/max slope constraints.
- Add two params high_log_lux_threshold and min_grad_high_lux.
- Simplified trainer interface by removing status.
- Simplified modeller impl so that personal curve is kept by trainer only.

Bug: 881215
Change-Id: Iad8a16df3c14440ee9086124cfc8e3e2d890e5ca
Reviewed-on: https://chromium-review.googlesource.com/c/1303317
Commit-Queue: Jia Meng <jiameng@chromium.org>
Reviewed-by: default avatarAndrew Moylan <amoylan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#604518}
parent 76f7d242
...@@ -122,63 +122,116 @@ double Gaussian(double x, double sigma) { ...@@ -122,63 +122,116 @@ double Gaussian(double x, double sigma) {
} // namespace } // namespace
// TODO(jiameng): move these params checking into another method and log errors GaussianTrainer::Params::Params() = default;
// in UMA if any value is invalid. Also disable it if param isn't valid.
// TODO(jiameng): log errors in UMA if any value is invalid.
GaussianTrainer::GaussianTrainer() { GaussianTrainer::GaussianTrainer() {
params_.brightness_bound_scale = GetFieldTrialParamByFeatureAsDouble( params_.brightness_bound_scale = GetFieldTrialParamByFeatureAsDouble(
features::kAutoScreenBrightness, "brightness_bound_scale", features::kAutoScreenBrightness, "brightness_bound_scale",
params_.brightness_bound_scale); params_.brightness_bound_scale);
DCHECK_GT(params_.brightness_bound_scale, 0.0); if (params_.brightness_bound_scale <= 0.0) {
valid_params_ = false;
return;
}
params_.brightness_bound_offset = GetFieldTrialParamByFeatureAsDouble( params_.brightness_bound_offset = GetFieldTrialParamByFeatureAsDouble(
features::kAutoScreenBrightness, "brightness_bound_offset", features::kAutoScreenBrightness, "brightness_bound_offset",
params_.brightness_bound_offset); params_.brightness_bound_offset);
DCHECK_GE(params_.brightness_bound_offset, 0.0); if (params_.brightness_bound_offset < 0.0) {
valid_params_ = false;
return;
}
params_.brightness_step_size = GetFieldTrialParamByFeatureAsDouble( params_.brightness_step_size = GetFieldTrialParamByFeatureAsDouble(
features::kAutoScreenBrightness, "brightness_step_size", features::kAutoScreenBrightness, "brightness_step_size",
params_.brightness_step_size); params_.brightness_step_size);
DCHECK_GT(params_.brightness_step_size, 0.0); if (params_.brightness_step_size <= 0.0) {
valid_params_ = false;
return;
}
params_.sigma = GetFieldTrialParamByFeatureAsDouble( params_.sigma = GetFieldTrialParamByFeatureAsDouble(
features::kAutoScreenBrightness, "sigma", params_.sigma); features::kAutoScreenBrightness, "sigma", params_.sigma);
DCHECK_GT(params_.sigma, 0.0); if (params_.sigma <= 0.0) {
valid_params_ = false;
return;
}
params_.low_log_lux_threshold = GetFieldTrialParamByFeatureAsDouble( params_.low_log_lux_threshold = GetFieldTrialParamByFeatureAsDouble(
features::kAutoScreenBrightness, "low_log_lux_threshold", features::kAutoScreenBrightness, "low_log_lux_threshold",
params_.low_log_lux_threshold); params_.low_log_lux_threshold);
params_.high_log_lux_threshold = GetFieldTrialParamByFeatureAsDouble(
features::kAutoScreenBrightness, "high_log_lux_threshold",
params_.high_log_lux_threshold);
if (params_.low_log_lux_threshold >= params_.high_log_lux_threshold) {
valid_params_ = false;
return;
}
params_.min_grad_low_lux = GetFieldTrialParamByFeatureAsDouble( params_.min_grad_low_lux = GetFieldTrialParamByFeatureAsDouble(
features::kAutoScreenBrightness, "min_grad_low_lux", features::kAutoScreenBrightness, "min_grad_low_lux",
params_.min_grad_low_lux); params_.min_grad_low_lux);
params_.min_grad_high_lux = GetFieldTrialParamByFeatureAsDouble(
features::kAutoScreenBrightness, "min_grad_high_lux",
params_.min_grad_high_lux);
params_.min_grad = GetFieldTrialParamByFeatureAsDouble( params_.min_grad = GetFieldTrialParamByFeatureAsDouble(
features::kAutoScreenBrightness, "min_grad", params_.min_grad); features::kAutoScreenBrightness, "min_grad", params_.min_grad);
params_.max_grad = GetFieldTrialParamByFeatureAsDouble( params_.max_grad = GetFieldTrialParamByFeatureAsDouble(
features::kAutoScreenBrightness, "max_grad", params_.max_grad); features::kAutoScreenBrightness, "max_grad", params_.max_grad);
DCHECK_GE(params_.min_grad_low_lux, 0.0); if (params_.min_grad_low_lux < 0.0 || params_.min_grad_low_lux >= 1.0) {
DCHECK_LT(params_.min_grad_low_lux, 1.0); valid_params_ = false;
DCHECK_GE(params_.min_grad, 0.0); return;
DCHECK_LT(params_.min_grad, 1.0); }
DCHECK_GE(params_.min_grad, params_.min_grad_low_lux);
DCHECK_GT(params_.max_grad, params_.min_grad); if (params_.min_grad_high_lux < 0.0 || params_.min_grad_high_lux >= 1.0) {
valid_params_ = false;
return;
}
if (params_.min_grad < 0.0 || params_.min_grad >= 1.0) {
valid_params_ = false;
return;
}
if (params_.min_grad < params_.min_grad_low_lux) {
valid_params_ = false;
return;
}
if (params_.min_grad < params_.min_grad_high_lux) {
valid_params_ = false;
return;
}
if (params_.max_grad < 1.0) {
valid_params_ = false;
return;
}
params_.min_brightness = GetFieldTrialParamByFeatureAsDouble( params_.min_brightness = GetFieldTrialParamByFeatureAsDouble(
features::kAutoScreenBrightness, "min_brightness", features::kAutoScreenBrightness, "min_brightness",
params_.min_brightness); params_.min_brightness);
DCHECK_GE(params_.min_brightness, 0.0); if (params_.min_brightness < 0.0) {
valid_params_ = false;
return;
}
} }
GaussianTrainer::~GaussianTrainer() = default; GaussianTrainer::~GaussianTrainer() = default;
// TODO(jiameng): add slope constraint check in |current_curve| and return check bool GaussianTrainer::HasValidConfiguration() const {
// result to the caller. return valid_params_;
void GaussianTrainer::SetInitialCurves( }
bool GaussianTrainer::SetInitialCurves(
const MonotoneCubicSpline& global_curve, const MonotoneCubicSpline& global_curve,
const MonotoneCubicSpline& current_curve) { const MonotoneCubicSpline& current_curve) {
// This function should be called once only. DCHECK(valid_params_);
DCHECK(!global_curve_);
DCHECK(!current_curve_); // This function could be called again if the caller wants to reset the
// curves.
global_curve_.emplace(global_curve); global_curve_.emplace(global_curve);
current_curve_.emplace(current_curve); current_curve_.emplace(current_curve);
...@@ -204,15 +257,39 @@ void GaussianTrainer::SetInitialCurves( ...@@ -204,15 +257,39 @@ void GaussianTrainer::SetInitialCurves(
DCHECK_GT(global_brightness[0], 0); DCHECK_GT(global_brightness[0], 0);
for (size_t i = 0; i < num_points - 1; ++i) { for (size_t i = 0; i < num_points - 1; ++i) {
const double min_grad = global_log_lux[i] < params_.low_log_lux_threshold double min_grad = params_.min_grad;
? params_.min_grad_low_lux if (global_log_lux[i] < params_.low_log_lux_threshold) {
: params_.min_grad; min_grad = params_.min_grad_low_lux;
} else if (global_log_lux[i] > params_.high_log_lux_threshold) {
min_grad = params_.min_grad_high_lux;
}
const double ratio = global_brightness[i + 1] / global_brightness[i]; const double ratio = global_brightness[i + 1] / global_brightness[i];
DCHECK_GE(ratio, 1); DCHECK_GE(ratio, 1);
min_ratios_[i] = std::pow(ratio, min_grad); min_ratios_[i] = std::pow(ratio, min_grad);
max_ratios_[i] = std::pow(ratio, params_.max_grad); max_ratios_[i] = std::pow(ratio, params_.max_grad);
} }
if (!IsInitialPersonalCurveValid()) {
// Use global curve instead if personal curve isn't valid.
current_curve_.emplace(global_curve);
brightness_ = current_curve_->GetControlPointsY();
return false;
}
return true;
}
MonotoneCubicSpline GaussianTrainer::GetGlobalCurve() const {
DCHECK(valid_params_);
DCHECK(global_curve_);
return *global_curve_;
}
MonotoneCubicSpline GaussianTrainer::GetCurrentCurve() const {
DCHECK(valid_params_);
DCHECK(current_curve_);
return *current_curve_;
} }
MonotoneCubicSpline GaussianTrainer::Train( MonotoneCubicSpline GaussianTrainer::Train(
...@@ -233,6 +310,20 @@ MonotoneCubicSpline GaussianTrainer::Train( ...@@ -233,6 +310,20 @@ MonotoneCubicSpline GaussianTrainer::Train(
return *current_curve_; return *current_curve_;
} }
bool GaussianTrainer::IsInitialPersonalCurveValid() const {
// |global_curve_| is valid by construction.
if (*global_curve_ == *current_curve_)
return true;
for (size_t i = 0; i < brightness_.size() - 1; ++i) {
const double ratio = brightness_[i + 1] / brightness_[i];
if (ratio < min_ratios_[i] || ratio > max_ratios_[i])
return false;
}
return true;
}
void GaussianTrainer::AdjustCurveWithSingleDataPoint( void GaussianTrainer::AdjustCurveWithSingleDataPoint(
const TrainingDataPoint& data) { const TrainingDataPoint& data) {
const double brightness_global = const double brightness_global =
......
...@@ -23,6 +23,7 @@ class GaussianTrainer : public Trainer { ...@@ -23,6 +23,7 @@ class GaussianTrainer : public Trainer {
public: public:
// TODO(jiameng): revise default values. // TODO(jiameng): revise default values.
struct Params { struct Params {
Params();
// |brightness_bound_scale| and |brightness_bound_offset| are used to define // |brightness_bound_scale| and |brightness_bound_offset| are used to define
// training example outliers. // training example outliers.
double brightness_bound_scale = 1.5; double brightness_bound_scale = 1.5;
...@@ -45,6 +46,11 @@ class GaussianTrainer : public Trainer { ...@@ -45,6 +46,11 @@ class GaussianTrainer : public Trainer {
double low_log_lux_threshold = 0.1; double low_log_lux_threshold = 0.1;
double min_grad_low_lux = 0; double min_grad_low_lux = 0;
// If log lux is above |high_log_lux_threshold| then we'll use
// |min_grad_high_lux| as gradient constraint.
double high_log_lux_threshold = 7.5;
double min_grad_high_lux = 0;
// Min and max grad as a power of brightness ratios. // Min and max grad as a power of brightness ratios.
double min_grad = 0.25; double min_grad = 0.25;
double max_grad = 1; double max_grad = 1;
...@@ -56,12 +62,19 @@ class GaussianTrainer : public Trainer { ...@@ -56,12 +62,19 @@ class GaussianTrainer : public Trainer {
~GaussianTrainer() override; ~GaussianTrainer() override;
// Trainer overrides: // Trainer overrides:
void SetInitialCurves(const MonotoneCubicSpline& global_curve, bool HasValidConfiguration() const override;
bool SetInitialCurves(const MonotoneCubicSpline& global_curve,
const MonotoneCubicSpline& current_curve) override; const MonotoneCubicSpline& current_curve) override;
MonotoneCubicSpline GetGlobalCurve() const override;
MonotoneCubicSpline GetCurrentCurve() const override;
MonotoneCubicSpline Train( MonotoneCubicSpline Train(
const std::vector<TrainingDataPoint>& data) override; const std::vector<TrainingDataPoint>& data) override;
private: private:
// Returns whether initial personal curve (passed in by |SetInitialCurves|) is
// valid, i.e. satisfying min/max ratio constraints.
bool IsInitialPersonalCurveValid() const;
// Updates |brightness_| using |data|. It also sets |need_to_update_curve_| // Updates |brightness_| using |data|. It also sets |need_to_update_curve_|
// to true if |brightness_| is actually changed. // to true if |brightness_| is actually changed.
void AdjustCurveWithSingleDataPoint(const TrainingDataPoint& data); void AdjustCurveWithSingleDataPoint(const TrainingDataPoint& data);
...@@ -72,6 +85,9 @@ class GaussianTrainer : public Trainer { ...@@ -72,6 +85,9 @@ class GaussianTrainer : public Trainer {
// of |center_index|. // of |center_index|.
void EnforceMonotonicity(size_t center_index); void EnforceMonotonicity(size_t center_index);
// Default params_ are valid.
bool valid_params_ = true;
Params params_; Params params_;
// |global_curve| does not change after |SetInitialCurves| is called. // |global_curve| does not change after |SetInitialCurves| is called.
base::Optional<MonotoneCubicSpline> global_curve_; base::Optional<MonotoneCubicSpline> global_curve_;
......
...@@ -16,16 +16,6 @@ namespace auto_screen_brightness { ...@@ -16,16 +16,6 @@ namespace auto_screen_brightness {
// Interface to on-device adaptive model. // Interface to on-device adaptive model.
class Modeller { class Modeller {
public: public:
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class Status {
kInitializing = 0,
kDisabled = 1,
kGlobal = 2,
kPersonal = 3,
kMaxValue = kPersonal
};
// Modeller must outlive its observers. // Modeller must outlive its observers.
class Observer : public base::CheckedObserver { class Observer : public base::CheckedObserver {
public: public:
......
...@@ -118,7 +118,7 @@ ModellerImpl::~ModellerImpl() = default; ...@@ -118,7 +118,7 @@ ModellerImpl::~ModellerImpl() = default;
void ModellerImpl::AddObserver(Modeller::Observer* observer) { void ModellerImpl::AddObserver(Modeller::Observer* observer) {
DCHECK(observer); DCHECK(observer);
observers_.AddObserver(observer); observers_.AddObserver(observer);
if (model_status_ != Modeller::Status::kInitializing) { if (is_modeller_enabled_.has_value()) {
NotifyObserverInitStatus(*observer); NotifyObserverInitStatus(*observer);
} }
} }
...@@ -129,7 +129,7 @@ void ModellerImpl::RemoveObserver(Modeller::Observer* observer) { ...@@ -129,7 +129,7 @@ void ModellerImpl::RemoveObserver(Modeller::Observer* observer) {
} }
void ModellerImpl::OnAmbientLightUpdated(int lux) { void ModellerImpl::OnAmbientLightUpdated(int lux) {
if (model_status_ == Status::kDisabled) if (is_modeller_enabled_.has_value() && !*is_modeller_enabled_)
return; return;
const AmbientLightSample sample = {lux, tick_clock_->NowTicks()}; const AmbientLightSample sample = {lux, tick_clock_->NowTicks()};
...@@ -137,8 +137,7 @@ void ModellerImpl::OnAmbientLightUpdated(int lux) { ...@@ -137,8 +137,7 @@ void ModellerImpl::OnAmbientLightUpdated(int lux) {
} }
void ModellerImpl::OnAlsReaderInitialized(AlsReader::AlsInitStatus status) { void ModellerImpl::OnAlsReaderInitialized(AlsReader::AlsInitStatus status) {
if (als_init_status_.has_value()) DCHECK(!als_init_status_);
return;
als_init_status_ = status; als_init_status_ = status;
...@@ -146,8 +145,7 @@ void ModellerImpl::OnAlsReaderInitialized(AlsReader::AlsInitStatus status) { ...@@ -146,8 +145,7 @@ void ModellerImpl::OnAlsReaderInitialized(AlsReader::AlsInitStatus status) {
} }
void ModellerImpl::OnBrightnessMonitorInitialized(bool success) { void ModellerImpl::OnBrightnessMonitorInitialized(bool success) {
if (brightness_monitor_success_.has_value()) DCHECK(!brightness_monitor_success_.has_value());
return;
brightness_monitor_success_ = success; brightness_monitor_success_ = success;
HandleStatusUpdate(); HandleStatusUpdate();
...@@ -155,7 +153,7 @@ void ModellerImpl::OnBrightnessMonitorInitialized(bool success) { ...@@ -155,7 +153,7 @@ void ModellerImpl::OnBrightnessMonitorInitialized(bool success) {
void ModellerImpl::OnUserBrightnessChanged(double old_brightness_percent, void ModellerImpl::OnUserBrightnessChanged(double old_brightness_percent,
double new_brightness_percent) { double new_brightness_percent) {
if (model_status_ == Status::kDisabled) if (is_modeller_enabled_.has_value() && !*is_modeller_enabled_)
return; return;
// We don't add any training data if there is no ambient light sample. // We don't add any training data if there is no ambient light sample.
...@@ -236,13 +234,18 @@ ModellerImpl::ModellerImpl( ...@@ -236,13 +234,18 @@ ModellerImpl::ModellerImpl(
DCHECK(user_activity_detector); DCHECK(user_activity_detector);
if (!profile) { if (!profile) {
model_status_ = Status::kDisabled; is_modeller_enabled_ = false;
return;
}
if (!trainer_->HasValidConfiguration()) {
is_modeller_enabled_ = false;
return; return;
} }
curve_path_ = GetCurvePathFromProfile(profile); curve_path_ = GetCurvePathFromProfile(profile);
if (curve_path_.empty()) { if (curve_path_.empty()) {
model_status_ = Status::kDisabled; is_modeller_enabled_ = false;
return; return;
} }
...@@ -271,16 +274,16 @@ base::FilePath ModellerImpl::GetCurvePathFromProfile(Profile* profile) const { ...@@ -271,16 +274,16 @@ base::FilePath ModellerImpl::GetCurvePathFromProfile(Profile* profile) const {
} }
void ModellerImpl::HandleStatusUpdate() { void ModellerImpl::HandleStatusUpdate() {
if (model_status_ != Modeller::Status::kInitializing) if (is_modeller_enabled_.has_value())
return; return;
if (!als_init_status_.has_value()) { if (!als_init_status_)
return; return;
}
const bool als_success = const bool als_success =
als_init_status_.value() == AlsReader::AlsInitStatus::kSuccess; *als_init_status_ == AlsReader::AlsInitStatus::kSuccess;
if (!als_success) { if (!als_success) {
model_status_ = Modeller::Status::kDisabled; is_modeller_enabled_ = false;
OnInitializationComplete(); OnInitializationComplete();
return; return;
} }
...@@ -288,8 +291,8 @@ void ModellerImpl::HandleStatusUpdate() { ...@@ -288,8 +291,8 @@ void ModellerImpl::HandleStatusUpdate() {
if (!brightness_monitor_success_.has_value()) { if (!brightness_monitor_success_.has_value()) {
return; return;
} }
if (!brightness_monitor_success_.value()) { if (!*brightness_monitor_success_) {
model_status_ = Modeller::Status::kDisabled; is_modeller_enabled_ = false;
OnInitializationComplete(); OnInitializationComplete();
return; return;
} }
...@@ -309,28 +312,31 @@ void ModellerImpl::OnInitializationComplete() { ...@@ -309,28 +312,31 @@ void ModellerImpl::OnInitializationComplete() {
} }
void ModellerImpl::NotifyObserverInitStatus(Modeller::Observer& observer) { void ModellerImpl::NotifyObserverInitStatus(Modeller::Observer& observer) {
DCHECK_NE(model_status_, Status::kInitializing); DCHECK(is_modeller_enabled_.has_value());
if (model_status_ == Status::kDisabled) { if (!*is_modeller_enabled_) {
observer.OnModelInitialized(base::nullopt, base::nullopt); observer.OnModelInitialized(base::nullopt, base::nullopt);
} else { } else {
observer.OnModelInitialized(global_curve_, personal_curve_); base::Optional<MonotoneCubicSpline> personal_curve;
if (has_initial_personal_curve_)
personal_curve.emplace(trainer_->GetCurrentCurve());
observer.OnModelInitialized(global_curve_, personal_curve);
} }
} }
void ModellerImpl::OnCurveLoadedFromDisk( void ModellerImpl::OnCurveLoadedFromDisk(
const base::Optional<MonotoneCubicSpline>& curve) { const base::Optional<MonotoneCubicSpline>& curve) {
if (!curve.has_value()) { const bool is_personal_curve_valid =
model_status_ = Status::kGlobal; trainer_->SetInitialCurves(global_curve_, curve ? *curve : global_curve_);
} else {
personal_curve_.emplace(curve.value()); has_initial_personal_curve_ = is_personal_curve_valid && curve;
model_status_ = Status::kPersonal; DCHECK(trainer_->GetGlobalCurve() == global_curve_);
} DCHECK(trainer_->GetCurrentCurve() ==
(has_initial_personal_curve_ ? *curve : global_curve_));
is_modeller_enabled_ = true;
OnInitializationComplete(); OnInitializationComplete();
trainer_->SetInitialCurves(global_curve_, model_status_ == Status::kGlobal
? global_curve_
: personal_curve_.value());
ScheduleTrainerStart(); ScheduleTrainerStart();
} }
...@@ -356,7 +362,6 @@ void ModellerImpl::StartTraining() { ...@@ -356,7 +362,6 @@ void ModellerImpl::StartTraining() {
} }
void ModellerImpl::OnTrainingFinished(const MonotoneCubicSpline& curve) { void ModellerImpl::OnTrainingFinished(const MonotoneCubicSpline& curve) {
personal_curve_.emplace(curve);
for (auto& observer : observers_) for (auto& observer : observers_)
observer.OnModelTrained(curve); observer.OnModelTrained(curve);
......
...@@ -188,12 +188,18 @@ class ModellerImpl : public Modeller, ...@@ -188,12 +188,18 @@ class ModellerImpl : public Modeller,
base::Optional<AlsReader::AlsInitStatus> als_init_status_; base::Optional<AlsReader::AlsInitStatus> als_init_status_;
base::Optional<bool> brightness_monitor_success_; base::Optional<bool> brightness_monitor_success_;
Status model_status_ = Status::kInitializing;
// Whether this modeller has initialized successfully, including connecting
// to AlsReader, BrightnessMonitor and loading a Trainer.
// Initially has no value. Guaranteed to have a value after the completion of
// |OnCurveLoadedFromDisk|.
base::Optional<bool> is_modeller_enabled_;
base::FilePath curve_path_; base::FilePath curve_path_;
// Latest personal curve, either loaded from disk or trained. // True if a personal curve was successfully loaded from disk and passed to
base::Optional<MonotoneCubicSpline> personal_curve_; // Trainer and Trainer reported it was valid.
bool has_initial_personal_curve_ = false;
// Global curve constructed from predefined params. // Global curve constructed from predefined params.
const MonotoneCubicSpline global_curve_; const MonotoneCubicSpline global_curve_;
......
...@@ -20,13 +20,37 @@ struct TrainingDataPoint { ...@@ -20,13 +20,37 @@ struct TrainingDataPoint {
}; };
// Interface to train an on-device adaptive brightness curve. // Interface to train an on-device adaptive brightness curve.
// User should call |HasValidConfiguration| first. If it returns true, then user
// should call |SetInitialCurves| before calling other methods.
class Trainer { class Trainer {
public: public:
virtual ~Trainer() = default; virtual ~Trainer() = default;
virtual void SetInitialCurves(const MonotoneCubicSpline& global_curve, // Returns whether trainer has been configured properly, i.e. if all params
// are set up. It is an error to call other methods unless
// |HasValidConfiguration| returns true.
virtual bool HasValidConfiguration() const = 0;
// Initializes this trainer with the specified default global curve and
// initial current curve (the personal curve). This should only be called if
// trainer |HasValidConfiguration| returns true.
// Returns true if |current_curve| is valid, i.e. satisfying constraints (e.g.
// slope). If |current_curve| is invalid, |global_curve| will be used in its
// place. The caller has an option to reset these curves.
virtual bool SetInitialCurves(const MonotoneCubicSpline& global_curve,
const MonotoneCubicSpline& current_curve) = 0; const MonotoneCubicSpline& current_curve) = 0;
// Returns the global curve. This should only be called if trainer
// |HasValidConfiguration| returns true and after |SetInitialCurves| is
// called.
virtual MonotoneCubicSpline GetGlobalCurve() const = 0;
// Returns the curve currently used as personal curve. It could be the same as
// the global curve. This should only be called if trainer
// |HasValidConfiguration| returns true and after |SetInitialCurves| is
// called.
virtual MonotoneCubicSpline GetCurrentCurve() const = 0;
// Updates current curve stored in trainer with |data|. This function should // Updates current curve stored in trainer with |data|. This function should
// only be called after |SetInitialCurves|. // only be called after |SetInitialCurves|.
virtual MonotoneCubicSpline Train( virtual MonotoneCubicSpline Train(
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment