Replaced the near similar filter constraint by a duplicate recording mbid one
This commit is contained in:
+22
-32
@@ -50,10 +50,10 @@
|
|||||||
|
|
||||||
#include "InterpolationFitConstraint.hpp"
|
#include "InterpolationFitConstraint.hpp"
|
||||||
#include "MaxDistanceConstraint.hpp"
|
#include "MaxDistanceConstraint.hpp"
|
||||||
#include "NearDuplicateEmbeddingConstraint.hpp"
|
|
||||||
#include "SmoothTransitionConstraint.hpp"
|
#include "SmoothTransitionConstraint.hpp"
|
||||||
#include "track-selection-constraints/DuplicateTrackConstraint.hpp"
|
#include "track-selection-constraints/DuplicateTrackConstraint.hpp"
|
||||||
#include "track-selection-constraints/SameArtistConstraint.hpp"
|
#include "track-selection-constraints/SameArtistConstraint.hpp"
|
||||||
|
#include "track-selection-constraints/SameRecordingMBIDConstraint.hpp"
|
||||||
#include "track-selection-constraints/SameReleaseConstraint.hpp"
|
#include "track-selection-constraints/SameReleaseConstraint.hpp"
|
||||||
|
|
||||||
#include "Types.hpp"
|
#include "Types.hpp"
|
||||||
@@ -121,11 +121,9 @@ namespace lms::recommendation
|
|||||||
constexpr float smoothTransitionWeight{ 0.2F };
|
constexpr float smoothTransitionWeight{ 0.2F };
|
||||||
constexpr float sameReleaseWeight{ 0.5F };
|
constexpr float sameReleaseWeight{ 0.5F };
|
||||||
constexpr float sameArtistWeight{ 0.5F };
|
constexpr float sameArtistWeight{ 0.5F };
|
||||||
constexpr float nearDuplicateThreshold{ 0.01F };
|
|
||||||
|
|
||||||
_similarityEvaluator = {};
|
_similarityEvaluator = {};
|
||||||
_similarityEvaluator.addHardConstraint(std::make_unique<DuplicateTrackConstraint>());
|
_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.addHardConstraint(std::make_unique<MaxDistanceConstraint<ReducedDimCount>>(_trackVectors, _trackDistanceThreshold));
|
||||||
_similarityEvaluator.addSoftConstraint(std::make_unique<InterpolationFitConstraint<ReducedDimCount>>(_trackVectors), interpolationFitWeight);
|
_similarityEvaluator.addSoftConstraint(std::make_unique<InterpolationFitConstraint<ReducedDimCount>>(_trackVectors), interpolationFitWeight);
|
||||||
_similarityEvaluator.addSoftConstraint(std::make_unique<SmoothTransitionConstraint<ReducedDimCount>>(_trackVectors), smoothTransitionWeight);
|
_similarityEvaluator.addSoftConstraint(std::make_unique<SmoothTransitionConstraint<ReducedDimCount>>(_trackVectors), smoothTransitionWeight);
|
||||||
@@ -134,7 +132,7 @@ namespace lms::recommendation
|
|||||||
|
|
||||||
_pathEvaluator = {};
|
_pathEvaluator = {};
|
||||||
_pathEvaluator.addHardConstraint(std::make_unique<DuplicateTrackConstraint>());
|
_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<InterpolationFitConstraint<ReducedDimCount>>(_trackVectors), interpolationFitWeight);
|
||||||
_pathEvaluator.addSoftConstraint(std::make_unique<SmoothTransitionConstraint<ReducedDimCount>>(_trackVectors), smoothTransitionWeight);
|
_pathEvaluator.addSoftConstraint(std::make_unique<SmoothTransitionConstraint<ReducedDimCount>>(_trackVectors), smoothTransitionWeight);
|
||||||
_pathEvaluator.addSoftConstraint(std::make_unique<SameReleaseConstraint>(_trackMetadata), sameReleaseWeight);
|
_pathEvaluator.addSoftConstraint(std::make_unique<SameReleaseConstraint>(_trackMetadata), sameReleaseWeight);
|
||||||
@@ -595,28 +593,29 @@ namespace lms::recommendation
|
|||||||
_trackVectors.try_emplace(trackId, &reducedVector);
|
_trackVectors.try_emplace(trackId, &reducedVector);
|
||||||
});
|
});
|
||||||
|
|
||||||
db::Release::find(session, db::Release::FindParameters{}, [&](const db::Release::pointer& release) {
|
db::Track::find(session, db::Track::FindParameters{}, [&](const db::Track::pointer& track) {
|
||||||
std::vector<std::reference_wrapper<const ReducedVector>> releaseTrackFeatures;
|
const auto itVec{ _trackVectors.find(track->getId()) };
|
||||||
|
if (itVec == _trackVectors.cend())
|
||||||
|
return;
|
||||||
|
|
||||||
db::Track::FindParameters params;
|
auto& meta{ _trackMetadata[track->getId()] };
|
||||||
params.setRelease(release->getId());
|
const db::ReleaseId releaseId{ track->getReleaseId() };
|
||||||
|
meta.releaseId = releaseId;
|
||||||
|
meta.recordingMBID = track->getRecordingMBID();
|
||||||
|
|
||||||
const auto trackIds{ db::Track::findIds(session, params) };
|
if (releaseId.isValid())
|
||||||
for (const db::TrackId trackId : trackIds.results)
|
_releaseVectors[releaseId].emplace_back(*itVec->second);
|
||||||
{
|
|
||||||
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));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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) {
|
db::Artist::find(session, db::Artist::FindParameters{}, [&](const db::Artist::pointer& artist) {
|
||||||
const auto mbid{ artist->getMBID() };
|
const auto mbid{ artist->getMBID() };
|
||||||
// skip "Various Artists" to avoid false artist matches
|
// skip "Various Artists" to avoid false artist matches
|
||||||
@@ -658,15 +657,6 @@ namespace lms::recommendation
|
|||||||
_artistVectors.try_emplace(artist->getId(), std::move(artistTrackVectors));
|
_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)
|
for (const auto& [id, vecs] : _artistVectors)
|
||||||
{
|
{
|
||||||
calc.clear();
|
calc.clear();
|
||||||
|
|||||||
@@ -23,7 +23,6 @@
|
|||||||
#include <limits>
|
#include <limits>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <random>
|
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
|
||||||
#include "database/IDb.hpp"
|
#include "database/IDb.hpp"
|
||||||
@@ -39,6 +38,7 @@
|
|||||||
|
|
||||||
#include "track-selection-constraints/DuplicateTrackConstraint.hpp"
|
#include "track-selection-constraints/DuplicateTrackConstraint.hpp"
|
||||||
#include "track-selection-constraints/SameArtistConstraint.hpp"
|
#include "track-selection-constraints/SameArtistConstraint.hpp"
|
||||||
|
#include "track-selection-constraints/SameRecordingMBIDConstraint.hpp"
|
||||||
#include "track-selection-constraints/SameReleaseConstraint.hpp"
|
#include "track-selection-constraints/SameReleaseConstraint.hpp"
|
||||||
#include "track-selection-constraints/TrackCandidateContext.hpp"
|
#include "track-selection-constraints/TrackCandidateContext.hpp"
|
||||||
|
|
||||||
@@ -103,6 +103,7 @@ namespace lms::recommendation
|
|||||||
constexpr float sameReleaseWeight{ 0.5F };
|
constexpr float sameReleaseWeight{ 0.5F };
|
||||||
constexpr float sameArtistWeight{ 0.5F };
|
constexpr float sameArtistWeight{ 0.5F };
|
||||||
_trackEvaluator.addHardConstraint(std::make_unique<DuplicateTrackConstraint>());
|
_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<SameReleaseConstraint>(_trackMetadata), sameReleaseWeight);
|
||||||
_trackEvaluator.addSoftConstraint(std::make_unique<SameArtistConstraint>(_trackMetadata), sameArtistWeight);
|
_trackEvaluator.addSoftConstraint(std::make_unique<SameArtistConstraint>(_trackMetadata), sameArtistWeight);
|
||||||
}
|
}
|
||||||
@@ -122,8 +123,8 @@ namespace lms::recommendation
|
|||||||
db::Session& session{ _db.getTLSSession() };
|
db::Session& session{ _db.getTLSSession() };
|
||||||
auto transaction{ session.createReadTransaction() };
|
auto transaction{ session.createReadTransaction() };
|
||||||
|
|
||||||
buildTrackMetadata(session);
|
|
||||||
buildTrackClusters(session);
|
buildTrackClusters(session);
|
||||||
|
buildTrackMetadata(session);
|
||||||
buildReleaseClusters();
|
buildReleaseClusters();
|
||||||
buildArtistClusters();
|
buildArtistClusters();
|
||||||
|
|
||||||
@@ -134,11 +135,17 @@ namespace lms::recommendation
|
|||||||
{
|
{
|
||||||
LOG(DEBUG, "building track metadata...");
|
LOG(DEBUG, "building track metadata...");
|
||||||
|
|
||||||
db::Release::find(session, db::Release::FindParameters{}, [&](const db::Release::pointer& release) {
|
// Ensure cluster tracks with no release/artist have an entry
|
||||||
db::Track::FindParameters params;
|
for (const auto& [trackId, _] : _trackClusters)
|
||||||
params.setRelease(release->getId());
|
_trackMetadata.try_emplace(trackId);
|
||||||
for (const db::TrackId trackId : db::Track::findIds(session, params).results)
|
|
||||||
_trackMetadata[trackId].releaseId = release->getId();
|
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) {
|
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;
|
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;
|
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 buildTrackClusters(db::Session& session);
|
||||||
|
void buildTrackMetadata(db::Session& session);
|
||||||
void buildReleaseClusters();
|
void buildReleaseClusters();
|
||||||
void buildArtistClusters();
|
void buildArtistClusters();
|
||||||
|
|
||||||
|
|||||||
+53
@@ -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
|
#pragma once
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "core/UUID.hpp"
|
||||||
#include "database/objects/ArtistId.hpp"
|
#include "database/objects/ArtistId.hpp"
|
||||||
#include "database/objects/ReleaseId.hpp"
|
#include "database/objects/ReleaseId.hpp"
|
||||||
#include "database/objects/TrackId.hpp"
|
#include "database/objects/TrackId.hpp"
|
||||||
@@ -30,9 +32,9 @@ namespace lms::recommendation
|
|||||||
{
|
{
|
||||||
struct TrackMetadata
|
struct TrackMetadata
|
||||||
{
|
{
|
||||||
db::ReleaseId releaseId; // invalid if track has no release
|
db::ReleaseId releaseId; // invalid if track has no release
|
||||||
std::vector<db::ArtistId> artistIds; // sorted; album artists only
|
std::vector<db::ArtistId> artistIds; // sorted, album artists only
|
||||||
// Future: db::MediaLibraryId mediaLibraryId;
|
std::optional<core::UUID> recordingMBID; // absent if track has no recording MBID
|
||||||
};
|
};
|
||||||
|
|
||||||
using TrackMetadataMap = std::unordered_map<db::TrackId, TrackMetadata>;
|
using TrackMetadataMap = std::unordered_map<db::TrackId, TrackMetadata>;
|
||||||
|
|||||||
@@ -24,390 +24,474 @@
|
|||||||
#include "database/objects/TrackId.hpp"
|
#include "database/objects/TrackId.hpp"
|
||||||
#include "math/Vector.hpp"
|
#include "math/Vector.hpp"
|
||||||
|
|
||||||
|
#include "core/UUID.hpp"
|
||||||
|
|
||||||
#include "audio-similarity/InterpolationFitConstraint.hpp"
|
#include "audio-similarity/InterpolationFitConstraint.hpp"
|
||||||
#include "audio-similarity/MaxDistanceConstraint.hpp"
|
#include "audio-similarity/MaxDistanceConstraint.hpp"
|
||||||
#include "audio-similarity/NearDuplicateEmbeddingConstraint.hpp"
|
#include "audio-similarity/NearDuplicateEmbeddingConstraint.hpp"
|
||||||
#include "audio-similarity/SmoothTransitionConstraint.hpp"
|
#include "audio-similarity/SmoothTransitionConstraint.hpp"
|
||||||
#include "track-selection-constraints/DuplicateTrackConstraint.hpp"
|
#include "track-selection-constraints/DuplicateTrackConstraint.hpp"
|
||||||
#include "track-selection-constraints/SameArtistConstraint.hpp"
|
#include "track-selection-constraints/SameArtistConstraint.hpp"
|
||||||
|
#include "track-selection-constraints/SameRecordingMBIDConstraint.hpp"
|
||||||
#include "track-selection-constraints/SameReleaseConstraint.hpp"
|
#include "track-selection-constraints/SameReleaseConstraint.hpp"
|
||||||
#include "track-selection-constraints/TrackCandidateContext.hpp"
|
#include "track-selection-constraints/TrackCandidateContext.hpp"
|
||||||
#include "track-selection-constraints/TrackCandidateEvaluator.hpp"
|
#include "track-selection-constraints/TrackCandidateEvaluator.hpp"
|
||||||
#include "track-selection-constraints/TrackMetadata.hpp"
|
#include "track-selection-constraints/TrackMetadata.hpp"
|
||||||
|
|
||||||
using namespace lms;
|
namespace lms::recommendation::tests
|
||||||
using namespace lms::recommendation;
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
{
|
||||||
const db::TrackId T1{ 1 };
|
namespace
|
||||||
const db::TrackId T2{ 2 };
|
{
|
||||||
const db::TrackId T3{ 3 };
|
const db::TrackId T1{ 1 };
|
||||||
const db::TrackId T4{ 4 };
|
const db::TrackId T2{ 2 };
|
||||||
|
const db::TrackId T3{ 3 };
|
||||||
|
const db::TrackId T4{ 4 };
|
||||||
|
|
||||||
const db::ArtistId A1{ 10 };
|
const db::ArtistId A1{ 10 };
|
||||||
const db::ArtistId A2{ 20 };
|
const db::ArtistId A2{ 20 };
|
||||||
|
|
||||||
const db::ReleaseId R1{ 100 };
|
const db::ReleaseId R1{ 100 };
|
||||||
const db::ReleaseId R2{ 200 };
|
const db::ReleaseId R2{ 200 };
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST(DuplicateTrackConstraint, acceptsNewCandidate)
|
TEST(DuplicateTrackConstraint, acceptsNewCandidate)
|
||||||
{
|
{
|
||||||
const std::vector<db::TrackId> selected{ T1, T2 };
|
const std::vector<db::TrackId> selected{ T1, T2 };
|
||||||
const TrackCandidateContext ctx{ .candidateTrackId = T3, .selectedTracks = selected, .seedTrackIds = {} };
|
const TrackCandidateContext ctx{ .candidateTrackId = T3, .selectedTracks = selected, .seedTrackIds = {} };
|
||||||
EXPECT_FALSE(DuplicateTrackConstraint{}.rejects(ctx));
|
EXPECT_FALSE(DuplicateTrackConstraint{}.rejects(ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(DuplicateTrackConstraint, rejectsAlreadySelected)
|
TEST(DuplicateTrackConstraint, rejectsAlreadySelected)
|
||||||
{
|
{
|
||||||
const std::vector<db::TrackId> selected{ T1, T2, T3 };
|
const std::vector<db::TrackId> selected{ T1, T2, T3 };
|
||||||
const TrackCandidateContext ctx{ .candidateTrackId = T2, .selectedTracks = selected, .seedTrackIds = {} };
|
const TrackCandidateContext ctx{ .candidateTrackId = T2, .selectedTracks = selected, .seedTrackIds = {} };
|
||||||
EXPECT_TRUE(DuplicateTrackConstraint{}.rejects(ctx));
|
EXPECT_TRUE(DuplicateTrackConstraint{}.rejects(ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(DuplicateTrackConstraint, acceptsWhenSelectionEmpty)
|
TEST(DuplicateTrackConstraint, acceptsWhenSelectionEmpty)
|
||||||
{
|
{
|
||||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = {} };
|
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = {} };
|
||||||
EXPECT_FALSE(DuplicateTrackConstraint{}.rejects(ctx));
|
EXPECT_FALSE(DuplicateTrackConstraint{}.rejects(ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SameArtistConstraint, zeroScoreWhenNoSharedArtist)
|
TEST(SameArtistConstraint, zeroScoreWhenNoSharedArtist)
|
||||||
{
|
{
|
||||||
const TrackMetadataMap meta{
|
const TrackMetadataMap meta{
|
||||||
{ T1, { .releaseId = {}, .artistIds = { A1 } } },
|
{ T1, { .releaseId = {}, .artistIds = { A1 } } },
|
||||||
{ T2, { .releaseId = {}, .artistIds = { A2 } } },
|
{ T2, { .releaseId = {}, .artistIds = { A2 } } },
|
||||||
};
|
};
|
||||||
const SameArtistConstraint constraint{ meta };
|
const SameArtistConstraint constraint{ meta };
|
||||||
const std::vector<db::TrackId> selected{ T2 };
|
const std::vector<db::TrackId> selected{ T2 };
|
||||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
||||||
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
|
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SameArtistConstraint, fullScoreWhenMostRecentMatchesArtist)
|
TEST(SameArtistConstraint, fullScoreWhenMostRecentMatchesArtist)
|
||||||
{
|
{
|
||||||
TrackMetadataMap meta{
|
TrackMetadataMap meta{
|
||||||
{ T1, { .releaseId = {}, .artistIds = { A1 } } },
|
{ T1, { .releaseId = {}, .artistIds = { A1 } } },
|
||||||
{ T2, { .releaseId = {}, .artistIds = { A1 } } },
|
{ T2, { .releaseId = {}, .artistIds = { A1 } } },
|
||||||
};
|
};
|
||||||
const SameArtistConstraint constraint{ meta };
|
const SameArtistConstraint constraint{ meta };
|
||||||
const std::vector<db::TrackId> selected{ T2 };
|
const std::vector<db::TrackId> selected{ T2 };
|
||||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
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)
|
TEST(SameArtistConstraint, halfScoreWhenSecondMostRecentMatchesArtist)
|
||||||
{
|
{
|
||||||
TrackMetadataMap meta{
|
TrackMetadataMap meta{
|
||||||
{ T1, { .releaseId = {}, .artistIds = { A1 } } },
|
{ T1, { .releaseId = {}, .artistIds = { A1 } } },
|
||||||
{ T2, { .releaseId = {}, .artistIds = { A2 } } },
|
{ T2, { .releaseId = {}, .artistIds = { A2 } } },
|
||||||
{ T3, { .releaseId = {}, .artistIds = { A1 } } },
|
{ T3, { .releaseId = {}, .artistIds = { A1 } } },
|
||||||
};
|
};
|
||||||
const SameArtistConstraint constraint{ meta };
|
const SameArtistConstraint constraint{ meta };
|
||||||
|
|
||||||
const std::vector<db::TrackId> selected{ T3, T2 };
|
const std::vector<db::TrackId> selected{ T3, T2 };
|
||||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
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)
|
TEST(SameArtistConstraint, trackOutsideWindowIsIgnored)
|
||||||
{
|
{
|
||||||
TrackMetadataMap meta{
|
TrackMetadataMap meta{
|
||||||
{ T1, { .releaseId = {}, .artistIds = { A1 } } },
|
{ T1, { .releaseId = {}, .artistIds = { A1 } } },
|
||||||
{ T2, { .releaseId = {}, .artistIds = { A2 } } },
|
{ T2, { .releaseId = {}, .artistIds = { A2 } } },
|
||||||
{ T3, { .releaseId = {}, .artistIds = { A1 } } },
|
{ T3, { .releaseId = {}, .artistIds = { A1 } } },
|
||||||
};
|
};
|
||||||
const SameArtistConstraint constraint{ meta, /*window=*/1 };
|
const SameArtistConstraint constraint{ meta, /*window=*/1 };
|
||||||
|
|
||||||
const std::vector<db::TrackId> selected{ T3, T2 };
|
const std::vector<db::TrackId> selected{ T3, T2 };
|
||||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
||||||
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
|
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SameArtistConstraint, zeroScoreWhenCandidateNotInMap)
|
TEST(SameArtistConstraint, zeroScoreWhenCandidateNotInMap)
|
||||||
{
|
{
|
||||||
const TrackMetadataMap meta{};
|
const TrackMetadataMap meta{};
|
||||||
const SameArtistConstraint constraint{ meta };
|
const SameArtistConstraint constraint{ meta };
|
||||||
const std::vector<db::TrackId> selected{ T2 };
|
const std::vector<db::TrackId> selected{ T2 };
|
||||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
||||||
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
|
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SameReleaseConstraint, zeroScoreWhenNoSharedRelease)
|
TEST(SameReleaseConstraint, zeroScoreWhenNoSharedRelease)
|
||||||
{
|
{
|
||||||
TrackMetadataMap meta{
|
TrackMetadataMap meta{
|
||||||
{ T1, { .releaseId = R1, .artistIds = {} } },
|
{ T1, { .releaseId = R1, .artistIds = {} } },
|
||||||
{ T2, { .releaseId = R2, .artistIds = {} } },
|
{ T2, { .releaseId = R2, .artistIds = {} } },
|
||||||
};
|
};
|
||||||
const SameReleaseConstraint constraint{ meta };
|
const SameReleaseConstraint constraint{ meta };
|
||||||
const std::vector<db::TrackId> selected{ T2 };
|
const std::vector<db::TrackId> selected{ T2 };
|
||||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
||||||
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
|
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SameReleaseConstraint, fullScoreWhenMostRecentMatchesRelease)
|
TEST(SameReleaseConstraint, fullScoreWhenMostRecentMatchesRelease)
|
||||||
{
|
{
|
||||||
TrackMetadataMap meta{
|
TrackMetadataMap meta{
|
||||||
{ T1, { .releaseId = R1, .artistIds = {} } },
|
{ T1, { .releaseId = R1, .artistIds = {} } },
|
||||||
{ T2, { .releaseId = R1, .artistIds = {} } },
|
{ T2, { .releaseId = R1, .artistIds = {} } },
|
||||||
};
|
};
|
||||||
const SameReleaseConstraint constraint{ meta };
|
const SameReleaseConstraint constraint{ meta };
|
||||||
const std::vector<db::TrackId> selected{ T2 };
|
const std::vector<db::TrackId> selected{ T2 };
|
||||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
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(SameReleaseConstraint, zeroScoreWhenCandidateHasNoRelease)
|
TEST(SameReleaseConstraint, zeroScoreWhenCandidateHasNoRelease)
|
||||||
{
|
{
|
||||||
TrackMetadataMap meta{
|
TrackMetadataMap meta{
|
||||||
{ T1, { .releaseId = {}, .artistIds = {} } },
|
{ T1, { .releaseId = {}, .artistIds = {} } },
|
||||||
{ T2, { .releaseId = R1, .artistIds = {} } },
|
{ T2, { .releaseId = R1, .artistIds = {} } },
|
||||||
};
|
};
|
||||||
const SameReleaseConstraint constraint{ meta };
|
const SameReleaseConstraint constraint{ meta };
|
||||||
const std::vector<db::TrackId> selected{ T2 };
|
const std::vector<db::TrackId> selected{ T2 };
|
||||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
||||||
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
|
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(TrackCandidateEvaluator, hardConstraintRejects)
|
TEST(TrackCandidateEvaluator, hardConstraintRejects)
|
||||||
{
|
{
|
||||||
TrackCandidateEvaluator evaluator;
|
TrackCandidateEvaluator evaluator;
|
||||||
evaluator.addHardConstraint(std::make_unique<DuplicateTrackConstraint>());
|
evaluator.addHardConstraint(std::make_unique<DuplicateTrackConstraint>());
|
||||||
|
|
||||||
const std::vector<db::TrackId> selected{ T1 };
|
const std::vector<db::TrackId> selected{ T1 };
|
||||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
||||||
EXPECT_TRUE(evaluator.rejects(ctx));
|
EXPECT_TRUE(evaluator.rejects(ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(TrackCandidateEvaluator, noHardConstraintDoesNotReject)
|
TEST(TrackCandidateEvaluator, noHardConstraintDoesNotReject)
|
||||||
{
|
{
|
||||||
TrackCandidateEvaluator evaluator;
|
TrackCandidateEvaluator evaluator;
|
||||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = {} };
|
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = {} };
|
||||||
EXPECT_FALSE(evaluator.rejects(ctx));
|
EXPECT_FALSE(evaluator.rejects(ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(TrackCandidateEvaluator, softConstraintScoreIsWeighted)
|
TEST(TrackCandidateEvaluator, softConstraintScoreIsWeighted)
|
||||||
{
|
{
|
||||||
TrackMetadataMap meta{
|
TrackMetadataMap meta{
|
||||||
{ T1, { .releaseId = R1, .artistIds = {} } },
|
{ T1, { .releaseId = R1, .artistIds = {} } },
|
||||||
{ T2, { .releaseId = R1, .artistIds = {} } },
|
{ T2, { .releaseId = R1, .artistIds = {} } },
|
||||||
};
|
};
|
||||||
TrackCandidateEvaluator evaluator;
|
TrackCandidateEvaluator evaluator;
|
||||||
evaluator.addSoftConstraint(std::make_unique<SameReleaseConstraint>(meta), 2.F);
|
evaluator.addSoftConstraint(std::make_unique<SameReleaseConstraint>(meta), 2.F);
|
||||||
|
|
||||||
const std::vector<db::TrackId> selected{ T2 };
|
const std::vector<db::TrackId> selected{ T2 };
|
||||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
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)
|
TEST(TrackCandidateEvaluator, multipleSoftConstraintsAreAccumulated)
|
||||||
{
|
{
|
||||||
TrackMetadataMap meta{
|
TrackMetadataMap meta{
|
||||||
{ T1, { .releaseId = R1, .artistIds = { A1 } } },
|
{ T1, { .releaseId = R1, .artistIds = { A1 } } },
|
||||||
{ T2, { .releaseId = R1, .artistIds = { A1 } } },
|
{ T2, { .releaseId = R1, .artistIds = { A1 } } },
|
||||||
};
|
};
|
||||||
TrackCandidateEvaluator evaluator;
|
TrackCandidateEvaluator evaluator;
|
||||||
evaluator.addSoftConstraint(std::make_unique<SameReleaseConstraint>(meta), 1.F);
|
evaluator.addSoftConstraint(std::make_unique<SameReleaseConstraint>(meta), 1.F);
|
||||||
evaluator.addSoftConstraint(std::make_unique<SameArtistConstraint>(meta), 1.F);
|
evaluator.addSoftConstraint(std::make_unique<SameArtistConstraint>(meta), 1.F);
|
||||||
|
|
||||||
const std::vector<db::TrackId> selected{ T2 };
|
const std::vector<db::TrackId> selected{ T2 };
|
||||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
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)
|
TEST(TrackCandidateEvaluator, hardConstraintPassesEvenWithSoftConstraints)
|
||||||
{
|
{
|
||||||
TrackMetadataMap meta{
|
TrackMetadataMap meta{
|
||||||
{ T1, { .releaseId = R1, .artistIds = {} } },
|
{ T1, { .releaseId = R1, .artistIds = {} } },
|
||||||
{ T2, { .releaseId = R1, .artistIds = {} } },
|
{ T2, { .releaseId = R1, .artistIds = {} } },
|
||||||
};
|
};
|
||||||
TrackCandidateEvaluator evaluator;
|
TrackCandidateEvaluator evaluator;
|
||||||
evaluator.addHardConstraint(std::make_unique<DuplicateTrackConstraint>());
|
evaluator.addHardConstraint(std::make_unique<DuplicateTrackConstraint>());
|
||||||
evaluator.addSoftConstraint(std::make_unique<SameReleaseConstraint>(meta), 1.F);
|
evaluator.addSoftConstraint(std::make_unique<SameReleaseConstraint>(meta), 1.F);
|
||||||
|
|
||||||
const std::vector<db::TrackId> selected{ T2 };
|
const std::vector<db::TrackId> selected{ T2 };
|
||||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
||||||
EXPECT_FALSE(evaluator.rejects(ctx));
|
EXPECT_FALSE(evaluator.rejects(ctx));
|
||||||
EXPECT_FLOAT_EQ(evaluator.score(ctx), 1.F);
|
EXPECT_FLOAT_EQ(evaluator.score(ctx), 1.F);
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
using TestVec = math::Vector<2, float>;
|
using TestVec = math::Vector<2, float>;
|
||||||
const TestVec v_x{ 1.F, 0.F };
|
const TestVec v_x{ 1.F, 0.F };
|
||||||
const TestVec v_y{ 0.F, 1.F };
|
const TestVec v_y{ 0.F, 1.F };
|
||||||
|
|
||||||
using TestConstraint = NearDuplicateEmbeddingConstraint<2>;
|
using TestConstraint = NearDuplicateEmbeddingConstraint<2>;
|
||||||
using TestVectorMap = TestConstraint::TrackVectorMap;
|
using TestVectorMap = TestConstraint::TrackVectorMap;
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST(NearDuplicateEmbeddingConstraint, acceptsWhenCandidateNotInMap)
|
TEST(NearDuplicateEmbeddingConstraint, acceptsWhenCandidateNotInMap)
|
||||||
{
|
{
|
||||||
const TestVectorMap trackVectors{ { T1, &v_x } };
|
const TestVectorMap trackVectors{ { T1, &v_x } };
|
||||||
const TestConstraint constraint{ trackVectors, 0.1F };
|
const TestConstraint constraint{ trackVectors, 0.1F };
|
||||||
const std::vector<db::TrackId> selected{ T1 };
|
const std::vector<db::TrackId> selected{ T1 };
|
||||||
const TrackCandidateContext ctx{ .candidateTrackId = T2, .selectedTracks = selected, .seedTrackIds = {} };
|
const TrackCandidateContext ctx{ .candidateTrackId = T2, .selectedTracks = selected, .seedTrackIds = {} };
|
||||||
EXPECT_FALSE(constraint.rejects(ctx));
|
EXPECT_FALSE(constraint.rejects(ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(NearDuplicateEmbeddingConstraint, acceptsWhenSelectionEmpty)
|
TEST(NearDuplicateEmbeddingConstraint, acceptsWhenSelectionEmpty)
|
||||||
{
|
{
|
||||||
const TestVectorMap trackVectors{ { T1, &v_x } };
|
const TestVectorMap trackVectors{ { T1, &v_x } };
|
||||||
const TestConstraint constraint{ trackVectors, 0.1F };
|
const TestConstraint constraint{ trackVectors, 0.1F };
|
||||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = {} };
|
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = {} };
|
||||||
EXPECT_FALSE(constraint.rejects(ctx));
|
EXPECT_FALSE(constraint.rejects(ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(NearDuplicateEmbeddingConstraint, acceptsWhenSelectedTrackNotInMap)
|
TEST(NearDuplicateEmbeddingConstraint, acceptsWhenSelectedTrackNotInMap)
|
||||||
{
|
{
|
||||||
const TestVectorMap trackVectors{ { T1, &v_x } };
|
const TestVectorMap trackVectors{ { T1, &v_x } };
|
||||||
const TestConstraint constraint{ trackVectors, 0.1F };
|
const TestConstraint constraint{ trackVectors, 0.1F };
|
||||||
const std::vector<db::TrackId> selected{ T2 };
|
const std::vector<db::TrackId> selected{ T2 };
|
||||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
||||||
EXPECT_FALSE(constraint.rejects(ctx));
|
EXPECT_FALSE(constraint.rejects(ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(NearDuplicateEmbeddingConstraint, rejectsWhenDistanceBelowThreshold)
|
TEST(NearDuplicateEmbeddingConstraint, rejectsWhenDistanceBelowThreshold)
|
||||||
{
|
{
|
||||||
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_x } };
|
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_x } };
|
||||||
const TestConstraint constraint{ trackVectors, 0.1F };
|
const TestConstraint constraint{ trackVectors, 0.1F };
|
||||||
const std::vector<db::TrackId> selected{ T2 };
|
const std::vector<db::TrackId> selected{ T2 };
|
||||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
||||||
EXPECT_TRUE(constraint.rejects(ctx));
|
EXPECT_TRUE(constraint.rejects(ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(NearDuplicateEmbeddingConstraint, acceptsWhenDistanceAboveThreshold)
|
TEST(NearDuplicateEmbeddingConstraint, acceptsWhenDistanceAboveThreshold)
|
||||||
{
|
{
|
||||||
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_y } };
|
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_y } };
|
||||||
const TestConstraint constraint{ trackVectors, 0.1F };
|
const TestConstraint constraint{ trackVectors, 0.1F };
|
||||||
const std::vector<db::TrackId> selected{ T2 };
|
const std::vector<db::TrackId> selected{ T2 };
|
||||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
||||||
EXPECT_FALSE(constraint.rejects(ctx));
|
EXPECT_FALSE(constraint.rejects(ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(NearDuplicateEmbeddingConstraint, rejectsWhenOneOfManySelectedIsNearDuplicate)
|
TEST(NearDuplicateEmbeddingConstraint, rejectsWhenOneOfManySelectedIsNearDuplicate)
|
||||||
{
|
{
|
||||||
const TestVectorMap trackVectors{ { T1, &v_x }, { T3, &v_y }, { T4, &v_x } };
|
const TestVectorMap trackVectors{ { T1, &v_x }, { T3, &v_y }, { T4, &v_x } };
|
||||||
const TestConstraint constraint{ trackVectors, 0.1F };
|
const TestConstraint constraint{ trackVectors, 0.1F };
|
||||||
const std::vector<db::TrackId> selected{ T3, T4 };
|
const std::vector<db::TrackId> selected{ T3, T4 };
|
||||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
||||||
EXPECT_TRUE(constraint.rejects(ctx));
|
EXPECT_TRUE(constraint.rejects(ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(NearDuplicateEmbeddingConstraint, acceptsWhenAllSelectedAreFarEnough)
|
TEST(NearDuplicateEmbeddingConstraint, acceptsWhenAllSelectedAreFarEnough)
|
||||||
{
|
{
|
||||||
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_y }, { T3, &v_y } };
|
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_y }, { T3, &v_y } };
|
||||||
const TestConstraint constraint{ trackVectors, 0.1F };
|
const TestConstraint constraint{ trackVectors, 0.1F };
|
||||||
const std::vector<db::TrackId> selected{ T2, T3 };
|
const std::vector<db::TrackId> selected{ T2, T3 };
|
||||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
||||||
EXPECT_FALSE(constraint.rejects(ctx));
|
EXPECT_FALSE(constraint.rejects(ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(InterpolationFitConstraint, zeroScoreWhenNoSeeds)
|
TEST(InterpolationFitConstraint, zeroScoreWhenNoSeeds)
|
||||||
{
|
{
|
||||||
const TestVectorMap trackVectors{ { T1, &v_x } };
|
const TestVectorMap trackVectors{ { T1, &v_x } };
|
||||||
const InterpolationFitConstraint<2> constraint{ trackVectors };
|
const InterpolationFitConstraint<2> constraint{ trackVectors };
|
||||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = {} };
|
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = {} };
|
||||||
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
|
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(InterpolationFitConstraint, zeroScoreWhenCandidateMatchesSeed)
|
TEST(InterpolationFitConstraint, zeroScoreWhenCandidateMatchesSeed)
|
||||||
{
|
{
|
||||||
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_x } };
|
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_x } };
|
||||||
const InterpolationFitConstraint<2> constraint{ trackVectors };
|
const InterpolationFitConstraint<2> constraint{ trackVectors };
|
||||||
const std::vector<db::TrackId> seeds{ T2 };
|
const std::vector<db::TrackId> seeds{ T2 };
|
||||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = seeds };
|
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = seeds };
|
||||||
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
|
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(InterpolationFitConstraint, halfScoreWhenCandidateOrthogonalToSeed)
|
TEST(InterpolationFitConstraint, halfScoreWhenCandidateOrthogonalToSeed)
|
||||||
{
|
{
|
||||||
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_y } };
|
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_y } };
|
||||||
const InterpolationFitConstraint<2> constraint{ trackVectors };
|
const InterpolationFitConstraint<2> constraint{ trackVectors };
|
||||||
const std::vector<db::TrackId> seeds{ T2 };
|
const std::vector<db::TrackId> seeds{ T2 };
|
||||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = seeds };
|
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = seeds };
|
||||||
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.5F);
|
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.5F);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(InterpolationFitConstraint, usesClosestSeed)
|
TEST(InterpolationFitConstraint, usesClosestSeed)
|
||||||
{
|
{
|
||||||
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_x }, { T3, &v_y } };
|
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_x }, { T3, &v_y } };
|
||||||
const InterpolationFitConstraint<2> constraint{ trackVectors };
|
const InterpolationFitConstraint<2> constraint{ trackVectors };
|
||||||
const std::vector<db::TrackId> seeds{ T3, T2 };
|
const std::vector<db::TrackId> seeds{ T3, T2 };
|
||||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = seeds };
|
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = seeds };
|
||||||
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
|
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(MaxDistanceConstraint, acceptsWhenNoSeeds)
|
TEST(MaxDistanceConstraint, acceptsWhenNoSeeds)
|
||||||
{
|
{
|
||||||
const TestVectorMap trackVectors{ { T1, &v_x } };
|
const TestVectorMap trackVectors{ { T1, &v_x } };
|
||||||
const MaxDistanceConstraint<2> constraint{ trackVectors, 0.5F };
|
const MaxDistanceConstraint<2> constraint{ trackVectors, 0.5F };
|
||||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = {} };
|
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = {} };
|
||||||
EXPECT_FALSE(constraint.rejects(ctx));
|
EXPECT_FALSE(constraint.rejects(ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(MaxDistanceConstraint, acceptsWhenWithinThresholdOfOneSeed)
|
TEST(MaxDistanceConstraint, acceptsWhenWithinThresholdOfOneSeed)
|
||||||
{
|
{
|
||||||
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_x } };
|
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_x } };
|
||||||
const MaxDistanceConstraint<2> constraint{ trackVectors, 0.5F };
|
const MaxDistanceConstraint<2> constraint{ trackVectors, 0.5F };
|
||||||
const std::vector<db::TrackId> seeds{ T2 };
|
const std::vector<db::TrackId> seeds{ T2 };
|
||||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = seeds };
|
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = seeds };
|
||||||
EXPECT_FALSE(constraint.rejects(ctx));
|
EXPECT_FALSE(constraint.rejects(ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(MaxDistanceConstraint, rejectsWhenBeyondThresholdFromAllSeeds)
|
TEST(MaxDistanceConstraint, rejectsWhenBeyondThresholdFromAllSeeds)
|
||||||
{
|
{
|
||||||
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_y } };
|
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_y } };
|
||||||
const MaxDistanceConstraint<2> constraint{ trackVectors, 0.1F };
|
const MaxDistanceConstraint<2> constraint{ trackVectors, 0.1F };
|
||||||
const std::vector<db::TrackId> seeds{ T2 };
|
const std::vector<db::TrackId> seeds{ T2 };
|
||||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = seeds };
|
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = seeds };
|
||||||
EXPECT_TRUE(constraint.rejects(ctx));
|
EXPECT_TRUE(constraint.rejects(ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(MaxDistanceConstraint, acceptsWhenAnyOfManySeedsIsCloseEnough)
|
TEST(MaxDistanceConstraint, acceptsWhenAnyOfManySeedsIsCloseEnough)
|
||||||
{
|
{
|
||||||
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_y }, { T3, &v_x } };
|
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_y }, { T3, &v_x } };
|
||||||
const MaxDistanceConstraint<2> constraint{ trackVectors, 0.5F };
|
const MaxDistanceConstraint<2> constraint{ trackVectors, 0.5F };
|
||||||
const std::vector<db::TrackId> seeds{ T2, T3 };
|
const std::vector<db::TrackId> seeds{ T2, T3 };
|
||||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = seeds };
|
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = seeds };
|
||||||
EXPECT_FALSE(constraint.rejects(ctx));
|
EXPECT_FALSE(constraint.rejects(ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SmoothTransitionConstraint, zeroScoreWhenNoSelectedTracks)
|
TEST(SmoothTransitionConstraint, zeroScoreWhenNoSelectedTracks)
|
||||||
{
|
{
|
||||||
const TestVectorMap trackVectors{ { T1, &v_x } };
|
const TestVectorMap trackVectors{ { T1, &v_x } };
|
||||||
const SmoothTransitionConstraint<2> constraint{ trackVectors };
|
const SmoothTransitionConstraint<2> constraint{ trackVectors };
|
||||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = {} };
|
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = {} };
|
||||||
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
|
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SmoothTransitionConstraint, zeroScoreWhenPreviousMatchesCandidate)
|
TEST(SmoothTransitionConstraint, zeroScoreWhenPreviousMatchesCandidate)
|
||||||
{
|
{
|
||||||
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_x } };
|
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_x } };
|
||||||
const SmoothTransitionConstraint<2> constraint{ trackVectors };
|
const SmoothTransitionConstraint<2> constraint{ trackVectors };
|
||||||
const std::vector<db::TrackId> selected{ T2 };
|
const std::vector<db::TrackId> selected{ T2 };
|
||||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
||||||
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
|
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(SmoothTransitionConstraint, halfScoreWhenPreviousOrthogonalToCandidate)
|
TEST(SmoothTransitionConstraint, halfScoreWhenPreviousOrthogonalToCandidate)
|
||||||
{
|
{
|
||||||
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_y } };
|
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_y } };
|
||||||
const SmoothTransitionConstraint<2> constraint{ trackVectors };
|
const SmoothTransitionConstraint<2> constraint{ trackVectors };
|
||||||
const std::vector<db::TrackId> selected{ T2 };
|
const std::vector<db::TrackId> selected{ T2 };
|
||||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
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(SmoothTransitionConstraint, usesMostRecentlySelectedTrack)
|
TEST(SmoothTransitionConstraint, usesMostRecentlySelectedTrack)
|
||||||
{
|
{
|
||||||
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_x }, { T3, &v_y } };
|
const TestVectorMap trackVectors{ { T1, &v_x }, { T2, &v_x }, { T3, &v_y } };
|
||||||
const SmoothTransitionConstraint<2> constraint{ trackVectors };
|
const SmoothTransitionConstraint<2> constraint{ trackVectors };
|
||||||
const std::vector<db::TrackId> selected{ T3, T2 };
|
const std::vector<db::TrackId> selected{ T3, T2 };
|
||||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
||||||
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
|
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
|
||||||
Reference in New Issue
Block a user