Skip to content

Commit

Permalink
Implement hash methods in C++ (#610)
Browse files Browse the repository at this point in the history
  • Loading branch information
lballabio committed Jan 29, 2024
2 parents 91c3d72 + 0f5c662 commit 71ed9ee
Show file tree
Hide file tree
Showing 10 changed files with 117 additions and 56 deletions.
12 changes: 12 additions & 0 deletions Python/test/test_calendars.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,18 @@ def test_joint_calendar_holidays(self):

class BespokeCalendarTest(unittest.TestCase):

def test_hash(self):
empty1, empty2 = ql.CalendarVector(2)
for cal1 in (ql.BespokeCalendar("one"), ql.BespokeCalendar("two"), empty1):
for cal2 in (ql.BespokeCalendar("one"), ql.BespokeCalendar("two"), empty2):
if cal1.empty() or cal2.empty():
expected = cal1.empty() == cal2.empty()
else:
expected = cal1.name() == cal2.name()
self.assertEqual(cal1 == cal2, expected)
self.assertEqual(cal1 != cal2, not expected)
self.assertEqual(hash(cal1) == hash(cal2), expected)

def test_reset_added_holidays(self):
calendar = ql.BespokeCalendar("bespoke thing")
test_date = ql.Date(1, ql.January, 2024)
Expand Down
11 changes: 11 additions & 0 deletions Python/test/test_currencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,17 @@ def test_bespoke_currency_constructor(self):
"CCY", "CCY", 100, "#", "", 100, ql.Rounding(), "")
self.assertFalse(custom_ccy.empty(), fail_msg)

def test_hash(self):
for ccy1 in (ql.EURCurrency(), ql.USDCurrency(), ql.Currency()):
for ccy2 in (ql.EURCurrency(), ql.USDCurrency(), ql.Currency()):
if ccy1.empty() or ccy2.empty():
expected = ccy1.empty() == ccy2.empty()
else:
expected = ccy1.name() == ccy2.name()
self.assertEqual(ccy1 == ccy2, expected)
self.assertEqual(ccy1 != ccy2, not expected)
self.assertEqual(hash(ccy1) == hash(ccy2), expected)


if __name__ == '__main__':
print("testing QuantLib", ql.__version__)
Expand Down
18 changes: 18 additions & 0 deletions Python/test/test_date.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ def testArithmetics(self):
mold = m
yold = y

def test_hash(self):
for date1 in (ql.Date(1, 2, 2020), ql.Date(3, 4, 2022), ql.Date()):
for date2 in (ql.Date(1, 2, 2020), ql.Date(3, 4, 2022), ql.Date()):
expected = str(date1) == str(date2)
self.assertEqual(date1 == date2, expected)
self.assertEqual(date1 != date2, not expected)
self.assertEqual(hash(date1) == hash(date2), expected)

def testHolidayList(self):
""" Testing Calendar testHolidayList() method. """
holidayLstFunction = ql.Calendar.holidayList(ql.Poland(), ql.Date(31, 12, 2014), ql.Date(3, 4, 2015), False)
Expand All @@ -78,6 +86,16 @@ def testConversion(self):
ql.Date.from_date("2020-01-02")


class PeriodTest(unittest.TestCase):
def test_hash(self):
for per1 in (ql.Period("1D"), ql.Period("1W"), ql.Period("12M"), ql.Period()):
for per2 in (ql.Period("1D"), ql.Period("1Y"), ql.Period()):
expected = str(per1.normalized()) == str(per2.normalized())
self.assertEqual(per1 == per2, expected)
self.assertEqual(per1 != per2, not expected)
self.assertEqual(hash(per1) == hash(per2), expected)


if __name__ == "__main__":
print("testing QuantLib", ql.__version__)
unittest.main(verbosity=2)
58 changes: 33 additions & 25 deletions Python/test/test_daycounters.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,33 @@
import QuantLib as ql
import unittest


class DayCountersTest(unittest.TestCase):
def test_bus252(self):
"""Test Business252 daycounter"""

calendar = ql.UnitedStates(ql.UnitedStates.GovernmentBond)

#
# Check that SWIG signature for Business252 calendar allows to
# pass custom calendar into the class constructor. Old
# QuantLib-SWIG versions allow only to create Business252
# calendar with default constructor parameter (Brazil
# calendar), and generate an exception when trying to pass a
# custom calendar as a parameter. So we just check here that
# no exception occurs.
#
ql.Business252(calendar)


if __name__ == "__main__":
print("testing QuantLib", ql.__version__)
unittest.main(verbosity=2)
import QuantLib as ql
import unittest


class DayCountersTest(unittest.TestCase):
def test_bus252(self):
"""Test Business252 daycounter"""

calendar = ql.UnitedStates(ql.UnitedStates.GovernmentBond)

#
# Check that SWIG signature for Business252 calendar allows to
# pass custom calendar into the class constructor. Old
# QuantLib-SWIG versions allow only to create Business252
# calendar with default constructor parameter (Brazil
# calendar), and generate an exception when trying to pass a
# custom calendar as a parameter. So we just check here that
# no exception occurs.
#
ql.Business252(calendar)

def test_hash(self):
for dc1 in (ql.Actual360(), ql.Thirty365()):
for dc2 in (ql.Actual360(), ql.Thirty365()):
expected = dc1.name() == dc2.name()
self.assertEqual(dc1 == dc2, expected)
self.assertEqual(dc1 != dc2, not expected)
self.assertEqual(hash(dc1) == hash(dc2), expected)


