Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
207ed57
docs(copilot): add python interface plan and task breakdown
zzcgumn Feb 27, 2026
d92b0b6
docs(tasks): complete python interface task 01 api inventory
zzcgumn Feb 27, 2026
f8e153b
build(python): complete task 02 bazel deps and smoke targets
zzcgumn Feb 27, 2026
d019c45
feat(python): complete task 03 package and binding scaffold
zzcgumn Feb 27, 2026
3b854b1
feat(python): complete task 04 mvp wrappers and error mapping
zzcgumn Feb 27, 2026
9ce68ad
feat(python): task 05 bounded array validation and comprehensive docs…
zzcgumn Feb 28, 2026
0b4264a
feat(python): task 06 batch table wrapper calc_all_tables_pbn
zzcgumn Feb 28, 2026
70ae85c
feat(python): task 07 comprehensive unit tests
zzcgumn Feb 28, 2026
e8ee8f9
ignore python virtual environment.
zzcgumn Feb 28, 2026
e5066eb
Fix Python unit tests and update .gitignore
zzcgumn Feb 28, 2026
dc6117e
Task 08: CI Workflow Integration - Add Python interface build and tes…
zzcgumn Feb 28, 2026
9007d25
Task 09: Documentation and Usage Examples
zzcgumn Feb 28, 2026
34a3443
Task 10: Final validation - PR summary and completion evidence
zzcgumn Feb 28, 2026
9507d32
Add completion summary - All 10 tasks finished and validated
zzcgumn Feb 28, 2026
e897557
Fix CI Bazel Python test target
zzcgumn Mar 1, 2026
8c1acc5
Fix Linux build and address PR review feedback
zzcgumn Mar 1, 2026
ecd07c2
Address open test-related review comments
zzcgumn Mar 1, 2026
5dddf69
Fix test to account for conditional par_results in default mode
zzcgumn Mar 1, 2026
0cb0485
Run pytest in CI with Bazel Python toolchain
zzcgumn Mar 1, 2026
c8e40e8
Address PR feedback: API consistency and documentation
zzcgumn Mar 1, 2026
6b6eda3
Update task status to reflect completion
zzcgumn Mar 1, 2026
465ca74
Apply suggestions from code review
zzcgumn Mar 2, 2026
c7de3f2
moves python interface instructions, plan and tasks into their comple…
zzcgumn Mar 2, 2026
7d8ca79
Fix test assertions to match FutureTricks return shape
zzcgumn Mar 2, 2026
8fc3e92
Add input validation to pbn_to_deal and calc_all_tables_pbn
zzcgumn Mar 2, 2026
101a017
creates a python wheel for easy install.
zzcgumn Mar 2, 2026
f2bfd0d
Remove and ignore dump.txt
zzcgumn Mar 2, 2026
9dd0233
Address latest Python API review feedback
zzcgumn Mar 2, 2026
a905bbe
Align Python masks/rank validation with DDS encoding
zzcgumn Mar 2, 2026
b8e45a4
Address five remaining PR review comments
zzcgumn Mar 3, 2026
6c2af5c
Add detailed comments explaining par computation constraints in calc_…
zzcgumn Mar 3, 2026
ed5135e
Address latest PR feedback on Python docs and par validation
zzcgumn Mar 3, 2026
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
20 changes: 18 additions & 2 deletions .github/workflows/ci_linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,26 @@ jobs:
- name: Install Doxygen
run: sudo apt-get update && sudo apt-get install -y doxygen

# 4️⃣ Build everything
# 4️⃣ Build Python extension
- name: Build Python extension
run: bazelisk build //python:_dds3

# 5️⃣ Build everything
- name: Build all targets
run: bazelisk build //...

# 5️⃣ Run all tests
# 6️⃣ Run Python smoke test
- name: Run Python smoke test
run: bazelisk test //python:python_interface_smoke_test

# 7️⃣ Run Python pytest suite
- name: Run Python pytest suite
run: |
bazelisk run @python_3_14//:python3 -- -m pip install --upgrade pip pytest
export DDS_PYTHON_EXT_DIR="$(bazelisk info bazel-bin)/python"
export PYTHONPATH="python:${DDS_PYTHON_EXT_DIR}"
bazelisk run @python_3_14//:python3 -- -m pytest python/tests/ -v

# 8️⃣ Run all tests
- name: Run all tests
run: bazelisk test //...
16 changes: 16 additions & 0 deletions .github/workflows/ci_macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,26 @@ jobs:
- name: Softlink Clang
run: ln -s /opt/homebrew/opt/llvm@20 /opt/homebrew/opt/llvm

# Build Python extension
- name: Build Python extension
run: bazelisk build //python:_dds3

# Build everything
- name: Build all targets
run: bazelisk build //...

# Run Python smoke test
- name: Run Python smoke test
run: bazelisk test //python:python_interface_smoke_test

