Skip to content

Commit 8e3fcef

Browse files
authored
Merge pull request #44 from popmonkey/input-changes
input parameter changes
2 parents 4b9376f + da5aaa5 commit 8e3fcef

11 files changed

Lines changed: 91 additions & 26 deletions

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ The `raceDataJson` string passed to `jres_input_from_json` must strictly follow
186186
| :--- | :--- | :--- | :--- |
187187
| `consecutiveStints` | Integer | No (Default `1`) | Hard constraint: Required number of consecutive stints a driver must perform (block size). |
188188
| `minimumRestHours` | Integer | No (Default `0`) | Hard constraint: Minimum contiguous rest time required once per race. |
189-
| `maxBusyHours` | Integer | No (Default `8`) | Hard constraint: Maximum total time (driving + spotting) a member can work before a required rest. |
189+
| `maximumBusyHours` | Integer | No (Default `8`) | Hard constraint: Maximum total time (driving + spotting) a member can work before a required rest. |
190190
| `firstStintDriver` | String | No | Hard constraint: The name of the team member who must drive the first stint. |
191191
| `teamMembers` | Array | Yes | List of drivers and spotters (see below). |
192192
| `availability` | Object | Yes | Map of availability constraints (see below). |

TOOLS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ jres_solver.exe [options]
6060

