Implement solver stats charts.

PiperOrigin-RevId: 837045292
Change-Id: I1dfe1e10bb6d62f4d994a82c9b645b471fca5500
This commit is contained in:
Haroon Qureshi
2025-11-26 03:24:34 -08:00
committed by Copybara-Service
parent 011166a13a
commit fcad41a5b1
2 changed files with 149 additions and 29 deletions
+146 -27
View File
@@ -296,19 +296,19 @@ void App::UpdatePhysics() {
}
}
bool stepped = false;
toolbox::StepControl::Status status = step_control_.Advance(model_, data_);
if (status == toolbox::StepControl::Status::kPaused) {
// do nothing
} else if (status == toolbox::StepControl::Status::kOk) {
std::span<mjtNum> state = history_.AddToHistory();
if (!state.empty()) {
mj_getState(model_, data_, state.data(), mjSTATE_INTEGRATION);
}
stepped = true;
// If we are adding to the history we didn't have a divergence error
error_ = "";
} else if (status == toolbox::StepControl::Status::kAutoReset) {
ResetPhysics();
} else if (status == toolbox::StepControl::Status::kDiverged) {
stepped = true;
for (mjtWarning w : toolbox::StepControl::kDivergedWarnings) {
if (data_->warning[w].number > 0) {
error_ = mju_warningText(w, data_->warning[w].lastinfo);
@@ -316,7 +316,13 @@ void App::UpdatePhysics() {
}
}
profiler_.Update(model_, data_);
if (stepped) {
profiler_.Update(model_, data_);
std::span<mjtNum> state = history_.AddToHistory();
if (!state.empty()) {
mj_getState(model_, data_, state.data(), mjSTATE_INTEGRATION);
}
}
}
void App::LoadHistory(int offset) {
@@ -558,13 +564,11 @@ void App::HandleKeyboardEvents() {
} else if (ImGui_IsChordJustPressed(ImGuiKey_F7)) {
vis_options_.label = (vis_options_.label + 1) % mjNLABEL;
} else if (ImGui_IsChordJustPressed(ImGuiKey_F9)) {
tmp_.chart_counts = !tmp_.chart_counts;
tmp_.chart_solver = !tmp_.chart_solver;
} else if (ImGui_IsChordJustPressed(ImGuiKey_F10)) {
tmp_.chart_convergence = !tmp_.chart_convergence;
tmp_.chart_cpu_time = !tmp_.chart_cpu_time;
} else if (ImGui_IsChordJustPressed(ImGuiKey_F11)) {
tmp_.chart_dimensions = !tmp_.chart_dimensions;
} else if (ImGui_IsChordJustPressed(ImGuiKey_F12)) {
tmp_.chart_cpu_time = !tmp_.chart_cpu_time;
// } else if (ImGui_IsChordJustPressed(ImGuiKey_Backquote)) {
// ToggleFlag(vis_options_.flags[mjVIS_BODYBVH]);
// } else if (ImGui_IsChordJustPressed(ImGuiKey_Quote)) {
@@ -782,20 +786,20 @@ void App::BuildGui() {
ImGui::End();
}
if (tmp_.chart_convergence) {
ImGui::SetNextWindowPos(chart_pos, ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSize(chart_size, ImGuiCond_FirstUseEver);
if (ImGui::Begin("Convergence", &tmp_.chart_convergence)) {
ImGui::Text("Coming soon!");
}
ImGui::End();
}
// if (tmp_.chart_convergence) {
// ImGui::SetNextWindowPos(chart_pos, ImGuiCond_FirstUseEver);
// ImGui::SetNextWindowSize(chart_size, ImGuiCond_FirstUseEver);
// if (ImGui::Begin("Convergence", &tmp_.chart_convergence)) {
// }
// ImGui::End();
// }
if (tmp_.chart_counts) {
if (tmp_.chart_solver) {
ImGui::SetNextWindowPos(chart_pos, ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSize(chart_size, ImGuiCond_FirstUseEver);
if (ImGui::Begin("Counts", &tmp_.chart_counts)) {
ImGui::Text("Coming soon!");
if (ImGui::Begin("Solver", &tmp_.chart_solver)) {
CountsGui();
ConvergenceGui();
}
ImGui::End();
}
@@ -1468,18 +1472,15 @@ void App::MainMenuGui() {
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Charts")) {
if (ImGui::MenuItem("Counts", "F9")) {
tmp_.chart_counts = !tmp_.chart_counts;
if (ImGui::MenuItem("Solver", "F9")) {
tmp_.chart_solver = !tmp_.chart_solver;
}
if (ImGui::MenuItem("Convergence", "F10")) {
tmp_.chart_convergence = !tmp_.chart_convergence;
if (ImGui::MenuItem("CPU Time", "F10")) {
tmp_.chart_cpu_time = !tmp_.chart_cpu_time;
}
if (ImGui::MenuItem("Dimensions", "F11")) {
tmp_.chart_dimensions = !tmp_.chart_dimensions;
}
if (ImGui::MenuItem("CPU Time", "F12")) {
tmp_.chart_cpu_time = !tmp_.chart_cpu_time;
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Help")) {
@@ -2135,6 +2136,124 @@ void App::ControlsGui() {
}
}
void App::ConvergenceGui() {
if (ImPlot::BeginPlot("Convergence (log 10)", ImVec2(-1, 0))) {
ImPlot::SetupAxis(ImAxis_X1, "iteration", ImPlotAxisFlags_AutoFit);
ImPlot::SetupAxisLimits(ImAxis_X1, 0, 20, ImPlotCond_Always);
ImPlot::SetupAxisFormat(ImAxis_Y1, "%.1f");
ImPlot::SetupAxisLimits(ImAxis_Y1, -20, 5, ImPlotCond_Always);
ImPlot::SetupLegend(ImPlotLocation_NorthEast);
ImPlot::SetupFinish();
const int nisland = data_->nefc ? mjMAX(1, mjMIN(data_->nisland, mjNISLAND)) : 0;
for (int k = 0; k < nisland; k++) {
mjSolverStat* stats = data_->solver + k * mjNSOLVER;
const int npoints =
mjMIN(mjMIN(data_->solver_niter[k], mjNSOLVER), mjMAXLINEPNT);
ImPlot::SetNextLineStyle(IMPLOT_AUTO_COL, 3.0f);
ImPlot::PlotLineG("improvement", +[](int i, void* user_data) {
const mjSolverStat* stats = static_cast<const mjSolverStat*>(user_data);
const float x = static_cast<float>(i);
const float y = mju_log10(mju_max(mjMINVAL, stats[i].improvement));
return ImPlotPoint{x, y};
}, stats, npoints);
if (model_->opt.solver == mjSOL_PGS) {
continue;
}
ImPlot::SetNextLineStyle(IMPLOT_AUTO_COL, 3.0f);
ImPlot::PlotLineG("gradient", +[](int i, void* user_data) {
const mjSolverStat* stats = static_cast<const mjSolverStat*>(user_data);
const float x = static_cast<float>(i);
const float y = mju_log10(mju_max(mjMINVAL, stats[i].gradient));
return ImPlotPoint{x, y};
}, stats, npoints);
ImPlot::SetNextLineStyle(IMPLOT_AUTO_COL, 3.0f);
ImPlot::PlotLineG("lineslope", +[](int i, void* user_data) {
const mjSolverStat* stats = static_cast<const mjSolverStat*>(user_data);
const float x = static_cast<float>(i);
const float y = mju_log10(mju_max(mjMINVAL, stats[i].lineslope));
return ImPlotPoint{x, y};
}, stats, npoints);
}
ImPlot::EndPlot();
}
}
void App::CountsGui() {
if (ImPlot::BeginPlot("Counts", ImVec2(-1, 0))) {
ImPlot::SetupAxis(ImAxis_X1, "iteration", ImPlotAxisFlags_AutoFit);
ImPlot::SetupAxisLimits(ImAxis_X1, 0, 20, ImPlotCond_Always);
ImPlot::SetupAxisFormat(ImAxis_Y1, "%.0f");
ImPlot::SetupAxisLimits(ImAxis_Y1, 0, 80, ImPlotCond_Always);
ImPlot::SetupLegend(ImPlotLocation_NorthEast);
ImPlot::SetupFinish();
const int nisland = data_->nefc ? mjMAX(1, mjMIN(data_->nisland, mjNISLAND)) : 0;
for (int k = 0; k < nisland; k++) {
const int npoints =
mjMIN(mjMIN(data_->solver_niter[k], mjNSOLVER), mjMAXLINEPNT);
mjSolverStat* stats = data_->solver + k*mjNSOLVER;
int nefc = nisland == 1 ? data_->nefc : data_->island_nefc[k];
ImPlot::SetNextLineStyle(IMPLOT_AUTO_COL, 3.0f);
ImPlot::PlotLineG("total", +[](int i, void* user_data) {
const float x = static_cast<float>(i);
const float y = *(static_cast<int*>(user_data));
return ImPlotPoint{x, y};
}, &nefc, npoints);
ImPlot::SetNextLineStyle(IMPLOT_AUTO_COL, 3.0f);
ImPlot::PlotLineG("active", +[](int i, void* user_data) {
const mjSolverStat* stats = static_cast<const mjSolverStat*>(user_data);
const float x = static_cast<float>(i);
const float y = stats[i].nactive;
return ImPlotPoint{x, y};
}, stats, npoints);
ImPlot::SetNextLineStyle(IMPLOT_AUTO_COL, 3.0f);
ImPlot::PlotLineG("changed", +[](int i, void* user_data) {
const mjSolverStat* stats = static_cast<const mjSolverStat*>(user_data);
const float x = static_cast<float>(i);
const float y = stats[i].nchange;
return ImPlotPoint{x, y};
}, stats, npoints);
if (model_->opt.solver == mjSOL_PGS) {
continue;
}
ImPlot::SetNextLineStyle(IMPLOT_AUTO_COL, 3.0f);
ImPlot::PlotLineG("evals", +[](int i, void* user_data) {
const mjSolverStat* stats = static_cast<const mjSolverStat*>(user_data);
const float x = static_cast<float>(i);
const float y = stats[i].neval;
return ImPlotPoint{x, y};
}, stats, npoints);
if (model_->opt.solver == mjSOL_CG) {
continue;
}
ImPlot::SetNextLineStyle(IMPLOT_AUTO_COL, 3.0f);
ImPlot::PlotLineG("updates", +[](int i, void* user_data) {
const mjSolverStat* stats = static_cast<const mjSolverStat*>(user_data);
const float x = static_cast<float>(i);
const float y = stats[i].nupdate;
return ImPlotPoint{x, y};
}, stats, npoints);
}
ImPlot::EndPlot();
}
}
float App::GetExpectedLabelWidth() {
// Find the longest label which we'll use to set the minimum toggle button
// width. This isn't perfect because we may have labels that are longer, but
+3 -2
View File
@@ -92,8 +92,7 @@ class App {
bool info = false;
bool chart_cpu_time = false;
bool chart_dimensions = false;
bool chart_counts = false;
bool chart_convergence = false;
bool chart_solver = false;
bool settings_panel = true;
bool inspector_panel = true;
bool style_editor = false;
@@ -170,6 +169,8 @@ class App {
void StateGui();
void JointsGui();
void ControlsGui();
void ConvergenceGui();
void CountsGui();
float GetExpectedLabelWidth();
std::vector<const char*> GetCameraNames();