Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow cell reuse when creating cells in parallel #48

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 14 additions & 9 deletions gdshelpers/export/gdsii_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,17 +95,22 @@ def write_cell_to_gdsii_file(outfile, cell, unit=1e-6, grid_steps_per_unit=1000,
grid_step_unit = unit / grid_steps_per_unit
timestamp = datetime.datetime.now() if timestamp is None else timestamp

cells = []
cell_names = []
cells = dict()

def cells_equivalent(cell_a, cell_b):
if cell_a._uuid is None:
return cell_a == cell_b
else:
return cell_a._uuid == cell_b._uuid

def add_cells_to_unique_list(start_cell):
cells.append(start_cell)
cell_names.append(start_cell.name)
cells[start_cell.name] = start_cell
for c in start_cell.cells:
if c['cell'] not in cells:
if c['cell'].name in cell_names:
if c['cell'].name in cells:
if not cells_equivalent(c['cell'], cells[c['cell'].name]):
raise AssertionError(
'Each cell name must be unique, "{}" is used more than once'.format(c['cell'].name))
else:
add_cells_to_unique_list(c['cell'])

add_cells_to_unique_list(cell)
Expand All @@ -121,11 +126,11 @@ def add_cells_to_unique_list(start_cell):
from concurrent.futures import ProcessPoolExecutor
with ProcessPoolExecutor(max_workers=max_workers) as pool:
num = len(cells)
for binary in pool.map(_cell_to_gdsii_binary, cells, (grid_steps_per_unit,) * num, (max_points,) * num,
(max_line_points,) * num, (timestamp,) * num):
for binary in pool.map(_cell_to_gdsii_binary, cells.values(), (grid_steps_per_unit,) * num,
(max_points,) * num, (max_line_points,) * num, (timestamp,) * num):
outfile.write(binary)
else:
for c in cells:
for c in cells.values():
outfile.write(_cell_to_gdsii_binary(c, grid_steps_per_unit, max_points, max_line_points, timestamp))
outfile.write(pack('>2H', 4, 0x0400)) # ENDLIB N0_DATA

Expand Down
5 changes: 5 additions & 0 deletions gdshelpers/geometry/chip.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from gdshelpers.geometry import geometric_union
from gdshelpers.parts.port import Port
import gdshelpers.helpers.layers as std_layers
import uuid


class Cell:
Expand All @@ -32,6 +33,10 @@ def __init__(self, name: str):
# self._bounds is None if the bounds need to be recalculated or if the cell is empty (in which case
# recalculating them is cheap and we don't need to cache them)

self._uuid = uuid.uuid4()
# assign a unique UUID for this cell such that we can identify cells which have been pickled or
# serialized.

@property
def bounds(self):
"""
Expand Down
45 changes: 45 additions & 0 deletions gdshelpers/tests/test_gdsii_export_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,21 @@

from gdshelpers.parts.waveguide import Waveguide
from gdshelpers.geometry.chip import Cell
from gdshelpers.parts.coupler import GratingCoupler
from gdshelpers.parts.port import Port
from gdshelpers.parts.pattern_import import GDSIIImport


def make_test_cell(args):
# Helper function for the test case 'test_parallel'.
# Needs to be global so that it can be pickled for parallel execution
i, gc_cell, port = args
cell = Cell('complex_cell_{}'.format(i))
cell.add_to_layer(1, Waveguide.make_at_port(port).add_straight_segment(10))
cell.add_cell(gc_cell, [0, 0])
return i, cell


class GdsTestCase(unittest.TestCase):
def test_export_import(self):
waveguide = Waveguide([0, 0], 0, 1)
Expand Down Expand Up @@ -60,3 +72,36 @@ def test_parallel_export(self):
cells[0].save('parallel.gds', parallel=True)

self.assertTrue(filecmp.cmp('serial.gds', 'parallel.gds'))

def test_parallel_cell_reuse(self):
"""
This test case tests if it is possible to generate Cells in parallel when they
reuse a common sub cell.
This can be useful when creating a cell is an expensive operation and should
be parallelized, but some components (such as grating couplers) can be reused.
"""
from concurrent.futures import ProcessPoolExecutor
port = Port([0, 0], 0, 1.)

# Create a common cell for grating couplers, which is reused
gc_cell = Cell("gc")
gc = GratingCoupler.make_traditional_coupler_at_port(port.inverted_direction,
full_opening_angle=np.deg2rad(40),
grating_period=1.13,
grating_ff=0.85,
n_gratings=20)
gc_cell.add_to_layer(1, gc)

top_cell = Cell("top")
with ProcessPoolExecutor(max_workers=4) as executor:
for i, cell in executor.map(make_test_cell, [(i, gc_cell, port) for i in range(8)]):
top_cell.add_cell(cell, origin=[i*100, 0])

top_cell.save("test_parallel.gds")

# However, adding two different cells with the same name should still be prohibited
top_cell.add_cell(Cell("foo"))
top_cell.add_cell(Cell("foo"))

with self.assertRaises(AssertionError):
top_cell.save("test_parallel.gds")