Sketcher: Use DenseQR for small sketches to avoid SparseQR rank issues (#26559)

This commit is contained in:
PaddleStroke
2026-01-04 21:57:13 +01:00
committed by GitHub
parent 65ef7da33c
commit e1ddc17dc6
6 changed files with 182 additions and 24 deletions

View File

@@ -641,6 +641,14 @@ public:
{
return debugMode;
}
inline void setAutoQRThreshold(int val)
{
GCSsys.autoQRThreshold = val;
}
inline void setSketchAutoAlgo(bool val)
{
GCSsys.autoChooseAlgorithm = val;
}
inline void setMaxIter(int maxiter)
{
GCSsys.maxIter = maxiter;

View File

@@ -486,6 +486,8 @@ System::System()
, convergence(1e-10)
, convergenceRedundant(1e-10)
, qrAlgorithm(EigenSparseQR)
, autoChooseAlgorithm(true)
, autoQRThreshold(1000)
, dogLegGaussStep(FullPivLU)
, qrpivotThreshold(1E-13)
, debugMode(Minimal)
@@ -4808,6 +4810,15 @@ int System::diagnose(Algorithm alg)
hasDiagnosis = true;
dofs = pdiagnoselist.size();
// Use DenseQR for small to medium systems to avoid SparseQR rank issues.
// SparseQR is known to fail rank detection on specific geometric structures (e.g. aligned slots).
// 200 parameters roughly corresponds to ~100 points/curves, covering most complex sketches
// where stability is preferred over pure O(N) performance.
// See: https://github.com/FreeCAD/FreeCAD/issues/10903
if (autoChooseAlgorithm) {
qrAlgorithm = dofs < autoQRThreshold ? EigenDenseQR : EigenSparseQR;
}
// There is a legacy decision to use QR decomposition. I (abdullah) do not know all the
// consideration taken in that decisions. I see that:
// - QR decomposition is able to provide information about the rank and

View File

@@ -239,6 +239,8 @@ public:
double convergence;
double convergenceRedundant;
QRAlgorithm qrAlgorithm;
bool autoChooseAlgorithm;
int autoQRThreshold;
DogLegGaussStep dogLegGaussStep;
double qrpivotThreshold;
DebugMode debugMode;

View File

@@ -71,6 +71,8 @@ TaskSketcherSolverAdvanced::TaskSketcherSolverAdvanced(ViewProviderSketch* sketc
ui->checkBoxSketchSizeMultiplier->onRestore();
ui->lineEditConvergence->onRestore();
ui->comboBoxQRMethod->onRestore();
ui->spinBoxAutoQRThreshold->onRestore();
ui->checkBoxAutoChooseAlgo->onRestore();
ui->lineEditQRPivotThreshold->onRestore();
ui->comboBoxRedundantDefaultSolver->onRestore();
ui->spinBoxRedundantSolverMaxIterations->onRestore();
@@ -78,6 +80,12 @@ TaskSketcherSolverAdvanced::TaskSketcherSolverAdvanced(ViewProviderSketch* sketc
ui->lineEditRedundantConvergence->onRestore();
ui->comboBoxDebugMode->onRestore();
bool autoAlgo = ui->checkBoxAutoChooseAlgo->isChecked();
ui->labelAutoQRThreshold->setVisible(autoAlgo);
ui->spinBoxAutoQRThreshold->setVisible(autoAlgo);
ui->labelQRAlgorithm->setVisible(!autoAlgo);
ui->comboBoxQRMethod->setVisible(!autoAlgo);
updateSketchObject();
}
@@ -104,6 +112,27 @@ void TaskSketcherSolverAdvanced::setupConnections()
this,
&TaskSketcherSolverAdvanced::onSpinBoxMaxIterValueChanged
);
connect(
ui->spinBoxAutoQRThreshold,
qOverload<int>(&QSpinBox::valueChanged),
this,
&TaskSketcherSolverAdvanced::onSpinBoxAutoQRAlgoChanged
);
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
connect(
ui->checkBoxAutoChooseAlgo,
&QCheckBox::checkStateChanged,
this,
&TaskSketcherSolverAdvanced::onCheckBoxAutoQRAlgoStateChanged
);
#else
connect(
ui->checkBoxAutoChooseAlgo,
&QCheckBox::stateChanged,
this,
&TaskSketcherSolverAdvanced::onCheckBoxAutoQRAlgoStateChanged
);
#endif
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
connect(
ui->checkBoxSketchSizeMultiplier,
@@ -637,6 +666,35 @@ void TaskSketcherSolverAdvanced::onSpinBoxMaxIterValueChanged(int i)
const_cast<Sketcher::Sketch&>(sketchView->getSketchObject()->getSolvedSketch()).setMaxIter(i);
}
void TaskSketcherSolverAdvanced::onSpinBoxAutoQRAlgoChanged(int i)
{
ui->spinBoxAutoQRThreshold->onSave();
const_cast<Sketcher::Sketch&>(sketchView->getSketchObject()->getSolvedSketch())
.setAutoQRThreshold(i);
}
void TaskSketcherSolverAdvanced::onCheckBoxAutoQRAlgoStateChanged(int state)
{
if (state == Qt::Checked) {
ui->spinBoxAutoQRThreshold->show();
ui->comboBoxQRMethod->hide();
ui->labelAutoQRThreshold->show();
ui->labelQRAlgorithm->hide();
ui->checkBoxAutoChooseAlgo->onSave();
const_cast<Sketcher::Sketch&>(sketchView->getSketchObject()->getSolvedSketch())
.setSketchAutoAlgo(true);
}
else if (state == Qt::Unchecked) {
ui->spinBoxAutoQRThreshold->hide();
ui->comboBoxQRMethod->show();
ui->labelAutoQRThreshold->hide();
ui->labelQRAlgorithm->show();
ui->checkBoxAutoChooseAlgo->onSave();
const_cast<Sketcher::Sketch&>(sketchView->getSketchObject()->getSolvedSketch())
.setSketchAutoAlgo(false);
}
}
void TaskSketcherSolverAdvanced::onCheckBoxSketchSizeMultiplierStateChanged(int state)
{
if (state == Qt::Checked) {
@@ -784,6 +842,8 @@ void TaskSketcherSolverAdvanced::onPushButtonDefaultsClicked(bool checked /* = f
ui->checkBoxSketchSizeMultiplier->onRestore();
ui->lineEditConvergence->onRestore();
ui->comboBoxQRMethod->onRestore();
ui->spinBoxAutoQRThreshold->onRestore();
ui->checkBoxAutoChooseAlgo->onRestore();
ui->lineEditQRPivotThreshold->onRestore();
ui->comboBoxRedundantDefaultSolver->onRestore();
ui->spinBoxRedundantSolverMaxIterations->onRestore();
@@ -796,30 +856,24 @@ void TaskSketcherSolverAdvanced::onPushButtonDefaultsClicked(bool checked /* = f
void TaskSketcherSolverAdvanced::updateSketchObject()
{
const_cast<Sketcher::Sketch&>(sketchView->getSketchObject()->getSolvedSketch())
.setDebugMode((GCS::DebugMode)ui->comboBoxDebugMode->currentIndex());
const_cast<Sketcher::Sketch&>(sketchView->getSketchObject()->getSolvedSketch())
.setSketchSizeMultiplierRedundant(ui->checkBoxRedundantSketchSizeMultiplier->isChecked());
const_cast<Sketcher::Sketch&>(sketchView->getSketchObject()->getSolvedSketch())
.setMaxIterRedundant(ui->spinBoxRedundantSolverMaxIterations->value());
const_cast<Sketcher::Sketch&>(sketchView->getSketchObject()->getSolvedSketch()).defaultSolverRedundant
= static_cast<GCS::Algorithm>(ui->comboBoxRedundantDefaultSolver->currentIndex());
const_cast<Sketcher::Sketch&>(sketchView->getSketchObject()->getSolvedSketch())
.setQRAlgorithm((GCS::QRAlgorithm)ui->comboBoxQRMethod->currentIndex());
const_cast<Sketcher::Sketch&>(sketchView->getSketchObject()->getSolvedSketch())
.setQRPivotThreshold(ui->lineEditQRPivotThreshold->text().toDouble());
const_cast<Sketcher::Sketch&>(sketchView->getSketchObject()->getSolvedSketch())
.setConvergenceRedundant(ui->lineEditRedundantConvergence->text().toDouble());
const_cast<Sketcher::Sketch&>(sketchView->getSketchObject()->getSolvedSketch())
.setConvergence(ui->lineEditConvergence->text().toDouble());
const_cast<Sketcher::Sketch&>(sketchView->getSketchObject()->getSolvedSketch())
.setSketchSizeMultiplier(ui->checkBoxSketchSizeMultiplier->isChecked());
const_cast<Sketcher::Sketch&>(sketchView->getSketchObject()->getSolvedSketch())
.setMaxIter(ui->spinBoxMaxIter->value());
const_cast<Sketcher::Sketch&>(sketchView->getSketchObject()->getSolvedSketch()).defaultSolver
= static_cast<GCS::Algorithm>(ui->comboBoxDefaultSolver->currentIndex());
const_cast<Sketcher::Sketch&>(sketchView->getSketchObject()->getSolvedSketch())
.setDogLegGaussStep((GCS::DogLegGaussStep)ui->comboBoxDogLegGaussStep->currentIndex());
auto& sketch = const_cast<Sketcher::Sketch&>(sketchView->getSketchObject()->getSolvedSketch());
sketch.setDebugMode((GCS::DebugMode)ui->comboBoxDebugMode->currentIndex());
sketch.setSketchSizeMultiplierRedundant(ui->checkBoxRedundantSketchSizeMultiplier->isChecked());
sketch.setMaxIterRedundant(ui->spinBoxRedundantSolverMaxIterations->value());
sketch.defaultSolverRedundant = static_cast<GCS::Algorithm>(
ui->comboBoxRedundantDefaultSolver->currentIndex()
);
sketch.setQRAlgorithm((GCS::QRAlgorithm)ui->comboBoxQRMethod->currentIndex());
sketch.setAutoQRThreshold(ui->spinBoxAutoQRThreshold->value());
sketch.setSketchAutoAlgo(ui->checkBoxAutoChooseAlgo->isChecked());
sketch.setQRPivotThreshold(ui->lineEditQRPivotThreshold->text().toDouble());
sketch.setConvergenceRedundant(ui->lineEditRedundantConvergence->text().toDouble());
sketch.setConvergence(ui->lineEditConvergence->text().toDouble());
sketch.setSketchSizeMultiplier(ui->checkBoxSketchSizeMultiplier->isChecked());
sketch.setMaxIter(ui->spinBoxMaxIter->value());
sketch.defaultSolver = static_cast<GCS::Algorithm>(ui->comboBoxDefaultSolver->currentIndex());
sketch.setDogLegGaussStep((GCS::DogLegGaussStep)ui->comboBoxDogLegGaussStep->currentIndex());
updateDefaultMethodParameters();
updateRedundantMethodParameters();

View File

@@ -53,6 +53,8 @@ private:
void onComboBoxDefaultSolverCurrentIndexChanged(int index);
void onComboBoxDogLegGaussStepCurrentIndexChanged(int index);
void onSpinBoxMaxIterValueChanged(int i);
void onSpinBoxAutoQRAlgoChanged(int i);
void onCheckBoxAutoQRAlgoStateChanged(int state);
void onCheckBoxSketchSizeMultiplierStateChanged(int state);
void onLineEditConvergenceEditingFinished();
void onComboBoxQRMethodCurrentIndexChanged(int index);

View File

@@ -290,6 +290,87 @@ to determine whether a solution converges or not</string>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_32">
<item>
<widget class="QLabel" name="labelAutoChooseAlgo">
<property name="toolTip">
<string>Automatically select the QR algorithm based on number of dofs</string>
</property>
<property name="text">
<string>Automatic QR algorithm</string>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefCheckBox" name="checkBoxAutoChooseAlgo">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="toolTip">
<string>Automatically select the QR algorithm based on number of dofs</string>
</property>
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text">
<string/>
</property>
<property name="prefEntry" stdset="0">
<cstring>AutoChooseAlgo</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Sketcher/SolverAdvanced</cstring>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_62">
<item>
<widget class="QLabel" name="labelAutoQRThreshold">
<property name="toolTip">
<string>Maximum number of parameters before switching to sparse QR algorithm</string>
</property>
<property name="text">
<string>Auto QR threshold</string>
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefSpinBox" name="spinBoxAutoQRThreshold">
<property name="toolTip">
<string>Maximum number of parameters before switching to sparse QR algorithm</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>10000000</number>
</property>
<property name="value">
<number>1000</number>
</property>
<property name="prefEntry" stdset="0">
<cstring>AutoQRThreshold</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Sketcher/SolverAdvanced</cstring>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>