import html
from collections.abc import Sequence
from typing import Union
from .extension import Z_MAX, Component, ExtrusionSpec, LayerSpec, PortSpec, Technology
from .tidy3d_model import _tidy3d_to_str
class _Tree:
"""Tree viewer for components.
Create a tree view of the component dependency tree for console and
notebook visualization.
Args:
component: Root component of the tree.
by_reference: If ``True`` shows all references (with index) within
a component. Otherwise, only shows unique dependencies.
interactive: If ``True``, the notebook visualization will use
interactive folds and includes SVG previews.
"""
def __init__(
self,
component: Component,
by_reference: bool = False,
interactive: bool = False,
):
self.component = component
self.by_reference = by_reference
self.interactive = interactive
@staticmethod
def _inner_tree(component: Component, prefix: str, index: str, by_reference: bool) -> list[str]:
result = [f"{prefix}{index}{component.name}"]
if by_reference:
dependencies = [reference.component for reference in component.references]
else:
dependencies = []
for reference in component.references:
ref_component = reference.component
if ref_component not in dependencies:
dependencies.append(ref_component)
ref_prefix = (
"".join("│" if p == "│" or p == "├" else " " for p in prefix) + " " * len(index) + "├─"
)
n = len(dependencies)
num_digits = len(str(n - 1))
index = " "
for i, dependency in enumerate(dependencies):
if by_reference:
index = "[" + str(i).rjust(num_digits) + "] "
if i == n - 1:
ref_prefix = ref_prefix[:-2] + "└─"
result.extend(_Tree._inner_tree(dependency, ref_prefix, index, by_reference))
return result
def __repr__(self) -> str:
return "\n".join(_Tree._inner_tree(self.component, "", "", self.by_reference))
@staticmethod
def _inner_html_tree(
component: Component, prefix: str, index: str, by_reference: bool
) -> list[str]:
result = [f'<span style="color:gray">{prefix}{index}</span>{component.name}<br>']
if by_reference:
dependencies = [reference.component for reference in component.references]
else:
dependencies = []
for reference in component.references:
ref_component = reference.component
if ref_component not in dependencies:
dependencies.append(ref_component)
ref_prefix = (
"".join("│" if p == "│" or p == "├" else " " for p in prefix) + " " * len(index) + "├─"
)
n = len(dependencies)
num_digits = len(str(n - 1))
index = " "
for i, dependency in enumerate(dependencies):
if by_reference:
index = "[" + str(i).rjust(num_digits, " ") + "] "
if i == n - 1:
ref_prefix = ref_prefix[:-2] + "└─"
result.extend(_Tree._inner_html_tree(dependency, ref_prefix, index, by_reference))
return result
@staticmethod
def _inner_interactive_html_tree(
component: Component, index: int, by_reference: bool
) -> list[str]:
if by_reference:
dependencies = [reference.component for reference in component.references]
else:
dependencies = []
for reference in component.references:
ref_component = reference.component
if ref_component not in dependencies:
dependencies.append(ref_component)
margin = "1em" if index >= 0 else "0"
details = "details open" if index < 0 else "details"
title = f'<span style="color:black">{component.name}</span>'
if by_reference and index >= 0:
title = f'<spam style="font-family:monospace;color:gray">[{index}] </span>{title}'
result = [
f'<{details} style="border:1px solid #bdbdbd;border-radius:3px;margin-left:{margin}">'
f'<summary style="padding:0.8ex;background-color:#f5f5f5;cursor:pointer">'
f'{title}</summary><iframe style="border:0;width:100%;min-height:300px" '
f'srcdoc="{html.escape(component._repr_svg_())}"></iframe>',
]
for i, dependency in enumerate(dependencies):
result.extend(_Tree._inner_interactive_html_tree(dependency, i, by_reference))
result.append("</details>")
return result
def _repr_html_(self) -> str:
if self.interactive:
contents = ["<div>"]
contents.extend(
_Tree._inner_interactive_html_tree(self.component, -1, self.by_reference)
)
else:
contents = ['<div style="font-family:monospace">']
contents.extend(_Tree._inner_html_tree(self.component, "", "", self.by_reference))
contents.append("</div>")
return "".join(contents)
_max_len = 32
def _console_table(titles: list[str], rows: list[list[str]], alignments: str) -> str:
lengths = [len(title) for title in titles]
for row in rows:
lengths = [max(w, len(data)) for w, data in zip(lengths, row)]
lengths = [min(w, _max_len) for w in lengths]
for row in rows:
for i, (w, alignment) in enumerate(zip(lengths, alignments)):
if alignment == "l":
row[i] = row[i].ljust(w)
elif alignment == "r":
row[i] = row[i].rjust(w)
else:
row[i] = row[i].center(w)
if len(row[i]) > _max_len:
row[i] = row[i][: _max_len - 1] + "…"
lines = [
" ".join(x.center(w) for x, w in zip(titles, lengths)),
"-" * (sum(lengths) + (len(lengths) - 1) * 2),
*(" ".join(row) for row in rows),
]
return "\n".join(lines)
def _html_table(titles: list[str], rows: list[list[str]], alignments: str) -> str:
a = {"l": "left", "r": "right"}
alignments = [a.get(x, "center") for x in alignments]
contents = ["<table><thead><tr>"]
contents.extend(f'<th style="text-align:center">{html.escape(t)}</th>' for t in titles)
contents.append("</tr></thead><tbody>")
for row in rows:
contents.append("<tr>")
for i, (title, alignment) in enumerate(zip(titles, alignments)):
data = html.escape(row[i])
if len(data) > _max_len:
for c in " =:+-*^@}{][)(;,_":
j = data[_max_len // 3 : _max_len].rfind(c)
if j >= 0:
j += _max_len // 3
if c not in " ([{":
j += 1
break
else:
j = _max_len // 2
summary = data[:j]
data = data[j:]
data = f"<details><summary>{summary}…</summary>…{data}</details>"
s = ""
if title == "Color":
s = f";background-color:{row[i][:7]}"
contents.append(f'<td style="text-align:{alignment}{s}">{data}</td>')
contents.append("</tr>")
contents.append("</tbody></table>")
return "".join(contents)
def _modes_str(port_spec) -> str:
s = str(port_spec.num_modes)
if port_spec.added_solver_modes > 0:
s += f" + {port_spec.added_solver_modes}"
pol = port_spec.polarization
if len(pol) > 0:
s += f" ({pol})"
return s
def _path_profile_str(path_profiles, technology) -> str:
names = {}
if technology is not None:
names = {v.layer: k for k, v in technology.layers.items()}
p = []
if isinstance(path_profiles, dict):
data = [(f"{k!r}@", *v) for k, v in path_profiles.items()]
else:
data = [("", *v) for v in path_profiles]
for name, width, offset, layer in data:
s = f"{name}{names.get(layer, layer)!r}: {width:g}"
if offset != 0:
s += f" ({offset:+g})"
p.append(s)
return ", ".join(p)
def _z_str(z) -> str:
return "inf" if z > Z_MAX else ("-inf" if z < -Z_MAX else f"{z:g}")
[docs]
class LayerTable(dict):
"""Layer specification table viewer.
Create a table of layer specifications for console and notebook
visualization.
Args:
obj: Technology instance or dictionary of layer specifications.
sort_by_name: Flag to select sorting by name or by layer number.
"""
def __init__(self, obj: Union[Technology, dict[str, LayerSpec]], sort_by_name: bool = False):
if isinstance(obj, Technology):
obj = obj._layers
elif not isinstance(obj, dict) or not all(
isinstance(k, str) and isinstance(v, LayerSpec) for k, v in obj.items()
):
raise TypeError(
"Expected a Technology instance or a dictionary of layer specifications."
)
super().__init__(obj)
self._sort_by_name = sort_by_name
def _table_data(self) -> tuple[list[str], list[list[str]], str]:
titles = ["Name", "Layer", "Description", "Color", "Pattern"]
rows = [
[k, str(v.layer), v.description, "#" + "".join(f"{c:02x}" for c in v.color), v.pattern]
for k, v in sorted(
self.items(), key=(lambda x: x[0]) if self._sort_by_name else (lambda x: x[1].layer)
)
]
alignments = "lclcc"
return titles, rows, alignments
def _repr_html_(self) -> str:
return _html_table(*self._table_data())
def __repr__(self) -> str:
return _console_table(*self._table_data())
[docs]
class PortSpecTable(dict):
"""Port specification table viewer.
Create a table of port specifications for console and notebook
visualization.
Args:
obj: Technology instance or dictionary of port specifications.
"""
def __init__(self, obj: Union[Technology, dict[str, PortSpec]]):
technology = None
if isinstance(obj, Technology):
technology = obj
obj = obj._ports
elif not isinstance(obj, dict) or not all(
isinstance(k, str) and isinstance(v, PortSpec) for k, v in obj.items()
):
raise TypeError(
"Expected a Technology instance or a dictionary of port specifications."
)
super().__init__(obj)
self._technology = technology
def _table_data(self) -> tuple[list[str], list[list[str]], str]:
titles = [
"Name",
"Classification",
"Description",
"Width (μm)",
"Limits (μm)",
"Radius (μm)",
"Modes",
"Target n_eff",
"Path profiles (μm)",
"Voltage path",
"Current path",
]
rows = [
[
k,
v.classification,
v.description,
f"{v.width:g}",
f"{_z_str(v.limits[0])}, {_z_str(v.limits[1])}",
f"{v.default_radius:g}",
_modes_str(v),
f"{v.target_neff:g}",
_path_profile_str(v.path_profiles, self._technology),
" ".join(f"({x[0]:g}, {x[1]:g})" for x in v.voltage_path)
if v.classification == "electrical"
else "",
" ".join(f"({x[0]:g}, {x[1]:g})" for x in v.current_path)
if v.classification == "electrical"
else "",
]
for k, v in sorted(self.items(), key=lambda x: (x[1].classification, x[0]))
]
alignments = "lclccccclll"
return titles, rows, alignments
def _repr_html_(self) -> str:
return _html_table(*self._table_data())
def __repr__(self) -> str:
return _console_table(*self._table_data())
[docs]
class ExtrusionTable(list):
"""Extrusion specification table viewer.
Create a table of extrusion specifications for console and notebook
visualization.
Args:
obj: Technology instance or sequence of extrusion specifications.
"""
def __init__(self, obj: Union[Technology, Sequence[ExtrusionSpec]]):
layer_names = {}
technology = None
if isinstance(obj, Technology):
layer_names = None
technology = obj
obj = obj._extrusion_specs
elif not isinstance(obj, (list, tuple)) or not all(
isinstance(x, ExtrusionSpec) for x in obj
):
raise TypeError(
"Expected a Technology instance or a sequence of extrusion specifications."
)
super().__init__(obj)
self._format_args = (layer_names, technology)
def _table_data(self) -> tuple[list[str], list[list[str]], str]:
titles = [
"#",
"Mask",
"Limits (μm)",
"Sidewal (°)",
"Opt. Medium",
"Elec. Medium",
]
rows = [
[
str(i),
x.mask_spec.format(*self._format_args),
f"{_z_str(x.limits[0])}, {_z_str(x.limits[1])}",
f"{x.sidewall_angle:g}",
_tidy3d_to_str(x.get_medium("optical")),
_tidy3d_to_str(x.get_medium("electrical")),
]
for i, x in enumerate(self)
]
alignments = "rlccll"
return titles, rows, alignments
def _repr_html_(self) -> str:
return _html_table(*self._table_data())
def __repr__(self) -> str:
return _console_table(*self._table_data())