Relaxed constraints on play queue auto filling + cleaned audio similarity constraints
This commit is contained in:
+27
-53
@@ -49,13 +49,13 @@
|
||||
#include "math/PrincipalComponents.hpp"
|
||||
#include "math/StatsAccumulator.hpp"
|
||||
|
||||
#include "InterpolationFitConstraint.hpp"
|
||||
#include "MaxDistanceConstraint.hpp"
|
||||
#include "NearDuplicateEmbeddingConstraint.hpp"
|
||||
#include "SmoothTransitionConstraint.hpp"
|
||||
#include "track-selection-constraints/DuplicateTrackConstraint.hpp"
|
||||
#include "track-selection-constraints/InterpolationFitConstraint.hpp"
|
||||
#include "track-selection-constraints/MaxDistanceConstraint.hpp"
|
||||
#include "track-selection-constraints/SameArtistConstraint.hpp"
|
||||
#include "track-selection-constraints/SameReleaseConstraint.hpp"
|
||||
#include "track-selection-constraints/SmoothTransitionConstraint.hpp"
|
||||
|
||||
#include "Types.hpp"
|
||||
|
||||
@@ -121,17 +121,17 @@ namespace lms::recommendation
|
||||
_similarityEvaluator = {};
|
||||
_similarityEvaluator.addHardConstraint(std::make_unique<DuplicateTrackConstraint>());
|
||||
_similarityEvaluator.addHardConstraint(std::make_unique<NearDuplicateEmbeddingConstraint<ReducedDimCount>>(_trackVectors, nearDuplicateThreshold));
|
||||
_similarityEvaluator.addHardConstraint(std::make_unique<MaxDistanceConstraint>(_trackDistanceThreshold));
|
||||
_similarityEvaluator.addSoftConstraint(std::make_unique<InterpolationFitConstraint>(), interpolationFitWeight);
|
||||
_similarityEvaluator.addSoftConstraint(std::make_unique<SmoothTransitionConstraint>(), smoothTransitionWeight);
|
||||
_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);
|
||||
_similarityEvaluator.addSoftConstraint(std::make_unique<SameReleaseConstraint>(_trackMetadata), sameReleaseWeight);
|
||||
_similarityEvaluator.addSoftConstraint(std::make_unique<SameArtistConstraint>(_trackMetadata), sameArtistWeight);
|
||||
|
||||
_pathEvaluator = {};
|
||||
_pathEvaluator.addHardConstraint(std::make_unique<DuplicateTrackConstraint>());
|
||||
_pathEvaluator.addHardConstraint(std::make_unique<NearDuplicateEmbeddingConstraint<ReducedDimCount>>(_trackVectors, nearDuplicateThreshold));
|
||||
_pathEvaluator.addSoftConstraint(std::make_unique<InterpolationFitConstraint>(), interpolationFitWeight);
|
||||
_pathEvaluator.addSoftConstraint(std::make_unique<SmoothTransitionConstraint>(), smoothTransitionWeight);
|
||||
_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);
|
||||
_pathEvaluator.addSoftConstraint(std::make_unique<SameArtistConstraint>(_trackMetadata), sameArtistWeight);
|
||||
}
|
||||
@@ -210,8 +210,6 @@ namespace lms::recommendation
|
||||
rankedTracks.resize(candidateCount);
|
||||
|
||||
// Greedy selection: at each step pick the candidate with the lowest penalized score.
|
||||
// distanceToPrevious is the cosine distance to the last selected track, so that
|
||||
// SmoothTransitionConstraint penalises large acoustic jumps between consecutive results.
|
||||
// Pre-seed selectedTracks with the input tracks so that soft constraints (same release,
|
||||
// same artist) treat them as already taken, preventing the first results from being
|
||||
// from the same release/artist as the inputs.
|
||||
@@ -219,8 +217,6 @@ namespace lms::recommendation
|
||||
selectedTracks.reserve(selectedTracks.size() + maxCount);
|
||||
res.reserve(maxCount);
|
||||
|
||||
const ReducedVector* previousVector{};
|
||||
|
||||
while (res.size() < maxCount && !rankedTracks.empty())
|
||||
{
|
||||
std::optional<std::size_t> bestIdx;
|
||||
@@ -228,15 +224,12 @@ namespace lms::recommendation
|
||||
|
||||
for (std::size_t i{}; i < rankedTracks.size(); ++i)
|
||||
{
|
||||
const auto& [candidateId, distanceToQuery]{ rankedTracks[i] };
|
||||
const ReducedVector* candidateVector{ _trackVectors.at(candidateId) };
|
||||
const float distanceToPrevious{ previousVector ? math::NormalizedCosineDistance{ *previousVector }(*candidateVector) : 0.F };
|
||||
const db::TrackId candidateId{ rankedTracks[i].first };
|
||||
|
||||
const TrackCandidateContext context{
|
||||
.candidateTrackId = candidateId,
|
||||
.selectedTracks = selectedTracks,
|
||||
.distanceToQuery = distanceToQuery,
|
||||
.distanceToPrevious = distanceToPrevious,
|
||||
.seedTrackIds = tracksId,
|
||||
};
|
||||
|
||||
if (_similarityEvaluator.rejects(context))
|
||||
@@ -256,7 +249,6 @@ namespace lms::recommendation
|
||||
const auto& [selectedId, distanceToQuery]{ rankedTracks[*bestIdx] };
|
||||
res.push_back({ .id = selectedId, .distance = distanceToQuery });
|
||||
selectedTracks.push_back(selectedId);
|
||||
previousVector = _trackVectors.at(selectedId);
|
||||
rankedTracks.erase(std::begin(rankedTracks) + static_cast<std::ptrdiff_t>(*bestIdx));
|
||||
}
|
||||
|
||||
@@ -284,26 +276,28 @@ namespace lms::recommendation
|
||||
path.reserve(maxCount);
|
||||
path.push_back(startTrackId);
|
||||
|
||||
const ReducedVector* previousVector{ itStart->second };
|
||||
static constexpr std::size_t DefaultNeighborCount{ 16 };
|
||||
static constexpr std::size_t BroadNeighborCount{ 64 };
|
||||
std::size_t neighborCount{ DefaultNeighborCount };
|
||||
static constexpr std::size_t NeighborCount{ 32 };
|
||||
const std::size_t interiorCount{ (maxCount > 2) ? (maxCount - 2) : 0 };
|
||||
|
||||
auto evaluateCandidates = [&](const TrackResults& neighborList) -> std::optional<db::TrackId> {
|
||||
for (std::size_t i{}; i < interiorCount; ++i)
|
||||
{
|
||||
const float t{ static_cast<float>(i + 1) / static_cast<float>(interiorCount + 1) };
|
||||
auto queryPoint{ startVector + direction * t };
|
||||
queryPoint.normalizeL2();
|
||||
|
||||
const auto neighbors{ detail::findNearestNeighbors(queryPoint, _trackVectors, NeighborCount, endTrackId) };
|
||||
const db::TrackId stepSeedTrackId{ neighbors.empty() ? startTrackId : neighbors[0].id };
|
||||
const std::array<db::TrackId, 1> stepSeedTrackIds{ stepSeedTrackId };
|
||||
|
||||
std::optional<db::TrackId> best;
|
||||
float bestScore{ std::numeric_limits<float>::max() };
|
||||
|
||||
for (const auto& [candidateId, candidateDistance] : neighborList)
|
||||
for (const auto& [candidateTrackId, candidateDistance] : neighbors)
|
||||
{
|
||||
const auto* candidateVector{ _trackVectors.at(candidateId) };
|
||||
const float transitionDistance{ math::NormalizedCosineDistance{ *previousVector }(*candidateVector) };
|
||||
|
||||
const TrackCandidateContext context{
|
||||
.candidateTrackId = candidateId,
|
||||
.candidateTrackId = candidateTrackId,
|
||||
.selectedTracks = path,
|
||||
.distanceToQuery = candidateDistance,
|
||||
.distanceToPrevious = transitionDistance,
|
||||
.seedTrackIds = stepSeedTrackIds,
|
||||
};
|
||||
|
||||
if (_pathEvaluator.rejects(context))
|
||||
@@ -313,34 +307,14 @@ namespace lms::recommendation
|
||||
if (score < bestScore)
|
||||
{
|
||||
bestScore = score;
|
||||
best = candidateId;
|
||||
best = candidateTrackId;
|
||||
}
|
||||
}
|
||||
|
||||
return best;
|
||||
};
|
||||
|
||||
for (std::size_t i{}; i < interiorCount; ++i)
|
||||
{
|
||||
const float t{ static_cast<float>(i + 1) / static_cast<float>(interiorCount + 1) };
|
||||
auto queryPoint{ startVector + direction * t };
|
||||
queryPoint.normalizeL2();
|
||||
|
||||
const auto neighbors{ detail::findNearestNeighbors(queryPoint, _trackVectors, neighborCount, endTrackId) };
|
||||
std::optional<db::TrackId> bestCandidate{ evaluateCandidates(neighbors) };
|
||||
|
||||
if (!bestCandidate && neighborCount < BroadNeighborCount)
|
||||
{
|
||||
neighborCount = BroadNeighborCount;
|
||||
const auto broaderNeighbors{ detail::findNearestNeighbors(queryPoint, _trackVectors, neighborCount, endTrackId) };
|
||||
bestCandidate = evaluateCandidates(broaderNeighbors);
|
||||
}
|
||||
|
||||
if (!bestCandidate)
|
||||
if (!best)
|
||||
continue;
|
||||
|
||||
path.push_back(*bestCandidate);
|
||||
previousVector = _trackVectors.at(*bestCandidate);
|
||||
path.push_back(*best);
|
||||
}
|
||||
|
||||
if (maxCount > 1)
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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 <limits>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "database/objects/TrackId.hpp"
|
||||
#include "math/NormalizedCosineDistance.hpp"
|
||||
#include "math/Vector.hpp"
|
||||
|
||||
#include "Types.hpp"
|
||||
#include "track-selection-constraints/ITrackCandidateSoftConstraint.hpp"
|
||||
|
||||
namespace lms::recommendation
|
||||
{
|
||||
template<std::size_t ReducedDimCount>
|
||||
class InterpolationFitConstraint : public ITrackCandidateSoftConstraint
|
||||
{
|
||||
public:
|
||||
using ReducedVector = math::Vector<ReducedDimCount, FloatType>;
|
||||
using TrackVectorMap = std::unordered_map<db::TrackId, const ReducedVector*>;
|
||||
|
||||
explicit InterpolationFitConstraint(const TrackVectorMap& trackVectors)
|
||||
: _trackVectors{ trackVectors }
|
||||
{
|
||||
}
|
||||
|
||||
~InterpolationFitConstraint() override = default;
|
||||
InterpolationFitConstraint(const InterpolationFitConstraint&) = delete;
|
||||
InterpolationFitConstraint& operator=(const InterpolationFitConstraint&) = delete;
|
||||
|
||||
float computeScore(const TrackCandidateContext& context) const override
|
||||
{
|
||||
const auto itCand{ _trackVectors.find(context.candidateTrackId) };
|
||||
if (itCand == _trackVectors.cend())
|
||||
return {};
|
||||
|
||||
float best{ std::numeric_limits<float>::max() };
|
||||
for (const db::TrackId seedId : context.seedTrackIds)
|
||||
{
|
||||
const auto it{ _trackVectors.find(seedId) };
|
||||
if (it != _trackVectors.cend())
|
||||
best = std::min(best, math::computeNormalizedCosineDistance(*it->second, *itCand->second));
|
||||
}
|
||||
return (best == std::numeric_limits<float>::max()) ? 0.F : best;
|
||||
}
|
||||
|
||||
private:
|
||||
const TrackVectorMap& _trackVectors;
|
||||
};
|
||||
} // namespace lms::recommendation
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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 <unordered_map>
|
||||
|
||||
#include "database/objects/TrackId.hpp"
|
||||
#include "math/NormalizedCosineDistance.hpp"
|
||||
#include "math/Vector.hpp"
|
||||
|
||||
#include "Types.hpp"
|
||||
#include "track-selection-constraints/ITrackCandidateHardConstraint.hpp"
|
||||
|
||||
namespace lms::recommendation
|
||||
{
|
||||
// Rejects any candidate whose distance to every seed track exceeds the threshold
|
||||
template<std::size_t ReducedDimCount>
|
||||
class MaxDistanceConstraint : public ITrackCandidateHardConstraint
|
||||
{
|
||||
public:
|
||||
using ReducedVector = math::Vector<ReducedDimCount, FloatType>;
|
||||
using TrackVectorMap = std::unordered_map<db::TrackId, const ReducedVector*>;
|
||||
|
||||
MaxDistanceConstraint(const TrackVectorMap& trackVectors, float threshold)
|
||||
: _trackVectors{ trackVectors }
|
||||
, _threshold{ threshold }
|
||||
{
|
||||
}
|
||||
|
||||
~MaxDistanceConstraint() override = default;
|
||||
MaxDistanceConstraint(const MaxDistanceConstraint&) = delete;
|
||||
MaxDistanceConstraint& operator=(const MaxDistanceConstraint&) = delete;
|
||||
|
||||
bool rejects(const TrackCandidateContext& context) const override
|
||||
{
|
||||
const auto itCand{ _trackVectors.find(context.candidateTrackId) };
|
||||
if (itCand == _trackVectors.cend())
|
||||
return false;
|
||||
|
||||
for (const db::TrackId seedId : context.seedTrackIds)
|
||||
{
|
||||
const auto it{ _trackVectors.find(seedId) };
|
||||
if (it != _trackVectors.cend() && math::computeNormalizedCosineDistance(*it->second, *itCand->second) <= _threshold)
|
||||
return false;
|
||||
}
|
||||
return !context.seedTrackIds.empty();
|
||||
}
|
||||
|
||||
private:
|
||||
const TrackVectorMap& _trackVectors;
|
||||
float _threshold;
|
||||
};
|
||||
} // namespace lms::recommendation
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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 <unordered_map>
|
||||
|
||||
#include "database/objects/TrackId.hpp"
|
||||
#include "math/NormalizedCosineDistance.hpp"
|
||||
#include "math/Vector.hpp"
|
||||
|
||||
#include "Types.hpp"
|
||||
#include "track-selection-constraints/ITrackCandidateSoftConstraint.hpp"
|
||||
|
||||
namespace lms::recommendation
|
||||
{
|
||||
template<std::size_t ReducedDimCount>
|
||||
class SmoothTransitionConstraint : public ITrackCandidateSoftConstraint
|
||||
{
|
||||
public:
|
||||
using ReducedVector = math::Vector<ReducedDimCount, FloatType>;
|
||||
using TrackVectorMap = std::unordered_map<db::TrackId, const ReducedVector*>;
|
||||
|
||||
explicit SmoothTransitionConstraint(const TrackVectorMap& trackVectors)
|
||||
: _trackVectors{ trackVectors }
|
||||
{
|
||||
}
|
||||
|
||||
~SmoothTransitionConstraint() override = default;
|
||||
SmoothTransitionConstraint(const SmoothTransitionConstraint&) = delete;
|
||||
SmoothTransitionConstraint& operator=(const SmoothTransitionConstraint&) = delete;
|
||||
|
||||
float computeScore(const TrackCandidateContext& context) const override
|
||||
{
|
||||
if (context.selectedTracks.empty())
|
||||
return {};
|
||||
|
||||
const auto itCand{ _trackVectors.find(context.candidateTrackId) };
|
||||
if (itCand == _trackVectors.cend())
|
||||
return {};
|
||||
|
||||
const auto it{ _trackVectors.find(context.selectedTracks.back()) };
|
||||
if (it == _trackVectors.cend())
|
||||
return {};
|
||||
|
||||
return math::computeNormalizedCosineDistance(*it->second, *itCand->second);
|
||||
}
|
||||
|
||||
private:
|
||||
const TrackVectorMap& _trackVectors;
|
||||
};
|
||||
} // namespace lms::recommendation
|
||||
@@ -278,6 +278,7 @@ namespace lms::recommendation
|
||||
const TrackCandidateContext context{
|
||||
.candidateTrackId = candidates[i],
|
||||
.selectedTracks = selectedTracks,
|
||||
.seedTrackIds = {},
|
||||
};
|
||||
|
||||
if (_trackEvaluator.rejects(context))
|
||||
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
* 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 "ITrackCandidateSoftConstraint.hpp"
|
||||
|
||||
namespace lms::recommendation
|
||||
{
|
||||
class InterpolationFitConstraint : public ITrackCandidateSoftConstraint
|
||||
{
|
||||
public:
|
||||
float computeScore(const TrackCandidateContext& context) const override
|
||||
{
|
||||
return context.distanceToQuery;
|
||||
}
|
||||
};
|
||||
} // namespace lms::recommendation
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
namespace lms::recommendation
|
||||
{
|
||||
// Rejects any candidate whose distance to the query exceeds a given threshold
|
||||
class MaxDistanceConstraint : public ITrackCandidateHardConstraint
|
||||
{
|
||||
public:
|
||||
explicit MaxDistanceConstraint(float threshold)
|
||||
: _threshold{ threshold }
|
||||
{
|
||||
}
|
||||
|
||||
bool rejects(const TrackCandidateContext& context) const override
|
||||
{
|
||||
return context.distanceToQuery > _threshold;
|
||||
}
|
||||
|
||||
private:
|
||||
float _threshold;
|
||||
};
|
||||
} // namespace lms::recommendation
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
* 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 "ITrackCandidateSoftConstraint.hpp"
|
||||
|
||||
namespace lms::recommendation
|
||||
{
|
||||
class SmoothTransitionConstraint : public ITrackCandidateSoftConstraint
|
||||
{
|
||||
public:
|
||||
float computeScore(const TrackCandidateContext& context) const override
|
||||
{
|
||||
return context.distanceToPrevious;
|
||||
}
|
||||
};
|
||||
} // namespace lms::recommendation
|
||||
+1
-2
@@ -29,7 +29,6 @@ namespace lms::recommendation
|
||||
{
|
||||
db::TrackId candidateTrackId;
|
||||
std::span<const db::TrackId> selectedTracks;
|
||||
float distanceToQuery{};
|
||||
float distanceToPrevious{};
|
||||
std::span<const db::TrackId> seedTrackIds;
|
||||
};
|
||||
} // namespace lms::recommendation
|
||||
|
||||
@@ -24,7 +24,10 @@
|
||||
#include "database/objects/TrackId.hpp"
|
||||
#include "math/Vector.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/SameReleaseConstraint.hpp"
|
||||
@@ -41,7 +44,6 @@ namespace
|
||||
const db::TrackId T2{ 2 };
|
||||
const db::TrackId T3{ 3 };
|
||||
const db::TrackId T4{ 4 };
|
||||
const db::TrackId T5{ 5 };
|
||||
|
||||
const db::ArtistId A1{ 10 };
|
||||
const db::ArtistId A2{ 20 };
|
||||
@@ -53,20 +55,20 @@ namespace
|
||||
TEST(DuplicateTrackConstraint, acceptsNewCandidate)
|
||||
{
|
||||
const std::vector<db::TrackId> selected{ T1, T2 };
|
||||
const TrackCandidateContext ctx{ .candidateTrackId = T3, .selectedTracks = selected };
|
||||
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 };
|
||||
const TrackCandidateContext ctx{ .candidateTrackId = T2, .selectedTracks = selected, .seedTrackIds = {} };
|
||||
EXPECT_TRUE(DuplicateTrackConstraint{}.rejects(ctx));
|
||||
}
|
||||
|
||||
TEST(DuplicateTrackConstraint, acceptsWhenSelectionEmpty)
|
||||
{
|
||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {} };
|
||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = {} };
|
||||
EXPECT_FALSE(DuplicateTrackConstraint{}.rejects(ctx));
|
||||
}
|
||||
|
||||
@@ -78,7 +80,7 @@ TEST(SameArtistConstraint, zeroScoreWhenNoSharedArtist)
|
||||
};
|
||||
const SameArtistConstraint constraint{ meta };
|
||||
const std::vector<db::TrackId> selected{ T2 };
|
||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected };
|
||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
||||
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
|
||||
}
|
||||
|
||||
@@ -90,7 +92,7 @@ TEST(SameArtistConstraint, fullScoreWhenMostRecentMatchesArtist)
|
||||
};
|
||||
const SameArtistConstraint constraint{ meta };
|
||||
const std::vector<db::TrackId> selected{ T2 };
|
||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected };
|
||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
||||
|
||||
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 1.F);
|
||||
}
|
||||
@@ -105,7 +107,7 @@ TEST(SameArtistConstraint, halfScoreWhenSecondMostRecentMatchesArtist)
|
||||
const SameArtistConstraint constraint{ meta };
|
||||
|
||||
const std::vector<db::TrackId> selected{ T3, T2 };
|
||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected };
|
||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
||||
|
||||
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.5F);
|
||||
}
|
||||
@@ -113,23 +115,15 @@ TEST(SameArtistConstraint, halfScoreWhenSecondMostRecentMatchesArtist)
|
||||
TEST(SameArtistConstraint, trackOutsideWindowIsIgnored)
|
||||
{
|
||||
TrackMetadataMap meta{
|
||||
{ T1, { .releaseId = {}, .artistIds = { A1 } } },
|
||||
{ T2, { .releaseId = {}, .artistIds = { A2 } } },
|
||||
{ T3, { .releaseId = {}, .artistIds = { A2 } } },
|
||||
{ T4, { .releaseId = {}, .artistIds = { A2 } } },
|
||||
{ T5, { .releaseId = {}, .artistIds = { A1 } } }, // outside window=4
|
||||
};
|
||||
const SameArtistConstraint constraint{ meta, /*window=*/4 };
|
||||
TrackMetadataMap meta2{
|
||||
{ T1, { .releaseId = {}, .artistIds = { A1 } } },
|
||||
{ T2, { .releaseId = {}, .artistIds = { A2 } } },
|
||||
{ T3, { .releaseId = {}, .artistIds = { A1 } } },
|
||||
};
|
||||
const SameArtistConstraint constraint2{ meta2, /*window=*/1 };
|
||||
const SameArtistConstraint constraint{ meta, /*window=*/1 };
|
||||
|
||||
const std::vector<db::TrackId> selected{ T3, T2 };
|
||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected };
|
||||
EXPECT_FLOAT_EQ(constraint2.computeScore(ctx), 0.F);
|
||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
||||
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
|
||||
}
|
||||
|
||||
TEST(SameArtistConstraint, zeroScoreWhenCandidateNotInMap)
|
||||
@@ -137,7 +131,7 @@ TEST(SameArtistConstraint, zeroScoreWhenCandidateNotInMap)
|
||||
const TrackMetadataMap meta{};
|
||||
const SameArtistConstraint constraint{ meta };
|
||||
const std::vector<db::TrackId> selected{ T2 };
|
||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected };
|
||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
||||
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
|
||||
}
|
||||
|
||||
@@ -149,7 +143,7 @@ TEST(SameReleaseConstraint, zeroScoreWhenNoSharedRelease)
|
||||
};
|
||||
const SameReleaseConstraint constraint{ meta };
|
||||
const std::vector<db::TrackId> selected{ T2 };
|
||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected };
|
||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
||||
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
|
||||
}
|
||||
|
||||
@@ -161,7 +155,7 @@ TEST(SameReleaseConstraint, fullScoreWhenMostRecentMatchesRelease)
|
||||
};
|
||||
const SameReleaseConstraint constraint{ meta };
|
||||
const std::vector<db::TrackId> selected{ T2 };
|
||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected };
|
||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
||||
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 1.F);
|
||||
}
|
||||
|
||||
@@ -173,7 +167,7 @@ TEST(SameReleaseConstraint, zeroScoreWhenCandidateHasNoRelease)
|
||||
};
|
||||
const SameReleaseConstraint constraint{ meta };
|
||||
const std::vector<db::TrackId> selected{ T2 };
|
||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected };
|
||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
||||
EXPECT_FLOAT_EQ(constraint.computeScore(ctx), 0.F);
|
||||
}
|
||||
|
||||
@@ -183,14 +177,14 @@ TEST(TrackCandidateEvaluator, hardConstraintRejects)
|
||||
evaluator.addHardConstraint(std::make_unique<DuplicateTrackConstraint>());
|
||||
|
||||
const std::vector<db::TrackId> selected{ T1 };
|
||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected };
|
||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
||||
EXPECT_TRUE(evaluator.rejects(ctx));
|
||||
}
|
||||
|
||||
TEST(TrackCandidateEvaluator, noHardConstraintDoesNotReject)
|
||||
{
|
||||
TrackCandidateEvaluator evaluator;
|
||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {} };
|
||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = {} };
|
||||
EXPECT_FALSE(evaluator.rejects(ctx));
|
||||
}
|
||||
|
||||
@@ -204,7 +198,7 @@ TEST(TrackCandidateEvaluator, softConstraintScoreIsWeighted)
|
||||
evaluator.addSoftConstraint(std::make_unique<SameReleaseConstraint>(meta), 2.F);
|
||||
|
||||
const std::vector<db::TrackId> selected{ T2 };
|
||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected };
|
||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
||||
|
||||
EXPECT_FLOAT_EQ(evaluator.score(ctx), 2.F);
|
||||
}
|
||||
@@ -220,7 +214,7 @@ TEST(TrackCandidateEvaluator, multipleSoftConstraintsAreAccumulated)
|
||||
evaluator.addSoftConstraint(std::make_unique<SameArtistConstraint>(meta), 1.F);
|
||||
|
||||
const std::vector<db::TrackId> selected{ T2 };
|
||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected };
|
||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
||||
|
||||
EXPECT_FLOAT_EQ(evaluator.score(ctx), 2.F);
|
||||
}
|
||||
@@ -236,7 +230,7 @@ TEST(TrackCandidateEvaluator, hardConstraintPassesEvenWithSoftConstraints)
|
||||
evaluator.addSoftConstraint(std::make_unique<SameReleaseConstraint>(meta), 1.F);
|
||||
|
||||
const std::vector<db::TrackId> selected{ T2 };
|
||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected };
|
||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
||||
EXPECT_FALSE(evaluator.rejects(ctx));
|
||||
EXPECT_FLOAT_EQ(evaluator.score(ctx), 1.F);
|
||||
}
|
||||
@@ -256,7 +250,7 @@ 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 };
|
||||
const TrackCandidateContext ctx{ .candidateTrackId = T2, .selectedTracks = selected, .seedTrackIds = {} };
|
||||
EXPECT_FALSE(constraint.rejects(ctx));
|
||||
}
|
||||
|
||||
@@ -264,7 +258,7 @@ TEST(NearDuplicateEmbeddingConstraint, acceptsWhenSelectionEmpty)
|
||||
{
|
||||
const TestVectorMap trackVectors{ { T1, &v_x } };
|
||||
const TestConstraint constraint{ trackVectors, 0.1F };
|
||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {} };
|
||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = {}, .seedTrackIds = {} };
|
||||
EXPECT_FALSE(constraint.rejects(ctx));
|
||||
}
|
||||
|
||||
@@ -273,7 +267,7 @@ 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 };
|
||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
||||
EXPECT_FALSE(constraint.rejects(ctx));
|
||||
}
|
||||
|
||||
@@ -282,7 +276,7 @@ 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 };
|
||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
||||
EXPECT_TRUE(constraint.rejects(ctx));
|
||||
}
|
||||
|
||||
@@ -291,7 +285,7 @@ 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 };
|
||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
||||
EXPECT_FALSE(constraint.rejects(ctx));
|
||||
}
|
||||
|
||||
@@ -300,7 +294,7 @@ TEST(NearDuplicateEmbeddingConstraint, rejectsWhenOneOfManySelectedIsNearDuplica
|
||||
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 };
|
||||
const TrackCandidateContext ctx{ .candidateTrackId = T1, .selectedTracks = selected, .seedTrackIds = {} };
|
||||
EXPECT_TRUE(constraint.rejects(ctx));
|
||||
}
|
||||
|
||||
@@ -309,6 +303,111 @@ 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 };
|
||||
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, 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, 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, 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, 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, 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, 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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user