Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
piecewise_linear_function.cc
Go to the documentation of this file.
1// Copyright 2010-2024 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <algorithm>
17#include <functional>
18#include <set>
19#include <string>
20#include <utility>
21#include <vector>
22
23#include "absl/container/btree_set.h"
24#include "absl/strings/str_format.h"
27
28namespace operations_research {
29namespace {
30// If the x value is in the function's domain, it returns the index of the
31// segment it belongs to. The segments are closed to the left and open to
32// the right, hence if x is a common endpoint of two segments, it returns
33// the index of the right segment. If the x value is not in the function's
34// domain, it returns the index of the previous segment or kNotFound if x
35// is before the first segment's start.
36int FindSegmentIndex(const std::vector<PiecewiseSegment>& segments, int64_t x) {
37 if (segments.empty() || segments.front().start_x() > x) {
39 }
40
41 // Returns an iterator pointing to the first segment whose the x coordinate
42 // of its start point which compares greater than the x value.
43 std::vector<PiecewiseSegment>::const_iterator position = std::upper_bound(
44 segments.begin(), segments.end(), x, PiecewiseSegment::FindComparator);
45 if (position == segments.end()) {
46 return segments.size() - 1;
47 }
48 position -= position->start_x() > x ? 1 : 0;
49
50 return position - segments.begin();
51}
52
53inline bool IsAtBounds(int64_t value) {
54 return value == kint64min || value == kint64max;
55}
56
57inline bool PointInsideRange(int64_t point, int64_t range_start,
58 int64_t range_end) {
59 return range_start <= point && range_end >= point;
60}
61
62// Checks whether two segments form a convex pair, i.e. they are continuous and
63// the slope of the right is bigger than the slope of the left.
64inline bool FormConvexPair(const PiecewiseSegment& left,
65 const PiecewiseSegment& right) {
66 return right.slope() >= left.slope() && right.start_x() == left.end_x() &&
67 right.start_y() == left.end_y();
68}
69
70uint64_t UnsignedCapAdd(uint64_t left, uint64_t right) {
71 return left > kuint64max - right ? kuint64max : left + right;
72}
73
74uint64_t UnsignedCapProd(uint64_t left, uint64_t right) {
75 if (right == 0) return 0;
76 if (left > kuint64max / right) return kuint64max;
77 return left * right;
78}
79} // namespace
80
81PiecewiseSegment::PiecewiseSegment(int64_t point_x, int64_t point_y,
82 int64_t slope, int64_t other_point_x)
83 : slope_(slope), reference_x_(point_x), reference_y_(point_y) {
84 start_x_ = std::min(point_x, other_point_x);
85 end_x_ = std::max(point_x, other_point_x);
86 intersection_y_ =
87 reference_x_ < 0 ? SafeValuePostReference(0) : SafeValuePreReference(0);
88}
89
90int64_t PiecewiseSegment::Value(int64_t x) const {
91 CHECK_GE(x, start_x_);
92 CHECK_LE(x, end_x_);
93
94 const int64_t span_x = CapSub(x, reference_x_);
95
96 if (span_x == kint64max) {
97 return SafeValuePostReference(x);
98 }
99 if (span_x == kint64min) {
100 return SafeValuePreReference(x);
101 }
102
103 const int64_t span_y = CapProd(slope_, span_x);
104 if (IsAtBounds(span_y)) {
105 if (span_x >= 0) {
106 return SafeValuePostReference(x);
107 } else {
108 return SafeValuePreReference(x);
109 }
110 }
111
112 const int64_t value = CapAdd(reference_y_, span_y);
113 if (IsAtBounds(value)) {
114 if (span_x >= 0) {
115 return SafeValuePostReference(x);
116 } else {
117 return SafeValuePreReference(x);
118 }
119 } else {
120 return value;
121 }
122}
123
124int64_t PiecewiseSegment::SafeValuePostReference(int64_t x) const {
125 DCHECK_GE(x, reference_x_);
126 const uint64_t span_x = static_cast<uint64_t>(x) - reference_x_;
127 if (span_x == 0) {
128 return reference_y_;
129 }
130 if (slope_ == 0) {
131 // Zero slope segment.
132 return reference_y_;
133 } else if (slope_ > 0) {
134 // Positive slope segment.
135 const uint64_t span_y = UnsignedCapProd(span_x, slope_);
136 if (reference_y_ == 0) {
137 return span_y > kint64max ? kint64max : span_y;
138 } else if (reference_y_ > 0) {
139 const uint64_t unsigned_sum = UnsignedCapAdd(reference_y_, span_y);
140 return unsigned_sum > kint64max ? kint64max
141 : static_cast<int64_t>(unsigned_sum);
142 } else {
143 const uint64_t opp_reference_y = -static_cast<uint64_t>(reference_y_);
144 if (span_y >= opp_reference_y) {
145 return span_y - opp_reference_y > kint64max
146 ? kint64max
147 : static_cast<int64_t>(span_y - opp_reference_y);
148 } else {
149 return opp_reference_y - span_y > static_cast<uint64_t>(kint64max) + 1
150 ? kint64min
151 : -static_cast<int64_t>(opp_reference_y - span_y);
152 }
153 }
154 } else {
155 // Negative slope segment.
156 const uint64_t span_y = UnsignedCapProd(span_x, -slope_);
157 if (reference_y_ == 0) {
158 return span_y > kint64max ? kint64min : -static_cast<int64_t>(span_y);
159 } else if (reference_y_ < 0) {
160 const uint64_t opp_reference_y = -static_cast<uint64_t>(reference_y_);
161 const uint64_t opp_unsigned_sum = UnsignedCapAdd(opp_reference_y, span_y);
162 return opp_unsigned_sum > kint64max
163 ? kint64min
164 : -static_cast<int64_t>(opp_unsigned_sum);
165 } else {
166 if (reference_y_ >= span_y) {
167 return reference_y_ - span_y > kint64max
168 ? kint64max
169 : static_cast<int64_t>(reference_y_ - span_y);
170 } else {
171 return span_y - reference_y_ > static_cast<uint64_t>(kint64max) + 1
172 ? kint64min
173 : -static_cast<int64_t>(span_y - reference_y_);
174 }
175 }
176 }
177}
178
179int64_t PiecewiseSegment::SafeValuePreReference(int64_t x) const {
180 DCHECK_LE(x, reference_x_);
181 const uint64_t span_x = static_cast<uint64_t>(reference_x_) - x;
182 if (slope_ == 0) {
183 // Zero slope segment.
184 return reference_y_;
185 } else if (slope_ > 0) {
186 // Positive slope segment.
187 const uint64_t span_y = UnsignedCapProd(span_x, slope_);
188 if (reference_y_ == 0) {
189 return span_y > kint64max ? kint64min : -static_cast<int64_t>(span_y);
190 } else if (reference_y_ > 0) {
191 if (reference_y_ >= span_y) {
192 return reference_y_ - span_y > kint64max
193 ? kint64max
194 : static_cast<int64_t>(reference_y_ - span_y);
195 } else {
196 return span_y - reference_y_ > static_cast<uint64_t>(kint64max) + 1
197 ? kint64min
198 : -static_cast<uint64_t>(span_y - reference_y_);
199 }
200 } else {
201 const uint64_t opp_reference_y = -static_cast<uint64_t>(reference_y_);
202 const uint64_t opp_unsigned_sum = UnsignedCapAdd(opp_reference_y, span_y);
203 return opp_unsigned_sum > kint64max
204 ? kint64min
205 : -static_cast<uint64_t>(opp_unsigned_sum);
206 }
207 } else {
208 // Negative slope segment.
209 const uint64_t span_y = UnsignedCapProd(span_x, -slope_);
210 if (reference_y_ == 0) {
211 return span_y > kint64max ? kint64max : span_y;
212 } else if (reference_y_ < 0) {
213 const uint64_t opp_reference_y = -static_cast<uint64_t>(reference_y_);
214 if (span_y >= opp_reference_y) {
215 return span_y - opp_reference_y > kint64max
216 ? kint64max
217 : static_cast<int64_t>(span_y - opp_reference_y);
218 } else {
219 return opp_reference_y - span_y > static_cast<uint64_t>(kint64max) + 1
220 ? kint64min
221 : -static_cast<uint64_t>(opp_reference_y - span_y);
222 }
223 } else {
224 const uint64_t unsigned_sum = UnsignedCapAdd(reference_y_, span_y);
225 return unsigned_sum > kint64max ? kint64max
226 : static_cast<int64_t>(unsigned_sum);
227 }
228 }
229}
230
232 const PiecewiseSegment& segment2) {
233 return segment1.start_x_ < segment2.start_x_;
234}
235
237 const PiecewiseSegment& segment) {
238 return point == kint64min || point < segment.start_x();
239}
240
241void PiecewiseSegment::ExpandEnd(int64_t end_x) {
242 end_x_ = std::max(end_x_, end_x);
243}
244
245void PiecewiseSegment::AddConstantToX(int64_t constant) {
246 if (IsAtBounds(CapAdd(reference_x_, constant))) {
247 LOG(ERROR) << "Segment Overflow: " << DebugString();
248 return;
249 }
250 start_x_ = CapAdd(start_x_, constant);
251 end_x_ = CapAdd(end_x_, constant);
252 reference_x_ = CapAdd(reference_x_, constant);
253}
254
255void PiecewiseSegment::AddConstantToY(int64_t constant) {
256 if (IsAtBounds(CapAdd(reference_y_, constant))) {
257 LOG(ERROR) << "Segment Overflow: " << DebugString();
258 return;
259 }
260 reference_y_ = CapAdd(reference_y_, constant);
261}
262
263std::string PiecewiseSegment::DebugString() const {
264 std::string result = absl::StrFormat(
265 "PiecewiseSegment(<start: (%d, %d), end: (%d, %d), "
266 "reference: (%d, %d), slope = %d>)",
267 start_x_, Value(start_x_), end_x_, Value(end_x_), reference_x_,
268 reference_y_, slope_);
269 return result;
270}
271
273
274PiecewiseLinearFunction::PiecewiseLinearFunction(
275 std::vector<PiecewiseSegment> segments)
276 : is_modified_(true),
277 is_convex_(false),
278 is_non_decreasing_(false),
279 is_non_increasing_(false) {
280 // Sort the segments in ascending order of start.
281 std::sort(segments.begin(), segments.end(), PiecewiseSegment::SortComparator);
282 // Check for overlapping segments.
283 for (int i = 0; i < segments.size() - 1; ++i) {
284 if (segments[i].end_x() > segments[i + 1].start_x()) {
285 LOG(FATAL) << "Overlapping segments: " << segments[i].DebugString()
286 << " & " << segments[i + 1].DebugString();
287 }
288 }
289 // Construct the piecewise linear function.
290 for (const auto& segment : segments) {
291 InsertSegment(segment);
292 }
293}
294
296 std::vector<int64_t> points_x, std::vector<int64_t> points_y,
297 std::vector<int64_t> slopes, std::vector<int64_t> other_points_x) {
298 CHECK_EQ(points_x.size(), points_y.size());
299 CHECK_EQ(points_x.size(), other_points_x.size());
300 CHECK_EQ(points_x.size(), slopes.size());
301 CHECK_GT(points_x.size(), 0);
302
303 std::vector<PiecewiseSegment> segments;
304 for (int i = 0; i < points_x.size(); ++i) {
305 segments.push_back(PiecewiseSegment(points_x[i], points_y[i], slopes[i],
306 other_points_x[i]));
307 }
308
309 return new PiecewiseLinearFunction(std::move(segments));
310}
311
313 std::vector<int64_t> points_x, std::vector<int64_t> points_y,
314 std::vector<int64_t> other_points_x) {
315 CHECK_EQ(points_x.size(), points_y.size());
316 CHECK_EQ(points_x.size(), other_points_x.size());
317 CHECK_GT(points_x.size(), 0);
318
319 std::vector<PiecewiseSegment> segments;
320 for (int i = 0; i < points_x.size(); ++i) {
321 segments.push_back(
322 PiecewiseSegment(points_x[i], points_y[i], 0, other_points_x[i]));
323 }
324
325 return new PiecewiseLinearFunction(std::move(segments));
326}
327
329 int64_t initial_level, std::vector<int64_t> points_x,
330 std::vector<int64_t> slopes) {
331 CHECK_EQ(points_x.size(), slopes.size() - 1);
332 CHECK_GT(points_x.size(), 0);
333
334 int64_t level = initial_level;
335 std::vector<PiecewiseSegment> segments;
336 PiecewiseSegment segment =
337 PiecewiseSegment(points_x[0], level, slopes[0], kint64min);
338 segments.push_back(segment);
339 level = segment.Value(points_x[0]);
340 for (int i = 1; i < points_x.size(); ++i) {
341 PiecewiseSegment segment =
342 PiecewiseSegment(points_x[i - 1], level, slopes[i], points_x[i]);
343 segments.push_back(segment);
344 level = segment.Value(points_x[i]);
345 }
346 segments.push_back(
347 PiecewiseSegment(points_x.back(), level, slopes.back(), kint64max));
348
349 return new PiecewiseLinearFunction(std::move(segments));
350}
351
353 int64_t point_x, int64_t point_y, int64_t slope, int64_t other_point_x) {
354 // Visual studio 2013: We cannot inline the vector in the
355 // PiecewiseLinearFunction ctor.
356 std::vector<PiecewiseSegment> segments = {
357 PiecewiseSegment(point_x, point_y, slope, other_point_x)};
358 return new PiecewiseLinearFunction(std::move(segments));
359}
360
362 int64_t point_x, int64_t point_y, int64_t slope) {
363 std::vector<PiecewiseSegment> segments = {
364 PiecewiseSegment(point_x, point_y, slope, kint64max)};
365 return new PiecewiseLinearFunction(std::move(segments));
366}
367
369 int64_t point_x, int64_t point_y, int64_t slope) {
370 std::vector<PiecewiseSegment> segments = {
371 PiecewiseSegment(point_x, point_y, slope, kint64min)};
372 return new PiecewiseLinearFunction(std::move(segments));
373}
374
376 int64_t slope, int64_t value) {
377 std::vector<PiecewiseSegment> segments = {
378 PiecewiseSegment(0, 0, 0, kint64min),
379 PiecewiseSegment(0, value, slope, kint64max)};
380 CHECK_GE(slope, 0);
381 CHECK_GE(value, 0);
382 return new PiecewiseLinearFunction(std::move(segments));
383}
384
386 int64_t reference, int64_t earliness_slope, int64_t tardiness_slope) {
387 std::vector<PiecewiseSegment> segments = {
388 PiecewiseSegment(reference, 0, -earliness_slope, kint64min),
389 PiecewiseSegment(reference, 0, tardiness_slope, kint64max)};
390 CHECK_GE(earliness_slope, 0);
391 CHECK_GE(tardiness_slope, 0);
392 return new PiecewiseLinearFunction(std::move(segments));
393}
394
397 int64_t early_slack, int64_t late_slack, int64_t earliness_slope,
398 int64_t tardiness_slope) {
399 std::vector<PiecewiseSegment> segments = {
400 PiecewiseSegment(early_slack, 0, -earliness_slope, kint64min),
401 PiecewiseSegment(early_slack, 0, 0, late_slack),
402 PiecewiseSegment(late_slack, 0, tardiness_slope, kint64max)};
403
404 CHECK_GE(earliness_slope, 0);
405 CHECK_GE(tardiness_slope, 0);
406 return new PiecewiseLinearFunction(std::move(segments));
407}
408
410 int index = FindSegmentIndex(segments_, x);
411 if (index == kNotFound) {
412 return false;
413 }
414 if (segments_[index].end_x() < x) {
415 return false;
416 }
417 return true;
418}
419
421 const_cast<PiecewiseLinearFunction*>(this)->UpdateStatus();
422 return is_convex_;
423}
424
426 const_cast<PiecewiseLinearFunction*>(this)->UpdateStatus();
427 return is_non_decreasing_;
428}
429
431 const_cast<PiecewiseLinearFunction*>(this)->UpdateStatus();
432 return is_non_increasing_;
433}
434
435int64_t PiecewiseLinearFunction::Value(int64_t x) const {
436 if (!InDomain(x)) {
437 // TODO(user): Allow the user to specify the
438 // undefined value and use kint64max as the default.
439 return kint64max;
440 }
441 const int index = FindSegmentIndex(segments_, x);
442 return segments_[index].Value(x);
443}
444
445int64_t PiecewiseLinearFunction::GetMaximum(int64_t range_start,
446 int64_t range_end) const {
447 if (IsNonDecreasing() && InDomain(range_end)) {
448 return Value(range_end);
449 } else if (IsNonIncreasing() && InDomain(range_start)) {
450 return Value(range_start);
451 }
452 int start_segment = -1;
453 int end_segment = -1;
454 if (!FindSegmentIndicesFromRange(range_start, range_end, &start_segment,
455 &end_segment)) {
456 return kint64max;
457 }
458 CHECK_GE(end_segment, start_segment);
459
460 int64_t range_maximum = kint64min;
461 if (InDomain(range_start)) {
462 range_maximum = std::max(Value(range_start), range_maximum);
463 }
464 if (InDomain(range_end)) {
465 range_maximum = std::max(Value(range_end), range_maximum);
466 }
467
468 for (int i = std::max(0, start_segment); i <= end_segment; ++i) {
469 if (PointInsideRange(segments_[i].start_x(), range_start, range_end)) {
470 range_maximum = std::max(range_maximum, segments_[i].start_y());
471 }
472 if (PointInsideRange(segments_[i].end_x(), range_start, range_end)) {
473 range_maximum = std::max(range_maximum, segments_[i].end_y());
474 }
475 }
476 return range_maximum;
477}
478
479int64_t PiecewiseLinearFunction::GetMinimum(int64_t range_start,
480 int64_t range_end) const {
481 if (IsNonDecreasing() && InDomain(range_start)) {
482 return Value(range_start);
483 } else if (IsNonIncreasing() && InDomain(range_end)) {
484 return Value(range_end);
485 }
486 int start_segment = -1;
487 int end_segment = -1;
488 if (!FindSegmentIndicesFromRange(range_start, range_end, &start_segment,
489 &end_segment)) {
490 return kint64max;
491 }
492 CHECK_GE(end_segment, start_segment);
493
494 int64_t range_minimum = kint64max;
495 if (InDomain(range_start)) {
496 range_minimum = std::min(Value(range_start), range_minimum);
497 }
498 if (InDomain(range_end)) {
499 range_minimum = std::min(Value(range_end), range_minimum);
500 }
501
502 for (int i = std::max(0, start_segment); i <= end_segment; ++i) {
503 if (PointInsideRange(segments_[i].start_x(), range_start, range_end)) {
504 range_minimum = std::min(range_minimum, segments_[i].start_y());
505 }
506 if (PointInsideRange(segments_[i].end_x(), range_start, range_end)) {
507 range_minimum = std::min(range_minimum, segments_[i].end_y());
508 }
509 }
510 return range_minimum;
511}
512
514 return GetMaximum(segments_.front().start_x(), segments_.back().end_x());
515}
516
518 return GetMinimum(segments_.front().start_x(), segments_.back().end_x());
519}
520
521std::pair<int64_t, int64_t>
523 int64_t range_end,
524 int64_t value) const {
525 return GetSmallestRangeInValueRange(range_start, range_end, value, kint64max);
526}
527
528std::pair<int64_t, int64_t>
530 int64_t range_end,
531 int64_t value) const {
532 return GetSmallestRangeInValueRange(range_start, range_end, kint64min, value);
533}
534
535namespace {
536std::pair<int64_t, int64_t> ComputeXFromY(int64_t start_x, int64_t start_y,
537 int64_t slope, int64_t y) {
538 DCHECK_NE(slope, 0);
539 const int64_t delta_y = CapSub(y, start_y);
540 const int64_t delta_x = delta_y / slope;
541 if ((delta_y >= 0 && slope >= 0) || (delta_y <= 0 && slope <= 0)) {
542 const int64_t delta_x_down = delta_x;
543 const int64_t delta_x_up = delta_y % slope == 0 ? delta_x : delta_x + 1;
544 return {delta_x_down + start_x, delta_x_up + start_x};
545 } else {
546 const int64_t delta_x_down = delta_y % slope == 0 ? delta_x : delta_x - 1;
547 const int64_t delta_x_up = -(-delta_y / slope);
548 return {delta_x_down + start_x, delta_x_up + start_x};
549 }
550}
551
552std::pair<int64_t, int64_t> GetRangeInValueRange(int64_t start_x, int64_t end_x,
553 int64_t start_y, int64_t end_y,
554 int64_t slope,
555 int64_t value_min,
556 int64_t value_max) {
557 if ((start_y > value_max && end_y > value_max) ||
558 (start_y < value_min && end_y < value_min)) {
559 return {kint64max, kint64min};
560 }
561 std::pair<int64_t, int64_t> x_range_max = {kint64max, kint64min};
562 if (start_y <= value_max && end_y <= value_max) {
563 x_range_max = {start_x, end_x};
564 } else if (start_y <= value_max || end_y <= value_max) {
565 const auto x = start_x == kint64min
566 ? ComputeXFromY(end_x, end_y, slope, value_max)
567 : ComputeXFromY(start_x, start_y, slope, value_max);
568 if (end_y <= value_max) {
569 x_range_max = {x.second, end_x};
570 } else {
571 x_range_max = {start_x, x.first};
572 }
573 }
574 std::pair<int64_t, int64_t> x_range_min = {kint64max, kint64min};
575 if (start_y >= value_min && end_y >= value_min) {
576 x_range_min = {start_x, end_x};
577 } else if (start_y >= value_min || end_y >= value_min) {
578 const auto x = start_x == kint64min
579 ? ComputeXFromY(end_x, end_y, slope, value_min)
580 : ComputeXFromY(start_x, start_y, slope, value_min);
581 if (end_y >= value_min) {
582 x_range_min = {x.second, end_x};
583 } else {
584 x_range_min = {start_x, x.first};
585 }
586 }
587 if (x_range_min.first > x_range_max.second ||
588 x_range_max.first > x_range_min.second) {
589 return {kint64max, kint64min};
590 }
591 return {std::max(x_range_min.first, x_range_max.first),
592 std::min(x_range_min.second, x_range_max.second)};
593}
594} // namespace
595
596std::pair<int64_t, int64_t>
598 int64_t range_end,
599 int64_t value_min,
600 int64_t value_max) const {
601 int64_t reduced_range_start = kint64max;
602 int64_t reduced_range_end = kint64min;
603 int start_segment = -1;
604 int end_segment = -1;
605 if (!FindSegmentIndicesFromRange(range_start, range_end, &start_segment,
606 &end_segment)) {
607 return {reduced_range_start, reduced_range_end};
608 }
609 for (int i = std::max(0, start_segment); i <= end_segment; ++i) {
610 const auto& segment = segments_[i];
611 const int64_t start_x = std::max(range_start, segment.start_x());
612 const int64_t end_x = std::min(range_end, segment.end_x());
613 const int64_t start_y = segment.Value(start_x);
614 const int64_t end_y = segment.Value(end_x);
615 const std::pair<int64_t, int64_t> range = GetRangeInValueRange(
616 start_x, end_x, start_y, end_y, segment.slope(), value_min, value_max);
617 reduced_range_start = std::min(reduced_range_start, range.first);
618 reduced_range_end = std::max(reduced_range_end, range.second);
619 }
620 return {reduced_range_start, reduced_range_end};
621}
622
624 is_modified_ = true;
625 for (int i = 0; i < segments_.size(); ++i) {
626 segments_[i].AddConstantToX(constant);
627 }
628}
629
631 is_modified_ = true;
632 for (int i = 0; i < segments_.size(); ++i) {
633 segments_[i].AddConstantToY(constant);
634 }
635}
636
638 Operation(other, [](int64_t a, int64_t b) { return CapAdd(a, b); });
639}
640
642 Operation(other, [](int64_t a, int64_t b) { return CapSub(a, b); });
643}
644
645std::vector<PiecewiseLinearFunction*>
647 CHECK_GE(segments_.size(), 1);
648 if (IsConvex()) {
649 return {new PiecewiseLinearFunction(segments_)};
650 }
651
652 std::vector<PiecewiseLinearFunction*> convex_functions;
653 std::vector<PiecewiseSegment> convex_segments;
654
655 for (const PiecewiseSegment& segment : segments_) {
656 if (convex_segments.empty()) {
657 convex_segments.push_back(segment);
658 continue;
659 }
660
661 const PiecewiseSegment& last = convex_segments.back();
662 if (FormConvexPair(last, segment)) {
663 // The segment belongs to the convex sub-function formulated up to now.
664 convex_segments.push_back(segment);
665 } else {
666 convex_functions.push_back(new PiecewiseLinearFunction(convex_segments));
667 convex_segments.clear();
668 convex_segments.push_back(segment);
669 }
670 }
671
672 if (!convex_segments.empty()) {
673 convex_functions.push_back(
674 new PiecewiseLinearFunction(std::move(convex_segments)));
675 }
676 return convex_functions;
677}
678
680 std::string result = "PiecewiseLinearFunction(";
681 for (int i = 0; i < segments_.size(); ++i) {
682 result.append(segments_[i].DebugString());
683 result.append(" ");
684 }
685 return result;
686}
687
688void PiecewiseLinearFunction::InsertSegment(const PiecewiseSegment& segment) {
689 is_modified_ = true;
690 // No intersection.
691 if (segments_.empty() || segments_.back().end_x() < segment.start_x()) {
692 segments_.push_back(segment);
693 return;
694 }
695
696 // Common endpoint.
697 if (segments_.back().end_x() == segment.start_x()) {
698 if (segments_.back().end_y() == segment.start_y() &&
699 segments_.back().slope() == segment.slope()) {
700 segments_.back().ExpandEnd(segment.end_x());
701 return;
702 }
703 segments_.push_back(segment);
704 }
705}
706
707void PiecewiseLinearFunction::Operation(
708 const PiecewiseLinearFunction& other,
709 const std::function<int64_t(int64_t, int64_t)>& operation) {
710 is_modified_ = true;
711 std::vector<PiecewiseSegment> own_segments;
712 const std::vector<PiecewiseSegment>& other_segments = other.segments();
713 own_segments.swap(segments_);
714
715 absl::btree_set<int64_t> start_x_points;
716 for (int i = 0; i < own_segments.size(); ++i) {
717 start_x_points.insert(own_segments[i].start_x());
718 }
719 for (int i = 0; i < other_segments.size(); ++i) {
720 start_x_points.insert(other_segments[i].start_x());
721 }
722
723 for (int64_t start_x : start_x_points) {
724 const int own_index = FindSegmentIndex(own_segments, start_x);
725 const int other_index = FindSegmentIndex(other_segments, start_x);
726 if (own_index >= 0 && other_index >= 0) {
727 const PiecewiseSegment& own_segment = own_segments[own_index];
728 const PiecewiseSegment& other_segment = other_segments[other_index];
729
730 const int64_t end_x =
731 std::min(own_segment.end_x(), other_segment.end_x());
732 const int64_t start_y =
733 operation(own_segment.Value(start_x), other_segment.Value(start_x));
734 const int64_t end_y =
735 operation(own_segment.Value(end_x), other_segment.Value(end_x));
736 const int64_t slope =
737 operation(own_segment.slope(), other_segment.slope());
738
739 int64_t point_x, point_y, other_point_x;
740 if (IsAtBounds(start_y)) {
741 point_x = end_x;
742 point_y = end_y;
743 other_point_x = start_x;
744 } else {
745 point_x = start_x;
746 point_y = start_y;
747 other_point_x = end_x;
748 }
749 InsertSegment(PiecewiseSegment(point_x, point_y, slope, other_point_x));
750 }
751 }
752}
753
754bool PiecewiseLinearFunction::FindSegmentIndicesFromRange(
755 int64_t range_start, int64_t range_end, int* start_segment,
756 int* end_segment) const {
757 *start_segment = FindSegmentIndex(segments_, range_start);
758 *end_segment = FindSegmentIndex(segments_, range_end);
759 if (*start_segment == *end_segment) {
760 if (*start_segment < 0) {
761 // Given range before function's domain start.
762 return false;
763 }
764 if (segments_[*start_segment].end_x() < range_start) {
765 // Given range in a hole of the function's domain.
766 return false;
767 }
768 }
769 return true;
770}
771
772bool PiecewiseLinearFunction::IsConvexInternal() const {
773 for (int i = 1; i < segments_.size(); ++i) {
774 if (!FormConvexPair(segments_[i - 1], segments_[i])) {
775 return false;
776 }
777 }
778 return true;
779}
780
781bool PiecewiseLinearFunction::IsNonDecreasingInternal() const {
782 int64_t value = kint64min;
783 for (const auto& segment : segments_) {
784 const int64_t start_y = segment.start_y();
785 const int64_t end_y = segment.end_y();
786 if (end_y < start_y || start_y < value) return false;
787 value = end_y;
788 }
789 return true;
790}
791
792bool PiecewiseLinearFunction::IsNonIncreasingInternal() const {
793 int64_t value = kint64max;
794 for (const auto& segment : segments_) {
795 const int64_t start_y = segment.start_y();
796 const int64_t end_y = segment.end_y();
797 if (end_y > start_y || start_y > value) return false;
798 value = end_y;
799 }
800 return true;
801}
802
803} // namespace operations_research
IntegerValue y
int64_t GetMaximum() const
Returns the maximum value of all the segments in the function.
std::vector< PiecewiseLinearFunction * > DecomposeToConvexFunctions() const
static PiecewiseLinearFunction * CreateFullDomainFunction(int64_t initial_level, std::vector< int64_t > points_x, std::vector< int64_t > slopes)
static PiecewiseLinearFunction * CreateRightRayFunction(int64_t point_x, int64_t point_y, int64_t slope)
bool IsNonIncreasing() const
Returns true if the piecewise linear function is non-increasing.
std::pair< int64_t, int64_t > GetSmallestRangeLessThanValue(int64_t range_start, int64_t range_end, int64_t value) const
static PiecewiseLinearFunction * CreateFixedChargeFunction(int64_t slope, int64_t value)
void Add(const PiecewiseLinearFunction &other)
static PiecewiseLinearFunction * CreateStepFunction(std::vector< int64_t > points_x, std::vector< int64_t > points_y, std::vector< int64_t > other_points_x)
int64_t GetMinimum() const
Returns the minimum value of all the segments in the function.
const std::vector< PiecewiseSegment > & segments() const
static PiecewiseLinearFunction * CreateOneSegmentFunction(int64_t point_x, int64_t point_y, int64_t slope, int64_t other_point_x)
Builds a function consisting of one segment.
std::pair< int64_t, int64_t > GetSmallestRangeInValueRange(int64_t range_start, int64_t range_end, int64_t value_min, int64_t value_max) const
static PiecewiseLinearFunction * CreateLeftRayFunction(int64_t point_x, int64_t point_y, int64_t slope)
bool IsNonDecreasing() const
Returns true if the piecewise linear function is non-decreasing.
static PiecewiseLinearFunction * CreateEarlyTardyFunction(int64_t reference, int64_t earliness_slope, int64_t tardiness_slope)
static 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)
static PiecewiseLinearFunction * CreateEarlyTardyFunctionWithSlack(int64_t early_slack, int64_t late_slack, int64_t earliness_slope, int64_t tardiness_slope)
int64_t Value(int64_t x) const
Returns the value of the piecewise linear function for x.
std::pair< int64_t, int64_t > GetSmallestRangeGreaterThanValue(int64_t range_start, int64_t range_end, int64_t value) const
bool InDomain(int64_t x) const
Returns if x is in the domain of the function.
void Subtract(const PiecewiseLinearFunction &other)
int64_t end_x() const
Returns the end of the segment's domain.
int64_t start_y() const
Returns the value at the start of the segment's domain.
static bool FindComparator(int64_t point, const PiecewiseSegment &segment)
Comparison method useful for finding in which segment a point belongs.
static bool SortComparator(const PiecewiseSegment &segment1, const PiecewiseSegment &segment2)
Comparison method useful for sorting a sequence of segments.
void AddConstantToY(int64_t constant)
Adds 'constant' to the 'y' the segments.
int64_t slope() const
Returns the segment's slope.
int64_t start_x() const
Returns the start of the segment's domain.
void AddConstantToX(int64_t constant)
Adds 'constant' to the 'x' the segments.
PiecewiseSegment(int64_t point_x, int64_t point_y, int64_t slope, int64_t other_point_x)
int64_t Value(int64_t x) const
Returns the value of the segment at point x.
int64_t end_y() const
Returns the value at the end of the segment's domain.
int64_t b
Definition table.cc:45
int64_t a
Definition table.cc:44
int64_t value
int index
In SWIG mode, we don't want anything besides these top-level includes.
int64_t CapAdd(int64_t x, int64_t y)
int64_t CapSub(int64_t x, int64_t y)
int64_t CapProd(int64_t x, int64_t y)
const Variable x
Definition qp_tests.cc:127
const std::optional< Range > & range
Definition statistics.cc:37
static const uint64_t kuint64max
Definition types.h:23
static const int64_t kint64max
Definition types.h:32
static const int64_t kint64min
Definition types.h:31