Skip to content

Commit

Permalink
Change error checking function messages to be callables
Browse files Browse the repository at this point in the history
  • Loading branch information
kurtamohler committed Jun 16, 2023
1 parent 9dda08c commit 702a86f
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 143 deletions.
74 changes: 38 additions & 36 deletions spacetime/basic_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,33 +53,33 @@ def boost(vec_st, boost_vel_s, light_speed=1, _old=False):
boost_vel_s = np.asarray([boost_vel_s])

vec_st = np.asarray(vec_st)
check(vec_st.ndim > 0, ValueError,
"expected 'vec_st' to have one or more dimensions, ",
f"but got {vec_st.ndim}")
check(vec_st.ndim > 0, ValueError, lambda: (
"expected 'vec_st' to have one or more dimensions, "
f"but got {vec_st.ndim}"))

# TODO: Need to think more about the logic here. It might be a bit wrong
if vec_st.shape[-1] == 2 and boost_vel_s.shape[-1] > 1:
boost_vel_s = np.expand_dims(boost_vel_s, -1)
else:
check(vec_st.shape[-1] - 1 == boost_vel_s.shape[-1], ValueError,
"expected 'vec_st.shape[-1] - 1 == boost_vel_s.shape[-1]', but ",
f"got '{vec_st.shape[-1]} - 1 != {boost_vel_s.shape[-1]}'")
check(vec_st.shape[-1] - 1 == boost_vel_s.shape[-1], ValueError, lambda: (
"expected 'vec_st.shape[-1] - 1 == boost_vel_s.shape[-1]', but "
f"got '{vec_st.shape[-1]} - 1 != {boost_vel_s.shape[-1]}'"))

frame_speed = np.linalg.norm(boost_vel_s, axis=-1)

# TODO: If boost_vel_s is batched, we should only print out the
# first speed in frame_speed that is greater than light_speed
check((frame_speed < light_speed).all(), ValueError,
"the norm of 'boost_vel_s' must be less than ",
f"'light_speed' ({light_speed}), but got {frame_speed}")
check((frame_speed < light_speed).all(), ValueError, lambda: (
"the norm of 'boost_vel_s' must be less than "
f"'light_speed' ({light_speed}), but got {frame_speed}"))

# TODO: Would batching the speed of light be useful at all? Probably best to
# wait and see before adding batching.
check(light_speed.ndim == 0, ValueError,
"expected 'light_speed' to have 0 dimensions, ",
f"but got {light_speed.ndim}")
check(light_speed > 0, ValueError,
f"expected 'light_speed' to be positive, but got {light_speed}")
check(light_speed.ndim == 0, ValueError, lambda: (
"expected 'light_speed' to have 0 dimensions, "
f"but got {light_speed.ndim}"))
check(light_speed > 0, ValueError, lambda: (
f"expected 'light_speed' to be positive, but got {light_speed}"))

dtype = np.find_common_type([boost_vel_s.dtype, light_speed.dtype], [])

Expand All @@ -92,7 +92,8 @@ def boost(vec_st, boost_vel_s, light_speed=1, _old=False):

# TODO: Need to check up front whether the args can broadcast with each other.

if not _old:
#if not _old:
if True:
# To perform the boost, we construct a boost matrix from the boost
# velocity. Then we can just do a matrix-vector multiplication of the
# boost matrix and the spacetime-vector to get the boosted
Expand Down Expand Up @@ -129,8 +130,8 @@ def boost(vec_st, boost_vel_s, light_speed=1, _old=False):
if frame_speed == 0:
return vec_st
else:
check((frame_speed > 0).all(), ValueError,
f"'boost_vel_s' must be nonzero, but got {boost_vel_s}")
check((frame_speed > 0).all(), ValueError, lambda: (
f"'boost_vel_s' must be nonzero, but got {boost_vel_s}"))

# γ = 1 / √(1 - v ⋅ v / c²)
lorentz_factor = 1 / np.sqrt(1 - np.square(frame_speed / light_speed))
Expand Down Expand Up @@ -213,17 +214,17 @@ def boost_velocity_s(vel_s, boost_vel_s, light_speed=1):

# TODO: If boost_vel_s is batched, we should only print out the
# first speed in frame_speed that is greater than light_speed
check((frame_speed < light_speed).all(), ValueError,
check((frame_speed < light_speed).all(), ValueError, lambda: (
"the norm of 'boost_vel_s' must be less than ",
f"'light_speed' ({light_speed}), but got {frame_speed}")
f"'light_speed' ({light_speed}), but got {frame_speed}"))

