Skip to content

Commit

Permalink
Merge pull request #201 from amccaugh/dev
Browse files Browse the repository at this point in the history
1.7.1
  • Loading branch information
amccaugh authored Apr 26, 2024
2 parents d600ee0 + ffc9b4c commit 474b656
Show file tree
Hide file tree
Showing 9 changed files with 362 additions and 109 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# Changelog


## 1.7.1 (April 26, 2024)

### New features
- Allow `Device.get_ports()` to be used with arrays
- Allow setting `num_cpu` in the new `pg.kl_boolean()`, `pg.kl_offset`, `pg.kl_invert()` functions. Global defaults for the number of CPU to be used can be set with e.g. `phidl.config["NUM_CPU"] = 8`
- Added `pg.flatten()` convenience function. This is identical to `Device.flatten()` but instead of modifying the geometry in-place, returns a flattened copy (thanks Bas Nijholt @basnijholt)
- Fixed an issue from `gdspy` where property keys are incorrectly interpreted as signed integers when importing GDS files using `import_gds()` (thanks Bas Nijholt @basnijholt)

### Bugfixes
- Fixed rare problem with mew `pg.kl_invert()` function when `tile_size` was too small



## 1.7.0 (April 9, 2024)

Major optimization update!
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ GDS scripting for Python that's intuitive, fast, and powerful.
- [**Installation / requirements**](#installation--requirements)
- [**Tutorial + examples**](https://phidl.readthedocs.io/en/latest/tutorials.html) (or [try an interactive notebook](https://mybinder.org/v2/gh/amccaugh/phidl/master?filepath=phidl_tutorial_example.ipynb))
- [**Geometry library + function documentation**](https://phidl.readthedocs.io/en/latest/geometry_reference.html)
- [Changelog](https://github.com/amccaugh/phidl/blob/master/CHANGELOG.md) (latest update 1.7.0 on April 9, 2024)
- New KLayout-based boolean/offset/outline functions! These are under the name `pg.kl_boolean()`, `pg.kl_offset`, `pg.kl_outline()`, `pg.kl_invert()`. They utilize the excellent KLayout tile processor, which allows breaking down & parallelizing these operations--in a nutshell, these operations should be much, much faster, and they also are more robust than the gdspy/clipper implementation.
- To use these new functions, you must first `pip install klayout`
- [Changelog](https://github.com/amccaugh/phidl/blob/master/CHANGELOG.md) (latest update 1.7.1 on April 26, 2024)
- New KLayout-based boolean/offset/outline functions! These are under the name `pg.kl_boolean()`, `pg.kl_offset`, `pg.kl_outline()`, `pg.kl_invert()`. They utilize the excellent KLayout tile processor, which allows breaking down & parallelizing these operations--in a nutshell, these operations should be much, much faster, and they also are more robust than the gdspy/clipper implementation. To use these new functions, you must first `pip install klayout`
- `Path.interpolate()` now allows easy placement of objects alongside a path (e.g. for placing vias)


# Citation
Expand Down
7 changes: 7 additions & 0 deletions docs/API.rst
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,13 @@ extract
.. autofunction:: phidl.geometry.extract



flatten
========

.. autofunction:: phidl.geometry.flatten


fill_rectangle
==============

Expand Down
132 changes: 91 additions & 41 deletions docs/tutorials/waveguides.ipynb

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions phidl/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
Path,
Port,
__version__,
config,
make_device,
reset,
)
Expand All @@ -28,4 +29,5 @@
"quickplot",
"quickplot2",
"set_quickplot_options",
"config",
]
197 changes: 148 additions & 49 deletions phidl/device_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@


import hashlib
import multiprocessing
import numbers
import warnings
from copy import deepcopy as _deepcopy
Expand All @@ -54,9 +55,11 @@

gdspy.library.use_current_library = False

__version__ = "1.7.0"
__version__ = "1.7.1"


config = dict(NUM_CPU=multiprocessing.cpu_count())

# ==============================================================================
# Useful transformation functions
# ==============================================================================
Expand Down Expand Up @@ -202,6 +205,49 @@ def _parse_move(origin, destination, axis):
return dx, dy


def _transform_port(
point, orientation, origin=(0, 0), rotation=None, x_reflection=False
):
"""Applies various transformations to a Port.
Parameters
----------
point : array-like[N][2]
Coordinates of the Port.
orientation : int, float, or None
Orientation of the Port
origin : array-like[2] or None
If given, shifts the transformed points to the specified origin.
rotation : int, float, or None
Angle of rotation to apply
x_reflection : bool
If True, reflects the Port across the x-axis before applying
rotation.
Returns
-------
new_point : array-like[N][2]
Coordinates of the transformed Port.
new_orientation : int, float, or None
"""
# Apply GDS-type transformations to a port (x_ref)
new_point = np.array(point)
new_orientation = orientation

if x_reflection:
new_point[1] = -new_point[1]
new_orientation = -orientation
if rotation is not None:
new_point = _rotate_points(new_point, angle=rotation, center=[0, 0])
new_orientation += rotation
if origin is not None:
new_point = new_point + np.array(origin)
new_orientation = mod(new_orientation, 360)

return new_point, new_orientation


def _distribute(elements, direction="x", spacing=100, separation=True, edge=None):
"""Takes a list of elements and distributes them either equally along a
grid or with a fixed spacing between them.
Expand Down Expand Up @@ -884,6 +930,49 @@ def rotate(self, angle=45, center=None):
return self


def _transform_port(
point, orientation, origin=(0, 0), rotation=None, x_reflection=False
):
"""Applies various transformations to a Port.
Parameters
----------
point : array-like[N][2]
Coordinates of the Port.
orientation : int, float, or None
Orientation of the Port
origin : array-like[2] or None
If given, shifts the transformed points to the specified origin.
rotation : int, float, or None
Angle of rotation to apply
x_reflection : bool
If True, reflects the Port across the x-axis before applying
rotation.
Returns
-------
new_point : array-like[N][2]
Coordinates of the transformed Port.
new_orientation : int, float, or None
"""
# Apply GDS-type transformations to a port (x_ref)
new_point = np.array(point)
new_orientation = orientation

if x_reflection:
new_point[1] = -new_point[1]
new_orientation = -orientation
if rotation is not None:
new_point = _rotate_points(new_point, angle=rotation, center=[0, 0])
new_orientation += rotation
if origin is not None:
new_point = new_point + np.array(origin)
new_orientation = mod(new_orientation, 360)

return new_point, new_orientation


class Polygon(gdspy.Polygon, _GeometryHelper):
"""Polygonal geometric object.
Expand Down Expand Up @@ -1656,9 +1745,10 @@ def absorb(self, reference):
def get_ports(self, depth=None):
"""Returns copies of all the ports of the Device, rotated and
translated so that they're in their top-level position. The Ports
returned are copies of the originals, but each copy has the same
``uid`` as the original so that they can be traced back to the
original if needed.
returned are copies of the originals, but each copy has the same ``uid``
as the original so that they can be traced back to the original if
needed. Ports taken from arrays have an additional `array_idx`
parameter added to their .info metadata describing the row/column
Parameters
----------
Expand All @@ -1680,20 +1770,32 @@ def get_ports(self, depth=None):
else:
new_depth = depth - 1
ref_ports = r.parent.get_ports(depth=new_depth)
if isinstance(r, CellArray):
ref_ports_array = []
xs = r.spacing[0]
ys = r.spacing[1]
for i in range(r.rows):
for j in range(r.columns):
for rp in ref_ports:
new_port = rp._copy(new_uid=False)
new_port.midpoint += np.array((xs * j, ys * i))
new_port.info["array_idx"] = (i, j)
ref_ports_array.append(new_port)
ref_ports = ref_ports_array

# Transform ports that came from a reference
ref_ports_transformed = []
for rp in ref_ports:
new_port = rp._copy(new_uid=False)
new_midpoint, new_orientation = r._transform_port(
new_midpoint, new_orientation = _transform_port(
rp.midpoint,
rp.orientation,
r.origin,
r.rotation,
r.x_reflection,
)
new_port.midpoint = new_midpoint
new_port.new_orientation = new_orientation
new_port.orientation = new_orientation
ref_ports_transformed.append(new_port)
port_list += ref_ports_transformed

Expand Down Expand Up @@ -2029,7 +2131,7 @@ def ports(self):
of the ports dict which is correctly rotated and translated."""
for name, port in self.parent.ports.items():
port = self.parent.ports[name]
new_midpoint, new_orientation = self._transform_port(
new_midpoint, new_orientation = _transform_port(
port.midpoint,
port.orientation,
self.origin,
Expand Down Expand Up @@ -2064,48 +2166,6 @@ def bbox(self):
bbox = ((0, 0), (0, 0))
return np.array(bbox)

def _transform_port(
self, point, orientation, origin=(0, 0), rotation=None, x_reflection=False
):
"""Applies various transformations to a Port.
Parameters
----------
point : array-like[N][2]
Coordinates of the Port.
orientation : int, float, or None
Orientation of the Port
origin : array-like[2] or None
If given, shifts the transformed points to the specified origin.
rotation : int, float, or None
Angle of rotation to apply
x_reflection : bool
If True, reflects the Port across the x-axis before applying
rotation.
Returns
-------
new_point : array-like[N][2]
Coordinates of the transformed Port.
new_orientation : int, float, or None
"""
# Apply GDS-type transformations to a port (x_ref)
new_point = np.array(point)
new_orientation = orientation

if x_reflection:
new_point[1] = -new_point[1]
new_orientation = -orientation
if rotation is not None:
new_point = _rotate_points(new_point, angle=rotation, center=[0, 0])
new_orientation += rotation
if origin is not None:
new_point = new_point + np.array(origin)
new_orientation = mod(new_orientation, 360)

return new_point, new_orientation

def move(self, origin=(0, 0), destination=None, axis=None):
"""Moves the DeviceReference from the origin point to the
destination. Both origin and destination can be 1x2 array-like,
Expand Down Expand Up @@ -2899,6 +2959,45 @@ def move(self, origin=(0, 0), destination=None, axis=None):

return self

def interpolate(self, distance, offset=0):
"""
Interpolates points along the length of the Path (with an optional offset)
so that it follows the Path centerline plus an offset. Any distance values
less than zero or greater than the path length will return NaN
Parameters
----------
distance : float, array-like[N]
Distance along the length of the path to interpolate
offset : float, array-like[N], callable
Magnitude of the offset
Returns
-------
points : array-like[N,2]
An array of N interpolated (x,y) points
"""

P = self.copy().offset(offset)

x = P.points[:, 0]
y = P.points[:, 1]
dx = np.diff(x)
dy = np.diff(y)
theta = np.arctan2(dy, dx)
theta = np.concatenate([theta, [theta[-1]]])
ds = np.sqrt((dx) ** 2 + (dy) ** 2)
s = np.cumsum(ds)
s = np.concatenate([[0], s])

interpx = np.interp(distance, s, x, left=np.nan, right=np.nan)
interpy = np.interp(distance, s, y, left=np.nan, right=np.nan)
interpn = np.interp(distance, s, range(len(x)), left=np.nan, right=np.nan)
angle = np.rad2deg(theta[np.floor(interpn).astype(int)])

return np.vstack([interpx, interpy]).T, angle

def rotate(self, angle=45, center=(0, 0)):
"""Rotates all Polygons in the Device around the specified
center point. If no center point specified will rotate around (0,0).
Expand Down
Loading

0 comments on commit 474b656

Please sign in to comment.