total_angle = sum(angles) fellswoop = initial.rotate(total_angle) note(f"One Fell Swoop: {fellswoop}") stepwise = initial for angle in angles: stepwise = stepwise.rotate(angle) note(f"Step-wise: {stepwise}") # Increase the tolerance on this comparison, # as stepwise rotations induce rounding errors assert fellswoop.isclose(stepwise, rel_tol=1e-6) assert math.isclose(fellswoop.length, initial.length, rel_tol=1e-15) @given(x=vectors(), y=vectors(), scalar=floats(), angle=angles()) # In this example: # * x * l == -y # * Rotation must not be an multiple of 90deg # * Must be sufficiently large @example(x=Vector(1e10, 1e10), y=Vector(1e19, 1e19), scalar=-1e9, angle=45) def test_rotation_linearity(x, y, scalar, angle): """(l*x + y).rotate is equivalent to l*x.rotate + y.rotate""" inner = (scalar * x + y).rotate(angle) outer = scalar * x.rotate(angle) + y.rotate(angle) note(f"scalar * x + y: {scalar * x + y}") note(f"scalar * x.rotate(): {scalar * x.rotate(angle)}") note(f"y.rotate(): {y.rotate(angle)}") note(f"Inner: {inner}") note(f"Outer: {outer}") assert inner.isclose(outer, rel_to=[x, scalar * x, y])
from hypothesis import given from ppb_vector import Vector from utils import floats, vectors @given(x=floats(), y=floats()) def test_class_member_access(x: float, y: float): v = Vector(x, y) assert v.x == x assert v.y == y @given(v=vectors()) def test_index_access(v: Vector): assert v[0] == v.x assert v[1] == v.y @given(v=vectors()) def test_key_access(v: Vector): assert v["x"] == v.x assert v["y"] == v.y
from hypothesis import given from ppb_vector import Vector from utils import floats, vectors @given(v=vectors(), x=floats()) def test_update_x(v: Vector, x: float): assert v.update(x=x) == (x, v.y) @given(v=vectors(), y=floats()) def test_update_y(v: Vector, y: float): assert v.update(y=y) == (v.x, y) @given(v=vectors(), x=floats(), y=floats()) def test_update_xy(v: Vector, x: float, y: float): assert v.update(x=x, y=y) == (x, y)
# Exclude near-orthogonal test inputs assume(abs(cos_t) > 1e-6) assert isclose(x * y, x.rotate(angle) * y.rotate(angle), rel_to=(x, y), rel_exp=2) MAGNITUDE = 1e10 @given( x=vectors(max_magnitude=MAGNITUDE), z=vectors(max_magnitude=MAGNITUDE), y=vectors(max_magnitude=sqrt(MAGNITUDE)), scalar=floats(max_magnitude=sqrt(MAGNITUDE)), ) def test_dot_linear(x: Vector, y: Vector, z: Vector, scalar: float): """Test that x · (λ y + z) = λ x·y + x·z""" inner, outer = x * (scalar * y + z), scalar * x * y + x * z note(f"inner: {inner}") note(f"outer: {outer}") assert isclose(inner, outer, rel_to=(x, scalar, y, z), rel_exp=2) @given(x=vectors(max_magnitude=1e7), y=vectors(max_magnitude=1e7)) def test_dot_from_angle(x: Vector, y: Vector): """Test x · y == |x| · |y| · cos(θ)""" t = x.angle(y) cos_t, _ = Vector._trig(t)
from hypothesis import assume, given, strategies as st from pytest import raises # type: ignore from ppb_vector import Vector from utils import floats, isclose, vectors @given(scalar=floats(), v=vectors()) def test_scalar_coordinates(scalar: float, v: Vector): assert scalar * v.x == (scalar * v).x assert scalar * v.y == (scalar * v).y @given(scalar1=floats(), scalar2=floats(), v=vectors()) def test_scalar_associative(scalar1: float, scalar2: float, v: Vector): """(scalar1 * scalar2) * v == scalar1 * (scalar2 * v)""" left = (scalar1 * scalar2) * v right = scalar1 * (scalar2 * v) assert left.isclose(right) @given(scalar=floats(), v=vectors(), w=vectors()) def test_scalar_linear(scalar: float, v: Vector, w: Vector): assert (scalar * (v + w)).isclose( scalar * v + scalar * w, rel_to=[v, w, scalar * v, scalar * w], ) @given(scalar=floats(), v=vectors()) def test_scalar_length(scalar: float, v: Vector):
@given(left=vectors(), right=vectors()) def test_angle_range(left, right): """Vector.angle produces values in [-180; 180] and is antisymmetric. Antisymmetry means that left.angle(right) == - right.angle(left). """ lr = left.angle(right) rl = right.angle(left) assert -180 < lr <= 180 assert -180 < rl <= 180 assert angle_isclose(lr, -rl) @given(left=vectors(), middle=vectors(), right=vectors()) def test_angle_additive(left, middle, right): """left.angle(middle) + middle.angle(right) == left.angle(right)""" lm = left.angle(middle) mr = middle.angle(right) lr = left.angle(right) assert angle_isclose(lm + mr, lr) @given(x=vectors(), scalar=floats()) def test_angle_aligned(x: Vector, scalar: float): """x.angle(scalar * x) is 0 or 180, depending on whether scalar > 0""" assume(scalar != 0) y = scalar * x assert angle_isclose(x.angle(y), 0 if scalar > 0 else 180)
from ppb_vector import Vector from utils import floats, lengths, vectors @given(x=vectors(), max_length=lengths()) def test_truncate_length(x: Vector, max_length: float): assert x.truncate(max_length).length <= (1 + 1e-14) * max_length @given(x=vectors(), max_length=lengths(max_value=1e150)) def test_truncate_invariant(x: Vector, max_length: float): assume(x.length <= max_length) assert x.truncate(max_length) == x @given(x=vectors(max_magnitude=1e150), max_length=floats()) @example( # Large example where x.length == max_length but 1 * x != x x=Vector(0.0, 7.1e62), max_length=7.1e62, ) def test_truncate_equivalent_to_scale(x: Vector, max_length: float): """Vector.scale_to and truncate are equivalent when max_length <= x.length""" assume(max_length <= x.length) note(f"x.length = {x.length}") if max_length > 0: note(f"x.length = {x.length / max_length} * max_length") scale: Union[Vector, Type[Exception]] truncate: Union[Vector, Type[Exception]] try: