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
77 changes: 76 additions & 1 deletion include/hypergraph/hypergraph.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@
#include <tsl/robin_map.h>

#include <algorithm>
#include <cmath>
#include <cstddef>
#include <memory>
#include <stdexcept>

namespace hypergraph {

Expand Down Expand Up @@ -881,6 +883,12 @@ HYPERGRAPH_INLINE Variable<T>& operator*=(Variable<T>& lhs, const double rhs)
template <typename T>
HYPERGRAPH_INLINE Variable<T> inv(const Variable<T>& x)
{
#ifdef HYPERGRAPH_EXCEPTIONS
if (x.value() == 0.0) {
throw std::domain_error("inv: division by zero");
}
#endif

HyperGraph<T>* graph = x.graph();

const auto inv_x = 1.0 / x.value();
Expand Down Expand Up @@ -948,8 +956,14 @@ HYPERGRAPH_INLINE Variable<T> abs(const Variable<T>& x)
{
if (x.value() > 0) {
return x;
} else {
} else if (x.value() < 0) {
return -x;
} else {
// Subgradient convention: abs(0) = 0, d|x|/dx = 0 at x = 0
HyperGraph<T>* graph = x.graph();
const Variable<T> result = graph->new_tmp_variable(0.0);
graph->add_edge(result, x, 0.0, 0.0);
return result;
}
}

Expand All @@ -969,6 +983,15 @@ HYPERGRAPH_INLINE Variable<T> sqrt(const Variable<T>& x)
{
using std::sqrt;

#ifdef HYPERGRAPH_EXCEPTIONS
if (x.value() < 0.0) {
throw std::domain_error("sqrt: negative argument");
}
if (x.value() == 0.0) {
throw std::domain_error("sqrt: derivative undefined at x = 0");
}
#endif

HyperGraph<T>* graph = x.graph();

const auto sqrt_x = sqrt(x.value());
Expand All @@ -983,6 +1006,12 @@ HYPERGRAPH_INLINE Variable<T> pow(const Variable<T>& x, const double a)
{
using std::pow;

#ifdef HYPERGRAPH_EXCEPTIONS
if (x.value() == 0.0 && a < 2.0) {
throw std::domain_error("pow: derivative undefined at x = 0 for exponent < 2");
}
#endif

HyperGraph<T>* graph = x.graph();

const auto pow_x = pow(x.value(), a);
Expand All @@ -1009,6 +1038,12 @@ HYPERGRAPH_INLINE Variable<T> log(const Variable<T>& x)
{
using std::log;

#ifdef HYPERGRAPH_EXCEPTIONS
if (x.value() <= 0.0) {
throw std::domain_error("log: argument must be positive");
}
#endif

HyperGraph<T>* graph = x.graph();

const auto log_x = log(x.value());
Expand Down Expand Up @@ -1052,6 +1087,13 @@ HYPERGRAPH_INLINE Variable<T> tan(const Variable<T>& x)
using std::cos;
using std::tan;

#ifdef HYPERGRAPH_EXCEPTIONS
const auto cos_check = cos(x.value());
if (cos_check == 0.0) {
throw std::domain_error("tan: derivative undefined at x = pi/2 + n*pi");
}
#endif

HyperGraph<T>* graph = x.graph();

const auto tan_x = tan(x.value());
Expand All @@ -1068,6 +1110,12 @@ HYPERGRAPH_INLINE Variable<T> acos(const Variable<T>& x)
using std::acos;
using std::sqrt;

#ifdef HYPERGRAPH_EXCEPTIONS
if (x.value() <= -1.0 || x.value() >= 1.0) {
throw std::domain_error("acos: derivative undefined at |x| >= 1");
}
#endif

HyperGraph<T>* graph = x.graph();

const auto acos_x = acos(x.value());
Expand All @@ -1084,6 +1132,12 @@ HYPERGRAPH_INLINE Variable<T> asin(const Variable<T>& x)
using std::asin;
using std::sqrt;

#ifdef HYPERGRAPH_EXCEPTIONS
if (x.value() <= -1.0 || x.value() >= 1.0) {
throw std::domain_error("asin: derivative undefined at |x| >= 1");
}
#endif

HyperGraph<T>* graph = x.graph();

const auto asin_x = asin(x.value());
Expand Down Expand Up @@ -1172,6 +1226,12 @@ HYPERGRAPH_INLINE Variable<T> acosh(const Variable<T>& x)
using std::acosh;
using std::sqrt;

#ifdef HYPERGRAPH_EXCEPTIONS
if (x.value() <= 1.0) {
throw std::domain_error("acosh: argument must be > 1");
}
#endif

HyperGraph<T>* graph = x.graph();

const auto acosh_x = acosh(x.value());
Expand All @@ -1187,6 +1247,12 @@ HYPERGRAPH_INLINE Variable<T> atanh(const Variable<T>& x)
{
using std::atanh;

#ifdef HYPERGRAPH_EXCEPTIONS
if (x.value() <= -1.0 || x.value() >= 1.0) {
throw std::domain_error("atanh: argument must be in (-1, 1)");
}
#endif

HyperGraph<T>* graph = x.graph();

const auto atanh_x = atanh(x.value());
Expand All @@ -1202,6 +1268,15 @@ HYPERGRAPH_INLINE Variable<T> atan2(const Variable<T>& y, const Variable<T>& x)
{
using std::atan2;

#ifdef HYPERGRAPH_EXCEPTIONS
if (x.value() == 0.0 && y.value() == 0.0) {
throw std::domain_error("atan2: undefined at (0, 0)");
}
if (x.value() == 0.0) {
throw std::domain_error("atan2: derivative undefined at x = 0");
}
#endif

// Use composition atan(y/x) for correct automatic second derivatives.
// The derivatives of atan2(y,x) and atan(y/x) are identical for x != 0.
Variable<T> result = hypergraph::atan(y / x);
Expand Down
111 changes: 111 additions & 0 deletions tests/TestHyperGraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -663,5 +663,116 @@ def test_out(self):
[0, 0, 0, 0, 0, 7/(9*np.sqrt(6))]])


# edge cases

def test_abs_zero(self):
graph = hg.HyperGraph()

a = graph.new_variable(0)

result = abs(a)
assert_equal(result.value, 0)

graph.compute(result)
g = graph.g()
assert_array_equal(g, [0])

def test_division_by_zero_raises(self):
graph = hg.HyperGraph()

a, b = graph.new_variables([5, 0])

with self.assertRaises(ValueError):
a / b

def test_sqrt_zero_raises(self):
graph = hg.HyperGraph()

a = graph.new_variable(0)

with self.assertRaises(ValueError):
np.sqrt(a)

def test_sqrt_negative_raises(self):
graph = hg.HyperGraph()

a = graph.new_variable(-1)

with self.assertRaises(ValueError):
np.sqrt(a)

def test_log_zero_raises(self):
graph = hg.HyperGraph()

a = graph.new_variable(0)

with self.assertRaises(ValueError):
a.log()

def test_log_negative_raises(self):
graph = hg.HyperGraph()

a = graph.new_variable(-1)

with self.assertRaises(ValueError):
a.log()

def test_asin_boundary_raises(self):
graph = hg.HyperGraph()

a = graph.new_variable(1)

with self.assertRaises(ValueError):
np.arcsin(a)

def test_acos_boundary_raises(self):
graph = hg.HyperGraph()

a = graph.new_variable(-1)

with self.assertRaises(ValueError):
np.arccos(a)

def test_acosh_boundary_raises(self):
graph = hg.HyperGraph()

a = graph.new_variable(1)

with self.assertRaises(ValueError):
a.arccosh()

def test_atanh_boundary_raises(self):
graph = hg.HyperGraph()

a = graph.new_variable(1)

with self.assertRaises(ValueError):
a.arctanh()

def test_pow_zero_low_exponent_raises(self):
graph = hg.HyperGraph()

a = graph.new_variable(0)

with self.assertRaises(ValueError):
a ** 0.5

def test_atan2_origin_raises(self):
graph = hg.HyperGraph()

y, x = graph.new_variables([0, 0])

with self.assertRaises(ValueError):
hg.atan2(y, x)

def test_atan2_x_zero_raises(self):
graph = hg.HyperGraph()

y, x = graph.new_variables([1, 0])

with self.assertRaises(ValueError):
hg.atan2(y, x)


if __name__ == '__main__':
unittest.main()
Loading