6161
> [!TIP]
6262
> **Constraint Configuration:**
63-
> While some options are available as CLI flags, core schedule constraints such as `maxBusyHours`, `minimumRestHours`, and `consecutiveStints` are defined strictly within the **Input JSON** file. See [README](./README.md#input-json-specification) for details.
63+
> While some options are available as CLI flags, core schedule constraints such as `maximumBusyHours`, `minimumRestHours`, and `consecutiveStints` are defined strictly within the **Input JSON** file. See [README](./README.md#input-json-specification) for details.
6464
6565
### Spotter Modes
6666

include/jres_solver/jres_solver.hpp

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ struct JresSolverInput {
123123
/** @brief Minimum rest time in hours required after a shift. */
124124
int minimumRestHours;
125125
/** @brief Maximum busy time in hours (driving or spotting) before a rest is required. */
126-
int maxBusyHours;
126+
int maximumBusyHours;
127127
/** @brief The name of the team member who must drive the first stint. */
128128
const char* firstStintDriver;
129129
/** @brief A pointer to an array of team members. */
@@ -176,6 +176,20 @@ struct JresSolverStats {
176176
double spotterSolveDurationMs;
177177
};
178178

179+
/**
180+
* @brief Configuration parameters used for the solution.
181+
*/
182+
struct JresInputConfig {
183+
/** @brief Required consecutive stints (drivers must do this many stints in a row, except potentially the last one). */
184+
int consecutiveStints;
185+
/** @brief Minimum rest time in hours required after a shift. */
186+
int minimumRestHours;
187+
/** @brief Maximum busy time in hours (driving or spotting) before a rest is required. */
188+
int maximumBusyHours;
189+
/** @brief The name of the team member who must drive the first stint. */
190+
const char* firstStintDriver;
191+
};
192+
179193
/**
180194
* @brief The main output struct from the solver.
181195
*/
@@ -192,6 +206,8 @@ struct JresSolverOutput {
192206
JresSolverStats* stats;
193207
/** @brief The options used to generate this solution. */
194208
JresSolverOptions* options;
209+
/** @brief The input configuration used to generate this solution. */
210+
JresInputConfig* config;
195211
/** @brief A pointer to an array of team members, including their tzOffset. */
196212
JresTeamMember* teamMembers;
197213
/** @brief The number of team members. */

src/constraints/max_busy_time.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ void apply_max_busy_time_constraints(
1818
{
1919
using namespace jres::internal;
2020

21-
if (input.maxBusyHours <= 0) return;
21+
if (input.maximumBusyHours <= 0) return;
2222

2323
// Calculate durations
2424
std::vector<double> stintDurations;
@@ -37,7 +37,7 @@ void apply_max_busy_time_constraints(
3737
for (size_t e = s; e < input.stints.size(); ++e) {
3838
currentDuration += stintDurations[e];
3939

40-
if (currentDuration > input.maxBusyHours) {
40+
if (currentDuration > input.maximumBusyHours) {
4141
// Violation if assigned to ALL stints in [s, e]
4242
// Constraint: Sum(coeff * x[k]) <= (e - s)
4343

src/jres_internal_types.cpp

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ SolverInput from_c_input(const JresSolverInput* c_input) {
6767

6868
input.consecutiveStints = c_input->consecutiveStints;
6969
input.minimumRestHours = c_input->minimumRestHours;
70-
input.maxBusyHours = c_input->maxBusyHours;
70+
input.maximumBusyHours = c_input->maximumBusyHours;
7171
if (c_input->firstStintDriver) {
7272
input.firstStintDriver = c_input->firstStintDriver;
7373
}
@@ -131,15 +131,35 @@ JresSolverOutput* to_c_output(const SolverOutput& output, const JresSolverOption
131131
c_output->diagnosis[i] = allocate_and_copy(output.diagnosis[i]);
132132
}
133133

134-
c_output->stats = new JresSolverStats();
135-
c_output->stats->modelColumns = output.stats.modelColumns;
136-
c_output->stats->modelRows = output.stats.modelRows;
137-
c_output->stats->searchNodes = output.stats.searchNodes;
138-
c_output->stats->finalGap = output.stats.finalGap;
139-
c_output->stats->setupDurationMs = output.stats.setupDurationMs;
140-
c_output->stats->driverSolveDurationMs = output.stats.driverSolveDurationMs;
141-
c_output->stats->spotterSolveDurationMs = output.stats.spotterSolveDurationMs;
134+
if (output.stats.modelRows > 0 || output.stats.modelColumns > 0) {
135+
c_output->stats = new JresSolverStats();
136+
c_output->stats->modelColumns = output.stats.modelColumns;
137+
c_output->stats->modelRows = output.stats.modelRows;
138+
c_output->stats->searchNodes = output.stats.searchNodes;
139+
c_output->stats->finalGap = output.stats.finalGap;
140+
c_output->stats->setupDurationMs = output.stats.setupDurationMs;
141+
c_output->stats->driverSolveDurationMs = output.stats.driverSolveDurationMs;
142+
c_output->stats->spotterSolveDurationMs = output.stats.spotterSolveDurationMs;
143+
} else {
144+
c_output->stats = nullptr;
145+
}
146+
147+
// Copy options
148+
c_output->options = new JresSolverOptions();
149+
*c_output->options = options;
150+
151+
// Copy config
152+
c_output->config = new JresInputConfig();
153+
c_output->config->consecutiveStints = output.config.consecutiveStints;
154+
c_output->config->minimumRestHours = output.config.minimumRestHours;
155+
c_output->config->maximumBusyHours = output.config.maximumBusyHours;
156+
if (!output.config.firstStintDriver.empty()) {
157+
c_output->config->firstStintDriver = allocate_and_copy(output.config.firstStintDriver);
158+
} else {
159+
c_output->config->firstStintDriver = nullptr;
160+
}
142161

162+
// Copy teamMembers
143163
c_output->teamMembers_len = output.teamMembers.size();
144164
c_output->teamMembers = new JresTeamMember[c_output->teamMembers_len];
145165
for (size_t i = 0; i < output.teamMembers.size(); ++i) {
@@ -149,8 +169,6 @@ JresSolverOutput* to_c_output(const SolverOutput& output, const JresSolverOption
149169
c_output->teamMembers[i].tzOffset = output.teamMembers[i].tzOffset;
150170
}
151171

152-
c_output->options = new JresSolverOptions(options);
153-
154172
return c_output;
155173
}
156174

src/jres_internal_types.hpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ struct SolverInput
4747
{
4848
int consecutiveStints = 1;
4949
int minimumRestHours = 0;
50-
int maxBusyHours = 8;
50+
int maximumBusyHours = 8;
5151
std::string firstStintDriver;
5252
std::vector<TeamMember> teamMembers;
5353
std::map<std::string, std::map<std::string, Availability>> availability;
@@ -80,12 +80,20 @@ struct SolverStats {
8080
double spotterSolveDurationMs = 0.0;
8181
};
8282

83+
struct InputConfig {
84+
int consecutiveStints = 1;
85+
int minimumRestHours = 0;
86+
int maximumBusyHours = 8;
87+
std::string firstStintDriver;
88+
};
89+
8390
struct SolverOutput
8491
{
8592
std::vector<ScheduleEntry> schedule;
8693
std::vector<std::string> diagnosis;
8794
SolverStats stats;
8895
std::vector<TeamMember> teamMembers;
96+
InputConfig config;
8997
// Add any other output fields here, like diagnosis or metrics
9098
};
9199

src/jres_json_converter.cpp

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ JRES_SOLVER_API JresSolverInput* jres_input_from_json(const char* jsonData) {
7676
// Global Constraints
7777
input->consecutiveStints = j.value("consecutiveStints", 1);
7878
input->minimumRestHours = j.value("minimumRestHours", 0);
79-
input->maxBusyHours = j.value("maxBusyHours", 8);
79+
input->maximumBusyHours = j.value("maximumBusyHours", 8);
8080

8181
if (j.contains("firstStintDriver") && !j["firstStintDriver"].is_null()) {
8282
input->firstStintDriver = allocate_and_copy(j["firstStintDriver"]);
@@ -194,6 +194,17 @@ JRES_SOLVER_API char* jres_output_to_json(const JresSolverOutput* output) {
194194
j["options"] = options_json;
195195
}
196196

197+
if (output->config) {
198+
json config_json;
199+
config_json["consecutiveStints"] = output->config->consecutiveStints;
200+
config_json["minimumRestHours"] = output->config->minimumRestHours;
201+
config_json["maximumBusyHours"] = output->config->maximumBusyHours;
202+
if (output->config->firstStintDriver) {
203+
config_json["firstStintDriver"] = output->config->firstStintDriver;
204+
}
205+
j["configuration"] = config_json;
206+
}
207+
197208
std::string json_str = j.dump();
198209
return allocate_and_copy(json_str);
199210

@@ -238,6 +249,12 @@ JRES_SOLVER_API void free_jres_solver_output(JresSolverOutput* output) {
238249
if (output->options) {
239250
delete output->options;
240251
}
252+
if (output->config) {
253+
if (output->config->firstStintDriver) {
254+
delete[] output->config->firstStintDriver; // allocated via allocate_and_copy
255+
}
256+
delete output->config;
257+
}
241258
delete output;
242259
}
243260

src/jres_standard_solver.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,12 @@ jres::internal::SolverOutput JresStandardSolver::solve()
124124
auto startTotal = high_resolution_clock::now();
125125
jres::internal::SolverOutput output;
126126

127+
// Populate Config
128+
output.config.consecutiveStints = m_input.consecutiveStints;
129+
output.config.minimumRestHours = m_input.minimumRestHours;
130+
output.config.maximumBusyHours = m_input.maximumBusyHours;
131+
output.config.firstStintDriver = m_input.firstStintDriver;
132+
127133
// --- Arithmetic Pre-flight Check ---
128134
int totalStints = (int)m_input.stints.size();
129135
auto capAnalysis = jres::internal::CapacityAnalyzer::calculate_max_potential_capacity(m_driverPool, m_input);

test/test_max_busy.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ TEST(MaxBusyTest, MaxBusyHoursInfeasible) {
1010
// Driver cannot do S1, S2, S3 continuously.
1111

1212
std::string json_str = R"({
13-
"maxBusyHours": 2,
13+
"maximumBusyHours": 2,
1414
"teamMembers": [
1515
{ "name": "D1", "isDriver": true }
1616
],
@@ -56,7 +56,7 @@ TEST(MaxBusyTest, MaxBusyHoursFeasible) {
5656
// D1: S1, S2. D2: S3. Valid.
5757

5858
std::string json_str = R"({
59-
"maxBusyHours": 2,
59+
"maximumBusyHours": 2,
6060
"teamMembers": [
6161
{ "name": "D1", "isDriver": true },
6262
{ "name": "D2", "isDriver": true }
@@ -101,7 +101,7 @@ TEST(MaxBusyTest, MaxBusySpotterIntegrated) {
101101
// Spotter cannot do S1, S2, S3 continuously.
102102

103103
std::string json_str = R"({
104-
"maxBusyHours": 2,
104+
"maximumBusyHours": 2,
105105
"teamMembers": [
106106
{ "name": "D1", "isDriver": true, "isSpotter": false },
107107
{ "name": "D2", "isDriver": true, "isSpotter": false },
@@ -153,7 +153,7 @@ TEST(MaxBusyTest, MaxBusySequential) {
153153
// Combined Kyle = 8h. > 6h.
154154

155155
std::string json_str = R"({
156-
"maxBusyHours": 6,
156+
"maximumBusyHours": 6,
157157
"teamMembers": [
158158
{ "name": "Kyle", "isDriver": true, "isSpotter": true },
159159
{ "name": "D2", "isDriver": true, "isSpotter": false },

test/test_max_busy_defaults.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ TEST(MaxBusyDefaults, DefaultValueCheck) {
4444
// Verify manually that input->maxBusyHours is 8 (if we can inspect it? It's in the opaque struct)
4545
// Actually we can inspect it because we have the struct definition in header.
4646
// But JresSolverInput definition is in jres_solver.hpp which is included.
47-
EXPECT_EQ(input->maxBusyHours, 8);
47+
EXPECT_EQ(input->maximumBusyHours, 8);
4848

4949
JresSolverOutput* output = solve_race_schedule(input, &options);
5050

@@ -56,7 +56,7 @@ TEST(MaxBusyDefaults, DefaultValueCheck) {
5656
}
5757

5858
if (feasible) {
59-
FAIL() << "Solver found schedule despite default maxBusyHours=8 violation.";
59+
FAIL() << "Solver found schedule despite default maximumBusyHours=8 violation.";
6060
}
6161

6262
free_jres_solver_output(output);

0 commit comments

Comments
 (0)