Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 35 additions & 31 deletions cmd/solver/cli.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -166,43 +166,47 @@ int main(int argc, char **argv)
}
else {
// --- Standard Schedule Output Handling ---
if (resultCode == 0) {
// Success
if (!quiet) {

// Print Metadata / Settings
if (resultJson.contains("metadata")) {
std::cout << "\n--- 🔧 Solver Settings ---" << std::endl;
json meta = resultJson["metadata"];
std::cout << "Time Limit: " << meta.value("timeLimit", 0) << "s | "
<< "Gap: " << meta.value("optimalityGap", 0.0) << " | "
<< "Spotter: " << meta.value("spotterMode", "unknown") << std::endl;
}
if (!quiet) {
// Print Metadata / Settings
if (resultJson.contains("metadata")) {
std::cout << "\n--- 🔧 Solver Settings ---" << std::endl;
json meta = resultJson["metadata"];
std::cout << "Time Limit: " << meta.value("timeLimit", 0) << "s | "
<< "Gap: " << meta.value("optimalityGap", 0.0) << " | "
<< "Spotter: " << meta.value("spotterMode", "unknown") << std::endl;
}

// Print Complexity
if (resultJson.contains("complexity")) {
std::cout << "\n--- 🧠 Complexity ---" << std::endl;
json c = resultJson["complexity"];
std::cout << "Rows: " << c.value("modelRows", 0) << " | "
<< "Cols: " << c.value("modelColumns", 0) << " | "
<< "Nodes: " << c.value("searchNodes", 0) << std::endl;
std::cout << "Big-M Rest Constraints: " << c.value("numRestConstraints", 0) << std::endl;
// Print Complexity (regardless of success/failure)
if (resultJson.contains("complexity")) {
std::cout << "\n--- 🧠 Complexity ---" << std::endl;
json c = resultJson["complexity"];
std::cout << "Rows: " << c.value("modelRows", 0) << " | "
<< "Cols: " << c.value("modelColumns", 0) << " | "
<< "Nodes: " << c.value("searchNodes", 0) << std::endl;
std::cout << "Big-M Rest Constraints: " << c.value("numRestConstraints", 0) << std::endl;
if (!c["finalGap"].is_null()) {
std::cout << "Final Gap: " << c.value("finalGap", 0.0) << std::endl;
}
}

// Print Timing
if (resultJson.contains("timing")) {
std::cout << "\n--- ⏱️ Timing Performance ---" << std::endl;
json t = resultJson["timing"];
std::cout << std::fixed << std::setprecision(2);
std::cout << "Setup/Model Build : " << std::setw(8) << t.value("setupMs", 0.0) << " ms" << std::endl;
std::cout << "Driver Solve : " << std::setw(8) << t.value("driverSolveMs", 0.0) << " ms" << std::endl;
if (t.contains("spotterSolveMs")) {
std::cout << "Spotter Solve : " << std::setw(8) << t.value("spotterSolveMs", 0.0) << " ms" << std::endl;
}
std::cout << "Total Wall Time : " << std::setw(8) << t.value("totalSeconds", 0.0) << " s" << std::endl;
// Print Timing (regardless of success/failure)
if (resultJson.contains("timing")) {
std::cout << "\n--- ⏱️ Timing Performance ---" << std::endl;
json t = resultJson["timing"];
std::cout << std::fixed << std::setprecision(2);
std::cout << "Setup/Model Build : " << std::setw(8) << t.value("setupMs", 0.0) << " ms" << std::endl;
std::cout << "Driver Solve : " << std::setw(8) << t.value("driverSolveMs", 0.0) << " ms" << std::endl;
if (t.contains("spotterSolveMs")) {
std::cout << "Spotter Solve : " << std::setw(8) << t.value("spotterSolveMs", 0.0) << " ms" << std::endl;
}
std::cout << "Total Wall Time : " << std::setw(8) << t.value("totalSeconds", 0.0) << " s" << std::endl;
}
}

// Check if the solve was successful
if (resultCode == 0) {
// Success
if (!quiet) {
// Print the schedule
std::cout << "\n--- 🏁 Race Schedule ---" << std::endl;
bool hasSpotters = (solverOptions.spotterMode != JRES_SPOTTER_MODE_NONE);
Expand Down
8 changes: 8 additions & 0 deletions src/jres_solver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,15 @@ int solve_race_schedule(const char* raceDataJson,
*outputJson = create_output_string(resultJson.dump(4));
return 0;

} catch (const SolverException& e) {
// Custom exception with metadata - preserve timing/complexity
json errorJson = e.metadata;
errorJson["success"] = false;
errorJson["error"] = e.what();
*outputJson = create_output_string(errorJson.dump(4));
return -1;
} catch (const std::exception& e) {
// Generic exception - minimal error JSON
json errorJson;
errorJson["success"] = false;
errorJson["error"] = e.what();
Expand Down
11 changes: 11 additions & 0 deletions src/jres_solver_types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,17 @@ struct ComplexityMetrics {
double finalGap = 0.0;
};

/**
* @brief Custom exception that carries solver metadata for better error reporting.
*/
class SolverException : public std::runtime_error {
public:
json metadata;

SolverException(const std::string& message, const json& meta = json::object())
: std::runtime_error(message), metadata(meta) {}
};

// --- JSON Deserialization Declarations ---
void from_json(const json &j, TeamMember &member);
void from_json(const json &j, RaceData &data);
Expand Down
46 changes: 27 additions & 19 deletions src/jres_standard_solver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,15 +141,21 @@ json JresStandardSolver::solve()
metrics.finalGap = info.mip_gap; // Relative gap

HighsModelStatus status = m_highs->getModelStatus();
if (status != HighsModelStatus::kOptimal && status != HighsModelStatus::kTimeLimit) {
// Strict check: if it's infeasible or unbounded
if (status == HighsModelStatus::kInfeasible) {
throw std::runtime_error("Model is infeasible. No solution exists.");
}
// Note: HiGHS might return kTimeLimit but still have a valid feasible solution

// --- Calculate Total Time ---
auto endTotal = high_resolution_clock::now();
double totalDurationSeconds = duration<double>(endTotal - startTotal).count();

// --- Build Timing JSON (always include, even on failure) ---
json timingJson;
timingJson["setupMs"] = setupDurationMs;
timingJson["driverSolveMs"] = driverSolveDurationMs;
if (m_ctx.spotterMode == SpotterMode::Sequential) {
timingJson["spotterSolveMs"] = spotterSolveDurationMs;
}
timingJson["totalSeconds"] = totalDurationSeconds;

// --- 6. Process Results ---
// --- Build base output JSON (always include, even on failure) ---
json outputJson;
json metaJson;
to_json(metaJson, m_ctx);
Expand All @@ -158,8 +164,20 @@ json JresStandardSolver::solve()
json complexityJson;
to_json(complexityJson, metrics);
outputJson["complexity"] = complexityJson;
outputJson["timing"] = timingJson;
outputJson["raceData"] = m_ctx.raceData;

// Check for infeasibility - throw exception with metadata for error handler
if (status != HighsModelStatus::kOptimal && status != HighsModelStatus::kTimeLimit) {
// Strict check: if it's infeasible or unbounded
if (status == HighsModelStatus::kInfeasible) {
throw SolverException("Model is infeasible. No solution exists.", outputJson);
}
// Note: HiGHS might return kTimeLimit but still have a valid feasible solution
}

// --- 6. Process Results (only for successful solves) ---

std::vector<ScheduleEntry> schedule;

// Get solution vector
Expand Down Expand Up @@ -257,21 +275,11 @@ json JresStandardSolver::solve()

auto spotterEnd = high_resolution_clock::now();
spotterSolveDurationMs = duration<double, std::milli>(spotterEnd - spotterStart).count();
}

auto endTotal = high_resolution_clock::now();
double totalDurationSeconds = duration<double>(endTotal - startTotal).count();

// Timing JSON
json timingJson;
timingJson["setupMs"] = setupDurationMs;
timingJson["driverSolveMs"] = driverSolveDurationMs;
if (m_ctx.spotterMode == SpotterMode::Sequential) {

// Update timing JSON with spotter solve time
timingJson["spotterSolveMs"] = spotterSolveDurationMs;
}
timingJson["totalSeconds"] = totalDurationSeconds;

outputJson["timing"] = timingJson;
json scheduleJson = json::array();
bool hasSpotters = (m_ctx.spotterMode != SpotterMode::None);
for(const auto& entry : schedule) {
Expand Down
Loading