|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Build & Test |
| 6 | + |
| 7 | +```bash |
| 8 | +# Build (from repo root) |
| 9 | +mkdir -p build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Debug && make |
| 10 | + |
| 11 | +# Run all tests |
| 12 | +./build/all_tests |
| 13 | + |
| 14 | +# Run tests via CTest |
| 15 | +cd build && ctest --output-on-failure |
| 16 | + |
| 17 | +# Format check (requires clang-format) |
| 18 | +find src include -name '*.c' -o -name '*.h' | xargs clang-format --dry-run -Werror |
| 19 | + |
| 20 | +# Build with sanitizers (for debugging) |
| 21 | +cd build && cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-fsanitize=address,undefined" && make |
| 22 | +``` |
| 23 | + |
| 24 | +There is no way to run a single test in isolation — `all_tests` runs the full suite. To test selectively, temporarily comment out `mu_run_test()` calls in `tests/all_tests.c`. |
| 25 | + |
| 26 | +## Architecture |
| 27 | + |
| 28 | +### Expr Node (Virtual Dispatch via Function Pointers) |
| 29 | + |
| 30 | +Every operation is an `expr` node (`include/expr.h`). Polymorphism is achieved through function pointers set at construction time via `init_expr()`: |
| 31 | + |
| 32 | +- `forward` — evaluate node value from input vector `u` |
| 33 | +- `jacobian_init` / `eval_jacobian` — allocate then fill sparse Jacobian (CSR) |
| 34 | +- `wsum_hess_init` / `eval_wsum_hess` — allocate then fill weighted-sum Hessian (CSR) |
| 35 | +- `is_affine` — used to skip zero Hessian computation |
| 36 | +- `local_jacobian` / `local_wsum_hess` — element-wise ops only: diagonal f'(g(x)) and f''(g(x)) |
| 37 | +- `free_type_data` — cleanup for type-specific allocations (e.g., param_source pointers) |
| 38 | + |
| 39 | +Type-specific data uses C struct inheritance: cast the first field (`expr base`) to a subtype defined in `include/subexpr.h` (e.g., `parameter_expr`, `scalar_mult_expr`, `left_matmul_expr`). |
| 40 | + |
| 41 | +### Operation Categories |
| 42 | + |
| 43 | +| Directory | Description | Chain rule pattern | |
| 44 | +|-----------|-------------|-------------------| |
| 45 | +| `src/affine/` | Linear ops (variable, parameter, add, index, matmul, etc.) | J = structural combination of child Jacobians | |
| 46 | +| `src/elementwise_full_dom/` | Univariate ops (exp, sin, log, power, etc.) | J = diag(f'(g(x))) · J_child; common chain rule in `common.c` | |
| 47 | +| `src/elementwise_restricted_dom/` | Univariate ops with domain restrictions (log, entropy) | Same pattern as full_dom | |
| 48 | +| `src/bivariate_full_dom/` | Two-arg ops (elementwise multiply) | Product rule on two children | |
| 49 | +| `src/bivariate_restricted_dom/` | Two-arg restricted domain (quad_over_lin, rel_entr) | Custom chain rules | |
| 50 | +| `src/other/` | Special ops (prod, quad_form) | Custom implementations | |
| 51 | + |
| 52 | +### Init vs Eval Split |
| 53 | + |
| 54 | +Derivative computation is split into two phases: |
| 55 | +1. **Init** (`jacobian_init`, `wsum_hess_init`) — allocates CSR matrices and sets sparsity patterns. Called once per expression tree. Guarded by `jacobian_init()` / `wsum_hess_init()` wrappers in `expr.c` to handle DAGs where nodes are visited multiple times. |
| 56 | +2. **Eval** (`eval_jacobian`, `eval_wsum_hess`) — fills numerical values into pre-allocated structures. Called each time input changes. |
| 57 | + |
| 58 | +### Elementwise Common Chain Rule |
| 59 | + |
| 60 | +`src/elementwise_full_dom/common.c` and `src/elementwise_restricted_dom/common.c` implement shared chain rule logic for all univariate ops. Each op only provides `local_jacobian` (diagonal of f') and `local_wsum_hess` (diagonal of f''). The common code handles: |
| 61 | +- Variable child: Jacobian is diagonal |
| 62 | +- Composite child: J = diag(f') · J_child; H = J_child^T · diag(w·f'') · J_child + f' · H_child |
| 63 | + |
| 64 | +### Parameter System |
| 65 | + |
| 66 | +Parameters (`src/affine/parameter.c`) unify constants and updatable values: |
| 67 | +- `param_id == PARAM_FIXED` (-1): fixed constant, value set at construction |
| 68 | +- `param_id >= 0`: updatable parameter, indexed into a global theta array |
| 69 | + |
| 70 | +Problem-level API (`src/problem.c`): |
| 71 | +- `problem_register_params()` — register parameter nodes |
| 72 | +- `problem_update_params()` — update all parameter values from theta vector |
| 73 | + |
| 74 | +Operations that use parameters (scalar_mult, vector_mult, left/right_matmul) store a `param_source` pointer to the parameter node and read its value during forward/jacobian/hessian evaluation. |
| 75 | + |
| 76 | +### Workspace (`Expr_Work`) |
| 77 | + |
| 78 | +Each node has a `work` pointer for cached intermediate results: CSC conversion of Jacobian, local Jacobian diagonal, Hessian term1/term2 matrices. Allocated during init, reused across evals. |
| 79 | + |
| 80 | +## Adding a New Operation |
| 81 | + |
| 82 | +1. If needed, add a subtype struct in `include/subexpr.h` |
| 83 | +2. Declare the constructor in the appropriate header (`include/affine.h`, `include/elementwise_full_dom.h`, etc.) |
| 84 | +3. Implement in `src/<category>/<op_name>.c` with static functions for forward, jacobian_init_impl, eval_jacobian, wsum_hess_init_impl, eval_wsum_hess, is_affine |
| 85 | +4. For elementwise univariate ops: only implement `local_jacobian` and `local_wsum_hess`, then use `common_jacobian_init`/`common_eval_jacobian`/etc. from `common.c` |
| 86 | +5. Add tests in `tests/forward_pass/`, `tests/jacobian_tests/`, `tests/wsum_hess/` and register them in `tests/all_tests.c` |
| 87 | +6. Use `check_jacobian()` and `check_wsum_hess()` from `tests/numerical_diff.h` to validate against finite differences |
| 88 | + |
| 89 | +## Code Style |
| 90 | + |
| 91 | +C99. Enforced by `.clang-format`: Allman braces, 4-space indent, 85-column limit, right-aligned pointers. Run `clang-format -i <file>` before committing. |
| 92 | + |
| 93 | +## CI Workflows (`.github/workflows/`) |
| 94 | + |
| 95 | +- `cmake.yml` — build and test on Linux, macOS, Windows |
| 96 | +- `formatting.yml` — clang-format check |
| 97 | +- `sanitizer.yml` — ASan, MSan, UBSan |
| 98 | +- `valgrind.yml` — memory leak detection |
| 99 | +- `release.yml` — release packaging |
| 100 | + |
| 101 | +## Platform Notes |
| 102 | + |
| 103 | +- **macOS**: Links Accelerate for BLAS |
| 104 | +- **Linux**: Links OpenBLAS (`libopenblas-dev`) |
| 105 | +- **Windows**: Links OpenBLAS via vcpkg; no dense matmul in some configurations |
| 106 | + |
| 107 | +## Test Tolerances |
| 108 | + |
| 109 | +`ABS_TOL = 1e-6`, `REL_TOL = 1e-6` (defined in test headers). Numerical differentiation step size: `NUMERICAL_DIFF_DEFAULT_H = 1e-7`. |
0 commit comments