Spaces:
Runtime error
Runtime error
| """ | |
| EM Embedded - Utility Functions | |
| Contains shared utilities for grid snapping, coordinate helpers, | |
| regex patterns, and logo loading. | |
| """ | |
| import re | |
| import os | |
| import base64 | |
| import numpy as np | |
| __all__ = [ | |
| "SAMPLE_PAIR_RE", | |
| "nearest_node_index", | |
| "snap_samples_to_grid", | |
| "nearest_gridline", | |
| "load_logo_data_uri", | |
| "normalized_position_label", | |
| "format_grid_label", | |
| ] | |
| # Regex pattern for parsing coordinate pairs like "(0.5, 0.5)" | |
| SAMPLE_PAIR_RE = re.compile(r"\(\s*([-+]?\d*\.?\d+)\s*,\s*([-+]?\d*\.?\d+)\s*\)") | |
| def nearest_node_index(x: float, y: float, nx: int, ny: int = None) -> tuple: | |
| """Map normalized [0,1] coordinates to nearest node index on an nx×ny grid.""" | |
| if ny is None: | |
| ny = nx | |
| ix = int(round(x * (nx - 1))) | |
| iy = int(round(y * (ny - 1))) | |
| ix = max(0, min(nx - 1, ix)) | |
| iy = max(0, min(ny - 1, iy)) | |
| return ix, iy | |
| def nearest_gridline(val: float, nx: int) -> float: | |
| """Snap a value to the nearest gridline.""" | |
| return round(val * (nx - 1)) / (nx - 1) if nx > 1 else val | |
| def snap_samples_to_grid(sample_str: str, nx: int) -> tuple: | |
| """ | |
| Parse and snap sample points to grid. | |
| Returns: | |
| tuple: (snapped_gridpoints_str, display_info_str) | |
| """ | |
| if nx is None or nx < 2: | |
| return "", "" | |
| matches = SAMPLE_PAIR_RE.findall(sample_str) | |
| if not matches: | |
| return "", "Enter sample position(s) as (x, y) pairs in [0,1] x [0,1]." | |
| snapped = [] | |
| info_parts = [] | |
| for x_str, y_str in matches: | |
| try: | |
| x_norm = float(x_str) | |
| y_norm = float(y_str) | |
| # Clamp to [0, 1] | |
| x_norm = max(0.0, min(1.0, x_norm)) | |
| y_norm = max(0.0, min(1.0, y_norm)) | |
| # Snap to grid | |
| ix = int(round(x_norm * (nx - 1))) | |
| iy = int(round(y_norm * (nx - 1))) | |
| ix = max(0, min(nx - 1, ix)) | |
| iy = max(0, min(nx - 1, iy)) | |
| snapped.append((ix, iy)) | |
| # Compute snapped normalized coords for display | |
| x_snapped = ix / (nx - 1) if nx > 1 else 0.0 | |
| y_snapped = iy / (nx - 1) if nx > 1 else 0.0 | |
| changed = (abs(x_norm - x_snapped) > 1e-9 or abs(y_norm - y_snapped) > 1e-9) | |
| descriptor = "adjusted" if changed else "aligned" | |
| info_parts.append(f"Input ({x_norm:.3f}, {y_norm:.3f}) {descriptor} to ({x_snapped:.3f}, {y_snapped:.3f}) → ({ix}, {iy})") | |
| except (ValueError, ZeroDivisionError): | |
| continue | |
| gridpoints_str = ", ".join(f"({ix}, {iy})" for ix, iy in snapped) | |
| info_str = "\n".join(info_parts) | |
| return gridpoints_str, info_str | |
| def normalized_position_label(px: int, py: int, gw: int, gh: int) -> str: | |
| """Create a normalized position label like 'Position (0.500, 0.500)'.""" | |
| px_i, py_i = int(px), int(py) | |
| denom_x = float(max(gw - 1, 1)) | |
| denom_y = float(max(gh - 1, 1)) | |
| x_norm = px_i / denom_x | |
| y_norm = py_i / denom_y | |
| return f"Position ({x_norm:.3f}, {y_norm:.3f})" | |
| def format_grid_label(px: int, py: int, field: str = None, nx: int = None, label_map: dict = None) -> str: | |
| """Format a grid position label, optionally using a label map.""" | |
| px_i, py_i = int(px), int(py) | |
| if label_map and field: | |
| label = label_map.get((str(field), px_i, py_i)) | |
| if label: | |
| return label | |
| if label_map: | |
| for (fld, gx, gy), label in label_map.items(): | |
| if gx == px_i and gy == py_i: | |
| return label | |
| if nx: | |
| denom = float(max(int(nx) - 1, 1)) | |
| return f"Position ({px_i / denom:.3f}, {py_i / denom:.3f})" | |
| return f"Position ({px_i}, {py_i})" | |
| def load_logo_data_uri() -> str: | |
| """Load the Synopsys logo as a data URI.""" | |
| base_dir = os.path.dirname(os.path.dirname(__file__)) # quantum_embedded folder | |
| candidates = [ | |
| os.path.join(base_dir, "ansys-part-of-synopsys-logo.svg"), | |
| # Also check in parent quantum folder | |
| os.path.join(os.path.dirname(base_dir), "quantum", "ansys-part-of-synopsys-logo.svg"), | |
| ] | |
| for p in candidates: | |
| if os.path.exists(p): | |
| ext = os.path.splitext(p)[1].lower() | |
| if ext == ".svg": | |
| mime = "image/svg+xml" | |
| elif ext == ".png": | |
| mime = "image/png" | |
| else: | |
| mime = "image/jpeg" | |
| try: | |
| with open(p, "rb") as f: | |
| b64 = base64.b64encode(f.read()).decode("ascii") | |
| return f"data:{mime};base64,{b64}" | |
| except Exception: | |
| continue | |
| return None | |
| def install_synopsys_plotly_theme(): | |
| """Install a Synopsys-aligned Plotly theme.""" | |
| import plotly.io as pio | |
| import plotly.graph_objects as go | |
| base = go.layout.Template(pio.templates["plotly_white"]) | |
| base.layout.update( | |
| font=dict( | |
| family="Inter, Segoe UI, Roboto, Helvetica, Arial, sans-serif", | |
| size=13, | |
| color="#1A1A1A", | |
| ), | |
| paper_bgcolor="#FFFFFF", | |
| plot_bgcolor="#FFFFFF", | |
| colorway=["#5F259F", "#7A3DB5", "#AE8BD8", "#1f77b4", "#ff7f0e", "#2ca02c", "#d62728"], | |
| hoverlabel=dict(bgcolor="#FFFFFF", bordercolor="#5F259F", font=dict(color="#1A1A1A")), | |
| legend=dict(orientation="h", x=1, xanchor="right", y=1.02, yanchor="bottom", title_text=""), | |
| margin=dict(l=40, r=20, t=40, b=40), | |
| ) | |
| base.layout.xaxis.update( | |
| showgrid=True, | |
| gridcolor="rgba(95,37,159,0.1)", | |
| zeroline=False, | |
| linecolor="rgba(0,0,0,.2)", | |
| ticks="outside", | |
| tickformat=".2f", | |
| ) | |
| base.layout.yaxis.update( | |
| showgrid=True, | |
| gridcolor="rgba(95,37,159,0.1)", | |
| zeroline=True, | |
| zerolinecolor="rgba(0,0,0,.25)", | |
| linecolor="rgba(0,0,0,.2)", | |
| ticks="outside", | |
| tickformat=".3g", | |
| ) | |
| pio.templates["syn_white"] = base | |
| pio.templates.default = "syn_white" | |