From dfba6b53609a1f5112e8305275f2c61eb94723a8 Mon Sep 17 00:00:00 2001 From: Eric Stanton Date: Tue, 27 May 2025 16:11:46 -0600 Subject: [PATCH 1/7] added basic emode plugin --- gplugins/emode/__init__.py | 3 +++ gplugins/emode/tests/SOI.py | 36 ++++++++++++++++++++++++++++++++++++ pyproject.toml | 1 + 3 files changed, 40 insertions(+) create mode 100644 gplugins/emode/__init__.py create mode 100644 gplugins/emode/tests/SOI.py diff --git a/gplugins/emode/__init__.py b/gplugins/emode/__init__.py new file mode 100644 index 00000000..217c03b3 --- /dev/null +++ b/gplugins/emode/__init__.py @@ -0,0 +1,3 @@ +from emodeconnection import EMode, open_file, get, inspect + +__all__ = ["EMode", "open_file", "get", "inspect"] diff --git a/gplugins/emode/tests/SOI.py b/gplugins/emode/tests/SOI.py new file mode 100644 index 00000000..58de3403 --- /dev/null +++ b/gplugins/emode/tests/SOI.py @@ -0,0 +1,36 @@ + +import gplugins.emode as emc + +## Set simulation parameters +wavelength = 1550 # [nm] wavelength +dx, dy = 10, 10 # [nm] resolution +w_core = 600 # [nm] waveguide core width +w_trench = 800 # [nm] waveguide side trench width +h_core = 500 # [nm] waveguide core height +h_clad = 800 # [nm] waveguide top and bottom clad +num_modes = 2 # [-] number of modes + +## Connect and initialize EMode +em = emc.EMode() + +## Settings +em.settings( + wavelength = wavelength, x_resolution = dx, y_resolution = dy, + window_width = w_core + w_trench*2, window_height = h_core + h_clad*2, + num_modes = num_modes, background_refractive_index = 'Air') + +## Draw shapes +em.shape(name = 'BOX', refractive_index = 'SiO2', height = h_clad) +em.shape(name = 'core', refractive_index = 'Si', width = w_core, height = h_core) + +## Launch FDM solver +em.FDM() + +## Display the effective indices, TE fractions, and core confinement +em.report() + +## Plot the field and refractive index profiles +em.plot() + +## Close EMode +em.close() \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index b2ce21e5..e1f2c33a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,6 +72,7 @@ sax = ["sax~=0.15.14"] schematic = ["bokeh", "ipywidgets", "natsort"] tidy3d = ["tidy3d>=2.8.2,<2.9", "gdstk"] vlsir = ["vlsir", "vlsirtools"] +emode = ["emodeconnection"] [tool.codespell] ignore-words-list = 'te, te/tm, te, ba, fpr, fpr_spacing, ro, nd, donot, schem, Ue' From 12538c783add4071ccb722965699c9bf05ce3ec5 Mon Sep 17 00:00:00 2001 From: emode-photonix Date: Wed, 28 May 2025 11:20:10 -0600 Subject: [PATCH 2/7] added documentaion for the emode plugin --- docs/_config.yml | 2 +- docs/_toc.yml | 1 + docs/plugins_mode_solver.md | 16 ++++--- notebooks/emode.ipynb | 88 +++++++++++++++++++++++++++++++++++++ 4 files changed, 100 insertions(+), 7 deletions(-) create mode 100644 notebooks/emode.ipynb diff --git a/docs/_config.yml b/docs/_config.yml index 9cf9615e..1f0f09c0 100755 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -85,4 +85,4 @@ sphinx: html_theme_options: logo: image_light: logo_white.png - image_dark: logo_black.png + image_dark: logo_black.png \ No newline at end of file diff --git a/docs/_toc.yml b/docs/_toc.yml index f6845f22..8aed0d03 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -32,6 +32,7 @@ parts: - file: notebooks/mode_solver_fdfd - file: notebooks/mpb_001_mpb_waveguide - file: notebooks/meow_01 + - file: notebooks/emode - file: plugins_fdtd sections: - file: notebooks/tidy3d_00_tidy3d diff --git a/docs/plugins_mode_solver.md b/docs/plugins_mode_solver.md index 25d91e03..3232b026 100644 --- a/docs/plugins_mode_solver.md +++ b/docs/plugins_mode_solver.md @@ -2,14 +2,18 @@ A mode solver computes the modes supported by a waveguide cross-section at a particular wavelength. Modes are definite-frequency eigenstates of Maxwell's equations. -You can use 3 open source mode solvers: +You can use several mode solvers with GDSFactory: -1. tidy3d. Finite difference Frequency Domain (FDFD). -2. MPB. FDFD with periodic boundary conditions. -3. Femwell. Finite Element (FEM). +**Open Source:** +- ``tidy3d``: Finite difference Frequency Domain (FDFD). +- ``MPB``: FDFD with periodic boundary conditions. +- ``Femwell``: Finite Element (FEM). +- ``meow``: The tidy3d mode solver is also used by the MEOW plugin to get the Sparameters of components via Eigenmode Expansion. -The tidy3d mode solver is also used by the MEOW plugin to get the Sparameters of components via Eigenmode Expansion. -Notice that the tidy3d FDTD solver is not open source as it runs on the cloud server, but the mode solver is open source and runs locally on your computer. + **Notice**: The tidy3d FDTD solver is not open source as it runs on the cloud server, but the mode solver is open source and runs locally on your computer. + +**Proprietary:** +- ``EMode``: A versatile waveguide mode solver using the Finite-Difference Frequency Domain (FDFD) method, and a propagation tool using the Eigenmode Expansion method (EME). Requires the EMode software suite to be installed separately. See [docs.emodephotonix.com/installation](https://docs.emodephotonix.com/installation) for installation instructions. ```{tableofcontents} ``` diff --git a/notebooks/emode.ipynb b/notebooks/emode.ipynb new file mode 100644 index 00000000..e2fd3114 --- /dev/null +++ b/notebooks/emode.ipynb @@ -0,0 +1,88 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "dec55ff8", + "metadata": {}, + "source": [ + "# EMode - Photonic Device Simulation\n", + "\n", + "The EMode plugin integrates simulation capabilities from [**EMode Photonix**](https://emodephotonix.com) with GDSFactory. This plugin enables the analysis of photonic devices through:\n", + "\n", + "- Waveguide mode solving using a Finite-Difference Frequency Domain (FDFD) method.\n", + "- Propagation simulations using the Eigenmode Expansion (EME) method.\n", + "\n", + "Cross-sectional simulations are available with a free EMode2D license. The propagation module requires an EMode3D license.\n", + "\n", + "See the complete EMode documentation at [**EMode Docs**](https://docs.emodephotonix.com).\n", + "\n", + "Get an EMode license at the [**EMode Shop**](https://emodephotonix.com/get-emode).\n", + "\n", + "## Software Requirement\n", + "\n", + "This plugin requires that the EMode software is already installed on the user's computer. Please refer to the installation guide at [docs.emodephotonix.com/installation](https://docs.emodephotonix.com/installation) for detailed instructions.\n", + "\n", + "## Using EMode Examples with gplugins\n", + "\n", + "All of the [**EMode examples**](https://docs.emodephotonix.com/examples) can be adapted for use with the GDSFactory plugin. Simply replace the standard EMode import:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "f0fbf103", + "metadata": {}, + "outputs": [], + "source": [ + "import emodeconnection as emc" + ] + }, + { + "cell_type": "markdown", + "id": "6f832fad", + "metadata": {}, + "source": [ + "with the plugin-specific import:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8b62c1bf", + "metadata": {}, + "outputs": [], + "source": [ + "import gplugins.emode as emc" + ] + }, + { + "cell_type": "markdown", + "id": "1f863dfd", + "metadata": {}, + "source": [ + "The ``gplugins.emode`` import provides the same interface and usage as the ``emodeconnection`` package, detailed in the [EMode Python connection documentation](https://docs.emodephotonix.com/emodeconnection_python)." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 5df7f2ba1ca115d5464aaced31a345ef09e3df8e Mon Sep 17 00:00:00 2001 From: estanton22 Date: Wed, 28 May 2025 13:40:19 -0600 Subject: [PATCH 3/7] removed emode tests --- gplugins/emode/tests/SOI.py | 36 ------------------------------------ 1 file changed, 36 deletions(-) delete mode 100644 gplugins/emode/tests/SOI.py diff --git a/gplugins/emode/tests/SOI.py b/gplugins/emode/tests/SOI.py deleted file mode 100644 index 58de3403..00000000 --- a/gplugins/emode/tests/SOI.py +++ /dev/null @@ -1,36 +0,0 @@ - -import gplugins.emode as emc - -## Set simulation parameters -wavelength = 1550 # [nm] wavelength -dx, dy = 10, 10 # [nm] resolution -w_core = 600 # [nm] waveguide core width -w_trench = 800 # [nm] waveguide side trench width -h_core = 500 # [nm] waveguide core height -h_clad = 800 # [nm] waveguide top and bottom clad -num_modes = 2 # [-] number of modes - -## Connect and initialize EMode -em = emc.EMode() - -## Settings -em.settings( - wavelength = wavelength, x_resolution = dx, y_resolution = dy, - window_width = w_core + w_trench*2, window_height = h_core + h_clad*2, - num_modes = num_modes, background_refractive_index = 'Air') - -## Draw shapes -em.shape(name = 'BOX', refractive_index = 'SiO2', height = h_clad) -em.shape(name = 'core', refractive_index = 'Si', width = w_core, height = h_core) - -## Launch FDM solver -em.FDM() - -## Display the effective indices, TE fractions, and core confinement -em.report() - -## Plot the field and refractive index profiles -em.plot() - -## Close EMode -em.close() \ No newline at end of file From f45ffee283a76efcb0e6be0008c0ca0e24474f9c Mon Sep 17 00:00:00 2001 From: Eric Stanton Date: Tue, 3 Jun 2025 14:28:25 -0600 Subject: [PATCH 4/7] integrating LayerStack with EMode --- gplugins/emode/__init__.py | 3 +- gplugins/emode/tests/SOI.py | 71 ++++++++++++++++++++++++++++++++ gplugins/emode/utils.py | 82 +++++++++++++++++++++++++++++++++++++ 3 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 gplugins/emode/tests/SOI.py create mode 100644 gplugins/emode/utils.py diff --git a/gplugins/emode/__init__.py b/gplugins/emode/__init__.py index 217c03b3..88d3fc07 100644 --- a/gplugins/emode/__init__.py +++ b/gplugins/emode/__init__.py @@ -1,3 +1,4 @@ -from emodeconnection import EMode, open_file, get, inspect +from emodeconnection import open_file, get, inspect +from .utils import EMode __all__ = ["EMode", "open_file", "get", "inspect"] diff --git a/gplugins/emode/tests/SOI.py b/gplugins/emode/tests/SOI.py new file mode 100644 index 00000000..723d64aa --- /dev/null +++ b/gplugins/emode/tests/SOI.py @@ -0,0 +1,71 @@ + +import gplugins.emode as emc +from gdsfactory.cross_section import rib +from gdsfactory.generic_tech import LAYER_STACK +from gdsfactory.technology import LayerStack + + +## Set up GDSFactory LayerStack and CrossSection in units of microns +layer_stack = LayerStack( + layers={ + k: LAYER_STACK.layers[k] + for k in ( + "core", + "clad", + "slab90", + "box", + ) + } +) + +layer_stack.layers[ + "core" +].thickness = 0.22 + +layer_stack.layers[ + "slab90" +].thickness = 0.09 + +## Connect and initialize EMode +em = emc.EMode() + +modes = em.build_waveguide( + cross_section=rib(width=0.6), + layer_stack=layer_stack, + wavelength=1.55, + num_modes=1, + x_resolution=0.010, + y_resolution=0.010, + window_width = w_core + w_trench*2, + window_height = h_core + h_clad*2, + background_refractive_index='Air', + max_effective_index=2.631, +) + +em.plot() + +em.close() + +quit() + +## Settings +em.settings( + wavelength = wavelength, x_resolution = dx, y_resolution = dy, + window_width = w_core + w_trench*2, window_height = h_core + h_clad*2, + num_modes = num_modes, background_refractive_index = 'Air') + +## Draw shapes +em.shape(name = 'BOX', refractive_index = 'SiO2', height = h_clad) +em.shape(name = 'core', refractive_index = 'Si', width = w_core, height = h_core) + +## Launch FDM solver +em.FDM() + +## Display the effective indices, TE fractions, and core confinement +em.report() + +## Plot the field and refractive index profiles +em.plot() + +## Close EMode +em.close() diff --git a/gplugins/emode/utils.py b/gplugins/emode/utils.py new file mode 100644 index 00000000..8290121f --- /dev/null +++ b/gplugins/emode/utils.py @@ -0,0 +1,82 @@ +import emodeconnection as emc +from gdsfactory import CrossSection +from gdsfactory.technology import LayerStack +import numbers + + +class EMode(emc.EMode): + def build_waveguide( + self, + cross_section: CrossSection, + layer_stack: LayerStack, + **kwargs, + ) -> None: + """Builds a waveguide structure in EMode based on GDSFactory CrossSection and LayerStack. + + Args: + cross_section: A GDSFactory CrossSection object defining the waveguide's geometry. + layer_stack: A GDSFactory LayerStack object defining the material layers. + **kwargs: Additional keyword arguments to be passed directly to EMode's settings() function. + """ + nm = 1e-3 # convert microns to nanometers + dim_keys = [ + 'wavelength', + 'x_resolution', + 'y_resolution', + 'window_width', + 'window_height', + 'bend_radius', + 'expansion_resolution', + 'expansion_size', + 'propagation_resolution', + ] + + # Pass all kwargs directly to EMode's settings function + kwargs = {key: (value * nm if key in dim_keys else value) for key, value in kwargs.items()} + self.settings(**kwargs) + + # Process layer_stack to create EMode shapes + for layer_name, layer_info in layer_stack.layers.items(): + shape_info = { + 'name': layer_name, + 'refractive_index': layer_info.material, + 'height': layer_info.thickness * nm, + 'width': layer_info.width_to_z * nm, + 'sidewall_angle': layer_info.sidewall_angle, + } + # material = layer_info.material + # thickness = layer_info.thickness + # z_min = layer_info.zmin + # layer=((WG - DEEP_ETCH) - SHALLOW_ETCH) + # derived_layer=WG + # thickness=0.22 + # thickness_tolerance=None + # width_tolerance=None + # zmin=0.0 + # zmin_tolerance=None + # sidewall_angle=10.0 + # sidewall_angle_tolerance=None + # width_to_z=0.5 + # z_to_bias=None + # bias=None + # mesh_order=2 + # material='si' + # info={} + + # Find a matching CrossSection for this layer + xsection_ind = [k for k, s in enumerate(cross_section.sections) if str(layer_info.layer) == str(s.layer) or str(layer_info.derived_layer) == str(s.layer)] + + # Apply settings from the matching CrossSection + if len(xsection_ind) > 0: + xsection = cross_section.sections[xsection_ind[0]] + shape_info['width'] = xsection.width * nm + shape_info['position'] = xsection.offset * nm + + # cross_section.sections[0] + # Section(width=0.6, offset=0.0, insets=None, layer='WG', port_names=('o1', 'o2'), port_types=('optical', 'optical'), name='_default', hidden=False, simplify=None, width_function=None, offset_function=None) + # cross_section.sections[1] + # Section(width=6.6, offset=0.0, insets=None, layer='SLAB90', port_names=(None, None), port_types=('optical', 'optical'), name='cladding_0', hidden=False, simplify=0.05, width_function=None, offset_function=None) + + self.shape(**shape_info) + + return From c66b109d70b8be52b963d75b2115d1139a4d76b8 Mon Sep 17 00:00:00 2001 From: Eric Stanton Date: Tue, 3 Jun 2025 14:37:18 -0600 Subject: [PATCH 5/7] cleaned up utils --- gplugins/emode/utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/gplugins/emode/utils.py b/gplugins/emode/utils.py index 8290121f..eb28e94e 100644 --- a/gplugins/emode/utils.py +++ b/gplugins/emode/utils.py @@ -1,7 +1,6 @@ import emodeconnection as emc from gdsfactory import CrossSection from gdsfactory.technology import LayerStack -import numbers class EMode(emc.EMode): From 49935a861ddd1bae1e71fef27c26f8153285acba Mon Sep 17 00:00:00 2001 From: estanton22 Date: Tue, 3 Jun 2025 16:07:05 -0600 Subject: [PATCH 6/7] first functioning EMode plugin with LayerStack --- gplugins/emode/tests/SOI.py | 37 ++++++++++------------------ gplugins/emode/utils.py | 49 +++++++++++++++---------------------- 2 files changed, 33 insertions(+), 53 deletions(-) diff --git a/gplugins/emode/tests/SOI.py b/gplugins/emode/tests/SOI.py index 723d64aa..b610544c 100644 --- a/gplugins/emode/tests/SOI.py +++ b/gplugins/emode/tests/SOI.py @@ -18,17 +18,22 @@ } ) -layer_stack.layers[ - "core" -].thickness = 0.22 +layer_stack.layers["core"].thickness = 0.22 +layer_stack.layers["core"].zmin = 0 -layer_stack.layers[ - "slab90" -].thickness = 0.09 +layer_stack.layers["slab90"].thickness = 0.09 +layer_stack.layers["slab90"].zmin = 0 + +layer_stack.layers["box"].thickness = 1.5 +layer_stack.layers["box"].zmin = -1.5 + +layer_stack.layers["clad"].thickness = 1.5 +layer_stack.layers["clad"].zmin = 0 ## Connect and initialize EMode em = emc.EMode() +## build_waveguide converts units to nanometers, which EMode's default modes = em.build_waveguide( cross_section=rib(width=0.6), layer_stack=layer_stack, @@ -36,28 +41,12 @@ num_modes=1, x_resolution=0.010, y_resolution=0.010, - window_width = w_core + w_trench*2, - window_height = h_core + h_clad*2, + window_width = 3.0, + window_height = 3.0, background_refractive_index='Air', max_effective_index=2.631, ) -em.plot() - -em.close() - -quit() - -## Settings -em.settings( - wavelength = wavelength, x_resolution = dx, y_resolution = dy, - window_width = w_core + w_trench*2, window_height = h_core + h_clad*2, - num_modes = num_modes, background_refractive_index = 'Air') - -## Draw shapes -em.shape(name = 'BOX', refractive_index = 'SiO2', height = h_clad) -em.shape(name = 'core', refractive_index = 'Si', width = w_core, height = h_core) - ## Launch FDM solver em.FDM() diff --git a/gplugins/emode/utils.py b/gplugins/emode/utils.py index eb28e94e..42233ea0 100644 --- a/gplugins/emode/utils.py +++ b/gplugins/emode/utils.py @@ -2,7 +2,6 @@ from gdsfactory import CrossSection from gdsfactory.technology import LayerStack - class EMode(emc.EMode): def build_waveguide( self, @@ -17,7 +16,7 @@ def build_waveguide( layer_stack: A GDSFactory LayerStack object defining the material layers. **kwargs: Additional keyword arguments to be passed directly to EMode's settings() function. """ - nm = 1e-3 # convert microns to nanometers + nm = 1e3 # convert microns to nanometers dim_keys = [ 'wavelength', 'x_resolution', @@ -34,33 +33,30 @@ def build_waveguide( kwargs = {key: (value * nm if key in dim_keys else value) for key, value in kwargs.items()} self.settings(**kwargs) + # Get a list of EMode materials for cleaning GDSFactory material names + emode_materials = self.get('materials') + # Process layer_stack to create EMode shapes + max_order = max([info.mesh_order for name, info in layer_stack.layers.items()]) + min_zmin = min([info.zmin for name, info in layer_stack.layers.items()]) + for layer_name, layer_info in layer_stack.layers.items(): + + material = next( + (mat for mat in emode_materials if mat.lower() == layer_info.material.lower()), + layer_info.material + ) + shape_info = { 'name': layer_name, - 'refractive_index': layer_info.material, + 'refractive_index': material, 'height': layer_info.thickness * nm, - 'width': layer_info.width_to_z * nm, + 'mask': layer_info.width_to_z * nm, 'sidewall_angle': layer_info.sidewall_angle, + 'etch_depth': layer_info.thickness * nm if layer_info.width_to_z > 0 else 0, + 'position': [0.0, (layer_info.zmin - min_zmin + layer_info.thickness/2) * nm], + 'priority': max_order - layer_info.mesh_order + 1, } - # material = layer_info.material - # thickness = layer_info.thickness - # z_min = layer_info.zmin - # layer=((WG - DEEP_ETCH) - SHALLOW_ETCH) - # derived_layer=WG - # thickness=0.22 - # thickness_tolerance=None - # width_tolerance=None - # zmin=0.0 - # zmin_tolerance=None - # sidewall_angle=10.0 - # sidewall_angle_tolerance=None - # width_to_z=0.5 - # z_to_bias=None - # bias=None - # mesh_order=2 - # material='si' - # info={} # Find a matching CrossSection for this layer xsection_ind = [k for k, s in enumerate(cross_section.sections) if str(layer_info.layer) == str(s.layer) or str(layer_info.derived_layer) == str(s.layer)] @@ -68,13 +64,8 @@ def build_waveguide( # Apply settings from the matching CrossSection if len(xsection_ind) > 0: xsection = cross_section.sections[xsection_ind[0]] - shape_info['width'] = xsection.width * nm - shape_info['position'] = xsection.offset * nm - - # cross_section.sections[0] - # Section(width=0.6, offset=0.0, insets=None, layer='WG', port_names=('o1', 'o2'), port_types=('optical', 'optical'), name='_default', hidden=False, simplify=None, width_function=None, offset_function=None) - # cross_section.sections[1] - # Section(width=6.6, offset=0.0, insets=None, layer='SLAB90', port_names=(None, None), port_types=('optical', 'optical'), name='cladding_0', hidden=False, simplify=0.05, width_function=None, offset_function=None) + shape_info['mask'] = xsection.width * nm + shape_info['mask_offset'] = xsection.offset * nm self.shape(**shape_info) From 368bb56966e4559a534cd8641f0c34f43ab18e52 Mon Sep 17 00:00:00 2001 From: Eric Stanton Date: Tue, 2 Sep 2025 14:23:05 -0600 Subject: [PATCH 7/7] fixed emodeconnection install error for docs compiling --- Makefile | 2 +- README.md | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index b7ce368e..8240334e 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ venv: install: uv venv --python 3.11 - uv pip install -e .[dev,docs,femwell,gmsh,meow,sax,tidy3d,klayout,vlsir] + uv pip install -e .[dev,docs,femwell,gmsh,meow,sax,tidy3d,klayout,vlsir,emode] uv run pre-commit install dev: test-data gmsh elmer install diff --git a/README.md b/README.md index ce7bc856..bd0de4e9 100644 --- a/README.md +++ b/README.md @@ -22,10 +22,12 @@ GDSFactory plugins: - `palace` for full-wave driven (S parameter) and electrostatic (capacitive) simulations. - EME - `meow` Eigen Mode Expansion (EME). + - `EMode` - Mode Solver - Tidy3d - Femwell - MPB + - `EMode` - TCAD - `devsim` TCAD device simulator. - Circuit simulations @@ -44,7 +46,7 @@ pip install "gdsfactory[full]" --upgrade or ```bash -pip install "gplugins[devsim,femwell,gmsh,schematic,meow,meshwell,sax,tidy3d]" --upgrade +pip install "gplugins[devsim,femwell,gmsh,schematic,meow,meshwell,ray,sax,tidy3d,emode]" --upgrade ``` Or install only the plugins you need. For example: