tesseract_robotics.tesseract_common¶
Common types, utilities, and resource handling.
Eigen arguments take arrays, lists and tuples
Every Eigen::Vector3d, VectorXd, Matrix3d, Matrix4d (and other dense Eigen)
parameter accepts a NumPy array, a list or a tuple, nested for a matrix:
iso.translate([0.5, 0.2, 0.3]), Isometry3d([x, y, z], q), Quaterniond(R.tolist()).
The shape must match exactly ([1, 2] for a Vector3d raises TypeError, nothing is
padded or truncated). Integers convert to float64; strings, None, complex numbers and
ragged nesting raise TypeError, and an integer parameter (mesh faces) refuses floats.
Returned matrices are always NumPy arrays. The exception is a parameter a function writes
into (see Frame and error math): it needs the array itself.
Transforms¶
Isometry3d¶
Eigen rigid-body transform (rotation + translation). Raw C++ binding.
from tesseract_robotics.tesseract_common import Isometry3d
import numpy as np
# Identity transform
pose = Isometry3d.Identity()
# From a 4x4 matrix
mat = np.eye(4)
mat[:3, 3] = [0.5, 0.2, 0.3]
pose = Isometry3d(mat)
# Access components (read-only properties; each returns a copy)
position = pose.translation # np.array([x, y, z])
rotation = pose.rotation # 3x3 rotation matrix
matrix = pose.matrix # 4x4 homogeneous matrix
# Compose
combined = pose1 * pose2
# Apply to many points at once: (N, 3) in, (N, 3) out, in one call
points = np.random.default_rng(0).normal(size=(1000, 3))
mapped = pose.apply_points(points) # row i == pose * points[i], bitwise
rotated = pose.apply_directions(points) # rotation only, no translation
apply_points and apply_directions take an (N, 3) array, including N = 0 and strided views
such as a[:, :3], and return a new (N, 3) float64 array. A single point stays pose * p; a
(3,) array passed to apply_points raises TypeError. Each row is computed exactly as
pose * p computes it, so the results agree bit for bit. A Python loop over pose * p costs about
1 µs per point; apply_points costs about 1 µs per call up to a few dozen points.
Prefer planning.Pose for authoring
For building/serializing transforms in Python, use
tesseract_robotics.planning.Pose — it accepts scalar-last quaternions
(qx, qy, qz, qw) and offers from_xyz_quat, from_rpy, etc. Convert
to an Isometry3d when calling the raw C++ API.
Quaterniond¶
Eigen quaternion. Project canonical order is scalar-last
[qx, qy, qz, qw] — use Quaterniond.from_xyzw(qx, qy, qz, qw) to build
and access components via the q.x, q.y, q.z, q.w properties.
from tesseract_robotics.tesseract_common import Quaterniond
q = Quaterniond.from_xyzw(0.0, 0.0, 0.0, 1.0) # identity: scalar-last
# Component access — properties, not method calls.
print(f"x={q.x}, y={q.y}, z={q.z}, w={q.w}")
# Flat coefficient vector is also scalar-last (Eigen storage order).
qx, qy, qz, qw = q.coeffs()
rotation_matrix = q.toRotationMatrix()
Eigen's 4-double ctor is scalar-first — avoid in Python
Quaterniond(w, x, y, z) exists for direct Eigen interop but is the
one signature in the binding that breaks the project convention. Use
Quaterniond.from_xyzw(qx, qy, qz, qw) everywhere else.
q.coeffs(), q.vec(), the Quaterniond(Vector4d) ctor, and Pose's
quaternion-returning factories all use scalar-last [qx, qy, qz, qw].
AngleAxisd¶
Axis-angle rotation representation.
from tesseract_robotics.tesseract_common import AngleAxisd
import numpy as np
# 90 degrees around Z axis
aa = AngleAxisd(np.pi/2, np.array([0, 0, 1]))
rotation_matrix = aa.toRotationMatrix()
Frame and error math¶
Re-base twists and jacobians, and compute pose errors, with tesseract's own math. A twist
is a (6,) array [vx, vy, vz, wx, wy, wz]; a jacobian is (6, n).
import numpy as np
from tesseract_robotics.tesseract_common import jacobianChangeBase, jacobianChangeRefPoint
J = joint_group.calcJacobian(q, "tool0") # (6, n), Fortran order, in the group base frame
jacobianChangeBase(J, T_new_base) # rotate every column into T_new_base, in place
jacobianChangeRefPoint(J, [0.0, 0.0, 0.1]) # v += ω × p, in place
In place: these functions write into the array you pass
twistChangeRefPoint, twistChangeBase, jacobianChangeBase, jacobianChangeRefPoint
and applyTolerances return None and change their first argument, as in C++. They
raise TypeError for anything they would have to convert first, since the result
would be written into a temporary copy and lost: a list or tuple, float32, read-only
or non-contiguous arrays, and C-order (6, n > 1) jacobians. calcJacobian returns Fortran order;
convert others with np.asfortranarray.
jacobianChangeRefPoint takes the point in the jacobian's base frame. To match
calcJacobian(q, link, p), whose p is in the link frame, rotate it first:
jacobianChangeRefPoint(J, joint_group.calcFwdKin(q)[link].rotation @ p).
Pose errors are [translation, angle-axis rotation] 6-vectors:
from tesseract_robotics.tesseract_common import (
applyTolerances, calcJacobianTransformErrorDiff, calcRotationalError, calcTransformError,
)
calcRotationalError(R) # θ·axis, θ in [-π, π]
calcTransformError(t1, t2) # error of t1.inverse() * t2
# Finite-difference column of a pose-error jacobian. Subtracting two calcTransformError
# results jumps by ~2π near the angle-axis ±π boundary; this does not.
d = calcJacobianTransformErrorDiff(target, source, source_perturbed)
d = calcJacobianTransformErrorDiff(target, target_perturbed, source, source_perturbed)
d = calcJacobianTransformErrorDiff(target, source, source_perturbed, lower_tol, upper_tol)
v = np.array([-2.0, 0.5, 3.0])
applyTolerances(v, np.full(3, -1.0), np.full(3, 1.0)) # v is now [-1., 0., 2.]
The tolerance pair of applyTolerances and of the tolerance overloads of
calcJacobianTransformErrorDiff must be both empty (no tolerance) or both of the value's
size (6 for the error diff); anything else raises ToleranceSizeMismatchError, a
ValueError.
Resource Locators¶
GeneralResourceLocator¶
Resolves package:// URLs to file paths.
from tesseract_robotics.tesseract_common import GeneralResourceLocator
locator = GeneralResourceLocator()
# Resolve package URL
resource = locator.locateResource("package://tesseract_support/urdf/abb_irb2400.urdf")
path = resource.getFilePath()
A package is a directory that holds a package.xml; its name is the directory name. The
locator finds packages in the directories it is given and in their subdirectories. Without
arguments it reads TESSERACT_RESOURCE_PATH, ROS_PACKAGE_PATH and AMENT_PREFIX_PATH.
Both other constructors take keyword arguments only, because a positional list[str] could
mean either directories or environment variable names:
from pathlib import Path
locator = GeneralResourceLocator(paths=[Path("~/ws/src").expanduser()]) # + the default env vars
locator = GeneralResourceLocator(environment_variables=["MY_RESOURCE_PATH"])
locator.addPath(Path("/opt/robots")) # False if the directory does not exist
locator.loadEnvironmentVariable("MY_PATHS") # False if the variable is unset
locateResource returns None when it cannot resolve the url.
BytesResource¶
In-memory resource from bytes.
from tesseract_robotics.tesseract_common import BytesResource
data = b"<robot name='test'></robot>"
resource = BytesResource("robot.urdf", data)
Every Resource is also a ResourceLocator: resource.locateResource(url) resolves a url
relative to the resource, through the parent locator it was built with (None without a
parent). BytesResource first asks the parent for the url as given, then for the file next
to its own url:
urdf = BytesResource("package://my_robot/urdf/robot.urdf", data, parent=locator)
mesh = urdf.locateResource("base.stl") # tries "base.stl", then "package://my_robot/urdf/base.stl"
Collision¶
AllowedCollisionMatrix¶
Defines which link pairs to skip during collision checking.
from tesseract_robotics.tesseract_common import AllowedCollisionMatrix
acm = AllowedCollisionMatrix()
# Allow collision between links
acm.addAllowedCollision("link_1", "link_2", "Adjacent links")
# Check if allowed
is_allowed = acm.isCollisionAllowed("link_1", "link_2")
# Remove entry
acm.removeAllowedCollision("link_1", "link_2")
# Clear all
acm.clearAllowedCollisions()
An ACM converts to and from its entries, a dict keyed on ordered link pairs. The
constructor orders each key, so ("link_b", "link_a") is stored as ("link_a", "link_b"):
from tesseract_robotics.tesseract_common import (
AllowedCollisionMatrix, getAllowedCollisions, makeOrderedLinkPair,
)
acm = AllowedCollisionMatrix({("link_b", "link_a"): "Adjacent links"})
entries = acm.getAllAllowedCollisions() # {("link_a", "link_b"): "Adjacent links"}
acm.removeAllowedCollision("link_a") # drop every entry involving link_a
acm.reserveAllowedCollisionMatrix(100) # pre-size; entries unchanged
print(acm) # one "link=<a> link=<b> reason=<r>" line per entry
makeOrderedLinkPair("link_b", "link_a") # ("link_a", "link_b")
# Links allowed to collide with any of the given links; each partner once unless
# remove_duplicates=False. The order follows the dict's, so treat it as a set.
getAllowedCollisions(["link_a"], entries)
CollisionMarginData¶
Configure collision margins per link pair.
from tesseract_robotics.tesseract_common import CollisionMarginData
margins = CollisionMarginData()
margins.setDefaultCollisionMargin(0.025)
margins.setCollisionMargin("link_a", "link_b", 0.05)
default = margins.getDefaultCollisionMargin()
pair_margin = margins.getCollisionMargin("link_a", "link_b")
CollisionMarginPairData holds the pair margins alone. Both types build from pair data,
shift and scale every margin, and merge another set of pair margins:
from tesseract_robotics.tesseract_common import (
CollisionMarginData,
CollisionMarginPairData,
CollisionMarginPairOverrideType,
)
pairs = CollisionMarginPairData({("link_a", "link_b"): 0.05, ("link_a", "link_c"): 0.02})
margins = CollisionMarginData(0.025, pairs) # or CollisionMarginData(pairs): default 0.0
margins.incrementMargins(0.01) # default and every pair margin +0.01 m
margins.scaleMargins(2.0) # then x2
# MODIFY sets the given pairs and keeps the rest; REPLACE keeps only the given pairs.
margins.apply(CollisionMarginPairData({("link_b", "link_d"): 0.07}), CollisionMarginPairOverrideType.MODIFY)
margins.getMaxCollisionMargin() # largest margin overall
margins.getMaxCollisionMargin("link_a") # largest margin involving link_a (at least the default)
pairs.getMaxCollisionMargin("link_z") # None: no pair margin involves link_z
State¶
JointState¶
Joint positions and velocities.
from tesseract_robotics.tesseract_common import JointState
state = JointState()
state.joint_names = ["j1", "j2", "j3"]
state.position = np.array([0.0, 0.5, -0.5])
state.velocity = np.array([0.0, 0.0, 0.0])
JointTrajectory¶
A sequence of JointStates with a description and a uuid (its canonical string; a
malformed string raises ValueError). It behaves like a list of states: len, indexing
(negative indices count from the end; out of range raises IndexError), assignment and
iteration, plus push_back, pop_back, front, back, at, clear and empty.
from tesseract_robotics.tesseract_common import JointState, JointTrajectory
names = ["j1", "j2", "j3"]
traj = JointTrajectory([JointState(names, np.zeros(3))], "approach")
traj.push_back(JointState(names, np.array([0.0, 0.5, -0.5])))
js = traj[-1]
js.time = 1.0
traj[-1] = js # write back: element access returns a copy
Element access returns a copy
traj[i], front(), back(), at(i), iteration and the states list are copies,
so traj[0].time = 1.0 changes nothing. Assign the edited state back with
traj[0] = js, or assign a whole new list to traj.states. A reference into the
underlying vector would dangle as soon as push_back reallocated it.
Trajectories compare by value (== compares uuid, description and states) and are
unhashable.
KinematicLimits¶
Joint position, velocity, acceleration limits.
from tesseract_robotics.tesseract_common import KinematicLimits
limits = kin_group.getLimits()
print(f"Position min: {limits.joint_limits[:, 0]}")
print(f"Position max: {limits.joint_limits[:, 1]}")
print(f"Velocity: {limits.velocity_limits}")
print(f"Acceleration: {limits.acceleration_limits}")
print(f"Jerk: {limits.jerk_limits}")
Each field is an (n, 2) array of [lower, upper] rows. limits.resize(n) resizes all
four to (n, 2); their values are unset afterwards, so assign them before use.
Three free functions check or clamp joint values against an (n, 2) limit array:
import numpy as np
from tesseract_robotics.tesseract_common import enforceLimits, isWithinLimits, satisfiesLimits
limits = np.array([[-1.0, 1.0], [-2.0, 2.0]])
isWithinLimits(np.array([0.5, -1.5]), limits) # True: inside or on the bounds, no tolerance
satisfiesLimits(np.array([1.0 + 1e-7, 0.0]), limits) # True: within the 1e-6 default max_diff
enforceLimits(np.array([1.5, -3.0]), limits) # array([ 1., -2.]): a clamped copy
enforceLimits returns a new array and leaves its input unchanged (C++ clamps in place).
isWithinLimits and enforceLimits raise LimitsSizeMismatchError (a ValueError) when
the number of values differs from the number of limit rows.
Manipulator Info¶
ManipulatorInfo¶
Describes a kinematic group configuration.
from tesseract_robotics.tesseract_common import ManipulatorInfo
info = ManipulatorInfo()
info.manipulator = "manipulator" # group name
info.working_frame = "base_link" # reference frame
info.tcp_frame = "tool0" # tool center point
info.tcp_offset = Isometry3d.Identity() # optional TCP offset
# or all at once; tcp_offset (a link name or an Isometry3d) defaults to the identity
info = ManipulatorInfo("manipulator", "base_link", "tool0")
tcp_offset holds a str (a link name) or an Isometry3d; any other type raises
TypeError. Reading it returns a copy, so assign a new value instead of editing it in
place.
info.empty() is True unless manipulator, working_frame and tcp_frame are all set.
base.getCombined(override) returns a copy of base with every non-empty field of
override; an overriding tcp_frame brings override.tcp_offset along with it:
override = ManipulatorInfo()
override.tcp_frame = "tool1"
combined = info.getCombined(override) # tcp_frame "tool1", tcp_offset from override
Plugin Info¶
Plugin configuration for the four plugin loaders: KinematicsPluginInfo,
ContactManagersPluginInfo, ProfilesPluginInfo and TaskComposerPluginInfo. Each has
search_paths, search_libraries, insert(other), clear(), empty() and a read-only
CONFIG_KEY (its key in a tesseract YAML config, e.g. "contact_manager_plugins").
from tesseract_robotics.tesseract_common import (
ContactManagersPluginInfo, PluginInfo, PluginInfoContainer,
)
bullet = PluginInfo()
bullet.class_name = "BulletDiscreteBVHManagerFactory"
info = ContactManagersPluginInfo()
info.search_libraries = ["tesseract_collision_bullet_factories"]
info.discrete_plugin_infos.default_plugin = "BulletDiscreteBVHManager"
info.discrete_plugin_infos.plugins = {"BulletDiscreteBVHManager": bullet}
Note
A PluginInfoContainer field (discrete_plugin_infos, continuous_plugin_infos,
executor_plugin_infos, task_plugin_infos) is a reference: editing it in place
persists. A dict field (PluginInfoContainer.plugins, ProfilesPluginInfo.plugin_infos,
KinematicsPluginInfo.fwd_plugin_infos) converts to a fresh dict on every access:
assign the whole field, since info.plugin_infos["x"] = ... edits a copy.
Calibration Info¶
CalibrationInfo holds calibrated joint origins: for each joint name in joints, the
environment replaces that joint's origin (a ChangeJointOriginCommand). An SRDF carries one
through its <calibration_config> file, read into SRDFModel.calibration_info.
from tesseract_robotics.tesseract_common import CalibrationInfo, Isometry3d
info = CalibrationInfo()
info.joints = {"joint_a1": Isometry3d.Identity()} # assign the whole dict
other = CalibrationInfo()
other.joints = {"joint_a2": Isometry3d.Identity()}
info.insert(other) # overwrites by joint name
assert CalibrationInfo.CONFIG_KEY == "calibration"
Note
joints converts to a fresh dict on every access: info.joints["j"] = t edits a copy.
Equality is upstream's: each transform compares with isApprox(1e-5).
Every class on this page whose C++ type declares operator== compares by value (==, with
the C++ tolerances) and is unhashable, except ResourceLocator and Resource, which compare by
identity (their C++ operator== is always true). KinematicLimits of different shapes are
unequal.
Logging¶
Control console_bridge logging level.
from tesseract_robotics.tesseract_common import (
getLogLevel, setLogLevel,
LogLevel,
)
# Suppress warnings
setLogLevel(LogLevel.CONSOLE_BRIDGE_LOG_ERROR)
# Enable debug output
setLogLevel(LogLevel.CONSOLE_BRIDGE_LOG_DEBUG)
Container Types¶
| Type | Description |
|---|---|
TransformMap |
dict[str, Isometry3d] - link name to transform |
VectorIsometry3d |
list[Isometry3d] |
VectorVector3d |
list[np.ndarray] - list of 3D points |
Auto-generated API Reference¶
tesseract_common Python bindings (nanobind)
Isometry3d
¶
apply_points
¶
apply_points(points: Annotated[NDArray[float64], dict(shape=(None, 3), writable=False)] | Sequence[Sequence[float]]) -> Annotated[NDArray[numpy.float64], dict(shape=(None, 3), order=C)]
apply_directions
¶
apply_directions(vectors: Annotated[NDArray[float64], dict(shape=(None, 3), writable=False)] | Sequence[Sequence[float]]) -> Annotated[NDArray[numpy.float64], dict(shape=(None, 3), order=C)]
translate
¶
translate(vec: Annotated[NDArray[float64], dict(shape=3, order=C)] | Sequence[float]) -> Isometry3d
pretranslate
¶
pretranslate(vec: Annotated[NDArray[float64], dict(shape=3, order=C)] | Sequence[float]) -> Isometry3d
Translation3d
¶
Quaterniond
¶
Hyperplane3d
¶
Through
staticmethod
¶
Through(p0: Annotated[NDArray[float64], dict(shape=3, order=C)] | Sequence[float], p1: Annotated[NDArray[float64], dict(shape=3, order=C)] | Sequence[float], p2: Annotated[NDArray[float64], dict(shape=3, order=C)] | Sequence[float]) -> Hyperplane3d
signedDistance
¶
signedDistance(point: Annotated[NDArray[float64], dict(shape=3, order=C)] | Sequence[float]) -> float
absDistance
¶
absDistance(point: Annotated[NDArray[float64], dict(shape=3, order=C)] | Sequence[float]) -> float
projection
¶
projection(point: Annotated[NDArray[float64], dict(shape=3, order=C)] | Sequence[float]) -> Annotated[NDArray[numpy.float64], dict(shape=3, order=C)]
ParametrizedLine3d
¶
__init__
¶
__init__(origin: Annotated[NDArray[float64], dict(shape=3, order=C)] | Sequence[float], direction: Annotated[NDArray[float64], dict(shape=3, order=C)] | Sequence[float]) -> None
Through
staticmethod
¶
Through(p0: Annotated[NDArray[float64], dict(shape=3, order=C)] | Sequence[float], p1: Annotated[NDArray[float64], dict(shape=3, order=C)] | Sequence[float]) -> ParametrizedLine3d
distance
¶
distance(point: Annotated[NDArray[float64], dict(shape=3, order=C)] | Sequence[float]) -> float
squaredDistance
¶
squaredDistance(point: Annotated[NDArray[float64], dict(shape=3, order=C)] | Sequence[float]) -> float
projection
¶
projection(point: Annotated[NDArray[float64], dict(shape=3, order=C)] | Sequence[float]) -> Annotated[NDArray[numpy.float64], dict(shape=3, order=C)]
intersectionPoint
¶
intersectionPoint(plane: Hyperplane3d) -> Annotated[NDArray[numpy.float64], dict(shape=3, order=C)]
ResourceLocator
¶
GeneralResourceLocator
¶
Bases: ResourceLocator
Resource
¶
BytesResource
¶
SimpleLocatedResource
¶
ManipulatorInfo
¶
tcp_offset
property
writable
¶
TCP offset: a link name (str) or a pose (Isometry3d). Reading returns a copy.
JointState
¶
JointTrajectory
¶
ACMContactAllowedValidator
¶
Bases: ContactAllowedValidator
CombinedContactAllowedValidatorType
¶
CombinedContactAllowedValidator
¶
Bases: ContactAllowedValidator
CollisionMarginPairOverrideType
¶
CollisionMarginPairData
¶
apply
¶
apply(pair_margin_data: CollisionMarginPairData, override_type: CollisionMarginPairOverrideType) -> None
CollisionMarginData
¶
apply
¶
apply(pair_margin_data: CollisionMarginPairData, override_type: CollisionMarginPairOverrideType) -> None
KinematicLimits
¶
LimitsSizeMismatchError
¶
Bases: ValueError
isWithinLimits / enforceLimits got values and limits of different lengths.
ToleranceSizeMismatchError
¶
Bases: ValueError
lower_tolerance / upper_tolerance are not both empty or both the expected size.
PluginInfo
¶
config
property
writable
¶
Plugin config as a YAML document string (a YAML::Node in C++).
PluginInfoContainer
¶
KinematicsPluginInfo
¶
ContactManagersPluginInfo
¶
CalibrationInfo
¶
joints
property
writable
¶
Joint name -> calibrated joint origin. A copy on read: assign the whole dict, or use insert.
TaskComposerPluginInfo
¶
ProfilesPluginInfo
¶
LogLevel
¶
Bases: Enum
OutputHandler
¶
VectorVector3d
¶
VectorIsometry3d
¶
makeOrderedLinkPair
¶
The pair with the lexicographically smaller link name first.
getAllowedCollisions
¶
getAllowedCollisions(link_names: Sequence[str], acm_entries: Mapping[tuple[str, str], str], remove_duplicates: bool = True) -> list[str]
isWithinLimits
¶
isWithinLimits(values: Annotated[NDArray[float64], dict(shape=(None,), writable=False)] | Sequence[float], limits: Annotated[NDArray[float64], dict(shape=(None, 2), writable=False)] | Sequence[Sequence[float]]) -> bool
True if every value lies in its [lower, upper] row; no tolerance.
enforceLimits
¶
enforceLimits(values: Annotated[NDArray[float64], dict(shape=(None,), writable=False)] | Sequence[float], limits: Annotated[NDArray[float64], dict(shape=(None, 2), writable=False)] | Sequence[Sequence[float]]) -> Annotated[NDArray[numpy.float64], dict(shape=(None,), order=C)]
values clamped into limits, as a new array; the input is unchanged.
twistChangeRefPoint
¶
twistChangeRefPoint(twist: Annotated[NDArray[float64], dict(shape=6, order=C)], ref_point: Annotated[NDArray[float64], dict(shape=3, writable=False)] | Sequence[float]) -> None
Move the twist's reference point by ref_point (v += ω × ref_point), in place.
twistChangeBase
¶
twistChangeBase(twist: Annotated[NDArray[float64], dict(shape=6, order=C)], change_base: Isometry3d) -> None
Rotate the twist into the frame change_base, in place.
jacobianChangeBase
¶
jacobianChangeBase(jacobian: Annotated[NDArray[float64], dict(shape=(6, None))], change_base: Isometry3d) -> None
Rotate every column of a (6, n) Fortran-order jacobian into change_base, in place.
jacobianChangeRefPoint
¶
jacobianChangeRefPoint(jacobian: Annotated[NDArray[float64], dict(shape=(6, None))], ref_point: Annotated[NDArray[float64], dict(shape=3, writable=False)] | Sequence[float]) -> None
Move the reference point of a (6, n) Fortran-order jacobian by ref_point, in place.
calcRotationalError
¶
calcRotationalError(R: Annotated[NDArray[float64], dict(shape=(3, 3), writable=False)] | Sequence[Sequence[float]]) -> Annotated[NDArray[numpy.float64], dict(shape=3, order=C)]
Angle-axis vector θ·a of the rotation R, with θ in [-π, π].
calcTransformError
¶
calcTransformError(t1: Isometry3d, t2: Isometry3d) -> Annotated[NDArray[numpy.float64], dict(shape=(None,), order=C)]
Error of t1.inverse() * t2 as [translation, angle-axis rotation].
applyTolerances
¶
applyTolerances(v: Annotated[NDArray[float64], dict(shape=(None,), order=C)], lower_tolerance: Annotated[NDArray[float64], dict(shape=(None,), writable=False)] | Sequence[float], upper_tolerance: Annotated[NDArray[float64], dict(shape=(None,), writable=False)] | Sequence[float]) -> None
In place: 0 inside [lower, upper], v - lower below it, v - upper above it; no-op when both are empty.