Files
ortools-clone/ortools/util/piecewise_linear_function.cc
2024-01-04 13:43:15 +01:00

804 lines
28 KiB
C++

// Copyright 2010-2024 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "ortools/util/piecewise_linear_function.h"
#include <algorithm>
#include <functional>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "absl/container/btree_set.h"
#include "absl/strings/str_format.h"
#include "ortools/base/logging.h"
#include "ortools/util/saturated_arithmetic.h"
namespace operations_research {
namespace {
// If the x value is in the function's domain, it returns the index of the
// segment it belongs to. The segments are closed to the left and open to
// the right, hence if x is a common endpoint of two segments, it returns
// the index of the right segment. If the x value is not in the function's
// domain, it returns the index of the previous segment or kNotFound if x
// is before the first segment's start.
int FindSegmentIndex(const std::vector<PiecewiseSegment>& segments, int64_t x) {
if (segments.empty() || segments.front().start_x() > x) {
return PiecewiseLinearFunction::kNotFound;
}
// Returns an iterator pointing to the first segment whose the x coordinate
// of its start point which compares greater than the x value.
std::vector<PiecewiseSegment>::const_iterator position = std::upper_bound(
segments.begin(), segments.end(), x, PiecewiseSegment::FindComparator);
if (position == segments.end()) {
return segments.size() - 1;
}
position -= position->start_x() > x ? 1 : 0;
return position - segments.begin();
}
inline bool IsAtBounds(int64_t value) {
return value == kint64min || value == kint64max;
}
inline bool PointInsideRange(int64_t point, int64_t range_start,
int64_t range_end) {
return range_start <= point && range_end >= point;
}
// Checks whether two segments form a convex pair, i.e. they are continuous and
// the slope of the right is bigger than the slope of the left.
inline bool FormConvexPair(const PiecewiseSegment& left,
const PiecewiseSegment& right) {
return right.slope() >= left.slope() && right.start_x() == left.end_x() &&
right.start_y() == left.end_y();
}
uint64_t UnsignedCapAdd(uint64_t left, uint64_t right) {
return left > kuint64max - right ? kuint64max : left + right;
}
uint64_t UnsignedCapProd(uint64_t left, uint64_t right) {
if (right == 0) return 0;
if (left > kuint64max / right) return kuint64max;
return left * right;
}
} // namespace
PiecewiseSegment::PiecewiseSegment(int64_t point_x, int64_t point_y,
int64_t slope, int64_t other_point_x)
: slope_(slope), reference_x_(point_x), reference_y_(point_y) {
start_x_ = std::min(point_x, other_point_x);
end_x_ = std::max(point_x, other_point_x);
intersection_y_ =
reference_x_ < 0 ? SafeValuePostReference(0) : SafeValuePreReference(0);
}
int64_t PiecewiseSegment::Value(int64_t x) const {
CHECK_GE(x, start_x_);
CHECK_LE(x, end_x_);
const int64_t span_x = CapSub(x, reference_x_);
if (span_x == kint64max) {
return SafeValuePostReference(x);
}
if (span_x == kint64min) {
return SafeValuePreReference(x);
}
const int64_t span_y = CapProd(slope_, span_x);
if (IsAtBounds(span_y)) {
if (span_x >= 0) {
return SafeValuePostReference(x);
} else {
return SafeValuePreReference(x);
}
}
const int64_t value = CapAdd(reference_y_, span_y);
if (IsAtBounds(value)) {
if (span_x >= 0) {
return SafeValuePostReference(x);
} else {
return SafeValuePreReference(x);
}
} else {
return value;
}
}
int64_t PiecewiseSegment::SafeValuePostReference(int64_t x) const {
DCHECK_GE(x, reference_x_);
const uint64_t span_x = static_cast<uint64_t>(x) - reference_x_;
if (span_x == 0) {
return reference_y_;
}
if (slope_ == 0) {
// Zero slope segment.
return reference_y_;
} else if (slope_ > 0) {
// Positive slope segment.
const uint64_t span_y = UnsignedCapProd(span_x, slope_);
if (reference_y_ == 0) {
return span_y > kint64max ? kint64max : span_y;
} else if (reference_y_ > 0) {
const uint64_t unsigned_sum = UnsignedCapAdd(reference_y_, span_y);
return unsigned_sum > kint64max ? kint64max
: static_cast<int64_t>(unsigned_sum);
} else {
const uint64_t opp_reference_y = -static_cast<uint64_t>(reference_y_);
if (span_y >= opp_reference_y) {
return span_y - opp_reference_y > kint64max
? kint64max
: static_cast<int64_t>(span_y - opp_reference_y);
} else {
return opp_reference_y - span_y > static_cast<uint64_t>(kint64max) + 1
? kint64min
: -static_cast<int64_t>(opp_reference_y - span_y);
}
}
} else {
// Negative slope segment.
const uint64_t span_y = UnsignedCapProd(span_x, -slope_);
if (reference_y_ == 0) {
return span_y > kint64max ? kint64min : -static_cast<int64_t>(span_y);
} else if (reference_y_ < 0) {
const uint64_t opp_reference_y = -static_cast<uint64_t>(reference_y_);
const uint64_t opp_unsigned_sum = UnsignedCapAdd(opp_reference_y, span_y);
return opp_unsigned_sum > kint64max
? kint64min
: -static_cast<int64_t>(opp_unsigned_sum);
} else {
if (reference_y_ >= span_y) {
return reference_y_ - span_y > kint64max
? kint64max
: static_cast<int64_t>(reference_y_ - span_y);
} else {
return span_y - reference_y_ > static_cast<uint64_t>(kint64max) + 1
? kint64min
: -static_cast<int64_t>(span_y - reference_y_);
}
}
}
}
int64_t PiecewiseSegment::SafeValuePreReference(int64_t x) const {
DCHECK_LE(x, reference_x_);
const uint64_t span_x = static_cast<uint64_t>(reference_x_) - x;
if (slope_ == 0) {
// Zero slope segment.
return reference_y_;
} else if (slope_ > 0) {
// Positive slope segment.
const uint64_t span_y = UnsignedCapProd(span_x, slope_);
if (reference_y_ == 0) {
return span_y > kint64max ? kint64min : -static_cast<int64_t>(span_y);
} else if (reference_y_ > 0) {
if (reference_y_ >= span_y) {
return reference_y_ - span_y > kint64max
? kint64max
: static_cast<int64_t>(reference_y_ - span_y);
} else {
return span_y - reference_y_ > static_cast<uint64_t>(kint64max) + 1
? kint64min
: -static_cast<uint64_t>(span_y - reference_y_);
}
} else {
const uint64_t opp_reference_y = -static_cast<uint64_t>(reference_y_);
const uint64_t opp_unsigned_sum = UnsignedCapAdd(opp_reference_y, span_y);
return opp_unsigned_sum > kint64max
? kint64min
: -static_cast<uint64_t>(opp_unsigned_sum);
}
} else {
// Negative slope segment.
const uint64_t span_y = UnsignedCapProd(span_x, -slope_);
if (reference_y_ == 0) {
return span_y > kint64max ? kint64max : span_y;
} else if (reference_y_ < 0) {
const uint64_t opp_reference_y = -static_cast<uint64_t>(reference_y_);
if (span_y >= opp_reference_y) {
return span_y - opp_reference_y > kint64max
? kint64max
: static_cast<int64_t>(span_y - opp_reference_y);
} else {
return opp_reference_y - span_y > static_cast<uint64_t>(kint64max) + 1
? kint64min
: -static_cast<uint64_t>(opp_reference_y - span_y);
}
} else {
const uint64_t unsigned_sum = UnsignedCapAdd(reference_y_, span_y);
return unsigned_sum > kint64max ? kint64max
: static_cast<int64_t>(unsigned_sum);
}
}
}
bool PiecewiseSegment::SortComparator(const PiecewiseSegment& segment1,
const PiecewiseSegment& segment2) {
return segment1.start_x_ < segment2.start_x_;
}
bool PiecewiseSegment::FindComparator(int64_t point,
const PiecewiseSegment& segment) {
return point == kint64min || point < segment.start_x();
}
void PiecewiseSegment::ExpandEnd(int64_t end_x) {
end_x_ = std::max(end_x_, end_x);
}
void PiecewiseSegment::AddConstantToX(int64_t constant) {
if (IsAtBounds(CapAdd(reference_x_, constant))) {
LOG(ERROR) << "Segment Overflow: " << DebugString();
return;
}
start_x_ = CapAdd(start_x_, constant);
end_x_ = CapAdd(end_x_, constant);
reference_x_ = CapAdd(reference_x_, constant);
}
void PiecewiseSegment::AddConstantToY(int64_t constant) {
if (IsAtBounds(CapAdd(reference_y_, constant))) {
LOG(ERROR) << "Segment Overflow: " << DebugString();
return;
}
reference_y_ = CapAdd(reference_y_, constant);
}
std::string PiecewiseSegment::DebugString() const {
std::string result = absl::StrFormat(
"PiecewiseSegment(<start: (%d, %d), end: (%d, %d), "
"reference: (%d, %d), slope = %d>)",
start_x_, Value(start_x_), end_x_, Value(end_x_), reference_x_,
reference_y_, slope_);
return result;
}
const int PiecewiseLinearFunction::kNotFound = -1;
PiecewiseLinearFunction::PiecewiseLinearFunction(
std::vector<PiecewiseSegment> segments)
: is_modified_(true),
is_convex_(false),
is_non_decreasing_(false),
is_non_increasing_(false) {
// Sort the segments in ascending order of start.
std::sort(segments.begin(), segments.end(), PiecewiseSegment::SortComparator);
// Check for overlapping segments.
for (int i = 0; i < segments.size() - 1; ++i) {
if (segments[i].end_x() > segments[i + 1].start_x()) {
LOG(FATAL) << "Overlapping segments: " << segments[i].DebugString()
<< " & " << segments[i + 1].DebugString();
}
}
// Construct the piecewise linear function.
for (const auto& segment : segments) {
InsertSegment(segment);
}
}
PiecewiseLinearFunction* PiecewiseLinearFunction::CreatePiecewiseLinearFunction(
std::vector<int64_t> points_x, std::vector<int64_t> points_y,
std::vector<int64_t> slopes, std::vector<int64_t> other_points_x) {
CHECK_EQ(points_x.size(), points_y.size());
CHECK_EQ(points_x.size(), other_points_x.size());
CHECK_EQ(points_x.size(), slopes.size());
CHECK_GT(points_x.size(), 0);
std::vector<PiecewiseSegment> segments;
for (int i = 0; i < points_x.size(); ++i) {
segments.push_back(PiecewiseSegment(points_x[i], points_y[i], slopes[i],
other_points_x[i]));
}
return new PiecewiseLinearFunction(std::move(segments));
}
PiecewiseLinearFunction* PiecewiseLinearFunction::CreateStepFunction(
std::vector<int64_t> points_x, std::vector<int64_t> points_y,
std::vector<int64_t> other_points_x) {
CHECK_EQ(points_x.size(), points_y.size());
CHECK_EQ(points_x.size(), other_points_x.size());
CHECK_GT(points_x.size(), 0);
std::vector<PiecewiseSegment> segments;
for (int i = 0; i < points_x.size(); ++i) {
segments.push_back(
PiecewiseSegment(points_x[i], points_y[i], 0, other_points_x[i]));
}
return new PiecewiseLinearFunction(std::move(segments));
}
PiecewiseLinearFunction* PiecewiseLinearFunction::CreateFullDomainFunction(
int64_t initial_level, std::vector<int64_t> points_x,
std::vector<int64_t> slopes) {
CHECK_EQ(points_x.size(), slopes.size() - 1);
CHECK_GT(points_x.size(), 0);
int64_t level = initial_level;
std::vector<PiecewiseSegment> segments;
PiecewiseSegment segment =
PiecewiseSegment(points_x[0], level, slopes[0], kint64min);
segments.push_back(segment);
level = segment.Value(points_x[0]);
for (int i = 1; i < points_x.size(); ++i) {
PiecewiseSegment segment =
PiecewiseSegment(points_x[i - 1], level, slopes[i], points_x[i]);
segments.push_back(segment);
level = segment.Value(points_x[i]);
}
segments.push_back(
PiecewiseSegment(points_x.back(), level, slopes.back(), kint64max));
return new PiecewiseLinearFunction(std::move(segments));
}
PiecewiseLinearFunction* PiecewiseLinearFunction::CreateOneSegmentFunction(
int64_t point_x, int64_t point_y, int64_t slope, int64_t other_point_x) {
// Visual studio 2013: We cannot inline the vector in the
// PiecewiseLinearFunction ctor.
std::vector<PiecewiseSegment> segments = {
PiecewiseSegment(point_x, point_y, slope, other_point_x)};
return new PiecewiseLinearFunction(std::move(segments));
}
PiecewiseLinearFunction* PiecewiseLinearFunction::CreateRightRayFunction(
int64_t point_x, int64_t point_y, int64_t slope) {
std::vector<PiecewiseSegment> segments = {
PiecewiseSegment(point_x, point_y, slope, kint64max)};
return new PiecewiseLinearFunction(std::move(segments));
}
PiecewiseLinearFunction* PiecewiseLinearFunction::CreateLeftRayFunction(
int64_t point_x, int64_t point_y, int64_t slope) {
std::vector<PiecewiseSegment> segments = {
PiecewiseSegment(point_x, point_y, slope, kint64min)};
return new PiecewiseLinearFunction(std::move(segments));
}
PiecewiseLinearFunction* PiecewiseLinearFunction::CreateFixedChargeFunction(
int64_t slope, int64_t value) {
std::vector<PiecewiseSegment> segments = {
PiecewiseSegment(0, 0, 0, kint64min),
PiecewiseSegment(0, value, slope, kint64max)};
CHECK_GE(slope, 0);
CHECK_GE(value, 0);
return new PiecewiseLinearFunction(std::move(segments));
}
PiecewiseLinearFunction* PiecewiseLinearFunction::CreateEarlyTardyFunction(
int64_t reference, int64_t earliness_slope, int64_t tardiness_slope) {
std::vector<PiecewiseSegment> segments = {
PiecewiseSegment(reference, 0, -earliness_slope, kint64min),
PiecewiseSegment(reference, 0, tardiness_slope, kint64max)};
CHECK_GE(earliness_slope, 0);
CHECK_GE(tardiness_slope, 0);
return new PiecewiseLinearFunction(std::move(segments));
}
PiecewiseLinearFunction*
PiecewiseLinearFunction::CreateEarlyTardyFunctionWithSlack(
int64_t early_slack, int64_t late_slack, int64_t earliness_slope,
int64_t tardiness_slope) {
std::vector<PiecewiseSegment> segments = {
PiecewiseSegment(early_slack, 0, -earliness_slope, kint64min),
PiecewiseSegment(early_slack, 0, 0, late_slack),
PiecewiseSegment(late_slack, 0, tardiness_slope, kint64max)};
CHECK_GE(earliness_slope, 0);
CHECK_GE(tardiness_slope, 0);
return new PiecewiseLinearFunction(std::move(segments));
}
bool PiecewiseLinearFunction::InDomain(int64_t x) const {
int index = FindSegmentIndex(segments_, x);
if (index == kNotFound) {
return false;
}
if (segments_[index].end_x() < x) {
return false;
}
return true;
}
bool PiecewiseLinearFunction::IsConvex() const {
const_cast<PiecewiseLinearFunction*>(this)->UpdateStatus();
return is_convex_;
}
bool PiecewiseLinearFunction::IsNonDecreasing() const {
const_cast<PiecewiseLinearFunction*>(this)->UpdateStatus();
return is_non_decreasing_;
}
bool PiecewiseLinearFunction::IsNonIncreasing() const {
const_cast<PiecewiseLinearFunction*>(this)->UpdateStatus();
return is_non_increasing_;
}
int64_t PiecewiseLinearFunction::Value(int64_t x) const {
if (!InDomain(x)) {
// TODO(user): Allow the user to specify the
// undefined value and use kint64max as the default.
return kint64max;
}
const int index = FindSegmentIndex(segments_, x);
return segments_[index].Value(x);
}
int64_t PiecewiseLinearFunction::GetMaximum(int64_t range_start,
int64_t range_end) const {
if (IsNonDecreasing() && InDomain(range_end)) {
return Value(range_end);
} else if (IsNonIncreasing() && InDomain(range_start)) {
return Value(range_start);
}
int start_segment = -1;
int end_segment = -1;
if (!FindSegmentIndicesFromRange(range_start, range_end, &start_segment,
&end_segment)) {
return kint64max;
}
CHECK_GE(end_segment, start_segment);
int64_t range_maximum = kint64min;
if (InDomain(range_start)) {
range_maximum = std::max(Value(range_start), range_maximum);
}
if (InDomain(range_end)) {
range_maximum = std::max(Value(range_end), range_maximum);
}
for (int i = std::max(0, start_segment); i <= end_segment; ++i) {
if (PointInsideRange(segments_[i].start_x(), range_start, range_end)) {
range_maximum = std::max(range_maximum, segments_[i].start_y());
}
if (PointInsideRange(segments_[i].end_x(), range_start, range_end)) {
range_maximum = std::max(range_maximum, segments_[i].end_y());
}
}
return range_maximum;
}
int64_t PiecewiseLinearFunction::GetMinimum(int64_t range_start,
int64_t range_end) const {
if (IsNonDecreasing() && InDomain(range_start)) {
return Value(range_start);
} else if (IsNonIncreasing() && InDomain(range_end)) {
return Value(range_end);
}
int start_segment = -1;
int end_segment = -1;
if (!FindSegmentIndicesFromRange(range_start, range_end, &start_segment,
&end_segment)) {
return kint64max;
}
CHECK_GE(end_segment, start_segment);
int64_t range_minimum = kint64max;
if (InDomain(range_start)) {
range_minimum = std::min(Value(range_start), range_minimum);
}
if (InDomain(range_end)) {
range_minimum = std::min(Value(range_end), range_minimum);
}
for (int i = std::max(0, start_segment); i <= end_segment; ++i) {
if (PointInsideRange(segments_[i].start_x(), range_start, range_end)) {
range_minimum = std::min(range_minimum, segments_[i].start_y());
}
if (PointInsideRange(segments_[i].end_x(), range_start, range_end)) {
range_minimum = std::min(range_minimum, segments_[i].end_y());
}
}
return range_minimum;
}
int64_t PiecewiseLinearFunction::GetMaximum() const {
return GetMaximum(segments_.front().start_x(), segments_.back().end_x());
}
int64_t PiecewiseLinearFunction::GetMinimum() const {
return GetMinimum(segments_.front().start_x(), segments_.back().end_x());
}
std::pair<int64_t, int64_t>
PiecewiseLinearFunction::GetSmallestRangeGreaterThanValue(int64_t range_start,
int64_t range_end,
int64_t value) const {
return GetSmallestRangeInValueRange(range_start, range_end, value, kint64max);
}
std::pair<int64_t, int64_t>
PiecewiseLinearFunction::GetSmallestRangeLessThanValue(int64_t range_start,
int64_t range_end,
int64_t value) const {
return GetSmallestRangeInValueRange(range_start, range_end, kint64min, value);
}
namespace {
std::pair<int64_t, int64_t> ComputeXFromY(int64_t start_x, int64_t start_y,
int64_t slope, int64_t y) {
DCHECK_NE(slope, 0);
const int64_t delta_y = CapSub(y, start_y);
const int64_t delta_x = delta_y / slope;
if ((delta_y >= 0 && slope >= 0) || (delta_y <= 0 && slope <= 0)) {
const int64_t delta_x_down = delta_x;
const int64_t delta_x_up = delta_y % slope == 0 ? delta_x : delta_x + 1;
return {delta_x_down + start_x, delta_x_up + start_x};
} else {
const int64_t delta_x_down = delta_y % slope == 0 ? delta_x : delta_x - 1;
const int64_t delta_x_up = -(-delta_y / slope);
return {delta_x_down + start_x, delta_x_up + start_x};
}
}
std::pair<int64_t, int64_t> GetRangeInValueRange(int64_t start_x, int64_t end_x,
int64_t start_y, int64_t end_y,
int64_t slope,
int64_t value_min,
int64_t value_max) {
if ((start_y > value_max && end_y > value_max) ||
(start_y < value_min && end_y < value_min)) {
return {kint64max, kint64min};
}
std::pair<int64_t, int64_t> x_range_max = {kint64max, kint64min};
if (start_y <= value_max && end_y <= value_max) {
x_range_max = {start_x, end_x};
} else if (start_y <= value_max || end_y <= value_max) {
const auto x = start_x == kint64min
? ComputeXFromY(end_x, end_y, slope, value_max)
: ComputeXFromY(start_x, start_y, slope, value_max);
if (end_y <= value_max) {
x_range_max = {x.second, end_x};
} else {
x_range_max = {start_x, x.first};
}
}
std::pair<int64_t, int64_t> x_range_min = {kint64max, kint64min};
if (start_y >= value_min && end_y >= value_min) {
x_range_min = {start_x, end_x};
} else if (start_y >= value_min || end_y >= value_min) {
const auto x = start_x == kint64min
? ComputeXFromY(end_x, end_y, slope, value_min)
: ComputeXFromY(start_x, start_y, slope, value_min);
if (end_y >= value_min) {
x_range_min = {x.second, end_x};
} else {
x_range_min = {start_x, x.first};
}
}
if (x_range_min.first > x_range_max.second ||
x_range_max.first > x_range_min.second) {
return {kint64max, kint64min};
}
return {std::max(x_range_min.first, x_range_max.first),
std::min(x_range_min.second, x_range_max.second)};
}
} // namespace
std::pair<int64_t, int64_t>
PiecewiseLinearFunction::GetSmallestRangeInValueRange(int64_t range_start,
int64_t range_end,
int64_t value_min,
int64_t value_max) const {
int64_t reduced_range_start = kint64max;
int64_t reduced_range_end = kint64min;
int start_segment = -1;
int end_segment = -1;
if (!FindSegmentIndicesFromRange(range_start, range_end, &start_segment,
&end_segment)) {
return {reduced_range_start, reduced_range_end};
}
for (int i = std::max(0, start_segment); i <= end_segment; ++i) {
const auto& segment = segments_[i];
const int64_t start_x = std::max(range_start, segment.start_x());
const int64_t end_x = std::min(range_end, segment.end_x());
const int64_t start_y = segment.Value(start_x);
const int64_t end_y = segment.Value(end_x);
const std::pair<int64_t, int64_t> range = GetRangeInValueRange(
start_x, end_x, start_y, end_y, segment.slope(), value_min, value_max);
reduced_range_start = std::min(reduced_range_start, range.first);
reduced_range_end = std::max(reduced_range_end, range.second);
}
return {reduced_range_start, reduced_range_end};
}
void PiecewiseLinearFunction::AddConstantToX(int64_t constant) {
is_modified_ = true;
for (int i = 0; i < segments_.size(); ++i) {
segments_[i].AddConstantToX(constant);
}
}
void PiecewiseLinearFunction::AddConstantToY(int64_t constant) {
is_modified_ = true;
for (int i = 0; i < segments_.size(); ++i) {
segments_[i].AddConstantToY(constant);
}
}
void PiecewiseLinearFunction::Add(const PiecewiseLinearFunction& other) {
Operation(other, [](int64_t a, int64_t b) { return CapAdd(a, b); });
}
void PiecewiseLinearFunction::Subtract(const PiecewiseLinearFunction& other) {
Operation(other, [](int64_t a, int64_t b) { return CapSub(a, b); });
}
std::vector<PiecewiseLinearFunction*>
PiecewiseLinearFunction::DecomposeToConvexFunctions() const {
CHECK_GE(segments_.size(), 1);
if (IsConvex()) {
return {new PiecewiseLinearFunction(segments_)};
}
std::vector<PiecewiseLinearFunction*> convex_functions;
std::vector<PiecewiseSegment> convex_segments;
for (const PiecewiseSegment& segment : segments_) {
if (convex_segments.empty()) {
convex_segments.push_back(segment);
continue;
}
const PiecewiseSegment& last = convex_segments.back();
if (FormConvexPair(last, segment)) {
// The segment belongs to the convex sub-function formulated up to now.
convex_segments.push_back(segment);
} else {
convex_functions.push_back(new PiecewiseLinearFunction(convex_segments));
convex_segments.clear();
convex_segments.push_back(segment);
}
}
if (!convex_segments.empty()) {
convex_functions.push_back(
new PiecewiseLinearFunction(std::move(convex_segments)));
}
return convex_functions;
}
std::string PiecewiseLinearFunction::DebugString() const {
std::string result = "PiecewiseLinearFunction(";
for (int i = 0; i < segments_.size(); ++i) {
result.append(segments_[i].DebugString());
result.append(" ");
}
return result;
}
void PiecewiseLinearFunction::InsertSegment(const PiecewiseSegment& segment) {
is_modified_ = true;
// No intersection.
if (segments_.empty() || segments_.back().end_x() < segment.start_x()) {
segments_.push_back(segment);
return;
}
// Common endpoint.
if (segments_.back().end_x() == segment.start_x()) {
if (segments_.back().end_y() == segment.start_y() &&
segments_.back().slope() == segment.slope()) {
segments_.back().ExpandEnd(segment.end_x());
return;
}
segments_.push_back(segment);
}
}
void PiecewiseLinearFunction::Operation(
const PiecewiseLinearFunction& other,
const std::function<int64_t(int64_t, int64_t)>& operation) {
is_modified_ = true;
std::vector<PiecewiseSegment> own_segments;
const std::vector<PiecewiseSegment>& other_segments = other.segments();
own_segments.swap(segments_);
absl::btree_set<int64_t> start_x_points;
for (int i = 0; i < own_segments.size(); ++i) {
start_x_points.insert(own_segments[i].start_x());
}
for (int i = 0; i < other_segments.size(); ++i) {
start_x_points.insert(other_segments[i].start_x());
}
for (int64_t start_x : start_x_points) {
const int own_index = FindSegmentIndex(own_segments, start_x);
const int other_index = FindSegmentIndex(other_segments, start_x);
if (own_index >= 0 && other_index >= 0) {
const PiecewiseSegment& own_segment = own_segments[own_index];
const PiecewiseSegment& other_segment = other_segments[other_index];
const int64_t end_x =
std::min(own_segment.end_x(), other_segment.end_x());
const int64_t start_y =
operation(own_segment.Value(start_x), other_segment.Value(start_x));
const int64_t end_y =
operation(own_segment.Value(end_x), other_segment.Value(end_x));
const int64_t slope =
operation(own_segment.slope(), other_segment.slope());
int64_t point_x, point_y, other_point_x;
if (IsAtBounds(start_y)) {
point_x = end_x;
point_y = end_y;
other_point_x = start_x;
} else {
point_x = start_x;
point_y = start_y;
other_point_x = end_x;
}
InsertSegment(PiecewiseSegment(point_x, point_y, slope, other_point_x));
}
}
}
bool PiecewiseLinearFunction::FindSegmentIndicesFromRange(
int64_t range_start, int64_t range_end, int* start_segment,
int* end_segment) const {
*start_segment = FindSegmentIndex(segments_, range_start);
*end_segment = FindSegmentIndex(segments_, range_end);
if (*start_segment == *end_segment) {
if (*start_segment < 0) {
// Given range before function's domain start.
return false;
}
if (segments_[*start_segment].end_x() < range_start) {
// Given range in a hole of the function's domain.
return false;
}
}
return true;
}
bool PiecewiseLinearFunction::IsConvexInternal() const {
for (int i = 1; i < segments_.size(); ++i) {
if (!FormConvexPair(segments_[i - 1], segments_[i])) {
return false;
}
}
return true;
}
bool PiecewiseLinearFunction::IsNonDecreasingInternal() const {
int64_t value = kint64min;
for (const auto& segment : segments_) {
const int64_t start_y = segment.start_y();
const int64_t end_y = segment.end_y();
if (end_y < start_y || start_y < value) return false;
value = end_y;
}
return true;
}
bool PiecewiseLinearFunction::IsNonIncreasingInternal() const {
int64_t value = kint64max;
for (const auto& segment : segments_) {
const int64_t start_y = segment.start_y();
const int64_t end_y = segment.end_y();
if (end_y > start_y || start_y > value) return false;
value = end_y;
}
return true;
}
} // namespace operations_research