Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
362 changes: 362 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"name": "quantum_modeling_app",
"version": "0.1.0",
"proxy": "http://localhost:3001/",
"private": true,
"dependencies": {
"@emotion/react": "^11.11.0",
Expand All @@ -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",
Expand All @@ -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"
Expand Down
196 changes: 146 additions & 50 deletions quantum_app_backend/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,65 @@
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)

# 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)
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(
Expand All @@ -20,67 +78,92 @@
app = Flask(__name__)
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()
cache = {}

def emit_status(message):
logger.info(message)

@app.route('/receive_data/tunneling/<barrier>/<width>/<momentum>', 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.debug(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/<spacing>/<slit_separation>/<int:momentum>', 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.debug(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:
plt.close('all')
plt.switch_backend('Agg')

animator = i_ani(i_wp(slit_space=spacing, slit_sep=slit_separation, k0=momentum))
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/<int:gate>/<int:init_state>/<int:mag>/<t2>', methods=['GET'])
def Qtrace(gate, init_state, mag, t2):
t2 = float(t2)
print("You evoked the API successfully")
emit_status("Calculating...")
plt.close('all')
plt.switch_backend('Agg')

Expand All @@ -94,8 +177,21 @@ 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.debug("Client disconnected")

@socketio.on('message')
def handle_message(data):
logger.info(f"Received message: {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)
logger.info("Starting quantum modeling server")
socketio.run(app, host="0.0.0.0", port=3001, debug=True)
92 changes: 75 additions & 17 deletions quantum_app_backend/db.py
Original file line number Diff line number Diff line change
@@ -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']}

Expand All @@ -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

# 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
Loading
Loading