Skip to content

Commit ec8e00e

Browse files
authored
Merge pull request #34 from popmonkey/2.0-fixes
2.0 fixes - balanced schedule and other fixes
2 parents 29f5bed + 789c0e7 commit ec8e00e

28 files changed

Lines changed: 859 additions & 193 deletions

.github/workflows/linux-build.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,11 @@ jobs:
132132
- name: Run Tests
133133
run: ctest --test-dir build --output-on-failure -C Release
134134

135+
- name: Run Integration Tests
136+
run: |
137+
chmod +x test/integration/run_test.sh
138+
./test/integration/run_test.sh
139+
135140
# ---------------------------------------------------------
136141
# Install (Stage Files)
137142
# ---------------------------------------------------------

.github/workflows/macos-build.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,11 @@ jobs:
108108
- name: Run Tests
109109
run: ctest --test-dir build --output-on-failure -C Release
110110

111+
- name: Run Integration Tests
112+
run: |
113+
chmod +x test/integration/run_test.sh
114+
./test/integration/run_test.sh
115+
111116
# ---------------------------------------------------------
112117
# Install (Stage Files)
113118
# ---------------------------------------------------------

.github/workflows/win-build.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,11 @@ jobs:
8686
cmake --build build --config Release --target formatter_tests
8787
ctest --test-dir build --output-on-failure -C Release
8888
89+
- name: Run Integration Tests
90+
if: inputs.run_integration_tests == true
91+
shell: pwsh
92+
run: ./test/integration/run_test.bat
93+
8994
# ---------------------------------------------------------
9095
# Install (Stage Files)
9196
# ---------------------------------------------------------

GEMINI.md

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# Gemini Project: JRES Solver C++
2+
3+
This document provides instructions for understanding, building, and contributing to the JRES Solver C++ project.
4+
5+
## Project Overview
6+
7+
JRES Solver 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 various constraints.
8+
9+
The project is structured as a C-API library (`libjres_solver.a`) with two command-line interface (CLI) clients:
10+
* `jres_solver`: A tool that uses the library to solve race schedules.
11+
* `jres_formatter`: A tool to format the output of the solver.
12+
13+
The library provides helper functions to convert between its C-API data structures and JSON, making it easier to integrate with other systems.
14+
15+
### Public headers
16+
17+
The library exposes its API via headers in `include/jres_solver/`. Designed for seamless Foreign Function Interface (FFI) integration with languages like Python, Ruby, and Go, these headers are strictly:
18+
* **C-Compatible:** All exported symbols utilize extern "C" linkage.
19+
* **Self-Contained:** The interface uses standard C types and opaque handles only, ensuring no dependency on the C++ Standard Library (STL) at the boundary.
20+
* **Standalone:** Each header is fully self-sufficient and requires no prior includes.
21+
22+
## Building and Running
23+
24+
This project uses CMake for building.
25+
26+
### Dependencies
27+
28+
* **CMake (version 3.15+)**
29+
* **Git** (for managing submodules)
30+
* **HiGHS Optimization Solver**: This must be installed on your system.
31+
* **macOS (Homebrew):** `brew install highs`
32+
* **Linux:** Build from source (see `CONTRIBUTING.md`).
33+
* **Windows (vcpkg):** `vcpkg install highs:x64-windows-static`
34+
35+
### Build Steps
36+
37+
1. **Clone the repository and initialize submodules:**
38+
```bash
39+
git clone <repository_url> jres_solver_cpp
40+
cd jres_solver_cpp
41+
git submodule update --init --recursive
42+
```
43+
44+
2. **Configure the project with CMake:**
45+
```bash
46+
mkdir build
47+
cd build
48+
# Add platform-specific flags if needed, e.g., for macOS with Homebrew:
49+
# cmake .. -DCMAKE_PREFIX_PATH=/opt/homebrew
50+
cmake ..
51+
```
52+
53+
3. **Build the project:**
54+
```bash
55+
cmake --build .
56+
```
57+
This will generate the library and executables in the `build/` directory.
58+
59+
### Running Tests
60+
61+
The project uses GoogleTest for its test suite. To run the tests, execute the following command from the `build` directory:
62+
63+
```bash
64+
ctest
65+
```
66+
67+
### Running the CLI Tools
68+
69+
The compiled executables are located in the `build/` directory.
70+
71+
**Solver (`jres_solver`):**
72+
73+
```bash
74+
# Run with an input file
75+
./jres_solver -i ../data/short_race.json -s integrated
76+
77+
# Pipe data from stdin and output to a file
78+
cat ../data/24h_race.json | ./jres_solver -s sequential -o /tmp/24_race_solution.json
79+
80+
# Run diagnostics on an infeasible schedule
81+
./jres_solver -i ../data/short_race_no_solution.json --diagnose
82+
```
83+
84+
**Formatter (`jres_formatter`):**
85+
86+
The formatter takes the JSON output from the solver and can generate different report formats.
87+
88+
```bash
89+
# Load a solution and generate a txt summary
90+
./jres_formatter -i /tmp/24_race_solution.json -o /tmp/summary.txt
91+
```
92+
93+
## Development Conventions
94+
95+
* **Build System:** The project uses CMake for building and configuration.
96+
* **Dependencies:** C++ library dependencies are managed as Git submodules (`cxxopts`, `nlohmann/json`). The HiGHS solver is an external dependency.
97+
* **Testing:** The test suite is built with GoogleTest and run via CTest. New tests should be added to the `test/` directory.
98+
* **API Design:** The core logic is exposed as a C-API for wider compatibility. Helper functions are provided for JSON serialization and deserialization.
99+
* **Code Style:** The codebase is written in C++. Please follow the existing coding style when contributing.
100+
101+
# MODEL INSTRUCTIONS
102+
- **Verbosity:** Low. Do not explain the code unless asked. Just output the diff or the file.
103+
- **Reasoning:** Perform deep reasoning internally, but output only the final solution.
104+
- **Execution:** Don't stage commits or otherwise try to manage the repo.

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,9 @@ The C-API uses the following structs to pass data to and from the solver.
3838
| `name` | `const char*` | Unique identifier for the member. |
3939
| `isDriver` | `int` | `1` if the member can drive, `0` otherwise. |
4040
| `isSpotter` | `int` | `1` if the member can spot, `0` otherwise. |
41-
| `preferredStints`| `int` | Soft constraint: solver attempts to limit consecutive stints to this number. |
41+
| `maxStints`| `int` | Hard constraint: Maximum number of consecutive stints a member can perform. |
4242
| `minimumRestHours` | `int` | Hard constraint: Minimum rest time required after a driving shift before driving again. |
43+
| `tzOffset` | `double` | Timezone offset in hours from UTC. |
4344

