Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENH: Proof of Concept: Move NPV to Numba #91

Closed
wants to merge 5 commits into from

Conversation

Kai-Striega
Copy link
Member

@Kai-Striega Kai-Striega commented Nov 30, 2023

This PR moves the Net Present Value function to Numba

It is still a WIP, I'm not sure if the design decisions I've made are correct. Feedback is welcome.

I've tried using numba.guvectorize to make gufuncs out of the NumPy-Financial functions. This didn't work well (because we're not using "proper" gufuncs. Instead I've written them as for loops that are spead up with numba's jit/njit functions.

This leads to some duplication as we need both a jit and njit decorator to support decimal.Decimal (which is a PyObject).

Note that Numba does not support Python3.12 yet, this is expected to be out soon (in the 0.59 release) see the tracking issue for more details

@Kai-Striega Kai-Striega added the enhancement New feature or request label Nov 30, 2023
@Kai-Striega Kai-Striega added this to the 2.0 milestone Nov 30, 2023
@Kai-Striega Kai-Striega self-assigned this Nov 30, 2023
Comment on lines +848 to +861
@nb.jit(forceobj=True) # Need ``forceobj`` to support decimal.Decimal
def _npv_decimal(rates, cashflows, result):
r"""Version of the ``npv`` function supporting ``decimal.Decimal`` types

Warnings
--------
For internal use only, note that this function performs no error checking.
"""
for i in range(rates.shape[0]):
for j in range(cashflows.shape[0]):
acc = Decimal("0.0")
for t in range(cashflows.shape[1]):
acc += cashflows[j, t] / ((Decimal("1.0") + rates[i]) ** t)
result[i, j] = acc
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is essentially a copy of _npv with the constants replaced with decimal.Decimals. I've tried doing something along the lines of dtype = Decimal if arr.dtype = np.dtype("O") else arr.dtype but this wouldn't compile in nopython mode. I'm trying to think of a neater way to implement this but haven't been able to think of one yet

Comment on lines +832 to +836
r"""Native version of the ``npv`` function.

Warnings
--------
For internal use only, note that this function performs no error checking.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documentation still needs to be improved; this is the workhorse powering the npv function.

@@ -825,6 +827,40 @@ def irr(values, *, guess=None, tol=1e-12, maxiter=100, raise_exceptions=False):
return np.nan


@nb.njit(parallel=True)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if it's worth parallelising this function.


r = np.atleast_1d(rate)
v = np.atleast_2d(values)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: Add checking of array shapes if they are compatible.

actual_npvs = npf.npv(0.05, cashflows)
assert_allclose(actual_npvs, expected_npvs)

@pytest.mark.parametrize("dtype", [Decimal, float])
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: check with other dtypes e.g. float32

Comment on lines +203 to +205
for i, r in enumerate(rates):
for j, cf in enumerate(cashflows):
expected[i, j] = npf.npv(r, cf).item()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we're using a for loop to calculate the equivalent to the "broadcasting" operation. This is slow, however I am more confident that it is correct.

@Kai-Striega Kai-Striega changed the title ENH: Move NPV to Numba ENH: Proof of Concept: Move NPV to Numba Dec 1, 2023
@Kai-Striega Kai-Striega closed this Dec 3, 2023
@Kai-Striega
Copy link
Member Author

Kai-Striega commented Dec 3, 2023

Closing as Numba hasn't released Python 3.12 support yet. I'll also be exploring the Cython approach as an alternative approach.

@Kai-Striega Kai-Striega deleted the feature/move-npv-to-numba branch December 13, 2023 03:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant