From dd6fb268924c60a12615ea40f6ebb0360b61e525 Mon Sep 17 00:00:00 2001 From: Vy Vu <55815025+vudangbaovy@users.noreply.github.com> Date: Thu, 6 Mar 2025 15:23:46 -0500 Subject: [PATCH 1/3] Implement websocket to stream loading messages to frontend --- package-lock.json | 362 +++++++++++++++++++++++++++++++++++++ package.json | 4 + quantum_app_backend/app.py | 184 ++++++++++++++----- src/pages/Interference.tsx | 40 +++- src/pages/Tunneling.tsx | 39 +++- 5 files changed, 575 insertions(+), 54 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6b568ce..ad22479 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "@types/node": "^16.11.59", "@types/react": "^18.0.20", "@types/react-dom": "^18.0.6", + "@types/socket.io-client": "^1.4.36", "antd": "^4.24.1", "axios": "^1.1.2", "dotenv": "^16.3.1", @@ -32,6 +33,8 @@ "react-markdown": "^9.0.1", "react-router-dom": "^6.4.2", "react-scripts": "^5.0.1", + "socket.io": "^4.5.1", + "socket.io-client": "^4.8.1", "styled-components": "^5.3.6", "typescript": "^4.8.3", "web-vitals": "^2.1.4" @@ -4327,6 +4330,12 @@ "@sinonjs/commons": "^1.7.0" } }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, "node_modules/@surma/rollup-plugin-off-main-thread": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", @@ -4732,6 +4741,15 @@ "@types/node": "*" } }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -5014,6 +5032,12 @@ "@types/node": "*" } }, + "node_modules/@types/socket.io-client": { + "version": "1.4.36", + "resolved": "https://registry.npmjs.org/@types/socket.io-client/-/socket.io-client-1.4.36.tgz", + "integrity": "sha512-ZJWjtFBeBy1kRSYpVbeGYTElf6BqPQUkXDlHHD4k/42byCN5Rh027f4yARHCink9sKAkbtGZXEAmR0ZCnc2/Ag==", + "license": "MIT" + }, "node_modules/@types/sockjs": { "version": "0.3.36", "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", @@ -6344,6 +6368,15 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "license": "MIT", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, "node_modules/batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", @@ -7082,6 +7115,19 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/cosmiconfig": { "version": "7.1.0", "license": "MIT", @@ -7984,6 +8030,145 @@ "node": ">= 0.8" } }, + "node_modules/engine.io": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz", + "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==", + "license": "MIT", + "dependencies": { + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-client": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz", + "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io-client/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/engine.io-client/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/engine.io/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/enhanced-resolve": { "version": "5.15.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", @@ -16610,6 +16795,175 @@ "node": ">=8" } }, + "node_modules/socket.io": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "license": "MIT", + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.17.1" + } + }, + "node_modules/socket.io-adapter/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-adapter/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/socket.io-adapter/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/socket.io-client": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", + "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-client/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/socket.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/sockjs": { "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", @@ -19116,6 +19470,14 @@ "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/y18n": { "version": "5.0.8", "license": "ISC", diff --git a/package.json b/package.json index 86d6715..9673a56 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "quantum_modeling_app", "version": "0.1.0", + "proxy": "http://localhost:3001/", "private": true, "dependencies": { "@emotion/react": "^11.11.0", @@ -17,6 +18,7 @@ "@types/node": "^16.11.59", "@types/react": "^18.0.20", "@types/react-dom": "^18.0.6", + "@types/socket.io-client": "^1.4.36", "antd": "^4.24.1", "axios": "^1.1.2", "dotenv": "^16.3.1", @@ -27,6 +29,8 @@ "react-markdown": "^9.0.1", "react-router-dom": "^6.4.2", "react-scripts": "^5.0.1", + "socket.io": "^4.5.1", + "socket.io-client": "^4.8.1", "styled-components": "^5.3.6", "typescript": "^4.8.3", "web-vitals": "^2.1.4" diff --git a/quantum_app_backend/app.py b/quantum_app_backend/app.py index a21a8a9..ecbc977 100755 --- a/quantum_app_backend/app.py +++ b/quantum_app_backend/app.py @@ -7,7 +7,50 @@ from model_generators.Qgate1 import Qgate1 import matplotlib.pyplot as plt import time +import logging +from logging.handlers import RotatingFileHandler +import os from db import MongoConnector +from flask_socketio import SocketIO, emit +from functools import wraps + +# Set up logging +log_dir = 'logs' +if not os.path.exists(log_dir): + os.makedirs(log_dir) + +formatter = logging.Formatter( + '[%(asctime)s] %(levelname)s in %(module)s: %(message)s' +) + +file_handler = RotatingFileHandler( + os.path.join(log_dir, 'quantum_app.log'), + maxBytes=10000000, # 10MB + backupCount=5 +) +file_handler.setFormatter(formatter) +file_handler.setLevel(logging.INFO) + +console_handler = logging.StreamHandler() +console_handler.setFormatter(formatter) +console_handler.setLevel(logging.DEBUG) + +logger = logging.getLogger('quantum_app') +logger.addHandler(file_handler) +logger.addHandler(console_handler) +logger.setLevel(logging.DEBUG) + +# Error handling decorator +def handle_errors(f): + @wraps(f) + def wrapped(*args, **kwargs): + try: + return f(*args, **kwargs) + except Exception as e: + logger.error(f"Error in {f.__name__}: {str(e)}", exc_info=True) + emit_status(f"An error occurred: {str(e)}") + return jsonify({"error": str(e)}), 500 + return wrapped #set swagger info api: Api = Api( @@ -20,67 +63,93 @@ app = Flask(__name__) api.init_app(app) CORS(app, resources={r"/*": {"origins": "*"}}) +socketio = SocketIO(app, cors_allowed_origins="*") mongo = MongoConnector() -cache = {} + +def emit_status(message): + logger.info(f"Status update: {message}") + socketio.emit('status_update', {'message': message}) @app.route('/receive_data/tunneling///', methods=['GET']) +@handle_errors def Qtunneling(barrier, width, momentum): - print("You evoked the tunneling API successfully") + try: + barrier = float(barrier) + width = float(width) + momentum = float(momentum) + except ValueError as e: + logger.error(f"Invalid parameters: {str(e)}") + return jsonify({"error": "Invalid parameters"}), 400 + + logger.info(f"Tunneling request - barrier: {barrier}, width: {width}, momentum: {momentum}") - barrier = float(barrier) - width = float(width) - momentum = float(momentum) - t_collection = mongo.collection('tunneling') - - parameters = {'barrier': barrier, 'width': width, 'momentum': momentum } - - tunneling_model = mongo.get(t_collection, parameters) - if not tunneling_model: - plt.close('all') - plt.switch_backend('Agg') - - print('Calculating tunneling') - animator = t_ani(t_wp(barrier_height=barrier, barrier_width=width, k0=momentum)) - - print('Modeling tunneling') - tunneling_model = animator.animate3D() - - print(f'Uploading tunneling model with parameters {parameters} to MongoDB') - mongo.upload(t_collection, parameters, tunneling_model) - return tunneling_model - + parameters = {'barrier': barrier, 'width': width, 'momentum': momentum} + + try: + tunneling_model = mongo.get(t_collection, parameters) + if not tunneling_model: + emit_status('Generating tunneling model...') + plt.close('all') + plt.switch_backend('Agg') + + emit_status('Calculating tunneling model...') + animator = t_ani(t_wp(barrier_height=barrier, barrier_width=width, k0=momentum)) + + emit_status('Animating tunneling model...') + tunneling_model = animator.animate3D() + + # Upload to MongoDB asynchronously after returning response + socketio.start_background_task(mongo.upload, t_collection, parameters, tunneling_model) + logger.info(f"Generated new tunneling model with parameters: {parameters}") + else: + logger.info(f"Retrieved existing tunneling model with parameters: {parameters}") + return tunneling_model + except Exception as e: + logger.error(f"Error generating tunneling model: {str(e)}", exc_info=True) + raise @app.route('/receive_data/interference///', methods=['GET']) +@handle_errors def Qinterference(spacing, slit_separation, momentum): - print("You evoked the interference API successfully") - - momentum = float(momentum) - spacing = float(spacing) - slit_separation = float(slit_separation) - - i_collection = mongo.collection('interference') + try: + momentum = float(momentum) + spacing = float(spacing) + slit_separation = float(slit_separation) + except ValueError as e: + logger.error(f"Invalid parameters: {str(e)}") + return jsonify({"error": "Invalid parameters"}), 400 + + logger.info(f"Interference request - spacing: {spacing}, slit_separation: {slit_separation}, momentum: {momentum}") - parameters = {'spacing': spacing, 'slit_separation': slit_separation, 'momentum': momentum, } - interference_model = mongo.get(i_collection, parameters) - if not interference_model: - plt.close('all') - plt.switch_backend('Agg') - - print('Calculating interference') - animator = i_ani(i_wp(slit_space=spacing, slit_sep=slit_separation, k0=momentum)) - - print('Modeling interference') - interference_model = animator.animate3D() - - print(f'Uploading interference model with parameters {parameters} to MongoDB') - mongo.upload(i_collection, parameters, interference_model) - return interference_model + i_collection = mongo.collection('interference') + parameters = {'spacing': spacing, 'slit_separation': slit_separation, 'momentum': momentum} + + try: + interference_model = mongo.get(i_collection, parameters) + if not interference_model: + emit_status('Generating interference model...') + plt.close('all') + plt.switch_backend('Agg') + + emit_status('Calculating interference model...') + animator = i_ani(i_wp(slit_space=spacing, slit_sep=slit_separation, k0=momentum)) + + emit_status('Animating interference model...') + interference_model = animator.animate3D() + socketio.start_background_task(mongo.upload, i_collection, parameters, interference_model) + logger.info(f"Generated new interference model with parameters: {parameters}") + else: + logger.info(f"Retrieved existing interference model with parameters: {parameters}") + return interference_model + except Exception as e: + logger.error(f"Error generating interference model: {str(e)}", exc_info=True) + raise @app.route('/receive_data/evotrace////', methods=['GET']) def Qtrace(gate, init_state, mag, t2): t2 = float(t2) - print("You evoked the API successfully") + emit_status("You evoked the API successfully") plt.close('all') plt.switch_backend('Agg') @@ -94,8 +163,23 @@ def Qtrace(gate, init_state, mag, t2): print(f"Elapsed 3D generator time: {elapsed_time} seconds") return {'GifRes': GifRes} - + +@socketio.on('connect') +def handle_connect(): + logger.info("Client connected") + emit('status_update', {'message': 'Connected to quantum model generator server'}) + +@socketio.on('disconnect') +def handle_disconnect(): + logger.info("Client disconnected") + emit_status('Client disconnected') + +@socketio.on('message') +def handle_message(data): + logger.info(f"Received message: {data}") + print(f"{data}") + if __name__ == '__main__': app.debug = True - #run backend server at port 3001 - app.run(host="0.0.0.0", port=3001, threaded=False, debug=True) \ No newline at end of file + logger.info("Starting quantum modeling server") + socketio.run(app, host="0.0.0.0", port=3001, debug=True) \ No newline at end of file diff --git a/src/pages/Interference.tsx b/src/pages/Interference.tsx index 0de6210..f08336a 100644 --- a/src/pages/Interference.tsx +++ b/src/pages/Interference.tsx @@ -22,7 +22,8 @@ import CircularProgress from '@mui/joy/CircularProgress'; import { Layout } from "antd"; import "antd/dist/antd.min.css"; import host from "../setup/host"; - +import { Socket } from "socket.io-client"; +import io from "socket.io-client"; // === Custom Components === import { Dashboard, @@ -39,6 +40,9 @@ const horizontal_center = { // === sub component imports === const { Sider, Content } = Layout; +interface StatusUpdate { + message: string; +} // ======================================================== const Interference = () => { @@ -58,6 +62,36 @@ const Interference = () => { const [severity, setSeverity] = useState('success'); const [openSnackBar, setOpenSnackbar] = useState(false); + // ========= socket connection ========= + useEffect(() => { + const socket = io(host); + + socket.on('connect', () => { + console.log('Connected to server'); + }); + + socket.on('connect_error', (error: any) => { + console.error('Connection error:', error); + setSnackbarMessage('Failed to connect to server'); + setSeverity('error'); + setOpenSnackbar(true); + }); + + socket.on('status_update', (data: StatusUpdate) => { + setSnackbarMessage(data.message); + setSeverity('info'); + setOpenSnackbar(true); + }); + + socket.on('disconnect', () => { + console.log('Disconnected from server'); + }); + + return () => { + socket.disconnect(); + }; + }, []); + // ========= handle functions ========= async function getGifFromServer(request_url: string) { try { @@ -179,10 +213,11 @@ const Interference = () => { wave_str + "!" ); - + setSeverity('success'); } else { setSnackbarMessage("Failed to generate model."); + setSeverity('error'); } setLoading(false); setSpacingSliderMoved(false); @@ -392,7 +427,6 @@ return ( setOpenSnackbar(false)} anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }} > diff --git a/src/pages/Tunneling.tsx b/src/pages/Tunneling.tsx index b83c68a..891aacf 100644 --- a/src/pages/Tunneling.tsx +++ b/src/pages/Tunneling.tsx @@ -19,6 +19,8 @@ import { import { Layout } from "antd"; import "antd/dist/antd.min.css"; import host from "../setup/host"; +import { Socket } from "socket.io-client"; +import io from "socket.io-client"; // === Custom Components === import { @@ -36,6 +38,10 @@ const horizontal_center = { justifyContent: "center", }; +interface StatusUpdate { + message: string; +} + const Tunneling = () => { // ========= states ========= const [loading, setLoading] = useState(false); @@ -53,6 +59,36 @@ const Tunneling = () => { const [severity, setSeverity] = useState('success'); const [openSnackBar, setOpenSnackbar] = useState(false); + // ========= socket connection ========= + useEffect(() => { + const socket = io(host); + + socket.on('connect', () => { + console.log('Connected to server'); + }); + + socket.on('connect_error', (error: any) => { + console.error('Connection error:', error); + setSnackbarMessage('Failed to connect to server'); + setSeverity('error'); + setOpenSnackbar(true); + }); + + socket.on('status_update', (data: StatusUpdate) => { + setSnackbarMessage(data.message); + setSeverity('info'); + setOpenSnackbar(true); + }); + + socket.on('disconnect', () => { + console.log('Disconnected from server'); + }); + + return () => { + socket.disconnect(); + }; + }, []); + // ========= handle functions ========= async function getGifFromServer(request_url: string) { try { @@ -163,9 +199,11 @@ const Tunneling = () => { wave_str + "!" ); + setSeverity('success'); } else { setSnackbarMessage("Failed to generate model."); + setSeverity('error'); } setLoading(false); setBarrierSliderMoved(false); @@ -377,7 +415,6 @@ const Tunneling = () => { setOpenSnackbar(false)} anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }} > From 5b6ee96bbe6369a1a3902a916b6e7db001974568 Mon Sep 17 00:00:00 2001 From: Vy Vu <55815025+vudangbaovy@users.noreply.github.com> Date: Thu, 6 Mar 2025 16:26:05 -0500 Subject: [PATCH 2/3] Send error and info logs to frontend through websocket, reorganize functions for readability, log progress of model generation --- quantum_app_backend/app.py | 36 +- quantum_app_backend/db.py | 92 +++- .../model_generators/interference.py | 398 +++++++++++------- .../model_generators/tunneling.py | 357 +++++++++------- 4 files changed, 549 insertions(+), 334 deletions(-) diff --git a/quantum_app_backend/app.py b/quantum_app_backend/app.py index ecbc977..a0413a1 100755 --- a/quantum_app_backend/app.py +++ b/quantum_app_backend/app.py @@ -40,6 +40,21 @@ logger.addHandler(console_handler) logger.setLevel(logging.DEBUG) +# Add this after the logger setup +class SocketHandler(logging.Handler): + def __init__(self, socketio): + super().__init__() + self.socketio = socketio + # No need for formatter since we're only using the message + + def emit(self, record): + try: + # Only emit INFO level and above to avoid flooding the client + if record.levelno >= logging.INFO: + self.socketio.emit('status_update', {'message': record.getMessage()}) + except Exception: + self.handleError(record) + # Error handling decorator def handle_errors(f): @wraps(f) @@ -64,11 +79,13 @@ def wrapped(*args, **kwargs): api.init_app(app) CORS(app, resources={r"/*": {"origins": "*"}}) socketio = SocketIO(app, cors_allowed_origins="*") +socket_handler = SocketHandler(socketio) +socket_handler.setLevel(logging.INFO) +logger.addHandler(socket_handler) mongo = MongoConnector() def emit_status(message): - logger.info(f"Status update: {message}") - socketio.emit('status_update', {'message': message}) + logger.info(message) @app.route('/receive_data/tunneling///', methods=['GET']) @handle_errors @@ -81,7 +98,7 @@ def Qtunneling(barrier, width, momentum): logger.error(f"Invalid parameters: {str(e)}") return jsonify({"error": "Invalid parameters"}), 400 - logger.info(f"Tunneling request - barrier: {barrier}, width: {width}, momentum: {momentum}") + logger.debug(f"Tunneling request - barrier: {barrier}, width: {width}, momentum: {momentum}") t_collection = mongo.collection('tunneling') parameters = {'barrier': barrier, 'width': width, 'momentum': momentum} @@ -120,7 +137,7 @@ def Qinterference(spacing, slit_separation, momentum): logger.error(f"Invalid parameters: {str(e)}") return jsonify({"error": "Invalid parameters"}), 400 - logger.info(f"Interference request - spacing: {spacing}, slit_separation: {slit_separation}, momentum: {momentum}") + logger.debug(f"Interference request - spacing: {spacing}, slit_separation: {slit_separation}, momentum: {momentum}") i_collection = mongo.collection('interference') parameters = {'spacing': spacing, 'slit_separation': slit_separation, 'momentum': momentum} @@ -128,15 +145,12 @@ def Qinterference(spacing, slit_separation, momentum): try: interference_model = mongo.get(i_collection, parameters) if not interference_model: - emit_status('Generating interference model...') plt.close('all') plt.switch_backend('Agg') - emit_status('Calculating interference model...') animator = i_ani(i_wp(slit_space=spacing, slit_sep=slit_separation, k0=momentum)) - - emit_status('Animating interference model...') interference_model = animator.animate3D() + socketio.start_background_task(mongo.upload, i_collection, parameters, interference_model) logger.info(f"Generated new interference model with parameters: {parameters}") else: @@ -149,7 +163,7 @@ def Qinterference(spacing, slit_separation, momentum): @app.route('/receive_data/evotrace////', methods=['GET']) def Qtrace(gate, init_state, mag, t2): t2 = float(t2) - emit_status("You evoked the API successfully") + emit_status("Calculating...") plt.close('all') plt.switch_backend('Agg') @@ -171,13 +185,11 @@ def handle_connect(): @socketio.on('disconnect') def handle_disconnect(): - logger.info("Client disconnected") - emit_status('Client disconnected') + logger.debug("Client disconnected") @socketio.on('message') def handle_message(data): logger.info(f"Received message: {data}") - print(f"{data}") if __name__ == '__main__': app.debug = True diff --git a/quantum_app_backend/db.py b/quantum_app_backend/db.py index 2f6967f..178188e 100644 --- a/quantum_app_backend/db.py +++ b/quantum_app_backend/db.py @@ -1,9 +1,12 @@ import pymongo import re import os +import logging from dotenv import load_dotenv load_dotenv() +logger = logging.getLogger('quantum_app') + VARIABLES = {'tunneling': ['momentum', 'barrier', 'width'], 'interference': ['momentum', 'spacing', 'slit_separation']} @@ -12,39 +15,94 @@ class MongoConnector: def __new__(cls, uri=os.getenv("MONGO_URI")): if cls._instance is None: - cls._instance = super(MongoConnector, cls).__new__(cls) - cls._instance.client = pymongo.MongoClient(uri) - cls._instance.db = cls._instance.client['models'] + try: + cls._instance = super(MongoConnector, cls).__new__(cls) + cls._instance.client = pymongo.MongoClient(uri) + # Test the connection + cls._instance.client.server_info() + cls._instance.db = cls._instance.client['models'] + logger.debug("Successfully connected to MongoDB") + except pymongo.errors.ConnectionError as e: + logger.debug(f"Failed to connect to MongoDB: {str(e)}") + raise + except pymongo.errors.ServerSelectionTimeoutError as e: + logger.debug(f"MongoDB server selection timeout: {str(e)}") + raise + except Exception as e: + logger.debug(f"Unexpected error connecting to MongoDB: {str(e)}") + raise return cls._instance def collection(self, model): + if model not in VARIABLES: + err_msg = f"Invalid model type: {model}" + logger.debug(err_msg) + raise ValueError(err_msg) return self.db[model] def get(self, collection, parameters): - for param, value in parameters.items(): - if param not in VARIABLES[collection.name]: - raise ValueError(f'{param} is not a valid parameter for model {collection.name}') - if not isinstance(value, (int, float)): - raise ValueError(f'{value} is not a valid value for parameter {param}') - try: + # Validate parameters + for param, value in parameters.items(): + if param not in VARIABLES[collection.name]: + err_msg = f'{param} is not a valid parameter for model {collection.name}' + logger.debug(err_msg) + raise ValueError(err_msg) + if not isinstance(value, (int, float)): + err_msg = f'{value} is not a valid value for parameter {param}' + logger.debug(err_msg) + raise ValueError(err_msg) + + # Query MongoDB + logger.debug(f"Querying {collection.name} with parameters: {parameters}") res = collection.find({'parameters': parameters}) + if res: document = res.sort('uploadDate', pymongo.DESCENDING).limit(1)[0] + logger.debug(f"Found existing model in {collection.name} with parameters: {parameters}") return document['animation'].decode('utf-8') else: + logger.debug(f"No existing model found in {collection.name} with parameters: {parameters}") return None + + except pymongo.errors.OperationFailure as e: + logger.debug(f"MongoDB operation failed: {str(e)}") + raise except IndexError as e: - print(e) + logger.debug(f"No model found in {collection.name} with parameters: {parameters}") return None + except Exception as e: + logger.debug(f"Error retrieving model from MongoDB: {str(e)}", exc_info=True) + raise def upload(self, collection, parameters, animation): try: - if collection.find({'parameters': parameters}): - # replace the existing document - print('Replacing existing document') + # Validate parameters before upload + for param, value in parameters.items(): + if param not in VARIABLES[collection.name]: + err_msg = f'{param} is not a valid parameter for model {collection.name}' + logger.debug(err_msg) + raise ValueError(err_msg) + if not isinstance(value, (int, float)): + err_msg = f'{value} is not a valid value for parameter {param}' + logger.debug(err_msg) + raise ValueError(err_msg) + + # Check if document exists and replace if it does + if collection.find_one({'parameters': parameters}): + logger.debug(f"Replacing existing model in {collection.name} with parameters: {parameters}") collection.delete_one({'parameters': parameters}) - collection.insert_one({'parameters': parameters, 'animation': animation.encode('utf-8')}) - except ValueError as e: - print(e) - return None \ No newline at end of file + + # Insert new document + collection.insert_one({ + 'parameters': parameters, + 'animation': animation.encode('utf-8') + }) + logger.debug(f"Successfully uploaded model to {collection.name} with parameters: {parameters}") + + except pymongo.errors.OperationFailure as e: + logger.debug(f"MongoDB operation failed during upload: {str(e)}") + raise + except Exception as e: + logger.debug(f"Error uploading model to MongoDB: {str(e)}", exc_info=True) + raise \ No newline at end of file diff --git a/quantum_app_backend/model_generators/interference.py b/quantum_app_backend/model_generators/interference.py index ca53418..41f7bb3 100644 --- a/quantum_app_backend/model_generators/interference.py +++ b/quantum_app_backend/model_generators/interference.py @@ -9,187 +9,277 @@ import os import time import sys +import logging current = os.path.dirname(os.path.realpath(__file__)) parent = os.path.dirname(current) sys.path.append(parent) from db import MongoConnector +logger = logging.getLogger('quantum_app') + def calculate_center_of_mass(x, psi): - density = np.abs(psi)**2 - total_density = np.sum(density) - x_cm = np.sum(x * density) / total_density - return x_cm + try: + density = np.abs(psi)**2 + total_density = np.sum(density) + if total_density == 0: + raise ValueError("Total density is zero") + x_cm = np.sum(x * density) / total_density + return x_cm + except Exception as e: + logger.debug(f"Error calculating center of mass: {str(e)}") + raise class Wave_Packet3D: def __init__(self, sigma0=0.5, k0=6, x0=0.2, barrier_height=200, barrier_width=0.6, slit_space=0.8, slit_sep=1.0): - self.L = 8 - self.t_Loc = 4.5 - self.dx = 0.1 - self.Nx = self.Ny = int(self.L / self.dx) + 1 - self.k0 = k0 - self.sigma0 = sigma0 - self.x0 = self.L * x0 - self.y0 = self.L / 2 - self.w = barrier_width - self.s = slit_sep - self.a = slit_space - self.b_left = int((self.t_Loc - self.w / 2) / self.dx) - self.b_right = int((self.t_Loc + self.w / 2) / self.dx) - self.lower_slit_b = int(1 / (2 * self.dx) * (self.L + self.s) + self.a / self.dx) - self.lower_slit_t = int(1 / (2 * self.dx) * (self.L + self.s)) - self.upper_slit_b = int(1 / (2 * self.dx) * (self.L - self.s)) - self.upper_slit_t = int(1 / (2 * self.dx) * (self.L - self.s) - self.a / self.dx) - # if Path(f'cache/interference/probs_{k0}_{slit_space}_{slit_sep}.npy').exists(): - # self.probs = np.load(f'cache/interference/probs_{k0}_{slit_space}_{slit_sep}.npy') - # self.Nt = self.probs.shape[0] - # else: - self.potential = self.potential_init(barrier_height) - self.Ni = (self.Nx - 2) * (self.Ny - 2) - - self.evo_matrix = self.evo_matrix_cons() - self.x = np.linspace(0, self.L, self.Ny - 2) - self.y = np.linspace(0, self.L, self.Ny - 2) - self.x, self.y = np.meshgrid(self.x, self.y) - - self.psi0 = self.psi0_init(self.sigma0, self.k0) - self.probs = [] - self.probs.append(np.sqrt(np.real(self.psi0) ** 2 + np.imag(self.psi0) ** 2)) - - for i in range(1, self.Nt): - psi_vect = self.psi0.reshape((self.Ni)) - psi_vect = self.evo_matrix.dot(psi_vect) - self.psi0 = psi_vect.reshape((self.Nx - 2, self.Ny - 2)) + try: + logger.info(f"Starting interference calculation with momentum={k0}") + self.L = 8 + self.t_Loc = 4.5 + self.dx = 0.1 + self.Nx = self.Ny = int(self.L / self.dx) + 1 + self.k0 = k0 + self.sigma0 = sigma0 + self.x0 = self.L * x0 + self.y0 = self.L / 2 + self.w = barrier_width + self.s = slit_sep + self.a = slit_space + + # Calculate positions + self.b_left = int((self.t_Loc - self.w / 2) / self.dx) + self.b_right = int((self.t_Loc + self.w / 2) / self.dx) + self.lower_slit_b = int(1 / (2 * self.dx) * (self.L + self.s) + self.a / self.dx) + self.lower_slit_t = int(1 / (2 * self.dx) * (self.L + self.s)) + self.upper_slit_b = int(1 / (2 * self.dx) * (self.L - self.s)) + self.upper_slit_t = int(1 / (2 * self.dx) * (self.L - self.s) - self.a / self.dx) + + self.potential = self.potential_init(barrier_height) + self.Ni = (self.Nx - 2) * (self.Ny - 2) + + logger.info("Constructing evolution matrix") + self.evo_matrix = self.evo_matrix_cons() + + self.x = np.linspace(0, self.L, self.Ny - 2) + self.y = np.linspace(0, self.L, self.Ny - 2) + self.x, self.y = np.meshgrid(self.x, self.y) + + logger.info("Initializing wave function") + self.psi0 = self.psi0_init(self.sigma0, self.k0) + self.probs = [] self.probs.append(np.sqrt(np.real(self.psi0) ** 2 + np.imag(self.psi0) ** 2)) - self.probs = np.array(self.probs) - # try: - # np.save(f'cache/interference/probs_{k0}_{slit_space}_{slit_sep}.npy', self.probs) - # except: - # Path('cache/interference/').mkdir(parents=True, exist_ok=True) - # np.save(f'cache/interference/probs_{k0}_{slit_space}_{slit_sep}.npy', self.probs) + logger.info("Beginning time evolution...") + for i in range(1, self.Nt): + psi_vect = self.psi0.reshape((self.Ni)) + psi_vect = self.evo_matrix.dot(psi_vect) + self.psi0 = psi_vect.reshape((self.Nx - 2, self.Ny - 2)) + self.probs.append(np.sqrt(np.real(self.psi0) ** 2 + np.imag(self.psi0) ** 2)) + if i % 100 == 0: # Periodic progress updates + logger.info(f"Time evolution {i/self.Nt*100:.0f}% complete") + self.probs = np.array(self.probs) + except Exception as e: + logger.debug(f"Error in calculation: {str(e)}") + raise def potential_init(self, v0): - v = np.zeros((self.Ny, self.Ny), complex) - v[0:self.upper_slit_t, self.b_left:self.b_right] = v0 - v[self.upper_slit_b:self.lower_slit_t, self.b_left:self.b_right] = v0 - v[self.lower_slit_b:, self.b_left:self.b_right] = v0 - return v + try: + v = np.zeros((self.Ny, self.Ny), complex) + v[0:self.upper_slit_t, self.b_left:self.b_right] = v0 + v[self.upper_slit_b:self.lower_slit_t, self.b_left:self.b_right] = v0 + v[self.lower_slit_b:, self.b_left:self.b_right] = v0 + return v + except Exception as e: + logger.debug(f"Error initializing potential: {str(e)}") + raise def psi0_init(self, sigma=0.5, k=1): - psi0 = np.exp(-1 / 2 * ((self.x - self.x0) ** 2 + (self.y - self.y0) ** 2) / sigma ** 2) * np.exp(1j * k * (self.x - self.x0)) - - x_cm_initial = calculate_center_of_mass(self.x, psi0) - psi_test = (self.evo_matrix.dot(np.copy(psi0).reshape((self.Ni)))).reshape((self.Nx - 2, self.Ny - 2)) - x_cm_later = calculate_center_of_mass(self.x, psi_test) - dx_per_step = x_cm_later - x_cm_initial - self.Nt = min(800, int(1.5 * self.L / abs(dx_per_step))) - if dx_per_step < 0: # Assuming "forward" is in the positive x direction - psi0 = np.exp(-1 / 2 * ((self.x - self.x0) ** 2 + (self.y - self.y0) ** 2) / sigma ** 2) * np.exp(1j * (-k) * (self.x - self.x0)) - psi0[0, :] = psi0[-1, :] = psi0[:, 0] = psi0[:, -1] = 0 - - return psi0 + try: + logger.info(f"Initializing wave function with sigma={sigma}, k={k}") + psi0 = np.exp(-1 / 2 * ((self.x - self.x0) ** 2 + (self.y - self.y0) ** 2) / sigma ** 2) * np.exp(1j * k * (self.x - self.x0)) + + x_cm_initial = calculate_center_of_mass(self.x, psi0) + psi_test = (self.evo_matrix.dot(np.copy(psi0).reshape((self.Ni)))).reshape((self.Nx - 2, self.Ny - 2)) + x_cm_later = calculate_center_of_mass(self.x, psi_test) + dx_per_step = x_cm_later - x_cm_initial + + self.Nt = min(800, int(1.5 * self.L / abs(dx_per_step))) + if dx_per_step < 0: # Assuming "forward" is in the positive x direction + logger.info("Reversing wave direction") + psi0 = np.exp(-1 / 2 * ((self.x - self.x0) ** 2 + (self.y - self.y0) ** 2) / sigma ** 2) * np.exp(1j * (-k) * (self.x - self.x0)) + + psi0[0, :] = psi0[-1, :] = psi0[:, 0] = psi0[:, -1] = 0 + return psi0 + + except Exception as e: + logger.debug(f"Error initializing wave function: {str(e)}") + raise def evo_matrix_cons(self): - Dt = self.dx ** 2 / 4 - rx, ry = -Dt / (2j * self.dx ** 2), -Dt / (2j * self.dx ** 2) - A = np.zeros((self.Ni, self.Ni), complex) - M = np.zeros((self.Ni, self.Ni), complex) - for k in range(self.Ni): - # k = (i-1)*(Ny-2) + (j-1) - i = 1 + k // (self.Ny - 2) - j = 1 + k % (self.Ny - 2) - - # Main central diagonal. - A[k, k] = 1 + 2 * rx + 2 * ry + 1j * Dt / 2 * self.potential[i, j] - M[k, k] = 1 - 2 * rx - 2 * ry - 1j * Dt / 2 * self.potential[i, j] - - if i != 1: # Lower lone diagonal. - A[k, (i - 2) * (self.Ny - 2) + j - 1] = -ry - M[k, (i - 2) * (self.Ny - 2) + j - 1] = ry - - if i != self.Nx - 2: # Upper lone diagonal. - A[k, i * (self.Ny - 2) + j - 1] = -ry - M[k, i * (self.Ny - 2) + j - 1] = ry - - if j != 1: # Lower main diagonal. - A[k, k - 1] = -rx - M[k, k - 1] = rx - - if j != self.Ny - 2: # Upper main diagonal. - A[k, k + 1] = -rx - M[k, k + 1] = rx - Asp = csc_matrix(A) - Msp = csc_matrix(M) - evolution_matrix = ln.inv(Asp).dot(Msp).tocsr() - return evolution_matrix + try: + logger.info("Constructing evolution matrix") + Dt = self.dx ** 2 / 4 + rx, ry = -Dt / (2j * self.dx ** 2), -Dt / (2j * self.dx ** 2) + A = np.zeros((self.Ni, self.Ni), complex) + M = np.zeros((self.Ni, self.Ni), complex) + + for k in range(self.Ni): + i = 1 + k // (self.Ny - 2) + j = 1 + k % (self.Ny - 2) + + A[k, k] = 1 + 2 * rx + 2 * ry + 1j * Dt / 2 * self.potential[i, j] + M[k, k] = 1 - 2 * rx - 2 * ry - 1j * Dt / 2 * self.potential[i, j] + + if i != 1: + A[k, (i - 2) * (self.Ny - 2) + j - 1] = -ry + M[k, (i - 2) * (self.Ny - 2) + j - 1] = ry + + if i != self.Nx - 2: + A[k, i * (self.Ny - 2) + j - 1] = -ry + M[k, i * (self.Ny - 2) + j - 1] = ry + + if j != 1: + A[k, k - 1] = -rx + M[k, k - 1] = rx + + if j != self.Ny - 2: + A[k, k + 1] = -rx + M[k, k + 1] = rx + + Asp = csc_matrix(A) + Msp = csc_matrix(M) + evolution_matrix = ln.inv(Asp).dot(Msp).tocsr() + logger.info("Evolution matrix construction completed") + return evolution_matrix + + except Exception as e: + logger.debug(f"Error constructing evolution matrix: {str(e)}") + raise class Animator3D: def __init__(self, wave_packet): - self.wave_packet = wave_packet - self.fig = plt.figure(figsize=(6, 5)) - self.ax = self.fig.add_subplot(111, xlim=(0, self.wave_packet.L), ylim=(0, self.wave_packet.L)) - - self.img = self.ax.imshow(self.wave_packet.probs[0], extent=[0, self.wave_packet.L, 0, self.wave_packet.L], - cmap=plt.get_cmap("hot"), vmin=0, vmax=np.max(self.wave_packet.probs), zorder=1) - self.colorbar = self.fig.colorbar(self.img, ax=self.ax, orientation='vertical', fraction=.1, pad=0.05) - self.ax.set_xlabel('X Position (nm)') - self.ax.set_ylabel('Y Position (nm)') - self.ax.text(0.5, 1.05, 'Probability Density', transform=self.ax.transAxes, ha='center') - slitcolor = "w" - slitalpha = 0.8 # Transparency of the rectangles. - wall_bottom = Rectangle((self.wave_packet.b_left * self.wave_packet.dx, 0), - self.wave_packet.w, self.wave_packet.upper_slit_t * self.wave_packet.dx, - color=slitcolor, zorder=20, alpha=slitalpha) - wall_middle = Rectangle((self.wave_packet.b_left * self.wave_packet.dx, self.wave_packet.upper_slit_b * self.wave_packet.dx), - self.wave_packet.w, (self.wave_packet.lower_slit_t - self.wave_packet.upper_slit_b) * self.wave_packet.dx, - color=slitcolor, zorder=20, alpha=slitalpha) - wall_top = Rectangle((self.wave_packet.b_left * self.wave_packet.dx, self.wave_packet.lower_slit_b * self.wave_packet.dx), - self.wave_packet.w, self.wave_packet.upper_slit_t * self.wave_packet.dx, - color=slitcolor, zorder=20, alpha=slitalpha) - - self.ax.add_patch(wall_bottom) - self.ax.add_patch(wall_middle) - self.ax.add_patch(wall_top) - - self.fig.tight_layout(pad=1) + try: + self.wave_packet = wave_packet + self.fig = plt.figure(figsize=(6, 5)) + self.ax = self.fig.add_subplot(111, xlim=(0, self.wave_packet.L), ylim=(0, self.wave_packet.L)) + + self.img = self.ax.imshow( + self.wave_packet.probs[0], + extent=[0, self.wave_packet.L, 0, self.wave_packet.L], + cmap=plt.get_cmap("hot"), + vmin=0, + vmax=np.max(self.wave_packet.probs), + zorder=1 + ) + + self.setup_plot() + + except Exception as e: + logger.debug(f"Error initializing Animator3D: {str(e)}") + raise + + def setup_plot(self): + try: + self.colorbar = self.fig.colorbar(self.img, ax=self.ax, orientation='vertical', fraction=.1, pad=0.05) + self.ax.set_xlabel('X Position (nm)') + self.ax.set_ylabel('Y Position (nm)') + self.ax.text(0.5, 1.05, 'Probability Density', transform=self.ax.transAxes, ha='center') + + # Add slits + slitcolor = "w" + slitalpha = 0.8 + self.add_walls(slitcolor, slitalpha) + self.fig.tight_layout(pad=1) + + except Exception as e: + logger.debug(f"Error setting up plot: {str(e)}") + raise + + def add_walls(self, color, alpha): + try: + wp = self.wave_packet + walls = [ + Rectangle((wp.b_left * wp.dx, 0), + wp.w, wp.upper_slit_t * wp.dx, + color=color, zorder=20, alpha=alpha), + Rectangle((wp.b_left * wp.dx, wp.upper_slit_b * wp.dx), + wp.w, (wp.lower_slit_t - wp.upper_slit_b) * wp.dx, + color=color, zorder=20, alpha=alpha), + Rectangle((wp.b_left * wp.dx, wp.lower_slit_b * wp.dx), + wp.w, wp.upper_slit_t * wp.dx, + color=color, zorder=20, alpha=alpha) + ] + + for wall in walls: + self.ax.add_patch(wall) + + except Exception as e: + logger.debug(f"Error adding walls to plot: {str(e)}") + raise def update(self, i): - self.img.set_data(self.wave_packet.probs[i]) - self.img.set_zorder(1) - - return self.img, + try: + self.img.set_data(self.wave_packet.probs[i]) + self.img.set_zorder(1) + return self.img, + except Exception as e: + logger.debug(f"Error updating animation frame {i}: {str(e)}") + raise def animate3D(self): - anim = FuncAnimation(self.fig, self.update, interval=1, frames=np.arange(0, self.wave_packet.Nt - 100, 10), repeat=False, - blit=0) - anim_js = anim.to_jshtml(fps=30) - - ## No longer need to save the file locally - # path = os.path.abspath(os.path.join(f"quantum_app_backend/cache/interference/probs_{self.wave_packet.k0}_{self.wave_packet.a}_{self.wave_packet.s}_3D.html")) - # if not Path(path).exists(): - # with open(path, "w", encoding="utf-8") as f: - # portalocker.lock(f, portalocker.LOCK_EX) - # f.write(anim_js) - - return anim_js + try: + logger.info("Generating animation of interference model...") + anim = FuncAnimation( + self.fig, + self.update, + interval=1, + frames=np.arange(0, self.wave_packet.Nt - 100, 10), + repeat=False, + blit=0 + ) + + logger.debug("Converting animation to HTML") + anim_js = anim.to_jshtml(fps=30) + + ## No longer need to save the file locally + # path = os.path.abspath(os.path.join(f"quantum_app_backend/cache/interference/probs_{self.wave_packet.k0}_{self.wave_packet.a}_{self.wave_packet.s}_3D.html")) + # if not Path(path).exists(): + # with open(path, "w", encoding="utf-8") as f: + # portalocker.lock(f, portalocker.LOCK_EX) + # f.write(anim_js) + return anim_js + + except Exception as e: + logger.debug(f"Error generating animation: {str(e)}") + raise # Driver to upload interference models to MongoDB if __name__ == "__main__": - mongo = MongoConnector() - wave_packet = Wave_Packet3D(slit_space=1, slit_sep=1, k0=2) - animator = Animator3D(wave_packet) - - parameters = {'momentum': float(wave_packet.k0), 'spacing': float(wave_packet.a), 'slit_separation': float(wave_packet.s)} - - start_time = time.time() - - anim_js = animator.animate3D() - mongo.upload(mongo.collection('interference'), parameters, anim_js) - - end_time = time.time() - - print('Interference model inserted successfully') - print(f"Time taken: {end_time - start_time}") \ No newline at end of file + try: + logger.debug("Starting interference model generation script") + mongo = MongoConnector() + wave_packet = Wave_Packet3D(slit_space=1, slit_sep=1, k0=2) + animator = Animator3D(wave_packet) + + parameters = { + 'momentum': float(wave_packet.k0), + 'spacing': float(wave_packet.a), + 'slit_separation': float(wave_packet.s) + } + + start_time = time.time() + logger.debug(f"Generating interference model with parameters: {parameters}") + + anim_js = animator.animate3D() + mongo.upload(mongo.collection('interference'), parameters, anim_js) + + end_time = time.time() + duration = end_time - start_time + + logger.debug(f'Interference model inserted successfully. Time taken: {duration:.2f}s') + + except Exception as e: + logger.debug("Failed to generate and upload interference model", exc_info=True) + raise \ No newline at end of file diff --git a/quantum_app_backend/model_generators/tunneling.py b/quantum_app_backend/model_generators/tunneling.py index e9fa395..1b1c965 100644 --- a/quantum_app_backend/model_generators/tunneling.py +++ b/quantum_app_backend/model_generators/tunneling.py @@ -10,188 +10,243 @@ import os import time import sys +import logging current = os.path.dirname(os.path.realpath(__file__)) parent = os.path.dirname(current) sys.path.append(parent) from db import MongoConnector +logger = logging.getLogger('quantum_app') + def calculate_center_of_mass(x, psi): - density = np.abs(psi)**2 - total_density = np.sum(density) - x_cm = np.sum(x * density) / total_density - return x_cm + try: + density = np.abs(psi)**2 + total_density = np.sum(density) + if total_density == 0: + raise ValueError("Total density is zero") + x_cm = np.sum(x * density) / total_density + return x_cm + except Exception as e: + logger.debug(f"Error calculating center of mass: {str(e)}") + raise class Wave_Packet3D: def __init__(self, sigma0=0.5, k0=1, x0=0.2, barrier_height=15, barrier_width=1.0): - self.L = 8 - self.t_Loc = 4.5 - self.dx = 0.1 - self.Nx = self.Ny = int(self.L / self.dx) + 1 - self.k0 = k0 - self.sigma0 = sigma0 - self.x0 = self.L * x0 - self.y0 = self.L / 2 - self.w = barrier_width - self.v_max = barrier_height - self.b_left = int((self.t_Loc - self.w / 2) / self.dx) - self.b_right = int((self.t_Loc + self.w / 2) / self.dx) - self.Ni = (self.Nx - 2) * (self.Ny - 2) - self.potential = self.potential_init(barrier_height) - - # if Path(f'cache/tunneling/probs_{k0}_{barrier_height}_{barrier_width}_3D.npy').exists(): - # self.probs = np.load(f'cache/tunneling/probs_{k0}_{barrier_height}_{barrier_width}_3D.npy') - # self.Nt = self.probs.shape[0] - # else: - self.evo_matrix = self.evo_matrix_cons() - self.x = np.linspace(0, self.L, self.Ny - 2) # Array of spatial points. - self.y = np.linspace(0, self.L, self.Ny - 2) # Array of spatial points. - self.x, self.y = np.meshgrid(self.x, self.y) - - self.psi0 = self.psi0_init(self.sigma0, self.k0) - self.probs = [] - self.probs.append(np.sqrt(np.real(self.psi0) ** 2 + np.imag(self.psi0) ** 2)) - - for i in range(1, self.Nt): - psi_vect = self.psi0.reshape((self.Ni)) - psi_vect = self.evo_matrix.dot(psi_vect) - self.psi0 = psi_vect.reshape((self.Nx - 2, self.Ny - 2)) + try: + logger.info(f"Starting tunneling calculation with momentum={k0}") + self.L = 8 + self.t_Loc = 4.5 + self.dx = 0.1 + self.Nx = self.Ny = int(self.L / self.dx) + 1 + self.k0 = k0 + self.sigma0 = sigma0 + self.x0 = self.L * x0 + self.y0 = self.L / 2 + self.w = barrier_width + self.v_max = barrier_height + self.b_left = int((self.t_Loc - self.w / 2) / self.dx) + self.b_right = int((self.t_Loc + self.w / 2) / self.dx) + self.Ni = (self.Nx - 2) * (self.Ny - 2) + self.potential = self.potential_init(barrier_height) + + # if Path(f'cache/tunneling/probs_{k0}_{barrier_height}_{barrier_width}_3D.npy').exists(): + # self.probs = np.load(f'cache/tunneling/probs_{k0}_{barrier_height}_{barrier_width}_3D.npy') + # self.Nt = self.probs.shape[0] + # else: + logger.debug("Constructing evolution matrix") + self.evo_matrix = self.evo_matrix_cons() + self.x = np.linspace(0, self.L, self.Ny - 2) # Array of spatial points. + self.y = np.linspace(0, self.L, self.Ny - 2) # Array of spatial points. + self.x, self.y = np.meshgrid(self.x, self.y) + + logger.debug("Initializing wave function") + self.psi0 = self.psi0_init(self.sigma0, self.k0) + self.probs = [] self.probs.append(np.sqrt(np.real(self.psi0) ** 2 + np.imag(self.psi0) ** 2)) - self.probs = np.array(self.probs) - # try: - # np.save(f'cache/tunneling/probs_{k0}_{barrier_height}_{barrier_width}_3D.npy', self.probs) - # except: - # Path('cache/tunneling/').mkdir(parents=True, exist_ok=True) - # np.save(f'cache/tunneling/probs_{k0}_{barrier_height}_{barrier_width}_3D.npy', self.probs) + + logger.info("Beginning time evolution...") + for i in range(1, self.Nt): + psi_vect = self.psi0.reshape((self.Ni)) + psi_vect = self.evo_matrix.dot(psi_vect) + self.psi0 = psi_vect.reshape((self.Nx - 2, self.Ny - 2)) + self.probs.append(np.sqrt(np.real(self.psi0) ** 2 + np.imag(self.psi0) ** 2)) + if i % 100 == 0: # Periodic progress updates + logger.info(f"Time evolution {i/self.Nt*100:.0f}% complete") + self.probs = np.array(self.probs) + # try: + # np.save(f'cache/tunneling/probs_{k0}_{barrier_height}_{barrier_width}_3D.npy', self.probs) + # except: + # Path('cache/tunneling/').mkdir(parents=True, exist_ok=True) + # np.save(f'cache/tunneling/probs_{k0}_{barrier_height}_{barrier_width}_3D.npy', self.probs) + + except Exception as e: + logger.debug(f"Error in calculation: {str(e)}") + raise def potential_init(self, v0): - v = np.zeros((self.Ny, self.Ny), complex) - v[:, self.b_left:self.b_right] = v0 - return v + try: + v = np.zeros((self.Ny, self.Ny), complex) + v[:, self.b_left:self.b_right] = v0 + return v + except Exception as e: + logger.debug(f"Error initializing potential: {str(e)}") + raise def psi0_init(self, sigma=0.5, k=1): - psi0 = np.exp(-1 / 2 * ((self.x - self.x0) ** 2 + (self.y - self.y0) ** 2) / sigma ** 2) * np.exp(1j * k * (self.x - self.x0)) - - x_cm_initial = calculate_center_of_mass(self.x, psi0) - psi_test = (self.evo_matrix.dot(np.copy(psi0).reshape((self.Ni)))).reshape((self.Nx - 2, self.Ny - 2)) - x_cm_later = calculate_center_of_mass(self.x, psi_test) - dx_per_step = x_cm_later - x_cm_initial - self.Nt = min(800, int(1.5 * self.L / abs(dx_per_step))) - if dx_per_step < 0: # Assuming "forward" is in the positive x direction - psi0 = np.exp(-1 / 2 * ((self.x - self.x0) ** 2 + (self.y - self.y0) ** 2) / sigma ** 2) * np.exp(1j * (-k) * (self.x - self.x0)) - psi0[0, :] = psi0[-1, :] = psi0[:, 0] = psi0[:, -1] = 0 - - return psi0 + try: + logger.info(f"Initializing wave function with sigma={sigma}, k={k}") + psi0 = np.exp(-1 / 2 * ((self.x - self.x0) ** 2 + (self.y - self.y0) ** 2) / sigma ** 2) * np.exp(1j * k * (self.x - self.x0)) + + x_cm_initial = calculate_center_of_mass(self.x, psi0) + psi_test = (self.evo_matrix.dot(np.copy(psi0).reshape((self.Ni)))).reshape((self.Nx - 2, self.Ny - 2)) + x_cm_later = calculate_center_of_mass(self.x, psi_test) + dx_per_step = x_cm_later - x_cm_initial + self.Nt = min(800, int(1.5 * self.L / abs(dx_per_step))) + if dx_per_step < 0: # Assuming "forward" is in the positive x direction + logger.info("Reversing wave direction") + psi0 = np.exp(-1 / 2 * ((self.x - self.x0) ** 2 + (self.y - self.y0) ** 2) / sigma ** 2) * np.exp(1j * (-k) * (self.x - self.x0)) + psi0[0, :] = psi0[-1, :] = psi0[:, 0] = psi0[:, -1] = 0 + + return psi0 + except Exception as e: + logger.debug(f"Error initializing wave function: {str(e)}") + raise def evo_matrix_cons(self): - Dt = self.dx ** 2 / 4 - rx, ry = -Dt / (2j * self.dx ** 2), -Dt / (2j * self.dx ** 2) - A = np.zeros((self.Ni, self.Ni), complex) - M = np.zeros((self.Ni, self.Ni), complex) - for k in range(self.Ni): - # k = (i-1)*(Ny-2) + (j-1) - i = 1 + k // (self.Ny - 2) - j = 1 + k % (self.Ny - 2) - - # Main central diagonal. - A[k, k] = 1 + 2 * rx + 2 * ry + 1j * Dt / 2 * self.potential[i, j] - M[k, k] = 1 - 2 * rx - 2 * ry - 1j * Dt / 2 * self.potential[i, j] - - if i != 1: # Lower lone diagonal. - A[k, (i - 2) * (self.Ny - 2) + j - 1] = -ry - M[k, (i - 2) * (self.Ny - 2) + j - 1] = ry - - if i != self.Nx - 2: # Upper lone diagonal. - A[k, i * (self.Ny - 2) + j - 1] = -ry - M[k, i * (self.Ny - 2) + j - 1] = ry - - if j != 1: # Lower main diagonal. - A[k, k - 1] = -rx - M[k, k - 1] = rx - - if j != self.Ny - 2: # Upper main diagonal. - A[k, k + 1] = -rx - M[k, k + 1] = rx - Asp = csc_matrix(A) - Msp = csc_matrix(M) - evolution_matrix = ln.inv(Asp).dot(Msp).tocsr() - return evolution_matrix + try: + logger.info("Constructing evolution matrix") + Dt = self.dx ** 2 / 4 + rx, ry = -Dt / (2j * self.dx ** 2), -Dt / (2j * self.dx ** 2) + A = np.zeros((self.Ni, self.Ni), complex) + M = np.zeros((self.Ni, self.Ni), complex) + for k in range(self.Ni): + # k = (i-1)*(Ny-2) + (j-1) + i = 1 + k // (self.Ny - 2) + j = 1 + k % (self.Ny - 2) + + # Main central diagonal. + A[k, k] = 1 + 2 * rx + 2 * ry + 1j * Dt / 2 * self.potential[i, j] + M[k, k] = 1 - 2 * rx - 2 * ry - 1j * Dt / 2 * self.potential[i, j] + + if i != 1: # Lower lone diagonal. + A[k, (i - 2) * (self.Ny - 2) + j - 1] = -ry + M[k, (i - 2) * (self.Ny - 2) + j - 1] = ry + + if i != self.Nx - 2: # Upper lone diagonal. + A[k, i * (self.Ny - 2) + j - 1] = -ry + M[k, i * (self.Ny - 2) + j - 1] = ry + + if j != 1: # Lower main diagonal. + A[k, k - 1] = -rx + M[k, k - 1] = rx + + if j != self.Ny - 2: # Upper main diagonal. + A[k, k + 1] = -rx + M[k, k + 1] = rx + Asp = csc_matrix(A) + Msp = csc_matrix(M) + evolution_matrix = ln.inv(Asp).dot(Msp).tocsr() + return evolution_matrix + except Exception as e: + logger.debug(f"Error constructing evolution matrix: {str(e)}") + raise class Animator3D: def __init__(self, wave_packet): - self.wave_packet = wave_packet - self.x_ticks = np.linspace(0, self.wave_packet.L, self.wave_packet.Nx - 2) - self.fig = plt.figure(figsize=(10, 5)) - self.ax_top = self.fig.add_subplot(121, xlim=(0, self.wave_packet.L), ylim=(0, self.wave_packet.L)) - - self.img_top = self.ax_top.imshow(self.wave_packet.probs[0], extent=[0, self.wave_packet.L, 0, self.wave_packet.L], - cmap=plt.get_cmap("hot"), vmin=0, vmax=np.max(self.wave_packet.probs), zorder=0) - self.colorbar = self.fig.colorbar(self.img_top, ax=self.ax_top, orientation='vertical', fraction=.1, pad=0.05) - self.ax_top.set_xlabel('X Position (nm)') - self.ax_top.set_ylabel('Y Position (nm)') - self.ax_top.text(0.5, 1.05, 'Probability Density', transform=self.ax_top.transAxes, ha='center') - slitcolor = "w" - slitalpha = 0.8 # Transparency of the rectangles. - wall = Rectangle((self.wave_packet.b_left * self.wave_packet.dx, 0), - self.wave_packet.w, self.wave_packet.L, - color=slitcolor, zorder=50, alpha=slitalpha) - - self.ax_top.add_patch(wall) - - self.ax_2ndC = self.fig.add_subplot(122) - self.ax_2ndC.axis('off') - self.ax_front = inset_axes(self.ax_2ndC, width="90%", height="78%", loc='center right', - bbox_to_anchor=(0, 0., 1, 1), bbox_transform=self.ax_2ndC.transAxes) - self.ax_front.plot(self.x_ticks, np.real(self.wave_packet.potential)[self.wave_packet.Ny // 2, 1:-1], color='r', - label='Potential') - self.line, = self.ax_front.plot(self.x_ticks, self.wave_packet.probs[0, self.wave_packet.Ny // 2, :], - color='blue') - self.ax_front.set_ylim(0, 1.5) - self.ax_front.set_xlim(0, self.wave_packet.L) - self.ax_front.set_xlabel('X Position (nm)') - self.ax_front.set_ylabel('Probability Density') - self.ax_front.text(0.5, 1.05, 'Front View', transform=self.ax_front.transAxes, ha='center') - - self.fig.tight_layout(pad=1) - self.fig.subplots_adjust(wspace=0.5) + try: + self.wave_packet = wave_packet + self.x_ticks = np.linspace(0, self.wave_packet.L, self.wave_packet.Nx - 2) + self.fig = plt.figure(figsize=(10, 5)) + self.ax_top = self.fig.add_subplot(121, xlim=(0, self.wave_packet.L), ylim=(0, self.wave_packet.L)) + + self.img_top = self.ax_top.imshow(self.wave_packet.probs[0], extent=[0, self.wave_packet.L, 0, self.wave_packet.L], + cmap=plt.get_cmap("hot"), vmin=0, vmax=np.max(self.wave_packet.probs), zorder=0) + self.colorbar = self.fig.colorbar(self.img_top, ax=self.ax_top, orientation='vertical', fraction=.1, pad=0.05) + self.ax_top.set_xlabel('X Position (nm)') + self.ax_top.set_ylabel('Y Position (nm)') + self.ax_top.text(0.5, 1.05, 'Probability Density', transform=self.ax_top.transAxes, ha='center') + slitcolor = "w" + slitalpha = 0.8 # Transparency of the rectangles. + wall = Rectangle((self.wave_packet.b_left * self.wave_packet.dx, 0), + self.wave_packet.w, self.wave_packet.L, + color=slitcolor, zorder=50, alpha=slitalpha) + + self.ax_top.add_patch(wall) + + self.ax_2ndC = self.fig.add_subplot(122) + self.ax_2ndC.axis('off') + self.ax_front = inset_axes(self.ax_2ndC, width="90%", height="78%", loc='center right', + bbox_to_anchor=(0, 0., 1, 1), bbox_transform=self.ax_2ndC.transAxes) + self.ax_front.plot(self.x_ticks, np.real(self.wave_packet.potential)[self.wave_packet.Ny // 2, 1:-1], color='r', + label='Potential') + self.line, = self.ax_front.plot(self.x_ticks, self.wave_packet.probs[0, self.wave_packet.Ny // 2, :], + color='blue') + self.ax_front.set_ylim(0, 1.5) + self.ax_front.set_xlim(0, self.wave_packet.L) + self.ax_front.set_xlabel('X Position (nm)') + self.ax_front.set_ylabel('Probability Density') + self.ax_front.text(0.5, 1.05, 'Front View', transform=self.ax_front.transAxes, ha='center') + + self.fig.tight_layout(pad=1) + self.fig.subplots_adjust(wspace=0.5) + + except Exception as e: + logger.debug(f"Error initializing Animator3D: {str(e)}") + raise def update(self, i): - self.img_top.set_data(self.wave_packet.probs[i]) # Fill img_top with the modulus data of the wave function. - self.img_top.set_zorder(1) + try: + self.img_top.set_data(self.wave_packet.probs[i]) # Fill img_top with the modulus data of the wave function. + self.img_top.set_zorder(1) - self.line.set_ydata(self.wave_packet.probs[i, self.wave_packet.Ny // 2, :]) + self.line.set_ydata(self.wave_packet.probs[i, self.wave_packet.Ny // 2, :]) - return self.img_top, self.line, + return self.img_top, self.line, + except Exception as e: + logger.debug(f"Error updating animation frame {i}: {str(e)}") + raise def animate3D(self): - anim = FuncAnimation(self.fig, self.update, interval=1, frames=np.arange(0, self.wave_packet.Nt - 100, 10), repeat=False, blit=0) - anim_js = anim.to_jshtml(fps=30) - - ## Uncomment to save file locally - # path = os.path.abspath(os.path.join(f"quantum_app_backend/cache/tunneling/probs_{self.wave_packet.k0}_{self.wave_packet.v_max}_{self.wave_packet.w}_3D.html")) - - # if not Path(path).exists(): - # with open(path, "w", encoding="utf-8") as f: - # portalocker.lock(f, portalocker.LOCK_EX) - # f.write(anim_js) - - return anim_js + try: + logger.info("Generating animation of tunneling model...") + anim = FuncAnimation(self.fig, self.update, interval=1, frames=np.arange(0, self.wave_packet.Nt - 100, 10), repeat=False, blit=0) + anim_js = anim.to_jshtml(fps=30) + + ## Uncomment to save file locally + # path = os.path.abspath(os.path.join(f"quantum_app_backend/cache/tunneling/probs_{self.wave_packet.k0}_{self.wave_packet.v_max}_{self.wave_packet.w}_3D.html")) + + # if not Path(path).exists(): + # with open(path, "w", encoding="utf-8") as f: + # portalocker.lock(f, portalocker.LOCK_EX) + # f.write(anim_js) + + return anim_js + except Exception as e: + logger.debug(f"Error generating animation: {str(e)}") + raise # Driver to upload tunneling models to MongoDB if __name__ == "__main__": - mongo = MongoConnector() - wave_packet = Wave_Packet3D(barrier_width=1.0, barrier_height=3.0, k0=2.0) - animator = Animator3D(wave_packet) + try: + logger.debug("Starting tunneling model generation script") + mongo = MongoConnector() + wave_packet = Wave_Packet3D(barrier_width=1.0, barrier_height=3.0, k0=2.0) + animator = Animator3D(wave_packet) - parameters = {'momentum': float(wave_packet.k0), 'barrier': float(wave_packet.v_max), 'width': float(wave_packet.w)} + parameters = {'momentum': float(wave_packet.k0), 'barrier': float(wave_packet.v_max), 'width': float(wave_packet.w)} - start_time = time.time() - - anim_js = animator.animate3D() - mongo.upload(mongo.collection('tunneling'), parameters, anim_js) + start_time = time.time() + logger.debug(f"Generating tunneling model with parameters: {parameters}") - end_time = time.time() + anim_js = animator.animate3D() + mongo.upload(mongo.collection('tunneling'), parameters, anim_js) - print('Tunneling model inserted successfully') - print(f"Time taken: {end_time - start_time}") \ No newline at end of file + end_time = time.time() + duration = end_time - start_time + + logger.debug(f'Tunneling model inserted successfully. Time taken: {duration:.2f}s') + except Exception as e: + logger.debug("Failed to generate and upload tunneling model", exc_info=True) + raise \ No newline at end of file From f03ababbb0ba68fa4ae1e4b4fdd44e46688b0545 Mon Sep 17 00:00:00 2001 From: Vy Vu <55815025+vudangbaovy@users.noreply.github.com> Date: Thu, 6 Mar 2025 16:26:56 -0500 Subject: [PATCH 3/3] Remove autosetting snackbar on frontend, now only emitting messages from Flask app --- src/pages/Interference.tsx | 9 --------- src/pages/Tunneling.tsx | 9 --------- 2 files changed, 18 deletions(-) diff --git a/src/pages/Interference.tsx b/src/pages/Interference.tsx index f08336a..0d23af1 100644 --- a/src/pages/Interference.tsx +++ b/src/pages/Interference.tsx @@ -204,15 +204,6 @@ const Interference = () => { setLoading(true); const gifData = await getGifFromServer(final_url); if (gifData) { - setSnackbarMessage( - "Interference model generated with barrier = " + - spacing_str + - ", thickness = " + - slit_separation_str + - ", and wave = " + - wave_str + - "!" - ); setSeverity('success'); } else { diff --git a/src/pages/Tunneling.tsx b/src/pages/Tunneling.tsx index 891aacf..295b48a 100644 --- a/src/pages/Tunneling.tsx +++ b/src/pages/Tunneling.tsx @@ -190,15 +190,6 @@ const Tunneling = () => { setLoading(true); const gifData = await getGifFromServer(final_url); if (gifData) { - setSnackbarMessage( - "Tunneling model generated with barrier = " + - barrier_str + - ", thickness = " + - thickness_str + - ", and wave = " + - wave_str + - "!" - ); setSeverity('success'); } else {