diff --git a/examples/example1.py b/examples/example1.py index 49c331c2e1..6c1a448023 100755 --- a/examples/example1.py +++ b/examples/example1.py @@ -9,8 +9,6 @@ from shapely import GeometryCollection, Polygon, to_wkt import jupedsim as jps -from jupedsim.trajectory_writer_sqlite import SqliteTrajectoryWriter -from jupedsim.util import build_jps_geometry def log_debug(msg): @@ -41,7 +39,7 @@ def main(): p1 = Polygon([(0, 0), (10, 0), (10, 10), (0, 10)]) p2 = Polygon([(10, 4), (20, 4), (20, 6), (10, 6)]) area = GeometryCollection(p1.union(p2)) - geometry = build_jps_geometry(area) + geometry = jps.build_jps_geometry(area) model_builder = jps.VelocityModelBuilder( a_ped=8, d_ped=0.1, a_wall=5, d_wall=0.02 @@ -56,7 +54,8 @@ def main(): simulation = jps.Simulation(model=model, geometry=geometry, dt=0.01) stage_id = simulation.add_waiting_set_stage([(16, 5), (15, 5), (14, 5)]) - exit_stage = simulation.get_stage_proxy(stage_id) + waiting_stage = simulation.get_stage_proxy(stage_id) + assert isinstance(waiting_stage, jps.WaitingSetProxy) exit_id = simulation.add_exit_stage([(18, 4), (20, 4), (20, 6), (18, 6)]) journey = jps.JourneyDescription() @@ -77,7 +76,7 @@ def main(): print("Running simulation") - writer = SqliteTrajectoryWriter(pathlib.Path("example1_out.sqlite")) + writer = jps.SqliteTrajectoryWriter(pathlib.Path("example1_out.sqlite")) writer.begin_writing(10, to_wkt(area, rounding_precision=-1)) while simulation.agent_count() > 0: @@ -89,7 +88,8 @@ def main(): print(f"{a.model.e0}") break if simulation.iteration_count() == 1300: - exit_stage.state = jps.WaitingSetState.Inactive + if waiting_stage.state == jps.WaitingSetState.ACTIVE: + waiting_stage.state = jps.WaitingSetState.INACTIVE except KeyboardInterrupt: writer.end_writing() print("CTRL-C Recieved! Shuting down") diff --git a/examples/example2.py b/examples/example2.py index 8254b5cfc7..df81164496 100755 --- a/examples/example2.py +++ b/examples/example2.py @@ -8,8 +8,6 @@ from shapely import GeometryCollection, Polygon, to_wkt import jupedsim as jps -from jupedsim.trajectory_writer_sqlite import SqliteTrajectoryWriter -from jupedsim.util import build_jps_geometry def log_debug(msg): @@ -40,7 +38,7 @@ def main(): area = GeometryCollection( Polygon([(0, 0), (100, 0), (100, 100), (0, 100)]) ) - geometry = build_jps_geometry(area) + geometry = jps.build_jps_geometry(area) model_builder = jps.VelocityModelBuilder( a_ped=8, d_ped=0.1, a_wall=5, d_wall=0.02 @@ -97,7 +95,7 @@ def main(): agent_parameters.position = (6, 50) simulation.add_agent(agent_parameters) - writer = SqliteTrajectoryWriter(pathlib.Path("example2_out.sqlite")) + writer = jps.SqliteTrajectoryWriter(pathlib.Path("example2_out.sqlite")) writer.begin_writing( 25, to_wkt(area, rounding_precision=-1), @@ -120,7 +118,7 @@ def main(): ) if signal_once and any(simulation.agents_in_range((60, 60), 1)): - stage.state = jps.WaitingSetState.Inactive + stage.state = jps.WaitingSetState.INACTIVE print(f"Stop Waiting @{simulation.iteration_count()}") signal_once = False if simulation.iteration_count() % 4 == 0: diff --git a/examples/example3.py b/examples/example3.py index 7e5d1d6be8..a9778e462b 100755 --- a/examples/example3.py +++ b/examples/example3.py @@ -8,8 +8,6 @@ from shapely import GeometryCollection, Polygon, to_wkt import jupedsim as jps -from jupedsim.trajectory_writer_sqlite import SqliteTrajectoryWriter -from jupedsim.util import build_jps_geometry def log_debug(msg): @@ -40,7 +38,7 @@ def main(): area = GeometryCollection( Polygon([(0, 0), (100, 0), (100, 100), (0, 100), (0, 0)]) ) - geometry = build_jps_geometry(area) + geometry = jps.build_jps_geometry(area) model_builder = jps.VelocityModelBuilder( a_ped=8, d_ped=0.1, a_wall=5, d_wall=0.02 @@ -82,7 +80,7 @@ def main(): agent_parameters.position = (0.5, y) simulation.add_agent(agent_parameters) - writer = SqliteTrajectoryWriter(pathlib.Path("example3_out.sqlite")) + writer = jps.SqliteTrajectoryWriter(pathlib.Path("example3_out.sqlite")) writer.begin_writing(25, to_wkt(area, rounding_precision=-1)) while ( simulation.agent_count() > 0 and simulation.iteration_count() < 20_000 diff --git a/examples/example4.py b/examples/example4.py index bf32649d3b..d9b746cc79 100755 --- a/examples/example4.py +++ b/examples/example4.py @@ -10,8 +10,6 @@ from shapely import GeometryCollection, Polygon, to_wkt import jupedsim as jps -from jupedsim.trajectory_writer_sqlite import SqliteTrajectoryWriter -from jupedsim.util import build_jps_geometry def log_debug(msg): @@ -42,7 +40,7 @@ def main(): area = GeometryCollection( Polygon(shell=[(0, 0), (1000, 0), (1000, 5000), (0, 5000), (0, 0)]) ) - geometry = build_jps_geometry(area) + geometry = jps.build_jps_geometry(area) model_builder = jps.VelocityModelBuilder( a_ped=8, d_ped=0.1, a_wall=5, d_wall=0.02 @@ -73,7 +71,7 @@ def main(): agent_parameters.position = (0.5 + x, y + 0.5) simulation.add_agent(agent_parameters) - writer = SqliteTrajectoryWriter(pathlib.Path("example4_out.sqlite")) + writer = jps.SqliteTrajectoryWriter(pathlib.Path("example4_out.sqlite")) writer.begin_writing(5, to_wkt(area, rounding_precision=-1)) while simulation.agent_count() > 0: try: diff --git a/performancetest/grosser_stern.py b/performancetest/grosser_stern.py index c777ede575..f02078c182 100755 --- a/performancetest/grosser_stern.py +++ b/performancetest/grosser_stern.py @@ -10,12 +10,10 @@ import sys import time -import py_jupedsim as jps import shapely from shapely import to_wkt -from jupedsim.trajectory_writer_sqlite import SqliteTrajectoryWriter -from jupedsim.util import build_jps_geometry +import jupedsim as jps from performancetest.geometry import geometries from performancetest.stats_writer import StatsWriter @@ -44491,7 +44489,7 @@ def main(): jps.set_error_callback(log_error) geo = shapely.from_wkt(geometries["grosser_stern"]) - geometry = build_jps_geometry(geo) + geometry = jps.build_jps_geometry(geo) model_builder = jps.VelocityModelBuilder( a_ped=8, d_ped=0.1, a_wall=5, d_wall=0.02 @@ -44525,7 +44523,7 @@ def main(): agent_parameters.journey_id = random.choice(journeys) simulation.add_agent(agent_parameters) - writer = SqliteTrajectoryWriter( + writer = jps.SqliteTrajectoryWriter( pathlib.Path( f"{jps.get_build_info().git_commit_hash}_grosser_stern.sqlite" ) @@ -44565,7 +44563,7 @@ def main(): ) except KeyboardInterrupt: writer.end_writing() - print("\nCTRL-C Recieved! Shuting down") + print("\nCTRL-C Received! Shutting down") sys.exit(1) writer.end_writing() diff --git a/performancetest/large_street_network.py b/performancetest/large_street_network.py index 38c9a72d0c..122c127118 100755 --- a/performancetest/large_street_network.py +++ b/performancetest/large_street_network.py @@ -10,12 +10,10 @@ import sys import time -import py_jupedsim as jps import shapely from shapely import to_wkt -from jupedsim.trajectory_writer_sqlite import SqliteTrajectoryWriter -from jupedsim.util import build_jps_geometry +import jupedsim as jps from performancetest.geometry import geometries from performancetest.stats_writer import StatsWriter @@ -200,7 +198,7 @@ def main(): jps.set_error_callback(log_error) geo = shapely.from_wkt(geometries["large_street_network"]) - geometry = build_jps_geometry(geo) + geometry = jps.build_jps_geometry(geo) model_builder = jps.VelocityModelBuilder( a_ped=8, d_ped=0.1, a_wall=5, d_wall=0.02 @@ -228,7 +226,7 @@ def main(): ), ] - writer = SqliteTrajectoryWriter( + writer = jps.SqliteTrajectoryWriter( pathlib.Path( f"{jps.get_build_info().git_commit_hash}_large_street_network.sqlite" ) @@ -246,9 +244,9 @@ def main(): for s in spawners: s.spawn(iteration) if (iteration + 100 * 30) % (100 * 60) == 0: - waiting_area.state = jps.WaitingSetState.Inactive + waiting_area.state = jps.WaitingSetState.INACTIVE if iteration % (100 * 60) == 0: - waiting_area.state = jps.WaitingSetState.Active + waiting_area.state = jps.WaitingSetState.ACTIVE if iteration % (100 * 8) == 0: queue.pop(1) if iteration % 50 == 0: @@ -276,7 +274,7 @@ def main(): ) except KeyboardInterrupt: writer.end_writing() - print("\nCTRL-C Recieved! Shuting down") + print("\nCTRL-C Received! Shutting down") sys.exit(1) writer.end_writing() diff --git a/python_bindings_jupedsim/bindings_jupedsim.cpp b/python_bindings_jupedsim/bindings_jupedsim.cpp index e98c3e6492..e9f60a2867 100644 --- a/python_bindings_jupedsim/bindings_jupedsim.cpp +++ b/python_bindings_jupedsim/bindings_jupedsim.cpp @@ -243,7 +243,7 @@ PYBIND11_MODULE(py_jupedsim, m) }, "Add area where agents can move") .def( - "exclude_from_accssible_area", + "exclude_from_accessible_area", [](const JPS_GeometryBuilder_Wrapper& w, std::vector polygon) { JPS_GeometryBuilder_ExcludeFromAccessibleArea( w.handle, polygon.data(), polygon.size()); @@ -321,14 +321,14 @@ PYBIND11_MODULE(py_jupedsim, m) maxf_Wall)); }), py::kw_only(), - py::arg("nuPed"), - py::arg("nuWall"), - py::arg("distEffPed"), - py::arg("distEffWall"), - py::arg("intpWidthPed"), - py::arg("intpWidthWall"), - py::arg("maxfPed"), - py::arg("maxfWall")) + py::arg("nu_ped"), + py::arg("nu_wall"), + py::arg("dist_eff_ped"), + py::arg("dist_eff_wall"), + py::arg("intp_width_ped"), + py::arg("intp_width_wall"), + py::arg("maxf_ped"), + py::arg("maxf_wall")) .def( "add_parameter_profile", [](JPS_GCFMModelBuilder_Wrapper& w, diff --git a/python_modules/jupedsim/jupedsim/__init__.py b/python_modules/jupedsim/jupedsim/__init__.py index e5024e681d..278456f28a 100644 --- a/python_modules/jupedsim/jupedsim/__init__.py +++ b/python_modules/jupedsim/jupedsim/__init__.py @@ -1,13 +1,88 @@ # Copyright © 2012-2023 Forschungszentrum Jülich GmbH # SPDX-License-Identifier: LGPL-3.0-or-later -try: - from .py_jupedsim import * -except ModuleNotFoundError: - from py_jupedsim import * +from jupedsim.distributions import ( + AgentNumberError, + IncorrectParameterError, + NegativeValueError, + OverlappingCirclesError, + distribute_by_density, + distribute_by_number, + distribute_by_percentage, + distribute_in_circles_by_density, + distribute_in_circles_by_number, + distribute_till_full, +) +from jupedsim.native.agent import Agent +from jupedsim.native.geometry import Geometry, GeometryBuilder +from jupedsim.native.journey import JourneyDescription +from jupedsim.native.library import ( + BuildInfo, + get_build_info, + set_debug_callback, + set_error_callback, + set_info_callback, + set_warning_callback, +) +from jupedsim.native.models import ( + GCFMModelAgentParameters, + GCFMModelBuilder, + GeneralizedCentrifugalForceModelState, + VelocityModelAgentParameters, + VelocityModelBuilder, + VelocityModelState, +) +from jupedsim.native.routing import RoutingEngine +from jupedsim.native.simulation import Simulation +from jupedsim.native.stages import ( + ExitProxy, + NotifiableQueueProxy, + WaitingSetProxy, + WaitingSetState, + WaypointProxy, +) +from jupedsim.native.tracing import Trace +from jupedsim.recording import Recording, RecordingAgent, RecordingFrame +from jupedsim.trajectory_writer_sqlite import SqliteTrajectoryWriter +from jupedsim.util import build_jps_geometry -import jupedsim.aabb -import jupedsim.distributions -import jupedsim.grid -import jupedsim.serialization -import jupedsim.trajectory_writer_sqlite -import jupedsim.util +__all__ = [ + "AgentNumberError", + "IncorrectParameterError", + "NegativeValueError", + "OverlappingCirclesError", + "distribute_by_density", + "distribute_by_number", + "distribute_by_percentage", + "distribute_in_circles_by_density", + "distribute_in_circles_by_number", + "distribute_till_full", + "Agent", + "Geometry", + "GeometryBuilder", + "JourneyDescription", + "BuildInfo", + "get_build_info", + "set_debug_callback", + "set_error_callback", + "set_info_callback", + "set_warning_callback", + "GCFMModelAgentParameters", + "GCFMModelBuilder", + "GeneralizedCentrifugalForceModelState", + "VelocityModelAgentParameters", + "VelocityModelBuilder", + "VelocityModelState", + "RoutingEngine", + "Simulation", + "ExitProxy", + "NotifiableQueueProxy", + "WaitingSetProxy", + "WaitingSetState", + "WaypointProxy", + "Trace", + "Recording", + "RecordingAgent", + "RecordingFrame", + "SqliteTrajectoryWriter", + "build_jps_geometry", +] diff --git a/python_modules/jupedsim/jupedsim/distributions.py b/python_modules/jupedsim/jupedsim/distributions.py index f4415d710f..55e2f104f1 100644 --- a/python_modules/jupedsim/jupedsim/distributions.py +++ b/python_modules/jupedsim/jupedsim/distributions.py @@ -3,7 +3,7 @@ import numpy as np import shapely.geometry as shply -from jupedsim.grid import Grid +from jupedsim.internal.grid import Grid class AgentNumberError(Exception): diff --git a/python_modules/jupedsim/jupedsim/aabb.py b/python_modules/jupedsim/jupedsim/internal/aabb.py similarity index 100% rename from python_modules/jupedsim/jupedsim/aabb.py rename to python_modules/jupedsim/jupedsim/internal/aabb.py diff --git a/python_modules/jupedsim/jupedsim/grid.py b/python_modules/jupedsim/jupedsim/internal/grid.py similarity index 100% rename from python_modules/jupedsim/jupedsim/grid.py rename to python_modules/jupedsim/jupedsim/internal/grid.py diff --git a/python_modules/jupedsim/jupedsim/native/agent.py b/python_modules/jupedsim/jupedsim/native/agent.py new file mode 100644 index 0000000000..053201aed9 --- /dev/null +++ b/python_modules/jupedsim/jupedsim/native/agent.py @@ -0,0 +1,48 @@ +# Copyright © 2012-2023 Forschungszentrum Jülich GmbH +# SPDX-License-Identifier: LGPL-3.0-or-later + +import py_jupedsim as py_jps + +from jupedsim.native.models import ( + GeneralizedCentrifugalForceModelState, + VelocityModelState, +) + + +class Agent: + def __init__(self, backing): + self._obj = backing + + @property + def id(self): + return self._obj.id + + @property + def journey_id(self): + return self._obj.journey_id + + @property + def stage_id(self): + return self._obj.stage_id + + @property + def stage_index(self): + return self._obj.stage_index + + @property + def position(self): + return self._obj.position + + @property + def orientation(self): + return self._obj.orientation + + @property + def model(self): + model = self._obj.model + if isinstance(model, py_jps.GeneralizedCentrifugalForceModelState): + return GeneralizedCentrifugalForceModelState(model) + elif isinstance(model, py_jps.VelocityModelState): + return VelocityModelState(model) + else: + raise Exception("Internal error") diff --git a/python_modules/jupedsim/jupedsim/native/geometry.py b/python_modules/jupedsim/jupedsim/native/geometry.py new file mode 100644 index 0000000000..677faaa7d1 --- /dev/null +++ b/python_modules/jupedsim/jupedsim/native/geometry.py @@ -0,0 +1,45 @@ +# Copyright © 2012-2023 Forschungszentrum Jülich GmbH +# SPDX-License-Identifier: LGPL-3.0-or-later + +import py_jupedsim as py_jps + + +class Geometry: + """Geometry object for simulations.""" + + def __init__(self, obj): + self._obj = obj + + +class GeometryBuilder: + def __init__(self): + self._obj = py_jps.GeometryBuilder() + + def add_accessible_area(self, polygon: list[tuple[float, float]]) -> None: + """Adds an area which can be accessed by the agents to the geometry. + + Args: + polygon (list[tuple[float, float]]): list of x,y coordinates of + the points of a polygon + """ + self._obj.add_accessible_area(polygon) + + def exclude_from_accessible_area( + self, polygon: list[tuple[float, float]] + ) -> None: + """Marks an area as un-accessible by the agents to the geometry. + + Args: + polygon (list[tuple[float, float]]): list of x,y coordinates of + the points of a polygon + """ + + self._obj.exclude_from_accessible_area(polygon) + + def build(self) -> Geometry: + """Builds a Geometry from the given accessible and un-accessible areas. + + Returns: + Geometry object + """ + return Geometry(self._obj.build()) diff --git a/python_modules/jupedsim/jupedsim/native/journey.py b/python_modules/jupedsim/jupedsim/native/journey.py new file mode 100644 index 0000000000..80b470e157 --- /dev/null +++ b/python_modules/jupedsim/jupedsim/native/journey.py @@ -0,0 +1,17 @@ +# Copyright © 2012-2023 Forschungszentrum Jülich GmbH +# SPDX-License-Identifier: LGPL-3.0-or-later + +from typing import Optional + +import py_jupedsim as py_jps + + +class JourneyDescription: + def __init__(self, stage_id: Optional[list[int]] = None): + if stage_id is None: + self._obj = py_jps.JourneyDescription() + else: + self._obj = py_jps.JourneyDescription(stage_id) + + def append(self, stages: int | list[int]) -> None: + self._obj.append(stages) diff --git a/python_modules/jupedsim/jupedsim/native/library.py b/python_modules/jupedsim/jupedsim/native/library.py new file mode 100644 index 0000000000..167ea021a6 --- /dev/null +++ b/python_modules/jupedsim/jupedsim/native/library.py @@ -0,0 +1,87 @@ +# Copyright © 2012-2023 Forschungszentrum Jülich GmbH +# SPDX-License-Identifier: LGPL-3.0-or-later + +import py_jupedsim as py_jps + + +# TODO(kkratz): add typehints for function params +def set_debug_callback(fn) -> None: + """ + Set reciever for debug messages. + + Parameters + ---------- + fn: fn + function that accepts a msg as string + """ + py_jps.set_debug_callback(fn) + + +def set_info_callback(fn) -> None: + """ + Set reciever for info messages. + + Parameters + ---------- + fn: fn + function that accepts a msg as string + """ + py_jps.set_info_callback(fn) + + +def set_warning_callback(fn) -> None: + """ + Set reciever for warning messages. + + Parameters + ---------- + fn: fn + function that accepts a msg as string + """ + py_jps.set_warning_callback(fn) + + +def set_error_callback(fn) -> None: + """ + Set reciever for error messages. + + Parameters + ---------- + fn: fn + function that accepts a msg as string + """ + py_jps.set_error_callback(fn) + + +# TODO(kkratz): refactor this into free functions in C-API / bindings +class BuildInfo: + def __init__(self): + self.__obj = py_jps.get_build_info() + + @property + def git_commit_hash(self) -> str: + return self.__obj.git_commit_hash + + @property + def git_commit_date(self) -> str: + return self.__obj.git_commit_date + + @property + def git_branch(self) -> str: + return self.__obj.git_branch + + @property + def compiler(self) -> str: + return self.__obj.compiler + + @property + def compiler_version(self) -> str: + return self.__obj.compiler_version + + @property + def library_version(self) -> str: + return self.__obj.library_version + + +def get_build_info() -> BuildInfo: + return BuildInfo() diff --git a/python_modules/jupedsim/jupedsim/native/models.py b/python_modules/jupedsim/jupedsim/native/models.py new file mode 100644 index 0000000000..78ef97db3b --- /dev/null +++ b/python_modules/jupedsim/jupedsim/native/models.py @@ -0,0 +1,311 @@ +# Copyright © 2012-2023 Forschungszentrum Jülich GmbH +# SPDX-License-Identifier: LGPL-3.0-or-later + +import py_jupedsim as py_jps + + +class VelocityModelBuilder: + def __init__( + self, a_ped: float, d_ped: float, a_wall: float, d_wall: float + ) -> None: + self._obj = py_jps.VelocityModelBuilder( + a_ped=a_ped, d_ped=d_ped, a_wall=a_wall, d_wall=d_wall + ) + + def add_parameter_profile( + self, id: int, time_gap: float, tau: float, v0: float, radius: float + ) -> None: + self._obj.add_parameter_profile( + id=id, time_gap=time_gap, tau=tau, v0=v0, radius=radius + ) + + def build(self): + return self._obj.build() + + +class GCFMModelBuilder: + def __init__( + self, + nu_ped: float, + nu_wall: float, + dist_eff_ped: float, + dist_eff_wall: float, + intp_width_ped: float, + intp_width_wall: float, + maxf_ped: float, + maxf_wall: float, + ) -> None: + self._obj = py_jps.GCFMModelBuilder( + nu_ped=nu_ped, + nu_wall=nu_wall, + dist_eff_ped=dist_eff_ped, + dist_eff_wall=dist_eff_wall, + intp_width_ped=intp_width_ped, + intp_width_wall=intp_width_wall, + maxf_ped=maxf_ped, + maxf_wall=maxf_wall, + ) + + def add_parameter_profile( + self, + profile_id: int, + mass: float, + tau: float, + v0: float, + a_v: float, + a_min: float, + b_min: float, + b_max: float, + ): + return self._obj.add_parameter_profile( + id=profile_id, + mass=mass, + tau=tau, + v0=v0, + a_v=a_v, + a_min=a_min, + b_min=b_min, + b_max=b_max, + ) + + def build(self): + return self._obj.build() + + +class GCFMModelAgentParameters: + """ + Agent parameters for Generalized Centrifugal Model. + + See the scientifc publication for more details about this model + https://arxiv.org/abs/1008.4297 + + Objects of this type can be used to add new agents to the simulation and are + returned by the simulation when inspecting agent state. Setting properties on + objects returned by the simulation has no effect on the agents as this object + is a copy of internal state. + + Setting properties on this object is only useful when adding multiple agents + and they share many properties without reprating them on each 'add_agent' + call + """ + + def __init__(self): + self._obj = py_jps.GCFMModelAgentParameters() + + @property + def speed(self) -> float: + """ + Current speed + + NOTE: Setting this property has no effect on agents that are already part of the simulation + """ + return self._obj.speed + + @speed.setter + def speed(self, value: float) -> None: + self._obj.speed = value + + @property + def e0(self) -> tuple[float, float]: + """ + e0 (Currently desired orientation) + + NOTE: Setting this property has no effect on agents that are already part of the simulation + """ + return self._obj.e0 + + @e0.setter + def e0(self, value: tuple[float, float]) -> None: + self._obj.e0 = value + + @property + def position(self) -> tuple[float, float]: + """ + Current position + + NOTE: Setting this property has no effect on agents that are already part of the simulation + """ + return self._obj.position + + @position.setter + def position(self, value: tuple[float, float]) -> None: + self._obj.position = value + + @property + def orientation(self) -> tuple[float, float]: + """ + Current orientation + + NOTE: Setting this property has no effect on agents that are already part of the simulation + """ + return self._obj.orientation + + @orientation.setter + def orientation(self, value: tuple[float, float]) -> None: + self._obj.orientation = value + + @property + def journey_id(self) -> int: + """ + Id of curently followed journey + + NOTE: Setting this property has no effect on agents that are already part of the simulation + """ + return self._obj.journey_id + + @journey_id.setter + def journey_id(self, value: int) -> None: + self._obj.journey_id = value + + @property + def profile_id(self) -> int: + """ + Id of curently used profile + + NOTE: Setting this property has no effect on agents that are already part of the simulation + """ + return self._obj.profile_id + + @profile_id.setter + def profile_id(self, value: int) -> None: + self._obj.profile_id = value + + @property + def id(self) -> int: + """ + Id of this Agent + + NOTE: Setting this property has no effect on agents that are already part of the simulation + """ + return self._obj.id + + @id.setter + def id(self, value: int) -> None: + self._obj.id = value + + def __str__(self) -> str: + return self._obj.__repr__() + + +class VelocityModelAgentParameters: + """ + Agent parameters for Velocity Model. + + See the scientifc publication for more details about this model + https://arxiv.org/abs/1512.05597 + + Objects of this type can be used to add new agents to the simulation and are + returned by the simulation when inspecting agent state. Setting properties on + objects returned by the simulation has no effect on the agents as this object + is a copy of internal state. + + Setting properties on this object is only useful when adding multiple agents + and they share many properties without reprating them on each 'add_agent' + call + """ + + def __init__(self): + self._obj = py_jps.VelocityModelAgentParameters() + + @property + def e0(self) -> tuple[float, float]: + """ + e0 (Currently desired direction) + + NOTE: Setting this property has no effect on agents that are already part of the simulation + """ + return self._obj.e0 + + @e0.setter + def e0(self, value: tuple[float, float]) -> None: + self._obj.e0 = value + + @property + def position(self) -> tuple[float, float]: + """ + Current position + + NOTE: Setting this property has no effect on agents that are already part of the simulation + """ + return self._obj.position + + @position.setter + def position(self, value: tuple[float, float]) -> None: + self._obj.position = value + + @property + def orientation(self) -> tuple[float, float]: + """ + Current orientation + + NOTE: Setting this property has no effect on agents that are already part of the simulation + """ + return self._obj.orientation + + @orientation.setter + def orientation(self, value: tuple[float, float]) -> None: + self._obj.orientation = value + + @property + def journey_id(self) -> int: + """ + Id of curently followed journey + + NOTE: Setting this property has no effect on agents that are already part of the simulation + """ + return self._obj.journey_id + + @journey_id.setter + def journey_id(self, value: int) -> None: + self._obj.journey_id = value + + @property + def profile_id(self) -> int: + """ + Id of curently used profile + + NOTE: Setting this property has no effect on agents that are already part of the simulation + """ + return self._obj.profile_id + + @profile_id.setter + def profile_id(self, value: int) -> None: + self._obj.profile_id = value + + @property + def id(self) -> int: + """ + Id of this Agent + + NOTE: Setting this property has no effect on agents that are already part of the simulation + """ + return self._obj.id + + @id.setter + def id(self, value: int) -> None: + self._obj.id = value + + def __str__(self) -> str: + return self._obj.__repr__() + + +class GeneralizedCentrifugalForceModelState: + def __init__(self, backing): + self._obj = backing + + @property + def speed(self) -> float: + return self._obj.speed + + @property + def e0(self) -> tuple[float, float]: + return self._obj.e0 + + +class VelocityModelState: + def __init__(self, backing): + self._obj = backing + + @property + def e0(self) -> tuple[float, float]: + return self._obj.e0 diff --git a/python_modules/jupedsim/jupedsim/native/routing.py b/python_modules/jupedsim/jupedsim/native/routing.py new file mode 100644 index 0000000000..bf963b6819 --- /dev/null +++ b/python_modules/jupedsim/jupedsim/native/routing.py @@ -0,0 +1,29 @@ +# Copyright © 2012-2023 Forschungszentrum Jülich GmbH +# SPDX-License-Identifier: LGPL-3.0-or-later + +import py_jupedsim as py_jps + +from jupedsim.native.geometry import Geometry + + +class RoutingEngine: + def __init__(self, geo: Geometry) -> None: + self._obj = py_jps.RoutingEngine(geo) + + def compute_waypoints( + self, frm: tuple[float, float], to: tuple[float, float] + ) -> list[tuple[float, float]]: + return self._obj.compute_waypoints(frm, to) + + def is_routable(self, p: tuple[float, float]) -> bool: + return self._obj.is_routable(p) + + def mesh( + self, + ) -> list[ + tuple[tuple[float, float], tuple[float, float], tuple[float, float]] + ]: + return self._obj.mesh() + + def edges_for(self, vertex_id: int): + return self._obj.edges_for(vertex_id) diff --git a/python_modules/jupedsim/jupedsim/native/simulation.py b/python_modules/jupedsim/jupedsim/native/simulation.py new file mode 100644 index 0000000000..e95403f155 --- /dev/null +++ b/python_modules/jupedsim/jupedsim/native/simulation.py @@ -0,0 +1,114 @@ +# Copyright © 2012-2023 Forschungszentrum Jülich GmbH +# SPDX-License-Identifier: LGPL-3.0-or-later + +import py_jupedsim as py_jps + +from jupedsim.native.geometry import Geometry +from jupedsim.native.journey import JourneyDescription +from jupedsim.native.models import ( + GCFMModelAgentParameters, + VelocityModelAgentParameters, +) +from jupedsim.native.stages import ( + ExitProxy, + NotifiableQueueProxy, + WaitingSetProxy, + WaypointProxy, +) +from jupedsim.native.tracing import Trace + + +class Simulation: + def __init__(self, model, geometry: Geometry, dt: float) -> None: + self._sim = py_jps.Simulation( + model=model, geometry=geometry._obj, dt=dt + ) + + def add_waypoint_stage( + self, position: tuple[float, float], distance + ) -> int: + return self._sim.add_waypoint_stage(position, distance) + + def add_queue_stage(self, positions: list[tuple[float, float]]) -> int: + return self._sim.add_queue_stage(positions) + + def add_waiting_set_stage( + self, positions: list[tuple[float, float]] + ) -> int: + return self._sim.add_waiting_set_stage(positions) + + def add_exit_stage(self, polygon: list[tuple[float, float]]) -> int: + return self._sim.add_exit_stage(polygon) + + def add_journey(self, journey: JourneyDescription) -> int: + return self._sim.add_journey(journey._obj) + + def add_agent( + self, + parameters: GCFMModelAgentParameters | VelocityModelAgentParameters, + ) -> int: + return self._sim.add_agent(parameters._obj) + + def remove_agent(self, agent_id: int) -> bool: + return self._sim.remove_agent(agent_id) + + def removed_agents(self) -> list[int]: + return self._sim.removed_agents() + + def iterate(self, count: int = 1) -> None: + self._sim.iterate(count) + + def switch_agent_profile(self, agent_id: int, profile_id: int) -> None: + self._sim.switch_agent_profile( + agent_id=agent_id, profile_id=profile_id + ) + + def switch_agent_journey( + self, agent_id: int, journey_id: int, stage_index: int + ) -> None: + self._sim.switch_agent_journey( + agent_id=agent_id, journey_id=journey_id, stage_index=stage_index + ) + + def agent_count(self) -> int: + return self._sim.agent_count() + + def elapsed_time(self) -> float: + return self._sim.elapsed_time() + + def delta_time(self) -> float: + return self._sim.delta_time() + + def iteration_count(self) -> int: + return self._sim.iteration_count() + + def agents(self): + return self._sim.agents() + + def agents_in_range(self, pos: tuple[float, float], distance: float): + return self._sim.agents_in_range(pos, distance) + + def agents_in_polygon(self, poly: list[tuple[float, float]]): + return self._sim.agents_in_polygon(poly) + + def get_stage_proxy(self, stage_id: int): + stage = self._sim.get_stage_proxy(stage_id) + match stage: + case py_jps.WaypointProxy(): + return WaypointProxy(stage) + case py_jps.ExitProxy(): + return ExitProxy(stage) + case py_jps.NotifiableQueueProxy(): + return NotifiableQueueProxy(stage) + case py_jps.WaitingSetProxy(): + return WaitingSetProxy(stage) + case _: + raise Exception( + f"Internal error, unexpected type: {type(stage)}" + ) + + def set_tracing(self, status: bool) -> None: + self._sim.set_tracing(status) + + def get_last_trace(self) -> Trace: + return self._sim.get_last_trace() diff --git a/python_modules/jupedsim/jupedsim/native/stages.py b/python_modules/jupedsim/jupedsim/native/stages.py new file mode 100644 index 0000000000..d1f8d1966d --- /dev/null +++ b/python_modules/jupedsim/jupedsim/native/stages.py @@ -0,0 +1,65 @@ +# Copyright © 2012-2023 Forschungszentrum Jülich GmbH +# SPDX-License-Identifier: LGPL-3.0-or-later +from enum import Enum + +import py_jupedsim as py_jps + + +class NotifiableQueueProxy: + def __init__(self, backing): + self._obj = backing + + def count_targeting(self) -> int: + return self._obj.count_targeting() + + def count_enqueued(self) -> int: + return self._obj.count_enqueued() + + def pop(self, count) -> None: + return self._obj.pop(count) + + def enqueued(self) -> list[int]: + return self._obj.enqueued() + + +class WaitingSetState(Enum): + ACTIVE = py_jps.WaitingSetState.Active + INACTIVE = py_jps.WaitingSetState.Inactive + + +class WaitingSetProxy: + def __init__(self, backing): + self._obj = backing + + def count_targeting(self): + return self._obj.count_targeting() + + def count_waiting(self): + return self._obj.count_waiting() + + def waiting(self): + return self._obj.waiting() + + @property + def state(self): + return WaitingSetState(self._obj.state) + + @state.setter + def state(self, new_state: WaitingSetState): + self._obj.state = new_state.value + + +class WaypointProxy: + def __init__(self, backing): + self._obj = backing + + def count_targeting(self): + return self._obj.count_targeting() + + +class ExitProxy: + def __init__(self, backing): + self._obj = backing + + def count_targeting(self): + return self._obj.count_targeting() diff --git a/python_modules/jupedsim/jupedsim/native/tracing.py b/python_modules/jupedsim/jupedsim/native/tracing.py new file mode 100644 index 0000000000..622c8aa032 --- /dev/null +++ b/python_modules/jupedsim/jupedsim/native/tracing.py @@ -0,0 +1,31 @@ +# Copyright © 2012-2023 Forschungszentrum Jülich GmbH +# SPDX-License-Identifier: LGPL-3.0-or-later + +import py_jupedsim as py_jps + + +class Trace: + def __init__(self) -> None: + self._obj = py_jps.Trace + + @property + def iteration_duration(self): + """Time for one simulation iteration in us. + + Returns: + Time for one simulation iteration in us + """ + return self._obj.iteration_duration + + @property + def operational_level_duration(self): + """Time for one simulation iteration in the operational level in us. + + Returns: + Time for one simulation iteration in the operational level in us + """ + + return self._obj.operational_level_duration + + def __str__(self) -> str: + return self._obj.__repr__() diff --git a/python_modules/jupedsim/jupedsim/recording.py b/python_modules/jupedsim/jupedsim/recording.py index 16dcf8c766..4e074bd55a 100644 --- a/python_modules/jupedsim/jupedsim/recording.py +++ b/python_modules/jupedsim/jupedsim/recording.py @@ -5,7 +5,7 @@ import shapely -from jupedsim.aabb import AABB +from jupedsim.internal.aabb import AABB @dataclass diff --git a/python_modules/jupedsim/jupedsim/serialization.py b/python_modules/jupedsim/jupedsim/serialization.py index 06c775f53f..503a3f9949 100644 --- a/python_modules/jupedsim/jupedsim/serialization.py +++ b/python_modules/jupedsim/jupedsim/serialization.py @@ -15,11 +15,11 @@ import shapely -from jupedsim import ( +from jupedsim.native.models import ( GCFMModelAgentParameters, - Simulation, VelocityModelAgentParameters, ) +from jupedsim.native.simulation import Simulation class TrajectoryWriter(metaclass=abc.ABCMeta): diff --git a/python_modules/jupedsim/jupedsim/trajectory_writer_sqlite.py b/python_modules/jupedsim/jupedsim/trajectory_writer_sqlite.py index 84cb58c95e..494c302027 100644 --- a/python_modules/jupedsim/jupedsim/trajectory_writer_sqlite.py +++ b/python_modules/jupedsim/jupedsim/trajectory_writer_sqlite.py @@ -2,9 +2,8 @@ # SPDX-License-Identifier: LGPL-3.0-or-later import sqlite3 from pathlib import Path -from typing import Optional -from jupedsim import Simulation +from jupedsim.native.simulation import Simulation from jupedsim.serialization import TrajectoryWriter diff --git a/python_modules/jupedsim/jupedsim/util.py b/python_modules/jupedsim/jupedsim/util.py index bbc5186d6a..96fd36de0d 100644 --- a/python_modules/jupedsim/jupedsim/util.py +++ b/python_modules/jupedsim/jupedsim/util.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: LGPL-3.0-or-later import shapely -from jupedsim import GeometryBuilder +from jupedsim.native.geometry import GeometryBuilder def build_jps_geometry(geo: shapely.GeometryCollection): @@ -15,5 +15,5 @@ def build_jps_geometry(geo: shapely.GeometryCollection): ) geo_builder.add_accessible_area(obj.exterior.coords[:-1]) for hole in obj.interiors: - geo_builder.exclude_from_accssible_area(hole.coords[:-1]) + geo_builder.exclude_from_accessible_area(hole.coords[:-1]) return geo_builder.build() diff --git a/systemtest/test_py_jupedsim.py b/systemtest/test_py_jupedsim.py index 1bfbe98b79..ae3cc90a83 100644 --- a/systemtest/test_py_jupedsim.py +++ b/systemtest/test_py_jupedsim.py @@ -129,7 +129,7 @@ def log_msg_handler(msg): assert simulation.remove_agent(agent_id) for actual, expected in zip(simulation.agents(), initial_agent_positions): - assert actual.position == jps.Point(expected) + assert actual.position == expected while simulation.agent_count() > 0: simulation.iterate() @@ -217,12 +217,12 @@ def log_msg_handler(msg): assert simulation.remove_agent(agent_id) for actual, expected in zip(simulation.agents(), initial_agent_positions): - assert actual.position == jps.Point(expected) + assert actual.position == expected while simulation.agent_count() > 0: simulation.iterate() if simulation.iteration_count() == 1000: - waiting_set.state = jps.WaitingSetState.Inactive + waiting_set.state = jps.WaitingSetState.INACTIVE def test_can_change_journey_while_waiting(): @@ -310,6 +310,6 @@ def log_msg_handler(msg): redirect_once = False if signal_once and simulation.agents_in_range((60, 60), 1): - stage.state = jps.WaitingSetState.Inactive + stage.state = jps.WaitingSetState.INACTIVE signal_once = False simulation.iterate()