Skip to content

Commit

Permalink
Let some thresholds be unreachable! (Fix bug found in real-life ASA d…
Browse files Browse the repository at this point in the history
…ata)
  • Loading branch information
jacquev6 committed May 23, 2024
1 parent 4d182a0 commit ae2b4bb
Show file tree
Hide file tree
Showing 15 changed files with 544 additions and 138 deletions.
12 changes: 12 additions & 0 deletions development/cycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,18 @@ def fix_signature(path, signature):
if parameter[0] == "upset_roots":
assert len(parameter) == 2
parameter[1] = "Iterable[Iterable[int]]"
if path == ["lincs", "classification", "AcceptedValues", "RealThresholds", "__init__"]:
if parameter[0] == "thresholds":
assert len(parameter) == 2
parameter[1] = "Iterable[Optional[float]]"
if path == ["lincs", "classification", "AcceptedValues", "IntegerThresholds", "__init__"]:
if parameter[0] == "thresholds":
assert len(parameter) == 2
parameter[1] = "Iterable[Optional[int]]"
if path == ["lincs", "classification", "AcceptedValues", "EnumeratedThresholds", "__init__"]:
if parameter[0] == "thresholds":
assert len(parameter) == 2
parameter[1] = "Iterable[Optional[str]]"
if path == ["lincs", "classification", "Alternative", "__init__"]:
if parameter[0] == "category_index":
assert parameter[2] == "None"
Expand Down
12 changes: 6 additions & 6 deletions doc-sources/reference/lincs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -339,12 +339,12 @@

Descriptor for thresholds for an real-valued criterion.

.. method:: __init__(thresholds: Iterable[float])
.. method:: __init__(thresholds: Iterable[Optional[float]])

Parameters map exactly to attributes with identical names.

.. property:: thresholds
:type: Iterable[float]
:type: Iterable[Optional[float]]

The thresholds for this descriptor.

Expand All @@ -357,12 +357,12 @@

Descriptor for thresholds for an integer-valued criterion.

.. method:: __init__(thresholds: Iterable[int])
.. method:: __init__(thresholds: Iterable[Optional[int]])

Parameters map exactly to attributes with identical names.

.. property:: thresholds
:type: Iterable[int]
:type: Iterable[Optional[int]]

The thresholds for this descriptor.

Expand All @@ -375,12 +375,12 @@

Descriptor for thresholds for a criterion taking enumerated values.

.. method:: __init__(thresholds: Iterable[str])
.. method:: __init__(thresholds: Iterable[Optional[str]])

Parameters map exactly to attributes with identical names.

.. property:: thresholds
:type: Iterable[str]
:type: Iterable[Optional[str]]

The thresholds for this descriptor.