# Run Python pytest suite
- name: Run Python pytest suite
run: |
bazelisk run @python_3_14//:python3 -- -m pip install --upgrade pip pytest
export DDS_PYTHON_EXT_DIR="$(bazelisk info bazel-bin)/python"
export PYTHONPATH="python:${DDS_PYTHON_EXT_DIR}"
bazelisk run @python_3_14//:python3 -- -m pytest python/tests/ -v

# Run all tests
- name: Run all tests
run: bazelisk test //...
26 changes: 26 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,27 @@

# Bazel Directories
bazel-*

# Python virtual environment
venv/
.venv/
env/
ENV/

# Python bytecode and cache
__pycache__/
*.py[cod]
*$py.class
*.so

# pytest cache
.pytest_cache/

# Python build artifacts
*.egg-info/
dist/
build/

#
# Build artefacts are placed in these directories.
#
Expand All @@ -56,3 +77,8 @@ xcuserdata/
.Rproj.user
.aider*
copilot/perf/artifacts

#
# Debug output from DDS3
#
dump.txt
16 changes: 16 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ module(
bazel_dep(name = "rules_cc", version = "0.2.16")
bazel_dep(name = "platforms", version = "1.0.0")
bazel_dep(name = "googletest", version = "1.17.0.bcr.1")
bazel_dep(name = "pybind11_bazel", version = "3.0.0")
bazel_dep(name = "rules_python", version = "1.7.0")
bazel_dep(name = "hedron_compile_commands", dev_dependency = True)

git_override(
Expand All @@ -22,4 +24,18 @@ git_override(
patch_strip = 1,
)

git_override(
module_name = "pybind11_bazel",
remote = "https://github.com/pybind/pybind11_bazel.git",
commit = "536e9fd8415705fc788658c0e116106c62116c1f", # Bazel 9 compatible
)

python = use_extension("@rules_python//python/extensions:python.bzl", "python")
python.toolchain(
configure_coverage_tool = True,
is_default = True,
python_version = "3.14",
)
use_repo(python, "python_3_14")

register_toolchains("//toolchain:brew_clang_toolchain")
6 changes: 3 additions & 3 deletions MODULE.bazel.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,30 @@ For detailed migration examples and best practices, see:
- **New C++ projects**: Use modern API (`#include <dds/dds.hpp>`)
- **Existing C projects**: Continue with legacy API (no changes required)
- **Migration**: Follow incremental migration guide in docs/api_migration.md
## Python Interface

DDS 3.0 includes a modern Python interface for bridge hand analysis:

**Build the Python extension:**
```bash
bazel build //python:_dds3
```

**Run Python tests:**
```bash
export PYTHONPATH=python:bazel-bin/python
bazel test //python:python_interface_smoke_test
```

**Use in Python:**
```python
from dds3 import solve_board_pbn

pbn = "N:QJ6.K652.J85.T98 873.J97.AT764.Q4 K5.T83.KQ9.A7652 AT942.AQ4.32.KJ3"
result = solve_board_pbn(pbn, trump=1) # Solve in hearts
print(f"Tricks: {result['score']}")
```

**For complete documentation, see:**
- **[Python Interface Guide](docs/python_interface.md)** - Full API reference, examples, and best practices
- **Unit Tests** - See `python/tests/` for usage examples
10 changes: 10 additions & 0 deletions copilot/instructions/completed/dds_python_interface.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Python Interface for Double Dummy Analysis

We want to create a Python interface for the C++ dds3 API. We will use
pybind11 to build the interface.

There should string based as well as functions as well as function accepting enumerated types, and arrays of enumerated types.

https://github.com/zzcgumn/BridgeLibraries has BridgePython library which illustrates how to use pybind11 for bridge concepts. It is also checked out locally to /Users/martinnygren/Source/C++/BridgeLibraries

Standard advice is to test a pybind11 interface from the python side. Unit tests for the interface must be included. They must also be run as part of .github/workflows/
141 changes: 141 additions & 0 deletions copilot/plans/completed/dds_python_interface.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
## Objective
Implement a Python interface for the dds3 C++ API using pybind11, with:
- string-based entry points
- strongly typed enum-based entry points
- APIs accepting arrays/lists of enum values
- Python-side unit tests executed in CI workflows

## Inputs and Constraints
- Primary API source: library/src/dds.hpp (C++ API)
- C++ API components included by dds.hpp: library/src/dds.h and library/src/api/solve_board.hpp
- Build system: Bazel (no Make/CMake)
- Reference implementation: BridgePython in /Users/martinnygren/Source/C++/BridgeLibraries
- Tests must run from Python and be included in .github/workflows

## Proposed Deliverables
1. Python extension module (pybind11) exposing core DDS operations.
2. Typed Python enums for strains/suits/hands/ranks and selected mode fields.
3. String-based convenience wrappers (PBN-first API).
4. Enum/array-based wrappers (typed API).
5. Python test suite validating correctness and conversion behavior.
6. Bazel targets for building the extension and running Python tests.
7. CI workflow updates (Linux + macOS) to run Python interface tests.
8. Usage documentation with examples.

## Suggested Repository Layout
- python/dds3/__init__.py
- python/dds3/_dds3.pyi (optional type stubs)
- python/src/bindings.cpp
- python/src/converters.hpp
- python/src/converters.cpp
- python/tests/test_solve_board.py
- python/tests/test_calc_tables.py
- python/tests/test_par.py
- python/tests/test_type_conversions.py
- python/BUILD.bazel

## Phase Plan

### Phase 1: API Surface Definition
1. Inventory C++ API entry points exposed via dds.hpp first:
- solve_board APIs from api/solve_board.hpp
- related core types from dds.h used by C++ wrappers
- additional batch/table/par/play functions only if available through stable C++ wrappers
2. Define first Python MVP scope (recommended):
- solve_board_pbn(...)
- solve_board(...)
- calc_dd_table(...)
- calc_all_tables_pbn(...)
- par(...)
3. Identify C++ return/input types to bind directly and define Python-facing data classes.

### Phase 2: Build + Dependency Wiring (Bazel)
1. Add pybind11 dependency in MODULE.bazel (Bzlmod).
2. Add Python rules/dependency support if not present (rules_python / pytest integration).
3. Create python/BUILD.bazel with:
- cc_binary/pybind11 extension target
- py_library package target
- py_test targets for interface tests
4. Ensure extension links against //library/src:dds.

### Phase 3: Binding Design
1. Create Python enums:
- Hand (N/E/S/W)
- Suit (S/H/D/C)
- Strain (S/H/D/C/NT)
- Rank (2..A)
- Optional: SolveMode/SolutionMode for mode and solutions fields
2. Expose low-level structs as Python classes/dataclasses-like wrappers where useful.
3. Add conversion helpers:
- Python string (PBN) -> DealPBN / BoardsPBN
- Python typed model -> Deal / Boards
- Python list[enum] -> fixed C arrays with validation
4. Map return/error behavior:
- Exception-based behavior for Python callers while preserving C++ API semantics
- Map solver errors to Python exception hierarchy with code/message payloads

### Phase 4: Implement Wrappers
1. String-based wrappers:
- Accept PBN strings and simple ints/enums
- Validate early and raise descriptive exceptions
2. Enum-based wrappers:
- Accept typed enums and arrays/lists of enums
- Convert to C++ API types and invoke C++ entry points from dds.hpp
3. Batch wrappers:
- Support list input for Boards/BoardsPBN and vectorized solving
4. Result wrappers:
- Convert FutureTricks / SolvedBoards / DdTableResults / ParResults to Python-native structures

### Phase 5: Python Tests (Required)
1. Add deterministic test fixtures from hands/ (e.g., list1.txt/list10.txt).
2. Add coverage for:
- string API happy path + invalid PBN
- enum API happy path + invalid enum/list lengths
- array-of-enum conversion edge cases
- parity checks between string API and enum API outputs
- known regression values for selected boards
3. Keep tests platform-stable (avoid timing-sensitive assertions).

### Phase 6: CI Integration
1. Update .github/workflows/ci_linux.yml:
- install Python test dependencies
- build extension via Bazel
- run py_test targets
2. Update .github/workflows/ci_macos.yml similarly.
3. Ensure Python tests are part of default CI path (same gating level as C++ tests).

### Phase 7: Documentation
1. Add docs/python_interface.md with:
- installation/build instructions
- API overview (string vs enum APIs)
- examples for single-board and batch use
2. Add short section in README.md linking Python interface docs.

## Technical Decisions (Proposed)
1. Error strategy: raise Python exceptions with DDS code/message while calling C++ API wrappers.
2. Keep raw C API exposure out of initial scope; prioritize clean C++-API-based bindings first.
3. Prefer immutable Python result objects for deterministic behavior.
4. Prefer explicit conversion helpers over implicit magic conversions.

## Validation Checklist
- bazel build //...
- bazel test //...
- bazel test //python/tests:all
- Python tests pass on Linux and macOS CI
- Enum/list conversion and string-based wrappers both covered

## Execution Tasks (Actionable)
1. Add Bzlmod deps for pybind11 (+ Python test rules if needed).
2. Create python/BUILD.bazel targets.
3. Implement minimal bindings MVP (solve_board_pbn + calc_dd_table + par).
4. Add enum definitions + converters.
5. Add array-of-enum APIs and tests.
6. Expand wrappers to batch operations.
7. Wire CI workflows.
8. Add docs and finalize.

## Out of Scope for First Iteration
- Full exposure of every legacy C API entry point.
- Re-designing or expanding the public C++ API in dds.hpp.
- Performance micro-optimizations beyond correctness and maintainability.
- Packaging/publishing to PyPI.
Loading
Loading