quantum / em /utils.py
harishaseebat92
Runtime Version and QLBM Upload Option
841a0ef
"""
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"