LaPDXYExclusion Overview

[1]:
%matplotlib inline
[2]:
import numpy as np
import matplotlib.pyplot as plt
import sys
import xarray as xr

from matplotlib.patches import Polygon

sys.executable;
[3]:
try:
    from bapsf_motion.motion_builder.exclusions import (
        CircularExclusion,
        DividerExclusion,
        GovernExclusion,
        LaPDXYExclusion,
    )
except ModuleNotFoundError:
    from pathlib import Path

    HERE = Path().cwd()
    BAPSF_MOTION = (HERE / ".." / ".." / ".." ).resolve()
    sys.path.append(str(BAPSF_MOTION))

    from bapsf_motion.motion_builder.exclusions import (
        CircularExclusion,
        DividerExclusion,
        GovernExclusion,
        LaPDXYExclusion,
    )
[4]:
plt.rcParams.update(
    {
        # "figure.figsize": [12, 0.56 * 12],
        "figure.figsize": [10, 0.8 * 10],
        "font.size": 16,
    }
)

Usage

Direct usage should never be needed, since the MotionBuilder will handle this given the correct configuration is given to MotionBuilder. The appropriate TOML or dictionary like configurations can be found in the documentation for LaPDXYExclusion.

Is a GovernExclusion

LaPDXYExclusion is a subclass of GovernExclusion. This means the mask generated by LaPDXYExclusion will examine the existing global mask to generate its own mask, and that generated mask will replace the global mask. As a result, there should only be one GovernExclusion used wheneve constructing a motion space.

[5]:
issubclass(LaPDXYExclusion, GovernExclusion)
[5]:
True

Is a Compound Exclusion

This means LaPDXYExclusion leverages other exclusion classes to generate its exclusion layer. These exclusions can be view with the composed_exclusions attribute, which you can see in the next section.

Defining a LaPDXYExclusion

Create an initial global mask.

[6]:
size = 111
side = np.linspace(-55, 55, num=size)
ds = xr.Dataset(
    {"mask": (("x", "y"), np.ones((size, size), dtype=bool))},
    coords={
        "x": side,
        "y": side,
    },
)

The default configuration parameters for LaPDXYExclusion define a probe drive beting deployed on the East side of the LaPD.

[7]:
ex = LaPDXYExclusion(ds)

ds.mask.plot(x="x", y="y");
plt.scatter(
    ex.insertion_point[0],
    ex.insertion_point[1],
    marker="+",
    color="black",
    s=8**2,
)
axis = plt.gca()
axis.set_xlim(-60, 60)
axis.set_ylim(-60, 60)
[7]:
(-60.0, 60.0)
../../_images/notebooks_motion_list_LaPDXYExclusion_12_1.png

The configuration dictionary for this exclusion looks like…

[8]:
ex.config
[8]:
{'diameter': 100,
 'type': 'lapd_xy',
 'port_location': 0,
 'pivot_radius': 58.771,
 'cone_full_angle': 58,
 'include_cone': True}

You can see the exclusion is composed of 4 other exclusions, 1x Shadow2DExclusion, 1x CircularExclusion, and 3x DividerExclusions.

[9]:
ex.composed_exclusions
[9]:
{'shadow': <bapsf_motion.motion_builder.exclusions.shadow.Shadow2DExclusion at 0x7fdaa8a11a90>,
 'chamber': <bapsf_motion.motion_builder.exclusions.circular.CircularExclusion at 0x7fdaa840a0d0>,
 'port': <bapsf_motion.motion_builder.exclusions.divider.DividerExclusion at 0x7fdaa82e22c0>,
 'divider_upper': <bapsf_motion.motion_builder.exclusions.divider.DividerExclusion at 0x7fdaa82e2190>,
 'divider_lower': <bapsf_motion.motion_builder.exclusions.divider.DividerExclusion at 0x7fdaa81915b0>}

Let’s make a slightly more complicated LaPD exclusion layer. In this ā€œcomplicatedā€ scenario let’s assume the probe drive is being deployed on the West side of the LaPD and there is a circular obstruction at the center of the chamber.

