Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
440 changes: 440 additions & 0 deletions ortools/util/BUILD.bazel

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions ortools/util/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,14 @@ if(BUILD_CXX_TESTING)
SOURCES
${_FILE_NAME}
LINK_LIBRARIES
absl::scoped_mock_log
GTest::gtest
GTest::gtest_main
GTest::gmock
)
endforeach()
# These tests are too long so we disable them.
set_tests_properties(
cxx_util_random_subset_test
PROPERTIES DISABLED TRUE)
endif()
7 changes: 0 additions & 7 deletions ortools/util/README

This file was deleted.

121 changes: 121 additions & 0 deletions ortools/util/adaptative_parameter_value_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// Copyright 2010-2025 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/adaptative_parameter_value.h"

#include <cmath>

#include "absl/random/random.h"
#include "gtest/gtest.h"

namespace operations_research {
namespace {

TEST(AdaptiveParameterValueTest, StayBelowOne) {
AdaptiveParameterValue param(0.5);
for (int i = 0; i < 1e6; ++i) param.Increase();
EXPECT_GE(param.value(), 0.99);
EXPECT_LE(param.value(), 1.0);
}

TEST(AdaptiveParameterValueTest, StayAboveZero) {
AdaptiveParameterValue param(0.5);
for (int i = 0; i < 1e6; ++i) param.Decrease();
EXPECT_GE(param.value(), 0.0);
EXPECT_LE(param.value(), 0.001);
}

TEST(AdaptiveParameterValueTest, ConvergeToTarget) {
absl::BitGen random;
AdaptiveParameterValue param(0.5);
for (int i = 0; i < 50; ++i) {
const double target = absl::Uniform<double>(random, 0.0, 1.0);
for (int j = 0; j < 10000; ++j) {
if (param.value() < target) param.Increase();
if (param.value() > target) param.Decrease();
}

// We expect convergence even after a lot of updates.
EXPECT_GE(param.value(), target - 0.1);
EXPECT_LE(param.value(), target + 0.1);
}
}

TEST(AdaptiveParameterValueTest, ExponentialConvergenceToOne) {
AdaptiveParameterValue param(0.5);
for (int i = 0; i < 100; ++i) param.Increase();
EXPECT_GE(param.value(), 1.0 - 1e-6);
}

TEST(AdaptiveParameterValueTest, ExponentialConvergenceToZero) {
AdaptiveParameterValue param(0.5);
for (int i = 0; i < 100; ++i) param.Decrease();
EXPECT_LE(param.value(), 1e-6);
}

TEST(AdaptiveParameterValueTest, ConvergenceToMedian) {
AdaptiveParameterValue param(0.5);

// The test should work (modulo tolerances) for any increasing function f in
// [0, 1] with f(0) = 0 and f(1) = 1.
const auto f = [](double x) { return x * x; };

// The final value should be around f(param.value) = 0.5 !
// The convergence is pretty slow though.
absl::BitGen random;
for (int i = 0; i < 1e6; ++i) {
const double decrease = absl::Bernoulli(random, f(param.value()));
if (decrease) {
param.Decrease();
} else {
param.Increase();
}
}

// No flakiness in 1000 runs with this tolerance.
EXPECT_NEAR(param.value(), sqrt(0.5), 0.05);
}

TEST(AdaptiveParameterValueTest, UpdateVsIncrease) {
AdaptiveParameterValue param1(0.1);
AdaptiveParameterValue param2(0.1);
param1.Increase();
param1.Increase();
param1.Increase();
param2.Update(/*num_decreases=*/0, /*num_increases=*/3);
EXPECT_EQ(param1.value(), param2.value());
}

TEST(AdaptiveParameterValueTest, UpdateVsDecrease) {
AdaptiveParameterValue param1(0.1);
AdaptiveParameterValue param2(0.1);
param1.Decrease();
param1.Decrease();
param2.Update(/*num_decreases=*/2, /*num_increases=*/0);
EXPECT_EQ(param1.value(), param2.value());
}

TEST(AdaptiveParameterValueTest, GenericUpdate) {
AdaptiveParameterValue param1(0.1);
AdaptiveParameterValue param2(0.1);
param1.Decrease();
param1.Increase();
param1.Increase();
param1.Decrease();
param2.Update(/*num_decreases=*/2, /*num_increases=*/2);
EXPECT_EQ(param2.value(), 0.1);
EXPECT_NE(param1.value(), param2.value());
}

} // namespace
} // namespace operations_research
70 changes: 70 additions & 0 deletions ortools/util/affine_relation_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright 2010-2025 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/affine_relation.h"

