Skip to content

Commit 8e87b9c

Browse files
authored
output complexity info always
Enhance output to always display complexity and timing information in the CLI, regardless of solver success or failure. (#30) Fixes #19
1 parent a7e04be commit 8e87b9c

4 files changed

Lines changed: 81 additions & 50 deletions

File tree

cmd/solver/cli.cpp

Lines changed: 35 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -166,43 +166,47 @@ int main(int argc, char **argv)
166166
}
167167
else {
168168
// --- Standard Schedule Output Handling ---
169-
if (resultCode == 0) {
170-
// Success
171-
if (!quiet) {
172-
173-
// Print Metadata / Settings
174-
if (resultJson.contains("metadata")) {
175-
std::cout << "\n--- 🔧 Solver Settings ---" << std::endl;
176-
json meta = resultJson["metadata"];
177-
std::cout << "Time Limit: " << meta.value("timeLimit", 0) << "s | "
178-
<< "Gap: " << meta.value("optimalityGap", 0.0) << " | "
179-
<< "Spotter: " << meta.value("spotterMode", "unknown") << std::endl;
180-
}
169+
if (!quiet) {
170+
// Print Metadata / Settings
171+
if (resultJson.contains("metadata")) {
172+
std::cout << "\n--- 🔧 Solver Settings ---" << std::endl;
173+
json meta = resultJson["metadata"];
174+
std::cout << "Time Limit: " << meta.value("timeLimit", 0) << "s | "
175+
<< "Gap: " << meta.value("optimalityGap", 0.0) << " | "
176+
<< "Spotter: " << meta.value("spotterMode", "unknown") << std::endl;
177+
}
181178

182-
// Print Complexity
183-
if (resultJson.contains("complexity")) {
184-
std::cout << "\n--- 🧠 Complexity ---" << std::endl;
185-
json c = resultJson["complexity"];
186-
std::cout << "Rows: " << c.value("modelRows", 0) << " | "
187-
<< "Cols: " << c.value("modelColumns", 0) << " | "
188-
<< "Nodes: " << c.value("searchNodes", 0) << std::endl;
189-
std::cout << "Big-M Rest Constraints: " << c.value("numRestConstraints", 0) << std::endl;
179+
// Print Complexity (regardless of success/failure)
180+
if (resultJson.contains("complexity")) {
181+
std::cout << "\n--- 🧠 Complexity ---" << std::endl;
182+
json c = resultJson["complexity"];
183+
std::cout << "Rows: " << c.value("modelRows", 0) << " | "
184+
<< "Cols: " << c.value("modelColumns", 0) << " | "
185+
<< "Nodes: " << c.value("searchNodes", 0) << std::endl;
186+
std::cout << "Big-M Rest Constraints: " << c.value("numRestConstraints", 0) << std::endl;
187+
if (!c["finalGap"].is_null()) {
190188
std::cout << "Final Gap: " << c.value("finalGap", 0.0) << std::endl;
191189
}
190+
}
192191

193-
// Print Timing
194-
if (resultJson.contains("timing")) {
195-
std::cout << "\n--- ⏱️ Timing Performance ---" << std::endl;
196-
json t = resultJson["timing"];
197-
std::cout << std::fixed << std::setprecision(2);
198-
std::cout << "Setup/Model Build : " << std::setw(8) << t.value("setupMs", 0.0) << " ms" << std::endl;
199-
std::cout << "Driver Solve : " << std::setw(8) << t.value("driverSolveMs", 0.0) << " ms" << std::endl;
200-
if (t.contains("spotterSolveMs")) {
201-
std::cout << "Spotter Solve : " << std::setw(8) << t.value("spotterSolveMs", 0.0) << " ms" << std::endl;
202-
}
203-
std::cout << "Total Wall Time : " << std::setw(8) << t.value("totalSeconds", 0.0) << " s" << std::endl;
192+
// Print Timing (regardless of success/failure)
193+
if (resultJson.contains("timing")) {
194+
std::cout << "\n--- ⏱️ Timing Performance ---" << std::endl;
195+
json t = resultJson["timing"];
196+
std::cout << std::fixed << std::setprecision(2);
197+
std::cout << "Setup/Model Build : " << std::setw(8) << t.value("setupMs", 0.0) << " ms" << std::endl;
198+
std::cout << "Driver Solve : " << std::setw(8) << t.value("driverSolveMs", 0.0) << " ms" << std::endl;
199+
if (t.contains("spotterSolveMs")) {
200+
std::cout << "Spotter Solve : " << std::setw(8) << t.value("spotterSolveMs", 0.0) << " ms" << std::endl;
204201
}
202+
std::cout << "Total Wall Time : " << std::setw(8) << t.value("totalSeconds", 0.0) << " s" << std::endl;
203+
}
204+
}
205205

206+
// Check if the solve was successful
207+
if (resultCode == 0) {
208+
// Success
209+
if (!quiet) {
206210
// Print the schedule
207211
std::cout << "\n--- 🏁 Race Schedule ---" << std::endl;
208212
bool hasSpotters = (solverOptions.spotterMode != JRES_SPOTTER_MODE_NONE);

src/jres_solver.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,15 @@ int solve_race_schedule(const char* raceDataJson,
5252
*outputJson = create_output_string(resultJson.dump(4));
5353
return 0;
5454

55+
} catch (const SolverException& e) {
56+
// Custom exception with metadata - preserve timing/complexity
57+
json errorJson = e.metadata;
58+
errorJson["success"] = false;
59+
errorJson["error"] = e.what();
60+
*outputJson = create_output_string(errorJson.dump(4));
61+
return -1;
5562
} catch (const std::exception& e) {
63+
// Generic exception - minimal error JSON
5664
json errorJson;
5765
errorJson["success"] = false;
5866
errorJson["error"] = e.what();

src/jres_solver_types.hpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,17 @@ struct ComplexityMetrics {
8080
double finalGap = 0.0;
8181
};
8282

83+
/**
84+
* @brief Custom exception that carries solver metadata for better error reporting.
85+
*/
86+
class SolverException : public std::runtime_error {
87+
public:
88+
json metadata;
89+
90+
SolverException(const std::string& message, const json& meta = json::object())
91+
: std::runtime_error(message), metadata(meta) {}
92+
};
93+
8394
// --- JSON Deserialization Declarations ---
8495
void from_json(const json &j, TeamMember &member);
8596
void from_json(const json &j, RaceData &data);

src/jres_standard_solver.cpp

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -141,15 +141,21 @@ json JresStandardSolver::solve()
141141
metrics.finalGap = info.mip_gap; // Relative gap
142142

143143
HighsModelStatus status = m_highs->getModelStatus();
144-
if (status != HighsModelStatus::kOptimal && status != HighsModelStatus::kTimeLimit) {
145-
// Strict check: if it's infeasible or unbounded
146-
if (status == HighsModelStatus::kInfeasible) {
147-
throw std::runtime_error("Model is infeasible. No solution exists.");
148-
}
149-
// Note: HiGHS might return kTimeLimit but still have a valid feasible solution
144+
145+
// --- Calculate Total Time ---
146+
auto endTotal = high_resolution_clock::now();
147+
double totalDurationSeconds = duration<double>(endTotal - startTotal).count();
148+
149+
// --- Build Timing JSON (always include, even on failure) ---
150+
json timingJson;
151+
timingJson["setupMs"] = setupDurationMs;
152+
timingJson["driverSolveMs"] = driverSolveDurationMs;
153+
if (m_ctx.spotterMode == SpotterMode::Sequential) {
154+
timingJson["spotterSolveMs"] = spotterSolveDurationMs;
150155
}
156+
timingJson["totalSeconds"] = totalDurationSeconds;
151157

152-
// --- 6. Process Results ---
158+
// --- Build base output JSON (always include, even on failure) ---
153159
json outputJson;
154160
json metaJson;
155161
to_json(metaJson, m_ctx);
@@ -158,8 +164,20 @@ json JresStandardSolver::solve()
158164
json complexityJson;
159165
to_json(complexityJson, metrics);
160166
outputJson["complexity"] = complexityJson;
167+
outputJson["timing"] = timingJson;
161168
outputJson["raceData"] = m_ctx.raceData;
162169

170+
// Check for infeasibility - throw exception with metadata for error handler
171+
if (status != HighsModelStatus::kOptimal && status != HighsModelStatus::kTimeLimit) {
172+
// Strict check: if it's infeasible or unbounded
173+
if (status == HighsModelStatus::kInfeasible) {
174+
throw SolverException("Model is infeasible. No solution exists.", outputJson);
175+
}
176+
// Note: HiGHS might return kTimeLimit but still have a valid feasible solution
177+
}
178+
179+
// --- 6. Process Results (only for successful solves) ---
180+
163181
std::vector<ScheduleEntry> schedule;
164182

165183
// Get solution vector
@@ -257,21 +275,11 @@ json JresStandardSolver::solve()
257275

258276
auto spotterEnd = high_resolution_clock::now();
259277
spotterSolveDurationMs = duration<double, std::milli>(spotterEnd - spotterStart).count();
260-
}
261-
262-
auto endTotal = high_resolution_clock::now();
263-
double totalDurationSeconds = duration<double>(endTotal - startTotal).count();
264-
265-
// Timing JSON
266-
json timingJson;
267-
timingJson["setupMs"] = setupDurationMs;
268-
timingJson["driverSolveMs"] = driverSolveDurationMs;
269-
if (m_ctx.spotterMode == SpotterMode::Sequential) {
278+
279+
// Update timing JSON with spotter solve time
270280
timingJson["spotterSolveMs"] = spotterSolveDurationMs;
271281
}
272-
timingJson["totalSeconds"] = totalDurationSeconds;
273282

274-
outputJson["timing"] = timingJson;
275283
json scheduleJson = json::array();
276284
bool hasSpotters = (m_ctx.spotterMode != SpotterMode::None);
277285
for(const auto& entry : schedule) {

0 commit comments

Comments
 (0)