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