4445
`JresStint`
4546

@@ -72,6 +73,8 @@ These structs are used to represent the availability of team members.
7273
| `schedule_len` | `int` | The number of schedule entries. |
7374
| `diagnosis` | `const char**` | An array of strings with diagnostic information. Empty on success. |
7475
| `diagnosis_len` | `int` | The number of diagnosis strings. |
76+
| `teamMembers` | `JresTeamMember*` | A pointer to an array of team members, including their tzOffset. |
77+
| `teamMembers_len` | `int` | The number of team members. |
7578

7679
`JresScheduleEntry`
7780

@@ -174,6 +177,7 @@ The `raceDataJson` string passed to `jres_input_from_json` must strictly follow
174177
| `isSpotter` | Boolean | `false` | Can this member spot? |
175178
| `maxStints` | Integer| `1` | Hard constraint: Maximum number of consecutive stints a member can perform. |
176179
| `minimumRestHours` | Integer| `0` | Hard constraint: Minimum rest time required after a driving shift before driving again. |
180+
| `tzOffset` | Number | `0.0` | Timezone offset in hours from UTC. |
177181
178182
#### Availability Map & Time Formatting
179183
@@ -233,6 +237,7 @@ The `jres_output_to_json` function returns a JSON string containing the solution
233237
| `schedule` | Array | List of optimized stint assignments. |
234238
| `diagnosis`| Array | List of strings with diagnostic information. Empty on success. |
235239
| `stats` | Object | Solver performance and complexity metrics. |
240+
| `teamMembers` | Array | List of team members and their properties. |
236241

237242
##### Stats Object
238243

cmd/formatter/cli.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,8 @@ int main(int argc, char* argv[]) {
106106
return 1;
107107
}
108108

109-
if (!solved_data.contains("schedule") || !solved_data.contains("raceData")) {
110-
std::cerr << "Error: Invalid JSON. Expected keys 'schedule' and 'raceData'." << std::endl;
109+
if (!solved_data.contains("schedule") || !solved_data.contains("teamMembers")) {
110+
std::cerr << "Error: Invalid JSON. Expected keys 'schedule' and 'teamMembers'." << std::endl;
111111
return 1;
112112
}
113113

cmd/solver/cli.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ int main(int argc, char **argv)
173173
if (solverOutput->schedule_len > 0) {
174174
for (int i = 0; i < solverOutput->schedule_len; ++i) {
175175
std::stringstream ss;
176-
ss << "Stint " << std::setw(3) << solverOutput->schedule[i].stintId
176+
ss << "Stint " << std::setw(3) << solverOutput->schedule[i].id
177177
<< ": Driver: " << std::setw(15) << std::left << solverOutput->schedule[i].driver;
178178

179179
if (hasSpotters) {

data/24h_race.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,41 +5,47 @@
55
"name": "Niki",
66
"isDriver": true,
77
"isSpotter": true,
8+
"tzOffset": 1,
89
"maxStints": 1,
910
"minimumRestHours": 8
1011
},
1112
{
1213
"name": "Ayrton",
1314
"isDriver": true,
1415
"isSpotter": false,
16+
"tzOffset": -3,
1517
"maxStints": 2,
1618
"minimumRestHours": 8
1719
},
1820
{
1921
"name": "Jack",
2022
"isDriver": true,
2123
"isSpotter": true,
24+
"tzOffset": 11,
2225
"maxStints": 3,
2326
"minimumRestHours": 8
2427
},
2528
{
2629
"name": "James",
2730
"isDriver": true,
2831
"isSpotter": true,
32+
"tzOffset": 0,
2933
"maxStints": 2,
3034
"minimumRestHours": 8
3135
},
3236
{
3337
"name": "Mario",
3438
"isDriver": true,
3539
"isSpotter": true,
40+
"tzOffset": -5,
3641
"maxStints": 1,
3742
"minimumRestHours": 8
3843
},
3944
{
4045
"name": "Ricky",
4146
"isDriver": false,
4247
"isSpotter": true,
48+
"tzOffset": -6,
4349
"maxStints": 2,
4450
"minimumRestHours": 8
4551
}

data/short_race.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,23 @@
33
"teamMembers": [
44
{
55
"name": "Niki",
6-
"timezone": "1",
6+
"tzOffset": 1,
77
"isDriver": true,
88
"isSpotter": true,
99
"preferredStints": 2,
1010
"minimumRestHours": 8
1111
},
1212
{
1313
"name": "Ayrton",
14-
"timezone": "-3",
14+
"tzOffset": -3,
1515
"isDriver": true,
1616
"isSpotter": true,
1717
"preferredStints": 2,
1818
"minimumRestHours": 8
1919
},
2020
{
2121
"name": "Alain",
22-
"timezone": "1",
22+
"tzOffset": 1,
2323
"isDriver": false,
2424
"isSpotter": true,
2525
"preferredStints": 2,

data/short_race_no_solution.json

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
{
2+
"success": true,
3+
"teamMembers": [
4+
{
5+
"name": "Niki",
6+
"tzOffset": 1,
7+
"isDriver": true,
8+
"isSpotter": true,
9+
"preferredStints": 2,
10+
"minimumRestHours": 8
11+
},
12+
{
13+
"name": "Ayrton",
14+
"tzOffset": -3,
15+
"isDriver": true,
16+
"isSpotter": true,
17+
"preferredStints": 2,
18+
"minimumRestHours": 8
19+
},
20+
{
21+
"name": "Alain",
22+
"tzOffset": 1,
23+
"isDriver": false,
24+
"isSpotter": true,
25+
"preferredStints": 2,
26+
"minimumRestHours": 8
27+
}
28+
],
29+
"availability": {
30+
"Niki": {
31+
"1973-06-09T14:00:00.000Z": "Unavailable",
32+
"1973-06-09T15:00:00.000Z": "Unavailable",
33+
"1973-06-09T16:00:00.000Z": "Available",
34+
"1973-06-09T17:00:00.000Z": "Available"
35+
},
36+
"Ayrton": {
37+
"1973-06-09T14:00:00.000Z": "Available",
38+
"1973-06-09T15:00:00.000Z": "Unavailable",
39+
"1973-06-09T16:00:00.000Z": "Available",
40+
"1973-06-09T17:00:00.000Z": "Available"
41+
},
42+
"Alain": {
43+
"1973-06-09T14:00:00.000Z": "Available",
44+
"1973-06-09T15:00:00.000Z": "Available",
45+
"1973-06-09T16:00:00.000Z": "Available",
46+
"1973-06-09T17:00:00.000Z": "Unavailable"
47+
}
48+
},
49+
"stints": [
50+
{
51+
"id": 1,
52+
"startTime": "1973-06-09T14:37:00.000Z",
53+
"endTime": "1973-06-09T15:27:16.500Z"
54+
},
55+
{
56+
"id": 2,
57+
"startTime": "1973-06-09T15:27:16.500Z",
58+
"endTime": "1973-06-09T16:17:33.000Z"
59+
},
60+
{
61+
"id": 3,
62+
"startTime": "1973-06-09T16:17:33.000Z",
63+
"endTime": "1973-06-09T16:37:00.000Z"
64+
}
65+
],
66+
"firstStintDriver": null
67+
}

0 commit comments

Comments
 (0)