harishaseebat92 commited on
Commit
610d3d1
·
1 Parent(s): 20f8534

Upload Window : Em

Browse files
Files changed (3) hide show
  1. em/simulation.py +265 -1
  2. em/state.py +12 -0
  3. em/ui.py +123 -1
em/simulation.py CHANGED
@@ -1436,4 +1436,268 @@ def update_value_display(point):
1436
  text = f"Index: ({ix}, {iy}) | Position: ({x_code:.3f}, {y_code:.3f})\nValue: {value:.3e}"
1437
 
1438
  plotter.add_text(text, name="value_text", position="lower_left", color="black", font_size=10)
1439
- ctrl.view_update()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1436
  text = f"Index: ({ix}, {iy}) | Position: ({x_code:.3f}, {y_code:.3f})\nValue: {value:.3e}"
1437
 
1438
  plotter.add_text(text, name="value_text", position="lower_left", color="black", font_size=10)
1439
+ ctrl.view_update()
1440
+
1441
+
1442
+ # ---------------------------------------------------------------------------
1443
+ # EM Job Result Upload Processing
1444
+ # ---------------------------------------------------------------------------
1445
+
1446
+ def process_uploaded_em_job_result():
1447
+ """
1448
+ Process an uploaded IBM/IonQ EM job result JSON file and generate a time-series plot.
1449
+
1450
+ This function:
1451
+ 1. Decodes the uploaded file (base64 -> JSON)
1452
+ 2. Parses the job result to extract field values (expectation values or counts)
1453
+ 3. Builds time frames based on user-specified T and dt
1454
+ 4. Generates a Plotly time-series figure
1455
+
1456
+ Required JSON structure (example):
1457
+ - IBM: List of expectation values or PrimitiveResult
1458
+ - IonQ: Dict with "field_values" or list of values
1459
+ - Simple format: {"field_values": [0.1, 0.2, ...], "times": [0.0, 0.1, ...]}
1460
+ """
1461
+ import base64
1462
+ import json
1463
+ import plotly.graph_objects as go
1464
+
1465
+ if not state.bound:
1466
+ return
1467
+
1468
+ # Validate file upload
1469
+ uploaded = state.em_job_upload
1470
+ if not uploaded:
1471
+ state.em_job_upload_error = "No file uploaded. Please select a JSON file."
1472
+ return
1473
+
1474
+ # Reset messages
1475
+ state.em_job_upload_error = ""
1476
+ state.em_job_upload_success = ""
1477
+ state.em_job_is_processing = True
1478
+
1479
+ try:
1480
+ from .simulation import log_to_console
1481
+ except ImportError:
1482
+ def log_to_console(msg):
1483
+ print(msg)
1484
+
1485
+ log_to_console("Processing uploaded EM job result...")
1486
+
1487
+ try:
1488
+ # Handle list or single file
1489
+ file_data = uploaded[0] if isinstance(uploaded, list) else uploaded
1490
+
1491
+ # Get filename for display
1492
+ filename = file_data.get("name", "unknown.json") if isinstance(file_data, dict) else "unknown.json"
1493
+ state.em_job_upload_filename = filename
1494
+ log_to_console(f"Processing file: {filename}")
1495
+
1496
+ # Decode content
1497
+ content = file_data.get("content", "") if isinstance(file_data, dict) else ""
1498
+
1499
+ if isinstance(content, bytes):
1500
+ json_str = content.decode("utf-8")
1501
+ else:
1502
+ if content.startswith("data:"):
1503
+ content = content.split(",", 1)[1]
1504
+ raw_bytes = base64.b64decode(content)
1505
+ json_str = raw_bytes.decode("utf-8")
1506
+
1507
+ # Parse parameters from UI
1508
+ field_type = str(state.em_job_field_type or "Ez").strip()
1509
+
1510
+ # Parse monitor point tuple string "(x, y)"
1511
+ monitor_point_str = str(state.em_job_monitor_point or "(0, 0)").strip()
1512
+ try:
1513
+ # Remove parentheses and split by comma
1514
+ cleaned = monitor_point_str.strip("() ")
1515
+ parts = [p.strip() for p in cleaned.split(",")]
1516
+ monitor_x = int(parts[0]) if len(parts) > 0 else 0
1517
+ monitor_y = int(parts[1]) if len(parts) > 1 else 0
1518
+ except (ValueError, IndexError):
1519
+ monitor_x, monitor_y = 0, 0
1520
+
1521
+ total_time = float(state.em_job_total_time or 1.0)
1522
+ snapshot_dt = float(state.em_job_snapshot_dt or 0.1)
1523
+ nx = int(state.em_job_nx or 4)
1524
+ platform = str(state.em_job_platform or "IBM")
1525
+
1526
+ log_to_console(f"Parameters: field={field_type}, pos=({monitor_x},{monitor_y}), T={total_time}, dt={snapshot_dt}, nx={nx}, platform={platform}")
1527
+
1528
+ # Parse JSON
1529
+ result = json.loads(json_str)
1530
+
1531
+ # Extract field values based on result structure
1532
+ field_values = []
1533
+ times = []
1534
+
1535
+ # Try different result formats
1536
+ if isinstance(result, dict):
1537
+ # Format 1: {"field_values": [...], "times": [...]}
1538
+ if "field_values" in result:
1539
+ field_values = result["field_values"]
1540
+ times = result.get("times", None)
1541
+ log_to_console(f"Found 'field_values' key with {len(field_values)} values")
1542
+
1543
+ # Format 2: {"results": {...}} with key -> value mapping
1544
+ elif "results" in result:
1545
+ results_dict = result["results"]
1546
+ # Sort by key (time index) and extract values
1547
+ sorted_keys = sorted(results_dict.keys(), key=lambda x: float(x) if x.replace('.','').isdigit() else 0)
1548
+ field_values = [results_dict[k] for k in sorted_keys]
1549
+ log_to_console(f"Found 'results' dict with {len(field_values)} entries")
1550
+
1551
+ # Format 3: {"expectation_values": [...]}
1552
+ elif "expectation_values" in result:
1553
+ exp_vals = result["expectation_values"]
1554
+ # Convert expectation values to field values: sqrt((1 - z_exp) / 2)
1555
+ field_values = [np.sqrt((1 - float(z)) / 2) for z in exp_vals]
1556
+ log_to_console(f"Found 'expectation_values' with {len(field_values)} values, converted to field values")
1557
+
1558
+ # Format 4: IBM RuntimeEncoder result with nested structure
1559
+ elif "pub_results" in result or "results" in result:
1560
+ pub_results = result.get("pub_results", result.get("results", []))
1561
+ for pr in pub_results:
1562
+ if isinstance(pr, dict) and "data" in pr:
1563
+ data = pr["data"]
1564
+ if "evs" in data:
1565
+ z_exp = float(data["evs"])
1566
+ field_values.append(np.sqrt((1 - z_exp) / 2))
1567
+ log_to_console(f"Parsed {len(field_values)} values from pub_results")
1568
+
1569
+ else:
1570
+ # Try to use the dict values directly if they look numeric
1571
+ if all(isinstance(v, (int, float)) for v in result.values()):
1572
+ sorted_keys = sorted(result.keys())
1573
+ field_values = [result[k] for k in sorted_keys]
1574
+ log_to_console(f"Using dict values directly: {len(field_values)} values")
1575
+
1576
+ elif isinstance(result, list):
1577
+ # Format 5: Simple list of values
1578
+ if all(isinstance(v, (int, float)) for v in result):
1579
+ field_values = result
1580
+ log_to_console(f"Simple list with {len(field_values)} values")
1581
+
1582
+ # Format 6: List of dicts (e.g., IBM PrimitiveResult-like)
1583
+ elif all(isinstance(v, dict) for v in result):
1584
+ for item in result:
1585
+ if "evs" in item:
1586
+ z_exp = float(item["evs"])
1587
+ field_values.append(np.sqrt((1 - z_exp) / 2))
1588
+ elif "value" in item:
1589
+ field_values.append(float(item["value"]))
1590
+ elif "data" in item and isinstance(item["data"], dict):
1591
+ if "evs" in item["data"]:
1592
+ z_exp = float(item["data"]["evs"])
1593
+ field_values.append(np.sqrt((1 - z_exp) / 2))
1594
+ log_to_console(f"Parsed {len(field_values)} values from list of dicts")
1595
+
1596
+ if not field_values:
1597
+ state.em_job_upload_error = "Could not extract field values from JSON. Check file format."
1598
+ state.em_job_is_processing = False
1599
+ return
1600
+
1601
+ # Generate times if not provided
1602
+ if not times:
1603
+ # Use create_time_frames from delta_impulse_generator
1604
+ try:
1605
+ times = create_time_frames(total_time, snapshot_dt)
1606
+ except:
1607
+ # Fallback: generate linearly
1608
+ num_steps = len(field_values)
1609
+ times = [i * snapshot_dt for i in range(num_steps)]
1610
+
1611
+ # Ensure times matches field_values length
1612
+ if len(times) != len(field_values):
1613
+ log_to_console(f"Warning: times ({len(times)}) != field_values ({len(field_values)}), regenerating times")
1614
+ num_steps = len(field_values)
1615
+ times = [i * snapshot_dt for i in range(num_steps)]
1616
+
1617
+ log_to_console(f"Building time-series plot: {len(field_values)} points")
1618
+
1619
+ # Build Plotly figure
1620
+ fig = go.Figure()
1621
+
1622
+ # Determine grid dimensions for label
1623
+ if field_type == 'Ez':
1624
+ gw, gh = nx, nx
1625
+ elif field_type == 'Hx':
1626
+ gw, gh = nx, nx - 1
1627
+ else:
1628
+ gw, gh = nx - 1, nx
1629
+
1630
+ from .utils import normalized_position_label
1631
+ label = normalized_position_label(monitor_x, monitor_y, gw, gh)
1632
+
1633
+ # Color based on field type
1634
+ if field_type == 'Ez':
1635
+ color = "#d32f2f" # Red
1636
+ elif field_type == 'Hx':
1637
+ color = "#388e3c" # Green
1638
+ else:
1639
+ color = "#1976d2" # Blue
1640
+
1641
+ fig.add_trace(
1642
+ go.Scatter(
1643
+ x=list(times),
1644
+ y=[float(v) for v in field_values],
1645
+ mode='lines+markers',
1646
+ name=f"{field_type} @ {label}",
1647
+ line=dict(color=color, width=2.5),
1648
+ marker=dict(size=7, symbol="circle", color=color),
1649
+ hovertemplate=f"{field_type} | t=%{{x:.3f}}s<br>Value=%{{y:.6g}}<extra>{label}</extra>",
1650
+ )
1651
+ )
1652
+
1653
+ max_abs = max((abs(float(v)) for v in field_values), default=1.0)
1654
+ pad = 0.12 * max_abs if max_abs > 0 else 0.1
1655
+
1656
+ fig.update_layout(
1657
+ title=f"{platform} QPU Time Series (Uploaded) - {field_type} @ {label}",
1658
+ height=660, width=900,
1659
+ margin=dict(l=50, r=30, t=50, b=50),
1660
+ hovermode="x unified",
1661
+ legend=dict(orientation='h', yanchor='bottom', y=1.02, xanchor='right', x=1, title_text=""),
1662
+ paper_bgcolor="#FFFFFF",
1663
+ plot_bgcolor="#FFFFFF",
1664
+ )
1665
+ fig.update_xaxes(title_text="Time (s)", title_font=dict(size=22), tickfont=dict(size=16), showgrid=True, gridcolor="rgba(0,0,0,.06)")
1666
+ fig.update_yaxes(title_text="Field Value", title_font=dict(size=22), tickfont=dict(size=16), showgrid=True, gridcolor="rgba(0,0,0,.06)")
1667
+ fig.update_yaxes(range=[-max_abs - pad, max_abs + pad])
1668
+
1669
+ # Cache the figure for export
1670
+ qpu_ts_cache["fig"] = fig
1671
+ qpu_ts_cache["times"] = list(times)
1672
+ qpu_ts_cache["series_map"] = {(field_type, monitor_x, monitor_y): list(field_values)}
1673
+ qpu_ts_cache["field"] = field_type
1674
+ qpu_ts_cache["unique_fields"] = [field_type]
1675
+
1676
+ # Update the Plotly figure widget
1677
+ try:
1678
+ ctrl.qpu_ts_update(fig)
1679
+ except Exception:
1680
+ pass
1681
+
1682
+ # Update state
1683
+ state.simulation_has_run = True
1684
+ state.qpu_ts_ready = True
1685
+ state.qpu_plot_style = "width: 900px; height: 660px; margin: 0 auto;"
1686
+ state.qpu_plot_field_options = ["All", field_type]
1687
+ state.qpu_plot_filter = "All"
1688
+ state.qpu_plot_position_options = ["All positions", label]
1689
+ state.qpu_plot_position_filter = "All positions"
1690
+
1691
+ state.em_job_upload_success = f"✓ Successfully processed {len(field_values)} time step(s) from {filename}"
1692
+ log_to_console(f"Upload processing complete: {len(field_values)} points plotted")
1693
+
1694
+ except json.JSONDecodeError as e:
1695
+ state.em_job_upload_error = f"Invalid JSON file: {e}"
1696
+ log_to_console(f"JSON decode error: {e}")
1697
+ except Exception as e:
1698
+ state.em_job_upload_error = f"Error processing job result: {e}"
1699
+ log_to_console(f"Processing error: {e}")
1700
+ import traceback
1701
+ log_to_console(traceback.format_exc())
1702
+ finally:
1703
+ state.em_job_is_processing = False
em/state.py CHANGED
@@ -297,6 +297,18 @@ def _init_state_defaults():
297
  "simulation_progress": 0,
