Skip to content

Commit cff9616

Browse files
gabrielfrasantosclaudegithub-actions[bot]
authored
feat(simulator): render every panel and chart through ui-cpp (#332)
* feat(simulator): render the FFT and PSD panels through ui-cpp Both panels become Qt-free field descriptions: two combos and a sample rate for FFT, five spectrum fields plus a noise group for PSD, with the shared signal-component table in place of SignalConfigWidget. PSD carried the conditional-enable bug the form model fixes structurally. Its noise handler was connected after the initial selection was set, so the amplitude control's enablement was never synchronised at startup; it only looked right because the default noise type happened to be the one that enables it. Conditions now apply at build time and on every change through the same path, and a test pins the starting state. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YQEnuDHtCQb5UyddnMup3Q * feat(simulator): render the PID, MPC and LQR panels through ui-cpp PID is the group-swapping case: the two plant parameterisations become groups with a selection condition instead of two setVisible calls made from a handler wired after the initial selection. Its root-locus drag writes Kp back through the model and refreshes the one control, which is what the view's re-entrancy flag exists for; the write is clamped to the field's own range so the model cannot hold a value the spin box would have rejected. LQR's three composite monospace state lines become five read-out fields. The content is the same and the angles are still converted to degrees for display only, but each value now gets its own labelled row rather than sharing a formatted line, since a read-out states one number. Its five buttons take semantic roles (Start, Stop, Reset) rather than the uniform default styling the panel gave them, and CartPoleWidget and LqrEvaluationWidget stay as they are, handed to the shell as bare QWidget* through the native-page escape hatch. Also dropped PidMainWindow::pendingGain, which was assigned on every drag and never read. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YQEnuDHtCQb5UyddnMup3Q * feat(simulator): render the RLS, Kalman and Bayesian MPC panels through ui-cpp The last three panels, which completes the migration: every one of the eleven applications now describes its parameters rather than building them, and simulator/widgets/SignalConfigWidget is deleted with the last consumer that referenced it. RLS keeps its three coefficients as three separate fields rather than gaining a fixed-arity array field for one call site, and its compute path gains the guard it never had - it was the only application with no try/catch around the simulation at all. Bayesian MPC's word-wrapped status label inside the panel becomes the shell's status line, which is where the other ten applications already report. Its run button still disables itself for the duration, now through the view rather than by holding the widget. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YQEnuDHtCQb5UyddnMup3Q * style(simulator): format the status-line chains in every main window clang-format wants a different continuation indent for QString().arg() chains than the eleven windows carried. Applied to all of them, including the three migrated earlier, so the set reads the same and the tree is format-clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YQEnuDHtCQb5UyddnMup3Q * feat(simulator): render the filter charts through ui-cpp IIR, FIR and Kalman drop the vendored chart widgets for ChartCore hosted in QtPaintedWidget, which is what the FFT application has done since the stop-gate. The impulse response of both filters plots a sample index, and the widget it replaces hardcoded "Time (s)" as its X-axis title whatever it was fed. LinearAxis takes that title as a parameter, so the two impulse charts now say "Sample" and read back "n = " at the cursor. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YQEnuDHtCQb5UyddnMup3Q * feat(simulator): render the PSD, LQG, MPC and PID charts through ui-cpp Four more applications move onto ChartCore. PID keeps PidRootLocusWidget as a native page beside three portable ones, which is what SetPage taking a bare QWidget* is for. MPC's per-state series names were built with QString::arg purely to make a QString the chart wanted; ui::charts::Series takes std::string, so they are std::format now and the window keeps one fewer Qt type in its inner loop. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YQEnuDHtCQb5UyddnMup3Q * feat(simulator): render the RLS, Bayesian MPC and LQR evaluation charts through ui-cpp The last three, and the ones the hardcoded axis title mislabelled worst. RLS plots a sample index on all three of its charts and Bayesian MPC plots an EM iteration on one and a BO evaluation on another; all five said "Time (s)". They now say "Sample", "Iteration" and "Evaluation", and the cursor reads back "n = " or "i = " to match. LqrEvaluationWidget composes two charts rather than being one, so it becomes a QWidget holding two QtPaintedWidgets and stays a native page handed to the shell. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YQEnuDHtCQb5UyddnMup3Q * refactor(simulator): delete the vendored chart widgets Nothing references simulator/widgets any more, so the 1287 lines it held go with the numerical.simulator.widgets target: TimeSeriesChartWidget, FrequencyChartWidget and the ChartInteraction they shared, all of which ui/charts now provides once for every consumer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YQEnuDHtCQb5UyddnMup3Q * Apply batched suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Update simulator/filters/KalmanFilter/view/KalmanMainWindow.cpp Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 09d8986 commit cff9616

115 files changed

Lines changed: 2864 additions & 3585 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎simulator/CMakeLists.txt‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
add_subdirectory(utils)
22
add_subdirectory(shell)
3-
add_subdirectory(widgets)
43
add_subdirectory(analysis)
54
add_subdirectory(controllers)
65
add_subdirectory(estimators)

‎simulator/analysis/FastFourierTransform/CMakeLists.txt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ emil_build_for(numerical.simulator.analysis.fast_fourier_transform HOST All PRER
99
target_link_libraries(numerical.simulator.analysis.fast_fourier_transform PRIVATE
1010
numerical.simulator.analysis.fft.application
1111
numerical.simulator.analysis.fft.view
12+
numerical.simulator.shell.qt
1213
)
1314

1415
target_sources(numerical.simulator.analysis.fast_fourier_transform PRIVATE
Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,7 @@
11
#include "simulator/analysis/FastFourierTransform/view/FftMainWindow.hpp"
2-
#include "ui/backend/qt/QtTheme.hpp"
3-
#include <QApplication>
2+
#include "simulator/shell/AppRunner.hpp"
43

54
int main(int argc, char* argv[])
65
{
7-
QApplication app(argc, argv);
8-
9-
ui::backend::qt::ApplyTheme(ui::theme::Light());
10-
11-
simulator::analysis::view::FftMainWindow window;
12-
window.show();
13-
14-
return app.exec();
6+
return simulator::shell::Run<simulator::analysis::view::FftMainWindow>(argc, argv, ui::theme::Light());
157
}

‎simulator/analysis/FastFourierTransform/application/CMakeLists.txt‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,15 @@ target_link_libraries(numerical.simulator.analysis.fft.application PUBLIC
99
numerical.analysis
1010
numerical.analysis.windowing
1111
numerical.simulator.utils
12+
numerical.simulator.shell
13+
ui.model
1214
)
1315

1416
target_sources(numerical.simulator.analysis.fft.application PRIVATE
1517
FftSimulator.cpp
1618
FftSimulator.hpp
19+
FftForm.cpp
20+
FftForm.hpp
1721
)
1822

1923
if(MSVC)
@@ -23,3 +27,7 @@ else()
2327
-Wno-error
2428
)
2529
endif()
30+
31+
if (NUMERICAL_TOOLBOX_BUILD_TESTS)
32+
add_subdirectory(test)
33+
endif()
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
#include "simulator/analysis/FastFourierTransform/application/FftForm.hpp"
2+
3+
namespace simulator::analysis
4+
{
5+
namespace
6+
{
7+
using ui::model::ActionSpec;
8+
using ui::model::FieldKind;
9+
using ui::model::FieldSpec;
10+
using ui::model::GroupSpec;
11+
using ui::model::OptionSpec;
12+
13+
constexpr std::array<OptionSpec, 7> fftSizes{
14+
OptionSpec{ "64", 64 },
15+
OptionSpec{ "128", 128 },
16+
OptionSpec{ "256", 256 },
17+
OptionSpec{ "512", 512 },
18+
OptionSpec{ "1024", 1024 },
19+
OptionSpec{ "2048", 2048 },
20+
OptionSpec{ "4096", 4096 }
21+
};
22+
23+
constexpr std::array<OptionSpec, 4> windowTypes{
24+
OptionSpec{ "Rectangular", static_cast<std::int64_t>(WindowType::Rectangular) },
25+
OptionSpec{ "Hamming", static_cast<std::int64_t>(WindowType::Hamming) },
26+
OptionSpec{ "Hanning", static_cast<std::int64_t>(WindowType::Hanning) },
27+
OptionSpec{ "Blackman", static_cast<std::int64_t>(WindowType::Blackman) }
28+
};
29+
30+
constexpr std::array<GroupSpec, 2> groups{
31+
GroupSpec{ field::transform, "FFT Configuration", {} },
32+
GroupSpec{ field::signal, "Signal Components", {} }
33+
};
34+
35+
constexpr std::array<FieldSpec, 3> fields{
36+
FieldSpec{ field::fftSize, field::transform, FieldKind::Choice, "FFT Size:", "", {}, fftSizes, {}, {} },
37+
FieldSpec{ field::sampleRate, field::transform, FieldKind::Number, "Sample Rate (Hz):", " Hz", { 100.0, 192000.0, 1.0, 44100.0, 1 }, {}, {}, {} },
38+
FieldSpec{ field::windowType, field::transform, FieldKind::Choice, "Window:", "", {}, windowTypes, {}, {} }
39+
};
40+
41+
constexpr std::array<ActionSpec, 1> actions{
42+
ActionSpec{ field::compute, "Compute FFT", ui::theme::ButtonRole::Primary, 40 }
43+
};
44+
45+
constexpr std::array<utils::SignalComponent, 3> defaultComponents{
46+
utils::SignalComponent{ 1000.0f, 0.15f },
47+
utils::SignalComponent{ 5000.0f, 0.5f },
48+
utils::SignalComponent{ 12000.0f, 0.25f }
49+
};
50+
}
51+
52+
FftForm::FftForm()
53+
: tableSpecs{ ui::model::TableSpec{ field::signal, shell::signalColumns, maximumComponents, "Add", "Remove" } }
54+
, tables{ ui::model::TableModel{ tableSpecs[0], cells } }
55+
, spec{ groups, fields, actions, tableSpecs }
56+
, model{ spec, values, tables }
57+
{
58+
model.SelectByData(field::fftSize, 1024);
59+
shell::SeedFrom(defaultComponents, tables[0]);
60+
}
61+
62+
ui::model::FormModel& FftForm::Model()
63+
{
64+
return model;
65+
}
66+
67+
const ui::model::FormModel& FftForm::Model() const
68+
{
69+
return model;
70+
}
71+
72+
FftSimulator::Configuration FftForm::BuildConfiguration() const
73+
{
74+
FftSimulator::Configuration config;
75+
76+
config.fftSize = static_cast<std::size_t>(model.SelectedData(field::fftSize));
77+
config.sampleRateHz = model.Float(field::sampleRate);
78+
config.windowType = static_cast<WindowType>(model.SelectedData(field::windowType));
79+
config.signalComponents = shell::ToComponents(model.Table(0));
80+
81+
return config;
82+
}
83+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#pragma once
2+
3+
#include "simulator/analysis/FastFourierTransform/application/FftSimulator.hpp"
4+
#include "simulator/shell/SignalComponents.hpp"
5+
#include "ui/model/FormModel.hpp"
6+
#include <array>
7+
8+
namespace simulator::analysis
9+
{
10+
namespace field
11+
{
12+
inline constexpr ui::model::FieldId fftSize{ 1 };
13+
inline constexpr ui::model::FieldId sampleRate{ 2 };
14+
inline constexpr ui::model::FieldId windowType{ 3 };
15+
16+
inline constexpr ui::model::GroupId transform{ 1 };
17+
inline constexpr ui::model::GroupId signal{ 2 };
18+
19+
inline constexpr ui::model::ActionId compute{ 1 };
20+
}
21+
22+
class FftForm
23+
{
24+
public:
25+
FftForm();
26+
27+
[[nodiscard]] ui::model::FormModel& Model();
28+
[[nodiscard]] const ui::model::FormModel& Model() const;
29+
30+
[[nodiscard]] FftSimulator::Configuration BuildConfiguration() const;
31+
32+
private:
33+
static constexpr std::size_t fieldCount{ 3 };
34+
static constexpr std::size_t maximumComponents{ 16 };
35+
36+
std::array<ui::model::TableSpec, 1> tableSpecs;
37+
std::array<double, maximumComponents * 2> cells{};
38+
std::array<ui::model::TableModel, 1> tables;
39+
std::array<ui::model::FieldValue, fieldCount> values{};
40+
41+
ui::model::FormSpec spec;
42+
ui::model::FormModel model;
43+
};
44+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
add_executable(numerical.simulator.analysis.fft.application_test)
2+
emil_build_for(numerical.simulator.analysis.fft.application_test HOST All PREREQUISITE_BOOL NUMERICAL_TOOLBOX_BUILD_SIMULATOR)
3+
emil_add_test(numerical.simulator.analysis.fft.application_test)
4+
5+
target_link_libraries(numerical.simulator.analysis.fft.application_test PUBLIC
6+
gmock_main
7+
numerical.simulator.analysis.fft.application
8+
)
9+
10+
target_sources(numerical.simulator.analysis.fft.application_test PRIVATE
11+
TestFftForm.cpp
12+
)
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#include "simulator/analysis/FastFourierTransform/application/FftForm.hpp"
2+
#include <gmock/gmock.h>
3+
4+
namespace
5+
{
6+
using simulator::analysis::FftForm;
7+
namespace field = simulator::analysis::field;
8+
9+
class FftFormTest
10+
: public ::testing::Test
11+
{
12+
protected:
13+
FftForm form;
14+
};
15+
}
16+
17+
TEST_F(FftFormTest, TheDefaultsMatchThePanelThisReplaced)
18+
{
19+
const auto config = form.BuildConfiguration();
20+
21+
EXPECT_EQ(config.fftSize, 1024u);
22+
EXPECT_NEAR(config.sampleRateHz, 44100.0f, 1e-3f);
23+
EXPECT_EQ(config.windowType, simulator::analysis::WindowType::Rectangular);
24+
}
25+
26+
TEST_F(FftFormTest, TheDefaultSizeIsSelectedByItsValueNotItsPosition)
27+
{
28+
EXPECT_EQ(form.Model().Selection(field::fftSize), 4u);
29+
}
30+
31+
TEST_F(FftFormTest, EverySupportedSizeIsOfferedAndReadsBackAsItself)
32+
{
33+
const auto sizes = simulator::analysis::FftSimulator::SupportedFftSizes();
34+
ASSERT_EQ(form.Model().Field(field::fftSize).options.size(), sizes.size());
35+
36+
for (std::size_t i = 0; i < sizes.size(); ++i)
37+
{
38+
form.Model().SetSelection(field::fftSize, i);
39+
EXPECT_EQ(form.BuildConfiguration().fftSize, sizes[i]);
40+
}
41+
}
42+
43+
TEST_F(FftFormTest, TheWindowTypeComesFromTheOptionDataNotItsPosition)
44+
{
45+
form.Model().SetSelection(field::windowType, 3);
46+
47+
EXPECT_EQ(form.BuildConfiguration().windowType, simulator::analysis::WindowType::Blackman);
48+
}
49+
50+
TEST_F(FftFormTest, TheSignalComponentsAreSeededAsThePanelSeededThem)
51+
{
52+
const auto config = form.BuildConfiguration();
53+
54+
ASSERT_EQ(config.signalComponents.size(), 3u);
55+
EXPECT_NEAR(config.signalComponents[0].frequencyHz, 1000.0f, 1e-3f);
56+
EXPECT_NEAR(config.signalComponents[0].amplitude, 0.15f, 1e-4f);
57+
EXPECT_NEAR(config.signalComponents[1].frequencyHz, 5000.0f, 1e-3f);
58+
EXPECT_NEAR(config.signalComponents[1].amplitude, 0.5f, 1e-4f);
59+
EXPECT_NEAR(config.signalComponents[2].frequencyHz, 12000.0f, 1e-3f);
60+
EXPECT_NEAR(config.signalComponents[2].amplitude, 0.25f, 1e-4f);
61+
}
62+
63+
TEST_F(FftFormTest, EditingTheTableReachesTheConfiguration)
64+
{
65+
auto& table = form.Model().Table(0);
66+
table.SetCell(1, 0, 6000.0);
67+
table.SetCell(1, 1, 0.75);
68+
69+
const auto config = form.BuildConfiguration();
70+
71+
EXPECT_NEAR(config.signalComponents[1].frequencyHz, 6000.0f, 1e-3f);
72+
EXPECT_NEAR(config.signalComponents[1].amplitude, 0.75f, 1e-4f);
73+
}

‎simulator/analysis/FastFourierTransform/view/CMakeLists.txt‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ target_include_directories(numerical.simulator.analysis.fft.view PUBLIC
77

88
target_link_libraries(numerical.simulator.analysis.fft.view PUBLIC
99
numerical.simulator.analysis.fft.application
10-
numerical.simulator.widgets
10+
numerical.simulator.shell
1111
ui.charts
1212
ui.backend.qt
1313
Qt6::Widgets
@@ -18,8 +18,6 @@ set_target_properties(numerical.simulator.analysis.fft.view PROPERTIES
1818
)
1919

2020
target_sources(numerical.simulator.analysis.fft.view PRIVATE
21-
FftConfigurationPanel.cpp
22-
FftConfigurationPanel.hpp
2321
FftMainWindow.cpp
2422
FftMainWindow.hpp
2523
)

‎simulator/analysis/FastFourierTransform/view/FftConfigurationPanel.cpp‎

Lines changed: 0 additions & 85 deletions
This file was deleted.

0 commit comments

Comments
 (0)