CoolFace
Apppublic

BoltzmannEntropy/QuantumLLMInstruct

sourceHugging Faceupdated 2y agoView on Hugging Face
7likes
Q_quantum_utils.py643 linesDownload Raw Back to root
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