diff --git a/.gitea/workflows/build_relase.yaml b/.gitea/workflows/build_relase.yaml new file mode 100644 index 0000000..bf55f88 --- /dev/null +++ b/.gitea/workflows/build_relase.yaml @@ -0,0 +1,67 @@ +name: Build and Release mxPIC Wheels + +# This tells GitHub to ONLY run this pipeline when you push a version tag (e.g., v1.0.0) +on: + release: + types: [published] + +jobs: + # --- JOB 1: THE COMPILER --- + build-wheels: + name: Build on ${{ matrix.os }} + # The matrix allows us to build for Windows and Linux simultaneously! + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + python-version: ['3.10'] + + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install Build Dependencies + # Install the tools required by our custom build_wheel.py script + run: | + python -m pip install --upgrade pip + pip install build wheel mypy Cython setuptools + + - name: Run Auto-Builder (Compiles C-extensions & Injects .pyi) + run: python build_wheel.py + + - name: Temporarily Store the Built Wheel + uses: actions/upload-artifact@v4 + with: + name: mxpic-wheels-${{ matrix.os }} + path: dist/*.whl + retention-days: 1 + + # --- JOB 2: THE RELEASER --- + create-release: + name: Publish to GitHub Releases + needs: build-wheels # Waits for both Windows and Linux to finish + runs-on: ubuntu-latest + permissions: + contents: write # Required to allow the bot to create a release page + + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Download all OS Wheels + uses: actions/download-artifact@v4 + with: + pattern: mxpic-wheels-* + path: dist + merge-multiple: true + + - name: Upload Assets to Gitea Release + uses: softprops/action-gh-release@v1 + with: + files: "dist/*.whl" + api_key: ${{ secrets.GITEA_TOKEN }} \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..7adfd3b --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +recursive-include mxpic *.pyi \ No newline at end of file diff --git a/build/temp.win-amd64-cpython-39/Release/mxpic/basic.cp39-win_amd64.exp b/build/temp.win-amd64-cpython-39/Release/mxpic/basic.cp39-win_amd64.exp deleted file mode 100644 index 8a90129..0000000 Binary files a/build/temp.win-amd64-cpython-39/Release/mxpic/basic.cp39-win_amd64.exp and /dev/null differ diff --git a/build/temp.win-amd64-cpython-39/Release/mxpic/basic.cp39-win_amd64.lib b/build/temp.win-amd64-cpython-39/Release/mxpic/basic.cp39-win_amd64.lib deleted file mode 100644 index 84a4998..0000000 Binary files a/build/temp.win-amd64-cpython-39/Release/mxpic/basic.cp39-win_amd64.lib and /dev/null differ diff --git a/build/temp.win-amd64-cpython-39/Release/mxpic/basic.obj b/build/temp.win-amd64-cpython-39/Release/mxpic/basic.obj deleted file mode 100644 index 8228b3e..0000000 Binary files a/build/temp.win-amd64-cpython-39/Release/mxpic/basic.obj and /dev/null differ diff --git a/build/temp.win-amd64-cpython-39/Release/mxpic/components/primitives/directional_couplers/directional_couplers.cp39-win_amd64.exp b/build/temp.win-amd64-cpython-39/Release/mxpic/components/primitives/directional_couplers/directional_couplers.cp39-win_amd64.exp deleted file mode 100644 index 396c5fa..0000000 Binary files a/build/temp.win-amd64-cpython-39/Release/mxpic/components/primitives/directional_couplers/directional_couplers.cp39-win_amd64.exp and /dev/null differ diff --git a/build/temp.win-amd64-cpython-39/Release/mxpic/components/primitives/directional_couplers/directional_couplers.cp39-win_amd64.lib b/build/temp.win-amd64-cpython-39/Release/mxpic/components/primitives/directional_couplers/directional_couplers.cp39-win_amd64.lib deleted file mode 100644 index a47fa35..0000000 Binary files a/build/temp.win-amd64-cpython-39/Release/mxpic/components/primitives/directional_couplers/directional_couplers.cp39-win_amd64.lib and /dev/null differ diff --git a/build/temp.win-amd64-cpython-39/Release/mxpic/components/primitives/directional_couplers/directional_couplers.obj b/build/temp.win-amd64-cpython-39/Release/mxpic/components/primitives/directional_couplers/directional_couplers.obj deleted file mode 100644 index 6f812b5..0000000 Binary files a/build/temp.win-amd64-cpython-39/Release/mxpic/components/primitives/directional_couplers/directional_couplers.obj and /dev/null differ diff --git a/build/temp.win-amd64-cpython-39/Release/mxpic/components/primitives/edge_couplers/EC_dual_layer_px3.cp39-win_amd64.exp b/build/temp.win-amd64-cpython-39/Release/mxpic/components/primitives/edge_couplers/EC_dual_layer_px3.cp39-win_amd64.exp deleted file mode 100644 index 2e28ff4..0000000 Binary files a/build/temp.win-amd64-cpython-39/Release/mxpic/components/primitives/edge_couplers/EC_dual_layer_px3.cp39-win_amd64.exp and /dev/null differ diff --git a/build/temp.win-amd64-cpython-39/Release/mxpic/components/primitives/edge_couplers/EC_dual_layer_px3.cp39-win_amd64.lib b/build/temp.win-amd64-cpython-39/Release/mxpic/components/primitives/edge_couplers/EC_dual_layer_px3.cp39-win_amd64.lib deleted file mode 100644 index 4c74f0e..0000000 Binary files a/build/temp.win-amd64-cpython-39/Release/mxpic/components/primitives/edge_couplers/EC_dual_layer_px3.cp39-win_amd64.lib and /dev/null differ diff --git a/build/temp.win-amd64-cpython-39/Release/mxpic/components/primitives/edge_couplers/EC_dual_layer_px3.obj b/build/temp.win-amd64-cpython-39/Release/mxpic/components/primitives/edge_couplers/EC_dual_layer_px3.obj deleted file mode 100644 index 3ce4bbe..0000000 Binary files a/build/temp.win-amd64-cpython-39/Release/mxpic/components/primitives/edge_couplers/EC_dual_layer_px3.obj and /dev/null differ diff --git a/build/temp.win-amd64-cpython-39/Release/mxpic/components/primitives/grating_couplers/grating_couplers.cp39-win_amd64.exp b/build/temp.win-amd64-cpython-39/Release/mxpic/components/primitives/grating_couplers/grating_couplers.cp39-win_amd64.exp deleted file mode 100644 index e665c88..0000000 Binary files a/build/temp.win-amd64-cpython-39/Release/mxpic/components/primitives/grating_couplers/grating_couplers.cp39-win_amd64.exp and /dev/null differ diff --git a/build/temp.win-amd64-cpython-39/Release/mxpic/components/primitives/grating_couplers/grating_couplers.cp39-win_amd64.lib b/build/temp.win-amd64-cpython-39/Release/mxpic/components/primitives/grating_couplers/grating_couplers.cp39-win_amd64.lib deleted file mode 100644 index 9ac0e24..0000000 Binary files a/build/temp.win-amd64-cpython-39/Release/mxpic/components/primitives/grating_couplers/grating_couplers.cp39-win_amd64.lib and /dev/null differ diff --git a/build/temp.win-amd64-cpython-39/Release/mxpic/components/primitives/grating_couplers/grating_couplers.obj b/build/temp.win-amd64-cpython-39/Release/mxpic/components/primitives/grating_couplers/grating_couplers.obj deleted file mode 100644 index b2b1cf0..0000000 Binary files a/build/temp.win-amd64-cpython-39/Release/mxpic/components/primitives/grating_couplers/grating_couplers.obj and /dev/null differ diff --git a/build/temp.win-amd64-cpython-39/Release/mxpic/core/generate_license.cp39-win_amd64.exp b/build/temp.win-amd64-cpython-39/Release/mxpic/core/generate_license.cp39-win_amd64.exp deleted file mode 100644 index a8d8b78..0000000 Binary files a/build/temp.win-amd64-cpython-39/Release/mxpic/core/generate_license.cp39-win_amd64.exp and /dev/null differ diff --git a/build/temp.win-amd64-cpython-39/Release/mxpic/core/generate_license.cp39-win_amd64.lib b/build/temp.win-amd64-cpython-39/Release/mxpic/core/generate_license.cp39-win_amd64.lib deleted file mode 100644 index 2a554f8..0000000 Binary files a/build/temp.win-amd64-cpython-39/Release/mxpic/core/generate_license.cp39-win_amd64.lib and /dev/null differ diff --git a/build/temp.win-amd64-cpython-39/Release/mxpic/core/generate_license.obj b/build/temp.win-amd64-cpython-39/Release/mxpic/core/generate_license.obj deleted file mode 100644 index abcb628..0000000 Binary files a/build/temp.win-amd64-cpython-39/Release/mxpic/core/generate_license.obj and /dev/null differ diff --git a/build/temp.win-amd64-cpython-39/Release/mxpic/core/license_check.cp39-win_amd64.exp b/build/temp.win-amd64-cpython-39/Release/mxpic/core/license_check.cp39-win_amd64.exp deleted file mode 100644 index d2821e7..0000000 Binary files a/build/temp.win-amd64-cpython-39/Release/mxpic/core/license_check.cp39-win_amd64.exp and /dev/null differ diff --git a/build/temp.win-amd64-cpython-39/Release/mxpic/core/license_check.cp39-win_amd64.lib b/build/temp.win-amd64-cpython-39/Release/mxpic/core/license_check.cp39-win_amd64.lib deleted file mode 100644 index c692579..0000000 Binary files a/build/temp.win-amd64-cpython-39/Release/mxpic/core/license_check.cp39-win_amd64.lib and /dev/null differ diff --git a/build/temp.win-amd64-cpython-39/Release/mxpic/core/license_check.obj b/build/temp.win-amd64-cpython-39/Release/mxpic/core/license_check.obj deleted file mode 100644 index 2869689..0000000 Binary files a/build/temp.win-amd64-cpython-39/Release/mxpic/core/license_check.obj and /dev/null differ diff --git a/build/temp.win-amd64-cpython-39/Release/mxpic/foundries/AMF.cp39-win_amd64.exp b/build/temp.win-amd64-cpython-39/Release/mxpic/foundries/AMF.cp39-win_amd64.exp deleted file mode 100644 index eb1a690..0000000 Binary files a/build/temp.win-amd64-cpython-39/Release/mxpic/foundries/AMF.cp39-win_amd64.exp and /dev/null differ diff --git a/build/temp.win-amd64-cpython-39/Release/mxpic/foundries/AMF.cp39-win_amd64.lib b/build/temp.win-amd64-cpython-39/Release/mxpic/foundries/AMF.cp39-win_amd64.lib deleted file mode 100644 index 4a56a97..0000000 Binary files a/build/temp.win-amd64-cpython-39/Release/mxpic/foundries/AMF.cp39-win_amd64.lib and /dev/null differ diff --git a/build/temp.win-amd64-cpython-39/Release/mxpic/foundries/AMF.obj b/build/temp.win-amd64-cpython-39/Release/mxpic/foundries/AMF.obj deleted file mode 100644 index fb14f43..0000000 Binary files a/build/temp.win-amd64-cpython-39/Release/mxpic/foundries/AMF.obj and /dev/null differ diff --git a/build/temp.win-amd64-cpython-39/Release/mxpic/foundries/Silterra.cp39-win_amd64.exp b/build/temp.win-amd64-cpython-39/Release/mxpic/foundries/Silterra.cp39-win_amd64.exp deleted file mode 100644 index 8dd3fe6..0000000 Binary files a/build/temp.win-amd64-cpython-39/Release/mxpic/foundries/Silterra.cp39-win_amd64.exp and /dev/null differ diff --git a/build/temp.win-amd64-cpython-39/Release/mxpic/foundries/Silterra.cp39-win_amd64.lib b/build/temp.win-amd64-cpython-39/Release/mxpic/foundries/Silterra.cp39-win_amd64.lib deleted file mode 100644 index 4dd3394..0000000 Binary files a/build/temp.win-amd64-cpython-39/Release/mxpic/foundries/Silterra.cp39-win_amd64.lib and /dev/null differ diff --git a/build/temp.win-amd64-cpython-39/Release/mxpic/foundries/Silterra.obj b/build/temp.win-amd64-cpython-39/Release/mxpic/foundries/Silterra.obj deleted file mode 100644 index 5584395..0000000 Binary files a/build/temp.win-amd64-cpython-39/Release/mxpic/foundries/Silterra.obj and /dev/null differ diff --git a/build/temp.win-amd64-cpython-39/Release/mxpic/routing/routing.cp39-win_amd64.exp b/build/temp.win-amd64-cpython-39/Release/mxpic/routing/routing.cp39-win_amd64.exp deleted file mode 100644 index bb7abc4..0000000 Binary files a/build/temp.win-amd64-cpython-39/Release/mxpic/routing/routing.cp39-win_amd64.exp and /dev/null differ diff --git a/build/temp.win-amd64-cpython-39/Release/mxpic/routing/routing.cp39-win_amd64.lib b/build/temp.win-amd64-cpython-39/Release/mxpic/routing/routing.cp39-win_amd64.lib deleted file mode 100644 index bdc1455..0000000 Binary files a/build/temp.win-amd64-cpython-39/Release/mxpic/routing/routing.cp39-win_amd64.lib and /dev/null differ diff --git a/build/temp.win-amd64-cpython-39/Release/mxpic/routing/routing.obj b/build/temp.win-amd64-cpython-39/Release/mxpic/routing/routing.obj deleted file mode 100644 index 4bb2547..0000000 Binary files a/build/temp.win-amd64-cpython-39/Release/mxpic/routing/routing.obj and /dev/null differ diff --git a/build/temp.win-amd64-cpython-39/Release/mxpic/structures.cp39-win_amd64.exp b/build/temp.win-amd64-cpython-39/Release/mxpic/structures.cp39-win_amd64.exp deleted file mode 100644 index cf773ae..0000000 Binary files a/build/temp.win-amd64-cpython-39/Release/mxpic/structures.cp39-win_amd64.exp and /dev/null differ diff --git a/build/temp.win-amd64-cpython-39/Release/mxpic/structures.cp39-win_amd64.lib b/build/temp.win-amd64-cpython-39/Release/mxpic/structures.cp39-win_amd64.lib deleted file mode 100644 index 824be02..0000000 Binary files a/build/temp.win-amd64-cpython-39/Release/mxpic/structures.cp39-win_amd64.lib and /dev/null differ diff --git a/build/temp.win-amd64-cpython-39/Release/mxpic/structures.obj b/build/temp.win-amd64-cpython-39/Release/mxpic/structures.obj deleted file mode 100644 index b45efa5..0000000 Binary files a/build/temp.win-amd64-cpython-39/Release/mxpic/structures.obj and /dev/null differ diff --git a/build_release.py b/build_release.py index 08e0b05..475d0e6 100644 --- a/build_release.py +++ b/build_release.py @@ -6,7 +6,20 @@ from pathlib import Path def build_and_harvest(): src_dir = Path("mxpic") release_dir = Path("mxpic_release/mxpic") + + print("🧠 Generating IDE Stub Files (.pyi)...") + # 1. Run stubgen on the mxpic directory + # -o . outputs the files directly next to their .py counterparts + try: + subprocess.run(["stubgen", "./mxpic", "-o", "."], check=True) + print("✅ Stubs generated successfully.") + except subprocess.CalledProcessError: + print("❌ Failed to generate stubs. Is mypy installed?") + return + + print("🚀 Starting Wheel Build Process...") + print("🧹 Cleaning old builds...") if release_dir.parent.exists(): shutil.rmtree(release_dir.parent) @@ -35,12 +48,12 @@ def build_and_harvest(): file_path = current_root / file # Copy compiled binaries (.so for Linux/WSL, .pyd for Windows) - if file.endswith((".so", ".pyd")): + if file.endswith((".so", ".pyd", ".pyi")): shutil.move(file_path, target_dir / file) # Copy __init__.py files so Python recognizes the packages - elif file == "__init__.py": - shutil.copy2(file_path, target_dir / file) + # elif file == "__init__.py": + # shutil.copy2(file_path, target_dir / file) # (Optional) Copy non-code assets like config files or templates elif file.endswith((".json", ".yaml", ".yml")): diff --git a/build_wheel.py b/build_wheel.py new file mode 100644 index 0000000..ca2ce04 --- /dev/null +++ b/build_wheel.py @@ -0,0 +1,64 @@ +import os +import shutil +import subprocess +import zipfile # <-- Add this import at the top +from pathlib import Path + +def build_and_clean(): + print("🧠 Generating IDE Stub Files (.pyi)...") + try: + subprocess.run(["stubgen", "--parse-only", "./mxpic", "-o", "."], check=True) + print("✅ Stubs generated successfully.") + except subprocess.CalledProcessError: + print("❌ Failed to generate stubs.") + return + + print("🚀 Starting Wheel Build Process...") + try: + subprocess.run(["python", "-m", "build", "--wheel"], check=True) + print("✅ Base Wheel built successfully.") + except subprocess.CalledProcessError: + print("❌ Build failed. Stopping cleanup.") + return + + # --- THE SURGICAL INJECTION --- + print("💉 Injecting .pyi stubs directly into the Wheel...") + wheel_files = list(Path("dist").glob("*.whl")) + + if wheel_files: + target_wheel = wheel_files[0] + # Open the wheel file in 'append' mode + with zipfile.ZipFile(target_wheel, 'a', compression=zipfile.ZIP_DEFLATED) as wheel_zip: + injected_count = 0 + for pyi_file in Path("mxpic").rglob("*.pyi"): + # Inject the file, preserving the 'mxpic/...' folder structure + wheel_zip.write(pyi_file, arcname=pyi_file) + injected_count += 1 + print(f"✅ Successfully injected {injected_count} stub files into {target_wheel.name}") + else: + print("❌ Could not find the .whl file in the dist/ directory.") + + # --- THE CLEANUP --- + print("\n🧹 Commencing Workspace Cleanup...") + + src_path = Path("mxpic") + if src_path.exists(): + # Now it is safe to delete the stubs from the source folder! + for pyi_file in src_path.rglob("*.pyi"): + pyi_file.unlink() + + # Clean Cython artifacts + for c_file in src_path.rglob("*.c"): + c_file.unlink() + + # Delete the bulky build directories + folders_to_remove = ["build", "mxpic.egg-info"] + for folder in folders_to_remove: + folder_path = Path(folder) + if folder_path.exists() and folder_path.is_dir(): + shutil.rmtree(folder_path) + + print("\n✨ Workspace is clean. Your ultimate .whl is ready in dist/!") + +if __name__ == "__main__": + build_and_clean() \ No newline at end of file diff --git a/mxpic.lic b/mxpic.lic deleted file mode 100644 index 0c2eb0e..0000000 --- a/mxpic.lic +++ /dev/null @@ -1,5 +0,0 @@ -{ - "mac_address": "D4:54:8B:F1:46:49", - "expiration": "2027-05-07", - "signature": "844002ba44b83a6bc5ddb9889db72fc614b1232ed980cc7e13dd02c2f8de25f2" -} \ No newline at end of file diff --git a/mxpic/__init__.py b/mxpic/__init__.py index a991874..364733a 100644 --- a/mxpic/__init__.py +++ b/mxpic/__init__.py @@ -6,7 +6,6 @@ from .core.license_check import verify_license verify_license() # If the check passes, the rest of the library loads -from .components import EC_dual_layer_px3 +from .components import EC_dual_layer_px3,DC,DC_bend,DC_pX_3sg from .routing import Route -from .components.primitives import * \ No newline at end of file diff --git a/mxpic/components/primitives/edge_couplers/EC_dual_layer_px3.py b/mxpic/components/primitives/EC_dual_layer_px3.py similarity index 100% rename from mxpic/components/primitives/edge_couplers/EC_dual_layer_px3.py rename to mxpic/components/primitives/EC_dual_layer_px3.py diff --git a/mxpic/components/primitives/__init__.py b/mxpic/components/primitives/__init__.py index c7dc1c3..2b61251 100644 --- a/mxpic/components/primitives/__init__.py +++ b/mxpic/components/primitives/__init__.py @@ -1,2 +1,6 @@ -from .directional_couplers.directional_couplers import * -from .edge_couplers.EC_dual_layer_px3 import * \ No newline at end of file +from .directional_couplers import * +from .EC_dual_layer_px3 import * +from .directional_couplers import * +from .beam_splitters import * +from .multimode_interferometers import * +from .spiral import * \ No newline at end of file diff --git a/mxpic/components/primitives/beam_splitters.py b/mxpic/components/primitives/beam_splitters.py new file mode 100644 index 0000000..1ae0765 --- /dev/null +++ b/mxpic/components/primitives/beam_splitters.py @@ -0,0 +1,250 @@ +import nazca as nd +import numpy as np +from scipy.interpolate import CubicSpline + +from ...routing import Route + +from ...structures import * +from ...foundries import * + +import pandas as pd +from ...structures import _my_polygon +from ...basic import __cell_arg__ + +class YBranch: + """ + Broadband spline-shaped Y-branch with two bent output ports. + + Parameters + ---------- + name : str or None, optional + Nazca cell name. ``None`` keeps the cell uninstantiated (default is None). + xs : str, optional + Cross-section key used for both the taper body and attachments (default is "strip"). + w : Sequence[float], optional + Width control points (µm) used by the cubic spline along the taper axis. + Length must be >= 2. Default is ``[1.2, 1.0, 1.8, 1.2, 1.0, 1.2, 1.2]``. + L : float, optional + Total spline length in microns (default is 6). + R_att : float, optional + Bend radius of each attachment waveguide in microns (default is 10). + A_att : float, optional + Bend angle (degrees) per attachment arc (default is 10). + w_port : float, optional + Output port width in microns (default is 0.45). + show_pins : bool, optional + Draw Nazca stub markers when True (default is False). + sharp_patch : bool, optional + Add chamfer helpers inside polygons when True (default is True). + res : float, optional + Longitudinal sampling pitch (µm) for polygon discretization (default is 0.1). + """ + def __init__(self, + name : str = None, + xs : str = 'strip', + w : 'list|np.ndarray' = [1.2,1.0,1.8,1.2,1.0,1.2,1.2], + L : float = 6, + R_att : float = 10, + A_att : float = 10, + w_port : float = 0.45, + show_pins : bool = False, + sharp_patch : bool = True, + + res : float = 0.1, + ) -> None: + + self.name = name + + if (name!=None): + self.instantiate = True + else : + self.instantiate = False + + self.w = w + self.L = L + self.res = res + self.R_att = R_att + self.A_att = A_att + self.w_port = w_port + self.xs = xs + + self.cell = self.generate_gds(show_pins=show_pins,sharp_patch=sharp_patch) + + def generate_gds(self,show_pins=False,sharp_patch=True): + with nd.Cell(name=self.name,instantiate=self.instantiate) as C: + + w = np.r_[self.w] + + n_sects = len(self.w)-1 + res = self.L/n_sects + n_points = int(self.L/self.res)+1 + L = np.linspace(0,self.L,n_sects+1) + L_act = np.linspace(0,self.L,n_points) + + f = CubicSpline(L,w) ## cubic spline interpolant + + w_act = f(L_act) + + + for layers,growx,growy,acc in nd.layeriter(xs=self.xs): + (a1,b1), (a2,b2),c1,c2 = growx + + w_cur = w_act*(a1-a2) + (b1-b2) + if (b1!=0 and b2!=0): + w_cur = max(w_cur)*np.ones(np.shape(w_cur)) + vtx_x = np.r_[L_act,np.flip(L_act,0)] + vtx_y = np.r_[w_cur/2,-np.flip(w_cur/2,0)] + vtx = np.c_[vtx_x,vtx_y] + _my_polygon(layer_wg=layers,vtx=vtx).put(0,0,0) + + temp = circle(xs=self.xs,radius=self.R_att,theta_start=0,theta_stop=self.A_att,width=self.w_port).cell.put('a1',self.L,self.w[-1]/2 - self.w_port/2,0) + temp = circle(xs=self.xs,radius=self.R_att,theta_start=-self.A_att,theta_stop=0,width=self.w_port).cell.put('a1',temp.pin['b1'],flip=1) + nd.Pin(name='b1',width=self.w_port).put(temp.pin['b1']) + + temp = circle(xs=self.xs,radius=self.R_att,theta_start=0,theta_stop=self.A_att,width=self.w_port).cell.put('a1',self.L,-self.w[-1]/2 + self.w_port/2,0,flip=1) + temp = circle(xs=self.xs,radius=self.R_att,theta_start=-self.A_att,theta_stop=0,width=self.w_port).cell.put('a1',temp.pin['b1'],flip=0) + nd.Pin(name='b2',width=self.w_port).put(temp.pin['b1']) + + nd.Pin(name='a1',width=self.w[0]).put(0,0,180) + + if (show_pins): + nd.put_stub() + + return C + + +class Ybranch_3wg: + """ + Initialization of a symmetric tapered coupler for 3dB coupling + + Parameters + ---------- + name : str or None, optional + Nazca cell name (default is None). + + 1. taper part + w0 : float, optional + Width (µm) of the center arm at the coupling region entrance (default is 0.4). + w1 : float, optional + Width (µm) of the outer arms at the coupling region exit (default is 0.2). + gap : float, optional + Vertical spacing (µm) between adjacent arms inside the coupler (default is 0.18). + Lcp : float, optional + Length (µm) of each taper section forming the coupler (default is 20). + xs : str, optional + Cross-section key for all segments (default is "strip"). + + 2. attachment part + w_wg : float, optional + External IO waveguide width in microns (default is 0.45). + R0 : float, optional + Bend radius (µm) used for both output waveguides (default is 10). + angle : float, optional + Bend deflection angle in degrees (default is 20). + L_attach : float, optional + Length (µm) of straight sections appended after the output tapers (default is 3). + L_in_tp : float, optional + Taper length (µm) that links the IO waveguide to width ``w0`` (default is 3). + + sharp_patch : bool, optional + Insert chamfer helpers when True (default is True). + """ + def __init__(self, + name = None, + w0:float=0.4, + w1:float=0.2, + gap:float=0.18, + Lcp:float=20, + xs:str='strip', + w_wg:float=0.45, + R0:float=10, + angle:float=20, + L_attach:float=3, + L_in_tp:float=3, + sharp_patch:bool=True): + + self.name = name + if (self.name==None): + self.instantiate = False + else : + self.instantiate = True + + self.w0 = w0 + self.w1 = w1 + self.gap = gap + self.Lcp = Lcp + self.xs = xs + self.w_wg = w_wg + self.R0 = R0 + self.angle = angle + self.L_attach = L_attach + self.L_in_tp = L_in_tp + + self.cell = self.generate_gds(sharp_patch=sharp_patch) + self.L = np.abs(self.cell.pin['a1'].x - self.cell.pin['b1'].x) + + def generate_gds(self,sharp_patch,err_asy=0): + with nd.Cell(instantiate=self.instantiate,name=self.name) as C: + + w0 = self.w0 + w1 = self.w1 + Lcp = self.Lcp + gap = self.gap + xs = self.xs + w_wg = self.w_wg + L_attach = self.L_attach + L_in_tp = self.L_in_tp + angle = self.angle + R0 = self.R0 + + t_mid = nd.taper(width1=w0,width2=w1,length=Lcp,xs=xs).put(0,0,0) + t_u = nd.taper(width2=w0,width1=w1,length=Lcp,xs=xs).put(0,w1/2+w0/2+gap,0) + t_d = nd.taper(width2=w0,width1=w1,length=Lcp,xs=xs).put(0,-(w1/2+w0/2+gap),0) + t_in = nd.taper(width1=w_wg,width2=w0,length=L_in_tp,xs=xs).put(-L_in_tp,0,0) + t_in = nd.strt(width=w_wg,length=L_attach,xs=xs).put(t_in.pin['a0'],flip=0) + nd.Pin(name='a1',pin=t_in.pin['b0']).put() + + au = nd.bend(radius=R0,angle=angle,xs=xs,width=w0).put(t_u.pin['b0'],flip=0) + au = nd.bend(radius=R0,angle=angle,xs=xs,width=w0).put(au.pin['b0'],flip=1) + au = nd.taper(width1=w0,width2=w_wg,length=L_in_tp,xs=xs).put(au.pin['b0'],flip=0) + au = nd.strt(width=w_wg,length=L_attach,xs=xs).put(au.pin['b0'],flip=0) + nd.Pin(name='b1',pin=au.pin['b0']).put() + + ad = nd.bend(radius=R0,angle=angle,xs=xs,width=w0).put(t_d.pin['b0'],flip=1) + ad = nd.bend(radius=R0,angle=angle,xs=xs,width=w0).put(ad.pin['b0'],flip=0) + ad = nd.taper(width1=w0,width2=w_wg,length=L_in_tp,xs=xs).put(ad.pin['b0'],flip=0) + ad = nd.strt(width=w_wg,length=L_attach,xs=xs).put(ad.pin['b0'],flip=0) + nd.Pin(name='b2',pin=ad.pin['b0']).put() + + + if (sharp_patch==True): + dY = np.abs(ad.pin['b0'].y-au.pin['b0'].y)+w_wg + for layers,growx,growy,acc in nd.layeriter(xs=xs): + (a1,b1), (a2,b2),c1,c2 = growx + if (b1!=0 and b2!=0): + L_patch = dY*(a1-a2)+(b1-b2) + W_patch = dY*(a1-a2)+(b1-b2) + nd.strt(length=W_patch,width=L_patch,layer=layers).put(ad.pin['b0'].x,0,0) + return C + + def generate_test_gds(self,gc,dX_gc2gc=400,dY_gc2gc=80,sharp_patch = True,Rbend=15): + with nd.Cell(instantiate=False) as C: + + + gc_cell = __cell_arg__(arg=gc,arg_name="gc",func_name="mxpic::Ybranch_3wg::generate_test_gds") + + inst = self.cell.put('a1',-self.L/2,0,0) + + gc_In = gc_cell.put('g1',-dX_gc2gc/2,0,180) + gc_O1 = gc_cell.put('g1',dX_gc2gc/2, dY_gc2gc/2,0) + gc_O2 = gc_cell.put('g1',dX_gc2gc/2,-dY_gc2gc/2,0) + pic_strip = Route(radius=Rbend,width=self.w_wg,xs=self.xs) + + pic_strip.taper(pin=gc_O1.pin['g1'],width1=gc_O1.pin['g1'].width,width2=self.w_wg,length=5,arrow=False) + pic_strip.sbend_p2p(original_function=not sharp_patch, pin2=inst.pin['b1'],arrow=False).put() + + pic_strip.taper(pin=gc_O2.pin['g1'],width1=gc_O2.pin['g1'].width,width2=self.w_wg,length=5,arrow=False) + pic_strip.sbend_p2p(original_function=not sharp_patch, pin2=inst.pin['b2'],arrow=False).put() + pic_strip.taper_p2p(pin1=gc_In.pin['g1'],pin2=inst.pin['a1'],arrow=False).put() + + return C \ No newline at end of file diff --git a/mxpic/components/primitives/directional_couplers/directional_couplers.py b/mxpic/components/primitives/directional_couplers.py similarity index 99% rename from mxpic/components/primitives/directional_couplers/directional_couplers.py rename to mxpic/components/primitives/directional_couplers.py index 3247dbd..50bdfed 100644 --- a/mxpic/components/primitives/directional_couplers/directional_couplers.py +++ b/mxpic/components/primitives/directional_couplers.py @@ -3,9 +3,9 @@ import nazca as nd import numpy as np from numpy import pi -from ....routing import Route -from ....structures import _my_polygon,circle,Clothoid -from ....basic import __cell_arg__ +from ...routing import Route +from ...structures import _my_polygon,circle,Clothoid +from ...basic import __cell_arg__ class ring_bus_wg: ## two types: diff --git a/mxpic/components/primitives/directional_couplers/__init__.py b/mxpic/components/primitives/directional_couplers/__init__.py deleted file mode 100644 index 88513f7..0000000 --- a/mxpic/components/primitives/directional_couplers/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .directional_couplers import * \ No newline at end of file diff --git a/mxpic/components/primitives/grating_couplers/grating_couplers.py b/mxpic/components/primitives/grating_couplers.py similarity index 99% rename from mxpic/components/primitives/grating_couplers/grating_couplers.py rename to mxpic/components/primitives/grating_couplers.py index ecfc2f8..e2ec1a8 100644 --- a/mxpic/components/primitives/grating_couplers/grating_couplers.py +++ b/mxpic/components/primitives/grating_couplers.py @@ -3,9 +3,9 @@ import numpy as np import math import pandas as pd -from ....routing import Route -from ....structures import _my_polygon,circle,Clothoid,hole -from ....basic import __cell_arg__ +from ...routing import Route +from ...structures import _my_polygon,circle,Clothoid,hole +from ...basic import __cell_arg__ ''' Class for nanoantenna ''' diff --git a/mxpic/components/primitives/multimode_interferometers.py b/mxpic/components/primitives/multimode_interferometers.py new file mode 100644 index 0000000..6884441 --- /dev/null +++ b/mxpic/components/primitives/multimode_interferometers.py @@ -0,0 +1,260 @@ +from turtle import shape +import nazca as nd +import numpy as np +import math + +from ...routing import Route + +from ...structures import * + +from ...structures import _my_polygon,Conchoid + +""" Mono layer MMI """ +class MMI_ML: + """ + Multi-layer (mono-layer) multimode interference (MMI) device generator. + + Parameters + ---------- + name : str or None, optional + Nazca cell name. ``None`` keeps the cell uninstantiated (default is None). + L_arm : Sequence[float], optional + Segment lengths (µm) of each arm taper section (default is ``[10]``). + w_arm : Sequence[float], optional + Corresponding arm widths (µm). Length must be ``len(L_arm) + 1`` (default is ``[0.45, 1.35]``). + xs : str, optional + Nazca cross-section key used for both arm and MMI regions (default is "strip"). + arm_sine_width : bool, optional + If True, arm width follows a cosine taper instead of linear interpolation (default is False). + L_mmi : Sequence[float], optional + Segment lengths (µm) within the central MMI body (default is ``[10]``). + w_mmi : Sequence[float], optional + MMI widths (µm). Length must be ``len(L_mmi) + 1`` (default is ``[5, 5]``). + mmi_sine_width : bool, optional + If True, MMI width transition uses cosine instead of linear interpolation (default is False). + sharp_patch : bool, optional + Insert chamfer polygons at acute corners when ``True`` (default is True). + show_pins : bool, optional + Draw Nazca stub markers for debugging when ``True`` (default is False). + res : float, optional + Longitudinal sampling resolution (µm) for polygon generation (default is 0.01). + N_out : int, optional + Number of output ports (default is 3). + N_in : int, optional + Number of input ports (default is 1). + Dp_out : float, optional + Vertical pitch (µm) between adjacent output ports (default is 1.5). + Dp_in : float, optional + Vertical pitch (µm) between adjacent input ports (default is 1.5). + """ + def __init__(self, + name=None, + L_arm=[10], + w_arm=[0.45,1.35], + xs = 'strip', + arm_sine_width=False, + L_mmi = [10], + w_mmi = [5,5], + + mmi_sine_width=False, + + sharp_patch=True, + show_pins = False, + + res = 0.01, + N_out = 3, + N_in = 1, + Dp_out = 1.5, + Dp_in = 1.5, + ) -> None: + + self.name = name + if (self.name==None): + self.instantiate = False + else : + self.instantiate = True + + self.L_arm = L_arm + self.xs = xs + self.w_arm = w_arm + self.arm_sine_width = arm_sine_width + self.L_mmi = L_mmi + self.w_mmi = w_mmi + self.res = res + self.N_out = N_out + self.N_in = N_in + self.Dp_out = Dp_out + self.Dp_in = Dp_in + self.mmi_sine_width = mmi_sine_width + + self.cell = self.generate_gds(sharp_patch=sharp_patch,show_pins=show_pins) + + self.L = np.sum(self.L_arm)*2+np.sum(self.L_mmi) + + def generate_gds(self,sharp_patch,show_pins): + + with nd.Cell(instantiate=self.instantiate,name=self.name) as C: + L = 0 + Lsg = [] + Wsg = [] + for idx in range(0,len(self.L_arm)): + n_points = round(self.L_arm[idx]/self.res)+1 + L_sect = np.linspace(L,L+self.L_arm[idx],n_points) + Lsg = np.r_[Lsg,L_sect] + if (self.arm_sine_width): + dw = self.w_arm[idx+1]-self.w_arm[idx] + w_sect = -np.cos(L_sect/self.L_arm[idx]*pi)*dw + (self.w_arm[idx+1]-self.w_arm[idx])/2 + else: + w_sect = np.linspace(self.w_arm[idx],self.w_arm[idx+1],n_points) + Wsg = np.r_[Wsg,w_sect] + + L = L + self.L_arm[idx] + + with nd.Cell(instantiate=False) as Arm: + for layers,growx,growy,acc in nd.layeriter(xs=self.xs): + (a1,b1), (a2,b2),c1,c2 = growx + + vtx_y = np.r_[Wsg*a1+b1, np.flip(Wsg,0)*a2+b2] + vtx_x = np.r_[Lsg, np.flip(Lsg,0)] + vtx = np.c_[vtx_x,vtx_y] + _my_polygon(layer_wg=layers,vtx=vtx).put(0,0,0) + + + nd.Pin(name='a1',width=Wsg[0]).put(0,0,180) + nd.Pin(name='b1',width=Wsg[-1]).put(L,0,0) + + """ For central MMI """ + L_mmi = 0 + Lsg_mmi = [] + Wsg_mmi = [] + for idx in range(0,len(self.L_mmi)): + n_points = round(self.L_mmi[idx]/self.res)+1 + L_sect = np.linspace(L_mmi,L_mmi+self.L_mmi[idx],n_points) + Lsg_mmi = np.r_[Lsg_mmi,L_sect] + if (self.arm_sine_width): + dw = self.w_mmi[idx+1]-self.w_mmi[idx] + w_sect = -np.cos(L_sect/self.L_mmi[idx]*pi)*dw + (self.w_mmi[idx+1]-self.w_mmi[idx])/2 + else: + w_sect = np.linspace(self.w_mmi[idx],self.w_mmi[idx+1],n_points) + Wsg_mmi = np.r_[Wsg_mmi,w_sect] + + L_mmi = L_mmi + self.L_mmi[idx] + + with nd.Cell(instantiate=False) as MMI: + for layers,growx,growy,acc in nd.layeriter(xs=self.xs): + (a1,b1), (a2,b2),c1,c2 = growx + + vtx_y = np.r_[Wsg_mmi*a1+b1, np.flip(Wsg_mmi,0)*a2+b2] + vtx_x = np.r_[Lsg_mmi, np.flip(Lsg_mmi,0)] + vtx = np.c_[vtx_x,vtx_y] + if (b1==0 and b2==0): + _my_polygon(layer_wg=layers,vtx=vtx).put(0,0,0) + else : + w = max(Wsg_mmi)+b1*2 + L = max(Lsg_mmi)+b1*2 + nd.strt(length=L,layer=layers,width=w).put(-b1,0,0) + + nd.Pin(name='a1',width=Wsg_mmi[0]).put(0,0,180) + nd.Pin(name='b1',width=Wsg_mmi[-1]).put(L_mmi,0,0) + + for idx_in in range(0,self.N_in): + Arm_inst = Arm.put('b1',0,self.Dp_in*(-idx_in+(self.N_in-1)/2),180) + nd.Pin(name='a'+str(round(idx_in+1)),pin=Arm_inst.pin['a1']).put() + + for idx_in in range(0,self.N_out): + Arm_inst = Arm.put('b1',L_mmi,self.Dp_out*(-idx_in+(self.N_out-1)/2),0) + nd.Pin(name='b'+str(round(idx_in+1)),pin=Arm_inst.pin['a1']).put() + MMI.put('a1',0,0,0) + + if (show_pins): + nd.put_stub() + + return C + + def generate_test_gds(self,gc,dX_gc2gc,dY_gc2gc,R_bend=10,Xout_offset=50): + if (isinstance(gc,nd.Cell)): + gc_cell =gc + elif (hasattr(gc,'cell')): + gc_cell = gc.cell + else : + raise Exception("ERROR: In , is not recongized as a cell") + + with nd.Cell(instantiate=False) as C: + + INST = self.cell.put(-self.L/2,0,0) + pic_strip = Route(width=self.w_arm[0],radius=R_bend,xs=self.xs) + for idx_in in range(0,self.N_in): + GC = gc_cell.put('g1',-dX_gc2gc/2,dY_gc2gc*(-idx_in + (self.N_in-1)/2),180) + pic_strip.sbend_p2p(pin1=GC.pin['g1'],pin2=INST.pin['a'+str(idx_in+1)],Lstart=dX_gc2gc/10).put() + + for idx_in in range(0,self.N_out): + toggle = np.mod(idx_in,2)-0.5 + GC = gc_cell.put('g1', dX_gc2gc/2+Xout_offset*toggle,dY_gc2gc*(-idx_in + (self.N_out-1)/2),0) + pic_strip.sbend_p2p(pin1=GC.pin['g1'],pin2=INST.pin['b'+str(idx_in+1)],Lstart=dX_gc2gc/10).put() + + return C + + +class MMI_STD(MMI_ML): + """ + Convenience wrapper for standard MMIs with equal-length arms and uniform MMI body. + + Parameters + ---------- + name : str or None, optional + Nazca cell name (default is None). + N_out : int, optional + Number of output ports (default is 3). + N_in : int, optional + Number of input ports (default is 1). + L_arm : float, optional + Single arm length in microns (default is 10). + w_wg : float, optional + Input/output waveguide width in microns (default is 0.45). + w_port : float, optional + Width at the transition between the taper and MMI (default is 1.2). + xs : str, optional + Cross-section key for all regions (default is "strip"). + L_mmi : float, optional + Central MMI length in microns (default is 10). + w_mmi : float, optional + Central MMI width in microns (default is 5). + sharp_patch : bool, optional + Add chamfer helpers when True (default is True). + show_pins : bool, optional + Draw Nazca stub markers when True (default is False). + Dp_out : float, optional + Output port pitch in microns (default is 1.5). + Dp_in : float, optional + Input port pitch in microns (default is 1.5). + """ + def __init__(self, + name=None, + N_out=3, + N_in=1, + L_arm=10, + w_wg=0.45, + w_port = 1.2, + xs='strip', + L_mmi=10, + w_mmi=5, + sharp_patch=True, + show_pins=False, + Dp_out=1.5, + Dp_in=1.5) -> None: + + super().__init__(name=name, + L_arm=[L_arm], + w_arm=[w_wg,w_port], + xs=xs, + arm_sine_width=False, + L_mmi=[L_mmi], + w_mmi=[w_mmi,w_mmi], + mmi_sine_width=False, + sharp_patch=sharp_patch, + show_pins=show_pins, + res=min([L_mmi,L_arm]), ## taper resolution + N_out=N_out, + N_in=N_in, + Dp_out=Dp_out, + Dp_in=Dp_in) \ No newline at end of file diff --git a/mxpic/components/primitives/spiral.py b/mxpic/components/primitives/spiral.py new file mode 100644 index 0000000..dfc2bbf --- /dev/null +++ b/mxpic/components/primitives/spiral.py @@ -0,0 +1,1186 @@ +from turtle import shape +import nazca as nd +import numpy as np +import math + +from ...routing import Route + +from ...structures import * +from ...foundries import * + +from ...structures import _my_polygon,Conchoid,_my_poly_spiral + +from scipy import optimize + +class spiral: + """ + Parametric waveguide spiral supporting circular or rectangular footprints. + + Parameters + ---------- + name : str, optional + Nazca cell name (default is None). + shape : str, optional + Footprint style used for the spiral path, circular or rectangular (default is "circle"). + Dmin : float, optional + Minimum inner diameter in microns; sets the first loop radius (default is 50). + R_bend : float, optional + Bend radius in microns for rectangular implementations (default is 10). + Rmin_euler : float, optional + Minimum radius inside Euler bends when ``Euler_bend`` is True (default is 10). + Lmin : float, optional + Straight length in microns used by the innermost rectangular loop (default is 50). + width : float, optional + Nominal waveguide width inside the spiral body (default is 2). + w_port : float, optional + Output-port width in microns. ``None`` inherits ``width`` (default is 0.45). + w_bend_center : float, optional + Waveguide width used in the central attachment bends (default is 1). + Rmin_bend_center : float, optional + Minimum radius for the attachment bends (default is 10). + gap : float, optional + Spacing between adjacent turns in microns (default is 1). + cycles : float, optional + Number of half-turns (π radians) laid out in the spiral (default is 20). + xs : str, optional + Cross-section key for the entire structure (default is "strip"). + layer : str, optional + Override layer for polygons; ``None`` derives from ``xs`` (default is None). + w_bend_port : float or None, optional + Width inside the outermost bends; ``None`` inherits ``width`` (default is None). + Ltp_port : float, optional + Length of straight tapers that adapt ``width`` to ``w_port`` (default is 10). + res : float, optional + Arc-length sampling step (µm) used for polygon tessellation (default is 0.5). + cell_transition : nazca.Cell, optional + Insert XS transitions cell to strip when connecting ports (default is None). + port_angle : float, optional + Output-port deflection angle in degrees, measured from +x (default is 180). + Euler_bend : bool, optional + Use Euler/Clothoid bends at the center instead of circular bends (default is False). + show_pins : bool, optional + Draw Nazca stub markers when True (default is False). + sharp_patch : bool, optional + Add chamfer helper polygons when True (default is True). + """ + def __init__(self, + name: str = None, + shape: str = 'circle', + Dmin: float = 50, + R_bend: float = 10, + Rmin_euler: float = 10, + Lmin: float = 50, + width: float = 2, + w_port: float = 0.45, ## not used at this moment + w_bend_center: float = 1, + Rmin_bend_center: float = 10, + gap: float = 1, + cycles: float = 20, + xs: str = 'strip', + layer: str = None, + w_bend_port=None, + Ltp_port = 10, + + res : float = 0.5, ## added in 2023.1.4, the length resolution + cell_transition : nd.Cell = None, + + port_angle: float = 180, + Euler_bend: bool = False, + show_pins: bool = False, + sharp_patch:bool = True +): + + self.Dmin = Dmin + self.Lmin = Lmin + self.R_bend = R_bend + self.shape = shape + self.cycles = cycles + self.width = width + self.w_port = w_port + self.gap = gap + self.xs = xs + self.layer = layer + + self.name=name + if (self.name==None): + self.instantiate = False + else : + self.instantiate = True + + + self.port_angle=port_angle + self.w_bend_center= w_bend_center + self.Rmin_bend_center= Rmin_bend_center + self.Euler_bend= Euler_bend + self.Rmin_euler= Rmin_euler + self.sharp_patch= sharp_patch + + self.w_bend_port = w_bend_port + self.Ltp_port = Ltp_port + + self.cell_transition = cell_transition + + self.res = res + + self.cell = self.generate_gds(show_pins=show_pins) + + def __strt_with_taper__(self,width1,width2,xs,length,Ltp=15,Lstart=2): + with nd.Cell(instantiate=False) as C: + + L_mm = length-Ltp*2-Lstart*2 + + if (L_mm>0): + + instr_a1 = nd.strt(length=Lstart,width=width1,xs=xs).put(0,0,0) + nd.taper(length=Ltp,width1=width1,width2=width2,xs=xs).put() + nd.strt(length=L_mm,width=width2,xs=xs).put() + nd.taper(length=Ltp,width1=width2,width2=width1,xs=xs).put() + instr_b1 = nd.strt(length=Lstart,width=width1,xs=xs).put() + + else : + instr_a1 = nd.strt(length=length/2,width=width1,xs=xs).put(0,0,0) + instr_b1 = nd.strt(length=length/2,width=width1,xs=xs).put() + + nd.Pin(name='a0',pin=instr_a1.pin['a0']).put() + nd.Pin(name='b0',pin=instr_b1.pin['b0']).put() + + return C + + def generate_gds(self,show_pins): + + if (self.w_port==None): + self.w_port = self.width + if (self.w_bend_port==None): + self.w_bend_port = self.width + + + """ Circular Spiral """ + if (self.shape=='circle'): + with nd.Cell(instantiate=self.instantiate,name=self.name) as C: + if (self.layer==None): + pitch = (self.width+self.gap)*2 ## a bi-twsited circle + + Dmin = self.Dmin + R0 = Dmin/2 + kR = pitch/(np.pi*2) + + K_att = (np.power(R0,2)+2*np.power(kR,2))/np.power((np.power(R0,2)+np.power(kR,2)),1.5) + R_att = 1/K_att + + + for layers,growx,growy,acc in nd.layeriter(xs=self.xs): + (a1,b1), (a2,b2),c1,c2 = growx + + """ Generating Central Euler bend """ + if (self.Euler_bend == True): + """ Modified in 2023.07.31, Clothoid simplified into the simple attachment of Clothoid and Conchoid + No Angle compensation were build due to no significant improvement + """ + # spr_bend = Clothoid(xs=self.xs,R=[R_att/1.5,R_att/2.4,R_att], + spr_bend = Clothoid(xs=self.xs,R=[R_att,R_att/2.5001,R_att], + w=[self.w_bend_center,self.w_bend_port],A=[0,90,180],dL_wg=self.res,width_type='dual_sine') + + else : + """ Genreating Circular bend for center """ + spr_bend = Clothoid(xs=self.xs,R=[R0/2,R0/2,R0/2], + w=[self.w_bend_port,self.w_bend_port],A=[0,90,180],dL_wg=self.res,width_type='dual_sine') + + w_cur = self.width*(a1-a2)+(b1-b2) + if (w_cur, Dmin too small") + D_port = self.Dmin - bend_sz[1]*2 + + with nd.Cell(instantiate=False) as wg_mid_cell: + wg = self.__strt_with_taper__(length=self.Lmin-bend_sz[0]*2,width2=self.width,width1=bend_cell.pin['a0'].width,xs=self.xs,Ltp=self.Ltp_port).put(-self.Lmin/2+bend_sz[0]*2,0,0) + bend_cell.put(wg.pin['a0'],flip=1) + self.__strt_with_taper__(length=D_port,width2=self.width,width1=bend_cell.pin['a0'].width,xs=self.xs,Ltp=self.Ltp_port).put() + bd = bend_cell.put(flip=0) + nd.Pin(name='a0',pin=bd.pin['b0']).put() + nd.Pin(name='b0',pin=wg.pin['b0']).put() + + wg_mid = wg_mid_cell.put(-self.Lmin/2,self.Dmin/2,0) + + pin_U_pre = wg_mid.pin['b0'] + pin_D_pre = wg_mid.pin['a0'] + + bend_U = bend_cell.put(pin_U_pre) + wg_U = self.__strt_with_taper__(length=pitch/2+D_port, + width2=self.width,width1=bend_U.pin['b0'].width,xs=self.xs,Ltp=self.Ltp_port).put(bend_U.pin['b0']) + bend_U2 = bend_cell.put(wg_U.pin['b0']) + wg_U = self.__strt_with_taper__(length=self.Lmin+pitch/2, + width2=self.width,width1=bend_U2.pin['b0'].width,xs=self.xs,Ltp=self.Ltp_port).put(bend_U2.pin['b0']) + pin_U_pre = wg_U.pin['b0'] + + bend_D = bend_cell.put(pin_D_pre) + wg_D = self.__strt_with_taper__(length=pitch/2+D_port, + width2=self.width,width1=bend_D.pin['b0'].width,xs=self.xs,Ltp=self.Ltp_port).put(bend_D.pin['b0']) + bend_D2 = bend_cell.put(wg_D.pin['b0']) + wg_D = self.__strt_with_taper__(length=self.Lmin+pitch/2, + width2=self.width,width1=bend_D2.pin['b0'].width,xs=self.xs,Ltp=self.Ltp_port).put(bend_D2.pin['b0']) + pin_D_pre = wg_D.pin['b0'] + + L = self.Lmin*3+pitch+L_bend + _cycle_ = 1 + for _cycle_ in range(1,self.cycles-1): + bend_U = bend_cell.put(pin_U_pre) + wg_U = self.__strt_with_taper__(length=_cycle_*pitch+pitch/2+D_port, + width2=self.width,width1=bend_U.pin['b0'].width,xs=self.xs,Ltp=self.Ltp_port).put(bend_U.pin['b0']) + bend_U2 = bend_cell.put(wg_U.pin['b0']) + wg_U = self.__strt_with_taper__(length=self.Lmin+_cycle_*pitch+pitch/2, + width2=self.width,width1=bend_U2.pin['b0'].width,xs=self.xs,Ltp=self.Ltp_port).put(bend_U2.pin['b0']) + pin_U_pre = wg_U.pin['b0'] + + bend_D = bend_cell.put(pin_D_pre) + wg_D = self.__strt_with_taper__(length=_cycle_*pitch+pitch/2+D_port, + width2=self.width,width1=bend_D.pin['b0'].width,xs=self.xs,Ltp=self.Ltp_port).put(bend_D.pin['b0']) + bend_D2 = bend_cell.put(wg_D.pin['b0']) + wg_D = self.__strt_with_taper__(length=self.Lmin+_cycle_*pitch+pitch/2, + width2=self.width,width1=bend_D2.pin['b0'].width,xs=self.xs,Ltp=self.Ltp_port).put(bend_D2.pin['b0']) + pin_D_pre = wg_D.pin['b0'] + + L = L+(_cycle_*pitch-pitch/2+D_port+self.Lmin+_cycle_*pitch+pitch/2)*2+L_bend*4 + + + """ 2023.03.19 REVISED, the spiral will end at the same Y level with begining """ + bend_D = bend_cell.put(pin_D_pre) + + + ## adding bend connection to outside + if(self.port_angle==90 or self.port_angle==270 or self.port_angle==-90): + wg_D = self.__strt_with_taper__(length=(_cycle_+1)*pitch+D_port, + width2=self.width,width1=bend_D.pin['b0'].width,xs=self.xs,Ltp=self.Ltp_port).put(bend_D.pin['b0']) + pin_D_pre = wg_D.pin['b0'] + + elif(self.port_angle==180) : + wg_D = self.__strt_with_taper__(length=(_cycle_+1)*pitch+D_port, + width2=self.width,width1=bend_D.pin['b0'].width,xs=self.xs,Ltp=self.Ltp_port).put(bend_D.pin['b0']) + + bend_D2 = bend_cell.put(wg_D.pin['b0'],flip=1) + pin_D_pre = bend_D2.pin['b0'] + elif(self.port_angle==0 or self.port_angle==360) : + + wg_D = self.__strt_with_taper__(length=(_cycle_+1.5)*pitch+D_port, + width2=self.width,width1=bend_D.pin['b0'].width,xs=self.xs,Ltp=self.Ltp_port).put(bend_D.pin['b0']) + + bend_D2 = bend_cell.put(wg_D.pin['b0'],flip=0) + pin_D_pre = bend_D2.pin['b0'] + + if (self.cell_transition is not None): + taper = self.cell_transition.put(pin_D_pre) + pin_D_pre = taper.pin['b0'] + taper = self.cell_transition.cell.put(pin_U_pre) + pin_U_pre = taper.pin['b0'] + + + """ 2023.03.19 REVISED, the begining will not be influenced by the rotation angle """ + if (self.w_port !=self.width) : + if (self.rib2strip): + nd.taper(length=self.Ltp_port,width1=self.width,width2=self.w_port,xs='strip').put(pin_D_pre) + nd.Pin(name='b1',width=self.w_bend_port).put() + + nd.taper(length=self.Ltp_port,width1=self.width,width2=self.w_port,xs='strip').put(pin_U_pre) + nd.Pin(name='a1',width=self.w_bend_port).put() + else : + nd.taper(length=self.Ltp_port,width1=self.width,width2=self.w_port,xs=self.xs).put(pin_D_pre) + nd.Pin(name='b1',width=self.w_bend_port).put() + + nd.taper(length=self.Ltp_port,width1=self.width,width2=self.w_port,xs=self.xs).put(pin_U_pre) + nd.Pin(name='a1',width=self.w_bend_port).put() + else: + nd.Pin(name='b1',width=self.w_bend_port).put(pin_D_pre) + nd.Pin(name='a1',width=self.w_bend_port).put(pin_U_pre) + + self.L = L + + ## revise 2022.08.18 + if (show_pins): + nd.put_stub() + + return C + +class spiral_rectangle: + """ + Rectangular spiral with optional cross-section transitions and alignment control. + + Parameters + ---------- + name : str or None, optional + Nazca cell name (default is None). + Dmin : float, optional + Minimum vertical separation between the first pair of bends (default is 50). + Rmax_bend : float, optional + Maximum radius used inside Clothoid bends (default is 10). + Rmin_bend : float, optional + Minimum radius reached inside Clothoid bends (default is 10). + wmin_bend : float, optional + Minimum waveguide width inside bends (default is 10). + Lmin : float, optional + Straight length of the innermost segment (default is 50). + width : float, optional + Nominal waveguide width along the spiral (default is 2). + w_port : float, optional + IO waveguide width after the final taper (default is 0.45). + gap : float, optional + Spacing between successive turns (default is 1). + cycles : float, optional + Number of rectangular loops (default is 20). + xs : str, optional + Cross-section key (default is "strip"). + layer : str, optional + Override polygon layer (default is None). + w_bend_port : float or None, optional + Bend waveguide width; inherits ``width`` when None (default is None). + Lport : float, optional + Length of straight sections appended at each port (default is 10). + Ltp : float, optional + Taper length that converts bend width to ``width`` (default is 10). + res : float, optional + Arc-length sampling resolution (default is 0.5). + cell_xs_transition : nd.Cell or object, optional + Pre-built cell that performs cross-section transitions after the ports (default is None). + port_angle : float, optional + Output bend angle in degrees (default is 180). + show_pins : bool, optional + Draw Nazca stub markers when True (default is False). + sharp_patch : bool, optional + Insert chamfer helpers when True (default is True). + in_out_align : bool, optional + If True, align input/output along the same axis when ``port_angle=180`` (default is True). + Lpatch : float, optional + Small straight length inserted before/after bends to ease Boolean ops (default is 0.05). + """ + def __init__(self, + name: str = None, + Dmin: float = 50, + Rmax_bend: float = 10, + Rmin_bend: float = 10, + wmin_bend: float = 10, + Lmin: float = 50, + width: float = 2, + w_port: float = 0.45, ## not used at this moment + gap: float = 1, + cycles: float = 20, + xs: str = 'strip', + layer: str = None, + w_bend_port=None, + Lport = 10, + Ltp = 10, + + # Tres: int = 256,## resolution for one cycle, these two parameters aborted in 2023.1.4 + # Bres: int = 64, ## resolution for bends, these two parameters aborted in 2023.1.4 + + res : float = 0.5, ## added in 2023.1.4, the length resolution + cell_xs_transition=None, + + port_angle: float = 180, + show_pins: bool = False, + sharp_patch:bool = True, + in_out_align = True, + Lpatch = 0.05, +): + + self.Dmin = Dmin + self.Lmin = Lmin + self.Rmax_bend = Rmax_bend + self.Rmin_bend = Rmin_bend + self.wmin_bend = wmin_bend + self.cycles = cycles + self.width = width + self.w_port = w_port + self.gap = gap + self.xs = xs + self.layer = layer + + self.Ltp = Ltp + self.Lpatch = Lpatch + self.name=name + if (self.name==None): + self.instantiate = False + else : + self.instantiate = True + + + self.port_angle=port_angle + self.sharp_patch= sharp_patch + + self.w_bend_port = w_bend_port + self.Lport = Lport + + if (hasattr(cell_xs_transition,'cell')): + self.cell_xs_transition = cell_xs_transition.cell + elif (isinstance(cell_xs_transition,nd.Cell)): + self.cell_xs_transition = cell_xs_transition + else : + self.cell_xs_transition = None + + self.res = res + + self.in_out_align = in_out_align + + self.cell = self.generate_gds(show_pins=show_pins) + + def __strt_with_taper__(self,width1,width2,xs,length,Ltp=15,Lstart=2): + with nd.Cell(instantiate=False) as C: + + L_mm = length-Ltp*2-Lstart*2 + + if (L_mm>0): + + instr_a1 = nd.strt(length=Lstart,width=width1,xs=xs).put(0,0,0) + nd.taper(length=Ltp,width1=width1,width2=width2,xs=xs).put() + nd.strt(length=L_mm,width=width2,xs=xs).put() + nd.taper(length=Ltp,width1=width2,width2=width1,xs=xs).put() + instr_b1 = nd.strt(length=Lstart,width=width1,xs=xs).put() + + else : + instr_a1 = nd.strt(length=length/2,width=width1,xs=xs).put(0,0,0) + instr_b1 = nd.strt(length=length/2,width=width1,xs=xs).put() + + nd.Pin(name='a0',pin=instr_a1.pin['a0']).put() + nd.Pin(name='b0',pin=instr_b1.pin['b0']).put() + + return C + + def generate_gds(self,show_pins): + + if (self.w_port==None): + self.w_port = self.width + if (self.w_bend_port==None): + self.w_bend_port = self.width + + """ Rectangluar Spiral """ + with nd.Cell(instantiate=self.instantiate,name=self.name) as C: + pitch = (self.width+self.gap)*2 ## a bi-twsited circle + + bend_rt = Clothoid(xs=self.xs,R=[self.Rmax_bend,self.Rmin_bend,self.Rmax_bend], + w=[self.w_bend_port,self.wmin_bend,self.w_bend_port], + A=[0,45,90], + dL_wg=self.res, + end_patch=False, + sharp_patch=self.sharp_patch) + + bend_rt_anti = Clothoid(xs=self.xs,R=[self.Rmax_bend,self.Rmin_bend,self.Rmax_bend], + w=[self.w_bend_port,self.wmin_bend,self.w_bend_port], + A=[0,-45,-90], + dL_wg=self.res, + end_patch=False, + sharp_patch=self.sharp_patch) + + """ Adding small patch to the bending connection """ + with nd.Cell(instantiate=False) as bend_cell: + inst = bend_rt.cell.put(0,0,0) + nd.strt(xs=self.xs,width=self.w_bend_port,length=self.Lpatch).put(inst.pin['a0'].move(-self.Lpatch/2,0,0),flip=1) + nd.strt(xs=self.xs,width=self.w_bend_port,length=self.Lpatch).put(inst.pin['b0'].move(-self.Lpatch/2,0,0)) + inst.raise_pins() + + with nd.Cell(instantiate=False) as bend_cell_anti: + inst = bend_rt_anti.cell.put(0,0,0) + nd.strt(xs=self.xs,width=self.w_bend_port,length=self.Lpatch).put(inst.pin['a0'].move(-self.Lpatch/2,0,0),flip=1) + nd.strt(xs=self.xs,width=self.w_bend_port,length=self.Lpatch).put(inst.pin['b0'].move(-self.Lpatch/2,0,0)) + inst.raise_pins() + + # bend_cell = bend_rt.cell + # bend_cell_anti = bend_rt_anti.cell + bend_sz = bend_rt.sz + L_bend = bend_rt.L0 + + self.bend_cell = bend_cell + if (self.Dmin < bend_sz[1]*2): + self.Dmin = bend_sz[1]*2 + print("WARNING: In , Dmin too small") + D_port = self.Dmin - bend_sz[1]*2 + + with nd.Cell(instantiate=True,name="wg_mid_cell"+self.name) as wg_mid_cell: + + wg = self.__strt_with_taper__(length=self.Lmin-bend_sz[0]*2-bend_sz[0],width2=self.width,width1=bend_cell.pin['a0']. + width,xs=self.xs,Ltp=self.Ltp).put(-self.Lmin/2+bend_sz[0]*2+bend_sz[0],0,0,flip=1) + bend_cell_anti.put(wg.pin['a0'],flip=0) + self.__strt_with_taper__(length=D_port,width2=self.width,width1=bend_cell.pin['a0'].width,xs=self.xs,Ltp=self.Ltp).put() + # bd = bend_cell.put(flip=0) + bd = bend_cell.put() + bd = self.__strt_with_taper__(length=bend_sz[0], + width2=bend_cell.pin['a0'].width, + width1=bend_cell.pin['a0'].width,xs=self.xs,Ltp=self.Ltp).put() + nd.Pin(name='a0',pin=bd.pin['b0']).put() + nd.Pin(name='b0',pin=wg.pin['b0']).put() + + wg_mid = wg_mid_cell.put(-self.Lmin/2,self.Dmin/2,0) + L = self.Lmin-bend_sz[0]*2 + D_port + L_bend*2 + + pin_U_pre = wg_mid.pin['b0'] + pin_D_pre = wg_mid.pin['a0'] + + bend_U = bend_cell_anti.put(pin_U_pre,flip=1) + wg_U = self.__strt_with_taper__(length=pitch/2+D_port, + width2=self.width,width1=bend_U.pin['b0'].width,xs=self.xs,Ltp=self.Ltp).put(bend_U.pin['b0'],flip=1) + L = L + pitch/2+D_port + L_bend + + bend_U2 = bend_cell_anti.put(wg_U.pin['b0'],flip=1) + wg_U = self.__strt_with_taper__(length=self.Lmin+pitch/2, + width2=self.width,width1=bend_U2.pin['b0'].width,xs=self.xs,Ltp=self.Ltp).put(bend_U2.pin['b0'],flip=1) + pin_U_pre = wg_U.pin['b0'] + L = L + self.Lmin+pitch/2 + L_bend + + bend_D = bend_cell.put(pin_D_pre) + wg_D = self.__strt_with_taper__(length=pitch/2+D_port, + width2=self.width,width1=bend_D.pin['b0'].width,xs=self.xs,Ltp=self.Ltp).put(bend_D.pin['b0']) + L = L + pitch/2+D_port + L_bend + + bend_D2 = bend_cell.put(wg_D.pin['b0']) + wg_D = self.__strt_with_taper__(length=self.Lmin+pitch/2, + width2=self.width,width1=bend_D2.pin['b0'].width,xs=self.xs,Ltp=self.Ltp).put(bend_D2.pin['b0']) + pin_D_pre = wg_D.pin['b0'] + L = L + self.Lmin+pitch/2 + L_bend + + # L = self.Lmin*3+pitch+L_bend + _cycle_ = 1 + + + for _cycle_ in range(1,self.cycles-1): + bend_U = bend_cell_anti.put(pin_U_pre,flip=1) + wg_U = self.__strt_with_taper__(length=_cycle_*pitch+pitch/2+D_port, + width2=self.width,width1=bend_U.pin['b0'].width,xs=self.xs,Ltp=self.Ltp).put(bend_U.pin['b0'],flip=1) + + L = L+_cycle_*pitch+pitch/2+D_port+L_bend + + bend_U2 = bend_cell_anti.put(wg_U.pin['b0'],flip=1) + wg_U = self.__strt_with_taper__(length=self.Lmin+_cycle_*pitch+pitch/2, + width2=self.width,width1=bend_U2.pin['b0'].width,xs=self.xs,Ltp=self.Ltp).put(bend_U2.pin['b0'],flip=1) + pin_U_pre = wg_U.pin['b0'] + L = L+self.Lmin+_cycle_*pitch+pitch/2+D_port+L_bend + + bend_D = bend_cell.put(pin_D_pre) + wg_D = self.__strt_with_taper__(length=_cycle_*pitch+pitch/2+D_port, + width2=self.width,width1=bend_D.pin['b0'].width,xs=self.xs,Ltp=self.Ltp).put(bend_D.pin['b0']) + L = L+_cycle_*pitch+pitch/2+D_port+L_bend + + bend_D2 = bend_cell.put(wg_D.pin['b0']) + wg_D = self.__strt_with_taper__(length=self.Lmin+_cycle_*pitch+pitch/2, + width2=self.width,width1=bend_D2.pin['b0'].width,xs=self.xs,Ltp=self.Ltp).put(bend_D2.pin['b0']) + pin_D_pre = wg_D.pin['b0'] + L = L+self.Lmin+_cycle_*pitch+pitch/2+L_bend + + + """ 2023.03.19 REVISED, the spiral will end at the same Y level with begining """ + + ## adding bend connection to outside + if(self.port_angle==90 or self.port_angle==270 or self.port_angle==-90): + + bend_D = bend_cell.put(pin_D_pre) + wg_D = self.__strt_with_taper__(length=(_cycle_+1)*pitch+D_port, + width2=self.width,width1=bend_D.pin['b0'].width,xs=self.xs,Ltp=self.Ltp).put(bend_D.pin['b0']) + pin_D_pre = wg_D.pin['b0'] + L = L+(_cycle_+1)*pitch+D_port+L_bend + + elif(self.port_angle==180 and self.in_out_align) : + + bend_D = bend_cell.put(pin_D_pre) + wg_D = self.__strt_with_taper__(length=(_cycle_+1)*pitch+D_port, + width2=self.width,width1=bend_D.pin['b0'].width,xs=self.xs,Ltp=self.Ltp).put(bend_D.pin['b0']) + + bend_D2 = bend_cell_anti.put(wg_D.pin['b0']) + pin_D_pre = bend_D2.pin['b0'] + L = L+(_cycle_+1)*pitch+D_port+L_bend*2 + + elif(self.port_angle==180 and self.in_out_align==False): + pass + + elif(self.port_angle==0 or self.port_angle==360) : + + bend_D = bend_cell.put(pin_D_pre) + wg_D = self.__strt_with_taper__(length=(_cycle_+1.5)*pitch+D_port, + width2=self.width,width1=bend_D.pin['b0'].width,xs=self.xs,Ltp=self.Ltp).put(bend_D.pin['b0']) + + bend_D2 = bend_cell.put(wg_D.pin['b0'],flip=0) + pin_D_pre = bend_D2.pin['b0'] + L = L+(_cycle_+1.5)*pitch+D_port+L_bend*2 + + + + + + """ 2023.03.19 REVISED, the begining will not be influenced by the rotation angle """ + """ 2023.09.18 REVISED, the xsection output can be defined with the transition area """ + if (self.w_port !=self.width) : + pin_D_pre = nd.taper(length=self.Lport,width1=self.w_port,width2=self.w_port,xs=self.xs).put(pin_D_pre) + + pin_U_pre = nd.taper(length=self.Lport,width1=self.w_port,width2=self.w_port,xs=self.xs).put(pin_U_pre) + + + """ 2023.09.18 REVISED, the transition area are only added at w_port area """ + ### Because putting transisiton in multimode area are dengerous + + if (self.cell_xs_transition != None): + + taper = self.cell_xs_transition.put(pin_D_pre) + pin_D_pre = taper.pin['b0'] + taper = self.cell_xs_transition.put(pin_U_pre) + pin_U_pre = taper.pin['b0'] + + nd.Pin(name='b1',width=self.w_port).put(pin_D_pre) + nd.Pin(name='a1',width=self.w_port).put(pin_U_pre) + + self.L = L + + ## revise 2022.08.18 + if (show_pins): + nd.put_stub() + + return C + +class Spiral_Rect_STD(spiral_rectangle): + """ + Convenience preset for rectangular spirals that share a single bend radius. + + Parameters + ---------- + name : str or None, optional + Nazca cell name (default is None). + Dmin : float, optional + Minimum inner spacing between the first pair of bends (default is 50). + R_bend : float, optional + Bend radius applied to every corner (default is 10). + Lmin : float, optional + Straight length of the innermost segment (default is 50). + width : float, optional + Waveguide width throughout the spiral (default is 2). + w_port : float, optional + IO waveguide width after the final taper (default is 0.45). + gap : float, optional + Spacing between successive turns (default is 1). + cycles : float, optional + Number of rectangular loops (default is 20). + xs : str, optional + Cross-section key (default is "strip"). + layer : str or None, optional + Override polygon layer (default is None). + Lport : float, optional + Length of straight port extensions (default is 10). + in_out_align : bool, optional + Align input/output along the same axis when ``port_angle=180`` (default is True). + res : float, optional + Arc-length sampling resolution (default is 0.5). + cell_xs_transition : nd.Cell or object, optional + Transition cell appended at the ports (default is None). + port_angle : float, optional + Output bend angle in degrees (default is 180). + show_pins : bool, optional + Draw Nazca stub markers when True (default is False). + sharp_patch : bool, optional + Insert chamfer helpers when True (default is True). + """ + def __init__(self, + name: str = None, + Dmin: float = 50, + R_bend: float = 10, + Lmin: float = 50, + width: float = 2, + w_port: float = 0.45, + gap: float = 1, + cycles: float = 20, + xs: str = 'strip', + layer: str = None, + Lport=10, + in_out_align = True, + res: float = 0.5, + cell_xs_transition=None, + port_angle: float = 180, + show_pins: bool = False, + sharp_patch: bool = True): + super().__init__(name=name, + Dmin=Dmin, + Rmax_bend=R_bend, + Rmin_bend=R_bend, + wmin_bend=width, + Lmin=Lmin, + width=width, + w_port=w_port, + gap=gap, + cycles=cycles, + xs=xs, + layer=layer, + w_bend_port=None, + Lport=Lport, + res=res, + cell_xs_transition=cell_xs_transition, + port_angle=port_angle, + show_pins=show_pins, + sharp_patch=sharp_patch, + in_out_align=in_out_align) + +class spiral_circle: + """ + Circular spiral with optional internal Euler S-bends and port transitions. + + Parameters + ---------- + name : str or None, optional + Nazca cell name (default is None). + Dmin : float, optional + Minimum inner diameter in microns (default is 50). + width : float, optional + Nominal waveguide width (default is 2). + w_port : float, optional + Output-port width after the final taper (default is 0.45). + w_bend_center : float, optional + Waveguide width within the central attachment bend (default is 1). + gap : float, optional + Spacing between adjacent turns (default is 1). + cycles : float, optional + Number of half-turns (π radians) (default is 20). + xs : str, optional + Cross-section key (default is "strip"). + layer : str or None, optional + Override layer for polygons (default is None). + Lport : float, optional + Length of straight sections appended to each port (default is 10). + res : float, optional + Arc-length sampling step (default is 0.5). + rib2strip : bool, optional + Insert rib-to-strip transitions at the ports (default is True). + port_angle : float, optional + Output bend angle in degrees (default is 180). + Euler_Sbend : bool, optional + Use optimized Euler S-bends at the center (default is False). + show_pins : bool, optional + Draw Nazca stub markers when True (default is False). + sharp_patch : bool, optional + Add chamfer polygons when True (default is True). + strict_condition : bool, optional + Enforce constant spacing by matching the conchoid tilt exactly (default is False). + R_ratio_mamnual : tuple or None, optional + Manually override the radius ratios used in Euler S-bends; expected form ``(Rc_ratio, Rm_ratio, tilt)``. + """ + def __init__(self, + name: str = None, + Dmin: float = 50, + width: float = 2, + w_port: float = 0.45, ## not used at this moment + w_bend_center: float = 1, + gap: float = 1, + cycles: float = 20, + xs: str = 'strip', + layer: str = None, + Lport = 10, + + res : float = 0.5, ## added in 2023.1.4, the length resolution + rib2strip=True, + + port_angle: float = 180, + Euler_Sbend: bool = False, + show_pins: bool = False, + sharp_patch:bool = True, + strict_condition = False, + R_ratio_mamnual = None, +): + + self.Dmin = Dmin + self.cycles = cycles + self.width = width + self.w_port = w_port + self.gap = gap + self.xs = xs + self.layer = layer + + self.name=name + if (self.name==None): + self.instantiate = False + else : + self.instantiate = True + + + self.port_angle=port_angle + self.w_bend_center= w_bend_center + self.Euler_Sbend= Euler_Sbend + self.sharp_patch= sharp_patch + + self.Lport = Lport + + self.rib2strip = rib2strip + + self.res = res + self.strict_condition = strict_condition + + self.R_ratio_mamnual = R_ratio_mamnual + + self.cell = self.generate_gds(show_pins=show_pins) + + """ Optimizing the bend radius for the minimum and central """ + def opt_euler(self,R,R0): + (R1,R2) = R + R1 = R1 + R2 = R2 + [A,LA] = _my_poly_spiral(r=[R0/R1,R0/R2],theta=[0,90],res=0.01,R_max=1000,order=1) + [B,LA] = _my_poly_spiral(r=[R0/R2,R0],theta=[90,180],res=0.01,R_max=1000,order=1) + + x_final = A[-1,0]+B[-1,0] + y_final = A[-1,1]+B[-1,1] + + D_final = np.sqrt(x_final**2 + y_final**2) + A_final = abs(np.arctan(x_final/y_final))*180/pi + + # Dmis = abs(D_final-D0) + # Amis = abs(abs(A_final)-abs(A0)) + + return [D_final,A_final] + + + def generate_gds(self,show_pins): + + if (self.w_port==None): + self.w_port = self.width + + with nd.Cell(instantiate=self.instantiate,name=self.name) as C: + if (self.layer==None): + pitch = (self.width+self.gap)*2 ## a bi-twsited circle + + Dmin = self.Dmin + R0 = Dmin/2 + kR = pitch/(np.pi*2) + + K_att = (np.power(R0,2)+2*np.power(kR,2))/np.power((np.power(R0,2)+np.power(kR,2)),1.5) + R_att = 1/K_att + + Atilt_con = abs(np.arctan(kR/R0))*180/pi ## the initial tilt norm of the conchoid + + """ Calculating the spiral with internal Sbend """ + if (self.Euler_Sbend): + + if (self.R_ratio_mamnual==None): + n_swp = 201 + R1_range = np.linspace(0.9,1.4,n_swp) + R2_range = np.linspace(2.4,2.6,n_swp) + mis = np.zeros((n_swp,n_swp)) + + for ix in range(0,len(R1_range)): + for iy in range(0,len(R2_range)): + temp = self.opt_euler((R1_range[ix],R2_range[iy]),R0=R_att) + mis[ix,iy] = abs(temp[0]-R0)+abs(temp[1]-abs(Atilt_con))*10 + + idx = mis.argmin() + + ix = idx//n_swp + iy = np.mod(idx,n_swp) + + Rc_ratio = R1_range[ix] + Rm_ratio = R2_range[iy] + final_mismatch = self.opt_euler((Rc_ratio,Rm_ratio),R_att) + + print("====================================================") + print("Optimized D/A = %.3f -- %.3f" % (final_mismatch[0],final_mismatch[1])) + print("Target D/A = %.3f -- %.3f" % (R0,Atilt_con)) + print("Optimized para = %.3f -- %.3f" % (Rc_ratio,Rm_ratio)) + + else : + Rc_ratio = self.R_ratio_mamnual[0] + Rm_ratio = self.R_ratio_mamnual[1] + print("====================================================") + print("manual para = %.3f -- %.3f" % (Rc_ratio,Rm_ratio)) + + + for layers,growx,growy,acc in nd.layeriter(xs=self.xs): + (a1,b1), (a2,b2),c1,c2 = growx + """ Generating Central Euler bend """ + if (self.Euler_Sbend == True): + spr_bend = Clothoid(xs=self.xs,R=[R_att/Rc_ratio,R_att/Rm_ratio,R_att], + w=[self.w_bend_center,self.width],A=[0,90,180],dL_wg=self.res, + width_type='dual_sine',dL_cal=0.01) + + else : + """ Genreating Circular bend for center """ + spr_bend = Clothoid(xs=self.xs,R=[R0/2,R0/2,R0/2], + w=[self.width,self.width],A=[0,90,180],dL_wg=self.res, + width_type='dual_sine',dL_cal=0.01) + + w_cur = self.width*(a1-a2)+(b1-b2) + + if (w_cur str: """Retrieves the physical MAC address of the current machine.""" mac = uuid.getnode() @@ -24,18 +28,22 @@ def _locate_license_file() -> Path: if env_path_str: path_obj = Path(env_path_str) - # If the user pointed to a directory, append the default filename if path_obj.is_dir(): return path_obj / "mxpic.lic" - # Otherwise, assume they pointed directly to the file itself return path_obj else: - # Fallback: Look in the current working directory where the script is run return Path("mxpic.lic") def verify_license() -> None: """Verifies the license file. Halts execution if invalid.""" + # 1. IMMEDIATE BYPASS: Check the Developer Whitelist first + local_mac = _get_local_mac() + if local_mac in _DEVELOPER_MACS: + # Silently approve and exit the verification function + return + + # 2. STANDARD VERIFICATION: For normal customers lic_file = _locate_license_file() if not lic_file.exists(): @@ -48,21 +56,19 @@ def verify_license() -> None: with open(lic_file, "r", encoding="utf-8") as f: lic = json.load(f) - local_mac = _get_local_mac() - - # 1. Check MAC match + # Check MAC match if lic["mac_address"] != local_mac: print(f"\n❌ mxPIC FATAL ERROR: License registered to different machine.") print(f"Registered: {lic['mac_address']} | Local: {local_mac}") sys.exit(1) - # 2. Check Expiration + # Check Expiration exp_date = datetime.strptime(lic["expiration"], "%Y-%m-%d") if datetime.now() > exp_date: print(f"\n❌ mxPIC FATAL ERROR: License expired on {lic['expiration']}.") sys.exit(1) - # 3. Verify Signature + # Verify Signature message = f"{lic['mac_address']}|{lic['expiration']}".encode('utf-8') expected_sig = hmac.new(_SECRET_KEY, message, hashlib.sha256).hexdigest() diff --git a/mxpic/docs/Makefile b/mxpic/docs/Makefile deleted file mode 100644 index d0c3cbf..0000000 --- a/mxpic/docs/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line, and also -# from the environment for the first two. -SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build -SOURCEDIR = source -BUILDDIR = build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/mxpic/docs/build/doctrees/environment.pickle b/mxpic/docs/build/doctrees/environment.pickle index ea1b745..bfc9c29 100644 Binary files a/mxpic/docs/build/doctrees/environment.pickle and b/mxpic/docs/build/doctrees/environment.pickle differ diff --git a/mxpic/docs/build/doctrees/index.doctree b/mxpic/docs/build/doctrees/index.doctree index e93d6aa..4a5a81d 100644 Binary files a/mxpic/docs/build/doctrees/index.doctree and b/mxpic/docs/build/doctrees/index.doctree differ diff --git a/mxpic/docs/build/doctrees/mxpic/components/primitives/EC_dual_layer_px3.doctree b/mxpic/docs/build/doctrees/mxpic/components/primitives/EC_dual_layer_px3.doctree new file mode 100644 index 0000000..3d0af00 Binary files /dev/null and b/mxpic/docs/build/doctrees/mxpic/components/primitives/EC_dual_layer_px3.doctree differ diff --git a/mxpic/docs/build/doctrees/mxpic/components/primitives/beam_splitters.doctree b/mxpic/docs/build/doctrees/mxpic/components/primitives/beam_splitters.doctree new file mode 100644 index 0000000..eeaaa0d Binary files /dev/null and b/mxpic/docs/build/doctrees/mxpic/components/primitives/beam_splitters.doctree differ diff --git a/mxpic/docs/build/doctrees/mxpic/components/primitives/directional_couplers/directional_couplers.doctree b/mxpic/docs/build/doctrees/mxpic/components/primitives/directional_couplers.doctree similarity index 58% rename from mxpic/docs/build/doctrees/mxpic/components/primitives/directional_couplers/directional_couplers.doctree rename to mxpic/docs/build/doctrees/mxpic/components/primitives/directional_couplers.doctree index fb8940a..267d1c6 100644 Binary files a/mxpic/docs/build/doctrees/mxpic/components/primitives/directional_couplers/directional_couplers.doctree and b/mxpic/docs/build/doctrees/mxpic/components/primitives/directional_couplers.doctree differ diff --git a/mxpic/docs/build/doctrees/mxpic/components/primitives/edge_couplers/EC_dual_layer_px3.doctree b/mxpic/docs/build/doctrees/mxpic/components/primitives/edge_couplers/EC_dual_layer_px3.doctree deleted file mode 100644 index 2be34a0..0000000 Binary files a/mxpic/docs/build/doctrees/mxpic/components/primitives/edge_couplers/EC_dual_layer_px3.doctree and /dev/null differ diff --git a/mxpic/docs/build/doctrees/mxpic/components/primitives/grating_couplers/grating_couplers.doctree b/mxpic/docs/build/doctrees/mxpic/components/primitives/grating_couplers.doctree similarity index 56% rename from mxpic/docs/build/doctrees/mxpic/components/primitives/grating_couplers/grating_couplers.doctree rename to mxpic/docs/build/doctrees/mxpic/components/primitives/grating_couplers.doctree index be9cffd..30258a5 100644 Binary files a/mxpic/docs/build/doctrees/mxpic/components/primitives/grating_couplers/grating_couplers.doctree and b/mxpic/docs/build/doctrees/mxpic/components/primitives/grating_couplers.doctree differ diff --git a/mxpic/docs/build/doctrees/mxpic/components/primitives/multimode_interferometers.doctree b/mxpic/docs/build/doctrees/mxpic/components/primitives/multimode_interferometers.doctree new file mode 100644 index 0000000..979222b Binary files /dev/null and b/mxpic/docs/build/doctrees/mxpic/components/primitives/multimode_interferometers.doctree differ diff --git a/mxpic/docs/build/doctrees/mxpic/components/primitives/spiral.doctree b/mxpic/docs/build/doctrees/mxpic/components/primitives/spiral.doctree new file mode 100644 index 0000000..987fa86 Binary files /dev/null and b/mxpic/docs/build/doctrees/mxpic/components/primitives/spiral.doctree differ diff --git a/mxpic/docs/build/html/_sources/index.md.txt b/mxpic/docs/build/html/_sources/index.md.txt index 61ac5c7..cb4406f 100644 --- a/mxpic/docs/build/html/_sources/index.md.txt +++ b/mxpic/docs/build/html/_sources/index.md.txt @@ -8,6 +8,9 @@ contain the root `toctree` directive. :maxdepth: 2 :caption: Components: -mxpic/components/primitives/directional_couplers/directional_couplers -mxpic/components/primitives/edge_couplers/EC_dual_layer_px3 -mxpic/components/primitives/grating_couplers/grating_couplers +mxpic/components/primitives/beam_splitters +mxpic/components/primitives/directional_couplers +mxpic/components/primitives/EC_dual_layer_px3 +mxpic/components/primitives/grating_couplers +mxpic/components/primitives/multimode_interferometers +mxpic/components/primitives/spiral diff --git a/mxpic/docs/build/html/_sources/mxpic/components/primitives/EC_dual_layer_px3.md.txt b/mxpic/docs/build/html/_sources/mxpic/components/primitives/EC_dual_layer_px3.md.txt new file mode 100644 index 0000000..81da869 --- /dev/null +++ b/mxpic/docs/build/html/_sources/mxpic/components/primitives/EC_dual_layer_px3.md.txt @@ -0,0 +1,7 @@ +# mxpic\components\primitives\EC_dual_layer_px3 + ```{eval-rst} + .. automodule:: mxpic.components.primitives.EC_dual_layer_px3 + :members: + :undoc-members: + :show-inheritance: +``` diff --git a/mxpic/docs/build/html/_sources/mxpic/components/primitives/beam_splitters.md.txt b/mxpic/docs/build/html/_sources/mxpic/components/primitives/beam_splitters.md.txt new file mode 100644 index 0000000..6b76fb5 --- /dev/null +++ b/mxpic/docs/build/html/_sources/mxpic/components/primitives/beam_splitters.md.txt @@ -0,0 +1,7 @@ +# mxpic\components\primitives\beam_splitters + ```{eval-rst} + .. automodule:: mxpic.components.primitives.beam_splitters + :members: + :undoc-members: + :show-inheritance: +``` diff --git a/mxpic/docs/build/html/_sources/mxpic/components/primitives/directional_couplers/directional_couplers.md.txt b/mxpic/docs/build/html/_sources/mxpic/components/primitives/directional_couplers.md.txt similarity index 59% rename from mxpic/docs/build/html/_sources/mxpic/components/primitives/directional_couplers/directional_couplers.md.txt rename to mxpic/docs/build/html/_sources/mxpic/components/primitives/directional_couplers.md.txt index b4da1d5..7776433 100644 --- a/mxpic/docs/build/html/_sources/mxpic/components/primitives/directional_couplers/directional_couplers.md.txt +++ b/mxpic/docs/build/html/_sources/mxpic/components/primitives/directional_couplers.md.txt @@ -1,6 +1,6 @@ -# mxpic\components\primitives\directional_couplers\directional_couplers +# mxpic\components\primitives\directional_couplers ```{eval-rst} - .. automodule:: mxpic.components.primitives.directional_couplers.directional_couplers + .. automodule:: mxpic.components.primitives.directional_couplers :members: :undoc-members: :show-inheritance: diff --git a/mxpic/docs/build/html/_sources/mxpic/components/primitives/edge_couplers/EC_dual_layer_px3.md.txt b/mxpic/docs/build/html/_sources/mxpic/components/primitives/edge_couplers/EC_dual_layer_px3.md.txt deleted file mode 100644 index 89b7496..0000000 --- a/mxpic/docs/build/html/_sources/mxpic/components/primitives/edge_couplers/EC_dual_layer_px3.md.txt +++ /dev/null @@ -1,7 +0,0 @@ -# mxpic\components\primitives\edge_couplers\EC_dual_layer_px3 - ```{eval-rst} - .. automodule:: mxpic.components.primitives.edge_couplers.EC_dual_layer_px3 - :members: - :undoc-members: - :show-inheritance: -``` diff --git a/mxpic/docs/build/html/_sources/mxpic/components/primitives/grating_couplers.md.txt b/mxpic/docs/build/html/_sources/mxpic/components/primitives/grating_couplers.md.txt new file mode 100644 index 0000000..de8b00c --- /dev/null +++ b/mxpic/docs/build/html/_sources/mxpic/components/primitives/grating_couplers.md.txt @@ -0,0 +1,7 @@ +# mxpic\components\primitives\grating_couplers + ```{eval-rst} + .. automodule:: mxpic.components.primitives.grating_couplers + :members: + :undoc-members: + :show-inheritance: +``` diff --git a/mxpic/docs/build/html/_sources/mxpic/components/primitives/grating_couplers/grating_couplers.md.txt b/mxpic/docs/build/html/_sources/mxpic/components/primitives/grating_couplers/grating_couplers.md.txt deleted file mode 100644 index 1342cc7..0000000 --- a/mxpic/docs/build/html/_sources/mxpic/components/primitives/grating_couplers/grating_couplers.md.txt +++ /dev/null @@ -1,7 +0,0 @@ -# mxpic\components\primitives\grating_couplers\grating_couplers - ```{eval-rst} - .. automodule:: mxpic.components.primitives.grating_couplers.grating_couplers - :members: - :undoc-members: - :show-inheritance: -``` diff --git a/mxpic/docs/build/html/_sources/mxpic/components/primitives/multimode_interferometers.md.txt b/mxpic/docs/build/html/_sources/mxpic/components/primitives/multimode_interferometers.md.txt new file mode 100644 index 0000000..cdcb821 --- /dev/null +++ b/mxpic/docs/build/html/_sources/mxpic/components/primitives/multimode_interferometers.md.txt @@ -0,0 +1,7 @@ +# mxpic\components\primitives\multimode_interferometers + ```{eval-rst} + .. automodule:: mxpic.components.primitives.multimode_interferometers + :members: + :undoc-members: + :show-inheritance: +``` diff --git a/mxpic/docs/build/html/_sources/mxpic/components/primitives/spiral.md.txt b/mxpic/docs/build/html/_sources/mxpic/components/primitives/spiral.md.txt new file mode 100644 index 0000000..b55a361 --- /dev/null +++ b/mxpic/docs/build/html/_sources/mxpic/components/primitives/spiral.md.txt @@ -0,0 +1,7 @@ +# mxpic\components\primitives\spiral + ```{eval-rst} + .. automodule:: mxpic.components.primitives.spiral + :members: + :undoc-members: + :show-inheritance: +``` diff --git a/mxpic/docs/build/html/genindex.html b/mxpic/docs/build/html/genindex.html index f8ccaaa..27d559e 100644 --- a/mxpic/docs/build/html/genindex.html +++ b/mxpic/docs/build/html/genindex.html @@ -122,25 +122,56 @@ @@ -213,22 +244,43 @@