Skip to content

Commit 29f5bed

Browse files
authored
Merge pull request #33 from popmonkey/2.0
v2.0! huge changes, see PR comment
2 parents f355962 + 1e958e4 commit 29f5bed

34 files changed

Lines changed: 1833 additions & 2479 deletions

CMakeLists.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,10 +157,11 @@ target_include_directories(jres_utils PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src)
157157
# --- JRES Solver Core (Shared or Static) ---
158158
add_library(jres_solver_lib
159159
src/jres_solver.cpp
160-
src/jres_solver_utils.cpp
160+
src/jres_json_converter.cpp
161+
src/jres_internal_types.cpp
162+
src/jres_solver_base.cpp
161163
src/jres_standard_solver.cpp
162164
src/jres_diagnostic_solver.cpp
163-
src/jres_solver_types.cpp
164165
)
165166
set_target_properties(jres_solver_lib PROPERTIES OUTPUT_NAME "jres_solver")
166167

DEVELOPMENT.md renamed to CONTRIBUTING.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@ It has been structured as a C-API library (`jres_solver`) and a simple CLI clien
1515
| └── jres_solver/ # The public C-API header for the library
1616
├── src/ # The C++ library implementation
1717
| ├── jres_solver.cpp # C-API Wrapper and Orchestrator
18-
| ├── jres_solver_utils.cpp # Shared utilities and Base Class
18+
| ├── jres_solver_base.cpp # Shared logic for both solvers
1919
| ├── jres_standard_solver.cpp # Optimized Strict Solver
2020
| ├── jres_diagnostic_solver.cpp # Relaxed Diagnostic Solver
21-
| ├── jres_solver_types.cpp # JSON serialization logic
21+
| ├── jres_internal_types.cpp # Internal C++ data structures
22+
| ├── jres_json_converter.cpp # JSON conversion logic
2223
| ├── formatter/ # Formatter implementation
2324
| └── utils/ # Utility functions
2425
├── lib/

README.md

Lines changed: 126 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,78 @@ This library can be used to solve for optimal driver and spotter schedules for e
88
## Additional Documentation
99

1010
* **[Tools](./TOOLS.md)** - releases include some command line tools that use the library
11-
* **[Development](./DEVELOPMENT.md)** - instructions for development of the library
11+
* **[Development](./CONTRIBUTING.md)** - instructions for development of the library
1212

1313
## The Library
1414

1515
**JresSolver** is a C++ library designed to optimize endurance racing schedules. It uses the **HiGHS** Mixed Integer Programming (MIP) solver to assign drivers (and optional spotters) to race stints while satisfying constraints such as fuel usage, maximum drive times, minimum rest periods, and driver availability.
1616

17-
### Integration (C-API)
17+
### Data Structures
1818

