cooptools 1.74__tar.gz → 1.76__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cooptools-1.74 → cooptools-1.76}/PKG-INFO +1 -1
- {cooptools-1.74 → cooptools-1.76}/cooptools/common.py +9 -5
- {cooptools-1.74 → cooptools-1.76}/cooptools/geometry_utils/vector_utils.py +39 -5
- {cooptools-1.74 → cooptools-1.76}/cooptools/graphs/graph.py +13 -3
- {cooptools-1.74 → cooptools-1.76}/cooptools/graphs/graph_dcs.py +6 -1
- {cooptools-1.74 → cooptools-1.76}/cooptools/sectors/sectorTree/sectorTree.py +8 -4
- {cooptools-1.74 → cooptools-1.76}/cooptools.egg-info/PKG-INFO +1 -1
- {cooptools-1.74 → cooptools-1.76}/cooptools.egg-info/SOURCES.txt +1 -0
- {cooptools-1.74 → cooptools-1.76}/setup.py +1 -1
- cooptools-1.76/tests/test_graph_node_classification.py +142 -0
- {cooptools-1.74 → cooptools-1.76}/MANIFEST.in +0 -0
- {cooptools-1.74 → cooptools-1.76}/README.md +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/__init__.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/anchor.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/asyncable.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/cli/CliAtomicUserInteraction.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/cli/CliMenu.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/cli/__init__.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/cli/fileContentReturn.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/cnxn_info.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/colors.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/commandDesignPattern/__init__.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/commandDesignPattern/commandController.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/commandDesignPattern/commandProtocol.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/commandDesignPattern/exceptions.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/config.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/coopDataclass.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/coopEnum.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/coopthreading.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/currency.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/dataRefresher/__init__.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/dataRefresher/dataHub.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/dataRefresher/dataRefresher.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/dataStore/__init__.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/dataStore/dataProcessor.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/dataStore/dataStoreProtocol.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/dataStore/dbConnectionURI.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/dataStore/inMemoryDataStore.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/date_utils.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/decay.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/decor.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/dictPolicies.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/exceptions.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/expertise/__init__.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/expertise/expertiseArgs.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/expertise/expertiseSchedules.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/finance/__init__.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/finance/dicounted_cashflow_analysis.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/finance/fund_projection.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/finance/fund_projection_renderer.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/finance/futureValueProjector.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/finance/growth_projections.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/finance/reports.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/finance/utils.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/geometry_utils/__init__.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/geometry_utils/circle_utils.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/geometry_utils/common.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/geometry_utils/curve_utils.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/geometry_utils/curves.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/geometry_utils/line_utils.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/geometry_utils/polygon_utils.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/geometry_utils/rect_utils.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/geometry_utils/triangle_utils.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/graphs/__init__.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/graphs/astar_results_viewer.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/graphs/draw.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/graphs/flow_field.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/graphs/graph_definitions.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/graphs/utils.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/ideas/__init__.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/ideas/actionItemTracker/__init__.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/ideas/actionItemTracker/ai_tracker.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/ideas/actionItemTracker/dcs.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/ideas/discreteEventSimulator/__init__.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/ideas/discreteEventSimulator/discreteEventSimulator.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/ideas/jsonIO.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/ideas/metrics.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/ideas/triggerActionSystem/__init__.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/ideas/triggerActionSystem/signal.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/ideas/triggerActionSystem/trigger.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/loggingHelpers.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/marchingSquares.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/materialHandling/__init__.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/materialHandling/cli.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/materialHandling/dcs.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/materialHandling/selectionCriteria.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/matrixManipulation.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/os_manip.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/pandasHelpers.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/pathing/__init__.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/pathing/catmullRomFollower.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/pathing/demo/__init__.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/pathing/demo/server.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/pathing/kinematicGoalSeeker.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/pathing/motion_profile_utils.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/pathing/orbit_fuzzer.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/pathing/path_utils.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/pathing/straightLineFollower.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/pathing/trajectory_provider.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/pathing/waypoint.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/pathing/waypointFollower.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/physics/__init__.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/physics/kinematic.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/physics/kinematic_utils.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/physics/movement.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/physics/trajectory.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/plotting.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/printing.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/protocols.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/qualifiers/__init__.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/qualifiers/cli.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/qualifiers/qualifier.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/randoms.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/register.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/reservation/__init__.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/reservation/dcs.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/reservation/enums.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/reservation/reservationmanager.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/retry.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/sectors/__init__.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/sectors/grids/__init__.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/sectors/grids/gridState.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/sectors/grids/grid_base.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/sectors/grids/hexGrid.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/sectors/grids/rectGrid.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/sectors/hex_utils.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/sectors/sect_utils.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/sectors/sectorTree/__init__.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/statistics/__init__.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/statistics/activityReport.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/statistics/controlChart/__init__.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/statistics/controlChart/controlChart.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/statistics/controlChart/plotting.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/statistics/oee/__init__.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/statistics/oee/oeeHub.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/taskProcessing/__init__.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/taskProcessing/dcs.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/taskProcessing/taskProcessor.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/timeTracker/__init__.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/timeTracker/decay.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/timeTracker/timeTracker.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/timeWindow.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/toggles.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/transform.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/trends.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/typeProviders.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/typevalidation.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools/version.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools.egg-info/dependency_links.txt +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools.egg-info/not-zip-safe +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools.egg-info/requires.txt +0 -0
- {cooptools-1.74 → cooptools-1.76}/cooptools.egg-info/top_level.txt +0 -0
- {cooptools-1.74 → cooptools-1.76}/requirements.txt +0 -0
- {cooptools-1.74 → cooptools-1.76}/setup.cfg +0 -0
- {cooptools-1.74 → cooptools-1.76}/tests/test_CliAtomicUserInteraction.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/tests/test_asyncable.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/tests/test_catmullRomFollower.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/tests/test_cdp_bugs.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/tests/test_commandDesignPattern.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/tests/test_common.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/tests/test_config.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/tests/test_coopEnum.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/tests/test_currency.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/tests/test_curves.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/tests/test_date_utils.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/tests/test_flow_field.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/tests/test_flow_field_perf.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/tests/test_fund_projection.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/tests/test_kinematicGoalSeeker.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/tests/test_kinematic_utils.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/tests/test_os_manip.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/tests/test_pandas.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/tests/test_path_utils.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/tests/test_printing.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/tests/test_qualifiers.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/tests/test_sectors.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/tests/test_sectors_hex.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/tests/test_sectors_sectorTree.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/tests/test_statistics.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/tests/test_straightLineFollower.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/tests/test_target_seek.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/tests/test_tasks.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/tests/test_toggles.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/tests/test_trajectory.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/tests/test_trends.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/tests/test_typevalidation.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/tests/test_vector_utils.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/tests/test_version.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/tests/test_waypointFollower.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/tests/tests_graph.py +0 -0
- {cooptools-1.74 → cooptools-1.76}/tests/tests_gridsystem.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import math
|
|
2
2
|
import uuid
|
|
3
|
-
from typing import List, Union, Tuple, Iterable, Callable, Sequence, TypeVar, Dict
|
|
3
|
+
from typing import List, Union, Tuple, Iterable, Callable, Sequence, TypeVar, Dict, Optional
|
|
4
4
|
import itertools
|
|
5
5
|
import numpy as np
|
|
6
6
|
import bisect
|
|
@@ -145,11 +145,15 @@ def verify_unique(lst: Iterable, error_msg: str = None):
|
|
|
145
145
|
def verify_len_match(iterable1,
|
|
146
146
|
iterable2,
|
|
147
147
|
error_msg: str = None,
|
|
148
|
-
block:bool = True):
|
|
149
|
-
|
|
148
|
+
block:bool = True) -> Optional[int]:
|
|
149
|
+
# Length is compared before the message is built. This sits under
|
|
150
|
+
# rect_contains_point on the sector-tree insert path, where formatting a
|
|
151
|
+
# message for a check that passes costs more than the check itself.
|
|
152
|
+
if (l1:=len(iterable1)) == len(iterable2):
|
|
153
|
+
return l1
|
|
150
154
|
|
|
151
|
-
|
|
152
|
-
verify(
|
|
155
|
+
msg = f"{iterable1} and {iterable2} do not have the same length ({len(iterable1)} vs {len(iterable2)})"
|
|
156
|
+
verify(lambda: False, msg, error_msg, block=block)
|
|
153
157
|
|
|
154
158
|
def verify_len(iterable, length: int, error_msg: str = None):
|
|
155
159
|
msg = f"{iterable} does not have len {length} ({len(iterable)})"
|
|
@@ -108,12 +108,30 @@ def vector_between(start: FloatVec,
|
|
|
108
108
|
allow_diff_lengths: bool = False
|
|
109
109
|
) -> FloatVec:
|
|
110
110
|
if not allow_diff_lengths:
|
|
111
|
-
verify_len_match(start, end)
|
|
111
|
+
l_start = verify_len_match(start, end)
|
|
112
|
+
l_end = l_start
|
|
113
|
+
else:
|
|
114
|
+
l_start, l_end = len(start), len(end)
|
|
115
|
+
|
|
116
|
+
# 2- and 3-D vectors are most of the traffic here, and for them the loop
|
|
117
|
+
# below costs far more than the subtractions it performs: a range, two
|
|
118
|
+
# bounds-checked conditionals per axis, a list append per axis, and a
|
|
119
|
+
# tuple() at the end. Naming the axes skips all of it.
|
|
120
|
+
#
|
|
121
|
+
# Equal lengths are part of the condition because that is the only case in
|
|
122
|
+
# which the loop's padding cannot apply; an unequal pair falls through and
|
|
123
|
+
# is padded or truncated exactly as before.
|
|
124
|
+
if l_start == l_end == 2:
|
|
125
|
+
return (end[0] - start[0], end[1] - start[1])
|
|
126
|
+
|
|
127
|
+
if l_start == l_end == 3:
|
|
128
|
+
return (end[0] - start[0], end[1] - start[1], end[2] - start[2])
|
|
112
129
|
|
|
113
130
|
ret = []
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
131
|
+
|
|
132
|
+
for idx in range(l_start):
|
|
133
|
+
e = end[idx] if idx < l_end else 0
|
|
134
|
+
s = start[idx] if idx < l_start else 0
|
|
117
135
|
|
|
118
136
|
ret.append((e - s))
|
|
119
137
|
|
|
@@ -175,7 +193,23 @@ def scale_vector_length(a: FloatVec, scale: float) -> FloatVec:
|
|
|
175
193
|
|
|
176
194
|
|
|
177
195
|
def vector_len(a: FloatVec, squared: bool = False) -> float:
|
|
178
|
-
|
|
196
|
+
# 2- and 3-D vectors are most of the traffic here, and for them the general
|
|
197
|
+
# branch spends more on interpreter overhead than on arithmetic: a generator
|
|
198
|
+
# object plus one resumption of it per axis, driven by sum(). Naming the
|
|
199
|
+
# axes skips both.
|
|
200
|
+
#
|
|
201
|
+
# sum() applies compensated (Neumaier) summation to floats, so for three
|
|
202
|
+
# terms it can land a ULP away from the plain addition used here. That
|
|
203
|
+
# difference is accepted: a ULP sits far below the precision a caller of a
|
|
204
|
+
# vector length can meaningfully rely on.
|
|
205
|
+
len_a = len(a)
|
|
206
|
+
if len_a == 2:
|
|
207
|
+
sum_sq = a[0] * a[0] + a[1] * a[1]
|
|
208
|
+
elif len_a == 3:
|
|
209
|
+
sum_sq = a[0] * a[0] + a[1] * a[1] + a[2] * a[2]
|
|
210
|
+
else:
|
|
211
|
+
sum_sq = sum(ii * ii for ii in a)
|
|
212
|
+
|
|
179
213
|
return sum_sq if squared else math.sqrt(sum_sq)
|
|
180
214
|
|
|
181
215
|
|
|
@@ -306,12 +306,17 @@ class Graph(object):
|
|
|
306
306
|
|
|
307
307
|
@property
|
|
308
308
|
def Sources(self) -> List[Node]:
|
|
309
|
-
|
|
309
|
+
# Membership is tested once per node, so the destinations are hashed
|
|
310
|
+
# rather than scanned -- DestinationNodes hands back a list.
|
|
311
|
+
dests = set(self.DestinationNodes)
|
|
310
312
|
return [n for n, cnxn in self._graph_dict.items() if n not in dests and len(cnxn) != 0]
|
|
311
313
|
|
|
312
314
|
@property
|
|
313
315
|
def Sinks(self) -> List[Node]:
|
|
314
|
-
|
|
316
|
+
# Orphans walks the whole node-edge map, so it is built once for the
|
|
317
|
+
# scan instead of once per node.
|
|
318
|
+
orphans = set(self.Orphans)
|
|
319
|
+
return [n for n, cnxn in self._graph_dict.items() if len(cnxn) == 0 and n not in orphans]
|
|
315
320
|
|
|
316
321
|
@property
|
|
317
322
|
def Orphans(self) -> List[Node]:
|
|
@@ -734,6 +739,11 @@ class Graph(object):
|
|
|
734
739
|
if not ignored_disablers:
|
|
735
740
|
ignored_disablers = []
|
|
736
741
|
|
|
742
|
+
# Hoisted out of the expansion loop below, which tests it once per edge
|
|
743
|
+
# considered -- millions of times on a large graph. Edge.enabled needs a
|
|
744
|
+
# set, so building one here keeps that per-edge cost to a lookup.
|
|
745
|
+
_ignored_disablers_set = set(ignored_disablers)
|
|
746
|
+
|
|
737
747
|
logger.debug(f"Performing A* over map of length: {len(self._graph_dict)}")
|
|
738
748
|
|
|
739
749
|
# Create start and end node
|
|
@@ -816,7 +826,7 @@ class Graph(object):
|
|
|
816
826
|
)
|
|
817
827
|
|
|
818
828
|
# Dont evaluate this node if the edge is not enabled
|
|
819
|
-
enabled_edges = [x for x in edges if x.enabled(ignored_disablers=
|
|
829
|
+
enabled_edges = [x for x in edges if x.enabled(ignored_disablers=_ignored_disablers_set)]
|
|
820
830
|
if len(enabled_edges) == 0:
|
|
821
831
|
continue
|
|
822
832
|
|
|
@@ -165,8 +165,13 @@ class Edge(object):
|
|
|
165
165
|
return self.length
|
|
166
166
|
|
|
167
167
|
def enabled(self, ignored_disablers: set = None):
|
|
168
|
+
# An edge with no disablers is enabled whatever the caller ignores, and
|
|
169
|
+
# that is the overwhelmingly common case on a pathfinding hot loop --
|
|
170
|
+
# answering it first avoids allocating an empty set per call.
|
|
171
|
+
if not self._disablers:
|
|
172
|
+
return True
|
|
168
173
|
if ignored_disablers is None:
|
|
169
|
-
|
|
174
|
+
return False
|
|
170
175
|
return self._disablers.issubset(ignored_disablers)
|
|
171
176
|
|
|
172
177
|
|
|
@@ -161,10 +161,14 @@ class SectorTree:
|
|
|
161
161
|
for client, type in clients.items():
|
|
162
162
|
ii += 1
|
|
163
163
|
if self.lvl == 0:
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
164
|
+
# Whether this client is already known is read from
|
|
165
|
+
# _last_mapped_pos -- the same dict remove_clients treats as the
|
|
166
|
+
# membership record -- because it answers in constant time. The
|
|
167
|
+
# Clients property flattens every sector mapping in the tree, so
|
|
168
|
+
# consulting it once per client made registering a batch
|
|
169
|
+
# quadratic in the size of the batch.
|
|
170
|
+
verb = "updating" if client in self._last_mapped_pos else "adding"
|
|
171
|
+
logger.info(f"[{self._id}] User requests {verb} {ii}/{len(clients)} [{client}]")
|
|
168
172
|
|
|
169
173
|
if not client.__hash__:
|
|
170
174
|
raise Exception(f"Client {client} must be hashable, but type {type(client)} is not")
|
|
@@ -163,6 +163,7 @@ tests/test_date_utils.py
|
|
|
163
163
|
tests/test_flow_field.py
|
|
164
164
|
tests/test_flow_field_perf.py
|
|
165
165
|
tests/test_fund_projection.py
|
|
166
|
+
tests/test_graph_node_classification.py
|
|
166
167
|
tests/test_kinematicGoalSeeker.py
|
|
167
168
|
tests/test_kinematic_utils.py
|
|
168
169
|
tests/test_os_manip.py
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
|
|
3
|
+
from cooptools.common import verify_len_match
|
|
4
|
+
from cooptools.graphs import Graph, Node
|
|
5
|
+
from cooptools.graphs.graph_dcs import Edge
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TestNodeClassification(unittest.TestCase):
|
|
9
|
+
"""Sources/Sinks/Orphans partition a graph's nodes by their edge directions."""
|
|
10
|
+
|
|
11
|
+
@staticmethod
|
|
12
|
+
def _graph():
|
|
13
|
+
# a -> b -> c (a is a source, c is a sink)
|
|
14
|
+
# d (no edges at all -- an orphan)
|
|
15
|
+
a = Node(name='A', pos=(0.0, 0.0))
|
|
16
|
+
b = Node(name='B', pos=(1.0, 0.0))
|
|
17
|
+
c = Node(name='C', pos=(2.0, 0.0))
|
|
18
|
+
d = Node(name='D', pos=(9.0, 9.0))
|
|
19
|
+
return Graph({a: [b], b: [c], c: [], d: []})
|
|
20
|
+
|
|
21
|
+
def test_Sources_returns_only_nodes_nothing_points_at(self):
|
|
22
|
+
self.assertEqual(['A'], [n.name for n in self._graph().Sources])
|
|
23
|
+
|
|
24
|
+
def test_Sinks_returns_connected_nodes_with_no_outbound_edge(self):
|
|
25
|
+
self.assertEqual(['C'], [n.name for n in self._graph().Sinks])
|
|
26
|
+
|
|
27
|
+
def test_Sinks_excludes_orphans(self):
|
|
28
|
+
# D has no edges in either direction, so it is an orphan rather than a
|
|
29
|
+
# sink -- the two categories must not both claim it.
|
|
30
|
+
graph = self._graph()
|
|
31
|
+
self.assertNotIn('D', [n.name for n in graph.Sinks])
|
|
32
|
+
self.assertIn('D', [n.name for n in graph.Orphans])
|
|
33
|
+
|
|
34
|
+
def test_Orphans_returns_only_wholly_unconnected_nodes(self):
|
|
35
|
+
self.assertEqual(['D'], [n.name for n in self._graph().Orphans])
|
|
36
|
+
|
|
37
|
+
def test_a_node_is_never_both_a_source_and_a_sink(self):
|
|
38
|
+
graph = self._graph()
|
|
39
|
+
sources = {n.name for n in graph.Sources}
|
|
40
|
+
sinks = {n.name for n in graph.Sinks}
|
|
41
|
+
self.assertEqual(set(), sources & sinks)
|
|
42
|
+
|
|
43
|
+
def test_fully_connected_cycle_has_no_sources_sinks_or_orphans(self):
|
|
44
|
+
a = Node(name='A', pos=(0.0, 0.0))
|
|
45
|
+
b = Node(name='B', pos=(1.0, 0.0))
|
|
46
|
+
graph = Graph({a: [b], b: [a]})
|
|
47
|
+
self.assertEqual([], graph.Sources)
|
|
48
|
+
self.assertEqual([], graph.Sinks)
|
|
49
|
+
self.assertEqual([], graph.Orphans)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class TestEdgeEnabled(unittest.TestCase):
|
|
53
|
+
"""An edge is enabled when every disabler on it is one the caller ignores."""
|
|
54
|
+
|
|
55
|
+
@staticmethod
|
|
56
|
+
def _edge(disablers=None):
|
|
57
|
+
return Edge(Node('A', (0.0, 0.0)), Node('B', (1.0, 1.0)), disablers=disablers)
|
|
58
|
+
|
|
59
|
+
def test_edge_without_disablers_is_enabled_when_nothing_is_ignored(self):
|
|
60
|
+
self.assertTrue(self._edge().enabled(None))
|
|
61
|
+
|
|
62
|
+
def test_edge_without_disablers_is_enabled_whatever_is_ignored(self):
|
|
63
|
+
self.assertTrue(self._edge().enabled({'anything'}))
|
|
64
|
+
|
|
65
|
+
def test_disabled_edge_is_not_enabled_when_nothing_is_ignored(self):
|
|
66
|
+
self.assertFalse(self._edge(['d1']).enabled(None))
|
|
67
|
+
self.assertFalse(self._edge(['d1']).enabled(set()))
|
|
68
|
+
|
|
69
|
+
def test_disabled_edge_is_enabled_once_its_disabler_is_ignored(self):
|
|
70
|
+
self.assertTrue(self._edge(['d1']).enabled({'d1'}))
|
|
71
|
+
|
|
72
|
+
def test_edge_needs_every_one_of_its_disablers_ignored(self):
|
|
73
|
+
edge = self._edge(['d1', 'd2'])
|
|
74
|
+
self.assertFalse(edge.enabled({'d1'}))
|
|
75
|
+
self.assertTrue(edge.enabled({'d1', 'd2'}))
|
|
76
|
+
|
|
77
|
+
def test_ignoring_an_unrelated_disabler_does_not_enable_the_edge(self):
|
|
78
|
+
self.assertFalse(self._edge(['d1']).enabled({'other'}))
|
|
79
|
+
|
|
80
|
+
def test_ignored_disablers_may_be_any_iterable(self):
|
|
81
|
+
self.assertTrue(self._edge(['d1']).enabled(['d1']))
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class TestAStarIgnoredDisablers(unittest.TestCase):
|
|
85
|
+
"""A* may only traverse a disabled edge when told to ignore its disabler."""
|
|
86
|
+
|
|
87
|
+
@staticmethod
|
|
88
|
+
def _graph_with_a_disabled_shortcut():
|
|
89
|
+
# A -> B is the only route; disabling it makes the graph unroutable.
|
|
90
|
+
a = Node(name='A', pos=(0.0, 0.0))
|
|
91
|
+
b = Node(name='B', pos=(1.0, 0.0))
|
|
92
|
+
graph = Graph({a: [b], b: []})
|
|
93
|
+
for edge in graph.Edges.values():
|
|
94
|
+
edge.add_disabler('BLOCKED')
|
|
95
|
+
return graph, a, b
|
|
96
|
+
|
|
97
|
+
def test_route_is_not_found_across_a_disabled_edge(self):
|
|
98
|
+
graph, a, b = self._graph_with_a_disabled_shortcut()
|
|
99
|
+
self.assertFalse(graph.astar(a, b).Success)
|
|
100
|
+
|
|
101
|
+
def test_route_is_found_when_the_disabler_is_ignored(self):
|
|
102
|
+
graph, a, b = self._graph_with_a_disabled_shortcut()
|
|
103
|
+
results = graph.astar(a, b, ignored_disablers=frozenset({'BLOCKED'}))
|
|
104
|
+
self.assertTrue(results.Success)
|
|
105
|
+
self.assertEqual(['A', 'B'], [n.name for n in results.nodes])
|
|
106
|
+
|
|
107
|
+
def test_ignored_disablers_accepts_a_list_as_well_as_a_set(self):
|
|
108
|
+
graph, a, b = self._graph_with_a_disabled_shortcut()
|
|
109
|
+
self.assertTrue(graph.astar(a, b, ignored_disablers=['BLOCKED']).Success)
|
|
110
|
+
|
|
111
|
+
def test_ignoring_an_unrelated_disabler_leaves_the_edge_closed(self):
|
|
112
|
+
graph, a, b = self._graph_with_a_disabled_shortcut()
|
|
113
|
+
self.assertFalse(graph.astar(a, b, ignored_disablers=frozenset({'SOMETHING_ELSE'})).Success)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class TestVerifyLenMatch(unittest.TestCase):
|
|
117
|
+
|
|
118
|
+
def test_matching_lengths_return_the_shared_length(self):
|
|
119
|
+
# The length is handed back so callers that need it -- vector_between
|
|
120
|
+
# being the reason -- do not have to measure the operands again.
|
|
121
|
+
self.assertEqual(2, verify_len_match((1, 2), (3, 4)))
|
|
122
|
+
|
|
123
|
+
def test_two_empties_match_and_report_zero(self):
|
|
124
|
+
self.assertEqual(0, verify_len_match((), ()))
|
|
125
|
+
|
|
126
|
+
def test_mismatch_returns_nothing_when_not_blocking(self):
|
|
127
|
+
self.assertIsNone(verify_len_match((1, 2, 3), (4, 5), block=False))
|
|
128
|
+
|
|
129
|
+
def test_mismatched_lengths_raise(self):
|
|
130
|
+
with self.assertRaises(ValueError):
|
|
131
|
+
verify_len_match((1, 2, 3), (4, 5))
|
|
132
|
+
|
|
133
|
+
def test_the_error_reports_both_lengths(self):
|
|
134
|
+
with self.assertRaises(ValueError) as ctx:
|
|
135
|
+
verify_len_match((1, 2, 3), (4, 5))
|
|
136
|
+
self.assertIn('3', str(ctx.exception))
|
|
137
|
+
self.assertIn('2', str(ctx.exception))
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
if __name__ == '__main__':
|
|
142
|
+
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cooptools-1.74 → cooptools-1.76}/cooptools/ideas/discreteEventSimulator/discreteEventSimulator.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|