[10]:
# Reset the motion space
size = 111
side = np.linspace(-55, 55, num=size)
ds = xr.Dataset(
    {"mask": (("x", "y"), np.ones((size, size), dtype=bool))},
    coords={
        "x": side,
        "y": side,
    },
)

# Add circular obstruction at the center
ex_1 = CircularExclusion(ds, radius=5, center=(0,0), exclude="inside")

# Define West side lapd XY exclusion
ex = LaPDXYExclusion(ds, port_location="W")

# plot mask
ds.mask.plot(x="x", y="y");
plt.scatter(
    ex.insertion_point[0],
    ex.insertion_point[1],
    marker="+",
    color="black",
    s=8**2,
)
axis = plt.gca()
axis.set_xlim(-60, 60)
axis.set_ylim(-60, 60)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[10], line 16
     12 # Add circular obstruction at the center
     13 ex_1 = CircularExclusion(ds, radius=5, center=(0,0), exclude="inside")
     14
     15 # Define West side lapd XY exclusion
---> 16 ex = LaPDXYExclusion(ds, port_location="W")
     17
     18 # plot mask
     19 ds.mask.plot(x="x", y="y");

File ~/checkouts/readthedocs.org/user_builds/bapsf-motion/envs/latest/lib/python3.13/site-packages/bapsf_motion/motion_builder/exclusions/lapd.py:173, in LaPDXYExclusion.__init__(self, ds, diameter, pivot_radius, port_location, cone_full_angle, include_cone, skip_ds_add)
    159 def __init__(
    160     self,
    161     ds: xr.Dataset,
   (...)    169 ):
    170     # pre-define attributes that will be fully defined by self._validate_inputs()
    171     self._insertion_point = None
--> 173     super().__init__(
    174         ds,
    175         diameter=diameter,
    176         pivot_radius=pivot_radius,
    177         port_location=port_location,
    178         cone_full_angle=cone_full_angle,
    179         include_cone=include_cone,
    180         skip_ds_add=skip_ds_add,
    181     )

File ~/checkouts/readthedocs.org/user_builds/bapsf-motion/envs/latest/lib/python3.13/site-packages/bapsf_motion/motion_builder/exclusions/base.py:70, in BaseExclusion.__init__(self, ds, skip_ds_add, **kwargs)
     67     return
     69 # store this mask to the Dataset
---> 70 self.regenerate_exclusion()
     72 # update the global mask
     73 self.update_global_mask()

File ~/checkouts/readthedocs.org/user_builds/bapsf-motion/envs/latest/lib/python3.13/site-packages/bapsf_motion/motion_builder/exclusions/base.py:212, in BaseExclusion.regenerate_exclusion(self)
    204     raise RuntimeError(
    205         f"For exclusion {self.name} skip_ds_add={self.skip_ds_add} and thus "
    206         f"the exclusion can not be regenerated and updated in the Dataset.  "
    207         f"To get the exclusion matrix use the 'ex.exclusion' property."
    208     )
    210 self.composed_exclusions.clear()
--> 212 self._ds[self.name] = self._generate_exclusion()

File ~/checkouts/readthedocs.org/user_builds/bapsf-motion/envs/latest/lib/python3.13/site-packages/bapsf_motion/motion_builder/exclusions/lapd.py:307, in LaPDXYExclusion._generate_exclusion(self)
    302 def _generate_exclusion(self):
    303     """
    304     Generate and return the boolean mask corresponding to the
    305     exclusion configuration.
    306     """
--> 307     ex = self._generate_shadow_exclusion()
    308     self.composed_exclusions["shadow"] = ex
    310     ex = self._generate_chamber_exclusion()

File ~/checkouts/readthedocs.org/user_builds/bapsf-motion/envs/latest/lib/python3.13/site-packages/bapsf_motion/motion_builder/exclusions/lapd.py:418, in LaPDXYExclusion._generate_shadow_exclusion(self)
    417 def _generate_shadow_exclusion(self):
--> 418     ex = Shadow2DExclusion(
    419         self._ds,
    420         skip_ds_add=True,
    421         source_point=self.insertion_point,
    422     )
    423     return ex

File ~/checkouts/readthedocs.org/user_builds/bapsf-motion/envs/latest/lib/python3.13/site-packages/bapsf_motion/motion_builder/exclusions/shadow.py:97, in Shadow2DExclusion.__init__(self, ds, source_point, skip_ds_add)
     94 self._boundaries = None
     95 self._insertion_edge_indices = None
---> 97 super().__init__(
     98     ds,
     99     source_point=source_point,
    100     skip_ds_add=skip_ds_add,
    101 )

File ~/checkouts/readthedocs.org/user_builds/bapsf-motion/envs/latest/lib/python3.13/site-packages/bapsf_motion/motion_builder/exclusions/base.py:66, in BaseExclusion.__init__(self, ds, skip_ds_add, **kwargs)
     64 self._stored_exclusion = None
     65 if self.skip_ds_add:
---> 66     self._stored_exclusion = self._generate_exclusion()
     67     return
     69 # store this mask to the Dataset

File ~/checkouts/readthedocs.org/user_builds/bapsf-motion/envs/latest/lib/python3.13/site-packages/bapsf_motion/motion_builder/exclusions/shadow.py:190, in Shadow2DExclusion._generate_exclusion(self)
    186 corner_rays = self._build_corner_rays(edge_pool)
    188 # Reduce corner rays
    189 # - remove corner arrays that point to points behind nearer edges
--> 190 ray_mask = self._build_corner_ray_mask(corner_rays, edge_pool)
    191 corner_rays = corner_rays[ray_mask]
    193 # Build an array of fanned rays from all the corner rays

File ~/checkouts/readthedocs.org/user_builds/bapsf-motion/envs/latest/lib/python3.13/site-packages/bapsf_motion/motion_builder/exclusions/shadow.py:314, in Shadow2DExclusion._build_corner_ray_mask(self, corner_rays, edge_pool)
    303 # determine if a corner_ray intersects an edge that is closer
    304 # to the insertion point
    305 # - solving the eqn:
   (...)    311 #     closer edge to the insertion point
    312 #
    313 point_deltas = edge_pool[..., 0, :] - self.source_point
--> 314 denominator = np.cross(corner_rays, edge_vectors[:, np.newaxis, ...]).swapaxes(
    315     0, 1
    316 )
    317 mu_array = np.cross(point_deltas, edge_vectors) / denominator
    318 nu_array = (
    319     np.cross(point_deltas[:, np.newaxis, ...], corner_rays).swapaxes(0, 1)
    320     / denominator
    321 )

File ~/checkouts/readthedocs.org/user_builds/bapsf-motion/envs/latest/lib/python3.13/site-packages/numpy/_core/numeric.py:1679, in cross(a, b, axisa, axisb, axisc, axis)
   1677 b = moveaxis(b, axisb, -1)
   1678 if a.shape[-1] != 3 or b.shape[-1] != 3:
-> 1679     raise ValueError(
   1680         f"Both input arrays must be (arrays of) 3-dimensional vectors, "
   1681         f"but they are {a.shape[-1]} and {b.shape[-1]} dimensional instead."
   1682     )
   1684 # Create the output array
   1685 shape = *broadcast(a[..., 0], b[..., 0]).shape, 3

ValueError: Both input arrays must be (arrays of) 3-dimensional vectors, but they are 2 and 2 dimensional instead.

Now you can see the component Shadow2DExclusion is casting a shadow from the center circular exclusion, so the probe can not be moved to that location.

The confiruation for this setup looks like…

[11]:
ex.config
[11]:
{'diameter': 100,
 'type': 'lapd_xy',
 'port_location': 0,
 'pivot_radius': 58.771,
 'cone_full_angle': 58,
 'include_cone': True}