File size: 9,032 Bytes
aa49145
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e5dfd1f
aa49145
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7f9a25d
aa49145
 
 
7f9a25d
aa49145
 
 
7f9a25d
 
 
 
aa49145
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7daf3b2
aa49145
 
 
c6a4866
aa49145
 
 
 
 
 
 
 
 
 
ff1f7d0
 
aa49145
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f916858
aa49145
f916858
 
 
aa49145
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7f9a25d
aa49145
 
 
 
 
 
 
 
 
7f9a25d
 
 
aa49145
 
 
7f9a25d
 
aa49145
 
 
 
7f9a25d
 
 
 
 
 
 
 
 
 
aa49145
7f9a25d
 
aa49145
7f9a25d
aa49145
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
"""
Quantum Applications - Unified Single-Server App

This app provides both EM Scattering and QLBM experiences in a single Trame server,
avoiding the multi-server/iframe approach that causes issues on HuggingFace Spaces.
"""
import os
import errno
os.environ["OMP_NUM_THREADS"] = "1"

from trame.app import get_server
from trame_vuetify.ui.vuetify3 import SinglePageLayout
from trame_vuetify.widgets import vuetify3
from trame.widgets import html as trame_html
import threading
import time
import base64

# Create a single server for the entire app
server = get_server()
state, ctrl = server.state, server.controller

# App state
state.current_page = None  # None = landing, "EM" or "QLBM"

# --- Logo Loading ---
def _load_logo_data_uri():
    base_dir = os.path.dirname(__file__)
    candidates = [
        os.path.join(base_dir, "ansys-part-of-synopsys-logo.svg")
    ]
    for p in candidates:
        if os.path.exists(p):
            ext = os.path.splitext(p)[1].lower()
            mime = "image/svg+xml" if ext == ".svg" else ("image/png" if ext == ".png" else "image/jpeg")
            with open(p, "rb") as f:
                b64 = base64.b64encode(f.read()).decode("ascii")
            return f"data:{mime};base64,{b64}"
    return None

state.logo_src = _load_logo_data_uri()

# --- Import Embedded Modules ---
# These are lightweight modules that build UI without creating their own servers
import qlbm_embedded
import em  # Use the modular em package instead of em_embedded

# Set the shared server on both modules
qlbm_embedded.set_server(server)
em.set_server(server)

# Initialize state for both modules
qlbm_embedded.init_state()
em.init_state()

# Register EM handlers (must be done after server binding)
em.register_handlers()