19-
The library exposes a C-compatible API and can be bound to languages like C, C++, Go, Python, or Rust.
19+
The C-API uses the following structs to pass data to and from the solver.
20+
21+
#### Input Structures
22+
23+
`JresSolverInput` is the main input struct. It contains arrays of the other input structs.
24+
25+
| Field | Type | Description |
26+
| :--- | :--- | :--- |
27+
| `teamMembers` | `JresTeamMember*` | A pointer to an array of team members. |
28+
| `teamMembers_len` | `int` | The number of team members. |
29+
| `availability` | `JresMemberAvailability*` | A pointer to an array of availability information. |
30+
| `availability_len`| `int` | The number of members with availability information. |
31+
| `stints` | `JresStint*` | A pointer to an array of stints. |
32+
| `stints_len`| `int` | The number of stints. |
33+
34+
`JresTeamMember`
35+
36+
| Field | Type | Description |
37+
| :--- | :--- | :--- |
38+
| `name` | `const char*` | Unique identifier for the member. |
39+
| `isDriver` | `int` | `1` if the member can drive, `0` otherwise. |
40+
| `isSpotter` | `int` | `1` if the member can spot, `0` otherwise. |
41+
| `preferredStints`| `int` | Soft constraint: solver attempts to limit consecutive stints to this number. |
42+
| `minimumRestHours` | `int` | Hard constraint: Minimum rest time required after a driving shift before driving again. |
43+
44+
`JresStint`
45+
46+
| Field | Type | Description |
47+
| :--- | :--- | :--- |
48+
| `id` | `int` | Unique identifier for the stint. |
49+
| `startTime` | `const char*` | ISO 8601 timestamp for the start of the stint. |
50+
| `endTime` | `const char*` | ISO 8601 timestamp for the end of the stint. |
51+
52+
`JresAvailabilityEntry` & `JresMemberAvailability`
53+
54+
These structs are used to represent the availability of team members.
55+
56+
| Struct | Field | Type | Description |
57+
| :--- | :--- | :--- | :--- |
58+
| `JresAvailabilityEntry` | `time` | `const char*` | An ISO 8601 timestamp for the hour slot. |
59+
| | `availability` | `JresAvailability` | The availability for that hour (`JRES_AVAILABILITY_UNAVAILABLE`, `JRES_AVAILABILITY_AVAILABLE`, `JRES_AVAILABILITY_PREFERRED`). |
60+
| `JresMemberAvailability`| `name` | `const char*` | The name of the team member. |
61+
| | `availability` | `JresAvailabilityEntry*` | A pointer to an array of availability entries. |
62+
| | `availability_len` | `int` | The number of availability entries for this member. |
63+
64+
65+
#### Output Structures
66+
67+
`JresSolverOutput` is the main output struct.
68+
69+
| Field | Type | Description |
70+
| :--- | :--- | :--- |
71+
| `schedule` | `JresScheduleEntry*` | A pointer to an array of schedule entries. |
72+
| `schedule_len` | `int` | The number of schedule entries. |
73+
| `diagnosis` | `const char**` | An array of strings with diagnostic information. Empty on success. |
74+
| `diagnosis_len` | `int` | The number of diagnosis strings. |
75+
76+
`JresScheduleEntry`
77+
78+
| Field | Type | Description |
79+
| :--- | :--- | :--- |
80+
| `stintId` | `int` | The ID of the stint. |
81+
| `driver` | `const char*` | Name of the assigned driver. |
82+
| `spotter` | `const char*` | Name of the assigned spotter. |
2083

2184
#### Basic Usage Example
2285

@@ -29,14 +92,16 @@ options.timeLimit = 5;
2992
options.spotterMode = JRES_SPOTTER_MODE_INTEGRATED;
3093
options.allowNoSpotter = false;
3194
options.optimalityGap = 0.2;
32-
options.quiet = false;
3395

34-
// Solve with race data JSON string
35-
char* resultJson = nullptr;
36-
int status = solve_race_schedule(raceDataJson, options, &resultJson);
96+
// Create input struct from JSON
97+
JresSolverInput* input = jres_input_from_json(raceDataJson);
98+
99+
// Solve the schedule
100+
JresSolverOutput* output = solve_race_schedule(input, &options);
37101

