MathUtils: Add tangential interpolation

Reuse it in TaskProgress and in ProgressTimer.
Rename MathUtils::interpolate() into interpolateLinear()

Change-Id: Iff4cda1e3b8782cd26277ec75046ca5526be92c0
Reviewed-by: Eike Ziller <eike.ziller@qt.io>
This commit is contained in:
Jarek Kobus
2022-11-25 18:34:28 +01:00
parent 6f299f19ac
commit 23f53dcbda
11 changed files with 124 additions and 23 deletions

View File

@@ -12,7 +12,7 @@ namespace Utils::MathUtils {
For x = x1 it returns y1. For x = x1 it returns y1.
For x = x2 it returns y2. For x = x2 it returns y2.
*/ */
int interpolate(int x, int x1, int x2, int y1, int y2) int interpolateLinear(int x, int x1, int x2, int y1, int y2)
{ {
if (x1 == x2) if (x1 == x2)
return y1; // or the middle point between y1 and y2? return y1; // or the middle point between y1 and y2?
@@ -27,4 +27,21 @@ int interpolate(int x, int x1, int x2, int y1, int y2)
return qRound((double)numerator / denominator); return qRound((double)numerator / denominator);
} }
/*!
Tangential interpolation:
For x = 0 it returns y1.
For x = xHalfLife it returns 50 % of the distance between y1 and y2.
For x = infinity it returns y2.
*/
int interpolateTangential(int x, int xHalfLife, int y1, int y2)
{
if (x == 0)
return y1;
if (y1 == y2)
return y1;
const double mapped = atan2(double(x), double(xHalfLife));
const double progress = y1 + (y2 - y1) * mapped * 2 / M_PI;
return qRound(progress);
}
} // namespace Utils::Math } // namespace Utils::Math

View File