# TODO: Would batching the speed of light be useful at all? Probably best to
# wait and see before adding batching.
check(light_speed.ndim == 0, ValueError,
"expected 'light_speed' to have 0 dimensions, ",
f"but got {light_speed.ndim}")
check(light_speed > 0, ValueError,
f"expected 'light_speed' to be positive, but got {light_speed}")
check(light_speed.ndim == 0, ValueError, lambda: (
"expected 'light_speed' to have 0 dimensions, "
f"but got {light_speed.ndim}"))
check(light_speed > 0, ValueError, lambda: (
f"expected 'light_speed' to be positive, but got {light_speed}"))

dtype = np.find_common_type([
boost_vel_s.dtype,
Expand All @@ -232,9 +233,9 @@ def boost_velocity_s(vel_s, boost_vel_s, light_speed=1):
], [])

speed = np.linalg.norm(vel_s, axis=-1)
check((speed <= light_speed).all(), ValueError,
"the norm of 'vel_s' must be less than or equal to ",
f"'light_speed' ({light_speed}), but got {speed}")
check((speed <= light_speed).all(), ValueError, lambda: (
"the norm of 'vel_s' must be less than or equal to "
f"'light_speed' ({light_speed}), but got {speed}"))

# Change dtypes to match each other
boost_vel_s = boost_vel_s.astype(dtype)
Expand All @@ -249,8 +250,8 @@ def boost_velocity_s(vel_s, boost_vel_s, light_speed=1):
else:
# TODO: This case should be supported, but will require a condition
# below to prevent the division by zero
check((frame_speed > 0).all(), ValueError,
f"'boost_vel_s' must be nonzero, but got {boost_vel_s}")
check((frame_speed > 0).all(), ValueError, lambda: (
f"'boost_vel_s' must be nonzero, but got {boost_vel_s}"))

# γ = 1 / √(1 - v ⋅ v / c²)
lorentz_factor = 1 / np.sqrt(1 - np.square(frame_speed / light_speed))
Expand Down Expand Up @@ -304,14 +305,15 @@ def proper_time_delta(event0, event1):
event1 = np.array(event1)

check(event0.shape[-1] >= 2 and event0.shape[-1] == event1.shape[-1], ValueError,
"expected events to have same number of spacetime dims, and to be at "
f"least 2, but got event0: {event0.shape[0]}, event1: {event1.shape[0]}")
lambda: (
"expected events to have same number of spacetime dims, and to be at "
f"least 2, but got event0: {event0.shape[0]}, event1: {event1.shape[0]}"))

diff = event1 - event0
norm2 = -norm2_st(diff)

check((norm2 >= 0).all(), ValueError,
"expected events to have time-like interval, but got space-like")
check((norm2 >= 0).all(), ValueError, lambda: (
"expected events to have time-like interval, but got space-like"))
norm = np.sqrt(norm2)

# Preserve the sign from time coordinate difference
Expand Down Expand Up @@ -401,9 +403,9 @@ def velocity_st(vel_s, light_speed=1):
vel_s = np.array([vel_s])

speed = np.linalg.norm(vel_s, axis=-1)
check((speed < light_speed).all(), ValueError,
"the norm of 'vel_s' must be less than ",
f"'light_speed' ({light_speed}), but got {speed}")
check((speed < light_speed).all(), ValueError, lambda: (
"the norm of 'vel_s' must be less than "
f"'light_speed' ({light_speed}), but got {speed}"))

shape = list(vel_s.shape)
shape[-1] += 1
Expand Down
26 changes: 13 additions & 13 deletions spacetime/error_checking.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,12 @@
# error_type (type):
# The type of error to raise.
#
# message (list of strings):
# message (callable):
# The message for the error.
# Variable arg list of objects that have `__str__` methods.
def check(condition, error_type, *message):
internal_assert(len(message), '`message` cannot be empty')
def check(condition, error_type, message):
assert callable(message), '`message` must be callable'
if not condition:
message_joined = ''.join([str(m) for m in message])
raise error_type(message_joined)
raise error_type(message())

# Check if the object has the expected type and raise an error if not
#
Expand All @@ -30,8 +28,8 @@ def check(condition, error_type, *message):
# obj_name (str):
# The name of the object, included in error message.
def check_type(obj, expected_type, obj_name):
check(isinstance(obj, expected_type), TypeError,
f"expected '{obj_name}' to be type {expected_type}, but got {type(obj)}")
check(isinstance(obj, expected_type), TypeError, lambda: (
f"expected '{obj_name}' to be type {expected_type}, but got {type(obj)}"))

