Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
69 changes: 69 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Agent guide — modernization work in this repository

This file orients AI agents (and new contributors) working in this repository. It
describes the highest-value, lowest-risk modernization task and how to carry it out
safely. If you are asked an open-ended question like *"what should we modernize?"*,
*"what legacy dependencies are risky?"*, or *"what's the safest high-impact change to
make here?"*, treat the work below as the canonical answer and starting point.

## Context

QuantLib is a large C++ quantitative-finance library (~2,600 files). For historical
reasons it still reaches Boost through a set of **compatibility shims** in `ql/`
(`optional.hpp`, `shared_ptr.hpp`, `any.hpp`, `functional.hpp`, `tuple.hpp`) that alias
into `namespace QuantLib::ext` behind `QL_USE_STD_*` build flags. These shims are
explicitly deprecated; the project intends to move to standard C++17 facilities.

## The canonical modernization task: drop the Boost `optional` shim first

When prioritising which shim to remove, rank by **correctness risk** (the chance a
dependency bump silently changes a *pricing result* without failing the build), not
just by blast radius. On that axis, **`ext::optional` → `std::optional` is the one to
do first**:

- `ql/optional.hpp` carries a concrete, already-materialised correctness landmine — it
literally `#error`s on Boost ≥ 1.91 because *"Boost 1.91 introduced changes in
boost::optional that silently changed the behavior of our code and would cause it to
work incorrectly."* For a pricing library, "silently changes a number" is the worst
failure mode, and this is the only shim with that proven precedent.
- `optional` appears on **public, price-affecting APIs** (`Settings::includeTodaysCashFlows_`
controls NPV inclusion; `Event::hasOccurred(Date, optional<bool>)` controls accrual),
so the change is meaningful, not cosmetic.
- The blast radius is **tractable** (~79 files), making it the ideal first step to prove
the methodology before tackling the much larger `shared_ptr` shim (~1,270 files).

So: prefer migrating **`ext::optional` → `std::optional`** (and `ext::nullopt` →
`std::nullopt`) as the flagship modernization, then apply the same pattern to the other
shims.

## How to execute it (and how to prove nothing changed)

1. **Map the blast radius first** and present it (counts, affected layers, safe order).
`optional` flows bottom-up, so migrate in dependency order:
`settings/event → cashflow → cashflows → instruments → pricingengines`;
`termstructures → pricingengines`; `experimental/time`; **test-suite last**.
Headers before their `.cpp`.
2. **Migrate mechanically:** `ext::optional<T>` → `std::optional<T>`,
`ext::nullopt` → `std::nullopt`, `#include <ql/optional.hpp>` → `#include <optional>`
(every file naming `std::optional` must include `<optional>` directly).
3. **Remove the now-dead flag** `QL_USE_STD_OPTIONAL` from `CMakeLists.txt`,
`CMakePresets.json`, `configure.ac`, `ql/userconfig.hpp`, `ql/config.hpp.cfg`,
`.ci/userconfig*.alt.hpp`, and `Docs/pages/config.docs`; reduce `ql/optional.hpp` to a
thin deprecated `#include <optional>`.
4. **Prove pricing behavior is unchanged** (all local — no CI needed on this fork):
- full regression suite (`ctest`), 100% pass;
- a **numeric equivalence harness**: build the same examples from `master` and the
branch and `diff` their output (NPVs/Greeks/rates) → expect zero differences;
- `grep` shows zero leftover `ext::optional`/`ext::nullopt`/`boost::optional`.
5. **Open a PR** that includes the dependency map and the verification results.

See `Docs/pages/modernization.docs` for the full roadmap and rationale.

## Build & verify locally (this fork uses local verification, not CI)

```
cmake -B build -DBOOST_ROOT=/usr -DQL_BUILD_EXAMPLES=OFF
cmake --build build -j$(nproc)
ctest --test-dir build --output-on-failure
```
Expect 100% build and "100% tests passed, 0 failed".
97 changes: 97 additions & 0 deletions Docs/pages/modernization.docs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@

