rateslib 3.0.0.dev4__cp311-abi3-win32.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rateslib/__init__.py +258 -0
- rateslib/ad/__init__.py +376 -0
- rateslib/ad/utils.py +22 -0
- rateslib/curves/__init__.py +66 -0
- rateslib/curves/curve_metrics_trait.py +50 -0
- rateslib/curves/user_defined.py +100 -0
- rateslib/default/__init__.py +32 -0
- rateslib/display/__init__.py +18 -0
- rateslib/display/frame.py +163 -0
- rateslib/display/plot.py +43 -0
- rateslib/enums/__init__.py +66 -0
- rateslib/fixings/__init__.py +26 -0
- rateslib/fx/__init__.py +37 -0
- rateslib/fx/fx_rate_metrics_trait.py +50 -0
- rateslib/instruments/__init__.py +61 -0
- rateslib/instruments/ir_linear/__init__.py +39 -0
- rateslib/instruments/ir_multi_ccy/__init__.py +18 -0
- rateslib/instruments/metrics_trait.py +47 -0
- rateslib/instruments/pseudo_and_combination/__init__.py +18 -0
- rateslib/instruments/securities/__init__.py +42 -0
- rateslib/legs/__init__.py +30 -0
- rateslib/licence/__init__.py +14 -0
- rateslib/periods/__init__.py +32 -0
- rateslib/periods/parameters/__init__.py +34 -0
- rateslib/py.typed +0 -0
- rateslib/rs/__init__.pyi +236 -0
- rateslib/rs/ad.pyi +154 -0
- rateslib/rs/curves.pyi +69 -0
- rateslib/rs/defaults.pyi +183 -0
- rateslib/rs/enums.pyi +230 -0
- rateslib/rs/fixings.pyi +99 -0
- rateslib/rs/fx.pyi +82 -0
- rateslib/rs/instruments.pyi +26 -0
- rateslib/rs/legs.pyi +60 -0
- rateslib/rs/licence.pyi +20 -0
- rateslib/rs/periods.pyi +121 -0
- rateslib/rs/scheduling.pyi +293 -0
- rateslib/rs.pyd +0 -0
- rateslib/scheduling/__init__.py +46 -0
- rateslib/scheduling/imm.py +173 -0
- rateslib/serialization/__init__.py +14 -0
- rateslib/solver/__init__.py +14 -0
- rateslib/splines/__init__.py +83 -0
- rateslib/splines/evaluate.py +68 -0
- rateslib/trait_protocols/__init__.py +33 -0
- rateslib/trait_protocols/cashflows_context.py +75 -0
- rateslib/trait_protocols/curves/__init__.py +15 -0
- rateslib/trait_protocols/curves/curve_metrics.py +65 -0
- rateslib/trait_protocols/curves/curve_metrics_mut.py +31 -0
- rateslib/trait_protocols/fx/__init__.py +14 -0
- rateslib/trait_protocols/fx/fx_rate_metrics.py +51 -0
- rateslib/trait_protocols/instruments/__init__.py +26 -0
- rateslib/trait_protocols/instruments/cashflows.py +44 -0
- rateslib/trait_protocols/instruments/local_ccy.py +34 -0
- rateslib/trait_protocols/instruments/metrics.py +39 -0
- rateslib/trait_protocols/instruments/npv.py +62 -0
- rateslib/trait_protocols/instruments/pricing_contexts.py +31 -0
- rateslib/trait_protocols/instruments/rate.py +63 -0
- rateslib/trait_protocols/instruments/solver_sensitivities.py +56 -0
- rateslib/trait_protocols/npv_context.py +162 -0
- rateslib/trait_protocols/utils/__init__.py +17 -0
- rateslib/trait_protocols/utils/homomophic_hash.py +34 -0
- rateslib/trait_protocols/utils/json.py +42 -0
- rateslib/trait_protocols/utils/model_prerequisites.py +46 -0
- rateslib/trait_protocols/utils/name.py +27 -0
- rateslib/trait_protocols/utils/state.py +18 -0
- rateslib/typing_rateslib/__init__.py +34 -0
- rateslib-3.0.0.dev4.dist-info/METADATA +63 -0
- rateslib-3.0.0.dev4.dist-info/RECORD +79 -0
- rateslib-3.0.0.dev4.dist-info/WHEEL +4 -0
- rateslib-3.0.0.dev4.dist-info/licenses/COMMERCIAL_LICENCE +171 -0
- rateslib-3.0.0.dev4.dist-info/licenses/COMMERCIAL_LICENCE_ADDENDUM1 +77 -0
- rateslib-3.0.0.dev4.dist-info/licenses/LICENCE +140 -0
- rateslib-3.0.0.dev4.dist-info/sboms/bridge.cyclonedx.json +6645 -0
- rateslib_json/calendars.json +51683 -0
- rateslib_json/float_rate_series.json +266 -0
- rateslib_json/fx_index.json +202 -0
- rateslib_json/irs_series.json +340 -0
- rateslib_json/specs.json +1462 -0
rateslib/__init__.py
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
# SPDX-License-Identifier: LicenseRef-Rateslib-Dual
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2026 Siffrorna Technology Limited
|
|
4
|
+
#
|
|
5
|
+
# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)
|
|
6
|
+
# Source-available, not open source.
|
|
7
|
+
#
|
|
8
|
+
# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,
|
|
9
|
+
# and/or contact info (at) rateslib (dot) com
|
|
10
|
+
####################################################################################################
|
|
11
|
+
|
|
12
|
+
__docformat__ = "restructuredtext"
|
|
13
|
+
__version__ = "3.0.0.dev4"
|
|
14
|
+
|
|
15
|
+
# Let users know if they're missing any of our hard dependencies
|
|
16
|
+
_hard_dependencies = ("pandas", "matplotlib", "numpy")
|
|
17
|
+
_missing_dependencies: list[str] = []
|
|
18
|
+
|
|
19
|
+
for _dependency in _hard_dependencies:
|
|
20
|
+
try:
|
|
21
|
+
__import__(_dependency)
|
|
22
|
+
except ImportError as _e: # pragma: no cover
|
|
23
|
+
raise ImportError(f"`rateslib` requires installation of {_dependency}: {_e}")
|
|
24
|
+
|
|
25
|
+
## User warnings convert from Rust::log
|
|
26
|
+
import logging
|
|
27
|
+
import warnings
|
|
28
|
+
import rateslib.rs
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class RustLogToUserWarningHandler(logging.Handler):
|
|
32
|
+
def emit(self, record):
|
|
33
|
+
if record.levelno >= logging.WARNING:
|
|
34
|
+
warnings.warn(
|
|
35
|
+
f"[{record.levelname}] {record.getMessage()}",
|
|
36
|
+
category=UserWarning,
|
|
37
|
+
stacklevel=3, # Adjusts the stack trace so it points to the Python user's line
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
logger = logging.getLogger("rateslib")
|
|
42
|
+
logger.addHandler(RustLogToUserWarningHandler())
|
|
43
|
+
logger.setLevel(logging.WARNING)
|
|
44
|
+
logger.propagate = False
|
|
45
|
+
##
|
|
46
|
+
|
|
47
|
+
from datetime import datetime as dt
|
|
48
|
+
|
|
49
|
+
from rateslib.default import *
|
|
50
|
+
|
|
51
|
+
defaults = DefaultManager()
|
|
52
|
+
calendars = CalendarManager()
|
|
53
|
+
specs = SpecManager()
|
|
54
|
+
indexes = IndexManager()
|
|
55
|
+
|
|
56
|
+
from rateslib.licence import *
|
|
57
|
+
|
|
58
|
+
licence = LicenceManager()
|
|
59
|
+
|
|
60
|
+
from rateslib.fixings import *
|
|
61
|
+
|
|
62
|
+
fixings = FixingsManager()
|
|
63
|
+
|
|
64
|
+
from rateslib.ad import *
|
|
65
|
+
from rateslib.enums import *
|
|
66
|
+
from rateslib.display import *
|
|
67
|
+
from rateslib.curves import *
|
|
68
|
+
from rateslib.scheduling import *
|
|
69
|
+
from rateslib.periods import *
|
|
70
|
+
from rateslib.periods.parameters import *
|
|
71
|
+
from rateslib.fx import *
|
|
72
|
+
from rateslib.solver import *
|
|
73
|
+
from rateslib.legs import *
|
|
74
|
+
from rateslib.instruments import *
|
|
75
|
+
from rateslib.trait_protocols import *
|
|
76
|
+
|
|
77
|
+
# fixings.set_data_directory(os.path.dirname(os.path.abspath(__file__)) + "/historical")
|
|
78
|
+
|
|
79
|
+
from contextlib import ContextDecorator
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class default_context(ContextDecorator):
|
|
83
|
+
"""
|
|
84
|
+
Context manager to temporarily set options in the `with` statement context.
|
|
85
|
+
|
|
86
|
+
You need to invoke as ``option_context(key, val, key2, val2, ...)``.
|
|
87
|
+
|
|
88
|
+
Examples
|
|
89
|
+
--------
|
|
90
|
+
>>> with default_context('convention', "act360", 'frequency', 'S'):
|
|
91
|
+
... pass
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
def __init__(self, *args) -> None: # type: ignore[no-untyped-def]
|
|
95
|
+
if len(args) % 2 != 0 or len(args) < 2:
|
|
96
|
+
raise ValueError("Need to invoke as default_context(key, val, key2, val2, ...).")
|
|
97
|
+
|
|
98
|
+
self.ops = list(zip(args[::2], args[1::2], strict=False))
|
|
99
|
+
|
|
100
|
+
def __enter__(self) -> None:
|
|
101
|
+
self.undo = [(key, defaults.get(key)) for key, _ in self.ops]
|
|
102
|
+
|
|
103
|
+
for key, val in self.ops:
|
|
104
|
+
defaults.set(key, val)
|
|
105
|
+
|
|
106
|
+
def __exit__(self, *args) -> None: # type: ignore[no-untyped-def]
|
|
107
|
+
if self.undo:
|
|
108
|
+
for key, val in self.undo:
|
|
109
|
+
defaults.set(key, val)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
__all__ = [
|
|
113
|
+
# globals
|
|
114
|
+
"dt",
|
|
115
|
+
"defaults",
|
|
116
|
+
"calendars",
|
|
117
|
+
"specs",
|
|
118
|
+
"indexes",
|
|
119
|
+
"fixings",
|
|
120
|
+
"licence",
|
|
121
|
+
# ad
|
|
122
|
+
"Dual",
|
|
123
|
+
"Dual2",
|
|
124
|
+
"Variable",
|
|
125
|
+
"ADOrder",
|
|
126
|
+
"gradient",
|
|
127
|
+
# curves
|
|
128
|
+
"CurveMetricsTrait",
|
|
129
|
+
"Curve",
|
|
130
|
+
"CurvesContext",
|
|
131
|
+
"CompositeCurve",
|
|
132
|
+
"CreditImpliedCurve",
|
|
133
|
+
"ProxyCurve",
|
|
134
|
+
"MultiCsaCurve",
|
|
135
|
+
"RolledCurve",
|
|
136
|
+
"TranslatedCurve",
|
|
137
|
+
"TenorsCurve",
|
|
138
|
+
"CurveMeta",
|
|
139
|
+
"CurvesContext",
|
|
140
|
+
"index_left",
|
|
141
|
+
"SmithWilsonCurve",
|
|
142
|
+
"NelsonSiegelCurve",
|
|
143
|
+
"NelsonSiegelSvenssonCurve",
|
|
144
|
+
"UserCurve",
|
|
145
|
+
# display
|
|
146
|
+
"frame",
|
|
147
|
+
"plot",
|
|
148
|
+
# scheduling
|
|
149
|
+
"Schedule",
|
|
150
|
+
"Tenor",
|
|
151
|
+
"Frequency",
|
|
152
|
+
"RollDay",
|
|
153
|
+
"Adjuster",
|
|
154
|
+
"Cal",
|
|
155
|
+
"UnionCal",
|
|
156
|
+
"NamedCal",
|
|
157
|
+
"Imm",
|
|
158
|
+
"Convention",
|
|
159
|
+
"StubInference",
|
|
160
|
+
"next_imm",
|
|
161
|
+
"get_imm",
|
|
162
|
+
"add_tenor",
|
|
163
|
+
"dcf",
|
|
164
|
+
# fx
|
|
165
|
+
"Ccy",
|
|
166
|
+
"FXPair",
|
|
167
|
+
"FXRate",
|
|
168
|
+
"FXRates",
|
|
169
|
+
"FXForwards",
|
|
170
|
+
"FXAggregator",
|
|
171
|
+
# solver
|
|
172
|
+
"Solver",
|
|
173
|
+
# periods
|
|
174
|
+
"Cashflow",
|
|
175
|
+
"MtmCashflow",
|
|
176
|
+
"FixedPeriod",
|
|
177
|
+
"FloatPeriod",
|
|
178
|
+
"ZeroFixedPeriod",
|
|
179
|
+
"ZeroFloatPeriod",
|
|
180
|
+
"CreditPremiumPeriod",
|
|
181
|
+
"CreditProtectionPeriod",
|
|
182
|
+
# periods.parameters
|
|
183
|
+
"SettlementParams",
|
|
184
|
+
"ScheduleParams",
|
|
185
|
+
"IndexParams",
|
|
186
|
+
"NonDeliverableParams",
|
|
187
|
+
"FixedRateParams",
|
|
188
|
+
"CreditParams",
|
|
189
|
+
"FloatRateParams",
|
|
190
|
+
"FixedRateParams",
|
|
191
|
+
"MtmParams",
|
|
192
|
+
# legs
|
|
193
|
+
"FixedLeg",
|
|
194
|
+
"FloatLeg",
|
|
195
|
+
"ZeroFloatLeg",
|
|
196
|
+
"ZeroFixedLeg",
|
|
197
|
+
"CreditPremiumLeg",
|
|
198
|
+
"CreditProtectionLeg",
|
|
199
|
+
"CustomLeg",
|
|
200
|
+
# instruments
|
|
201
|
+
"MetricsTrait",
|
|
202
|
+
"Bill",
|
|
203
|
+
"BondFuture",
|
|
204
|
+
"FixedRateLoan",
|
|
205
|
+
"FixedRateBond",
|
|
206
|
+
"FloatRateLoan",
|
|
207
|
+
"FloatRateNote",
|
|
208
|
+
"Fee",
|
|
209
|
+
"FXForward",
|
|
210
|
+
"FXSwap",
|
|
211
|
+
"NDF",
|
|
212
|
+
"FRA",
|
|
213
|
+
"IRS",
|
|
214
|
+
"SBS",
|
|
215
|
+
"STIRFuture",
|
|
216
|
+
"IIRS",
|
|
217
|
+
"YoYIS",
|
|
218
|
+
"XCS",
|
|
219
|
+
"XCSFixed",
|
|
220
|
+
"XCSFixedFixed",
|
|
221
|
+
"ZCIS",
|
|
222
|
+
"ZCS",
|
|
223
|
+
"CDS",
|
|
224
|
+
"Value",
|
|
225
|
+
"Portfolio",
|
|
226
|
+
"Spread",
|
|
227
|
+
"Fly",
|
|
228
|
+
# user defined object traits
|
|
229
|
+
"CurveMetrics",
|
|
230
|
+
"CurveMetricsMut",
|
|
231
|
+
"Metrics",
|
|
232
|
+
# enums
|
|
233
|
+
"AccrualMethod",
|
|
234
|
+
"Amortization",
|
|
235
|
+
"BillConvention",
|
|
236
|
+
"ConversionFactorConvention",
|
|
237
|
+
"CurveType",
|
|
238
|
+
"FloatFixingMethod",
|
|
239
|
+
"FXDeltaMethod",
|
|
240
|
+
"IndexMethod",
|
|
241
|
+
"IROptionMetric",
|
|
242
|
+
"LegIndexBase",
|
|
243
|
+
"LegMtm",
|
|
244
|
+
"LocalInterpolation",
|
|
245
|
+
"PPSplineEndpoint",
|
|
246
|
+
"RateMetric",
|
|
247
|
+
"SecurityMetric",
|
|
248
|
+
"SolverAlgo",
|
|
249
|
+
"SolverStatus",
|
|
250
|
+
"SpreadCompoundMethod",
|
|
251
|
+
"SwaptionSettlementMethod",
|
|
252
|
+
"Transform",
|
|
253
|
+
"YtmConvention",
|
|
254
|
+
"YtmC",
|
|
255
|
+
"YtmV1",
|
|
256
|
+
"YtmV2",
|
|
257
|
+
"YtmV3",
|
|
258
|
+
]
|
rateslib/ad/__init__.py
ADDED
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
# SPDX-License-Identifier: LicenseRef-Rateslib-Dual
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2026 Siffrorna Technology Limited
|
|
4
|
+
#
|
|
5
|
+
# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)
|
|
6
|
+
# Source-available, not open source.
|
|
7
|
+
#
|
|
8
|
+
# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,
|
|
9
|
+
# and/or contact info (at) rateslib (dot) com
|
|
10
|
+
####################################################################################################
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import math
|
|
15
|
+
from functools import partial
|
|
16
|
+
from statistics import NormalDist
|
|
17
|
+
from typing import TYPE_CHECKING, TypeAlias, Union
|
|
18
|
+
import numpy as np
|
|
19
|
+
|
|
20
|
+
from rateslib.rs import (
|
|
21
|
+
Dual,
|
|
22
|
+
Dual2,
|
|
23
|
+
Variable,
|
|
24
|
+
ADOrder,
|
|
25
|
+
_dsolve1,
|
|
26
|
+
_dsolve2,
|
|
27
|
+
_fdsolve1,
|
|
28
|
+
_fdsolve2,
|
|
29
|
+
set_order,
|
|
30
|
+
ift_1dim,
|
|
31
|
+
newton_1dim,
|
|
32
|
+
newton_ndim,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
PRECISION = 1e-14
|
|
36
|
+
FLOATS = float | np.float16 | np.float32 | np.float64 | np.longdouble
|
|
37
|
+
INTS = int | np.int8 | np.int16 | np.int32 | np.int32 | np.int64
|
|
38
|
+
|
|
39
|
+
Number: TypeAlias = Union[float, Dual, Dual2, Variable]
|
|
40
|
+
|
|
41
|
+
if TYPE_CHECKING:
|
|
42
|
+
from rateslib.typing_rateslib import (
|
|
43
|
+
# Any,
|
|
44
|
+
Arr1dF64,
|
|
45
|
+
Arr1dObj,
|
|
46
|
+
Arr2dF64,
|
|
47
|
+
Arr2dObj,
|
|
48
|
+
# DualTypes,
|
|
49
|
+
# Number,
|
|
50
|
+
# Result,
|
|
51
|
+
Sequence,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def set_order_convert(
|
|
56
|
+
val: Number, order: int, tag: list[str] | None, vars_from: Dual | Dual2 | None = None
|
|
57
|
+
) -> Number:
|
|
58
|
+
"""
|
|
59
|
+
Convert a float, :class:`Dual` or :class:`Dual2` type to a specified alternate type with
|
|
60
|
+
tagged variables.
|
|
61
|
+
|
|
62
|
+
Parameters
|
|
63
|
+
----------
|
|
64
|
+
val : float, Dual, Dual2, Variable
|
|
65
|
+
The value to convert.
|
|
66
|
+
order : int
|
|
67
|
+
The AD order to convert the value to if necessary.
|
|
68
|
+
tag : list of str, optional
|
|
69
|
+
The variable name(s) if upcasting a float to a Dual or Dual2
|
|
70
|
+
vars_from : optional, Dual or Dual2
|
|
71
|
+
A pre-existing Dual of correct order from which the Vars are extracted. Improves efficiency
|
|
72
|
+
when given.
|
|
73
|
+
|
|
74
|
+
Returns
|
|
75
|
+
-------
|
|
76
|
+
float, Dual, Dual2
|
|
77
|
+
|
|
78
|
+
Notes
|
|
79
|
+
-----
|
|
80
|
+
This function is used for AD variable management.
|
|
81
|
+
|
|
82
|
+
``tag`` and ``vars_from`` are only used when floats are upcast and the variables need to be
|
|
83
|
+
specifically define.
|
|
84
|
+
|
|
85
|
+
"""
|
|
86
|
+
if isinstance(val, FLOATS | INTS):
|
|
87
|
+
_ = [] if tag is None else tag
|
|
88
|
+
if order == 0:
|
|
89
|
+
return float(val)
|
|
90
|
+
elif order == 1:
|
|
91
|
+
if vars_from is None:
|
|
92
|
+
return Dual(val, _, [])
|
|
93
|
+
elif isinstance(vars_from, Dual):
|
|
94
|
+
return Dual.vars_from(vars_from, val, _, [])
|
|
95
|
+
else:
|
|
96
|
+
raise TypeError("`vars_from` must be a Dual when converting to ADOrder:1.")
|
|
97
|
+
elif order == 2:
|
|
98
|
+
if vars_from is None:
|
|
99
|
+
return Dual2(val, _, [], [])
|
|
100
|
+
elif isinstance(vars_from, Dual2):
|
|
101
|
+
return Dual2.vars_from(vars_from, val, _, [], [])
|
|
102
|
+
else:
|
|
103
|
+
raise TypeError("`vars_from` must be a Dual2 when converting to ADOrder:2.")
|
|
104
|
+
# else val is Dual or Dual2 so convert directly
|
|
105
|
+
return set_order(val, order)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def gradient(
|
|
109
|
+
dual: Number,
|
|
110
|
+
vars: Sequence[str] | None = None, # noqa: A002
|
|
111
|
+
order: int = 1,
|
|
112
|
+
keep_manifold: bool = False,
|
|
113
|
+
) -> Arr1dF64 | Arr2dF64:
|
|
114
|
+
"""
|
|
115
|
+
Return derivatives of a dual number.
|
|
116
|
+
|
|
117
|
+
Parameters
|
|
118
|
+
----------
|
|
119
|
+
dual : Dual, Dual2, Variable, float
|
|
120
|
+
The dual variable from which to derive derivatives.
|
|
121
|
+
vars : str, tuple, list optional
|
|
122
|
+
Name of the variables which to return gradients for. If not given
|
|
123
|
+
defaults to all vars attributed to the instance.
|
|
124
|
+
order : {1, 2}
|
|
125
|
+
Whether to return the first or second derivative of the dual number.
|
|
126
|
+
Second order will raise if applied to a ``Dual`` and not ``Dual2`` instance.
|
|
127
|
+
keep_manifold : bool
|
|
128
|
+
If ``order`` is 1 and the type is ``Dual2`` one can return a ``Dual2``
|
|
129
|
+
where the ``dual2`` values are converted to ``dual`` values to represent
|
|
130
|
+
a first order manifold of the first derivative (and the ``dual2`` values
|
|
131
|
+
set to zero). Useful for propagation in iterations.
|
|
132
|
+
|
|
133
|
+
Returns
|
|
134
|
+
-------
|
|
135
|
+
float, ndarray, Dual2
|
|
136
|
+
"""
|
|
137
|
+
_validate_keep_manifold(keep_manifold, order, dual)
|
|
138
|
+
|
|
139
|
+
if order == 1:
|
|
140
|
+
if not isinstance(dual, Dual | Dual2 | Variable):
|
|
141
|
+
if isinstance(dual, float | int):
|
|
142
|
+
return np.zeros(shape=(len(vars) if vars is not None else 0,))
|
|
143
|
+
else:
|
|
144
|
+
raise TypeError("Can call `gradient` only on dual-type variables.")
|
|
145
|
+
if isinstance(dual, Variable):
|
|
146
|
+
dual = Dual(dual.real, vars=dual.vars, dual=dual.dual)
|
|
147
|
+
if vars is None and not keep_manifold:
|
|
148
|
+
return dual.dual
|
|
149
|
+
elif vars is not None and not keep_manifold:
|
|
150
|
+
return dual.grad1(vars)
|
|
151
|
+
_ = dual.grad1_manifold(dual.vars if vars is None else vars) # type: ignore[union-attr]
|
|
152
|
+
return np.asarray(_)
|
|
153
|
+
|
|
154
|
+
elif order == 2:
|
|
155
|
+
if not isinstance(dual, Dual | Dual2 | Variable):
|
|
156
|
+
if isinstance(dual, float | int):
|
|
157
|
+
n = len(vars) if vars is not None else 0
|
|
158
|
+
return np.zeros(shape=(n, n))
|
|
159
|
+
else:
|
|
160
|
+
raise TypeError("Can call `gradient` only on dual-type variables.")
|
|
161
|
+
if isinstance(dual, Variable):
|
|
162
|
+
dual = Dual2(dual.real, vars=dual.vars, dual=dual.dual, dual2=[])
|
|
163
|
+
elif isinstance(dual, Dual):
|
|
164
|
+
raise TypeError("Dual type cannot derive second order automatic derivatives.")
|
|
165
|
+
|
|
166
|
+
if vars is None:
|
|
167
|
+
return 2.0 * dual.dual2
|
|
168
|
+
else:
|
|
169
|
+
return dual.grad2(vars)
|
|
170
|
+
else:
|
|
171
|
+
raise ValueError("`order` must be in {1, 2} for gradient calculation.")
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _validate_keep_manifold(keep_manifold: bool, order: int, dual: Number) -> None:
|
|
175
|
+
"""Validate the keep_manifold argument for gradient."""
|
|
176
|
+
if keep_manifold and not isinstance(dual, Dual2):
|
|
177
|
+
if isinstance(dual, Dual):
|
|
178
|
+
raise TypeError("Dual type cannot perform `keep_manifold`.")
|
|
179
|
+
elif isinstance(dual, Variable):
|
|
180
|
+
raise TypeError("Variable type cannot perform `keep_manifold`.")
|
|
181
|
+
else:
|
|
182
|
+
raise TypeError("Float type cannot perform `keep_manifold`.")
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def dual_exp(x: Number) -> Number:
|
|
186
|
+
"""
|
|
187
|
+
Calculate the exponential value of a regular int or float or a dual number.
|
|
188
|
+
|
|
189
|
+
Parameters
|
|
190
|
+
----------
|
|
191
|
+
x : int, float, Dual, Dual2, Variable
|
|
192
|
+
Value to calculate exponent of.
|
|
193
|
+
|
|
194
|
+
Returns
|
|
195
|
+
-------
|
|
196
|
+
float, Dual, Dual2
|
|
197
|
+
"""
|
|
198
|
+
if isinstance(x, Dual | Dual2 | Variable | Variable):
|
|
199
|
+
return x.__exp__()
|
|
200
|
+
return math.exp(x)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def dual_log(x: Number, base: int | None = None) -> Number:
|
|
204
|
+
"""
|
|
205
|
+
Calculate the logarithm of a regular int or float or a dual number.
|
|
206
|
+
|
|
207
|
+
Parameters
|
|
208
|
+
----------
|
|
209
|
+
x : int, float, Dual, Dual2, Variable
|
|
210
|
+
Value to calculate exponent of.
|
|
211
|
+
base : int, float, optional
|
|
212
|
+
Base of the logarithm. Defaults to e to compute natural logarithm
|
|
213
|
+
|
|
214
|
+
Returns
|
|
215
|
+
-------
|
|
216
|
+
float, Dual, Dual2
|
|
217
|
+
"""
|
|
218
|
+
if isinstance(x, Dual | Dual2 | Variable | Variable):
|
|
219
|
+
val = x.__log__()
|
|
220
|
+
if base is None:
|
|
221
|
+
return val
|
|
222
|
+
else:
|
|
223
|
+
return val * (1 / math.log(base))
|
|
224
|
+
elif base is None:
|
|
225
|
+
return math.log(x)
|
|
226
|
+
else:
|
|
227
|
+
return math.log(x, base)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def dual_norm_pdf(x: Number) -> Number:
|
|
231
|
+
"""
|
|
232
|
+
Return the standard normal probability density function.
|
|
233
|
+
|
|
234
|
+
Parameters
|
|
235
|
+
----------
|
|
236
|
+
x : float, Dual, Dual2, Variable
|
|
237
|
+
|
|
238
|
+
Returns
|
|
239
|
+
-------
|
|
240
|
+
float, Dual, Dual2
|
|
241
|
+
"""
|
|
242
|
+
return dual_exp(-0.5 * x**2) / math.sqrt(2.0 * math.pi)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def dual_norm_cdf(x: Number) -> Number:
|
|
246
|
+
"""
|
|
247
|
+
Return the cumulative standard normal distribution for given value.
|
|
248
|
+
|
|
249
|
+
Parameters
|
|
250
|
+
----------
|
|
251
|
+
x : float, Dual, Dual2, Variable
|
|
252
|
+
|
|
253
|
+
Returns
|
|
254
|
+
-------
|
|
255
|
+
float, Dual, Dual2
|
|
256
|
+
"""
|
|
257
|
+
if isinstance(x, Dual | Dual2 | Variable):
|
|
258
|
+
return x.__norm_cdf__()
|
|
259
|
+
else:
|
|
260
|
+
return NormalDist().cdf(x)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def dual_inv_norm_cdf(x: Number) -> Number:
|
|
264
|
+
"""
|
|
265
|
+
Return the inverse cumulative standard normal distribution for given value.
|
|
266
|
+
|
|
267
|
+
Parameters
|
|
268
|
+
----------
|
|
269
|
+
x : float, Dual, Dual2, Variable
|
|
270
|
+
|
|
271
|
+
Returns
|
|
272
|
+
-------
|
|
273
|
+
float, Dual, Dual2
|
|
274
|
+
"""
|
|
275
|
+
if isinstance(x, Dual | Dual2 | Variable):
|
|
276
|
+
return x.__norm_inv_cdf__()
|
|
277
|
+
else:
|
|
278
|
+
return NormalDist().inv_cdf(x)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def dual_solve(
|
|
282
|
+
A: Arr2dObj | Arr2dF64,
|
|
283
|
+
b: Arr1dObj | Arr1dF64,
|
|
284
|
+
allow_lsq: bool = False,
|
|
285
|
+
types: tuple[type[float] | type[Dual] | type[Dual2], type[float] | type[Dual] | type[Dual2]] = (
|
|
286
|
+
Dual,
|
|
287
|
+
Dual,
|
|
288
|
+
),
|
|
289
|
+
) -> Arr1dObj | Arr1dF64:
|
|
290
|
+
"""
|
|
291
|
+
Solve a linear system of equations involving dual number data types.
|
|
292
|
+
|
|
293
|
+
The `x` value is found for the equation :math:`Ax=b`.
|
|
294
|
+
|
|
295
|
+
.. warning::
|
|
296
|
+
|
|
297
|
+
This method has not yet implemented :class:`~rateslib.dual.Variable` types.
|
|
298
|
+
|
|
299
|
+
Parameters
|
|
300
|
+
----------
|
|
301
|
+
A: 2-d array
|
|
302
|
+
Left side matrix of values.
|
|
303
|
+
b: 1-d array
|
|
304
|
+
Right side vector of values.
|
|
305
|
+
allow_lsq: bool
|
|
306
|
+
Whether to allow solutions for non-square `A`, i.e. when `len(b) > len(x)`.
|
|
307
|
+
types: tuple
|
|
308
|
+
Defining the input data type elements of `A` and `b`, e.g. (float, float) or (Dual, Dual).
|
|
309
|
+
|
|
310
|
+
Returns
|
|
311
|
+
-------
|
|
312
|
+
1-d array
|
|
313
|
+
"""
|
|
314
|
+
if types == (float, float):
|
|
315
|
+
# Use basic Numpy LinAlg
|
|
316
|
+
if allow_lsq:
|
|
317
|
+
return np.linalg.lstsq(A, b, rcond=None)[0] # type: ignore[arg-type]
|
|
318
|
+
else:
|
|
319
|
+
return np.linalg.solve(A, b) # type: ignore[arg-type]
|
|
320
|
+
|
|
321
|
+
# Move to Rust implementation
|
|
322
|
+
if types in [(Dual, float), (Dual2, float)]:
|
|
323
|
+
raise TypeError(
|
|
324
|
+
"Not implemented for type crossing. Use (Dual, Dual) or (Dual2, Dual2). It is no less"
|
|
325
|
+
"efficient to preconvert `b` to dual types and then solve.",
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
def order_from_type(t: float | Dual | Dual2):
|
|
329
|
+
if t is float:
|
|
330
|
+
return 0
|
|
331
|
+
elif t is Dual:
|
|
332
|
+
return 1
|
|
333
|
+
else:
|
|
334
|
+
return 2
|
|
335
|
+
|
|
336
|
+
A_ = np.vectorize(
|
|
337
|
+
partial(set_order_convert, tag=[], order=order_from_type(types[0]), vars_from=None)
|
|
338
|
+
)(A)
|
|
339
|
+
b_ = np.vectorize(
|
|
340
|
+
partial(set_order_convert, tag=[], order=order_from_type(types[1]), vars_from=None)
|
|
341
|
+
)(b)
|
|
342
|
+
|
|
343
|
+
a_ = [item for sublist in A_.tolist() for item in sublist] # 1D array of A_
|
|
344
|
+
b_ = b_[:, 0].tolist()
|
|
345
|
+
|
|
346
|
+
if types == (Dual, Dual):
|
|
347
|
+
return np.array(_dsolve1(a_, b_, allow_lsq))[:, None]
|
|
348
|
+
elif types == (Dual2, Dual2):
|
|
349
|
+
return np.array(_dsolve2(a_, b_, allow_lsq))[:, None]
|
|
350
|
+
elif types == (float, Dual):
|
|
351
|
+
return np.array(_fdsolve1(A_, b_, allow_lsq))[:, None]
|
|
352
|
+
elif types == (float, Dual2):
|
|
353
|
+
return np.array(_fdsolve2(A_, b_, allow_lsq))[:, None]
|
|
354
|
+
else:
|
|
355
|
+
raise TypeError(
|
|
356
|
+
"Provided `types` argument are not permitted. Must be a 2-tuple with "
|
|
357
|
+
"elements from {float, Dual, Dual2}"
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
__all__ = [
|
|
362
|
+
"Dual",
|
|
363
|
+
"Dual2",
|
|
364
|
+
"Variable",
|
|
365
|
+
"ADOrder",
|
|
366
|
+
"dual_exp",
|
|
367
|
+
"dual_log",
|
|
368
|
+
"dual_solve",
|
|
369
|
+
"dual_norm_cdf",
|
|
370
|
+
"dual_inv_norm_cdf",
|
|
371
|
+
"gradient",
|
|
372
|
+
"set_order",
|
|
373
|
+
"ift_1dim",
|
|
374
|
+
"newton_1dim",
|
|
375
|
+
"newton_ndim",
|
|
376
|
+
]
|
rateslib/ad/utils.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# SPDX-License-Identifier: LicenseRef-Rateslib-Dual
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2026 Siffrorna Technology Limited
|
|
4
|
+
#
|
|
5
|
+
# Dual-licensed: Free Educational Licence or Paid Commercial Licence (commercial/professional use)
|
|
6
|
+
# Source-available, not open source.
|
|
7
|
+
#
|
|
8
|
+
# See LICENSE and https://rateslib.com/py/en/latest/i_licence.html for details,
|
|
9
|
+
# and/or contact info (at) rateslib (dot) com
|
|
10
|
+
####################################################################################################
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from rateslib.rs import Dual, Dual2, Variable
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _abs_float(val: float | Dual | Dual2 | Variable) -> float:
|
|
18
|
+
"""Overload the abs() builtin to return the abs of the real component only"""
|
|
19
|
+
if isinstance(val, Dual | Dual2 | Variable):
|
|
20
|
+
return abs(val.real)
|
|
21
|
+
else:
|
|
22
|
+
return abs(val)
|