LLNL / Abmarl

Agent Based Modeling and Reinforcement Learning
Other
56 stars 16 forks source link

Agent decorator #32

Open rusu24edward opened 3 years ago

rusu24edward commented 3 years ago

Agent classes are really just dictionaries, and they're all pretty much the same. We should modify the dataclass decorator to give agents a configured function automatically and use this decorator everywhere we define an Agent to save from writing so much boilerplate code.

In epic #12

rusu24edward commented 3 years ago

Should also consider using decorators for the setters in state components, like so:

class Circle:
    def __init__(self, radius):
        self._radius = radius

    @property
    def radius(self):
        """Get value of radius"""
        return self._radius

    @radius.setter
    def radius(self, value):
        """Set radius, raise error if negative"""
        if value >= 0:
            self._radius = value
        else:
            raise ValueError("Radius must be positive")

    @property
    def area(self):
        """Calculate area inside circle"""
        return self.pi() * self.radius**2

    def cylinder_volume(self, height):
        """Calculate volume of cylinder with circle as base"""
        return self.area * height

    @classmethod
    def unit_circle(cls):
        """Factory method creating a circle with radius 1"""
        return cls(1)

    @staticmethod
    def pi():
        """Value of π, could use math.pi instead though"""
        return 3.1415926535

>>> c = Circle(5)
>>> c.radius
5

>>> c.area
78.5398163375

>>> c.radius = 2
>>> c.area
12.566370614

>>> c.area = 100
AttributeError: can't set attribute

>>> c.cylinder_volume(height=4)
50.265482456

>>> c.radius = -1
ValueError: Radius must be positive

>>> c = Circle.unit_circle()
>>> c.radius
1

>>> c.pi()
3.1415926535

>>> Circle.pi()
3.1415926535
rusu24edward commented 5 months ago

The default dataclass decorator is not intelligent enough to handle additional logic in the properties. For example, setting a min/max value, reserving some values and unusable, etc. This may be good to use in some cases, but cannot be used in every definition.

This article is a way to reconcile the two, but you're not saving much at this point...

from dataclasses import dataclass, field

@dataclass
class Vehicle:
    wheels: int
    _wheels: int = field(init=False, repr=False)

    @property
    def wheels(self) -> int:
        print("getting wheels")
        return self._wheels

    @wheels.setter
    def wheels(self, wheels: int):
        print("setting wheels to", wheels)
        self._wheels = wheels