# --- Build the Layout ---
with SinglePageLayout(server) as layout:
    layout.title.set_text("Quantum Applications")
    layout.toolbar.classes = "pl-2 pr-1 py-1 elevation-0"
    layout.toolbar.style = "background-color: #ffffff; border-bottom: 3px solid #5f259f;"
    
    # Custom CSS
    trame_html.Style("""
    :root { --v-theme-primary: 95, 37, 159; }
    .landing-card:hover { transform: translateY(-4px); transition: transform 0.2s ease; }
    """)
    
    with layout.toolbar:
        vuetify3.VSpacer()
        
        # Back button (shown when in a sub-app)
        vuetify3.VBtn(
            v_if="current_page",
            text="Main Page",
            variant="text",
            color="primary",
            prepend_icon="mdi-arrow-left",
            click="current_page = null",
            classes="mr-2",
        )
        
        # Current page indicator
        vuetify3.VChip(
            v_if="current_page",
            label=True,
            color="primary",
            text_color="white",
            children=["{{ current_page === 'EM' ? 'Electromagnetic Scattering' : 'Quantum LBM' }}"],
            classes="mr-2",
        )
        
        # Logo
        vuetify3.VImg(
            v_if="logo_src",
            src=("logo_src", None),
            style="height: 40px; width: auto;",
            classes="ml-2",
        )
    
    with layout.content:
        # === Landing Page ===
        with vuetify3.VContainer(
            v_if="!current_page",
            fluid=True,
            classes="fill-height d-flex align-center justify-center pa-6",
        ):
            with vuetify3.VSheet(
                elevation=6,
                rounded=True,
                style="max-width: 1080px; width: 100%; background: linear-gradient(135deg, #fdfbff, #f3ecff);",
                classes="pa-8",
            ):
                vuetify3.VCardTitle(
                    "Quantum Applications Hub",
                    classes="text-h4 text-primary font-weight-bold mb-2 text-center",
                )
                vuetify3.VCardSubtitle(
                    "Choose a quantum CAE app",
                    classes="text-body-1 text-center mb-6",
                )
                
                with vuetify3.VRow(justify="center", align="stretch", class_="text-left"):
                    # EM Card
                    with vuetify3.VCol(cols=12, md=5, class_="d-flex"):
                        with vuetify3.VCard(elevation=4, classes="pa-6 flex-grow-1 landing-card"):
                            vuetify3.VIcon("mdi-radar", size=52, color="primary", classes="mb-4")
                            vuetify3.VCardTitle("Electromagnetic Scattering", classes="text-h5 mb-2")
                            vuetify3.VCardText(
                                    "Simulate electromagnetic wave scattering using quantum Hamiltonian Simulation. Time-domain Maxwell equations are re-cast to Schrödinger-type equations and an equivalent Hamiltonian is computed, and evolved over time. "
                                "Configure geometry, excitation, and visualize field propagation.",
                                classes="text-body-2 mb-6",
                            )
                            vuetify3.VBtn(
                                text="Launch EM",
                                color="primary",
                                block=True,
                                prepend_icon="mdi-play-circle",
                                size="large",
                                click="current_page = 'EM'",
                            )
                    
                    # QLBM Card
                    with vuetify3.VCol(cols=12, md=5, class_="d-flex"):
                        with vuetify3.VCard(elevation=4, classes="pa-6 flex-grow-1 landing-card"):
                            vuetify3.VIcon("mdi-water", size=52, color="secondary", classes="mb-4")
                            vuetify3.VCardTitle("Fluids", classes="text-h5 mb-2")
                            vuetify3.VCardText(
                                "3D fluid simulation using a quantum analog the classical Lattice Boltzmann method."
                                " Explore advection-diffusion with quantum-enhanced computation."
                                " Configure geometry, initial condition, and visualize field propagation.",
                                classes="text-body-2 mb-6",
                            )
                            vuetify3.VBtn(
                                text="Launch QLBM",
                                color="secondary",
                                block=True,
                                prepend_icon="mdi-play-circle",
                                size="large",
                                click="current_page = 'QLBM'",
                            )
        
        # === EM Experience ===
        with vuetify3.VContainer(
            v_if="current_page === 'EM'",
            fluid=True,
            classes="pa-0 fill-height",
        ):
            em.build_ui()
        
        # === QLBM Experience ===
        with vuetify3.VContainer(
            v_if="current_page === 'QLBM'",
            fluid=True,
            classes="pa-0 fill-height",
        ):
            qlbm_embedded.build_ui()

# Enable point picking after UI is built (prevents KeyError with Trame state)
em.enable_point_picking_on_plotter()

# --- Heartbeat for HuggingFace ---
def _start_hf_heartbeat_thread(interval_s: int = 5):
    """Keep the WebSocket alive for HuggingFace Spaces."""
    import asyncio
    
    def _loop():
        while True:
            time.sleep(interval_s)
            try:
                # Create a new event loop for this thread if needed
                try:
                    loop = asyncio.get_event_loop()
                except RuntimeError:
                    loop = asyncio.new_event_loop()
                    asyncio.set_event_loop(loop)
                
                # Try to flush, but don't crash if it fails
                if hasattr(server, 'controller') and hasattr(server.controller, 'flush'):
                    server.controller.flush()
            except Exception:
                # Silently continue - heartbeat is optional
                pass
    
    t = threading.Thread(target=_loop, daemon=True, name="HeartbeatThread")
    t.start()

# --- Entry Point ---
if __name__ == "__main__":
    # Start heartbeat
    _start_hf_heartbeat_thread(interval_s=5)
    
    # Get port from environment (HuggingFace) or use default
    base_port = int(os.environ.get("APP_PORT") or os.environ.get("PORT", 7860))
    host = os.environ.get("APP_HOST", "0.0.0.0")
    max_attempts = 10

    print(f"Starting Quantum Applications server on {host}:{base_port}")
    print("This is a SINGLE SERVER serving both EM and QLBM experiences.")

    for attempt in range(max_attempts):
        port = base_port + attempt
        try:
            server.start(host=host, port=port, open_browser=False)
            break
        except OSError as exc:
            if getattr(exc, "errno", None) == errno.EADDRINUSE and attempt < max_attempts - 1:
                print(f"Port {port} busy, retrying on port {port + 1}...")
                continue
            raise