Skip to content

Commit

Permalink
Update main with next releasable version (#25)
Browse files Browse the repository at this point in the history
* Docs for data gathering. Updates for usability an clarity.

* Updating NamedEntity and Monitoring resources to assist data gathering.

* New docs for new features. Updated other docs for new features.

* Making states initialize to default right away to correct data gathering bugs. (#23)

* Location data object improvements. (#24)
  • Loading branch information
JamesArruda authored Jan 3, 2025
1 parent e32f344 commit fed5051
Show file tree
Hide file tree
Showing 10 changed files with 1,594 additions and 3,186 deletions.
4,615 changes: 1,466 additions & 3,149 deletions pixi.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ requires = [

[project]
name = "upstage-des"
version = "0.2.0"
version = "0.2.1"
description = "A library for behavior-driven discrete event simulation."
readme = "README.md"
keywords = [
Expand Down
2 changes: 1 addition & 1 deletion src/upstage_des/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
"""Declare the UPSTAGE version."""

__authors__ = "UPSTAGE Contributors, GTRI"
__version__ = "0.2.0"
__version__ = "0.2.1"
4 changes: 3 additions & 1 deletion src/upstage_des/actor.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,10 @@ def __init_states(self, **states: Any) -> None:
exist = set(self._state_defs.keys())
unseen = exist - seen
for state_name in unseen:
if self._state_defs[state_name].has_default():
_state = self._state_defs[state_name]
if _state.has_default():
seen.add(state_name)
_state._set_default(self)
if len(seen) != len(exist):
raise UpstageError(
f"Missing values for states! These states need values: "
Expand Down
24 changes: 24 additions & 0 deletions src/upstage_des/data_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,15 @@ def make_location(self) -> CartesianLocation:
x=self.x, y=self.y, z=self.z, use_altitude_units=self.use_altitude_unts
)

def __eq__(self, value: Any) -> bool:
"""Test for equality of two cartesian locations data objects."""
if not isinstance(value, CartesianLocationData):
raise ValueError(
f"Cannot compare a {value.__class__.__name__} to a CartesianLocationData"
)
check = ["x", "y", "z"]
return all(getattr(self, c) == getattr(value, c) for c in check)


class GeodeticLocationData:
"""Object for storing geodetic data without an environment."""
Expand Down Expand Up @@ -596,3 +605,18 @@ def make_location(self) -> GeodeticLocation:
alt=self.alt,
in_radians=self.in_radians,
)

def __eq__(self, value: Any) -> bool:
"""Test for equality of two cartesian locations data objects."""
if not isinstance(value, GeodeticLocationData):
raise ValueError(
f"Cannot compare a {value.__class__.__name__} to a GeodeticLocationData"
)
other = [value.lat, value.lon]
if value.in_radians != self.in_radians:
if self.in_radians:
other = list(map(radians, other))
else:
other = list(map(degrees, other))
angles = [self.lat, self.lon] == other
return angles and self.alt == value.alt
4 changes: 2 additions & 2 deletions src/upstage_des/data_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ def _actor_state_data(

for state_name, state in actor._state_defs.items():
if skip_locations and isinstance(state, LOCATION_TYPES):
break
_value = actor.__dict__[state_name] # skips data recording after the fact
continue
_value = actor.__dict__[state_name]
is_active = isinstance(state, ActiveState)
if state_name in actor._state_histories:
data.extend(
Expand Down
28 changes: 24 additions & 4 deletions src/upstage_des/states.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,15 +212,23 @@ def __get__(self, instance: "Actor", objtype: type | None = None) -> ST:
value = getattr(actor, name)
self.__set__(instance, value)
if self.name not in instance.__dict__:
# Just set the value to the default
# Mutable types will be tricky here, so deepcopy them
instance.__dict__[self.name] = deepcopy(self._default)
raise SimulationError(f"State {self.name} should have been set.")
v = instance.__dict__[self.name]
return cast(ST, v)

def __set_name__(self, owner: "Actor", name: str) -> None:
self.name = name

def _set_default(self, instance: "Actor") -> None:
"""Set the state's value on the actor the default.
Args:
instance (Actor): Actor holding the state.
"""
assert self._default is not None
value = deepcopy(self._default)
self.__set__(instance, value)

def has_default(self) -> bool:
"""Check if a default exists.
Expand Down Expand Up @@ -283,8 +291,13 @@ def __set__(self, instance: "Actor", value: bool) -> None:
instance (Actor): The actor
value (bool): The value to set
"""
# Setting the default value shouldn't trigger the callback
# to the motion manager.
was_set = True
if self.name not in instance.__dict__:
was_set = False
super().__set__(instance, value)
if hasattr(instance.stage, "motion_manager"):
if hasattr(instance.stage, "motion_manager") and was_set:
mgr = instance.stage.motion_manager
if not value:
mgr._mover_not_detectable(instance)
Expand Down Expand Up @@ -964,6 +977,13 @@ def __set__(self, instance: "Actor", value: dict | Any) -> None:
self._broadcast_change(instance, self.name, value)

def _set_default(self, instance: "Actor") -> None:
"""Set the default conditions.
The empty dictionary input forces default to happen the right way.
Args:
instance (Actor): The actor holding this state.
"""
self.__set__(instance, {})

def __get__(self, instance: "Actor", owner: type | None = None) -> T:
Expand Down
21 changes: 14 additions & 7 deletions src/upstage_des/test/test_data_reporting.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class Cashier(UP.Actor):
class Cart(UP.Actor):
location = UP.CartesianLocationChangingState(recording=True)
location_two = UP.CartesianLocationChangingState(recording=True)
holding = UP.State[float](default=0.0, recording=True)


def test_data_reporting() -> None:
Expand Down Expand Up @@ -117,14 +118,19 @@ def test_data_reporting() -> None:
assert ctr[("Ertha", "Cashier", "items_scanned")] == 5
assert ctr[("Ertha", "Cashier", "cue")] == 4
assert ctr[("Ertha", "Cashier", "cue2")] == 4
assert ctr[("Ertha", "Cashier", "time_working")] == 5
assert ctr[("Ertha", "Cashier", "time_working")] == 6
assert ctr[("Bertha", "Cashier", "items_scanned")] == 2
assert ctr[("Bertha", "Cashier", "cue")] == 4
assert ctr[("Bertha", "Cashier", "cue2")] == 4
assert ctr[("Bertha", "Cashier", "time_working")] == 4
assert ctr[("Bertha", "Cashier", "time_working")] == 5
assert ctr[("Store Test", "SelfMonitoringFilterStore", "Resource")] == 3
assert not any(x[0] == "Wobbly Wheel" for x in state_table)
assert len(state_table) == 35
# Test for default values untouched in the sim showing up in the data.
assert ctr[("Wobbly Wheel", "Cart", "holding")] == 1
row = [r for r in state_table if r[:3] == ("Wobbly Wheel", "Cart", "holding")][0]
assert row[4] == 0
assert row[3] == 0.0
# Continuing as before
assert len(state_table) == 38
assert cols == all_cols
assert cols == [
"Entity Name",
Expand All @@ -139,15 +145,16 @@ def test_data_reporting() -> None:
assert ctr[("Ertha", "Cashier", "items_scanned")] == 5
assert ctr[("Ertha", "Cashier", "cue")] == 4
assert ctr[("Ertha", "Cashier", "cue2")] == 4
assert ctr[("Ertha", "Cashier", "time_working")] == 5
assert ctr[("Ertha", "Cashier", "time_working")] == 6
assert ctr[("Bertha", "Cashier", "items_scanned")] == 2
assert ctr[("Bertha", "Cashier", "cue")] == 4
assert ctr[("Bertha", "Cashier", "cue2")] == 4
assert ctr[("Bertha", "Cashier", "time_working")] == 4
assert ctr[("Bertha", "Cashier", "time_working")] == 5
assert ctr[("Store Test", "SelfMonitoringFilterStore", "Resource")] == 3
assert ctr[("Wobbly Wheel", "Cart", "holding")] == 1
assert ctr[("Wobbly Wheel", "Cart", "location")] == 4
assert ctr[("Wobbly Wheel", "Cart", "location_two")] == 4
assert len(all_state_table) == 35 + 8
assert len(all_state_table) == 38 + 8

assert loc_cols == [
"Entity Name",
Expand Down
36 changes: 36 additions & 0 deletions src/upstage_des/test/test_data_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,39 @@ def test_geodetic() -> None:

assert loc_up_rad - UP.GeodeticLocation(10, 10) > 0
assert UP.GeodeticLocation(10, 10) - loc_up_rad > 0


def test_data_objects() -> None:
cart1 = UP.CartesianLocationData(1.0, 2.1, 3.2)
cart2 = UP.CartesianLocationData(1.0, 2.1, 3.2)
assert cart1 == cart2

with pytest.raises(ValueError):
cart1 == (1.0, 2.1, 3.2)

geo1 = UP.GeodeticLocationData(13.0, 12.1, 11.2)
geo2 = UP.GeodeticLocationData(13.0, 12.1, 11.2)
assert geo1 == geo2

geo3 = UP.GeodeticLocationData(radians(13.0), radians(12.1), 11.2, in_radians=True)
assert geo1 == geo3
assert geo3 == geo1

with pytest.raises(ValueError):
geo1 == (13.0, 12.1, 11.2)

with UP.EnvironmentContext():
for k, v in STAGE_SETUP.items():
UP.add_stage_variable(k, v)

loc1 = geo1.make_location()
assert isinstance(loc1, UP.GeodeticLocation)

loc3 = geo3.make_location()

assert loc1 - loc3 == 0.0
assert loc1 == loc3

loc1 = cart1.make_location()
loc2 = cart2.make_location()
assert loc1 == loc2
44 changes: 23 additions & 21 deletions src/upstage_des/test/test_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@
class StateTest:
state_one = State[Any]()
state_two = State[Any](recording=True)
lister = State[list](default=[])
diction = State[dict](default={})
setstate = State[set](default=set())

def __init__(self, env: Environment | None) -> None:
cast(UP.Actor, self)
Expand All @@ -44,6 +41,12 @@ class StateTestActor(Actor):
state_three = LinearChangingState(recording=True)


class MutableDefaultActor(Actor):
lister = State[list](default=[])
diction = State[dict](default={})
setstate = State[set](default=set())


def test_state_fails_without_env() -> None:
"""Test that recording states need the class to have an env attribute"""
tester = StateTest(None)
Expand Down Expand Up @@ -77,23 +80,23 @@ def test_state_recording() -> None:


def test_state_mutable_default() -> None:
with EnvironmentContext(initial_time=1.5) as env:
tester = StateTest(env)
tester2 = StateTest(env)
assert id(tester.lister) != id(tester2.lister) # type: ignore [arg-type]
tester.lister.append(1) # type: ignore [arg-type]
assert len(tester2.lister) == 0 # type: ignore [arg-type]
assert len(tester.lister) == 1 # type: ignore [arg-type]
with EnvironmentContext(initial_time=1.5):
tester = MutableDefaultActor(name="Example")
tester2 = MutableDefaultActor(name="Example2")
assert id(tester.lister) != id(tester2.lister)
tester.lister.append(1)
assert len(tester2.lister) == 0
assert len(tester.lister) == 1

assert id(tester.diction) != id(tester2.diction) # type: ignore [arg-type]
tester2.diction[1] = 2 # type: ignore [arg-type]
assert len(tester.diction) == 0 # type: ignore [arg-type]
assert len(tester2.diction) == 1 # type: ignore [arg-type]
assert id(tester.diction) != id(tester2.diction)
tester2.diction[1] = 2
assert len(tester.diction) == 0
assert len(tester2.diction) == 1

assert id(tester.setstate) != id(tester2.setstate) # type: ignore [arg-type]
tester2.setstate.add(1) # type: ignore [arg-type]
assert len(tester.setstate) == 0 # type: ignore [arg-type]
assert len(tester2.setstate) == 1 # type: ignore [arg-type]
assert id(tester.setstate) != id(tester2.setstate)
tester2.setstate.add(1)
assert len(tester.setstate) == 0
assert len(tester2.setstate) == 1


def test_state_values_from_init() -> None:
Expand Down Expand Up @@ -232,9 +235,8 @@ class HolderBad(Actor):
assert h.res2.capacity == 12
assert h.res2.level == 5

hb = HolderBad(name="Bad one")
with pytest.raises(UpstageError):
hb.res2.capacity
HolderBad(name="Bad one")


def test_resource_state_kind_init() -> None:
Expand Down Expand Up @@ -339,7 +341,7 @@ def test_matching_states() -> None:
"""

class Worker(UP.Actor):
sleepiness = UP.State(default=0, valid_types=(float,))
sleepiness = UP.State[float](default=0.0, valid_types=(float,))
walkie = UP.CommunicationStore(mode="UHF")
intercom = UP.CommunicationStore(mode="loudspeaker")

Expand Down

0 comments on commit fed5051

Please sign in to comment.