if __name__ == "__main__":
print("testing QuantLib", ql.__version__)
unittest.main(verbosity=2)
10 changes: 4 additions & 6 deletions SWIG/calendars.i
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ class Calendar {
std::vector<Date> businessDayList(const Date& from,
const Date& to);
std::string name();
bool empty();
%extend {
std::string __str__() {
return self->name()+" calendar";
Expand All @@ -121,14 +122,11 @@ class Calendar {
bool __ne__(const Calendar& other) {
return (*self) != other;
}
hash_t __hash__() {
return self->empty() ? 0 : std::hash<std::string>()(self->name());
}
#endif
}
#if defined(SWIGPYTHON)
%pythoncode %{
def __hash__(self):
return hash(self.name())
%}
#endif
};

namespace std {
Expand Down
8 changes: 8 additions & 0 deletions SWIG/common.i
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,14 @@ a
#endif
%enddef

%inline %{
#if defined(SWIGPYTHON)
// This should be Py_hash_t, but SWIG does not know this type.
typedef long hash_t;
#else
typedef int hash_t;
#endif
%}

%define deprecate_feature(OldName, NewName)
#if defined(SWIGPYTHON)
Expand Down
9 changes: 3 additions & 6 deletions SWIG/currencies.i
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ class Currency {
bool __ne__(const Currency& other) {
return (*self) != other;
}
hash_t __hash__() {
return self->empty() ? 0 : std::hash<std::string>()(self->name());
}
#endif
#if defined(SWIGPYTHON)
Money operator*(Decimal x) {
Expand All @@ -83,12 +86,6 @@ class Currency {
}
#endif
}
#if defined(SWIGPYTHON)
%pythoncode %{
def __hash__(self):
return hash(self.name())
%}
#endif
};


Expand Down
36 changes: 23 additions & 13 deletions SWIG/date.i
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ class Period {
Period __mul__(Integer n) {
return *self * n;
}
#endif
#if defined(SWIGPYTHON)
Period __rmul__(Integer n) {
return *self * n;
Expand All @@ -346,9 +347,20 @@ class Period {
return !(*self < other);
}
#endif
#if defined(SWIGPYTHON) || defined(SWIGR) || defined(SWIGJAVA)
bool __eq__(const Period& other) {
return *self == other;
}
bool __ne__(const Period& other) {
return *self != other;
}
hash_t __hash__() {
size_t seed = 0;
Period p = self->normalized();
boost::hash_combine(seed, p.length());
boost::hash_combine(seed, p.units());
return seed;
}
int __cmp__(const Period& other) {
return *self < other ? -1 :
*self == other ? 0 :
Expand All @@ -364,12 +376,6 @@ class Period {
}
#endif
}
#if defined(SWIGPYTHON)
%pythoncode %{
def __hash__(self):
return hash(str(self.normalized()))
%}
#endif
};

#if defined(SWIGPYTHON)
Expand Down Expand Up @@ -557,6 +563,10 @@ function(from) {Period(from)})
%}

class Date {
#if defined(SWIGJAVA)
%rename("repr") __repr__;
%rename("compare") __cmp__;
#endif
public:
Date();
Date(Day d, Month m, Year y);
Expand Down Expand Up @@ -722,10 +732,16 @@ class Date {
BigInteger operator-(const Date& other) {
return *self - other;
}
#if defined(SWIGPYTHON) || defined(SWIGR)
#if defined(SWIGPYTHON) || defined(SWIGR) || defined(SWIGJAVA)
bool __eq__(const Date& other) {
return *self == other;
}
bool __ne__(const Date& other) {
return *self != other;
}
hash_t __hash__() {
return std::hash<Date>()(*self);
}
int __cmp__(const Date& other) {
if (*self < other)
return -1;
Expand All @@ -742,9 +758,6 @@ class Date {
bool __bool__() {
return (*self != Date());
}
int __hash__() {
return self->serialNumber();
}
bool __lt__(const Date& other) {
return *self < other;
}
Expand All @@ -757,9 +770,6 @@ class Date {
bool __ge__(const Date& other) {
return !(*self < other);
}
bool __ne__(const Date& other) {
return *self != other;
}
PyObject* to_date() {
return PyDate_FromDate(self->year(), self->month(), self->dayOfMonth());
}
Expand Down
10 changes: 4 additions & 6 deletions SWIG/daycounters.i
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class DayCounter {
const Date& startRef = Date(),
const Date& endRef = Date()) const;
std::string name() const;
bool empty();
%extend {
std::string __str__() {
return self->name()+" day counter";
Expand All @@ -53,14 +54,11 @@ class DayCounter {
bool __ne__(const DayCounter& other) {
return (*self) != other;
}
hash_t __hash__() {
return self->empty() ? 0 : std::hash<std::string>()(self->name());
}
#endif
}
#if defined(SWIGPYTHON)
%pythoncode %{
def __hash__(self):
return hash(self.name())
%}
#endif
};

namespace QuantLib {
Expand Down
1 change: 1 addition & 0 deletions SWIG/ql.i
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ GNU autoconf configure script.
%rename(getValue) operator();
%rename(equals) __eq__;
%rename(unEquals) __ne__;
%rename(hashCode) __hash__;
%rename(toString) __str__;
#elif defined(SWIGCSHARP)
%rename(Add) operator+;
Expand Down

0 comments on commit 71ed9ee

Please sign in to comment.