#include "gtest/gtest.h"

namespace operations_research {
namespace {

TEST(AffineRelationTest, Empty) {
AffineRelation r;
EXPECT_EQ(r.Get(10), AffineRelation::Relation(10, 1, 0));
}

TEST(AffineRelationTest, CoeffOfMagnitudeOne) {
AffineRelation r;
r.TryAdd(1, 0, /*coeff=*/1, /*offset=*/1); // r1 = r0 + 1
r.TryAdd(2, 0, /*coeff=*/1, /*offset=*/2); // r2 = r0 + 2
r.TryAdd(3, 0, /*coeff=*/-1, /*offset=*/3); // r3 = -r0 + 3
r.TryAdd(4, 0, /*coeff=*/-1, /*offset=*/4); // r4 = -r0 + 4
r.TryAdd(5, 2, /*coeff=*/-1, /*offset=*/-3); // r5 = -r2 - 3 = -r0 - 5
r.TryAdd(6, 3, /*coeff=*/-1, /*offset=*/-3); // r6 = -r3 - 3 = -r0 - 8
r.TryAdd(7, 4, /*coeff=*/-1, /*offset=*/-3); // r7 = -r4 - 3 = r0 - 7

EXPECT_EQ(r.Get(0), AffineRelation::Relation(0, 1, 0));
EXPECT_EQ(r.Get(1), AffineRelation::Relation(0, 1, 1));
EXPECT_EQ(r.Get(2), AffineRelation::Relation(0, 1, 2));
EXPECT_EQ(r.Get(3), AffineRelation::Relation(0, -1, 3));
EXPECT_EQ(r.Get(4), AffineRelation::Relation(0, -1, 4));
EXPECT_EQ(r.Get(5), AffineRelation::Relation(0, -1, -1 * 2 - 3));
EXPECT_EQ(r.Get(6), AffineRelation::Relation(0, 1, -1 * 3 - 3));
EXPECT_EQ(r.Get(7), AffineRelation::Relation(0, 1, -1 * 4 - 3));
}

TEST(AffineRelationTest, ChainOfMultiple) {
AffineRelation r;
EXPECT_TRUE(r.TryAdd(1, 0, /*coeff=*/2, /*offset=*/1));
EXPECT_TRUE(r.TryAdd(2, 1, /*coeff=*/2, /*offset=*/1));
EXPECT_TRUE(r.TryAdd(3, 2, /*coeff=*/2, /*offset=*/1));
EXPECT_TRUE(r.TryAdd(4, 3, /*coeff=*/2, /*offset=*/1));

for (int i = 0; i < 4; ++i) {
EXPECT_EQ(r.Get(i), AffineRelation::Relation(0, 1 << i, (1 << i) - 1));
}

EXPECT_TRUE(r.TryAdd(6, 5, /*coeff=*/3, /*offset=*/0));

// We can't add any of the following.
EXPECT_FALSE(r.TryAdd(1, 0, 2, 1));
EXPECT_FALSE(r.TryAdd(3, 4, 2, 1));
EXPECT_FALSE(r.TryAdd(1, 6, 1, 0));
EXPECT_FALSE(r.TryAdd(6, 1, 1, 0));

// But we can connect the root of the chain.
EXPECT_TRUE(r.TryAdd(0, 6, 1, 0));
}

} // namespace
} // namespace operations_research
36 changes: 36 additions & 0 deletions ortools/util/cached_log_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright 2010-2025 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/cached_log.h"

