|
|
import streamlit as st
|
|
|
import streamlit.components.v1 as components
|
|
|
import numpy as np
|
|
|
import json
|
|
|
import math
|
|
|
import random
|
|
|
|
|
|
def get_color_by_state(state):
|
|
|
"""Get color for task state"""
|
|
|
color_map = {
|
|
|
'PENDING': '#4299e1',
|
|
|
'IN_PROGRESS': '#f6ad55',
|
|
|
'COMPLETED': '#68d391',
|
|
|
'BLOCKED': '#fc8181'
|
|
|
}
|
|
|
return color_map.get(state, '#4299e1')
|
|
|
|
|
|
def quantum_simulation_visualizer(simulation_data, height=700):
|
|
|
"""Create an advanced quantum simulation visualization with interactive animations"""
|
|
|
|
|
|
if not simulation_data:
|
|
|
return None
|
|
|
|
|
|
|
|
|
steps = simulation_data.get('simulation_steps', [])
|
|
|
tasks = simulation_data.get('tasks', [])
|
|
|
entanglement_matrix = simulation_data.get('entanglement_matrix', [])
|
|
|
|
|
|
if not steps or not tasks:
|
|
|
return None
|
|
|
|
|
|
|
|
|
task_data = {}
|
|
|
for task in tasks:
|
|
|
task_id = task.get('id')
|
|
|
task_data[task_id] = {
|
|
|
'title': task.get('title', 'Unknown Task'),
|
|
|
'initial_state': task.get('state', 'PENDING'),
|
|
|
'color': get_color_by_state(task.get('state', 'PENDING')),
|
|
|
'priority': task.get('priority', 1)
|
|
|
}
|
|
|
|
|
|
|
|
|
vis_steps = []
|
|
|
for i, step in enumerate(steps):
|
|
|
step_data = {'step': i, 'tasks': {}}
|
|
|
for task_id, task_step_data in step.items():
|
|
|
|
|
|
if task_id not in task_data:
|
|
|
continue
|
|
|
|
|
|
entropy = task_step_data.get('entropy', 0.5)
|
|
|
state = task_step_data.get('state', task_data[task_id]['initial_state'])
|
|
|
prob_dist = task_step_data.get('probability_distribution', {})
|
|
|
|
|
|
|
|
|
if not prob_dist:
|
|
|
prob_dist = {'PENDING': 0.25, 'IN_PROGRESS': 0.25, 'COMPLETED': 0.25, 'BLOCKED': 0.25}
|
|
|
|
|
|
|
|
|
quantum_state = task_step_data.get('quantum_state', {})
|
|
|
vis_data = quantum_state.get('visualization_data', [0.25, 0.25, 0.25, 0.25])
|
|
|
|
|
|
|
|
|
step_data['tasks'][task_id] = {
|
|
|
'title': task_data[task_id]['title'],
|
|
|
'state': state,
|
|
|
'entropy': entropy,
|
|
|
'color': get_color_by_state(state),
|
|
|
'probability_distribution': prob_dist,
|
|
|
'bloch_vector': vis_data[:3] if len(vis_data) >= 3 else [0, 0, 0]
|
|
|
}
|
|
|
|
|
|
vis_steps.append(step_data)
|
|
|
|
|
|
|
|
|
steps_json = json.dumps(vis_steps)
|
|
|
tasks_json = json.dumps(task_data)
|
|
|
entanglement_json = json.dumps(entanglement_matrix)
|
|
|
|
|
|
|
|
|
d3_src = "https://d3js.org/d3.v7.min.js"
|
|
|
three_src = "https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"
|
|
|
|
|
|
|
|
|
html_content = f"""
|
|
|
<!DOCTYPE html>
|
|
|
<html>
|
|
|
<head>
|
|
|
<meta charset="utf-8">
|
|
|
<script src="{d3_src}"></script>
|
|
|
<script src="{three_src}"></script>
|
|
|
<style>
|
|
|
body {{
|
|
|
margin: 0;
|
|
|
padding: 0;
|
|
|
font-family: sans-serif;
|
|
|
background: transparent;
|
|
|
color: #f8fafc;
|
|
|
}}
|
|
|
#quantum-simulation {{
|
|
|
width: 100%;
|
|
|
height: {height}px;
|
|
|
position: relative;
|
|
|
background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
|
|
|
border-radius: 8px;
|
|
|
overflow: hidden;
|
|
|
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
|
|
}}
|
|
|
.control-panel {{
|
|
|
position: absolute;
|
|
|
top: 20px;
|
|
|
left: 20px;
|
|
|
width: 180px;
|
|
|
background: rgba(15, 23, 42, 0.8);
|
|
|
padding: 15px;
|
|
|
border-radius: 8px;
|
|
|
z-index: 100;
|
|
|
backdrop-filter: blur(4px);
|
|
|
border: 1px solid rgba(100, 116, 139, 0.2);
|
|
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
|
}}
|
|
|
.panel-title {{
|
|
|
margin: 0 0 15px 0;
|
|
|
color: #e2e8f0;
|
|
|
font-size: 16px;
|
|
|
font-weight: 600;
|
|
|
text-align: center;
|
|
|
}}
|
|
|
.progress-tracker {{
|
|
|
width: 100%;
|
|
|
height: 5px;
|
|
|
background: rgba(100, 116, 139, 0.2);
|
|
|
margin: 10px 0;
|
|
|
border-radius: 3px;
|
|
|
overflow: hidden;
|
|
|
}}
|
|
|
.progress-fill {{
|
|
|
height: 100%;
|
|
|
width: 0%;
|
|
|
background: linear-gradient(90deg, #4338CA, #3B82F6);
|
|
|
transition: width 0.3s ease;
|
|
|
}}
|
|
|
.control-button {{
|
|
|
background: rgba(59, 130, 246, 0.2);
|
|
|
border: 1px solid rgba(59, 130, 246, 0.5);
|
|
|
color: #e2e8f0;
|
|
|
width: 40px;
|
|
|
height: 40px;
|
|
|
border-radius: 50%;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
cursor: pointer;
|
|
|
font-size: 18px;
|
|
|
transition: all 0.2s ease;
|
|
|
margin: 0 5px;
|
|
|
}}
|
|
|
.control-button:hover {{
|
|
|
background: rgba(59, 130, 246, 0.4);
|
|
|
}}
|
|
|
.control-button:active {{
|
|
|
transform: scale(0.95);
|
|
|
}}
|
|
|
.controls {{
|
|
|
display: flex;
|
|
|
justify-content: center;
|
|
|
margin-top: 15px;
|
|
|
}}
|
|
|
.step-counter {{
|
|
|
text-align: center;
|
|
|
margin: 10px 0;
|
|
|
color: #e2e8f0;
|
|
|
font-size: 14px;
|
|
|
}}
|
|
|
.vizcontainer {{
|
|
|
display: flex;
|
|
|
position: absolute;
|
|
|
top: 20px;
|
|
|
left: 220px;
|
|
|
right: 20px;
|
|
|
bottom: 20px;
|
|
|
}}
|
|
|
.task-panel {{
|
|
|
width: 300px;
|
|
|
height: 100%;
|
|
|
background: rgba(15, 23, 42, 0.7);
|
|
|
border-radius: 8px;
|
|
|
padding: 15px;
|
|
|
overflow-y: auto;
|
|
|
backdrop-filter: blur(4px);
|
|
|
border: 1px solid rgba(100, 116, 139, 0.2);
|
|
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
|
margin-right: 15px;
|
|
|
}}
|
|
|
.vizspace {{
|
|
|
flex: 1;
|
|
|
position: relative;
|
|
|
background: rgba(15, 23, 42, 0.5);
|
|
|
border-radius: 8px;
|
|
|
overflow: hidden;
|
|
|
padding: 15px;
|
|
|
backdrop-filter: blur(4px);
|
|
|
border: 1px solid rgba(100, 116, 139, 0.2);
|
|
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
}}
|
|
|
.visualization-container {{
|
|
|
flex: 1;
|
|
|
position: relative;
|
|
|
overflow: hidden;
|
|
|
}}
|
|
|
#bloch-sphere {{
|
|
|
width: 100%;
|
|
|
height: 50%;
|
|
|
position: relative;
|
|
|
}}
|
|
|
#network-viz {{
|
|
|
width: 100%;
|
|
|
height: 50%;
|
|
|
position: relative;
|
|
|
}}
|
|
|
.task-card {{
|
|
|
background: rgba(30, 41, 59, 0.6);
|
|
|
border-radius: 6px;
|
|
|
margin-bottom: 10px;
|
|
|
padding: 12px;
|
|
|
border-left: 4px solid #3B82F6;
|
|
|
transition: all 0.3s ease;
|
|
|
position: relative;
|
|
|
overflow: hidden;
|
|
|
}}
|
|
|
.task-card.active {{
|
|
|
box-shadow: 0 0 15px rgba(59, 130, 246, 0.5);
|
|
|
transform: translateX(5px);
|
|
|
}}
|
|
|
.task-card-title {{
|
|
|
font-weight: 600;
|
|
|
margin: 0 0 5px 0;
|
|
|
font-size: 14px;
|
|
|
color: #e2e8f0;
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
}}
|
|
|
.task-state {{
|
|
|
padding: 3px 6px;
|
|
|
border-radius: 4px;
|
|
|
font-size: 11px;
|
|
|
font-weight: 500;
|
|
|
}}
|
|
|
.task-entropy {{
|
|
|
margin: 10px 0 5px 0;
|
|
|
font-size: 12px;
|
|
|
color: #cbd5e1;
|
|
|
}}
|
|
|
.entropy-bar {{
|
|
|
height: 4px;
|
|
|
background: rgba(100, 116, 139, 0.2);
|
|
|
margin-top: 3px;
|
|
|
border-radius: 2px;
|
|
|
overflow: hidden;
|
|
|
}}
|
|
|
.entropy-fill {{
|
|
|
height: 100%;
|
|
|
background: linear-gradient(90deg, #4338CA, #3B82F6);
|
|
|
width: 0%;
|
|
|
transition: width 0.5s ease;
|
|
|
}}
|
|
|
.probabilities {{
|
|
|
display: flex;
|
|
|
margin-top: 8px;
|
|
|
height: 20px;
|
|
|
border-radius: 4px;
|
|
|
overflow: hidden;
|
|
|
}}
|
|
|
.prob-segment {{
|
|
|
height: 100%;
|
|
|
transition: width 0.5s ease;
|
|
|
position: relative;
|
|
|
}}
|
|
|
.prob-label {{
|
|
|
position: absolute;
|
|
|
top: 50%;
|
|
|
left: 50%;
|
|
|
transform: translate(-50%, -50%);
|
|
|
font-size: 10px;
|
|
|
white-space: nowrap;
|
|
|
color: rgba(255, 255, 255, 0.9);
|
|
|
font-weight: 600;
|
|
|
text-shadow: 0 0 3px rgba(0, 0, 0, 0.6);
|
|
|
opacity: 0;
|
|
|
transition: opacity 0.3s ease;
|
|
|
}}
|
|
|
.prob-segment:hover .prob-label {{
|
|
|
opacity: 1;
|
|
|
}}
|
|
|
.quantum-pulse {{
|
|
|
position: absolute;
|
|
|
top: 0;
|
|
|
right: 0;
|
|
|
width: 8px;
|
|
|
height: 8px;
|
|
|
border-radius: 50%;
|
|
|
background: #3B82F6;
|
|
|
opacity: 0.8;
|
|
|
}}
|
|
|
@keyframes pulse {{
|
|
|
0% {{
|
|
|
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.8);
|
|
|
}}
|
|
|
70% {{
|
|
|
box-shadow: 0 0 0 5px rgba(59, 130, 246, 0);
|
|
|
}}
|
|
|
100% {{
|
|
|
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0);
|
|
|
}}
|
|
|
}}
|
|
|
.quantum-particles {{
|
|
|
position: absolute;
|
|
|
top: 0;
|
|
|
left: 0;
|
|
|
width: 100%;
|
|
|
height: 100%;
|
|
|
pointer-events: none;
|
|
|
z-index: 0;
|
|
|
}}
|
|
|
.particle {{
|
|
|
position: absolute;
|
|
|
width: 2px;
|
|
|
height: 2px;
|
|
|
border-radius: 50%;
|
|
|
background: rgba(59, 130, 246, 0.7);
|
|
|
opacity: 0.5;
|
|
|
animation: float 15s infinite linear;
|
|
|
}}
|
|
|
@keyframes float {{
|
|
|
0% {{ transform: translateY(0) translateX(0); opacity: 0; }}
|
|
|
10% {{ opacity: 0.5; }}
|
|
|
90% {{ opacity: 0.5; }}
|
|
|
100% {{ transform: translateY(-100px) translateX(50px); opacity: 0; }}
|
|
|
}}
|
|
|
/* Tabs styling */
|
|
|
.viz-tabs {{
|
|
|
display: flex;
|
|
|
margin-bottom: 10px;
|
|
|
}}
|
|
|
.viz-tab {{
|
|
|
padding: 8px 15px;
|
|
|
cursor: pointer;
|
|
|
border-radius: 6px 6px 0 0;
|
|
|
background: rgba(30, 41, 59, 0.4);
|
|
|
margin-right: 3px;
|
|
|
font-size: 13px;
|
|
|
transition: all 0.2s ease;
|
|
|
}}
|
|
|
.viz-tab.active {{
|
|
|
background: rgba(59, 130, 246, 0.2);
|
|
|
border-bottom: 2px solid #3B82F6;
|
|
|
}}
|
|
|
.viz-tab:hover:not(.active) {{
|
|
|
background: rgba(30, 41, 59, 0.7);
|
|
|
}}
|
|
|
.viz-panel {{
|
|
|
display: none;
|
|
|
height: calc(100% - 40px);
|
|
|
}}
|
|
|
.viz-panel.active {{
|
|
|
display: block;
|
|
|
}}
|
|
|
.matrix-viz {{
|
|
|
width: 100%;
|
|
|
padding: 15px;
|
|
|
box-sizing: border-box;
|
|
|
}}
|
|
|
/* Tooltip */
|
|
|
.tooltip {{
|
|
|
position: absolute;
|
|
|
background: rgba(15, 23, 42, 0.9);
|
|
|
border: 1px solid #3B82F6;
|
|
|
border-radius: 6px;
|
|
|
padding: 10px;
|
|
|
font-size: 12px;
|
|
|
pointer-events: none;
|
|
|
z-index: 1000;
|
|
|
opacity: 0;
|
|
|
transition: opacity 0.2s;
|
|
|
max-width: 220px;
|
|
|
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.4);
|
|
|
}}
|
|
|
#label-placeholder {{
|
|
|
margin-top: 5px;
|
|
|
font-size: 13px;
|
|
|
text-align: center;
|
|
|
color: #94a3b8;
|
|
|
}}
|
|
|
/* Animations */
|
|
|
.animation-container {{
|
|
|
position: absolute;
|
|
|
top: 0;
|
|
|
left: 0;
|
|
|
width: 100%;
|
|
|
height: 100%;
|
|
|
pointer-events: none;
|
|
|
z-index: 50;
|
|
|
opacity: 0;
|
|
|
transition: opacity 0.3s;
|
|
|
}}
|
|
|
.show-animation {{
|
|
|
opacity: 1;
|
|
|
}}
|
|
|
</style>
|
|
|
</head>
|
|
|
<body>
|
|
|
<div id="quantum-simulation">
|
|
|
<div class="quantum-particles" id="particles"></div>
|
|
|
|
|
|
<!-- Control Panel -->
|
|
|
<div class="control-panel">
|
|
|
<h3 class="panel-title">Quantum Simulation</h3>
|
|
|
<div class="progress-tracker">
|
|
|
<div class="progress-fill" id="progress-bar"></div>
|
|
|
</div>
|
|
|
<div class="step-counter">
|
|
|
Step <span id="step-counter">1</span> of <span id="step-total">5</span>
|
|
|
</div>
|
|
|
<div class="controls">
|
|
|
<div class="control-button" id="prev-btn">⟨</div>
|
|
|
<div class="control-button" id="play-btn">▶</div>
|
|
|
<div class="control-button" id="next-btn">⟩</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Visualization Space -->
|
|
|
<div class="vizcontainer">
|
|
|
<!-- Task Panel -->
|
|
|
<div class="task-panel" id="task-panel">
|
|
|
<!-- Task cards will be rendered here -->
|
|
|
</div>
|
|
|
|
|
|
<!-- Visualization Space -->
|
|
|
<div class="vizspace">
|
|
|
<!-- Tabs -->
|
|
|
<div class="viz-tabs">
|
|
|
<div class="viz-tab active" data-tab="bloch">Quantum States</div>
|
|
|
<div class="viz-tab" data-tab="network">Entanglement Network</div>
|
|
|
<div class="viz-tab" data-tab="matrix">Entanglement Matrix</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Visualization Panels -->
|
|
|
<div class="visualization-container">
|
|
|
<!-- Bloch Sphere Viz Panel -->
|
|
|
<div class="viz-panel active" id="bloch-panel">
|
|
|
<div id="bloch-sphere"></div>
|
|
|
<div id="label-placeholder">Select a task to see its quantum state visualization</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Network Viz Panel -->
|
|
|
<div class="viz-panel" id="network-panel">
|
|
|
<div id="network-viz"></div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Matrix Viz Panel -->
|
|
|
<div class="viz-panel" id="matrix-panel">
|
|
|
<div class="matrix-viz" id="matrix-viz"></div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- Tooltip -->
|
|
|
<div class="tooltip" id="tooltip"></div>
|
|
|
|
|
|
<!-- Animation container for transitions -->
|
|
|
<div class="animation-container" id="animation-container"></div>
|
|
|
</div>
|
|
|
|
|
|
<script>
|
|
|
// Simulation data
|
|
|
const simulationSteps = {steps_json};
|
|
|
const tasksData = {tasks_json};
|
|
|
const entanglementMatrix = {entanglement_json};
|
|
|
|
|
|
// State
|
|
|
let currentStep = 0;
|
|
|
let isPlaying = false;
|
|
|
let playInterval;
|
|
|
let selectedTaskId = null;
|
|
|
let blochSphere;
|
|
|
let network;
|
|
|
|
|
|
// Elements
|
|
|
const prevBtn = document.getElementById('prev-btn');
|
|
|
const playBtn = document.getElementById('play-btn');
|
|
|
const nextBtn = document.getElementById('next-btn');
|
|
|
const progressBar = document.getElementById('progress-bar');
|
|
|
const stepCounter = document.getElementById('step-counter');
|
|
|
const stepTotal = document.getElementById('step-total');
|
|
|
const taskPanel = document.getElementById('task-panel');
|
|
|
const tooltip = document.getElementById('tooltip');
|
|
|
const blochPanel = document.getElementById('bloch-panel');
|
|
|
const networkPanel = document.getElementById('network-panel');
|
|
|
const matrixPanel = document.getElementById('matrix-panel');
|
|
|
const labelPlaceholder = document.getElementById('label-placeholder');
|
|
|
const animationContainer = document.getElementById('animation-container');
|
|
|
|
|
|
// Initialize particles
|
|
|
function createParticles() {{
|
|
|
const particlesContainer = document.getElementById('particles');
|
|
|
for (let i = 0; i < 50; i++) {{
|
|
|
const particle = document.createElement('div');
|
|
|
particle.className = 'particle';
|
|
|
particle.style.left = Math.random() * 100 + '%';
|
|
|
particle.style.top = Math.random() * 100 + '%';
|
|
|
particle.style.animationDelay = (Math.random() * 10) + 's';
|
|
|
particlesContainer.appendChild(particle);
|
|
|
}}
|
|
|
}}
|
|
|
|
|
|
// Initialize tabs
|
|
|
function initTabs() {{
|
|
|
const tabs = document.querySelectorAll('.viz-tab');
|
|
|
tabs.forEach(tab => {{
|
|
|
tab.addEventListener('click', () => {{
|
|
|
// Update active tab
|
|
|
tabs.forEach(t => t.classList.remove('active'));
|
|
|
tab.classList.add('active');
|
|
|
|
|
|
// Update active panel
|
|
|
const panels = document.querySelectorAll('.viz-panel');
|
|
|
panels.forEach(p => p.classList.remove('active'));
|
|
|
const panelId = tab.getAttribute('data-tab') + '-panel';
|
|
|
document.getElementById(panelId).classList.add('active');
|
|
|
}});
|
|
|
}});
|
|
|
}}
|
|
|
|
|
|
// Update UI for current step
|
|
|
function updateUI() {{
|
|
|
// Update progress
|
|
|
const progress = (currentStep / (simulationSteps.length - 1)) * 100;
|
|
|
progressBar.style.width = `${{progress}}%`;
|
|
|
stepCounter.textContent = currentStep + 1;
|
|
|
stepTotal.textContent = simulationSteps.length;
|
|
|
|
|
|
// Get current step data
|
|
|
const stepData = simulationSteps[currentStep];
|
|
|
|
|
|
// Update task cards
|
|
|
updateTaskCards(stepData);
|
|
|
|
|
|
// Update visualizations
|
|
|
if (selectedTaskId) {{
|
|
|
updateBlochSphere(selectedTaskId, stepData);
|
|
|
}}
|
|
|
updateNetworkVisualization(stepData);
|
|
|
updateMatrixVisualization();
|
|
|
}}
|
|
|
|
|
|
// Initialize task cards
|
|
|
function initTaskCards() {{
|
|
|
taskPanel.innerHTML = '';
|
|
|
const firstStep = simulationSteps[0];
|
|
|
|
|
|
for (const [taskId, task] of Object.entries(firstStep.tasks)) {{
|
|
|
// Create task card
|
|
|
const card = document.createElement('div');
|
|
|
card.className = 'task-card';
|
|
|
card.id = `task-card-${{taskId}}`;
|
|
|
card.style.borderLeftColor = task.color;
|
|
|
|
|
|
// Pulse indicator
|
|
|
const pulse = document.createElement('div');
|
|
|
pulse.className = 'quantum-pulse';
|
|
|
pulse.style.animation = 'pulse 2s infinite';
|
|
|
card.appendChild(pulse);
|
|
|
|
|
|
// Title
|
|
|
const title = document.createElement('div');
|
|
|
title.className = 'task-card-title';
|
|
|
title.innerHTML = `
|
|
|
<span>${{task.title}}</span>
|
|
|
<span class="task-state" style="background: ${{task.color}}20; color: ${{task.color}}">
|
|
|
${{task.state}}
|
|
|
</span>
|
|
|
`;
|
|
|
card.appendChild(title);
|
|
|
|
|
|
// Entropy
|
|
|
const entropy = document.createElement('div');
|
|
|
entropy.className = 'task-entropy';
|
|
|
entropy.innerHTML = `
|
|
|
Entropy: <span id="entropy-value-${{taskId}}">${{task.entropy.toFixed(2)}}</span>
|
|
|
<div class="entropy-bar">
|
|
|
<div class="entropy-fill" id="entropy-fill-${{taskId}}" style="width: ${{task.entropy * 100}}%"></div>
|
|
|
</div>
|
|
|
`;
|
|
|
card.appendChild(entropy);
|
|
|
|
|
|
// Probability distribution
|
|
|
const probs = document.createElement('div');
|
|
|
probs.className = 'probabilities';
|
|
|
probs.id = `probs-${{taskId}}`;
|
|
|
|
|
|
// Add probability segments
|
|
|
for (const [state, prob] of Object.entries(task.probability_distribution)) {{
|
|
|
const segment = document.createElement('div');
|
|
|
segment.className = 'prob-segment';
|
|
|
segment.style.width = `${{prob * 100}}%`;
|
|
|
segment.style.background = getColorByState(state);
|
|
|
|
|
|
const label = document.createElement('div');
|
|
|
label.className = 'prob-label';
|
|
|
label.textContent = `${{state}}: ${{(prob * 100).toFixed(0)}}%`;
|
|
|
segment.appendChild(label);
|
|
|
|
|
|
probs.appendChild(segment);
|
|
|
}}
|
|
|
|
|
|
card.appendChild(probs);
|
|
|
|
|
|
// Add click event
|
|
|
card.addEventListener('click', () => {{
|
|
|
selectTask(taskId);
|
|
|
}});
|
|
|
|
|
|
// Add hover event for tooltip
|
|
|
card.addEventListener('mouseover', event => {{
|
|
|
showTooltip(event, task, taskId);
|
|
|
}});
|
|
|
|
|
|
card.addEventListener('mousemove', event => {{
|
|
|
positionTooltip(event);
|
|
|
}});
|
|
|
|
|
|
card.addEventListener('mouseout', () => {{
|
|
|
hideTooltip();
|
|
|
}});
|
|
|
|
|
|
taskPanel.appendChild(card);
|
|
|
}}
|
|
|
|
|
|
// Select first task by default
|
|
|
if (Object.keys(firstStep.tasks).length > 0) {{
|
|
|
selectTask(Object.keys(firstStep.tasks)[0]);
|
|
|
}}
|
|
|
}}
|
|
|
|
|
|
// Update task cards for current step
|
|
|
function updateTaskCards(stepData) {{
|
|
|
for (const [taskId, task] of Object.entries(stepData.tasks)) {{
|
|
|
const card = document.getElementById(`task-card-${{taskId}}`);
|
|
|
if (!card) continue;
|
|
|
|
|
|
// Update state
|
|
|
const stateEl = card.querySelector('.task-state');
|
|
|
stateEl.textContent = task.state;
|
|
|
stateEl.style.background = `${{task.color}}20`;
|
|
|
stateEl.style.color = task.color;
|
|
|
|
|
|
// Update card border
|
|
|
card.style.borderLeftColor = task.color;
|
|
|
|
|
|
// Update entropy with animation
|
|
|
const entropyValue = card.querySelector(`#entropy-value-${{taskId}}`);
|
|
|
const entropyFill = card.querySelector(`#entropy-fill-${{taskId}}`);
|
|
|
|
|
|
// Animate entropy change
|
|
|
animateValue(entropyValue, parseFloat(entropyValue.textContent), task.entropy, 500);
|
|
|
entropyFill.style.width = `${{task.entropy * 100}}%`;
|
|
|
|
|
|
// Update probability distribution
|
|
|
const probs = card.querySelector(`#probs-${{taskId}}`);
|
|
|
probs.innerHTML = '';
|
|
|
|
|
|
// Add probability segments with animation
|
|
|
for (const [state, prob] of Object.entries(task.probability_distribution)) {{
|
|
|
const segment = document.createElement('div');
|
|
|
segment.className = 'prob-segment';
|
|
|
segment.style.width = `${{prob * 100}}%`;
|
|
|
segment.style.background = getColorByState(state);
|
|
|
|
|
|
const label = document.createElement('div');
|
|
|
label.className = 'prob-label';
|
|
|
label.textContent = `${{state}}: ${{(prob * 100).toFixed(0)}}%`;
|
|
|
segment.appendChild(label);
|
|
|
|
|
|
probs.appendChild(segment);
|
|
|
}}
|
|
|
}}
|
|
|
}}
|
|
|
|
|
|
// Select a task for detailed visualization
|
|
|
function selectTask(taskId) {{
|
|
|
// Update selection
|
|
|
selectedTaskId = taskId;
|
|
|
|
|
|
// Update UI to show selected task
|
|
|
document.querySelectorAll('.task-card').forEach(card => {{
|
|
|
card.classList.remove('active');
|
|
|
}});
|
|
|
|
|
|
const selectedCard = document.getElementById(`task-card-${{taskId}}`);
|
|
|
if (selectedCard) {{
|
|
|
selectedCard.classList.add('active');
|
|
|
}}
|
|
|
|
|
|
// Hide placeholder text
|
|
|
labelPlaceholder.style.display = 'none';
|
|
|
|
|
|
// Update visualizations
|
|
|
const stepData = simulationSteps[currentStep];
|
|
|
updateBlochSphere(taskId, stepData);
|
|
|
}}
|
|
|
|
|
|
// Show tooltip
|
|
|
function showTooltip(event, task, taskId) {{
|
|
|
const stateProbabilities = Object.entries(task.probability_distribution)
|
|
|
.map(([state, prob]) => `<div style="display: flex; justify-content: space-between; margin: 3px 0;">
|
|
|
<span style="color: ${{getColorByState(state)}}">${{state}}</span>
|
|
|
<span>${{(prob * 100).toFixed(1)}}%</span>
|
|
|
</div>`)
|
|
|
.join('');
|
|
|
|
|
|
tooltip.innerHTML = `
|
|
|
<div style="font-weight: 600; margin-bottom: 5px; color: #e2e8f0;">${{task.title}}</div>
|
|
|
<div style="margin-bottom: 5px;">
|
|
|
<span style="font-weight: 500;">State:</span>
|
|
|
<span style="color: ${{task.color}};">${{task.state}}</span>
|
|
|
</div>
|
|
|
<div style="margin-bottom: 5px;">
|
|
|
<span style="font-weight: 500;">Entropy:</span> ${{task.entropy.toFixed(2)}}
|
|
|
</div>
|
|
|
<div style="font-weight: 500; margin-bottom: 2px;">Probabilities:</div>
|
|
|
<div style="border-top: 1px solid rgba(100, 116, 139, 0.3); padding-top: 3px;">
|
|
|
${{stateProbabilities}}
|
|
|
</div>
|
|
|
`;
|
|
|
|
|
|
tooltip.style.opacity = 1;
|
|
|
positionTooltip(event);
|
|
|
}}
|
|
|
|
|
|
// Position tooltip
|
|
|
function positionTooltip(event) {{
|
|
|
tooltip.style.left = (event.pageX + 10) + 'px';
|
|
|
tooltip.style.top = (event.pageY - 20) + 'px';
|
|
|
}}
|
|
|
|
|
|
// Hide tooltip
|
|
|
function hideTooltip() {{
|
|
|
tooltip.style.opacity = 0;
|
|
|
}}
|
|
|
|
|
|
// Get color for task state
|
|
|
function getColorByState(state) {{
|
|
|
const colorMap = {{
|
|
|
'PENDING': '#4299e1', // Blue
|
|
|
'IN_PROGRESS': '#f6ad55', // Orange
|
|
|
'COMPLETED': '#68d391', // Green
|
|
|
'BLOCKED': '#fc8181' // Red
|
|
|
}};
|
|
|
return colorMap[state] || '#4299e1';
|
|
|
}}
|
|
|
|
|
|
// Initialize Three.js Bloch sphere
|
|
|
function initBlochSphere() {{
|
|
|
const container = document.getElementById('bloch-sphere');
|
|
|
|
|
|
// Scene
|
|
|
const scene = new THREE.Scene();
|
|
|
|
|
|
// Camera
|
|
|
const camera = new THREE.PerspectiveCamera(75, container.clientWidth / container.clientHeight, 0.1, 1000);
|
|
|
camera.position.z = 2.5;
|
|
|
|
|
|
// Renderer
|
|
|
const renderer = new THREE.WebGLRenderer({{ antialias: true, alpha: true }});
|
|
|
renderer.setSize(container.clientWidth, container.clientHeight);
|
|
|
renderer.setClearColor(0x000000, 0);
|
|
|
container.appendChild(renderer.domElement);
|
|
|
|
|
|
// Sphere (Bloch sphere)
|
|
|
const sphereGeometry = new THREE.SphereGeometry(1, 32, 32);
|
|
|
const sphereMaterial = new THREE.MeshBasicMaterial({{
|
|
|
color: 0x3B82F6,
|
|
|
wireframe: true,
|
|
|
transparent: true,
|
|
|
opacity: 0.3
|
|
|
}});
|
|
|
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
|
|
|
scene.add(sphere);
|
|
|
|
|
|
// Axes
|
|
|
const axisLength = 1.2;
|
|
|
|
|
|
// X axis (red)
|
|
|
const xAxisGeometry = new THREE.BufferGeometry().setFromPoints([
|
|
|
new THREE.Vector3(-axisLength, 0, 0),
|
|
|
new THREE.Vector3(axisLength, 0, 0)
|
|
|
]);
|
|
|
const xAxisMaterial = new THREE.LineBasicMaterial({{ color: 0xff0000 }});
|
|
|
const xAxis = new THREE.Line(xAxisGeometry, xAxisMaterial);
|
|
|
scene.add(xAxis);
|
|
|
|
|
|
// X axis label
|
|
|
const xLabelDiv = document.createElement('div');
|
|
|
xLabelDiv.textContent = 'X';
|
|
|
xLabelDiv.style.position = 'absolute';
|
|
|
xLabelDiv.style.color = '#ff0000';
|
|
|
xLabelDiv.style.padding = '2px';
|
|
|
xLabelDiv.style.fontWeight = '600';
|
|
|
container.appendChild(xLabelDiv);
|
|
|
|
|
|
// Y axis (green)
|
|
|
const yAxisGeometry = new THREE.BufferGeometry().setFromPoints([
|
|
|
new THREE.Vector3(0, -axisLength, 0),
|
|
|
new THREE.Vector3(0, axisLength, 0)
|
|
|
]);
|
|
|
const yAxisMaterial = new THREE.LineBasicMaterial({{ color: 0x00ff00 }});
|
|
|
const yAxis = new THREE.Line(yAxisGeometry, yAxisMaterial);
|
|
|
scene.add(yAxis);
|
|
|
|
|
|
// Y axis label
|
|
|
const yLabelDiv = document.createElement('div');
|
|
|
yLabelDiv.textContent = 'Y';
|
|
|
yLabelDiv.style.position = 'absolute';
|
|
|
yLabelDiv.style.color = '#00ff00';
|
|
|
yLabelDiv.style.padding = '2px';
|
|
|
yLabelDiv.style.fontWeight = '600';
|
|
|
container.appendChild(yLabelDiv);
|
|
|
|
|
|
// Z axis (blue)
|
|
|
const zAxisGeometry = new THREE.BufferGeometry().setFromPoints([
|
|
|
new THREE.Vector3(0, 0, -axisLength),
|
|
|
new THREE.Vector3(0, 0, axisLength)
|
|
|
]);
|
|
|
const zAxisMaterial = new THREE.LineBasicMaterial({{ color: 0x0000ff }});
|
|
|
const zAxis = new THREE.Line(zAxisGeometry, zAxisMaterial);
|
|
|
scene.add(zAxis);
|
|
|
|
|
|
// Z axis label
|
|
|
const zLabelDiv = document.createElement('div');
|
|
|
zLabelDiv.textContent = 'Z';
|
|
|
zLabelDiv.style.position = 'absolute';
|
|
|
zLabelDiv.style.color = '#0000ff';
|
|
|
zLabelDiv.style.padding = '2px';
|
|
|
zLabelDiv.style.fontWeight = '600';
|
|
|
container.appendChild(zLabelDiv);
|
|
|
|
|
|
// State vector
|
|
|
const vectorGeometry = new THREE.BufferGeometry().setFromPoints([
|
|
|
new THREE.Vector3(0, 0, 0),
|
|
|
new THREE.Vector3(0, 0, 1) // Will be updated dynamically
|
|
|
]);
|
|
|
const vectorMaterial = new THREE.LineBasicMaterial({{ color: 0xffffff, linewidth: 3 }});
|
|
|
const stateVector = new THREE.Line(vectorGeometry, vectorMaterial);
|
|
|
scene.add(stateVector);
|
|
|
|
|
|
// Vector endpoint
|
|
|
const endpointGeometry = new THREE.SphereGeometry(0.05, 16, 16);
|
|
|
const endpointMaterial = new THREE.MeshBasicMaterial({{ color: 0xffffff }});
|
|
|
const endpoint = new THREE.Mesh(endpointGeometry, endpointMaterial);
|
|
|
scene.add(endpoint);
|
|
|
|
|
|
// Rotation animation
|
|
|
let rotationSpeed = 0.005;
|
|
|
|
|
|
// Handle window resize
|
|
|
window.addEventListener('resize', () => {{
|
|
|
const width = container.clientWidth;
|
|
|
const height = container.clientHeight;
|
|
|
camera.aspect = width / height;
|
|
|
camera.updateProjectionMatrix();
|
|
|
renderer.setSize(width, height);
|
|
|
}});
|
|
|
|
|
|
// Animation loop
|
|
|
function animate() {{
|
|
|
requestAnimationFrame(animate);
|
|
|
|
|
|
// Rotate sphere
|
|
|
sphere.rotation.y += rotationSpeed;
|
|
|
sphere.rotation.z += rotationSpeed * 0.5;
|
|
|
|
|
|
// Update axis labels positions
|
|
|
const width = container.clientWidth;
|
|
|
const height = container.clientHeight;
|
|
|
|
|
|
// Project 3D points to 2D
|
|
|
const vector = new THREE.Vector3();
|
|
|
|
|
|
// X axis label
|
|
|
vector.set(axisLength + 0.1, 0, 0);
|
|
|
vector.project(camera);
|
|
|
xLabelDiv.style.left = ((vector.x * 0.5 + 0.5) * width) + 'px';
|
|
|
xLabelDiv.style.top = ((-vector.y * 0.5 + 0.5) * height) + 'px';
|
|
|
|
|
|
// Y axis label
|
|
|
vector.set(0, axisLength + 0.1, 0);
|
|
|
vector.project(camera);
|
|
|
yLabelDiv.style.left = ((vector.x * 0.5 + 0.5) * width) + 'px';
|
|
|
yLabelDiv.style.top = ((-vector.y * 0.5 + 0.5) * height) + 'px';
|
|
|
|
|
|
// Z axis label
|
|
|
vector.set(0, 0, axisLength + 0.1);
|
|
|
vector.project(camera);
|
|
|
zLabelDiv.style.left = ((vector.x * 0.5 + 0.5) * width) + 'px';
|
|
|
zLabelDiv.style.top = ((-vector.y * 0.5 + 0.5) * height) + 'px';
|
|
|
|
|
|
renderer.render(scene, camera);
|
|
|
}}
|
|
|
|
|
|
// Start animation
|
|
|
animate();
|
|
|
|
|
|
// Define update method for the Bloch sphere
|
|
|
const updateVector = (x, y, z, color) => {{
|
|
|
// Normalize the vector
|
|
|
const length = Math.sqrt(x*x + y*y + z*z);
|
|
|
const nx = length > 0 ? x/length : 0;
|
|
|
const ny = length > 0 ? y/length : 0;
|
|
|
const nz = length > 0 ? z/length : 0;
|
|
|
|
|
|
// Update vector line
|
|
|
const points = stateVector.geometry.attributes.position.array;
|
|
|
points[3] = nx;
|
|
|
points[4] = ny;
|
|
|
points[5] = nz;
|
|
|
stateVector.geometry.attributes.position.needsUpdate = true;
|
|
|
|
|
|
// Update endpoint position
|
|
|
endpoint.position.set(nx, ny, nz);
|
|
|
|
|
|
// Update colors
|
|
|
vectorMaterial.color.set(color);
|
|
|
endpointMaterial.color.set(color);
|
|
|
}};
|
|
|
|
|
|
return {{ updateVector }};
|
|
|
}}
|
|
|
|
|
|
// Update Bloch sphere visualization
|
|
|
function updateBlochSphere(taskId, stepData) {{
|
|
|
const task = stepData.tasks[taskId];
|
|
|
if (!task) return;
|
|
|
|
|
|
// Extract Bloch vector
|
|
|
const blochVector = task.bloch_vector;
|
|
|
|
|
|
// Calculate vector components
|
|
|
let x = 0, y = 0, z = 0;
|
|
|
|
|
|
if (blochVector && blochVector.length >= 3) {{
|
|
|
[x, y, z] = blochVector;
|
|
|
}}
|
|
|
|
|
|
// Update Bloch sphere vector
|
|
|
blochSphere.updateVector(x, y, z, task.color);
|
|
|
}}
|
|
|
|
|
|
// Initialize network visualization
|
|
|
function initNetworkVisualization() {{
|
|
|
const container = document.getElementById('network-viz');
|
|
|
|
|
|
// Create SVG
|
|
|
const width = container.clientWidth;
|
|
|
const height = container.clientHeight;
|
|
|
|
|
|
const svg = d3.select(container)
|
|
|
.append('svg')
|
|
|
.attr('width', width)
|
|
|
.attr('height', height);
|
|
|
|
|
|
// Define force simulation
|
|
|
const simulation = d3.forceSimulation()
|
|
|
.force('charge', d3.forceManyBody().strength(-300))
|
|
|
.force('center', d3.forceCenter(width / 2, height / 2))
|
|
|
.force('collision', d3.forceCollide().radius(d => d.size + 5))
|
|
|
.force('link', d3.forceLink().id(d => d.id).distance(100));
|
|
|
|
|
|
// Create groups for links and nodes
|
|
|
const linkGroup = svg.append('g').attr('class', 'links');
|
|
|
const nodeGroup = svg.append('g').attr('class', 'nodes');
|
|
|
const labelGroup = svg.append('g').attr('class', 'labels');
|
|
|
|
|
|
// First step data
|
|
|
const firstStep = simulationSteps[0];
|
|
|
|
|
|
// Create nodes and links data
|
|
|
const nodes = [];
|
|
|
const nodeIds = new Set();
|
|
|
|
|
|
for (const [taskId, task] of Object.entries(firstStep.tasks)) {{
|
|
|
nodes.push({{
|
|
|
id: taskId,
|
|
|
name: task.title,
|
|
|
color: task.color,
|
|
|
state: task.state,
|
|
|
entropy: task.entropy,
|
|
|
size: 10 + (task.entropy * 15)
|
|
|
}});
|
|
|
nodeIds.add(taskId);
|
|
|
}}
|
|
|
|
|
|
// Create links from entanglement matrix
|
|
|
const links = [];
|
|
|
|
|
|
if (entanglementMatrix && entanglementMatrix.length > 0) {{
|
|
|
for (let i = 0; i < entanglementMatrix.length; i++) {{
|
|
|
for (let j = i + 1; j < entanglementMatrix[i].length; j++) {{
|
|
|
const strength = entanglementMatrix[i][j];
|
|
|
if (strength > 0) {{
|
|
|
// Get task IDs from node indices
|
|
|
const source = Array.from(nodeIds)[i];
|
|
|
const target = Array.from(nodeIds)[j];
|
|
|
|
|
|
links.push({{
|
|
|
source,
|
|
|
target,
|
|
|
strength
|
|
|
}});
|
|
|
}}
|
|
|
}}
|
|
|
}}
|
|
|
}}
|
|
|
|
|
|
// Draw links
|
|
|
const link = linkGroup.selectAll('line')
|
|
|
.data(links)
|
|
|
.enter()
|
|
|
.append('line')
|
|
|
.attr('stroke', '#4299e1')
|
|
|
.attr('stroke-opacity', 0.6)
|
|
|
.attr('stroke-width', d => d.strength * 3);
|
|
|
|
|
|
// Draw nodes
|
|
|
const node = nodeGroup.selectAll('circle')
|
|
|
.data(nodes)
|
|
|
.enter()
|
|
|
.append('circle')
|
|
|
.attr('r', d => d.size)
|
|
|
.attr('fill', d => d.color)
|
|
|
.attr('stroke', '#ffffff')
|
|
|
.attr('stroke-width', 1.5)
|
|
|
.call(d3.drag()
|
|
|
.on('start', dragStarted)
|
|
|
.on('drag', dragging)
|
|
|
.on('end', dragEnded));
|
|
|
|
|
|
// Add labels
|
|
|
const label = labelGroup.selectAll('text')
|
|
|
.data(nodes)
|
|
|
.enter()
|
|
|
.append('text')
|
|
|
.text(d => d.name.length > 15 ? d.name.substring(0, 15) + '...' : d.name)
|
|
|
.attr('font-size', 10)
|
|
|
.attr('fill', 'white')
|
|
|
.attr('text-anchor', 'middle')
|
|
|
.attr('dy', -15);
|
|
|
|
|
|
// Handle simulation ticks
|
|
|
simulation.nodes(nodes)
|
|
|
.on('tick', () => {{
|
|
|
link
|
|
|
.attr('x1', d => d.source.x)
|
|
|
.attr('y1', d => d.source.y)
|
|
|
.attr('x2', d => d.target.x)
|
|
|
.attr('y2', d => d.target.y);
|
|
|
|
|
|
node
|
|
|
.attr('cx', d => d.x)
|
|
|
.attr('cy', d => d.y);
|
|
|
|
|
|
label
|
|
|
.attr('x', d => d.x)
|
|
|
.attr('y', d => d.y);
|
|
|
}});
|
|
|
|
|
|
simulation.force('link').links(links);
|
|
|
|
|
|
// Drag handlers
|
|
|
function dragStarted(event, d) {{
|
|
|
if (!event.active) simulation.alphaTarget(0.3).restart();
|
|
|
d.fx = d.x;
|
|
|
d.fy = d.y;
|
|
|
}}
|
|
|
|
|
|
function dragging(event, d) {{
|
|
|
d.fx = event.x;
|
|
|
d.fy = event.y;
|
|
|
}}
|
|
|
|
|
|
function dragEnded(event, d) {{
|
|
|
if (!event.active) simulation.alphaTarget(0);
|
|
|
d.fx = null;
|
|
|
d.fy = null;
|
|
|
}}
|
|
|
|
|
|
// Update method for network
|
|
|
const updateNetwork = (stepData) => {{
|
|
|
// Update nodes
|
|
|
for (const [taskId, task] of Object.entries(stepData.tasks)) {{
|
|
|
const nodeIndex = nodes.findIndex(n => n.id === taskId);
|
|
|
if (nodeIndex >= 0) {{
|
|
|
nodes[nodeIndex].color = task.color;
|
|
|
nodes[nodeIndex].state = task.state;
|
|
|
nodes[nodeIndex].entropy = task.entropy;
|
|
|
nodes[nodeIndex].size = 10 + (task.entropy * 15);
|
|
|
}}
|
|
|
}}
|
|
|
|
|
|
// Update visual elements
|
|
|
node.transition().duration(500)
|
|
|
.attr('r', d => d.size)
|
|
|
.attr('fill', d => d.color);
|
|
|
}};
|
|
|
|
|
|
return {{ updateNetwork }};
|
|
|
}}
|
|
|
|
|
|
// Update network visualization
|
|
|
function updateNetworkVisualization(stepData) {{
|
|
|
network.updateNetwork(stepData);
|
|
|
}}
|
|
|
|
|
|
// Initialize matrix visualization
|
|
|
function initMatrixVisualization() {{
|
|
|
const container = document.getElementById('matrix-viz');
|
|
|
|
|
|
// Create matrix visualization
|
|
|
const margin = {{top: 20, right: 20, bottom: 20, left: 20}};
|
|
|
const width = container.clientWidth - margin.left - margin.right;
|
|
|
const height = container.clientHeight - margin.top - margin.bottom;
|
|
|
|
|
|
const svg = d3.select(container)
|
|
|
.append('svg')
|
|
|
.attr('width', width + margin.left + margin.right)
|
|
|
.attr('height', height + margin.top + margin.bottom)
|
|
|
.append('g')
|
|
|
.attr('transform', `translate(${{margin.left}}, ${{margin.top}})`);
|
|
|
|
|
|
// Get task names
|
|
|
const taskIds = Object.keys(simulationSteps[0].tasks);
|
|
|
const taskNames = taskIds.map(id => simulationSteps[0].tasks[id].title);
|
|
|
|
|
|
// Create scales
|
|
|
const x = d3.scaleBand()
|
|
|
.range([0, width])
|
|
|
.domain(taskNames)
|
|
|
.padding(0.05);
|
|
|
|
|
|
const y = d3.scaleBand()
|
|
|
.range([0, height])
|
|
|
.domain(taskNames)
|
|
|
.padding(0.05);
|
|
|
|
|
|
// Add X axis
|
|
|
svg.append('g')
|
|
|
.attr('class', 'x-axis')
|
|
|
.attr('transform', `translate(0, ${{height}})`)
|
|
|
.call(d3.axisBottom(x).tickSize(0))
|
|
|
.selectAll('text')
|
|
|
.attr('transform', 'translate(-10,0)rotate(-45)')
|
|
|
.style('text-anchor', 'end')
|
|
|
.style('font-size', '10px')
|
|
|
.style('fill', '#cbd5e1');
|
|
|
|
|
|
// Add Y axis
|
|
|
svg.append('g')
|
|
|
.attr('class', 'y-axis')
|
|
|
.call(d3.axisLeft(y).tickSize(0))
|
|
|
.selectAll('text')
|
|
|
.style('font-size', '10px')
|
|
|
.style('fill', '#cbd5e1');
|
|
|
|
|
|
// Remove axis lines
|
|
|
svg.selectAll('.domain').style('stroke', 'none');
|
|
|
|
|
|
// Create color scale
|
|
|
const color = d3.scaleLinear()
|
|
|
.range(['#e2e8f0', '#3B82F6'])
|
|
|
.domain([0, 1]);
|
|
|
|
|
|
// Create matrix data
|
|
|
const matrixData = [];
|
|
|
|
|
|
for (let i = 0; i < taskIds.length; i++) {{
|
|
|
for (let j = 0; j < taskIds.length; j++) {{
|
|
|
const strength = i === j ? 1 :
|
|
|
(entanglementMatrix && entanglementMatrix[i] &&
|
|
|
entanglementMatrix[i][j]) || 0;
|
|
|
|
|
|
matrixData.push({{
|
|
|
x: taskNames[j],
|
|
|
y: taskNames[i],
|
|
|
strength: strength,
|
|
|
xId: taskIds[j],
|
|
|
yId: taskIds[i]
|
|
|
}});
|
|
|
}}
|
|
|
}}
|
|
|
|
|
|
// Add matrix cells
|
|
|
svg.selectAll('rect')
|
|
|
.data(matrixData)
|
|
|
.enter()
|
|
|
.append('rect')
|
|
|
.attr('x', d => x(d.x))
|
|
|
.attr('y', d => y(d.y))
|
|
|
.attr('width', x.bandwidth())
|
|
|
.attr('height', y.bandwidth())
|
|
|
.style('fill', d => color(d.strength))
|
|
|
.style('stroke', '#0f172a')
|
|
|
.style('opacity', 0.8);
|
|
|
|
|
|
// Add matrix labels
|
|
|
svg.selectAll('.matrix-value')
|
|
|
.data(matrixData.filter(d => d.strength > 0))
|
|
|
.enter()
|
|
|
.append('text')
|
|
|
.attr('class', 'matrix-value')
|
|
|
.attr('x', d => x(d.x) + x.bandwidth() / 2)
|
|
|
.attr('y', d => y(d.y) + y.bandwidth() / 2)
|
|
|
.attr('text-anchor', 'middle')
|
|
|
.attr('dominant-baseline', 'middle')
|
|
|
.text(d => d.strength.toFixed(1))
|
|
|
.style('font-size', '10px')
|
|
|
.style('fill', d => d.strength > 0.5 ? 'white' : '#334155');
|
|
|
}}
|
|
|
|
|
|
// Update matrix visualization
|
|
|
function updateMatrixVisualization() {{
|
|
|
// Matrix doesn't change during steps in this version
|
|
|
// Could be enhanced in future versions with dynamic entanglement
|
|
|
}}
|
|
|
|
|
|
// Animate value changes
|
|
|
function animateValue(element, start, end, duration) {{
|
|
|
let startTimestamp = null;
|
|
|
const step = timestamp => {{
|
|
|
if (!startTimestamp) startTimestamp = timestamp;
|
|
|
const progress = Math.min((timestamp - startTimestamp) / duration, 1);
|
|
|
const value = start + progress * (end - start);
|
|
|
element.textContent = value.toFixed(2);
|
|
|
if (progress < 1) {{
|
|
|
window.requestAnimationFrame(step);
|
|
|
}}
|
|
|
}};
|
|
|
window.requestAnimationFrame(step);
|
|
|
}}
|
|
|
|
|
|
// Step control functions
|
|
|
function goToNextStep() {{
|
|
|
if (currentStep < simulationSteps.length - 1) {{
|
|
|
currentStep++;
|
|
|
|
|
|
// Show transition animation
|
|
|
showStepAnimation();
|
|
|
|
|
|
// Update UI
|
|
|
updateUI();
|
|
|
}} else if (isPlaying) {{
|
|
|
// Stop playback when reaching the end
|
|
|
stopPlayback();
|
|
|
}}
|
|
|
}}
|
|
|
|
|
|
function goToPrevStep() {{
|
|
|
if (currentStep > 0) {{
|
|
|
currentStep--;
|
|
|
|
|
|
// Show transition animation
|
|
|
showStepAnimation();
|
|
|
|
|
|
// Update UI
|
|
|
updateUI();
|
|
|
}}
|
|
|
}}
|
|
|
|
|
|
// Start playback
|
|
|
function startPlayback() {{
|
|
|
if (!isPlaying) {{
|
|
|
isPlaying = true;
|
|
|
playBtn.textContent = '⏸︎';
|
|
|
|
|
|
// Set interval for automatic progression
|
|
|
playInterval = setInterval(() => {{
|
|
|
goToNextStep();
|
|
|
|
|
|
// Stop at the end
|
|
|
if (currentStep >= simulationSteps.length - 1) {{
|
|
|
stopPlayback();
|
|
|
}}
|
|
|
}}, 2000);
|
|
|
}}
|
|
|
}}
|
|
|
|
|
|
// Stop playback
|
|
|
function stopPlayback() {{
|
|
|
if (isPlaying) {{
|
|
|
isPlaying = false;
|
|
|
playBtn.textContent = '▶';
|
|
|
clearInterval(playInterval);
|
|
|
}}
|
|
|
}}
|
|
|
|
|
|
// Toggle playback
|
|
|
function togglePlayback() {{
|
|
|
if (isPlaying) {{
|
|
|
stopPlayback();
|
|
|
}} else {{
|
|
|
startPlayback();
|
|
|
}}
|
|
|
}}
|
|
|
|
|
|
// Step transition animation
|
|
|
function showStepAnimation() {{
|
|
|
animationContainer.innerHTML = '';
|
|
|
animationContainer.classList.add('show-animation');
|
|
|
|
|
|
// Create particles for animation
|
|
|
for (let i = 0; i < 50; i++) {{
|
|
|
const particle = document.createElement('div');
|
|
|
particle.style.position = 'absolute';
|
|
|
particle.style.width = '3px';
|
|
|
particle.style.height = '3px';
|
|
|
particle.style.background = '#3B82F6';
|
|
|
particle.style.borderRadius = '50%';
|
|
|
particle.style.left = `${{Math.random() * 100}}%`;
|
|
|
particle.style.top = `${{Math.random() * 100}}%`;
|
|
|
particle.style.boxShadow = '0 0 5px #3B82F6';
|
|
|
|
|
|
// Random animation
|
|
|
const duration = 300 + Math.random() * 500;
|
|
|
const distance = 20 + Math.random() * 50;
|
|
|
const angle = Math.random() * Math.PI * 2;
|
|
|
const dx = Math.cos(angle) * distance;
|
|
|
const dy = Math.sin(angle) * distance;
|
|
|
|
|
|
particle.animate(
|
|
|
[
|
|
|
{{ opacity: 0, transform: 'scale(0)' }},
|
|
|
{{ opacity: 1, transform: 'scale(1) translate(0, 0)' }},
|
|
|
{{ opacity: 0, transform: `scale(0) translate(${{dx}}px, ${{dy}}px)` }}
|
|
|
],
|
|
|
{{
|
|
|
duration: duration,
|
|
|
easing: 'ease-out'
|
|
|
}}
|
|
|
);
|
|
|
|
|
|
animationContainer.appendChild(particle);
|
|
|
}}
|
|
|
|
|
|
// Remove animation after it completes
|
|
|
setTimeout(() => {{
|
|
|
animationContainer.classList.remove('show-animation');
|
|
|
}}, 800);
|
|
|
}}
|
|
|
|
|
|
// Initialize visualization
|
|
|
function init() {{
|
|
|
// Create particles
|
|
|
createParticles();
|
|
|
|
|
|
// Initialize tabs
|
|
|
initTabs();
|
|
|
|
|
|
// Initialize task cards
|
|
|
initTaskCards();
|
|
|
|
|
|
// Initialize Bloch sphere
|
|
|
blochSphere = initBlochSphere();
|
|
|
|
|
|
// Initialize network visualization
|
|
|
network = initNetworkVisualization();
|
|
|
|
|
|
// Initialize matrix visualization
|
|
|
initMatrixVisualization();
|
|
|
|
|
|
// Set up UI
|
|
|
stepTotal.textContent = simulationSteps.length;
|
|
|
|
|
|
// Button event listeners
|
|
|
prevBtn.addEventListener('click', () => {{
|
|
|
stopPlayback();
|
|
|
goToPrevStep();
|
|
|
}});
|
|
|
|
|
|
nextBtn.addEventListener('click', () => {{
|
|
|
stopPlayback();
|
|
|
goToNextStep();
|
|
|
}});
|
|
|
|
|
|
playBtn.addEventListener('click', togglePlayback);
|
|
|
|
|
|
// Initial UI update
|
|
|
updateUI();
|
|
|
}}
|
|
|
|
|
|
// Initialize when page loads
|
|
|
window.addEventListener('load', init);
|
|
|
</script>
|
|
|
</body>
|
|
|
</html>
|
|
|
"""
|
|
|
|
|
|
|
|
|
components.html(html_content, height=height, scrolling=False)
|
|
|
|
|
|
return None |