Skip to content

Commit

Permalink
Merge pull request #56 from ABSESpy/dev
Browse files Browse the repository at this point in the history
Improved code and docs formats
  • Loading branch information
SongshGeo authored Apr 6, 2024
2 parents 879ca08 + fa0103e commit 517df26
Show file tree
Hide file tree
Showing 30 changed files with 2,024 additions and 1,533 deletions.
5 changes: 5 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,8 @@ repos:
# - id: poetry-lock
- id: poetry-export
args: ["-f", "requirements.txt", "--without-hashes", "-o", "requirements.txt"]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: 'v1.6.1' # Use the sha / tag you want to point at
hooks:
- id: mypy
args: [--ignore-missing-imports]
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,27 @@

<a id='changelog-0.5.5'></a>
# 0.5.5 — 2024-04-06

## Refactoring

- [x] #refactor♻️ Improved code formats

## Documentation changes

- [x] **LINE 30**: Missing space between "...tools (Schlüter et al., 2023)" and "to implement...". Should appear as "...tools (Schlüter et al., 2023) to implement..."
- [x] **LINE 42**: The figure reference "(Figure 1)" should not be bold but instead use formatting that enables linking to the actual figure. You can see how this is done in other JOSS publications such as [https://joss.theoj.org/papers/10.21105/joss.06294](https://joss.theoj.org/papers/10.21105/joss.06294)[](https://www.sci-hub.ee/10.21105/joss.06294). I'll only mention this for This figure, but note that this should be done for all figure references in your paper.
- [x] **LINE 48**: The statement "...but somehow enhanced." should be changed here. Perhaps use "...but with enhanced functionality." or the something similar.
- [x] **LINE 49**: Since YAML is a data serialization language, it should be referenced by its name here instead of the extension which sometimes varies. So instead of "...through .yaml files." you could use "...through the use of YAML configuration files." or something similar. You can read more about YAML [here](https://yaml.org/) if you like. Note that there are several of these usages throughout the paper that you may need to correct.
- [x] **LINE 53**: Wording is off here. Instead of "...(2) enhancing reality and manageability of ABMs." something like the following would be more clear "...(2) enhancing the reality and manageability of ABMs." This is phrased several times like this throughout the paper, so please let me know if it should be written as stated. I'll not mention the other occurrences, but address those if needed.
- [x] **LINE 56**: "...and can be..." should be "...which can be..."
- [x] **LINE 63**: "(Schlüter et al., 2017), (Beckage et al., 2022)" should appear as "(Schlüter et al., 2017; Beckage et al., 2022)"
- [x] **LINE 87-88**: You use the formatting `{"start: '2022-12-31', "end": 2024-01-01, year: 1}` please add in what I believe should be the correct, consistent formatting as following: `{"start: "2022-12-31", "end": "2024-01-01", "year": 1}`. Please correct me if I am wrong. I am also assuming "year" requires an integer as you have written.
- [x] **LINE 88**: "...to the 'time' module..." should be formatted as "...to the `time` module..." where backticks are used.
- [x] **LINE 102**: You use "input/output" though earlier in the paper you use "Input/Ouput" please choose one method to be consistent.
- [x] **LINE 131**: Your reference for the Janssen et al. paper is not formatted correctly. See [https://www.jasss.org/11/2/6/citation.html](https://www.jasss.org/11/2/6/citation.html)
- [x] **LINE 135**: Should have a colon after "In". See [https://link.springer.com/chapter/10.1007/978-3-030-61255-9_30#citeas](https://link.springer.com/chapter/10.1007/978-3-030-61255-9_30#citeas)[](https://www.sci-hub.ee/10.1007/978-3-030-61255-9_30)
- [x] **LINE 159**: Missing colon after "In" see [https://link.springer.com/chapter/10.1007/978-3-319-67217-5_2#citeas](https://link.springer.com/chapter/10.1007/978-3-319-67217-5_2#citeas)[](https://www.sci-hub.ee/10.1007/978-3-319-67217-5_2)

<a id='changelog-0.5.4'></a>
# 0.5.4 — 2024-03-28

Expand Down
2 changes: 1 addition & 1 deletion abses/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"alive_required",
"time_condition",
]
__version__ = "v0.5.4"
__version__ = "v0.5.5"

from .actor import Actor, alive_required, perception
from .container import ActorsList
Expand Down
110 changes: 41 additions & 69 deletions abses/actor.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,43 +14,41 @@
from __future__ import annotations

from functools import cached_property, wraps
from typing import TYPE_CHECKING, Any, Callable, Iterable, Optional, Union
from typing import (
TYPE_CHECKING,
Any,
Callable,
Iterable,
Optional,
Union,
cast,
)

try:
from typing import Self, TypeAlias
from typing import TypeAlias
except ImportError:
from typing_extensions import TypeAlias, Self
from typing_extensions import TypeAlias

import mesa_geo as mg

from abses.decision import _DecisionFactory
from abses.errors import ABSESpyError
from abses.links import _LinkNode, _LinkProxy
from abses.links import TargetName, _LinkNode
from abses.move import _Movements
from abses.objects import _BaseObj
from abses.tools.func import make_list

if TYPE_CHECKING:
from abses.human import LinkContainer
from abses.human import _LinkContainer
from abses.main import MainModel
from abses.nature import PatchCell
from abses.nature import PatchCell, PatchModule
from abses.sequences import ActorsList


Selection: TypeAlias = Union[str, Iterable[bool]]
Trigger: TypeAlias = Union[Callable, str]
Targets: TypeAlias = Union["ActorsList", "Self", "PatchCell", "str"]
Breeds: TypeAlias = Optional[Union[str, Iterable[str]]]

TARGET_KEYWORDS = {
"at": "at",
"world": "at",
"nature": "at",
"me": "self",
"actor": "self",
"agent": "self",
}


def alive_required(method):
"""
Expand Down Expand Up @@ -83,8 +81,10 @@ def perception_result(name, result, nodata: Any = 0.0) -> Any:


def perception(
decorated_func: Optional[Callable] = None, *, nodata: Optional[Any] = None
) -> Callable:
decorated_func: Optional[Callable[..., Any]] = None,
*,
nodata: Optional[Any] = None,
) -> Callable[..., Any]:
"""Change the decorated function into a perception attribute.
Parameters:
Expand All @@ -97,16 +97,20 @@ def perception(
The decorated perception attribute or a decorator.
"""

def decorator(func) -> Callable:
def decorator(func) -> Callable[..., Any]:
@wraps(func)
def wrapper(self: Actor, *args, **kwargs):
def wrapper(self: Actor, *args, **kwargs) -> Callable[..., Any]:
result = func(self, *args, **kwargs)
return perception_result(func.__name__, result, nodata=nodata)

return wrapper

# 检查是否有参数传递给装饰器,若没有则返回装饰器本身
return decorator(decorated_func) if decorated_func else decorator
return (
decorator(decorated_func)
if decorated_func
else cast(Callable[..., Any], decorator)
)


class Actor(mg.GeoAgent, _BaseObj, _LinkNode):
Expand Down Expand Up @@ -145,7 +149,7 @@ class Actor(mg.GeoAgent, _BaseObj, _LinkNode):

def __init__(
self,
model: MainModel,
model: MainModel[Any, Any],
observer: bool = True,
unique_id: Optional[int] = None,
**kwargs,
Expand All @@ -159,14 +163,19 @@ def __init__(
self, unique_id, model=model, geometry=geometry, crs=crs
)
_LinkNode.__init__(self)
self._cell: PatchCell = None
self._cell: Optional[PatchCell] = None
self._decisions: _DecisionFactory = self._setup_decisions()
self._alive: bool = True
self._setup()

def __repr__(self) -> str:
return f"<{self.breed} [{self.unique_id}]>"

def _default_redirection(
self, target: Optional[TargetName]
) -> Optional[PatchCell]:
return self if target == "actor" else self._cell

def _setup_decisions(self) -> _DecisionFactory:
"""Decisions that this actor makes."""
decisions = make_list(getattr(self, "__decisions__", None))
Expand All @@ -186,7 +195,7 @@ def decisions(self) -> _DecisionFactory:
d = decisions

@property
def layer(self) -> mg.RasterLayer | None:
def layer(self) -> Optional[PatchModule]:
"""Get the layer where the actor is located."""
return None if self._cell is None else self._cell.layer

Expand Down Expand Up @@ -215,7 +224,8 @@ def at(self, cell: PatchCell) -> None:
@at.deleter
def at(self) -> None:
"""Remove the agent from the located cell."""
if self.on_earth and self in self.at.agents:
cell = cast("PatchCell", self.at)
if self.on_earth and cell.agents is not None and self in cell.agents:
raise ABSESpyError(
"Cannot remove location directly because the actor is still on earth."
)
Expand All @@ -232,42 +242,11 @@ def move(self) -> _Movements:
"""
return _Movements(self)

@cached_property
def link(self) -> _LinkProxy:
"""A proxy which can be used to manipulate the links:
1. `link.to()`: creates a new link from this actor to another.
2. `link.by()`: creates a new link from another to this actor.
3. `link.get()`: gets the links of this actor.
4. `link.has()`: checks if there is a link between this actor and another.
5. `link.unlink()`: removes a link between this actor and another.
6. `link.clean()`: removes all links of this actor.
"""
return _LinkProxy(self, self.model)

def _get_correct_target(self, target: Targets, attr: str) -> Targets:
"""Which targets should be used when getting or setting."""
# If the target is not None, get the target from dict.
if target is not None:
if isinstance(target, str):
target = TARGET_KEYWORDS.get(target, target)
target = {"self": self, "at": self.at}[target]
return target
# If the attribute is in the agent, the agent is the target.
if hasattr(self, attr):
return self
# If the attribute is in the cell, the cell is the target.
if self.on_earth and hasattr(self.at, attr):
return self._cell
# If the attribute is not found, raise an error.
warn = "Set a new attribute outside '__init__' is not allowed."
raise AttributeError(f"Attribute '{attr}' not found in {self}. {warn}")

@alive_required
def get(
self,
attr: str,
target: Optional[Self | PatchCell] = None,
target: Optional[TargetName] = None,
) -> Any:
"""Gets attribute value from target.
Expand All @@ -283,11 +262,12 @@ def get(
Returns:
The value of the attribute.
"""
target = self._get_correct_target(target, attr=attr)
return getattr(self, attr) if target is self else target.get(attr)
return super().get(attr=attr, target=target)

@alive_required
def set(self, attr: str, value: Any, target: Targets) -> None:
def set(
self, attr: str, value: Any, target: Optional[TargetName] = None
) -> None:
"""Sets the value of an attribute.
Parameters:
Expand All @@ -306,15 +286,7 @@ def set(self, attr: str, value: Any, target: Targets) -> None:
ABSESpyError:
If the attribute is protected.
"""
# If the attribute is not a string, raise an error.
if not isinstance(attr, str):
raise TypeError("The attribute must be a string.")
# If the attribute is protected, raise an error
if attr.startswith("_"):
raise ABSESpyError(f"Attribute '{attr}' is protected.")
# Set the attribute on the target.
target = self._get_correct_target(target=target, attr=attr)
setattr(target, attr, value)
super().set(attr=attr, value=value, target=target)

@alive_required
def die(self) -> None:
Expand Down
2 changes: 1 addition & 1 deletion abses/bases.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
class _Notice:
"""Notice class for the observer pattern."""

__glob_vars__: Set[str] = []
__glob_vars__: Set[str] = set()

def __init__(self, observer: Optional[_Observer] = None):
self.observers: Set[_Observer] = set()
Expand Down
Loading

0 comments on commit 517df26

Please sign in to comment.