From 4e0929b7cd33cdd763d2fd3c4babc011786d623c Mon Sep 17 00:00:00 2001 From: Joaquin Matres <4514346+joamatab@users.noreply.github.com> Date: Sun, 15 May 2022 08:03:46 -0700 Subject: [PATCH 1/6] pins have a have name --- simphony/pins.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/simphony/pins.py b/simphony/pins.py index ceeb39a6..89cd54b8 100644 --- a/simphony/pins.py +++ b/simphony/pins.py @@ -38,6 +38,9 @@ def __init__(self, component: "Model", name: str) -> None: self._connection = None self.name = name + def __repr__(self): + return self.name + def _isconnected(self, *, include_simulators: bool = True) -> bool: """Returns whether or not this pin is connected to another pin. From cde8a68a2f67276d7d98381a738f682bd11f96f0 Mon Sep 17 00:00:00 2001 From: Joaquin Matres <4514346+joamatab@users.noreply.github.com> Date: Sun, 15 May 2022 08:05:34 -0700 Subject: [PATCH 2/6] pin repr returns a string --- simphony/pins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simphony/pins.py b/simphony/pins.py index 89cd54b8..93ada597 100644 --- a/simphony/pins.py +++ b/simphony/pins.py @@ -38,7 +38,7 @@ def __init__(self, component: "Model", name: str) -> None: self._connection = None self.name = name - def __repr__(self): + def __repr__(self) -> str: return self.name def _isconnected(self, *, include_simulators: bool = True) -> bool: From 4f4523bff1a8c9850578d5c0da323ab42a72eea2 Mon Sep 17 00:00:00 2001 From: Joaquin Matres <4514346+joamatab@users.noreply.github.com> Date: Sun, 15 May 2022 08:06:10 -0700 Subject: [PATCH 3/6] clean code with sourcery --- .sourcery.yaml | 2 ++ simphony/__init__.py | 3 ++- simphony/formatters.py | 13 +++------- simphony/libraries/siepic/__init__.py | 32 +++++++---------------- simphony/libraries/siepic/loader.py | 5 +--- simphony/libraries/siepic/parser.py | 8 ++---- simphony/libraries/sipann.py | 2 +- simphony/models.py | 37 +++++++++++---------------- simphony/plugins/siepic/__init__.py | 6 +---- simphony/simulation.py | 11 +++----- simphony/simulators.py | 2 +- simphony/tools.py | 4 +-- 12 files changed, 42 insertions(+), 83 deletions(-) create mode 100644 .sourcery.yaml diff --git a/.sourcery.yaml b/.sourcery.yaml new file mode 100644 index 00000000..f300aba4 --- /dev/null +++ b/.sourcery.yaml @@ -0,0 +1,2 @@ +refactor: + python_version: '3.7' diff --git a/simphony/__init__.py b/simphony/__init__.py index 3918a871..ece776ba 100644 --- a/simphony/__init__.py +++ b/simphony/__init__.py @@ -30,6 +30,7 @@ A Simulator for Photonic circuits """ + import platform import sys @@ -46,5 +47,5 @@ __license__ = "MIT" __project_url__ = "https://github.com/BYUCamachoLab/simphony" __forum_url__ = "https://github.com/BYUCamachoLab/simphony/issues" -__trouble_url__ = __project_url__ + "/wiki/Troubleshooting-Guide" +__trouble_url__ = f"{__project_url__}/wiki/Troubleshooting-Guide" __website_url__ = "https://camacholab.byu.edu/" diff --git a/simphony/formatters.py b/simphony/formatters.py index a4f5769d..d448feca 100644 --- a/simphony/formatters.py +++ b/simphony/formatters.py @@ -226,9 +226,7 @@ def format(self, circuit: "Circuit", freqs: np.array) -> str: data = {"components": [], "connections": []} for i, component in enumerate(circuit): # skip simulators - if isinstance(component, Simulator) or isinstance( - component, SimulationModel - ): + if isinstance(component, (Simulator, SimulationModel)): continue # get a representation for each component @@ -259,9 +257,7 @@ def parse(self, string: str) -> "Circuit": data = json.loads(string) # load all of the components - components = [] - for string in data["components"]: - components.append(Model.from_string(string, formatter=ModelJSONFormatter())) + components = [Model.from_string(string, formatter=ModelJSONFormatter()) for string in data["components"]] # connect the components to each other for i, j, k, l in data["connections"]: @@ -309,10 +305,7 @@ def _instantiate_component(self, component: Dict[str, Any]) -> "Model": mapping = self.__class__.mappings[self.pdk.__name__][component["model"]] # remap the parameter values so they match the components' API - parameters = {} - for k, v in component["params"].items(): - if k in mapping["parameters"]: - parameters[mapping["parameters"][k]] = v + parameters = {mapping["parameters"][k]: v for k, v in component["params"].items() if k in mapping["parameters"]} # instantiate the model and pass in the corrected parameters return getattr(self.pdk, mapping["name"])(**parameters) diff --git a/simphony/libraries/siepic/__init__.py b/simphony/libraries/siepic/__init__.py index 4649492e..b32d20aa 100644 --- a/simphony/libraries/siepic/__init__.py +++ b/simphony/libraries/siepic/__init__.py @@ -113,10 +113,7 @@ def closest(sorted_list, value): return sorted_list[-1] before = sorted_list[pos - 1] after = sorted_list[pos] - if after - value < value - before: - return after - else: - return before + return after if after - value < value - before else before def get_files_from_dir(path): @@ -428,13 +425,8 @@ def _get_matched_args(cls, norm_args, req_args): return norm_args.index(req_args) except ValueError: adjusted_args = cls._find_closest(norm_args, req_args) - msg = ( - "Exact parameters not available for '{}', ".format(cls) - + "using closest approximation (results may not be as accurate).\n" - + "{:<11}{}\n".format("Requested:", req_args) - + "{:<11}{}\n".format("Selected:", adjusted_args) - + "NOTE: Model attributes may have been automatically modified." - ) + msg = (f"Exact parameters not available for '{cls}', " + "using closest approximation (results may not be as accurate).\n") + "{:<11}{}\n".format("Requested:", req_args) + "{:<11}{}\n".format("Selected:", adjusted_args) + "NOTE: Model attributes may have been automatically modified." + warnings.warn(msg, UserWarning) return norm_args.index(adjusted_args) @@ -471,7 +463,7 @@ def _find_closest(normalized, args): errors = [] for count, keys, argset in candidates: - sum_error = sum([abs(percent_diff(argset[key], args[key])) for key in keys]) + sum_error = sum(abs(percent_diff(argset[key], args[key])) for key in keys) errors.append(sum_error / count) idx = np.argmin(errors) return candidates[idx].argset @@ -904,11 +896,8 @@ def __init__( **kwargs ): if polarization not in ["TE", "TM"]: - raise ValueError( - "Unknown polarization value '{}', must be one of 'TE' or 'TM'".format( - polarization - ) - ) + raise ValueError(f"Unknown polarization value '{polarization}', must be one of 'TE' or 'TM'") + # TODO: TM calculations if polarization == "TM": @@ -1012,7 +1001,7 @@ def cacl_s_params(freqs, length, lam0, ne, ng, nd): - (nd * lam0 ** 2 / (4 * np.pi * SPEED_OF_LIGHT)) * ((w - w0) ** 2) ) - for x in range(0, len(freqs)): # build s-matrix from K and waveguide length + for x in range(len(freqs)): # build s-matrix from K and waveguide length s[x, 0, 1] = s[x, 1, 0] = np.exp(-alpha * length + (K[x] * length * 1j)) return s @@ -1057,11 +1046,8 @@ class YBranch(SiEPIC_PDK_Base): def __init__(self, thickness=220e-9, width=500e-9, polarization="TE", **kwargs): if polarization not in ["TE", "TM"]: - raise ValueError( - "Unknown polarization value '{}', must be one of 'TE' or 'TM'".format( - polarization - ) - ) + raise ValueError(f"Unknown polarization value '{polarization}', must be one of 'TE' or 'TM'") + super().__init__( **kwargs, thickness=thickness, width=width, polarization=polarization ) diff --git a/simphony/libraries/siepic/loader.py b/simphony/libraries/siepic/loader.py index d5b81dac..260f7d4f 100644 --- a/simphony/libraries/siepic/loader.py +++ b/simphony/libraries/siepic/loader.py @@ -39,10 +39,7 @@ def take_closest(sorted_list, value): return associations[-1] before = associations[pos - 1] after = associations[pos] - if after - Lc < Lc - before: - return after - else: - return before + return after if after - Lc < Lc - before else before associations[take_closest(list(associations.keys()), 1.35e-5)] diff --git a/simphony/libraries/siepic/parser.py b/simphony/libraries/siepic/parser.py index 7d3f5deb..e4f52123 100644 --- a/simphony/libraries/siepic/parser.py +++ b/simphony/libraries/siepic/parser.py @@ -54,10 +54,7 @@ def visit_file(self, node, visited_children): Returns: list : List of all paramsets (dict) in the file. """ - paramsets = [] - for paramset in visited_children[1]: - paramsets.append(paramset) - return paramsets + return list(visited_children[1]) def visit_paramset(self, node, visited_children): """ @@ -185,8 +182,7 @@ def visit_number(self, node, visited_children): Returns: float : the number cast to a float. """ - value = float(node.text) - return value + return float(node.text) def generic_visit(self, node, visited_children): """The generic visit method.""" diff --git a/simphony/libraries/sipann.py b/simphony/libraries/sipann.py index 8c679d9a..3324e207 100644 --- a/simphony/libraries/sipann.py +++ b/simphony/libraries/sipann.py @@ -71,7 +71,7 @@ def __init__(self, model: TypeVar("M"), sigmas: Dict[str, float], **kwargs) -> N ) self.params = self.model.__dict__.copy() - self.rand_params = dict() + self.rand_params = {} self.regenerate_monte_carlo_parameters() def s_parameters(self, freqs: np.array) -> np.ndarray: diff --git a/simphony/models.py b/simphony/models.py index 3dfce47e..2d751a7a 100644 --- a/simphony/models.py +++ b/simphony/models.py @@ -141,11 +141,7 @@ def _get_next_unconnected_pin(self) -> Pin: def _isconnected(self) -> bool: """Returns whether this component is connected to other components.""" - for pin in self.pins: - if pin._isconnected(): - return True - - return False + return any(pin._isconnected() for pin in self.pins) def _on_connect(self, component: "Model") -> None: """This method is called whenever one of this component's pins is @@ -253,7 +249,7 @@ def interface(self, component: "Model") -> "Model": """ for selfpin in self.pins: for componentpin in component.pins: - if selfpin.name[0:3] != "pin" and selfpin.name == componentpin.name: + if selfpin.name[:3] != "pin" and selfpin.name == componentpin.name: selfpin.connect(componentpin) return self @@ -508,16 +504,15 @@ def __init__( raise ValueError( f"Multiple pins named '{pin.name}' cannot exist in a subcircuit." ) - else: - # keep track of the pin to re-expose - pins.append(pin) + # keep track of the pin to re-expose + pins.append(pin) - # make the pin's owner this component if permanent - if permanent: - pin_names[pin.name] = True - pin._component = self + # make the pin's owner this component if permanent + if permanent: + pin_names[pin.name] = True + pin._component = self - if len(pins) == 0: + if not pins: raise ValueError( "A subcircuit needs to contain at least one unconnected pin." ) @@ -554,13 +549,15 @@ def _s_parameters( # merge all of the s_params into one giant block diagonal matrix for component in self._wrapped_circuit: # simulators don't have scattering parameters - if isinstance(component, Simulator) or isinstance( - component, SimulationModel - ): + if isinstance(component, (Simulator, SimulationModel)): continue # get the s_params from the cache if possible - if s_parameters_method == "s_parameters": + if s_parameters_method == "monte_carlo_s_parameters": + # don't cache Monte Carlo scattering parameters + s_params = getattr(component, s_parameters_method)(freqs) + + elif s_parameters_method == "s_parameters": # each frequency has a different s-matrix, so we need to cache # the s-matrices by frequency as well as component s_params = [] @@ -584,10 +581,6 @@ def _s_parameters( # convert to numpy array for the rest of the function s_params = np.array(s_params) - elif s_parameters_method == "monte_carlo_s_parameters": - # don't cache Monte Carlo scattering parameters - s_params = getattr(component, s_parameters_method)(freqs) - # merge the s_params into the block diagonal matrix if s_block is None: s_block = s_params diff --git a/simphony/plugins/siepic/__init__.py b/simphony/plugins/siepic/__init__.py index 5f654ad2..bff372e8 100644 --- a/simphony/plugins/siepic/__init__.py +++ b/simphony/plugins/siepic/__init__.py @@ -245,11 +245,7 @@ def : dict return {"name": name, "model": model, "ports": ports, "params": params} def visit_ports(self, node, visited_children): - # ports = ((external / internal) ws?)+ - ports = [] - for port in visited_children: - ports.append(port[0][0]) - return ports + return [port[0][0] for port in visited_children] def visit_external(self, node, visited_children): # external = ~r"([-\w]+(detector|laser)[\d]?)" diff --git a/simphony/simulation.py b/simphony/simulation.py index 372989eb..624bde80 100644 --- a/simphony/simulation.py +++ b/simphony/simulation.py @@ -140,9 +140,7 @@ def _expand_array(self, arr: np.array, size: int) -> np.array: # calculate how many times each value needs to be repeated expanded = np.zeros(size) - repeat = int(size / arr_len) - remainder = size % arr_len - + repeat, remainder = divmod(size, arr_len) # expand each value in the given array for i, value in enumerate(arr): # calculate ranges, accounting for remainders @@ -311,10 +309,7 @@ def sample(self, num_samples: int = 1) -> np.ndarray: signals = self._get_signals() # remove the extra sample if we added one - if _num_samples != num_samples: - return signals[:, :, :num_samples] - else: - return signals + return signals[:, :, :num_samples] if _num_samples != num_samples else signals @classmethod def get_context(cls) -> "Simulation": @@ -414,7 +409,7 @@ def __init__( # initialize properties self._coupled_powers = np.array([]) - self._freqs = np.array([freq if freq else wl2freq(wl)]) + self._freqs = np.array([freq or wl2freq(wl)]) self._powers = np.array([power]) self.coupling_ratio = from_db(-np.abs(coupling_loss)) self.freqs = np.array([]) diff --git a/simphony/simulators.py b/simphony/simulators.py index 6da20084..19da7f08 100644 --- a/simphony/simulators.py +++ b/simphony/simulators.py @@ -163,7 +163,7 @@ def simulate( """ freqs, power_ratios = super().simulate(**kwargs, freqs=self.freqs) - mode = mode if mode else self.mode + mode = mode or self.mode if mode == "wl": return (freq2wl(freqs), power_ratios) diff --git a/simphony/tools.py b/simphony/tools.py index 280796b6..1b6a7232 100644 --- a/simphony/tools.py +++ b/simphony/tools.py @@ -77,7 +77,7 @@ def str2float(num): r"([-+]?[0-9]+(?:[.][0-9]+)?)((?:[eE][-+]?[0-9]+)|(?:[a-zA-Z]))?", num ) if len(matches) > 1: - raise ValueError("'{}' is malformed".format(num)) + raise ValueError(f"'{num}' is malformed") num, suffix = matches[0] try: if suffix.startswith("e") or suffix.startswith("E"): @@ -85,7 +85,7 @@ def str2float(num): else: return float(num + (MATH_SUFFIXES[suffix] if suffix != "" else "")) except KeyError as e: - raise ValueError("Suffix {} in '{}' not recognized.".format(str(e), matches[0])) + raise ValueError(f"Suffix {str(e)} in '{matches[0]}' not recognized.") def freq2wl(freq): From e7f74e0edc4b7007cf1140a0c162bdc685571a44 Mon Sep 17 00:00:00 2001 From: Joaquin Matres <4514346+joamatab@users.noreply.github.com> Date: Sun, 15 May 2022 09:34:15 -0700 Subject: [PATCH 4/6] improve docstrings --- simphony/libraries/sipann.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/simphony/libraries/sipann.py b/simphony/libraries/sipann.py index 3324e207..8389f535 100644 --- a/simphony/libraries/sipann.py +++ b/simphony/libraries/sipann.py @@ -132,7 +132,7 @@ def converted_func(input: float) -> float: class GapFuncSymmetric(SipannWrapper): - """Symmetric directional coupler, meaning both waveguides are the same + r"""Symmetric directional coupler, meaning both waveguides are the same shape. A gap function must describe the shape of the two @@ -211,7 +211,7 @@ def __init__( class GapFuncAntiSymmetric(SipannWrapper): - """Antisymmetric directional coupler, meaning both waveguides are + r"""Antisymmetric directional coupler, meaning both waveguides are differently shaped. A gap function describing the vertical distance between @@ -303,7 +303,7 @@ def __init__( class HalfRing(SipannWrapper): - """Half of a ring resonator. + r"""Half of a ring resonator. Uses a radius and a gap to describe the shape. @@ -363,7 +363,7 @@ def __init__( class HalfRacetrack(SipannWrapper): - """Half of a ring resonator, similar to the HalfRing class. + r"""Half of a ring resonator, similar to the HalfRing class. Uses a radius, gap and length to describe the shape of the device. @@ -490,7 +490,7 @@ def __init__( class Standard(SipannWrapper): - """Standard-shaped directional coupler. + r"""Standard-shaped directional coupler. Described by a gap, length, horizontal and vertical distance. @@ -567,7 +567,7 @@ def __init__( class DoubleHalfRing(SipannWrapper): - """Two equally sized half-rings coupling along their edges. + r"""Two equally sized half-rings coupling along their edges. Described by a radius and a gap between the two rings. @@ -629,7 +629,7 @@ def __init__( class AngledHalfRing(SipannWrapper): - """A halfring resonator, except what was the straight waveguide is now + r"""A halfring resonator, except what was the straight waveguide is now curved. Described by a radius, gap, and angle (theta) that the @@ -746,7 +746,7 @@ def __init__( class Racetrack(SipannWrapper): - """Racetrack waveguide arc, used to connect to a racetrack directional + r"""Racetrack waveguide arc, used to connect to a racetrack directional coupler. Ports labeled as: @@ -815,7 +815,7 @@ def __init__( class PremadeCoupler(SipannWrapper): - """Loads premade couplers. + r"""Loads premade couplers. Various splitting ratio couplers have been made and saved. This function reloads them. Note that each of their lengths are different and are also returned for the users info. These have all been designed with waveguide From ade8a01fefa97541893225f93f4c58f3b7878487 Mon Sep 17 00:00:00 2001 From: SkandanC Date: Wed, 26 Oct 2022 20:39:50 -0400 Subject: [PATCH 5/6] Revert "Merge branch 'add-3.11-to-workflows' into pr/66" This reverts commit 35739af27977062bf1ca2f1224f54f99b40fa752, reversing changes made to e07d61a2fa8dc1e08973c9db2ebe8fb227afd3ef. --- .github/workflows/build-and-test.yml | 6 +- simphony/tests/test_models.py | 3 +- simphony/tests/test_simulation.py | 86 +++++++++++++--------------- tox.ini | 13 +---- 4 files changed, 45 insertions(+), 63 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index f7ef113a..1b5e3fb3 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -19,11 +19,11 @@ jobs: flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics flake8 . --count --ignore=E501,E741,W503,W605 --max-complexity=12 --statistics - name: Run Tox - run: tox -e 'py${{ matrix.python-version }}' + run: tox -e py strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] - python-version: ['3.7', '3.8', '3.9', '3.10', '3.11'] + python-version: ['3.7', '3.8', '3.9', '3.10', '3.11.0'] name: build and test on: pull_request: @@ -31,4 +31,4 @@ on: - master push: branches: - - master \ No newline at end of file + - master diff --git a/simphony/tests/test_models.py b/simphony/tests/test_models.py index 5e5e43aa..a4403f26 100644 --- a/simphony/tests/test_models.py +++ b/simphony/tests/test_models.py @@ -128,5 +128,4 @@ class TestModelComponent: def testcomponent(self, y1): y2 = siepic.YBranch(name="y2") - if hasattr(y1, "component"): - assert y1.component != y2.component + assert y1.component != y2.component diff --git a/simphony/tests/test_simulation.py b/simphony/tests/test_simulation.py index 4387f0c4..5b4e907b 100644 --- a/simphony/tests/test_simulation.py +++ b/simphony/tests/test_simulation.py @@ -2,11 +2,7 @@ # Licensed under the terms of the MIT License # (see simphony/__init__.py for details) -try: - import gdsfactory as gf - _has_gf = True -except ImportError: - _has_gf = False +import gdsfactory as gf import numpy as np import pytest @@ -32,59 +28,57 @@ def mzi(): @pytest.fixture def mzi_gf(): - if _has_gf: - gc_input = siepic.GratingCoupler(name="gc_input") - y_splitter = siepic.YBranch(name="y_splitter") - wg_long = siepic.Waveguide(name="wg_long", length=150e-6) - wg_short = siepic.Waveguide(name="wg_short", length=50e-6) - y_recombiner = siepic.YBranch(name="y_recombiner") - gc_output = siepic.GratingCoupler(name="gc_output") - c = gf.Component("mzi") + gc_input = siepic.GratingCoupler(name="gc_input") + y_splitter = siepic.YBranch(name="y_splitter") + wg_long = siepic.Waveguide(name="wg_long", length=150e-6) + wg_short = siepic.Waveguide(name="wg_short", length=50e-6) + y_recombiner = siepic.YBranch(name="y_recombiner") + gc_output = siepic.GratingCoupler(name="gc_output") - ysplit = c << y_splitter.component + c = gf.Component("mzi") - gcin = c << gc_input.component + ysplit = c << y_splitter.component - gcout = c << gc_output.component + gcin = c << gc_input.component - yrecomb = c << y_recombiner.component + gcout = c << gc_output.component - yrecomb.move(destination=(0, -55.5)) - gcout.move(destination=(-20.4, -55.5)) - gcin.move(destination=(-20.4, 0)) + yrecomb = c << y_recombiner.component - gc_input["pin2"].connect(y_splitter, gcin, ysplit) - gc_output["pin2"].connect(y_recombiner["pin1"], gcout, yrecomb) - y_splitter["pin2"].connect(wg_long) - y_recombiner["pin3"].connect(wg_long) - y_splitter["pin3"].connect(wg_short) - y_recombiner["pin2"].connect(wg_short) + yrecomb.move(destination=(0, -55.5)) + gcout.move(destination=(-20.4, -55.5)) + gcin.move(destination=(-20.4, 0)) - wg_long_ref = gf.routing.get_route_from_steps( - ysplit.ports["pin2"], - yrecomb.ports["pin3"], - steps=[{"dx": 91.75 / 2}, {"dy": -61}], - ) - wg_short_ref = gf.routing.get_route_from_steps( - ysplit.ports["pin3"], - yrecomb.ports["pin2"], - steps=[{"dx": 47.25 / 2}, {"dy": -50}], - ) + gc_input["pin2"].connect(y_splitter, gcin, ysplit) + gc_output["pin2"].connect(y_recombiner["pin1"], gcout, yrecomb) + y_splitter["pin2"].connect(wg_long) + y_recombiner["pin3"].connect(wg_long) + y_splitter["pin3"].connect(wg_short) + y_recombiner["pin2"].connect(wg_short) - wg_long.path = wg_long_ref - print(wg_long.path) - wg_short.path = wg_short_ref + wg_long_ref = gf.routing.get_route_from_steps( + ysplit.ports["pin2"], + yrecomb.ports["pin3"], + steps=[{"dx": 91.75 / 2}, {"dy": -61}], + ) + wg_short_ref = gf.routing.get_route_from_steps( + ysplit.ports["pin3"], + yrecomb.ports["pin2"], + steps=[{"dx": 47.25 / 2}, {"dy": -50}], + ) - c.add(wg_long_ref.references) - c.add(wg_short_ref.references) + wg_long.path = wg_long_ref + print(wg_long.path) + wg_short.path = wg_short_ref - c.add_port("o1", port=gcin.ports["pin2"]) - c.add_port("o2", port=gcout.ports["pin2"]) + c.add(wg_long_ref.references) + c.add(wg_short_ref.references) - return (c, gc_input, gc_output) + c.add_port("o1", port=gcin.ports["pin2"]) + c.add_port("o2", port=gcout.ports["pin2"]) - return + return (c, gc_input, gc_output) @pytest.fixture @@ -328,8 +322,6 @@ def test_sampling_frequency(self, mzi): assert np.allclose(data1[0][0], data2[0][0], rtol=0, atol=1e-11) def test_layout_aware(self, mzi_gf): - if not _has_gf: - return c, gc_input, gc_output = mzi_gf with Simulation(fs=10e9, seed=117) as sim: diff --git a/tox.ini b/tox.ini index 9bcefe35..4b9a48e6 100644 --- a/tox.ini +++ b/tox.ini @@ -1,18 +1,9 @@ [tox] -envlist = 'py3.7', 'py3.8', 'py3.9', 'py3.10', 'py3.11' - -[testenv:py3.11] -deps = - numpy - pytest - scipy +envlist = py37, py38, py39, py310 [testenv] deps = - {py3.7}: gdsfactory - {py3.8}: gdsfactory - {py3.9}: gdsfactory - {py3.10}: gdsfactory + gdsfactory numpy pytest scipy From 037927fff36ed2d5695d2f5ae578059c97f21361 Mon Sep 17 00:00:00 2001 From: SkandanC Date: Wed, 26 Oct 2022 20:40:38 -0400 Subject: [PATCH 6/6] Revert "Merge remote-tracking branch 'upstream/master' into pr/66" This reverts commit e07d61a2fa8dc1e08973c9db2ebe8fb227afd3ef, reversing changes made to 52f4928efc880e2e495af17d214eec9654601fb6. --- .github/workflows/build-and-test.yml | 6 +- README.md | 20 - docs/source/index.rst | 1 - docs/source/reference/api.rst | 5 - docs/source/tutorials/images/layout_aware.png | Bin 65671 -> 0 bytes .../tutorials/images/mzi_layout_aware.png | Bin 23348 -> 0 bytes docs/source/tutorials/intro.rst | 4 +- docs/source/tutorials/layout_aware.rst | 172 ---- examples/filters.py | 2 +- examples/layout_aware.py | 115 --- examples/mzi.py | 1 + examples/subnetwork_growth.py | 1 - setup.py | 1 - simphony/connect.py | 1 - simphony/formatters.py | 56 +- simphony/layout.py | 4 +- simphony/libraries/siepic/__init__.py | 727 +++-------------- .../siepic/source_data/ebeam_y_1550.gds | Bin 12006 -> 0 bytes simphony/libraries/sipann.py | 150 ---- simphony/models.py | 244 ++---- simphony/pins.py | 33 +- simphony/simulation.py | 373 +++------ simphony/simulators.py | 5 +- .../tests/mzi_monte_carlo_sparameters.npy | Bin 3328 -> 0 bytes simphony/tests/mzi_sparameters.npy | Bin 3328 -> 0 bytes simphony/tests/test_formatters.py | 8 +- simphony/tests/test_layout.py | 411 +++++++++- simphony/tests/test_models.py | 7 - simphony/tests/test_simulation.py | 767 ++++++++---------- tox.ini | 1 - 30 files changed, 1030 insertions(+), 2085 deletions(-) delete mode 100644 docs/source/tutorials/images/layout_aware.png delete mode 100644 docs/source/tutorials/images/mzi_layout_aware.png delete mode 100644 docs/source/tutorials/layout_aware.rst delete mode 100644 examples/layout_aware.py delete mode 100644 simphony/libraries/siepic/source_data/ebeam_y_1550.gds delete mode 100644 simphony/tests/mzi_monte_carlo_sparameters.npy delete mode 100644 simphony/tests/mzi_sparameters.npy diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 1b5e3fb3..c9dc1db2 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -5,9 +5,9 @@ jobs: build: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v1 with: python-version: ${{ matrix.python-version }} - name: Install dependencies @@ -23,7 +23,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] - python-version: ['3.7', '3.8', '3.9', '3.10', '3.11.0'] + python-version: ['3.7', '3.8', '3.9', '3.10'] name: build and test on: pull_request: diff --git a/README.md b/README.md index 264be800..d5220120 100644 --- a/README.md +++ b/README.md @@ -44,23 +44,3 @@ The documentation is hosted [online](https://simphonyphotonics.readthedocs.io/en Changelogs can be found in docs/changelog/. There is a changelog file for each released version of the software. - -## Bibtex citation - -``` -@article{DBLP:journals/corr/abs-2009-05146, - author = {Sequoia Ploeg and - Hyrum Gunther and - Ryan M. Camacho}, - title = {Simphony: An open-source photonic integrated circuit simulation framework}, - journal = {CoRR}, - volume = {abs/2009.05146}, - year = {2020}, - url = {https://arxiv.org/abs/2009.05146}, - eprinttype = {arXiv}, - eprint = {2009.05146}, - timestamp = {Thu, 17 Sep 2020 12:49:52 +0200}, - biburl = {https://dblp.org/rec/journals/corr/abs-2009-05146.bib}, - bibsource = {dblp computer science bibliography, https://dblp.org} -} -``` diff --git a/docs/source/index.rst b/docs/source/index.rst index cc62ca03..35ecad3e 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -38,7 +38,6 @@ accessible through the sidebar navigation. tutorials/intro tutorials/mzi tutorials/filters - tutorials/layout_aware .. toctree:: :hidden: diff --git a/docs/source/reference/api.rst b/docs/source/reference/api.rst index 29092041..6ccd6fb3 100644 --- a/docs/source/reference/api.rst +++ b/docs/source/reference/api.rst @@ -8,11 +8,6 @@ API :inherited-members: :show-inheritance: -.. automodule:: simphony.die - :members: - :inherited-members: - :show-inheritance: - .. automodule:: simphony.formatters :members: :inherited-members: diff --git a/docs/source/tutorials/images/layout_aware.png b/docs/source/tutorials/images/layout_aware.png deleted file mode 100644 index 496e95bf68855dc041bfb64fb5f85a02b8a9d80a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65671 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
sp(I$-ItPPK|paOu#PVKix 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 q8+}R0|0RE; diff --git a/docs/source/tutorials/images/mzi_layout_aware.png b/docs/source/tutorials/images/mzi_layout_aware.png deleted file mode 100644 index 17c28fbe98083a22391dbba0de31da8fa4e0d20e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23348 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 diff --git a/docs/source/tutorials/intro.rst b/docs/source/tutorials/intro.rst index 11f51b81..5eaa9041 100644 --- a/docs/source/tutorials/intro.rst +++ b/docs/source/tutorials/intro.rst @@ -133,8 +133,8 @@ order to preserve the original circuit. Let's run a simple sweep simulation on the circuit we have created: :: - from simphony.simulators import SweepSimulator - simulation = SweepSimulator(1500e-9, 1600e-9) + from simphony.simulators import SweepSimulation + simulation = SweepSimulation(1500e-9, 1600e-9) simulation.multiconnect(component1['input'], component2['pin2']) result = simulation.simulate() diff --git a/docs/source/tutorials/layout_aware.rst b/docs/source/tutorials/layout_aware.rst deleted file mode 100644 index 416cbcba..00000000 --- a/docs/source/tutorials/layout_aware.rst +++ /dev/null @@ -1,172 +0,0 @@ -.. _example-layout_aware: - -Layout-Aware Monte Carlo Simulations for Yield Estimation -========================================================= - -Manufacturing variability can cause fabrication errors in waveguide width and thickness, -which can affect the device performance. Hence, incorporating manufacturing variability -into the photonic device design process is crucial. Simphony provides the ability to run -layout-aware Monte Carlo simulations for yield estimation to aid in robust photonic device design. -This tutorial will walk you through the codes found in ``examples/layout_aware.py`` of the Simphony repository. -We expect you to have read the previous tutorial, :doc:`mzi`. - - -The Workflow ------------- -The workflow for running layout-aware Monte Carlo simulations is as follows: - -1. instantiate the components -2. generate a layout using the components' ``component`` attributes -3. connect the components to form a circuit -4. run the simulation -5. extract and plot the results - -Example - Mach-Zehnder Interferometer (MZI) -------------------------------------------- -We will run a layout-aware Monte Carlo simulation for the -Mach-Zehnder Interferometer (MZI) described in the previous -example. - -First we need to import the necessary Simphony modules. We will need the ``siepic`` model library, -the ``Simulation``, ``Laser``, and ``Detector``. We will also need to import ``gdsfactory`` to generate the layout. -We will also import ``matplotlib.pyplot``, from outside of Simphony, to view the results -of our simulation. - -:: - import gdsfactory as gf - import matplotlib.pyplot as plt - import numpy as np - - from simphony.libraries import siepic - from simphony.simulation import Detector, Laser, Simulation - -We then create all the components and give them names. These -include the grating couplers, the Y-branches, and the -waveguides (which can be defined at any arbitrary length, -on the condition that the two lengths are different). - -:: - gc_input = siepic.GratingCoupler(name="gcinput") - y_splitter = siepic.YBranch(name="ysplit") - wg_long = siepic.Waveguide(length=150e-6, name="wglong") - wg_short = siepic.Waveguide(length=50e-6, name="wgshort") - y_recombiner = siepic.YBranch(name="y_recombiner") - gc_output = siepic.GratingCoupler(name="gcoutput") - -We then use the components' ``component`` attributes to create the layout. -The ``component`` attributes are ``gdsfactory.Component`` objects, and so can be used to create a -layout. We will next define a Parametric Cell (PCell) for the MZI. We will connect -the components to form a circuit, route the Waveguides using ``gdsfactory's`` routing functions. - -:: - @gf.cell - def mzi(): - c = gf.Component("mzi") - - ysplit = c << y_splitter.component - - gcin = c << gc_input.component - - gcout = c << gc_output.component - - yrecomb = c << y_recombiner.component - - yrecomb.move(destination=(0, -55.5)) - gcout.move(destination=(-20.4, -55.5)) - gcin.move(destination=(-20.4, 0)) - - gc_input["pin1"].connect(y_splitter, gcin, ysplit) - gc_output["pin1"].connect(y_recombiner["pin1"], gcout, yrecomb) - y_splitter["pin2"].connect(wg_long) - y_recombiner["pin3"].connect(wg_long) - y_splitter["pin3"].connect(wg_short) - y_recombiner["pin2"].connect(wg_short) - - wg_long_ref = gf.routing.get_route_from_steps( - ysplit.ports["pin2"], - yrecomb.ports["pin3"], - steps=[{"dx": 91.75 / 2}, {"dy": -61}], - ) - wg_short_ref = gf.routing.get_route_from_steps( - ysplit.ports["pin3"], - yrecomb.ports["pin2"], - steps=[{"dx": 47.25 / 2}, {"dy": -50}], - ) - - wg_long.path = wg_long_ref - wg_short.path = wg_short_ref - - c.add(wg_long_ref.references) - c.add(wg_short_ref.references) - - c.add_port("o1", port=gcin.ports["pin2"]) - c.add_port("o2", port=gcout.ports["pin2"]) - - return c - -We can then call the function, and visualize the layout in KLayout. - -:: - c = mzi() - c.show() - -. image:: images/mzi_layout_aware.png - :alt: layout - :align: center - -We can also view a 3D representation of the layout. - -:: - c.to_3d().show("gl") - - -Layout-Aware Monte Carlo Simulation ------------------------------------ -We use the ``Simulation`` class to run a simulation. We attach a ``Laser`` to one of the GratingCouplers, -and a ``Detector`` to the other GratingCoupler. - -:: - with Simulation() as sim: - l = Laser(power=1) - l.freqsweep(187370000000000.0, 199862000000000.0) - l.connect(gc_input['pin2']) - d = Detector() - d.connect(gc_output['pin2']) - - results = sim.layout_aware_simulation(c) - -Here, we can pass in the standard deviations of the widths and thicknesses, as well as the correlation length -as arguments to the ``layout_aware_simulation`` method. For this example, we use the default values. - -After the simulation is run, we can extract the results, and plot them. We will see several, slightly different curves -due to random variations incorporated into the components' widths and thicknesses. - -:: - f = l.freqs - for run in results: - p = [] - for sample in run: - for data_list in sample: - for data in data_list: - p.append(data) - plt.plot(f, p) - - run_0 = results[0] - p = [] - for sample in run_0: - for data_list in sample: - for data in data_list: - p.append(data) - plt.plot(f, p, 'k') - plt.title('MZI Layout Aware Monte Carlo') - plt.show() - -You should see something similar to this graph when you run -your MZI now: - -.. image:: images/layout_aware.png - :alt: layout-aware simulation - :align: center - -From our data, we can then compute various performance markers which are sensitive -to width and thickness variations. diff --git a/examples/filters.py b/examples/filters.py index da7ca572..ca124683 100644 --- a/examples/filters.py +++ b/examples/filters.py @@ -5,8 +5,8 @@ # # File: filters.py -import matplotlib.gridspec as gridspec import matplotlib.pyplot as plt +import matplotlib.gridspec as gridspec from simphony.libraries import siepic, sipann from simphony.simulators import SweepSimulator diff --git a/examples/layout_aware.py b/examples/layout_aware.py deleted file mode 100644 index fe11a776..00000000 --- a/examples/layout_aware.py +++ /dev/null @@ -1,115 +0,0 @@ -# Copyright © Simphony Project Contributors -# Licensed under the terms of the MIT License -# (see simphony/__init__.py for details) - -""" -layout_aware.py ---------------- - -Author: Skandan Chandrasekar -Modified: August 10, 2022 - -A script that walks the user through the process of running layout-aware Monte Carlo simulations. -""" - - -# import necessary modules -import gdsfactory as gf -import matplotlib.pyplot as plt - -from simphony.libraries import siepic -from simphony.simulation import Detector, Laser, Simulation - -# instantiate components -ysplitter = siepic.YBranch(name="ysplitter") -gcinput = siepic.GratingCoupler(name="gcinput") -gcoutput = siepic.GratingCoupler(name="gcoutput") -yrecombiner = siepic.YBranch(name="yrecombiner") -wg_long = siepic.Waveguide(name="wg_long", length=150e-6) -wg_short = siepic.Waveguide(name="wg_short", length=50e-6) - - -# define a PCell using simphony components -@gf.cell -def mzi(): - c = gf.Component("mzi") - - ysplit = c << ysplitter.component - - gcin = c << gcinput.component - - gcout = c << gcoutput.component - - yrecomb = c << yrecombiner.component - - yrecomb.move(destination=(0, -55.5)) - gcout.move(destination=(-20.4, -55.5)) - gcin.move(destination=(-20.4, 0)) - - gcinput["pin1"].connect(ysplitter, gcin, ysplit) - gcoutput["pin1"].connect(yrecombiner["pin1"], gcout, yrecomb) - ysplitter["pin2"].connect(wg_long) - yrecombiner["pin3"].connect(wg_long) - ysplitter["pin3"].connect(wg_short) - yrecombiner["pin2"].connect(wg_short) - - wg_long_ref = gf.routing.get_route_from_steps( - ysplit.ports["pin2"], - yrecomb.ports["pin3"], - steps=[{"dx": 91.75 / 2}, {"dy": -61}], - ) - wg_short_ref = gf.routing.get_route_from_steps( - ysplit.ports["pin3"], - yrecomb.ports["pin2"], - steps=[{"dx": 47.25 / 2}, {"dy": -50}], - ) - - wg_long.path = wg_long_ref - wg_short.path = wg_short_ref - - c.add(wg_long_ref.references) - c.add(wg_short_ref.references) - - c.add_port("o1", port=gcin.ports["pin2"]) - c.add_port("o2", port=gcout.ports["pin2"]) - - return c - - -c = mzi() -c.show() # open in KLayout -c.to_3d().show("gl") # 3D visualization - - -# run the layout aware simulation -with Simulation() as sim: - l = Laser(name="laser", power=1) - l.freqsweep(187370000000000.0, 199862000000000.0) - l.connect(gcinput.pins["pin2"]) - - d = Detector(name="detector") - d.connect(gcoutput.pins["pin2"]) - - results = sim.layout_aware_simulation(c) - -# Plot the results -f = l.freqs -for run in results: - p = [] - for sample in run: - for data_list in sample: - for data in data_list: - p.append(data) - plt.plot(f, p) - -run = results[0] -p = [] -for sample in run: - for data_list in sample: - for data in data_list: - p.append(data) -plt.plot(f, p, "k") -plt.xlabel("Frequency (Hz)") -plt.ylabel("Power Ratios") -plt.title("MZI Location - Aware Monte Carlo") -plt.show() diff --git a/examples/mzi.py b/examples/mzi.py index 9358d12e..3f4457b2 100644 --- a/examples/mzi.py +++ b/examples/mzi.py @@ -3,6 +3,7 @@ # (see simphony/__init__.py for details) import matplotlib.pyplot as plt +import numpy as np from simphony.libraries import siepic from simphony.simulation import Detector, Laser, Simulation diff --git a/examples/subnetwork_growth.py b/examples/subnetwork_growth.py index f67ca121..cb6b39b9 100644 --- a/examples/subnetwork_growth.py +++ b/examples/subnetwork_growth.py @@ -98,7 +98,6 @@ import matplotlib.pyplot as plt import numpy as np - from simphony.connect import connect_s, innerconnect_s from simphony.libraries import siepic, sipann from simphony.tools import wl2freq diff --git a/setup.py b/setup.py index 68676754..1786fb0a 100644 --- a/setup.py +++ b/setup.py @@ -78,7 +78,6 @@ extras_require = { "test": ["pytest"], - "gdsfactory": ["gdsfactory"], } if "setuptools" in sys.modules: diff --git a/simphony/connect.py b/simphony/connect.py index 5da7c51f..d91cd6bc 100644 --- a/simphony/connect.py +++ b/simphony/connect.py @@ -46,7 +46,6 @@ from simphony.tools import add_polar, mul_polar - # Functions operating on s-parameter matrices def connect_s(A, k, B, l): """ diff --git a/simphony/formatters.py b/simphony/formatters.py index 3f6845b6..d448feca 100644 --- a/simphony/formatters.py +++ b/simphony/formatters.py @@ -271,57 +271,19 @@ class CircuitSiEPICFormatter(CircuitFormatter): mappings = { "simphony.libraries.siepic": { - "ebeam_bdc_te1550": { - "name": "BidirectionalCoupler", - "parameters": { - "lay_x": "component.x", - "lay_y": "component.y", - }, - }, - "ebeam_dc_halfring_straight": { - "name": "HalfRing", - "parameters": { - "lay_x": "component.x", - "lay_y": "component.y", - }, - }, - "ebeam_dc_te1550": { - "name": "DirectionalCoupler", - "parameters": { - "lay_x": "component.x", - "lay_y": "component.y", - }, - }, - "ebeam_gc_te1550": { - "name": "GratingCoupler", - "parameters": { - "lay_x": "component.x", - "lay_y": "component.y", - }, - }, - "ebeam_terminator_te1550": { - "name": "Terminator", - "parameters": { - "lay_x": "component.x", - "lay_y": "component.y", - }, - }, + "ebeam_bdc_te1550": {"name": "BidirectionalCoupler", "parameters": {}}, + "ebeam_dc_halfring_straight": {"name": "HalfRing", "parameters": {}}, + "ebeam_dc_te1550": {"name": "DirectionalCoupler", "parameters": {}}, + "ebeam_gc_te1550": {"name": "GratingCoupler", "parameters": {}}, + "ebeam_terminator_te1550": {"name": "Terminator", "parameters": {}}, "ebeam_wg_integral_1550": { "name": "Waveguide", "parameters": { "wg_length": "length", "wg_width": "width", - "lay_x": "component.x", - "lay_y": "component.y", - }, - }, - "ebeam_y_1550": { - "name": "YBranch", - "parameters": { - "lay_x": "component.x", - "lay_y": "component.y", }, }, + "ebeam_y_1550": {"name": "YBranch", "parameters": {}}, }, } @@ -391,9 +353,9 @@ def parse(self, string: str) -> "Circuit": else "freq" ) - simulator = SweepSimulator(start, stop, points) - simulator.mode = mode - simulator.multiconnect(subcircuit[output], subcircuit[input]) + simulator = SweepSimulator(start, stop, points) + simulator.mode = mode + simulator.multiconnect(subcircuit[output], subcircuit[input]) # no reason to include the subcircuit component for now return subcircuit._wrapped_circuit diff --git a/simphony/layout.py b/simphony/layout.py index 8fedafab..8bd95fdb 100644 --- a/simphony/layout.py +++ b/simphony/layout.py @@ -14,11 +14,11 @@ import os from typing import TYPE_CHECKING, List, Optional -import numpy as np - from simphony.formatters import CircuitFormatter, CircuitJSONFormatter if TYPE_CHECKING: + import numpy as np + from simphony import Model from simphony.models import Subcircuit from simphony.pins import Pin diff --git a/simphony/libraries/siepic/__init__.py b/simphony/libraries/siepic/__init__.py index db7e4a3b..b32d20aa 100644 --- a/simphony/libraries/siepic/__init__.py +++ b/simphony/libraries/siepic/__init__.py @@ -80,14 +80,7 @@ def __init__(self, thickness=220e-9, width=500e-9, polarization='TE'): from bisect import bisect_left from collections import namedtuple -try: - import gdsfactory as gf - from gdsfactory.types import Route - _has_gf = True -except ImportError: - _has_gf = False import numpy as np -import scipy.interpolate as interp from scipy.constants import c as SPEED_OF_LIGHT from simphony import Model @@ -511,101 +504,31 @@ class BidirectionalCoupler(SiEPIC_PDK_Base): r"(?:\.sparam)" ) - def __init__( - self, - thickness=220e-9, - width=500e-9, - **kwargs, - ): - super().__init__( - **kwargs, - thickness=thickness, - width=width, - ) - - if _has_gf: - gf.clear_cache() - self.component = gf.components.coupler(gap=0.2, length=50.55, dy=4.7, width=width * 1e6) - self.component.name = self.name - pin_names = [pin.name for pin in self.pins] - for i, port in enumerate(self.component.ports.values()): - port.name = pin_names[i] - self.component.ports = dict(zip(pin_names, self.component.ports.values())) + def __init__(self, thickness=220e-9, width=500e-9, **kwargs): + super().__init__(**kwargs, thickness=thickness, width=width) def on_args_changed(self): - try: - if self.layout_aware: - self.suspend_autoupdate() - - available = self._source_argsets() - widths = [] - heights = [] - s_params = [] - for idx in range(0, len(available), 2): - d = available[idx] - widths.append(d["width"]) - heights.append(d["thickness"]) - valid_args = available[idx] - sparams = parser.read_params(self._get_file(valid_args)) - self._f, s = parser.build_matrix(sparams) - s_params.append(s) - - s_params = np.asarray(s_params) - - widths = np.asarray(widths, dtype=np.double) - heights = np.asarray(heights, dtype=np.double) - - dim = len(s_params) - s_list = [s_params[dimidx][:][:][:] for dimidx in range(dim)] - s_list = np.asarray(s_list, dtype=complex) - self._s = interp.griddata( - (widths, heights), - s_list, - (self.width * 1e9, self.thickness * 1e9), - method="cubic", - ) - - self.freq_range = (self._f[0], self._f[-1]) - - self.enable_autoupdate() - except AttributeError: - self.suspend_autoupdate() + self.suspend_autoupdate() - available = self._source_argsets() - normalized = [ - {k: round(str2float(v) * 1e-9, 21) for k, v in d.items()} - for d in available - ] - idx = self._get_matched_args(normalized, self.args) + available = self._source_argsets() + normalized = [ + {k: round(str2float(v) * 1e-9, 21) for k, v in d.items()} for d in available + ] + idx = self._get_matched_args(normalized, self.args) - valid_args = available[idx] - sparams = parser.read_params(self._get_file(valid_args)) - self._f, self._s = parser.build_matrix(sparams) + valid_args = available[idx] + sparams = parser.read_params(self._get_file(valid_args)) + self._f, self._s = parser.build_matrix(sparams) - self.freq_range = (self._f[0], self._f[-1]) - for key, value in normalized[idx].items(): - setattr(self, key, value) + self.freq_range = (self._f[0], self._f[-1]) + for key, value in normalized[idx].items(): + setattr(self, key, value) - self.enable_autoupdate() + self.enable_autoupdate() def s_parameters(self, freqs): return interpolate(freqs, self._f, self._s) - def update_variations(self, **kwargs): - self.nominal_width = self.width - self.nominal_thickness = self.thickness - - w = self.width + kwargs.get("corr_w") * 1e-9 - t = self.thickness + kwargs.get("corr_t") * 1e-9 - - self.layout_aware = True - self.width = w - self.thickness = t - - def regenerate_layout_aware_monte_carlo_parameters(self): - self.width = self.nominal_width - self.thickness = self.nominal_thickness - class HalfRing(SiEPIC_PDK_Base): """A half-ring resonator optimized for TE polarized light at 1550 @@ -660,9 +583,8 @@ def __init__( width=500e-9, thickness=220e-9, couple_length=0.0, - **kwargs, + **kwargs ): - super().__init__( **kwargs, gap=gap, @@ -672,90 +594,24 @@ def __init__( couple_length=couple_length, ) - if _has_gf: - gf.clear_cache() - self.component = gf.components.coupler_ring(gap=gap * 1e6, length_x=couple_length * 1e6, radius=radius * 1e6, width=width * 1e6,) - self.component.name = self.name - pin_names = [pin.name for pin in self.pins] - for i, port in enumerate(self.component.ports.values()): - port.name = pin_names[i] - self.component.ports = dict(zip(pin_names, self.component.ports.values())) - def on_args_changed(self): - try: - if self.layout_aware: - self.suspend_autoupdate() - - available = self._source_argsets() - widths = [] - heights = [] - s_params = [] - for idx in range(0, len(available), 2): - d = available[idx] - widths.append(d["width"]) - heights.append(d["thickness"]) - valid_args = available[idx] - sparams = parser.read_params(self._get_file(valid_args)) - sparams = list( - filter( - lambda sparams: sparams["mode"] == self.polarization, - sparams, - ) - ) - self._f, s = parser.build_matrix(sparams) - - s_params.append(s) - - s_params = np.asarray(s_params) - - widths = np.asarray(widths, dtype=complex) - heights = np.asarray(heights, dtype=complex) - - dim, _, _, _ = s_params.shape - s_list = [] - - for dimidx in range(dim): - s_list.append(s_params[dimidx][:][:][:]) - s_list = np.asarray(s_list, dtype=complex) - self._s = interp.griddata( - (widths, heights), - s_list, - (self.width * 1e9, self.thickness * 1e9), - method="cubic", - ) - - self.freq_range = (self._f[0], self._f[-1]) - - self.enable_autoupdate() - except AttributeError: - self.suspend_autoupdate() - - available = self._source_argsets() - normalized = [ - {k: round(str2float(v), 15) for k, v in d.items()} for d in available - ] - idx = self._get_matched_args(normalized, self.args) - - valid_args = available[idx] - sparams = parser.read_params(self._get_file(valid_args)) - self._f, self._s = parser.build_matrix(sparams) - - self.freq_range = (self._f[0], self._f[-1]) - for key, value in normalized[idx].items(): - setattr(self, key, value) + self.suspend_autoupdate() - self.enable_autoupdate() + available = self._source_argsets() + normalized = [ + {k: round(str2float(v), 15) for k, v in d.items()} for d in available + ] + idx = self._get_matched_args(normalized, self.args) - def update_variations(self, **kwargs): - self.nominal_width = self.width - self.nominal_thickness = self.thickness + valid_args = available[idx] + sparams = parser.read_params(self._get_file(valid_args)) + self._f, self._s = parser.build_matrix(sparams) - w = self.width + kwargs.get("corr_w") * 1e-9 - t = self.thickness + kwargs.get("corr_t") * 1e-9 + self.freq_range = (self._f[0], self._f[-1]) + for key, value in normalized[idx].items(): + setattr(self, key, value) - self.layout_aware = True - self.width = w - self.thickness = t + self.enable_autoupdate() def s_parameters(self, freqs): return interpolate(freqs, self._f, self._s) @@ -797,95 +653,30 @@ class DirectionalCoupler(SiEPIC_PDK_Base): ) def __init__(self, gap=200e-9, Lc=10e-6, **kwargs): - - super().__init__( - **kwargs, - gap=gap, - Lc=Lc, - ) - - if _has_gf: - gf.clear_cache() - self.component = gf.components.coupler(gap=gap * 1e6, length=Lc * 1e6, dy=10) - self.component.name = self.name - pin_names = [pin.name for pin in self.pins] - for i, port in enumerate(self.component.ports.values()): - port.name = pin_names[i] - self.component.ports = dict(zip(pin_names, self.component.ports.values())) + super().__init__(**kwargs, gap=gap, Lc=Lc) def on_args_changed(self): - try: - if self.layout_aware: - self.suspend_autoupdate() - - available = self._source_argsets() - Lcs = [] - s_params = [] - for idx in range(len(available)): - d = available[idx] - Lcs.append(d["Lc"]) - valid_args = available[idx] - sparams = parser.read_params(self._get_file(valid_args)) - self._f, s = parser.build_matrix(sparams) - - s_params.append(s) - - s_params = np.asarray(s_params) - - Lcs = [Lcs[i].replace("u", "") for i in range(len(Lcs))] - Lcs = np.asarray(Lcs, dtype=float) - - dim, freq, inp, out = s_params.shape - s_new = np.ndarray((freq, inp, out), dtype=complex) - for freqidx in range(freq): - for inpidx in range(inp): - for outidx in range(out): - s_list = [] - for dimidx in range(dim): - s_list.append(s_params[dimidx][freqidx][inpidx][outidx]) - - s_interp = interp.interp1d( - Lcs, np.asarray(s_list), kind="cubic" - ) - s_new[freqidx][inpidx][outidx] = s_interp(self.Lc * 1e6) - - self._s = s_new - self.freq_range = (self._f[0], self._f[-1]) - - self.enable_autoupdate() - except AttributeError: - self.suspend_autoupdate() + self.suspend_autoupdate() - available = self._source_argsets() - normalized = [ - {k: round(str2float(v), 15) for k, v in d.items()} for d in available - ] - idx = self._get_matched_args(normalized, self.args) + available = self._source_argsets() + normalized = [ + {k: round(str2float(v), 15) for k, v in d.items()} for d in available + ] + idx = self._get_matched_args(normalized, self.args) - valid_args = available[idx] - sparams = parser.read_params(self._get_file(valid_args)) - self._f, self._s = parser.build_matrix(sparams) + valid_args = available[idx] + sparams = parser.read_params(self._get_file(valid_args)) + self._f, self._s = parser.build_matrix(sparams) - self.freq_range = (self._f[0], self._f[-1]) - for key, value in normalized[idx].items(): - setattr(self, key, value) + self.freq_range = (self._f[0], self._f[-1]) + for key, value in normalized[idx].items(): + setattr(self, key, value) - self.enable_autoupdate() + self.enable_autoupdate() def s_parameters(self, freqs): return interpolate(freqs, self._f, self._s) - def update_variations(self, **kwargs): - self.nominal_Lc = self.Lc - - w = self.nominal_Lc + kwargs.get("corr_w") * 1e-6 - - self.layout_aware = True - self.Lc = w - - def regenerate_layout_aware_monte_carlo_parameters(self): - self.Lc = self.nominal_Lc - class Terminator(SiEPIC_PDK_Base): """A terminator component that dissipates light into free space optimized @@ -924,29 +715,8 @@ class Terminator(SiEPIC_PDK_Base): r"(?:_TE.sparam)" ) - def __init__( - self, - w1=500e-9, - w2=60e-9, - L=10e-6, - **kwargs, - ): - - super().__init__( - **kwargs, - w1=w1, - w2=w2, - L=L, - ) - - if _has_gf: - gf.clear_cache() - self.component = gf.components.taper(width1=w1 * 1e6, width2=w2 * 1e6, with_two_ports=False) - self.component.name = self.name - pin_names = [pin.name for pin in self.pins] - for i, port in enumerate(self.component.ports.values()): - port.name = pin_names[i] - self.component.ports = dict(zip(pin_names, self.component.ports.values())) + def __init__(self, w1=500e-9, w2=60e-9, L=10e-6, **kwargs): + super().__init__(**kwargs, w1=w1, w2=w2, L=L) def on_args_changed(self): self.suspend_autoupdate() @@ -977,9 +747,6 @@ def on_args_changed(self): def s_parameters(self, freqs): return interpolate(freqs, self._f, self._s) - def layout_aware_monte_carlo_parameters(self): - pass - class GratingCoupler(SiEPIC_PDK_Base): """A grating coupler optimized for TE polarized light at 1550 nanometers. @@ -1018,51 +785,14 @@ class GratingCoupler(SiEPIC_PDK_Base): r"(?:\.txt)" ) - def __init__( - self, - thickness=220e-9, - deltaw=0, - polarization="TE", - **kwargs, - ): - + def __init__(self, thickness=220e-9, deltaw=0, polarization="TE", **kwargs): super().__init__( - **kwargs, - thickness=thickness, - deltaw=deltaw, - polarization=polarization, + **kwargs, thickness=thickness, deltaw=deltaw, polarization=polarization ) - if _has_gf: - gf.clear_cache() - self.component = gf.components.grating_coupler_te() - self.component.name = self.name - pin_names = [pin.name for pin in self.pins] - for i, port in enumerate(self.component.ports.values()): - port.name = pin_names[i] - self.component.ports = dict(zip(pin_names, self.component.ports.values())) - port1 = self.component.ports["pin1"] - self.component.ports["pin1"] = self.component.ports["pin2"] - self.component.ports["pin2"] = port1 - def on_args_changed(self): - try: - if self.layout_aware: - self.suspend_autoupdate() - - self._update_lay_aware_true() - - self.freq_range = (self._f[0], self._f[-1]) - - self.enable_autoupdate() - except AttributeError: - self.suspend_autoupdate() - - self._update_no_lay_aware() - - self.enable_autoupdate() + self.suspend_autoupdate() - def _update_no_lay_aware(self): available = self._source_argsets() normalized = [] for d in available: @@ -1095,103 +825,11 @@ def _update_no_lay_aware(self): self._s = self._s[::-1] self.freq_range = (self._f[0], self._f[-1]) - def _update_lay_aware_true(self): - pass - # available = self._source_argsets() - # thicknesses = [] - # deltaws = [] - # s_params = [] - # for d in available: - # _, thickness, deltaw = [(key, d.get(key)) for key in self._args_keys] - # thicknesses.append(round(str2float(thickness[1]) * 1e-9, 15)) - # deltaws.append(round(str2float(deltaw[1]) * 1e-9, 15)) - - # if self.polarization == "TE": - # for idx in range(round(len(thicknesses) / 2)): - # valid_args = available[idx] - # params = np.genfromtxt(self._get_file(valid_args), delimiter="\t") - # self._f = params[:, 0] - # s = np.zeros((len(self._f), 2, 2), dtype="complex128") - # s[:, 0, 0] = params[:, 1] * np.exp(1j * params[:, 2]) - # s[:, 0, 1] = params[:, 3] * np.exp(1j * params[:, 4]) - # s[:, 1, 0] = params[:, 5] * np.exp(1j * params[:, 6]) - # s[:, 1, 1] = params[:, 7] * np.exp(1j * params[:, 8]) - - # self._f = self._f[::-1] - # s = s[::-1] - # s_params.append(s) - - # s_params = np.asarray(s_params, dtype=object) - # thicknesses = np.asarray(thicknesses, dtype=float) - # deltaws = np.asarray(deltaws, dtype=float) - - # dim = len(s_params) - # for dimidx in range(dim): - # print(f'[s_params[dimidx][:][:][:]) - # s_list.append(s_params[dimidx][:][:][:]) - # s_list = np.asarray(s_list, dtype=complex) - # self._s = interp.griddata( - # ( - # thicknesses[0 : round(len(thicknesses) / 2)], - # deltaws[0 : round(len(deltaws) / 2)], - # ), - # s_list, - # (self.thickness, self.deltaw), - # method="cubic", - # ) - - # elif self.polarization == "TM": - # for idx in range(round(len(thicknesses) / 2) + 1, len(thicknesses)): - # valid_args = available[idx] - # params = np.genfromtxt(self._get_file(valid_args), delimiter="\t") - # self._f = params[:, 0] - # s = np.zeros((len(self._f), 2, 2), dtype="complex128") - # s[:, 0, 0] = params[:, 1] * np.exp(1j * params[:, 2]) - # s[:, 0, 1] = params[:, 3] * np.exp(1j * params[:, 4]) - # s[:, 1, 0] = params[:, 5] * np.exp(1j * params[:, 6]) - # s[:, 1, 1] = params[:, 7] * np.exp(1j * params[:, 8]) - - # self._f = self._f[::-1] - # s = s[::-1] - # s_params.append(s) - - # s_params = np.asarray(s_params, dtype=object) - # thicknesses = np.asarray(thicknesses, dtype=float) - # deltaws = np.asarray(deltaws, dtype=float) - - # dim = len(s_params) - # s_list = [] - # for dimidx in range(dim): - # s_list.append(s_params[dimidx][:][:][:]) - # s_list = np.asarray(s_list, dtype=complex) - # self._s = interp.griddata( - # ( - # thicknesses[round(len(thicknesses) / 2) + 1, len(thicknesses)], - # deltaws[round(len(thicknesses) / 2) + 1, len(thicknesses)], - # ), - # s_list, - # (self.thickness, self.deltaw), - # method="cubic", - # ) + self.enable_autoupdate() def s_parameters(self, freqs): return interpolate(freqs, self._f, self._s) - def update_variations(self, **kwargs): - self.nominal_deltaw = self.deltaw - self.nominal_thickness = self.thickness - - w = self.deltaw + kwargs.get("corr_w") * 1e-9 - t = self.thickness + kwargs.get("corr_t") * 1e-9 - - self.layout_aware = True - self.deltaw = w - self.thickness = t - - def regenerate_layout_aware_monte_carlo_parameters(self): - self.thickness = self.nominal_thickness - self.deltaw = self.nominal_deltaw - class Waveguide(SiEPIC_PDK_Base): """Model for an waveguide optimized for TE polarized light at 1550 @@ -1228,7 +866,6 @@ class Waveguide(SiEPIC_PDK_Base): """ pin_count = 2 - freq_range = ( 187370000000000.0, 199862000000000.0, @@ -1256,7 +893,7 @@ def __init__( sigma_ne=0.05, sigma_ng=0.05, sigma_nd=0.0001, - **kwargs, + **kwargs ): if polarization not in ["TE", "TM"]: raise ValueError(f"Unknown polarization value '{polarization}', must be one of 'TE' or 'TM'") @@ -1277,105 +914,35 @@ def __init__( sigma_nd=sigma_nd, ) - if _has_gf: - self.path: Route = None - self.regenerate_monte_carlo_parameters() def on_args_changed(self): - try: - if self.layout_aware: - self.suspend_autoupdate() - - available = self._source_argsets() - - widths = [] - heights = [] - for d in available: - widths.append(d["width"]) - heights.append(d["height"]) - - lam0_all = [] - ne_all = [] - ng_all = [] - nd_all = [] - for idx in range(len(available)): - valid_args = available[idx] - with open(self._get_file(valid_args), "r") as f: - params = f.read().rstrip("\n") - if self.polarization == "TE": - lam0, ne, _, ng, _, nd, _ = params.split(" ") - elif self.polarization == "TM": - lam0, _, ne, _, ng, _, nd = params.split(" ") - raise NotImplementedError - - lam0_all.append(lam0) - ne_all.append(ne) - ng_all.append(ng) - nd_all.append(nd) - - widths = np.asarray(widths).astype(float) - heights = np.asarray(heights).astype(float) - lam0_all = np.asarray(lam0_all).astype(float) - ne_all = np.asarray(ne_all).astype(float) - ng_all = np.asarray(ng_all).astype(float) - nd_all = np.asarray(nd_all).astype(float) - - self.lam0 = interp.griddata( - (widths, heights), - lam0_all, - (self.width * 1e9, self.height * 1e9), - method="cubic", - ) - self.ne = interp.griddata( - (widths, heights), - ne_all, - (self.width * 1e9, self.height * 1e9), - method="cubic", - ) - self.ng = interp.griddata( - (widths, heights), - ng_all, - (self.width * 1e9, self.height * 1e9), - method="cubic", - ) - self.nd = interp.griddata( - (widths, heights), - nd_all, - (self.width * 1e9, self.height * 1e9), - method="cubic", - ) - - self.enable_autoupdate() + self.suspend_autoupdate() - except AttributeError: - self.suspend_autoupdate() + available = self._source_argsets() + normalized = [ + {k: round(str2float(v) * 1e-9, 21) for k, v in d.items()} for d in available + ] + idx = self._get_matched_args(normalized, self.args) - available = self._source_argsets() - normalized = [ - {k: round(str2float(v) * 1e-9, 21) for k, v in d.items()} - for d in available - ] - idx = self._get_matched_args(normalized, self.args) - - valid_args = available[idx] - with open(self._get_file(valid_args), "r") as f: - params = f.read().rstrip("\n") - if self.polarization == "TE": - lam0, ne, _, ng, _, nd, _ = params.split(" ") - elif self.polarization == "TM": - lam0, _, ne, _, ng, _, nd = params.split(" ") - raise NotImplementedError - self.lam0 = float(lam0) - self.ne = float(ne) - self.ng = float(ng) - self.nd = float(nd) - - # Updates parameters width and thickness to closest match. - for key, value in normalized[idx].items(): - setattr(self, key, value) + valid_args = available[idx] + with open(self._get_file(valid_args), "r") as f: + params = f.read().rstrip("\n") + if self.polarization == "TE": + lam0, ne, _, ng, _, nd, _ = params.split(" ") + elif self.polarization == "TM": + lam0, _, ne, _, ng, _, nd = params.split(" ") + raise NotImplementedError + self.lam0 = float(lam0) + self.ne = float(ne) + self.ng = float(ng) + self.nd = float(nd) + + # Updates parameters width and thickness to closest match. + for key, value in normalized[idx].items(): + setattr(self, key, value) - self.enable_autoupdate() + self.enable_autoupdate() def s_parameters(self, freqs): """Get the s-parameters of a waveguide. @@ -1391,7 +958,7 @@ def s_parameters(self, freqs): Returns a tuple containing the frequency array, `freqs`, corresponding to the calculated s-parameter matrix, `s`. """ - return self.calc_s_params( + return self.cacl_s_params( freqs, self.length, self.lam0, self.ne, self.ng, self.nd ) @@ -1406,44 +973,17 @@ def monte_carlo_s_parameters(self, freqs): monte carlo parameters but they are consistent within a single circuit. """ - return self.calc_s_params( + return self.cacl_s_params( freqs, self.length, self.lam0, self.rand_ne, self.rand_ng, self.rand_nd ) - def layout_aware_monte_carlo_s_parameters(self, freqs): - """Returns a monte carlo (randomized) set of s-parameters. - - In this implementation of the monte carlo routine, values generated - for lam0, ne, ng, and nd using the Reduced Spatial Correlation Matrix method - are used to return a set of s-parameters. This is repeated for each - run of the Monte Carlo analysis for every wavehuide component in the circuit. - """ - return self.calc_s_params( - freqs, self.length, self.lam0, self.ne, self.ng, self.nd - ) - def regenerate_monte_carlo_parameters(self): self.rand_ne = np.random.normal(self.ne, self.sigma_ne) self.rand_ng = np.random.normal(self.ng, self.sigma_ng) self.rand_nd = np.random.normal(self.nd, self.sigma_nd) - def update_variations(self, **kwargs): - self.nominal_width = self.width - self.nominal_height = self.height - - w = self.width + kwargs.get("corr_w") * 1e-9 - h = self.height + kwargs.get("corr_t") * 1e-9 - - self.layout_aware = True - self.width = w - self.height = h - - def regenerate_layout_aware_monte_carlo_parameters(self): - self.width = self.nominal_width - self.height = self.nominal_height - @staticmethod - def calc_s_params(freqs, length, lam0, ne, ng, nd): + def cacl_s_params(freqs, length, lam0, ne, ng, nd): # Initialize array to hold s-params s = np.zeros((len(freqs), 2, 2), dtype=complex) @@ -1504,99 +1044,35 @@ class YBranch(SiEPIC_PDK_Base): r"(?:\.sparam)" ) - def __init__( - self, - thickness=220e-9, - width=500e-9, - polarization="TE", - **kwargs, - ): + def __init__(self, thickness=220e-9, width=500e-9, polarization="TE", **kwargs): if polarization not in ["TE", "TM"]: raise ValueError(f"Unknown polarization value '{polarization}', must be one of 'TE' or 'TM'") super().__init__( - **kwargs, - thickness=thickness, - width=width, - polarization=polarization, + **kwargs, thickness=thickness, width=width, polarization=polarization ) - if _has_gf: - gf.clear_cache() - self.component = gf.read.import_gds(os.path.join(os.path.dirname(__file__), 'source_data/ebeam_y_1550.gds')) - self.component.name = self.name - self.component.ports[self.pins[0].name] = gf.Port(self.pins[0].name, 180, center=(-7.4, 0), width=width * 1e6, layer="PORT", parent=self.component) - self.component.ports[self.pins[1].name] = gf.Port(self.pins[1].name, 0, center=(7.4, 2.75), width=width * 1e6, layer="PORT", parent=self.component) - self.component.ports[self.pins[2].name] = gf.Port(self.pins[2].name, 0, center=(7.4, -2.75), width=width * 1e6, layer="PORT", parent=self.component) - def on_args_changed(self): - try: - if self.layout_aware: - self.suspend_autoupdate() - - available = self._source_argsets() - widths = [] - heights = [] - s_params = [] - for idx in range(0, len(available)): - d = available[idx] - widths.append(d["width"]) - heights.append(d["thickness"]) - valid_args = available[idx] - sparams = parser.read_params(self._get_file(valid_args)) - sparams = list( - filter( - lambda sparams: sparams["mode"] == self.polarization, - sparams, - ) - ) - self._f, s = parser.build_matrix(sparams) - - s_params.append(s) - - s_params = np.asarray(s_params) - - widths = np.asarray(widths, dtype=float) - heights = np.asarray(heights, dtype=float) - - dim, _, _, _ = s_params.shape - s_list = [] - for dimidx in range(dim): - s_list.append(s_params[dimidx][:][:][:]) - s_list = np.asarray(s_list, dtype=complex) - self._s = interp.griddata( - (widths, heights), - s_list, - (self.width * 1e9, self.thickness * 1e9), - method="cubic", - ) - - self.freq_range = (self._f[0], self._f[-1]) - - self.enable_autoupdate() - - except AttributeError: - self.suspend_autoupdate() + self.suspend_autoupdate() - available = self._source_argsets() - normalized = [ - {k: round(str2float(v) * 1e-9, 21) for k, v in d.items()} - for d in available - ] - idx = self._get_matched_args(normalized, self.args) + available = self._source_argsets() + normalized = [ + {k: round(str2float(v) * 1e-9, 21) for k, v in d.items()} for d in available + ] + idx = self._get_matched_args(normalized, self.args) - valid_args = available[idx] - sparams = parser.read_params(self._get_file(valid_args)) - sparams = list( - filter(lambda sparams: sparams["mode"] == self.polarization, sparams) - ) + valid_args = available[idx] + sparams = parser.read_params(self._get_file(valid_args)) + sparams = list( + filter(lambda sparams: sparams["mode"] == self.polarization, sparams) + ) - for key, value in normalized[idx].items(): - setattr(self, key, value) - self._f, self._s = parser.build_matrix(sparams) - self.freq_range = (self._f[0], self._f[-1]) + for key, value in normalized[idx].items(): + setattr(self, key, value) + self._f, self._s = parser.build_matrix(sparams) + self.freq_range = (self._f[0], self._f[-1]) - self.enable_autoupdate() + self.enable_autoupdate() def s_parameters(self, freqs): """Returns scattering parameters for the y-branch based on its @@ -1613,18 +1089,3 @@ def s_parameters(self, freqs): The scattering parameters corresponding to the frequency range. """ return interpolate(freqs, self._f, self._s) - - def update_variations(self, **kwargs): - self.nominal_width = self.width - self.nominal_thickness = self.thickness - - w = self.width + kwargs.get("corr_w") * 1e-9 - t = self.thickness + kwargs.get("corr_t") * 1e-9 - - self.layout_aware = True - self.width = w - self.thickness = t - - def regenerate_layout_aware_monte_carlo_parameters(self): - self.width = self.nominal_width - self.thickness = self.nominal_thickness diff --git a/simphony/libraries/siepic/source_data/ebeam_y_1550.gds b/simphony/libraries/siepic/source_data/ebeam_y_1550.gds deleted file mode 100644 index f05fbc54dbfd33169540ad2245a2b3d103dceb72..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12006 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 diff --git a/simphony/libraries/sipann.py b/simphony/libraries/sipann.py index bce0e2f1..8389f535 100644 --- a/simphony/libraries/sipann.py +++ b/simphony/libraries/sipann.py @@ -209,21 +209,6 @@ def __init__( **kwargs ) - def update_variations(self, **kwargs): - self.nominal_width = self.params["width"] - self.nominal_thickness = self.params["thickness"] - - w = self.params["width"] + kwargs.get("corr_w") - h = self.params["thickness"] + kwargs.get("corr_t") - - self.layout_aware = True - self.params["width"] = w - self.params["thickness"] = h - - def regenerate_layout_aware_monte_carlo_parameters(self): - self.params["width"] = self.nominal_width - self.params["thickness"] = self.nominal_thickness - class GapFuncAntiSymmetric(SipannWrapper): r"""Antisymmetric directional coupler, meaning both waveguides are @@ -316,21 +301,6 @@ def __init__( **kwargs ) - def update_variations(self, **kwargs): - self.nominal_width = self.params["width"] - self.nominal_thickness = self.params["thickness"] - - w = self.params["width"] + kwargs.get("corr_w") - h = self.params["thickness"] + kwargs.get("corr_t") - - self.layout_aware = True - self.params["width"] = w - self.params["thickness"] = h - - def regenerate_layout_aware_monte_carlo_parameters(self): - self.params["width"] = self.nominal_width - self.params["thickness"] = self.nominal_thickness - class HalfRing(SipannWrapper): r"""Half of a ring resonator. @@ -391,21 +361,6 @@ def __init__( **kwargs ) - def update_variations(self, **kwargs): - self.nominal_width = self.params["width"] - self.nominal_thickness = self.params["thickness"] - - w = self.params["width"] + kwargs.get("corr_w") - h = self.params["thickness"] + kwargs.get("corr_t") - - self.layout_aware = True - self.params["width"] = w - self.params["thickness"] = h - - def regenerate_layout_aware_monte_carlo_parameters(self): - self.params["width"] = self.nominal_width - self.params["thickness"] = self.nominal_thickness - class HalfRacetrack(SipannWrapper): r"""Half of a ring resonator, similar to the HalfRing class. @@ -476,21 +431,6 @@ def __init__( **kwargs ) - def update_variations(self, **kwargs): - self.nominal_width = self.params["width"] - self.nominal_thickness = self.params["thickness"] - - w = self.params["width"] + kwargs.get("corr_w") - h = self.params["thickness"] + kwargs.get("corr_t") - - self.layout_aware = True - self.params["width"] = w - self.params["thickness"] = h - - def regenerate_layout_aware_monte_carlo_parameters(self): - self.params["width"] = self.nominal_width - self.params["thickness"] = self.nominal_thickness - class StraightCoupler(SipannWrapper): """Straight directional coupler, both waveguides run parallel. @@ -548,21 +488,6 @@ def __init__( **kwargs ) - def update_variations(self, **kwargs): - self.nominal_width = self.params["width"] - self.nominal_thickness = self.params["thickness"] - - w = self.params["width"] + kwargs.get("corr_w") - h = self.params["thickness"] + kwargs.get("corr_t") - - self.layout_aware = True - self.params["width"] = w - self.params["thickness"] = h - - def regenerate_layout_aware_monte_carlo_parameters(self): - self.params["width"] = self.nominal_width - self.params["thickness"] = self.nominal_thickness - class Standard(SipannWrapper): r"""Standard-shaped directional coupler. @@ -640,21 +565,6 @@ def __init__( **kwargs ) - def update_variations(self, **kwargs): - self.nominal_width = self.params["width"] - self.nominal_thickness = self.params["thickness"] - - w = self.params["width"] + kwargs.get("corr_w") - h = self.params["thickness"] + kwargs.get("corr_t") - - self.layout_aware = True - self.params["width"] = w - self.params["thickness"] = h - - def regenerate_layout_aware_monte_carlo_parameters(self): - self.params["width"] = self.nominal_width - self.params["thickness"] = self.nominal_thickness - class DoubleHalfRing(SipannWrapper): r"""Two equally sized half-rings coupling along their edges. @@ -717,21 +627,6 @@ def __init__( **kwargs ) - def update_variations(self, **kwargs): - self.nominal_width = self.params["width"] - self.nominal_thickness = self.params["thickness"] - - w = self.params["width"] + kwargs.get("corr_w") - h = self.params["thickness"] + kwargs.get("corr_t") - - self.layout_aware = True - self.params["width"] = w - self.params["thickness"] = h - - def regenerate_layout_aware_monte_carlo_parameters(self): - self.params["width"] = self.nominal_width - self.params["thickness"] = self.nominal_thickness - class AngledHalfRing(SipannWrapper): r"""A halfring resonator, except what was the straight waveguide is now @@ -801,21 +696,6 @@ def __init__( **kwargs ) - def update_variations(self, **kwargs): - self.nominal_width = self.params["width"] - self.nominal_thickness = self.params["thickness"] - - w = self.params["width"] + kwargs.get("corr_w") - h = self.params["thickness"] + kwargs.get("corr_t") - - self.layout_aware = True - self.params["width"] = w - self.params["thickness"] = h - - def regenerate_layout_aware_monte_carlo_parameters(self): - self.params["width"] = self.nominal_width - self.params["thickness"] = self.nominal_thickness - class Waveguide(SipannWrapper): """Lossless model for a straight waveguide. Main use case is for playing @@ -864,21 +744,6 @@ def __init__( **kwargs ) - def update_variations(self, **kwargs): - self.nominal_width = self.params["width"] - self.nominal_thickness = self.params["thickness"] - - w = self.params["width"] + kwargs.get("corr_w") - h = self.params["thickness"] + kwargs.get("corr_t") - - self.layout_aware = True - self.params["width"] = w - self.params["thickness"] = h - - def regenerate_layout_aware_monte_carlo_parameters(self): - self.params["width"] = self.nominal_width - self.params["thickness"] = self.nominal_thickness - class Racetrack(SipannWrapper): r"""Racetrack waveguide arc, used to connect to a racetrack directional @@ -948,21 +813,6 @@ def __init__( **kwargs ) - def update_variations(self, **kwargs): - self.nominal_width = self.params["width"] - self.nominal_thickness = self.params["thickness"] - - w = self.params["width"] + kwargs.get("corr_w") - h = self.params["thickness"] + kwargs.get("corr_t") - - self.layout_aware = True - self.params["width"] = w - self.params["thickness"] = h - - def regenerate_layout_aware_monte_carlo_parameters(self): - self.params["width"] = self.nominal_width - self.params["thickness"] = self.nominal_thickness - class PremadeCoupler(SipannWrapper): r"""Loads premade couplers. diff --git a/simphony/models.py b/simphony/models.py index 6fd83a82..823e0105 100644 --- a/simphony/models.py +++ b/simphony/models.py @@ -23,12 +23,6 @@ class is the base class for all models. The ``Subcircuit`` class is where import numpy as np -try: - from gdsfactory import Component, ComponentReference - _has_gf = True -except ImportError: - _has_gf = False - from simphony.connect import create_block_diagonal, innerconnect_s from simphony.formatters import ModelFormatter, ModelJSONFormatter from simphony.layout import Circuit @@ -52,8 +46,6 @@ class Model: pins : A tuple of all the default pin names of the device. Must be set if pin_count is not. - component : - A gdsfactory Component object which is a representation of this component (optional). """ freq_range: ClassVar[Tuple[Optional[float], Optional[float]]] @@ -85,8 +77,6 @@ def __init__( The pins for the model. If not specified, the pins will be be initialized from cls.pins. If that is not specified, cls.pin_count number of pins will be initialized. - component : - A gdsfactory Component object for this component (optional). Raises ------ @@ -95,6 +85,8 @@ def __init__( NotImplementedError when pins is None and cls.pin_count and cls.pins are undefined. """ + self.circuit = Circuit(self) + # set the frequency range for the instance. resolution order: # 1. freq_range # 2. cls.freq_range @@ -108,6 +100,7 @@ def __init__( self.freq_range = (0, float("inf")) self.name = name + # initiate the Pin objects for the instance. resolution order: # 1. pins (list of Pin objects) # 2. cls.pins (tuple of pin names) @@ -127,13 +120,6 @@ def __init__( f"{name}.pin_count or {name}.pins needs to be defined." ) - if _has_gf: - self.component = Component() - self.component.name = name - - # Set circuit - self.circuit = Circuit(self) - def __str__(self) -> str: name = self.name or f"{self.__class__.__name__} component" return f"{name} with pins: {', '.join([pin.name for pin in self.pins])}" @@ -239,12 +225,7 @@ def _on_disconnect_recursive(self, circuit: Circuit) -> None: if circuit._add(component): component._on_disconnect_recursive(circuit) - def connect( - self, - component_or_pin: Union["Model", Pin], - component1_ref: "ComponentReference" = None, - component2_ref: "ComponentReference" = None, - ) -> "Model": + def connect(self, component_or_pin: Union["Model", Pin]) -> "Model": """Connects the next available (unconnected) pin from this component to the component/pin passed in as the argument. @@ -252,15 +233,7 @@ def connect( component is connected to the first available pin from the other component. """ - if None in (component1_ref, component2_ref): - self._get_next_unconnected_pin().connect(component_or_pin) - elif _has_gf: - self._get_next_unconnected_pin().connect( - component_or_pin, component1_ref, component2_ref - ) - else: - raise ImportError("gdsfactory must be installed to connect gdsfactory components. Try `pip install gdsfactory`.") - + self._get_next_unconnected_pin().connect(component_or_pin) return self def disconnect(self) -> None: @@ -268,29 +241,17 @@ def disconnect(self) -> None: for pin in self.pins: pin.disconnect() - def interface( - self, - component: "Model", - component1_ref: "ComponentReference" = None, - component2_ref: "ComponentReference" = None, - ) -> "Model": + def interface(self, component: "Model") -> "Model": """Interfaces this component to the component passed in by connecting pins with the same names. Only pins that have been renamed will be connected. """ - if None in (component1_ref, component2_ref): - for selfpin in self.pins: - for componentpin in component.pins: - if selfpin.name[0:3] != "pin" and selfpin.name == componentpin.name: - selfpin.connect(componentpin) - elif _has_gf: - for selfpin in self.pins: - for componentpin in component.pins: - if selfpin.name[0:3] != "pin" and selfpin.name == componentpin.name: - selfpin.connect(componentpin, component1_ref, component2_ref) - else: - raise ImportError("gdsfactory must be installed to connect gdsfactory components. Try `pip install gdsfactory`.") + for selfpin in self.pins: + for componentpin in component.pins: + if selfpin.name[:3] != "pin" and selfpin.name == componentpin.name: + selfpin.connect(componentpin) + return self def monte_carlo_s_parameters(self, freqs: "np.array") -> "np.ndarray": @@ -315,28 +276,6 @@ def monte_carlo_s_parameters(self, freqs: "np.array") -> "np.ndarray": """ return self.s_parameters(freqs) - def layout_aware_monte_carlo_s_parameters(self, freqs: "np.array") -> "np.ndarray": - """Implements the monte carlo routine for the given Model. - - If no monte carlo routine is defined, the default behavior returns the - result of a call to ``s_parameters()``. - - Parameters - ---------- - freqs : np.array - The frequency range to generate monte carlo s-parameters over. - - Returns - ------- - s : np.ndarray - The scattering parameters corresponding to the frequency range. - Its shape should be (the number of frequency points x ports x ports). - If the scattering parameters are requested for only a single - frequency, for example, and the device has 4 ports, the shape - returned by ``monte_carlo_s_parameters`` would be (1, 4, 4). - """ - return self.s_parameters(freqs) - def multiconnect(self, *connections: Union["Model", Pin, None]) -> "Model": """Connects this component to the specified connections by looping through each connection and connecting it with the corresponding pin. @@ -366,7 +305,7 @@ def regenerate_monte_carlo_parameters(self) -> None: circuit; they will be more or less consistent within a single small circuit. - The ``MonteCarloSweepSimulator`` calls this function once per run over + The ``MonteCarloSweepSimulation`` calls this function once per run over the circuit. Notes @@ -376,27 +315,6 @@ def regenerate_monte_carlo_parameters(self) -> None: """ pass - def regenerate_layout_aware_monte_carlo_parameters(self): - """Reassigns dimension parameters to the nominal values for the component. - - If a monte carlo method is not implemented for a given model, this - method does nothing. However, it can optionally be implemented so that - parameters are reassigned for every run. - - The ``LayoutAwareMonteCarloSweepSimulator`` calls this function once per run per component. - - Notes - ----- - This function should not accept any parameters, but may act on instance - or class attributes. - """ - pass - - def update_variations(self, **kwargs): - """Update width and thickness variations for the component using correlated - samples. This is used for layout-aware Monte Carlo runs.""" - pass - def rename_pins(self, *names: str) -> None: """Renames the pins for this component. @@ -601,67 +519,7 @@ def __init__( self._wrapped_circuit = circuit - super().__init__( - **kwargs, - freq_range=freq_range, - name=name, - pins=pins, - ) - - def __get_s_params_from_cache(self, component, freqs: "np.array", s_parameters_method: str = "s_parameters"): - """Get the s_params from the cache if possible.""" - if s_parameters_method == "s_parameters": - # each frequency has a different s-matrix, so we need to cache - # the s-matrices by frequency as well as component - s_params = [] - for freq in freqs: - try: - # use the cached s-matrix if available - s_matrix = self.__class__.scache[component][freq] - except KeyError: - # make sure the frequency dict is created - if component not in self.__class__.scache: - self.__class__.scache[component] = {} - - # store the s-matrix for the frequency and component - s_matrix = getattr(component, s_parameters_method)( - np.array([freq]) - )[0] - self.__class__.scache[component][freq] = s_matrix - - # add the s-matrix to our list of s-matrices - s_params.append(s_matrix) - - # convert to numpy array for the rest of the function - return np.array(s_params) - elif ( - s_parameters_method == "monte_carlo_s_parameters" - or "layout_aware_monte_carlo_s_parameters" - ): - # don't cache Monte Carlo scattering parameters - return getattr(component, s_parameters_method)(freqs) - - def __compute_s_block(self, s_block, available_pins, all_pins): - """Use the subnetwork growth algorithm for each connection.""" - for pin in all_pins: - # make sure pins only get connected once - # and pins connected to simulators get skipped - if ( - pin._isconnected(include_simulators=False) - and pin in available_pins - and pin._connection in available_pins - ): - # the pin indices in available_pins lines up with the row/column - # indices in the matrix. as the matrix shrinks, we remove pins - # from available_pins so the indices always line up - k = available_pins.index(pin) - l = available_pins.index(pin._connection) - - s_block = innerconnect_s(s_block, k, l) - - available_pins.remove(pin) - available_pins.remove(pin._connection) - return s_block + super().__init__(**kwargs, freq_range=freq_range, name=name, pins=pins) def _s_parameters( self, @@ -695,8 +553,34 @@ def _s_parameters( continue # get the s_params from the cache if possible - s_params = self.__get_s_params_from_cache(component, freqs, s_parameters_method) - + if s_parameters_method == "monte_carlo_s_parameters": + # don't cache Monte Carlo scattering parameters + s_params = getattr(component, s_parameters_method)(freqs) + + elif s_parameters_method == "s_parameters": + # each frequency has a different s-matrix, so we need to cache + # the s-matrices by frequency as well as component + s_params = [] + for freq in freqs: + try: + # use the cached s-matrix if available + s_matrix = self.__class__.scache[component][freq] + except KeyError: + # make sure the frequency dict is created + if component not in self.__class__.scache: + self.__class__.scache[component] = {} + + # store the s-matrix for the frequency and component + s_matrix = getattr(component, s_parameters_method)( + np.array([freq]) + )[0] + self.__class__.scache[component][freq] = s_matrix + + # add the s-matrix to our list of s-matrices + s_params.append(s_matrix) + + # convert to numpy array for the rest of the function + s_params = np.array(s_params) # merge the s_params into the block diagonal matrix if s_block is None: if s_params.ndim == 3: @@ -711,42 +595,36 @@ def _s_parameters( available_pins += component.pins # use the subnetwork growth algorithm for each connection - return self.__compute_s_block(s_block, available_pins, all_pins) + for pin in all_pins: + # make sure pins only get connected once + # and pins connected to simulators get skipped + if ( + pin._isconnected(include_simulators=False) + and pin in available_pins + and pin._connection in available_pins + ): + # the pin indices in available_pins lines up with the row/column + # indices in the matrix. as the matrix shrinks, we remove pins + # from available_pins so the indices always line up + k = available_pins.index(pin) + l = available_pins.index(pin._connection) + + s_block = innerconnect_s(s_block, k, l) + + available_pins.remove(pin) + available_pins.remove(pin._connection) + + return s_block def monte_carlo_s_parameters(self, freqs: "np.array") -> "np.ndarray": """Returns the Monte Carlo scattering parameters for the subcircuit.""" return self._s_parameters(freqs, "monte_carlo_s_parameters") - def layout_aware_monte_carlo_s_parameters(self, freqs: "np.array") -> "np.ndarray": - """Returns the Monte Carlo scattering parameters for the subcircuit.""" - return self._s_parameters(freqs, "layout_aware_monte_carlo_s_parameters") - def regenerate_monte_carlo_parameters(self) -> None: """Regenerates parameters used to generate Monte Carlo s-matrices.""" for component in self._wrapped_circuit: component.regenerate_monte_carlo_parameters() - def regenerate_layout_aware_monte_carlo_parameters(self): - """Reassigns dimension parameters to the nominal values for the component. - - If a monte carlo method is not implemented for a given model, this - method does nothing. However, it can optionally be implemented so that - parameters are reassigned for every run. - - The ``LayoutAwareMonteCarloSweepSimulator`` calls this function once per run per component. - - Notes - ----- - This function should not accept any parameters, but may act on instance - or class attributes. - """ - for component in self._wrapped_circuit: - component.regenerate_layout_aware_monte_carlo_parameters() - - def update_variations(self, **kwargs): - for component in self._wrapped_circuit: - component.update_variations(**kwargs) - def s_parameters(self, freqs: "np.array") -> "np.ndarray": """Returns the scattering parameters for the subcircuit.""" return self._s_parameters(freqs) diff --git a/simphony/pins.py b/simphony/pins.py index b2fad1a8..93ada597 100644 --- a/simphony/pins.py +++ b/simphony/pins.py @@ -14,12 +14,7 @@ from typing import TYPE_CHECKING, Union if TYPE_CHECKING: - from simphony.models import Model -try: - from gdsfactory.types import ComponentReference - _has_gf = True -except ImportError: - _has_gf = False + from simphony import Model class Pin: @@ -68,12 +63,7 @@ def _isconnected(self, *, include_simulators: bool = True) -> bool: self._connection._component, Simulator ) and not isinstance(self._connection._component, SimulationModel) - def connect( - self, - pin_or_component: Union["Pin", "Model"], - component1_ref: "ComponentReference" = None, - component2_ref: "ComponentReference" = None, - ) -> None: + def connect(self, pin_or_component: Union["Pin", "Model"]) -> None: """Connects this pin to the pin/component that is passed in. If a component instance is passed in, this pin will connect to @@ -95,20 +85,6 @@ def connect( # let the components know that a new connection was established self._component._on_connect(pin._component) - if None not in (component1_ref, component2_ref): - if _has_gf: - if ( - component1_ref.parent.name is self._component.name - and component2_ref.parent.name is pin._component.name - ): - component1_ref.connect(self.name, component2_ref.ports[pin.name]) - else: - raise ValueError( - f"Invalid component reference(s) passed. {component1_ref}, {component2_ref}, {self._component.name}, {pin._component.name}" - ) - else: - raise ImportError("gdsfactory is not installed. Try `pip install gdsfactory`.") - def disconnect(self) -> None: """Disconnects this pin to whatever it is connected to.""" if self._isconnected(): @@ -122,11 +98,6 @@ def disconnect(self) -> None: def rename(self, name: str) -> None: """Renames the pin.""" - try: - if self._component.component.ports: - self._component.component.ports[name] = self._component.component.ports.pop(self.name) - except AttributeError: - pass self.name = name diff --git a/simphony/simulation.py b/simphony/simulation.py index 099cd940..70a6fd89 100644 --- a/simphony/simulation.py +++ b/simphony/simulation.py @@ -11,25 +11,18 @@ """ from cmath import rect -from typing import Callable, ClassVar, List, Optional, Tuple, Union +from typing import TYPE_CHECKING, Callable, ClassVar, List, Optional, Tuple, Union import numpy as np - -try: - from gdsfactory import Component - - _has_gf = True -except ImportError: - _has_gf = False from scipy.constants import h -from scipy.linalg import cholesky, lu from scipy.signal import butter, sosfiltfilt from simphony import Model -from simphony.layout import Circuit -from simphony.libraries import siepic from simphony.tools import add_polar, wl2freq +if TYPE_CHECKING: + from simphony.layout import Circuit + # this variable keeps track of the current simulation context (if any) context = None @@ -158,103 +151,6 @@ def _expand_array(self, arr: np.array, size: int) -> np.array: return expanded - def __compute_contributions_no_mod(self, t, scattering): - contributions = scattering[:, np.newaxis] + np.zeros((self.shape[1], 2)) - return contributions[:, :, np.newaxis] + np.zeros((self.num_samples, 2)) - - def __compute_contributions_with_mod(self, t, contributions, scattering, source): - for i, freq in enumerate(self.freqs): - for j, power in enumerate(source._coupled_powers): - angle = scattering[i, 1] - offset = angle / (2 * np.pi * freq) - t = np.linspace( - offset, - offset + (self.num_samples - 1) / self.fs, - self.num_samples, - ) - - mod = source.modfn(freq, power, t) - if np.shape(mod)[0] == 2: - contributions[i, j] = np.stack( - ( - contributions[i, j, 0, 0] * np.sqrt(mod[0]), - contributions[i, j, 0, 1] + source.phase + mod[1], - ), - axis=-1, - ) - else: - contributions[i, j] = np.stack( - ( - contributions[i, j, 0, 0] * np.sqrt(mod), - contributions[i, j, 0, 1] - + np.repeat(source.phase, self.num_samples), - ), - axis=-1, - ) - return contributions - - def __compute_detector_power(self, t, detector): - powers = [] - for pin in detector.pins: - output_index = self.circuit.get_pin_index(pin._connection) - - # figure out how the sources interfere - transmissions = np.zeros( - (self.shape[0], self.shape[1], self.num_samples, 2) - ) - for i, pin in enumerate(self.circuit.pins): - # calculate transmissions for every source connected to - # the circuit - - # find the source that corresponds to this pin - source = None - for s in self.sources: - if s.index == i: - source = s - break - else: - continue - - # lookup how much this source contributes to the output field - scattering = self.s_params[:, output_index, source.index] - - # convert to polar coordinates if necessary - # NOTE: complex values should be stored as a tuple. this conversion - # is for backwards compatibility with siepic library. once that gets - # updated, we can remove this, although it will be a breaking change - if np.shape(scattering[0]) != (2,): - scattering = np.column_stack( - (np.abs(scattering), np.angle(scattering)) - ) - - if not source.modfn: - # no modulation - contributions = self.__compute_contributions_no_mod(t, scattering) - else: - contributions = scattering[:, np.newaxis] + np.zeros( - (self.shape[1], 2) - ) - contributions = contributions[:, :, np.newaxis] + np.zeros( - (self.num_samples, 2) - ) - contributions = self.__compute_contributions_with_mod( - t, contributions, scattering, source - ) - - # add all of the different source contributions together - for i in range(self.shape[0]): - for j in range(self.shape[1]): - for k in range(self.num_samples): - transmissions[i, j, k] = add_polar( - transmissions[i, j, k], contributions[i, j, k] - ) - - # convert the output fields to powers - self.transmissions.append(transmissions) - powers.append((transmissions[:, :, :, 0] ** 2)) - - return powers - def _get_signals(self) -> np.ndarray: """Get the signals in the order set by the detectors. Each signal is a multi-dimensional array. The first index corresponds to frequency. The @@ -305,7 +201,103 @@ def _get_signals(self) -> np.ndarray: signals = [] for detector in self.detectors: # calculate the power detected at each detector pin - powers = self.__compute_detector_power(t, detector) + powers = [] + for pin in detector.pins: + output_index = self.circuit.get_pin_index(pin._connection) + + # figure out how the sources interfere + transmissions = np.zeros( + (self.shape[0], self.shape[1], self.num_samples, 2) + ) + for i, pin in enumerate(self.circuit.pins): + # calculate transmissions for every source connected to + # the circuit + + # find the source that corresponds to this pin + source = None + for s in self.sources: + if s.index == i: + source = s + break + else: + continue + + # lookup how much this source contributes to the output field + scattering = self.s_params[:, output_index, source.index] + + # convert to polar coordinates if necessary + # NOTE: complex values should be stored as a tuple. this conversion + # is for backwards compatibility with siepic library. once that gets + # updated, we can remove this, although it will be a breaking change + if np.shape(scattering[0]) != (2,): + scattering = np.column_stack( + (np.abs(scattering), np.angle(scattering)) + ) + + if not source.modfn: + # no modulation + contributions = np.stack( + ( + scattering[:, 0, np.newaxis] + * np.sqrt(source._coupled_powers), + scattering[:, 1, np.newaxis] + + np.repeat(source.phase, self.shape[1]), + ), + axis=-1, + ) + contributions = contributions[:, :, np.newaxis] + np.zeros( + (self.num_samples, 2) + ) + else: + contributions = scattering[:, np.newaxis] + np.zeros( + (self.shape[1], 2) + ) + contributions = contributions[:, :, np.newaxis] + np.zeros( + (self.num_samples, 2) + ) + for i, freq in enumerate(self.freqs): + for j, power in enumerate(source._coupled_powers): + angle = scattering[i, 1] + offset = angle / (2 * np.pi * freq) + t = np.linspace( + offset, + offset + (self.num_samples - 1) / self.fs, + self.num_samples, + ) + + mod = source.modfn(freq, power, t) + if np.shape(mod)[0] == 2: + contributions[i, j] = np.stack( + ( + contributions[i, j, 0, 0] * np.sqrt(mod[0]), + contributions[i, j, 0, 1] + + source.phase + + mod[1], + ), + axis=-1, + ) + else: + contributions[i, j] = np.stack( + ( + contributions[i, j, 0, 0] * np.sqrt(mod), + contributions[i, j, 0, 1] + + np.repeat(source.phase, self.num_samples), + ), + axis=-1, + ) + + # add all of the different source contributions together + for i in range(self.shape[0]): + for j in range(self.shape[1]): + for k in range(self.num_samples): + transmissions[i, j, k] = add_polar( + transmissions[i, j, k], contributions[i, j, k] + ) + + # convert the output fields to powers + self.transmissions.append(transmissions) + powers.append((transmissions[:, :, :, 0] ** 2)) + # send the powers through the detectors to convert to signals signals.extend(detector._detect(powers)) @@ -329,164 +321,6 @@ def monte_carlo(self, flag: bool) -> None: "monte_carlo_s_parameters" if flag else "s_parameters" ) - def _compute_correlated_samples(self, coords, sigmaw, sigmat, l, runs): - x = [0] * len(coords) - y = [0] * len(coords) - - # get x and y co-ordinates - components = [ - component - for component in self.circuit._get_components() - if not isinstance(component, (Laser, Detector)) - ] - if len(coords) != len(components): - raise ValueError( - f'Incorrect number of component coordinates passed to argument "coords". Expected {len(components)}, got {len(coords)}' - ) - - n = len(self.circuit._get_components()) - 2 - corr_matrix_w = np.zeros((n, n)) - corr_matrix_t = np.zeros((n, n)) - - # generate correlation values - for i in range(n): - for k in range(n): - - corr_val = np.exp( - -((x[k] - x[i]) ** 2 + (y[k] - y[i]) ** 2) / (0.5 * (l**2)) - ) - - corr_matrix_w[i][k] = corr_matrix_w[k][i] = corr_val - corr_matrix_t[i][k] = corr_matrix_t[k][i] = corr_val - - cov_matrix_w = np.zeros((n, n)) - cov_matrix_t = np.zeros((n, n)) - # generate covariance matrix - for i in range(n): - for k in range(n): - cov_matrix_w[i][k] = sigmaw * corr_matrix_w[i][k] * sigmaw - cov_matrix_t[i][k] = sigmat * corr_matrix_t[i][k] * sigmat - - try: - # perform Cholesky decomposition on the covariance matrices - l_w = cholesky(cov_matrix_w, lower=True) - l_t = cholesky(cov_matrix_t, lower=True) - except np.linalg.LinAlgError: - # if matrix is not positive-definite, do LU decomposition - _, l_w, _ = lu(cov_matrix_w) - _, l_t, _ = lu(cov_matrix_t) - - # generate random distribution with mean 0 and standard deviation of 1, size: no. of elements x no. of runs - X = np.random.multivariate_normal(np.zeros(n), np.eye(n, n), runs).T - - # generate correlation samples - corr_sample_matrix_w = np.dot(l_w, X) - corr_sample_matrix_t = np.dot(l_t, X) - - return (corr_sample_matrix_w, corr_sample_matrix_t) - - def layout_aware_simulation( - self, - component_or_circuit: Union["Component", Circuit], - sigmaw: float = 5, - sigmat: float = 2, - l: float = 4.5e-3, - runs: int = 10, - num_samples: int = 1, - ) -> Tuple[np.array, np.array]: - """Runs the layout-aware Monte Carlo sweep simulation for the circuit. - - Parameters - ---------- - component_or_circuit : - The component or circuit to run the simulation on. Can either - be a gdsfactory Component or Simphony Circuit object. - sigmaw : - Standard deviation of width variations (default 5) - sigmat : - Standard deviation of thickness variations (default 2) - l : - Correlation length (m) (default 4.5e-3) - runs : - The number of Monte Carlo iterations to run (default 10). - num_samples: - The number of samples to take. If only one sample is taken, it will - be the theoretical value of the circuit. If more than one sample is - taken, they will vary based on simulated noise. - """ - results = [] # store results - - if _has_gf and isinstance(component_or_circuit, Component): - components = [ - component - for component in self.circuit._get_components() - if not isinstance(component, (Laser, Detector)) - ] # get all components except the Laser and Detector - gfcomponents = [ - c.path if isinstance(c, siepic.Waveguide) else c.component - for c in components - ] # get all gdsfactory component attributes - - # Extract co-ordinates - coords = { - component: { - "x": (gfcomponents[i].ports[0].x + gfcomponents[i].ports[-1].x) / 2, - "y": (gfcomponents[i].ports[0].y + gfcomponents[i].ports[-1].y) / 2, - } - if isinstance(component, siepic.Waveguide) - else {"x": gfcomponents[i].x, "y": gfcomponents[i].y} - for i, component in enumerate(components) - } - elif isinstance(component_or_circuit, Circuit): - components = [ - component - for component in self.circuit._get_components() - if not isinstance(component, (Laser, Detector)) - ] # get all components except the Laser and Detector - gfcomponents = [ - c.component for c in components - ] # get all gdsfactory component attributes - - coords = { - component: {"x": gfcomponents[i].x, "y": gfcomponents[i].y} - for i, component in enumerate(components) - } - else: - raise ValueError( - "component_or_circuit must be qa gdsfactory component or a Simphony Circuit object." - ) - - # compute correlated samples - corr_sample_matrix_w, corr_sample_matrix_t = self._compute_correlated_samples( - coords, sigmaw, sigmat, l, runs - ) - - n = len(components) - for i in range(runs): - # use s_parameters for the first run, then monte_carlo_* for the rest - - # update component parameters - [ - components[idx].update_variations( - corr_w=corr_sample_matrix_w[idx][i], - corr_t=corr_sample_matrix_t[idx][i], - ) - for idx in range(n) - ] - - self.s_parameters_method = ( - "layout_aware_monte_carlo_s_parameters" if i else "s_parameters" - ) - - results.append(self.sample(num_samples=num_samples)) - - [ - components[idx].regenerate_layout_aware_monte_carlo_parameters() - for idx in range(n) - ] - - return results - def s_parameters(self, freqs: np.array) -> np.ndarray: """Gets the scattering parameters for the specified frequencies. @@ -915,10 +749,7 @@ def __init__( rf_noise=0, **kwargs, ): - super().__init__( - *args, - **kwargs, - ) + super().__init__(*args, **kwargs) # initialize parameters self.monitor_conversion_gain = monitor_conversion_gain diff --git a/simphony/simulators.py b/simphony/simulators.py index f964d665..13ae700a 100644 --- a/simphony/simulators.py +++ b/simphony/simulators.py @@ -83,10 +83,7 @@ def simulate( # if the scattering parameters for the circuit are cached, use those try: - if ( - s_parameters_method == "monte_carlo_s_parameters" - or "layout_aware_monte_carlo_s_parameters" - ): + if s_parameters_method == "monte_carlo_s_parameters": raise RuntimeError("No caching for Monte Carlo simulations.") s_params = self.__class__.scache[self.circuit] diff --git a/simphony/tests/mzi_monte_carlo_sparameters.npy b/simphony/tests/mzi_monte_carlo_sparameters.npy deleted file mode 100644 index 844024af2460cec6eb13a9bc63bd7591881176f9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3328 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 diff --git a/simphony/tests/mzi_sparameters.npy b/simphony/tests/mzi_sparameters.npy deleted file mode 100644 index 05e309b2102b85ac34c611c1c2413cb0915c70e9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3328 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