298
  "show_progress": False,
299
  "console_logs": "Console initialized...\n",
 
 
 
 
 
 
 
 
 
 
 
 
300
  })
301
 
302
  # Ensure hole snap state exists
 
297
  "simulation_progress": 0,
298
  "show_progress": False,
299
  "console_logs": "Console initialized...\n",
300
+ # --- EM Job Upload State ---
301
+ "em_job_upload": None,
302
+ "em_job_upload_filename": "",
303
+ "em_job_upload_error": "",
304
+ "em_job_upload_success": "",
305
+ "em_job_is_processing": False,
306
+ "em_job_platform": "IBM",
307
+ "em_job_field_type": "Ez",
308
+ "em_job_monitor_point": "(0, 0)",
309
+ "em_job_total_time": 1.0,
310
+ "em_job_snapshot_dt": 0.1,
311
+ "em_job_nx": 4,
312
  })
313
 
314
  # Ensure hole snap state exists
em/ui.py CHANGED
@@ -11,7 +11,7 @@ from .state import state, ctrl, get_server
11
  from .globals import plotter, GRID_SIZES
12
  from .geometry import build_geometry_placeholder as _build_geometry_placeholder
13
  from .excitation import build_excitation_placeholder as _build_excitation_placeholder
14
- from .simulation import run_simulation_only, reset_to_defaults, stop_simulation_handler
15
  from .exports import (
16
  export_vtk, export_vtk_all_frames, export_mp4,
17
  export_sim_timeseries_csv, export_sim_timeseries_png, export_sim_timeseries_html,
@@ -103,6 +103,7 @@ def build_ui():
103
  _build_backends_card()
104
  _build_output_preferences_card()
105
  _build_run_buttons()
 
106
  vuetify3.VSpacer()
107
  _build_reset_button()
108
 
@@ -490,6 +491,127 @@ def _build_output_preferences_card():
490
  )
