From d50778c6f0c984096863135ec4a0658fc09a8888 Mon Sep 17 00:00:00 2001 From: Ozaq Date: Thu, 7 Sep 2023 13:40:48 +0200 Subject: [PATCH] Wrap byton mbindings to C-API again in Python We want to provide the best possible user experience when using jupedsim. Therfore we want to provide all docstrings from the python layer itself. Further this allows us to extend the Simulation type for example with more custom functionality only present in python, e.g. builtin serialisation or working directly with shaply types. Co-authored-by: schroedtert Co-authored-by: Christian Hirt --- examples/example1.py | 12 +- examples/example2.py | 8 +- examples/example3.py | 6 +- examples/example4.py | 6 +- performancetest/grosser_stern.py | 10 +- performancetest/large_street_network.py | 14 +- .../bindings_jupedsim.cpp | 18 +- python_modules/jupedsim/jupedsim/__init__.py | 95 +++++- .../jupedsim/jupedsim/distributions.py | 2 +- .../jupedsim/jupedsim/{ => internal}/aabb.py | 0 .../jupedsim/jupedsim/{ => internal}/grid.py | 0 .../jupedsim/jupedsim/native/agent.py | 48 +++ .../jupedsim/jupedsim/native/geometry.py | 45 +++ .../jupedsim/jupedsim/native/journey.py | 17 + .../jupedsim/jupedsim/native/library.py | 87 +++++ .../jupedsim/jupedsim/native/models.py | 311 ++++++++++++++++++ .../jupedsim/jupedsim/native/routing.py | 29 ++ .../jupedsim/jupedsim/native/simulation.py | 114 +++++++ .../jupedsim/jupedsim/native/stages.py | 65 ++++ .../jupedsim/jupedsim/native/tracing.py | 31 ++ python_modules/jupedsim/jupedsim/recording.py | 2 +- .../jupedsim/jupedsim/serialization.py | 4 +- .../jupedsim/trajectory_writer_sqlite.py | 3 +- python_modules/jupedsim/jupedsim/util.py | 4 +- systemtest/test_py_jupedsim.py | 8 +- 25 files changed, 875 insertions(+), 64 deletions(-) rename python_modules/jupedsim/jupedsim/{ => internal}/aabb.py (100%) rename python_modules/jupedsim/jupedsim/{ => internal}/grid.py (100%) create mode 100644 python_modules/jupedsim/jupedsim/native/agent.py create mode 100644 python_modules/jupedsim/jupedsim/native/geometry.py create mode 100644 python_modules/jupedsim/jupedsim/native/journey.py create mode 100644 python_modules/jupedsim/jupedsim/native/library.py create mode 100644 python_modules/jupedsim/jupedsim/native/models.py create mode 100644 python_modules/jupedsim/jupedsim/native/routing.py create mode 100644 python_modules/jupedsim/jupedsim/native/simulation.py create mode 100644 python_modules/jupedsim/jupedsim/native/stages.py create mode 100644 python_modules/jupedsim/jupedsim/native/tracing.py 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()