diff --git a/README.md b/README.md
index ec13adec..7740f26b 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,5 @@
+![ALNS logo](docs/source/assets/images/logo.svg)
+
[![PyPI version](https://badge.fury.io/py/alns.svg)](https://badge.fury.io/py/alns)
[![ALNS](https://github.com/N-Wouda/ALNS/actions/workflows/alns.yaml/badge.svg)](https://github.com/N-Wouda/ALNS/actions/workflows/alns.yaml)
[![Documentation Status](https://readthedocs.org/projects/alns/badge/?version=latest)](https://alns.readthedocs.io/en/latest/?badge=latest)
diff --git a/alns/accept/RandomWalk.py b/alns/accept/AlwaysAccept.py
similarity index 51%
rename from alns/accept/RandomWalk.py
rename to alns/accept/AlwaysAccept.py
index d513118a..126cfd4f 100644
--- a/alns/accept/RandomWalk.py
+++ b/alns/accept/AlwaysAccept.py
@@ -1,6 +1,6 @@
-class RandomWalk:
+class AlwaysAccept:
"""
- The random walk criterion always accepts the candidate solution.
+ This criterion always accepts the candidate solution.
"""
def __call__(self, rnd, best, current, candidate):
diff --git a/alns/accept/AdaptiveThreshold.py b/alns/accept/MovingAverageThreshold.py
similarity index 86%
rename from alns/accept/AdaptiveThreshold.py
rename to alns/accept/MovingAverageThreshold.py
index 5ed69076..741682c0 100644
--- a/alns/accept/AdaptiveThreshold.py
+++ b/alns/accept/MovingAverageThreshold.py
@@ -3,11 +3,12 @@
from typing import Deque, List
-class AdaptiveThreshold:
+class MovingAverageThreshold:
"""
- The Adaptive Threshold (AT) criterion of [1]. This criterion accepts a
- candidate solution if it is better than an adaptive threshold value. The
- adaptive threshold is computed as:
+ The Moving Average Threshold (MAT) criterion of [1]. This criterion accepts
+ a candidate solution if it is better than a threshold value that is based
+ on the moving average of the objective values of recently observed
+ candidate solutions. The threshold is computed as:
.. math::
diff --git a/alns/accept/WorseAccept.py b/alns/accept/RandomAccept.py
similarity index 89%
rename from alns/accept/WorseAccept.py
rename to alns/accept/RandomAccept.py
index 6c06921b..02117cf9 100644
--- a/alns/accept/WorseAccept.py
+++ b/alns/accept/RandomAccept.py
@@ -1,11 +1,11 @@
from alns.accept.update import update
-class WorseAccept:
+class RandomAccept:
"""
- The Worse Accept criterion accepts a candidate solution if it improves over
- the current one, or with a given probability :math:`P` regardless of the
- cost. :math:`P` is updated in each iteration as:
+ The Random Accept criterion accepts a candidate solution if it improves
+ over the current one, or with a given probability :math:`P` regardless of
+ the cost. :math:`P` is updated in each iteration as:
.. math::
diff --git a/alns/accept/__init__.py b/alns/accept/__init__.py
index e3d3e5f6..e455c01f 100644
--- a/alns/accept/__init__.py
+++ b/alns/accept/__init__.py
@@ -1,10 +1,10 @@
from .AcceptanceCriterion import AcceptanceCriterion
-from .AdaptiveThreshold import AdaptiveThreshold
+from .AlwaysAccept import AlwaysAccept
from .GreatDeluge import GreatDeluge
from .HillClimbing import HillClimbing
from .LateAcceptanceHillClimbing import LateAcceptanceHillClimbing
+from .MovingAverageThreshold import MovingAverageThreshold
from .NonLinearGreatDeluge import NonLinearGreatDeluge
-from .RandomWalk import RandomWalk
+from .RandomAccept import RandomAccept
from .RecordToRecordTravel import RecordToRecordTravel
from .SimulatedAnnealing import SimulatedAnnealing
-from .WorseAccept import WorseAccept
diff --git a/alns/accept/tests/test_adaptive_threshold.py b/alns/accept/tests/test_adaptive_threshold.py
deleted file mode 100644
index a0c2822b..00000000
--- a/alns/accept/tests/test_adaptive_threshold.py
+++ /dev/null
@@ -1,124 +0,0 @@
-import numpy.random as rnd
-from numpy.testing import assert_, assert_equal, assert_raises
-from pytest import mark
-
-from alns.accept import AdaptiveThreshold
-from alns.tests.states import One, Two, VarObj, Zero
-
-
-@mark.parametrize(
- "eta, gamma",
- [
- (-1, 3), # eta cannot be < 0
- (2, 3), # eta cannot be > 1
- (0.5, -2), # gamma cannot be < 0
- (0.5, 0), # gamma cannot be 0
- ],
-)
-def test_raise_invalid_parameters(eta, gamma):
- with assert_raises(ValueError):
- AdaptiveThreshold(eta=eta, gamma=gamma)
-
-
-@mark.parametrize("eta, gamma", [(1, 3), (0.4, 4)])
-def test_no_raise_valid_parameters(eta, gamma):
- AdaptiveThreshold(eta=eta, gamma=gamma)
-
-
-@mark.parametrize("eta", [0, 0.01, 0.5, 0.99, 1])
-def test_eta(eta):
- adaptive_threshold = AdaptiveThreshold(eta, 3)
- assert_equal(adaptive_threshold.eta, eta)
-
-
-@mark.parametrize("gamma", range(1, 10))
-def test_gamma(gamma):
- adaptive_threshold = AdaptiveThreshold(0.5, gamma)
- assert_equal(adaptive_threshold.gamma, gamma)
-
-
-def test_accepts_below_threshold():
- adaptive_threshold = AdaptiveThreshold(eta=0.5, gamma=4)
- adaptive_threshold(rnd.RandomState(), One(), One(), One())
- adaptive_threshold(rnd.RandomState(), One(), One(), Zero())
-
- # The threshold is set at 0 + 0.5 * (0.5 - 0) = 0.25
- assert_(adaptive_threshold(rnd.RandomState(), One(), One(), Zero()))
-
-
-def test_rejects_above_threshold():
- adaptive_threshold = AdaptiveThreshold(eta=0.5, gamma=4)
- adaptive_threshold(rnd.RandomState(), One(), One(), Two())
- adaptive_threshold(rnd.RandomState(), One(), One(), Zero())
-
- # The threshold is set at 0 + 0.5 * (1 - 0) = 0.5
- assert_(not adaptive_threshold(rnd.RandomState(), One(), One(), One()))
-
-
-def test_accepts_equal_threshold():
- accept = AdaptiveThreshold(eta=0.5, gamma=4)
- accept(rnd.RandomState(), One(), One(), VarObj(7100))
- accept(rnd.RandomState(), One(), One(), VarObj(7200))
-
- # The threshold is set at 7100 + 0.5 * (7140 - 7100) = 7120
- assert_(accept(rnd.RandomState(), One(), One(), VarObj(7120)))
-
-
-def test_accepts_over_gamma_candidates():
- accept = AdaptiveThreshold(eta=0.2, gamma=3)
- accept(rnd.RandomState(), One(), One(), VarObj(7100))
- accept(rnd.RandomState(), One(), One(), VarObj(7200))
- accept(rnd.RandomState(), One(), One(), VarObj(7200))
-
- # The threshold is set at 7000 + 0.2 * (7133.33 - 7000) = 7013.33
- assert_(accept(rnd.RandomState(), One(), One(), VarObj(7000)))
-
-
-def test_rejects_over_gamma_candidates():
- accept = AdaptiveThreshold(eta=0.2, gamma=3)
-
- for value in [7100, 7200, 7200, 7000]:
- accept(rnd.RandomState(), One(), One(), VarObj(value))
-
- # The threshold is set at 7000 + 0.2 * (7100 - 7000) = 7020
- result = accept(rnd.RandomState(), One(), One(), VarObj(7100))
- assert_(not result)
-
-
-def test_evaluate_consecutive_solutions():
- """
- Test if AT correctly accepts and rejects consecutive solutions.
- """
- accept = AdaptiveThreshold(eta=0.5, gamma=4)
-
- # The threshold is set at 7100, hence the solution is accepted.
- assert_(accept(rnd.RandomState(), One(), One(), VarObj(7100)))
-
- # The threshold is set at 7125, hence the solution is accepted.
- result = accept(rnd.RandomState(), One(), One(), VarObj(7200))
- assert_(not result)
-
- # The threshold is set at 7120, hence the solution is accepted.
- assert_(accept(rnd.RandomState(), One(), One(), VarObj(7120)))
-
-
-def test_history():
- """
- Test if AT correctly stores the history of the thresholds correctly.
- """
- accept = AdaptiveThreshold(eta=0.5, gamma=4)
-
- accept(rnd.RandomState(), One(), One(), VarObj(7100))
- assert_equal(accept.history, [7100])
-
- accept(rnd.RandomState(), One(), One(), VarObj(7200))
- assert_equal(accept.history, [7100, 7200])
-
- accept(rnd.RandomState(), One(), One(), VarObj(7120))
- assert_equal(accept.history, [7100, 7200, 7120])
-
- accept(rnd.RandomState(), One(), One(), VarObj(7100))
- assert_equal(accept.history, [7100, 7200, 7120, 7100])
-
- accept(rnd.RandomState(), One(), One(), VarObj(7200))
- assert_equal(accept.history, [7200, 7120, 7100, 7200])
diff --git a/alns/accept/tests/test_moving_average_threshold.py b/alns/accept/tests/test_moving_average_threshold.py
new file mode 100644
index 00000000..36713730
--- /dev/null
+++ b/alns/accept/tests/test_moving_average_threshold.py
@@ -0,0 +1,124 @@
+import numpy.random as rnd
+from numpy.testing import assert_, assert_equal, assert_raises
+from pytest import mark
+
+from alns.accept import MovingAverageThreshold
+from alns.tests.states import One, Two, VarObj, Zero
+
+
+@mark.parametrize(
+ "eta, gamma",
+ [
+ (-1, 3), # eta cannot be < 0
+ (2, 3), # eta cannot be > 1
+ (0.5, -2), # gamma cannot be < 0
+ (0.5, 0), # gamma cannot be 0
+ ],
+)
+def test_raise_invalid_parameters(eta, gamma):
+ with assert_raises(ValueError):
+ MovingAverageThreshold(eta=eta, gamma=gamma)
+
+
+@mark.parametrize("eta, gamma", [(1, 3), (0.4, 4)])
+def test_no_raise_valid_parameters(eta, gamma):
+ MovingAverageThreshold(eta=eta, gamma=gamma)
+
+
+@mark.parametrize("eta", [0, 0.01, 0.5, 0.99, 1])
+def test_eta(eta):
+ moving_average = MovingAverageThreshold(eta, 3)
+ assert_equal(moving_average.eta, eta)
+
+
+@mark.parametrize("gamma", range(1, 10))
+def test_gamma(gamma):
+ moving_average = MovingAverageThreshold(0.5, gamma)
+ assert_equal(moving_average.gamma, gamma)
+
+
+def test_accepts_below_threshold():
+ moving_average = MovingAverageThreshold(eta=0.5, gamma=4)
+ moving_average(rnd.RandomState(), One(), One(), One())
+ moving_average(rnd.RandomState(), One(), One(), Zero())
+
+ # The threshold is set at 0 + 0.5 * (0.5 - 0) = 0.25
+ assert_(moving_average(rnd.RandomState(), One(), One(), Zero()))
+
+
+def test_rejects_above_threshold():
+ moving_average = MovingAverageThreshold(eta=0.5, gamma=4)
+ moving_average(rnd.RandomState(), One(), One(), Two())
+ moving_average(rnd.RandomState(), One(), One(), Zero())
+
+ # The threshold is set at 0 + 0.5 * (1 - 0) = 0.5
+ assert_(not moving_average(rnd.RandomState(), One(), One(), One()))
+
+
+def test_accepts_equal_threshold():
+ moving_average = MovingAverageThreshold(eta=0.5, gamma=4)
+ moving_average(rnd.RandomState(), One(), One(), VarObj(7100))
+ moving_average(rnd.RandomState(), One(), One(), VarObj(7200))
+
+ # The threshold is set at 7100 + 0.5 * (7140 - 7100) = 7120
+ assert_(moving_average(rnd.RandomState(), One(), One(), VarObj(7120)))
+
+
+def test_accepts_over_gamma_candidates():
+ moving_average = MovingAverageThreshold(eta=0.2, gamma=3)
+ moving_average(rnd.RandomState(), One(), One(), VarObj(7100))
+ moving_average(rnd.RandomState(), One(), One(), VarObj(7200))
+ moving_average(rnd.RandomState(), One(), One(), VarObj(7200))
+
+ # The threshold is set at 7000 + 0.2 * (7133.33 - 7000) = 7013.33
+ assert_(moving_average(rnd.RandomState(), One(), One(), VarObj(7000)))
+
+
+def test_rejects_over_gamma_candidates():
+ moving_average = MovingAverageThreshold(eta=0.2, gamma=3)
+
+ for value in [7100, 7200, 7200, 7000]:
+ moving_average(rnd.RandomState(), One(), One(), VarObj(value))
+
+ # The threshold is set at 7000 + 0.2 * (7100 - 7000) = 7020
+ result = moving_average(rnd.RandomState(), One(), One(), VarObj(7100))
+ assert_(not result)
+
+
+def test_evaluate_consecutive_solutions():
+ """
+ Test if MAT correctly accepts and rejects consecutive solutions.
+ """
+ moving_average = MovingAverageThreshold(eta=0.5, gamma=4)
+
+ # The threshold is set at 7100, hence the solution is accepted.
+ assert_(moving_average(rnd.RandomState(), One(), One(), VarObj(7100)))
+
+ # The threshold is set at 7125, hence the solution is accepted.
+ result = moving_average(rnd.RandomState(), One(), One(), VarObj(7200))
+ assert_(not result)
+
+ # The threshold is set at 7120, hence the solution is accepted.
+ assert_(moving_average(rnd.RandomState(), One(), One(), VarObj(7120)))
+
+
+def test_history():
+ """
+ Test if MAT correctly stores the history of the thresholds correctly.
+ """
+ moving_average = MovingAverageThreshold(eta=0.5, gamma=4)
+
+ moving_average(rnd.RandomState(), One(), One(), VarObj(7100))
+ assert_equal(moving_average.history, [7100])
+
+ moving_average(rnd.RandomState(), One(), One(), VarObj(7200))
+ assert_equal(moving_average.history, [7100, 7200])
+
+ moving_average(rnd.RandomState(), One(), One(), VarObj(7120))
+ assert_equal(moving_average.history, [7100, 7200, 7120])
+
+ moving_average(rnd.RandomState(), One(), One(), VarObj(7100))
+ assert_equal(moving_average.history, [7100, 7200, 7120, 7100])
+
+ moving_average(rnd.RandomState(), One(), One(), VarObj(7200))
+ assert_equal(moving_average.history, [7200, 7120, 7100, 7200])
diff --git a/alns/accept/tests/test_worse_accept.py b/alns/accept/tests/test_random_accept.py
similarity index 62%
rename from alns/accept/tests/test_worse_accept.py
rename to alns/accept/tests/test_random_accept.py
index bc2062c4..0bb8bb56 100644
--- a/alns/accept/tests/test_worse_accept.py
+++ b/alns/accept/tests/test_random_accept.py
@@ -4,7 +4,7 @@
from numpy.testing import assert_, assert_equal, assert_raises
from pytest import mark
-from alns.accept import WorseAccept
+from alns.accept import RandomAccept
from alns.tests.states import One, Two, Zero
@@ -22,7 +22,7 @@
)
def test_raises_invalid_parameters(start, end, step, method):
with assert_raises(ValueError):
- WorseAccept(start, end, step, method)
+ RandomAccept(start, end, step, method)
@mark.parametrize(
@@ -36,7 +36,7 @@ def test_raises_invalid_parameters(start, end, step, method):
],
)
def test_no_raise_valid_parameters(start, end, step, method):
- WorseAccept(start, end, step, method)
+ RandomAccept(start, end, step, method)
@mark.parametrize(
@@ -53,80 +53,82 @@ def test_properties(start, end, step, method):
"""
Tests if the properties are correctly set.
"""
- worse_accept = WorseAccept(start, end, step, method)
+ random_accept = RandomAccept(start, end, step, method)
- assert_equal(worse_accept.start_prob, start)
- assert_equal(worse_accept.end_prob, end)
- assert_equal(worse_accept.step, step)
- assert_equal(worse_accept.method, method)
+ assert_equal(random_accept.start_prob, start)
+ assert_equal(random_accept.end_prob, end)
+ assert_equal(random_accept.step, step)
+ assert_equal(random_accept.method, method)
def test_zero_prob_accepts_better():
"""
- Tests if WA with a zero start probability accepts better solutions.
+ Tests if random accept with a zero start probability accepts better
+ solutions.
"""
rnd_vals = [1]
rng = Mock(spec_set=rnd.RandomState, random=lambda: rnd_vals.pop(0))
- worse_accept = WorseAccept(0, 0, 0.1)
+ random_accept = RandomAccept(0, 0, 0.1)
- assert_(worse_accept(rng, Zero(), One(), Zero()))
- assert_(worse_accept(rng, Zero(), Two(), Zero()))
+ assert_(random_accept(rng, Zero(), One(), Zero()))
+ assert_(random_accept(rng, Zero(), Two(), Zero()))
def test_zero_prob_never_accept_worse():
"""
- Tests if WA with a zero start probability does not accept worse solutions.
+ Tests if random accept with a zero start probability does not accept worse
+ solutions.
"""
- worse_accept = WorseAccept(0, 0, 0, "linear")
+ random_accept = RandomAccept(0, 0, 0, "linear")
- assert_(not worse_accept(rnd.RandomState(), Zero(), One(), One()))
- assert_(not worse_accept(rnd.RandomState(), Zero(), Zero(), One()))
+ assert_(not random_accept(rnd.RandomState(), Zero(), One(), One()))
+ assert_(not random_accept(rnd.RandomState(), Zero(), Zero(), One()))
def test_one_prob_always_accept():
"""
- Tests if WA with a fixed probability of 1 leads to always accepting
- solutions.
+ Tests if random accept with a fixed probability of 1 leads to always
+ accepting solutions.
"""
- worse_accept = WorseAccept(1, 0, 0, "linear")
+ random_accept = RandomAccept(1, 0, 0, "linear")
for _ in range(100):
- assert_(worse_accept(rnd.RandomState(), Zero(), Zero(), One()))
+ assert_(random_accept(rnd.RandomState(), Zero(), Zero(), One()))
def test_linear_consecutive_solutions():
"""
- Test if WA with linear updating method correctly accepts and rejects
- consecutive solutions.
+ Test if random accept with linear updating method correctly accepts and
+ rejects consecutive solutions.
"""
rnd_vals = [0.9, 0.8, 0.7, 0.6, 0.5, 1]
rng = Mock(spec_set=rnd.RandomState, random=lambda: rnd_vals.pop(0))
- worse_accept = WorseAccept(1, 0, 0.1, "linear")
+ random_accept = RandomAccept(1, 0, 0.1, "linear")
# For the first five, the probability is, resp., 1, 0.9, 0.8, 0.7, 0.6
# The random draw is, resp., 0.9, 0.8, 0.7, 0.6, 0.5 so the worsening
# solution is still accepted.
for _ in range(5):
- assert_(worse_accept(rng, Zero(), Zero(), One()))
+ assert_(random_accept(rng, Zero(), Zero(), One()))
# The probability is now 0.5 and the draw is 1, so reject.
- assert_(not worse_accept(rng, Zero(), Zero(), One()))
+ assert_(not random_accept(rng, Zero(), Zero(), One()))
def test_exponential_consecutive_solutions():
"""
- Test if WA with exponential updating method correctly accepts and rejects
- consecutive solutions.
+ Test if random accept with exponential updating method correctly accepts
+ and rejects consecutive solutions.
"""
rnd_vals = [0.5, 0.25, 0.125, 1]
rng = Mock(spec_set=rnd.RandomState, random=lambda: rnd_vals.pop(0))
- worse_accept = WorseAccept(1, 0, 0.5, "exponential")
+ random_accept = RandomAccept(1, 0, 0.5, "exponential")
# For the first three, the probability is, resp., 1, 0.5, 0.25
# The random draw is, resp., 0.5, 0.25, 0.125, so the worsening
# solution is still accepted.
for _ in range(3):
- assert_(worse_accept(rng, Zero(), Zero(), One()))
+ assert_(random_accept(rng, Zero(), Zero(), One()))
# The probability is now 0.5 and the draw is 1, so reject.
- assert_(not worse_accept(rng, Zero(), Zero(), One()))
+ assert_(not random_accept(rng, Zero(), Zero(), One()))
diff --git a/alns/accept/tests/test_random_walk.py b/alns/accept/tests/test_random_walk.py
index eda1e609..c5bd47b3 100644
--- a/alns/accept/tests/test_random_walk.py
+++ b/alns/accept/tests/test_random_walk.py
@@ -1,30 +1,30 @@
import numpy.random as rnd
from numpy.testing import assert_
-from alns.accept import RandomWalk
+from alns.accept import AlwaysAccept
from alns.tests.states import One, Zero
def test_accepts_better():
"""
- Tests if the random walk method accepts a better solution.
+ Tests if the always accept method accepts a better solution.
"""
- random_walk = RandomWalk()
- assert_(random_walk(rnd.RandomState(), One(), One(), Zero()))
+ always_accept = AlwaysAccept()
+ assert_(always_accept(rnd.RandomState(), One(), One(), Zero()))
def test_accepts_worse():
"""
- Tests if the random walk method accepts a worse solution.
+ Tests if the always accept method accepts a worse solution.
"""
- random_walk = RandomWalk()
- assert_(random_walk(rnd.RandomState(), Zero(), Zero(), One()))
+ always_accept = AlwaysAccept()
+ assert_(always_accept(rnd.RandomState(), Zero(), Zero(), One()))
def test_accepts_equal():
"""
- Tests if the random walk method accepts a solution that results in the
+ Tests if the always accept method accepts a solution that results in the
same objective value.
"""
- random_walk = RandomWalk()
- assert_(random_walk(rnd.RandomState(), Zero(), Zero(), Zero()))
+ always_accept = AlwaysAccept()
+ assert_(always_accept(rnd.RandomState(), Zero(), Zero(), Zero()))
diff --git a/docs/source/api/accept.rst b/docs/source/api/accept.rst
index 4005ec88..e0cb6c13 100644
--- a/docs/source/api/accept.rst
+++ b/docs/source/api/accept.rst
@@ -13,29 +13,29 @@ All acceptance criteria implement :class:`~alns.accept.AcceptanceCriterion.Accep
.. automodule:: alns.accept.AcceptanceCriterion
:members:
-.. automodule:: alns.accept.AdaptiveThreshold
+.. automodule:: alns.accept.AlwaysAccept
:members:
.. automodule:: alns.accept.GreatDeluge
:members:
-.. automodule:: alns.accept.NonLinearGreatDeluge
- :members:
-
.. automodule:: alns.accept.HillClimbing
:members:
.. automodule:: alns.accept.LateAcceptanceHillClimbing
:members:
-.. automodule:: alns.accept.RandomWalk
+.. automodule:: alns.accept.MovingAverageThreshold
:members:
-.. automodule:: alns.accept.RecordToRecordTravel
+.. automodule:: alns.accept.NonLinearGreatDeluge
:members:
-.. automodule:: alns.accept.SimulatedAnnealing
+.. automodule:: alns.accept.RandomAccept
:members:
-.. automodule:: alns.accept.WorseAccept
+.. automodule:: alns.accept.RecordToRecordTravel
+ :members:
+
+.. automodule:: alns.accept.SimulatedAnnealing
:members:
diff --git a/docs/source/assets/images/icon.svg b/docs/source/assets/images/icon.svg
new file mode 100644
index 00000000..79f6fdaf
--- /dev/null
+++ b/docs/source/assets/images/icon.svg
@@ -0,0 +1,30 @@
+
+
diff --git a/docs/source/assets/images/logo.svg b/docs/source/assets/images/logo.svg
new file mode 100644
index 00000000..038176c3
--- /dev/null
+++ b/docs/source/assets/images/logo.svg
@@ -0,0 +1,40 @@
+
+
diff --git a/docs/source/index.rst b/docs/source/index.rst
index c25085dd..f43b57e4 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -1,5 +1,6 @@
-ALNS
-====
+.. figure:: assets/images/logo.svg
+ :alt: ALNS logo
+ :figwidth: 100%
``alns`` is a general, well-documented and tested implementation of the adaptive large neighbourhood search (ALNS) metaheuristic in Python.
ALNS is an algorithm that can be used to solve difficult combinatorial optimisation problems.