Skip to content

Commit

Permalink
ADD: Tools tests (#208)
Browse files Browse the repository at this point in the history
  • Loading branch information
VincentAuriau authored Dec 28, 2024
1 parent 9928d46 commit e85b002
Show file tree
Hide file tree
Showing 3 changed files with 215 additions and 4 deletions.
19 changes: 15 additions & 4 deletions choice_learn/toolbox/or_tools_opt.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ def __init__(self, utilities, itemwise_values, assortment_size, outside_option_g
if not self.outside_option_given:
self.utilities = np.concatenate([[np.exp(0.0)], utilities], axis=0)
self.itemwise_values = np.concatenate([[0.0], itemwise_values], axis=0)
else:
self.utilities = utilities
self.itemwise_values = itemwise_values
self.n_items = len(self.utilities) - 1
self.assortment_size = assortment_size

Expand Down Expand Up @@ -460,9 +463,13 @@ def __init__(
self.class_utilities = class_utilities
self.itemwise_values = itemwise_values
else:
# TO DO
self.class_utilities = class_utilities
self.itemwise_values = itemwise_values
# First Specified item
self.outside_utility = [class_utilities[i][0][0] for i in range(len(class_weights))]
self.outside_value = [itemwise_values[i][0] for i in range(len(class_weights))]

self.class_utilities = [class_utilities[i][1:] for i in range(len(class_weights))]
self.itemwise_values = [itemwise_values[i][1:] for i in range(len(class_weights))]

self.n_items = len(self.itemwise_values) - 1
self.assortment_size = assortment_size
self.class_weights = class_weights
Expand Down Expand Up @@ -639,7 +646,11 @@ def add_minimal_capacity_constraint(self, itemwise_capacities, minimum_capacity)
Value of the maximal capacity.
"""
assortment_capacity = sum(
[self.y[j] * itemwise_capacities[j - 1] for j in range(1, self.n_items + 1)]
[
self.y[(j, k)] * itemwise_capacities[j - 1]
for j in range(1, self.n_items + 1)
for k in range(len(self.itemwise_values[j - 1]))
]
)
self.solver.Add(assortment_capacity >= minimum_capacity)

Expand Down
1 change: 1 addition & 0 deletions requirements-developer.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ mkdocs==1.5.3
mkdocs-material==9.5.3
mkdocs-nbconvert==0.2.1
mkdocstrings-python==1.7.5
ortools
python-markdown-math
bandit==1.7.5
nbstripout==0.6.1
Expand Down
199 changes: 199 additions & 0 deletions tests/unit_tests/tools/test_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
"""Testing base ChoiceModel."""

import numpy as np
import pytest

from choice_learn.toolbox.assortment_optimizer import (
LatentClassAssortmentOptimizer,
LatentClassPricingOptimizer,
MNLAssortmentOptimizer,
)

solvers = ["or-tools"]


def test_mnl_assort_instantiate():
"""Test instantiation with both solvers."""
for solv in solvers:
opt = MNLAssortmentOptimizer(
solver=solv,
utilities=np.array([1.0, 2.0, 3.0]),
itemwise_values=np.array([0.5, 0.5, 0.5]),
assortment_size=2,
)
opt.solve()


def test_various_params():
"""Test specific parametrizations."""
MNLAssortmentOptimizer(
solver="ortools",
utilities=np.array([1.0, 2.0, 3.0]),
itemwise_values=np.array([0.5, 0.5, 0.5]),
assortment_size=2,
outside_option_given=True,
)
LatentClassAssortmentOptimizer(
solver="ortools",
class_weights=np.array([0.2, 0.8]),
class_utilities=np.array([[1.0, 2.0, 3.0], [3.0, 2.0, 1.0]]),
itemwise_values=np.array([0.5, 0.5, 0.5]),
assortment_size=12,
outside_option_given=True,
)
LatentClassPricingOptimizer(
solver="ortools",
class_weights=np.array([0.2, 0.8]),
class_utilities=np.array(
[[[1.0, 1.1], [2.0, 2.1], [3.0, 3.1]], [[3.0, 3.1], [2.0, 2.1], [1.0, 1.1]]]
),
itemwise_values=np.array([[0.5, 1.2], [0.5, 1.2], [0.5, 1.2]]),
assortment_size=12,
outside_option_given=True,
)


def test_capacity_constraints():
"""Test that capacity constraints work."""
opt = LatentClassAssortmentOptimizer(
solver="ortools",
class_weights=np.array([0.2, 0.8]),
class_utilities=np.array([[1.0, 2.0, 3.0], [3.0, 2.0, 1.0]]),
itemwise_values=np.array([0.5, 0.5, 0.5]),
assortment_size=12,
)

opt.add_maximal_capacity_constraint(itemwise_capacities=[1.1, 2.2, 3.3], maximum_capacity=4.5)
opt.add_minimal_capacity_constraint(itemwise_capacities=[1.1, 2.2, 3.3], minimum_capacity=1.2)

opt = LatentClassPricingOptimizer(
solver="ortools",
class_weights=np.array([0.2, 0.8]),
class_utilities=np.array(
[[[1.0, 1.1], [2.0, 2.1], [3.0, 3.1]], [[3.0, 3.1], [2.0, 2.1], [1.0, 1.1]]]
),
itemwise_values=np.array([[0.5, 1.2], [0.5, 1.2], [0.5, 1.2]]),
assortment_size=2,
)

opt.add_maximal_capacity_constraint(itemwise_capacities=[1.1, 2.2, 3.3], maximum_capacity=4.5)
opt.add_minimal_capacity_constraint(itemwise_capacities=[1.1, 2.2, 3.3], minimum_capacity=1.2)


def test_lc_assort_instantiate():
"""Test instantiation with both solvers."""
for solv in solvers:
opt = LatentClassAssortmentOptimizer(
solver=solv,
class_weights=np.array([0.2, 0.8]),
class_utilities=np.array([[1.0, 2.0, 3.0], [3.0, 2.0, 1.0]]),
itemwise_values=np.array([0.5, 0.5, 0.5]),
assortment_size=2,
)
opt.solve()


def test_lc_pricing_instantiate():
"""Test instantiation with both solvers."""
for solv in solvers:
opt = LatentClassPricingOptimizer(
solver=solv,
class_weights=np.array([0.2, 0.8]),
class_utilities=np.array(
[[[1.0, 1.1], [2.0, 2.1], [3.0, 3.1]], [[3.0, 3.1], [2.0, 2.1], [1.0, 1.1]]]
),
itemwise_values=np.array([[0.5, 1.2], [0.5, 1.2], [0.5, 1.2]]),
assortment_size=2,
)
opt.solve()


def test_wrong_solver():
"""Test error raised when specifying wrong solver."""
solver = "rotools"
with pytest.raises(ValueError):
MNLAssortmentOptimizer(
solver=solver,
utilities=np.array([1.0, 2.0, 3.0]),
itemwise_values=np.array([0.5, 0.5, 0.5]),
assortment_size=2,
)
with pytest.raises(ValueError):
LatentClassAssortmentOptimizer(
solver=solver,
class_weights=np.array([0.2, 0.8]),
class_utilities=np.array([[1.0, 2.0, 3.0], [3.0, 2.0, 1.0]]),
itemwise_values=np.array([0.5, 0.5, 0.5]),
assortment_size=2,
)
with pytest.raises(ValueError):
LatentClassPricingOptimizer(
solver=solver,
class_weights=np.array([0.2, 0.8]),
class_utilities=np.array(
[[[1.0, 1.1], [2.0, 2.1], [3.0, 3.1]], [[3.0, 3.1], [2.0, 2.1], [1.0, 1.1]]]
),
itemwise_values=np.array([[0.5, 1.2], [0.5, 1.2], [0.5, 1.2]]),
assortment_size=2,
)


def test_raised_errors():
"""Test diverse parametrization that should raise errors."""
with pytest.raises(ValueError):
MNLAssortmentOptimizer(
solver="ortools",
utilities=np.array([1.0, 2.0, 3.0, 4.0]),
itemwise_values=np.array([0.5, 0.5, 0.5]),
assortment_size=2,
)
with pytest.raises(ValueError):
LatentClassAssortmentOptimizer(
solver="ortools",
class_weights=np.array([0.2, 0.8]),
class_utilities=np.array([[1.0, 2.0], [3.0, 2.0]]),
itemwise_values=np.array([0.5, 0.5, 0.5]),
assortment_size=2,
)

with pytest.raises(ValueError):
LatentClassAssortmentOptimizer(
solver="ortools",
class_weights=np.array([0.2, 0.8]),
class_utilities=np.array([[1.0, 2.0, 3.0], [3.0, 2.0, 1.0], [4.0, 4.0, 4.0]]),
itemwise_values=np.array([0.5, 0.5, 0.5]),
assortment_size=2,
)

with pytest.raises(ValueError):
LatentClassPricingOptimizer(
solver="ortools",
class_weights=np.array([0.2, 0.8]),
class_utilities=np.array(
[[[1.0, 1.1], [2.0, 2.1], [3.0, 3.1]], [[3.0, 3.1], [2.0, 2.1], [1.0, 1.1]]]
),
itemwise_values=np.array([[0.5, 1.2, 2.4], [0.5, 1.2, 2.4], [0.5, 1.2, 2.4]]),
assortment_size=2,
)

with pytest.raises(ValueError):
LatentClassPricingOptimizer(
solver="ortools",
class_weights=np.array([0.2, 0.7, 0.1]),
class_utilities=np.array(
[[[1.0, 1.1], [2.0, 2.1], [3.0, 3.1]], [[3.0, 3.1], [2.0, 2.1], [1.0, 1.1]]]
),
itemwise_values=np.array([[0.5, 1.2], [0.5, 1.2], [0.5, 1.2]]),
assortment_size=2,
)

with pytest.raises(ValueError):
LatentClassPricingOptimizer(
solver="ortools",
class_weights=np.array([0.2, 0.8]),
class_utilities=np.array(
[[[1.0, 1.1], [2.0, 2.1], [3.0, 3.1]], [[3.0, 3.1], [2.0, 2.1], [1.0, 1.1]]]
),
itemwise_values=np.array([[0.5, 1.2], [0.5, 1.2], [0.5, 1.2], [0.5, 1.2]]),
assortment_size=2,
)

0 comments on commit e85b002

Please sign in to comment.