11import math
2+ import warnings
23from decimal import Decimal
34
4- import hypothesis .extra .numpy as npst
5- import hypothesis .strategies as st
6-
75# Don't use 'import numpy as np', to avoid accidentally testing
86# the versions in numpy instead of numpy_financial.
97import numpy
108import pytest
11- from hypothesis import given , settings
9+ from hypothesis import assume , given
1210from numpy .testing import (
1311 assert_ ,
1412 assert_allclose ,
1715)
1816
1917import numpy_financial as npf
20-
21-
22- def float_dtype ():
23- return npst .floating_dtypes (sizes = [32 , 64 ], endianness = "<" )
24-
25-
26- def int_dtype ():
27- return npst .integer_dtypes (sizes = [32 , 64 ], endianness = "<" )
28-
29-
30- def uint_dtype ():
31- return npst .unsigned_integer_dtypes (sizes = [32 , 64 ], endianness = "<" )
32-
33-
34- real_scalar_dtypes = st .one_of (float_dtype (), int_dtype (), uint_dtype ())
35-
36-
37- cashflow_array_strategy = npst .arrays (
38- dtype = real_scalar_dtypes ,
39- shape = npst .array_shapes (min_dims = 1 , max_dims = 2 , min_side = 0 , max_side = 25 ),
40- )
41- cashflow_list_strategy = cashflow_array_strategy .map (lambda x : x .tolist ())
42-
43- cashflow_array_like_strategy = st .one_of (
18+ from numpy_financial .tests .strategies import (
19+ cashflow_array_like_strategy ,
4420 cashflow_array_strategy ,
45- cashflow_list_strategy ,
46- )
47-
48- short_scalar_array_strategy = npst .arrays (
49- dtype = real_scalar_dtypes ,
50- shape = npst .array_shapes (min_dims = 0 , max_dims = 1 , min_side = 0 , max_side = 5 ),
51- )
52-
53-
54- when_strategy = st .sampled_from (
55- ['end' , 'begin' , 'e' , 'b' , 0 , 1 , 'beginning' , 'start' , 'finish' ]
21+ short_nicely_behaved_doubles ,
22+ when_strategy ,
5623)
5724
5825
@@ -285,8 +252,7 @@ def test_npv(self):
285252 rtol = 1e-2 ,
286253 )
287254
288- @given (rates = short_scalar_array_strategy , values = cashflow_array_strategy )
289- @settings (deadline = None )
255+ @given (rates = short_nicely_behaved_doubles , values = cashflow_array_strategy )
290256 def test_fuzz (self , rates , values ):
291257 npf .npv (rates , values )
292258
@@ -393,6 +359,23 @@ def test_mirr(self, values, finance_rate, reinvest_rate, expected):
393359 else :
394360 assert_ (numpy .isnan (result ))
395361
362+ def test_mirr_broadcast (self ):
363+ values = [
364+ [- 4500 , - 800 , 800 , 800 , 600 ],
365+ [- 120000 , 39000 , 30000 , 21000 , 37000 ],
366+ [100 , 200 , - 50 , 300 , - 200 ],
367+ ]
368+ finance_rate = [0.05 , 0.08 , 0.10 ]
369+ reinvestment_rate = [0.08 , 0.10 , 0.12 ]
370+ # Found using Google sheets
371+ expected = numpy .array ([
372+ [- 0.1784449 , - 0.17328716 , - 0.1684366 ],
373+ [0.04627293 , 0.05437856 , 0.06252201 ],
374+ [0.35712458 , 0.40628857 , 0.44435295 ]
375+ ])
376+ actual = npf .mirr (values , finance_rate , reinvestment_rate )
377+ assert_allclose (actual , expected )
378+
396379 def test_mirr_no_real_solution_exception (self ):
397380 # Test that if there is no solution because all the cashflows
398381 # have the same sign, then npf.mirr returns NoRealSolutionException
@@ -402,6 +385,31 @@ def test_mirr_no_real_solution_exception(self):
402385 with pytest .raises (npf .NoRealSolutionError ):
403386 npf .mirr (val , 0.10 , 0.12 , raise_exceptions = True )
404387
388+ @given (
389+ values = cashflow_array_like_strategy ,
390+ finance_rate = short_nicely_behaved_doubles ,
391+ reinvestment_rate = short_nicely_behaved_doubles ,
392+ )
393+ def test_fuzz (self , values , finance_rate , reinvestment_rate ):
394+ assume (finance_rate .size == reinvestment_rate .size )
395+
396+ # NumPy warns us of arithmetic overflow/underflow
397+ # this only occurs when hypothesis generates extremely large values
398+ # that are unlikely to ever occur in the real world.
399+ with warnings .catch_warnings ():
400+ warnings .simplefilter ("ignore" )
401+ npf .mirr (values , finance_rate , reinvestment_rate )
402+
403+ @given (
404+ values = cashflow_array_like_strategy ,
405+ finance_rate = short_nicely_behaved_doubles ,
406+ reinvestment_rate = short_nicely_behaved_doubles ,
407+ )
408+ def test_mismatching_rates_raise (self , values , finance_rate , reinvestment_rate ):
409+ assume (finance_rate .size != reinvestment_rate .size )
410+ with pytest .raises (ValueError ):
411+ npf .mirr (values , finance_rate , reinvestment_rate , raise_exceptions = True )
412+
405413
406414class TestNper :
407415 def test_basic_values (self ):
@@ -432,10 +440,10 @@ def test_broadcast(self):
432440 )
433441
434442 @given (
435- rates = short_scalar_array_strategy ,
436- payments = short_scalar_array_strategy ,
437- present_values = short_scalar_array_strategy ,
438- future_values = short_scalar_array_strategy ,
443+ rates = short_nicely_behaved_doubles ,
444+ payments = short_nicely_behaved_doubles ,
445+ present_values = short_nicely_behaved_doubles ,
446+ future_values = short_nicely_behaved_doubles ,
439447 whens = when_strategy ,
440448 )
441449 def test_fuzz (self , rates , payments , present_values , future_values , whens ):
0 commit comments