# Check a condition and raise an internal assert if it is False
#
Expand All @@ -42,12 +40,14 @@ def check_type(obj, expected_type, obj_name):
# message (list of strings):
# The message for the error.
# Variable arg list of objects that have `__str__` methods.
def internal_assert(condition, *message):
def internal_assert(condition, message=None):
if message is not None:
assert callable(message), '`message` must be callable'
if not condition:
message_final = 'Internal assert failed'

if len(message):
message_final += ': ' + ''.join([str(m) for m in message]) if len(message) else ''
if message is not None:
message_final += ': ' + message()
raise AssertionError(
f"{message_final}\n"
"Please report an issue with the error message and traceback here: "
Expand All @@ -68,7 +68,7 @@ def maybe_wrap_index(idx, length):
else:
idx_wrapped = idx

check(idx_wrapped >= 0 and idx_wrapped < length, ValueError,
f"index '{idx}' is out of range for container of length '{length}'")
check(idx_wrapped >= 0 and idx_wrapped < length, ValueError, lambda: (
f"index '{idx}' is out of range for container of length '{length}'"))

return idx_wrapped
94 changes: 47 additions & 47 deletions spacetime/frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ def __init__(self, worldlines=None, names=None):
self._next_default_name_id = 0

if worldlines is None:
check(names is None, ValueError,
"'names' cannot be given without 'worldlines'")
check(names is None, ValueError, lambda: (
"'names' cannot be given without 'worldlines'"))

else:
self.append(worldlines, names)
Expand All @@ -51,47 +51,47 @@ def _append_multiple(self, worldlines, names=None):
# TODO: Separate this into an `_append_multiple` function, and move the
# current `append` impl to `_append_single`. Then make `append`
# conditionally call either the single or multiple append func
check(isinstance(worldlines, (list, tuple)), TypeError,
"Expected 'worldlines' to be a list or tuple, but got ",
f"{type(worldlines)}")
check(isinstance(worldlines, (list, tuple)), TypeError, lambda: (
"Expected 'worldlines' to be a list or tuple, but got "
f"{type(worldlines)}"))

if names is not None:
check(isinstance(names, (list, tuple)), TypeError,
"Expected 'names' to be a list or tuple, but got ",
f"{type(names)}")
check(len(names) == len(worldlines), ValueError,
"Expected 'len(names) == len(worldlines)', but got '",
f"{len(names)} != {len(worldlines)}'")
check(isinstance(names, (list, tuple)), TypeError, lambda: (
"Expected 'names' to be a list or tuple, but got "
f"{type(names)}"))
check(len(names) == len(worldlines), ValueError, lambda: (
"Expected 'len(names) == len(worldlines)', but got '"
f"{len(names)} != {len(worldlines)}'"))

# TODO: Add ndim checks here
for idx, worldline in enumerate(worldlines):
if idx == 0:
if self.ndim is not None:
check(worldline.ndim == self.ndim, ValueError,
f"expected worldlines[{idx}] to have {self.ndim} dims, ",
f"but got {worldline.ndim}")
check(worldline.ndim == self.ndim, ValueError, lambda: (
f"expected worldlines[{idx}] to have {self.ndim} dims, "
f"but got {worldline.ndim}"))
else:
check(worldline.ndim == worldlines[0].ndim, ValueError,
f"expected worldlines[{idx}] to have {worldlines[0].ndim} dims, ",
f"but got {worldline.ndim}")
check(worldline.ndim == worldlines[0].ndim, ValueError, lambda: (
f"expected worldlines[{idx}] to have {worldlines[0].ndim} dims, "
f"but got {worldline.ndim}"))


for idx, worldline in enumerate(worldlines):
name = names[idx] if names is not None else None
self._append_single(worldline, name)

def _append_single(self, worldline, name=None):
check(isinstance(worldline, st.Worldline), TypeError,
f"expected an object of type Worldline, but got '{type(worldline)}'")
check(isinstance(worldline, st.Worldline), TypeError, lambda: (
f"expected an object of type Worldline, but got '{type(worldline)}'"))

if self.ndim is not None:
check(worldline.ndim == self.ndim, ValueError,
f"expected worldline to have {self.ndim} dims, but got {worldline.ndim}")
check(worldline.ndim == self.ndim, ValueError, lambda: (
f"expected worldline to have {self.ndim} dims, but got {worldline.ndim}"))

if name is not None:
assert isinstance(name, str)
check(name not in self._worldlines.keys(), ValueError,
f"The name '{name}' is already used by a different worldline")
check(name not in self._worldlines.keys(), ValueError, lambda: (
f"The name '{name}' is already used by a different worldline"))

# If the user-defined name matches the default name format, maybe
# reset the next default ID to avoid generating a duplicate later
Expand Down Expand Up @@ -140,40 +140,40 @@ def eval(self, time):
return state

def __getitem__(self, key):
check(isinstance(key, (int, str)), TypeError,
f"key must be either int or str, but got {type(key)}")
check(isinstance(key, (int, str)), TypeError, lambda: (
f"key must be either int or str, but got {type(key)}"))

if isinstance(key, int):
idx = maybe_wrap_index(key, len(self))
return next(islice(self._worldlines.values(), idx, None))

else:
check(key in self._worldlines, KeyError,
f"worldline of name '{key}' not found")
check(key in self._worldlines, KeyError, lambda: (
f"worldline of name '{key}' not found"))
return self._worldlines[key]

def __setitem__(self, key, value):
check(isinstance(key, (int, str)), TypeError,
f"key must be either int or str, but got {type(key)}")
check(isinstance(value, st.Worldline), TypeError,
f"value must be a Worldline, but got {type(value)}")
check(isinstance(key, (int, str)), TypeError, lambda: (
f"key must be either int or str, but got {type(key)}"))
check(isinstance(value, st.Worldline), TypeError, lambda: (
f"value must be a Worldline, but got {type(value)}"))

if isinstance(key, int):
idx = maybe_wrap_index(key, len(self))
name = next(islice(self._worldlines.keys(), idx, None))

else:
check(key in self._worldlines, KeyError,
f"worldline of name '{key}' not found")
check(key in self._worldlines, KeyError, lambda: (
f"worldline of name '{key}' not found"))
name = key

check(value.ndim == self.ndim, ValueError,
f"expected 'value.ndim' to be {self.ndim}, but got {value.ndim}")
check(value.ndim == self.ndim, ValueError, lambda: (
f"expected 'value.ndim' to be {self.ndim}, but got {value.ndim}"))
self._worldlines[name] = value

def name(self, idx):
check(isinstance(idx, int), TypeError,
f"idx must be an int, but got {type(idx)}")
check(isinstance(idx, int), TypeError, lambda: (
f"idx must be an int, but got {type(idx)}"))
idx_wrapped = maybe_wrap_index(idx, len(self))
return next(islice(self._worldlines.keys(), idx, None))

Expand All @@ -182,10 +182,10 @@ def name(self, idx):
# in `examples/clock_grid.py`. I should probably do the latter regardless,
# and make the return of `Frame.eval` include the worldline names
def index(self, name):
check(isinstance(name, str), TypeError,
f"name must be a str, but got {type(name)}")
check(name in self._worldlines, ValueError,
f"no worldline named '{name}' was found")
check(isinstance(name, str), TypeError, lambda: (
f"name must be a str, but got {type(name)}"))
check(name in self._worldlines, ValueError, lambda: (
f"no worldline named '{name}' was found"))
return list(self._worldlines.keys()).index(name)

def __len__(self):
Expand Down Expand Up @@ -235,20 +235,20 @@ def boost(self, boost_vel_s, event_delta_pre=None, event_delta_post=None, _batch
# Check `event_delta_*` args
if event_delta_pre is not None:
event_delta_pre = np.array(event_delta_pre)
check(event_delta_pre.shape == (self.ndim,), ValueError,
check(event_delta_pre.shape == (self.ndim,), ValueError, lambda: (
f"'event_delta_pre' must have shape {(self.ndim,)}, "
f"but got {event_delta_pre.shape}")
f"but got {event_delta_pre.shape}"))

if event_delta_post is not None:
event_delta_post = np.array(event_delta_post)
check(event_delta_post.shape == (self.ndim,), ValueError,
check(event_delta_post.shape == (self.ndim,), ValueError, lambda: (
f"'event_delta_post' must have shape {(self.ndim,)}, "
f"but got {event_delta_post.shape}")
f"but got {event_delta_post.shape}"))

boost_vel_s = np.array(boost_vel_s)
check(boost_vel_s.shape == (self.ndim - 1,), ValueError,
check(boost_vel_s.shape == (self.ndim - 1,), ValueError, lambda: (
f"'boost_vel_s' must have shape {(self.ndim - 1,)}, "
f"but got {boost_vel_s.shape}")
f"but got {boost_vel_s.shape}"))

speed = np.linalg.norm(boost_vel_s)
# Don't allow faster than light transformations
Expand Down
Loading

0 comments on commit 702a86f

Please sign in to comment.