Skip to content

Commit a804b4b

Browse files
authored
Merge pull request #919 from UWB-Biocomputing/SharedDevelopment
Release 1.2 Release for spring 2026. Includes GPU implementation for 911 simulation, CoPilot customizations, etc.
2 parents 6a02426 + 7616b04 commit a804b4b

File tree

85 files changed

+62088
-893
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

85 files changed

+62088
-893
lines changed

.github/copilot-instructions.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# Graphitti Copilot Instructions
2+
3+
## 1. Project Overview
4+
5+
Graphitti is a high-performance graph-based simulator for computational neuroscience and emergency communications research, developed at the University of Washington Bothell. It simulates large-scale graphs (tens of thousands of vertices; millions of edges) over billions of time steps. It runs on both CPUs and GPUs.
6+
7+
Repository: https://github.com/UWB-Biocomputing/Graphitti
8+
9+
## 2. Tech Stack
10+
11+
- **Language:** C++17 (strict)
12+
- **Build System:** CMake
13+
- **Testing:** Google Test (gtest)
14+
- **Logging:** log4cplus
15+
- **GPU:** CUDA (guarded by `USE_GPU` and `ENABLE_CUDA` macros)
16+
- **Parallelism:** CUDA (GPU); OpenMP planned for CPU multi-threading
17+
- **Data Recording:** HDF5 (binary) and XML
18+
- **Config Format:** XML (parsed via `ParameterManager`)
19+
- **OS:** GNU/Linux
20+
21+
## 3. Code Standards
22+
23+
Apply these rules to every code generation and review task.
24+
25+
### Language & Modern C++
26+
27+
- **Standard:** C++17. Do not use features from C++20 or later.
28+
- Avoid manual `delete` and owning raw pointers; prefer RAII and smart pointers (`std::unique_ptr` / `std::shared_ptr`).
29+
- Avoid `printf`; use standard streams or log4cplus.
30+
- Prefer `<algorithm>` when it improves clarity, but traditional loops are acceptable in performance-critical paths.
31+
- Use `[[nodiscard]]` on functions with non-void return values to prevent silent discard of error codes or computed results.
32+
- Use `const` and `constexpr` wherever possible.
33+
- Use `#pragma once` for all headers.
34+
- Use explicit `override` on virtual function overrides.
35+
36+
### Formatting
37+
38+
- **Indentation:** 3 spaces. Not 2, not 4. This is a project-wide convention for codebase consistency.
39+
- **Column Limit:** 100 characters.
40+
- **Naming:**
41+
- `CamelCase` for classes: `Vertex`, `Graph`, `EdgeIndexMap`.
42+
- `camelCase` for variables and functions: `numVertices`, `calculateEdges`.
43+
- No `snake_case`.
44+
- **Braces:**
45+
- Control flow: Cuddled (`} else {`).
46+
- Functions: Opening `{` on a new line.
47+
- Always use braces, even for single-line blocks, to prevent dangling-else bugs when lines are added during maintenance.
48+
49+
## 4. Architecture Map
50+
51+
Understand where code belongs so you can suggest correct file paths and appropriate performance considerations.
52+
53+
- **`Simulator/Core/`** — The simulation hot path. Code here must be highly optimized. `Graphitti_Main.cpp` is the entry point; `Simulator::simulate()` and `Simulator::advanceEpoch()` are the main loop.
54+
- **`Simulator/Edges/`** and **`Simulator/Vertices/`** — Graph element implementations with internal state. Frequently called per time step.
55+
- **`Simulator/Recorders/`** — Data recording subsystem. Supports `XmlRecorder` and `HDF5Recorder`.
56+
- **`Testing/UnitTesting/`** — Google Test unit tests. Must be fast and isolated.
57+
- **`Testing/RegressionTesting/`** — Full simulation runs. Only modify when physics or logic changes.
58+
- **`ThirdParty/`** — External dependencies. **Read-only.** Do not suggest changes here.
59+
60+
## 5. Pull Request Review Priorities
61+
62+
When reviewing PRs or suggesting fixes, check in this order:
63+
64+
1. Flag unnecessary object copying; suggest `const&` or move semantics.
65+
2. Flag expensive allocations or dynamic_cast calls inside simulation loops (`Simulator/Core/`).
66+
3. Identify potential cache misses in hot loops.
67+
4. Check for iterator invalidation and thread-safety in shared data structures (OpenMP/CUDA context).
68+
5. Verify `CMakeLists.txt` is updated if source files were added or removed.
69+
6. Verify all required headers are included.
70+
71+
## 6. Testing Requirements
72+
73+
- **New logic** must have corresponding `TEST()` or `TEST_F()` cases in `Testing/UnitTesting/`.
74+
- **Bug fixes** require a regression test if the bug was a logic error.
75+
- **GPU code** (`.cu` files) must check `ENABLE_CUDA` macros and have CPU-path equivalents tested.
76+
- Test names use `PascalCase`: `TEST(Graph, AddsVertexCorrectly)`.
77+
- Use `EXPECT_*` for non-fatal assertions; use `ASSERT_*` for preconditions where continuing would crash.
78+
79+
## 7. Interaction Behavior
80+
81+
- **On PR review:** Start by identifying the impact area (e.g., "This PR modifies the core simulation loop; checking performance constraints...").
82+
- **On code generation:** Always specify the file path where the generated code should be placed, based on the Architecture Map above.
83+
- **On refactoring:** Preserve existing public API signatures unless the user explicitly requests breaking changes.