#include <math.h>

#include <cstdint>

#include "gtest/gtest.h"

namespace {

TEST(CachedLogTest, LogAccess) {
const int kSize = 100;
operations_research::CachedLog cache;
for (int64_t i = 1; i < kSize; ++i) {
EXPECT_DOUBLE_EQ(log2(i), cache.Log2(i));
}
cache.Init(kSize / 2);
for (int64_t i = 1; i < kSize; ++i) {
EXPECT_DOUBLE_EQ(log2(i), cache.Log2(i));
}
}

} // namespace
100 changes: 100 additions & 0 deletions ortools/util/dense_set_benchmark.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright 2010-2025 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 <cstdint>
#include <utility>

#include "absl/container/flat_hash_set.h"
#include "absl/random/bit_gen_ref.h"
#include "absl/random/random.h"
#include "benchmark/benchmark.h"
#include "ortools/util/dense_set.h"

namespace operations_research {
namespace {

template <typename Set>
int64_t DoOneBMIteration(Set& set, absl::BitGenRef gen) {
int64_t sum = 0;
// Don't use generate random numbers inside the innermost loop to avoid
// benchmarking absl::Uniform.
int64_t base = absl::Uniform<int64_t>(gen, 0, 1024 * 1024);
for (int i = 1; i <= 100; ++i) {
set.insert((i * base) % (1024 * 1024));
}
for (int v : set) {
sum += v;
}
while (set.size() > 1000) {
set.erase(set.begin());
}
return sum;
}

template <typename Set>
void BM_SetIteration(benchmark::State& state, Set set = Set()) {
absl::BitGen gen;
int64_t sum = 0;
int base = absl::Uniform<int64_t>(gen, 1, 1024 * 1024);
for (int i = 0; i < 1000; ++i) {
set.insert(i * base % (1024 * 1024));
}
for (auto s : state) {
sum += DoOneBMIteration(set, gen);
}
}

void BM_HashSetIteration(benchmark::State& state) {
BM_SetIteration<absl::flat_hash_set<int>>(state);
}
void BM_DenseSetIteration(benchmark::State& state) {
BM_SetIteration<DenseSet<int>>(state);
}
void BM_HashSetIterationPreReserved(benchmark::State& state) {
absl::flat_hash_set<int> set;
set.reserve(1500);
BM_SetIteration(state, std::move(set));
}
void BM_DenseSetIterationPreReserved(benchmark::State& state) {
DenseSet<int> set;
set.reserve(1024 * 1024);
BM_SetIteration(state, std::move(set));
}
void BM_UnsafeDenseSetIterationPreReserved(benchmark::State& state) {
UnsafeDenseSet<int> set;
set.reserve(1024 * 1024);
BM_SetIteration(state, std::move(set));
}
void BM_HashSetIterationPreReservedRehash(benchmark::State& state) {
absl::flat_hash_set<int> set;
set.reserve(1500);
absl::BitGen gen;
int64_t sum = 0;
for (int i = 0; i < 1000; ++i) {
set.insert(absl::Uniform(gen, 0, 1024 * 1024));
}
for (auto s : state) {
set.rehash(1500);
sum += DoOneBMIteration(set, gen);
}
}

BENCHMARK(BM_HashSetIteration);
BENCHMARK(BM_DenseSetIteration);
BENCHMARK(BM_HashSetIterationPreReserved);
BENCHMARK(BM_DenseSetIterationPreReserved);
BENCHMARK(BM_UnsafeDenseSetIterationPreReserved);
BENCHMARK(BM_HashSetIterationPreReservedRehash);

} // namespace
} // namespace operations_research
Loading
Loading