Expand Down
6 changes: 3 additions & 3 deletions doc-sources/reference/lincs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -216,21 +216,21 @@ children:
children:
- name: __init__
- name: thresholds
type: Iterable[float]
type: Iterable[Optional[float]]
- name: real_thresholds
type: RealThresholds
- name: IntegerThresholds
children:
- name: __init__
- name: thresholds
type: Iterable[int]
type: Iterable[Optional[int]]
- name: integer_thresholds
type: IntegerThresholds
- name: EnumeratedThresholds
children:
- name: __init__
- name: thresholds
type: Iterable[str]
type: Iterable[Optional[str]]
- name: enumerated_thresholds
type: EnumeratedThresholds
- name: SufficientCoalitions
Expand Down
106 changes: 90 additions & 16 deletions lincs/liblincs/classification.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,31 +26,43 @@ bool better_or_equal(
model.get_accepted_values()[criterion_index].get(),
[&model, &performance, &criterion, boundary_index](const AcceptedValues::RealThresholds& accepted_values) {
const float value = performance.get_real().get_value();
const float threshold = accepted_values.get_thresholds()[boundary_index];
switch (criterion.get_real_values().get_preference_direction()) {
case Criterion::PreferenceDirection::increasing:
return value >= threshold;
case Criterion::PreferenceDirection::decreasing:
return value <= threshold;
const std::optional<float> threshold = accepted_values.get_thresholds()[boundary_index];
if (threshold) {
switch (criterion.get_real_values().get_preference_direction()) {
case Criterion::PreferenceDirection::increasing:
return value >= *threshold;
case Criterion::PreferenceDirection::decreasing:
return value <= *threshold;
}
unreachable();
} else {
return false;
}
unreachable();
},
[&model, &performance, &criterion, boundary_index](const AcceptedValues::IntegerThresholds& accepted_values) {
const int value = performance.get_integer().get_value();
const int threshold = accepted_values.get_thresholds()[boundary_index];
switch (criterion.get_integer_values().get_preference_direction()) {
case Criterion::PreferenceDirection::increasing:
return value >= threshold;
case Criterion::PreferenceDirection::decreasing:
return value <= threshold;
const std::optional<int> threshold = accepted_values.get_thresholds()[boundary_index];
if (threshold) {
switch (criterion.get_integer_values().get_preference_direction()) {
case Criterion::PreferenceDirection::increasing:
return value >= *threshold;
case Criterion::PreferenceDirection::decreasing:
return value <= *threshold;
}
unreachable();
} else {
return false;
}
unreachable();
},
[&model, &performance, &criterion, boundary_index](const AcceptedValues::EnumeratedThresholds& accepted_values) {
const auto& ranks = criterion.get_enumerated_values().get_value_ranks();
const std::string& value = performance.get_enumerated().get_value();
const std::string& threshold = accepted_values.get_thresholds()[boundary_index];
return ranks.at(value) >= ranks.at(threshold);
const std::optional<std::string>& threshold = accepted_values.get_thresholds()[boundary_index];
if (threshold) {
return ranks.at(value) >= ranks.at(*threshold);
} else {
return false;
}
}
);
}
Expand Down Expand Up @@ -270,4 +282,66 @@ TEST_CASE("Classification with decreasing criteria") {
CHECK(result.changed == 8);
}

TEST_CASE("Classification with unreachable thresholds") {
Problem problem{
{
Criterion("Criterion 1", Criterion::RealValues(Criterion::PreferenceDirection::increasing, 0, 1)),
},
{{"Cat 1"}, {"Cat 2"}, {"Cat 3"}, {"Cat 4"}},
};

Alternatives alternatives{problem, {
{"A", {Performance(Performance::Real(0.24))}, std::nullopt},
{"A", {Performance(Performance::Real(0.25))}, std::nullopt},
{"A", {Performance(Performance::Real(0.49))}, std::nullopt},
{"A", {Performance(Performance::Real(0.50))}, std::nullopt},
{"A", {Performance(Performance::Real(0.74))}, std::nullopt},
{"A", {Performance(Performance::Real(0.75))}, std::nullopt},
}};

classify_alternatives(
problem,
Model{
problem,
{
AcceptedValues(AcceptedValues::RealThresholds({0.25, 0.5, 0.75})),
},
{
SufficientCoalitions(SufficientCoalitions::Weights({1})),
SufficientCoalitions(SufficientCoalitions::Weights({1})),
SufficientCoalitions(SufficientCoalitions::Weights({1})),
},
},
&alternatives);

CHECK(*alternatives.get_alternatives()[0].get_category_index() == 0);
CHECK(*alternatives.get_alternatives()[1].get_category_index() == 1);
CHECK(*alternatives.get_alternatives()[2].get_category_index() == 1);
CHECK(*alternatives.get_alternatives()[3].get_category_index() == 2);
CHECK(*alternatives.get_alternatives()[4].get_category_index() == 2);
CHECK(*alternatives.get_alternatives()[5].get_category_index() == 3);

classify_alternatives(
problem,
Model{
problem,
{
AcceptedValues(AcceptedValues::RealThresholds({0.25, 0.5, std::nullopt})),
},
{
SufficientCoalitions(SufficientCoalitions::Weights({1})),
SufficientCoalitions(SufficientCoalitions::Weights({1})),
SufficientCoalitions(SufficientCoalitions::Weights({1})),
},
},
&alternatives);

CHECK(*alternatives.get_alternatives()[0].get_category_index() == 0);
CHECK(*alternatives.get_alternatives()[1].get_category_index() == 1);
CHECK(*alternatives.get_alternatives()[2].get_category_index() == 1);
CHECK(*alternatives.get_alternatives()[3].get_category_index() == 2);
CHECK(*alternatives.get_alternatives()[4].get_category_index() == 2);
CHECK(*alternatives.get_alternatives()[5].get_category_index() == 2);
}

} // namespace lincs
14 changes: 7 additions & 7 deletions lincs/liblincs/generation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -211,23 +211,23 @@ Model generate_mrsort_classification_model(const Problem& problem, const unsigne
accepted_values.push_back(dispatch(
problem.get_criteria()[criterion_index].get_values(),
[boundaries_count, &profiles, criterion_index](const Criterion::RealValues&) {
std::vector<float> thresholds;
std::vector<std::optional<float>> thresholds;
thresholds.reserve(boundaries_count);
for (unsigned boundary_index = 0; boundary_index != boundaries_count; ++boundary_index) {
thresholds.push_back(std::get<float>(profiles[boundary_index][criterion_index]));
}
return AcceptedValues(AcceptedValues::RealThresholds(thresholds));
},
[boundaries_count, &profiles, criterion_index](const Criterion::IntegerValues&) {
std::vector<int> thresholds;
std::vector<std::optional<int>> thresholds;
thresholds.reserve(boundaries_count);
for (unsigned boundary_index = 0; boundary_index != boundaries_count; ++boundary_index) {
thresholds.push_back(std::get<int>(profiles[boundary_index][criterion_index]));
}
return AcceptedValues(AcceptedValues::IntegerThresholds(thresholds));
},
[boundaries_count, &profiles, criterion_index](const Criterion::EnumeratedValues&) {
std::vector<std::string> thresholds;
std::vector<std::optional<std::string>> thresholds;
thresholds.reserve(boundaries_count);
for (unsigned boundary_index = 0; boundary_index != boundaries_count; ++boundary_index) {
thresholds.push_back(std::get<std::string>(profiles[boundary_index][criterion_index]));
Expand Down Expand Up @@ -671,12 +671,12 @@ TEST_CASE("Random min/max") {

CHECK(problem.get_criteria()[0].get_real_values().get_min_value() == doctest::Approx(-25.092));
CHECK(problem.get_criteria()[0].get_real_values().get_max_value() == doctest::Approx(59.3086));
CHECK(model.get_accepted_values()[0].get_real_thresholds().get_thresholds()[0] == doctest::Approx(6.52194));
CHECK(*model.get_accepted_values()[0].get_real_thresholds().get_thresholds()[0] == doctest::Approx(6.52194));
CHECK(alternatives.get_alternatives()[0].get_profile()[0].get_real().get_value() == doctest::Approx(45.3692));

CHECK(problem.get_criteria()[1].get_real_values().get_min_value() == doctest::Approx(-63.313));
CHECK(problem.get_criteria()[1].get_real_values().get_max_value() == doctest::Approx(46.3988));
CHECK(model.get_accepted_values()[1].get_real_thresholds().get_thresholds()[0] == doctest::Approx(24.0712));
CHECK(*model.get_accepted_values()[1].get_real_thresholds().get_thresholds()[0] == doctest::Approx(24.0712));
CHECK(alternatives.get_alternatives()[0].get_profile()[1].get_real().get_value() == doctest::Approx(-15.8581));
}

Expand All @@ -692,8 +692,8 @@ TEST_CASE("Decreasing criterion") {

CHECK(problem.get_criteria()[0].get_real_values().get_preference_direction() == Criterion::PreferenceDirection::decreasing);
// Profiles are in decreasing order
CHECK(model.get_accepted_values()[0].get_real_thresholds().get_thresholds()[0] == doctest::Approx(0.790612));
CHECK(model.get_accepted_values()[0].get_real_thresholds().get_thresholds()[1] == doctest::Approx(0.377049));
CHECK(*model.get_accepted_values()[0].get_real_thresholds().get_thresholds()[0] == doctest::Approx(0.790612));
CHECK(*model.get_accepted_values()[0].get_real_thresholds().get_thresholds()[1] == doctest::Approx(0.377049));

CHECK(alternatives.get_alternatives()[0].get_profile()[0].get_real().get_value() == doctest::Approx(0.834842));
CHECK(*alternatives.get_alternatives()[0].get_category_index() == 0);
Expand Down
Loading

0 comments on commit ae2b4bb

Please sign in to comment.