491
 
492
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
493
  def _build_run_buttons():
494
  """Build the Run and Stop buttons row."""
495
  with vuetify3.VRow(dense=True, classes="mb-2"):
 
11
  from .globals import plotter, GRID_SIZES
12
  from .geometry import build_geometry_placeholder as _build_geometry_placeholder
13
  from .excitation import build_excitation_placeholder as _build_excitation_placeholder
14
+ from .simulation import run_simulation_only, reset_to_defaults, stop_simulation_handler, process_uploaded_em_job_result
15
  from .exports import (
16
  export_vtk, export_vtk_all_frames, export_mp4,
17
  export_sim_timeseries_csv, export_sim_timeseries_png, export_sim_timeseries_html,
 
103
  _build_backends_card()
104
  _build_output_preferences_card()
105
  _build_run_buttons()
106
+ _build_job_upload_card()
107
  vuetify3.VSpacer()
108
  _build_reset_button()
109
 
 
491
  )
492
 
493
 
494
+ def _build_job_upload_card():
495
+ """Build the Job Result Upload card for loading previously saved QPU results."""
496
+ with vuetify3.VCard(classes="mb-1", style="font-size: 0.8rem;"):
497
+ with vuetify3.VCardTitle("Upload Job Results", classes="text-subtitle-1 text-primary", style="font-size: 0.9rem; padding: 6px 10px;"):
498
+ pass
499
+ with vuetify3.VCardText(classes="py-1 px-2"):
500
+ trame_html.Div("Load previously saved IBM/IonQ QPU job results (JSON format)",
501
+ classes="text-caption text-medium-emphasis mb-2")
502
+
503
+ # Platform and Field Type row
504
+ with vuetify3.VRow(dense=True, classes="mb-2"):
505
+ with vuetify3.VCol(cols=6):
506
+ vuetify3.VSelect(
507
+ label="Platform",
508
+ v_model=("em_job_platform", "IBM"),
509
+ items=("['IBM', 'IonQ']",),
510
+ density="compact",
511
+ hide_details=True,
512
+ color="primary",
513
+ )
514
+ with vuetify3.VCol(cols=6):
515
+ vuetify3.VSelect(
516
+ label="Field Type",
517
+ v_model=("em_job_field_type", "Ez"),
518
+ items=("['Ez', 'Hx', 'Hy']",),
519
+ density="compact",
520
+ hide_details=True,
521
+ color="primary",
522
+ )
523
+
524
+ # Monitor position row
525
+ with vuetify3.VRow(dense=True, classes="mb-2"):
526
+ with vuetify3.VCol(cols=12):
527
+ vuetify3.VTextField(
528
+ label="Monitor Point (x, y)",
529
+ v_model=("em_job_monitor_point", "(0, 0)"),
530
+ density="compact",
531
+ hide_details=True,
532
+ color="primary",
533
+ placeholder="(x, y)",
534
+ )
535
+
536
+ # Time and grid parameters row
537
+ with vuetify3.VRow(dense=True, classes="mb-2"):
538
+ with vuetify3.VCol(cols=4):
539
+ vuetify3.VTextField(
540
+ label="Total Time (T)",
541
+ v_model=("em_job_total_time", 1.0),
542
+ type="number",
543
+ density="compact",
544
+ hide_details=True,
545
+ color="primary",
546
+ )
547
+ with vuetify3.VCol(cols=4):
548
+ vuetify3.VTextField(
549
+ label="Snapshot Δt",
550
+ v_model=("em_job_snapshot_dt", 0.1),
551
+ type="number",
552
+ density="compact",
553
+ hide_details=True,
554
+ color="primary",
555
+ )
556
+ with vuetify3.VCol(cols=4):
557
+ vuetify3.VTextField(
558
+ label="No. of Points per Direction",
559
+ v_model=("em_job_nx", 4),
560
+ type="number",
561
+ density="compact",
562
+ hide_details=True,
563
+ color="primary",
564
+ )
565
+
566
+ # File upload and Generate button
567
+ with vuetify3.VRow(dense=True, classes="align-center mt-2"):
568
+ with vuetify3.VCol(cols=8):
569
+ vuetify3.VFileInput(
570
+ label="Upload Job Result (JSON)",
571
+ v_model=("em_job_upload", None),
572
+ accept=".json",
573
+ prepend_icon="mdi-file-upload",
574
+ density="compact",
575
+ hide_details=True,
576
+ color="primary",
577
+ show_size=True,
578
+ clearable=True,
579
+ )
580
+ with vuetify3.VCol(cols=4):
581
+ vuetify3.VBtn(
582
+ text="Generate",
583
+ color="secondary",
584
+ variant="tonal",
585
+ block=True,
586
+ disabled=("!em_job_upload || em_job_is_processing", True),
587
+ loading=("em_job_is_processing", False),
588
+ click=process_uploaded_em_job_result,
589
+ prepend_icon="mdi-chart-box-outline",
590
+ )
591
+
592
+ # Success message
593
+ vuetify3.VAlert(
594
+ v_if="em_job_upload_success",
595
+ type="success",
596
+ variant="tonal",
597
+ density="compact",
598
+ closable=True,
599
+ children=["{{ em_job_upload_success }}"],
600
+ classes="mt-2",
601
+ )
602
+
603
+ # Error message
604
+ vuetify3.VAlert(
605
+ v_if="em_job_upload_error",
606
+ type="error",
607
+ variant="tonal",
608
+ density="compact",
609
+ closable=True,
610
+ children=["{{ em_job_upload_error }}"],
611
+ classes="mt-2",
612
+ )
613
+
614
+
615
  def _build_run_buttons():
616
  """Build the Run and Stop buttons row."""
617
  with vuetify3.VRow(dense=True, classes="mb-2"):