BoltzmannEntropy/QuantumLLMInstruct
7
1"""2Quantum Physics Problem Generator3Shlomo Kashani 4 5Description:6------------7This module is part of the QuantumLLMInstruct system, designed to generate and solve quantum physics problems 8using advanced Large Language Models (LLMs). It utilizes a multi-stage pipeline for problem generation, 9solution generation, and database management.10 11Core Functionalities:12---------------------131. **Problem Generation**:14 - Generates quantum physics problems in LaTeX format using LLMs.15 - Supports domain-specific problem generation across multiple quantum fields.16 172. **Solution Generation**:18 - Provides step-by-step LaTeX solutions for the generated problems using a second LLM.19 203. **Data Management**:21 - Stores generated problems and solutions in DuckDB and Parquet files.22 - Enables exporting data in Parquet format for scalability and compatibility.23 244. **Gradio Interface**:25 - A user-friendly interface to interact with the system, including problem generation, 26 solution generation, and database exploration.27 285. **Hugging Face Integration**:29 - Supports visualization and interaction with the dataset on the Hugging Face platform.30 31Main Components:32----------------33- **initialize_duckdb() / initialize_parquet()**: Initializes the database schema.34- **generate_multiple_problems()**: Generates multiple problems for the selected quantum domains.35- **generate_solutions()**: Solves unsolved problems in the database.36- **export_parquet()**: Exports the database to a Parquet file for external use.37 38Dependencies:39-------------40- Python 3.7+41- Transformers: `transformers`42- DuckDB: `duckdb`43- Gradio: `gradio`44- Pandas: `pandas`45"""46 47import numpy as np48import random49import io50import duckdb51import math52from datetime import datetime53import PIL54from PIL import Image55import pennylane as qml56import base6457import platform58from math import pi59import pandas as pd 60import os 61from transformers import AutoModelForCausalLM, AutoTokenizer62import tqdm 63import duckdb64from tqdm import tqdm65import uuid66import random67import sympy68from datetime import datetime69 70from Q_llm_prompts import * 71 72# Predefined Qwen models73# Qwen2.5 offers multiple model sizes, including 72B, 32B, 14B, 7B, 3B, 1.5B, 0.5B, etc.74# You can choose the appropriate model based on your needs and GPU memory size75model_options = [76 "Qwen/Qwen2.5-Coder-1.5B-Instruct",77 "Qwen/Qwen2.5-Coder-3B-Instruct",78 "Qwen/Qwen2.5-Coder-7B-Instruct",79 "Qwen/Qwen2.5-Math-7B-Instruct",80 "Qwen/Qwen2.5-Coder-32B-Instruct",81 "meta-llama/Llama-3.2-3B-Instruct"82 # "unsloth/Qwen2.5-Math-7B-Instruct",83 # "unsloth/Llama-3.2-3B-Instruct-bnb-4bit",84 # "nvidia/OpenMath-CodeLlama-7b-Python-hf" tokenizer.chat_template is not set and no template argument was passed! 85]86 87solutions_model_options = model_options88 89# Load default model and tokenizer90selected_model = model_options[0]91model = AutoModelForCausalLM.from_pretrained(92 selected_model,93 torch_dtype="auto",94 device_map="auto"95)96tokenizer = AutoTokenizer.from_pretrained(selected_model)97solution_model = selected_model98solution_tokenizer =tokenizer99solution_model_instance =model100 101# Function to reload the model when selection changes102def reload_model(model_name):103 global model, tokenizer104 model = AutoModelForCausalLM.from_pretrained(105 model_name,106 torch_dtype="auto",107 device_map="auto"108 )109 tokenizer = AutoTokenizer.from_pretrained(model_name)110 return f"Model loaded: {model_name}"111 112 113 114# Define a Pennylane device115dev = qml.device('default.qubit', wires=10)116 117# Detect platform-specific device118def is_mac_os():119 return platform.system() == 'Darwin'120 121device = 'cpu' if is_mac_os() else 'cuda'122 123RESPONSE_SOLUTION_LLM_SYS_PROMPT = "You are an expert in quantum physics and provide detailed solutions in plain text. All mathematical equations and symbols must strictly be in LaTeX."124RESPONSE_SOLUTION_LLM_USR_PROMPT = """125Provide a complete solution to the following quantum physics problem in plain text format:126{problem}127"""128 129# Parquet file setup130PARQUET_FILE = 'quantum_problems.parquet'131 132def initialize_parquet():133 """Initialize Parquet file with the required schema if it doesn't exist."""134 if not os.path.exists(PARQUET_FILE):135 data = {136 "uuid": [],137 "timestamp": [],138 "problem": [],139 "sub_domain": [],140 "main_domain": [],141 "model_name": [],142 "solution": [],143 "solution_model_name": []144 }145 df = pd.DataFrame(data)146 df.to_parquet(PARQUET_FILE, index=False)147 print("Initialized Parquet file with schema.")148 149def load_parquet():150 """Load data from the Parquet file."""151 if os.path.exists(PARQUET_FILE):152 return pd.read_parquet(PARQUET_FILE)153 else:154 initialize_parquet()155 return pd.read_parquet(PARQUET_FILE)156 157def save_parquet(df):158 """Save DataFrame to Parquet file."""159 df.to_parquet(PARQUET_FILE, index=False)160 161def insert_problem_pqt(uuid, timestamp, problem, main_domain, sub_domain, model_name, solution=None, solution_model_name=None):162 """Insert a new problem into the Parquet file."""163 df = load_parquet()164 new_row = {165 "uuid": uuid,166 "timestamp": timestamp,167 "problem": problem,168 "sub_domain": sub_domain,169 "main_domain": main_domain,170 "model_name": model_name,171 "solution": solution,172 "solution_model_name": solution_model_name173 }174 df = pd.concat([df, pd.DataFrame([new_row])], ignore_index=True)175 save_parquet(df)176 177def update_solution_pqt(uuid, solution, solution_model_name):178 """Update the solution for a given problem UUID."""179 df = load_parquet()180 df.loc[df["uuid"] == uuid, ["solution", "solution_model_name"]] = solution, solution_model_name181 save_parquet(df)182 183 184# DuckDB setup185DB_FILE = 'quantum_problems.duckdb' # persistant path on HF186 187def initialize_duckdb():188 conn = duckdb.connect(database=DB_FILE)189 190 conn.execute("""191 CREATE TABLE IF NOT EXISTS problems (192 uuid TEXT UNIQUE NOT NULL,193 timestamp TEXT,194 problem TEXT,195 sub_domain TEXT,196 main_domain TEXT,197 model_name TEXT,198 solution TEXT,199 solution_model_name TEXT200 )201 """)202 # print ("Created schema")203 # df = conn.execute("SELECT * FROM problems").df()204 # print (df.count)205 conn.close()206 207# Function to buffer the plot and return as PIL image208def buffer_plot_and_get(fig):209 buf = io.BytesIO()210 fig.savefig(buf, format='png')211 buf.seek(0)212 return PIL.Image.open(buf)213 214# Store image in bytes for DuckDB215def pil_image_to_bytes(image):216 img_byte_arr = io.BytesIO()217 image.save(img_byte_arr, format='PNG')218 return img_byte_arr.getvalue()219 220# Encode the image in base64 to display in HTML221def encode_image_from_blob(blob):222 img_buffer = io.BytesIO(blob)223 image = Image.open(img_buffer)224 img_str = base64.b64encode(img_buffer.getvalue()).decode("utf-8")225 return f'<img src="data:image/png;base64,{img_str}" style="max-width:500px;"/>'226 227# Function to generate a random Hamiltonian228def generate_random_hamiltonian(num_qubits):229 terms = []230 for _ in range(random.randint(1, 5)):231 coeff = round(random.uniform(-1, 1), 2)232 pauli_ops = [random.choice(['I', 'X', 'Y', 'Z']) for _ in range(num_qubits)]233 term = f"{coeff} * {' '.join(pauli_ops)}"234 terms.append(term)235 return " + ".join(terms)236 237# Function to convert Hamiltonian to QASM code238def hamiltonian_to_qasm(hamiltonian, num_qubits):239 qasm_code = f"OPENQASM 2.0;\ninclude \"qelib1.inc\";\nqreg q[{num_qubits}];\n"240 rotations = {i: 0.0 for i in range(num_qubits)}241 terms = hamiltonian.split(" + ")242 243 for term in terms:244 coeff, paulis = term.split(" * ")245 paulis = paulis.split()246 coeff = float(coeff)247 248 for i, pauli in enumerate(paulis):249 if pauli == "X":250 qasm_code += f"x q[{i}];\n"251 elif pauli == "Y":252 qasm_code += f"ry(pi/2) q[{i}];\n"253 elif pauli == "Z":254 rotations[i] += coeff255 256 for i, angle in rotations.items():257 if angle != 0:258 angle_degrees = round(angle * 180 / math.pi, 2)259 qasm_code += f"rz({angle_degrees}) q[{i}];\n"260 261 return qasm_code262 263# Function to parse QASM code and create Pennylane circuit264def qasm_to_pennylane(qasm_code):265 qasm_lines = qasm_code.split("\n")266 num_qubits = int(qasm_lines[2].split('[')[1].split(']')[0]) # Extract number of qubits from QASM267 268 @qml.qnode(dev)269 def circuit():270 for line in qasm_lines:271 if "x" in line:272 qml.PauliX(int(line.split('q[')[1].split(']')[0]))273 elif "rz" in line:274 angle = float(line.split('(')[1].split(')')[0])275 qml.RZ(angle, int(line.split('q[')[1].split(']')[0]))276 elif "ry" in line:277 qml.RY(pi / 2, int(line.split('q[')[1].split(']')[0]))278 return qml.state()279 280 return circuit281 282# # Store data in DuckDB283# def store_in_duckdb(data, db_file='quantum_hamiltonians.duckdb'):284# conn = duckdb.connect(database=db_file)285# conn.execute("""CREATE TABLE IF NOT EXISTS hamiltonians (286# id INTEGER,287# plot BLOB,288# hamiltonian VARCHAR,289# qasm_code VARCHAR,290# trotter_code VARCHAR,291# num_qubits INTEGER,292# trotter_order INTEGER,293# timestamp TIMESTAMP294# )""")295# conn.executemany("""INSERT INTO hamiltonians (id, plot, hamiltonian, qasm_code, trotter_code, num_qubits, trotter_order, timestamp)296# VALUES (?, ?, ?, ?, ?, ?, ?, ?)""", data)297# conn.close()298 299# Function to load results from DuckDB300def load_from_duckdb(db_file='quantum_hamiltonians.duckdb'):301 conn = duckdb.connect(database=db_file)302 df = conn.execute("SELECT * FROM hamiltonians").df()303 conn.close()304 305 # Convert results to HTML with images306 html_content = []307 for index, row in df.iterrows():308 plot_blob = row['plot']309 encoded_img = encode_image_from_blob(plot_blob)310 311 html_content.append(f"""312 <table style='width: 100%; border-collapse: collapse; margin: 10px;'>313 <tr>314 <td style='width: 30%; text-align: center;'>315 <h3>Circuit {index + 1}</h3>316 {encoded_img} <!-- Display the image -->317 </td> 318 <td style='padding: 10px;'>319 <table style='width: 100%; border-collapse: collapse;'>320 <tr>321 <td><strong>Hamiltonian:</strong></td><td>{row['hamiltonian']}</td>322 </tr>323 <tr>324 <td><strong>QASM Representation:</strong></td><td>{row['qasm_code']}</td>325 </tr>326 <tr>327 <td><strong>Trotter Decomposition:</strong></td><td>{row['trotter_code']}</td>328 </tr>329 <tr>330 <td><strong>Number of Qubits:</strong></td><td>{row['num_qubits']}</td>331 </tr>332 <tr>333 <td><strong>Trotter Order:</strong></td><td>{row['trotter_order']}</td>334 </tr>335 <tr>336 <td><strong>Timestamp:</strong></td><td>{row['timestamp']}</td>337 </tr>338 </table>339 </td>340 </tr>341 </table>342 """)343 344 return "".join(html_content)345 346# Function to generate Hamiltonians347def generate_hamiltonians(num_hamiltonians, selected_qubits, selected_order):348 results_table = []349 timestamp = str(datetime.now())350 351 for i in range(num_hamiltonians):352 num_qubits = random.choice(selected_qubits)353 order = selected_order354 hamiltonian = generate_random_hamiltonian(num_qubits)355 qasm_code = hamiltonian_to_qasm(hamiltonian, num_qubits)356 trotter_code = trotter_decomposition(hamiltonian, order)357 358 # Generate Pennylane circuit from QASM code359 circuit = qasm_to_pennylane(qasm_code)360 361 # Draw the Pennylane circuit and save as an image362 fig, ax = qml.draw_mpl(circuit)()363 circuit_plot_image = buffer_plot_and_get(fig)364 circuit_plot_bytes = pil_image_to_bytes(circuit_plot_image)365 366 # Append data to results table367 results_table.append((i + 1, circuit_plot_bytes, hamiltonian, qasm_code, trotter_code, num_qubits, order, timestamp))368 369 370# Function for Trotter decomposition371def trotter_decomposition(hamiltonian, order):372 terms = hamiltonian.split(" + ")373 trotter_steps = []374 375 for term in terms:376 coeff, *pauli_ops = term.split(" * ")377 coeff = float(coeff)378 for _ in range(order):379 trotter_steps.append(f"exp({coeff / order}) * ({' * '.join(pauli_ops)})")380 for _ in range(order):381 trotter_steps.append(f"exp({-coeff / order}) * ({' * '.join(pauli_ops)})")382 383 return " + ".join(trotter_steps)384 385# def export_parquet(db_file):386# """Export DuckDB table to a Parquet file using COPY."""387# try:388# conn = duckdb.connect(database=db_file)389# parquet_file = f"quantum_problems_{datetime.now().strftime('%Y%m%d_%H%M%S')}.parquet"390# conn.execute(f"COPY problems TO '{parquet_file}' (FORMAT PARQUET);")391# conn.close()392# return f"Data successfully exported to Parquet file: {parquet_file}"393# except Exception as e:394# return f"Error exporting to Parquet: {e}"395 396def export_parquet(db_file):397 """Export DuckDB table to a Parquet file using COPY."""398 try:399 conn = duckdb.connect(database=db_file)400 parquet_file = f"quantum_problems_{datetime.now().strftime('%Y%m%d_%H%M%S')}.parquet"401 conn.execute(f"""402 COPY (403 SELECT 404 uuid,405 CAST(timestamp AS VARCHAR) AS timestamp, 406 problem, 407 sub_domain, 408 main_domain, 409 model_name, 410 solution, 411 solution_model_name412 FROM problems413 ) TO '{parquet_file}' (FORMAT PARQUET);414 """)415 conn.close()416 df = pd.read_parquet(parquet_file)417 df['timestamp'] = df['timestamp'].astype(str) 418 df.to_parquet(parquet_file, index=False)419 420 return f"Data successfully exported to Parquet file: {parquet_file}"421 except Exception as e:422 return f"Error exporting to Parquet: {e}"423 424def generate_dynamic_prompt(selected_domains):425 if not selected_domains:426 raise ValueError("No domains selected. Please select at least one domain.")427 # Select a single domain randomly428 selected_domain = random.choice(selected_domains)429 430 # Retrieve the description and template431 domain_details = quantum_problem_domains[selected_domain]432 domain_description = domain_details["description"]433 example_output = domain_details["template"]434 RESPONSE_INSTRUCTION_LLM_PROMPT = f"""435 Generate a single detailed quantum physics problem for an exam in LaTeX format. Do not solve the problem. 436 Do not include additional explanations or comments outside of LaTeX, and avoid unnecessary LaTeX imports (e.g., \\documentclass{{}}, \\usepackage{{}}, or \\begin{{document}}). 437 All mathematical equations and symbols must strictly be in LaTeX. 438 Your response must strictly follow this provided format:439 1) {{Problem:}} Clearly define the quantum physics problem here, using mathematical precision and LaTeX formatting. Provide any equations or detailed descriptions necessary for students to understand and solve the problem.440 2) {{Domain:}} Provide a concise two-word domain description in CAPS such as "ISING HAMILTONIAN".441 Do not solve the problem!. The problem must strictly adhere to one and only one of the following domain types:442 {domain_description}443 Example Response Output:444 {example_output}445 """446 return RESPONSE_INSTRUCTION_LLM_PROMPT, selected_domain447 448# Function to generate a quantum physics problem449def generate_problem(pair_id, model_name, selected_domains):450 try:451 prompt, selected_domain = generate_dynamic_prompt(selected_domains)452 453 messages = [454 {"role": "system", "content": "You are a quantum physics professor and an expert in quantum computing."},455 {"role": "user", "content": prompt}456 ]457 text = tokenizer.apply_chat_template(458 messages,459 tokenize=False,460 add_generation_prompt=True461 )462 model_inputs = tokenizer([text], return_tensors="pt").to(model.device)463 464 generated_ids = model.generate(465 **model_inputs,466 max_new_tokens=10024467 )468 generated_ids = [469 output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)470 ]471 response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]472 473 if "{Problem:}" not in response or "{Domain:}" not in response:474 raise ValueError(f"Generated problem does not match the expected format. Response:\n{response}")475 476 problem = response.split("{Problem:}")[1].split("{Domain:}")[0].strip()477 sub_domain = response.split("{Domain:}")[1].strip() 478 479 # Insert the problem into DuckDB480 conn = duckdb.connect(database=DB_FILE)481 conn.execute("""482 INSERT INTO problems (uuid, timestamp, problem, main_domain, sub_domain, model_name)483 VALUES (?, ?, ?, ?, ?, ?)484 """, (str(uuid.uuid4()), datetime.now().isoformat(), problem, selected_domain, sub_domain, model_name.split("/")[-1]))485 conn.close()486 487 # print(response)488 return response, selected_domain489 except Exception as e:490 print(f"Error generating problem {pair_id}: {e}")491 return None, None492 493def generate_multiple_problems(num_pairs, selected_domains):494 if not selected_domains:495 return "Please select at least one domain type."496 497 conn = duckdb.connect(database=DB_FILE)498 current_count = conn.execute("SELECT COUNT(*) FROM problems").fetchone()[0]499 conn.close()500 501 # Prepare a descriptive header for TQDM502 model_name = selected_model.split("/")[-1]503 domain_list = ", ".join(selected_domains[:3]) # Include up to 3 domains for brevity504 505 tqdm_desc = f"Generating Instructions - Model: {model_name} | Total: {num_pairs}"506 507 responses = []508 with tqdm(total=num_pairs, desc=tqdm_desc, unit="problem") as pbar:509 for i in range(num_pairs):510 response, selected_domain = generate_problem(current_count + i + 1, selected_model, selected_domains)511 if response:512 responses.append(response)513 pbar.set_postfix_str(f"Last Domain: {selected_domain}") # Updates progress bar with last domain514 pbar.update(1)515 516 return "\n\n".join(responses)517 518 519def generate_solutions_pqt(solution_model_name):520 df = load_parquet()521 unsolved_problems = df[df["solution"].isna()]522 523 if unsolved_problems.empty:524 return "No unsolved problems found in the database."525 526 with tqdm(total=len(unsolved_problems), desc="Generating Solutions", unit="solution") as pbar:527 for _, row in unsolved_problems.iterrows():528 try:529 solution_prompt = RESPONSE_SOLUTION_LLM_USR_PROMPT.format(problem=row["problem"])530 531 messages = [532 {"role": "system", "content": RESPONSE_SOLUTION_LLM_SYS_PROMPT},533 {"role": "user", "content": solution_prompt}534 ]535 text = solution_tokenizer.apply_chat_template(536 messages,537 tokenize=False,538 add_generation_prompt=True539 )540 model_inputs = solution_tokenizer([text], return_tensors="pt").to(solution_model_instance.device)541 542 generated_ids = solution_model_instance.generate(543 **model_inputs,544 max_new_tokens=10024545 )546 generated_ids = [547 output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)548 ]549 solution = solution_tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]550 551 # Update solution in Parquet552 update_solution_pqt(row["uuid"], solution, solution_model_name.split("/")[-1])553 except Exception as e:554 print(f"Error generating solution for problem {row['uuid']}: {e}")555 pbar.update(1)556 return "Solutions generated successfully!"557 558def generate_solutions(solution_model_name):559 conn = duckdb.connect(database=DB_FILE)560 problems = conn.execute("SELECT uuid, problem FROM problems WHERE solution IS NULL").fetchall()561 562 if not problems:563 return "No unsolved problems found in the database."564 565 # Prepare a descriptive header for TQDM566 model_name = solution_model_name.split("/")[-1]567 total_problems = len(problems)568 tqdm_desc = f"Solution Model: {model_name} | Total Problems: {total_problems}"569 570 with tqdm(total=total_problems, desc=tqdm_desc, unit="solution") as pbar:571 for problem_id, problem_text in problems:572 try:573 solution_prompt = RESPONSE_SOLUTION_LLM_USR_PROMPT.format(problem=problem_text)574 575 messages = [576 {"role": "system", "content": RESPONSE_SOLUTION_LLM_SYS_PROMPT},577 {"role": "user", "content": solution_prompt}578 ]579 text = solution_tokenizer.apply_chat_template(580 messages,581 tokenize=False,582 add_generation_prompt=True583 )584 model_inputs = solution_tokenizer([text], return_tensors="pt").to(solution_model_instance.device)585 586 generated_ids = solution_model_instance.generate(587 **model_inputs,588 max_new_tokens=10024589 )590 generated_ids = [591 output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)592 ]593 solution = solution_tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]594 595 # Update the database with the generated solution596 conn.execute("""597 UPDATE problems598 SET solution = ?, solution_model_name = ?599 WHERE uuid = ?600 """, (solution, model_name, problem_id))601 602 # Update progress bar with the last processed problem ID603 pbar.set_postfix_str(f"Last Problem UUID: {problem_id}")604 except Exception as e:605 print(f"Error generating solution for problem {problem_id}: {e}")606 pbar.update(1)607 conn.close()608 return "Solutions generated successfully!"609 610 611# Load problems from DuckDB612def load_problems_from_duckdb():613 """Load all problems and solutions from the DuckDB database."""614 conn = duckdb.connect(database=DB_FILE)615 df = conn.execute("SELECT * FROM problems").df()616 conn.close()617 return df618 619# Load summary from DuckDB620def load_summary_from_duckdb():621 conn = duckdb.connect(database=DB_FILE)622 623 # Total number of problems624 total_problems = conn.execute("SELECT COUNT(*) FROM problems").fetchone()[0]625 626 # Count of distinct domains627 distinct_domains_count = conn.execute("SELECT COUNT(DISTINCT main_domain) FROM problems").fetchone()[0]628 629 # Problems by model630 problems_by_model = conn.execute("SELECT model_name, COUNT(*) as count FROM problems GROUP BY model_name").fetchall()631 conn.close()632 633 # Build the summary634 summary = f"<h3>Total Problems: {total_problems}</h3>"635 summary += f"<h4>Distinct Domains: {distinct_domains_count}</h4>"636 637 summary += "<h4>Problems by Model:</h4><ul>"638 for model_name, count in problems_by_model:639 summary += f"<li>{model_name}: {count}</li>"640 summary += "</ul>"641 642 return summary643 