@@ -7,6 +7,7 @@
namespace Utils::MathUtils { namespace Utils::MathUtils {
QTCREATOR_UTILS_EXPORT int interpolate(int x, int x1, int x2, int y1, int y2); QTCREATOR_UTILS_EXPORT int interpolateLinear(int x, int x1, int x2, int y1, int y2);
QTCREATOR_UTILS_EXPORT int interpolateTangential(int x, int xHalfLife, int y1, int y2);
} // namespace Utils::Math } // namespace Utils::Math

View File

@@ -13,6 +13,7 @@
#include <extensionsystem/pluginmanager.h> #include <extensionsystem/pluginmanager.h>
#include <utils/hostosinfo.h> #include <utils/hostosinfo.h>
#include <utils/mathutils.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <utils/stylehelper.h> #include <utils/stylehelper.h>
#include <utils/theme/theme.h> #include <utils/theme/theme.h>
@@ -27,7 +28,6 @@
#include <QStyle> #include <QStyle>
#include <QStyleOption> #include <QStyleOption>
#include <QTimer> #include <QTimer>
#include <QtMath>
#include <QVariant> #include <QVariant>
static const char kSettingsGroup[] = "Progress"; static const char kSettingsGroup[] = "Progress";
@@ -779,13 +779,8 @@ ProgressTimer::ProgressTimer(const QFutureInterfaceBase &futureInterface,
void ProgressTimer::handleTimeout() void ProgressTimer::handleTimeout()
{ {
++m_currentTime; ++m_currentTime;
const int halfLife = qRound(1000.0 * m_expectedTime / TimerInterval);
// This maps expectation to atan(1) to Pi/4 ~= 0.78, i.e. snaps const int progress = MathUtils::interpolateTangential(m_currentTime, halfLife, 0, 100);
// from 78% to 100% when expectations are met at the time the m_futureInterface.setProgressValue(progress);
// future finishes. That's not bad for a random choice.
const double mapped = atan2(double(m_currentTime) * TimerInterval / 1000.0,
double(m_expectedTime));
const double progress = 100 * 2 * mapped / M_PI;
m_futureInterface.setProgressValue(int(progress));
} }

View File

@@ -6,12 +6,12 @@
#include "futureprogress.h" #include "futureprogress.h"
#include "progressmanager.h" #include "progressmanager.h"
#include <utils/mathutils.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <utils/tasktree.h> #include <utils/tasktree.h>
#include <QFutureWatcher> #include <QFutureWatcher>
#include <QTimer> #include <QTimer>
#include <QtMath>
using namespace Utils; using namespace Utils;
@@ -80,13 +80,11 @@ void TaskProgressPrivate::advanceProgress(int newValue)
void TaskProgressPrivate::updateProgress() void TaskProgressPrivate::updateProgress()
{ {
// This maps expectation to atan(1) to Pi/4 ~= 0.78, i.e. snaps const int halfLife = qRound(1000.0 * m_expectedTime / TimerInterval);
// from 78% to 100% when expectations are met at the time the const int pMin = ProgressResolution * m_currentProgress;
// future finishes. That's not bad for a random choice. const int pMax = ProgressResolution * (m_currentProgress + 1);
const double mapped = atan2(double(m_currentTick) * TimerInterval / 1000.0, const int newValue = MathUtils::interpolateTangential(m_currentTick, halfLife, pMin, pMax);
double(m_expectedTime)); m_futureInterface.setProgressValue(newValue);
const double progress = ProgressResolution * 2 * mapped / M_PI;
m_futureInterface.setProgressValue(ProgressResolution * m_currentProgress + progress);
} }
/*! /*!

View File

@@ -365,7 +365,7 @@ SideBySideDiffOutput SideDiffData::diffOutput(QFutureInterface<void> &fi, int pr
diffText[RightSide].replace('\r', ' '); diffText[RightSide].replace('\r', ' ');
output.side[LeftSide].diffText += diffText[LeftSide]; output.side[LeftSide].diffText += diffText[LeftSide];
output.side[RightSide].diffText += diffText[RightSide]; output.side[RightSide].diffText += diffText[RightSide];
fi.setProgressValue(MathUtils::interpolate(++i, 0, count, progressMin, progressMax)); fi.setProgressValue(MathUtils::interpolateLinear(++i, 0, count, progressMin, progressMax));
if (fi.isCanceled()) if (fi.isCanceled())
return {}; return {};
} }
@@ -952,7 +952,7 @@ void SideBySideDiffEditorWidget::showDiff()
const QString package = output.side[side].diffText.mid(currentPos, packageSize); const QString package = output.side[side].diffText.mid(currentPos, packageSize);
cursor.insertText(package); cursor.insertText(package);
currentPos += package.size(); currentPos += package.size();
fi.setProgressValue(MathUtils::interpolate(currentPos, 0, diffSize, progressMin, progressMax)); fi.setProgressValue(MathUtils::interpolateLinear(currentPos, 0, diffSize, progressMin, progressMax));
if (fi.isCanceled()) if (fi.isCanceled())
return; return;
} }

View File

@@ -436,7 +436,7 @@ UnifiedDiffOutput UnifiedDiffData::diffOutput(QFutureInterface<void> &fi, int pr
output.diffData.m_chunkInfo.setChunkIndex(oldBlock, blockNumber - oldBlock, j); output.diffData.m_chunkInfo.setChunkIndex(oldBlock, blockNumber - oldBlock, j);
} }
} }
fi.setProgressValue(MathUtils::interpolate(++i, 0, count, progressMin, progressMax)); fi.setProgressValue(MathUtils::interpolateLinear(++i, 0, count, progressMin, progressMax));
if (fi.isCanceled()) if (fi.isCanceled())
return {}; return {};
} }
@@ -511,7 +511,7 @@ void UnifiedDiffEditorWidget::showDiff()
const QString package = output.diffText.mid(currentPos, packageSize); const QString package = output.diffText.mid(currentPos, packageSize);
cursor.insertText(package); cursor.insertText(package);
currentPos += package.size(); currentPos += package.size();
fi.setProgressValue(MathUtils::interpolate(currentPos, 0, diffSize, firstPartMax, progressMax)); fi.setProgressValue(MathUtils::interpolateLinear(currentPos, 0, diffSize, firstPartMax, progressMax));
if (futureInterface.isCanceled()) if (futureInterface.isCanceled())
return; return;
} }

View File

@@ -6,6 +6,7 @@ add_subdirectory(fileutils)
add_subdirectory(fsengine) add_subdirectory(fsengine)
add_subdirectory(fuzzymatcher) add_subdirectory(fuzzymatcher)
add_subdirectory(indexedcontainerproxyconstiterator) add_subdirectory(indexedcontainerproxyconstiterator)
add_subdirectory(mathutils)
add_subdirectory(multicursor) add_subdirectory(multicursor)
add_subdirectory(persistentsettings) add_subdirectory(persistentsettings)
add_subdirectory(qtcprocess) add_subdirectory(qtcprocess)

View File

@@ -0,0 +1,4 @@
add_qtc_test(tst_utils_mathutils
DEPENDS Utils
SOURCES tst_mathutils.cpp
)

View File

@@ -0,0 +1,7 @@
import qbs
QtcAutotest {
name: "MathUtils autotest"
Depends { name: "Utils" }
files: "tst_mathutils.cpp"
}

View File

@@ -0,0 +1,77 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#include "utils/mathutils.h"
#include <QtTest>
using namespace Utils;
class tst_MathUtils : public QObject
{
Q_OBJECT
private slots:
void interpolateLinear_data();
void interpolateLinear();
void interpolateTangential_data();
void interpolateTangential();
};
void tst_MathUtils::interpolateLinear_data()
{
QTest::addColumn<int>("x");
QTest::addColumn<int>("x1");
QTest::addColumn<int>("x2");
QTest::addColumn<int>("y1");
QTest::addColumn<int>("y2");
QTest::addColumn<int>("result");
QTest::newRow("x1") << 2 << 2 << 8 << 10 << 20 << 10;
QTest::newRow("middleValue") << 5 << 2 << 8 << 10 << 20 << 15;
QTest::newRow("x2") << 8 << 2 << 8 << 10 << 20 << 20;
QTest::newRow("belowX1") << -1 << 2 << 8 << 10 << 20 << 5;
QTest::newRow("aboveX2") << 11 << 2 << 8 << 10 << 20 << 25;
}
void tst_MathUtils::interpolateLinear()
{
QFETCH(int, x);
QFETCH(int, x1);
QFETCH(int, x2);
QFETCH(int, y1);
QFETCH(int, y2);
QFETCH(int, result);
const int y = MathUtils::interpolateLinear(x, x1, x2, y1, y2);
QCOMPARE(y, result);
}
void tst_MathUtils::interpolateTangential_data()
{
QTest::addColumn<int>("x");
QTest::addColumn<int>("xHalfLife");
QTest::addColumn<int>("y1");
QTest::addColumn<int>("y2");
QTest::addColumn<int>("result");
QTest::newRow("zero") << 0 << 8 << 10 << 20 << 10;
QTest::newRow("halfLife") << 8 << 8 << 10 << 20 << 15;
QTest::newRow("approxInfinity") << 1000 << 8 << 10 << 20 << 20;
}
void tst_MathUtils::interpolateTangential()
{
QFETCH(int, x);
QFETCH(int, xHalfLife);
QFETCH(int, y1);
QFETCH(int, y2);
QFETCH(int, result);
const int y = MathUtils::interpolateTangential(x, xHalfLife, y1, y2);
QCOMPARE(y, result);
}
QTEST_GUILESS_MAIN(tst_MathUtils)
#include "tst_mathutils.moc"

View File

@@ -11,6 +11,7 @@ Project {
"fsengine/fsengine.qbs", "fsengine/fsengine.qbs",
"fuzzymatcher/fuzzymatcher.qbs", "fuzzymatcher/fuzzymatcher.qbs",
"indexedcontainerproxyconstiterator/indexedcontainerproxyconstiterator.qbs", "indexedcontainerproxyconstiterator/indexedcontainerproxyconstiterator.qbs",
"mathutils/mathutils.qbs",
"multicursor/multicursor.qbs", "multicursor/multicursor.qbs",
"persistentsettings/persistentsettings.qbs", "persistentsettings/persistentsettings.qbs",
"qtcprocess/qtcprocess.qbs", "qtcprocess/qtcprocess.qbs",