.github/prompts/debug.prompt.md

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
---
2+
name: debug-code
3+
description: Trace and explain the root cause of a bug in the C++17 Graphitti code
4+
agent: agent
5+
tools: ["search", "read"]
6+
---
7+
8+
# Inputs and Context
9+
10+
Use the following inputs as the primary source of truth for expected vs. actual behavior. Treat them as ground truth unless you explicitly state otherwise in your assumptions.
11+
12+
- **Problem description from the user (required):**
13+
14+
${input:problemDescription}
15+
16+
- **Target code (optional selection from the editor):**
17+
18+
${selection}
19+
20+
- **Steps to reproduce (optional):**
21+
22+
${input:stepsToReproduce}
23+
24+
- **Logs, stack traces, or failing test output (optional):**
25+
26+
${input:logs}
27+
28+
- **Environment / configuration details (optional):**
29+
30+
${input:environment}
31+
32+
If any of these are missing or incomplete, call that out explicitly in the **Assumptions and Scope** section instead of silently guessing.
33+
34+
# Step 1: Understand the Code and Problem
35+
36+
Before proposing any fix, read and summarize the target code and the reported behavior. Answer these questions internally (do not output them):
37+
38+
1. **What is the primary function, method, or code path under suspicion?** Is this a class (`Graph`, `Vertex`), a free function, or a template?
39+
2. **What is the expected behavior versus the actual behavior?** Include inputs, outputs, and any side effects.
40+
3. **What dependencies does it have?** Other Graphitti classes, standard library containers, external libraries?
41+
4. **What invariants or assumptions are relevant?** (e.g., "vertex count must equal the size of the adjacency list", "counter should increase by exactly 1").
42+
5. **What categories of bug are most likely?** (logic error, off-by-one, stale or shared state, wrong constant, incorrect default, misuse of a dependency, etc.).
43+
44+
Use the **Inputs and Context** section above as your starting point. Do **not** output your answers to these questions directly. Use them only to guide the trace and explanation in later steps.
45+
46+
# Step 2: Trace the Execution Path
47+
48+
Now that you understand the problem, trace how the code executes from the entry point of the bug to the final incorrect result.
49+
50+
Use the `search` and `read` tools to locate and inspect any relevant files, definitions, and call sites in the Graphitti codebase (for example, where a method is defined and where it is called).
51+
52+
Design an internal execution trace that answers:
53+
54+
1. Which public or entry-point function is called when this bug occurs?
55+
2. Which functions, methods, or constructors are invoked along the way (including helpers, virtual overrides, and templates)?
56+
3. How do the key values change at each step (inputs, member variables, return values, accumulators, caches)?
57+
4. Which branches or conditions are taken in the failing scenario?
58+
5. Where is the first point at which the state diverges from what is expected?
59+
60+
Do not output this raw trace verbatim. In the next step, you will convert it into a clear explanation for the user.
61+
62+
When exploring the repository:
63+
64+
- Only reference functions, classes, and files that you have actually located with `search` / `read`.
65+
- If you cannot find a symbol, file, or configuration that seems important, note this as a limitation in **Assumptions and Scope** rather than inventing its behavior.
66+
67+
# Step 3: Explain the Root Cause and Fix
68+
69+
Using the analysis from Step 1 and the trace from Step 2, generate a structured debugging report following these rules:
70+
71+
## Project Conventions
72+
73+
- Organize the answer into the following sections, in this exact order:
74+
1. **Problem Summary** — Restate the bug in 1–3 sentences, including the relevant function(s) and the expected vs. actual behavior.
75+
2. **Assumptions and Scope** — Briefly list any assumptions you are making about inputs, configuration, or environment, including any missing information from the Inputs and Context section.
76+
3. **Execution Trace** — Summarize the key steps in the call chain that lead to the incorrect result, focusing on functions, important state changes, and branches taken.
77+
4. **Root Cause Analysis** — Explain precisely _why_ the bug happens, referencing specific functions, conditions, or state transitions.
78+
5. **Proposed Fix** — Describe a minimal, targeted change that would correct the behavior, including a concrete code snippet or patch-style suggestion when appropriate.
79+
6. **Verification Steps** — Outline how to verify the fix (existing tests to run, new tests to add, and any manual steps).
80+
81+
- In **Execution Trace**, summarize only the essential steps, state changes, and branches relevant to the failing scenario. Avoid line-by-line commentary or speculative paths that are not actually taken when the bug occurs.
82+
- Only reference functions, classes, files, and configuration values that you have actually seen in the repository or in the provided inputs. If something is inferred or assumed, make that explicit in **Assumptions and Scope**.
83+
- If there are multiple plausible root causes or the available information is incomplete, clearly:
84+
- Identify the most likely root cause first.
85+
- List alternative possibilities as secondary hypotheses, along with what extra data would be needed to confirm or reject them.
86+
- Use concise paragraphs and bullet points. Avoid long, unstructured walls of text.
87+
- When referencing functions or methods, include both the name and file path where possible (e.g., ``Counter::increment` in `Simulator/Utils/Counter.cpp``).
88+
89+
## File Placement
90+
91+
- You are **not** required to create or edit files directly in this prompt. Focus on analysis and explanation.
92+
- When you propose code changes, show them as **snippets** the user can apply manually, rather than editing files yourself.
93+
- If you reference tests or new test files, follow the existing project structure:
94+
- Unit tests live under `Testing/UnitTesting/`.
95+
- For functions touched by the fix, you may suggest using the `/generate-unit-tests` prompt on the affected code to create new tests that reproduce the bug and guard against regressions.
96+
97+
## Code Style
98+
99+
- Keep code examples minimal and focused on the bug and its fix.
100+
- Use C++17 features and the project’s conventions when writing example code (e.g., `auto` where appropriate, no `using namespace std;`).
101+
- When showing a fix, include only the smallest necessary surrounding context (e.g., the function signature plus the corrected lines).
102+
- Add brief inline comments explaining _why_ the fix works, especially when adjusting constants, conditions, or state transitions.
103+
104+
## Few-Shot Example
105+
106+
Below is an example of the expected output format. Match this style exactly, adapting it to the actual bug and code under analysis.
107+
108+
**Input:** Problem description — “`Counter::increment()` always returns 2 when it should return 1 after a single call.” The user selects the implementation of `Counter::increment()` and its helper `internalAdd`.
109+
110+
**Output:**
111+
112+
````md
113+
1. Problem Summary
114+
115+
`Counter::increment()` increases the internal counter by 2 instead of 1 on a single call.
116+
Expected: calling `increment()` once from an initial count of 0 should yield 1.
117+
Actual: the count becomes 2.
118+
119+
2. Assumptions and Scope
120+
121+
- We assume `Counter` starts at 0 unless explicitly initialized otherwise.
122+
- We focus on the non-thread-safe, single-threaded usage of `Counter`.
123+
- No external configuration or environment flags modify the increment step.
124+
125+
3. Execution Trace
126+
127+
- Step 1: `Counter::increment()` is called while `count_` is 0 (`Simulator/Utils/Counter.cpp`, ~line 25).
128+
- Step 2: Inside `increment()`, it calls `internalAdd(2)` instead of `internalAdd(1)`.
129+
- Step 3: `internalAdd(2)` updates `count_` from 0 to 2 and returns.
130+
- Step 4: `getCount()` simply returns `count_` without further modification, so the observed result is 2.
131+
132+
4. Root Cause Analysis
133+
134+
The root cause is a hard-coded increment value of 2 inside `Counter::increment()`.
135+
The intended behavior is to increase the count by exactly 1 per call, but the call to `internalAdd(2)` doubles the expected increment.
136+
There are no additional side effects or state mutations that compensate for this, so the incorrect value propagates directly to `getCount()`.
137+
138+
5. Proposed Fix
139+
140+
Change the argument passed to `internalAdd` from 2 to 1:
141+
142+
```cpp
143+
void Counter::increment() {
144+
// Increment by 1 to match the documented behavior.
145+
internalAdd(1);
146+
}
147+
```
148+
149+
This ensures a single call to increment() increases count\_ by exactly 1.
150+
151+
6. Verification Steps
152+
153+
Add or update a unit test that:
154+
155+
- Constructs a Counter at 0.
156+
- Calls increment() once.
157+
- Asserts that getCount() returns 1.
158+
159+
Run the existing unit test suite (for example, ctest or your standard test command) to confirm no unrelated regressions.
160+
161+
Optionally, use the /generate-unit-tests prompt on Counter to generate additional tests that:
162+
163+
- Call increment() twice and confirm the result is 2.
164+
- Interleave increment() and decrement() and ensure the count returns to the original value.
165+
````
166+
167+
Now generate the debugging report for the target problem and selection.
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
---
2+
name: generate-unit-tests
3+
description: Generate comprehensive Google Test cases for C++17 Graphitti code
4+
agent: agent
5+
tools: ["search", "read", "edit"]
6+
---
7+
8+
# Step 1: Understand the Code
9+
10+
Before writing any tests, read and summarize the target code. Answer these questions internally (do not output them):
11+
12+
1. **What is the SUT (System Under Test)?** Is this a class (`Graph`, `Vertex`), a free function, or a template?
13+
2. **What are the public methods and their signatures?** List each method, its parameters, return type, and any preconditions.
14+
3. **What dependencies does it have?** Other Graphitti classes, standard library containers, external libraries?
15+
4. **What invariants does the class maintain?** (e.g., "vertex count must equal the size of the adjacency list")
16+
5. **What can go wrong?** Null pointers, empty containers, out-of-range indices, integer overflow, floating point precision.
17+
18+
Target Code:
19+
${selection}
20+
21+
# Step 2: Design the Test Plan
22+
23+
Now that you understand the code, design 5–7 test scenarios covering these categories. For each scenario, write one sentence describing the test and the expected outcome.
24+
25+
1. **Happy Path** — The standard use case works as expected.
26+
2. **Boundary Values** — Min/max values (0 nodes, max edges, empty containers, single-element collections).
27+
3. **Error Handling** — Verify correct exceptions are thrown or error codes are returned for invalid input.
28+
4. **State Preservation** — After an operation, the object is in a valid and expected state.
29+
5. **Idempotency / Repeated Calls** — Calling a method twice produces consistent results.
30+
6. **Interaction Between Methods** — A sequence of operations (e.g., add then remove) leaves the object in the correct state.
31+
32+
# Step 3: Generate the Test Code
33+
34+
Using the analysis from Step 1 and the plan from Step 2, generate the C++ test code following these rules:
35+
36+
## Project Conventions
37+
38+
- Use `PascalCase` for test names: `TEST(Graph, AddsVertexCorrectly)`.
39+
- Do NOT use `using namespace std;`.
40+
- Use `EXPECT_*` for assertions that should not abort the test. Use `ASSERT_*` for pointer validity or preconditions where continuing would crash.
41+
- Use the AAA pattern: **Arrange** (setup), **Act** (call the method), **Assert** (verify the result). Separate each section with a blank line.
42+
43+
## File Placement
44+
45+
- All tests go into `Testing/UnitTesting/`.
46+
- If a test file already exists (e.g., `Testing/UnitTesting/VertexTests.cpp`), generate **only the new test cases** to append.
47+
- If no test file exists, generate the **entire file** including headers and fixture setup.
48+
49+
## Code Style
50+
51+
- Include necessary headers with relative paths (e.g., `#include "Simulator/Core/Vertex.h"`).
52+
- If the class requires complex setup, create a test fixture: `class VertexTest : public ::testing::Test { ... }`.
53+
- Use C++17 features where appropriate: `auto`, structured bindings, `std::optional`, `constexpr`.
54+
- Add a brief inline comment on each test explaining **why** that specific value or scenario is being tested.
55+
56+
## Few-Shot Example
57+
58+
Below is an example of the expected output format. Match this style exactly.
59+
60+
**Input:** A class `Counter` with methods `increment()`, `decrement()`, and `getCount()`.
61+
62+
**Output:**
63+
64+
```cpp
65+
#include <gtest/gtest.h>
66+
#include "Simulator/Utils/Counter.h"
67+
68+
class CounterTest : public ::testing::Test {
69+
protected:
70+
Counter counter_;
71+
72+
void SetUp() override {
73+
counter_ = Counter();
74+
}
75+
};
76+
77+
// Happy path: incrementing increases the count by 1
78+
TEST_F(CounterTest, IncrementIncreasesCount) {
79+
counter_.increment();
80+
81+
EXPECT_EQ(counter_.getCount(), 1);
82+
}
83+
84+
// Boundary: decrementing from zero should not produce a negative count
85+
TEST_F(CounterTest, DecrementFromZeroDoesNotGoNegative) {
86+
counter_.decrement();
87+
88+
EXPECT_GE(counter_.getCount(), 0);
89+
}
90+
91+
// State preservation: increment then decrement returns to original state
92+
TEST_F(CounterTest, IncrementThenDecrementReturnsToOriginal) {
93+
int original = counter_.getCount();
94+
95+
counter_.increment();
96+
counter_.decrement();
97+
98+
EXPECT_EQ(counter_.getCount(), original);
99+
}
100+
```
101+
102+
Now generate the test code for the target selection.

0 commit comments

Comments
 (0)