mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-08-21 13:33:47 +08:00
Merge branch 'master' into dev-priv/master
# Conflicts: # selfdrive/controls/lib/longitudinal_planner.py # selfdrive/controls/plannerd.py # selfdrive/ui/ui.cc
This commit is contained in:
@@ -212,6 +212,7 @@ jobs:
|
||||
$UNIT_TEST selfdrive/athena && \
|
||||
$UNIT_TEST selfdrive/thermald && \
|
||||
$UNIT_TEST system/hardware/tici && \
|
||||
$UNIT_TEST selfdrive/mapd && \
|
||||
$UNIT_TEST tools/lib/tests && \
|
||||
./selfdrive/ui/tests/create_test_translations.sh && \
|
||||
QT_QPA_PLATFORM=offscreen ./selfdrive/ui/tests/test_translations && \
|
||||
|
||||
+1
-1
@@ -84,7 +84,7 @@ function launch {
|
||||
|
||||
# start manager
|
||||
cd selfdrive/manager
|
||||
./build.py && ./manager.py
|
||||
./custom_dep.py && ./build.py && ./manager.py
|
||||
|
||||
# if broken, keep on screen error
|
||||
while true; do sleep 1; done
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.6 KiB |
@@ -10,6 +10,13 @@ from opendbc.can.parser import CANParser
|
||||
from selfdrive.car.interfaces import CarStateBase
|
||||
from selfdrive.car.toyota.values import ToyotaFlags, CAR, DBC, STEER_THRESHOLD, NO_STOP_TIMER_CAR, TSS2_CAR, RADAR_ACC_CAR, EPS_SCALE, UNSUPPORTED_DSU_CAR
|
||||
|
||||
_TRAFFIC_SINGAL_MAP = {
|
||||
1: "kph",
|
||||
36: "mph",
|
||||
65: "No overtake",
|
||||
66: "No overtake"
|
||||
}
|
||||
|
||||
|
||||
class CarState(CarStateBase):
|
||||
def __init__(self, CP):
|
||||
@@ -25,6 +32,7 @@ class CarState(CarStateBase):
|
||||
# Need to apply an offset as soon as the steering angle measurements are both received
|
||||
self.accurate_steer_angle_seen = False
|
||||
self.angle_offset = FirstOrderFilter(None, 60.0, DT_CTRL, initialized=False)
|
||||
self._init_traffic_signals()
|
||||
|
||||
self.low_speed_lockout = False
|
||||
self.acc_type = 1
|
||||
@@ -171,8 +179,89 @@ class CarState(CarStateBase):
|
||||
if self.CP.carFingerprint != CAR.PRIUS_V:
|
||||
self.lkas_hud = copy.copy(cp_cam.vl["LKAS_HUD"])
|
||||
|
||||
self._update_traffic_signals(cp_cam)
|
||||
ret.cruiseState.speedLimit = self._calculate_speed_limit()
|
||||
|
||||
return ret
|
||||
|
||||
def _init_traffic_signals(self):
|
||||
self._tsgn1 = None
|
||||
self._spdval1 = None
|
||||
self._splsgn1 = None
|
||||
self._tsgn2 = None
|
||||
self._splsgn2 = None
|
||||
self._tsgn3 = None
|
||||
self._splsgn3 = None
|
||||
self._tsgn4 = None
|
||||
self._splsgn4 = None
|
||||
|
||||
def _update_traffic_signals(self, cp_cam):
|
||||
# Print out car signals for traffic signal detection
|
||||
tsgn1 = cp_cam.vl["RSA1"]['TSGN1']
|
||||
spdval1 = cp_cam.vl["RSA1"]['SPDVAL1']
|
||||
splsgn1 = cp_cam.vl["RSA1"]['SPLSGN1']
|
||||
tsgn2 = cp_cam.vl["RSA1"]['TSGN2']
|
||||
splsgn2 = cp_cam.vl["RSA1"]['SPLSGN2']
|
||||
tsgn3 = cp_cam.vl["RSA2"]['TSGN3']
|
||||
splsgn3 = cp_cam.vl["RSA2"]['SPLSGN3']
|
||||
tsgn4 = cp_cam.vl["RSA2"]['TSGN4']
|
||||
splsgn4 = cp_cam.vl["RSA2"]['SPLSGN4']
|
||||
|
||||
has_changed = tsgn1 != self._tsgn1 \
|
||||
or spdval1 != self._spdval1 \
|
||||
or splsgn1 != self._splsgn1 \
|
||||
or tsgn2 != self._tsgn2 \
|
||||
or splsgn2 != self._splsgn2 \
|
||||
or tsgn3 != self._tsgn3 \
|
||||
or splsgn3 != self._splsgn3 \
|
||||
or tsgn4 != self._tsgn4 \
|
||||
or splsgn4 != self._splsgn4
|
||||
|
||||
self._tsgn1 = tsgn1
|
||||
self._spdval1 = spdval1
|
||||
self._splsgn1 = splsgn1
|
||||
self._tsgn2 = tsgn2
|
||||
self._splsgn2 = splsgn2
|
||||
self._tsgn3 = tsgn3
|
||||
self._splsgn3 = splsgn3
|
||||
self._tsgn4 = tsgn4
|
||||
self._splsgn4 = splsgn4
|
||||
|
||||
if not has_changed:
|
||||
return
|
||||
|
||||
print('---- TRAFFIC SIGNAL UPDATE -----')
|
||||
if tsgn1 is not None and tsgn1 != 0:
|
||||
print(f'TSGN1: {self._traffic_signal_description(tsgn1)}')
|
||||
if spdval1 is not None and spdval1 != 0:
|
||||
print(f'SPDVAL1: {spdval1}')
|
||||
if splsgn1 is not None and splsgn1 != 0:
|
||||
print(f'SPLSGN1: {splsgn1}')
|
||||
if tsgn2 is not None and tsgn2 != 0:
|
||||
print(f'TSGN2: {self._traffic_signal_description(tsgn2)}')
|
||||
if splsgn2 is not None and splsgn2 != 0:
|
||||
print(f'SPLSGN2: {splsgn2}')
|
||||
if tsgn3 is not None and tsgn3 != 0:
|
||||
print(f'TSGN3: {self._traffic_signal_description(tsgn3)}')
|
||||
if splsgn3 is not None and splsgn3 != 0:
|
||||
print(f'SPLSGN3: {splsgn3}')
|
||||
if tsgn4 is not None and tsgn4 != 0:
|
||||
print(f'TSGN4: {self._traffic_signal_description(tsgn4)}')
|
||||
if splsgn4 is not None and splsgn4 != 0:
|
||||
print(f'SPLSGN4: {splsgn4}')
|
||||
print('------------------------')
|
||||
|
||||
def _traffic_signal_description(self, tsgn):
|
||||
desc = _TRAFFIC_SINGAL_MAP.get(int(tsgn))
|
||||
return f'{tsgn}: {desc}' if desc is not None else f'{tsgn}'
|
||||
|
||||
def _calculate_speed_limit(self):
|
||||
if self._tsgn1 == 1:
|
||||
return self._spdval1 * CV.KPH_TO_MS
|
||||
if self._tsgn1 == 36:
|
||||
return self._spdval1 * CV.MPH_TO_MS
|
||||
return 0
|
||||
|
||||
@staticmethod
|
||||
def get_can_parser(CP):
|
||||
signals = [
|
||||
@@ -286,6 +375,24 @@ class CarState(CarStateBase):
|
||||
signals = []
|
||||
checks = []
|
||||
|
||||
# Include traffic signal signals.
|
||||
signals += [
|
||||
("TSGN1", "RSA1", 0),
|
||||
("SPDVAL1", "RSA1", 0),
|
||||
("SPLSGN1", "RSA1", 0),
|
||||
("TSGN2", "RSA1", 0),
|
||||
("SPLSGN2", "RSA1", 0),
|
||||
("TSGN3", "RSA2", 0),
|
||||
("SPLSGN3", "RSA2", 0),
|
||||
("TSGN4", "RSA2", 0),
|
||||
("SPLSGN4", "RSA2", 0),
|
||||
]
|
||||
|
||||
checks += [
|
||||
("RSA1", 0),
|
||||
("RSA2", 0),
|
||||
]
|
||||
|
||||
if CP.carFingerprint != CAR.PRIUS_V:
|
||||
signals += [
|
||||
("LANE_SWAY_FLD", "LKAS_HUD"),
|
||||
|
||||
@@ -276,6 +276,7 @@ class Controls:
|
||||
|
||||
if not self.CP.notCar:
|
||||
self.events.add_from_msg(self.sm['driverMonitoringState'].events)
|
||||
self.events.add_from_msg(self.sm['longitudinalPlan'].eventsDEPRECATED)
|
||||
|
||||
# Add car events, ignore if CAN isn't valid
|
||||
if CS.canValid:
|
||||
|
||||
@@ -37,6 +37,14 @@ CRUISE_INTERVAL_SIGN = {
|
||||
ButtonType.decelCruise: -1,
|
||||
}
|
||||
|
||||
# Constants for Limit controllers.
|
||||
LIMIT_ADAPT_ACC = -1. # m/s^2 Ideal acceleration for the adapting (braking) phase when approaching speed limits.
|
||||
LIMIT_MIN_ACC = -1.5 # m/s^2 Maximum deceleration allowed for limit controllers to provide.
|
||||
LIMIT_MAX_ACC = 1.0 # m/s^2 Maximum acceleration allowed for limit controllers to provide while active.
|
||||
LIMIT_MIN_SPEED = 8.33 # m/s, Minimum speed limit to provide as solution on limit controllers.
|
||||
LIMIT_SPEED_OFFSET_TH = -1. # m/s Maximum offset between speed limit and current speed for adapting state.
|
||||
LIMIT_MAX_MAP_DATA_AGE = 10. # s Maximum time to hold to map data, then consider it invalid inside limits controllers.
|
||||
|
||||
|
||||
class VCruiseHelper:
|
||||
def __init__(self, CP):
|
||||
|
||||
@@ -329,6 +329,16 @@ def joystick_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster,
|
||||
vals = f"Gas: {round(gb * 100.)}%, Steer: {round(steer * 100.)}%"
|
||||
return NormalPermanentAlert("Joystick Mode", vals)
|
||||
|
||||
def speed_limit_adjust_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert:
|
||||
speedLimit = sm['longitudinalPlan'].speedLimit
|
||||
speed = round(speedLimit * (CV.MS_TO_KPH if metric else CV.MS_TO_MPH))
|
||||
message = f'Adjusting to {speed} {"km/h" if metric else "mph"} speed limit'
|
||||
return Alert(
|
||||
message,
|
||||
"",
|
||||
AlertStatus.normal, AlertSize.small,
|
||||
Priority.LOW, VisualAlert.none, AudibleAlert.none, 4.)
|
||||
|
||||
|
||||
|
||||
EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = {
|
||||
@@ -490,6 +500,26 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = {
|
||||
Priority.HIGH, VisualAlert.steerRequired, AudibleAlert.warningImmediate, .1),
|
||||
},
|
||||
|
||||
EventName.preKeepHandsOnWheel: {
|
||||
ET.WARNING: Alert(
|
||||
"No hands on steering wheel detected",
|
||||
"",
|
||||
AlertStatus.userPrompt, AlertSize.small,
|
||||
Priority.MID, VisualAlert.steerRequired, AudibleAlert.none, .1, alert_rate=0.75),
|
||||
},
|
||||
|
||||
EventName.promptKeepHandsOnWheel: {
|
||||
ET.WARNING: Alert(
|
||||
"HANDS OFF STEERING WHEEL",
|
||||
"Place hands on steering wheel",
|
||||
AlertStatus.critical, AlertSize.mid,
|
||||
Priority.MID, VisualAlert.steerRequired, AudibleAlert.promptDistracted, .1),
|
||||
},
|
||||
|
||||
EventName.keepHandsOnWheel: {
|
||||
ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Driver kept hands off sterring wheel"),
|
||||
},
|
||||
|
||||
EventName.manualRestart: {
|
||||
ET.WARNING: Alert(
|
||||
"TAKE CONTROL",
|
||||
@@ -604,6 +634,18 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = {
|
||||
# ET.PERMANENT: NormalPermanentAlert("Sensor Malfunction", "Hardware Malfunction"),
|
||||
},
|
||||
|
||||
EventName.speedLimitActive: {
|
||||
ET.WARNING: Alert(
|
||||
"Cruise set to speed limit",
|
||||
"",
|
||||
AlertStatus.normal, AlertSize.small,
|
||||
Priority.LOW, VisualAlert.none, AudibleAlert.none, 2.),
|
||||
},
|
||||
|
||||
EventName.speedLimitValueChange: {
|
||||
ET.WARNING: speed_limit_adjust_alert,
|
||||
},
|
||||
|
||||
# ********** events that affect controls state transitions **********
|
||||
|
||||
EventName.pcmEnable: {
|
||||
|
||||
@@ -12,6 +12,10 @@ from selfdrive.controls.lib.longcontrol import LongCtrlState
|
||||
from selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc, MIN_ACCEL, MAX_ACCEL
|
||||
from selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import T_IDXS as T_IDXS_MPC
|
||||
from selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, CONTROL_N
|
||||
from selfdrive.controls.lib.vision_turn_controller import VisionTurnController
|
||||
from selfdrive.controls.lib.speed_limit_controller import SpeedLimitController, SpeedLimitResolver
|
||||
from selfdrive.controls.lib.turn_speed_controller import TurnSpeedController
|
||||
from selfdrive.controls.lib.events import Events
|
||||
from system.swaglog import cloudlog
|
||||
|
||||
LON_MPC_STEP = 0.2 # first step is 0.2s
|
||||
@@ -58,6 +62,12 @@ class LongitudinalPlanner:
|
||||
self.j_desired_trajectory = np.zeros(CONTROL_N)
|
||||
self.solverExecutionTime = 0.0
|
||||
|
||||
self.cruise_source = 'cruise'
|
||||
self.vision_turn_controller = VisionTurnController(CP)
|
||||
self.speed_limit_controller = SpeedLimitController()
|
||||
self.events = Events()
|
||||
self.turn_speed_controller = TurnSpeedController()
|
||||
|
||||
@staticmethod
|
||||
def parse_model(model_msg, model_error):
|
||||
if (len(model_msg.position.x) == 33 and
|
||||
@@ -109,17 +119,24 @@ class LongitudinalPlanner:
|
||||
if len(sm['modelV2'].temporalPose.trans):
|
||||
self.v_model_error = sm['modelV2'].temporalPose.trans[0] - v_ego
|
||||
|
||||
accel_limits = [A_CRUISE_MIN, get_max_accel(v_ego)]
|
||||
accel_limits_turns = limit_accel_in_turns(v_ego, sm['carState'].steeringAngleDeg, accel_limits, self.CP)
|
||||
if force_slow_decel:
|
||||
v_cruise = 0.0
|
||||
|
||||
# Get acceleration and active solutions for custom long mpc.
|
||||
self.cruise_source, a_min_sol, v_cruise_sol = self.cruise_solutions(not reset_state, self.v_desired_filter.x,
|
||||
self.a_desired, v_cruise, sm)
|
||||
|
||||
# clip limits, cannot init MPC outside of bounds
|
||||
accel_limits_turns[0] = min(accel_limits_turns[0], self.a_desired + 0.05)
|
||||
accel_limits_turns[0] = min(accel_limits_turns[0], self.a_desired + 0.05, a_min_sol)
|
||||
accel_limits_turns[1] = max(accel_limits_turns[1], self.a_desired - 0.05)
|
||||
|
||||
self.mpc.set_weights(prev_accel_constraint)
|
||||
self.mpc.set_accel_limits(accel_limits_turns[0], accel_limits_turns[1])
|
||||
self.mpc.set_cur_state(self.v_desired_filter.x, self.a_desired)
|
||||
x, v, a, j = self.parse_model(sm['modelV2'], self.v_model_error)
|
||||
self.mpc.update(sm['radarState'], v_cruise, x, v, a, j)
|
||||
self.mpc.update(sm['radarState'], v_cruise_sol, x, v, a, j)
|
||||
|
||||
self.v_desired_trajectory = np.interp(T_IDXS[:CONTROL_N], T_IDXS_MPC, self.mpc.v_solution)
|
||||
self.a_desired_trajectory = np.interp(T_IDXS[:CONTROL_N], T_IDXS_MPC, self.mpc.a_solution)
|
||||
@@ -149,11 +166,53 @@ class LongitudinalPlanner:
|
||||
longitudinalPlan.jerks = self.j_desired_trajectory.tolist()
|
||||
|
||||
longitudinalPlan.hasLead = sm['radarState'].leadOne.status
|
||||
longitudinalPlan.longitudinalPlanSource = self.mpc.source
|
||||
longitudinalPlan.longitudinalPlanSource = self.mpc.source if self.mpc.source != 'cruise' else self.cruise_source
|
||||
longitudinalPlan.fcw = self.fcw
|
||||
|
||||
longitudinalPlan.solverExecutionTime = self.mpc.solve_time
|
||||
|
||||
longitudinalPlan.e2eX = self.mpc.e2e_x.tolist()
|
||||
|
||||
longitudinalPlan.visionTurnControllerState = self.vision_turn_controller.state
|
||||
longitudinalPlan.visionTurnSpeed = float(self.vision_turn_controller.v_turn)
|
||||
|
||||
longitudinalPlan.speedLimitControlState = self.speed_limit_controller.state
|
||||
longitudinalPlan.speedLimit = float(self.speed_limit_controller.speed_limit)
|
||||
longitudinalPlan.speedLimitOffset = float(self.speed_limit_controller.speed_limit_offset)
|
||||
longitudinalPlan.distToSpeedLimit = float(self.speed_limit_controller.distance)
|
||||
longitudinalPlan.isMapSpeedLimit = bool(self.speed_limit_controller.source == SpeedLimitResolver.Source.map_data)
|
||||
longitudinalPlan.eventsDEPRECATED = self.events.to_msg()
|
||||
|
||||
longitudinalPlan.turnSpeedControlState = self.turn_speed_controller.state
|
||||
longitudinalPlan.turnSpeed = float(self.turn_speed_controller.speed_limit)
|
||||
longitudinalPlan.distToTurn = float(self.turn_speed_controller.distance)
|
||||
longitudinalPlan.turnSign = int(self.turn_speed_controller.turn_sign)
|
||||
|
||||
pm.send('longitudinalPlan', plan_send)
|
||||
|
||||
def cruise_solutions(self, enabled, v_ego, a_ego, v_cruise, sm):
|
||||
# Update controllers
|
||||
self.vision_turn_controller.update(enabled, v_ego, a_ego, v_cruise, sm)
|
||||
self.events = Events()
|
||||
self.speed_limit_controller.update(enabled, v_ego, a_ego, sm, v_cruise, self.events)
|
||||
self.turn_speed_controller.update(enabled, v_ego, a_ego, sm)
|
||||
|
||||
# Pick solution with the lowest velocity target.
|
||||
a_solutions = {'cruise': float("inf")}
|
||||
v_solutions = {'cruise': v_cruise}
|
||||
|
||||
if self.vision_turn_controller.is_active:
|
||||
a_solutions['turn'] = self.vision_turn_controller.a_target
|
||||
v_solutions['turn'] = self.vision_turn_controller.v_turn
|
||||
|
||||
if self.speed_limit_controller.is_active:
|
||||
a_solutions['limit'] = self.speed_limit_controller.a_target
|
||||
v_solutions['limit'] = self.speed_limit_controller.speed_limit_offseted
|
||||
|
||||
if self.turn_speed_controller.is_active:
|
||||
a_solutions['turnlimit'] = self.turn_speed_controller.a_target
|
||||
v_solutions['turnlimit'] = self.turn_speed_controller.speed_limit
|
||||
|
||||
source = min(v_solutions, key=v_solutions.get)
|
||||
|
||||
return source, a_solutions[source], v_solutions[source]
|
||||
|
||||
@@ -0,0 +1,377 @@
|
||||
import numpy as np
|
||||
import time
|
||||
from common.numpy_fast import interp
|
||||
from enum import IntEnum
|
||||
from cereal import log, car
|
||||
from common.params import Params
|
||||
from common.realtime import sec_since_boot
|
||||
from selfdrive.controls.lib.drive_helpers import LIMIT_ADAPT_ACC, LIMIT_MIN_ACC, LIMIT_MAX_ACC, LIMIT_SPEED_OFFSET_TH, \
|
||||
LIMIT_MAX_MAP_DATA_AGE, CONTROL_N
|
||||
from selfdrive.controls.lib.events import Events
|
||||
from selfdrive.modeld.constants import T_IDXS
|
||||
|
||||
|
||||
_PARAMS_UPDATE_PERIOD = 2. # secs. Time between parameter updates.
|
||||
_TEMP_INACTIVE_GUARD_PERIOD = 1. # secs. Time to wait after activation before considering temp deactivation signal.
|
||||
|
||||
# Lookup table for speed limit percent offset depending on speed.
|
||||
_LIMIT_PERC_OFFSET_V = [0.1, 0.05, 0.038] # 55, 105, 135 km/h
|
||||
_LIMIT_PERC_OFFSET_BP = [13.9, 27.8, 36.1] # 50, 100, 130 km/h
|
||||
|
||||
SpeedLimitControlState = log.LongitudinalPlan.SpeedLimitControlState
|
||||
EventName = car.CarEvent.EventName
|
||||
|
||||
_DEBUG = False
|
||||
|
||||
|
||||
def _debug(msg):
|
||||
if not _DEBUG:
|
||||
return
|
||||
print(msg)
|
||||
|
||||
|
||||
def _description_for_state(speed_limit_control_state):
|
||||
if speed_limit_control_state == SpeedLimitControlState.inactive:
|
||||
return 'INACTIVE'
|
||||
if speed_limit_control_state == SpeedLimitControlState.tempInactive:
|
||||
return 'TEMP_INACTIVE'
|
||||
if speed_limit_control_state == SpeedLimitControlState.adapting:
|
||||
return 'ADAPTING'
|
||||
if speed_limit_control_state == SpeedLimitControlState.active:
|
||||
return 'ACTIVE'
|
||||
|
||||
|
||||
class SpeedLimitResolver():
|
||||
class Source(IntEnum):
|
||||
none = 0
|
||||
car_state = 1
|
||||
map_data = 2
|
||||
|
||||
class Policy(IntEnum):
|
||||
car_state_only = 0
|
||||
map_data_only = 1
|
||||
car_state_priority = 2
|
||||
map_data_priority = 3
|
||||
combined = 4
|
||||
|
||||
def __init__(self, policy=Policy.map_data_priority):
|
||||
self._limit_solutions = {} # Store for speed limit solutions from different sources
|
||||
self._distance_solutions = {} # Store for distance to current speed limit start for different sources
|
||||
self._v_ego = 0.
|
||||
self._current_speed_limit = 0.
|
||||
self._policy = policy
|
||||
self._next_speed_limit_prev = 0.
|
||||
self.speed_limit = 0.
|
||||
self.distance = 0.
|
||||
self.source = SpeedLimitResolver.Source.none
|
||||
|
||||
def resolve(self, v_ego, current_speed_limit, sm):
|
||||
self._v_ego = v_ego
|
||||
self._current_speed_limit = current_speed_limit
|
||||
self._sm = sm
|
||||
|
||||
self._get_from_car_state()
|
||||
self._get_from_map_data()
|
||||
self._consolidate()
|
||||
|
||||
return self.speed_limit, self.distance, self.source
|
||||
|
||||
def _get_from_car_state(self):
|
||||
self._limit_solutions[SpeedLimitResolver.Source.car_state] = self._sm['carState'].cruiseState.speedLimit
|
||||
self._distance_solutions[SpeedLimitResolver.Source.car_state] = 0.
|
||||
|
||||
def _get_from_map_data(self):
|
||||
# Ignore if no live map data
|
||||
sock = 'liveMapData'
|
||||
if self._sm.logMonoTime[sock] is None:
|
||||
self._limit_solutions[SpeedLimitResolver.Source.map_data] = 0.
|
||||
self._distance_solutions[SpeedLimitResolver.Source.map_data] = 0.
|
||||
_debug('SL: No map data for speed limit')
|
||||
return
|
||||
|
||||
# Load limits from map_data
|
||||
map_data = self._sm[sock]
|
||||
speed_limit = map_data.speedLimit if map_data.speedLimitValid else 0.
|
||||
next_speed_limit = map_data.speedLimitAhead if map_data.speedLimitAheadValid else 0.
|
||||
|
||||
# Calculate the age of the gps fix. Ignore if too old.
|
||||
gps_fix_age = time.time() - map_data.lastGpsTimestamp * 1e-3
|
||||
if gps_fix_age > LIMIT_MAX_MAP_DATA_AGE:
|
||||
self._limit_solutions[SpeedLimitResolver.Source.map_data] = 0.
|
||||
self._distance_solutions[SpeedLimitResolver.Source.map_data] = 0.
|
||||
_debug(f'SL: Ignoring map data as is too old. Age: {gps_fix_age}')
|
||||
return
|
||||
|
||||
# When we have no ahead speed limit to consider or it is greater than current speed limit
|
||||
# or car has stopped, then provide current value and reset tracking.
|
||||
if next_speed_limit == 0. or self._v_ego <= 0. or next_speed_limit > self._current_speed_limit:
|
||||
self._limit_solutions[SpeedLimitResolver.Source.map_data] = speed_limit
|
||||
self._distance_solutions[SpeedLimitResolver.Source.map_data] = 0.
|
||||
self._next_speed_limit_prev = 0.
|
||||
return
|
||||
|
||||
# Calculate the actual distance to the speed limit ahead corrected by gps_fix_age
|
||||
distance_since_fix = self._v_ego * gps_fix_age
|
||||
distance_to_speed_limit_ahead = max(0., map_data.speedLimitAheadDistance - distance_since_fix)
|
||||
|
||||
# When we have a next_speed_limit value that has not changed from a provided next speed limit value
|
||||
# in previous resolutions, we keep providing it.
|
||||
if next_speed_limit == self._next_speed_limit_prev:
|
||||
self._limit_solutions[SpeedLimitResolver.Source.map_data] = next_speed_limit
|
||||
self._distance_solutions[SpeedLimitResolver.Source.map_data] = distance_to_speed_limit_ahead
|
||||
return
|
||||
|
||||
# Reset tracking
|
||||
self._next_speed_limit_prev = 0.
|
||||
|
||||
# Calculated the time needed to adapt to the new limit and the corresponding distance.
|
||||
adapt_time = (next_speed_limit - self._v_ego) / LIMIT_ADAPT_ACC
|
||||
adapt_distance = self._v_ego * adapt_time + 0.5 * LIMIT_ADAPT_ACC * adapt_time**2
|
||||
|
||||
# When we detect we are close enough, we provide the next limit value and track it.
|
||||
if distance_to_speed_limit_ahead <= adapt_distance:
|
||||
self._limit_solutions[SpeedLimitResolver.Source.map_data] = next_speed_limit
|
||||
self._distance_solutions[SpeedLimitResolver.Source.map_data] = distance_to_speed_limit_ahead
|
||||
self._next_speed_limit_prev = next_speed_limit
|
||||
return
|
||||
|
||||
# Otherwise we just provide the map data speed limit.
|
||||
self.distance_to_map_speed_limit = 0.
|
||||
self._limit_solutions[SpeedLimitResolver.Source.map_data] = speed_limit
|
||||
self._distance_solutions[SpeedLimitResolver.Source.map_data] = 0.
|
||||
|
||||
def _consolidate(self):
|
||||
limits = np.array([], dtype=float)
|
||||
distances = np.array([], dtype=float)
|
||||
sources = np.array([], dtype=int)
|
||||
|
||||
if self._policy == SpeedLimitResolver.Policy.car_state_only or \
|
||||
self._policy == SpeedLimitResolver.Policy.car_state_priority or \
|
||||
self._policy == SpeedLimitResolver.Policy.combined:
|
||||
limits = np.append(limits, self._limit_solutions[SpeedLimitResolver.Source.car_state])
|
||||
distances = np.append(distances, self._distance_solutions[SpeedLimitResolver.Source.car_state])
|
||||
sources = np.append(sources, SpeedLimitResolver.Source.car_state.value)
|
||||
|
||||
if self._policy == SpeedLimitResolver.Policy.map_data_only or \
|
||||
self._policy == SpeedLimitResolver.Policy.map_data_priority or \
|
||||
self._policy == SpeedLimitResolver.Policy.combined:
|
||||
limits = np.append(limits, self._limit_solutions[SpeedLimitResolver.Source.map_data])
|
||||
distances = np.append(distances, self._distance_solutions[SpeedLimitResolver.Source.map_data])
|
||||
sources = np.append(sources, SpeedLimitResolver.Source.map_data.value)
|
||||
|
||||
if np.amax(limits) == 0.:
|
||||
if self._policy == SpeedLimitResolver.Policy.car_state_priority:
|
||||
limits = np.append(limits, self._limit_solutions[SpeedLimitResolver.Source.map_data])
|
||||
distances = np.append(distances, self._distance_solutions[SpeedLimitResolver.Source.map_data])
|
||||
sources = np.append(sources, SpeedLimitResolver.Source.map_data.value)
|
||||
|
||||
elif self._policy == SpeedLimitResolver.Policy.map_data_priority:
|
||||
limits = np.append(limits, self._limit_solutions[SpeedLimitResolver.Source.car_state])
|
||||
distances = np.append(distances, self._distance_solutions[SpeedLimitResolver.Source.car_state])
|
||||
sources = np.append(sources, SpeedLimitResolver.Source.car_state.value)
|
||||
|
||||
# Get all non-zero values and set the minimum if any, otherwise 0.
|
||||
mask = limits > 0.
|
||||
limits = limits[mask]
|
||||
distances = distances[mask]
|
||||
sources = sources[mask]
|
||||
|
||||
if len(limits) > 0:
|
||||
min_idx = np.argmin(limits)
|
||||
self.speed_limit = limits[min_idx]
|
||||
self.distance = distances[min_idx]
|
||||
self.source = SpeedLimitResolver.Source(sources[min_idx])
|
||||
else:
|
||||
self.speed_limit = 0.
|
||||
self.distance = 0.
|
||||
self.source = SpeedLimitResolver.Source.none
|
||||
|
||||
_debug(f'SL: *** Speed Limit set: {self.speed_limit}, distance: {self.distance}, source: {self.source}')
|
||||
|
||||
|
||||
class SpeedLimitController():
|
||||
def __init__(self):
|
||||
self._params = Params()
|
||||
self._resolver = SpeedLimitResolver()
|
||||
self._last_params_update = 0.0
|
||||
self._last_op_enabled_time = 0.0
|
||||
self._is_metric = self._params.get_bool("IsMetric")
|
||||
self._is_enabled = self._params.get_bool("SpeedLimitControl")
|
||||
self._offset_enabled = self._params.get_bool("SpeedLimitPercOffset")
|
||||
self._op_enabled = False
|
||||
self._op_enabled_prev = False
|
||||
self._v_ego = 0.
|
||||
self._a_ego = 0.
|
||||
self._v_offset = 0.
|
||||
self._v_cruise_setpoint = 0.
|
||||
self._v_cruise_setpoint_prev = 0.
|
||||
self._v_cruise_setpoint_changed = False
|
||||
self._speed_limit = 0.
|
||||
self._speed_limit_prev = 0.
|
||||
self._speed_limit_changed = False
|
||||
self._distance = 0.
|
||||
self._source = SpeedLimitResolver.Source.none
|
||||
self._state = SpeedLimitControlState.inactive
|
||||
self._state_prev = SpeedLimitControlState.inactive
|
||||
self._gas_pressed = False
|
||||
self._a_target = 0.
|
||||
|
||||
@property
|
||||
def a_target(self):
|
||||
return self._a_target if self.is_active else self._a_ego
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
return self._state
|
||||
|
||||
@state.setter
|
||||
def state(self, value):
|
||||
if value != self._state:
|
||||
_debug(f'Speed Limit Controller state: {_description_for_state(value)}')
|
||||
|
||||
if value == SpeedLimitControlState.tempInactive:
|
||||
# Reset previous speed limit to current value as to prevent going out of tempInactive in
|
||||
# a single cycle when the speed limit changes at the same time the user has temporarily deactivate it.
|
||||
self._speed_limit_prev = self._speed_limit
|
||||
|
||||
self._state = value
|
||||
|
||||
@property
|
||||
def is_active(self):
|
||||
return self.state > SpeedLimitControlState.tempInactive
|
||||
|
||||
@property
|
||||
def speed_limit_offseted(self):
|
||||
return self._speed_limit + self.speed_limit_offset
|
||||
|
||||
@property
|
||||
def speed_limit_offset(self):
|
||||
if self._offset_enabled:
|
||||
return interp(self._speed_limit, _LIMIT_PERC_OFFSET_BP, _LIMIT_PERC_OFFSET_V) * self._speed_limit
|
||||
return 0.
|
||||
|
||||
@property
|
||||
def speed_limit(self):
|
||||
return self._speed_limit
|
||||
|
||||
@property
|
||||
def distance(self):
|
||||
return self._distance
|
||||
|
||||
@property
|
||||
def source(self):
|
||||
return self._source
|
||||
|
||||
def _update_params(self):
|
||||
time = sec_since_boot()
|
||||
if time > self._last_params_update + _PARAMS_UPDATE_PERIOD:
|
||||
self._is_enabled = self._params.get_bool("SpeedLimitControl")
|
||||
self._offset_enabled = self._params.get_bool("SpeedLimitPercOffset")
|
||||
_debug(f'Updated Speed limit params. enabled: {self._is_enabled}, with offset: {self._offset_enabled}')
|
||||
self._last_params_update = time
|
||||
|
||||
def _update_calculations(self):
|
||||
# Update current velocity offset (error)
|
||||
self._v_offset = self.speed_limit_offseted - self._v_ego
|
||||
|
||||
# Track the time op becomes active to prevent going to tempInactive right away after
|
||||
# op enabling since controlsd will change the cruise speed every time on enabling and this will
|
||||
# cause a temp inactive transition if the controller is updated before controlsd sets actual cruise
|
||||
# speed.
|
||||
if not self._op_enabled_prev and self._op_enabled:
|
||||
self._last_op_enabled_time = sec_since_boot()
|
||||
|
||||
# Update change tracking variables
|
||||
self._speed_limit_changed = self._speed_limit != self._speed_limit_prev
|
||||
self._v_cruise_setpoint_changed = self._v_cruise_setpoint != self._v_cruise_setpoint_prev
|
||||
self._speed_limit_prev = self._speed_limit
|
||||
self._v_cruise_setpoint_prev = self._v_cruise_setpoint
|
||||
self._op_enabled_prev = self._op_enabled
|
||||
|
||||
def _state_transition(self):
|
||||
self._state_prev = self._state
|
||||
|
||||
# In any case, if op is disabled, or speed limit control is disabled
|
||||
# or the reported speed limit is 0 or gas is pressed, deactivate.
|
||||
if not self._op_enabled or not self._is_enabled or self._speed_limit == 0 or self._gas_pressed:
|
||||
self.state = SpeedLimitControlState.inactive
|
||||
return
|
||||
|
||||
# In any case, we deactivate the speed limit controller temporarily if the user changes the cruise speed.
|
||||
# Ignore if a minimum amount of time has not passed since activation. This is to prevent temp inactivations
|
||||
# due to controlsd logic changing cruise setpoint when going active.
|
||||
if self._v_cruise_setpoint_changed and \
|
||||
sec_since_boot() > (self._last_op_enabled_time + _TEMP_INACTIVE_GUARD_PERIOD):
|
||||
self.state = SpeedLimitControlState.tempInactive
|
||||
return
|
||||
|
||||
# inactive
|
||||
if self.state == SpeedLimitControlState.inactive:
|
||||
# If the limit speed offset is negative (i.e. reduce speed) and lower than threshold
|
||||
# we go to adapting state to quickly reduce speed, otherwise we go directly to active
|
||||
if self._v_offset < LIMIT_SPEED_OFFSET_TH:
|
||||
self.state = SpeedLimitControlState.adapting
|
||||
else:
|
||||
self.state = SpeedLimitControlState.active
|
||||
# tempInactive
|
||||
elif self.state == SpeedLimitControlState.tempInactive:
|
||||
# if speed limit changes, transition to inactive,
|
||||
# proper active state will be set on next iteration.
|
||||
if self._speed_limit_changed:
|
||||
self.state = SpeedLimitControlState.inactive
|
||||
# adapting
|
||||
elif self.state == SpeedLimitControlState.adapting:
|
||||
# Go to active once the speed offset is over threshold.
|
||||
if self._v_offset >= LIMIT_SPEED_OFFSET_TH:
|
||||
self.state = SpeedLimitControlState.active
|
||||
# active
|
||||
elif self.state == SpeedLimitControlState.active:
|
||||
# Go to adapting if the speed offset goes below threshold.
|
||||
if self._v_offset < LIMIT_SPEED_OFFSET_TH:
|
||||
self.state = SpeedLimitControlState.adapting
|
||||
|
||||
def _update_solution(self):
|
||||
# inactive or tempInactive state
|
||||
if self.state <= SpeedLimitControlState.tempInactive:
|
||||
# Preserve current values
|
||||
a_target = self._a_ego
|
||||
# adapting
|
||||
elif self.state == SpeedLimitControlState.adapting:
|
||||
# When adapting we target to achieve the speed limit on the distance if not there yet,
|
||||
# otherwise try to keep the speed constant around the control time horizon.
|
||||
if self.distance > 0:
|
||||
a_target = (self.speed_limit_offseted**2 - self._v_ego**2) / (2. * self.distance)
|
||||
else:
|
||||
a_target = self._v_offset / T_IDXS[CONTROL_N]
|
||||
# active
|
||||
elif self.state == SpeedLimitControlState.active:
|
||||
# When active we are trying to keep the speed constant around the control time horizon.
|
||||
a_target = self._v_offset / T_IDXS[CONTROL_N]
|
||||
|
||||
# Keep solution limited.
|
||||
self._a_target = np.clip(a_target, LIMIT_MIN_ACC, LIMIT_MAX_ACC)
|
||||
|
||||
def _update_events(self, events):
|
||||
if not self.is_active:
|
||||
# no event while inactive
|
||||
return
|
||||
|
||||
if self._state_prev <= SpeedLimitControlState.tempInactive:
|
||||
events.add(EventName.speedLimitActive)
|
||||
elif self._speed_limit_changed != 0:
|
||||
events.add(EventName.speedLimitValueChange)
|
||||
|
||||
def update(self, enabled, v_ego, a_ego, sm, v_cruise_setpoint, events=Events()):
|
||||
self._op_enabled = enabled
|
||||
self._v_ego = v_ego
|
||||
self._a_ego = a_ego
|
||||
self._v_cruise_setpoint = v_cruise_setpoint
|
||||
self._gas_pressed = sm['carState'].gasPressed
|
||||
|
||||
self._speed_limit, self._distance, self._source = self._resolver.resolve(v_ego, self.speed_limit, sm)
|
||||
|
||||
self._update_params()
|
||||
self._update_calculations()
|
||||
self._state_transition()
|
||||
self._update_solution()
|
||||
self._update_events(events)
|
||||
@@ -0,0 +1,244 @@
|
||||
import numpy as np
|
||||
import time
|
||||
from common.params import Params
|
||||
from cereal import log
|
||||
from common.realtime import sec_since_boot
|
||||
from selfdrive.controls.lib.drive_helpers import LIMIT_ADAPT_ACC, LIMIT_MIN_SPEED, LIMIT_MAX_MAP_DATA_AGE, \
|
||||
LIMIT_SPEED_OFFSET_TH, CONTROL_N, LIMIT_MIN_ACC, LIMIT_MAX_ACC
|
||||
from selfdrive.modeld.constants import T_IDXS
|
||||
|
||||
|
||||
_ACTIVE_LIMIT_MIN_ACC = -0.5 # m/s^2 Maximum deceleration allowed while active.
|
||||
_ACTIVE_LIMIT_MAX_ACC = 0.5 # m/s^2 Maximum acelration allowed while active.
|
||||
|
||||
|
||||
_DEBUG = False
|
||||
|
||||
TurnSpeedControlState = log.LongitudinalPlan.SpeedLimitControlState
|
||||
|
||||
|
||||
def _debug(msg):
|
||||
if not _DEBUG:
|
||||
return
|
||||
print(msg)
|
||||
|
||||
|
||||
def _description_for_state(turn_speed_control_state):
|
||||
if turn_speed_control_state == TurnSpeedControlState.inactive:
|
||||
return 'INACTIVE'
|
||||
if turn_speed_control_state == TurnSpeedControlState.tempInactive:
|
||||
return 'TEMP INACTIVE'
|
||||
if turn_speed_control_state == TurnSpeedControlState.adapting:
|
||||
return 'ADAPTING'
|
||||
if turn_speed_control_state == TurnSpeedControlState.active:
|
||||
return 'ACTIVE'
|
||||
|
||||
|
||||
class TurnSpeedController():
|
||||
def __init__(self):
|
||||
self._params = Params()
|
||||
self._last_params_update = 0.
|
||||
self._is_enabled = self._params.get_bool("TurnSpeedControl")
|
||||
self._op_enabled = False
|
||||
self._v_ego = 0.
|
||||
self._a_ego = 0.
|
||||
self._v_cruise_setpoint = 0.
|
||||
|
||||
self._v_offset = 0.
|
||||
self._speed_limit = 0.
|
||||
self._speed_limit_temp_inactive = 0.
|
||||
self._distance = 0.
|
||||
self._turn_sign = 0
|
||||
self._state = TurnSpeedControlState.inactive
|
||||
|
||||
self._next_speed_limit_prev = 0.
|
||||
|
||||
self._a_target = 0.
|
||||
|
||||
@property
|
||||
def a_target(self):
|
||||
return self._a_target if self.is_active else self._a_ego
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
return self._state
|
||||
|
||||
@state.setter
|
||||
def state(self, value):
|
||||
if value != self._state:
|
||||
_debug(f'Turn Speed Controller state: {_description_for_state(value)}')
|
||||
|
||||
if value == TurnSpeedControlState.adapting:
|
||||
_debug('TSC: Enteriing Adapting as speed offset is below threshold')
|
||||
_debug(f'_v_offset: {self._v_offset * 3.6}\nspeed_limit: {self.speed_limit * 3.6}')
|
||||
_debug(f'_v_ego: {self._v_ego * 3.6}\ndistance: {self.distance}')
|
||||
|
||||
if value == TurnSpeedControlState.tempInactive:
|
||||
# Track the speed limit value when controller was set to temp inactive.
|
||||
self._speed_limit_temp_inactive = self._speed_limit
|
||||
|
||||
self._state = value
|
||||
|
||||
@property
|
||||
def is_active(self):
|
||||
return self.state > TurnSpeedControlState.tempInactive
|
||||
|
||||
@property
|
||||
def speed_limit(self):
|
||||
return max(self._speed_limit, LIMIT_MIN_SPEED) if self._speed_limit > 0. else 0.
|
||||
|
||||
@property
|
||||
def distance(self):
|
||||
return max(self._distance, 0.)
|
||||
|
||||
@property
|
||||
def turn_sign(self):
|
||||
return self._turn_sign
|
||||
|
||||
def _get_limit_from_map_data(self, sm):
|
||||
"""Provides the speed limit, distance and turn sign to it for turns based on map data.
|
||||
"""
|
||||
# Ignore if no live map data
|
||||
sock = 'liveMapData'
|
||||
if sm.logMonoTime[sock] is None:
|
||||
_debug('TS: No map data for turn speed limit')
|
||||
return 0., 0., 0
|
||||
|
||||
# Load map_data and initialize
|
||||
map_data = sm[sock]
|
||||
speed_limit = 0.
|
||||
|
||||
# Calculate the age of the gps fix. Ignore if too old.
|
||||
gps_fix_age = time.time() - map_data.lastGpsTimestamp * 1e-3
|
||||
if gps_fix_age > LIMIT_MAX_MAP_DATA_AGE:
|
||||
_debug(f'TS: Ignoring map data as is too old. Age: {gps_fix_age}')
|
||||
return 0., 0., 0
|
||||
|
||||
# Load turn ahead sections info from map_data with distances corrected by gps_fix_age
|
||||
distance_since_fix = self._v_ego * gps_fix_age
|
||||
distances_to_sections_ahead = np.maximum(0., np.array(map_data.turnSpeedLimitsAheadDistances) - distance_since_fix)
|
||||
speed_limit_in_sections_ahead = map_data.turnSpeedLimitsAhead
|
||||
turn_signs_in_sections_ahead = map_data.turnSpeedLimitsAheadSigns
|
||||
|
||||
# Ensure current speed limit is considered only if we are inside the section.
|
||||
if map_data.turnSpeedLimitValid and self._v_ego > 0.:
|
||||
speed_limit_end_time = (map_data.turnSpeedLimitEndDistance / self._v_ego) - gps_fix_age
|
||||
if speed_limit_end_time > 0.:
|
||||
speed_limit = map_data.turnSpeedLimit
|
||||
|
||||
# When we have no ahead speed limit to consider or all are greater than current speed limit
|
||||
# or car has stopped, then provide current value and reset tracking.
|
||||
turn_sign = map_data.turnSpeedLimitSign if map_data.turnSpeedLimitValid else 0
|
||||
if len(speed_limit_in_sections_ahead) == 0 or self._v_ego <= 0. or \
|
||||
(speed_limit > 0 and np.amin(speed_limit_in_sections_ahead) > speed_limit):
|
||||
self._next_speed_limit_prev = 0.
|
||||
return speed_limit, 0., turn_sign
|
||||
|
||||
# Calculated the time needed to adapt to the limits ahead and the corresponding distances.
|
||||
adapt_times = (np.maximum(speed_limit_in_sections_ahead, LIMIT_MIN_SPEED) - self._v_ego) / LIMIT_ADAPT_ACC
|
||||
adapt_distances = self._v_ego * adapt_times + 0.5 * LIMIT_ADAPT_ACC * adapt_times**2
|
||||
distance_gaps = distances_to_sections_ahead - adapt_distances
|
||||
|
||||
# We select as next speed limit, the one that have the lowest distance gap.
|
||||
next_idx = np.argmin(distance_gaps)
|
||||
next_speed_limit = speed_limit_in_sections_ahead[next_idx]
|
||||
distance_to_section_ahead = distances_to_sections_ahead[next_idx]
|
||||
next_turn_sign = turn_signs_in_sections_ahead[next_idx]
|
||||
distance_gap = distance_gaps[next_idx]
|
||||
|
||||
# When we have a next_speed_limit value that has not changed from a provided next speed limit value
|
||||
# in previous resolutions, we keep providing it along with the updated distance to it.
|
||||
if next_speed_limit == self._next_speed_limit_prev:
|
||||
return next_speed_limit, distance_to_section_ahead, next_turn_sign
|
||||
|
||||
# Reset tracking
|
||||
self._next_speed_limit_prev = 0.
|
||||
|
||||
# When we detect we are close enough, we provide the next limit value and track it.
|
||||
if distance_gap <= 0.:
|
||||
self._next_speed_limit_prev = next_speed_limit
|
||||
return next_speed_limit, distance_to_section_ahead, next_turn_sign
|
||||
|
||||
# Otherwise we just provide the calculated speed_limit
|
||||
return speed_limit, 0., turn_sign
|
||||
|
||||
def _update_params(self):
|
||||
time = sec_since_boot()
|
||||
if time > self._last_params_update + 5.0:
|
||||
self._is_enabled = self._params.get_bool("TurnSpeedControl")
|
||||
self._last_params_update = time
|
||||
|
||||
def _update_calculations(self):
|
||||
# Update current velocity offset (error)
|
||||
self._v_offset = self.speed_limit - self._v_ego
|
||||
|
||||
def _state_transition(self, sm):
|
||||
# In any case, if op is disabled, or turn speed limit control is disabled
|
||||
# or the reported speed limit is 0, deactivate.
|
||||
if not self._op_enabled or not self._is_enabled or self.speed_limit == 0.:
|
||||
self.state = TurnSpeedControlState.inactive
|
||||
return
|
||||
|
||||
# In any case, we deactivate the speed limit controller temporarily
|
||||
# if gas is pressed (to support gas override implementations).
|
||||
if sm['carState'].gasPressed:
|
||||
self.state = TurnSpeedControlState.tempInactive
|
||||
return
|
||||
|
||||
# inactive
|
||||
if self.state == TurnSpeedControlState.inactive:
|
||||
# If the limit speed offset is negative (i.e. reduce speed) and lower than threshold and distanct to turn limit
|
||||
# is positive (not in turn yet) we go to adapting state to reduce speed, otherwise we go directly to active
|
||||
if self._v_offset < LIMIT_SPEED_OFFSET_TH and self.distance > 0.:
|
||||
self.state = TurnSpeedControlState.adapting
|
||||
else:
|
||||
self.state = TurnSpeedControlState.active
|
||||
# tempInactive
|
||||
elif self.state == TurnSpeedControlState.tempInactive:
|
||||
# if the speed limit recorded when going to temp Inactive changes
|
||||
# then set to inactive, activation will happen on next cycle
|
||||
if self._speed_limit != self._speed_limit_temp_inactive:
|
||||
self.state = TurnSpeedControlState.inactive
|
||||
# adapting
|
||||
elif self.state == TurnSpeedControlState.adapting:
|
||||
# Go to active once the speed offset is over threshold or the distance to turn is now 0.
|
||||
if self._v_offset >= LIMIT_SPEED_OFFSET_TH or self.distance == 0.:
|
||||
self.state = TurnSpeedControlState.active
|
||||
# active
|
||||
elif self.state == TurnSpeedControlState.active:
|
||||
# Go to adapting if the speed offset goes below threshold as long as the distance to turn is still positive.
|
||||
if self._v_offset < LIMIT_SPEED_OFFSET_TH and self.distance > 0.:
|
||||
self.state = TurnSpeedControlState.adapting
|
||||
|
||||
def _update_solution(self):
|
||||
# inactive or tempInactive state
|
||||
if self.state <= TurnSpeedControlState.tempInactive:
|
||||
# Preserve current values
|
||||
a_target = self._a_ego
|
||||
# adapting
|
||||
elif self.state == TurnSpeedControlState.adapting:
|
||||
# When adapting we target to achieve the speed limit on the distance.
|
||||
a_target = (self.speed_limit**2 - self._v_ego**2) / (2. * self.distance)
|
||||
a_target = np.clip(a_target, LIMIT_MIN_ACC, LIMIT_MAX_ACC)
|
||||
# active
|
||||
elif self.state == TurnSpeedControlState.active:
|
||||
# When active we are trying to keep the speed constant around the control time horizon.
|
||||
# but under constrained acceleration limits since we are in a turn.
|
||||
a_target = self._v_offset / T_IDXS[CONTROL_N]
|
||||
a_target = np.clip(a_target, _ACTIVE_LIMIT_MIN_ACC, _ACTIVE_LIMIT_MAX_ACC)
|
||||
|
||||
# update solution values.
|
||||
self._a_target = a_target
|
||||
|
||||
def update(self, enabled, v_ego, a_ego, sm):
|
||||
self._op_enabled = enabled
|
||||
self._v_ego = v_ego
|
||||
self._a_ego = a_ego
|
||||
|
||||
# Get the speed limit from Map Data
|
||||
self._speed_limit, self._distance, self._turn_sign = self._get_limit_from_map_data(sm)
|
||||
|
||||
self._update_params()
|
||||
self._update_calculations()
|
||||
self._state_transition(sm)
|
||||
self._update_solution()
|
||||
@@ -0,0 +1,291 @@
|
||||
import numpy as np
|
||||
import math
|
||||
from cereal import log
|
||||
from common.numpy_fast import interp
|
||||
from common.params import Params
|
||||
from common.realtime import sec_since_boot
|
||||
from common.conversions import Conversions as CV
|
||||
from selfdrive.controls.lib.lateral_planner import TRAJECTORY_SIZE
|
||||
from selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, CONTROL_N
|
||||
|
||||
|
||||
_MIN_V = 5.6 # Do not operate under 20km/h
|
||||
|
||||
_ENTERING_PRED_LAT_ACC_TH = 1.3 # Predicted Lat Acc threshold to trigger entering turn state.
|
||||
_ABORT_ENTERING_PRED_LAT_ACC_TH = 1.1 # Predicted Lat Acc threshold to abort entering state if speed drops.
|
||||
|
||||
_TURNING_LAT_ACC_TH = 1.6 # Lat Acc threshold to trigger turning turn state.
|
||||
|
||||
_LEAVING_LAT_ACC_TH = 1.3 # Lat Acc threshold to trigger leaving turn state.
|
||||
_FINISH_LAT_ACC_TH = 1.1 # Lat Acc threshold to trigger end of turn cycle.
|
||||
|
||||
_EVAL_STEP = 5. # mts. Resolution of the curvature evaluation.
|
||||
_EVAL_START = 20. # mts. Distance ahead where to start evaluating vision curvature.
|
||||
_EVAL_LENGHT = 150. # mts. Distance ahead where to stop evaluating vision curvature.
|
||||
_EVAL_RANGE = np.arange(_EVAL_START, _EVAL_LENGHT, _EVAL_STEP)
|
||||
|
||||
_A_LAT_REG_MAX = 2. # Maximum lateral acceleration
|
||||
|
||||
_NO_OVERSHOOT_TIME_HORIZON = 4. # s. Time to use for velocity desired based on a_target when not overshooting.
|
||||
|
||||
# Lookup table for the minimum smooth deceleration during the ENTERING state
|
||||
# depending on the actual maximum absolute lateral acceleration predicted on the turn ahead.
|
||||
_ENTERING_SMOOTH_DECEL_V = [-0.2, -1.] # min decel value allowed on ENTERING state
|
||||
_ENTERING_SMOOTH_DECEL_BP = [1.3, 3.] # absolute value of lat acc ahead
|
||||
|
||||
# Lookup table for the acceleration for the TURNING state
|
||||
# depending on the current lateral acceleration of the vehicle.
|
||||
_TURNING_ACC_V = [0.5, 0., -0.4] # acc value
|
||||
_TURNING_ACC_BP = [1.5, 2.3, 3.] # absolute value of current lat acc
|
||||
|
||||
_LEAVING_ACC = 0.5 # Confortble acceleration to regain speed while leaving a turn.
|
||||
|
||||
_MIN_LANE_PROB = 0.6 # Minimum lanes probability to allow curvature prediction based on lanes.
|
||||
|
||||
_DEBUG = False
|
||||
|
||||
|
||||
def _debug(msg):
|
||||
if not _DEBUG:
|
||||
return
|
||||
print(msg)
|
||||
|
||||
|
||||
VisionTurnControllerState = log.LongitudinalPlan.VisionTurnControllerState
|
||||
|
||||
|
||||
def eval_curvature(poly, x_vals):
|
||||
"""
|
||||
This function returns a vector with the curvature based on path defined by `poly`
|
||||
evaluated on distance vector `x_vals`
|
||||
"""
|
||||
# https://en.wikipedia.org/wiki/Curvature# Local_expressions
|
||||
def curvature(x):
|
||||
a = abs(2 * poly[1] + 6 * poly[0] * x) / (1 + (3 * poly[0] * x**2 + 2 * poly[1] * x + poly[2])**2)**(1.5)
|
||||
return a
|
||||
|
||||
return np.vectorize(curvature)(x_vals)
|
||||
|
||||
|
||||
def eval_lat_acc(v_ego, x_curv):
|
||||
"""
|
||||
This function returns a vector with the lateral acceleration based
|
||||
for the provided speed `v_ego` evaluated over curvature vector `x_curv`
|
||||
"""
|
||||
|
||||
def lat_acc(curv):
|
||||
a = v_ego**2 * curv
|
||||
return a
|
||||
|
||||
return np.vectorize(lat_acc)(x_curv)
|
||||
|
||||
|
||||
def _description_for_state(turn_controller_state):
|
||||
if turn_controller_state == VisionTurnControllerState.disabled:
|
||||
return 'DISABLED'
|
||||
if turn_controller_state == VisionTurnControllerState.entering:
|
||||
return 'ENTERING'
|
||||
if turn_controller_state == VisionTurnControllerState.turning:
|
||||
return 'TURNING'
|
||||
if turn_controller_state == VisionTurnControllerState.leaving:
|
||||
return 'LEAVING'
|
||||
|
||||
|
||||
class VisionTurnController():
|
||||
def __init__(self, CP):
|
||||
self._params = Params()
|
||||
self._CP = CP
|
||||
self._op_enabled = False
|
||||
self._gas_pressed = False
|
||||
self._is_enabled = self._params.get_bool("TurnVisionControl")
|
||||
self._last_params_update = 0.
|
||||
self._v_cruise_setpoint = 0.
|
||||
self._v_ego = 0.
|
||||
self._a_ego = 0.
|
||||
self._a_target = 0.
|
||||
self._v_overshoot = 0.
|
||||
self._state = VisionTurnControllerState.disabled
|
||||
|
||||
self._reset()
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
return self._state
|
||||
|
||||
@state.setter
|
||||
def state(self, value):
|
||||
if value != self._state:
|
||||
_debug(f'TVC: TurnVisionController state: {_description_for_state(value)}')
|
||||
if value == VisionTurnControllerState.disabled:
|
||||
self._reset()
|
||||
self._state = value
|
||||
|
||||
@property
|
||||
def a_target(self):
|
||||
return self._a_target if self.is_active else self._a_ego
|
||||
|
||||
@property
|
||||
def v_turn(self):
|
||||
if not self.is_active:
|
||||
return self._v_cruise_setpoint
|
||||
return self._v_overshoot if self._lat_acc_overshoot_ahead \
|
||||
else self._v_ego + self._a_target * _NO_OVERSHOOT_TIME_HORIZON
|
||||
|
||||
@property
|
||||
def is_active(self):
|
||||
return self._state != VisionTurnControllerState.disabled
|
||||
|
||||
def _reset(self):
|
||||
self._current_lat_acc = 0.
|
||||
self._max_v_for_current_curvature = 0.
|
||||
self._max_pred_lat_acc = 0.
|
||||
self._v_overshoot_distance = 200.
|
||||
self._lat_acc_overshoot_ahead = False
|
||||
|
||||
def _update_params(self):
|
||||
time = sec_since_boot()
|
||||
if time > self._last_params_update + 5.0:
|
||||
self._is_enabled = self._params.get_bool("TurnVisionControl")
|
||||
self._last_params_update = time
|
||||
|
||||
def _update_calculations(self, sm):
|
||||
# Get path polynomial approximation for curvature estimation from model data.
|
||||
path_poly = None
|
||||
model_data = sm['modelV2'] if sm.valid.get('modelV2', False) else None
|
||||
lat_planner_data = sm['lateralPlan'] if sm.valid.get('lateralPlan', False) else None
|
||||
|
||||
# 1. When the probability of lanes is good enough, compute polynomial from lanes as they are way more stable
|
||||
# on current mode than drving path.
|
||||
if model_data is not None and len(model_data.laneLines) == 4 and len(model_data.laneLines[0].t) == TRAJECTORY_SIZE:
|
||||
ll_x = model_data.laneLines[1].x # left and right ll x is the same
|
||||
lll_y = np.array(model_data.laneLines[1].y)
|
||||
rll_y = np.array(model_data.laneLines[2].y)
|
||||
l_prob = model_data.laneLineProbs[1]
|
||||
r_prob = model_data.laneLineProbs[2]
|
||||
lll_std = model_data.laneLineStds[1]
|
||||
rll_std = model_data.laneLineStds[2]
|
||||
|
||||
# Reduce reliance on lanelines that are too far apart or will be in a few seconds
|
||||
width_pts = rll_y - lll_y
|
||||
prob_mods = []
|
||||
for t_check in [0.0, 1.5, 3.0]:
|
||||
width_at_t = interp(t_check * (self._v_ego + 7), ll_x, width_pts)
|
||||
prob_mods.append(interp(width_at_t, [4.0, 5.0], [1.0, 0.0]))
|
||||
mod = min(prob_mods)
|
||||
l_prob *= mod
|
||||
r_prob *= mod
|
||||
|
||||
# Reduce reliance on uncertain lanelines
|
||||
l_std_mod = interp(lll_std, [.15, .3], [1.0, 0.0])
|
||||
r_std_mod = interp(rll_std, [.15, .3], [1.0, 0.0])
|
||||
l_prob *= l_std_mod
|
||||
r_prob *= r_std_mod
|
||||
|
||||
# Find path from lanes as the average center lane only if min probability on both lanes is above threshold.
|
||||
if l_prob > _MIN_LANE_PROB and r_prob > _MIN_LANE_PROB:
|
||||
c_y = width_pts / 2 + lll_y
|
||||
path_poly = np.polyfit(ll_x, c_y, 3)
|
||||
|
||||
# 2. If not polynomial derived from lanes, then derive it from driving path as provided by `lateralPlanner`.
|
||||
if path_poly is None and lat_planner_data is not None and len(lat_planner_data.psis) == CONTROL_N \
|
||||
and lat_planner_data.dPathPoints[0] > 0:
|
||||
path_poly = np.polyfit(lat_planner_data.psis, lat_planner_data.dPathPoints, 3)
|
||||
|
||||
# 3. If no polynomial derived from lanes or driving path, then provide a straight line poly.
|
||||
if path_poly is None:
|
||||
path_poly = np.array([0., 0., 0., 0.])
|
||||
|
||||
current_curvature = abs(
|
||||
sm['carState'].steeringAngleDeg * CV.DEG_TO_RAD / (self._CP.steerRatio * self._CP.wheelbase))
|
||||
self._current_lat_acc = current_curvature * self._v_ego**2
|
||||
self._max_v_for_current_curvature = math.sqrt(_A_LAT_REG_MAX / current_curvature) if current_curvature > 0 \
|
||||
else V_CRUISE_MAX * CV.KPH_TO_MS
|
||||
|
||||
pred_curvatures = eval_curvature(path_poly, _EVAL_RANGE)
|
||||
max_pred_curvature = np.amax(pred_curvatures)
|
||||
self._max_pred_lat_acc = self._v_ego**2 * max_pred_curvature
|
||||
|
||||
max_curvature_for_vego = _A_LAT_REG_MAX / max(self._v_ego, 0.1)**2
|
||||
lat_acc_overshoot_idxs = np.nonzero(pred_curvatures >= max_curvature_for_vego)[0]
|
||||
self._lat_acc_overshoot_ahead = len(lat_acc_overshoot_idxs) > 0
|
||||
|
||||
if self._lat_acc_overshoot_ahead:
|
||||
self._v_overshoot = min(math.sqrt(_A_LAT_REG_MAX / max_pred_curvature), self._v_cruise_setpoint)
|
||||
self._v_overshoot_distance = max(lat_acc_overshoot_idxs[0] * _EVAL_STEP + _EVAL_START, _EVAL_STEP)
|
||||
_debug(f'TVC: High LatAcc. Dist: {self._v_overshoot_distance:.2f}, v: {self._v_overshoot * CV.MS_TO_KPH:.2f}')
|
||||
|
||||
def _state_transition(self):
|
||||
# In any case, if system is disabled or the feature is disabeld or gas is pressed, disable.
|
||||
if not self._op_enabled or not self._is_enabled or self._gas_pressed:
|
||||
self.state = VisionTurnControllerState.disabled
|
||||
return
|
||||
|
||||
# DISABLED
|
||||
if self.state == VisionTurnControllerState.disabled:
|
||||
# Do not enter a turn control cycle if speed is low.
|
||||
if self._v_ego <= _MIN_V:
|
||||
pass
|
||||
# If substantial lateral acceleration is predicted ahead, then move to Entering turn state.
|
||||
elif self._max_pred_lat_acc >= _ENTERING_PRED_LAT_ACC_TH:
|
||||
self.state = VisionTurnControllerState.entering
|
||||
# ENTERING
|
||||
elif self.state == VisionTurnControllerState.entering:
|
||||
# Transition to Turning if current lateral acceleration is over the threshold.
|
||||
if self._current_lat_acc >= _TURNING_LAT_ACC_TH:
|
||||
self.state = VisionTurnControllerState.turning
|
||||
# Abort if the predicted lateral acceleration drops
|
||||
elif self._max_pred_lat_acc < _ABORT_ENTERING_PRED_LAT_ACC_TH:
|
||||
self.state = VisionTurnControllerState.disabled
|
||||
# TURNING
|
||||
elif self.state == VisionTurnControllerState.turning:
|
||||
# Transition to Leaving if current lateral acceleration drops drops below threshold.
|
||||
if self._current_lat_acc <= _LEAVING_LAT_ACC_TH:
|
||||
self.state = VisionTurnControllerState.leaving
|
||||
# LEAVING
|
||||
elif self.state == VisionTurnControllerState.leaving:
|
||||
# Transition back to Turning if current lateral acceleration goes back over the threshold.
|
||||
if self._current_lat_acc >= _TURNING_LAT_ACC_TH:
|
||||
self.state = VisionTurnControllerState.turning
|
||||
# Finish if current lateral acceleration goes below threshold.
|
||||
elif self._current_lat_acc < _FINISH_LAT_ACC_TH:
|
||||
self.state = VisionTurnControllerState.disabled
|
||||
|
||||
def _update_solution(self):
|
||||
# DISABLED
|
||||
if self.state == VisionTurnControllerState.disabled:
|
||||
# when not overshooting, calculate v_turn as the speed at the prediction horizon when following
|
||||
# the smooth deceleration.
|
||||
a_target = self._a_ego
|
||||
# ENTERING
|
||||
elif self.state == VisionTurnControllerState.entering:
|
||||
# when not overshooting, target a smooth deceleration in preparation for a sharp turn to come.
|
||||
a_target = interp(self._max_pred_lat_acc, _ENTERING_SMOOTH_DECEL_BP, _ENTERING_SMOOTH_DECEL_V)
|
||||
if self._lat_acc_overshoot_ahead:
|
||||
# when overshooting, target the acceleration needed to achieve the overshoot speed at
|
||||
# the required distance
|
||||
a_target = min((self._v_overshoot**2 - self._v_ego**2) / (2 * self._v_overshoot_distance), a_target)
|
||||
_debug(f'TVC Entering: Overshooting: {self._lat_acc_overshoot_ahead}')
|
||||
_debug(f' Decel: {a_target:.2f}, target v: {self.v_turn * CV.MS_TO_KPH}')
|
||||
# TURNING
|
||||
elif self.state == VisionTurnControllerState.turning:
|
||||
# When turning we provide a target acceleration that is comfortable for the lateral accelearation felt.
|
||||
a_target = interp(self._current_lat_acc, _TURNING_ACC_BP, _TURNING_ACC_V)
|
||||
# LEAVING
|
||||
elif self.state == VisionTurnControllerState.leaving:
|
||||
# When leaving we provide a comfortable acceleration to regain speed.
|
||||
a_target = _LEAVING_ACC
|
||||
|
||||
# update solution values.
|
||||
self._a_target = a_target
|
||||
|
||||
def update(self, enabled, v_ego, a_ego, v_cruise_setpoint, sm):
|
||||
self._op_enabled = enabled
|
||||
self._gas_pressed = sm['carState'].gasPressed
|
||||
self._v_ego = v_ego
|
||||
self._a_ego = a_ego
|
||||
self._v_cruise_setpoint = v_cruise_setpoint
|
||||
|
||||
self._update_params()
|
||||
self._update_calculations(sm)
|
||||
self._state_transition()
|
||||
self._update_solution()
|
||||
@@ -25,7 +25,7 @@ def plannerd_thread(sm=None, pm=None):
|
||||
lateral_planner = LateralPlanner(CP, use_lanelines=use_lanelines, wide_camera=wide_camera)
|
||||
|
||||
if sm is None:
|
||||
sm = messaging.SubMaster(['carControl', 'carState', 'controlsState', 'radarState', 'modelV2', 'longitudinalPlan'],
|
||||
sm = messaging.SubMaster(['carControl', 'carState', 'controlsState', 'radarState', 'modelV2', 'longitudinalPlan', 'lateralPlan', 'liveMapData'],
|
||||
poll=['radarState', 'modelV2'], ignore_avg_freq=['radarState'])
|
||||
|
||||
if pm is None:
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
#!/usr/bin/env python3
|
||||
# type: ignore
|
||||
|
||||
import os
|
||||
import argparse
|
||||
import signal
|
||||
import sys
|
||||
|
||||
import cereal.messaging as messaging
|
||||
from cereal import log
|
||||
from selfdrive.monitoring.hands_on_wheel_monitor import HandsOnWheelStatus
|
||||
from selfdrive.controls.lib.events import Events
|
||||
|
||||
HandsOnWheelState = log.DriverMonitoringState.HandsOnWheelState
|
||||
|
||||
|
||||
def sigint_handler(signal, frame):
|
||||
print("handler!")
|
||||
exit(0)
|
||||
|
||||
|
||||
signal.signal(signal.SIGINT, sigint_handler)
|
||||
|
||||
|
||||
def status_monitor():
|
||||
# use driverState socker to drive timing.
|
||||
driverState = messaging.sub_sock('driverState', addr=args.addr, conflate=True)
|
||||
sm = messaging.SubMaster(['carState', 'dMonitoringState'], addr=args.addr)
|
||||
steering_status = HandsOnWheelStatus()
|
||||
v_cruise_last = 0
|
||||
|
||||
while messaging.recv_one(driverState):
|
||||
try:
|
||||
sm.update()
|
||||
|
||||
v_cruise = sm['carState'].cruiseState.speed
|
||||
steering_wheel_engaged = len(sm['carState'].buttonEvents) > 0 or \
|
||||
v_cruise != v_cruise_last or sm['carState'].steeringPressed
|
||||
v_cruise_last = v_cruise
|
||||
|
||||
# Get status from our own instance of SteeringStatus
|
||||
steering_status.update(Events(), steering_wheel_engaged, sm['carState'].cruiseState.enabled, sm['carState'].vEgo)
|
||||
steering_state = steering_status.hands_on_wheel_state
|
||||
state_name = "Unknown "
|
||||
if steering_state == HandsOnWheelState.none:
|
||||
state_name = "Not Active "
|
||||
elif steering_state == HandsOnWheelState.ok:
|
||||
state_name = "Hands On Wheel "
|
||||
elif steering_state == HandsOnWheelState.minor:
|
||||
state_name = "Hands Off Wheel - Minor "
|
||||
elif steering_state == HandsOnWheelState.warning:
|
||||
state_name = "Hands Off Wheel - Warning "
|
||||
elif steering_state == HandsOnWheelState.critical:
|
||||
state_name = "Hands Off Wheel - Critical"
|
||||
elif steering_state == HandsOnWheelState.terminal:
|
||||
state_name = "Hands Off Wheel - Terminal"
|
||||
|
||||
# Get events from `dMonitoringState`
|
||||
events = sm['dMonitoringState'].events
|
||||
event_name = events[0].name if len(events) else "None"
|
||||
event_name = "{:<30}".format(event_name[:30])
|
||||
|
||||
# Print output
|
||||
sys.stdout.write(f'\rSteering State: {state_name} | event: {event_name}')
|
||||
sys.stdout.flush()
|
||||
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description='Sniff a communication socket')
|
||||
parser.add_argument('--addr', default='127.0.0.1')
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.addr != "127.0.0.1":
|
||||
os.environ["ZMQ"] = "1"
|
||||
messaging.context = messaging.Context()
|
||||
|
||||
status_monitor()
|
||||
Executable
+86
@@ -0,0 +1,86 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import sys
|
||||
import errno
|
||||
import shutil
|
||||
import time
|
||||
from common.basedir import BASEDIR
|
||||
from urllib.request import urlopen
|
||||
from glob import glob
|
||||
import subprocess
|
||||
import importlib.util
|
||||
|
||||
# NOTE: Do NOT import anything here that needs be built (e.g. params)
|
||||
from common.spinner import Spinner
|
||||
|
||||
|
||||
sys.path.append(os.path.join(BASEDIR, "third_party"))
|
||||
OPSPLINE_SPEC = importlib.util.find_spec('scipy')
|
||||
OVERPY_SPEC = importlib.util.find_spec('overpy')
|
||||
MAX_BUILD_PROGRESS = 100
|
||||
TMP_DIR = '/data/tmp'
|
||||
THIRD_PARTY_DIR = '/data/openpilot/third_party'
|
||||
|
||||
|
||||
def wait_for_internet_connection(return_on_failure=False):
|
||||
retries = 0
|
||||
while True:
|
||||
try:
|
||||
_ = urlopen('https://www.google.com/', timeout=10)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f'Wait for internet failed: {e}')
|
||||
if return_on_failure and retries == 15:
|
||||
return False
|
||||
retries += 1
|
||||
time.sleep(2) # Wait for 2 seconds before retrying
|
||||
|
||||
|
||||
def install_dep(spinner):
|
||||
wait_for_internet_connection()
|
||||
|
||||
TOTAL_PIP_STEPS = 2986
|
||||
|
||||
try:
|
||||
os.makedirs(TMP_DIR)
|
||||
except OSError as e:
|
||||
if e.errno != errno.EEXIST:
|
||||
raise
|
||||
my_env = os.environ.copy()
|
||||
my_env['TMPDIR'] = TMP_DIR
|
||||
|
||||
pip_target = [f'--target={THIRD_PARTY_DIR}']
|
||||
packages = []
|
||||
if OPSPLINE_SPEC is None:
|
||||
packages.append('scipy==1.7.1')
|
||||
if OVERPY_SPEC is None:
|
||||
packages.append('overpy==0.6')
|
||||
|
||||
pip = subprocess.Popen([sys.executable, "-m", "pip", "install", "-v"] + pip_target + packages,
|
||||
stdout=subprocess.PIPE, env=my_env)
|
||||
|
||||
# Read progress from pip and update spinner
|
||||
steps = 0
|
||||
while True:
|
||||
output = pip.stdout.readline()
|
||||
if pip.poll() is not None:
|
||||
break
|
||||
if output:
|
||||
steps += 1
|
||||
spinner.update_progress(MAX_BUILD_PROGRESS * min(1., steps / TOTAL_PIP_STEPS), 100.)
|
||||
print(output.decode('utf8', 'replace'))
|
||||
|
||||
shutil.rmtree(TMP_DIR)
|
||||
os.unsetenv('TMPDIR')
|
||||
|
||||
# remove numpy installed to THIRD_PARTY_DIR since numpy is already present in the AGNOS image
|
||||
if OPSPLINE_SPEC is None:
|
||||
for directory in glob(f'{THIRD_PARTY_DIR}/numpy*'):
|
||||
shutil.rmtree(directory)
|
||||
shutil.rmtree(f'{THIRD_PARTY_DIR}/bin')
|
||||
|
||||
|
||||
if __name__ == "__main__" and (OPSPLINE_SPEC is None or OVERPY_SPEC is None):
|
||||
spinner = Spinner()
|
||||
spinner.update_progress(0, 100)
|
||||
install_dep(spinner)
|
||||
@@ -106,6 +106,10 @@ def manager_init() -> None:
|
||||
if params.get(k) is None:
|
||||
params.put(k, v)
|
||||
|
||||
# parameters set by Environment Variables
|
||||
if os.getenv("HANDSMONITORING") is not None:
|
||||
params.put_bool("HandsOnWheelMonitoring", bool(int(os.getenv("HANDSMONITORING", "0"))))
|
||||
|
||||
# is this dashcam?
|
||||
if os.getenv("PASSIVE") is not None:
|
||||
params.put_bool("Passive", bool(int(os.getenv("PASSIVE", "0"))))
|
||||
|
||||
@@ -66,6 +66,7 @@ procs = [
|
||||
|
||||
PythonProcess("gpxd", "selfdrive.gpxd.gpxd"),
|
||||
PythonProcess("gpxd_uploader", "selfdrive.gpxd.gpx_uploader", offroad=True),
|
||||
PythonProcess("mapd", "selfdrive.mapd.mapd"),
|
||||
|
||||
# debug procs
|
||||
NativeProcess("bridge", "cereal/messaging", ["./bridge"], onroad=False, callback=notcar),
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# Map query config
|
||||
|
||||
QUERY_RADIUS = 3000 # mts. Radius to use on OSM data queries.
|
||||
MIN_DISTANCE_FOR_NEW_QUERY = 1000 # mts. Minimum distance to query area edge before issuing a new query.
|
||||
FULL_STOP_MAX_SPEED = 1.39 # m/s Max speed for considering car is stopped.
|
||||
LOOK_AHEAD_HORIZON_TIME = 15. # s. Time horizon for look ahead of turn speed sections to provide on liveMapData msg.
|
||||
LANE_WIDTH = 3.7 # Lane width estimate. Used for detecting departures from way.
|
||||
@@ -0,0 +1,392 @@
|
||||
import numpy as np
|
||||
from enum import Enum
|
||||
from selfdrive.mapd.lib.geo import DIRECTION, R, vectors
|
||||
|
||||
from scipy.interpolate import splev, splprep
|
||||
|
||||
|
||||
_TURN_CURVATURE_THRESHOLD = 0.002 # 1/mts. A curvature over this value will generate a speed limit section.
|
||||
_MAX_LAT_ACC = 2.3 # Maximum lateral acceleration in turns.
|
||||
_SPLINE_EVAL_STEP = 5 # mts for spline evaluation for curvature calculation
|
||||
_MIN_SPEED_SECTION_LENGTH = 100. # mts. Sections below this value will not be split in smaller sections.
|
||||
_MAX_CURV_DEVIATION_FOR_SPLIT = 2. # Split a speed section if the max curvature deviates from mean by this factor.
|
||||
_MAX_CURV_SPLIT_ARC_ANGLE = 90. # degrees. Arc section to split into new speed section around max curvature.
|
||||
_MIN_NODE_DISTANCE = 50. # mts. Minimum distance between nodes for spline evaluation. Data is enhanced if not met.
|
||||
_ADDED_NODES_DIST = 15. # mts. Distance between added nodes when data is enhanced for spline evaluation.
|
||||
_DIVERTION_SEARCH_RANGE = [-200., 50.] # mt. Range of distance to current location for diversion search.
|
||||
|
||||
|
||||
def nodes_raw_data_array_for_wr(wr, drop_last=False):
|
||||
"""Provides an array of raw node data (id, lat, lon, speed_limit) for all nodes in way relation
|
||||
"""
|
||||
sl = wr.speed_limit
|
||||
data = np.array([(n.id, n.lat, n.lon, sl) for n in wr.way.nodes], dtype=float)
|
||||
|
||||
# reverse the order if way direction is backwards
|
||||
if wr.direction == DIRECTION.BACKWARD:
|
||||
data = np.flip(data, axis=0)
|
||||
|
||||
# drop last if requested
|
||||
return data[:-1] if drop_last else data
|
||||
|
||||
|
||||
def node_calculations(points):
|
||||
"""Provides node calculations based on an array of (lat, lon) points in radians.
|
||||
points is a (N x 1) array where N >= 3
|
||||
"""
|
||||
if len(points) < 3:
|
||||
raise(IndexError)
|
||||
|
||||
# Get the vector representation of node points in cartesian plane.
|
||||
# (N-1, 2) array. Not including (0., 0.)
|
||||
v = vectors(points) * R
|
||||
|
||||
# Calculate the vector magnitudes (or distance)
|
||||
# (N-1, 1) array. No distance for v[-1]
|
||||
d = np.linalg.norm(v, axis=1)
|
||||
|
||||
# Calculate the bearing (from true north clockwise) for every node.
|
||||
# (N-1, 1) array. No bearing for v[-1]
|
||||
b = np.arctan2(v[:, 0], v[:, 1])
|
||||
|
||||
# Add origin to vector space. (i.e first node in list)
|
||||
v = np.concatenate(([[0., 0.]], v))
|
||||
|
||||
# Provide distance to previous node and distance to next node
|
||||
dp = np.concatenate(([0.], d))
|
||||
dn = np.concatenate((d, [0.]))
|
||||
|
||||
# Provide cumulative distance on route
|
||||
dr = np.cumsum(dp, axis=0)
|
||||
|
||||
# Bearing of last node should keep bearing from previous.
|
||||
b = np.concatenate((b, [b[-1]]))
|
||||
|
||||
return v, dp, dn, dr, b
|
||||
|
||||
|
||||
def spline_curvature_calculations(vect, dist_prev):
|
||||
"""Provides an array of curvatures and its distances by applying a spline interpolation
|
||||
to the path described by the nodes data.
|
||||
"""
|
||||
# We need to artificially enhance the data before applying spline interpolation to avoid getting
|
||||
# inexistent curvature values close to irregularities on the road when the resolution of nodes data
|
||||
# approaching the irregularity is low.
|
||||
|
||||
# - Find indexes where dist_prev is greater than threshold
|
||||
too_far_idxs = np.nonzero(dist_prev >= _MIN_NODE_DISTANCE)[0]
|
||||
|
||||
# - Traversing in reverse order, enhance data by adding points at the found indexes.
|
||||
for idx in too_far_idxs[::-1]:
|
||||
dp = dist_prev[idx] # distance of vector that needs to be replaced by higher resolution vectors.
|
||||
n = int(np.ceil(dp / _ADDED_NODES_DIST)) # number of vectors that need to be added.
|
||||
new_v = vect[idx, :] / n # new relative vector to insert.
|
||||
vect = np.delete(vect, idx, axis=0) # remove the relative vector to be replaced by the insertion of new vectors.
|
||||
vect = np.insert(vect, [idx] * n, [new_v] * n, axis=0) # insert n new relative vectors
|
||||
|
||||
# Data is now enhanced, we can proceed with curvature evaluation.
|
||||
# - Create cumulative arrays for distance traveled and vector (x, y)
|
||||
ds = np.cumsum(dist_prev, axis=0)
|
||||
vs = np.cumsum(vect, axis=0)
|
||||
|
||||
# - spline interpolation
|
||||
tck, u = splprep([vs[:, 0], vs[:, 1]]) # pylint: disable=unbalanced-tuple-unpacking
|
||||
|
||||
# - evaluate every _SPLINE_EVAL_STEP mts.
|
||||
n = max(int(ds[-1] / _SPLINE_EVAL_STEP), len(u))
|
||||
unew = np.arange(0, n + 1) / n
|
||||
|
||||
# - get derivatives
|
||||
d1 = splev(unew, tck, der=1)
|
||||
d2 = splev(unew, tck, der=2)
|
||||
|
||||
# - calculate curvatures
|
||||
num = d1[0] * d2[1] - d1[1] * d2[0]
|
||||
den = (d1[0]**2 + d1[1]**2)**(1.5)
|
||||
curv = num / den
|
||||
curv_ds = unew * ds[-1]
|
||||
|
||||
return curv, curv_ds
|
||||
|
||||
|
||||
def speed_section(curv_sec):
|
||||
"""Map curvature section data into turn speed sections data.
|
||||
Returns: [section start distance, section end distance, speed limit based on max curvature, sing of curvature]
|
||||
"""
|
||||
max_curv_idx = np.argmax(curv_sec[:, 0])
|
||||
start = np.amin(curv_sec[:, 2])
|
||||
end = np.amax(curv_sec[:, 2])
|
||||
|
||||
return np.array([start, end, np.sqrt(_MAX_LAT_ACC / curv_sec[max_curv_idx, 0]), curv_sec[max_curv_idx, 1]])
|
||||
|
||||
|
||||
def split_speed_section_by_sign(curv_sec):
|
||||
"""Will split the given curvature section in subsections if there is a change of sign on the curvature value
|
||||
in the section.
|
||||
"""
|
||||
# Find the indexes where the curvatures change signs (if any).
|
||||
c_idx = np.nonzero(np.diff(curv_sec[:, 1]))[0] + 1
|
||||
|
||||
# Split section base on change of sign.
|
||||
return np.split(curv_sec, c_idx)
|
||||
|
||||
|
||||
def split_speed_section_by_curv_degree(curv_sec):
|
||||
"""Will split the given curvature section in subsections as to isolate peaks of turn with substantially
|
||||
higher curvature values. This will aid on preventing having very long turn sections with low speed limit
|
||||
that is only really necessary for a small region of the section.
|
||||
"""
|
||||
# Only consider splitting a section if long enough.
|
||||
length = curv_sec[-1, 2] - curv_sec[0, 2]
|
||||
if length <= _MIN_SPEED_SECTION_LENGTH:
|
||||
return [curv_sec]
|
||||
|
||||
# Only split if max curvature deviates substantially from mean curvature.
|
||||
max_curv_idx = np.argmax(curv_sec[:, 0])
|
||||
max_curv = curv_sec[max_curv_idx, 0]
|
||||
mean_curv = np.mean(curv_sec[:, 0])
|
||||
if max_curv / mean_curv <= _MAX_CURV_DEVIATION_FOR_SPLIT:
|
||||
return [curv_sec]
|
||||
|
||||
# Calculate where to split as to isolate a curve section around the max curvature peak.
|
||||
arc_side = (np.radians(_MAX_CURV_SPLIT_ARC_ANGLE) / max_curv) / 2.
|
||||
arc_side_idx_lenght = int(np.ceil(arc_side / _SPLINE_EVAL_STEP))
|
||||
split_idxs = [max_curv_idx - arc_side_idx_lenght, max_curv_idx + arc_side_idx_lenght]
|
||||
split_idxs = list(filter(lambda idx: idx > 0 and idx < len(curv_sec) - 1, split_idxs))
|
||||
|
||||
# If the arc section to split extendes outside the section, then no need to split.
|
||||
if len(split_idxs) == 0:
|
||||
return [curv_sec]
|
||||
|
||||
# Create the splits and split the resulting sections recursevly.
|
||||
splits = [split_speed_section_by_curv_degree(cs) for cs in np.split(curv_sec, split_idxs)]
|
||||
|
||||
# Flatten the results and return the new list of curvature sections.
|
||||
curv_secs = [cs for split in splits for cs in split]
|
||||
return curv_secs
|
||||
|
||||
|
||||
def speed_limits_for_curvatures_data(curv, dist):
|
||||
"""Provides the calculations for the speed limits from the curvatures array and distances,
|
||||
by providing distances to curvature sections and corresponding speed limit values as well as
|
||||
curvature direction/sign.
|
||||
"""
|
||||
# Prepare a data array for processing with absolute curvature values, curvature sign and distances.
|
||||
curv_abs = np.abs(curv)
|
||||
data = np.column_stack((curv_abs, np.sign(curv), dist))
|
||||
|
||||
# Find where curvatures overshoot turn curvature threshold and define as section
|
||||
is_section = curv_abs >= _TURN_CURVATURE_THRESHOLD
|
||||
|
||||
# Find the indexes where the sections start and end. i.e. change indexes.
|
||||
c_idx = np.nonzero(np.diff(is_section))[0] + 1
|
||||
|
||||
# Create independent arrays for each split section base on change indexes.
|
||||
splits = np.array(np.split(data, c_idx), dtype=object)
|
||||
|
||||
# Filter the splits to keep only the curvature section arrays by getting the odd or even split arrays depending
|
||||
# on whether the first split is a curvature split or not.
|
||||
curv_sec_idxs = np.arange(0 if is_section[0] else 1, len(splits), 2, dtype=int)
|
||||
curv_secs = splits[curv_sec_idxs]
|
||||
|
||||
# Further split the curv sections by sign change
|
||||
sub_secs = [split_speed_section_by_sign(cs) for cs in curv_secs]
|
||||
curv_secs = [cs for sub_sec in sub_secs for cs in sub_sec]
|
||||
|
||||
# Further split the curv sections by degree of curvature
|
||||
sub_secs = [split_speed_section_by_curv_degree(cs) for cs in curv_secs]
|
||||
curv_secs = [cs for sub_sec in sub_secs for cs in sub_sec]
|
||||
|
||||
# Return an array where each row represents a turn speed limit section.
|
||||
# [start, end, speed_limit, curvature_sign]
|
||||
return np.array([speed_section(cs) for cs in curv_secs])
|
||||
|
||||
def is_wr_a_valid_divertion_from_node(wr, node_id, wr_ids):
|
||||
"""
|
||||
Evaluates if the way relation `wr` is a valid diversion from node with id `node_id`.
|
||||
A valid diversion is a way relation with an edge node with the given `node_id` that is not already included
|
||||
in the list of way relations in the route (`wr_ids`) and that can be travaled in the direction as if starting
|
||||
from node with id `node_id`
|
||||
"""
|
||||
if wr.id in wr_ids:
|
||||
return False
|
||||
wr.update_direction_from_starting_node(node_id)
|
||||
return not wr.is_prohibited
|
||||
|
||||
|
||||
class SpeedLimitSection():
|
||||
"""And object representing a speed limited road section ahead.
|
||||
provides the start and end distance and the speed limit value
|
||||
"""
|
||||
def __init__(self, start, end, value):
|
||||
self.start = start
|
||||
self.end = end
|
||||
self.value = value
|
||||
|
||||
def __repr__(self):
|
||||
return f'from: {self.start}, to: {self.end}, limit: {self.value}'
|
||||
|
||||
|
||||
class TurnSpeedLimitSection(SpeedLimitSection):
|
||||
def __init__(self, start, end, value, sign):
|
||||
super().__init__(start, end, value)
|
||||
self.curv_sign = sign
|
||||
|
||||
def __repr__(self):
|
||||
return f'{super().__repr__()}, sign: {self.curv_sign}'
|
||||
|
||||
|
||||
class NodeDataIdx(Enum):
|
||||
"""Column index for data elements on NodesData underlying data store.
|
||||
"""
|
||||
node_id = 0
|
||||
lat = 1
|
||||
lon = 2
|
||||
speed_limit = 3
|
||||
x = 4 # x value of cartesian vector representing the section between last node and this node.
|
||||
y = 5 # y value of cartesian vector representing the section between last node and this node.
|
||||
dist_prev = 6 # distance to previous node.
|
||||
dist_next = 7 # distance to next node
|
||||
dist_route = 8 # cumulative distance on route
|
||||
bearing = 9 # bearing of the vector departing from this node.
|
||||
|
||||
|
||||
class NodesData:
|
||||
"""Container for the list of node data from a ordered list of way relations to be used in a Route
|
||||
"""
|
||||
def __init__(self, way_relations, wr_index):
|
||||
self._nodes_data = np.array([])
|
||||
self._divertions = [[]]
|
||||
self._curvature_speed_sections_data = np.array([])
|
||||
|
||||
way_count = len(way_relations)
|
||||
if way_count == 0:
|
||||
return
|
||||
|
||||
# We want all the nodes from the last way section
|
||||
nodes_data = nodes_raw_data_array_for_wr(way_relations[-1])
|
||||
|
||||
# For the ways before the last in the route we want all the nodes but the last, as that one is the first on
|
||||
# the next section. Collect them, append last way node data and concatenate the numpy arrays.
|
||||
if way_count > 1:
|
||||
wrs_data = tuple([nodes_raw_data_array_for_wr(wr, drop_last=True) for wr in way_relations[:-1]])
|
||||
wrs_data += (nodes_data,)
|
||||
nodes_data = np.concatenate(wrs_data)
|
||||
|
||||
# Get a subarray with lat, lon to compute the remaining node values.
|
||||
lat_lon_array = nodes_data[:, [1, 2]]
|
||||
points = np.radians(lat_lon_array)
|
||||
# Ensure we have more than 3 points, if not calculations are not possible.
|
||||
if len(points) <= 3:
|
||||
return
|
||||
vect, dist_prev, dist_next, dist_route, bearing = node_calculations(points)
|
||||
|
||||
# append calculations to nodes_data
|
||||
# nodes_data structure: [id, lat, lon, speed_limit, x, y, dist_prev, dist_next, dist_route, bearing]
|
||||
self._nodes_data = np.column_stack((nodes_data, vect, dist_prev, dist_next, dist_route, bearing))
|
||||
|
||||
# Build route diversion options data from the wr_index.
|
||||
wr_ids = [wr.id for wr in way_relations]
|
||||
self._divertions = [[wr for wr in wr_index.way_relations_with_edge_node_id(node_id)
|
||||
if is_wr_a_valid_divertion_from_node(wr, node_id, wr_ids)]
|
||||
for node_id in nodes_data[:, 0]]
|
||||
|
||||
# Store calculcations for curvature sections speed limits. We need more than 3 points to be able to process.
|
||||
# _curvature_speed_sections_data structure: [dist_start, dist_stop, speed_limits, curv_sign]
|
||||
if len(vect) > 3:
|
||||
curv, curv_ds = spline_curvature_calculations(vect, dist_prev)
|
||||
self._curvature_speed_sections_data = speed_limits_for_curvatures_data(curv, curv_ds)
|
||||
|
||||
@property
|
||||
def count(self):
|
||||
return len(self._nodes_data)
|
||||
|
||||
def get(self, node_data_idx):
|
||||
"""Returns the array containing all the elements of a specific NodeDataIdx type.
|
||||
"""
|
||||
if len(self._nodes_data) == 0 or node_data_idx.value >= self._nodes_data.shape[1]:
|
||||
return np.array([])
|
||||
|
||||
return self._nodes_data[:, node_data_idx.value]
|
||||
|
||||
def speed_limits_ahead(self, ahead_idx, distance_to_node_ahead):
|
||||
"""Returns and array of SpeedLimitSection objects for the actual route ahead of current location
|
||||
"""
|
||||
if len(self._nodes_data) == 0 or ahead_idx is None:
|
||||
return []
|
||||
|
||||
# Find the cumulative distances where speed limit changes. Build Speed limit sections for those.
|
||||
dist = np.concatenate(([distance_to_node_ahead], self.get(NodeDataIdx.dist_next)[ahead_idx:]))
|
||||
dist = np.cumsum(dist, axis=0)
|
||||
sl = self.get(NodeDataIdx.speed_limit)[ahead_idx - 1:]
|
||||
sl_next = np.concatenate((sl[1:], [0.]))
|
||||
|
||||
# Create a boolean mask where speed limit changes and filter values
|
||||
sl_change = sl != sl_next
|
||||
distances = dist[sl_change]
|
||||
speed_limits = sl[sl_change]
|
||||
|
||||
# Create speed limits sections combining all continuous nodes that have same speed limit value.
|
||||
start = 0.
|
||||
limits_ahead = []
|
||||
for idx, end in enumerate(distances):
|
||||
limits_ahead.append(SpeedLimitSection(start, end, speed_limits[idx]))
|
||||
start = end
|
||||
|
||||
return limits_ahead
|
||||
|
||||
def distance_to_end(self, ahead_idx, distance_to_node_ahead):
|
||||
if len(self._nodes_data) == 0 or ahead_idx is None:
|
||||
return None
|
||||
|
||||
return np.sum(np.concatenate(([distance_to_node_ahead], self.get(NodeDataIdx.dist_next)[ahead_idx:])))
|
||||
|
||||
def curvatures_speed_limit_sections_ahead(self, ahead_idx, distance_to_node_ahead):
|
||||
"""Returns and array of TurnSpeedLimitSection objects for the actual route ahead of current location for
|
||||
speed limit sections due to curvatures in the road.
|
||||
"""
|
||||
if len(self._curvature_speed_sections_data) == 0 or ahead_idx is None:
|
||||
return []
|
||||
|
||||
# Find the current distance traveled so far on the route.
|
||||
dist_curr = self.get(NodeDataIdx.dist_route)[ahead_idx] - distance_to_node_ahead
|
||||
|
||||
# Filter the sections to get only those where the stop distance is ahead of current.
|
||||
sec_filter = self._curvature_speed_sections_data[:, 1] > dist_curr
|
||||
data = self._curvature_speed_sections_data[sec_filter]
|
||||
|
||||
# Offset distances to current distance.
|
||||
data[:, [0, 1]] -= dist_curr
|
||||
|
||||
# Create speed limits sections
|
||||
limits_ahead = [TurnSpeedLimitSection(max(0., d[0]), d[1], d[2], d[3]) for d in data]
|
||||
|
||||
return limits_ahead
|
||||
|
||||
def possible_divertions(self, ahead_idx, distance_to_node_ahead):
|
||||
""" Returns and array with the way relations the route could possible divert to by finding
|
||||
the alternative way diversions on the nodes in the vicinity of the current location.
|
||||
"""
|
||||
if len(self._nodes_data) == 0 or ahead_idx is None:
|
||||
return []
|
||||
|
||||
dist_route = self.get(NodeDataIdx.dist_route)
|
||||
rel_dist = dist_route - dist_route[ahead_idx] + distance_to_node_ahead
|
||||
valid_idxs = np.nonzero(np.logical_and(rel_dist >= _DIVERTION_SEARCH_RANGE[0],
|
||||
rel_dist <= _DIVERTION_SEARCH_RANGE[1]))[0]
|
||||
valid_divertions = [self._divertions[i] for i in valid_idxs]
|
||||
|
||||
return [wr for wrs in valid_divertions for wr in wrs] # flatten.
|
||||
|
||||
def distance_to_node(self, node_id, ahead_idx, distance_to_node_ahead):
|
||||
"""
|
||||
Provides the distance to a specific node in the route identified by `node_id` in reference to the node ahead
|
||||
(`ahead_idx`) and the distance from current location to the node ahead (`distance_to_node_ahead`).
|
||||
"""
|
||||
node_ids = self.get(NodeDataIdx.node_id)
|
||||
node_idxs = np.nonzero(node_ids == node_id)[0]
|
||||
if len(self._nodes_data) == 0 or ahead_idx is None or len(node_idxs) == 0:
|
||||
return None
|
||||
|
||||
return self.get(NodeDataIdx.dist_route)[node_idxs[0]] - self.get(NodeDataIdx.dist_route)[ahead_idx] + \
|
||||
distance_to_node_ahead
|
||||
@@ -0,0 +1,340 @@
|
||||
from selfdrive.mapd.lib.NodesData import NodesData, NodeDataIdx
|
||||
from selfdrive.mapd.config import QUERY_RADIUS
|
||||
from selfdrive.mapd.lib.geo import ref_vectors, R, distance_to_points
|
||||
from itertools import compress
|
||||
import numpy as np
|
||||
|
||||
|
||||
_ACCEPTABLE_BEARING_DELTA_COSINE = -0.7 # Continuation paths with a bearing of 180 +/- 45 degrees.
|
||||
_MAX_ALLOWED_BEARING_DELTA_COSINE_AT_EDGE = -0.3420 # bearing delta at route edge must be 180 +/- 70 degrees.
|
||||
_MAP_DATA_EDGE_DISTANCE = 50 # mts. Consider edge of map data from this distance to edge of query radius.
|
||||
|
||||
|
||||
class Route():
|
||||
"""A set of consecutive way relations forming a default driving route.
|
||||
"""
|
||||
def __init__(self, current, wr_index, way_collection_id, query_center):
|
||||
"""Create a Route object from a given `wr_index` (Way relation index)
|
||||
|
||||
Args:
|
||||
current (WayRelation): The Way Relation that is currently located. It must be active.
|
||||
wr_index (WayRelationIndex): The indexes of WayRelations by node id.
|
||||
way_collection_id (UUID): The id of the Way Collection that created this Route.
|
||||
query_center (Numpy Array): lat, lon] numpy array in radians indicating the center of the data query.
|
||||
"""
|
||||
self.way_collection_id = way_collection_id
|
||||
self._ordered_way_relations = []
|
||||
self._nodes_data = None
|
||||
self._reset()
|
||||
|
||||
# An active current way is needed to be able to build a route
|
||||
if not current.active:
|
||||
return
|
||||
|
||||
# Build the route by finding iteratavely the best matching ways continuing after the end of the
|
||||
# current (last_wr) way. Use the index to find the continuation possibilities on each iteration.
|
||||
last_wr = current
|
||||
ordered_way_ids = []
|
||||
split_wrs = []
|
||||
while True:
|
||||
# - Append current element to the route list of ordered way relations.
|
||||
self._ordered_way_relations.append(last_wr)
|
||||
ordered_way_ids.append(last_wr.id)
|
||||
|
||||
# - Get the id of the node at the end of the way and then fetch the way relations that share the end node id.
|
||||
last_node_id = last_wr.last_node.id
|
||||
way_relations = wr_index.way_relations_with_edge_node_id(last_node_id)
|
||||
|
||||
# - Add split way relations when necessary and remove parent way relations.
|
||||
split_wrs_to_add = [wr for wr in split_wrs if last_node_id in wr.edge_nodes_ids]
|
||||
way_relations.extend(split_wrs_to_add)
|
||||
parent_ids = [wr.parent_wr_id for wr in split_wrs_to_add]
|
||||
way_relations = [wr for wr in way_relations if wr.id not in parent_ids]
|
||||
|
||||
# - If no more way_relations than last_wr, we have to check if we join another wr on an internal node, and
|
||||
# if we do, we replace such way relation with the split of it and continue.
|
||||
if len(way_relations) == 1:
|
||||
way_relations = wr_index.way_relations_with_node_id(last_node_id)
|
||||
# If no more way_relations than last_wr or its parent, we got to the end.
|
||||
if len(way_relations) == 1:
|
||||
break
|
||||
|
||||
# If last_wr is a split, replace its parent with last_wr
|
||||
way_relations = [last_wr if wr is last_wr.parent else wr for wr in way_relations]
|
||||
|
||||
# If we join a wr on an internal node, then we artificially split the wr in two and pass both wrs as
|
||||
# candidates to the wr selection code below.
|
||||
wr_to_split = [wr for wr in way_relations if wr is not last_wr][0]
|
||||
next_split_way_id = -len(split_wrs) - 1 # Keep split wrs ids unique on Route
|
||||
new_wrs = wr_to_split.split(last_node_id, [next_split_way_id, next_split_way_id - 1])
|
||||
# If it could not be splited, we are done.
|
||||
if len(new_wrs) != 2:
|
||||
break
|
||||
|
||||
# Replace the original way relation for the split version on way_relations and track splited wrs.
|
||||
split_wrs.extend(new_wrs)
|
||||
way_relations.remove(wr_to_split)
|
||||
way_relations.extend(new_wrs)
|
||||
|
||||
# - Get the coordinates for the edge node and build the array of coordinates for the nodes before the edge node
|
||||
# on each of the common way relations, then get the vectors in cartesian plane for the end sections of each way.
|
||||
ref_point = last_wr.last_node_coordinates
|
||||
points = np.array([wr.node_before_edge_coordinates(last_node_id) for wr in way_relations])
|
||||
v = ref_vectors(ref_point, points) * R
|
||||
|
||||
# - Calculate the bearing (from true north clockwise) for every end section of each way.
|
||||
b = np.arctan2(v[:, 0], v[:, 1])
|
||||
|
||||
# - Find index of las_wr section and calculate deltas of bearings to the other sections.
|
||||
last_wr_idx = way_relations.index(last_wr)
|
||||
b_ref = b[last_wr_idx]
|
||||
delta = b - b_ref
|
||||
|
||||
# - Update the direction of the possible route continuation ways as starting from last_node_id.
|
||||
# Make sure to exclude any ways already included in the ordered list as to not modify direction when there
|
||||
# are looping roads (like roundabouts). A way will never be included twice in a route anyway.
|
||||
for wr in way_relations:
|
||||
if wr.id not in ordered_way_ids:
|
||||
wr.update_direction_from_starting_node(last_node_id)
|
||||
|
||||
# - Filter the possible route continuation way relations:
|
||||
# - exclude any way already added to the ordered list.
|
||||
# - exclude all way relations that are prohibited due to traffic direction.
|
||||
mask = [wr.id not in ordered_way_ids and not wr.is_prohibited for wr in way_relations]
|
||||
way_relations = list(compress(way_relations, mask))
|
||||
delta = delta[mask]
|
||||
|
||||
# if no options left, we got to the end.
|
||||
if len(way_relations) == 0:
|
||||
break
|
||||
|
||||
# - The cosine of the bearing delta will aid us in choosing the way that continues. The cosine is
|
||||
# minimum (-1) for a perfect straight continuation as delta would be pi or -pi.
|
||||
cos_delta = np.cos(delta)
|
||||
|
||||
def pick_best_idx(cos_delta):
|
||||
"""Selects the best index on `cos_delta` array for a way that continues the route.
|
||||
In principle we want to choose the way that continues as straight as possible.
|
||||
Bue we need to make sure that if there are 2 or more ways continuing relatively straight, then we
|
||||
need to disambiguate, either by matching the `ref` or `name` value of the continuing way with the
|
||||
last way selected.
|
||||
This can prevent cases where the chosen route could be for instance an exit ramp of a way due to the fact
|
||||
that the ramp has a better match on bearing to previous way. We choose to stay on the road with the same `ref`
|
||||
or `name` value if available.
|
||||
If there is no ambiguity or there are no `name` or `ref` values to disambiguate, then we pick the one with
|
||||
the straightest following direction.
|
||||
"""
|
||||
# Find the indexes of the cosine of the deltas that are considered straight enough to continue.
|
||||
idxs = np.nonzero(cos_delta < _ACCEPTABLE_BEARING_DELTA_COSINE)[0]
|
||||
|
||||
# If no amiguity or no way to break it, just return the straightest line.
|
||||
if len(idxs) <= 1 or (last_wr.ref is None and last_wr.name is None):
|
||||
# The section with the best continuation is the one with a bearing delta closest to pi. This is equivalent
|
||||
# to taking the one with the smallest cosine of the bearing delta, as cosine is minimum (-1) on both pi
|
||||
# and -pi.
|
||||
return np.argmin(cos_delta)
|
||||
|
||||
wrs = [way_relations[idx] for idx in idxs]
|
||||
|
||||
# If we find a continuation way with the same reference we just choose it.
|
||||
refs = list(map(lambda wr: wr.ref, wrs))
|
||||
if last_wr.ref is not None:
|
||||
idx = next((idx for idx, ref in enumerate(refs) if ref == last_wr.ref), None)
|
||||
if idx is not None:
|
||||
return idxs[idx]
|
||||
|
||||
# If we find a continuation way with the same name we just choose it.
|
||||
names = list(map(lambda wr: wr.name, wrs))
|
||||
if last_wr.name is not None:
|
||||
idx = next((idx for idx, name in enumerate(names) if name == last_wr.name), None)
|
||||
if idx is not None:
|
||||
return idxs[idx]
|
||||
|
||||
# We did not manage to disambiguate, choose straightest path.
|
||||
return np.argmin(cos_delta)
|
||||
|
||||
# Get the index of the continuation way.
|
||||
best_idx = pick_best_idx(cos_delta)
|
||||
|
||||
# - Make sure to not select as route continuation a way that turns too much if we are close to the border of
|
||||
# map data queried. This is to avoid building a route that takes a sharp turn just because we do not have the
|
||||
# data for the way that actually continues straight.
|
||||
if cos_delta[best_idx] > _MAX_ALLOWED_BEARING_DELTA_COSINE_AT_EDGE:
|
||||
dist_to_center = distance_to_points(query_center, np.array([ref_point]))[0]
|
||||
if dist_to_center > QUERY_RADIUS - _MAP_DATA_EDGE_DISTANCE:
|
||||
break
|
||||
|
||||
# - Select next way.
|
||||
last_wr = way_relations[best_idx]
|
||||
|
||||
# Build the node data from the ordered list of way relations
|
||||
self._nodes_data = NodesData(self._ordered_way_relations, wr_index)
|
||||
|
||||
# Locate where we are in the route node list.
|
||||
self._locate()
|
||||
|
||||
def __repr__(self):
|
||||
count = self._nodes_data.count if self._nodes_data is not None else None
|
||||
return f'Route: {self.way_collection_id}, idx ahead: {self._ahead_idx} of {count}'
|
||||
|
||||
def _reset(self):
|
||||
self._limits_ahead = None
|
||||
self._cuvature_limits_ahead = None
|
||||
self._curvatures_ahead = None
|
||||
self._ahead_idx = None
|
||||
self._distance_to_node_ahead = None
|
||||
|
||||
@property
|
||||
def located(self):
|
||||
return self._ahead_idx is not None
|
||||
|
||||
def _locate(self):
|
||||
"""Will resolve the index in the nodes_data list for the node ahead of the current location.
|
||||
It updates as well the distance from the current location to the node ahead.
|
||||
"""
|
||||
current = self.current_wr
|
||||
if current is None:
|
||||
return
|
||||
|
||||
node_ahead_id = current.node_ahead.id
|
||||
self._distance_to_node_ahead = current.distance_to_node_ahead
|
||||
start_idx = self._ahead_idx if self._ahead_idx is not None else 1
|
||||
self._ahead_idx = None
|
||||
|
||||
ids = self._nodes_data.get(NodeDataIdx.node_id)
|
||||
for idx in range(start_idx, len(ids)):
|
||||
if ids[idx] == node_ahead_id:
|
||||
self._ahead_idx = idx
|
||||
break
|
||||
|
||||
@property
|
||||
def current_wr(self):
|
||||
return self._ordered_way_relations[0] if len(self._ordered_way_relations) else None
|
||||
|
||||
def update(self, location_rad, bearing_rad, location_stdev):
|
||||
"""Will update the route structure based on the given `location_rad` and `bearing_rad` assuming progress on the
|
||||
route on the original direction. If direction has changed or active point on the route can not be found, the route
|
||||
will become invalid.
|
||||
"""
|
||||
if len(self._ordered_way_relations) == 0 or location_rad is None or bearing_rad is None:
|
||||
return
|
||||
|
||||
# Skip if no update on location or bearing.
|
||||
if np.array_equal(self.current_wr.location_rad, location_rad) and self.current_wr.bearing_rad == bearing_rad:
|
||||
return
|
||||
|
||||
# Transverse the way relations on the actual order until we find an active one. From there, rebuild the route
|
||||
# with the way relations remaining ahead.
|
||||
for idx, wr in enumerate(self._ordered_way_relations):
|
||||
active_direction = wr.direction
|
||||
wr.update(location_rad, bearing_rad, location_stdev)
|
||||
|
||||
if not wr.active:
|
||||
continue
|
||||
|
||||
if wr.direction != active_direction:
|
||||
# Driving direction on the route has changed. stop.
|
||||
break
|
||||
|
||||
# We have now the current wr. Repopulate from here till the end and locate
|
||||
self._ordered_way_relations = self._ordered_way_relations[idx:]
|
||||
self._reset()
|
||||
self._locate()
|
||||
|
||||
# If the active way is diverting, check whether there are possibilities to divert from the route in the
|
||||
# vecinity of the current location. If there are possibilities, then stop here to loose the route as we are
|
||||
# most likely driving away. If there are no possibilities, then stick to the route as the diversion is probably
|
||||
# just a matter of GPS accuracy. (It can happen after driving under a bridge)
|
||||
if wr.diverting and len(self._nodes_data.possible_divertions(self._ahead_idx, self._distance_to_node_ahead)) > 0:
|
||||
break
|
||||
|
||||
# The current location in route is valid, return.
|
||||
return
|
||||
|
||||
# if we got here, there is no new active way relation or driving direction has changed. Reset.
|
||||
self._reset()
|
||||
|
||||
@property
|
||||
def speed_limits_ahead(self):
|
||||
"""Returns and array of SpeedLimitSection objects for the actual route ahead of current location
|
||||
"""
|
||||
if self._limits_ahead is not None:
|
||||
return self._limits_ahead
|
||||
|
||||
if self._nodes_data is None or self._ahead_idx is None:
|
||||
return []
|
||||
|
||||
self._limits_ahead = self._nodes_data.speed_limits_ahead(self._ahead_idx, self._distance_to_node_ahead)
|
||||
return self._limits_ahead
|
||||
|
||||
@property
|
||||
def curvature_speed_limits_ahead(self):
|
||||
"""Returns and array of TurnSpeedLimitSection objects for the actual route ahead of current location due
|
||||
to curvatures
|
||||
"""
|
||||
if self._cuvature_limits_ahead is not None:
|
||||
return self._cuvature_limits_ahead
|
||||
|
||||
if self._nodes_data is None or self._ahead_idx is None:
|
||||
return []
|
||||
|
||||
self._cuvature_limits_ahead = self._nodes_data. \
|
||||
curvatures_speed_limit_sections_ahead(self._ahead_idx, self._distance_to_node_ahead)
|
||||
|
||||
return self._cuvature_limits_ahead
|
||||
|
||||
@property
|
||||
def current_speed_limit(self):
|
||||
if not self.located:
|
||||
return None
|
||||
|
||||
limits_ahead = self.speed_limits_ahead
|
||||
if len(limits_ahead) == 0 or limits_ahead[0].start != 0:
|
||||
return None
|
||||
|
||||
return limits_ahead[0].value
|
||||
|
||||
@property
|
||||
def current_curvature_speed_limit_section(self):
|
||||
if not self.located:
|
||||
return None
|
||||
|
||||
limits_ahead = self.curvature_speed_limits_ahead
|
||||
if len(limits_ahead) == 0 or limits_ahead[0].start != 0:
|
||||
return None
|
||||
|
||||
return limits_ahead[0]
|
||||
|
||||
@property
|
||||
def next_speed_limit_section(self):
|
||||
if not self.located:
|
||||
return None
|
||||
|
||||
limits_ahead = self.speed_limits_ahead
|
||||
if len(limits_ahead) == 0:
|
||||
return None
|
||||
|
||||
# Find the first section that does not start in 0. i.e. the next section
|
||||
for section in limits_ahead:
|
||||
if section.start > 0:
|
||||
return section
|
||||
|
||||
return None
|
||||
|
||||
def next_curvature_speed_limit_sections(self, horizon_mts):
|
||||
if not self.located:
|
||||
return []
|
||||
|
||||
# Provide the curvature speed sections that start ahead (> 0) and up to horizon
|
||||
return list(filter(lambda la: la.start > 0 and la.start <= horizon_mts, self.curvature_speed_limits_ahead))
|
||||
|
||||
@property
|
||||
def distance_to_end(self):
|
||||
if not self.located:
|
||||
return None
|
||||
|
||||
return self._nodes_data.distance_to_end(self._ahead_idx, self._distance_to_node_ahead)
|
||||
|
||||
@property
|
||||
def current_road_name(self):
|
||||
return self.current_wr.road_name if self.located else None
|
||||
@@ -0,0 +1,85 @@
|
||||
from selfdrive.mapd.lib.WayRelation import WayRelation
|
||||
from selfdrive.mapd.lib.WayRelationIndex import WayRelationIndex
|
||||
from selfdrive.mapd.lib.Route import Route
|
||||
from selfdrive.mapd.config import LANE_WIDTH
|
||||
import uuid
|
||||
|
||||
|
||||
_ACCEPTABLE_BEARING_DELTA_IND = 0.7071067811865475 # sin(pi/4) | 45 degrees acceptable bearing delta
|
||||
|
||||
|
||||
class WayCollection():
|
||||
"""A collection of WayRelations to use for maps data analysis.
|
||||
"""
|
||||
def __init__(self, ways, query_center):
|
||||
"""Creates a WayCollection with a set of OSM way objects.
|
||||
|
||||
Args:
|
||||
ways (Array): Collection of Way objects fetched from OSM in a radius around `query_center`
|
||||
query_center (Numpy Array): [lat, lon] numpy array in radians indicating the center of the data query.
|
||||
"""
|
||||
self.id = uuid.uuid4()
|
||||
self.way_relations = [WayRelation(way) for way in ways]
|
||||
self.query_center = query_center
|
||||
|
||||
self.wr_index = WayRelationIndex(self.way_relations)
|
||||
|
||||
def get_route(self, location_rad, bearing_rad, location_stdev):
|
||||
"""Provides the best route found in the way collection based on current location and bearing.
|
||||
"""
|
||||
if location_rad is None or bearing_rad is None or location_stdev is None:
|
||||
return None
|
||||
|
||||
# Update all way relations in collection to the provided location and bearing.
|
||||
for wr in self.way_relations:
|
||||
wr.update(location_rad, bearing_rad, location_stdev)
|
||||
|
||||
# Get the way relations where a match was found. i.e. those now marked as active as long as the direction of
|
||||
# travel is valid.
|
||||
valid_way_relations = [wr for wr in self.way_relations if wr.active and not wr.is_prohibited]
|
||||
|
||||
# If no active, then we could not find a current way to build a route.
|
||||
if len(valid_way_relations) == 0:
|
||||
return None
|
||||
|
||||
# If only one valid, then pick it as current.
|
||||
if len(valid_way_relations) == 1:
|
||||
current = valid_way_relations[0]
|
||||
|
||||
# If more than one is valid, filter out any valid way relation where the bearing delta indicator is too high.
|
||||
else:
|
||||
wr_acceptable_bearing = list(filter(lambda wr: wr.active_bearing_delta <= _ACCEPTABLE_BEARING_DELTA_IND,
|
||||
valid_way_relations))
|
||||
|
||||
# If delta bearing indicator is too high for all, then use as current the one that has the shorter one.
|
||||
if len(wr_acceptable_bearing) == 0:
|
||||
valid_way_relations.sort(key=lambda wr: wr.active_bearing_delta)
|
||||
current = valid_way_relations[0]
|
||||
|
||||
# If only one with acceptable bearing, use it.
|
||||
elif len(wr_acceptable_bearing) == 1:
|
||||
current = wr_acceptable_bearing[0]
|
||||
|
||||
else:
|
||||
# If more than one with acceptable bearing, filter the ones with distance to way lower than 2 standard
|
||||
# deviation from GPS accuracy (95%) + half the road width estimate.
|
||||
wr_accurate_distance = [wr for wr in wr_acceptable_bearing
|
||||
if wr.distance_to_way <= 2. * location_stdev + wr.lanes * LANE_WIDTH / 2.]
|
||||
|
||||
# If none with accurate distance to way, then select the closest to the way
|
||||
if len(wr_accurate_distance) == 0:
|
||||
wr_acceptable_bearing.sort(key=lambda wr: wr.distance_to_way)
|
||||
current = wr_acceptable_bearing[0]
|
||||
|
||||
# If only one with distance under accuracy, select this one.
|
||||
elif len(wr_accurate_distance) == 1:
|
||||
current = wr_accurate_distance[0]
|
||||
|
||||
# If more than one with distance under accuracy. Then select the one with lowest highway rank.
|
||||
# i.e. preferred motorways over other roads and so on. This is to prevent selecting a small parallel
|
||||
# road to a main road when the accuracy is poor.
|
||||
else:
|
||||
wr_accurate_distance.sort(key=lambda wr: wr.highway_rank)
|
||||
current = wr_accurate_distance[0]
|
||||
|
||||
return Route(current, self.wr_index, self.id, self.query_center)
|
||||
@@ -0,0 +1,422 @@
|
||||
from selfdrive.mapd.lib.geo import DIRECTION, R, vectors, bearing_to_points, distance_to_points
|
||||
from selfdrive.mapd.lib.osm import create_way
|
||||
from common.conversions import Conversions as CV
|
||||
from selfdrive.mapd.config import LANE_WIDTH
|
||||
from common.basedir import BASEDIR
|
||||
from datetime import datetime as dt
|
||||
import numpy as np
|
||||
import re
|
||||
import json
|
||||
|
||||
|
||||
_WAY_BBOX_PADING = 80. / R # 80 mts of padding to bounding box. (expressed in radians)
|
||||
|
||||
|
||||
with open(BASEDIR + "/selfdrive/mapd/lib/default_speeds.json", "rb") as f:
|
||||
_COUNTRY_LIMITS = json.loads(f.read())
|
||||
|
||||
|
||||
_WD = {
|
||||
'Mo': 0,
|
||||
'Tu': 1,
|
||||
'We': 2,
|
||||
'Th': 3,
|
||||
'Fr': 4,
|
||||
'Sa': 5,
|
||||
'Su': 6
|
||||
}
|
||||
|
||||
_HIGHWAY_RANK = {
|
||||
'motorway': 0,
|
||||
'motorway_link': 1,
|
||||
'trunk': 10,
|
||||
'trunk_link': 11,
|
||||
'primary': 20,
|
||||
'primary_link': 21,
|
||||
'secondary': 30,
|
||||
'secondary_link': 31,
|
||||
'tertiary': 40,
|
||||
'tertiary_link': 41,
|
||||
'unclassified': 50,
|
||||
'residential': 60,
|
||||
'living_street': 61
|
||||
}
|
||||
|
||||
|
||||
def is_osm_time_condition_active(condition_string):
|
||||
"""
|
||||
Will indicate if a time condition for a restriction as described
|
||||
@ https://wiki.openstreetmap.org/wiki/Conditional_restrictions
|
||||
is active for the current date and time of day.
|
||||
"""
|
||||
now = dt.now().astimezone()
|
||||
today = now.date()
|
||||
week_days = []
|
||||
|
||||
# Look for days of week matched and validate if today matches criteria.
|
||||
dr = re.findall(r'(Mo|Tu|We|Th|Fr|Sa|Su[-,\s]*?)', condition_string)
|
||||
|
||||
if len(dr) == 1:
|
||||
week_days = [_WD[dr[0]]]
|
||||
# If two or more matches condider it a range of days between 1st and 2nd element.
|
||||
elif len(dr) > 1:
|
||||
week_days = list(range(_WD[dr[0]], _WD[dr[1]] + 1))
|
||||
|
||||
# If valid week days list is not empty and today day is not in the list, then the time-date range is not active.
|
||||
if len(week_days) > 0 and now.weekday() not in week_days:
|
||||
return False
|
||||
|
||||
# Look for time ranges on the day. No time range, means all day
|
||||
tr = re.findall(r'([0-9]{1,2}:[0-9]{2})\s*?-\s*?([0-9]{1,2}:[0-9]{2})', condition_string)
|
||||
|
||||
# if no time range but there were week days set, consider it active during the whole day
|
||||
if len(tr) == 0:
|
||||
return len(dr) > 0
|
||||
|
||||
# Search among time ranges matched, one where now time belongs too. If found range is active.
|
||||
for times_tup in tr:
|
||||
times = list(map(lambda tt: dt.
|
||||
combine(today, dt.strptime(tt, '%H:%M').time().replace(tzinfo=now.tzinfo)), times_tup))
|
||||
if now >= times[0] and now <= times[1]:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def speed_limit_value_for_limit_string(limit_string):
|
||||
# Look for matches of speed by default in kph, or in mph when explicitly noted.
|
||||
v = re.match(r'^\s*([0-9]{1,3})\s*?(mph)?\s*$', limit_string)
|
||||
if v is None:
|
||||
return None
|
||||
conv = CV.MPH_TO_MS if v[2] is not None and v[2] == "mph" else CV.KPH_TO_MS
|
||||
return conv * float(v[1])
|
||||
|
||||
|
||||
def speed_limit_for_osm_tag_limit_string(limit_string):
|
||||
# https://wiki.openstreetmap.org/wiki/Key:maxspeed
|
||||
if limit_string is None:
|
||||
# When limit is set to 0. is considered not existing.
|
||||
return 0.
|
||||
|
||||
# Attempt to parse limit as simple numeric value considering units.
|
||||
limit = speed_limit_value_for_limit_string(limit_string)
|
||||
if limit is not None:
|
||||
return limit
|
||||
|
||||
# Look for matches of speed with country implicit values.
|
||||
v = re.match(r'^\s*([A-Z]{2}):([a-z_]+):?([0-9]{1,3})?(\s+)?(mph)?\s*', limit_string)
|
||||
if v is None:
|
||||
return 0.
|
||||
|
||||
if v[2] == "zone" and v[3] is not None:
|
||||
conv = CV.MPH_TO_MS if v[5] is not None and v[5] == "mph" else CV.KPH_TO_MS
|
||||
limit = conv * float(v[3])
|
||||
elif f'{v[1]}:{v[2]}' in _COUNTRY_LIMITS:
|
||||
limit = speed_limit_value_for_limit_string(_COUNTRY_LIMITS[f'{v[1]}:{v[2]}'])
|
||||
|
||||
return limit if limit is not None else 0.
|
||||
|
||||
|
||||
def conditional_speed_limit_for_osm_tag_limit_string(limit_string):
|
||||
if limit_string is None:
|
||||
# When limit is set to 0. is considered not existing.
|
||||
return 0.
|
||||
|
||||
# Look for matches of the `<restriction-value> @ (<condition>)` format
|
||||
v = re.match(r'^(.*)@\s*\((.*)\).*$', limit_string)
|
||||
if v is None:
|
||||
return 0. # No valid format match
|
||||
|
||||
value = speed_limit_for_osm_tag_limit_string(v[1])
|
||||
if value == 0.:
|
||||
return 0. # Invalid speed limit value
|
||||
|
||||
# Look for date-time conditions separated by semicolon
|
||||
v = re.findall(r'(?:;|^)([^;]*)', v[2])
|
||||
for datetime_condition in v:
|
||||
if is_osm_time_condition_active(datetime_condition):
|
||||
return value
|
||||
|
||||
# If we get here, no current date-time condition is active.
|
||||
return 0.
|
||||
|
||||
|
||||
class WayRelation():
|
||||
"""A class that represent the relationship of an OSM way and a given `location` and `bearing` of a driving vehicle.
|
||||
"""
|
||||
def __init__(self, way, parent=None):
|
||||
self.way = way
|
||||
self.parent = parent
|
||||
self.parent_wr_id = parent.id if parent is not None else None # For WRs created as splits of other WRs
|
||||
self.reset_location_variables()
|
||||
self.direction = DIRECTION.NONE
|
||||
self._speed_limit = None
|
||||
self._one_way = way.tags.get("oneway")
|
||||
self.name = way.tags.get('name')
|
||||
self.ref = way.tags.get('ref')
|
||||
self.highway_type = way.tags.get("highway")
|
||||
self.highway_rank = _HIGHWAY_RANK.get(self.highway_type, 1000)
|
||||
try:
|
||||
self.lanes = int(way.tags.get('lanes'))
|
||||
except Exception:
|
||||
self.lanes = 2
|
||||
|
||||
# Create numpy arrays with nodes data to support calculations.
|
||||
self._nodes_np = np.radians(np.array([[node.lat, node.lon] for node in way.nodes], dtype=float))
|
||||
self._nodes_ids = np.array([node.id for node in way .nodes], dtype=int)
|
||||
|
||||
# Get the vectors representation of the segments betwheen consecutive nodes. (N-1, 2)
|
||||
v = vectors(self._nodes_np) * R
|
||||
|
||||
# Calculate the vector magnitudes (or distance) between nodes. (N-1)
|
||||
self._way_distances = np.linalg.norm(v, axis=1)
|
||||
|
||||
# Calculate the bearing (from true north clockwise) for every section of the way (vectors between nodes). (N-1)
|
||||
self._way_bearings = np.arctan2(v[:, 0], v[:, 1])
|
||||
|
||||
# Define bounding box to ease the process of locating a node in a way.
|
||||
# [[min_lat, min_lon], [max_lat, max_lon]]
|
||||
self.bbox = np.row_stack((np.amin(self._nodes_np, 0) - _WAY_BBOX_PADING,
|
||||
np.amax(self._nodes_np, 0) + _WAY_BBOX_PADING))
|
||||
|
||||
# Get the edge nodes ids.
|
||||
self.edge_nodes_ids = [way.nodes[0].id, way.nodes[-1].id]
|
||||
|
||||
def __repr__(self):
|
||||
return f'(id: {self.id}, between {self.behind_idx} and {self.ahead_idx}, {self.direction}, active: {self.active})'
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, WayRelation):
|
||||
return self.id == other.id
|
||||
return False
|
||||
|
||||
def reset_location_variables(self):
|
||||
self.distance_to_node_ahead = 0.
|
||||
self.location_rad = None
|
||||
self.bearing_rad = None
|
||||
self.active = False
|
||||
self.diverting = False
|
||||
self.ahead_idx = None
|
||||
self.behind_idx = None
|
||||
self._active_bearing_delta = None
|
||||
self._distance_to_way = None
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
return self.way.id
|
||||
|
||||
@property
|
||||
def road_name(self):
|
||||
if self.name is not None:
|
||||
return self.name
|
||||
return self.ref
|
||||
|
||||
def update(self, location_rad, bearing_rad, location_stdev):
|
||||
"""Will update and validate the associated way with a given `location_rad` and `bearing_rad`.
|
||||
Specifically it will find the nodes behind and ahead of the current location and bearing.
|
||||
If no proper fit to the way geometry, the way relation is marked as invalid.
|
||||
"""
|
||||
self.reset_location_variables()
|
||||
|
||||
# Ignore if location not in way bounding box
|
||||
if not self.is_location_in_bbox(location_rad):
|
||||
return
|
||||
|
||||
# - Get the distance and bearings from location to all nodes. (N)
|
||||
bearings = bearing_to_points(location_rad, self._nodes_np)
|
||||
distances = distance_to_points(location_rad, self._nodes_np)
|
||||
|
||||
# - Get absolute bearing delta to current driving bearing. (N)
|
||||
delta = np.abs(bearing_rad - bearings)
|
||||
|
||||
# - Nodes are ahead if the cosine of the delta is positive (N)
|
||||
is_ahead = np.cos(delta) >= 0.
|
||||
|
||||
# - Possible locations on the way are those where adjacent nodes change from ahead to behind or vice-versa.
|
||||
possible_idxs = np.nonzero(np.diff(is_ahead))[0]
|
||||
|
||||
# - when no possible locations found, then the location is not in this way.
|
||||
if len(possible_idxs) == 0:
|
||||
return
|
||||
|
||||
# - Find then angle formed between the vectors from the current location to consecutive nodes. This is the
|
||||
# value of the difference in the bearings of the vectors.
|
||||
teta = np.diff(bearings)
|
||||
|
||||
# - When two consecutive nodes will be ahead and behind, they will form a triangle with the current location.
|
||||
# We find the closest distance to the way by solving the area of the triangle and finding the height (h).
|
||||
# We must use the absolute value of the sin of the angle in the formula, which is equivalent to ensure we
|
||||
# are considering the smallest of the two angles formed between the two vectors.
|
||||
# https://www.mathsisfun.com/algebra/trig-area-triangle-without-right-angle.html
|
||||
h = distances[:-1] * distances[1:] * np.abs(np.sin(teta)) / self._way_distances
|
||||
|
||||
# - Calculate the delta between driving bearing and way bearings. (N-1)
|
||||
bw_delta = self._way_bearings - bearing_rad
|
||||
|
||||
# - The absolute value of the sin of `bw_delta` indicates how close the bearings match independent of direction.
|
||||
# We will use this value along the distance to the way to aid on way selection. (N-1)
|
||||
abs_sin_bw_delta = np.abs(np.sin(bw_delta))
|
||||
|
||||
# - Get the delta to way bearing indicators and the distance to the way for the possible locations.
|
||||
abs_sin_bw_delta_possible = abs_sin_bw_delta[possible_idxs]
|
||||
h_possible = h[possible_idxs]
|
||||
|
||||
# - Get the index where the distance to the way is minimum. That is the chosen location.
|
||||
min_h_possible_idx = np.argmin(h_possible)
|
||||
min_delta_idx = possible_idxs[min_h_possible_idx]
|
||||
|
||||
# - If the distance to the way is over 4 standard deviations of the gps accuracy + half the maximum road width
|
||||
# estimate, then we are way too far to stick to this way (i.e. we are not on this way anymore)
|
||||
half_road_width_estimate = self.lanes * LANE_WIDTH / 2.
|
||||
if h_possible[min_h_possible_idx] > 4. * location_stdev + half_road_width_estimate:
|
||||
return
|
||||
|
||||
# - If the distance to the road is greater than 2 standard deviations of the gps accuracy + half the maximum road
|
||||
# width estimate then we are most likely diverting from this route.
|
||||
diverting = h_possible[min_h_possible_idx] > 2. * location_stdev + half_road_width_estimate
|
||||
|
||||
# Populate location variables with result
|
||||
if is_ahead[min_delta_idx]:
|
||||
self.direction = DIRECTION.BACKWARD
|
||||
self.ahead_idx = min_delta_idx
|
||||
self.behind_idx = min_delta_idx + 1
|
||||
else:
|
||||
self.direction = DIRECTION.FORWARD
|
||||
self.ahead_idx = min_delta_idx + 1
|
||||
self.behind_idx = min_delta_idx
|
||||
|
||||
self._distance_to_way = h[min_delta_idx]
|
||||
self._active_bearing_delta = abs_sin_bw_delta_possible[min_h_possible_idx]
|
||||
# TODO: The distance to node ahead currently represent the distance from the GPS fix location.
|
||||
# It would be perhaps more accurate to use the distance on the projection over the direct line between
|
||||
# the two nodes.
|
||||
self.distance_to_node_ahead = distances[self.ahead_idx]
|
||||
self.active = True
|
||||
self.diverting = diverting
|
||||
self.location_rad = location_rad
|
||||
self.bearing_rad = bearing_rad
|
||||
self._speed_limit = None
|
||||
|
||||
def update_direction_from_starting_node(self, start_node_id):
|
||||
self._speed_limit = None
|
||||
if self.edge_nodes_ids[0] == start_node_id:
|
||||
self.direction = DIRECTION.FORWARD
|
||||
elif self.edge_nodes_ids[-1] == start_node_id:
|
||||
self.direction = DIRECTION.BACKWARD
|
||||
else:
|
||||
self.direction = DIRECTION.NONE
|
||||
|
||||
def is_location_in_bbox(self, location_rad):
|
||||
"""Indicates if a given location is contained in the bounding box surrounding the way.
|
||||
self.bbox = [[min_lat, min_lon], [max_lat, max_lon]]
|
||||
"""
|
||||
is_g = np.greater_equal(location_rad, self.bbox[0, :])
|
||||
is_l = np.less_equal(location_rad, self.bbox[1, :])
|
||||
|
||||
return np.all(np.concatenate((is_g, is_l)))
|
||||
|
||||
@property
|
||||
def speed_limit(self):
|
||||
if self._speed_limit is not None:
|
||||
return self._speed_limit
|
||||
|
||||
# Get string from corresponding tag, consider conditional limits first.
|
||||
limit_string = self.way.tags.get("maxspeed:conditional")
|
||||
if limit_string is None:
|
||||
if self.direction == DIRECTION.FORWARD:
|
||||
limit_string = self.way.tags.get("maxspeed:forward:conditional")
|
||||
elif self.direction == DIRECTION.BACKWARD:
|
||||
limit_string = self.way.tags.get("maxspeed:backward:conditional")
|
||||
|
||||
limit = conditional_speed_limit_for_osm_tag_limit_string(limit_string)
|
||||
|
||||
# When no conditional limit set, attempt to get from regular speed limit tags.
|
||||
if limit == 0.:
|
||||
limit_string = self.way.tags.get("maxspeed")
|
||||
if limit_string is None:
|
||||
if self.direction == DIRECTION.FORWARD:
|
||||
limit_string = self.way.tags.get("maxspeed:forward")
|
||||
elif self.direction == DIRECTION.BACKWARD:
|
||||
limit_string = self.way.tags.get("maxspeed:backward")
|
||||
|
||||
limit = speed_limit_for_osm_tag_limit_string(limit_string)
|
||||
|
||||
self._speed_limit = limit
|
||||
return self._speed_limit
|
||||
|
||||
@property
|
||||
def active_bearing_delta(self):
|
||||
"""Returns the sine of the delta between the current location bearing and the exact
|
||||
bearing of the portion of way we are currentluy located at.
|
||||
"""
|
||||
return self._active_bearing_delta
|
||||
|
||||
@property
|
||||
def is_one_way(self):
|
||||
return self._one_way in ['yes'] or self.highway_type in ["motorway"]
|
||||
|
||||
@property
|
||||
def is_prohibited(self):
|
||||
# Direction must be defined to asses this property. Default to `True` if not.
|
||||
if self.direction == DIRECTION.NONE:
|
||||
return True
|
||||
return self.is_one_way and self.direction == DIRECTION.BACKWARD
|
||||
|
||||
@property
|
||||
def distance_to_way(self):
|
||||
"""Returns the perpendicular (i.e. minimum) distance between current location and the way
|
||||
"""
|
||||
return self._distance_to_way
|
||||
|
||||
@property
|
||||
def node_ahead(self):
|
||||
return self.way.nodes[self.ahead_idx] if self.ahead_idx is not None else None
|
||||
|
||||
@property
|
||||
def last_node(self):
|
||||
"""Returns the last node on the way considering the traveling direction
|
||||
"""
|
||||
if self.direction == DIRECTION.FORWARD:
|
||||
return self.way.nodes[-1]
|
||||
if self.direction == DIRECTION.BACKWARD:
|
||||
return self.way.nodes[0]
|
||||
return None
|
||||
|
||||
@property
|
||||
def last_node_coordinates(self):
|
||||
"""Returns the coordinates for the last node on the way considering the traveling direction. (in radians)
|
||||
"""
|
||||
if self.direction == DIRECTION.FORWARD:
|
||||
return self._nodes_np[-1]
|
||||
if self.direction == DIRECTION.BACKWARD:
|
||||
return self._nodes_np[0]
|
||||
return None
|
||||
|
||||
def node_before_edge_coordinates(self, node_id):
|
||||
"""Returns the coordinates of the node before the edge node identifeid with `node_id`. (in radians)
|
||||
"""
|
||||
if self.edge_nodes_ids[0] == node_id:
|
||||
return self._nodes_np[1]
|
||||
|
||||
if self.edge_nodes_ids[-1] == node_id:
|
||||
return self._nodes_np[-2]
|
||||
|
||||
return np.array([0., 0.])
|
||||
|
||||
def split(self, node_id, way_ids=None):
|
||||
""" Returns and array with the way relations resulting from splitting the current way relation at node_id
|
||||
"""
|
||||
idxs = np.nonzero(self._nodes_ids == node_id)[0]
|
||||
if len(idxs) == 0:
|
||||
return []
|
||||
|
||||
idx = idxs[0]
|
||||
if idx == 0 or idx == len(self._nodes_ids) - 1:
|
||||
return [self]
|
||||
|
||||
if not isinstance(way_ids, list):
|
||||
way_ids = [-1, -2] # Default id values.
|
||||
|
||||
ways = [create_way(way_ids[0], node_ids=self._nodes_ids[:idx + 1], from_way=self.way),
|
||||
create_way(way_ids[1], node_ids=self._nodes_ids[idx:], from_way=self.way)]
|
||||
return [WayRelation(way, parent=self) for way in ways]
|
||||
@@ -0,0 +1,34 @@
|
||||
|
||||
|
||||
class WayRelationIndex():
|
||||
"""
|
||||
A class containing an index of WayRelations by node ids of internal nodes and edge nodes.
|
||||
"""
|
||||
def __init__(self, way_relations):
|
||||
self._edge_nodes_index_dict = {}
|
||||
self._full_nodes_index_dict = {}
|
||||
|
||||
for wr in way_relations:
|
||||
self.add(wr)
|
||||
|
||||
def add(self, way_relation):
|
||||
for node in way_relation.way.nodes:
|
||||
node_id = node.id
|
||||
self._full_nodes_index_dict[node_id] = self._full_nodes_index_dict.get(node_id, []) + [way_relation]
|
||||
if node_id in way_relation.edge_nodes_ids:
|
||||
self._edge_nodes_index_dict[node_id] = self._edge_nodes_index_dict.get(node_id, []) + [way_relation]
|
||||
|
||||
def remove(self, way_relation):
|
||||
for node in way_relation.way.nodes:
|
||||
node_id = node.id
|
||||
self._full_nodes_index_dict[node_id] = [wr for wr in self._full_nodes_index_dict.get(node_id, [])
|
||||
if wr is not way_relation]
|
||||
if node_id in way_relation.edge_nodes_ids:
|
||||
self._edge_nodes_index_dict[node_id] = [wr for wr in self._edge_nodes_index_dict.get(node_id, [])
|
||||
if wr is not way_relation]
|
||||
|
||||
def way_relations_with_edge_node_id(self, node_id):
|
||||
return self._edge_nodes_index_dict.get(node_id, [])
|
||||
|
||||
def way_relations_with_node_id(self, node_id):
|
||||
return self._full_nodes_index_dict.get(node_id, [])
|
||||
@@ -0,0 +1,111 @@
|
||||
{
|
||||
"_comment": "These speeds are from https://wiki.openstreetmap.org/wiki/Speed_limits Special cases have been stripped",
|
||||
"AR:urban": "40",
|
||||
"AR:urban:primary": "60",
|
||||
"AR:urban:secondary": "60",
|
||||
"AR:rural": "110",
|
||||
"AT:urban": "50",
|
||||
"AT:rural": "100",
|
||||
"AT:trunk": "100",
|
||||
"AT:motorway": "130",
|
||||
"BE:urban": "50",
|
||||
"BE-VLG:rural": "70",
|
||||
"BE-WAL:rural": "90",
|
||||
"BE:trunk": "120",
|
||||
"BE:motorway": "120",
|
||||
"CH:urban[1]": "50",
|
||||
"CH:rural": "80",
|
||||
"CH:trunk": "100",
|
||||
"CH:motorway": "120",
|
||||
"CZ:pedestrian_zone": "20",
|
||||
"CZ:living_street": "20",
|
||||
"CZ:urban": "50",
|
||||
"CZ:urban_trunk": "80",
|
||||
"CZ:urban_motorway": "80",
|
||||
"CZ:rural": "90",
|
||||
"CZ:trunk": "110",
|
||||
"CZ:motorway": "130",
|
||||
"DK:urban": "50",
|
||||
"DK:rural": "80",
|
||||
"DK:motorway": "130",
|
||||
"DE:living_street": "7",
|
||||
"DE:residential": "30",
|
||||
"DE:urban": "50",
|
||||
"DE:rural": "100",
|
||||
"DE:trunk": "none",
|
||||
"DE:motorway": "none",
|
||||
"FI:urban": "50",
|
||||
"FI:rural": "80",
|
||||
"FI:trunk": "100",
|
||||
"FI:motorway": "120",
|
||||
"FR:urban": "50",
|
||||
"FR:rural": "80",
|
||||
"FR:trunk": "110",
|
||||
"FR:motorway": "130",
|
||||
"GR:urban": "50",
|
||||
"GR:rural": "90",
|
||||
"GR:trunk": "110",
|
||||
"GR:motorway": "130",
|
||||
"HU:urban": "50",
|
||||
"HU:rural": "90",
|
||||
"HU:trunk": "110",
|
||||
"HU:motorway": "130",
|
||||
"IT:urban": "50",
|
||||
"IT:rural": "90",
|
||||
"IT:trunk": "110",
|
||||
"IT:motorway": "130",
|
||||
"JP:national": "60",
|
||||
"JP:motorway": "100",
|
||||
"LT:living_street": "20",
|
||||
"LT:urban": "50",
|
||||
"LT:rural": "90",
|
||||
"LT:trunk": "120",
|
||||
"LT:motorway": "130",
|
||||
"PL:living_street": "20",
|
||||
"PL:urban": "50",
|
||||
"PL:rural": "90",
|
||||
"PL:trunk": "100",
|
||||
"PL:motorway": "140",
|
||||
"RO:urban": "50",
|
||||
"RO:rural": "90",
|
||||
"RO:trunk": "100",
|
||||
"RO:motorway": "130",
|
||||
"RU:living_street": "20",
|
||||
"RU:urban": "60",
|
||||
"RU:rural": "90",
|
||||
"RU:motorway": "110",
|
||||
"SK:urban": "50",
|
||||
"SK:rural": "90",
|
||||
"SK:trunk": "90",
|
||||
"SK:motorway": "90",
|
||||
"SI:urban": "50",
|
||||
"SI:rural": "90",
|
||||
"SI:trunk": "110",
|
||||
"SI:motorway": "130",
|
||||
"ES:living_street": "20",
|
||||
"ES:urban": "50",
|
||||
"ES:rural": "50",
|
||||
"ES:trunk": "90",
|
||||
"ES:motorway": "120",
|
||||
"SE:urban": "50",
|
||||
"SE:rural": "70",
|
||||
"SE:trunk": "90",
|
||||
"SE:motorway": "110",
|
||||
"GB:nsl_restricted": "30 mph",
|
||||
"GB:nsl_single": "60 mph",
|
||||
"GB:nsl_dual": "70 mph",
|
||||
"GB:motorway": "70 mph",
|
||||
"UA:urban": "50",
|
||||
"UA:rural": "90",
|
||||
"UA:trunk": "110",
|
||||
"UA:motorway": "130",
|
||||
"UZ:living_street": "30",
|
||||
"UZ:urban": "70",
|
||||
"UZ:rural": "100",
|
||||
"UZ:motorway": "110",
|
||||
"ZA:trunk": "120",
|
||||
"ZA:residential": "60",
|
||||
"ZA:rural": "100",
|
||||
"ZA:urban": "60",
|
||||
"ZA:motorway": "120"
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
from enum import Enum
|
||||
import numpy as np
|
||||
|
||||
|
||||
R = 6373000.0 # approximate radius of earth in mts
|
||||
|
||||
|
||||
def vectors(points):
|
||||
"""Provides a array of vectors on cartesian space (x, y).
|
||||
Each vector represents the path from a point in `points` to the next.
|
||||
`points` must by a (N, 2) array of [lat, lon] pairs in radians.
|
||||
"""
|
||||
latA = points[:-1, 0]
|
||||
latB = points[1:, 0]
|
||||
delta = np.diff(points, axis=0)
|
||||
dlon = delta[:, 1]
|
||||
|
||||
x = np.sin(dlon) * np.cos(latB)
|
||||
y = np.cos(latA) * np.sin(latB) - (np.sin(latA) * np.cos(latB) * np.cos(dlon))
|
||||
|
||||
return np.column_stack((x, y))
|
||||
|
||||
|
||||
def ref_vectors(ref, points):
|
||||
"""Provides a array of vectors on cartesian space (x, y).
|
||||
Each vector represents the path from ref to a point in `points`.
|
||||
`points` must by a (N, 2) array of [lat, lon] pairs in radians.
|
||||
"""
|
||||
latA = ref[0]
|
||||
latB = points[:, 0]
|
||||
delta = points - ref
|
||||
dlon = delta[:, 1]
|
||||
|
||||
x = np.sin(dlon) * np.cos(latB)
|
||||
y = np.cos(latA) * np.sin(latB) - (np.sin(latA) * np.cos(latB) * np.cos(dlon))
|
||||
|
||||
return np.column_stack((x, y))
|
||||
|
||||
|
||||
def bearing_to_points(point, points):
|
||||
"""Calculate the bearings (angle from true north clockwise) of the vectors between `point` and each
|
||||
one of the entries in `points`. Both `point` and `points` elements are 2 element arrays containing a latitud,
|
||||
longitude pair in radians.
|
||||
"""
|
||||
delta = points - point
|
||||
x = np.sin(delta[:, 1]) * np.cos(points[:, 0])
|
||||
y = np.cos(point[0]) * np.sin(points[:, 0]) - (np.sin(point[0]) * np.cos(points[:, 0]) * np.cos(delta[:, 1]))
|
||||
return np.arctan2(x, y)
|
||||
|
||||
|
||||
def distance_to_points(point, points):
|
||||
"""Calculate the distance of the vectors between `point` and each one of the entries in `points`.
|
||||
Both `point` and `points` elements are 2 element arrays containing a latitud, longitude pair in radians.
|
||||
"""
|
||||
delta = points - point
|
||||
a = np.sin(delta[:, 0] / 2)**2 + np.cos(point[0]) * np.cos(points[:, 0]) * np.sin(delta[:, 1] / 2)**2
|
||||
c = 2 * np.arctan2(np.sqrt(a), np.sqrt(1 - a))
|
||||
return c * R
|
||||
|
||||
|
||||
class DIRECTION(Enum):
|
||||
NONE = 0
|
||||
AHEAD = 1
|
||||
BEHIND = 2
|
||||
FORWARD = 3
|
||||
BACKWARD = 4
|
||||
@@ -0,0 +1,37 @@
|
||||
import overpy
|
||||
import numpy as np
|
||||
from selfdrive.mapd.lib.geo import R
|
||||
|
||||
|
||||
def create_way(way_id, node_ids, from_way):
|
||||
"""
|
||||
Creates and OSM Way with the given `way_id` and list of `node_ids`, copying attributes and tags from `from_way`
|
||||
"""
|
||||
return overpy.Way(way_id, node_ids=node_ids, attributes={}, result=from_way._result,
|
||||
tags=from_way.tags)
|
||||
|
||||
|
||||
class OSM():
|
||||
def __init__(self):
|
||||
self.api = overpy.Overpass()
|
||||
# self.api = overpy.Overpass(url='http://3.65.170.21/api/interpreter')
|
||||
|
||||
def fetch_road_ways_around_location(self, lat, lon, radius):
|
||||
# Calculate the bounding box coordinates for the bbox containing the circle around location.
|
||||
bbox_angle = np.degrees(radius / R)
|
||||
# fetch all ways and nodes on this ways in bbox
|
||||
bbox_str = f'{str(lat - bbox_angle)},{str(lon - bbox_angle)},{str(lat + bbox_angle)},{str(lon + bbox_angle)}'
|
||||
q = """
|
||||
way(""" + bbox_str + """)
|
||||
[highway]
|
||||
[highway!~"^(footway|path|corridor|bridleway|steps|cycleway|construction|bus_guideway|escape|service|track)$"];
|
||||
(._;>;);
|
||||
out;
|
||||
"""
|
||||
try:
|
||||
ways = self.api.query(q).ways
|
||||
except Exception as e:
|
||||
print(f'Exception while querying OSM:\n{e}')
|
||||
ways = []
|
||||
|
||||
return ways
|
||||
@@ -0,0 +1,266 @@
|
||||
#!/usr/bin/env python3
|
||||
import threading
|
||||
from traceback import print_exception
|
||||
import numpy as np
|
||||
from time import strftime, gmtime
|
||||
import cereal.messaging as messaging
|
||||
from common.realtime import Ratekeeper
|
||||
from selfdrive.mapd.lib.osm import OSM
|
||||
from selfdrive.mapd.lib.geo import distance_to_points
|
||||
from selfdrive.mapd.lib.WayCollection import WayCollection
|
||||
from selfdrive.mapd.config import QUERY_RADIUS, MIN_DISTANCE_FOR_NEW_QUERY, FULL_STOP_MAX_SPEED, LOOK_AHEAD_HORIZON_TIME
|
||||
from system.swaglog import cloudlog
|
||||
|
||||
|
||||
_DEBUG = False
|
||||
_CLOUDLOG_DEBUG = True
|
||||
|
||||
|
||||
def _debug(msg, log_to_cloud=True):
|
||||
if _CLOUDLOG_DEBUG and log_to_cloud:
|
||||
cloudlog.debug(msg)
|
||||
if _DEBUG:
|
||||
print(msg)
|
||||
|
||||
|
||||
def excepthook(args):
|
||||
_debug(f'MapD: Threading exception:\n{args}')
|
||||
print_exception(args.exc_type, args.exc_value, args.exc_traceback)
|
||||
|
||||
|
||||
threading.excepthook = excepthook
|
||||
|
||||
|
||||
class MapD():
|
||||
def __init__(self):
|
||||
self.osm = OSM()
|
||||
self.way_collection = None
|
||||
self.route = None
|
||||
self.last_gps_fix_timestamp = 0
|
||||
self.last_gps = None
|
||||
self.location_deg = None # The current location in degrees.
|
||||
self.location_rad = None # The current location in radians as a Numpy array.
|
||||
self.bearing_rad = None
|
||||
self.location_stdev = None # The current location accuracy in mts. 1 standard devitation.
|
||||
self.gps_speed = 0.
|
||||
self.last_fetch_location = None
|
||||
self.last_route_update_fix_timestamp = 0
|
||||
self.last_publish_fix_timestamp = 0
|
||||
self._op_enabled = False
|
||||
self._disengaging = False
|
||||
self._query_thread = None
|
||||
self._lock = threading.RLock()
|
||||
|
||||
def udpate_state(self, sm):
|
||||
sock = 'controlsState'
|
||||
if not sm.updated[sock] or not sm.valid[sock]:
|
||||
return
|
||||
|
||||
controls_state = sm[sock]
|
||||
self._disengaging = not controls_state.enabled and self._op_enabled
|
||||
self._op_enabled = controls_state.enabled
|
||||
|
||||
def update_gps(self, sm):
|
||||
sock = 'gpsLocationExternal'
|
||||
if not sm.updated[sock] or not sm.valid[sock]:
|
||||
return
|
||||
|
||||
log = sm[sock]
|
||||
self.last_gps = log
|
||||
|
||||
# ignore the message if the fix is invalid
|
||||
if log.flags % 2 == 0:
|
||||
return
|
||||
|
||||
self.last_gps_fix_timestamp = log.unixTimestampMillis # Unix TS. Milliseconds since January 1, 1970.
|
||||
self.location_rad = np.radians(np.array([log.latitude, log.longitude], dtype=float))
|
||||
self.location_deg = (log.latitude, log.longitude)
|
||||
self.bearing_rad = np.radians(log.bearingDeg, dtype=float)
|
||||
self.gps_speed = log.speed
|
||||
self.location_stdev = log.accuracy # log accuracies are presumably 1 standard deviation.
|
||||
|
||||
_debug('Mapd: ********* Got GPS fix'
|
||||
+ f'Pos: {self.location_deg} +/- {self.location_stdev * 2.} mts.\n'
|
||||
+ f'Bearing: {log.bearingDeg} +/- {log.bearingAccuracyDeg * 2.} deg.\n'
|
||||
+ f'timestamp: {strftime("%d-%m-%y %H:%M:%S", gmtime(self.last_gps_fix_timestamp * 1e-3))}'
|
||||
+ '*******', log_to_cloud=False)
|
||||
|
||||
def _query_osm_not_blocking(self):
|
||||
def query(osm, location_deg, location_rad, radius):
|
||||
_debug(f'Mapd: Start query for OSM map data at {location_deg}')
|
||||
lat, lon = location_deg
|
||||
ways = osm.fetch_road_ways_around_location(lat, lon, radius)
|
||||
_debug(f'Mapd: Query to OSM finished with {len(ways)} ways')
|
||||
|
||||
# Only issue an update if we received some ways. Otherwise it is most likely a connectivity issue.
|
||||
# Will retry on next loop.
|
||||
if len(ways) > 0:
|
||||
new_way_collection = WayCollection(ways, location_rad)
|
||||
|
||||
# Use the lock to update the way_collection as it might be being used to update the route.
|
||||
_debug('Mapd: Locking to write results from osm.', log_to_cloud=False)
|
||||
with self._lock:
|
||||
self.way_collection = new_way_collection
|
||||
self.last_fetch_location = location_rad
|
||||
_debug(f'Mapd: Updated map data @ {location_deg} - got {len(ways)} ways')
|
||||
|
||||
_debug('Mapd: Releasing Lock to write results from osm', log_to_cloud=False)
|
||||
|
||||
# Ignore if we have a query thread already running.
|
||||
if self._query_thread is not None and self._query_thread.is_alive():
|
||||
return
|
||||
|
||||
self._query_thread = threading.Thread(target=query, args=(self.osm, self.location_deg, self.location_rad,
|
||||
QUERY_RADIUS))
|
||||
self._query_thread.start()
|
||||
|
||||
def updated_osm_data(self):
|
||||
if self.route is not None:
|
||||
distance_to_end = self.route.distance_to_end
|
||||
if distance_to_end is not None and distance_to_end >= MIN_DISTANCE_FOR_NEW_QUERY:
|
||||
# do not query as long as we have a route with enough distance ahead.
|
||||
return
|
||||
|
||||
if self.location_rad is None:
|
||||
return
|
||||
|
||||
if self.last_fetch_location is not None:
|
||||
distance_since_last = distance_to_points(self.last_fetch_location, np.array([self.location_rad]))[0]
|
||||
if distance_since_last < QUERY_RADIUS - MIN_DISTANCE_FOR_NEW_QUERY:
|
||||
# do not query if are still not close to the border of previous query area
|
||||
return
|
||||
|
||||
self._query_osm_not_blocking()
|
||||
|
||||
def update_route(self):
|
||||
def update_proc():
|
||||
# Ensure we clear the route on op disengage, this way we can correct possible incorrect map data due
|
||||
# to wrongly locating or picking up the wrong route.
|
||||
if self._disengaging:
|
||||
self.route = None
|
||||
_debug('Mapd *****: Clearing Route as system is disengaging. ********')
|
||||
|
||||
if self.way_collection is None or self.location_rad is None or self.bearing_rad is None:
|
||||
_debug('Mapd *****: Can not update route. Missing WayCollection, location or bearing ********')
|
||||
return
|
||||
|
||||
if self.route is not None and self.last_route_update_fix_timestamp == self.last_gps_fix_timestamp:
|
||||
_debug('Mapd *****: Skipping route update. No new fix since last update ********')
|
||||
return
|
||||
|
||||
self.last_route_update_fix_timestamp = self.last_gps_fix_timestamp
|
||||
|
||||
# Create the route if not existent or if it was generated by an older way collection
|
||||
if self.route is None or self.route.way_collection_id != self.way_collection.id:
|
||||
self.route = self.way_collection.get_route(self.location_rad, self.bearing_rad, self.location_stdev)
|
||||
_debug(f'Mapd *****: Route created: \n{self.route}\n********')
|
||||
return
|
||||
|
||||
# Do not attempt to update the route if the car is going close to a full stop, as the bearing can start
|
||||
# jumping and creating unnecessary losing of the route. Since the route update timestamp has been updated
|
||||
# a new liveMapData message will be published with the current values (which is desirable)
|
||||
if self.gps_speed < FULL_STOP_MAX_SPEED:
|
||||
_debug('Mapd *****: Route Not updated as car has Stopped ********')
|
||||
return
|
||||
|
||||
self.route.update(self.location_rad, self.bearing_rad, self.location_stdev)
|
||||
if self.route.located:
|
||||
_debug(f'Mapd *****: Route updated: \n{self.route}\n********')
|
||||
return
|
||||
|
||||
# if an old route did not mange to locate, attempt to regenerate form way collection.
|
||||
self.route = self.way_collection.get_route(self.location_rad, self.bearing_rad, self.location_stdev)
|
||||
_debug(f'Mapd *****: Failed to update location in route. Regenerated with route: \n{self.route}\n********')
|
||||
|
||||
# We use the lock when updating the route, as it reads `way_collection` which can ben updated by
|
||||
# a new query result from the _query_thread.
|
||||
_debug('Mapd: Locking to update route.', log_to_cloud=False)
|
||||
with self._lock:
|
||||
update_proc()
|
||||
|
||||
_debug('Mapd: Releasing Lock to update route', log_to_cloud=False)
|
||||
|
||||
def publish(self, pm, sm):
|
||||
# Ensure we have a route currently located
|
||||
if self.route is None or not self.route.located:
|
||||
_debug('Mapd: Skipping liveMapData message as there is no route or is not located.')
|
||||
return
|
||||
|
||||
# Ensure we have a route update since last publish
|
||||
if self.last_publish_fix_timestamp == self.last_route_update_fix_timestamp:
|
||||
_debug('Mapd: Skipping liveMapData since there is no new gps fix.')
|
||||
return
|
||||
|
||||
self.last_publish_fix_timestamp = self.last_route_update_fix_timestamp
|
||||
|
||||
speed_limit = self.route.current_speed_limit
|
||||
next_speed_limit_section = self.route.next_speed_limit_section
|
||||
turn_speed_limit_section = self.route.current_curvature_speed_limit_section
|
||||
horizon_mts = self.gps_speed * LOOK_AHEAD_HORIZON_TIME
|
||||
next_turn_speed_limit_sections = self.route.next_curvature_speed_limit_sections(horizon_mts)
|
||||
current_road_name = self.route.current_road_name
|
||||
|
||||
map_data_msg = messaging.new_message('liveMapData')
|
||||
map_data_msg.valid = sm.all_alive(service_list=['gpsLocationExternal']) and \
|
||||
sm.all_valid(service_list=['gpsLocationExternal'])
|
||||
|
||||
map_data_msg.liveMapData.lastGpsTimestamp = self.last_gps.unixTimestampMillis
|
||||
map_data_msg.liveMapData.lastGpsLatitude = float(self.last_gps.latitude)
|
||||
map_data_msg.liveMapData.lastGpsLongitude = float(self.last_gps.longitude)
|
||||
map_data_msg.liveMapData.lastGpsSpeed = float(self.last_gps.speed)
|
||||
map_data_msg.liveMapData.lastGpsBearingDeg = float(self.last_gps.bearingDeg)
|
||||
map_data_msg.liveMapData.lastGpsAccuracy = float(self.last_gps.accuracy)
|
||||
map_data_msg.liveMapData.lastGpsBearingAccuracyDeg = float(self.last_gps.bearingAccuracyDeg)
|
||||
|
||||
map_data_msg.liveMapData.speedLimitValid = bool(speed_limit is not None)
|
||||
map_data_msg.liveMapData.speedLimit = float(speed_limit if speed_limit is not None else 0.0)
|
||||
map_data_msg.liveMapData.speedLimitAheadValid = bool(next_speed_limit_section is not None)
|
||||
map_data_msg.liveMapData.speedLimitAhead = float(next_speed_limit_section.value
|
||||
if next_speed_limit_section is not None else 0.0)
|
||||
map_data_msg.liveMapData.speedLimitAheadDistance = float(next_speed_limit_section.start
|
||||
if next_speed_limit_section is not None else 0.0)
|
||||
|
||||
map_data_msg.liveMapData.turnSpeedLimitValid = bool(turn_speed_limit_section is not None)
|
||||
map_data_msg.liveMapData.turnSpeedLimit = float(turn_speed_limit_section.value
|
||||
if turn_speed_limit_section is not None else 0.0)
|
||||
map_data_msg.liveMapData.turnSpeedLimitSign = int(turn_speed_limit_section.curv_sign
|
||||
if turn_speed_limit_section is not None else 0)
|
||||
map_data_msg.liveMapData.turnSpeedLimitEndDistance = float(turn_speed_limit_section.end
|
||||
if turn_speed_limit_section is not None else 0.0)
|
||||
map_data_msg.liveMapData.turnSpeedLimitsAhead = [float(s.value) for s in next_turn_speed_limit_sections]
|
||||
map_data_msg.liveMapData.turnSpeedLimitsAheadDistances = [float(s.start) for s in next_turn_speed_limit_sections]
|
||||
map_data_msg.liveMapData.turnSpeedLimitsAheadSigns = [float(s.curv_sign) for s in next_turn_speed_limit_sections]
|
||||
|
||||
map_data_msg.liveMapData.currentRoadName = str(current_road_name if current_road_name is not None else "")
|
||||
|
||||
pm.send('liveMapData', map_data_msg)
|
||||
_debug(f'Mapd *****: Publish: \n{map_data_msg}\n********', log_to_cloud=False)
|
||||
|
||||
|
||||
# provides live map data information
|
||||
def mapd_thread(sm=None, pm=None):
|
||||
mapd = MapD()
|
||||
rk = Ratekeeper(1., print_delay_threshold=None) # Keeps rate at 1 hz
|
||||
|
||||
# *** setup messaging
|
||||
if sm is None:
|
||||
sm = messaging.SubMaster(['gpsLocationExternal', 'controlsState'])
|
||||
if pm is None:
|
||||
pm = messaging.PubMaster(['liveMapData'])
|
||||
|
||||
while True:
|
||||
sm.update()
|
||||
mapd.udpate_state(sm)
|
||||
mapd.update_gps(sm)
|
||||
mapd.updated_osm_data()
|
||||
mapd.update_route()
|
||||
mapd.publish(pm, sm)
|
||||
rk.keep_time()
|
||||
|
||||
|
||||
def main(sm=None, pm=None):
|
||||
mapd_thread(sm, pm)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,266 @@
|
||||
from selfdrive.mapd.lib.WayCollection import WayCollection
|
||||
from selfdrive.mapd.lib.geo import vectors, R
|
||||
from selfdrive.mapd.lib.NodesData import _MIN_NODE_DISTANCE, _ADDED_NODES_DIST, _SPLINE_EVAL_STEP, \
|
||||
_MIN_SPEED_SECTION_LENGTH, nodes_raw_data_array_for_wr, node_calculations, is_wr_a_valid_divertion_from_node, \
|
||||
spline_curvature_calculations, speed_limits_for_curvatures_data
|
||||
from scipy.interpolate import splev, splprep
|
||||
import numpy as np
|
||||
import overpy
|
||||
|
||||
|
||||
class MockNodesData():
|
||||
def __init__(self, way_coords):
|
||||
self.degrees = np.array(way_coords)
|
||||
self.radians = np.radians(self.degrees)
|
||||
|
||||
# *****************
|
||||
# Expected code implementation nodes_data
|
||||
self.v = vectors(self.radians) * R
|
||||
self.d = np.linalg.norm(self.v, axis=1)
|
||||
self.b = np.arctan2(self.v[:, 0], self.v[:, 1])
|
||||
self.v = np.concatenate(([[0., 0.]], self.v))
|
||||
self.dp = np.concatenate(([0.], self.d))
|
||||
self.dn = np.concatenate((self.d, [0.]))
|
||||
self.dr = np.cumsum(self.dp, axis=0)
|
||||
self.b = np.concatenate((self.b, [self.b[-1]]))
|
||||
|
||||
# Expected code implementation spline_curvature_calculations
|
||||
vect = self.v
|
||||
dist_prev = self.dp
|
||||
too_far_idxs = np.nonzero(self.dp >= _MIN_NODE_DISTANCE)[0]
|
||||
for idx in too_far_idxs[::-1]:
|
||||
dp = dist_prev[idx] # distance of vector that needs to be replaced by higher resolution vectors.
|
||||
n = int(np.ceil(dp / _ADDED_NODES_DIST)) # number of vectors that need to be added.
|
||||
new_v = vect[idx, :] / n # new relative vector to insert.
|
||||
vect = np.delete(vect, idx, axis=0) # remove the relative vector to be replaced by the insertion of new vectors.
|
||||
vect = np.insert(vect, [idx] * n, [new_v] * n, axis=0) # insert n new relative vectors
|
||||
ds = np.cumsum(dist_prev, axis=0)
|
||||
vs = np.cumsum(vect, axis=0)
|
||||
tck, u = splprep([vs[:, 0], vs[:, 1]]) # pylint: disable=W0632
|
||||
n = max(int(ds[-1] / _SPLINE_EVAL_STEP), len(u))
|
||||
unew = np.arange(0, n + 1) / n
|
||||
d1 = splev(unew, tck, der=1)
|
||||
d2 = splev(unew, tck, der=2)
|
||||
num = d1[0] * d2[1] - d1[1] * d2[0]
|
||||
den = (d1[0]**2 + d1[1]**2)**(1.5)
|
||||
self.curv = num / den
|
||||
self.curv_ds = unew * ds[-1]
|
||||
# *****************
|
||||
|
||||
|
||||
class MockCurveSection():
|
||||
def __init__(self, func, di=0., df=1000., step=10.):
|
||||
self.di = di
|
||||
self.df = df
|
||||
self.n = (df - di) // step
|
||||
self.u = np.arange(0, self.n + 1) / self.n
|
||||
self.curv_ds = self.u * (df - di) + di
|
||||
self.curv = func(self.u)
|
||||
self.curv_abs = np.abs(self.curv)
|
||||
self.curv_sec = np.column_stack((self.curv_abs, np.sign(self.curv), self.curv_ds))
|
||||
|
||||
|
||||
class MockOSMQueryResponse():
|
||||
def __init__(self, xml_path, query_center):
|
||||
self.api = overpy.Overpass()
|
||||
self.query_center = np.radians(np.array(query_center))
|
||||
|
||||
with open(xml_path, 'r') as f:
|
||||
overpass_xml = f.read()
|
||||
self.ways = self.api.parse_xml(overpass_xml).ways
|
||||
|
||||
self.wayCollection = WayCollection(self.ways, self.query_center)
|
||||
|
||||
class MockRouteData():
|
||||
def __init__(self, way_ids, way_collection, first_node_id): # way)ids must be in order forming a route.
|
||||
self.wrs = [next(wr for wr in way_collection.way_relations if wr.id == way_id) for way_id in way_ids]
|
||||
self.way_collection = way_collection
|
||||
self.first_node_id = first_node_id
|
||||
|
||||
def reset(self):
|
||||
way_relations = self.wrs
|
||||
wr_index = self.way_collection.wr_index
|
||||
|
||||
# Nodes Data processing expects way relations to be updated with direction before running.
|
||||
for idx, wr in enumerate(way_relations):
|
||||
if idx == 0:
|
||||
wr.update_direction_from_starting_node(self.first_node_id)
|
||||
else:
|
||||
wr.update_direction_from_starting_node(way_relations[idx - 1].last_node.id)
|
||||
|
||||
# ***** Expected calculations
|
||||
self._nodes_data = np.array([])
|
||||
self._divertions = [[]]
|
||||
self._curvature_speed_sections_data = np.array([])
|
||||
way_count = len(way_relations)
|
||||
if way_count == 0:
|
||||
return
|
||||
# We want all the nodes from the last way section
|
||||
nodes_data = nodes_raw_data_array_for_wr(way_relations[-1])
|
||||
# For the ways before the last in the route we want all the nodes but the last, as that one is the first on
|
||||
# the next section. Collect them, append last way node data and concatenate the numpy arrays.
|
||||
if way_count > 1:
|
||||
wrs_data = tuple([nodes_raw_data_array_for_wr(wr, drop_last=True) for wr in way_relations[:-1]])
|
||||
wrs_data += (nodes_data,)
|
||||
nodes_data = np.concatenate(wrs_data)
|
||||
# Get a subarray with lat, lon to compute the remaining node values.
|
||||
lat_lon_array = nodes_data[:, [1, 2]]
|
||||
points = np.radians(lat_lon_array)
|
||||
# Ensure we have more than 3 points, if not calculations are not possible.
|
||||
if len(points) <= 3:
|
||||
return
|
||||
vect, dist_prev, dist_next, dist_route, bearing = node_calculations(points)
|
||||
# append calculations to nodes_data
|
||||
# nodes_data structure: [id, lat, lon, speed_limit, x, y, dist_prev, dist_next, dist_route, bearing]
|
||||
self._nodes_data = np.column_stack((nodes_data, vect, dist_prev, dist_next, dist_route, bearing))
|
||||
# Build route diversion options data from the wr_index.
|
||||
wr_ids = [wr.id for wr in way_relations]
|
||||
self._divertions = [[wr for wr in wr_index.way_relations_with_edge_node_id(node_id)
|
||||
if is_wr_a_valid_divertion_from_node(wr, node_id, wr_ids)]
|
||||
for node_id in nodes_data[:, 0]]
|
||||
# Store calculcations for curvature sections speed limits. We need more than 3 points to be able to process.
|
||||
# _curvature_speed_sections_data structure: [dist_start, dist_stop, speed_limits, curv_sign]
|
||||
if len(vect) > 3:
|
||||
self._curv, self._curv_ds = spline_curvature_calculations(vect, dist_prev)
|
||||
self._curvature_speed_sections_data = speed_limits_for_curvatures_data(self._curv, self._curv_ds)
|
||||
# *****
|
||||
|
||||
|
||||
# Test data in degrees from this road:
|
||||
# https://www.google.de/maps/@52.209263,13.8723137,13z
|
||||
_WAY_NODES_COORDS_01 = [
|
||||
[52.1933703, 13.8723799],
|
||||
[52.1939477, 13.8711273],
|
||||
[52.1942004, 13.8705818],
|
||||
[52.1945408, 13.8698496],
|
||||
[52.1948447, 13.8691873],
|
||||
[52.1950772, 13.8685726],
|
||||
[52.1951168, 13.8684641],
|
||||
[52.1956681, 13.8670323],
|
||||
[52.1958716, 13.8664936],
|
||||
[52.1964366, 13.8649875],
|
||||
[52.1969283, 13.8636040],
|
||||
[52.1970203, 13.8634430],
|
||||
[52.1975486, 13.8626307],
|
||||
[52.1976354, 13.8624971],
|
||||
[52.1977827, 13.8621795],
|
||||
[52.1978564, 13.8619220],
|
||||
[52.1981843, 13.8604497],
|
||||
[52.1982614, 13.8602140],
|
||||
[52.1983351, 13.8600595],
|
||||
[52.1992768, 13.8579824],
|
||||
[52.1995107, 13.8574321],
|
||||
[52.1995948, 13.8572604],
|
||||
[52.1996818, 13.8571155],
|
||||
[52.1998000, 13.8570029],
|
||||
[52.2000659, 13.8568236],
|
||||
[52.2003868, 13.8566005],
|
||||
[52.2007182, 13.8564460],
|
||||
[52.2008760, 13.8564117],
|
||||
[52.2009865, 13.8564117],
|
||||
[52.2011390, 13.8564202],
|
||||
[52.2012267, 13.8564496],
|
||||
[52.2012544, 13.8564577],
|
||||
[52.2013179, 13.8564803],
|
||||
[52.2020491, 13.8571756],
|
||||
[52.2026014, 13.8576991],
|
||||
[52.2027592, 13.8578879],
|
||||
[52.2027960, 13.8579309],
|
||||
[52.2028960, 13.8580939],
|
||||
[52.2030170, 13.8583343],
|
||||
[52.2036587, 13.8597076],
|
||||
[52.2052946, 13.8633039],
|
||||
[52.2064332, 13.8658435],
|
||||
[52.2067856, 13.8666332],
|
||||
[52.2068961, 13.8668477],
|
||||
[52.2070777, 13.8670890],
|
||||
[52.2073723, 13.8674409],
|
||||
[52.2077457, 13.8679387],
|
||||
[52.2083874, 13.8687455],
|
||||
[52.2093341, 13.8699214],
|
||||
[52.2099652, 13.8707540],
|
||||
[52.2102282, 13.8712089],
|
||||
[52.2104228, 13.8715694],
|
||||
[52.2106122, 13.8718955],
|
||||
[52.2107619, 13.8721756],
|
||||
[52.2108695, 13.8723771],
|
||||
[52.2110747, 13.8727610],
|
||||
[52.2111514, 13.8729047],
|
||||
[52.2114010, 13.8733718],
|
||||
[52.2114694, 13.8735006],
|
||||
[52.2115430, 13.8736636],
|
||||
[52.2116086, 13.8737571],
|
||||
[52.2116770, 13.8738172],
|
||||
[52.2117611, 13.8738515],
|
||||
[52.2118664, 13.8738566],
|
||||
[52.2119322, 13.8738439],
|
||||
[52.2121058, 13.8737924],
|
||||
[52.2122583, 13.8737495],
|
||||
[52.2123265, 13.8737260],
|
||||
[52.2124213, 13.8736894],
|
||||
[52.2127466, 13.8734888],
|
||||
[52.2128263, 13.8734491],
|
||||
[52.2131313, 13.8733117],
|
||||
[52.2133943, 13.8731830],
|
||||
[52.2136625, 13.8731057],
|
||||
[52.2139465, 13.8730456],
|
||||
[52.2143619, 13.8730113],
|
||||
[52.2148773, 13.8729942],
|
||||
[52.2152275, 13.8730325],
|
||||
[52.2153110, 13.8730398],
|
||||
[52.2157442, 13.8730848],
|
||||
[52.2158833, 13.8731036]]
|
||||
|
||||
|
||||
mockNodesData01 = MockNodesData(_WAY_NODES_COORDS_01)
|
||||
|
||||
# OSM Query around B96 south of Berlin
|
||||
mockOSMResponse01 = MockOSMQueryResponse('selfdrive/mapd/test/mock_osm_response_01.xml',
|
||||
[52.31400353586984, 13.447158941786366])
|
||||
|
||||
# OSM Query on curvy town area south of Germany.
|
||||
mockOSMResponse02 = MockOSMQueryResponse('selfdrive/mapd/test/mock_osm_response_02.xml',
|
||||
[48.16573269276522, 9.81418473659117])
|
||||
|
||||
mockWayCollection01 = WayCollection(mockOSMResponse01.ways, mockOSMResponse01.query_center)
|
||||
mockWayCollection02 = WayCollection(mockOSMResponse02.ways, mockOSMResponse02.query_center)
|
||||
|
||||
# Normal curvy Way. way id: 179532213 with 35 Nodes.
|
||||
mockOSMWay_01_01_LongCurvy = next(way for way in mockOSMResponse01.ways if way.id == 179532213)
|
||||
|
||||
# Looped way. way id: 29233907
|
||||
mockOSMWay_01_02_Loop = next(way for way in mockOSMResponse01.ways if way.id == 29233907)
|
||||
|
||||
# Complex curvy road through town with intersections. way id:178450395
|
||||
mockOSMWay_02_01_CurvyTownWithIntersections = next(way for way in mockOSMResponse02.ways if way.id == 178450395)
|
||||
|
||||
# Valid diversion for way 02_01 at node: 34785115. way id: 27955186
|
||||
mockOSMWay_02_02_Divertion_34785115 = next(way for way in mockOSMResponse02.ways if way.id == 27955186)
|
||||
|
||||
# 3 node way. way id: 807781992
|
||||
mockOSMWay_02_03_Short_3_node_way = next(way for way in mockOSMResponse02.ways if way.id == 807781992)
|
||||
|
||||
# data composing route 01 in way collection 02
|
||||
mockRouteData_02_01 = MockRouteData([60890967, 737120246, 601406617, 60890971, 178450395], mockWayCollection02,
|
||||
first_node_id=201962346)
|
||||
|
||||
# data composing route 02 in way collection 02. Single WR
|
||||
mockRouteData_02_02_single_wr = MockRouteData([178450395], mockWayCollection02, first_node_id=762086638)
|
||||
|
||||
# data composing route 03 in way collection 02. Multiple speed limits
|
||||
mockRouteData_02_03 = MockRouteData([158799549, 798805532, 28707704, 158797898, 602249535, 602249536, 825823509,
|
||||
178449088, 916462523, 158796386], mockWayCollection02,
|
||||
first_node_id=252601829)
|
||||
|
||||
# 1000mt section with one full sin cycle as curv values.
|
||||
mockCurveSectionSin = MockCurveSection(lambda x: np.sin(x * 2 * np.pi))
|
||||
|
||||
# 200mt section with changing curvature rate.
|
||||
mockCurveSteepCurvChange = MockCurveSection(lambda x: 0.05 * x**3 - 0.007 * x**2 + 0.001 * x, df=200)
|
||||
|
||||
# _MIN_SPEED_SECTION_LENGTH section with changing curvature rate.
|
||||
mockCurveSteepCurvChangeShort = MockCurveSection(
|
||||
lambda x: 0.05 * x**3 - 0.007 * x**2 + 0.001 * x, df=_MIN_SPEED_SECTION_LENGTH)
|
||||
|
||||
# 200mt section with smooth changing curvature rate. no deviation over 2.
|
||||
mockCurveSmoothCurveChange = MockCurveSection(lambda x: 0.0002 * x**3 - 0.001 * x**2 + 0.6 * x, df=200)
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,354 @@
|
||||
import unittest
|
||||
import numpy as np
|
||||
from selfdrive.mapd.lib.geo import DIRECTION
|
||||
from common.conversions import Conversions as CV
|
||||
from selfdrive.mapd.lib.WayRelation import WayRelation
|
||||
from selfdrive.mapd.lib.NodesData import nodes_raw_data_array_for_wr, node_calculations, \
|
||||
spline_curvature_calculations, split_speed_section_by_sign, split_speed_section_by_curv_degree, speed_section, \
|
||||
speed_limits_for_curvatures_data, is_wr_a_valid_divertion_from_node, SpeedLimitSection, TurnSpeedLimitSection, \
|
||||
NodesData, NodeDataIdx
|
||||
from selfdrive.mapd.test.mock_data import mockOSMWay_01_01_LongCurvy, mockNodesData01, mockCurveSectionSin, \
|
||||
mockCurveSteepCurvChange, mockCurveSteepCurvChangeShort, mockCurveSmoothCurveChange, \
|
||||
mockOSMWay_02_01_CurvyTownWithIntersections, mockOSMWay_02_02_Divertion_34785115, mockOSMWay_02_03_Short_3_node_way, \
|
||||
mockRouteData_02_01, mockRouteData_02_02_single_wr, mockRouteData_02_03
|
||||
from numpy.testing import assert_array_almost_equal
|
||||
|
||||
|
||||
class TestNodesDataFileFunctions(unittest.TestCase):
|
||||
def test_nodes_raw_data_array_for_wr(self):
|
||||
wr = WayRelation(mockOSMWay_01_01_LongCurvy)
|
||||
data_e = np.array([(n.id, n.lat, n.lon, wr.speed_limit) for n in wr.way.nodes], dtype=float)
|
||||
data = nodes_raw_data_array_for_wr(wr)
|
||||
|
||||
assert_array_almost_equal(data, data_e)
|
||||
|
||||
def test_nodes_raw_data_array_for_wr_flips_when_backwards(self):
|
||||
wr = WayRelation(mockOSMWay_01_01_LongCurvy)
|
||||
wr.direction = DIRECTION.BACKWARD
|
||||
|
||||
data_e = np.array([(n.id, n.lat, n.lon, wr.speed_limit) for n in wr.way.nodes], dtype=float)
|
||||
data_e = np.flip(data_e, axis=0)
|
||||
|
||||
data = nodes_raw_data_array_for_wr(wr)
|
||||
|
||||
assert_array_almost_equal(data, data_e)
|
||||
|
||||
def test_nodes_raw_data_array_for_wr_drops_last(self):
|
||||
wr = WayRelation(mockOSMWay_01_01_LongCurvy)
|
||||
data_e = np.array([(n.id, n.lat, n.lon, wr.speed_limit) for n in wr.way.nodes], dtype=float)[:-1]
|
||||
data = nodes_raw_data_array_for_wr(wr, drop_last=True)
|
||||
|
||||
assert_array_almost_equal(data, data_e)
|
||||
|
||||
def test_node_calculations(self):
|
||||
points = mockNodesData01.radians
|
||||
|
||||
v, dp, dn, dr, b = node_calculations(points)
|
||||
|
||||
assert_array_almost_equal(v, mockNodesData01.v)
|
||||
assert_array_almost_equal(dp, mockNodesData01.dp)
|
||||
assert_array_almost_equal(dn, mockNodesData01.dn)
|
||||
assert_array_almost_equal(dr, mockNodesData01.dr)
|
||||
assert_array_almost_equal(b, mockNodesData01.b)
|
||||
|
||||
def test_node_calculations_index_error(self):
|
||||
points = mockNodesData01.radians[:2]
|
||||
|
||||
with self.assertRaises(IndexError):
|
||||
node_calculations(points)
|
||||
|
||||
def test_spline_curvature_calculations(self):
|
||||
vect = mockNodesData01.v
|
||||
dist_prev = mockNodesData01.dp
|
||||
|
||||
curv, curv_ds = spline_curvature_calculations(vect, dist_prev)
|
||||
|
||||
assert_array_almost_equal(curv, mockNodesData01.curv)
|
||||
assert_array_almost_equal(curv_ds, mockNodesData01.curv_ds)
|
||||
|
||||
def test_spline_curvature_calculations_with_route_data(self):
|
||||
mockRouteData_02_01.reset()
|
||||
nodes_data = mockRouteData_02_01._nodes_data
|
||||
vect = np.column_stack((nodes_data[:, 4], nodes_data[:, 5]))
|
||||
dist_prev = nodes_data[:, 6]
|
||||
|
||||
curv, curv_ds = spline_curvature_calculations(vect, dist_prev)
|
||||
|
||||
assert_array_almost_equal(curv, mockRouteData_02_01._curv)
|
||||
assert_array_almost_equal(curv_ds, mockRouteData_02_01._curv_ds)
|
||||
|
||||
def test_split_speed_section_by_sign(self):
|
||||
curv_sec = mockCurveSectionSin.curv_sec
|
||||
new_secs = split_speed_section_by_sign(curv_sec)
|
||||
|
||||
# 3 sections with matching initial and final distance
|
||||
self.assertEqual(len(new_secs), 3)
|
||||
self.assertEqual(new_secs[0][0][2], mockCurveSectionSin.di)
|
||||
self.assertEqual(new_secs[2][-1][2], mockCurveSectionSin.df)
|
||||
|
||||
# All new sections has same sign internally
|
||||
for sec in new_secs:
|
||||
self.assertEqual(np.average(sec, axis=0)[1], sec[0][1])
|
||||
|
||||
# Sections change sign
|
||||
for idx in range(2):
|
||||
self.assertNotEqual(new_secs[idx][0][1], new_secs[idx + 1][0][1])
|
||||
|
||||
# total items consistency
|
||||
lengths = [len(sec) for sec in new_secs]
|
||||
self.assertEqual(len(curv_sec), sum(lengths))
|
||||
|
||||
def test_split_speed_section_by_curv_degree(self):
|
||||
curv_sec = mockCurveSteepCurvChange.curv_sec
|
||||
new_secs = split_speed_section_by_curv_degree(curv_sec)
|
||||
|
||||
# 3 sections with matching initial and final distance
|
||||
self.assertEqual(len(new_secs), 3)
|
||||
self.assertEqual(new_secs[0][0][2], mockCurveSteepCurvChange.di)
|
||||
self.assertEqual(new_secs[2][-1][2], mockCurveSteepCurvChange.df)
|
||||
|
||||
# Sections split at the right points
|
||||
split_dist = [sec[-1][2] for sec in new_secs]
|
||||
self.assertListEqual(split_dist, [50., 150., 200.])
|
||||
|
||||
def test_split_speed_section_by_curv_degree_does_nothing_if_short(self):
|
||||
curv_sec = mockCurveSteepCurvChangeShort.curv_sec
|
||||
new_secs = split_speed_section_by_curv_degree(curv_sec)
|
||||
|
||||
self.assertEqual(len(new_secs), 1)
|
||||
assert_array_almost_equal(curv_sec, new_secs[0])
|
||||
|
||||
def test_split_speed_section_by_curv_degree_does_nothing_if_no_substantial_change(self):
|
||||
curv_sec = mockCurveSmoothCurveChange.curv_sec
|
||||
new_secs = split_speed_section_by_curv_degree(curv_sec)
|
||||
|
||||
self.assertEqual(len(new_secs), 1)
|
||||
assert_array_almost_equal(curv_sec, new_secs[0])
|
||||
|
||||
def test_speed_section(self):
|
||||
curv_sec = mockCurveSectionSin.curv_sec
|
||||
|
||||
speed_secs = speed_section(curv_sec)
|
||||
expected = np.array([0., 1000., 1.51657509, 1.])
|
||||
|
||||
assert_array_almost_equal(speed_secs, expected)
|
||||
|
||||
def test_speed_limits_for_curvatures_data(self):
|
||||
curv = mockCurveSectionSin.curv
|
||||
curv_ds = mockCurveSectionSin.curv_ds
|
||||
|
||||
expected = np.array([
|
||||
[10., 490., 1.51657509, 1.],
|
||||
[510., 990., 1.51657509, -1.]])
|
||||
limits = speed_limits_for_curvatures_data(curv, curv_ds)
|
||||
|
||||
assert_array_almost_equal(limits, expected)
|
||||
|
||||
def test_is_wr_a_valid_divertion_from_node(self):
|
||||
wr = WayRelation(mockOSMWay_02_01_CurvyTownWithIntersections)
|
||||
mockOSMWay_02_02_Divertion_34785115.tags['oneway'] = 'yes'
|
||||
wr_div = WayRelation(mockOSMWay_02_02_Divertion_34785115)
|
||||
|
||||
# False if id already in route
|
||||
wr_ids = [wr.id, wr_div.id]
|
||||
self.assertFalse(is_wr_a_valid_divertion_from_node(wr_div, 34785115, wr_ids))
|
||||
|
||||
# True if id not in route, node_id is edge and not prohibited
|
||||
wr_ids = [wr.id, 11111, 22222]
|
||||
self.assertTrue(is_wr_a_valid_divertion_from_node(wr_div, 34785115, wr_ids))
|
||||
|
||||
# False if id not in route, node_id is edge but prohibited (wrong direction from node 319503453)
|
||||
self.assertFalse(is_wr_a_valid_divertion_from_node(wr_div, 319503453, wr_ids))
|
||||
|
||||
# False if id not in route, node_id is not edge
|
||||
self.assertFalse(is_wr_a_valid_divertion_from_node(wr_div, 44444, wr_ids))
|
||||
|
||||
|
||||
class TestSpeedLimitSection(unittest.TestCase):
|
||||
def test_speed_limit_section_init(self):
|
||||
section = SpeedLimitSection(10., 20., 50.)
|
||||
|
||||
self.assertEqual(section.start, 10.)
|
||||
self.assertEqual(section.end, 20.)
|
||||
self.assertEqual(section.value, 50.)
|
||||
|
||||
|
||||
class TestTurnSpeedLimitSection(unittest.TestCase):
|
||||
def test_turn_speed_limit_section_init(self):
|
||||
section = TurnSpeedLimitSection(10., 20., 50., -1.)
|
||||
|
||||
self.assertEqual(section.start, 10.)
|
||||
self.assertEqual(section.end, 20.)
|
||||
self.assertEqual(section.value, 50.)
|
||||
self.assertEqual(section.curv_sign, -1.)
|
||||
|
||||
|
||||
class TestNodesData(unittest.TestCase):
|
||||
def test_init_with_empty_list(self):
|
||||
nodesData = NodesData([], {})
|
||||
|
||||
self.assertEqual(len(nodesData._nodes_data), 0)
|
||||
num_diverstions = sum([len(d) for d in nodesData._divertions])
|
||||
self.assertEqual(num_diverstions, 0)
|
||||
self.assertEqual(len(nodesData._curvature_speed_sections_data), 0)
|
||||
|
||||
def test_init_with_single_wr_includes_all_wr_nodes(self):
|
||||
mockRouteData_02_02_single_wr.reset()
|
||||
way_relations = mockRouteData_02_02_single_wr.wrs
|
||||
wr_index = mockRouteData_02_02_single_wr.way_collection.wr_index
|
||||
|
||||
nodesData = NodesData(way_relations, wr_index)
|
||||
|
||||
assert_array_almost_equal(nodesData._nodes_data, mockRouteData_02_02_single_wr._nodes_data)
|
||||
assert_array_almost_equal(nodesData._curvature_speed_sections_data,
|
||||
mockRouteData_02_02_single_wr._curvature_speed_sections_data)
|
||||
self.assertListEqual(nodesData._divertions, mockRouteData_02_02_single_wr._divertions)
|
||||
self.assertEqual(len(nodesData._nodes_data), len(way_relations[0].way.nodes))
|
||||
self.assertEqual(len(nodesData._curvature_speed_sections_data), 6)
|
||||
num_diverstions = sum([len(d) for d in nodesData._divertions])
|
||||
self.assertEqual(num_diverstions, 6)
|
||||
|
||||
def test_init_with_less_than_4_nodes(self):
|
||||
wr_t = WayRelation(mockOSMWay_02_03_Short_3_node_way)
|
||||
|
||||
nodesData = NodesData([wr_t], {})
|
||||
|
||||
self.assertEqual(len(nodesData._nodes_data), 0)
|
||||
num_diverstions = sum([len(d) for d in nodesData._divertions])
|
||||
self.assertEqual(num_diverstions, 0)
|
||||
self.assertEqual(len(nodesData._curvature_speed_sections_data), 0)
|
||||
|
||||
def test_init_with_multiple_wr(self):
|
||||
mockRouteData_02_01.reset()
|
||||
way_relations = mockRouteData_02_01.wrs
|
||||
wr_index = mockRouteData_02_01.way_collection.wr_index
|
||||
|
||||
nodesData = NodesData(way_relations, wr_index)
|
||||
|
||||
assert_array_almost_equal(nodesData._nodes_data, mockRouteData_02_01._nodes_data)
|
||||
assert_array_almost_equal(nodesData._curvature_speed_sections_data, mockRouteData_02_01._curvature_speed_sections_data)
|
||||
self.assertListEqual(nodesData._divertions, mockRouteData_02_01._divertions)
|
||||
self.assertEqual(len(nodesData._curvature_speed_sections_data), 9)
|
||||
num_diverstions = sum([len(d) for d in nodesData._divertions])
|
||||
self.assertEqual(num_diverstions, 14)
|
||||
|
||||
def test_count(self):
|
||||
mockRouteData_02_01.reset()
|
||||
way_relations = mockRouteData_02_01.wrs
|
||||
wr_index = mockRouteData_02_01.way_collection.wr_index
|
||||
num_n = sum([len(wr.way.nodes) for wr in way_relations]) - len(way_relations) + 1
|
||||
|
||||
nodesData = NodesData(way_relations, wr_index)
|
||||
|
||||
self.assertEqual(nodesData.count, num_n)
|
||||
|
||||
def test_get_on_empty(self):
|
||||
wr_t = WayRelation(mockOSMWay_02_03_Short_3_node_way)
|
||||
|
||||
nodesData = NodesData([wr_t], {})
|
||||
assert_array_almost_equal(nodesData.get(NodeDataIdx.node_id), np.array([]))
|
||||
|
||||
def test_get_values(self):
|
||||
mockRouteData_02_01.reset()
|
||||
way_relations = mockRouteData_02_01.wrs
|
||||
wr_index = mockRouteData_02_01.way_collection.wr_index
|
||||
|
||||
nodesData = NodesData(way_relations, wr_index)
|
||||
|
||||
assert_array_almost_equal(nodesData.get(NodeDataIdx.node_id), mockRouteData_02_01._nodes_data[:, 0])
|
||||
assert_array_almost_equal(nodesData.get(NodeDataIdx.lat), mockRouteData_02_01._nodes_data[:, 1])
|
||||
assert_array_almost_equal(nodesData.get(NodeDataIdx.lon), mockRouteData_02_01._nodes_data[:, 2])
|
||||
assert_array_almost_equal(nodesData.get(NodeDataIdx.speed_limit), mockRouteData_02_01._nodes_data[:, 3])
|
||||
assert_array_almost_equal(nodesData.get(NodeDataIdx.x), mockRouteData_02_01._nodes_data[:, 4])
|
||||
assert_array_almost_equal(nodesData.get(NodeDataIdx.y), mockRouteData_02_01._nodes_data[:, 5])
|
||||
assert_array_almost_equal(nodesData.get(NodeDataIdx.dist_prev), mockRouteData_02_01._nodes_data[:, 6])
|
||||
assert_array_almost_equal(nodesData.get(NodeDataIdx.dist_next), mockRouteData_02_01._nodes_data[:, 7])
|
||||
assert_array_almost_equal(nodesData.get(NodeDataIdx.dist_route), mockRouteData_02_01._nodes_data[:, 8])
|
||||
assert_array_almost_equal(nodesData.get(NodeDataIdx.bearing), mockRouteData_02_01._nodes_data[:, 9])
|
||||
|
||||
def test_speed_limits_ahead_from_empty(self):
|
||||
wr_t = WayRelation(mockOSMWay_02_03_Short_3_node_way)
|
||||
|
||||
nodesData = NodesData([wr_t], {})
|
||||
self.assertEqual(len(nodesData.speed_limits_ahead(1, 10.)), 0)
|
||||
|
||||
def test_speed_limits_ahead(self):
|
||||
mockRouteData_02_03.reset()
|
||||
way_relations = mockRouteData_02_03.wrs
|
||||
wr_index = mockRouteData_02_03.way_collection.wr_index
|
||||
|
||||
nodesData = NodesData(way_relations, wr_index)
|
||||
|
||||
# empty when ahead_idx is none.
|
||||
self.assertEqual(len(nodesData.speed_limits_ahead(None, 10.)), 0)
|
||||
|
||||
# All limist from 0
|
||||
all_limits = nodesData.speed_limits_ahead(1, nodesData.get(NodeDataIdx.dist_next)[0])
|
||||
self.assertEqual(len(all_limits), 4) # 4 limits on this mock road.
|
||||
self.assertListEqual([sl.value for sl in all_limits], [v * CV.KPH_TO_MS for v in [50, 100, 50, 100]])
|
||||
for idx, sl in enumerate(all_limits):
|
||||
self.assertTrue(sl.end > sl.start)
|
||||
self.assertTrue(sl.value > 0.)
|
||||
if idx == 0:
|
||||
self.assertEqual(sl.start, 0.)
|
||||
else:
|
||||
self.assertEqual(sl.start, all_limits[idx - 1].end)
|
||||
self.assertNotEqual(sl.value, all_limits[idx - 1].value)
|
||||
|
||||
def test_distance_to_end_from_empty(self):
|
||||
wr_t = WayRelation(mockOSMWay_02_03_Short_3_node_way)
|
||||
|
||||
nodesData = NodesData([wr_t], {})
|
||||
self.assertIsNone(nodesData.distance_to_end(1, 10.))
|
||||
|
||||
def test_distance_to_end(self):
|
||||
mockRouteData_02_03.reset()
|
||||
way_relations = mockRouteData_02_03.wrs
|
||||
wr_index = mockRouteData_02_03.way_collection.wr_index
|
||||
|
||||
nodesData = NodesData(way_relations, wr_index)
|
||||
|
||||
# none when ahead_idx is none.
|
||||
self.assertIsNone(nodesData.distance_to_end(None, 10.))
|
||||
|
||||
# From the beginning
|
||||
expected = np.sum(nodesData.get(NodeDataIdx.dist_next))
|
||||
self.assertAlmostEqual(nodesData.distance_to_end(1, nodesData.get(NodeDataIdx.dist_next)[0]), expected)
|
||||
self.assertAlmostEqual(nodesData.get(NodeDataIdx.dist_route)[-1], expected)
|
||||
|
||||
# From the node next to last
|
||||
expected = nodesData.get(NodeDataIdx.dist_next)[-2]
|
||||
self.assertAlmostEqual(nodesData.distance_to_end(nodesData.count - 2, 0.), expected)
|
||||
|
||||
def test_distance_to_node(self):
|
||||
mockRouteData_02_03.reset()
|
||||
way_relations = mockRouteData_02_03.wrs
|
||||
wr_index = mockRouteData_02_03.way_collection.wr_index
|
||||
|
||||
nodesData = NodesData(way_relations, wr_index)
|
||||
dist_to_node_ahead = 10.
|
||||
node_id = 1887995486 # Some node id in the middle of the way. idx 50
|
||||
node_idx = np.nonzero(nodesData.get(NodeDataIdx.node_id) == node_id)[0][0]
|
||||
|
||||
# none when ahead_idx is none.
|
||||
self.assertIsNone(nodesData.distance_to_node(node_id, None, dist_to_node_ahead))
|
||||
|
||||
# From the beginning
|
||||
expected = nodesData.get(NodeDataIdx.dist_route)[node_idx]
|
||||
self.assertAlmostEqual(nodesData.distance_to_node(node_id, 1, nodesData.get(NodeDataIdx.dist_next)[0]), expected)
|
||||
|
||||
# From the end
|
||||
expected = -np.sum(nodesData.get(NodeDataIdx.dist_next)[node_idx:])
|
||||
self.assertAlmostEqual(nodesData.distance_to_node(node_id, len(nodesData.get(NodeDataIdx.node_id)) - 1, 0.), expected)
|
||||
|
||||
# From some node behind including dist to node ahead
|
||||
ahead_idx = node_idx - 10
|
||||
expected = np.sum(nodesData.get(NodeDataIdx.dist_next)[ahead_idx:node_idx]) + dist_to_node_ahead
|
||||
self.assertAlmostEqual(nodesData.distance_to_node(node_id, ahead_idx, dist_to_node_ahead), expected)
|
||||
|
||||
# From some node ahead including dist to node ahead
|
||||
ahead_idx = node_idx + 10
|
||||
expected = -np.sum(nodesData.get(NodeDataIdx.dist_next)[node_idx:ahead_idx]) + dist_to_node_ahead
|
||||
self.assertAlmostEqual(nodesData.distance_to_node(node_id, ahead_idx, dist_to_node_ahead), expected)
|
||||
|
||||
# TODO: Missing tests for curvatures_speed_limit_sections_ahead and possible_divertions
|
||||
@@ -0,0 +1,651 @@
|
||||
import copy
|
||||
import unittest
|
||||
import numpy as np
|
||||
from unittest import mock
|
||||
from numpy.testing import assert_array_almost_equal
|
||||
from datetime import datetime as dt, timezone, timedelta
|
||||
from common.conversions import Conversions as CV
|
||||
from selfdrive.mapd.lib.WayRelation import WayRelation, is_osm_time_condition_active, \
|
||||
conditional_speed_limit_for_osm_tag_limit_string, speed_limit_for_osm_tag_limit_string
|
||||
from selfdrive.mapd.config import LANE_WIDTH
|
||||
from selfdrive.mapd.lib.geo import DIRECTION, R, vectors
|
||||
from selfdrive.mapd.test.mock_data import mockOSMWay_01_01_LongCurvy, mockOSMWay_01_02_Loop, \
|
||||
mockOSMWay_02_01_CurvyTownWithIntersections
|
||||
|
||||
|
||||
class TestWayRelationFileFunctions(unittest.TestCase):
|
||||
def test_speed_limit_for_osm_tag_limit_string(self):
|
||||
values = [
|
||||
None, # Invalid
|
||||
"1000", # Invalid
|
||||
"60 kph", # Invalid
|
||||
"100",
|
||||
"30 mph",
|
||||
"DE:zone:40",
|
||||
"DE:zone:50 mph",
|
||||
"AR:urban",
|
||||
"CZ:pedestrian_zone",
|
||||
"DK:urban",
|
||||
"DK:rural",
|
||||
"DK:motorway",
|
||||
"DE:living_street",
|
||||
"DE:residential",
|
||||
"DE:urban",
|
||||
"DE:rural",
|
||||
"DE:trunk", # No limit
|
||||
"DE:motorway", # No limit
|
||||
"GB:nsl_restricted",
|
||||
"GB:nsl_single",
|
||||
"GB:nsl_dual",
|
||||
"GB:motorway",
|
||||
"GB:invalid", # Invalid
|
||||
]
|
||||
|
||||
expected = [
|
||||
0.,
|
||||
0.,
|
||||
0.,
|
||||
100. * CV.KPH_TO_MS,
|
||||
30. * CV.MPH_TO_MS,
|
||||
40. * CV.KPH_TO_MS,
|
||||
50. * CV.MPH_TO_MS,
|
||||
40. * CV.KPH_TO_MS,
|
||||
20. * CV.KPH_TO_MS,
|
||||
50. * CV.KPH_TO_MS,
|
||||
80. * CV.KPH_TO_MS,
|
||||
130. * CV.KPH_TO_MS,
|
||||
7. * CV.KPH_TO_MS,
|
||||
30. * CV.KPH_TO_MS,
|
||||
50. * CV.KPH_TO_MS,
|
||||
100. * CV.KPH_TO_MS,
|
||||
0.,
|
||||
0.,
|
||||
30. * CV.MPH_TO_MS,
|
||||
60. * CV.MPH_TO_MS,
|
||||
70. * CV.MPH_TO_MS,
|
||||
70. * CV.MPH_TO_MS,
|
||||
0.,
|
||||
]
|
||||
|
||||
result = [speed_limit_for_osm_tag_limit_string(sls) for sls in values]
|
||||
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
@mock.patch('selfdrive.mapd.lib.WayRelation.dt')
|
||||
def test_is_osm_time_condition_active(self, mock_dt):
|
||||
tz = timezone(timedelta(hours=1), 'berlin')
|
||||
wed_10_10_am = dt(2021, 9, 1, 10, 10, 0)
|
||||
mock_dt.now.return_value = wed_10_10_am
|
||||
mock_dt.tzinfo = tz
|
||||
mock_dt.combine = dt.combine
|
||||
mock_dt.strptime = dt.strptime
|
||||
|
||||
values = [
|
||||
"WE", # Invalid
|
||||
"We",
|
||||
"Mo",
|
||||
"Fr",
|
||||
"Tu-Th",
|
||||
"10:00", # Invalid
|
||||
"10:00-10:30",
|
||||
"We 10:00-10:30",
|
||||
"SU 10:00-10:30", # Valid, SU string not considered a day string.
|
||||
"Sa 10:00-10:30",
|
||||
"Tu-Th 10:00-10:30",
|
||||
]
|
||||
|
||||
expected = [
|
||||
False, # Invalid
|
||||
True,
|
||||
False,
|
||||
False,
|
||||
True,
|
||||
False, # Invalid
|
||||
True,
|
||||
True,
|
||||
True,
|
||||
False,
|
||||
True,
|
||||
]
|
||||
|
||||
result = [is_osm_time_condition_active(cs) for cs in values]
|
||||
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
@mock.patch('selfdrive.mapd.lib.WayRelation.dt')
|
||||
def test_conditional_speed_limit_for_osm_tag_limit_string(self, mock_dt):
|
||||
tz = timezone(timedelta(hours=1), 'berlin')
|
||||
wed_10_10_am = dt(2021, 9, 1, 10, 10, 0)
|
||||
mock_dt.now.return_value = wed_10_10_am
|
||||
mock_dt.tzinfo = tz
|
||||
mock_dt.combine = dt.combine
|
||||
mock_dt.strptime = dt.strptime
|
||||
|
||||
values = [
|
||||
None, # Invalid
|
||||
"Hola", # Invalid
|
||||
"100 @ (WE)", # Invalid
|
||||
"x @ (We)", # Invalid
|
||||
"100 @ (We)",
|
||||
"100 @ (Mo)",
|
||||
"100 @ (Fr)",
|
||||
"100 @ (Tu-Th)",
|
||||
"100 @ (10:00)", # Invalid
|
||||
"100 @ (10:00-10:30)",
|
||||
"100 @ (We 10:00-10:30)",
|
||||
"100 @ (SU 10:00-10:30)", # Valid, SU string not considered a day string.
|
||||
"100 @ (Sa 10:00-10:30)",
|
||||
"100 @ (Tu-Th 10:00-10:30)",
|
||||
"100 @ (Mo-Th;Su)",
|
||||
"100 @ (Mo Th;Fr-Sa)",
|
||||
"100 @ (Fr-Su;Mo-Tu)",
|
||||
"100 @ (10:00-10:30;15:00-16:00)",
|
||||
"100 @ (We;Mo-Tu)",
|
||||
"100 @ (We 10:00-10:30;Th 15:00-16:00)",
|
||||
"100 @ (Tu 10:00-10:30;Th 15:00-16:00)",
|
||||
]
|
||||
|
||||
_100 = 100. * CV.KPH_TO_MS
|
||||
|
||||
expected = [
|
||||
0., # Invalid
|
||||
0., # Invalid
|
||||
0., # Invalid
|
||||
0., # Invalid
|
||||
_100,
|
||||
0.,
|
||||
0.,
|
||||
_100,
|
||||
0., # Invalid
|
||||
_100,
|
||||
_100,
|
||||
_100,
|
||||
0.,
|
||||
_100,
|
||||
_100,
|
||||
_100,
|
||||
0.,
|
||||
_100,
|
||||
_100,
|
||||
_100,
|
||||
0.
|
||||
]
|
||||
|
||||
result = [conditional_speed_limit_for_osm_tag_limit_string(ls) for ls in values]
|
||||
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
|
||||
class TestWayRelation(unittest.TestCase):
|
||||
def test_way_relation_init(self):
|
||||
wayRelation = WayRelation(mockOSMWay_01_01_LongCurvy)
|
||||
|
||||
nodes_np_expected = np.radians(np.array([[node.lat, node.lon] for node in wayRelation.way.nodes], dtype=float))
|
||||
v = vectors(wayRelation._nodes_np)
|
||||
way_distances_expected = np.linalg.norm(v * R, axis=1)
|
||||
way_bearings_expected = np.arctan2(v[:, 0], v[:, 1])
|
||||
bbox_expected = np.array([
|
||||
[0.91321784, 0.2346417],
|
||||
[0.91344672, 0.23475751]])
|
||||
|
||||
self.assertEqual(wayRelation.way.id, 179532213)
|
||||
self.assertIsNone(wayRelation.parent_wr_id)
|
||||
self.assertEqual(wayRelation.direction, DIRECTION.NONE)
|
||||
self.assertEqual(wayRelation._speed_limit, None)
|
||||
self.assertEqual(wayRelation._one_way, 'yes')
|
||||
self.assertEqual(wayRelation.name, None)
|
||||
self.assertEqual(wayRelation.ref, 'B 96')
|
||||
self.assertEqual(wayRelation.highway_type, 'trunk')
|
||||
self.assertEqual(wayRelation.highway_rank, 10)
|
||||
self.assertEqual(wayRelation.lanes, 2)
|
||||
assert_array_almost_equal(wayRelation._nodes_np, nodes_np_expected)
|
||||
assert_array_almost_equal(wayRelation._way_distances, way_distances_expected)
|
||||
assert_array_almost_equal(wayRelation._way_bearings, way_bearings_expected)
|
||||
assert_array_almost_equal(wayRelation.bbox, bbox_expected)
|
||||
self.assertEqual(wayRelation.edge_nodes_ids, [wayRelation.way.nodes[0].id, wayRelation.way.nodes[-1].id])
|
||||
|
||||
def test_way_relation_init_with_parent(self):
|
||||
wayRelation = WayRelation(mockOSMWay_01_01_LongCurvy, parent=WayRelation(mockOSMWay_01_02_Loop))
|
||||
|
||||
self.assertEqual(wayRelation.way.id, 179532213)
|
||||
self.assertEqual(wayRelation.parent_wr_id, 29233907)
|
||||
|
||||
def test_way_relation_equality(self):
|
||||
wayRelation1 = WayRelation(mockOSMWay_01_01_LongCurvy)
|
||||
wayRelation2 = copy.copy(wayRelation1)
|
||||
wayRelation3 = copy.deepcopy(wayRelation1)
|
||||
wayRelation3.way.id = 123
|
||||
|
||||
self.assertEqual(wayRelation1, wayRelation2)
|
||||
self.assertNotEqual(wayRelation1, wayRelation3)
|
||||
|
||||
def test_way_relation_reset_location_variables(self):
|
||||
wayRelation = WayRelation(mockOSMWay_01_01_LongCurvy)
|
||||
self.make_wayRelation_location_dirty(wayRelation)
|
||||
|
||||
wayRelation.reset_location_variables()
|
||||
|
||||
self.assert_wayRelation_variables_reset(wayRelation)
|
||||
|
||||
def test_way_relation_id(self):
|
||||
wayRelation = WayRelation(mockOSMWay_01_01_LongCurvy)
|
||||
|
||||
self.assertEqual(wayRelation.id, 179532213)
|
||||
|
||||
def test_way_relation_road_name(self):
|
||||
# road name when no tag for name or ref
|
||||
wayRelation = WayRelation(mockOSMWay_01_02_Loop)
|
||||
self.assertIsNone(wayRelation.road_name)
|
||||
# road name based on ref tag
|
||||
wayRelation = WayRelation(mockOSMWay_01_01_LongCurvy)
|
||||
self.assertEqual(wayRelation.road_name, "B 96")
|
||||
# road name based on name tag
|
||||
wayRelation = WayRelation(mockOSMWay_02_01_CurvyTownWithIntersections)
|
||||
self.assertEqual(wayRelation.road_name, "Hauptstraße")
|
||||
|
||||
def test_way_relation_update_resets_on_update(self):
|
||||
wayRelation = WayRelation(mockOSMWay_01_01_LongCurvy)
|
||||
self.make_wayRelation_location_dirty(wayRelation)
|
||||
location_rad = np.array([0., 0.]) # Location outside bbox
|
||||
|
||||
wayRelation.update(location_rad, 0., 10.)
|
||||
|
||||
self.assertFalse(wayRelation.is_location_in_bbox(location_rad))
|
||||
self.assert_wayRelation_variables_reset(wayRelation)
|
||||
|
||||
def test_way_relation_update_only_resets_if_no_possible_found(self):
|
||||
wayRelation = WayRelation(mockOSMWay_01_01_LongCurvy)
|
||||
location_rad = wayRelation.bbox[0] # Location inside bbox but outside actual way (due to padding)
|
||||
|
||||
wayRelation.update(location_rad, 0., 10.)
|
||||
|
||||
self.assertTrue(wayRelation.is_location_in_bbox(location_rad))
|
||||
self.assert_wayRelation_variables_reset(wayRelation)
|
||||
|
||||
def test_way_relation_updates_in_the_correct_direction_with_correct_property_values(self):
|
||||
wayRelation = WayRelation(mockOSMWay_01_01_LongCurvy)
|
||||
location_rad = np.radians(np.array([52.32855593146639, 13.445320150125069]))
|
||||
bearing_rad = 0.
|
||||
|
||||
wayRelation.update(location_rad, bearing_rad, 10.)
|
||||
|
||||
self.assertTrue(wayRelation.is_location_in_bbox(location_rad))
|
||||
self.assertEqual(wayRelation.direction, DIRECTION.FORWARD)
|
||||
self.assertEqual(wayRelation.ahead_idx, 17)
|
||||
self.assertEqual(wayRelation.behind_idx, 16)
|
||||
self.assertAlmostEqual(wayRelation._distance_to_way, 3.43290781621360)
|
||||
self.assertAlmostEqual(wayRelation._active_bearing_delta, 0.320717420388962)
|
||||
self.assertAlmostEqual(wayRelation.distance_to_node_ahead, 25.4998961709014)
|
||||
self.assertTrue(wayRelation.active)
|
||||
self.assertFalse(wayRelation.diverting)
|
||||
assert_array_almost_equal(wayRelation.location_rad, location_rad)
|
||||
self.assertEqual(wayRelation.bearing_rad, bearing_rad)
|
||||
self.assertIsNone(wayRelation._speed_limit)
|
||||
|
||||
bearing_rad = 180.
|
||||
|
||||
wayRelation.update(location_rad, bearing_rad, 10.)
|
||||
|
||||
self.assertTrue(wayRelation.is_location_in_bbox(location_rad))
|
||||
self.assertEqual(wayRelation.direction, DIRECTION.BACKWARD)
|
||||
self.assertEqual(wayRelation.ahead_idx, 16)
|
||||
self.assertEqual(wayRelation.behind_idx, 17)
|
||||
self.assertAlmostEqual(wayRelation._distance_to_way, 3.43290781621360)
|
||||
self.assertAlmostEqual(wayRelation._active_bearing_delta, 0.9507682562504284)
|
||||
self.assertAlmostEqual(wayRelation.distance_to_node_ahead, 11.11623371145368)
|
||||
self.assertTrue(wayRelation.active)
|
||||
self.assertFalse(wayRelation.diverting)
|
||||
assert_array_almost_equal(wayRelation.location_rad, location_rad)
|
||||
self.assertEqual(wayRelation.bearing_rad, bearing_rad)
|
||||
self.assertIsNone(wayRelation._speed_limit)
|
||||
|
||||
def test_way_relation_updates_with_location_closest_to_way_when_multiple_possible(self):
|
||||
wayRelation = WayRelation(mockOSMWay_01_02_Loop)
|
||||
location_rad = np.radians(np.array([52.313303275461564, 13.437729236325788]))
|
||||
bearing_rad = np.radians(10.)
|
||||
|
||||
wayRelation.update(location_rad, bearing_rad, 10.)
|
||||
|
||||
self.assertTrue(wayRelation.is_location_in_bbox(location_rad))
|
||||
self.assertEqual(wayRelation.direction, DIRECTION.BACKWARD)
|
||||
self.assertEqual(wayRelation.ahead_idx, 26)
|
||||
self.assertEqual(wayRelation.behind_idx, 27)
|
||||
self.assertAlmostEqual(wayRelation._distance_to_way, 10.151775235257011)
|
||||
self.assertAlmostEqual(wayRelation._active_bearing_delta, 0.06371131069242782)
|
||||
self.assertAlmostEqual(wayRelation.distance_to_node_ahead, 10.174073707120915)
|
||||
self.assertTrue(wayRelation.active)
|
||||
self.assertFalse(wayRelation.diverting)
|
||||
assert_array_almost_equal(wayRelation.location_rad, location_rad)
|
||||
self.assertEqual(wayRelation.bearing_rad, bearing_rad)
|
||||
self.assertIsNone(wayRelation._speed_limit)
|
||||
|
||||
def test_way_relation_updates_will_become_inactive_if_too_far_from_way(self):
|
||||
wayRelation = WayRelation(mockOSMWay_01_01_LongCurvy)
|
||||
# Location is 24.9 mts away from the way. There are 2 Lanes in this way.
|
||||
location_rad = np.radians(np.array([52.328634560607746, 13.445609877522788]))
|
||||
location_stdev = 5.5 # threshold is 4 * location_stdev + LANE_WIDTH
|
||||
distance_threshold = 4. * location_stdev + wayRelation.lanes * LANE_WIDTH / 2.
|
||||
|
||||
wayRelation.update(location_rad, 0., location_stdev)
|
||||
self.assertTrue(wayRelation.active)
|
||||
self.assertLess(wayRelation._distance_to_way, distance_threshold)
|
||||
|
||||
location_stdev = 5.
|
||||
|
||||
wayRelation.update(location_rad, 0., location_stdev)
|
||||
self.assertFalse(wayRelation.active)
|
||||
|
||||
def test_way_relation_updates_will_update_diverting_correctly(self):
|
||||
wayRelation = WayRelation(mockOSMWay_01_01_LongCurvy)
|
||||
# Location is 24.9 mts away from the way. There are 2 Lanes in this way.
|
||||
location_rad = np.radians(np.array([52.328634560607746, 13.445609877522788]))
|
||||
location_stdev = 11.
|
||||
distance_threshold = 2. * location_stdev + wayRelation.lanes * LANE_WIDTH / 2.
|
||||
|
||||
wayRelation.update(location_rad, 0., location_stdev)
|
||||
|
||||
self.assertLess(wayRelation._distance_to_way, distance_threshold)
|
||||
self.assertFalse(wayRelation.diverting)
|
||||
|
||||
location_stdev = 10.
|
||||
distance_threshold = 2. * location_stdev + wayRelation.lanes * LANE_WIDTH / 2.
|
||||
|
||||
wayRelation.update(location_rad, 0., location_stdev)
|
||||
|
||||
self.assertGreater(wayRelation._distance_to_way, distance_threshold)
|
||||
self.assertTrue(wayRelation.diverting)
|
||||
|
||||
def test_way_relation_update_direction_from_starting_node_resets_speed_limit(self):
|
||||
wayRelation = WayRelation(mockOSMWay_01_01_LongCurvy)
|
||||
wayRelation._speed_limit = 10.
|
||||
|
||||
wayRelation.update_direction_from_starting_node(wayRelation.way.nodes[0].id)
|
||||
|
||||
self.assertIsNone(wayRelation._speed_limit)
|
||||
|
||||
def test_way_relation_update_direction_from_starting_node_updates_correctly(self):
|
||||
wayRelation = WayRelation(mockOSMWay_01_01_LongCurvy)
|
||||
wayRelation.update_direction_from_starting_node(wayRelation.way.nodes[0].id)
|
||||
self.assertEqual(wayRelation.direction, DIRECTION.FORWARD)
|
||||
|
||||
wayRelation.update_direction_from_starting_node(wayRelation.way.nodes[-1].id)
|
||||
self.assertEqual(wayRelation.direction, DIRECTION.BACKWARD)
|
||||
|
||||
wayRelation.update_direction_from_starting_node(0)
|
||||
self.assertEqual(wayRelation.direction, DIRECTION.NONE)
|
||||
|
||||
def test_way_relation_is_location_in_bbox(self):
|
||||
wayRelation = WayRelation(mockOSMWay_01_02_Loop)
|
||||
bbox = wayRelation.bbox
|
||||
|
||||
loc_avg = np.average(bbox, axis=0)
|
||||
loc_min = np.min(bbox, axis=0)
|
||||
loc_max = np.max(bbox, axis=0)
|
||||
|
||||
locations = [
|
||||
loc_avg,
|
||||
loc_min,
|
||||
loc_max,
|
||||
[loc_avg[0], loc_min[1]],
|
||||
[loc_avg[0], loc_max[1]],
|
||||
[loc_min[0], loc_avg[1]],
|
||||
[loc_max[0], loc_avg[1]],
|
||||
loc_min - 0.1,
|
||||
loc_max + 0.1,
|
||||
[loc_avg[0], loc_min[1] - 0.1],
|
||||
[loc_avg[0], loc_max[1] + 0.1],
|
||||
[loc_min[0] - 0.1, loc_avg[1]],
|
||||
[loc_max[0] + 0.1, loc_avg[1]],
|
||||
]
|
||||
|
||||
is_in = [wayRelation.is_location_in_bbox(loc) for loc in locations]
|
||||
|
||||
self.assertEqual(is_in, [True, True, True, True, True, True, True, False, False, False, False, False, False])
|
||||
|
||||
def test_way_relation_speed_limit_when_set(self):
|
||||
wayRelation = WayRelation(mockOSMWay_01_02_Loop)
|
||||
wayRelation._speed_limit = 10.
|
||||
|
||||
self.assertEqual(wayRelation.speed_limit, 10.)
|
||||
|
||||
@mock.patch('selfdrive.mapd.lib.WayRelation.dt')
|
||||
def test_way_relation_speed_limit_conditional(self, mock_dt):
|
||||
tz = timezone(timedelta(hours=1), 'berlin')
|
||||
wed_10_10_am = dt(2021, 9, 1, 10, 10, 0)
|
||||
mock_dt.now.return_value = wed_10_10_am
|
||||
mock_dt.tzinfo = tz
|
||||
mock_dt.combine = dt.combine
|
||||
mock_dt.strptime = dt.strptime
|
||||
|
||||
# Reset all tags before teting
|
||||
mockOSMWay_01_02_Loop.tags = {}
|
||||
wayRelation = WayRelation(mockOSMWay_01_02_Loop)
|
||||
|
||||
# No Value
|
||||
self.assertEqual(wayRelation.speed_limit, 0.)
|
||||
|
||||
# Value on both directions
|
||||
wayRelation._speed_limit = None
|
||||
wayRelation.way.tags["maxspeed:conditional"] = "100 @ (We 10:00-10:30)"
|
||||
self.assertEqual(wayRelation.speed_limit, 100. * CV.KPH_TO_MS)
|
||||
|
||||
# Value on forward
|
||||
wayRelation.way.tags.pop("maxspeed:conditional")
|
||||
wayRelation._speed_limit = None
|
||||
wayRelation.direction = DIRECTION.FORWARD
|
||||
self.assertEqual(wayRelation.speed_limit, 0.)
|
||||
|
||||
wayRelation._speed_limit = None
|
||||
wayRelation.way.tags["maxspeed:forward:conditional"] = "100 @ (We 10:00-10:30)"
|
||||
self.assertEqual(wayRelation.speed_limit, 100. * CV.KPH_TO_MS)
|
||||
|
||||
# Value on backward
|
||||
wayRelation._speed_limit = None
|
||||
wayRelation.direction = DIRECTION.BACKWARD
|
||||
self.assertEqual(wayRelation.speed_limit, 0.)
|
||||
|
||||
wayRelation._speed_limit = None
|
||||
wayRelation.way.tags["maxspeed:backward:conditional"] = "100 @ (We 10:00-10:30)"
|
||||
self.assertEqual(wayRelation.speed_limit, 100. * CV.KPH_TO_MS)
|
||||
|
||||
def test_way_relation_speed_limit_maxspeed(self):
|
||||
# Reset all tags before teting
|
||||
mockOSMWay_01_02_Loop.tags = {}
|
||||
wayRelation = WayRelation(mockOSMWay_01_02_Loop)
|
||||
|
||||
# No Value
|
||||
self.assertEqual(wayRelation.speed_limit, 0.)
|
||||
|
||||
# Value on both directions
|
||||
wayRelation._speed_limit = None
|
||||
wayRelation.way.tags["maxspeed"] = "100"
|
||||
self.assertEqual(wayRelation.speed_limit, 100. * CV.KPH_TO_MS)
|
||||
|
||||
# Value on forward
|
||||
wayRelation.way.tags.pop("maxspeed")
|
||||
wayRelation._speed_limit = None
|
||||
wayRelation.direction = DIRECTION.FORWARD
|
||||
self.assertEqual(wayRelation.speed_limit, 0.)
|
||||
|
||||
wayRelation._speed_limit = None
|
||||
wayRelation.way.tags["maxspeed:forward"] = "100"
|
||||
self.assertEqual(wayRelation.speed_limit, 100. * CV.KPH_TO_MS)
|
||||
|
||||
# Value on backward
|
||||
wayRelation._speed_limit = None
|
||||
wayRelation.direction = DIRECTION.BACKWARD
|
||||
self.assertEqual(wayRelation.speed_limit, 0.)
|
||||
|
||||
wayRelation._speed_limit = None
|
||||
wayRelation.way.tags["maxspeed:backward"] = "100"
|
||||
self.assertEqual(wayRelation.speed_limit, 100. * CV.KPH_TO_MS)
|
||||
|
||||
def test_way_relation_active_bearing_delta_reflects_internal_value(self):
|
||||
wayRelation = WayRelation(mockOSMWay_01_02_Loop)
|
||||
wayRelation._active_bearing_delta = 10.
|
||||
self.assertEqual(wayRelation.active_bearing_delta, 10.)
|
||||
|
||||
def test_way_relation_is_one_way(self):
|
||||
# Setup initial tags
|
||||
mockOSMWay_01_02_Loop.tags = {
|
||||
'oneway': 'yes',
|
||||
'highway': 'unclassified'
|
||||
}
|
||||
wayRelation = WayRelation(mockOSMWay_01_02_Loop)
|
||||
|
||||
# oneway = yes
|
||||
self.assertTrue(wayRelation.is_one_way)
|
||||
|
||||
# oneway non existing
|
||||
wayRelation._one_way = None
|
||||
self.assertFalse(wayRelation.is_one_way)
|
||||
|
||||
# highway = motorway
|
||||
wayRelation.highway_type = 'motorway'
|
||||
self.assertTrue(wayRelation.is_one_way)
|
||||
|
||||
def test_way_relation_is_prohibited(self):
|
||||
# Setup initial tags
|
||||
mockOSMWay_01_02_Loop.tags = {
|
||||
'oneway': 'yes'
|
||||
}
|
||||
wayRelation = WayRelation(mockOSMWay_01_02_Loop)
|
||||
|
||||
# Direction undefined
|
||||
wayRelation.direction = DIRECTION.NONE
|
||||
self.assertTrue(wayRelation.is_prohibited)
|
||||
|
||||
# oneway = yes
|
||||
wayRelation.direction = DIRECTION.BACKWARD
|
||||
self.assertTrue(wayRelation.is_prohibited)
|
||||
|
||||
wayRelation.direction = DIRECTION.FORWARD
|
||||
self.assertFalse(wayRelation.is_prohibited)
|
||||
|
||||
# oneway non existing
|
||||
wayRelation._one_way = None
|
||||
self.assertFalse(wayRelation.is_one_way)
|
||||
|
||||
wayRelation.direction = DIRECTION.BACKWARD
|
||||
self.assertFalse(wayRelation.is_prohibited)
|
||||
|
||||
def test_way_relation_distance_to_way_reflects_internal_value(self):
|
||||
wayRelation = WayRelation(mockOSMWay_01_02_Loop)
|
||||
wayRelation._distance_to_way = 10.
|
||||
self.assertEqual(wayRelation.distance_to_way, 10.)
|
||||
|
||||
def test_way_relation_node_ahead(self):
|
||||
wayRelation = WayRelation(mockOSMWay_01_01_LongCurvy)
|
||||
# ahead_ids is None on init
|
||||
self.assertIsNone(wayRelation.node_ahead)
|
||||
|
||||
wayRelation.ahead_idx = 15
|
||||
self.assertEqual(wayRelation.node_ahead, wayRelation.way.nodes[15])
|
||||
|
||||
def test_way_relation_last_node(self):
|
||||
wayRelation = WayRelation(mockOSMWay_01_01_LongCurvy)
|
||||
# direction is NONE on init
|
||||
self.assertIsNone(wayRelation.last_node)
|
||||
|
||||
# forward
|
||||
wayRelation.direction = DIRECTION.FORWARD
|
||||
self.assertEqual(wayRelation.last_node, wayRelation.way.nodes[-1])
|
||||
|
||||
# backward
|
||||
wayRelation.direction = DIRECTION.BACKWARD
|
||||
self.assertEqual(wayRelation.last_node, wayRelation.way.nodes[0])
|
||||
|
||||
def test_way_relation_last_node_coordinates(self):
|
||||
wayRelation = WayRelation(mockOSMWay_01_01_LongCurvy)
|
||||
# direction is NONE on init
|
||||
self.assertIsNone(wayRelation.last_node_coordinates)
|
||||
|
||||
# forward
|
||||
wayRelation.direction = DIRECTION.FORWARD
|
||||
coords = np.radians(np.array([wayRelation.way.nodes[-1].lat, wayRelation.way.nodes[-1].lon], dtype=float))
|
||||
assert_array_almost_equal(wayRelation.last_node_coordinates, coords)
|
||||
|
||||
# backward
|
||||
wayRelation.direction = DIRECTION.BACKWARD
|
||||
coords = np.radians(np.array([wayRelation.way.nodes[0].lat, wayRelation.way.nodes[0].lon], dtype=float))
|
||||
assert_array_almost_equal(wayRelation.last_node_coordinates, coords)
|
||||
|
||||
def test_way_relation_node_before_edge_coordinates(self):
|
||||
wayRelation = WayRelation(mockOSMWay_01_01_LongCurvy)
|
||||
|
||||
coords = wayRelation.node_before_edge_coordinates(0)
|
||||
assert_array_almost_equal(coords, np.array([0., 0.]))
|
||||
|
||||
coords = wayRelation.node_before_edge_coordinates(wayRelation.way.nodes[0].id)
|
||||
coords_e = np.radians(np.array([wayRelation.way.nodes[1].lat, wayRelation.way.nodes[1].lon], dtype=float))
|
||||
assert_array_almost_equal(coords, coords_e)
|
||||
|
||||
coords = wayRelation.node_before_edge_coordinates(wayRelation.way.nodes[-1].id)
|
||||
coords_e = np.radians(np.array([wayRelation.way.nodes[-2].lat, wayRelation.way.nodes[-2].lon], dtype=float))
|
||||
assert_array_almost_equal(coords, coords_e)
|
||||
|
||||
def test_way_relation_split_no_matching_node(self):
|
||||
wayRelation = WayRelation(mockOSMWay_01_01_LongCurvy)
|
||||
|
||||
wrs = wayRelation.split(0)
|
||||
self.assertEqual(len(wrs), 0)
|
||||
|
||||
def test_way_relation_split_use_correct_ids(self):
|
||||
wayRelation = WayRelation(mockOSMWay_01_01_LongCurvy)
|
||||
|
||||
wrs = wayRelation.split(wayRelation._nodes_ids[5], [-100, -200])
|
||||
self.assertEqual(wrs[0].id, -100)
|
||||
self.assertEqual(wrs[1].id, -200)
|
||||
|
||||
def test_way_relation_split_on_edge_node(self):
|
||||
wayRelation = WayRelation(mockOSMWay_01_01_LongCurvy)
|
||||
edge_node_ids = wayRelation.edge_nodes_ids
|
||||
|
||||
for edge_node_id in edge_node_ids:
|
||||
wrs = wayRelation.split(edge_node_id)
|
||||
self.assertEqual(len(wrs), 1)
|
||||
self.assertEqual(wrs[0], wayRelation)
|
||||
self.assertEqual(wrs[0].way.tags, wayRelation.way.tags)
|
||||
|
||||
def test_way_relation_split_on_internal_node(self):
|
||||
wayRelation = WayRelation(mockOSMWay_01_01_LongCurvy)
|
||||
way_ids = [-10, -20]
|
||||
|
||||
for idx, node_id in enumerate(wayRelation._nodes_ids):
|
||||
if idx == 0 or idx == len(wayRelation._nodes_ids) - 1:
|
||||
continue
|
||||
wrs = wayRelation.split(node_id, way_ids)
|
||||
self.assertEqual(len(wrs), 2)
|
||||
assert_array_almost_equal(wrs[0]._nodes_ids, wayRelation._nodes_ids[:idx + 1])
|
||||
assert_array_almost_equal(wrs[1]._nodes_ids, wayRelation._nodes_ids[idx:])
|
||||
self.assertIn(node_id, wrs[0].edge_nodes_ids)
|
||||
self.assertIn(node_id, wrs[1].edge_nodes_ids)
|
||||
self.assertEqual(wrs[0].way.tags, wayRelation.way.tags)
|
||||
self.assertEqual(wrs[1].way.tags, wayRelation.way.tags)
|
||||
self.assertEqual(way_ids, [wr.id for wr in wrs])
|
||||
|
||||
# Helpers
|
||||
def make_wayRelation_location_dirty(self, wayRelation):
|
||||
wayRelation.distance_to_node_ahead = 10.
|
||||
wayRelation.location_rad = 0.8
|
||||
wayRelation.bearing_rad = 2.
|
||||
wayRelation.active = True
|
||||
wayRelation.diverting = True
|
||||
wayRelation.ahead_idx = 5
|
||||
wayRelation.behind_idx = 4
|
||||
wayRelation._active_bearing_delta = 3.
|
||||
wayRelation._distance_to_way = 20.
|
||||
|
||||
def assert_wayRelation_variables_reset(self, wayRelation):
|
||||
self.assertEqual(wayRelation.distance_to_node_ahead, 0.)
|
||||
self.assertIsNone(wayRelation.location_rad)
|
||||
self.assertIsNone(wayRelation.bearing_rad)
|
||||
self.assertFalse(wayRelation.active)
|
||||
self.assertFalse(wayRelation.diverting)
|
||||
self.assertIsNone(wayRelation.ahead_idx)
|
||||
self.assertIsNone(wayRelation.behind_idx)
|
||||
self.assertIsNone(wayRelation._active_bearing_delta)
|
||||
self.assertIsNone(wayRelation._distance_to_way)
|
||||
|
||||
def wayRelation_mid_point_rad(self, wayRelation):
|
||||
return np.average(wayRelation.bbox, axis=0)
|
||||
@@ -0,0 +1,74 @@
|
||||
import unittest
|
||||
from selfdrive.mapd.lib.WayRelationIndex import WayRelationIndex
|
||||
from selfdrive.mapd.test.mock_data import mockWayCollection01
|
||||
|
||||
|
||||
class TestWayRelationIndex(unittest.TestCase):
|
||||
def test_init_and_add(self):
|
||||
wrs = mockWayCollection01.way_relations
|
||||
wr_index = WayRelationIndex(wrs)
|
||||
|
||||
# expected init logic, including add logic.
|
||||
edge_nodes_index_dict = {}
|
||||
full_nodes_index_dict = {}
|
||||
for wr in wrs:
|
||||
for node in wr.way.nodes:
|
||||
node_id = node.id
|
||||
full_nodes_index_dict[node_id] = full_nodes_index_dict.get(node_id, []) + [wr]
|
||||
if node_id in wr.edge_nodes_ids:
|
||||
edge_nodes_index_dict[node_id] = edge_nodes_index_dict.get(node_id, []) + [wr]
|
||||
|
||||
# assert logic delivers same result
|
||||
self.assertDictEqual(edge_nodes_index_dict, wr_index._edge_nodes_index_dict)
|
||||
self.assertDictEqual(full_nodes_index_dict, wr_index._full_nodes_index_dict)
|
||||
self.assertEqual(len(wr_index._edge_nodes_index_dict), 586)
|
||||
self.assertEqual(len(wr_index._full_nodes_index_dict), 2342)
|
||||
|
||||
def test_remove(self):
|
||||
wrs = mockWayCollection01.way_relations
|
||||
wr_index = WayRelationIndex(wrs)
|
||||
|
||||
wr_to_remove = wrs[0]
|
||||
affected_full_node_ids = [nodesData.id for nodesData in wr_to_remove.way.nodes]
|
||||
affected_edge_node_ids = wr_to_remove.edge_nodes_ids
|
||||
|
||||
initial_full_lists = [wr_index._full_nodes_index_dict[ndid] for ndid in affected_full_node_ids]
|
||||
initial_edge_lists = [wr_index._edge_nodes_index_dict[ndid] for ndid in affected_edge_node_ids]
|
||||
|
||||
expected_final_full_lists = [[wr for wr in li if wr is not wr_to_remove] for li in initial_full_lists]
|
||||
expected_final_edge_lists = [[wr for wr in li if wr is not wr_to_remove] for li in initial_edge_lists]
|
||||
|
||||
wr_index.remove(wr_to_remove)
|
||||
|
||||
final_full_lists = [wr_index._full_nodes_index_dict[ndid] for ndid in affected_full_node_ids]
|
||||
final_edge_lists = [wr_index._edge_nodes_index_dict[ndid] for ndid in affected_edge_node_ids]
|
||||
|
||||
for idx, li in enumerate(final_full_lists):
|
||||
self.assertListEqual(li, expected_final_full_lists[idx])
|
||||
|
||||
for idx, li in enumerate(final_edge_lists):
|
||||
self.assertListEqual(li, expected_final_edge_lists[idx])
|
||||
|
||||
def test_way_relations_with_edge_node_id(self):
|
||||
wr_index = WayRelationIndex([])
|
||||
ref_dict = {
|
||||
0: ["fake_wr1", "fake_wr2"],
|
||||
1: ["fake_wr3"],
|
||||
3: ["fake_wr4", "fake_wr5", "fake_wr6"],
|
||||
}
|
||||
wr_index._edge_nodes_index_dict = ref_dict
|
||||
|
||||
for key, li in ref_dict.items():
|
||||
self.assertListEqual(li, wr_index.way_relations_with_edge_node_id(key))
|
||||
|
||||
def test_way_relations_with_node_id(self):
|
||||
wr_index = WayRelationIndex([])
|
||||
ref_dict = {
|
||||
0: ["fake_wr1", "fake_wr2"],
|
||||
1: ["fake_wr3"],
|
||||
3: ["fake_wr4", "fake_wr5", "fake_wr6"],
|
||||
}
|
||||
wr_index._full_nodes_index_dict = ref_dict
|
||||
|
||||
for key, li in ref_dict.items():
|
||||
self.assertListEqual(li, wr_index.way_relations_with_node_id(key))
|
||||
@@ -0,0 +1,234 @@
|
||||
import unittest
|
||||
from selfdrive.mapd.lib.geo import vectors, ref_vectors, bearing_to_points, distance_to_points
|
||||
import numpy as np
|
||||
from numpy.testing import assert_array_almost_equal
|
||||
from selfdrive.mapd.test.mock_data import mockNodesData01
|
||||
|
||||
|
||||
class TestMapsdGeoLibrary(unittest.TestCase):
|
||||
def test_vectors(self):
|
||||
points = mockNodesData01.radians
|
||||
expected = np.array([
|
||||
[-1.34011951e-05, 1.00776468e-05],
|
||||
[-5.83610920e-06, 4.41046897e-06],
|
||||
[-7.83348567e-06, 5.94114032e-06],
|
||||
[-7.08560788e-06, 5.30408795e-06],
|
||||
[-6.57632550e-06, 4.05791838e-06],
|
||||
[-1.16077872e-06, 6.91151252e-07],
|
||||
[-1.53178098e-05, 9.62215139e-06],
|
||||
[-5.76314175e-06, 3.55176643e-06],
|
||||
[-1.61124141e-05, 9.86127759e-06],
|
||||
[-1.48006628e-05, 8.58192512e-06],
|
||||
[-1.72237209e-06, 1.60570482e-06],
|
||||
[-8.68985228e-06, 9.22062311e-06],
|
||||
[-1.42922812e-06, 1.51494711e-06],
|
||||
[-3.39761486e-06, 2.57087743e-06],
|
||||
[-2.75467373e-06, 1.28631255e-06],
|
||||
[-1.57501989e-05, 5.72309451e-06],
|
||||
[-2.52143954e-06, 1.34565295e-06],
|
||||
[-1.65278643e-06, 1.28630942e-06],
|
||||
[-2.22196114e-05, 1.64360838e-05],
|
||||
[-5.88675934e-06, 4.08234746e-06],
|
||||
[-1.83673390e-06, 1.46782408e-06],
|
||||
[-1.55004206e-06, 1.51843800e-06],
|
||||
[-1.20451533e-06, 2.06298011e-06],
|
||||
[-1.91801338e-06, 4.64083285e-06],
|
||||
[-2.38653483e-06, 5.60076524e-06],
|
||||
[-1.65269781e-06, 5.78402290e-06],
|
||||
[-3.66908309e-07, 2.75412965e-06],
|
||||
[0.00000000e+00, 1.92858882e-06],
|
||||
[9.09242615e-08, 2.66162711e-06],
|
||||
[3.14490354e-07, 1.53065382e-06],
|
||||
[8.66452477e-08, 4.83456208e-07],
|
||||
[2.41750593e-07, 1.10828411e-06],
|
||||
[7.43745228e-06, 1.27618831e-05],
|
||||
[5.59968054e-06, 9.63947367e-06],
|
||||
[2.01951467e-06, 2.75413219e-06],
|
||||
[4.59952643e-07, 6.42281301e-07],
|
||||
[1.74353749e-06, 1.74533121e-06],
|
||||
[2.57144338e-06, 2.11185266e-06],
|
||||
[1.46893187e-05, 1.11999169e-05],
|
||||
[3.84659229e-05, 2.85527952e-05],
|
||||
[2.71627936e-05, 1.98727946e-05],
|
||||
[8.44632540e-06, 6.15058628e-06],
|
||||
[2.29420323e-06, 1.92859222e-06],
|
||||
[2.58083439e-06, 3.16952222e-06],
|
||||
[3.76373643e-06, 5.14174911e-06],
|
||||
[5.32416098e-06, 6.51707770e-06],
|
||||
[8.62890928e-06, 1.11998258e-05],
|
||||
[1.25762497e-05, 1.65231340e-05],
|
||||
[8.90452991e-06, 1.10148240e-05],
|
||||
[4.86505726e-06, 4.59023120e-06],
|
||||
[3.85545276e-06, 3.39642031e-06],
|
||||
[3.48753893e-06, 3.30566145e-06],
|
||||
[2.99557303e-06, 2.61276368e-06],
|
||||
[2.15496788e-06, 1.87797727e-06],
|
||||
[4.10564937e-06, 3.58142649e-06],
|
||||
[1.53680853e-06, 1.33866906e-06],
|
||||
[4.99540175e-06, 4.35635790e-06],
|
||||
[1.37744970e-06, 1.19380643e-06],
|
||||
[1.74319821e-06, 1.28456429e-06],
|
||||
[9.99931238e-07, 1.14493663e-06],
|
||||
[6.42735560e-07, 1.19380547e-06],
|
||||
[3.66818436e-07, 1.46782199e-06],
|
||||
[5.45413874e-08, 1.83783170e-06],
|
||||
[-1.35818548e-07, 1.14842666e-06],
|
||||
[-5.50758101e-07, 3.02989178e-06],
|
||||
[-4.58785270e-07, 2.66162724e-06],
|
||||
[-2.51315555e-07, 1.19031459e-06],
|
||||
[-3.91409773e-07, 1.65457223e-06],
|
||||
[-2.14525206e-06, 5.67755902e-06],
|
||||
[-4.24558096e-07, 1.39102753e-06],
|
||||
[-1.46936730e-06, 5.32325561e-06],
|
||||
[-1.37632061e-06, 4.59021715e-06],
|
||||
[-8.26642899e-07, 4.68097349e-06],
|
||||
[-6.42702724e-07, 4.95673534e-06],
|
||||
[-3.66796960e-07, 7.25009780e-06],
|
||||
[-1.82861669e-07, 8.99542699e-06],
|
||||
[4.09564134e-07, 6.11214315e-06],
|
||||
[7.80629912e-08, 1.45734993e-06],
|
||||
[4.81205526e-07, 7.56076647e-06],
|
||||
[2.01036346e-07, 2.42775302e-06]])
|
||||
|
||||
v = vectors(points)
|
||||
assert_array_almost_equal(v, expected)
|
||||
|
||||
def test_ref_vectors(self):
|
||||
points = mockNodesData01.radians
|
||||
expected = np.array([
|
||||
[1.59924145e-04, -1.07153714e-04],
|
||||
[1.46520873e-04, -9.70788297e-05],
|
||||
[1.40683931e-04, -9.26694631e-05],
|
||||
[1.32849368e-04, -8.67297434e-05],
|
||||
[1.25762852e-04, -8.14268689e-05],
|
||||
[1.19185869e-04, -7.73700167e-05],
|
||||
[1.18024984e-04, -7.66790438e-05],
|
||||
[1.02705711e-04, -6.70592230e-05],
|
||||
[9.69420991e-05, -6.35082196e-05],
|
||||
[8.08284530e-05, -5.36489556e-05],
|
||||
[6.60268961e-05, -4.50685727e-05],
|
||||
[6.43043874e-05, -4.34630144e-05],
|
||||
[5.56137708e-05, -3.42431117e-05],
|
||||
[5.41844341e-05, -3.27282671e-05],
|
||||
[5.07866397e-05, -3.01576270e-05],
|
||||
[4.80318817e-05, -2.88714948e-05],
|
||||
[3.22813286e-05, -2.31493755e-05],
|
||||
[2.97598330e-05, -2.18038275e-05],
|
||||
[2.81069973e-05, -2.05175815e-05],
|
||||
[5.88679032e-06, -4.08230278e-06],
|
||||
[0.00000000e+00, 0.00000000e+00],
|
||||
[-1.83673390e-06, 1.46782408e-06],
|
||||
[-3.38677236e-06, 2.98626574e-06],
|
||||
[-4.59127869e-06, 5.04925111e-06],
|
||||
[-6.50926460e-06, 9.69009532e-06],
|
||||
[-8.89575243e-06, 1.52908806e-05],
|
||||
[-1.05483839e-05, 2.10749224e-05],
|
||||
[-1.09152548e-05, 2.38290571e-05],
|
||||
[-1.09152276e-05, 2.57576459e-05],
|
||||
[-1.08242659e-05, 2.84192717e-05],
|
||||
[-1.05097542e-05, 2.99499212e-05],
|
||||
[-1.04231024e-05, 3.04333762e-05],
|
||||
[-1.01813369e-05, 3.15416571e-05],
|
||||
[-2.74371711e-06, 4.43034426e-05],
|
||||
[2.85599752e-06, 5.39428964e-05],
|
||||
[4.87550206e-06, 5.66970360e-05],
|
||||
[5.33545066e-06, 5.73393202e-05],
|
||||
[7.07897615e-06, 5.90846634e-05],
|
||||
[9.65040026e-06, 6.11965396e-05],
|
||||
[2.43395796e-05, 7.23966392e-05],
|
||||
[6.28046063e-05, 1.00950641e-04],
|
||||
[8.99657904e-05, 1.20825635e-04],
|
||||
[9.84114021e-05, 1.26977201e-04],
|
||||
[1.00705361e-04, 1.28906084e-04],
|
||||
[1.03285783e-04, 1.32075942e-04],
|
||||
[1.07048835e-04, 1.37218192e-04],
|
||||
[1.12372096e-04, 1.43736004e-04],
|
||||
[1.20999382e-04, 1.54937080e-04],
|
||||
[1.33573053e-04, 1.71462176e-04],
|
||||
[1.42475686e-04, 1.82478533e-04],
|
||||
[1.47339899e-04, 1.87069658e-04],
|
||||
[1.51194707e-04, 1.90466811e-04],
|
||||
[1.54681601e-04, 1.93773152e-04],
|
||||
[1.57676653e-04, 1.96386513e-04],
|
||||
[1.59831239e-04, 1.98264929e-04],
|
||||
[1.63936150e-04, 2.01847201e-04],
|
||||
[1.65472675e-04, 2.03186195e-04],
|
||||
[1.70467147e-04, 2.07543619e-04],
|
||||
[1.71844334e-04, 2.08737728e-04],
|
||||
[1.73587247e-04, 2.10022678e-04],
|
||||
[1.74586922e-04, 2.11167839e-04],
|
||||
[1.75229389e-04, 2.12361789e-04],
|
||||
[1.75595876e-04, 2.13829694e-04],
|
||||
[1.75650001e-04, 2.15667538e-04],
|
||||
[1.75513922e-04, 2.16815933e-04],
|
||||
[1.74962478e-04, 2.19845700e-04],
|
||||
[1.74503092e-04, 2.22507224e-04],
|
||||
[1.74251509e-04, 2.23697482e-04],
|
||||
[1.73859727e-04, 2.25351966e-04],
|
||||
[1.71713202e-04, 2.31029044e-04],
|
||||
[1.71288336e-04, 2.32419977e-04],
|
||||
[1.69817793e-04, 2.37742908e-04],
|
||||
[1.68440467e-04, 2.42332824e-04],
|
||||
[1.67612807e-04, 2.47013617e-04],
|
||||
[1.66969033e-04, 2.51970213e-04],
|
||||
[1.66600674e-04, 2.59220232e-04],
|
||||
[1.66415880e-04, 2.68215619e-04],
|
||||
[1.66824132e-04, 2.74327850e-04],
|
||||
[1.66901881e-04, 2.75785216e-04],
|
||||
[1.67381459e-04, 2.83346086e-04],
|
||||
[1.67581971e-04, 2.85773882e-04]])
|
||||
|
||||
v = ref_vectors(points[20], points)
|
||||
assert_array_almost_equal(v, expected)
|
||||
|
||||
def test_bearing_to_points(self):
|
||||
points = mockNodesData01.radians
|
||||
expected = np.array([
|
||||
2.16112265, 2.15595027, 2.15326799, 2.14916735, 2.14538642,
|
||||
2.14657678, 2.14694997, 2.1492257, 2.1507589, 2.15676899,
|
||||
2.16973441, 2.1651606, 2.12270237, 2.11416356, 2.10665211,
|
||||
2.11201708, 2.19291574, 2.2031069, 2.20136186, 2.17712517,
|
||||
0., -0.8965745, -0.84815954, -0.73792895, -0.59150953,
|
||||
-0.5269061, -0.46406215, -0.42954043, -0.4008254, -0.36391371,
|
||||
-0.33748609, -0.32996807, -0.31223189, -0.06185112, 0.05289544,
|
||||
0.08578116, 0.0927833, 0.11924233, 0.15640718, 0.32432622,
|
||||
0.55653415, 0.64003094, 0.6593301, 0.66319086, 0.66367982,
|
||||
0.66251077, 0.66354137, 0.66302176, 0.66181884, 0.66291139,
|
||||
0.66714676, 0.67095594, 0.67367984, 0.6765003, 0.67847961,
|
||||
0.68212344, 0.68345356, 0.68762778, 0.68876073, 0.69070183,
|
||||
0.69085143, 0.68988665, 0.68753177, 0.68348884, 0.68051081,
|
||||
0.67220053, 0.66506824, 0.66177969, 0.65712162, 0.63916951,
|
||||
0.6351146, 0.62025347, 0.60741567, 0.59618923, 0.58521935,
|
||||
0.57122582, 0.55532475, 0.54636839, 0.54422542, 0.53357655,
|
||||
0.53037033])
|
||||
|
||||
v = bearing_to_points(points[20], points)
|
||||
assert_array_almost_equal(v, expected)
|
||||
|
||||
def test_distance_to_points(self):
|
||||
points = mockNodesData01.radians
|
||||
expected = np.array([
|
||||
1226.82569068, 1120.13820773, 1073.61121415, 1011.10016574,
|
||||
954.81557436, 905.58045038, 896.97734399, 781.7102819,
|
||||
738.58271117, 618.26145463, 509.47052142, 494.6403804,
|
||||
416.22483123, 403.42108699, 376.42615499, 357.15106681,
|
||||
253.15957483, 235.11572972, 221.77439728, 45.65465979,
|
||||
0., 14.98414, 28.77606056, 43.49299446,
|
||||
74.39463425, 112.74005248, 150.19482607, 167.03665191,
|
||||
178.28443483, 193.80834084, 202.28154097, 205.01173833,
|
||||
211.22777104, 282.88676739, 344.25957352, 362.66370657,
|
||||
367.00206795, 379.23951996, 394.82505328, 486.76073331,
|
||||
757.70254732, 960.03439155, 1023.81434529, 1042.49401713,
|
||||
1068.53770096, 1109.12696535, 1162.74555108, 1252.847351,
|
||||
1385.17179405, 1475.42502599, 1517.57849916, 1549.79838056,
|
||||
1580.12405964, 1605.05483058, 1622.98937809, 1657.19268821,
|
||||
1669.99157205, 1711.63883132, 1723.09133393, 1736.47655688,
|
||||
1746.16073119, 1754.63481838, 1763.34186103, 1772.62691273,
|
||||
1777.76189094, 1790.62024447, 1802.11488235, 1807.1040605,
|
||||
1813.90756815, 1834.49265566, 1840.00708445, 1861.96087374,
|
||||
1880.81678093, 1902.42091191, 1926.37194131, 1963.78301115,
|
||||
2011.62679077, 2046.18028824, 2054.37811294, 2097.30347724,
|
||||
2111.28586072])
|
||||
|
||||
v = distance_to_points(points[20], points)
|
||||
assert_array_almost_equal(v, expected)
|
||||
@@ -8,6 +8,7 @@ from common.realtime import set_realtime_priority
|
||||
from selfdrive.controls.lib.events import Events
|
||||
from selfdrive.locationd.calibrationd import Calibration
|
||||
from selfdrive.monitoring.driver_monitor import DriverStatus
|
||||
from selfdrive.monitoring.hands_on_wheel_monitor import HandsOnWheelStatus
|
||||
|
||||
|
||||
def dmonitoringd_thread(sm=None, pm=None):
|
||||
@@ -21,6 +22,7 @@ def dmonitoringd_thread(sm=None, pm=None):
|
||||
sm = messaging.SubMaster(['driverStateV2', 'liveCalibration', 'carState', 'controlsState', 'modelV2'], poll=['driverStateV2'])
|
||||
|
||||
driver_status = DriverStatus(rhd_saved=Params().get_bool("IsRhdDetected"))
|
||||
hands_on_wheel_status = HandsOnWheelStatus()
|
||||
|
||||
sm['liveCalibration'].calStatus = Calibration.INVALID
|
||||
sm['liveCalibration'].rpyCalib = [0, 0, 0]
|
||||
@@ -29,6 +31,8 @@ def dmonitoringd_thread(sm=None, pm=None):
|
||||
|
||||
v_cruise_last = 0
|
||||
driver_engaged = False
|
||||
steering_wheel_engaged = False
|
||||
hands_on_wheel_monitoring_enabled = Params().get_bool("HandsOnWheelMonitoring")
|
||||
|
||||
# 10Hz <- dmonitoringmodeld
|
||||
while True:
|
||||
@@ -40,10 +44,13 @@ def dmonitoringd_thread(sm=None, pm=None):
|
||||
# Get interaction
|
||||
if sm.updated['carState']:
|
||||
v_cruise = sm['carState'].cruiseState.speed
|
||||
driver_engaged = len(sm['carState'].buttonEvents) > 0 or \
|
||||
v_cruise != v_cruise_last or \
|
||||
sm['carState'].steeringPressed or \
|
||||
sm['carState'].gasPressed
|
||||
steering_wheel_engaged = len(sm['carState'].buttonEvents) > 0 or \
|
||||
v_cruise != v_cruise_last or \
|
||||
sm['carState'].steeringPressed
|
||||
driver_engaged = steering_wheel_engaged or sm['carState'].gasPressed
|
||||
# Update events and state from hands on wheel monitoring status when steering wheel in engaged
|
||||
if steering_wheel_engaged and hands_on_wheel_monitoring_enabled:
|
||||
hands_on_wheel_status.update(Events(), True, sm['controlsState'].enabled, sm['carState'].vEgo)
|
||||
v_cruise_last = v_cruise
|
||||
|
||||
if sm.updated['modelV2']:
|
||||
@@ -60,6 +67,9 @@ def dmonitoringd_thread(sm=None, pm=None):
|
||||
|
||||
# Update events from driver state
|
||||
driver_status.update_events(events, driver_engaged, sm['controlsState'].enabled, sm['carState'].standstill)
|
||||
# Update events and state from hands on wheel monitoring status
|
||||
if hands_on_wheel_monitoring_enabled:
|
||||
hands_on_wheel_status.update(events, steering_wheel_engaged, sm['controlsState'].enabled, sm['carState'].vEgo)
|
||||
|
||||
# build driverMonitoringState packet
|
||||
dat = messaging.new_message('driverMonitoringState')
|
||||
@@ -80,6 +90,7 @@ def dmonitoringd_thread(sm=None, pm=None):
|
||||
"hiStdCount": driver_status.hi_stds,
|
||||
"isActiveMode": driver_status.active_monitoring_mode,
|
||||
"isRHD": driver_status.wheel_on_right,
|
||||
"handsOnWheelState": hands_on_wheel_status.hands_on_wheel_state,
|
||||
}
|
||||
pm.send('driverMonitoringState', dat)
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
from cereal import log, car
|
||||
from common.conversions import Conversions as CV
|
||||
|
||||
EventName = car.CarEvent.EventName
|
||||
HandsOnWheelState = log.DriverMonitoringState.HandsOnWheelState
|
||||
|
||||
_PRE_ALERT_THRESHOLD = 150 # 15s
|
||||
_PROMPT_ALERT_THRESHOLD = 300 # 30s
|
||||
_TERMINAL_ALERT_THRESHOLD = 600 # 60s
|
||||
|
||||
_MIN_MONITORING_SPEED = 10 * CV.KPH_TO_MS # No monitoring underd 10kph
|
||||
|
||||
|
||||
class HandsOnWheelStatus():
|
||||
def __init__(self):
|
||||
self.hands_on_wheel_state = HandsOnWheelState.none
|
||||
self.hands_off_wheel_cnt = 0
|
||||
|
||||
def update(self, events, steering_wheel_engaged, ctrl_active, v_ego):
|
||||
if v_ego < _MIN_MONITORING_SPEED or not ctrl_active:
|
||||
self.hands_on_wheel_state = HandsOnWheelState.none
|
||||
self.hands_off_wheel_cnt = 0
|
||||
return
|
||||
|
||||
if steering_wheel_engaged:
|
||||
# Driver has hands on steering wheel
|
||||
self.hands_on_wheel_state = HandsOnWheelState.ok
|
||||
self.hands_off_wheel_cnt = 0
|
||||
return
|
||||
|
||||
self.hands_off_wheel_cnt += 1
|
||||
alert = None
|
||||
|
||||
if self.hands_off_wheel_cnt >= _TERMINAL_ALERT_THRESHOLD:
|
||||
# terminal red alert: disengagement required
|
||||
self.hands_on_wheel_state = HandsOnWheelState.terminal
|
||||
alert = EventName.keepHandsOnWheel
|
||||
elif self.hands_off_wheel_cnt >= _PROMPT_ALERT_THRESHOLD:
|
||||
# prompt orange alert
|
||||
self.hands_on_wheel_state = HandsOnWheelState.critical
|
||||
alert = EventName.promptKeepHandsOnWheel
|
||||
elif self.hands_off_wheel_cnt >= _PRE_ALERT_THRESHOLD:
|
||||
# pre green alert
|
||||
self.hands_on_wheel_state = HandsOnWheelState.warning
|
||||
alert = EventName.preKeepHandsOnWheel
|
||||
else:
|
||||
# hands off wheel for acceptable period of time.
|
||||
self.hands_on_wheel_state = HandsOnWheelState.minor
|
||||
|
||||
if alert is not None:
|
||||
events.add(alert)
|
||||
@@ -0,0 +1,139 @@
|
||||
# flake8: noqa
|
||||
|
||||
import unittest
|
||||
import numpy as np
|
||||
from cereal import car, log
|
||||
from common.realtime import DT_DMON
|
||||
from selfdrive.controls.lib.events import Events
|
||||
from selfdrive.monitoring.hands_on_wheel_monitor import HandsOnWheelStatus, _PRE_ALERT_THRESHOLD, \
|
||||
_PROMPT_ALERT_THRESHOLD, _TERMINAL_ALERT_THRESHOLD, \
|
||||
_MIN_MONITORING_SPEED
|
||||
|
||||
EventName = car.CarEvent.EventName
|
||||
HandsOnWheelState = log.DriverMonitoringState.HandsOnWheelState
|
||||
|
||||
_TEST_TIMESPAN = 120 # seconds
|
||||
|
||||
# some common state vectors
|
||||
test_samples = int(_TEST_TIMESPAN / DT_DMON)
|
||||
half_test_samples = int(test_samples / 2.)
|
||||
always_speed_over_threshold = [_MIN_MONITORING_SPEED + 1.] * test_samples
|
||||
always_speed_under_threshold = [_MIN_MONITORING_SPEED - 1.] * test_samples
|
||||
always_true = [True] * test_samples
|
||||
always_false = [False] * test_samples
|
||||
true_then_false = [True] * half_test_samples + [False] * (test_samples - half_test_samples)
|
||||
|
||||
|
||||
def run_HOWState_seq(steering_wheel_interaction, openpilot_status, speed_status):
|
||||
# inputs are all 10Hz
|
||||
HOWS = HandsOnWheelStatus()
|
||||
events_from_HOWM = []
|
||||
hands_on_wheel_state_from_HOWM = []
|
||||
|
||||
for idx in range(len(steering_wheel_interaction)):
|
||||
e = Events()
|
||||
# evaluate events at 10Hz for tests
|
||||
HOWS.update(e, steering_wheel_interaction[idx], openpilot_status[idx], speed_status[idx])
|
||||
events_from_HOWM.append(e)
|
||||
hands_on_wheel_state_from_HOWM.append(HOWS.hands_on_wheel_state)
|
||||
|
||||
assert len(events_from_HOWM) == len(steering_wheel_interaction), 'somethings wrong'
|
||||
assert len(hands_on_wheel_state_from_HOWM) == len(steering_wheel_interaction), 'somethings wrong'
|
||||
return events_from_HOWM, hands_on_wheel_state_from_HOWM
|
||||
|
||||
|
||||
class TestHandsMonitoring(unittest.TestCase):
|
||||
# 0. op engaged over monitoring speed, driver has hands on wheel all the time
|
||||
def test_hands_on_all_the_time(self):
|
||||
events_output, state_output = run_HOWState_seq(always_true, always_true, always_speed_over_threshold)
|
||||
self.assertTrue(np.sum([len(event) for event in events_output]) == 0)
|
||||
self.assertEqual(state_output, [HandsOnWheelState.ok for x in range(len(state_output))])
|
||||
|
||||
# 1. op engaged under monitoring speed, steering wheel interaction is irrelevant
|
||||
def test_monitoring_under_threshold_speed(self):
|
||||
events_output, state_output = run_HOWState_seq(true_then_false, always_true, always_speed_under_threshold)
|
||||
self.assertTrue(np.sum([len(event) for event in events_output]) == 0)
|
||||
self.assertEqual(state_output, [HandsOnWheelState.none for x in range(len(state_output))])
|
||||
|
||||
# 2. op engaged over monitoring speed, driver has no hands on wheel all the time
|
||||
def test_hands_off_all_the_time(self):
|
||||
events_output, state_output = run_HOWState_seq(always_false, always_true, always_speed_over_threshold)
|
||||
# Assert correctness before _PRE_ALERT_THRESHOLD
|
||||
self.assertTrue(np.sum([len(event) for event in events_output[:_PRE_ALERT_THRESHOLD - 1]]) == 0)
|
||||
self.assertEqual(state_output[:_PRE_ALERT_THRESHOLD - 1],
|
||||
[HandsOnWheelState.minor for x in range(_PRE_ALERT_THRESHOLD - 1)])
|
||||
# Assert correctness before _PROMPT_ALERT_THRESHOLD
|
||||
self.assertEqual([event.names[0] for event in events_output[_PRE_ALERT_THRESHOLD:_PROMPT_ALERT_THRESHOLD - 1]],
|
||||
[EventName.preKeepHandsOnWheel for x in range(_PROMPT_ALERT_THRESHOLD - 1 - _PRE_ALERT_THRESHOLD)])
|
||||
self.assertEqual(state_output[_PRE_ALERT_THRESHOLD:_PROMPT_ALERT_THRESHOLD - 1],
|
||||
[HandsOnWheelState.warning for x in range(_PROMPT_ALERT_THRESHOLD - 1 - _PRE_ALERT_THRESHOLD)])
|
||||
# Assert correctness before _TERMINAL_ALERT_THRESHOLD
|
||||
self.assertEqual(
|
||||
[event.names[0] for event in events_output[_PROMPT_ALERT_THRESHOLD:_TERMINAL_ALERT_THRESHOLD - 1]],
|
||||
[EventName.promptKeepHandsOnWheel for x in range(_TERMINAL_ALERT_THRESHOLD - 1 - _PROMPT_ALERT_THRESHOLD)])
|
||||
self.assertEqual(
|
||||
state_output[_PROMPT_ALERT_THRESHOLD:_TERMINAL_ALERT_THRESHOLD - 1],
|
||||
[HandsOnWheelState.critical for x in range(_TERMINAL_ALERT_THRESHOLD - 1 - _PROMPT_ALERT_THRESHOLD)])
|
||||
# Assert correctness after _TERMINAL_ALERT_THRESHOLD
|
||||
self.assertEqual([event.names[0] for event in events_output[_TERMINAL_ALERT_THRESHOLD:]],
|
||||
[EventName.keepHandsOnWheel for x in range(test_samples - _TERMINAL_ALERT_THRESHOLD)])
|
||||
self.assertEqual(state_output[_TERMINAL_ALERT_THRESHOLD:],
|
||||
[HandsOnWheelState.terminal for x in range(test_samples - _TERMINAL_ALERT_THRESHOLD)])
|
||||
|
||||
# 3. op engaged over monitoring speed, alert status resets to none when going under monitoring speed
|
||||
def test_status_none_when_speeds_goes_down(self):
|
||||
speed_vector = always_speed_over_threshold[:-1] + [_MIN_MONITORING_SPEED - 1.]
|
||||
events_output, state_output = run_HOWState_seq(always_false, always_true, speed_vector)
|
||||
# Assert correctness after _TERMINAL_ALERT_THRESHOLD
|
||||
self.assertEqual([event.names[0] for event in events_output[_TERMINAL_ALERT_THRESHOLD:test_samples - 1]],
|
||||
[EventName.keepHandsOnWheel for x in range(test_samples - 1 - _TERMINAL_ALERT_THRESHOLD)])
|
||||
self.assertEqual(state_output[_TERMINAL_ALERT_THRESHOLD:test_samples - 1],
|
||||
[HandsOnWheelState.terminal for x in range(test_samples - 1 - _TERMINAL_ALERT_THRESHOLD)])
|
||||
# Assert correctes on last sample where speed went under monitoring threshold
|
||||
self.assertEqual(len(events_output[-1]), 0)
|
||||
self.assertEqual(state_output[-1], HandsOnWheelState.none)
|
||||
|
||||
# 4. op engaged over monitoring speed, alert status resets to ok when user interacts with steering wheel,
|
||||
# process repeats once hands are off wheel.
|
||||
def test_status_ok_after_interaction_with_wheel(self):
|
||||
interaction_vector = always_false[:_TERMINAL_ALERT_THRESHOLD] + [True
|
||||
] + always_false[_TERMINAL_ALERT_THRESHOLD + 1:]
|
||||
events_output, state_output = run_HOWState_seq(interaction_vector, always_true, always_speed_over_threshold)
|
||||
# Assert correctness after _TERMINAL_ALERT_THRESHOLD
|
||||
self.assertEqual(events_output[_TERMINAL_ALERT_THRESHOLD - 1].names[0], EventName.keepHandsOnWheel)
|
||||
self.assertEqual(state_output[_TERMINAL_ALERT_THRESHOLD - 1], HandsOnWheelState.terminal)
|
||||
# Assert correctness for one sample when user interacts with steering wheel
|
||||
self.assertEqual(len(events_output[_TERMINAL_ALERT_THRESHOLD]), 0)
|
||||
self.assertEqual(state_output[_TERMINAL_ALERT_THRESHOLD], HandsOnWheelState.ok)
|
||||
# Assert process correctness on second run
|
||||
offset = _TERMINAL_ALERT_THRESHOLD + 1
|
||||
self.assertTrue(np.sum([len(event) for event in events_output[offset:offset + _PRE_ALERT_THRESHOLD - 1]]) == 0)
|
||||
self.assertEqual(state_output[offset:offset + _PRE_ALERT_THRESHOLD - 1],
|
||||
[HandsOnWheelState.minor for x in range(_PRE_ALERT_THRESHOLD - 1)])
|
||||
self.assertEqual(
|
||||
[event.names[0] for event in events_output[offset + _PRE_ALERT_THRESHOLD:offset + _PROMPT_ALERT_THRESHOLD - 1]],
|
||||
[EventName.preKeepHandsOnWheel for x in range(_PROMPT_ALERT_THRESHOLD - 1 - _PRE_ALERT_THRESHOLD)])
|
||||
self.assertEqual(state_output[offset + _PRE_ALERT_THRESHOLD:offset + _PROMPT_ALERT_THRESHOLD - 1],
|
||||
[HandsOnWheelState.warning for x in range(_PROMPT_ALERT_THRESHOLD - 1 - _PRE_ALERT_THRESHOLD)])
|
||||
self.assertEqual([
|
||||
event.names[0]
|
||||
for event in events_output[offset + _PROMPT_ALERT_THRESHOLD:offset + _TERMINAL_ALERT_THRESHOLD - 1]
|
||||
], [EventName.promptKeepHandsOnWheel for x in range(_TERMINAL_ALERT_THRESHOLD - 1 - _PROMPT_ALERT_THRESHOLD)])
|
||||
self.assertEqual(
|
||||
state_output[offset + _PROMPT_ALERT_THRESHOLD:offset + _TERMINAL_ALERT_THRESHOLD - 1],
|
||||
[HandsOnWheelState.critical for x in range(_TERMINAL_ALERT_THRESHOLD - 1 - _PROMPT_ALERT_THRESHOLD)])
|
||||
self.assertEqual([event.names[0] for event in events_output[offset + _TERMINAL_ALERT_THRESHOLD:]],
|
||||
[EventName.keepHandsOnWheel for x in range(test_samples - offset - _TERMINAL_ALERT_THRESHOLD)])
|
||||
self.assertEqual(state_output[offset + _TERMINAL_ALERT_THRESHOLD:],
|
||||
[HandsOnWheelState.terminal for x in range(test_samples - offset - _TERMINAL_ALERT_THRESHOLD)])
|
||||
|
||||
# 5. op not engaged, always hands off wheel
|
||||
# - monitor should stay quiet when not engaged
|
||||
def test_pure_dashcam_user(self):
|
||||
events_output, state_output = run_HOWState_seq(always_false, always_false, always_speed_over_threshold)
|
||||
self.assertTrue(np.sum([len(event) for event in events_output]) == 0)
|
||||
self.assertEqual(state_output, [HandsOnWheelState.none for x in range(len(state_output))])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <cmath>
|
||||
|
||||
#include <QDebug>
|
||||
#include <iomanip>
|
||||
|
||||
#include "common/timing.h"
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
@@ -52,7 +53,7 @@ OnroadWindow::OnroadWindow(QWidget *parent) : QWidget(parent) {
|
||||
|
||||
void OnroadWindow::updateState(const UIState &s) {
|
||||
QColor bgColor = bg_colors[s.status];
|
||||
Alert alert = Alert::get(*(s.sm), s.scene.started_frame);
|
||||
Alert alert = Alert::get(*(s.sm), s.scene.started_frame, s.scene.display_debug_alert_frame);
|
||||
if (s.sm->updated("controlsState") || !alert.equal({})) {
|
||||
if (alert.type == "controlsUnresponsive") {
|
||||
bgColor = bg_colors[STATUS_ALERT];
|
||||
@@ -77,6 +78,70 @@ void OnroadWindow::updateState(const UIState &s) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void issue_debug_snapshot(SubMaster &sm) {
|
||||
auto longitudinal_plan = sm["longitudinalPlan"].getLongitudinalPlan();
|
||||
auto live_map_data = sm["liveMapData"].getLiveMapData();
|
||||
auto car_state = sm["carState"].getCarState();
|
||||
|
||||
auto t = std::time(nullptr);
|
||||
auto tm = *std::localtime(&t);
|
||||
std::ostringstream param_name_os;
|
||||
param_name_os << std::put_time(&tm, "%Y-%m-%d--%H-%M-%S");
|
||||
|
||||
std::ostringstream os;
|
||||
os.setf(std::ios_base::fixed);
|
||||
os.precision(2);
|
||||
os << "Datetime: " << param_name_os.str() << ", vEgo: " << car_state.getVEgo() * 3.6 << "\n\n";
|
||||
os.precision(6);
|
||||
os << "Location: (" << live_map_data.getLastGpsLatitude() << ", " << live_map_data.getLastGpsLongitude() << ")\n";
|
||||
os.precision(2);
|
||||
os << "Bearing: " << live_map_data.getLastGpsBearingDeg() << "; ";
|
||||
os << "GPSSpeed: " << live_map_data.getLastGpsSpeed() * 3.6 << "\n\n";
|
||||
os.precision(1);
|
||||
os << "Speed Limit: " << live_map_data.getSpeedLimit() * 3.6 << ", ";
|
||||
os << "Valid: " << live_map_data.getSpeedLimitValid() << "\n";
|
||||
os << "Speed Limit Ahead: " << live_map_data.getSpeedLimitAhead() * 3.6 << ", ";
|
||||
os << "Valid: " << live_map_data.getSpeedLimitAheadValid() << ", ";
|
||||
os << "Distance: " << live_map_data.getSpeedLimitAheadDistance() << "\n";
|
||||
os << "Turn Speed Limit: " << live_map_data.getTurnSpeedLimit() * 3.6 << ", ";
|
||||
os << "Valid: " << live_map_data.getTurnSpeedLimitValid() << ", ";
|
||||
os << "End Distance: " << live_map_data.getTurnSpeedLimitEndDistance() << ", ";
|
||||
os << "Sign: " << live_map_data.getTurnSpeedLimitSign() << "\n\n";
|
||||
|
||||
const auto turn_speeds = live_map_data.getTurnSpeedLimitsAhead();
|
||||
os << "Turn Speed Limits Ahead:\n";
|
||||
os << "VALUE\tDIST\tSIGN\n";
|
||||
|
||||
if (turn_speeds.size() == 0) {
|
||||
os << "-\t-\t-" << "\n\n";
|
||||
} else {
|
||||
const auto distances = live_map_data.getTurnSpeedLimitsAheadDistances();
|
||||
const auto signs = live_map_data.getTurnSpeedLimitsAheadSigns();
|
||||
for(int i = 0; i < turn_speeds.size(); i++) {
|
||||
os << turn_speeds[i] * 3.6 << "\t" << distances[i] << "\t" << signs[i] << "\n";
|
||||
}
|
||||
os << "\n";
|
||||
}
|
||||
|
||||
os << "SPEED LIMIT CONTROLLER:\n";
|
||||
os << "sl: " << longitudinal_plan.getSpeedLimit() * 3.6 << ", ";
|
||||
os << "state: " << int(longitudinal_plan.getSpeedLimitControlState()) << ", ";
|
||||
os << "isMap: " << longitudinal_plan.getIsMapSpeedLimit() << "\n\n";
|
||||
|
||||
os << "TURN SPEED CONTROLLER:\n";
|
||||
os << "speed: " << longitudinal_plan.getTurnSpeed() * 3.6 << ", ";
|
||||
os << "state: " << int(longitudinal_plan.getTurnSpeedControlState()) << "\n\n";
|
||||
|
||||
os << "VISION TURN CONTROLLER:\n";
|
||||
os << "speed: " << longitudinal_plan.getVisionTurnSpeed() * 3.6 << ", ";
|
||||
os << "state: " << int(longitudinal_plan.getVisionTurnControllerState());
|
||||
|
||||
Params().put(param_name_os.str().c_str(), os.str().c_str(), os.str().length());
|
||||
uiState()->scene.display_debug_alert_frame = sm.frame;
|
||||
}
|
||||
|
||||
|
||||
void OnroadWindow::mousePressEvent(QMouseEvent* e) {
|
||||
bool sidebarVisible = geometry().x() > 0;
|
||||
bool propagate_event = true;
|
||||
|
||||
+5
-1
@@ -223,6 +223,10 @@ void ui_update_params(UIState *s) {
|
||||
s->scene.onroadScreenOffBrightness = std::atoi(params.get("OnroadScreenOffBrightness").c_str());
|
||||
s->scene.brightness = std::atoi(params.get("BrightnessControl").c_str());
|
||||
s->scene.stand_still_timer = params.getBool("StandStillTimer");
|
||||
s->scene.speed_limit_control_enabled = params.getBool("SpeedLimitControl");
|
||||
s->scene.speed_limit_perc_offset = params.getBool("SpeedLimitPercOffset");
|
||||
s->scene.show_debug_ui = params.getBool("ShowDebugUI");
|
||||
s->scene.debug_snapshot_enabled = params.getBool("EnableDebugSnapshot");
|
||||
|
||||
if (s->scene.onroadScreenOff > 0) {
|
||||
s->scene.osoTimer = s->scene.onroadScreenOff * 60 * UI_FREQ;
|
||||
@@ -277,7 +281,7 @@ UIState::UIState(QObject *parent) : QObject(parent) {
|
||||
"modelV2", "controlsState", "liveCalibration", "radarState", "deviceState", "roadCameraState",
|
||||
"pandaStates", "carParams", "driverMonitoringState", "carState", "liveLocationKalman", "driverStateV2",
|
||||
"wideRoadCameraState", "managerState", "navInstruction", "navRoute", "gnssMeasurements",
|
||||
"carControl", "lateralPlan",
|
||||
"carControl", "lateralPlan", "longitudinalPlan", "liveMapData",
|
||||
});
|
||||
|
||||
Params params;
|
||||
|
||||
+23
-2
@@ -46,9 +46,13 @@ struct Alert {
|
||||
return text1 == a2.text1 && text2 == a2.text2 && type == a2.type && sound == a2.sound;
|
||||
}
|
||||
|
||||
static Alert get(const SubMaster &sm, uint64_t started_frame) {
|
||||
static Alert get(const SubMaster &sm, uint64_t started_frame, uint64_t display_debug_alert_frame = 0) {
|
||||
const cereal::ControlsState::Reader &cs = sm["controlsState"].getControlsState();
|
||||
if (sm.updated("controlsState")) {
|
||||
if (display_debug_alert_frame > 0 && (sm.frame - display_debug_alert_frame) <= 1 * UI_FREQ) {
|
||||
return {"Debug snapshot collected", "",
|
||||
"debugTapDetected", cereal::ControlsState::AlertSize::SMALL,
|
||||
AudibleAlert::WARNING_SOFT};
|
||||
} else if (sm.updated("controlsState")) {
|
||||
return {cs.getAlertText1().cStr(), cs.getAlertText2().cStr(),
|
||||
cs.getAlertType().cStr(), cs.getAlertSize(),
|
||||
cs.getAlertSound()};
|
||||
@@ -97,6 +101,13 @@ const QColor bg_colors [] = {
|
||||
[STATUS_ALERT] = QColor(0xC9, 0x22, 0x31, 0xf1),
|
||||
};
|
||||
|
||||
const QColor tcs_colors [] = {
|
||||
[int(cereal::LongitudinalPlan::VisionTurnControllerState::DISABLED)] = QColor(0x0, 0x0, 0x0, 0xff),
|
||||
[int(cereal::LongitudinalPlan::VisionTurnControllerState::ENTERING)] = QColor(0xC9, 0x22, 0x31, 0xf1),
|
||||
[int(cereal::LongitudinalPlan::VisionTurnControllerState::TURNING)] = QColor(0xDA, 0x6F, 0x25, 0xf1),
|
||||
[int(cereal::LongitudinalPlan::VisionTurnControllerState::LEAVING)] = QColor(0x17, 0x86, 0x44, 0xf1),
|
||||
};
|
||||
|
||||
typedef struct UIScene {
|
||||
bool calibration_valid = false;
|
||||
bool calibration_wide_valid = false;
|
||||
@@ -106,6 +117,16 @@ typedef struct UIScene {
|
||||
cereal::PandaState::PandaType pandaType;
|
||||
cereal::ControlsState::Reader controlsState;
|
||||
|
||||
// Debug UI
|
||||
bool show_debug_ui;
|
||||
bool debug_snapshot_enabled;
|
||||
uint64_t display_debug_alert_frame;
|
||||
|
||||
// Speed limit control
|
||||
bool speed_limit_control_enabled;
|
||||
bool speed_limit_perc_offset;
|
||||
double last_speed_limit_sign_tap;
|
||||
|
||||
// modelV2
|
||||
float lane_line_probs[4];
|
||||
float road_edge_stds[2];
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
pip
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 PhiBo (DinoTools)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: overpy
|
||||
Version: 0.6
|
||||
Summary: Python Wrapper to access the OpenStreepMap Overpass API
|
||||
Home-page: https://github.com/DinoTools/python-overpy
|
||||
Author: PhiBo (DinoTools)
|
||||
License: MIT
|
||||
Project-URL: Documentation, https://python-overpy.readthedocs.io/
|
||||
Project-URL: Source, https://github.com/DinoTools/python-overpy
|
||||
Project-URL: Issue Tracker, https://github.com/DinoTools/python-overpy/issues
|
||||
Keywords: OverPy Overpass OSM OpenStreetMap
|
||||
Classifier: Development Status :: 4 - Beta
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.6
|
||||
Classifier: Programming Language :: Python :: 3.7
|
||||
Classifier: Programming Language :: Python :: 3.8
|
||||
Classifier: Programming Language :: Python :: 3.9
|
||||
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||
Requires-Python: >=3.6
|
||||
Description-Content-Type: text/x-rst
|
||||
License-File: LICENSE
|
||||
|
||||
Python Overpass Wrapper
|
||||
=======================
|
||||
|
||||
A Python Wrapper to access the Overpass API.
|
||||
|
||||
Have a look at the `documentation`_ to find additional information.
|
||||
|
||||
.. image:: https://img.shields.io/pypi/v/overpy.svg
|
||||
:target: https://pypi.python.org/pypi/overpy/
|
||||
:alt: Latest Version
|
||||
|
||||
.. image:: https://img.shields.io/pypi/l/overpy.svg
|
||||
:target: https://pypi.python.org/pypi/overpy/
|
||||
:alt: License
|
||||
|
||||
.. image:: https://github.com/DinoTools/python-overpy/actions/workflows/ci.yml/badge.svg?branch=master
|
||||
:target: https://github.com/DinoTools/python-overpy/actions/workflows/ci.yml?query=branch%3Amaster+
|
||||
|
||||
.. image:: https://coveralls.io/repos/DinoTools/python-overpy/badge.png?branch=master
|
||||
:target: https://coveralls.io/r/DinoTools/python-overpy?branch=master
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
* Query Overpass API
|
||||
* Parse JSON and XML response data
|
||||
* Additional helper functions
|
||||
|
||||
Install
|
||||
-------
|
||||
|
||||
**Requirements:**
|
||||
|
||||
Supported Python versions:
|
||||
|
||||
* Python >= 3.6
|
||||
* PyPy3
|
||||
|
||||
**Install:**
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ pip install overpy
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
Additional examples can be found in the `documentation`_ and in the *examples* directory.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import overpy
|
||||
|
||||
api = overpy.Overpass()
|
||||
|
||||
# fetch all ways and nodes
|
||||
result = api.query("""
|
||||
way(50.746,7.154,50.748,7.157) ["highway"];
|
||||
(._;>;);
|
||||
out body;
|
||||
""")
|
||||
|
||||
for way in result.ways:
|
||||
print("Name: %s" % way.tags.get("name", "n/a"))
|
||||
print(" Highway: %s" % way.tags.get("highway", "n/a"))
|
||||
print(" Nodes:")
|
||||
for node in way.nodes:
|
||||
print(" Lat: %f, Lon: %f" % (node.lat, node.lon))
|
||||
|
||||
|
||||
Helper
|
||||
~~~~~~
|
||||
|
||||
Helper methods are available to provide easy access to often used requests.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import overpy.helper
|
||||
|
||||
# 3600062594 is the OSM id of Chemnitz and is the bounding box for the request
|
||||
street = overpy.helper.get_street(
|
||||
"Straße der Nationen",
|
||||
"3600062594"
|
||||
)
|
||||
|
||||
# this finds an intersection between Straße der Nationen and Carolastraße in Chemnitz
|
||||
intersection = overpy.helper.get_intersection(
|
||||
"Straße der Nationen",
|
||||
"Carolastraße",
|
||||
"3600062594"
|
||||
)
|
||||
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
Published under the MIT (see LICENSE for more information)
|
||||
|
||||
.. _`documentation`: http://python-overpy.readthedocs.org/
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
overpy-0.6.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
overpy-0.6.dist-info/LICENSE,sha256=a10N2C2Las6J2gATvr32uDtYSB2nAd8C5XW0cVlroBI,1084
|
||||
overpy-0.6.dist-info/METADATA,sha256=dwaoOpofBy-H9gNZKwbCB06rvM89FrzN5vt-WcYXzZ0,3458
|
||||
overpy-0.6.dist-info/RECORD,,
|
||||
overpy-0.6.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
overpy-0.6.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
|
||||
overpy-0.6.dist-info/top_level.txt,sha256=FiaqzHIMidUeLrHBRTIvBi8VRsSXXisK7IeJwiZvIZs,7
|
||||
overpy/__about__.py,sha256=jFFo43qrDi1Qt0wCOhmJaoaPjygB_fC83K9K2oaEPR0,453
|
||||
overpy/__init__.py,sha256=RKzCa20y8kI-74WJglKujKOIknCxaEsTB2FcYWHnHek,52663
|
||||
overpy/__pycache__/__about__.cpython-38.pyc,,
|
||||
overpy/__pycache__/__init__.cpython-38.pyc,,
|
||||
overpy/__pycache__/exception.cpython-38.pyc,,
|
||||
overpy/__pycache__/helper.cpython-38.pyc,,
|
||||
overpy/exception.py,sha256=TfOq2SVo_56acHg569Eoze0ETW62c1vgJBPumUMNVys,4753
|
||||
overpy/helper.py,sha256=J1es5zRLrhEQnlONTYfsIhEsxvv0WLtU8D-lzFYtyGg,1724
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.37.1)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
overpy
|
||||
Vendored
+22
@@ -0,0 +1,22 @@
|
||||
__all__ = [
|
||||
"__author__",
|
||||
"__copyright__",
|
||||
"__email__",
|
||||
"__license__",
|
||||
"__summary__",
|
||||
"__title__",
|
||||
"__uri__",
|
||||
"__version__",
|
||||
]
|
||||
|
||||
__title__ = "overpy"
|
||||
__summary__ = "Python Wrapper to access the OpenStreepMap Overpass API"
|
||||
__uri__ = "https://github.com/DinoTools/python-overpy"
|
||||
|
||||
__version__ = "0.6"
|
||||
|
||||
__author__ = "PhiBo (DinoTools)"
|
||||
__email__ = ""
|
||||
|
||||
__license__ = "MIT"
|
||||
__copyright__ = "Copyright 2014-2021 %s" % __author__
|
||||
Vendored
+1614
File diff suppressed because it is too large
Load Diff
Vendored
+166
@@ -0,0 +1,166 @@
|
||||
class OverPyException(Exception):
|
||||
"""OverPy base exception"""
|
||||
pass
|
||||
|
||||
|
||||
class DataIncomplete(OverPyException):
|
||||
"""
|
||||
Raised if the requested data isn't available in the result.
|
||||
Try to improve the query or to resolve the missing data.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
OverPyException.__init__(
|
||||
self,
|
||||
"Data incomplete try to improve the query to resolve the missing data",
|
||||
*args,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
|
||||
class ElementDataWrongType(OverPyException):
|
||||
"""
|
||||
Raised if the provided element does not match the expected type.
|
||||
|
||||
:param type_expected: The expected element type
|
||||
:type type_expected: String
|
||||
:param type_provided: The provided element type
|
||||
:type type_provided: String|None
|
||||
"""
|
||||
def __init__(self, type_expected, type_provided=None):
|
||||
self.type_expected = type_expected
|
||||
self.type_provided = type_provided
|
||||
|
||||
def __str__(self):
|
||||
return "Type expected '{}' but '{}' provided".format(
|
||||
self.type_expected,
|
||||
str(self.type_provided)
|
||||
)
|
||||
|
||||
|
||||
class MaxRetriesReached(OverPyException):
|
||||
"""
|
||||
Raised if max retries reached and the Overpass server didn't respond with a result.
|
||||
"""
|
||||
def __init__(self, retry_count, exceptions):
|
||||
self.exceptions = exceptions
|
||||
self.retry_count = retry_count
|
||||
|
||||
def __str__(self):
|
||||
return "Unable get any result from the Overpass API server after %d retries." % self.retry_count
|
||||
|
||||
|
||||
class OverpassBadRequest(OverPyException):
|
||||
"""
|
||||
Raised if the Overpass API service returns a syntax error.
|
||||
|
||||
:param query: The encoded query how it was send to the server
|
||||
:type query: Bytes
|
||||
:param msgs: List of error messages
|
||||
:type msgs: List
|
||||
"""
|
||||
def __init__(self, query, msgs=None):
|
||||
self.query = query
|
||||
if msgs is None:
|
||||
msgs = []
|
||||
self.msgs = msgs
|
||||
|
||||
def __str__(self):
|
||||
tmp_msgs = []
|
||||
for tmp_msg in self.msgs:
|
||||
if not isinstance(tmp_msg, str):
|
||||
tmp_msg = str(tmp_msg)
|
||||
tmp_msgs.append(tmp_msg)
|
||||
|
||||
return "\n".join(tmp_msgs)
|
||||
|
||||
|
||||
class OverpassError(OverPyException):
|
||||
"""
|
||||
Base exception to report errors if the response returns a remark tag or element.
|
||||
|
||||
.. note::
|
||||
If you are not sure which of the subexceptions you should use, use this one and try to parse the message.
|
||||
|
||||
For more information have a look at https://github.com/DinoTools/python-overpy/issues/62
|
||||
|
||||
:param str msg: The message from the remark tag or element
|
||||
"""
|
||||
def __init__(self, msg=None):
|
||||
#: The message from the remark tag or element
|
||||
self.msg = msg
|
||||
|
||||
def __str__(self):
|
||||
if self.msg is None:
|
||||
return "No error message provided"
|
||||
if not isinstance(self.msg, str):
|
||||
return str(self.msg)
|
||||
return self.msg
|
||||
|
||||
|
||||
class OverpassGatewayTimeout(OverPyException):
|
||||
"""
|
||||
Raised if load of the Overpass API service is too high and it can't handle the request.
|
||||
"""
|
||||
def __init__(self):
|
||||
OverPyException.__init__(self, "Server load too high")
|
||||
|
||||
|
||||
class OverpassRuntimeError(OverpassError):
|
||||
"""
|
||||
Raised if the server returns a remark-tag(xml) or remark element(json) with a message starting with
|
||||
'runtime error:'.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class OverpassRuntimeRemark(OverpassError):
|
||||
"""
|
||||
Raised if the server returns a remark-tag(xml) or remark element(json) with a message starting with
|
||||
'runtime remark:'.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class OverpassTooManyRequests(OverPyException):
|
||||
"""
|
||||
Raised if the Overpass API service returns a 429 status code.
|
||||
"""
|
||||
def __init__(self):
|
||||
OverPyException.__init__(self, "Too many requests")
|
||||
|
||||
|
||||
class OverpassUnknownContentType(OverPyException):
|
||||
"""
|
||||
Raised if the reported content type isn't handled by OverPy.
|
||||
|
||||
:param content_type: The reported content type
|
||||
:type content_type: None or String
|
||||
"""
|
||||
def __init__(self, content_type):
|
||||
self.content_type = content_type
|
||||
|
||||
def __str__(self):
|
||||
if self.content_type is None:
|
||||
return "No content type returned"
|
||||
return "Unknown content type: %s" % self.content_type
|
||||
|
||||
|
||||
class OverpassUnknownError(OverpassError):
|
||||
"""
|
||||
Raised if the server returns a remark-tag(xml) or remark element(json) and we are unable to find any reason.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class OverpassUnknownHTTPStatusCode(OverPyException):
|
||||
"""
|
||||
Raised if the returned HTTP status code isn't handled by OverPy.
|
||||
|
||||
:param code: The HTTP status code
|
||||
:type code: Integer
|
||||
"""
|
||||
def __init__(self, code):
|
||||
self.code = code
|
||||
|
||||
def __str__(self):
|
||||
return "Unknown/Unhandled status code: %d" % self.code
|
||||
Vendored
+64
@@ -0,0 +1,64 @@
|
||||
__author__ = 'mjob'
|
||||
|
||||
import overpy
|
||||
|
||||
|
||||
def get_street(street, areacode, api=None):
|
||||
"""
|
||||
Retrieve streets in a given bounding area
|
||||
|
||||
:param overpy.Overpass api: First street of intersection
|
||||
:param String street: Name of street
|
||||
:param String areacode: The OSM id of the bounding area
|
||||
:return: Parsed result
|
||||
:raises overpy.exception.OverPyException: If something bad happens.
|
||||
"""
|
||||
if api is None:
|
||||
api = overpy.Overpass()
|
||||
|
||||
query = """
|
||||
area(%s)->.location;
|
||||
(
|
||||
way[highway][name="%s"](area.location);
|
||||
- (
|
||||
way[highway=service](area.location);
|
||||
way[highway=track](area.location);
|
||||
);
|
||||
);
|
||||
out body;
|
||||
>;
|
||||
out skel qt;
|
||||
"""
|
||||
|
||||
data = api.query(query % (areacode, street))
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def get_intersection(street1, street2, areacode, api=None):
|
||||
"""
|
||||
Retrieve intersection of two streets in a given bounding area
|
||||
|
||||
:param overpy.Overpass api: First street of intersection
|
||||
:param String street1: Name of first street of intersection
|
||||
:param String street2: Name of second street of intersection
|
||||
:param String areacode: The OSM id of the bounding area
|
||||
:return: List of intersections
|
||||
:raises overpy.exception.OverPyException: If something bad happens.
|
||||
"""
|
||||
if api is None:
|
||||
api = overpy.Overpass()
|
||||
|
||||
query = """
|
||||
area(%s)->.location;
|
||||
(
|
||||
way[highway][name="%s"](area.location); node(w)->.n1;
|
||||
way[highway][name="%s"](area.location); node(w)->.n2;
|
||||
);
|
||||
node.n1.n2;
|
||||
out meta;
|
||||
"""
|
||||
|
||||
data = api.query(query % (areacode, street1, street2))
|
||||
|
||||
return data.get_nodes()
|
||||
+1
@@ -0,0 +1 @@
|
||||
pip
|
||||
+910
@@ -0,0 +1,910 @@
|
||||
Copyright (c) 2001-2002 Enthought, Inc. 2003-2019, SciPy Developers.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
----
|
||||
|
||||
This binary distribution of Scipy also bundles the following software:
|
||||
|
||||
|
||||
Name: OpenBLAS
|
||||
Files: .libs/libopenb*.so
|
||||
Description: bundled as a dynamically linked library
|
||||
Availability: https://github.com/xianyi/OpenBLAS/
|
||||
License: 3-clause BSD
|
||||
Copyright (c) 2011-2014, The OpenBLAS Project
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
3. Neither the name of the OpenBLAS project nor the names of
|
||||
its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written
|
||||
permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
Name: LAPACK
|
||||
Files: .libs/libopenb*.so
|
||||
Description: bundled in OpenBLAS
|
||||
Availability: https://github.com/xianyi/OpenBLAS/
|
||||
License 3-clause BSD
|
||||
Copyright (c) 1992-2013 The University of Tennessee and The University
|
||||
of Tennessee Research Foundation. All rights
|
||||
reserved.
|
||||
Copyright (c) 2000-2013 The University of California Berkeley. All
|
||||
rights reserved.
|
||||
Copyright (c) 2006-2013 The University of Colorado Denver. All rights
|
||||
reserved.
|
||||
|
||||
$COPYRIGHT$
|
||||
|
||||
Additional copyrights may follow
|
||||
|
||||
$HEADER$
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer listed
|
||||
in this license in the documentation and/or other materials
|
||||
provided with the distribution.
|
||||
|
||||
- Neither the name of the copyright holders nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
The copyright holders provide no reassurances that the source code
|
||||
provided does not infringe any patent, copyright, or any other
|
||||
intellectual property rights of third parties. The copyright holders
|
||||
disclaim any liability to any recipient for claims brought against
|
||||
recipient by any third party for infringement of that parties
|
||||
intellectual property rights.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
Name: GCC runtime library
|
||||
Files: .libs/libgfortran*.so
|
||||
Description: dynamically linked to files compiled with gcc
|
||||
Availability: https://gcc.gnu.org/viewcvs/gcc/
|
||||
License: GPLv3 + runtime exception
|
||||
Copyright (C) 2002-2017 Free Software Foundation, Inc.
|
||||
|
||||
Libgfortran is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3, or (at your option)
|
||||
any later version.
|
||||
|
||||
Libgfortran is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
Under Section 7 of GPL version 3, you are granted additional
|
||||
permissions described in the GCC Runtime Library Exception, version
|
||||
3.1, as published by the Free Software Foundation.
|
||||
|
||||
You should have received a copy of the GNU General Public License and
|
||||
a copy of the GCC Runtime Library Exception along with this program;
|
||||
see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
----
|
||||
|
||||
Full text of license texts referred to above follows (that they are
|
||||
listed below does not necessarily imply the conditions apply to the
|
||||
present binary release):
|
||||
|
||||
----
|
||||
|
||||
GCC RUNTIME LIBRARY EXCEPTION
|
||||
|
||||
Version 3.1, 31 March 2009
|
||||
|
||||
Copyright (C) 2009 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this
|
||||
license document, but changing it is not allowed.
|
||||
|
||||
This GCC Runtime Library Exception ("Exception") is an additional
|
||||
permission under section 7 of the GNU General Public License, version
|
||||
3 ("GPLv3"). It applies to a given file (the "Runtime Library") that
|
||||
bears a notice placed by the copyright holder of the file stating that
|
||||
the file is governed by GPLv3 along with this Exception.
|
||||
|
||||
When you use GCC to compile a program, GCC may combine portions of
|
||||
certain GCC header files and runtime libraries with the compiled
|
||||
program. The purpose of this Exception is to allow compilation of
|
||||
non-GPL (including proprietary) programs to use, in this way, the
|
||||
header files and runtime libraries covered by this Exception.
|
||||
|
||||
0. Definitions.
|
||||
|
||||
A file is an "Independent Module" if it either requires the Runtime
|
||||
Library for execution after a Compilation Process, or makes use of an
|
||||
interface provided by the Runtime Library, but is not otherwise based
|
||||
on the Runtime Library.
|
||||
|
||||
"GCC" means a version of the GNU Compiler Collection, with or without
|
||||
modifications, governed by version 3 (or a specified later version) of
|
||||
the GNU General Public License (GPL) with the option of using any
|
||||
subsequent versions published by the FSF.
|
||||
|
||||
"GPL-compatible Software" is software whose conditions of propagation,
|
||||
modification and use would permit combination with GCC in accord with
|
||||
the license of GCC.
|
||||
|
||||
"Target Code" refers to output from any compiler for a real or virtual
|
||||
target processor architecture, in executable form or suitable for
|
||||
input to an assembler, loader, linker and/or execution
|
||||
phase. Notwithstanding that, Target Code does not include data in any
|
||||
format that is used as a compiler intermediate representation, or used
|
||||
for producing a compiler intermediate representation.
|
||||
|
||||
The "Compilation Process" transforms code entirely represented in
|
||||
non-intermediate languages designed for human-written code, and/or in
|
||||
Java Virtual Machine byte code, into Target Code. Thus, for example,
|
||||
use of source code generators and preprocessors need not be considered
|
||||
part of the Compilation Process, since the Compilation Process can be
|
||||
understood as starting with the output of the generators or
|
||||
preprocessors.
|
||||
|
||||
A Compilation Process is "Eligible" if it is done using GCC, alone or
|
||||
with other GPL-compatible software, or if it is done without using any
|
||||
work based on GCC. For example, using non-GPL-compatible Software to
|
||||
optimize any GCC intermediate representations would not qualify as an
|
||||
Eligible Compilation Process.
|
||||
|
||||
1. Grant of Additional Permission.
|
||||
|
||||
You have permission to propagate a work of Target Code formed by
|
||||
combining the Runtime Library with Independent Modules, even if such
|
||||
propagation would otherwise violate the terms of GPLv3, provided that
|
||||
all Target Code was generated by Eligible Compilation Processes. You
|
||||
may then convey such a combination under terms of your choice,
|
||||
consistent with the licensing of the Independent Modules.
|
||||
|
||||
2. No Weakening of GCC Copyleft.
|
||||
|
||||
The availability of this Exception does not imply any general
|
||||
presumption that third-party software is unaffected by the copyleft
|
||||
requirements of the license of GCC.
|
||||
|
||||
----
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
@@ -0,0 +1,248 @@
|
||||
The SciPy repository and source distributions bundle a number of libraries that
|
||||
are compatibly licensed. We list these here.
|
||||
|
||||
Name: Numpydoc
|
||||
Files: doc/sphinxext/numpydoc/*
|
||||
License: 2-clause BSD
|
||||
For details, see doc/sphinxext/LICENSE.txt
|
||||
|
||||
Name: scipy-sphinx-theme
|
||||
Files: doc/scipy-sphinx-theme/*
|
||||
License: 3-clause BSD, PSF and Apache 2.0
|
||||
For details, see doc/sphinxext/LICENSE.txt
|
||||
|
||||
Name: Decorator
|
||||
Files: scipy/_lib/decorator.py
|
||||
License: 2-clause BSD
|
||||
For details, see the header inside scipy/_lib/decorator.py
|
||||
|
||||
Name: ID
|
||||
Files: scipy/linalg/src/id_dist/*
|
||||
License: 3-clause BSD
|
||||
For details, see scipy/linalg/src/id_dist/doc/doc.tex
|
||||
|
||||
Name: L-BFGS-B
|
||||
Files: scipy/optimize/lbfgsb/*
|
||||
License: BSD license
|
||||
For details, see scipy/optimize/lbfgsb/README
|
||||
|
||||
Name: LAPJVsp
|
||||
Files: scipy/sparse/csgraph/_matching.pyx
|
||||
License: 3-clause BSD
|
||||
Copyright 1987-, A. Volgenant/Amsterdam School of Economics,
|
||||
University of Amsterdam
|
||||
|
||||
Distributed under 3-clause BSD license with permission from
|
||||
University of Amsterdam.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
Name: SuperLU
|
||||
Files: scipy/sparse/linalg/dsolve/SuperLU/*
|
||||
License: 3-clause BSD
|
||||
For details, see scipy/sparse/linalg/dsolve/SuperLU/License.txt
|
||||
|
||||
Name: ARPACK
|
||||
Files: scipy/sparse/linalg/eigen/arpack/ARPACK/*
|
||||
License: 3-clause BSD
|
||||
For details, see scipy/sparse/linalg/eigen/arpack/ARPACK/COPYING
|
||||
|
||||
Name: Qhull
|
||||
Files: scipy/spatial/qhull/*
|
||||
License: Qhull license (BSD-like)
|
||||
For details, see scipy/spatial/qhull/COPYING.txt
|
||||
|
||||
Name: Cephes
|
||||
Files: scipy/special/cephes/*
|
||||
License: 3-clause BSD
|
||||
Distributed under 3-clause BSD license with permission from the author,
|
||||
see https://lists.debian.org/debian-legal/2004/12/msg00295.html
|
||||
|
||||
Cephes Math Library Release 2.8: June, 2000
|
||||
Copyright 1984, 1995, 2000 by Stephen L. Moshier
|
||||
|
||||
This software is derived from the Cephes Math Library and is
|
||||
incorporated herein by permission of the author.
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the <organization> nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
Name: Faddeeva
|
||||
Files: scipy/special/Faddeeva.*
|
||||
License: MIT
|
||||
Copyright (c) 2012 Massachusetts Institute of Technology
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
Name: qd
|
||||
Files: scipy/special/cephes/dd_*.[ch]
|
||||
License: modified BSD license ("BSD-LBNL-License.doc")
|
||||
This work was supported by the Director, Office of Science, Division
|
||||
of Mathematical, Information, and Computational Sciences of the
|
||||
U.S. Department of Energy under contract numbers DE-AC03-76SF00098 and
|
||||
DE-AC02-05CH11231.
|
||||
|
||||
Copyright (c) 2003-2009, The Regents of the University of California,
|
||||
through Lawrence Berkeley National Laboratory (subject to receipt of
|
||||
any required approvals from U.S. Dept. of Energy) All rights reserved.
|
||||
|
||||
1. Redistribution and use in source and binary forms, with or
|
||||
without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
(1) Redistributions of source code must retain the copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
(2) Redistributions in binary form must reproduce the copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
(3) Neither the name of the University of California, Lawrence
|
||||
Berkeley National Laboratory, U.S. Dept. of Energy nor the names
|
||||
of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written
|
||||
permission.
|
||||
|
||||
2. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
3. You are under no obligation whatsoever to provide any bug fixes,
|
||||
patches, or upgrades to the features, functionality or performance of
|
||||
the source code ("Enhancements") to anyone; however, if you choose to
|
||||
make your Enhancements available either publicly, or directly to
|
||||
Lawrence Berkeley National Laboratory, without imposing a separate
|
||||
written license agreement for such Enhancements, then you hereby grant
|
||||
the following license: a non-exclusive, royalty-free perpetual license
|
||||
to install, use, modify, prepare derivative works, incorporate into
|
||||
other computer software, distribute, and sublicense such enhancements
|
||||
or derivative works thereof, in binary and source code form.
|
||||
|
||||
Name: pypocketfft
|
||||
Files: scipy/fft/_pocketfft/[pocketfft.h, pypocketfft.cxx]
|
||||
License: 3-Clause BSD
|
||||
For details, see scipy/fft/_pocketfft/LICENSE.md
|
||||
|
||||
Name: uarray
|
||||
Files: scipy/_lib/uarray/*
|
||||
License: 3-Clause BSD
|
||||
For details, see scipy/_lib/uarray/LICENSE
|
||||
|
||||
Name: ampgo
|
||||
Files: benchmarks/benchmarks/go_benchmark_functions/*.py
|
||||
License: MIT
|
||||
Functions for testing global optimizers, forked from the AMPGO project,
|
||||
https://code.google.com/archive/p/ampgo
|
||||
|
||||
Name: pybind11
|
||||
Files: no source files are included, however pybind11 binary artifacts are
|
||||
included with every binary build of SciPy.
|
||||
License:
|
||||
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>, All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
Name: HiGHS
|
||||
Files: scipy/optimize/_highs/*
|
||||
License: MIT
|
||||
For details, see scipy/optimize/_highs/LICENCE
|
||||
|
||||
Name: Boost
|
||||
Files: scipy/_lib/boost/*
|
||||
License: Boost Software License - Version 1.0
|
||||
For details, see scipy/_lib/boost/LICENSE_1_0.txt
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: scipy
|
||||
Version: 1.7.1
|
||||
Summary: SciPy: Scientific Library for Python
|
||||
Home-page: https://www.scipy.org
|
||||
Maintainer: SciPy Developers
|
||||
Maintainer-email: scipy-dev@python.org
|
||||
License: BSD
|
||||
Download-URL: https://github.com/scipy/scipy/releases
|
||||
Project-URL: Bug Tracker, https://github.com/scipy/scipy/issues
|
||||
Project-URL: Documentation, https://docs.scipy.org/doc/scipy/reference/
|
||||
Project-URL: Source Code, https://github.com/scipy/scipy
|
||||
Platform: Windows
|
||||
Platform: Linux
|
||||
Platform: Solaris
|
||||
Platform: Mac OS-X
|
||||
Platform: Unix
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Science/Research
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: BSD License
|
||||
Classifier: Programming Language :: C
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.7
|
||||
Classifier: Programming Language :: Python :: 3.8
|
||||
Classifier: Programming Language :: Python :: 3.9
|
||||
Classifier: Topic :: Software Development :: Libraries
|
||||
Classifier: Topic :: Scientific/Engineering
|
||||
Classifier: Operating System :: Microsoft :: Windows
|
||||
Classifier: Operating System :: POSIX :: Linux
|
||||
Classifier: Operating System :: POSIX
|
||||
Classifier: Operating System :: Unix
|
||||
Classifier: Operating System :: MacOS
|
||||
Requires-Python: >=3.7,<3.10
|
||||
License-File: LICENSE.txt
|
||||
License-File: LICENSES_bundled.txt
|
||||
Requires-Dist: numpy (<1.23.0,>=1.16.5)
|
||||
|
||||
SciPy (pronounced "Sigh Pie") is open-source software for mathematics,
|
||||
science, and engineering. The SciPy library
|
||||
depends on NumPy, which provides convenient and fast N-dimensional
|
||||
array manipulation. The SciPy library is built to work with NumPy
|
||||
arrays, and provides many user-friendly and efficient numerical
|
||||
routines such as routines for numerical integration and optimization.
|
||||
Together, they run on all popular operating systems, are quick to
|
||||
install, and are free of charge. NumPy and SciPy are easy to use,
|
||||
but powerful enough to be depended upon by some of the world's
|
||||
leading scientists and engineers. If you need to manipulate
|
||||
numbers on a computer and display or publish the results,
|
||||
give SciPy a try!
|
||||
|
||||
|
||||
|
||||
+1825
File diff suppressed because it is too large
Load Diff
+6
@@ -0,0 +1,6 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.36.2)
|
||||
Root-Is-Purelib: false
|
||||
Tag: cp38-cp38-manylinux_2_17_aarch64
|
||||
Tag: cp38-cp38-manylinux2014_aarch64
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
scipy
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
+297
@@ -0,0 +1,297 @@
|
||||
.. _hacking:
|
||||
|
||||
==================
|
||||
Ways to Contribute
|
||||
==================
|
||||
|
||||
This document aims to give an overview of the ways to contribute to SciPy. It
|
||||
tries to answer commonly asked questions and provide some insight into how the
|
||||
community process works in practice. Readers who are familiar with the SciPy
|
||||
community and are experienced Python coders may want to jump straight to the
|
||||
:ref:`contributor-toc`.
|
||||
|
||||
There are a lot of ways you can contribute:
|
||||
|
||||
- Contributing new code
|
||||
- Fixing bugs, improving documentation, and other maintenance work
|
||||
- Reviewing open pull requests
|
||||
- Triaging issues
|
||||
- Working on the `scipy.org`_ website
|
||||
- Answering questions and participating on the scipy-dev and scipy-user
|
||||
`mailing lists`_.
|
||||
|
||||
Contributing new code
|
||||
=====================
|
||||
|
||||
If you have been working with the scientific Python toolstack for a while, you
|
||||
probably have some code lying around of which you think "this could be useful
|
||||
for others too". Perhaps it's a good idea then to contribute it to SciPy or
|
||||
another open source project. The first question to ask is then, where does
|
||||
this code belong? That question is hard to answer here, so we start with a
|
||||
more specific one: *what code is suitable for putting into SciPy?*
|
||||
Almost all of the new code added to SciPy has in common that it's potentially
|
||||
useful in multiple scientific domains and it fits in the scope of existing
|
||||
SciPy subpackages (see :ref:`deciding-on-new-features`). In principle new
|
||||
subpackages can be added too, but this is far less common. For code that is
|
||||
specific to a single application, there may be an existing project that can
|
||||
use the code. Some SciKits (`scikit-learn`_, `scikit-image`_, `statsmodels`_,
|
||||
etc.) are good examples here; they have a narrower focus and because of that
|
||||
more domain-specific code than SciPy.
|
||||
|
||||
Now if you have code that you would like to see included in SciPy, how do you
|
||||
go about it? After checking that your code can be distributed in SciPy under a
|
||||
compatible license (see :ref:`license-considerations`), the first step is to
|
||||
discuss on the scipy-dev mailing list. All new features, as well as changes to
|
||||
existing code, are discussed and decided on there. You can, and probably
|
||||
should, already start this discussion before your code is finished. Remember
|
||||
that in order to be added to SciPy your code will need to be reviewed by
|
||||
someone else, so try to find someone willing to review your work while you're
|
||||
at it.
|
||||
|
||||
Assuming the outcome of the discussion on the mailing list is positive and you
|
||||
have a function or piece of code that does what you need it to do, what next?
|
||||
Before code is added to SciPy, it at least has to have good documentation, unit
|
||||
tests, benchmarks, and correct code style.
|
||||
|
||||
1. Unit tests
|
||||
In principle you should aim to create unit tests that exercise all the code
|
||||
that you are adding. This gives some degree of confidence that your code
|
||||
runs correctly, also on Python versions and hardware or OSes that you don't
|
||||
have available yourself. An extensive description of how to write unit
|
||||
tests is given in :doc:`numpy:reference/testing`, and :ref:`runtests`
|
||||
documents how to run them.
|
||||
|
||||
2. Benchmarks
|
||||
Unit tests check for correct functionality; benchmarks measure code
|
||||
performance. Not all existing SciPy code has benchmarks, but it should:
|
||||
as SciPy grows it is increasingly important to monitor execution times in
|
||||
order to catch unexpected regressions. More information about writing
|
||||
and running benchmarks is available in :ref:`benchmarking-with-asv`.
|
||||
|
||||
3. Documentation
|
||||
Clear and complete documentation is essential in order for users to be able
|
||||
to find and understand the code. Documentation for individual functions
|
||||
and classes -- which includes at least a basic description, type and
|
||||
meaning of all parameters and returns values, and usage examples in
|
||||
`doctest`_ format -- is put in docstrings. Those docstrings can be read
|
||||
within the interpreter, and are compiled into a reference guide in html and
|
||||
pdf format. Higher-level documentation for key (areas of) functionality is
|
||||
provided in tutorial format and/or in module docstrings. A guide on how to
|
||||
write documentation is given in :ref:`numpy:howto-document`, and
|
||||
:ref:`rendering-documentation` explains how to preview the documentation
|
||||
as it will appear online.
|
||||
|
||||
4. Code style
|
||||
Uniformity of style in which code is written is important to others trying
|
||||
to understand the code. SciPy follows the standard Python guidelines for
|
||||
code style, `PEP8`_. In order to check that your code conforms to PEP8,
|
||||
you can use the `pep8 package`_ style checker. Most IDEs and text editors
|
||||
have settings that can help you follow PEP8, for example by translating
|
||||
tabs by four spaces. Using `pyflakes`_ to check your code is also a good
|
||||
idea. More information is available in :ref:`pep8-scipy`.
|
||||
|
||||
A :ref:`checklist<pr-checklist>`, including these and other requirements, is
|
||||
available at the end of the example :ref:`development-workflow`.
|
||||
|
||||
Another question you may have is: *where exactly do I put my code*? To answer
|
||||
this, it is useful to understand how the SciPy public API (application
|
||||
programming interface) is defined. For most modules the API is two levels
|
||||
deep, which means your new function should appear as
|
||||
``scipy.subpackage.my_new_func``. ``my_new_func`` can be put in an existing or
|
||||
new file under ``/scipy/<subpackage>/``, its name is added to the ``__all__``
|
||||
list in that file (which lists all public functions in the file), and those
|
||||
public functions are then imported in ``/scipy/<subpackage>/__init__.py``. Any
|
||||
private functions/classes should have a leading underscore (``_``) in their
|
||||
name. A more detailed description of what the public API of SciPy is, is given
|
||||
in :ref:`scipy-api`.
|
||||
|
||||
Once you think your code is ready for inclusion in SciPy, you can send a pull
|
||||
request (PR) on Github. We won't go into the details of how to work with git
|
||||
here, this is described well in :ref:`git-development`
|
||||
and on the `Github help pages`_. When you send the PR for a new
|
||||
feature, be sure to also mention this on the scipy-dev mailing list. This can
|
||||
prompt interested people to help review your PR. Assuming that you already got
|
||||
positive feedback before on the general idea of your code/feature, the purpose
|
||||
of the code review is to ensure that the code is correct, efficient and meets
|
||||
the requirements outlined above. In many cases the code review happens
|
||||
relatively quickly, but it's possible that it stalls. If you have addressed
|
||||
all feedback already given, it's perfectly fine to ask on the mailing list
|
||||
again for review (after a reasonable amount of time, say a couple of weeks, has
|
||||
passed). Once the review is completed, the PR is merged into the "master"
|
||||
branch of SciPy.
|
||||
|
||||
The above describes the requirements and process for adding code to SciPy. It
|
||||
doesn't yet answer the question though how decisions are made exactly. The
|
||||
basic answer is: decisions are made by consensus, by everyone who chooses to
|
||||
participate in the discussion on the mailing list. This includes developers,
|
||||
other users and yourself. Aiming for consensus in the discussion is important
|
||||
-- SciPy is a project by and for the scientific Python community. In those
|
||||
rare cases that agreement cannot be reached, the maintainers of the module
|
||||
in question can decide the issue.
|
||||
|
||||
.. _license-considerations:
|
||||
|
||||
License Considerations
|
||||
----------------------
|
||||
|
||||
*I based my code on existing Matlab/R/... code I found online, is this OK?*
|
||||
|
||||
It depends. SciPy is distributed under a BSD license, so if the code that you
|
||||
based your code on is also BSD licensed or has a BSD-compatible license (e.g.
|
||||
MIT, PSF) then it's OK. Code which is GPL or Apache licensed, has no
|
||||
clear license, requires citation or is free for academic use only can't be
|
||||
included in SciPy. Therefore if you copied existing code with such a license
|
||||
or made a direct translation to Python of it, your code can't be included.
|
||||
If you're unsure, please ask on the scipy-dev `mailing list <mailing lists>`_.
|
||||
|
||||
*Why is SciPy under the BSD license and not, say, the GPL?*
|
||||
|
||||
Like Python, SciPy uses a "permissive" open source license, which allows
|
||||
proprietary re-use. While this allows companies to use and modify the software
|
||||
without giving anything back, it is felt that the larger user base results in
|
||||
more contributions overall, and companies often publish their modifications
|
||||
anyway, without being required to. See John Hunter's `BSD pitch`_.
|
||||
|
||||
For more information about SciPy's license, see :ref:`scipy-licensing`.
|
||||
|
||||
|
||||
Maintaining existing code
|
||||
=========================
|
||||
|
||||
The previous section talked specifically about adding new functionality to
|
||||
SciPy. A large part of that discussion also applies to maintenance of existing
|
||||
code. Maintenance means fixing bugs, improving code quality, documenting
|
||||
existing functionality better, adding missing unit tests, adding performance
|
||||
benchmarks, keeping build scripts up-to-date, etc. The SciPy `issue list`_
|
||||
contains all reported bugs, build/documentation issues, etc. Fixing issues
|
||||
helps improve the overall quality of SciPy, and is also a good way
|
||||
of getting familiar with the project. You may also want to fix a bug because
|
||||
you ran into it and need the function in question to work correctly.
|
||||
|
||||
The discussion on code style and unit testing above applies equally to bug
|
||||
fixes. It is usually best to start by writing a unit test that shows the
|
||||
problem, i.e. it should pass but doesn't. Once you have that, you can fix the
|
||||
code so that the test does pass. That should be enough to send a PR for this
|
||||
issue. Unlike when adding new code, discussing this on the mailing list may
|
||||
not be necessary - if the old behavior of the code is clearly incorrect, no one
|
||||
will object to having it fixed. It may be necessary to add some warning or
|
||||
deprecation message for the changed behavior. This should be part of the
|
||||
review process.
|
||||
|
||||
.. note::
|
||||
|
||||
Pull requests that *only* change code style, e.g. fixing some PEP8 issues in
|
||||
a file, are discouraged. Such PRs are often not worth cluttering the git
|
||||
annotate history, and take reviewer time that may be better spent in other ways.
|
||||
Code style cleanups of code that is touched as part of a functional change
|
||||
are fine however.
|
||||
|
||||
|
||||
Reviewing pull requests
|
||||
=======================
|
||||
|
||||
Reviewing open pull requests (PRs) is very welcome, and a valuable way to help
|
||||
increase the speed at which the project moves forward. If you have specific
|
||||
knowledge/experience in a particular area (say "optimization algorithms" or
|
||||
"special functions") then reviewing PRs in that area is especially valuable -
|
||||
sometimes PRs with technical code have to wait for a long time to get merged
|
||||
due to a shortage of appropriate reviewers.
|
||||
|
||||
We encourage everyone to get involved in the review process; it's also a
|
||||
great way to get familiar with the code base. Reviewers should ask
|
||||
themselves some or all of the following questions:
|
||||
|
||||
- Was this change adequately discussed (relevant for new features and changes
|
||||
in existing behavior)?
|
||||
- Is the feature scientifically sound? Algorithms may be known to work based on
|
||||
literature; otherwise, closer look at correctness is valuable.
|
||||
- Is the intended behavior clear under all conditions (e.g. unexpected inputs
|
||||
like empty arrays or nan/inf values)?
|
||||
- Does the code meet the quality, test and documentation expectation outline
|
||||
under `Contributing new code`_?
|
||||
|
||||
If we do not know you yet, consider introducing yourself.
|
||||
|
||||
|
||||
Other ways to contribute
|
||||
========================
|
||||
|
||||
There are many ways to contribute other than writing code.
|
||||
|
||||
Triaging issues (investigating bug reports for validity and possible actions to
|
||||
take) is also a useful activity. SciPy has many hundreds of open issues;
|
||||
closing invalid ones and correctly labeling valid ones (ideally with some first
|
||||
thoughts in a comment) allows prioritizing maintenance work and finding related
|
||||
issues easily when working on an existing function or subpackage.
|
||||
|
||||
Participating in discussions on the scipy-user and scipy-dev `mailing lists`_ is
|
||||
a contribution in itself. Everyone who writes to those lists with a problem or
|
||||
an idea would like to get responses, and writing such responses makes the
|
||||
project and community function better and appear more welcoming.
|
||||
|
||||
The `scipy.org`_ website contains a lot of information on both SciPy the
|
||||
project and SciPy the community, and it can always use a new pair of hands.
|
||||
The sources for the website live in their own separate repo:
|
||||
https://github.com/scipy/scipy.org
|
||||
|
||||
Getting started
|
||||
===============
|
||||
|
||||
Thanks for your interest in contributing to SciPy! If you're interested in
|
||||
contributing code, we hope you'll continue on to the :ref:`contributor-toc`
|
||||
for details on how to set up your development environment, implement your
|
||||
improvements, and submit your first PR!
|
||||
|
||||
.. _scikit-learn: http://scikit-learn.org
|
||||
|
||||
.. _scikit-image: http://scikit-image.org/
|
||||
|
||||
.. _statsmodels: https://www.statsmodels.org/
|
||||
|
||||
.. _testing guidelines: https://docs.scipy.org/doc/numpy/reference/testing.html
|
||||
|
||||
.. _formatted correctly: https://docs.scipy.org/doc/numpy/dev/gitwash/development_workflow.html#writing-the-commit-message
|
||||
|
||||
.. _bug report: https://scipy.org/bug-report.html
|
||||
|
||||
.. _PEP8: https://www.python.org/dev/peps/pep-0008/
|
||||
|
||||
.. _pep8 package: https://pypi.python.org/pypi/pep8
|
||||
|
||||
.. _pyflakes: https://pypi.python.org/pypi/pyflakes
|
||||
|
||||
.. _Github help pages: https://help.github.com/articles/set-up-git/
|
||||
|
||||
.. _issue list: https://github.com/scipy/scipy/issues
|
||||
|
||||
.. _Github: https://github.com/scipy/scipy
|
||||
|
||||
.. _scipy.org: https://scipy.org/
|
||||
|
||||
.. _scipy.github.com: https://scipy.github.com/
|
||||
|
||||
.. _scipy.org-new: https://github.com/scipy/scipy.org-new
|
||||
|
||||
.. _documentation wiki: https://docs.scipy.org/scipy/Front%20Page/
|
||||
|
||||
.. _SciPy Central: https://web.archive.org/web/20170520065729/http://central.scipy.org/
|
||||
|
||||
.. _doctest: https://pymotw.com/3/doctest/
|
||||
|
||||
.. _virtualenv: https://virtualenv.pypa.io/
|
||||
|
||||
.. _virtualenvwrapper: https://bitbucket.org/dhellmann/virtualenvwrapper/
|
||||
|
||||
.. _bsd pitch: http://nipy.sourceforge.net/nipy/stable/faq/johns_bsd_pitch.html
|
||||
|
||||
.. _Pytest: https://pytest.org/
|
||||
|
||||
.. _mailing lists: https://www.scipy.org/scipylib/mailing-lists.html
|
||||
|
||||
.. _Spyder: https://www.spyder-ide.org/
|
||||
|
||||
.. _Anaconda SciPy Dev Part I (macOS): https://youtu.be/1rPOSNd0ULI
|
||||
|
||||
.. _Anaconda SciPy Dev Part II (macOS): https://youtu.be/Faz29u5xIZc
|
||||
|
||||
.. _SciPy Development Workflow: https://youtu.be/HgU01gJbzMY
|
||||
Vendored
+255
@@ -0,0 +1,255 @@
|
||||
Building and installing SciPy
|
||||
+++++++++++++++++++++++++++++
|
||||
|
||||
See https://www.scipy.org/install.html
|
||||
|
||||
.. Contents::
|
||||
|
||||
|
||||
INTRODUCTION
|
||||
============
|
||||
|
||||
It is *strongly* recommended that you use either a complete scientific Python
|
||||
distribution or binary packages on your platform if they are available, in
|
||||
particular on Windows and Mac OS X. You should not attempt to build SciPy if
|
||||
you are not familiar with compiling software from sources.
|
||||
|
||||
Recommended distributions are:
|
||||
|
||||
- Enthought Canopy (https://www.enthought.com/products/canopy/)
|
||||
- Anaconda (https://www.anaconda.com)
|
||||
- Python(x,y) (https://python-xy.github.io/)
|
||||
- WinPython (https://winpython.github.io/)
|
||||
|
||||
The rest of this install documentation summarizes how to build Scipy. Note
|
||||
that more extensive (and possibly more up-to-date) build instructions are
|
||||
maintained at https://scipy.github.io/devdocs/building/
|
||||
|
||||
|
||||
PREREQUISITES
|
||||
=============
|
||||
|
||||
SciPy requires the following software installed for your platform:
|
||||
|
||||
1) Python__ >= 3.7
|
||||
|
||||
__ https://www.python.org
|
||||
|
||||
2) NumPy__ >= 1.16.5
|
||||
|
||||
__ https://www.numpy.org/
|
||||
|
||||
If building from source, SciPy also requires:
|
||||
|
||||
3) setuptools__
|
||||
|
||||
__ https://github.com/pypa/setuptools
|
||||
|
||||
4) pybind11__ >= 2.4.3
|
||||
|
||||
__ https://github.com/pybind/pybind11
|
||||
|
||||
5) If you want to build the documentation: Sphinx__ >= 2.4.0 and < 3.1.0
|
||||
|
||||
__ http://www.sphinx-doc.org/
|
||||
|
||||
6) If you want to build SciPy master or other unreleased version from source
|
||||
(Cython-generated C sources are included in official releases):
|
||||
Cython__ >= 0.29.18
|
||||
|
||||
__ http://cython.org/
|
||||
|
||||
Windows
|
||||
-------
|
||||
|
||||
Compilers
|
||||
~~~~~~~~~
|
||||
|
||||
There are two ways to build SciPy on Windows:
|
||||
|
||||
1. Use Intel MKL, and Intel compilers or ifort + MSVC. This is what Anaconda
|
||||
and Enthought Canopy use.
|
||||
2. Use MSVC + GFortran with OpenBLAS. This is how the SciPy Windows wheels are
|
||||
built.
|
||||
|
||||
Mac OS X
|
||||
--------
|
||||
|
||||
It is recommended to use GCC or Clang, both work fine. Gcc is available for
|
||||
free when installing Xcode, the developer toolsuite on Mac OS X. You also
|
||||
need a Fortran compiler, which is not included with Xcode: you should use a
|
||||
recent GFortran from an OS X package manager (like Homebrew).
|
||||
|
||||
Please do NOT use GFortran from `hpc.sourceforge.net <http://hpc.sourceforge.net>`_,
|
||||
it is known to generate buggy SciPy binaries.
|
||||
|
||||
You should also use a BLAS/LAPACK library from an OS X package manager.
|
||||
ATLAS, OpenBLAS, and MKL all work.
|
||||
|
||||
As of SciPy version 1.2.0, we do not support compiling against the system
|
||||
Accelerate library for BLAS and LAPACK. It does not support a sufficiently
|
||||
recent LAPACK interface.
|
||||
|
||||
Linux
|
||||
-----
|
||||
|
||||
Most common distributions include all the dependencies. You will need to
|
||||
install a BLAS/LAPACK (all of ATLAS, OpenBLAS, MKL work fine) including
|
||||
development headers, as well as development headers for Python itself. Those
|
||||
are typically packaged as python-dev.
|
||||
|
||||
|
||||
INSTALLING SCIPY
|
||||
================
|
||||
|
||||
For the latest information, see the website:
|
||||
|
||||
https://www.scipy.org
|
||||
|
||||
|
||||
Development version from Git
|
||||
----------------------------
|
||||
Use the command::
|
||||
|
||||
git clone https://github.com/scipy/scipy.git
|
||||
|
||||
cd scipy
|
||||
git clean -xdf
|
||||
python setup.py install --user
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
Type::
|
||||
|
||||
cd scipy/doc
|
||||
make html
|
||||
|
||||
From tarballs
|
||||
-------------
|
||||
Unpack ``SciPy-<version>.tar.gz``, change to the ``SciPy-<version>/``
|
||||
directory, and run::
|
||||
|
||||
pip install . -v --user
|
||||
|
||||
This may take several minutes to half an hour depending on the speed of your
|
||||
computer.
|
||||
|
||||
|
||||
TESTING
|
||||
=======
|
||||
|
||||
To test SciPy after installation (highly recommended), execute in Python::
|
||||
|
||||
>>> import scipy
|
||||
>>> scipy.test()
|
||||
|
||||
To run the full test suite use::
|
||||
|
||||
>>> scipy.test('full')
|
||||
|
||||
If you are upgrading from an older SciPy release, please test your code for any
|
||||
deprecation warnings before and after upgrading to avoid surprises:
|
||||
|
||||
$ python -Wd -c my_code_that_shouldnt_break.py
|
||||
|
||||
Please note that you must have version 1.0 or later of the Pytest test
|
||||
framework installed in order to run the tests. More information about Pytest is
|
||||
available on the website__.
|
||||
|
||||
__ https://pytest.org/
|
||||
|
||||
COMPILER NOTES
|
||||
==============
|
||||
|
||||
You can specify which Fortran compiler to use by using the following
|
||||
install command::
|
||||
|
||||
python setup.py config_fc --fcompiler=<Vendor> install
|
||||
|
||||
To see a valid list of <Vendor> names, run::
|
||||
|
||||
python setup.py config_fc --help-fcompiler
|
||||
|
||||
IMPORTANT: It is highly recommended that all libraries that SciPy uses (e.g.
|
||||
BLAS and ATLAS libraries) are built with the same Fortran compiler. In most
|
||||
cases, if you mix compilers, you will not be able to import SciPy at best, and will have
|
||||
crashes and random results at worst.
|
||||
|
||||
UNINSTALLING
|
||||
============
|
||||
|
||||
When installing with ``python setup.py install`` or a variation on that, you do
|
||||
not get proper uninstall behavior for an older already installed SciPy version.
|
||||
In many cases that's not a problem, but if it turns out to be an issue, you
|
||||
need to manually uninstall it first (remove from e.g. in
|
||||
``/usr/lib/python3.4/site-packages/scipy`` or
|
||||
``$HOME/lib/python3.4/site-packages/scipy``).
|
||||
|
||||
Alternatively, you can use ``pip install . --user`` instead of ``python
|
||||
setup.py install --user`` in order to get reliable uninstall behavior.
|
||||
The downside is that ``pip`` doesn't show you a build log and doesn't support
|
||||
incremental rebuilds (it copies the whole source tree to a tempdir).
|
||||
|
||||
TROUBLESHOOTING
|
||||
===============
|
||||
|
||||
If you experience problems when building/installing/testing SciPy, you
|
||||
can ask help from scipy-user@python.org or scipy-dev@python.org mailing
|
||||
lists. Please include the following information in your message:
|
||||
|
||||
NOTE: You can generate some of the following information (items 1-5,7)
|
||||
in one command::
|
||||
|
||||
python -c 'from numpy.f2py.diagnose import run; run()'
|
||||
|
||||
1) Platform information::
|
||||
|
||||
python -c 'import os, sys; print(os.name, sys.platform)'
|
||||
uname -a
|
||||
OS, its distribution name and version information
|
||||
etc.
|
||||
|
||||
2) Information about C, C++, Fortran compilers/linkers as reported by
|
||||
the compilers when requesting their version information, e.g.,
|
||||
the output of
|
||||
::
|
||||
|
||||
gcc -v
|
||||
g77 --version
|
||||
|
||||
3) Python version::
|
||||
|
||||
python -c 'import sys; print(sys.version)'
|
||||
|
||||
4) NumPy version::
|
||||
|
||||
python -c 'import numpy; print(numpy.__version__)'
|
||||
|
||||
5) ATLAS version, the locations of atlas and lapack libraries, building
|
||||
information if any. If you have ATLAS version 3.3.6 or newer, then
|
||||
give the output of the last command in
|
||||
::
|
||||
|
||||
cd scipy/Lib/linalg
|
||||
python setup_atlas_version.py build_ext --inplace --force
|
||||
python -c 'import atlas_version'
|
||||
|
||||
7) The output of the following commands
|
||||
::
|
||||
|
||||
python INSTALLDIR/numpy/distutils/system_info.py
|
||||
|
||||
where INSTALLDIR is, for example, /usr/lib/python3.4/site-packages/.
|
||||
|
||||
8) Feel free to add any other relevant information.
|
||||
For example, the full output (both stdout and stderr) of the SciPy
|
||||
installation command can be very helpful. Since this output can be
|
||||
rather large, ask before sending it into the mailing list (or
|
||||
better yet, to one of the developers, if asked).
|
||||
|
||||
9) In case of failing to import extension modules, the output of
|
||||
::
|
||||
|
||||
ldd /path/to/ext_module.so
|
||||
|
||||
can be useful.
|
||||
Vendored
+910
@@ -0,0 +1,910 @@
|
||||
Copyright (c) 2001-2002 Enthought, Inc. 2003-2019, SciPy Developers.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
----
|
||||
|
||||
This binary distribution of Scipy also bundles the following software:
|
||||
|
||||
|
||||
Name: OpenBLAS
|
||||
Files: .libs/libopenb*.so
|
||||
Description: bundled as a dynamically linked library
|
||||
Availability: https://github.com/xianyi/OpenBLAS/
|
||||
License: 3-clause BSD
|
||||
Copyright (c) 2011-2014, The OpenBLAS Project
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
3. Neither the name of the OpenBLAS project nor the names of
|
||||
its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written
|
||||
permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
Name: LAPACK
|
||||
Files: .libs/libopenb*.so
|
||||
Description: bundled in OpenBLAS
|
||||
Availability: https://github.com/xianyi/OpenBLAS/
|
||||
License 3-clause BSD
|
||||
Copyright (c) 1992-2013 The University of Tennessee and The University
|
||||
of Tennessee Research Foundation. All rights
|
||||
reserved.
|
||||
Copyright (c) 2000-2013 The University of California Berkeley. All
|
||||
rights reserved.
|
||||
Copyright (c) 2006-2013 The University of Colorado Denver. All rights
|
||||
reserved.
|
||||
|
||||
$COPYRIGHT$
|
||||
|
||||
Additional copyrights may follow
|
||||
|
||||
$HEADER$
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer listed
|
||||
in this license in the documentation and/or other materials
|
||||
provided with the distribution.
|
||||
|
||||
- Neither the name of the copyright holders nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
The copyright holders provide no reassurances that the source code
|
||||
provided does not infringe any patent, copyright, or any other
|
||||
intellectual property rights of third parties. The copyright holders
|
||||
disclaim any liability to any recipient for claims brought against
|
||||
recipient by any third party for infringement of that parties
|
||||
intellectual property rights.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
Name: GCC runtime library
|
||||
Files: .libs/libgfortran*.so
|
||||
Description: dynamically linked to files compiled with gcc
|
||||
Availability: https://gcc.gnu.org/viewcvs/gcc/
|
||||
License: GPLv3 + runtime exception
|
||||
Copyright (C) 2002-2017 Free Software Foundation, Inc.
|
||||
|
||||
Libgfortran is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3, or (at your option)
|
||||
any later version.
|
||||
|
||||
Libgfortran is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
Under Section 7 of GPL version 3, you are granted additional
|
||||
permissions described in the GCC Runtime Library Exception, version
|
||||
3.1, as published by the Free Software Foundation.
|
||||
|
||||
You should have received a copy of the GNU General Public License and
|
||||
a copy of the GCC Runtime Library Exception along with this program;
|
||||
see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
----
|
||||
|
||||
Full text of license texts referred to above follows (that they are
|
||||
listed below does not necessarily imply the conditions apply to the
|
||||
present binary release):
|
||||
|
||||
----
|
||||
|
||||
GCC RUNTIME LIBRARY EXCEPTION
|
||||
|
||||
Version 3.1, 31 March 2009
|
||||
|
||||
Copyright (C) 2009 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this
|
||||
license document, but changing it is not allowed.
|
||||
|
||||
This GCC Runtime Library Exception ("Exception") is an additional
|
||||
permission under section 7 of the GNU General Public License, version
|
||||
3 ("GPLv3"). It applies to a given file (the "Runtime Library") that
|
||||
bears a notice placed by the copyright holder of the file stating that
|
||||
the file is governed by GPLv3 along with this Exception.
|
||||
|
||||
When you use GCC to compile a program, GCC may combine portions of
|
||||
certain GCC header files and runtime libraries with the compiled
|
||||
program. The purpose of this Exception is to allow compilation of
|
||||
non-GPL (including proprietary) programs to use, in this way, the
|
||||
header files and runtime libraries covered by this Exception.
|
||||
|
||||
0. Definitions.
|
||||
|
||||
A file is an "Independent Module" if it either requires the Runtime
|
||||
Library for execution after a Compilation Process, or makes use of an
|
||||
interface provided by the Runtime Library, but is not otherwise based
|
||||
on the Runtime Library.
|
||||
|
||||
"GCC" means a version of the GNU Compiler Collection, with or without
|
||||
modifications, governed by version 3 (or a specified later version) of
|
||||
the GNU General Public License (GPL) with the option of using any
|
||||
subsequent versions published by the FSF.
|
||||
|
||||
"GPL-compatible Software" is software whose conditions of propagation,
|
||||
modification and use would permit combination with GCC in accord with
|
||||
the license of GCC.
|
||||
|
||||
"Target Code" refers to output from any compiler for a real or virtual
|
||||
target processor architecture, in executable form or suitable for
|
||||
input to an assembler, loader, linker and/or execution
|
||||
phase. Notwithstanding that, Target Code does not include data in any
|
||||
format that is used as a compiler intermediate representation, or used
|
||||
for producing a compiler intermediate representation.
|
||||
|
||||
The "Compilation Process" transforms code entirely represented in
|
||||
non-intermediate languages designed for human-written code, and/or in
|
||||
Java Virtual Machine byte code, into Target Code. Thus, for example,
|
||||
use of source code generators and preprocessors need not be considered
|
||||
part of the Compilation Process, since the Compilation Process can be
|
||||
understood as starting with the output of the generators or
|
||||
preprocessors.
|
||||
|
||||
A Compilation Process is "Eligible" if it is done using GCC, alone or
|
||||
with other GPL-compatible software, or if it is done without using any
|
||||
work based on GCC. For example, using non-GPL-compatible Software to
|
||||
optimize any GCC intermediate representations would not qualify as an
|
||||
Eligible Compilation Process.
|
||||
|
||||
1. Grant of Additional Permission.
|
||||
|
||||
You have permission to propagate a work of Target Code formed by
|
||||
combining the Runtime Library with Independent Modules, even if such
|
||||
propagation would otherwise violate the terms of GPLv3, provided that
|
||||
all Target Code was generated by Eligible Compilation Processes. You
|
||||
may then convey such a combination under terms of your choice,
|
||||
consistent with the licensing of the Independent Modules.
|
||||
|
||||
2. No Weakening of GCC Copyleft.
|
||||
|
||||
The availability of this Exception does not imply any general
|
||||
presumption that third-party software is unaffected by the copyleft
|
||||
requirements of the license of GCC.
|
||||
|
||||
----
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
The SciPy repository and source distributions bundle a number of libraries that
|
||||
are compatibly licensed. We list these here.
|
||||
|
||||
Name: Numpydoc
|
||||
Files: doc/sphinxext/numpydoc/*
|
||||
License: 2-clause BSD
|
||||
For details, see doc/sphinxext/LICENSE.txt
|
||||
|
||||
Name: scipy-sphinx-theme
|
||||
Files: doc/scipy-sphinx-theme/*
|
||||
License: 3-clause BSD, PSF and Apache 2.0
|
||||
For details, see doc/sphinxext/LICENSE.txt
|
||||
|
||||
Name: Decorator
|
||||
Files: scipy/_lib/decorator.py
|
||||
License: 2-clause BSD
|
||||
For details, see the header inside scipy/_lib/decorator.py
|
||||
|
||||
Name: ID
|
||||
Files: scipy/linalg/src/id_dist/*
|
||||
License: 3-clause BSD
|
||||
For details, see scipy/linalg/src/id_dist/doc/doc.tex
|
||||
|
||||
Name: L-BFGS-B
|
||||
Files: scipy/optimize/lbfgsb/*
|
||||
License: BSD license
|
||||
For details, see scipy/optimize/lbfgsb/README
|
||||
|
||||
Name: LAPJVsp
|
||||
Files: scipy/sparse/csgraph/_matching.pyx
|
||||
License: 3-clause BSD
|
||||
Copyright 1987-, A. Volgenant/Amsterdam School of Economics,
|
||||
University of Amsterdam
|
||||
|
||||
Distributed under 3-clause BSD license with permission from
|
||||
University of Amsterdam.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
Name: SuperLU
|
||||
Files: scipy/sparse/linalg/dsolve/SuperLU/*
|
||||
License: 3-clause BSD
|
||||
For details, see scipy/sparse/linalg/dsolve/SuperLU/License.txt
|
||||
|
||||
Name: ARPACK
|
||||
Files: scipy/sparse/linalg/eigen/arpack/ARPACK/*
|
||||
License: 3-clause BSD
|
||||
For details, see scipy/sparse/linalg/eigen/arpack/ARPACK/COPYING
|
||||
|
||||
Name: Qhull
|
||||
Files: scipy/spatial/qhull/*
|
||||
License: Qhull license (BSD-like)
|
||||
For details, see scipy/spatial/qhull/COPYING.txt
|
||||
|
||||
Name: Cephes
|
||||
Files: scipy/special/cephes/*
|
||||
License: 3-clause BSD
|
||||
Distributed under 3-clause BSD license with permission from the author,
|
||||
see https://lists.debian.org/debian-legal/2004/12/msg00295.html
|
||||
|
||||
Cephes Math Library Release 2.8: June, 2000
|
||||
Copyright 1984, 1995, 2000 by Stephen L. Moshier
|
||||
|
||||
This software is derived from the Cephes Math Library and is
|
||||
incorporated herein by permission of the author.
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the <organization> nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
Name: Faddeeva
|
||||
Files: scipy/special/Faddeeva.*
|
||||
License: MIT
|
||||
Copyright (c) 2012 Massachusetts Institute of Technology
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
Name: qd
|
||||
Files: scipy/special/cephes/dd_*.[ch]
|
||||
License: modified BSD license ("BSD-LBNL-License.doc")
|
||||
This work was supported by the Director, Office of Science, Division
|
||||
of Mathematical, Information, and Computational Sciences of the
|
||||
U.S. Department of Energy under contract numbers DE-AC03-76SF00098 and
|
||||
DE-AC02-05CH11231.
|
||||
|
||||
Copyright (c) 2003-2009, The Regents of the University of California,
|
||||
through Lawrence Berkeley National Laboratory (subject to receipt of
|
||||
any required approvals from U.S. Dept. of Energy) All rights reserved.
|
||||
|
||||
1. Redistribution and use in source and binary forms, with or
|
||||
without modification, are permitted provided that the following
|
||||
conditions are met:
|
||||
|
||||
(1) Redistributions of source code must retain the copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
(2) Redistributions in binary form must reproduce the copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
(3) Neither the name of the University of California, Lawrence
|
||||
Berkeley National Laboratory, U.S. Dept. of Energy nor the names
|
||||
of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written
|
||||
permission.
|
||||
|
||||
2. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
3. You are under no obligation whatsoever to provide any bug fixes,
|
||||
patches, or upgrades to the features, functionality or performance of
|
||||
the source code ("Enhancements") to anyone; however, if you choose to
|
||||
make your Enhancements available either publicly, or directly to
|
||||
Lawrence Berkeley National Laboratory, without imposing a separate
|
||||
written license agreement for such Enhancements, then you hereby grant
|
||||
the following license: a non-exclusive, royalty-free perpetual license
|
||||
to install, use, modify, prepare derivative works, incorporate into
|
||||
other computer software, distribute, and sublicense such enhancements
|
||||
or derivative works thereof, in binary and source code form.
|
||||
|
||||
Name: pypocketfft
|
||||
Files: scipy/fft/_pocketfft/[pocketfft.h, pypocketfft.cxx]
|
||||
License: 3-Clause BSD
|
||||
For details, see scipy/fft/_pocketfft/LICENSE.md
|
||||
|
||||
Name: uarray
|
||||
Files: scipy/_lib/uarray/*
|
||||
License: 3-Clause BSD
|
||||
For details, see scipy/_lib/uarray/LICENSE
|
||||
|
||||
Name: ampgo
|
||||
Files: benchmarks/benchmarks/go_benchmark_functions/*.py
|
||||
License: MIT
|
||||
Functions for testing global optimizers, forked from the AMPGO project,
|
||||
https://code.google.com/archive/p/ampgo
|
||||
|
||||
Name: pybind11
|
||||
Files: no source files are included, however pybind11 binary artifacts are
|
||||
included with every binary build of SciPy.
|
||||
License:
|
||||
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>, All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
Name: HiGHS
|
||||
Files: scipy/optimize/_highs/*
|
||||
License: MIT
|
||||
For details, see scipy/optimize/_highs/LICENCE
|
||||
|
||||
Name: Boost
|
||||
Files: scipy/_lib/boost/*
|
||||
License: Boost Software License - Version 1.0
|
||||
For details, see scipy/_lib/boost/LICENSE_1_0.txt
|
||||
Vendored
+77
@@ -0,0 +1,77 @@
|
||||
# This file is generated by numpy's setup.py
|
||||
# It contains system_info results at the time of building this package.
|
||||
__all__ = ["get_info","show"]
|
||||
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
extra_dll_dir = os.path.join(os.path.dirname(__file__), '.libs')
|
||||
|
||||
if sys.platform == 'win32' and os.path.isdir(extra_dll_dir):
|
||||
if sys.version_info >= (3, 8):
|
||||
os.add_dll_directory(extra_dll_dir)
|
||||
else:
|
||||
os.environ.setdefault('PATH', '')
|
||||
os.environ['PATH'] += os.pathsep + extra_dll_dir
|
||||
|
||||
lapack_mkl_info={}
|
||||
openblas_lapack_info={'libraries': ['openblas', 'openblas'], 'library_dirs': ['/usr/local/lib'], 'language': 'c', 'define_macros': [('HAVE_CBLAS', None)]}
|
||||
lapack_opt_info={'libraries': ['openblas', 'openblas'], 'library_dirs': ['/usr/local/lib'], 'language': 'c', 'define_macros': [('HAVE_CBLAS', None)]}
|
||||
blas_mkl_info={}
|
||||
blis_info={}
|
||||
openblas_info={'libraries': ['openblas', 'openblas'], 'library_dirs': ['/usr/local/lib'], 'language': 'c', 'define_macros': [('HAVE_CBLAS', None)]}
|
||||
blas_opt_info={'libraries': ['openblas', 'openblas'], 'library_dirs': ['/usr/local/lib'], 'language': 'c', 'define_macros': [('HAVE_CBLAS', None)]}
|
||||
|
||||
def get_info(name):
|
||||
g = globals()
|
||||
return g.get(name, g.get(name + "_info", {}))
|
||||
|
||||
def show():
|
||||
"""
|
||||
Show libraries in the system on which NumPy was built.
|
||||
|
||||
Print information about various resources (libraries, library
|
||||
directories, include directories, etc.) in the system on which
|
||||
NumPy was built.
|
||||
|
||||
See Also
|
||||
--------
|
||||
get_include : Returns the directory containing NumPy C
|
||||
header files.
|
||||
|
||||
Notes
|
||||
-----
|
||||
Classes specifying the information to be printed are defined
|
||||
in the `numpy.distutils.system_info` module.
|
||||
|
||||
Information may include:
|
||||
|
||||
* ``language``: language used to write the libraries (mostly
|
||||
C or f77)
|
||||
* ``libraries``: names of libraries found in the system
|
||||
* ``library_dirs``: directories containing the libraries
|
||||
* ``include_dirs``: directories containing library header files
|
||||
* ``src_dirs``: directories containing library source files
|
||||
* ``define_macros``: preprocessor macros used by
|
||||
``distutils.setup``
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> np.show_config()
|
||||
blas_opt_info:
|
||||
language = c
|
||||
define_macros = [('HAVE_CBLAS', None)]
|
||||
libraries = ['openblas', 'openblas']
|
||||
library_dirs = ['/usr/local/lib']
|
||||
"""
|
||||
for name,info_dict in globals().items():
|
||||
if name[0] == "_" or type(info_dict) is not type({}): continue
|
||||
print(name + ":")
|
||||
if not info_dict:
|
||||
print(" NOT AVAILABLE")
|
||||
for k,v in info_dict.items():
|
||||
v = str(v)
|
||||
if k == "sources" and len(v) > 200:
|
||||
v = v[:60] + " ...\n... " + v[-60:]
|
||||
print(" %s = %s" % (k,v))
|
||||
Vendored
+160
@@ -0,0 +1,160 @@
|
||||
"""
|
||||
SciPy: A scientific computing package for Python
|
||||
================================================
|
||||
|
||||
Documentation is available in the docstrings and
|
||||
online at https://docs.scipy.org.
|
||||
|
||||
Contents
|
||||
--------
|
||||
SciPy imports all the functions from the NumPy namespace, and in
|
||||
addition provides:
|
||||
|
||||
Subpackages
|
||||
-----------
|
||||
Using any of these subpackages requires an explicit import. For example,
|
||||
``import scipy.cluster``.
|
||||
|
||||
::
|
||||
|
||||
cluster --- Vector Quantization / Kmeans
|
||||
fft --- Discrete Fourier transforms
|
||||
fftpack --- Legacy discrete Fourier transforms
|
||||
integrate --- Integration routines
|
||||
interpolate --- Interpolation Tools
|
||||
io --- Data input and output
|
||||
linalg --- Linear algebra routines
|
||||
linalg.blas --- Wrappers to BLAS library
|
||||
linalg.lapack --- Wrappers to LAPACK library
|
||||
misc --- Various utilities that don't have
|
||||
another home.
|
||||
ndimage --- N-D image package
|
||||
odr --- Orthogonal Distance Regression
|
||||
optimize --- Optimization Tools
|
||||
signal --- Signal Processing Tools
|
||||
signal.windows --- Window functions
|
||||
sparse --- Sparse Matrices
|
||||
sparse.linalg --- Sparse Linear Algebra
|
||||
sparse.linalg.dsolve --- Linear Solvers
|
||||
sparse.linalg.dsolve.umfpack --- :Interface to the UMFPACK library:
|
||||
Conjugate Gradient Method (LOBPCG)
|
||||
sparse.linalg.eigen --- Sparse Eigenvalue Solvers
|
||||
sparse.linalg.eigen.lobpcg --- Locally Optimal Block Preconditioned
|
||||
Conjugate Gradient Method (LOBPCG)
|
||||
spatial --- Spatial data structures and algorithms
|
||||
special --- Special functions
|
||||
stats --- Statistical Functions
|
||||
|
||||
Utility tools
|
||||
-------------
|
||||
::
|
||||
|
||||
test --- Run scipy unittests
|
||||
show_config --- Show scipy build configuration
|
||||
show_numpy_config --- Show numpy build configuration
|
||||
__version__ --- SciPy version string
|
||||
__numpy_version__ --- Numpy version string
|
||||
|
||||
"""
|
||||
|
||||
|
||||
def __dir__():
|
||||
return ['test']
|
||||
|
||||
|
||||
__all__ = __dir__()
|
||||
|
||||
from numpy import show_config as show_numpy_config
|
||||
if show_numpy_config is None:
|
||||
raise ImportError(
|
||||
"Cannot import SciPy when running from NumPy source directory.")
|
||||
from numpy import __version__ as __numpy_version__
|
||||
|
||||
# Import numpy symbols to scipy name space (DEPRECATED)
|
||||
from ._lib.deprecation import _deprecated
|
||||
import numpy as _num
|
||||
linalg = None
|
||||
_msg = ('scipy.{0} is deprecated and will be removed in SciPy 2.0.0, '
|
||||
'use numpy.{0} instead')
|
||||
# deprecate callable objects, skipping classes
|
||||
for _key in _num.__all__:
|
||||
_fun = getattr(_num, _key)
|
||||
if callable(_fun) and not isinstance(_fun, type):
|
||||
_fun = _deprecated(_msg.format(_key))(_fun)
|
||||
globals()[_key] = _fun
|
||||
from numpy.random import rand, randn
|
||||
_msg = ('scipy.{0} is deprecated and will be removed in SciPy 2.0.0, '
|
||||
'use numpy.random.{0} instead')
|
||||
rand = _deprecated(_msg.format('rand'))(rand)
|
||||
randn = _deprecated(_msg.format('randn'))(randn)
|
||||
# fft is especially problematic, so was removed in SciPy 1.6.0
|
||||
from numpy.fft import ifft
|
||||
ifft = _deprecated('scipy.ifft is deprecated and will be removed in SciPy '
|
||||
'2.0.0, use scipy.fft.ifft instead')(ifft)
|
||||
import numpy.lib.scimath as _sci
|
||||
_msg = ('scipy.{0} is deprecated and will be removed in SciPy 2.0.0, '
|
||||
'use numpy.lib.scimath.{0} instead')
|
||||
for _key in _sci.__all__:
|
||||
_fun = getattr(_sci, _key)
|
||||
if callable(_fun):
|
||||
_fun = _deprecated(_msg.format(_key))(_fun)
|
||||
globals()[_key] = _fun
|
||||
|
||||
__all__ += _num.__all__
|
||||
__all__ += ['randn', 'rand', 'ifft']
|
||||
|
||||
del _num
|
||||
# Remove the linalg imported from NumPy so that the scipy.linalg package can be
|
||||
# imported.
|
||||
del linalg
|
||||
__all__.remove('linalg')
|
||||
|
||||
# We first need to detect if we're being called as part of the SciPy
|
||||
# setup procedure itself in a reliable manner.
|
||||
try:
|
||||
__SCIPY_SETUP__
|
||||
except NameError:
|
||||
__SCIPY_SETUP__ = False
|
||||
|
||||
|
||||
if __SCIPY_SETUP__:
|
||||
import sys as _sys
|
||||
_sys.stderr.write('Running from SciPy source directory.\n')
|
||||
del _sys
|
||||
else:
|
||||
try:
|
||||
from scipy.__config__ import show as show_config
|
||||
except ImportError as e:
|
||||
msg = """Error importing SciPy: you cannot import SciPy while
|
||||
being in scipy source directory; please exit the SciPy source
|
||||
tree first and relaunch your Python interpreter."""
|
||||
raise ImportError(msg) from e
|
||||
|
||||
from scipy.version import version as __version__
|
||||
|
||||
# Allow distributors to run custom init code
|
||||
from . import _distributor_init
|
||||
|
||||
from scipy._lib import _pep440
|
||||
# In maintenance branch, change to np_maxversion N+3 if numpy is at N
|
||||
# See setup.py for more details
|
||||
np_minversion = '1.16.5'
|
||||
np_maxversion = '1.23.0'
|
||||
if (_pep440.parse(__numpy_version__) < _pep440.Version(np_minversion) or
|
||||
_pep440.parse(__numpy_version__) >= _pep440.Version(np_maxversion)):
|
||||
import warnings
|
||||
warnings.warn(f"A NumPy version >={np_minversion} and <{np_maxversion}"
|
||||
f" is required for this version of SciPy (detected "
|
||||
f"version {__numpy_version__}",
|
||||
UserWarning)
|
||||
|
||||
del _pep440
|
||||
|
||||
from scipy._lib._ccallback import LowLevelCallable
|
||||
|
||||
from scipy._lib._testutils import PytestTester
|
||||
test = PytestTester(__name__)
|
||||
del PytestTester
|
||||
|
||||
# This makes "from scipy import fft" return scipy.fft, not np.fft
|
||||
del fft
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import os
|
||||
|
||||
import numpy as np
|
||||
from ._fortran import *
|
||||
from .system_info import combine_dict
|
||||
|
||||
|
||||
# Don't use the deprecated NumPy C API. Define this to a fixed version instead of
|
||||
# NPY_API_VERSION in order not to break compilation for released SciPy versions
|
||||
# when NumPy introduces a new deprecation. Use in setup.py::
|
||||
#
|
||||
# config.add_extension('_name', sources=['source_fname'], **numpy_nodepr_api)
|
||||
#
|
||||
numpy_nodepr_api = dict(define_macros=[("NPY_NO_DEPRECATED_API",
|
||||
"NPY_1_9_API_VERSION")])
|
||||
|
||||
|
||||
def uses_blas64():
|
||||
return (os.environ.get("NPY_USE_BLAS_ILP64", "0") != "0")
|
||||
|
||||
def import_file(folder, module_name):
|
||||
"""Import a file directly, avoiding importing scipy"""
|
||||
import importlib
|
||||
import pathlib
|
||||
|
||||
fname = pathlib.Path(folder) / f'{module_name}.py'
|
||||
spec = importlib.util.spec_from_file_location(module_name, str(fname))
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
from scipy._lib._testutils import PytestTester
|
||||
test = PytestTester(__name__)
|
||||
del PytestTester
|
||||
+444
@@ -0,0 +1,444 @@
|
||||
import re
|
||||
import os
|
||||
import sys
|
||||
from distutils.util import get_platform
|
||||
|
||||
import numpy as np
|
||||
|
||||
from .system_info import combine_dict
|
||||
|
||||
|
||||
__all__ = ['needs_g77_abi_wrapper', 'get_g77_abi_wrappers',
|
||||
'gfortran_legacy_flag_hook', 'blas_ilp64_pre_build_hook',
|
||||
'get_f2py_int64_options', 'generic_pre_build_hook',
|
||||
'write_file_content', 'ilp64_pre_build_hook']
|
||||
|
||||
|
||||
def get_fcompiler_ilp64_flags():
|
||||
"""
|
||||
Dictionary of compiler flags for switching to 8-byte default integer
|
||||
size.
|
||||
"""
|
||||
flags = {
|
||||
'absoft': ['-i8'], # Absoft
|
||||
'compaq': ['-i8'], # Compaq Fortran
|
||||
'compaqv': ['/integer_size:64'], # Compaq Visual Fortran
|
||||
'g95': ['-i8'], # g95
|
||||
'gnu95': ['-fdefault-integer-8'], # GNU gfortran
|
||||
'ibm': ['-qintsize=8'], # IBM XL Fortran
|
||||
'intel': ['-i8'], # Intel Fortran Compiler for 32-bit
|
||||
'intele': ['-i8'], # Intel Fortran Compiler for Itanium
|
||||
'intelem': ['-i8'], # Intel Fortran Compiler for 64-bit
|
||||
'intelv': ['-i8'], # Intel Visual Fortran Compiler for 32-bit
|
||||
'intelev': ['-i8'], # Intel Visual Fortran Compiler for Itanium
|
||||
'intelvem': ['-i8'], # Intel Visual Fortran Compiler for 64-bit
|
||||
'lahey': ['--long'], # Lahey/Fujitsu Fortran 95 Compiler
|
||||
'mips': ['-i8'], # MIPSpro Fortran Compiler
|
||||
'nag': ['-i8'], # NAGWare Fortran 95 compiler
|
||||
'nagfor': ['-i8'], # NAG Fortran compiler
|
||||
'pathf95': ['-i8'], # PathScale Fortran compiler
|
||||
'pg': ['-i8'], # Portland Group Fortran Compiler
|
||||
'flang': ['-i8'], # Portland Group Fortran LLVM Compiler
|
||||
'sun': ['-i8'], # Sun or Forte Fortran 95 Compiler
|
||||
}
|
||||
# No support for this:
|
||||
# - g77
|
||||
# - hpux
|
||||
# Unknown:
|
||||
# - vast
|
||||
return flags
|
||||
|
||||
|
||||
def get_fcompiler_macro_include_flags(path):
|
||||
"""
|
||||
Dictionary of compiler flags for cpp-style preprocessing, with
|
||||
an #include search path, and safety options necessary for macro
|
||||
expansion.
|
||||
"""
|
||||
intel_opts = ['-fpp', '-I' + path]
|
||||
nag_opts = ['-fpp', '-I' + path]
|
||||
|
||||
flags = {
|
||||
'absoft': ['-W132', '-cpp', '-I' + path],
|
||||
'gnu95': ['-cpp', '-ffree-line-length-none',
|
||||
'-ffixed-line-length-none', '-I' + path],
|
||||
'intel': intel_opts,
|
||||
'intele': intel_opts,
|
||||
'intelem': intel_opts,
|
||||
'intelv': intel_opts,
|
||||
'intelev': intel_opts,
|
||||
'intelvem': intel_opts,
|
||||
'lahey': ['-Cpp', '--wide', '-I' + path],
|
||||
'mips': ['-col120', '-I' + path],
|
||||
'nag': nag_opts,
|
||||
'nagfor': nag_opts,
|
||||
'pathf95': ['-ftpp', '-macro-expand', '-I' + path],
|
||||
'flang': ['-Mpreprocess', '-Mextend', '-I' + path],
|
||||
'sun': ['-fpp', '-I' + path],
|
||||
}
|
||||
# No support for this:
|
||||
# - ibm (line length option turns on fixed format)
|
||||
# TODO:
|
||||
# - pg
|
||||
return flags
|
||||
|
||||
|
||||
def uses_mkl(info):
|
||||
r_mkl = re.compile("mkl")
|
||||
libraries = info.get('libraries', '')
|
||||
for library in libraries:
|
||||
if r_mkl.search(library):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def needs_g77_abi_wrapper(info):
|
||||
"""Returns True if g77 ABI wrapper must be used."""
|
||||
try:
|
||||
needs_wrapper = int(os.environ["SCIPY_USE_G77_ABI_WRAPPER"]) != 0
|
||||
except KeyError:
|
||||
needs_wrapper = uses_mkl(info)
|
||||
return needs_wrapper
|
||||
|
||||
|
||||
def get_g77_abi_wrappers(info):
|
||||
"""
|
||||
Returns file names of source files containing Fortran ABI wrapper
|
||||
routines.
|
||||
"""
|
||||
wrapper_sources = []
|
||||
|
||||
path = os.path.abspath(os.path.dirname(__file__))
|
||||
if needs_g77_abi_wrapper(info):
|
||||
wrapper_sources += [
|
||||
os.path.join(path, 'src', 'wrap_g77_abi_f.f'),
|
||||
os.path.join(path, 'src', 'wrap_g77_abi_c.c'),
|
||||
]
|
||||
else:
|
||||
wrapper_sources += [
|
||||
os.path.join(path, 'src', 'wrap_dummy_g77_abi.f'),
|
||||
]
|
||||
return wrapper_sources
|
||||
|
||||
|
||||
def gfortran_legacy_flag_hook(cmd, ext):
|
||||
"""
|
||||
Pre-build hook to add dd gfortran legacy flag -fallow-argument-mismatch
|
||||
"""
|
||||
from .compiler_helper import try_add_flag
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
if isinstance(ext, dict):
|
||||
# build_clib
|
||||
compilers = ((cmd._f_compiler, ext.setdefault('extra_f77_compile_args', [])),
|
||||
(cmd._f_compiler, ext.setdefault('extra_f90_compile_args', [])))
|
||||
else:
|
||||
# build_ext
|
||||
compilers = ((cmd._f77_compiler, ext.extra_f77_compile_args),
|
||||
(cmd._f90_compiler, ext.extra_f90_compile_args))
|
||||
|
||||
for compiler, args in compilers:
|
||||
if compiler is None:
|
||||
continue
|
||||
|
||||
if compiler.compiler_type == "gnu95" and compiler.version >= LooseVersion("10"):
|
||||
try_add_flag(args, compiler, "-fallow-argument-mismatch")
|
||||
|
||||
|
||||
def _get_build_src_dir():
|
||||
plat_specifier = ".{}-{}.{}".format(get_platform(), *sys.version_info[:2])
|
||||
return os.path.join('build', 'src' + plat_specifier)
|
||||
|
||||
|
||||
def get_f2py_int64_options():
|
||||
if np.dtype('i') == np.dtype(np.int64):
|
||||
int64_name = 'int'
|
||||
elif np.dtype('l') == np.dtype(np.int64):
|
||||
int64_name = 'long'
|
||||
elif np.dtype('q') == np.dtype(np.int64):
|
||||
int64_name = 'long_long'
|
||||
else:
|
||||
raise RuntimeError("No 64-bit integer type available in f2py!")
|
||||
|
||||
f2cmap_fn = os.path.join(_get_build_src_dir(), 'int64.f2cmap')
|
||||
text = "{'integer': {'': '%s'}, 'logical': {'': '%s'}}\n" % (
|
||||
int64_name, int64_name)
|
||||
|
||||
write_file_content(f2cmap_fn, text)
|
||||
|
||||
return ['--f2cmap', f2cmap_fn]
|
||||
|
||||
|
||||
def ilp64_pre_build_hook(cmd, ext):
|
||||
"""
|
||||
Pre-build hook for adding Fortran compiler flags that change
|
||||
default integer size to 64-bit.
|
||||
"""
|
||||
fcompiler_flags = get_fcompiler_ilp64_flags()
|
||||
return generic_pre_build_hook(cmd, ext, fcompiler_flags=fcompiler_flags)
|
||||
|
||||
|
||||
def blas_ilp64_pre_build_hook(blas_info):
|
||||
"""
|
||||
Pre-build hook for adding ILP64 BLAS compilation flags, and
|
||||
mangling Fortran source files to rename BLAS/LAPACK symbols when
|
||||
there are symbol suffixes.
|
||||
|
||||
Examples
|
||||
--------
|
||||
::
|
||||
|
||||
from scipy._build_utils import blas_ilp64_pre_build_hook
|
||||
ext = config.add_extension(...)
|
||||
ext._pre_build_hook = blas_ilp64_pre_build_hook(blas_info)
|
||||
|
||||
"""
|
||||
return lambda cmd, ext: _blas_ilp64_pre_build_hook(cmd, ext, blas_info)
|
||||
|
||||
|
||||
def _blas_ilp64_pre_build_hook(cmd, ext, blas_info):
|
||||
# Determine BLAS symbol suffix/prefix, if any
|
||||
macros = dict(blas_info.get('define_macros', []))
|
||||
prefix = macros.get('BLAS_SYMBOL_PREFIX', '')
|
||||
suffix = macros.get('BLAS_SYMBOL_SUFFIX', '')
|
||||
|
||||
if suffix:
|
||||
if not suffix.endswith('_'):
|
||||
# Symbol suffix has to end with '_' to be Fortran-compatible
|
||||
raise RuntimeError("BLAS/LAPACK has incompatible symbol suffix: "
|
||||
"{!r}".format(suffix))
|
||||
|
||||
suffix = suffix[:-1]
|
||||
|
||||
# When symbol prefix/suffix is present, we have to patch sources
|
||||
if prefix or suffix:
|
||||
include_dir = os.path.join(_get_build_src_dir(), 'blas64-include')
|
||||
|
||||
fcompiler_flags = combine_dict(get_fcompiler_ilp64_flags(),
|
||||
get_fcompiler_macro_include_flags(include_dir))
|
||||
|
||||
# Add the include dir for C code
|
||||
if isinstance(ext, dict):
|
||||
ext.setdefault('include_dirs', [])
|
||||
ext['include_dirs'].append(include_dir)
|
||||
else:
|
||||
ext.include_dirs.append(include_dir)
|
||||
|
||||
# Create name-mapping include files
|
||||
include_name_f = 'blas64-prefix-defines.inc'
|
||||
include_name_c = 'blas64-prefix-defines.h'
|
||||
include_fn_f = os.path.join(include_dir, include_name_f)
|
||||
include_fn_c = os.path.join(include_dir, include_name_c)
|
||||
|
||||
text = ""
|
||||
for symbol in get_blas_lapack_symbols():
|
||||
text += '#define {} {}{}_{}\n'.format(symbol, prefix, symbol, suffix)
|
||||
text += '#define {} {}{}_{}\n'.format(symbol.upper(), prefix, symbol, suffix)
|
||||
|
||||
# Code generation may give source codes with mixed-case names
|
||||
for j in (1, 2):
|
||||
s = symbol[:j].lower() + symbol[j:].upper()
|
||||
text += '#define {} {}{}_{}\n'.format(s, prefix, symbol, suffix)
|
||||
s = symbol[:j].upper() + symbol[j:].lower()
|
||||
text += '#define {} {}{}_{}\n'.format(s, prefix, symbol, suffix)
|
||||
|
||||
write_file_content(include_fn_f, text)
|
||||
|
||||
ctext = re.sub(r'^#define (.*) (.*)$', r'#define \1_ \2_', text, flags=re.M)
|
||||
write_file_content(include_fn_c, text + "\n" + ctext)
|
||||
|
||||
# Patch sources to include it
|
||||
def patch_source(filename, old_text):
|
||||
text = '#include "{}"\n'.format(include_name_f)
|
||||
text += old_text
|
||||
return text
|
||||
else:
|
||||
fcompiler_flags = get_fcompiler_ilp64_flags()
|
||||
patch_source = None
|
||||
|
||||
return generic_pre_build_hook(cmd, ext,
|
||||
fcompiler_flags=fcompiler_flags,
|
||||
patch_source_func=patch_source,
|
||||
source_fnpart="_blas64")
|
||||
|
||||
|
||||
def generic_pre_build_hook(cmd, ext, fcompiler_flags, patch_source_func=None,
|
||||
source_fnpart=None):
|
||||
"""
|
||||
Pre-build hook for adding compiler flags and patching sources.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
cmd : distutils.core.Command
|
||||
Hook input. Current distutils command (build_clib or build_ext).
|
||||
ext : dict or numpy.distutils.extension.Extension
|
||||
Hook input. Configuration information for library (dict, build_clib)
|
||||
or extension (numpy.distutils.extension.Extension, build_ext).
|
||||
fcompiler_flags : dict
|
||||
Dictionary of ``{'compiler_name': ['-flag1', ...]}`` containing
|
||||
compiler flags to set.
|
||||
patch_source_func : callable, optional
|
||||
Function patching sources, see `_generic_patch_sources` below.
|
||||
source_fnpart : str, optional
|
||||
String to append to the modified file basename before extension.
|
||||
|
||||
"""
|
||||
is_clib = isinstance(ext, dict)
|
||||
|
||||
if is_clib:
|
||||
build_info = ext
|
||||
del ext
|
||||
|
||||
# build_clib doesn't have separate f77/f90 compilers
|
||||
f77 = cmd._f_compiler
|
||||
f90 = cmd._f_compiler
|
||||
else:
|
||||
f77 = cmd._f77_compiler
|
||||
f90 = cmd._f90_compiler
|
||||
|
||||
# Add compiler flags
|
||||
if is_clib:
|
||||
f77_args = build_info.setdefault('extra_f77_compile_args', [])
|
||||
f90_args = build_info.setdefault('extra_f90_compile_args', [])
|
||||
compilers = [(f77, f77_args), (f90, f90_args)]
|
||||
else:
|
||||
compilers = [(f77, ext.extra_f77_compile_args),
|
||||
(f90, ext.extra_f90_compile_args)]
|
||||
|
||||
for compiler, args in compilers:
|
||||
if compiler is None:
|
||||
continue
|
||||
|
||||
try:
|
||||
flags = fcompiler_flags[compiler.compiler_type]
|
||||
except KeyError as e:
|
||||
raise RuntimeError(
|
||||
"Compiler {!r} is not supported in this "
|
||||
"configuration.".format(compiler.compiler_type)
|
||||
) from e
|
||||
|
||||
args.extend(flag for flag in flags if flag not in args)
|
||||
|
||||
# Mangle sources
|
||||
if patch_source_func is not None:
|
||||
if is_clib:
|
||||
build_info.setdefault('depends', []).extend(build_info['sources'])
|
||||
new_sources = _generic_patch_sources(build_info['sources'], patch_source_func,
|
||||
source_fnpart)
|
||||
build_info['sources'][:] = new_sources
|
||||
else:
|
||||
ext.depends.extend(ext.sources)
|
||||
new_sources = _generic_patch_sources(ext.sources, patch_source_func,
|
||||
source_fnpart)
|
||||
ext.sources[:] = new_sources
|
||||
|
||||
|
||||
def _generic_patch_sources(filenames, patch_source_func, source_fnpart, root_dir=None):
|
||||
"""
|
||||
Patch Fortran sources, creating new source files.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
filenames : list
|
||||
List of Fortran source files to patch.
|
||||
Files not ending in ``.f`` or ``.f90`` are left unaltered.
|
||||
patch_source_func : callable(filename, old_contents) -> new_contents
|
||||
Function to apply to file contents, returning new file contents
|
||||
as a string.
|
||||
source_fnpart : str
|
||||
String to append to the modified file basename before extension.
|
||||
root_dir : str, optional
|
||||
Source root directory. Default: cwd
|
||||
|
||||
Returns
|
||||
-------
|
||||
new_filenames : list
|
||||
List of names of the newly created patched sources.
|
||||
|
||||
"""
|
||||
new_filenames = []
|
||||
|
||||
if root_dir is None:
|
||||
root_dir = os.getcwd()
|
||||
|
||||
root_dir = os.path.abspath(root_dir)
|
||||
src_dir = os.path.join(root_dir, _get_build_src_dir())
|
||||
|
||||
for src in filenames:
|
||||
base, ext = os.path.splitext(os.path.basename(src))
|
||||
|
||||
if ext not in ('.f', '.f90'):
|
||||
new_filenames.append(src)
|
||||
continue
|
||||
|
||||
with open(src, 'r') as fsrc:
|
||||
text = patch_source_func(src, fsrc.read())
|
||||
|
||||
# Generate useful target directory name under src_dir
|
||||
src_path = os.path.abspath(os.path.dirname(src))
|
||||
|
||||
for basedir in [src_dir, root_dir]:
|
||||
if os.path.commonpath([src_path, basedir]) == basedir:
|
||||
rel_path = os.path.relpath(src_path, basedir)
|
||||
break
|
||||
else:
|
||||
raise ValueError(f"{src!r} not under {root_dir!r}")
|
||||
|
||||
dst = os.path.join(src_dir, rel_path, base + source_fnpart + ext)
|
||||
write_file_content(dst, text)
|
||||
|
||||
new_filenames.append(dst)
|
||||
|
||||
return new_filenames
|
||||
|
||||
|
||||
def write_file_content(filename, content):
|
||||
"""
|
||||
Write content to file, but only if it differs from the current one.
|
||||
"""
|
||||
if os.path.isfile(filename):
|
||||
with open(filename, 'r') as f:
|
||||
old_content = f.read()
|
||||
|
||||
if old_content == content:
|
||||
return
|
||||
|
||||
dirname = os.path.dirname(filename)
|
||||
if not os.path.isdir(dirname):
|
||||
os.makedirs(dirname)
|
||||
|
||||
with open(filename, 'w') as f:
|
||||
f.write(content)
|
||||
|
||||
|
||||
def get_blas_lapack_symbols():
|
||||
cached = getattr(get_blas_lapack_symbols, 'cached', None)
|
||||
if cached is not None:
|
||||
return cached
|
||||
|
||||
# Obtain symbol list from Cython Blas/Lapack interface
|
||||
srcdir = os.path.join(os.path.dirname(__file__), os.pardir, 'linalg')
|
||||
|
||||
symbols = []
|
||||
|
||||
# Get symbols from the generated files
|
||||
for fn in ['cython_blas_signatures.txt', 'cython_lapack_signatures.txt']:
|
||||
with open(os.path.join(srcdir, fn), 'r') as f:
|
||||
for line in f:
|
||||
m = re.match(r"^\s*[a-z]+\s+([a-z0-9]+)\(", line)
|
||||
if m:
|
||||
symbols.append(m.group(1))
|
||||
|
||||
# Get the rest from the generator script
|
||||
# (we cannot import it directly here, so use exec)
|
||||
sig_fn = os.path.join(srcdir, '_cython_signature_generator.py')
|
||||
with open(sig_fn, 'r') as f:
|
||||
code = f.read()
|
||||
ns = {'__name__': '<module>'}
|
||||
exec(code, ns)
|
||||
symbols.extend(ns['blas_exclusions'])
|
||||
symbols.extend(ns['lapack_exclusions'])
|
||||
|
||||
get_blas_lapack_symbols.cached = tuple(sorted(set(symbols)))
|
||||
return get_blas_lapack_symbols.cached
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
"""
|
||||
Helpers for detection of compiler features
|
||||
"""
|
||||
import tempfile
|
||||
import os
|
||||
import sys
|
||||
from numpy.distutils.system_info import dict_append
|
||||
|
||||
def try_compile(compiler, code=None, flags=[], ext=None):
|
||||
"""Returns True if the compiler is able to compile the given code"""
|
||||
from distutils.errors import CompileError
|
||||
from numpy.distutils.fcompiler import FCompiler
|
||||
|
||||
if code is None:
|
||||
if isinstance(compiler, FCompiler):
|
||||
code = " program main\n return\n end"
|
||||
else:
|
||||
code = 'int main (int argc, char **argv) { return 0; }'
|
||||
|
||||
ext = ext or compiler.src_extensions[0]
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
fname = os.path.join(temp_dir, 'main'+ext)
|
||||
with open(fname, 'w') as f:
|
||||
f.write(code)
|
||||
|
||||
try:
|
||||
compiler.compile([fname], output_dir=temp_dir, extra_postargs=flags)
|
||||
except CompileError:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def has_flag(compiler, flag, ext=None):
|
||||
"""Returns True if the compiler supports the given flag"""
|
||||
return try_compile(compiler, flags=[flag], ext=ext)
|
||||
|
||||
|
||||
def get_cxx_std_flag(compiler):
|
||||
"""Detects compiler flag for c++14, c++11, or None if not detected"""
|
||||
# GNU C compiler documentation uses single dash:
|
||||
# https://gcc.gnu.org/onlinedocs/gcc/Standards.html
|
||||
# but silently understands two dashes, like --std=c++11 too.
|
||||
# Other GCC compatible compilers, like Intel C Compiler on Linux do not.
|
||||
gnu_flags = ['-std=c++14', '-std=c++11']
|
||||
flags_by_cc = {
|
||||
'msvc': ['/std:c++14', None],
|
||||
'intelw': ['/Qstd=c++14', '/Qstd=c++11'],
|
||||
'intelem': ['-std=c++14', '-std=c++11']
|
||||
}
|
||||
flags = flags_by_cc.get(compiler.compiler_type, gnu_flags)
|
||||
|
||||
for flag in flags:
|
||||
if flag is None:
|
||||
return None
|
||||
|
||||
if has_flag(compiler, flag, ext='.cpp'):
|
||||
return flag
|
||||
|
||||
from numpy.distutils import log
|
||||
log.warn('Could not detect c++ standard flag')
|
||||
return None
|
||||
|
||||
|
||||
def get_c_std_flag(compiler):
|
||||
"""Detects compiler flag to enable C99"""
|
||||
gnu_flag = '-std=c99'
|
||||
flag_by_cc = {
|
||||
'msvc': None,
|
||||
'intelw': '/Qstd=c99',
|
||||
'intelem': '-std=c99'
|
||||
}
|
||||
flag = flag_by_cc.get(compiler.compiler_type, gnu_flag)
|
||||
|
||||
if flag is None:
|
||||
return None
|
||||
|
||||
if has_flag(compiler, flag, ext='.c'):
|
||||
return flag
|
||||
|
||||
from numpy.distutils import log
|
||||
log.warn('Could not detect c99 standard flag')
|
||||
return None
|
||||
|
||||
|
||||
def try_add_flag(args, compiler, flag, ext=None):
|
||||
"""Appends flag to the list of arguments if supported by the compiler"""
|
||||
if try_compile(compiler, flags=args+[flag], ext=ext):
|
||||
args.append(flag)
|
||||
|
||||
|
||||
def set_c_flags_hook(build_ext, ext):
|
||||
"""Sets basic compiler flags for compiling C99 code"""
|
||||
std_flag = get_c_std_flag(build_ext.compiler)
|
||||
if std_flag is not None:
|
||||
ext.extra_compile_args.append(std_flag)
|
||||
|
||||
|
||||
def set_cxx_flags_hook(build_ext, ext):
|
||||
"""Sets basic compiler flags for compiling C++11 code"""
|
||||
cc = build_ext._cxx_compiler
|
||||
args = ext.extra_compile_args
|
||||
|
||||
std_flag = get_cxx_std_flag(cc)
|
||||
if std_flag is not None:
|
||||
args.append(std_flag)
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
# Set min macOS version
|
||||
min_macos_flag = '-mmacosx-version-min=10.9'
|
||||
if has_flag(cc, min_macos_flag):
|
||||
args.append(min_macos_flag)
|
||||
ext.extra_link_args.append(min_macos_flag)
|
||||
|
||||
|
||||
def set_cxx_flags_clib_hook(build_clib, build_info):
|
||||
cc = build_clib.compiler
|
||||
new_args = []
|
||||
new_link_args = []
|
||||
|
||||
std_flag = get_cxx_std_flag(cc)
|
||||
if std_flag is not None:
|
||||
new_args.append(std_flag)
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
# Set min macOS version
|
||||
min_macos_flag = '-mmacosx-version-min=10.9'
|
||||
if has_flag(cc, min_macos_flag):
|
||||
new_args.append(min_macos_flag)
|
||||
new_link_args.append(min_macos_flag)
|
||||
|
||||
dict_append(build_info, extra_compiler_args=new_args,
|
||||
extra_link_args=new_link_args)
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
|
||||
def configuration(parent_package='', top_path=None):
|
||||
from numpy.distutils.misc_util import Configuration
|
||||
config = Configuration('_build_utils', parent_package, top_path)
|
||||
config.add_data_dir('tests')
|
||||
return config
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from numpy.distutils.core import setup
|
||||
setup(**configuration(top_path='').todict())
|
||||
+205
@@ -0,0 +1,205 @@
|
||||
import warnings
|
||||
|
||||
import numpy as np
|
||||
import numpy.distutils.system_info
|
||||
|
||||
from numpy.distutils.system_info import (system_info,
|
||||
numpy_info,
|
||||
NotFoundError,
|
||||
BlasNotFoundError,
|
||||
LapackNotFoundError,
|
||||
AtlasNotFoundError,
|
||||
LapackSrcNotFoundError,
|
||||
BlasSrcNotFoundError,
|
||||
dict_append,
|
||||
get_info as old_get_info)
|
||||
|
||||
from scipy._lib import _pep440
|
||||
|
||||
|
||||
def combine_dict(*dicts, **kw):
|
||||
"""
|
||||
Combine Numpy distutils style library configuration dictionaries.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
*dicts
|
||||
Dictionaries of keys. List-valued keys will be concatenated.
|
||||
Otherwise, duplicate keys with different values result to
|
||||
an error. The input arguments are not modified.
|
||||
**kw
|
||||
Keyword arguments are treated as an additional dictionary
|
||||
(the first one, i.e., prepended).
|
||||
|
||||
Returns
|
||||
-------
|
||||
combined
|
||||
Dictionary with combined values.
|
||||
"""
|
||||
new_dict = {}
|
||||
|
||||
for d in (kw,) + dicts:
|
||||
for key, value in d.items():
|
||||
if new_dict.get(key, None) is not None:
|
||||
old_value = new_dict[key]
|
||||
if isinstance(value, (list, tuple)):
|
||||
if isinstance(old_value, (list, tuple)):
|
||||
new_dict[key] = list(old_value) + list(value)
|
||||
continue
|
||||
elif value == old_value:
|
||||
continue
|
||||
|
||||
raise ValueError("Conflicting configuration dicts: {!r} {!r}"
|
||||
"".format(new_dict, d))
|
||||
else:
|
||||
new_dict[key] = value
|
||||
|
||||
return new_dict
|
||||
|
||||
|
||||
if _pep440.parse(np.__version__) >= _pep440.Version("1.15.0.dev"):
|
||||
# For new enough numpy.distutils, the ACCELERATE=None environment
|
||||
# variable in the top-level setup.py is enough, so no need to
|
||||
# customize BLAS detection.
|
||||
get_info = old_get_info
|
||||
else:
|
||||
# For NumPy < 1.15.0, we need overrides.
|
||||
|
||||
def get_info(name, notfound_action=0):
|
||||
# Special case our custom *_opt_info.
|
||||
cls = {'lapack_opt': lapack_opt_info,
|
||||
'blas_opt': blas_opt_info}.get(name.lower())
|
||||
if cls is None:
|
||||
return old_get_info(name, notfound_action)
|
||||
return cls().get_info(notfound_action)
|
||||
|
||||
#
|
||||
# The following is copypaste from numpy.distutils.system_info, with
|
||||
# OSX Accelerate-related parts removed.
|
||||
#
|
||||
|
||||
class lapack_opt_info(system_info):
|
||||
|
||||
notfounderror = LapackNotFoundError
|
||||
|
||||
def calc_info(self):
|
||||
|
||||
lapack_mkl_info = get_info('lapack_mkl')
|
||||
if lapack_mkl_info:
|
||||
self.set_info(**lapack_mkl_info)
|
||||
return
|
||||
|
||||
openblas_info = get_info('openblas_lapack')
|
||||
if openblas_info:
|
||||
self.set_info(**openblas_info)
|
||||
return
|
||||
|
||||
openblas_info = get_info('openblas_clapack')
|
||||
if openblas_info:
|
||||
self.set_info(**openblas_info)
|
||||
return
|
||||
|
||||
atlas_info = get_info('atlas_3_10_threads')
|
||||
if not atlas_info:
|
||||
atlas_info = get_info('atlas_3_10')
|
||||
if not atlas_info:
|
||||
atlas_info = get_info('atlas_threads')
|
||||
if not atlas_info:
|
||||
atlas_info = get_info('atlas')
|
||||
|
||||
need_lapack = 0
|
||||
need_blas = 0
|
||||
info = {}
|
||||
if atlas_info:
|
||||
l = atlas_info.get('define_macros', [])
|
||||
if ('ATLAS_WITH_LAPACK_ATLAS', None) in l \
|
||||
or ('ATLAS_WITHOUT_LAPACK', None) in l:
|
||||
need_lapack = 1
|
||||
info = atlas_info
|
||||
|
||||
else:
|
||||
warnings.warn(AtlasNotFoundError.__doc__, stacklevel=2)
|
||||
need_blas = 1
|
||||
need_lapack = 1
|
||||
dict_append(info, define_macros=[('NO_ATLAS_INFO', 1)])
|
||||
|
||||
if need_lapack:
|
||||
lapack_info = get_info('lapack')
|
||||
#lapack_info = {} ## uncomment for testing
|
||||
if lapack_info:
|
||||
dict_append(info, **lapack_info)
|
||||
else:
|
||||
warnings.warn(LapackNotFoundError.__doc__, stacklevel=2)
|
||||
lapack_src_info = get_info('lapack_src')
|
||||
if not lapack_src_info:
|
||||
warnings.warn(LapackSrcNotFoundError.__doc__, stacklevel=2)
|
||||
return
|
||||
dict_append(info, libraries=[('flapack_src', lapack_src_info)])
|
||||
|
||||
if need_blas:
|
||||
blas_info = get_info('blas')
|
||||
if blas_info:
|
||||
dict_append(info, **blas_info)
|
||||
else:
|
||||
warnings.warn(BlasNotFoundError.__doc__, stacklevel=2)
|
||||
blas_src_info = get_info('blas_src')
|
||||
if not blas_src_info:
|
||||
warnings.warn(BlasSrcNotFoundError.__doc__, stacklevel=2)
|
||||
return
|
||||
dict_append(info, libraries=[('fblas_src', blas_src_info)])
|
||||
|
||||
self.set_info(**info)
|
||||
return
|
||||
|
||||
class blas_opt_info(system_info):
|
||||
|
||||
notfounderror = BlasNotFoundError
|
||||
|
||||
def calc_info(self):
|
||||
|
||||
blas_mkl_info = get_info('blas_mkl')
|
||||
if blas_mkl_info:
|
||||
self.set_info(**blas_mkl_info)
|
||||
return
|
||||
|
||||
blis_info = get_info('blis')
|
||||
if blis_info:
|
||||
self.set_info(**blis_info)
|
||||
return
|
||||
|
||||
openblas_info = get_info('openblas')
|
||||
if openblas_info:
|
||||
self.set_info(**openblas_info)
|
||||
return
|
||||
|
||||
atlas_info = get_info('atlas_3_10_blas_threads')
|
||||
if not atlas_info:
|
||||
atlas_info = get_info('atlas_3_10_blas')
|
||||
if not atlas_info:
|
||||
atlas_info = get_info('atlas_blas_threads')
|
||||
if not atlas_info:
|
||||
atlas_info = get_info('atlas_blas')
|
||||
|
||||
need_blas = 0
|
||||
info = {}
|
||||
if atlas_info:
|
||||
info = atlas_info
|
||||
else:
|
||||
warnings.warn(AtlasNotFoundError.__doc__, stacklevel=2)
|
||||
need_blas = 1
|
||||
dict_append(info, define_macros=[('NO_ATLAS_INFO', 1)])
|
||||
|
||||
if need_blas:
|
||||
blas_info = get_info('blas')
|
||||
if blas_info:
|
||||
dict_append(info, **blas_info)
|
||||
else:
|
||||
warnings.warn(BlasNotFoundError.__doc__, stacklevel=2)
|
||||
blas_src_info = get_info('blas_src')
|
||||
if not blas_src_info:
|
||||
warnings.warn(BlasSrcNotFoundError.__doc__, stacklevel=2)
|
||||
return
|
||||
dict_append(info, libraries=[('fblas_src', blas_src_info)])
|
||||
|
||||
self.set_info(**info)
|
||||
return
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
import sys
|
||||
import os
|
||||
|
||||
from Cython import Tempita as tempita
|
||||
# XXX: If this import ever fails (does it really?), vendor either
|
||||
# cython.tempita or numpy/npy_tempita.
|
||||
|
||||
|
||||
def process_tempita(fromfile):
|
||||
"""Process tempita templated file and write out the result.
|
||||
|
||||
The template file is expected to end in `.c.in` or `.pyx.in`:
|
||||
E.g. processing `template.c.in` generates `template.c`.
|
||||
|
||||
"""
|
||||
if not fromfile.endswith('.in'):
|
||||
raise ValueError("Unexpected extension: %s" % fromfile)
|
||||
|
||||
from_filename = tempita.Template.from_filename
|
||||
template = from_filename(fromfile,
|
||||
encoding=sys.getdefaultencoding())
|
||||
|
||||
content = template.substitute()
|
||||
|
||||
outfile = os.path.splitext(fromfile)[0]
|
||||
with open(outfile, 'w') as f:
|
||||
f.write(content)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
process_tempita(sys.argv[1])
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import re
|
||||
|
||||
import scipy
|
||||
from numpy.testing import assert_
|
||||
|
||||
|
||||
def test_valid_scipy_version():
|
||||
# Verify that the SciPy version is a valid one (no .post suffix or other
|
||||
# nonsense). See NumPy issue gh-6431 for an issue caused by an invalid
|
||||
# version.
|
||||
version_pattern = r"^[0-9]+\.[0-9]+\.[0-9]+(|a[0-9]|b[0-9]|rc[0-9])"
|
||||
dev_suffix = r"(\.dev0\+.+([0-9a-f]{7}|Unknown))"
|
||||
if scipy.version.release:
|
||||
res = re.match(version_pattern, scipy.__version__)
|
||||
else:
|
||||
res = re.match(version_pattern + dev_suffix, scipy.__version__)
|
||||
|
||||
assert_(res is not None, scipy.__version__)
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
""" Distributor init file
|
||||
|
||||
Distributors: you can add custom code here to support particular distributions
|
||||
of SciPy.
|
||||
|
||||
For example, this is a good place to put any checks for hardware requirements.
|
||||
|
||||
The SciPy standard source distribution will not put code in this file, so you
|
||||
can safely replace this file with your own version.
|
||||
"""
|
||||
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
"""
|
||||
Module containing private utility functions
|
||||
===========================================
|
||||
|
||||
The ``scipy._lib`` namespace is empty (for now). Tests for all
|
||||
utilities in submodules of ``_lib`` can be run with::
|
||||
|
||||
from scipy import _lib
|
||||
_lib.test()
|
||||
|
||||
"""
|
||||
from scipy._lib._testutils import PytestTester
|
||||
test = PytestTester(__name__)
|
||||
del PytestTester
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
'''Helper functions to get location of header files.'''
|
||||
|
||||
import pathlib
|
||||
from typing import Union
|
||||
|
||||
|
||||
def _boost_dir(ret_path: bool = False) -> Union[pathlib.Path, str]:
|
||||
'''Directory where root Boost/ directory lives.'''
|
||||
p = pathlib.Path(__file__).parent / 'boost'
|
||||
return p if ret_path else str(p)
|
||||
Vendored
+225
@@ -0,0 +1,225 @@
|
||||
|
||||
import sys as _sys
|
||||
from keyword import iskeyword as _iskeyword
|
||||
|
||||
|
||||
def _validate_names(typename, field_names, extra_field_names):
|
||||
"""
|
||||
Ensure that all the given names are valid Python identifiers that
|
||||
do not start with '_'. Also check that there are no duplicates
|
||||
among field_names + extra_field_names.
|
||||
"""
|
||||
for name in [typename] + field_names + extra_field_names:
|
||||
if type(name) is not str:
|
||||
raise TypeError('typename and all field names must be strings')
|
||||
if not name.isidentifier():
|
||||
raise ValueError('typename and all field names must be valid '
|
||||
f'identifiers: {name!r}')
|
||||
if _iskeyword(name):
|
||||
raise ValueError('typename and all field names cannot be a '
|
||||
f'keyword: {name!r}')
|
||||
|
||||
seen = set()
|
||||
for name in field_names + extra_field_names:
|
||||
if name.startswith('_'):
|
||||
raise ValueError('Field names cannot start with an underscore: '
|
||||
f'{name!r}')
|
||||
if name in seen:
|
||||
raise ValueError(f'Duplicate field name: {name!r}')
|
||||
seen.add(name)
|
||||
|
||||
|
||||
# Note: This code is adapted from CPython:Lib/collections/__init__.py
|
||||
def _make_tuple_bunch(typename, field_names, extra_field_names=None,
|
||||
module=None):
|
||||
"""
|
||||
Create a namedtuple-like class with additional attributes.
|
||||
|
||||
This function creates a subclass of tuple that acts like a namedtuple
|
||||
and that has additional attributes.
|
||||
|
||||
The additional attributes are listed in `extra_field_names`. The
|
||||
values assigned to these attributes are not part of the tuple.
|
||||
|
||||
The reason this function exists is to allow functions in SciPy
|
||||
that currently return a tuple or a namedtuple to returned objects
|
||||
that have additional attributes, while maintaining backwards
|
||||
compatibility.
|
||||
|
||||
This should only be used to enhance *existing* functions in SciPy.
|
||||
New functions are free to create objects as return values without
|
||||
having to maintain backwards compatibility with an old tuple or
|
||||
namedtuple return value.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
typename : str
|
||||
The name of the type.
|
||||
field_names : list of str
|
||||
List of names of the values to be stored in the tuple. These names
|
||||
will also be attributes of instances, so the values in the tuple
|
||||
can be accessed by indexing or as attributes. At least one name
|
||||
is required. See the Notes for additional restrictions.
|
||||
extra_field_names : list of str, optional
|
||||
List of names of values that will be stored as attributes of the
|
||||
object. See the notes for additional restrictions.
|
||||
|
||||
Returns
|
||||
-------
|
||||
cls : type
|
||||
The new class.
|
||||
|
||||
Notes
|
||||
-----
|
||||
There are restrictions on the names that may be used in `field_names`
|
||||
and `extra_field_names`:
|
||||
|
||||
* The names must be unique--no duplicates allowed.
|
||||
* The names must be valid Python identifiers, and must not begin with
|
||||
an underscore.
|
||||
* The names must not be Python keywords (e.g. 'def', 'and', etc., are
|
||||
not allowed).
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from scipy._lib._bunch import _make_tuple_bunch
|
||||
|
||||
Create a class that acts like a namedtuple with length 2 (with field
|
||||
names `x` and `y`) that will also have the attributes `w` and `beta`:
|
||||
|
||||
>>> Result = _make_tuple_bunch('Result', ['x', 'y'], ['w', 'beta'])
|
||||
|
||||
`Result` is the new class. We call it with keyword arguments to create
|
||||
a new instance with given values.
|
||||
|
||||
>>> result1 = Result(x=1, y=2, w=99, beta=0.5)
|
||||
>>> result1
|
||||
Result(x=1, y=2, w=99, beta=0.5)
|
||||
|
||||
`result1` acts like a tuple of length 2:
|
||||
|
||||
>>> len(result1)
|
||||
2
|
||||
>>> result1[:]
|
||||
(1, 2)
|
||||
|
||||
The values assigned when the instance was created are available as
|
||||
attributes:
|
||||
|
||||
>>> result1.y
|
||||
2
|
||||
>>> result1.beta
|
||||
0.5
|
||||
"""
|
||||
if len(field_names) == 0:
|
||||
raise ValueError('field_names must contain at least one name')
|
||||
|
||||
if extra_field_names is None:
|
||||
extra_field_names = []
|
||||
_validate_names(typename, field_names, extra_field_names)
|
||||
|
||||
typename = _sys.intern(str(typename))
|
||||
field_names = tuple(map(_sys.intern, field_names))
|
||||
extra_field_names = tuple(map(_sys.intern, extra_field_names))
|
||||
|
||||
all_names = field_names + extra_field_names
|
||||
arg_list = ', '.join(field_names)
|
||||
full_list = ', '.join(all_names)
|
||||
repr_fmt = ''.join(('(',
|
||||
', '.join(f'{name}=%({name})r' for name in all_names),
|
||||
')'))
|
||||
tuple_new = tuple.__new__
|
||||
_dict, _tuple, _zip = dict, tuple, zip
|
||||
|
||||
# Create all the named tuple methods to be added to the class namespace
|
||||
|
||||
s = f"""\
|
||||
def __new__(_cls, {arg_list}, **extra_fields):
|
||||
return _tuple_new(_cls, ({arg_list},))
|
||||
|
||||
def __init__(self, {arg_list}, **extra_fields):
|
||||
for key in self._extra_fields:
|
||||
if key not in extra_fields:
|
||||
raise TypeError("missing keyword argument '%s'" % (key,))
|
||||
for key, val in extra_fields.items():
|
||||
if key not in self._extra_fields:
|
||||
raise TypeError("unexpected keyword argument '%s'" % (key,))
|
||||
self.__dict__[key] = val
|
||||
|
||||
def __setattr__(self, key, val):
|
||||
raise AttributeError("can't set attribute %r of class %r"
|
||||
% (key, self.__class__.__name__))
|
||||
"""
|
||||
del arg_list
|
||||
namespace = {'_tuple_new': tuple_new,
|
||||
'__builtins__': dict(TypeError=TypeError,
|
||||
AttributeError=AttributeError),
|
||||
'__name__': f'namedtuple_{typename}'}
|
||||
exec(s, namespace)
|
||||
__new__ = namespace['__new__']
|
||||
__new__.__doc__ = f'Create new instance of {typename}({full_list})'
|
||||
__init__ = namespace['__init__']
|
||||
__init__.__doc__ = f'Instantiate instance of {typename}({full_list})'
|
||||
__setattr__ = namespace['__setattr__']
|
||||
|
||||
def __repr__(self):
|
||||
'Return a nicely formatted representation string'
|
||||
return self.__class__.__name__ + repr_fmt % self._asdict()
|
||||
|
||||
def _asdict(self):
|
||||
'Return a new dict which maps field names to their values.'
|
||||
out = _dict(_zip(self._fields, self))
|
||||
out.update(self.__dict__)
|
||||
return out
|
||||
|
||||
def __getnewargs_ex__(self):
|
||||
'Return self as a plain tuple. Used by copy and pickle.'
|
||||
return _tuple(self), self.__dict__
|
||||
|
||||
# Modify function metadata to help with introspection and debugging
|
||||
for method in (__new__, __repr__, _asdict, __getnewargs_ex__):
|
||||
method.__qualname__ = f'{typename}.{method.__name__}'
|
||||
|
||||
# Build-up the class namespace dictionary
|
||||
# and use type() to build the result class
|
||||
class_namespace = {
|
||||
'__doc__': f'{typename}({full_list})',
|
||||
'_fields': field_names,
|
||||
'__new__': __new__,
|
||||
'__init__': __init__,
|
||||
'__repr__': __repr__,
|
||||
'__setattr__': __setattr__,
|
||||
'_asdict': _asdict,
|
||||
'_extra_fields': extra_field_names,
|
||||
'__getnewargs_ex__': __getnewargs_ex__,
|
||||
}
|
||||
for index, name in enumerate(field_names):
|
||||
doc = _sys.intern(f'Alias for field number {index}')
|
||||
|
||||
def _get(self, index=index):
|
||||
return self[index]
|
||||
class_namespace[name] = property(_get, doc=doc)
|
||||
for name in extra_field_names:
|
||||
doc = _sys.intern(f'Alias for name {name}')
|
||||
|
||||
def _get(self, name=name):
|
||||
return self.__dict__[name]
|
||||
class_namespace[name] = property(_get, doc=doc)
|
||||
|
||||
result = type(typename, (tuple,), class_namespace)
|
||||
|
||||
# For pickling to work, the __module__ variable needs to be set to the
|
||||
# frame where the named tuple is created. Bypass this step in environments
|
||||
# where sys._getframe is not defined (Jython for example) or sys._getframe
|
||||
# is not defined for arguments greater than 0 (IronPython), or where the
|
||||
# user has specified a particular module.
|
||||
if module is None:
|
||||
try:
|
||||
module = _sys._getframe(1).f_globals.get('__name__', '__main__')
|
||||
except (AttributeError, ValueError):
|
||||
pass
|
||||
if module is not None:
|
||||
result.__module__ = module
|
||||
__new__.__module__ = module
|
||||
|
||||
return result
|
||||
Vendored
+227
@@ -0,0 +1,227 @@
|
||||
from . import _ccallback_c
|
||||
|
||||
import ctypes
|
||||
|
||||
PyCFuncPtr = ctypes.CFUNCTYPE(ctypes.c_void_p).__bases__[0]
|
||||
|
||||
ffi = None
|
||||
|
||||
class CData:
|
||||
pass
|
||||
|
||||
def _import_cffi():
|
||||
global ffi, CData
|
||||
|
||||
if ffi is not None:
|
||||
return
|
||||
|
||||
try:
|
||||
import cffi
|
||||
ffi = cffi.FFI()
|
||||
CData = ffi.CData
|
||||
except ImportError:
|
||||
ffi = False
|
||||
|
||||
|
||||
class LowLevelCallable(tuple):
|
||||
"""
|
||||
Low-level callback function.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
function : {PyCapsule, ctypes function pointer, cffi function pointer}
|
||||
Low-level callback function.
|
||||
user_data : {PyCapsule, ctypes void pointer, cffi void pointer}
|
||||
User data to pass on to the callback function.
|
||||
signature : str, optional
|
||||
Signature of the function. If omitted, determined from *function*,
|
||||
if possible.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
function
|
||||
Callback function given.
|
||||
user_data
|
||||
User data given.
|
||||
signature
|
||||
Signature of the function.
|
||||
|
||||
Methods
|
||||
-------
|
||||
from_cython
|
||||
Class method for constructing callables from Cython C-exported
|
||||
functions.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The argument ``function`` can be one of:
|
||||
|
||||
- PyCapsule, whose name contains the C function signature
|
||||
- ctypes function pointer
|
||||
- cffi function pointer
|
||||
|
||||
The signature of the low-level callback must match one of those expected
|
||||
by the routine it is passed to.
|
||||
|
||||
If constructing low-level functions from a PyCapsule, the name of the
|
||||
capsule must be the corresponding signature, in the format::
|
||||
|
||||
return_type (arg1_type, arg2_type, ...)
|
||||
|
||||
For example::
|
||||
|
||||
"void (double)"
|
||||
"double (double, int *, void *)"
|
||||
|
||||
The context of a PyCapsule passed in as ``function`` is used as ``user_data``,
|
||||
if an explicit value for ``user_data`` was not given.
|
||||
|
||||
"""
|
||||
|
||||
# Make the class immutable
|
||||
__slots__ = ()
|
||||
|
||||
def __new__(cls, function, user_data=None, signature=None):
|
||||
# We need to hold a reference to the function & user data,
|
||||
# to prevent them going out of scope
|
||||
item = cls._parse_callback(function, user_data, signature)
|
||||
return tuple.__new__(cls, (item, function, user_data))
|
||||
|
||||
def __repr__(self):
|
||||
return "LowLevelCallable({!r}, {!r})".format(self.function, self.user_data)
|
||||
|
||||
@property
|
||||
def function(self):
|
||||
return tuple.__getitem__(self, 1)
|
||||
|
||||
@property
|
||||
def user_data(self):
|
||||
return tuple.__getitem__(self, 2)
|
||||
|
||||
@property
|
||||
def signature(self):
|
||||
return _ccallback_c.get_capsule_signature(tuple.__getitem__(self, 0))
|
||||
|
||||
def __getitem__(self, idx):
|
||||
raise ValueError()
|
||||
|
||||
@classmethod
|
||||
def from_cython(cls, module, name, user_data=None, signature=None):
|
||||
"""
|
||||
Create a low-level callback function from an exported Cython function.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
module : module
|
||||
Cython module where the exported function resides
|
||||
name : str
|
||||
Name of the exported function
|
||||
user_data : {PyCapsule, ctypes void pointer, cffi void pointer}, optional
|
||||
User data to pass on to the callback function.
|
||||
signature : str, optional
|
||||
Signature of the function. If omitted, determined from *function*.
|
||||
|
||||
"""
|
||||
try:
|
||||
function = module.__pyx_capi__[name]
|
||||
except AttributeError as e:
|
||||
raise ValueError("Given module is not a Cython module with __pyx_capi__ attribute") from e
|
||||
except KeyError as e:
|
||||
raise ValueError("No function {!r} found in __pyx_capi__ of the module".format(name)) from e
|
||||
return cls(function, user_data, signature)
|
||||
|
||||
@classmethod
|
||||
def _parse_callback(cls, obj, user_data=None, signature=None):
|
||||
_import_cffi()
|
||||
|
||||
if isinstance(obj, LowLevelCallable):
|
||||
func = tuple.__getitem__(obj, 0)
|
||||
elif isinstance(obj, PyCFuncPtr):
|
||||
func, signature = _get_ctypes_func(obj, signature)
|
||||
elif isinstance(obj, CData):
|
||||
func, signature = _get_cffi_func(obj, signature)
|
||||
elif _ccallback_c.check_capsule(obj):
|
||||
func = obj
|
||||
else:
|
||||
raise ValueError("Given input is not a callable or a low-level callable (pycapsule/ctypes/cffi)")
|
||||
|
||||
if isinstance(user_data, ctypes.c_void_p):
|
||||
context = _get_ctypes_data(user_data)
|
||||
elif isinstance(user_data, CData):
|
||||
context = _get_cffi_data(user_data)
|
||||
elif user_data is None:
|
||||
context = 0
|
||||
elif _ccallback_c.check_capsule(user_data):
|
||||
context = user_data
|
||||
else:
|
||||
raise ValueError("Given user data is not a valid low-level void* pointer (pycapsule/ctypes/cffi)")
|
||||
|
||||
return _ccallback_c.get_raw_capsule(func, signature, context)
|
||||
|
||||
|
||||
#
|
||||
# ctypes helpers
|
||||
#
|
||||
|
||||
def _get_ctypes_func(func, signature=None):
|
||||
# Get function pointer
|
||||
func_ptr = ctypes.cast(func, ctypes.c_void_p).value
|
||||
|
||||
# Construct function signature
|
||||
if signature is None:
|
||||
signature = _typename_from_ctypes(func.restype) + " ("
|
||||
for j, arg in enumerate(func.argtypes):
|
||||
if j == 0:
|
||||
signature += _typename_from_ctypes(arg)
|
||||
else:
|
||||
signature += ", " + _typename_from_ctypes(arg)
|
||||
signature += ")"
|
||||
|
||||
return func_ptr, signature
|
||||
|
||||
|
||||
def _typename_from_ctypes(item):
|
||||
if item is None:
|
||||
return "void"
|
||||
elif item is ctypes.c_void_p:
|
||||
return "void *"
|
||||
|
||||
name = item.__name__
|
||||
|
||||
pointer_level = 0
|
||||
while name.startswith("LP_"):
|
||||
pointer_level += 1
|
||||
name = name[3:]
|
||||
|
||||
if name.startswith('c_'):
|
||||
name = name[2:]
|
||||
|
||||
if pointer_level > 0:
|
||||
name += " " + "*"*pointer_level
|
||||
|
||||
return name
|
||||
|
||||
|
||||
def _get_ctypes_data(data):
|
||||
# Get voidp pointer
|
||||
return ctypes.cast(data, ctypes.c_void_p).value
|
||||
|
||||
|
||||
#
|
||||
# CFFI helpers
|
||||
#
|
||||
|
||||
def _get_cffi_func(func, signature=None):
|
||||
# Get function pointer
|
||||
func_ptr = ffi.cast('uintptr_t', func)
|
||||
|
||||
# Get signature
|
||||
if signature is None:
|
||||
signature = ffi.getctype(ffi.typeof(func)).replace('(*)', ' ')
|
||||
|
||||
return func_ptr, signature
|
||||
|
||||
|
||||
def _get_cffi_data(data):
|
||||
# Get pointer
|
||||
return ffi.cast('uintptr_t', data)
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
"""
|
||||
Disjoint set data structure
|
||||
"""
|
||||
|
||||
|
||||
class DisjointSet:
|
||||
""" Disjoint set data structure for incremental connectivity queries.
|
||||
|
||||
.. versionadded:: 1.6.0
|
||||
|
||||
Attributes
|
||||
----------
|
||||
n_subsets : int
|
||||
The number of subsets.
|
||||
|
||||
Methods
|
||||
-------
|
||||
add
|
||||
merge
|
||||
connected
|
||||
subset
|
||||
subsets
|
||||
__getitem__
|
||||
|
||||
Notes
|
||||
-----
|
||||
This class implements the disjoint set [1]_, also known as the *union-find*
|
||||
or *merge-find* data structure. The *find* operation (implemented in
|
||||
`__getitem__`) implements the *path halving* variant. The *merge* method
|
||||
implements the *merge by size* variant.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] https://en.wikipedia.org/wiki/Disjoint-set_data_structure
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from scipy.cluster.hierarchy import DisjointSet
|
||||
|
||||
Initialize a disjoint set:
|
||||
|
||||
>>> disjoint_set = DisjointSet([1, 2, 3, 'a', 'b'])
|
||||
|
||||
Merge some subsets:
|
||||
|
||||
>>> disjoint_set.merge(1, 2)
|
||||
True
|
||||
>>> disjoint_set.merge(3, 'a')
|
||||
True
|
||||
>>> disjoint_set.merge('a', 'b')
|
||||
True
|
||||
>>> disjoint_set.merge('b', 'b')
|
||||
False
|
||||
|
||||
Find root elements:
|
||||
|
||||
>>> disjoint_set[2]
|
||||
1
|
||||
>>> disjoint_set['b']
|
||||
3
|
||||
|
||||
Test connectivity:
|
||||
|
||||
>>> disjoint_set.connected(1, 2)
|
||||
True
|
||||
>>> disjoint_set.connected(1, 'b')
|
||||
False
|
||||
|
||||
List elements in disjoint set:
|
||||
|
||||
>>> list(disjoint_set)
|
||||
[1, 2, 3, 'a', 'b']
|
||||
|
||||
Get the subset containing 'a':
|
||||
|
||||
>>> disjoint_set.subset('a')
|
||||
{'a', 3, 'b'}
|
||||
|
||||
Get all subsets in the disjoint set:
|
||||
|
||||
>>> disjoint_set.subsets()
|
||||
[{1, 2}, {'a', 3, 'b'}]
|
||||
"""
|
||||
def __init__(self, elements=None):
|
||||
self.n_subsets = 0
|
||||
self._sizes = {}
|
||||
self._parents = {}
|
||||
# _nbrs is a circular linked list which links connected elements.
|
||||
self._nbrs = {}
|
||||
# _indices tracks the element insertion order in `__iter__`.
|
||||
self._indices = {}
|
||||
if elements is not None:
|
||||
for x in elements:
|
||||
self.add(x)
|
||||
|
||||
def __iter__(self):
|
||||
"""Returns an iterator of the elements in the disjoint set.
|
||||
|
||||
Elements are ordered by insertion order.
|
||||
"""
|
||||
return iter(self._indices)
|
||||
|
||||
def __len__(self):
|
||||
return len(self._indices)
|
||||
|
||||
def __contains__(self, x):
|
||||
return x in self._indices
|
||||
|
||||
def __getitem__(self, x):
|
||||
"""Find the root element of `x`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x : hashable object
|
||||
Input element.
|
||||
|
||||
Returns
|
||||
-------
|
||||
root : hashable object
|
||||
Root element of `x`.
|
||||
"""
|
||||
if x not in self._indices:
|
||||
raise KeyError(x)
|
||||
|
||||
# find by "path halving"
|
||||
parents = self._parents
|
||||
while self._indices[x] != self._indices[parents[x]]:
|
||||
parents[x] = parents[parents[x]]
|
||||
x = parents[x]
|
||||
return x
|
||||
|
||||
def add(self, x):
|
||||
"""Add element `x` to disjoint set
|
||||
"""
|
||||
if x in self._indices:
|
||||
return
|
||||
|
||||
self._sizes[x] = 1
|
||||
self._parents[x] = x
|
||||
self._nbrs[x] = x
|
||||
self._indices[x] = len(self._indices)
|
||||
self.n_subsets += 1
|
||||
|
||||
def merge(self, x, y):
|
||||
"""Merge the subsets of `x` and `y`.
|
||||
|
||||
The smaller subset (the child) is merged into the larger subset (the
|
||||
parent). If the subsets are of equal size, the root element which was
|
||||
first inserted into the disjoint set is selected as the parent.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x, y : hashable object
|
||||
Elements to merge.
|
||||
|
||||
Returns
|
||||
-------
|
||||
merged : bool
|
||||
True if `x` and `y` were in disjoint sets, False otherwise.
|
||||
"""
|
||||
xr = self[x]
|
||||
yr = self[y]
|
||||
if self._indices[xr] == self._indices[yr]:
|
||||
return False
|
||||
|
||||
sizes = self._sizes
|
||||
if (sizes[xr], self._indices[yr]) < (sizes[yr], self._indices[xr]):
|
||||
xr, yr = yr, xr
|
||||
self._parents[yr] = xr
|
||||
self._sizes[xr] += self._sizes[yr]
|
||||
self._nbrs[xr], self._nbrs[yr] = self._nbrs[yr], self._nbrs[xr]
|
||||
self.n_subsets -= 1
|
||||
return True
|
||||
|
||||
def connected(self, x, y):
|
||||
"""Test whether `x` and `y` are in the same subset.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x, y : hashable object
|
||||
Elements to test.
|
||||
|
||||
Returns
|
||||
-------
|
||||
result : bool
|
||||
True if `x` and `y` are in the same set, False otherwise.
|
||||
"""
|
||||
return self._indices[self[x]] == self._indices[self[y]]
|
||||
|
||||
def subset(self, x):
|
||||
"""Get the subset containing `x`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x : hashable object
|
||||
Input element.
|
||||
|
||||
Returns
|
||||
-------
|
||||
result : set
|
||||
Subset containing `x`.
|
||||
"""
|
||||
if x not in self._indices:
|
||||
raise KeyError(x)
|
||||
|
||||
result = [x]
|
||||
nxt = self._nbrs[x]
|
||||
while self._indices[nxt] != self._indices[x]:
|
||||
result.append(nxt)
|
||||
nxt = self._nbrs[nxt]
|
||||
return set(result)
|
||||
|
||||
def subsets(self):
|
||||
"""Get all the subsets in the disjoint set.
|
||||
|
||||
Returns
|
||||
-------
|
||||
result : list
|
||||
Subsets in the disjoint set.
|
||||
"""
|
||||
result = []
|
||||
visited = set()
|
||||
for x in self:
|
||||
if x not in visited:
|
||||
xset = self.subset(x)
|
||||
visited.update(xset)
|
||||
result.append(xset)
|
||||
return result
|
||||
Vendored
+105
@@ -0,0 +1,105 @@
|
||||
"""
|
||||
Module for testing automatic garbage collection of objects
|
||||
|
||||
.. autosummary::
|
||||
:toctree: generated/
|
||||
|
||||
set_gc_state - enable or disable garbage collection
|
||||
gc_state - context manager for given state of garbage collector
|
||||
assert_deallocated - context manager to check for circular references on object
|
||||
|
||||
"""
|
||||
import weakref
|
||||
import gc
|
||||
|
||||
from contextlib import contextmanager
|
||||
from platform import python_implementation
|
||||
|
||||
__all__ = ['set_gc_state', 'gc_state', 'assert_deallocated']
|
||||
|
||||
|
||||
IS_PYPY = python_implementation() == 'PyPy'
|
||||
|
||||
|
||||
class ReferenceError(AssertionError):
|
||||
pass
|
||||
|
||||
|
||||
def set_gc_state(state):
|
||||
""" Set status of garbage collector """
|
||||
if gc.isenabled() == state:
|
||||
return
|
||||
if state:
|
||||
gc.enable()
|
||||
else:
|
||||
gc.disable()
|
||||
|
||||
|
||||
@contextmanager
|
||||
def gc_state(state):
|
||||
""" Context manager to set state of garbage collector to `state`
|
||||
|
||||
Parameters
|
||||
----------
|
||||
state : bool
|
||||
True for gc enabled, False for disabled
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> with gc_state(False):
|
||||
... assert not gc.isenabled()
|
||||
>>> with gc_state(True):
|
||||
... assert gc.isenabled()
|
||||
"""
|
||||
orig_state = gc.isenabled()
|
||||
set_gc_state(state)
|
||||
yield
|
||||
set_gc_state(orig_state)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def assert_deallocated(func, *args, **kwargs):
|
||||
"""Context manager to check that object is deallocated
|
||||
|
||||
This is useful for checking that an object can be freed directly by
|
||||
reference counting, without requiring gc to break reference cycles.
|
||||
GC is disabled inside the context manager.
|
||||
|
||||
This check is not available on PyPy.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
func : callable
|
||||
Callable to create object to check
|
||||
\\*args : sequence
|
||||
positional arguments to `func` in order to create object to check
|
||||
\\*\\*kwargs : dict
|
||||
keyword arguments to `func` in order to create object to check
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> class C: pass
|
||||
>>> with assert_deallocated(C) as c:
|
||||
... # do something
|
||||
... del c
|
||||
|
||||
>>> class C:
|
||||
... def __init__(self):
|
||||
... self._circular = self # Make circular reference
|
||||
>>> with assert_deallocated(C) as c: #doctest: +IGNORE_EXCEPTION_DETAIL
|
||||
... # do something
|
||||
... del c
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ReferenceError: Remaining reference(s) to object
|
||||
"""
|
||||
if IS_PYPY:
|
||||
raise RuntimeError("assert_deallocated is unavailable on PyPy")
|
||||
|
||||
with gc_state(False):
|
||||
obj = func(*args, **kwargs)
|
||||
ref = weakref.ref(obj)
|
||||
yield obj
|
||||
del obj
|
||||
if ref() is not None:
|
||||
raise ReferenceError("Remaining reference(s) to object")
|
||||
Vendored
+487
@@ -0,0 +1,487 @@
|
||||
"""Utility to compare pep440 compatible version strings.
|
||||
|
||||
The LooseVersion and StrictVersion classes that distutils provides don't
|
||||
work; they don't recognize anything like alpha/beta/rc/dev versions.
|
||||
"""
|
||||
|
||||
# Copyright (c) Donald Stufft and individual contributors.
|
||||
# All rights reserved.
|
||||
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
|
||||
# 1. Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import collections
|
||||
import itertools
|
||||
import re
|
||||
|
||||
|
||||
__all__ = [
|
||||
"parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN",
|
||||
]
|
||||
|
||||
|
||||
# BEGIN packaging/_structures.py
|
||||
|
||||
|
||||
class Infinity:
|
||||
def __repr__(self):
|
||||
return "Infinity"
|
||||
|
||||
def __hash__(self):
|
||||
return hash(repr(self))
|
||||
|
||||
def __lt__(self, other):
|
||||
return False
|
||||
|
||||
def __le__(self, other):
|
||||
return False
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, self.__class__)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not isinstance(other, self.__class__)
|
||||
|
||||
def __gt__(self, other):
|
||||
return True
|
||||
|
||||
def __ge__(self, other):
|
||||
return True
|
||||
|
||||
def __neg__(self):
|
||||
return NegativeInfinity
|
||||
|
||||
|
||||
Infinity = Infinity()
|
||||
|
||||
|
||||
class NegativeInfinity:
|
||||
def __repr__(self):
|
||||
return "-Infinity"
|
||||
|
||||
def __hash__(self):
|
||||
return hash(repr(self))
|
||||
|
||||
def __lt__(self, other):
|
||||
return True
|
||||
|
||||
def __le__(self, other):
|
||||
return True
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, self.__class__)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not isinstance(other, self.__class__)
|
||||
|
||||
def __gt__(self, other):
|
||||
return False
|
||||
|
||||
def __ge__(self, other):
|
||||
return False
|
||||
|
||||
def __neg__(self):
|
||||
return Infinity
|
||||
|
||||
|
||||
# BEGIN packaging/version.py
|
||||
|
||||
|
||||
NegativeInfinity = NegativeInfinity()
|
||||
|
||||
_Version = collections.namedtuple(
|
||||
"_Version",
|
||||
["epoch", "release", "dev", "pre", "post", "local"],
|
||||
)
|
||||
|
||||
|
||||
def parse(version):
|
||||
"""
|
||||
Parse the given version string and return either a :class:`Version` object
|
||||
or a :class:`LegacyVersion` object depending on if the given version is
|
||||
a valid PEP 440 version or a legacy version.
|
||||
"""
|
||||
try:
|
||||
return Version(version)
|
||||
except InvalidVersion:
|
||||
return LegacyVersion(version)
|
||||
|
||||
|
||||
class InvalidVersion(ValueError):
|
||||
"""
|
||||
An invalid version was found, users should refer to PEP 440.
|
||||
"""
|
||||
|
||||
|
||||
class _BaseVersion:
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self._key)
|
||||
|
||||
def __lt__(self, other):
|
||||
return self._compare(other, lambda s, o: s < o)
|
||||
|
||||
def __le__(self, other):
|
||||
return self._compare(other, lambda s, o: s <= o)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self._compare(other, lambda s, o: s == o)
|
||||
|
||||
def __ge__(self, other):
|
||||
return self._compare(other, lambda s, o: s >= o)
|
||||
|
||||
def __gt__(self, other):
|
||||
return self._compare(other, lambda s, o: s > o)
|
||||
|
||||
def __ne__(self, other):
|
||||
return self._compare(other, lambda s, o: s != o)
|
||||
|
||||
def _compare(self, other, method):
|
||||
if not isinstance(other, _BaseVersion):
|
||||
return NotImplemented
|
||||
|
||||
return method(self._key, other._key)
|
||||
|
||||
|
||||
class LegacyVersion(_BaseVersion):
|
||||
|
||||
def __init__(self, version):
|
||||
self._version = str(version)
|
||||
self._key = _legacy_cmpkey(self._version)
|
||||
|
||||
def __str__(self):
|
||||
return self._version
|
||||
|
||||
def __repr__(self):
|
||||
return "<LegacyVersion({0})>".format(repr(str(self)))
|
||||
|
||||
@property
|
||||
def public(self):
|
||||
return self._version
|
||||
|
||||
@property
|
||||
def base_version(self):
|
||||
return self._version
|
||||
|
||||
@property
|
||||
def local(self):
|
||||
return None
|
||||
|
||||
@property
|
||||
def is_prerelease(self):
|
||||
return False
|
||||
|
||||
@property
|
||||
def is_postrelease(self):
|
||||
return False
|
||||
|
||||
|
||||
_legacy_version_component_re = re.compile(
|
||||
r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE,
|
||||
)
|
||||
|
||||
_legacy_version_replacement_map = {
|
||||
"pre": "c", "preview": "c", "-": "final-", "rc": "c", "dev": "@",
|
||||
}
|
||||
|
||||
|
||||
def _parse_version_parts(s):
|
||||
for part in _legacy_version_component_re.split(s):
|
||||
part = _legacy_version_replacement_map.get(part, part)
|
||||
|
||||
if not part or part == ".":
|
||||
continue
|
||||
|
||||
if part[:1] in "0123456789":
|
||||
# pad for numeric comparison
|
||||
yield part.zfill(8)
|
||||
else:
|
||||
yield "*" + part
|
||||
|
||||
# ensure that alpha/beta/candidate are before final
|
||||
yield "*final"
|
||||
|
||||
|
||||
def _legacy_cmpkey(version):
|
||||
# We hardcode an epoch of -1 here. A PEP 440 version can only have an epoch
|
||||
# greater than or equal to 0. This will effectively put the LegacyVersion,
|
||||
# which uses the defacto standard originally implemented by setuptools,
|
||||
# as before all PEP 440 versions.
|
||||
epoch = -1
|
||||
|
||||
# This scheme is taken from pkg_resources.parse_version setuptools prior to
|
||||
# its adoption of the packaging library.
|
||||
parts = []
|
||||
for part in _parse_version_parts(version.lower()):
|
||||
if part.startswith("*"):
|
||||
# remove "-" before a prerelease tag
|
||||
if part < "*final":
|
||||
while parts and parts[-1] == "*final-":
|
||||
parts.pop()
|
||||
|
||||
# remove trailing zeros from each series of numeric parts
|
||||
while parts and parts[-1] == "00000000":
|
||||
parts.pop()
|
||||
|
||||
parts.append(part)
|
||||
parts = tuple(parts)
|
||||
|
||||
return epoch, parts
|
||||
|
||||
|
||||
# Deliberately not anchored to the start and end of the string, to make it
|
||||
# easier for 3rd party code to reuse
|
||||
VERSION_PATTERN = r"""
|
||||
v?
|
||||
(?:
|
||||
(?:(?P<epoch>[0-9]+)!)? # epoch
|
||||
(?P<release>[0-9]+(?:\.[0-9]+)*) # release segment
|
||||
(?P<pre> # pre-release
|
||||
[-_\.]?
|
||||
(?P<pre_l>(a|b|c|rc|alpha|beta|pre|preview))
|
||||
[-_\.]?
|
||||
(?P<pre_n>[0-9]+)?
|
||||
)?
|
||||
(?P<post> # post release
|
||||
(?:-(?P<post_n1>[0-9]+))
|
||||
|
|
||||
(?:
|
||||
[-_\.]?
|
||||
(?P<post_l>post|rev|r)
|
||||
[-_\.]?
|
||||
(?P<post_n2>[0-9]+)?
|
||||
)
|
||||
)?
|
||||
(?P<dev> # dev release
|
||||
[-_\.]?
|
||||
(?P<dev_l>dev)
|
||||
[-_\.]?
|
||||
(?P<dev_n>[0-9]+)?
|
||||
)?
|
||||
)
|
||||
(?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version
|
||||
"""
|
||||
|
||||
|
||||
class Version(_BaseVersion):
|
||||
|
||||
_regex = re.compile(
|
||||
r"^\s*" + VERSION_PATTERN + r"\s*$",
|
||||
re.VERBOSE | re.IGNORECASE,
|
||||
)
|
||||
|
||||
def __init__(self, version):
|
||||
# Validate the version and parse it into pieces
|
||||
match = self._regex.search(version)
|
||||
if not match:
|
||||
raise InvalidVersion("Invalid version: '{0}'".format(version))
|
||||
|
||||
# Store the parsed out pieces of the version
|
||||
self._version = _Version(
|
||||
epoch=int(match.group("epoch")) if match.group("epoch") else 0,
|
||||
release=tuple(int(i) for i in match.group("release").split(".")),
|
||||
pre=_parse_letter_version(
|
||||
match.group("pre_l"),
|
||||
match.group("pre_n"),
|
||||
),
|
||||
post=_parse_letter_version(
|
||||
match.group("post_l"),
|
||||
match.group("post_n1") or match.group("post_n2"),
|
||||
),
|
||||
dev=_parse_letter_version(
|
||||
match.group("dev_l"),
|
||||
match.group("dev_n"),
|
||||
),
|
||||
local=_parse_local_version(match.group("local")),
|
||||
)
|
||||
|
||||
# Generate a key which will be used for sorting
|
||||
self._key = _cmpkey(
|
||||
self._version.epoch,
|
||||
self._version.release,
|
||||
self._version.pre,
|
||||
self._version.post,
|
||||
self._version.dev,
|
||||
self._version.local,
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return "<Version({0})>".format(repr(str(self)))
|
||||
|
||||
def __str__(self):
|
||||
parts = []
|
||||
|
||||
# Epoch
|
||||
if self._version.epoch != 0:
|
||||
parts.append("{0}!".format(self._version.epoch))
|
||||
|
||||
# Release segment
|
||||
parts.append(".".join(str(x) for x in self._version.release))
|
||||
|
||||
# Pre-release
|
||||
if self._version.pre is not None:
|
||||
parts.append("".join(str(x) for x in self._version.pre))
|
||||
|
||||
# Post-release
|
||||
if self._version.post is not None:
|
||||
parts.append(".post{0}".format(self._version.post[1]))
|
||||
|
||||
# Development release
|
||||
if self._version.dev is not None:
|
||||
parts.append(".dev{0}".format(self._version.dev[1]))
|
||||
|
||||
# Local version segment
|
||||
if self._version.local is not None:
|
||||
parts.append(
|
||||
"+{0}".format(".".join(str(x) for x in self._version.local))
|
||||
)
|
||||
|
||||
return "".join(parts)
|
||||
|
||||
@property
|
||||
def public(self):
|
||||
return str(self).split("+", 1)[0]
|
||||
|
||||
@property
|
||||
def base_version(self):
|
||||
parts = []
|
||||
|
||||
# Epoch
|
||||
if self._version.epoch != 0:
|
||||
parts.append("{0}!".format(self._version.epoch))
|
||||
|
||||
# Release segment
|
||||
parts.append(".".join(str(x) for x in self._version.release))
|
||||
|
||||
return "".join(parts)
|
||||
|
||||
@property
|
||||
def local(self):
|
||||
version_string = str(self)
|
||||
if "+" in version_string:
|
||||
return version_string.split("+", 1)[1]
|
||||
|
||||
@property
|
||||
def is_prerelease(self):
|
||||
return bool(self._version.dev or self._version.pre)
|
||||
|
||||
@property
|
||||
def is_postrelease(self):
|
||||
return bool(self._version.post)
|
||||
|
||||
|
||||
def _parse_letter_version(letter, number):
|
||||
if letter:
|
||||
# We assume there is an implicit 0 in a pre-release if there is
|
||||
# no numeral associated with it.
|
||||
if number is None:
|
||||
number = 0
|
||||
|
||||
# We normalize any letters to their lower-case form
|
||||
letter = letter.lower()
|
||||
|
||||
# We consider some words to be alternate spellings of other words and
|
||||
# in those cases we want to normalize the spellings to our preferred
|
||||
# spelling.
|
||||
if letter == "alpha":
|
||||
letter = "a"
|
||||
elif letter == "beta":
|
||||
letter = "b"
|
||||
elif letter in ["c", "pre", "preview"]:
|
||||
letter = "rc"
|
||||
elif letter in ["rev", "r"]:
|
||||
letter = "post"
|
||||
|
||||
return letter, int(number)
|
||||
if not letter and number:
|
||||
# We assume that if we are given a number but not given a letter,
|
||||
# then this is using the implicit post release syntax (e.g., 1.0-1)
|
||||
letter = "post"
|
||||
|
||||
return letter, int(number)
|
||||
|
||||
|
||||
_local_version_seperators = re.compile(r"[\._-]")
|
||||
|
||||
|
||||
def _parse_local_version(local):
|
||||
"""
|
||||
Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").
|
||||
"""
|
||||
if local is not None:
|
||||
return tuple(
|
||||
part.lower() if not part.isdigit() else int(part)
|
||||
for part in _local_version_seperators.split(local)
|
||||
)
|
||||
|
||||
|
||||
def _cmpkey(epoch, release, pre, post, dev, local):
|
||||
# When we compare a release version, we want to compare it with all of the
|
||||
# trailing zeros removed. So we'll use a reverse the list, drop all the now
|
||||
# leading zeros until we come to something non-zero, then take the rest,
|
||||
# re-reverse it back into the correct order, and make it a tuple and use
|
||||
# that for our sorting key.
|
||||
release = tuple(
|
||||
reversed(list(
|
||||
itertools.dropwhile(
|
||||
lambda x: x == 0,
|
||||
reversed(release),
|
||||
)
|
||||
))
|
||||
)
|
||||
|
||||
# We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0.
|
||||
# We'll do this by abusing the pre-segment, but we _only_ want to do this
|
||||
# if there is no pre- or a post-segment. If we have one of those, then
|
||||
# the normal sorting rules will handle this case correctly.
|
||||
if pre is None and post is None and dev is not None:
|
||||
pre = -Infinity
|
||||
# Versions without a pre-release (except as noted above) should sort after
|
||||
# those with one.
|
||||
elif pre is None:
|
||||
pre = Infinity
|
||||
|
||||
# Versions without a post-segment should sort before those with one.
|
||||
if post is None:
|
||||
post = -Infinity
|
||||
|
||||
# Versions without a development segment should sort after those with one.
|
||||
if dev is None:
|
||||
dev = Infinity
|
||||
|
||||
if local is None:
|
||||
# Versions without a local segment should sort before those with one.
|
||||
local = -Infinity
|
||||
else:
|
||||
# Versions with a local segment need that segment parsed to implement
|
||||
# the sorting rules in PEP440.
|
||||
# - Alphanumeric segments sort before numeric segments
|
||||
# - Alphanumeric segments sort lexicographically
|
||||
# - Numeric segments sort numerically
|
||||
# - Shorter versions sort before longer versions when the prefixes
|
||||
# match exactly
|
||||
local = tuple(
|
||||
(i, "") if isinstance(i, int) else (-Infinity, i)
|
||||
for i in local
|
||||
)
|
||||
|
||||
return epoch, release, pre, post, dev, local
|
||||
Vendored
+143
@@ -0,0 +1,143 @@
|
||||
"""
|
||||
Generic test utilities.
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
__all__ = ['PytestTester', 'check_free_memory']
|
||||
|
||||
|
||||
class FPUModeChangeWarning(RuntimeWarning):
|
||||
"""Warning about FPU mode change"""
|
||||
pass
|
||||
|
||||
|
||||
class PytestTester:
|
||||
"""
|
||||
Pytest test runner entry point.
|
||||
"""
|
||||
|
||||
def __init__(self, module_name):
|
||||
self.module_name = module_name
|
||||
|
||||
def __call__(self, label="fast", verbose=1, extra_argv=None, doctests=False,
|
||||
coverage=False, tests=None, parallel=None):
|
||||
import pytest
|
||||
|
||||
module = sys.modules[self.module_name]
|
||||
module_path = os.path.abspath(module.__path__[0])
|
||||
|
||||
pytest_args = ['--showlocals', '--tb=short']
|
||||
|
||||
if doctests:
|
||||
raise ValueError("Doctests not supported")
|
||||
|
||||
if extra_argv:
|
||||
pytest_args += list(extra_argv)
|
||||
|
||||
if verbose and int(verbose) > 1:
|
||||
pytest_args += ["-" + "v"*(int(verbose)-1)]
|
||||
|
||||
if coverage:
|
||||
pytest_args += ["--cov=" + module_path]
|
||||
|
||||
if label == "fast":
|
||||
pytest_args += ["-m", "not slow"]
|
||||
elif label != "full":
|
||||
pytest_args += ["-m", label]
|
||||
|
||||
if tests is None:
|
||||
tests = [self.module_name]
|
||||
|
||||
if parallel is not None and parallel > 1:
|
||||
if _pytest_has_xdist():
|
||||
pytest_args += ['-n', str(parallel)]
|
||||
else:
|
||||
import warnings
|
||||
warnings.warn('Could not run tests in parallel because '
|
||||
'pytest-xdist plugin is not available.')
|
||||
|
||||
pytest_args += ['--pyargs'] + list(tests)
|
||||
|
||||
try:
|
||||
code = pytest.main(pytest_args)
|
||||
except SystemExit as exc:
|
||||
code = exc.code
|
||||
|
||||
return (code == 0)
|
||||
|
||||
|
||||
def _pytest_has_xdist():
|
||||
"""
|
||||
Check if the pytest-xdist plugin is installed, providing parallel tests
|
||||
"""
|
||||
# Check xdist exists without importing, otherwise pytests emits warnings
|
||||
from importlib.util import find_spec
|
||||
return find_spec('xdist') is not None
|
||||
|
||||
|
||||
def check_free_memory(free_mb):
|
||||
"""
|
||||
Check *free_mb* of memory is available, otherwise do pytest.skip
|
||||
"""
|
||||
import pytest
|
||||
|
||||
try:
|
||||
mem_free = _parse_size(os.environ['SCIPY_AVAILABLE_MEM'])
|
||||
msg = '{0} MB memory required, but environment SCIPY_AVAILABLE_MEM={1}'.format(
|
||||
free_mb, os.environ['SCIPY_AVAILABLE_MEM'])
|
||||
except KeyError:
|
||||
mem_free = _get_mem_available()
|
||||
if mem_free is None:
|
||||
pytest.skip("Could not determine available memory; set SCIPY_AVAILABLE_MEM "
|
||||
"variable to free memory in MB to run the test.")
|
||||
msg = '{0} MB memory required, but {1} MB available'.format(
|
||||
free_mb, mem_free/1e6)
|
||||
|
||||
if mem_free < free_mb * 1e6:
|
||||
pytest.skip(msg)
|
||||
|
||||
|
||||
def _parse_size(size_str):
|
||||
suffixes = {'': 1e6,
|
||||
'b': 1.0,
|
||||
'k': 1e3, 'M': 1e6, 'G': 1e9, 'T': 1e12,
|
||||
'kb': 1e3, 'Mb': 1e6, 'Gb': 1e9, 'Tb': 1e12,
|
||||
'kib': 1024.0, 'Mib': 1024.0**2, 'Gib': 1024.0**3, 'Tib': 1024.0**4}
|
||||
m = re.match(r'^\s*(\d+)\s*({0})\s*$'.format('|'.join(suffixes.keys())),
|
||||
size_str,
|
||||
re.I)
|
||||
if not m or m.group(2) not in suffixes:
|
||||
raise ValueError("Invalid size string")
|
||||
|
||||
return float(m.group(1)) * suffixes[m.group(2)]
|
||||
|
||||
|
||||
def _get_mem_available():
|
||||
"""
|
||||
Get information about memory available, not counting swap.
|
||||
"""
|
||||
try:
|
||||
import psutil
|
||||
return psutil.virtual_memory().available
|
||||
except (ImportError, AttributeError):
|
||||
pass
|
||||
|
||||
if sys.platform.startswith('linux'):
|
||||
info = {}
|
||||
with open('/proc/meminfo', 'r') as f:
|
||||
for line in f:
|
||||
p = line.split()
|
||||
info[p[0].strip(':').lower()] = float(p[1]) * 1e3
|
||||
|
||||
if 'memavailable' in info:
|
||||
# Linux >= 3.14
|
||||
return info['memavailable']
|
||||
else:
|
||||
return info['memfree'] + info['cached']
|
||||
|
||||
return None
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
import threading
|
||||
|
||||
import scipy._lib.decorator
|
||||
|
||||
|
||||
__all__ = ['ReentrancyError', 'ReentrancyLock', 'non_reentrant']
|
||||
|
||||
|
||||
class ReentrancyError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
class ReentrancyLock:
|
||||
"""
|
||||
Threading lock that raises an exception for reentrant calls.
|
||||
|
||||
Calls from different threads are serialized, and nested calls from the
|
||||
same thread result to an error.
|
||||
|
||||
The object can be used as a context manager or to decorate functions
|
||||
via the decorate() method.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, err_msg):
|
||||
self._rlock = threading.RLock()
|
||||
self._entered = False
|
||||
self._err_msg = err_msg
|
||||
|
||||
def __enter__(self):
|
||||
self._rlock.acquire()
|
||||
if self._entered:
|
||||
self._rlock.release()
|
||||
raise ReentrancyError(self._err_msg)
|
||||
self._entered = True
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
self._entered = False
|
||||
self._rlock.release()
|
||||
|
||||
def decorate(self, func):
|
||||
def caller(func, *a, **kw):
|
||||
with self:
|
||||
return func(*a, **kw)
|
||||
return scipy._lib.decorator.decorate(func, caller)
|
||||
|
||||
|
||||
def non_reentrant(err_msg=None):
|
||||
"""
|
||||
Decorate a function with a threading lock and prevent reentrant calls.
|
||||
"""
|
||||
def decorator(func):
|
||||
msg = err_msg
|
||||
if msg is None:
|
||||
msg = "%s is not re-entrant" % func.__name__
|
||||
lock = ReentrancyLock(msg)
|
||||
return lock.decorate(func)
|
||||
return decorator
|
||||
Vendored
+86
@@ -0,0 +1,86 @@
|
||||
''' Contexts for *with* statement providing temporary directories
|
||||
'''
|
||||
import os
|
||||
from contextlib import contextmanager
|
||||
from shutil import rmtree
|
||||
from tempfile import mkdtemp
|
||||
|
||||
|
||||
@contextmanager
|
||||
def tempdir():
|
||||
"""Create and return a temporary directory. This has the same
|
||||
behavior as mkdtemp but can be used as a context manager.
|
||||
|
||||
Upon exiting the context, the directory and everything contained
|
||||
in it are removed.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import os
|
||||
>>> with tempdir() as tmpdir:
|
||||
... fname = os.path.join(tmpdir, 'example_file.txt')
|
||||
... with open(fname, 'wt') as fobj:
|
||||
... _ = fobj.write('a string\\n')
|
||||
>>> os.path.exists(tmpdir)
|
||||
False
|
||||
"""
|
||||
d = mkdtemp()
|
||||
yield d
|
||||
rmtree(d)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def in_tempdir():
|
||||
''' Create, return, and change directory to a temporary directory
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import os
|
||||
>>> my_cwd = os.getcwd()
|
||||
>>> with in_tempdir() as tmpdir:
|
||||
... _ = open('test.txt', 'wt').write('some text')
|
||||
... assert os.path.isfile('test.txt')
|
||||
... assert os.path.isfile(os.path.join(tmpdir, 'test.txt'))
|
||||
>>> os.path.exists(tmpdir)
|
||||
False
|
||||
>>> os.getcwd() == my_cwd
|
||||
True
|
||||
'''
|
||||
pwd = os.getcwd()
|
||||
d = mkdtemp()
|
||||
os.chdir(d)
|
||||
yield d
|
||||
os.chdir(pwd)
|
||||
rmtree(d)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def in_dir(dir=None):
|
||||
""" Change directory to given directory for duration of ``with`` block
|
||||
|
||||
Useful when you want to use `in_tempdir` for the final test, but
|
||||
you are still debugging. For example, you may want to do this in the end:
|
||||
|
||||
>>> with in_tempdir() as tmpdir:
|
||||
... # do something complicated which might break
|
||||
... pass
|
||||
|
||||
But, indeed, the complicated thing does break, and meanwhile, the
|
||||
``in_tempdir`` context manager wiped out the directory with the
|
||||
temporary files that you wanted for debugging. So, while debugging, you
|
||||
replace with something like:
|
||||
|
||||
>>> with in_dir() as tmpdir: # Use working directory by default
|
||||
... # do something complicated which might break
|
||||
... pass
|
||||
|
||||
You can then look at the temporary file outputs to debug what is happening,
|
||||
fix, and finally replace ``in_dir`` with ``in_tempdir`` again.
|
||||
"""
|
||||
cwd = os.getcwd()
|
||||
if dir is None:
|
||||
yield cwd
|
||||
return
|
||||
os.chdir(dir)
|
||||
yield dir
|
||||
os.chdir(cwd)
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2018, Quansight-Labs
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
"""
|
||||
.. note::
|
||||
If you are looking for overrides for NumPy-specific methods, see the
|
||||
documentation for :obj:`unumpy`. This page explains how to write
|
||||
back-ends and multimethods.
|
||||
|
||||
``uarray`` is built around a back-end protocol and overridable multimethods.
|
||||
It is necessary to define multimethods for back-ends to be able to override them.
|
||||
See the documentation of :obj:`generate_multimethod` on how to write multimethods.
|
||||
|
||||
|
||||
|
||||
Let's start with the simplest:
|
||||
|
||||
``__ua_domain__`` defines the back-end *domain*. The domain consists of period-
|
||||
separated string consisting of the modules you extend plus the submodule. For
|
||||
example, if a submodule ``module2.submodule`` extends ``module1``
|
||||
(i.e., it exposes dispatchables marked as types available in ``module1``),
|
||||
then the domain string should be ``"module1.module2.submodule"``.
|
||||
|
||||
|
||||
For the purpose of this demonstration, we'll be creating an object and setting
|
||||
its attributes directly. However, note that you can use a module or your own type
|
||||
as a backend as well.
|
||||
|
||||
>>> class Backend: pass
|
||||
>>> be = Backend()
|
||||
>>> be.__ua_domain__ = "ua_examples"
|
||||
|
||||
It might be useful at this point to sidetrack to the documentation of
|
||||
:obj:`generate_multimethod` to find out how to generate a multimethod
|
||||
overridable by :obj:`uarray`. Needless to say, writing a backend and
|
||||
creating multimethods are mostly orthogonal activities, and knowing
|
||||
one doesn't necessarily require knowledge of the other, although it
|
||||
is certainly helpful. We expect core API designers/specifiers to write the
|
||||
multimethods, and implementors to override them. But, as is often the case,
|
||||
similar people write both.
|
||||
|
||||
Without further ado, here's an example multimethod:
|
||||
|
||||
>>> import uarray as ua
|
||||
>>> from uarray import Dispatchable
|
||||
>>> def override_me(a, b):
|
||||
... return Dispatchable(a, int),
|
||||
>>> def override_replacer(args, kwargs, dispatchables):
|
||||
... return (dispatchables[0], args[1]), {}
|
||||
>>> overridden_me = ua.generate_multimethod(
|
||||
... override_me, override_replacer, "ua_examples"
|
||||
... )
|
||||
|
||||
Next comes the part about overriding the multimethod. This requires
|
||||
the ``__ua_function__`` protocol, and the ``__ua_convert__``
|
||||
protocol. The ``__ua_function__`` protocol has the signature
|
||||
``(method, args, kwargs)`` where ``method`` is the passed
|
||||
multimethod, ``args``/``kwargs`` specify the arguments and ``dispatchables``
|
||||
is the list of converted dispatchables passed in.
|
||||
|
||||
>>> def __ua_function__(method, args, kwargs):
|
||||
... return method.__name__, args, kwargs
|
||||
>>> be.__ua_function__ = __ua_function__
|
||||
|
||||
The other protocol of interest is the ``__ua_convert__`` protocol. It has the
|
||||
signature ``(dispatchables, coerce)``. When ``coerce`` is ``False``, conversion
|
||||
between the formats should ideally be an ``O(1)`` operation, but it means that
|
||||
no memory copying should be involved, only views of the existing data.
|
||||
|
||||
>>> def __ua_convert__(dispatchables, coerce):
|
||||
... for d in dispatchables:
|
||||
... if d.type is int:
|
||||
... if coerce and d.coercible:
|
||||
... yield str(d.value)
|
||||
... else:
|
||||
... yield d.value
|
||||
>>> be.__ua_convert__ = __ua_convert__
|
||||
|
||||
Now that we have defined the backend, the next thing to do is to call the multimethod.
|
||||
|
||||
>>> with ua.set_backend(be):
|
||||
... overridden_me(1, "2")
|
||||
('override_me', (1, '2'), {})
|
||||
|
||||
Note that the marked type has no effect on the actual type of the passed object.
|
||||
We can also coerce the type of the input.
|
||||
|
||||
>>> with ua.set_backend(be, coerce=True):
|
||||
... overridden_me(1, "2")
|
||||
... overridden_me(1.0, "2")
|
||||
('override_me', ('1', '2'), {})
|
||||
('override_me', ('1.0', '2'), {})
|
||||
|
||||
Another feature is that if you remove ``__ua_convert__``, the arguments are not
|
||||
converted at all and it's up to the backend to handle that.
|
||||
|
||||
>>> del be.__ua_convert__
|
||||
>>> with ua.set_backend(be):
|
||||
... overridden_me(1, "2")
|
||||
('override_me', (1, '2'), {})
|
||||
|
||||
You also have the option to return ``NotImplemented``, in which case processing moves on
|
||||
to the next back-end, which, in this case, doesn't exist. The same applies to
|
||||
``__ua_convert__``.
|
||||
|
||||
>>> be.__ua_function__ = lambda *a, **kw: NotImplemented
|
||||
>>> with ua.set_backend(be):
|
||||
... overridden_me(1, "2")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
uarray.backend.BackendNotImplementedError: ...
|
||||
|
||||
The last possibility is if we don't have ``__ua_convert__``, in which case the job is left
|
||||
up to ``__ua_function__``, but putting things back into arrays after conversion will not be
|
||||
possible.
|
||||
"""
|
||||
|
||||
from ._backend import *
|
||||
|
||||
__version__ = '0.5.1+49.g4c3f1d7.scipy'
|
||||
+425
@@ -0,0 +1,425 @@
|
||||
import typing
|
||||
import inspect
|
||||
import functools
|
||||
from . import _uarray # type: ignore
|
||||
import copyreg # type: ignore
|
||||
import atexit
|
||||
import pickle
|
||||
|
||||
ArgumentExtractorType = typing.Callable[..., typing.Tuple["Dispatchable", ...]]
|
||||
ArgumentReplacerType = typing.Callable[
|
||||
[typing.Tuple, typing.Dict, typing.Tuple], typing.Tuple[typing.Tuple, typing.Dict]
|
||||
]
|
||||
|
||||
from ._uarray import ( # type: ignore
|
||||
BackendNotImplementedError,
|
||||
_Function,
|
||||
_SkipBackendContext,
|
||||
_SetBackendContext,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"set_backend",
|
||||
"set_global_backend",
|
||||
"skip_backend",
|
||||
"register_backend",
|
||||
"clear_backends",
|
||||
"create_multimethod",
|
||||
"generate_multimethod",
|
||||
"_Function",
|
||||
"BackendNotImplementedError",
|
||||
"Dispatchable",
|
||||
"wrap_single_convertor",
|
||||
"all_of_type",
|
||||
"mark_as",
|
||||
]
|
||||
|
||||
|
||||
def unpickle_function(mod_name, qname):
|
||||
import importlib
|
||||
|
||||
try:
|
||||
module = importlib.import_module(mod_name)
|
||||
func = getattr(module, qname)
|
||||
return func
|
||||
except (ImportError, AttributeError) as e:
|
||||
from pickle import UnpicklingError
|
||||
|
||||
raise UnpicklingError from e
|
||||
|
||||
|
||||
def pickle_function(func):
|
||||
mod_name = getattr(func, "__module__", None)
|
||||
qname = getattr(func, "__qualname__", None)
|
||||
|
||||
try:
|
||||
test = unpickle_function(mod_name, qname)
|
||||
except pickle.UnpicklingError:
|
||||
test = None
|
||||
|
||||
if test is not func:
|
||||
raise pickle.PicklingError(
|
||||
"Can't pickle {}: it's not the same object as {}".format(func, test)
|
||||
)
|
||||
|
||||
return unpickle_function, (mod_name, qname)
|
||||
|
||||
|
||||
copyreg.pickle(_Function, pickle_function)
|
||||
atexit.register(_uarray.clear_all_globals)
|
||||
|
||||
|
||||
def create_multimethod(*args, **kwargs):
|
||||
"""
|
||||
Creates a decorator for generating multimethods.
|
||||
|
||||
This function creates a decorator that can be used with an argument
|
||||
extractor in order to generate a multimethod. Other than for the
|
||||
argument extractor, all arguments are passed on to
|
||||
:obj:`generate_multimethod`.
|
||||
|
||||
See Also
|
||||
--------
|
||||
generate_multimethod : Generates a multimethod.
|
||||
"""
|
||||
|
||||
def wrapper(a):
|
||||
return generate_multimethod(a, *args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def generate_multimethod(
|
||||
argument_extractor: ArgumentExtractorType,
|
||||
argument_replacer: ArgumentReplacerType,
|
||||
domain: str,
|
||||
default: typing.Optional[typing.Callable] = None
|
||||
):
|
||||
"""
|
||||
Generates a multimethod.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
argument_extractor : ArgumentExtractorType
|
||||
A callable which extracts the dispatchable arguments. Extracted arguments
|
||||
should be marked by the :obj:`Dispatchable` class. It has the same signature
|
||||
as the desired multimethod.
|
||||
argument_replacer : ArgumentReplacerType
|
||||
A callable with the signature (args, kwargs, dispatchables), which should also
|
||||
return an (args, kwargs) pair with the dispatchables replaced inside the args/kwargs.
|
||||
domain : str
|
||||
A string value indicating the domain of this multimethod.
|
||||
default : Optional[Callable], optional
|
||||
The default implementation of this multimethod, where ``None`` (the default) specifies
|
||||
there is no default implementation.
|
||||
|
||||
Examples
|
||||
--------
|
||||
In this example, ``a`` is to be dispatched over, so we return it, while marking it as an ``int``.
|
||||
The trailing comma is needed because the args have to be returned as an iterable.
|
||||
|
||||
>>> def override_me(a, b):
|
||||
... return Dispatchable(a, int),
|
||||
|
||||
Next, we define the argument replacer that replaces the dispatchables inside args/kwargs with the
|
||||
supplied ones.
|
||||
|
||||
>>> def override_replacer(args, kwargs, dispatchables):
|
||||
... return (dispatchables[0], args[1]), {}
|
||||
|
||||
Next, we define the multimethod.
|
||||
|
||||
>>> overridden_me = generate_multimethod(
|
||||
... override_me, override_replacer, "ua_examples"
|
||||
... )
|
||||
|
||||
Notice that there's no default implementation, unless you supply one.
|
||||
|
||||
>>> overridden_me(1, "a")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
uarray.backend.BackendNotImplementedError: ...
|
||||
>>> overridden_me2 = generate_multimethod(
|
||||
... override_me, override_replacer, "ua_examples", default=lambda x, y: (x, y)
|
||||
... )
|
||||
>>> overridden_me2(1, "a")
|
||||
(1, 'a')
|
||||
|
||||
See Also
|
||||
--------
|
||||
uarray :
|
||||
See the module documentation for how to override the method by creating backends.
|
||||
"""
|
||||
kw_defaults, arg_defaults, opts = get_defaults(argument_extractor)
|
||||
ua_func = _Function(
|
||||
argument_extractor,
|
||||
argument_replacer,
|
||||
domain,
|
||||
arg_defaults,
|
||||
kw_defaults,
|
||||
default,
|
||||
)
|
||||
|
||||
return functools.update_wrapper(ua_func, argument_extractor)
|
||||
|
||||
|
||||
def set_backend(backend, coerce=False, only=False):
|
||||
"""
|
||||
A context manager that sets the preferred backend.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
backend
|
||||
The backend to set.
|
||||
coerce
|
||||
Whether or not to coerce to a specific backend's types. Implies ``only``.
|
||||
only
|
||||
Whether or not this should be the last backend to try.
|
||||
|
||||
See Also
|
||||
--------
|
||||
skip_backend : A context manager that allows skipping of backends.
|
||||
set_global_backend : Set a single, global backend for a domain.
|
||||
"""
|
||||
try:
|
||||
return backend.__ua_cache__["set", coerce, only]
|
||||
except AttributeError:
|
||||
backend.__ua_cache__ = {}
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
ctx = _SetBackendContext(backend, coerce, only)
|
||||
backend.__ua_cache__["set", coerce, only] = ctx
|
||||
return ctx
|
||||
|
||||
|
||||
def skip_backend(backend):
|
||||
"""
|
||||
A context manager that allows one to skip a given backend from processing
|
||||
entirely. This allows one to use another backend's code in a library that
|
||||
is also a consumer of the same backend.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
backend
|
||||
The backend to skip.
|
||||
|
||||
See Also
|
||||
--------
|
||||
set_backend : A context manager that allows setting of backends.
|
||||
set_global_backend : Set a single, global backend for a domain.
|
||||
"""
|
||||
try:
|
||||
return backend.__ua_cache__["skip"]
|
||||
except AttributeError:
|
||||
backend.__ua_cache__ = {}
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
ctx = _SkipBackendContext(backend)
|
||||
backend.__ua_cache__["skip"] = ctx
|
||||
return ctx
|
||||
|
||||
|
||||
def get_defaults(f):
|
||||
sig = inspect.signature(f)
|
||||
kw_defaults = {}
|
||||
arg_defaults = []
|
||||
opts = set()
|
||||
for k, v in sig.parameters.items():
|
||||
if v.default is not inspect.Parameter.empty:
|
||||
kw_defaults[k] = v.default
|
||||
if v.kind in (
|
||||
inspect.Parameter.POSITIONAL_ONLY,
|
||||
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
||||
):
|
||||
arg_defaults.append(v.default)
|
||||
opts.add(k)
|
||||
|
||||
return kw_defaults, tuple(arg_defaults), opts
|
||||
|
||||
|
||||
def set_global_backend(backend, coerce=False, only=False):
|
||||
"""
|
||||
This utility method replaces the default backend for permanent use. It
|
||||
will be tried in the list of backends automatically, unless the
|
||||
``only`` flag is set on a backend. This will be the first tried
|
||||
backend outside the :obj:`set_backend` context manager.
|
||||
|
||||
Note that this method is not thread-safe.
|
||||
|
||||
.. warning::
|
||||
We caution library authors against using this function in
|
||||
their code. We do *not* support this use-case. This function
|
||||
is meant to be used only by users themselves, or by a reference
|
||||
implementation, if one exists.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
backend
|
||||
The backend to register.
|
||||
|
||||
See Also
|
||||
--------
|
||||
set_backend : A context manager that allows setting of backends.
|
||||
skip_backend : A context manager that allows skipping of backends.
|
||||
"""
|
||||
_uarray.set_global_backend(backend, coerce, only)
|
||||
|
||||
|
||||
def register_backend(backend):
|
||||
"""
|
||||
This utility method sets registers backend for permanent use. It
|
||||
will be tried in the list of backends automatically, unless the
|
||||
``only`` flag is set on a backend.
|
||||
|
||||
Note that this method is not thread-safe.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
backend
|
||||
The backend to register.
|
||||
"""
|
||||
_uarray.register_backend(backend)
|
||||
|
||||
|
||||
def clear_backends(domain, registered=True, globals=False):
|
||||
"""
|
||||
This utility method clears registered backends.
|
||||
|
||||
.. warning::
|
||||
We caution library authors against using this function in
|
||||
their code. We do *not* support this use-case. This function
|
||||
is meant to be used only by the users themselves.
|
||||
|
||||
.. warning::
|
||||
Do NOT use this method inside a multimethod call, or the
|
||||
program is likely to crash.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
domain : Optional[str]
|
||||
The domain for which to de-register backends. ``None`` means
|
||||
de-register for all domains.
|
||||
registered : bool
|
||||
Whether or not to clear registered backends. See :obj:`register_backend`.
|
||||
globals : bool
|
||||
Whether or not to clear global backends. See :obj:`set_global_backend`.
|
||||
|
||||
See Also
|
||||
--------
|
||||
register_backend : Register a backend globally.
|
||||
set_global_backend : Set a global backend.
|
||||
"""
|
||||
_uarray.clear_backends(domain, registered, globals)
|
||||
|
||||
|
||||
class Dispatchable:
|
||||
"""
|
||||
A utility class which marks an argument with a specific dispatch type.
|
||||
|
||||
|
||||
Attributes
|
||||
----------
|
||||
value
|
||||
The value of the Dispatchable.
|
||||
|
||||
type
|
||||
The type of the Dispatchable.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> x = Dispatchable(1, str)
|
||||
>>> x
|
||||
<Dispatchable: type=<class 'str'>, value=1>
|
||||
|
||||
See Also
|
||||
--------
|
||||
all_of_type
|
||||
Marks all unmarked parameters of a function.
|
||||
|
||||
mark_as
|
||||
Allows one to create a utility function to mark as a given type.
|
||||
"""
|
||||
|
||||
def __init__(self, value, dispatch_type, coercible=True):
|
||||
self.value = value
|
||||
self.type = dispatch_type
|
||||
self.coercible = coercible
|
||||
|
||||
def __getitem__(self, index):
|
||||
return (self.type, self.value)[index]
|
||||
|
||||
def __str__(self):
|
||||
return "<{0}: type={1!r}, value={2!r}>".format(
|
||||
type(self).__name__, self.type, self.value
|
||||
)
|
||||
|
||||
__repr__ = __str__
|
||||
|
||||
|
||||
def mark_as(dispatch_type):
|
||||
"""
|
||||
Creates a utility function to mark something as a specific type.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> mark_int = mark_as(int)
|
||||
>>> mark_int(1)
|
||||
<Dispatchable: type=<class 'int'>, value=1>
|
||||
"""
|
||||
return functools.partial(Dispatchable, dispatch_type=dispatch_type)
|
||||
|
||||
|
||||
def all_of_type(arg_type):
|
||||
"""
|
||||
Marks all unmarked arguments as a given type.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> @all_of_type(str)
|
||||
... def f(a, b):
|
||||
... return a, Dispatchable(b, int)
|
||||
>>> f('a', 1)
|
||||
(<Dispatchable: type=<class 'str'>, value='a'>, <Dispatchable: type=<class 'int'>, value=1>)
|
||||
"""
|
||||
|
||||
def outer(func):
|
||||
@functools.wraps(func)
|
||||
def inner(*args, **kwargs):
|
||||
extracted_args = func(*args, **kwargs)
|
||||
return tuple(
|
||||
Dispatchable(arg, arg_type)
|
||||
if not isinstance(arg, Dispatchable)
|
||||
else arg
|
||||
for arg in extracted_args
|
||||
)
|
||||
|
||||
return inner
|
||||
|
||||
return outer
|
||||
|
||||
|
||||
def wrap_single_convertor(convert_single):
|
||||
"""
|
||||
Wraps a ``__ua_convert__`` defined for a single element to all elements.
|
||||
If any of them return ``NotImplemented``, the operation is assumed to be
|
||||
undefined.
|
||||
|
||||
Accepts a signature of (value, type, coerce).
|
||||
"""
|
||||
|
||||
@functools.wraps(convert_single)
|
||||
def __ua_convert__(dispatchables, coerce):
|
||||
converted = []
|
||||
for d in dispatchables:
|
||||
c = convert_single(d.value, d.type, coerce and d.coercible)
|
||||
|
||||
if c is NotImplemented:
|
||||
return NotImplemented
|
||||
|
||||
converted.append(c)
|
||||
|
||||
return converted
|
||||
|
||||
return __ua_convert__
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
|
||||
def pre_build_hook(build_ext, ext):
|
||||
from scipy._build_utils.compiler_helper import (
|
||||
set_cxx_flags_hook, try_add_flag)
|
||||
cc = build_ext._cxx_compiler
|
||||
args = ext.extra_compile_args
|
||||
|
||||
set_cxx_flags_hook(build_ext, ext)
|
||||
|
||||
if cc.compiler_type == 'msvc':
|
||||
args.append('/EHsc')
|
||||
else:
|
||||
try_add_flag(args, cc, '-fvisibility=hidden')
|
||||
|
||||
|
||||
def configuration(parent_package='', top_path=None):
|
||||
from numpy.distutils.misc_util import Configuration
|
||||
|
||||
config = Configuration('_uarray', parent_package, top_path)
|
||||
config.add_data_files('LICENSE')
|
||||
ext = config.add_extension('_uarray',
|
||||
sources=['_uarray_dispatch.cxx'],
|
||||
language='c++')
|
||||
ext._pre_build_hook = pre_build_hook
|
||||
return config
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from numpy.distutils.core import setup
|
||||
setup(**configuration(top_path='').todict())
|
||||
Vendored
+550
@@ -0,0 +1,550 @@
|
||||
from contextlib import contextmanager
|
||||
import functools
|
||||
import operator
|
||||
import sys
|
||||
import warnings
|
||||
import numbers
|
||||
from collections import namedtuple
|
||||
import inspect
|
||||
import math
|
||||
from typing import (
|
||||
Optional,
|
||||
Union,
|
||||
TYPE_CHECKING,
|
||||
TypeVar,
|
||||
)
|
||||
|
||||
import numpy as np
|
||||
|
||||
IntNumber = Union[int, np.integer]
|
||||
DecimalNumber = Union[float, np.floating, np.integer]
|
||||
|
||||
# Since Generator was introduced in numpy 1.17, the following condition is needed for
|
||||
# backward compatibility
|
||||
if TYPE_CHECKING:
|
||||
SeedType = Optional[Union[IntNumber, np.random.Generator,
|
||||
np.random.RandomState]]
|
||||
GeneratorType = TypeVar("GeneratorType", bound=Union[np.random.Generator,
|
||||
np.random.RandomState])
|
||||
|
||||
try:
|
||||
from numpy.random import Generator as Generator
|
||||
except ImportError:
|
||||
class Generator(): # type: ignore[no-redef]
|
||||
pass
|
||||
|
||||
|
||||
def _lazywhere(cond, arrays, f, fillvalue=None, f2=None):
|
||||
"""
|
||||
np.where(cond, x, fillvalue) always evaluates x even where cond is False.
|
||||
This one only evaluates f(arr1[cond], arr2[cond], ...).
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> a, b = np.array([1, 2, 3, 4]), np.array([5, 6, 7, 8])
|
||||
>>> def f(a, b):
|
||||
... return a*b
|
||||
>>> _lazywhere(a > 2, (a, b), f, np.nan)
|
||||
array([ nan, nan, 21., 32.])
|
||||
|
||||
Notice, it assumes that all `arrays` are of the same shape, or can be
|
||||
broadcasted together.
|
||||
|
||||
"""
|
||||
cond = np.asarray(cond)
|
||||
if fillvalue is None:
|
||||
if f2 is None:
|
||||
raise ValueError("One of (fillvalue, f2) must be given.")
|
||||
else:
|
||||
fillvalue = np.nan
|
||||
else:
|
||||
if f2 is not None:
|
||||
raise ValueError("Only one of (fillvalue, f2) can be given.")
|
||||
|
||||
args = np.broadcast_arrays(cond, *arrays)
|
||||
cond, arrays = args[0], args[1:]
|
||||
temp = tuple(np.extract(cond, arr) for arr in arrays)
|
||||
tcode = np.mintypecode([a.dtype.char for a in arrays])
|
||||
out = np.full(np.shape(arrays[0]), fill_value=fillvalue, dtype=tcode)
|
||||
np.place(out, cond, f(*temp))
|
||||
if f2 is not None:
|
||||
temp = tuple(np.extract(~cond, arr) for arr in arrays)
|
||||
np.place(out, ~cond, f2(*temp))
|
||||
|
||||
return out
|
||||
|
||||
|
||||
def _lazyselect(condlist, choicelist, arrays, default=0):
|
||||
"""
|
||||
Mimic `np.select(condlist, choicelist)`.
|
||||
|
||||
Notice, it assumes that all `arrays` are of the same shape or can be
|
||||
broadcasted together.
|
||||
|
||||
All functions in `choicelist` must accept array arguments in the order
|
||||
given in `arrays` and must return an array of the same shape as broadcasted
|
||||
`arrays`.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> x = np.arange(6)
|
||||
>>> np.select([x <3, x > 3], [x**2, x**3], default=0)
|
||||
array([ 0, 1, 4, 0, 64, 125])
|
||||
|
||||
>>> _lazyselect([x < 3, x > 3], [lambda x: x**2, lambda x: x**3], (x,))
|
||||
array([ 0., 1., 4., 0., 64., 125.])
|
||||
|
||||
>>> a = -np.ones_like(x)
|
||||
>>> _lazyselect([x < 3, x > 3],
|
||||
... [lambda x, a: x**2, lambda x, a: a * x**3],
|
||||
... (x, a), default=np.nan)
|
||||
array([ 0., 1., 4., nan, -64., -125.])
|
||||
|
||||
"""
|
||||
arrays = np.broadcast_arrays(*arrays)
|
||||
tcode = np.mintypecode([a.dtype.char for a in arrays])
|
||||
out = np.full(np.shape(arrays[0]), fill_value=default, dtype=tcode)
|
||||
for index in range(len(condlist)):
|
||||
func, cond = choicelist[index], condlist[index]
|
||||
if np.all(cond is False):
|
||||
continue
|
||||
cond, _ = np.broadcast_arrays(cond, arrays[0])
|
||||
temp = tuple(np.extract(cond, arr) for arr in arrays)
|
||||
np.place(out, cond, func(*temp))
|
||||
return out
|
||||
|
||||
|
||||
def _aligned_zeros(shape, dtype=float, order="C", align=None):
|
||||
"""Allocate a new ndarray with aligned memory.
|
||||
|
||||
Primary use case for this currently is working around a f2py issue
|
||||
in NumPy 1.9.1, where dtype.alignment is such that np.zeros() does
|
||||
not necessarily create arrays aligned up to it.
|
||||
|
||||
"""
|
||||
dtype = np.dtype(dtype)
|
||||
if align is None:
|
||||
align = dtype.alignment
|
||||
if not hasattr(shape, '__len__'):
|
||||
shape = (shape,)
|
||||
size = functools.reduce(operator.mul, shape) * dtype.itemsize
|
||||
buf = np.empty(size + align + 1, np.uint8)
|
||||
offset = buf.__array_interface__['data'][0] % align
|
||||
if offset != 0:
|
||||
offset = align - offset
|
||||
# Note: slices producing 0-size arrays do not necessarily change
|
||||
# data pointer --- so we use and allocate size+1
|
||||
buf = buf[offset:offset+size+1][:-1]
|
||||
data = np.ndarray(shape, dtype, buf, order=order)
|
||||
data.fill(0)
|
||||
return data
|
||||
|
||||
|
||||
def _prune_array(array):
|
||||
"""Return an array equivalent to the input array. If the input
|
||||
array is a view of a much larger array, copy its contents to a
|
||||
newly allocated array. Otherwise, return the input unchanged.
|
||||
"""
|
||||
if array.base is not None and array.size < array.base.size // 2:
|
||||
return array.copy()
|
||||
return array
|
||||
|
||||
|
||||
def prod(iterable):
|
||||
"""
|
||||
Product of a sequence of numbers.
|
||||
|
||||
Faster than np.prod for short lists like array shapes, and does
|
||||
not overflow if using Python integers.
|
||||
"""
|
||||
product = 1
|
||||
for x in iterable:
|
||||
product *= x
|
||||
return product
|
||||
|
||||
|
||||
def float_factorial(n: int) -> float:
|
||||
"""Compute the factorial and return as a float
|
||||
|
||||
Returns infinity when result is too large for a double
|
||||
"""
|
||||
return float(math.factorial(n)) if n < 171 else np.inf
|
||||
|
||||
|
||||
class DeprecatedImport:
|
||||
"""
|
||||
Deprecated import with redirection and warning.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Suppose you previously had in some module::
|
||||
|
||||
from foo import spam
|
||||
|
||||
If this has to be deprecated, do::
|
||||
|
||||
spam = DeprecatedImport("foo.spam", "baz")
|
||||
|
||||
to redirect users to use "baz" module instead.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, old_module_name, new_module_name):
|
||||
self._old_name = old_module_name
|
||||
self._new_name = new_module_name
|
||||
__import__(self._new_name)
|
||||
self._mod = sys.modules[self._new_name]
|
||||
|
||||
def __dir__(self):
|
||||
return dir(self._mod)
|
||||
|
||||
def __getattr__(self, name):
|
||||
warnings.warn("Module %s is deprecated, use %s instead"
|
||||
% (self._old_name, self._new_name),
|
||||
DeprecationWarning)
|
||||
return getattr(self._mod, name)
|
||||
|
||||
|
||||
# copy-pasted from scikit-learn utils/validation.py
|
||||
# change this to scipy.stats._qmc.check_random_state once numpy 1.16 is dropped
|
||||
def check_random_state(seed):
|
||||
"""Turn `seed` into a `np.random.RandomState` instance.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
seed : {None, int, `numpy.random.Generator`,
|
||||
`numpy.random.RandomState`}, optional
|
||||
|
||||
If `seed` is None (or `np.random`), the `numpy.random.RandomState`
|
||||
singleton is used.
|
||||
If `seed` is an int, a new ``RandomState`` instance is used,
|
||||
seeded with `seed`.
|
||||
If `seed` is already a ``Generator`` or ``RandomState`` instance then
|
||||
that instance is used.
|
||||
|
||||
Returns
|
||||
-------
|
||||
seed : {`numpy.random.Generator`, `numpy.random.RandomState`}
|
||||
Random number generator.
|
||||
|
||||
"""
|
||||
if seed is None or seed is np.random:
|
||||
return np.random.mtrand._rand
|
||||
if isinstance(seed, (numbers.Integral, np.integer)):
|
||||
return np.random.RandomState(seed)
|
||||
if isinstance(seed, np.random.RandomState):
|
||||
return seed
|
||||
try:
|
||||
# Generator is only available in numpy >= 1.17
|
||||
if isinstance(seed, np.random.Generator):
|
||||
return seed
|
||||
except AttributeError:
|
||||
pass
|
||||
raise ValueError('%r cannot be used to seed a numpy.random.RandomState'
|
||||
' instance' % seed)
|
||||
|
||||
|
||||
def _asarray_validated(a, check_finite=True,
|
||||
sparse_ok=False, objects_ok=False, mask_ok=False,
|
||||
as_inexact=False):
|
||||
"""
|
||||
Helper function for SciPy argument validation.
|
||||
|
||||
Many SciPy linear algebra functions do support arbitrary array-like
|
||||
input arguments. Examples of commonly unsupported inputs include
|
||||
matrices containing inf/nan, sparse matrix representations, and
|
||||
matrices with complicated elements.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
a : array_like
|
||||
The array-like input.
|
||||
check_finite : bool, optional
|
||||
Whether to check that the input matrices contain only finite numbers.
|
||||
Disabling may give a performance gain, but may result in problems
|
||||
(crashes, non-termination) if the inputs do contain infinities or NaNs.
|
||||
Default: True
|
||||
sparse_ok : bool, optional
|
||||
True if scipy sparse matrices are allowed.
|
||||
objects_ok : bool, optional
|
||||
True if arrays with dype('O') are allowed.
|
||||
mask_ok : bool, optional
|
||||
True if masked arrays are allowed.
|
||||
as_inexact : bool, optional
|
||||
True to convert the input array to a np.inexact dtype.
|
||||
|
||||
Returns
|
||||
-------
|
||||
ret : ndarray
|
||||
The converted validated array.
|
||||
|
||||
"""
|
||||
if not sparse_ok:
|
||||
import scipy.sparse
|
||||
if scipy.sparse.issparse(a):
|
||||
msg = ('Sparse matrices are not supported by this function. '
|
||||
'Perhaps one of the scipy.sparse.linalg functions '
|
||||
'would work instead.')
|
||||
raise ValueError(msg)
|
||||
if not mask_ok:
|
||||
if np.ma.isMaskedArray(a):
|
||||
raise ValueError('masked arrays are not supported')
|
||||
toarray = np.asarray_chkfinite if check_finite else np.asarray
|
||||
a = toarray(a)
|
||||
if not objects_ok:
|
||||
if a.dtype is np.dtype('O'):
|
||||
raise ValueError('object arrays are not supported')
|
||||
if as_inexact:
|
||||
if not np.issubdtype(a.dtype, np.inexact):
|
||||
a = toarray(a, dtype=np.float_)
|
||||
return a
|
||||
|
||||
|
||||
def _validate_int(k, name, minimum=None):
|
||||
"""
|
||||
Validate a scalar integer.
|
||||
|
||||
This functon can be used to validate an argument to a function
|
||||
that expects the value to be an integer. It uses `operator.index`
|
||||
to validate the value (so, for example, k=2.0 results in a
|
||||
TypeError).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
k : int
|
||||
The value to be validated.
|
||||
name : str
|
||||
The name of the parameter.
|
||||
minimum : int, optional
|
||||
An optional lower bound.
|
||||
"""
|
||||
try:
|
||||
k = operator.index(k)
|
||||
except TypeError:
|
||||
raise TypeError(f'{name} must be an integer.') from None
|
||||
if minimum is not None and k < minimum:
|
||||
raise ValueError(f'{name} must be an integer not less '
|
||||
f'than {minimum}') from None
|
||||
return k
|
||||
|
||||
|
||||
# Add a replacement for inspect.getfullargspec()/
|
||||
# The version below is borrowed from Django,
|
||||
# https://github.com/django/django/pull/4846.
|
||||
|
||||
# Note an inconsistency between inspect.getfullargspec(func) and
|
||||
# inspect.signature(func). If `func` is a bound method, the latter does *not*
|
||||
# list `self` as a first argument, while the former *does*.
|
||||
# Hence, cook up a common ground replacement: `getfullargspec_no_self` which
|
||||
# mimics `inspect.getfullargspec` but does not list `self`.
|
||||
#
|
||||
# This way, the caller code does not need to know whether it uses a legacy
|
||||
# .getfullargspec or a bright and shiny .signature.
|
||||
|
||||
FullArgSpec = namedtuple('FullArgSpec',
|
||||
['args', 'varargs', 'varkw', 'defaults',
|
||||
'kwonlyargs', 'kwonlydefaults', 'annotations'])
|
||||
|
||||
|
||||
def getfullargspec_no_self(func):
|
||||
"""inspect.getfullargspec replacement using inspect.signature.
|
||||
|
||||
If func is a bound method, do not list the 'self' parameter.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
func : callable
|
||||
A callable to inspect
|
||||
|
||||
Returns
|
||||
-------
|
||||
fullargspec : FullArgSpec(args, varargs, varkw, defaults, kwonlyargs,
|
||||
kwonlydefaults, annotations)
|
||||
|
||||
NOTE: if the first argument of `func` is self, it is *not*, I repeat
|
||||
*not*, included in fullargspec.args.
|
||||
This is done for consistency between inspect.getargspec() under
|
||||
Python 2.x, and inspect.signature() under Python 3.x.
|
||||
|
||||
"""
|
||||
sig = inspect.signature(func)
|
||||
args = [
|
||||
p.name for p in sig.parameters.values()
|
||||
if p.kind in [inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
||||
inspect.Parameter.POSITIONAL_ONLY]
|
||||
]
|
||||
varargs = [
|
||||
p.name for p in sig.parameters.values()
|
||||
if p.kind == inspect.Parameter.VAR_POSITIONAL
|
||||
]
|
||||
varargs = varargs[0] if varargs else None
|
||||
varkw = [
|
||||
p.name for p in sig.parameters.values()
|
||||
if p.kind == inspect.Parameter.VAR_KEYWORD
|
||||
]
|
||||
varkw = varkw[0] if varkw else None
|
||||
defaults = tuple(
|
||||
p.default for p in sig.parameters.values()
|
||||
if (p.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD and
|
||||
p.default is not p.empty)
|
||||
) or None
|
||||
kwonlyargs = [
|
||||
p.name for p in sig.parameters.values()
|
||||
if p.kind == inspect.Parameter.KEYWORD_ONLY
|
||||
]
|
||||
kwdefaults = {p.name: p.default for p in sig.parameters.values()
|
||||
if p.kind == inspect.Parameter.KEYWORD_ONLY and
|
||||
p.default is not p.empty}
|
||||
annotations = {p.name: p.annotation for p in sig.parameters.values()
|
||||
if p.annotation is not p.empty}
|
||||
return FullArgSpec(args, varargs, varkw, defaults, kwonlyargs,
|
||||
kwdefaults or None, annotations)
|
||||
|
||||
|
||||
class MapWrapper:
|
||||
"""
|
||||
Parallelisation wrapper for working with map-like callables, such as
|
||||
`multiprocessing.Pool.map`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
pool : int or map-like callable
|
||||
If `pool` is an integer, then it specifies the number of threads to
|
||||
use for parallelization. If ``int(pool) == 1``, then no parallel
|
||||
processing is used and the map builtin is used.
|
||||
If ``pool == -1``, then the pool will utilize all available CPUs.
|
||||
If `pool` is a map-like callable that follows the same
|
||||
calling sequence as the built-in map function, then this callable is
|
||||
used for parallelization.
|
||||
"""
|
||||
def __init__(self, pool=1):
|
||||
self.pool = None
|
||||
self._mapfunc = map
|
||||
self._own_pool = False
|
||||
|
||||
if callable(pool):
|
||||
self.pool = pool
|
||||
self._mapfunc = self.pool
|
||||
else:
|
||||
from multiprocessing import Pool
|
||||
# user supplies a number
|
||||
if int(pool) == -1:
|
||||
# use as many processors as possible
|
||||
self.pool = Pool()
|
||||
self._mapfunc = self.pool.map
|
||||
self._own_pool = True
|
||||
elif int(pool) == 1:
|
||||
pass
|
||||
elif int(pool) > 1:
|
||||
# use the number of processors requested
|
||||
self.pool = Pool(processes=int(pool))
|
||||
self._mapfunc = self.pool.map
|
||||
self._own_pool = True
|
||||
else:
|
||||
raise RuntimeError("Number of workers specified must be -1,"
|
||||
" an int >= 1, or an object with a 'map' "
|
||||
"method")
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def terminate(self):
|
||||
if self._own_pool:
|
||||
self.pool.terminate()
|
||||
|
||||
def join(self):
|
||||
if self._own_pool:
|
||||
self.pool.join()
|
||||
|
||||
def close(self):
|
||||
if self._own_pool:
|
||||
self.pool.close()
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
if self._own_pool:
|
||||
self.pool.close()
|
||||
self.pool.terminate()
|
||||
|
||||
def __call__(self, func, iterable):
|
||||
# only accept one iterable because that's all Pool.map accepts
|
||||
try:
|
||||
return self._mapfunc(func, iterable)
|
||||
except TypeError as e:
|
||||
# wrong number of arguments
|
||||
raise TypeError("The map-like callable must be of the"
|
||||
" form f(func, iterable)") from e
|
||||
|
||||
|
||||
def rng_integers(gen, low, high=None, size=None, dtype='int64',
|
||||
endpoint=False):
|
||||
"""
|
||||
Return random integers from low (inclusive) to high (exclusive), or if
|
||||
endpoint=True, low (inclusive) to high (inclusive). Replaces
|
||||
`RandomState.randint` (with endpoint=False) and
|
||||
`RandomState.random_integers` (with endpoint=True).
|
||||
|
||||
Return random integers from the "discrete uniform" distribution of the
|
||||
specified dtype. If high is None (the default), then results are from
|
||||
0 to low.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
gen : {None, np.random.RandomState, np.random.Generator}
|
||||
Random number generator. If None, then the np.random.RandomState
|
||||
singleton is used.
|
||||
low : int or array-like of ints
|
||||
Lowest (signed) integers to be drawn from the distribution (unless
|
||||
high=None, in which case this parameter is 0 and this value is used
|
||||
for high).
|
||||
high : int or array-like of ints
|
||||
If provided, one above the largest (signed) integer to be drawn from
|
||||
the distribution (see above for behavior if high=None). If array-like,
|
||||
must contain integer values.
|
||||
size : array-like of ints, optional
|
||||
Output shape. If the given shape is, e.g., (m, n, k), then m * n * k
|
||||
samples are drawn. Default is None, in which case a single value is
|
||||
returned.
|
||||
dtype : {str, dtype}, optional
|
||||
Desired dtype of the result. All dtypes are determined by their name,
|
||||
i.e., 'int64', 'int', etc, so byteorder is not available and a specific
|
||||
precision may have different C types depending on the platform.
|
||||
The default value is np.int_.
|
||||
endpoint : bool, optional
|
||||
If True, sample from the interval [low, high] instead of the default
|
||||
[low, high) Defaults to False.
|
||||
|
||||
Returns
|
||||
-------
|
||||
out: int or ndarray of ints
|
||||
size-shaped array of random integers from the appropriate distribution,
|
||||
or a single such random int if size not provided.
|
||||
"""
|
||||
if isinstance(gen, Generator):
|
||||
return gen.integers(low, high=high, size=size, dtype=dtype,
|
||||
endpoint=endpoint)
|
||||
else:
|
||||
if gen is None:
|
||||
# default is RandomState singleton used by np.random.
|
||||
gen = np.random.mtrand._rand
|
||||
if endpoint:
|
||||
# inclusive of endpoint
|
||||
# remember that low and high can be arrays, so don't modify in
|
||||
# place
|
||||
if high is None:
|
||||
return gen.randint(low + 1, size=size, dtype=dtype)
|
||||
if high is not None:
|
||||
return gen.randint(low, high=high + 1, size=size, dtype=dtype)
|
||||
|
||||
# exclusive
|
||||
return gen.randint(low, high=high, size=size, dtype=dtype)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def _fixed_default_rng(seed=1638083107694713882823079058616272161):
|
||||
"""Context with a fixed np.random.default_rng seed."""
|
||||
orig_fun = np.random.default_rng
|
||||
np.random.default_rng = lambda seed=seed: orig_fun(seed)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
np.random.default_rng = orig_fun
|
||||
Vendored
+399
@@ -0,0 +1,399 @@
|
||||
# ######################### LICENSE ############################ #
|
||||
|
||||
# Copyright (c) 2005-2015, Michele Simionato
|
||||
# All rights reserved.
|
||||
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met:
|
||||
|
||||
# Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# Redistributions in bytecode form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in
|
||||
# the documentation and/or other materials provided with the
|
||||
# distribution.
|
||||
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
||||
# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
||||
# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
# DAMAGE.
|
||||
|
||||
"""
|
||||
Decorator module, see https://pypi.python.org/pypi/decorator
|
||||
for the documentation.
|
||||
"""
|
||||
import re
|
||||
import sys
|
||||
import inspect
|
||||
import operator
|
||||
import itertools
|
||||
import collections
|
||||
|
||||
from inspect import getfullargspec
|
||||
|
||||
__version__ = '4.0.5'
|
||||
|
||||
|
||||
def get_init(cls):
|
||||
return cls.__init__
|
||||
|
||||
|
||||
# getargspec has been deprecated in Python 3.5
|
||||
ArgSpec = collections.namedtuple(
|
||||
'ArgSpec', 'args varargs varkw defaults')
|
||||
|
||||
|
||||
def getargspec(f):
|
||||
"""A replacement for inspect.getargspec"""
|
||||
spec = getfullargspec(f)
|
||||
return ArgSpec(spec.args, spec.varargs, spec.varkw, spec.defaults)
|
||||
|
||||
|
||||
DEF = re.compile(r'\s*def\s*([_\w][_\w\d]*)\s*\(')
|
||||
|
||||
|
||||
# basic functionality
|
||||
class FunctionMaker:
|
||||
"""
|
||||
An object with the ability to create functions with a given signature.
|
||||
It has attributes name, doc, module, signature, defaults, dict, and
|
||||
methods update and make.
|
||||
"""
|
||||
|
||||
# Atomic get-and-increment provided by the GIL
|
||||
_compile_count = itertools.count()
|
||||
|
||||
def __init__(self, func=None, name=None, signature=None,
|
||||
defaults=None, doc=None, module=None, funcdict=None):
|
||||
self.shortsignature = signature
|
||||
if func:
|
||||
# func can be a class or a callable, but not an instance method
|
||||
self.name = func.__name__
|
||||
if self.name == '<lambda>': # small hack for lambda functions
|
||||
self.name = '_lambda_'
|
||||
self.doc = func.__doc__
|
||||
self.module = func.__module__
|
||||
if inspect.isfunction(func):
|
||||
argspec = getfullargspec(func)
|
||||
self.annotations = getattr(func, '__annotations__', {})
|
||||
for a in ('args', 'varargs', 'varkw', 'defaults', 'kwonlyargs',
|
||||
'kwonlydefaults'):
|
||||
setattr(self, a, getattr(argspec, a))
|
||||
for i, arg in enumerate(self.args):
|
||||
setattr(self, 'arg%d' % i, arg)
|
||||
allargs = list(self.args)
|
||||
allshortargs = list(self.args)
|
||||
if self.varargs:
|
||||
allargs.append('*' + self.varargs)
|
||||
allshortargs.append('*' + self.varargs)
|
||||
elif self.kwonlyargs:
|
||||
allargs.append('*') # single star syntax
|
||||
for a in self.kwonlyargs:
|
||||
allargs.append('%s=None' % a)
|
||||
allshortargs.append('%s=%s' % (a, a))
|
||||
if self.varkw:
|
||||
allargs.append('**' + self.varkw)
|
||||
allshortargs.append('**' + self.varkw)
|
||||
self.signature = ', '.join(allargs)
|
||||
self.shortsignature = ', '.join(allshortargs)
|
||||
self.dict = func.__dict__.copy()
|
||||
# func=None happens when decorating a caller
|
||||
if name:
|
||||
self.name = name
|
||||
if signature is not None:
|
||||
self.signature = signature
|
||||
if defaults:
|
||||
self.defaults = defaults
|
||||
if doc:
|
||||
self.doc = doc
|
||||
if module:
|
||||
self.module = module
|
||||
if funcdict:
|
||||
self.dict = funcdict
|
||||
# check existence required attributes
|
||||
assert hasattr(self, 'name')
|
||||
if not hasattr(self, 'signature'):
|
||||
raise TypeError('You are decorating a non-function: %s' % func)
|
||||
|
||||
def update(self, func, **kw):
|
||||
"Update the signature of func with the data in self"
|
||||
func.__name__ = self.name
|
||||
func.__doc__ = getattr(self, 'doc', None)
|
||||
func.__dict__ = getattr(self, 'dict', {})
|
||||
func.__defaults__ = getattr(self, 'defaults', ())
|
||||
func.__kwdefaults__ = getattr(self, 'kwonlydefaults', None)
|
||||
func.__annotations__ = getattr(self, 'annotations', None)
|
||||
try:
|
||||
frame = sys._getframe(3)
|
||||
except AttributeError: # for IronPython and similar implementations
|
||||
callermodule = '?'
|
||||
else:
|
||||
callermodule = frame.f_globals.get('__name__', '?')
|
||||
func.__module__ = getattr(self, 'module', callermodule)
|
||||
func.__dict__.update(kw)
|
||||
|
||||
def make(self, src_templ, evaldict=None, addsource=False, **attrs):
|
||||
"Make a new function from a given template and update the signature"
|
||||
src = src_templ % vars(self) # expand name and signature
|
||||
evaldict = evaldict or {}
|
||||
mo = DEF.match(src)
|
||||
if mo is None:
|
||||
raise SyntaxError('not a valid function template\n%s' % src)
|
||||
name = mo.group(1) # extract the function name
|
||||
names = set([name] + [arg.strip(' *') for arg in
|
||||
self.shortsignature.split(',')])
|
||||
for n in names:
|
||||
if n in ('_func_', '_call_'):
|
||||
raise NameError('%s is overridden in\n%s' % (n, src))
|
||||
if not src.endswith('\n'): # add a newline just for safety
|
||||
src += '\n' # this is needed in old versions of Python
|
||||
|
||||
# Ensure each generated function has a unique filename for profilers
|
||||
# (such as cProfile) that depend on the tuple of (<filename>,
|
||||
# <definition line>, <function name>) being unique.
|
||||
filename = '<decorator-gen-%d>' % (next(self._compile_count),)
|
||||
try:
|
||||
code = compile(src, filename, 'single')
|
||||
exec(code, evaldict)
|
||||
except: # noqa: E722
|
||||
print('Error in generated code:', file=sys.stderr)
|
||||
print(src, file=sys.stderr)
|
||||
raise
|
||||
func = evaldict[name]
|
||||
if addsource:
|
||||
attrs['__source__'] = src
|
||||
self.update(func, **attrs)
|
||||
return func
|
||||
|
||||
@classmethod
|
||||
def create(cls, obj, body, evaldict, defaults=None,
|
||||
doc=None, module=None, addsource=True, **attrs):
|
||||
"""
|
||||
Create a function from the strings name, signature, and body.
|
||||
evaldict is the evaluation dictionary. If addsource is true, an
|
||||
attribute __source__ is added to the result. The attributes attrs
|
||||
are added, if any.
|
||||
"""
|
||||
if isinstance(obj, str): # "name(signature)"
|
||||
name, rest = obj.strip().split('(', 1)
|
||||
signature = rest[:-1] # strip a right parens
|
||||
func = None
|
||||
else: # a function
|
||||
name = None
|
||||
signature = None
|
||||
func = obj
|
||||
self = cls(func, name, signature, defaults, doc, module)
|
||||
ibody = '\n'.join(' ' + line for line in body.splitlines())
|
||||
return self.make('def %(name)s(%(signature)s):\n' + ibody,
|
||||
evaldict, addsource, **attrs)
|
||||
|
||||
|
||||
def decorate(func, caller):
|
||||
"""
|
||||
decorate(func, caller) decorates a function using a caller.
|
||||
"""
|
||||
evaldict = func.__globals__.copy()
|
||||
evaldict['_call_'] = caller
|
||||
evaldict['_func_'] = func
|
||||
fun = FunctionMaker.create(
|
||||
func, "return _call_(_func_, %(shortsignature)s)",
|
||||
evaldict, __wrapped__=func)
|
||||
if hasattr(func, '__qualname__'):
|
||||
fun.__qualname__ = func.__qualname__
|
||||
return fun
|
||||
|
||||
|
||||
def decorator(caller, _func=None):
|
||||
"""decorator(caller) converts a caller function into a decorator"""
|
||||
if _func is not None: # return a decorated function
|
||||
# this is obsolete behavior; you should use decorate instead
|
||||
return decorate(_func, caller)
|
||||
# else return a decorator function
|
||||
if inspect.isclass(caller):
|
||||
name = caller.__name__.lower()
|
||||
callerfunc = get_init(caller)
|
||||
doc = 'decorator(%s) converts functions/generators into ' \
|
||||
'factories of %s objects' % (caller.__name__, caller.__name__)
|
||||
elif inspect.isfunction(caller):
|
||||
if caller.__name__ == '<lambda>':
|
||||
name = '_lambda_'
|
||||
else:
|
||||
name = caller.__name__
|
||||
callerfunc = caller
|
||||
doc = caller.__doc__
|
||||
else: # assume caller is an object with a __call__ method
|
||||
name = caller.__class__.__name__.lower()
|
||||
callerfunc = caller.__call__.__func__
|
||||
doc = caller.__call__.__doc__
|
||||
evaldict = callerfunc.__globals__.copy()
|
||||
evaldict['_call_'] = caller
|
||||
evaldict['_decorate_'] = decorate
|
||||
return FunctionMaker.create(
|
||||
'%s(func)' % name, 'return _decorate_(func, _call_)',
|
||||
evaldict, doc=doc, module=caller.__module__,
|
||||
__wrapped__=caller)
|
||||
|
||||
|
||||
# ####################### contextmanager ####################### #
|
||||
|
||||
try: # Python >= 3.2
|
||||
from contextlib import _GeneratorContextManager
|
||||
except ImportError: # Python >= 2.5
|
||||
from contextlib import GeneratorContextManager as _GeneratorContextManager
|
||||
|
||||
|
||||
class ContextManager(_GeneratorContextManager):
|
||||
def __call__(self, func):
|
||||
"""Context manager decorator"""
|
||||
return FunctionMaker.create(
|
||||
func, "with _self_: return _func_(%(shortsignature)s)",
|
||||
dict(_self_=self, _func_=func), __wrapped__=func)
|
||||
|
||||
|
||||
init = getfullargspec(_GeneratorContextManager.__init__)
|
||||
n_args = len(init.args)
|
||||
if n_args == 2 and not init.varargs: # (self, genobj) Python 2.7
|
||||
def __init__(self, g, *a, **k):
|
||||
return _GeneratorContextManager.__init__(self, g(*a, **k))
|
||||
ContextManager.__init__ = __init__
|
||||
elif n_args == 2 and init.varargs: # (self, gen, *a, **k) Python 3.4
|
||||
pass
|
||||
elif n_args == 4: # (self, gen, args, kwds) Python 3.5
|
||||
def __init__(self, g, *a, **k):
|
||||
return _GeneratorContextManager.__init__(self, g, a, k)
|
||||
ContextManager.__init__ = __init__
|
||||
|
||||
contextmanager = decorator(ContextManager)
|
||||
|
||||
|
||||
# ############################ dispatch_on ############################ #
|
||||
|
||||
def append(a, vancestors):
|
||||
"""
|
||||
Append ``a`` to the list of the virtual ancestors, unless it is already
|
||||
included.
|
||||
"""
|
||||
add = True
|
||||
for j, va in enumerate(vancestors):
|
||||
if issubclass(va, a):
|
||||
add = False
|
||||
break
|
||||
if issubclass(a, va):
|
||||
vancestors[j] = a
|
||||
add = False
|
||||
if add:
|
||||
vancestors.append(a)
|
||||
|
||||
|
||||
# inspired from simplegeneric by P.J. Eby and functools.singledispatch
|
||||
def dispatch_on(*dispatch_args):
|
||||
"""
|
||||
Factory of decorators turning a function into a generic function
|
||||
dispatching on the given arguments.
|
||||
"""
|
||||
assert dispatch_args, 'No dispatch args passed'
|
||||
dispatch_str = '(%s,)' % ', '.join(dispatch_args)
|
||||
|
||||
def check(arguments, wrong=operator.ne, msg=''):
|
||||
"""Make sure one passes the expected number of arguments"""
|
||||
if wrong(len(arguments), len(dispatch_args)):
|
||||
raise TypeError('Expected %d arguments, got %d%s' %
|
||||
(len(dispatch_args), len(arguments), msg))
|
||||
|
||||
def gen_func_dec(func):
|
||||
"""Decorator turning a function into a generic function"""
|
||||
|
||||
# first check the dispatch arguments
|
||||
argset = set(getfullargspec(func).args)
|
||||
if not set(dispatch_args) <= argset:
|
||||
raise NameError('Unknown dispatch arguments %s' % dispatch_str)
|
||||
|
||||
typemap = {}
|
||||
|
||||
def vancestors(*types):
|
||||
"""
|
||||
Get a list of sets of virtual ancestors for the given types
|
||||
"""
|
||||
check(types)
|
||||
ras = [[] for _ in range(len(dispatch_args))]
|
||||
for types_ in typemap:
|
||||
for t, type_, ra in zip(types, types_, ras):
|
||||
if issubclass(t, type_) and type_ not in t.__mro__:
|
||||
append(type_, ra)
|
||||
return [set(ra) for ra in ras]
|
||||
|
||||
def ancestors(*types):
|
||||
"""
|
||||
Get a list of virtual MROs, one for each type
|
||||
"""
|
||||
check(types)
|
||||
lists = []
|
||||
for t, vas in zip(types, vancestors(*types)):
|
||||
n_vas = len(vas)
|
||||
if n_vas > 1:
|
||||
raise RuntimeError(
|
||||
'Ambiguous dispatch for %s: %s' % (t, vas))
|
||||
elif n_vas == 1:
|
||||
va, = vas
|
||||
mro = type('t', (t, va), {}).__mro__[1:]
|
||||
else:
|
||||
mro = t.__mro__
|
||||
lists.append(mro[:-1]) # discard t and object
|
||||
return lists
|
||||
|
||||
def register(*types):
|
||||
"""
|
||||
Decorator to register an implementation for the given types
|
||||
"""
|
||||
check(types)
|
||||
|
||||
def dec(f):
|
||||
check(getfullargspec(f).args, operator.lt, ' in ' + f.__name__)
|
||||
typemap[types] = f
|
||||
return f
|
||||
return dec
|
||||
|
||||
def dispatch_info(*types):
|
||||
"""
|
||||
An utility to introspect the dispatch algorithm
|
||||
"""
|
||||
check(types)
|
||||
lst = [tuple(a.__name__ for a in anc)
|
||||
for anc in itertools.product(*ancestors(*types))]
|
||||
return lst
|
||||
|
||||
def _dispatch(dispatch_args, *args, **kw):
|
||||
types = tuple(type(arg) for arg in dispatch_args)
|
||||
try: # fast path
|
||||
f = typemap[types]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
return f(*args, **kw)
|
||||
combinations = itertools.product(*ancestors(*types))
|
||||
next(combinations) # the first one has been already tried
|
||||
for types_ in combinations:
|
||||
f = typemap.get(types_)
|
||||
if f is not None:
|
||||
return f(*args, **kw)
|
||||
|
||||
# else call the default implementation
|
||||
return func(*args, **kw)
|
||||
|
||||
return FunctionMaker.create(
|
||||
func, 'return _f_(%s, %%(shortsignature)s)' % dispatch_str,
|
||||
dict(_f_=_dispatch), register=register, default=func,
|
||||
typemap=typemap, vancestors=vancestors, ancestors=ancestors,
|
||||
dispatch_info=dispatch_info, __wrapped__=func)
|
||||
|
||||
gen_func_dec.__name__ = 'dispatch_on' + dispatch_str
|
||||
return gen_func_dec
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
import functools
|
||||
import warnings
|
||||
|
||||
__all__ = ["_deprecated"]
|
||||
|
||||
|
||||
def _deprecated(msg, stacklevel=2):
|
||||
"""Deprecate a function by emitting a warning on use."""
|
||||
def wrap(fun):
|
||||
if isinstance(fun, type):
|
||||
warnings.warn(
|
||||
"Trying to deprecate class {!r}".format(fun),
|
||||
category=RuntimeWarning, stacklevel=2)
|
||||
return fun
|
||||
|
||||
@functools.wraps(fun)
|
||||
def call(*args, **kwargs):
|
||||
warnings.warn(msg, category=DeprecationWarning,
|
||||
stacklevel=stacklevel)
|
||||
return fun(*args, **kwargs)
|
||||
call.__doc__ = msg
|
||||
return call
|
||||
|
||||
return wrap
|
||||
|
||||
|
||||
class _DeprecationHelperStr:
|
||||
"""
|
||||
Helper class used by deprecate_cython_api
|
||||
"""
|
||||
def __init__(self, content, message):
|
||||
self._content = content
|
||||
self._message = message
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self._content)
|
||||
|
||||
def __eq__(self, other):
|
||||
res = (self._content == other)
|
||||
if res:
|
||||
warnings.warn(self._message, category=DeprecationWarning,
|
||||
stacklevel=2)
|
||||
return res
|
||||
|
||||
|
||||
def deprecate_cython_api(module, routine_name, new_name=None, message=None):
|
||||
"""
|
||||
Deprecate an exported cdef function in a public Cython API module.
|
||||
|
||||
Only functions can be deprecated; typedefs etc. cannot.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
module : module
|
||||
Public Cython API module (e.g. scipy.linalg.cython_blas).
|
||||
routine_name : str
|
||||
Name of the routine to deprecate. May also be a fused-type
|
||||
routine (in which case its all specializations are deprecated).
|
||||
new_name : str
|
||||
New name to include in the deprecation warning message
|
||||
message : str
|
||||
Additional text in the deprecation warning message
|
||||
|
||||
Examples
|
||||
--------
|
||||
Usually, this function would be used in the top-level of the
|
||||
module ``.pyx`` file:
|
||||
|
||||
>>> from scipy._lib.deprecation import deprecate_cython_api
|
||||
>>> import scipy.linalg.cython_blas as mod
|
||||
>>> deprecate_cython_api(mod, "dgemm", "dgemm_new",
|
||||
... message="Deprecated in Scipy 1.5.0")
|
||||
>>> del deprecate_cython_api, mod
|
||||
|
||||
After this, Cython modules that use the deprecated function emit a
|
||||
deprecation warning when they are imported.
|
||||
|
||||
"""
|
||||
old_name = "{}.{}".format(module.__name__, routine_name)
|
||||
|
||||
if new_name is None:
|
||||
depdoc = "`%s` is deprecated!" % old_name
|
||||
else:
|
||||
depdoc = "`%s` is deprecated, use `%s` instead!" % \
|
||||
(old_name, new_name)
|
||||
|
||||
if message is not None:
|
||||
depdoc += "\n" + message
|
||||
|
||||
d = module.__pyx_capi__
|
||||
|
||||
# Check if the function is a fused-type function with a mangled name
|
||||
j = 0
|
||||
has_fused = False
|
||||
while True:
|
||||
fused_name = "__pyx_fuse_{}{}".format(j, routine_name)
|
||||
if fused_name in d:
|
||||
has_fused = True
|
||||
d[_DeprecationHelperStr(fused_name, depdoc)] = d.pop(fused_name)
|
||||
j += 1
|
||||
else:
|
||||
break
|
||||
|
||||
# If not, apply deprecation to the named routine
|
||||
if not has_fused:
|
||||
d[_DeprecationHelperStr(routine_name, depdoc)] = d.pop(routine_name)
|
||||
|
||||
Vendored
+272
@@ -0,0 +1,272 @@
|
||||
''' Utilities to allow inserting docstring fragments for common
|
||||
parameters into function and method docstrings'''
|
||||
|
||||
import sys
|
||||
|
||||
__all__ = ['docformat', 'inherit_docstring_from', 'indentcount_lines',
|
||||
'filldoc', 'unindent_dict', 'unindent_string', 'doc_replace']
|
||||
|
||||
|
||||
def docformat(docstring, docdict=None):
|
||||
''' Fill a function docstring from variables in dictionary
|
||||
|
||||
Adapt the indent of the inserted docs
|
||||
|
||||
Parameters
|
||||
----------
|
||||
docstring : string
|
||||
docstring from function, possibly with dict formatting strings
|
||||
docdict : dict, optional
|
||||
dictionary with keys that match the dict formatting strings
|
||||
and values that are docstring fragments to be inserted. The
|
||||
indentation of the inserted docstrings is set to match the
|
||||
minimum indentation of the ``docstring`` by adding this
|
||||
indentation to all lines of the inserted string, except the
|
||||
first.
|
||||
|
||||
Returns
|
||||
-------
|
||||
outstring : string
|
||||
string with requested ``docdict`` strings inserted
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> docformat(' Test string with %(value)s', {'value':'inserted value'})
|
||||
' Test string with inserted value'
|
||||
>>> docstring = 'First line\\n Second line\\n %(value)s'
|
||||
>>> inserted_string = "indented\\nstring"
|
||||
>>> docdict = {'value': inserted_string}
|
||||
>>> docformat(docstring, docdict)
|
||||
'First line\\n Second line\\n indented\\n string'
|
||||
'''
|
||||
if not docstring:
|
||||
return docstring
|
||||
if docdict is None:
|
||||
docdict = {}
|
||||
if not docdict:
|
||||
return docstring
|
||||
lines = docstring.expandtabs().splitlines()
|
||||
# Find the minimum indent of the main docstring, after first line
|
||||
if len(lines) < 2:
|
||||
icount = 0
|
||||
else:
|
||||
icount = indentcount_lines(lines[1:])
|
||||
indent = ' ' * icount
|
||||
# Insert this indent to dictionary docstrings
|
||||
indented = {}
|
||||
for name, dstr in docdict.items():
|
||||
lines = dstr.expandtabs().splitlines()
|
||||
try:
|
||||
newlines = [lines[0]]
|
||||
for line in lines[1:]:
|
||||
newlines.append(indent+line)
|
||||
indented[name] = '\n'.join(newlines)
|
||||
except IndexError:
|
||||
indented[name] = dstr
|
||||
return docstring % indented
|
||||
|
||||
|
||||
def inherit_docstring_from(cls):
|
||||
"""
|
||||
This decorator modifies the decorated function's docstring by
|
||||
replacing occurrences of '%(super)s' with the docstring of the
|
||||
method of the same name from the class `cls`.
|
||||
|
||||
If the decorated method has no docstring, it is simply given the
|
||||
docstring of `cls`s method.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
cls : Python class or instance
|
||||
A class with a method with the same name as the decorated method.
|
||||
The docstring of the method in this class replaces '%(super)s' in the
|
||||
docstring of the decorated method.
|
||||
|
||||
Returns
|
||||
-------
|
||||
f : function
|
||||
The decorator function that modifies the __doc__ attribute
|
||||
of its argument.
|
||||
|
||||
Examples
|
||||
--------
|
||||
In the following, the docstring for Bar.func created using the
|
||||
docstring of `Foo.func`.
|
||||
|
||||
>>> class Foo:
|
||||
... def func(self):
|
||||
... '''Do something useful.'''
|
||||
... return
|
||||
...
|
||||
>>> class Bar(Foo):
|
||||
... @inherit_docstring_from(Foo)
|
||||
... def func(self):
|
||||
... '''%(super)s
|
||||
... Do it fast.
|
||||
... '''
|
||||
... return
|
||||
...
|
||||
>>> b = Bar()
|
||||
>>> b.func.__doc__
|
||||
'Do something useful.\n Do it fast.\n '
|
||||
|
||||
"""
|
||||
def _doc(func):
|
||||
cls_docstring = getattr(cls, func.__name__).__doc__
|
||||
func_docstring = func.__doc__
|
||||
if func_docstring is None:
|
||||
func.__doc__ = cls_docstring
|
||||
else:
|
||||
new_docstring = func_docstring % dict(super=cls_docstring)
|
||||
func.__doc__ = new_docstring
|
||||
return func
|
||||
return _doc
|
||||
|
||||
|
||||
def extend_notes_in_docstring(cls, notes):
|
||||
"""
|
||||
This decorator replaces the decorated function's docstring
|
||||
with the docstring from corresponding method in `cls`.
|
||||
It extends the 'Notes' section of that docstring to include
|
||||
the given `notes`.
|
||||
"""
|
||||
def _doc(func):
|
||||
cls_docstring = getattr(cls, func.__name__).__doc__
|
||||
# If python is called with -OO option,
|
||||
# there is no docstring
|
||||
if cls_docstring is None:
|
||||
return func
|
||||
end_of_notes = cls_docstring.find(' References\n')
|
||||
if end_of_notes == -1:
|
||||
end_of_notes = cls_docstring.find(' Examples\n')
|
||||
if end_of_notes == -1:
|
||||
end_of_notes = len(cls_docstring)
|
||||
func.__doc__ = (cls_docstring[:end_of_notes] + notes +
|
||||
cls_docstring[end_of_notes:])
|
||||
return func
|
||||
return _doc
|
||||
|
||||
|
||||
def replace_notes_in_docstring(cls, notes):
|
||||
"""
|
||||
This decorator replaces the decorated function's docstring
|
||||
with the docstring from corresponding method in `cls`.
|
||||
It replaces the 'Notes' section of that docstring with
|
||||
the given `notes`.
|
||||
"""
|
||||
def _doc(func):
|
||||
cls_docstring = getattr(cls, func.__name__).__doc__
|
||||
notes_header = ' Notes\n -----\n'
|
||||
# If python is called with -OO option,
|
||||
# there is no docstring
|
||||
if cls_docstring is None:
|
||||
return func
|
||||
start_of_notes = cls_docstring.find(notes_header)
|
||||
end_of_notes = cls_docstring.find(' References\n')
|
||||
if end_of_notes == -1:
|
||||
end_of_notes = cls_docstring.find(' Examples\n')
|
||||
if end_of_notes == -1:
|
||||
end_of_notes = len(cls_docstring)
|
||||
func.__doc__ = (cls_docstring[:start_of_notes + len(notes_header)] +
|
||||
notes +
|
||||
cls_docstring[end_of_notes:])
|
||||
return func
|
||||
return _doc
|
||||
|
||||
|
||||
def indentcount_lines(lines):
|
||||
''' Minimum indent for all lines in line list
|
||||
|
||||
>>> lines = [' one', ' two', ' three']
|
||||
>>> indentcount_lines(lines)
|
||||
1
|
||||
>>> lines = []
|
||||
>>> indentcount_lines(lines)
|
||||
0
|
||||
>>> lines = [' one']
|
||||
>>> indentcount_lines(lines)
|
||||
1
|
||||
>>> indentcount_lines([' '])
|
||||
0
|
||||
'''
|
||||
indentno = sys.maxsize
|
||||
for line in lines:
|
||||
stripped = line.lstrip()
|
||||
if stripped:
|
||||
indentno = min(indentno, len(line) - len(stripped))
|
||||
if indentno == sys.maxsize:
|
||||
return 0
|
||||
return indentno
|
||||
|
||||
|
||||
def filldoc(docdict, unindent_params=True):
|
||||
''' Return docstring decorator using docdict variable dictionary
|
||||
|
||||
Parameters
|
||||
----------
|
||||
docdict : dictionary
|
||||
dictionary containing name, docstring fragment pairs
|
||||
unindent_params : {False, True}, boolean, optional
|
||||
If True, strip common indentation from all parameters in
|
||||
docdict
|
||||
|
||||
Returns
|
||||
-------
|
||||
decfunc : function
|
||||
decorator that applies dictionary to input function docstring
|
||||
|
||||
'''
|
||||
if unindent_params:
|
||||
docdict = unindent_dict(docdict)
|
||||
|
||||
def decorate(f):
|
||||
f.__doc__ = docformat(f.__doc__, docdict)
|
||||
return f
|
||||
return decorate
|
||||
|
||||
|
||||
def unindent_dict(docdict):
|
||||
''' Unindent all strings in a docdict '''
|
||||
can_dict = {}
|
||||
for name, dstr in docdict.items():
|
||||
can_dict[name] = unindent_string(dstr)
|
||||
return can_dict
|
||||
|
||||
|
||||
def unindent_string(docstring):
|
||||
''' Set docstring to minimum indent for all lines, including first
|
||||
|
||||
>>> unindent_string(' two')
|
||||
'two'
|
||||
>>> unindent_string(' two\\n three')
|
||||
'two\\n three'
|
||||
'''
|
||||
lines = docstring.expandtabs().splitlines()
|
||||
icount = indentcount_lines(lines)
|
||||
if icount == 0:
|
||||
return docstring
|
||||
return '\n'.join([line[icount:] for line in lines])
|
||||
|
||||
|
||||
def doc_replace(obj, oldval, newval):
|
||||
"""Decorator to take the docstring from obj, with oldval replaced by newval
|
||||
|
||||
Equivalent to ``func.__doc__ = obj.__doc__.replace(oldval, newval)``
|
||||
|
||||
Parameters
|
||||
----------
|
||||
obj : object
|
||||
The object to take the docstring from.
|
||||
oldval : string
|
||||
The string to replace from the original docstring.
|
||||
newval : string
|
||||
The string to replace ``oldval`` with.
|
||||
"""
|
||||
# __doc__ may be None for optimized Python (-OO)
|
||||
doc = (obj.__doc__ or '').replace(oldval, newval)
|
||||
|
||||
def inner(func):
|
||||
func.__doc__ = doc
|
||||
return func
|
||||
|
||||
return inner
|
||||
Vendored
+88
@@ -0,0 +1,88 @@
|
||||
import os
|
||||
import pathlib
|
||||
|
||||
|
||||
def check_boost_submodule():
|
||||
from scipy._lib._boost_utils import _boost_dir
|
||||
|
||||
if not os.path.exists(_boost_dir(ret_path=True) / 'README.md'):
|
||||
raise RuntimeError("Missing the `boost` submodule! Run `git submodule "
|
||||
"update --init` to fix this.")
|
||||
|
||||
|
||||
def build_clib_pre_build_hook(cmd, ext):
|
||||
from scipy._build_utils.compiler_helper import get_cxx_std_flag
|
||||
std_flag = get_cxx_std_flag(cmd.compiler)
|
||||
ext.setdefault('extra_compiler_args', [])
|
||||
if std_flag is not None:
|
||||
ext['extra_compiler_args'].append(std_flag)
|
||||
|
||||
|
||||
def configuration(parent_package='',top_path=None):
|
||||
from numpy.distutils.misc_util import Configuration
|
||||
from scipy._lib._boost_utils import _boost_dir
|
||||
|
||||
check_boost_submodule()
|
||||
|
||||
config = Configuration('_lib', parent_package, top_path)
|
||||
config.add_data_files('tests/*.py')
|
||||
|
||||
include_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), 'src'))
|
||||
depends = [os.path.join(include_dir, 'ccallback.h')]
|
||||
|
||||
config.add_extension("_ccallback_c",
|
||||
sources=["_ccallback_c.c"],
|
||||
depends=depends,
|
||||
include_dirs=[include_dir])
|
||||
|
||||
config.add_extension("_test_ccallback",
|
||||
sources=["src/_test_ccallback.c"],
|
||||
depends=depends,
|
||||
include_dirs=[include_dir])
|
||||
|
||||
config.add_extension("_fpumode",
|
||||
sources=["_fpumode.c"])
|
||||
|
||||
def get_messagestream_config(ext, build_dir):
|
||||
# Generate a header file containing defines
|
||||
config_cmd = config.get_config_cmd()
|
||||
defines = []
|
||||
if config_cmd.check_func('open_memstream', decl=True, call=True):
|
||||
defines.append(('HAVE_OPEN_MEMSTREAM', '1'))
|
||||
target = os.path.join(os.path.dirname(__file__), 'src',
|
||||
'messagestream_config.h')
|
||||
with open(target, 'w') as f:
|
||||
for name, value in defines:
|
||||
f.write('#define {0} {1}\n'.format(name, value))
|
||||
|
||||
depends = [os.path.join(include_dir, 'messagestream.h')]
|
||||
config.add_extension("messagestream",
|
||||
sources=["messagestream.c"] + [get_messagestream_config],
|
||||
depends=depends,
|
||||
include_dirs=[include_dir])
|
||||
|
||||
config.add_extension("_test_deprecation_call",
|
||||
sources=["_test_deprecation_call.c"],
|
||||
include_dirs=[include_dir])
|
||||
|
||||
config.add_extension("_test_deprecation_def",
|
||||
sources=["_test_deprecation_def.c"],
|
||||
include_dirs=[include_dir])
|
||||
|
||||
config.add_subpackage('_uarray')
|
||||
|
||||
# ensure Boost was checked out and builds
|
||||
config.add_library(
|
||||
'test_boost_build',
|
||||
sources=['tests/test_boost_build.cpp'],
|
||||
include_dirs=_boost_dir(),
|
||||
language='c++',
|
||||
_pre_build_hook=build_clib_pre_build_hook)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from numpy.distutils.core import setup
|
||||
|
||||
setup(**configuration(top_path='').todict())
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
""" Test for assert_deallocated context manager and gc utilities
|
||||
"""
|
||||
import gc
|
||||
|
||||
from scipy._lib._gcutils import (set_gc_state, gc_state, assert_deallocated,
|
||||
ReferenceError, IS_PYPY)
|
||||
|
||||
from numpy.testing import assert_equal
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def test_set_gc_state():
|
||||
gc_status = gc.isenabled()
|
||||
try:
|
||||
for state in (True, False):
|
||||
gc.enable()
|
||||
set_gc_state(state)
|
||||
assert_equal(gc.isenabled(), state)
|
||||
gc.disable()
|
||||
set_gc_state(state)
|
||||
assert_equal(gc.isenabled(), state)
|
||||
finally:
|
||||
if gc_status:
|
||||
gc.enable()
|
||||
|
||||
|
||||
def test_gc_state():
|
||||
# Test gc_state context manager
|
||||
gc_status = gc.isenabled()
|
||||
try:
|
||||
for pre_state in (True, False):
|
||||
set_gc_state(pre_state)
|
||||
for with_state in (True, False):
|
||||
# Check the gc state is with_state in with block
|
||||
with gc_state(with_state):
|
||||
assert_equal(gc.isenabled(), with_state)
|
||||
# And returns to previous state outside block
|
||||
assert_equal(gc.isenabled(), pre_state)
|
||||
# Even if the gc state is set explicitly within the block
|
||||
with gc_state(with_state):
|
||||
assert_equal(gc.isenabled(), with_state)
|
||||
set_gc_state(not with_state)
|
||||
assert_equal(gc.isenabled(), pre_state)
|
||||
finally:
|
||||
if gc_status:
|
||||
gc.enable()
|
||||
|
||||
|
||||
@pytest.mark.skipif(IS_PYPY, reason="Test not meaningful on PyPy")
|
||||
def test_assert_deallocated():
|
||||
# Ordinary use
|
||||
class C:
|
||||
def __init__(self, arg0, arg1, name='myname'):
|
||||
self.name = name
|
||||
for gc_current in (True, False):
|
||||
with gc_state(gc_current):
|
||||
# We are deleting from with-block context, so that's OK
|
||||
with assert_deallocated(C, 0, 2, 'another name') as c:
|
||||
assert_equal(c.name, 'another name')
|
||||
del c
|
||||
# Or not using the thing in with-block context, also OK
|
||||
with assert_deallocated(C, 0, 2, name='third name'):
|
||||
pass
|
||||
assert_equal(gc.isenabled(), gc_current)
|
||||
|
||||
|
||||
@pytest.mark.skipif(IS_PYPY, reason="Test not meaningful on PyPy")
|
||||
def test_assert_deallocated_nodel():
|
||||
class C:
|
||||
pass
|
||||
with pytest.raises(ReferenceError):
|
||||
# Need to delete after using if in with-block context
|
||||
# Note: assert_deallocated(C) needs to be assigned for the test
|
||||
# to function correctly. It is assigned to c, but c itself is
|
||||
# not referenced in the body of the with, it is only there for
|
||||
# the refcount.
|
||||
with assert_deallocated(C) as c:
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.skipif(IS_PYPY, reason="Test not meaningful on PyPy")
|
||||
def test_assert_deallocated_circular():
|
||||
class C:
|
||||
def __init__(self):
|
||||
self._circular = self
|
||||
with pytest.raises(ReferenceError):
|
||||
# Circular reference, no automatic garbage collection
|
||||
with assert_deallocated(C) as c:
|
||||
del c
|
||||
|
||||
|
||||
@pytest.mark.skipif(IS_PYPY, reason="Test not meaningful on PyPy")
|
||||
def test_assert_deallocated_circular2():
|
||||
class C:
|
||||
def __init__(self):
|
||||
self._circular = self
|
||||
with pytest.raises(ReferenceError):
|
||||
# Still circular reference, no automatic garbage collection
|
||||
with assert_deallocated(C):
|
||||
pass
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user