38-
// Always free the result
39-
free_solver_result(resultJson);
102+
// Free the memory
103+
free_jres_solver_input(input);
104+
free_jres_solver_output(output);
40105
```
41106
42107
For a complete working example, see [`cmd/solver/cli.cpp`](./cmd/solver/cli.cpp).
@@ -70,32 +135,44 @@ The solver prioritizes hard constraints (rest times, fuel, availability) first.
70135
71136
-----
72137
138+
### JSON Helper Functions
139+
140+
The library provides helper functions to convert between the C-API structs and JSON strings.
141+
142+
#### `jres_input_from_json(const char* jsonData)`
143+
Parses a JSON string and returns a `JresSolverInput*`. The caller is responsible for freeing the memory using `free_jres_solver_input`.
144+
145+
#### `jres_output_to_json(const JresSolverOutput* output)`
146+
Converts a `JresSolverOutput` struct to a JSON string. The caller is responsible for freeing the memory using `free_json_string`.
147+
73148
### Input JSON Specification
74149
75-
The `raceDataJson` string passed to `solve_race_schedule` must strictly follow this schema.
150+
The `raceDataJson` string passed to `jres_input_from_json` must strictly follow this schema.
76151
77-
#### Root Object (`RaceData`)
152+
#### Root Object
78153
79154
| Field | Type | Required | Description |
80155
| :--- | :--- | :--- | :--- |
81-
| `raceStartUTC` | String | Yes | ISO 8601 timestamp for the start of the race (e.g., `"2023-06-10T14:00:00"`). |
82-
| `durationHours` | Number | Yes | Total length of the race in hours. |
83-
| `avgLapTimeInSeconds`| Number | Yes | Average lap time used to calculate stint duration. |
84-
| `pitTimeInSeconds` | Number | Yes | Time lost during a pit stop. |
85-
| `fuelTankSize` | Number | Yes | Total fuel capacity (units must match `fuelUsePerLap`). |
86-
| `fuelUsePerLap` | Number | Yes | Fuel consumed per lap. |
87156
| `teamMembers` | Array | Yes | List of drivers and spotters (see below). |
88157
| `availability` | Object | Yes | Map of availability constraints (see below). |
89-
| `firstStintDriver` | String | No | Name of the driver forced to take the first stint. |
158+
| `stints` | Array | Yes | List of pre-defined race stints (see below). |
159+
160+
#### Stint Object
161+
162+
| Field | Type | Required | Description |
163+
| :--- | :--- | :--- | :--- |
164+
| `id` | Integer | Yes | Unique identifier for the stint. |
165+
| `startTime` | String | Yes | ISO 8601 timestamp for the start of the stint. |
166+
| `endTime` | String | Yes | ISO 8601 timestamp for the end of the stint. |
90167
91168
#### Team Member Object
92169
93170
| Field | Type | Default | Description |
94171
| :--- | :--- | :--- | :--- |
95172
| `name` | String | **Required** | Unique identifier for the member. |
96-
| `isDriver` | Boolean | `false` | Can this member drive? |
173+
| `isDriver` | Boolean | `true` | Can this member drive? |
97174
| `isSpotter` | Boolean | `false` | Can this member spot? |
98-
| `preferredStints` | Integer| `3` | Soft constraint: solver attempts to limit consecutive stints to this number. |
175+
| `maxStints` | Integer| `1` | Hard constraint: Maximum number of consecutive stints a member can perform. |
99176
| `minimumRestHours` | Integer| `0` | Hard constraint: Minimum rest time required after a driving shift before driving again. |
100177
101178
#### Availability Map & Time Formatting
@@ -106,32 +183,24 @@ The `availability` object maps a **Team Member's Name** to a dictionary of **Tim
106183
107184
* You must provide availability for every hour the race covers.
108185
* The keys must be formatted exactly as: `YYYY-MM-DDTHH:00:00.000Z`.
109-
* If a driver/time pair is missing, the solver assumes the driver is **Available** (Standard) unless specific logic interprets missing keys as unavailable.
110-
* *Note on current implementation:* The solver checks for `"Unavailable"` explicitly. If the key exists and value is `"Unavailable"`, they cannot drive. If the key is `"Preferred"`, the cost is reduced.
186+
* If a driver/time pair is missing, the solver assumes the driver is **Available**.
111187
112188
##### Values:
113189
114190
* `"Preferred"`: The solver is incentivized to schedule the driver here.
115191
* `"Unavailable"`: The driver is strictly forbidden from being scheduled.
116-
* (Missing/Other): The driver is available but not preferred.
192+
* `"Available"`: The driver is available but not preferred.
117193
118194
##### JSON Example
119195
120196
```json
121197
{
122-
"raceStartUTC": "2024-06-15T15:00:00",
123-
"durationHours": 24,
124-
"avgLapTimeInSeconds": 210.5,
125-
"pitTimeInSeconds": 45.0,
126-
"fuelTankSize": 100.0,
127-
"fuelUsePerLap": 3.2,
128-
"firstStintDriver": "Niki",
129198
"teamMembers": [
130199
{
131200
"name": "Niki",
132201
"isDriver": true,
133202
"isSpotter": true,
134-
"preferredStints": 2,
203+
"maxStints": 2,
135204
"minimumRestHours": 4
136205
},
137206
{
@@ -145,57 +214,63 @@ The `availability` object maps a **Team Member's Name** to a dictionary of **Tim
145214
"2024-06-15T18:00:00.000Z": "Unavailable",
146215
"2024-06-15T19:00:00.000Z": "Preferred"
147216
}
148-
}
217+
},
218+
"stints": [
219+
{ "id": 1, "startTime": "2024-06-15T15:00:00Z", "endTime": "2024-06-15T16:00:00Z" },
220+
{ "id": 2, "startTime": "2024-06-15T16:00:00Z", "endTime": "2024-06-15T17:00:00Z" }
221+
]
149222
}
150223
```
151224

152-
-----
153-
154225
### Output JSON Specification
155226

156-
The function returns a JSON string containing the solution or error details.
227+
The `jres_output_to_json` function returns a JSON string containing the solution or error details.
157228

158229
#### Success Response
159230

160231
| Field | Type | Description |
161232
| :--- | :--- | :--- |
162-
| `success` | Boolean | Always `true`. |
163-
| `solveDurationSeconds` | Number | Time taken by the C++ solver to reach the solution. |
164233
| `schedule` | Array | List of optimized stint assignments. |
165-
| `raceData` | Object | Echoes the input configuration for verification. |
234+
| `diagnosis`| Array | List of strings with diagnostic information. Empty on success. |
235+
| `stats` | Object | Solver performance and complexity metrics. |
236+
237+
##### Stats Object
238+
239+
| Field | Type | Description |
240+
| :--- | :--- | :--- |
241+
| `modelColumns`| Integer | The number of columns in the solver model. |
242+
| `modelRows` | Integer | The number of rows in the solver model. |
243+
| `searchNodes` | Integer | The number of nodes explored by the solver. |
244+
| `finalGap` | Number | The final optimality gap of the solution. |
245+
| `setupDurationMs`| Number | The time taken to set up the model in milliseconds. |
246+
| `driverSolveDurationMs` | Number | The time taken to solve for the drivers in milliseconds. |
247+
| `spotterSolveDurationMs` | Number | The time taken to solve for the spotters in milliseconds (sequential mode only). |
166248

167249
##### Schedule Entry Object
168250

169251
| Field | Type | Description |
170252
| :--- | :--- | :--- |
171-
| `stint` | Integer | 1-based stint index. |
253+
| `stintId` | Integer | The ID of the stint. |
172254
| `driver` | String | Name of the assigned driver. |
173255
| `spotter` | String | Name of the assigned spotter (if Spotter Mode is active). |
174256

175257
#### Error Response
176258

177-
| Field | Type | Description |
178-
| :--- | :--- | :--- |
179-
| `success` | Boolean | Always `false`. |
180-
| `error` | String | Description of the failure (e.g., "Infeasible model", "Parse error"). |
181-
| `diagnosis` | Array | (Diagnostic Mode Only) List of strings explaining why the schedule is infeasible. |
259+
When the solver fails, the `schedule` array will be empty, and the `diagnosis` array will contain one or more strings explaining the failure.
182260

183261
##### Example Output
184262

185263
```json
186264
{
187-
"success": true,
188-
"solveDurationSeconds": 0.45,
189265
"schedule": [
190-
{ "stint": 1, "driver": "Niki", "spotter": "Alain" },
191-
{ "stint": 2, "driver": "Niki", "spotter": "Alain" },
192-
{ "stint": 3, "driver": "Alain", "spotter": "Niki" }
266+
{ "stintId": 1, "driver": "Niki", "spotter": "Alain" },
267+
{ "stintId": 2, "driver": "Niki", "spotter": "Alain" },
268+
{ "stintId": 3, "driver": "Alain", "spotter": "Niki" }
193269
],
194-
"raceData": { ... }
270+
"diagnosis": []
195271
}
196272
```
197273

198-
199274
---
200275

201276
_Created by popmonkey, Gemini 2.5, Gemini 3.0, and ChatGPT 5.1_

cmd/formatter/cli.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/**
22
* @author popmonkey+jres@gmail.com
33
* @file cmd/formatter/cli.cpp
4-
* @brief Command-line interface for JRES Schedule Formatter
4+
* @brief Command-line interface for the JRES Schedule Formatter.
55
*/
66
#include <cxxopts.hpp>
77
#include <nlohmann/json.hpp>

0 commit comments

Comments
 (0)