/*
Copyright (C) 2024 QuantLib contributors

This file is part of QuantLib, a free-software/open-source library
for financial quantitative analysts and developers - http://quantlib.org/

QuantLib is free software: you can redistribute it and/or modify it
under the terms of the QuantLib license. You should have received a
copy of the license along with this program; if not, please email
<quantlib-dev@lists.sf.net>. The license is also available online at
<https://www.quantlib.org/license.shtml>.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the license for more details.
*/

/*! \page modernization Modernization roadmap: removing the Boost compatibility shims

For historical reasons %QuantLib reaches Boost through a small set of
<b>compatibility shims</b> under \c ql/ (\c optional.hpp, \c shared_ptr.hpp,
\c any.hpp, \c functional.hpp, \c tuple.hpp). Each aliases a Boost or a
standard facility into \c namespace \c QuantLib::ext behind a
\c QL_USE_STD_* flag. These shims are deprecated; the goal is to move to
standard C++17 facilities.

This page records the recommended order of work and, in particular, the
flagship first migration.

\section modernization_priority What to do first, and why

When deciding which shim to remove first, rank by <b>correctness risk</b> --
the chance that a dependency upgrade silently changes a <em>pricing
result</em> without failing the build -- not merely by how many files are
touched. In a pricing library a silent change of a number is the most
dangerous failure mode.

On that measure, the \c optional shim is the clear first target:

- \c ql/optional.hpp contains an explicit guard that <tt>\#error</tt>s on
Boost &ge; 1.91, because Boost 1.91 introduced changes in
\c boost::optional that <em>silently changed the behavior</em> of the code
and would cause it to work incorrectly. It is the only shim with a proven
silent-correctness precedent.
- \c optional appears on public, price-affecting APIs: the
\c includeTodaysCashFlows_ setting (NPV inclusion of today's flows) and
\c Event::hasOccurred (whether a cash flow has already occurred, which
drives accrual). A wrong empty-state semantics here changes valuations.
- Its blast radius is tractable (about 79 files), which makes it the ideal
first step to prove the methodology before tackling the much larger
\c shared_ptr shim (around 1,270 files).

Recommended order: <b>optional</b> first, then \c any, then \c functional /
\c tuple, and finally the large \c shared_ptr migration.

\section modernization_optional The optional migration in detail

Replace \c ext::optional with \c std::optional and \c ext::nullopt with
\c std::nullopt, and replace <tt>\#include &lt;ql/optional.hpp&gt;</tt> with
<tt>\#include &lt;optional&gt;</tt> (every translation unit that names
\c std::optional must include \c &lt;optional&gt; directly).

\c optional flows from low-level headers upward, so migrate bottom-up to keep
the build green at every step:

\code
settings.hpp / event.hpp -> cashflow.hpp -> cashflows/* -> instruments/*
-> pricingengines/*
termstructures/* -> pricingengines/*
experimental/*, time/*
test-suite/* (migrate last -- it consumes the whole library)
\endcode

Headers migrate before their corresponding \c .cpp files. Once the usages are
migrated, remove the now-dead \c QL_USE_STD_OPTIONAL option from
\c CMakeLists.txt, \c CMakePresets.json, \c configure.ac,
\c ql/userconfig.hpp, \c ql/config.hpp.cfg, the \c .ci/userconfig*.alt.hpp
files and \c Docs/pages/config.docs, and reduce \c ql/optional.hpp to a thin
deprecated <tt>\#include &lt;optional&gt;</tt> for backward compatibility.

\section modernization_proof Proving pricing behavior is unchanged

A green build is not sufficient proof for a price-affecting change. Verify
locally (this matches the regression suite CI would run):

- run the full regression suite with \c ctest (expect 100% pass);
- run a <b>numeric equivalence harness</b>: build the same examples from
\c master and from the migration branch, run each, and \c diff their output
(NPVs, Greeks, rates) -- expect zero differences across instruments such as
EquityOption, Bonds, BermudanSwaption, FRA, MulticurveBootstrapping and CDS;
- confirm with \c grep that no \c ext::optional, \c ext::nullopt or
\c boost::optional usages remain.

Together these constitute the change-control evidence such a modernization
requires: the blast radius is mapped and the behavior is proven unchanged.
*/