Replaced the near similar filter constraint by a duplicate recording mbid one

This commit is contained in:
emeric
2026-06-13 23:28:07 +02:00
parent af018a9166
commit 845c437364
6 changed files with 505 additions and 369 deletions
@@ -50,10 +50,10 @@
#include "InterpolationFitConstraint.hpp"
#include "MaxDistanceConstraint.hpp"
#include "NearDuplicateEmbeddingConstraint.hpp"
#include "SmoothTransitionConstraint.hpp"
#include "track-selection-constraints/DuplicateTrackConstraint.hpp"
#include "track-selection-constraints/SameArtistConstraint.hpp"
#include "track-selection-constraints/SameRecordingMBIDConstraint.hpp"
#include "track-selection-constraints/SameReleaseConstraint.hpp"
#include "Types.hpp"
@@ -121,11 +121,9 @@ namespace lms::recommendation
constexpr float smoothTransitionWeight{ 0.2F };
constexpr float sameReleaseWeight{ 0.5F };
constexpr float sameArtistWeight{ 0.5F };
constexpr float nearDuplicateThreshold{ 0.01F };
_similarityEvaluator = {};
_similarityEvaluator.addHardConstraint(std::make_unique<DuplicateTrackConstraint>());
_similarityEvaluator.addHardConstraint(std::make_unique<NearDuplicateEmbeddingConstraint<ReducedDimCount>>(_trackVectors, nearDuplicateThreshold));
_similarityEvaluator.addHardConstraint(std::make_unique<SameRecordingMBIDConstraint>(_trackMetadata));
_similarityEvaluator.addHardConstraint(std::make_unique<MaxDistanceConstraint<ReducedDimCount>>(_trackVectors, _trackDistanceThreshold));
_similarityEvaluator.addSoftConstraint(std::make_unique<InterpolationFitConstraint<ReducedDimCount>>(_trackVectors), interpolationFitWeight);
_similarityEvaluator.addSoftConstraint(std::make_unique<SmoothTransitionConstraint<ReducedDimCount>>(_trackVectors), smoothTransitionWeight);
@@ -134,7 +132,7 @@ namespace lms::recommendation
_pathEvaluator = {};
_pathEvaluator.addHardConstraint(std::make_unique<DuplicateTrackConstraint>());
_pathEvaluator.addHardConstraint(std::make_unique<NearDuplicateEmbeddingConstraint<ReducedDimCount>>(_trackVectors, nearDuplicateThreshold));
_pathEvaluator.addHardConstraint(std::make_unique<SameRecordingMBIDConstraint>(_trackMetadata));
_pathEvaluator.addSoftConstraint(std::make_unique<InterpolationFitConstraint<ReducedDimCount>>(_trackVectors), interpolationFitWeight);
_pathEvaluator.addSoftConstraint(std::make_unique<SmoothTransitionConstraint<ReducedDimCount>>(_trackVectors), smoothTransitionWeight);
_pathEvaluator.addSoftConstraint(std::make_unique<SameReleaseConstraint>(_trackMetadata), sameReleaseWeight);
@@ -595,28 +593,29 @@ namespace lms::recommendation
_trackVectors.try_emplace(trackId, &reducedVector);
});
db::Release::find(session, db::Release::FindParameters{}, [&](const db::Release::pointer& release) {
std::vector<std::reference_wrapper<const ReducedVector>> releaseTrackFeatures;
db::Track::find(session, db::Track::FindParameters{}, [&](const db::Track::pointer& track) {
const auto itVec{ _trackVectors.find(track->getId()) };
if (itVec == _trackVectors.cend())
return;
db::Track::FindParameters params;
params.setRelease(release->getId());
auto& meta{ _trackMetadata[track->getId()] };
const db::ReleaseId releaseId{ track->getReleaseId() };
meta.releaseId = releaseId;
meta.recordingMBID = track->getRecordingMBID();
const auto trackIds{ db::Track::findIds(session, params) };
for (const db::TrackId trackId : trackIds.results)
{
const auto itFeatures{ _trackVectors.find(trackId) };
if (itFeatures != std::cend(_trackVectors))
{
assert(itFeatures->second);
releaseTrackFeatures.emplace_back(*itFeatures->second);
_trackMetadata[trackId].releaseId = release->getId();
}
}
if (!releaseTrackFeatures.empty())
_releaseVectors.try_emplace(release->getId(), std::move(releaseTrackFeatures));
if (releaseId.isValid())
_releaseVectors[releaseId].emplace_back(*itVec->second);
});
math::MedoidCalculator<ReducedVector> calc;
for (const auto& [id, vecs] : _releaseVectors)
{
calc.clear();
for (const auto& v : vecs)
calc.add(v.get());
_releaseMedoids.try_emplace(id, calc.finalize());
}
db::Artist::find(session, db::Artist::FindParameters{}, [&](const db::Artist::pointer& artist) {
const auto mbid{ artist->getMBID() };
// skip "Various Artists" to avoid false artist matches
@@ -658,15 +657,6 @@ namespace lms::recommendation
_artistVectors.try_emplace(artist->getId(), std::move(artistTrackVectors));
});
math::MedoidCalculator<ReducedVector> calc;
for (const auto& [id, vecs] : _releaseVectors)
{
calc.clear();
for (const auto& v : vecs)
calc.add(v.get());
_releaseMedoids.try_emplace(id, calc.finalize());
}
for (const auto& [id, vecs] : _artistVectors)
{
calc.clear();
@@ -23,7 +23,6 @@
#include <limits>
#include <memory>
#include <optional>
#include <random>
#include <unordered_set>
#include "database/IDb.hpp"
@@ -39,6 +38,7 @@
#include "track-selection-constraints/DuplicateTrackConstraint.hpp"
#include "track-selection-constraints/SameArtistConstraint.hpp"
#include "track-selection-constraints/SameRecordingMBIDConstraint.hpp"
#include "track-selection-constraints/SameReleaseConstraint.hpp"
#include "track-selection-constraints/TrackCandidateContext.hpp"
@@ -103,6 +103,7 @@ namespace lms::recommendation
constexpr float sameReleaseWeight{ 0.5F };
constexpr float sameArtistWeight{ 0.5F };
_trackEvaluator.addHardConstraint(std::make_unique<DuplicateTrackConstraint>());
_trackEvaluator.addHardConstraint(std::make_unique<SameRecordingMBIDConstraint>(_trackMetadata));
_trackEvaluator.addSoftConstraint(std::make_unique<SameReleaseConstraint>(_trackMetadata), sameReleaseWeight);
_trackEvaluator.addSoftConstraint(std::make_unique<SameArtistConstraint>(_trackMetadata), sameArtistWeight);
}
@@ -122,8 +123,8 @@ namespace lms::recommendation
db::Session& session{ _db.getTLSSession() };
auto transaction{ session.createReadTransaction() };
buildTrackMetadata(session);
buildTrackClusters(session);
buildTrackMetadata(session);
buildReleaseClusters();
buildArtistClusters();
@@ -134,11 +135,17 @@ namespace lms::recommendation
{
LOG(DEBUG, "building track metadata...");
db::Release::find(session, db::Release::FindParameters{}, [&](const db::Release::pointer& release) {
db::Track::FindParameters params;
params.setRelease(release->getId());
for (const db::TrackId trackId : db::Track::findIds(session, params).results)
_trackMetadata[trackId].releaseId = release->getId();
// Ensure cluster tracks with no release/artist have an entry
for (const auto& [trackId, _] : _trackClusters)
_trackMetadata.try_emplace(trackId);
db::Track::find(session, db::Track::FindParameters{}, [&](const db::Track::pointer& track) {
const auto it{ _trackMetadata.find(track->getId()) };
if (it != _trackMetadata.cend())
{
it->second.releaseId = track->getReleaseId();
it->second.recordingMBID = track->getRecordingMBID();
}
});
db::Artist::find(session, db::Artist::FindParameters{}, [&](const db::Artist::pointer& artist) {
@@ -54,8 +54,8 @@ namespace lms::recommendation
ArtistResults findSimilarArtists(db::ArtistId artistId, core::EnumSet<db::TrackArtistLinkType> linkTypes, std::size_t maxCount) const override;
TrackResults greedySelect(std::vector<db::TrackId> candidates, std::vector<db::TrackId> selectedTracks, std::size_t maxCount) const;
void buildTrackMetadata(db::Session& session);
void buildTrackClusters(db::Session& session);
void buildTrackMetadata(db::Session& session);
void buildReleaseClusters();
void buildArtistClusters();
@@ -0,0 +1,53 @@
/*
* Copyright (C) 2026 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "ITrackCandidateHardConstraint.hpp"
#include "TrackMetadata.hpp"
namespace lms::recommendation
{
class SameRecordingMBIDConstraint : public ITrackCandidateHardConstraint
{
public:
SameRecordingMBIDConstraint(const TrackMetadataMap& trackMetadata)
: _trackMetadata{ trackMetadata }
{
}
bool rejects(const TrackCandidateContext& context) const override
{
const auto it{ _trackMetadata.find(context.candidateTrackId) };
if (it == _trackMetadata.cend() || !it->second.recordingMBID)
return false;
for (const db::TrackId selectedId : context.selectedTracks)
{
const auto itSel{ _trackMetadata.find(selectedId) };
if (itSel != _trackMetadata.cend() && itSel->second.recordingMBID == it->second.recordingMBID)
return true;
}
return false;
}
private:
const TrackMetadataMap& _trackMetadata;
};
} // namespace lms::recommendation
@@ -19,9 +19,11 @@
#pragma once
#include <optional>
#include <unordered_map>
#include <vector>
#include "core/UUID.hpp"
#include "database/objects/ArtistId.hpp"
#include "database/objects/ReleaseId.hpp"
#include "database/objects/TrackId.hpp"
@@ -30,9 +32,9 @@ namespace lms::recommendation
{
struct TrackMetadata
{
db::ReleaseId releaseId; // invalid if track has no release
std::vector<db::ArtistId> artistIds; // sorted; album artists only
// Future: db::MediaLibraryId mediaLibraryId;
db::ReleaseId releaseId; // invalid if track has no release
std::vector<db::ArtistId> artistIds; // sorted, album artists only
std::optional<core::UUID> recordingMBID; // absent if track has no recording MBID
};
using TrackMetadataMap = std::unordered_map<db::TrackId, TrackMetadata>;
@@ -24,390 +24,474 @@
#include "database/objects/TrackId.hpp"
#include "math/Vector.hpp"
#include "core/UUID.hpp"
#include "audio-similarity/InterpolationFitConstraint.hpp"
#include "audio-similarity/MaxDistanceConstraint.hpp"
#include "audio-similarity/NearDuplicateEmbeddingConstraint.hpp"
#include "audio-similarity/SmoothTransitionConstraint.hpp"
#include "track-selection-constraints/DuplicateTrackConstraint.hpp"
#include "track-selection-constraints/SameArtistConstraint.hpp"
#include "track-selection-constraints/SameRecordingMBIDConstraint.hpp"
#include "track-selection-constraints/SameReleaseConstraint.hpp"
#include "track-selection-constraints/TrackCandidateContext.hpp"
#include "track-selection-constraints/TrackCandidateEvaluator.hpp"
#include "track-selection-constraints/TrackMetadata.hpp"
using namespace lms;
using namespace lms::recommendation;
namespace
namespace lms::recommendation::tests
{
const db::TrackId T1{ 1 };
const db::TrackId T2{ 2 };
const db::TrackId T3{ 3 };
const db::TrackId T4{ 4 };
namespace
{
const db::TrackId T1{ 1 };
const db::TrackId T2{ 2 };
const db::TrackId T3{ 3 };
const db::TrackId T4{ 4 };
const db::ArtistId A1{ 10 };
const db::ArtistId A2{ 20 };
const db::ArtistId A1{ 10 };
const db::ArtistId A2{ 20 };
const db::ReleaseId R1{ 100 };
const db::ReleaseId R2{ 200 };
} // namespace
const db::ReleaseId R1{ 100 };
const db::ReleaseId R2{ 200 };
} // namespace
TEST(DuplicateTrackConstraint, acceptsNewCandidate)
{
const std::vector<db::TrackId> selected{ T1, T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T3, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_FALSE(DuplicateTrackConstraint{}.rejects(ctx));
}
TEST(DuplicateTrackConstraint, acceptsNewCandidate)
{
const std::vector<db::TrackId> selected{ T1, T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T3, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_FALSE(DuplicateTrackConstraint{}.rejects(ctx));
}
TEST(DuplicateTrackConstraint, rejectsAlreadySelected)
{
const std::vector<db::TrackId> selected{ T1, T2, T3 };
const TrackCandidateContext ctx{ .candidateTrackId = T2, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_TRUE(DuplicateTrackConstraint{}.rejects(ctx));
}
TEST(DuplicateTrackConstraint, rejectsAlreadySelected)
{
const std::vector<db::TrackId> selected{ T1, T2, T3 };
const TrackCandidateContext ctx{ .candidateTrackId = T2, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_TRUE(DuplicateTrackConstraint{}.rejects(ctx));
}
TEST(DuplicateTrackConstraint, acceptsWhenSelectionEmpty)
{
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = {} };
EXPECT_FALSE(DuplicateTrackConstraint{}.rejects(ctx));
}
TEST(DuplicateTrackConstraint, acceptsWhenSelectionEmpty)
{
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = {} };
EXPECT_FALSE(DuplicateTrackConstraint{}.rejects(ctx));
}
TEST(SameArtistConstraint, zeroScoreWhenNoSharedArtist)
{
const TrackMetadataMap meta{
{ T1, { .releaseId = {}, .artistIds = { A1 } } },
{ T2, { .releaseId = {}, .artistIds = { A2 } } },
};
const SameArtistConstraint constraint{ meta };
const std::vector<db::TrackId> selected{ T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
}
TEST(SameArtistConstraint, zeroScoreWhenNoSharedArtist)
{
const TrackMetadataMap meta{
{ T1, { .releaseId = {}, .artistIds = { A1 } } },
{ T2, { .releaseId = {}, .artistIds = { A2 } } },
};
const SameArtistConstraint constraint{ meta };
const std::vector<db::TrackId> selected{ T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
}
TEST(SameArtistConstraint, fullScoreWhenMostRecentMatchesArtist)
{
TrackMetadataMap meta{
{ T1, { .releaseId = {}, .artistIds = { A1 } } },
{ T2, { .releaseId = {}, .artistIds = { A1 } } },
};
const SameArtistConstraint constraint{ meta };
const std::vector<db::TrackId> selected{ T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
TEST(SameArtistConstraint, fullScoreWhenMostRecentMatchesArtist)
{
TrackMetadataMap meta{
{ T1, { .releaseId = {}, .artistIds = { A1 } } },
{ T2, { .releaseId = {}, .artistIds = { A1 } } },
};
const SameArtistConstraint constraint{ meta };
const std::vector<db::TrackId> selected{ T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 1.F);
}
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 1.F);
}
TEST(SameArtistConstraint, halfScoreWhenSecondMostRecentMatchesArtist)
{
TrackMetadataMap meta{
{ T1, { .releaseId = {}, .artistIds = { A1 } } },
{ T2, { .releaseId = {}, .artistIds = { A2 } } },
{ T3, { .releaseId = {}, .artistIds = { A1 } } },
};
const SameArtistConstraint constraint{ meta };
TEST(SameArtistConstraint, halfScoreWhenSecondMostRecentMatchesArtist)
{
TrackMetadataMap meta{
{ T1, { .releaseId = {}, .artistIds = { A1 } } },
{ T2, { .releaseId = {}, .artistIds = { A2 } } },
{ T3, { .releaseId = {}, .artistIds = { A1 } } },
};
const SameArtistConstraint constraint{ meta };
const std::vector<db::TrackId> selected{ T3, T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
const std::vector<db::TrackId> selected{ T3, T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.5F);
}
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.5F);
}
TEST(SameArtistConstraint, trackOutsideWindowIsIgnored)
{
TrackMetadataMap meta{
{ T1, { .releaseId = {}, .artistIds = { A1 } } },
{ T2, { .releaseId = {}, .artistIds = { A2 } } },
{ T3, { .releaseId = {}, .artistIds = { A1 } } },
};
const SameArtistConstraint constraint{ meta, /*window=*/1 };
TEST(SameArtistConstraint, trackOutsideWindowIsIgnored)
{
TrackMetadataMap meta{
{ T1, { .releaseId = {}, .artistIds = { A1 } } },
{ T2, { .releaseId = {}, .artistIds = { A2 } } },
{ T3, { .releaseId = {}, .artistIds = { A1 } } },
};
const SameArtistConstraint constraint{ meta, /*window=*/1 };
const std::vector<db::TrackId> selected{ T3, T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
}
const std::vector<db::TrackId> selected{ T3, T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
}
TEST(SameArtistConstraint, zeroScoreWhenCandidateNotInMap)
{
const TrackMetadataMap meta{};
const SameArtistConstraint constraint{ meta };
const std::vector<db::TrackId> selected{ T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
}
TEST(SameArtistConstraint, zeroScoreWhenCandidateNotInMap)
{
const TrackMetadataMap meta{};
const SameArtistConstraint constraint{ meta };
const std::vector<db::TrackId> selected{ T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
}
TEST(SameReleaseConstraint, zeroScoreWhenNoSharedRelease)
{
TrackMetadataMap meta{
{ T1, { .releaseId = R1, .artistIds = {} } },
{ T2, { .releaseId = R2, .artistIds = {} } },
};
const SameReleaseConstraint constraint{ meta };
const std::vector<db::TrackId> selected{ T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
}
TEST(SameReleaseConstraint, zeroScoreWhenNoSharedRelease)
{
TrackMetadataMap meta{
{ T1, { .releaseId = R1, .artistIds = {} } },
{ T2, { .releaseId = R2, .artistIds = {} } },
};
const SameReleaseConstraint constraint{ meta };
const std::vector<db::TrackId> selected{ T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
}
TEST(SameReleaseConstraint, fullScoreWhenMostRecentMatchesRelease)
{
TrackMetadataMap meta{
{ T1, { .releaseId = R1, .artistIds = {} } },
{ T2, { .releaseId = R1, .artistIds = {} } },
};
const SameReleaseConstraint constraint{ meta };
const std::vector<db::TrackId> selected{ T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 1.F);
}
TEST(SameReleaseConstraint, fullScoreWhenMostRecentMatchesRelease)
{
TrackMetadataMap meta{
{ T1, { .releaseId = R1, .artistIds = {} } },
{ T2, { .releaseId = R1, .artistIds = {} } },
};
const SameReleaseConstraint constraint{ meta };
const std::vector<db::TrackId> selected{ T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 1.F);
}
TEST(SameReleaseConstraint, zeroScoreWhenCandidateHasNoRelease)
{
TrackMetadataMap meta{
{ T1, { .releaseId = {}, .artistIds = {} } },
{ T2, { .releaseId = R1, .artistIds = {} } },
};
const SameReleaseConstraint constraint{ meta };
const std::vector<db::TrackId> selected{ T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
}
TEST(SameReleaseConstraint, zeroScoreWhenCandidateHasNoRelease)
{
TrackMetadataMap meta{
{ T1, { .releaseId = {}, .artistIds = {} } },
{ T2, { .releaseId = R1, .artistIds = {} } },
};
const SameReleaseConstraint constraint{ meta };
const std::vector<db::TrackId> selected{ T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
}
TEST(TrackCandidateEvaluator, hardConstraintRejects)
{
TrackCandidateEvaluator evaluator;
evaluator.addHardConstraint(std::make_unique<DuplicateTrackConstraint>());
TEST(TrackCandidateEvaluator, hardConstraintRejects)
{
TrackCandidateEvaluator evaluator;
evaluator.addHardConstraint(std::make_unique<DuplicateTrackConstraint>());
const std::vector<db::TrackId> selected{ T1 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_TRUE(evaluator.rejects(ctx));
}
const std::vector<db::TrackId> selected{ T1 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_TRUE(evaluator.rejects(ctx));
}
TEST(TrackCandidateEvaluator, noHardConstraintDoesNotReject)
{
TrackCandidateEvaluator evaluator;
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = {} };
EXPECT_FALSE(evaluator.rejects(ctx));
}
TEST(TrackCandidateEvaluator, noHardConstraintDoesNotReject)
{
TrackCandidateEvaluator evaluator;
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = {} };
EXPECT_FALSE(evaluator.rejects(ctx));
}
TEST(TrackCandidateEvaluator, softConstraintScoreIsWeighted)
{
TrackMetadataMap meta{
{ T1, { .releaseId = R1, .artistIds = {} } },
{ T2, { .releaseId = R1, .artistIds = {} } },
};
TrackCandidateEvaluator evaluator;
evaluator.addSoftConstraint(std::make_unique<SameReleaseConstraint>(meta), 2.F);
TEST(TrackCandidateEvaluator, softConstraintScoreIsWeighted)
{
TrackMetadataMap meta{
{ T1, { .releaseId = R1, .artistIds = {} } },
{ T2, { .releaseId = R1, .artistIds = {} } },
};
TrackCandidateEvaluator evaluator;
evaluator.addSoftConstraint(std::make_unique<SameReleaseConstraint>(meta), 2.F);
const std::vector<db::TrackId> selected{ T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
const std::vector<db::TrackId> selected{ T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_FLOAT_EQ(evaluator.score(ctx), 2.F);
}
EXPECT_FLOAT_EQ(evaluator.score(ctx), 2.F);
}
TEST(TrackCandidateEvaluator, multipleSoftConstraintsAreAccumulated)
{
TrackMetadataMap meta{
{ T1, { .releaseId = R1, .artistIds = { A1 } } },
{ T2, { .releaseId = R1, .artistIds = { A1 } } },
};
TrackCandidateEvaluator evaluator;
evaluator.addSoftConstraint(std::make_unique<SameReleaseConstraint>(meta), 1.F);
evaluator.addSoftConstraint(std::make_unique<SameArtistConstraint>(meta), 1.F);
TEST(TrackCandidateEvaluator, multipleSoftConstraintsAreAccumulated)
{
TrackMetadataMap meta{
{ T1, { .releaseId = R1, .artistIds = { A1 } } },
{ T2, { .releaseId = R1, .artistIds = { A1 } } },
};
TrackCandidateEvaluator evaluator;
evaluator.addSoftConstraint(std::make_unique<SameReleaseConstraint>(meta), 1.F);
evaluator.addSoftConstraint(std::make_unique<SameArtistConstraint>(meta), 1.F);
const std::vector<db::TrackId> selected{ T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
const std::vector<db::TrackId> selected{ T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_FLOAT_EQ(evaluator.score(ctx), 2.F);
}
EXPECT_FLOAT_EQ(evaluator.score(ctx), 2.F);
}
TEST(TrackCandidateEvaluator, hardConstraintPassesEvenWithSoftConstraints)
{
TrackMetadataMap meta{
{ T1, { .releaseId = R1, .artistIds = {} } },
{ T2, { .releaseId = R1, .artistIds = {} } },
};
TrackCandidateEvaluator evaluator;
evaluator.addHardConstraint(std::make_unique<DuplicateTrackConstraint>());
evaluator.addSoftConstraint(std::make_unique<SameReleaseConstraint>(meta), 1.F);
TEST(TrackCandidateEvaluator, hardConstraintPassesEvenWithSoftConstraints)
{
TrackMetadataMap meta{
{ T1, { .releaseId = R1, .artistIds = {} } },
{ T2, { .releaseId = R1, .artistIds = {} } },
};
TrackCandidateEvaluator evaluator;
evaluator.addHardConstraint(std::make_unique<DuplicateTrackConstraint>());
evaluator.addSoftConstraint(std::make_unique<SameReleaseConstraint>(meta), 1.F);
const std::vector<db::TrackId> selected{ T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_FALSE(evaluator.rejects(ctx));
EXPECT_FLOAT_EQ(evaluator.score(ctx), 1.F);
}
const std::vector<db::TrackId> selected{ T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_FALSE(evaluator.rejects(ctx));
EXPECT_FLOAT_EQ(evaluator.score(ctx), 1.F);
}
namespace
{
using TestVec = math::Vector<2, float>;
const TestVec v_x{ 1.F, 0.F };
const TestVec v_y{ 0.F, 1.F };
namespace
{
using TestVec = math::Vector<2, float>;
const TestVec v_x{ 1.F, 0.F };
const TestVec v_y{ 0.F, 1.F };
using TestConstraint = NearDuplicateEmbeddingConstraint<2>;
using TestVectorMap = TestConstraint::TrackVectorMap;
} // namespace
using TestConstraint = NearDuplicateEmbeddingConstraint<2>;
using TestVectorMap = TestConstraint::TrackVectorMap;
} // namespace
TEST(NearDuplicateEmbeddingConstraint, acceptsWhenCandidateNotInMap)
{
const TestVectorMap trackVectors{ { T1, &v_x } };
const TestConstraint constraint{ trackVectors, 0.1F };
const std::vector<db::TrackId> selected{ T1 };
const TrackCandidateContext ctx{ .candidateTrackId = T2, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_FALSE(constraint.rejects(ctx));
}
TEST(NearDuplicateEmbeddingConstraint, acceptsWhenCandidateNotInMap)
{
const TestVectorMap trackVectors{ { T1, &v_x } };
const TestConstraint constraint{ trackVectors, 0.1F };
const std::vector<db::TrackId> selected{ T1 };
const TrackCandidateContext ctx{ .candidateTrackId = T2, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_FALSE(constraint.rejects(ctx));
}
TEST(NearDuplicateEmbeddingConstraint, acceptsWhenSelectionEmpty)
{
const TestVectorMap trackVectors{ { T1, &v_x } };
const TestConstraint constraint{ trackVectors, 0.1F };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = {} };
EXPECT_FALSE(constraint.rejects(ctx));
}
TEST(NearDuplicateEmbeddingConstraint, acceptsWhenSelectionEmpty)
{
const TestVectorMap trackVectors{ { T1, &v_x } };
const TestConstraint constraint{ trackVectors, 0.1F };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = {} };
EXPECT_FALSE(constraint.rejects(ctx));
}
TEST(NearDuplicateEmbeddingConstraint, acceptsWhenSelectedTrackNotInMap)
{
const TestVectorMap trackVectors{ { T1, &v_x } };
const TestConstraint constraint{ trackVectors, 0.1F };
const std::vector<db::TrackId> selected{ T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_FALSE(constraint.rejects(ctx));
}
TEST(NearDuplicateEmbeddingConstraint, acceptsWhenSelectedTrackNotInMap)
{
const TestVectorMap trackVectors{ { T1, &v_x } };
const TestConstraint constraint{ trackVectors, 0.1F };
const std::vector<db::TrackId> selected{ T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_FALSE(constraint.rejects(ctx));
}
TEST(NearDuplicateEmbeddingConstraint, rejectsWhenDistanceBelowThreshold)
{
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_x } };
const TestConstraint constraint{ trackVectors, 0.1F };
const std::vector<db::TrackId> selected{ T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_TRUE(constraint.rejects(ctx));
}
TEST(NearDuplicateEmbeddingConstraint, rejectsWhenDistanceBelowThreshold)
{
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_x } };
const TestConstraint constraint{ trackVectors, 0.1F };
const std::vector<db::TrackId> selected{ T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_TRUE(constraint.rejects(ctx));
}
TEST(NearDuplicateEmbeddingConstraint, acceptsWhenDistanceAboveThreshold)
{
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_y } };
const TestConstraint constraint{ trackVectors, 0.1F };
const std::vector<db::TrackId> selected{ T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_FALSE(constraint.rejects(ctx));
}
TEST(NearDuplicateEmbeddingConstraint, acceptsWhenDistanceAboveThreshold)
{
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_y } };
const TestConstraint constraint{ trackVectors, 0.1F };
const std::vector<db::TrackId> selected{ T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_FALSE(constraint.rejects(ctx));
}
TEST(NearDuplicateEmbeddingConstraint, rejectsWhenOneOfManySelectedIsNearDuplicate)
{
const TestVectorMap trackVectors{ { T1, &v_x }, { T3, &v_y }, { T4, &v_x } };
const TestConstraint constraint{ trackVectors, 0.1F };
const std::vector<db::TrackId> selected{ T3, T4 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_TRUE(constraint.rejects(ctx));
}
TEST(NearDuplicateEmbeddingConstraint, rejectsWhenOneOfManySelectedIsNearDuplicate)
{
const TestVectorMap trackVectors{ { T1, &v_x }, { T3, &v_y }, { T4, &v_x } };
const TestConstraint constraint{ trackVectors, 0.1F };
const std::vector<db::TrackId> selected{ T3, T4 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_TRUE(constraint.rejects(ctx));
}
TEST(NearDuplicateEmbeddingConstraint, acceptsWhenAllSelectedAreFarEnough)
{
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_y }, { T3, &v_y } };
const TestConstraint constraint{ trackVectors, 0.1F };
const std::vector<db::TrackId> selected{ T2, T3 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_FALSE(constraint.rejects(ctx));
}
TEST(NearDuplicateEmbeddingConstraint, acceptsWhenAllSelectedAreFarEnough)
{
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_y }, { T3, &v_y } };
const TestConstraint constraint{ trackVectors, 0.1F };
const std::vector<db::TrackId> selected{ T2, T3 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_FALSE(constraint.rejects(ctx));
}
TEST(InterpolationFitConstraint, zeroScoreWhenNoSeeds)
{
const TestVectorMap trackVectors{ { T1, &v_x } };
const InterpolationFitConstraint<2> constraint{ trackVectors };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = {} };
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
}
TEST(InterpolationFitConstraint, zeroScoreWhenNoSeeds)
{
const TestVectorMap trackVectors{ { T1, &v_x } };
const InterpolationFitConstraint<2> constraint{ trackVectors };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = {} };
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
}
TEST(InterpolationFitConstraint, zeroScoreWhenCandidateMatchesSeed)
{
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_x } };
const InterpolationFitConstraint<2> constraint{ trackVectors };
const std::vector<db::TrackId> seeds{ T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = seeds };
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
}
TEST(InterpolationFitConstraint, zeroScoreWhenCandidateMatchesSeed)
{
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_x } };
const InterpolationFitConstraint<2> constraint{ trackVectors };
const std::vector<db::TrackId> seeds{ T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = seeds };
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
}
TEST(InterpolationFitConstraint, halfScoreWhenCandidateOrthogonalToSeed)
{
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_y } };
const InterpolationFitConstraint<2> constraint{ trackVectors };
const std::vector<db::TrackId> seeds{ T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = seeds };
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.5F);
}
TEST(InterpolationFitConstraint, halfScoreWhenCandidateOrthogonalToSeed)
{
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_y } };
const InterpolationFitConstraint<2> constraint{ trackVectors };
const std::vector<db::TrackId> seeds{ T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = seeds };
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.5F);
}
TEST(InterpolationFitConstraint, usesClosestSeed)
{
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_x }, { T3, &v_y } };
const InterpolationFitConstraint<2> constraint{ trackVectors };
const std::vector<db::TrackId> seeds{ T3, T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = seeds };
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
}
TEST(InterpolationFitConstraint, usesClosestSeed)
{
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_x }, { T3, &v_y } };
const InterpolationFitConstraint<2> constraint{ trackVectors };
const std::vector<db::TrackId> seeds{ T3, T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = seeds };
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
}
TEST(MaxDistanceConstraint, acceptsWhenNoSeeds)
{
const TestVectorMap trackVectors{ { T1, &v_x } };
const MaxDistanceConstraint<2> constraint{ trackVectors, 0.5F };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = {} };
EXPECT_FALSE(constraint.rejects(ctx));
}
TEST(MaxDistanceConstraint, acceptsWhenNoSeeds)
{
const TestVectorMap trackVectors{ { T1, &v_x } };
const MaxDistanceConstraint<2> constraint{ trackVectors, 0.5F };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = {} };
EXPECT_FALSE(constraint.rejects(ctx));
}
TEST(MaxDistanceConstraint, acceptsWhenWithinThresholdOfOneSeed)
{
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_x } };
const MaxDistanceConstraint<2> constraint{ trackVectors, 0.5F };
const std::vector<db::TrackId> seeds{ T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = seeds };
EXPECT_FALSE(constraint.rejects(ctx));
}
TEST(MaxDistanceConstraint, acceptsWhenWithinThresholdOfOneSeed)
{
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_x } };
const MaxDistanceConstraint<2> constraint{ trackVectors, 0.5F };
const std::vector<db::TrackId> seeds{ T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = seeds };
EXPECT_FALSE(constraint.rejects(ctx));
}
TEST(MaxDistanceConstraint, rejectsWhenBeyondThresholdFromAllSeeds)
{
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_y } };
const MaxDistanceConstraint<2> constraint{ trackVectors, 0.1F };
const std::vector<db::TrackId> seeds{ T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = seeds };
EXPECT_TRUE(constraint.rejects(ctx));
}
TEST(MaxDistanceConstraint, rejectsWhenBeyondThresholdFromAllSeeds)
{
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_y } };
const MaxDistanceConstraint<2> constraint{ trackVectors, 0.1F };
const std::vector<db::TrackId> seeds{ T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = seeds };
EXPECT_TRUE(constraint.rejects(ctx));
}
TEST(MaxDistanceConstraint, acceptsWhenAnyOfManySeedsIsCloseEnough)
{
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_y }, { T3, &v_x } };
const MaxDistanceConstraint<2> constraint{ trackVectors, 0.5F };
const std::vector<db::TrackId> seeds{ T2, T3 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = seeds };
EXPECT_FALSE(constraint.rejects(ctx));
}
TEST(MaxDistanceConstraint, acceptsWhenAnyOfManySeedsIsCloseEnough)
{
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_y }, { T3, &v_x } };
const MaxDistanceConstraint<2> constraint{ trackVectors, 0.5F };
const std::vector<db::TrackId> seeds{ T2, T3 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = seeds };
EXPECT_FALSE(constraint.rejects(ctx));
}
TEST(SmoothTransitionConstraint, zeroScoreWhenNoSelectedTracks)
{
const TestVectorMap trackVectors{ { T1, &v_x } };
const SmoothTransitionConstraint<2> constraint{ trackVectors };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = {} };
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
}
TEST(SmoothTransitionConstraint, zeroScoreWhenNoSelectedTracks)
{
const TestVectorMap trackVectors{ { T1, &v_x } };
const SmoothTransitionConstraint<2> constraint{ trackVectors };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = {} };
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
}
TEST(SmoothTransitionConstraint, zeroScoreWhenPreviousMatchesCandidate)
{
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_x } };
const SmoothTransitionConstraint<2> constraint{ trackVectors };
const std::vector<db::TrackId> selected{ T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
}
TEST(SmoothTransitionConstraint, zeroScoreWhenPreviousMatchesCandidate)
{
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_x } };
const SmoothTransitionConstraint<2> constraint{ trackVectors };
const std::vector<db::TrackId> selected{ T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
}
TEST(SmoothTransitionConstraint, halfScoreWhenPreviousOrthogonalToCandidate)
{
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_y } };
const SmoothTransitionConstraint<2> constraint{ trackVectors };
const std::vector<db::TrackId> selected{ T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.5F);
}
TEST(SmoothTransitionConstraint, halfScoreWhenPreviousOrthogonalToCandidate)
{
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_y } };
const SmoothTransitionConstraint<2> constraint{ trackVectors };
const std::vector<db::TrackId> selected{ T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.5F);
}
TEST(SmoothTransitionConstraint, usesMostRecentlySelectedTrack)
{
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_x }, { T3, &v_y } };
const SmoothTransitionConstraint<2> constraint{ trackVectors };
const std::vector<db::TrackId> selected{ T3, T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
}
TEST(SmoothTransitionConstraint, usesMostRecentlySelectedTrack)
{
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_x }, { T3, &v_y } };
const SmoothTransitionConstraint<2> constraint{ trackVectors };
const std::vector<db::TrackId> selected{ T3, T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
}
namespace
{
const core::UUID MBID1{ *core::UUID::fromString("3d2508f7-3a7e-4f25-895c-fca079dd71ce") };
const core::UUID MBID2{ *core::UUID::fromString("8e44e4ea-9bce-47c2-a48c-8aa338242fb2") };
} // namespace
TEST(SameRecordingMBIDConstraint, acceptsWhenCandidateNotInMap)
{
const TrackMetadataMap meta{ { T2, { .releaseId = {}, .artistIds = {}, .recordingMBID = MBID1 } } };
const SameRecordingMBIDConstraint constraint{ meta };
const std::vector<db::TrackId> selected{ T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_FALSE(constraint.rejects(ctx));
}
TEST(SameRecordingMBIDConstraint, acceptsWhenCandidateHasNoMBID)
{
const TrackMetadataMap meta{
{ T1, { .releaseId = {}, .artistIds = {}, .recordingMBID = std::nullopt } },
{ T2, { .releaseId = {}, .artistIds = {}, .recordingMBID = MBID1 } },
};
const SameRecordingMBIDConstraint constraint{ meta };
const std::vector<db::TrackId> selected{ T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_FALSE(constraint.rejects(ctx));
}
TEST(SameRecordingMBIDConstraint, acceptsWhenSelectionEmpty)
{
const TrackMetadataMap meta{ { T1, { .releaseId = {}, .artistIds = {}, .recordingMBID = MBID1 } } };
const SameRecordingMBIDConstraint constraint{ meta };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = {} };
EXPECT_FALSE(constraint.rejects(ctx));
}
TEST(SameRecordingMBIDConstraint, acceptsWhenNoneOfSelectedHasSameMBID)
{
const TrackMetadataMap meta{
{ T1, { .releaseId = {}, .artistIds = {}, .recordingMBID = MBID1 } },
{ T2, { .releaseId = {}, .artistIds = {}, .recordingMBID = MBID2 } },
};
const SameRecordingMBIDConstraint constraint{ meta };
const std::vector<db::TrackId> selected{ T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_FALSE(constraint.rejects(ctx));
}
TEST(SameRecordingMBIDConstraint, rejectsWhenOneOfSelectedHasSameMBID)
{
const TrackMetadataMap meta{
{ T1, { .releaseId = {}, .artistIds = {}, .recordingMBID = MBID1 } },
{ T2, { .releaseId = {}, .artistIds = {}, .recordingMBID = MBID2 } },
{ T3, { .releaseId = {}, .artistIds = {}, .recordingMBID = MBID1 } },
};
const SameRecordingMBIDConstraint constraint{ meta };
const std::vector<db::TrackId> selected{ T2, T3 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_TRUE(constraint.rejects(ctx));
}
TEST(SameRecordingMBIDConstraint, acceptsWhenSelectedTrackNotInMap)
{
const TrackMetadataMap meta{ { T1, { .releaseId = {}, .artistIds = {}, .recordingMBID = MBID1 } } };
const SameRecordingMBIDConstraint constraint{ meta };
const std::vector<db::TrackId> selected{ T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_FALSE(constraint.rejects(ctx));
}
TEST(SameRecordingMBIDConstraint, acceptsWhenSelectedTrackHasNoMBID)
{
const TrackMetadataMap meta{
{ T1, { .releaseId = {}, .artistIds = {}, .recordingMBID = MBID1 } },
{ T2, { .releaseId = {}, .artistIds = {}, .recordingMBID = std::nullopt } },
};
const SameRecordingMBIDConstraint constraint{ meta };
const std::vector<db::TrackId> selected{ T2 };
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
EXPECT_FALSE(constraint.rejects(ctx));
}
} // namespace lms::recommendation::tests