Spaces:
Sleeping
Sleeping
import os | |
import gradio as gr | |
import numpy as np | |
import matplotlib.pyplot as plt | |
import random | |
from typing import List | |
from rcwa import Material, Layer, LayerStack, Source, Solver | |
from smolagents import tool, CodeAgent, InferenceClientModel, stream_to_gradio | |
# --- Constants --- | |
start_wl = 0.32 | |
stop_wl = 0.80 | |
step_wl = 0.01 | |
wavelengths = np.arange(start_wl, stop_wl + step_wl, step_wl) | |
materials = ['Si', 'Si3N4', 'SiO2', 'AlN'] | |
def simulate_spectrum_10nm(layer_order: List[str]) -> List[float]: | |
""" | |
Simulates the optical transmission spectrum for a given sequence of material layers at 10nm thickness. | |
Args: | |
layer_order (List[str]): A list of material names (e.g., ["Si", "SiO2", "AlN"]) representing the order of layers in the optical stack. | |
Returns: | |
List[float]: The transmission spectrum across a predefined wavelength range. | |
""" | |
source = Source(wavelength=start_wl) | |
reflection_layer = Layer(n=1.0) | |
transmission_layer = Layer(material=Material("Si")) | |
try: | |
layers = [Layer(material=Material(m), thickness=0.01) for m in layer_order] | |
stack = LayerStack(*layers, incident_layer=reflection_layer, transmission_layer=transmission_layer) | |
solver = Solver(stack, source, (1, 1)) | |
result = solver.solve(wavelength=wavelengths) | |
return np.array(result['TTot']).tolist() | |
except Exception as e: | |
return [] | |
def simulate_spectrum_100nm(layer_order: List[str]) -> List[float]: | |
""" | |
Simulates the optical transmission spectrum for a given sequence of material layers at 100nm thickness. | |
Args: | |
layer_order (List[str]): A list of material names (e.g., ["Si", "SiO2", "AlN"]) representing the order of layers in the optical stack. | |
Returns: | |
List[float]: The transmission spectrum across a predefined wavelength range. | |
""" | |
source = Source(wavelength=start_wl) | |
reflection_layer = Layer(n=1.0) | |
transmission_layer = Layer(material=Material("Si")) | |
try: | |
layers = [Layer(material=Material(m), thickness=0.1) for m in layer_order] | |
stack = LayerStack(*layers, incident_layer=reflection_layer, transmission_layer=transmission_layer) | |
solver = Solver(stack, source, (1, 1)) | |
result = solver.solve(wavelength=wavelengths) | |
return np.array(result['TTot']).tolist() | |
except Exception as e: | |
return [] | |
def cosine_similarity(vec1: List[float], vec2: List[float]) -> float: | |
""" | |
Computes the cosine similarity between two vectors. | |
Args: | |
vec1 (List[float]): The first vector. | |
vec2 (List[float]): The second vector. | |
Returns: | |
float: A similarity score between -1 and 1. | |
""" | |
a, b = np.array(vec1), np.array(vec2) | |
return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))) | |
# --- Target Spectrum Generator --- | |
def get_target_spectrum(layer_order, thickness=0.1): | |
source = Source(wavelength=start_wl) | |
reflection_layer = Layer(n=1.0) | |
transmission_layer = Layer(material=Material("Si")) | |
try: | |
layers = [Layer(material=Material(m), thickness=thickness) for m in layer_order] | |
stack = LayerStack(*layers, incident_layer=reflection_layer, transmission_layer=transmission_layer) | |
solver = Solver(stack, source, (1, 1)) | |
result = solver.solve(wavelength=wavelengths) | |
return np.array(result['TTot']).tolist() | |
except Exception: | |
return None | |
# --- Model Setup --- | |
from smolagents import LiteLLMModel | |
openai_key = os.getenv("OPENAI_API_KEY") | |
model = LiteLLMModel(model_id="openai/gpt-4.1-mini", temperature=0, api_key=openai_key) | |
# --- Agent Setup --- | |
agent_10nm_simulator = CodeAgent( | |
tools=[simulate_spectrum_10nm], | |
model=model, | |
stream_outputs=True, | |
name="agent_10nm_simulator", | |
description="You are an AI agent that uses tools to simulate optical spectra for materials with thickness 10nm. You must provide the simulated response back. Do not provide any other information. " | |
) | |
agent_10nm_simulator.prompt_templates['managed_agent'] = { | |
"task": """You're an assistant agent named '{{name}}'. | |
You have been given this task: | |
--- | |
{{task}} | |
--- | |
Just return the result of your tool call. Do not add explanations or formatting. | |
Call a tool immediately and use `final_answer(...)` to return the result. | |
""", | |
"report": """{{final_answer}}""" # Minimal required key | |
} | |
agent_100nm_simulator = CodeAgent( | |
tools=[simulate_spectrum_100nm], | |
model=model, | |
stream_outputs=True, | |
name="agent_100nm_simulator", | |
description="You are an AI agent that uses tools to simulate optical spectra for materials with thickness 100nm. You must provide the simulated response back. Do not provide any other information." | |
) | |
agent_100nm_simulator.prompt_templates['managed_agent'] = { | |
"task": """You're an assistant agent named '{{name}}'. | |
You have been given this task: | |
--- | |
{{task}} | |
--- | |
Just return the result of your tool call. Do not add explanations or formatting. | |
Call a tool immediately and use `final_answer(...)` to return the result. | |
""", | |
"report": """{{final_answer}}""" # Minimal required key | |
} | |
coordinator = CodeAgent( | |
tools=[cosine_similarity], | |
managed_agents=[agent_10nm_simulator, agent_100nm_simulator], | |
model=model, | |
stream_outputs=True, | |
additional_authorized_imports = ["numpy"] | |
) | |
# --- Gradio UI --- | |
with gr.Blocks() as demo: | |
gr.Markdown( | |
""" | |
# π§ Multi-Agent Thin-Film Stack Optimizer (10nm + 100nm RCWA) | |
This interactive demo showcases a **multi-agent AI system** that cooperatively solves an inverse optics problem using physics-based simulation. | |
Instead of relying on a single agent or a single simulator, the **Coordinator Agent** orchestrates two specialized agents, each tuned to simulate optical spectra for different layer thicknesses (10nm and 100nm), and uses a comparison tool to evaluate results. | |
--- | |
### π€ Objective: Discover the correct layer **order** and **thickness** | |
Given only a target transmission spectrum, the multi-agent system must identify: | |
- The correct **material permutation** (from a fixed set), and | |
- The correct **thickness choice** (10nm or 100nm, uniformly applied) | |
**Constraints:** | |
- Materials: `Si`, `SiβNβ`, `SiOβ`, `AlN` (used once each) | |
- Layer thickness options: `10nm` or `100nm` | |
- Terminate when `cosine_similarity > 0.999` | |
--- | |
### 𧬠Agent Architecture Overview | |
- π§ **Coordinator Agent** (`CodeAgent | gpt-4.1-mini`): | |
Receives the target spectrum and is responsible for reasoning, selecting candidates, and delegating simulations to sub-agents. | |
- π§ **agent_10nm_simulator**: | |
Can simulate any material order using **10nm** thick layers via RCWA. | |
- π§ **agent_100nm_simulator**: | |
Can simulate any material order using **100nm** thick layers via RCWA. | |
- π **cosine_similarity tool**: | |
Measures how close a simulated spectrum is to the target. | |
--- | |
### π Whatβs Happening Under the Hood | |
1. A **random 4-layer stack** is selected from `Si`, `SiβNβ`, `SiOβ`, `AlN`, and simulated (at 100nm) to serve as the **target spectrum**. | |
2. The **Coordinator Agent** is provided the target and access to two sub-agents: | |
- `agent_10nm_simulator` for 10nm stacks | |
- `agent_100nm_simulator` for 100nm stacks | |
3. It explores permutations + thickness combinations by: | |
- Calling a simulation agent | |
- Comparing simulated output with the target using `cosine_similarity` | |
- Continuing exploration until similarity exceeds threshold | |
4. The system **halts automatically** once the optimal stack is found, and reports: | |
- The matched material order | |
- Thickness value | |
- Number of permutations tried | |
--- | |
### π Visualization | |
""" | |
) | |
gr.Markdown("### π Transmission Spectra of Layer Stack Designs for all 24 permutations of material order for thickness 100nm") | |
gr.Image(value="121_resized.png", interactive=False) | |
gr.Markdown("### π Transmission Spectra of Layer Stack Designs for all 24 permutations of material order for thickness 10nm") | |
gr.Image(value="122_resized.png", interactive=False) | |
gr.Markdown(""" | |
### π€ Multi-Agent System Overview | |
#### π§ Coordinator Agent: `CodeAgent | openai/gpt-4.1-mini` | |
- β **Authorized Imports**: `['numpy']` | |
##### π οΈ Tools: | |
| Tool | Description | Arguments | | |
|--------------------|--------------------------------------------|-------------------------------------------| | |
| `cosine_similarity`| Computes the cosine similarity between two vectors | `vec1`, `vec2`: Lists of floats | | |
#### π§ Managed Agent: `agent_10nm_simulator | CodeAgent | openai/gpt-4.1-mini` | |
- β **Authorized Imports**: `[]` | |
- π **Description**: Simulates optical spectra for **10nm** thickness. | |
##### π οΈ Tools: | |
| Tool | Description | Arguments | | |
|-----------------------|----------------------------------------------|--------------------------------------------| | |
| `simulate_spectrum_10nm` | Simulates spectrum for 10nm layers. | `layer_order`: List of materials | | |
--- | |
#### π§ Managed Agent: `agent_100nm_simulator | CodeAgent | openai/gpt-4.1-mini` | |
- β **Authorized Imports**: `[]` | |
- π **Description**: Simulates optical spectra for **100nm** thickness. | |
##### π οΈ Tools: | |
| Tool | Description | Arguments | | |
|------------------------|-----------------------------------------------|--------------------------------------------| | |
| `simulate_spectrum_100nm` | Simulates spectrum for 100nm layers. | `layer_order`: List of materials | | |
""") | |
run_btn = gr.Button("π Run Agent on Random Stack") | |
true_order = gr.Textbox(label="True Material Order") | |
prompt_box = gr.Textbox(label="Agent Prompt") | |
chatbot = gr.Chatbot(label="Agent Reasoning Stream") | |
def run_agent_streaming(): | |
true_order_val = random.sample(materials, 4) | |
target_val = get_target_spectrum(true_order_val) | |
true_order_display = ", ".join(true_order_val) | |
if target_val is None: | |
yield gr.update(value="Simulation failed"), gr.update(), gr.update() | |
return | |
prompt = f""" | |
You are the Coordinator Agent. Your objective is to identify a 4-layer material stack **order** and **thickness** that reproduces a given target optical transmission spectrum. | |
Constraints: | |
- Materials: [Si, Si3N4, SiO2, AlN] (use each exactly once) | |
- Two fixed thickness options for all layers: 10nm and 100nm | |
You have access to the following: | |
- agent_10nm_simulator: An agent that simulates a spectrum for a given material order with **10nm** layer thickness | |
- agent_100nm_simulator: An agent that simulates a spectrum for a given material order with **100nm** layer thickness | |
- cosine_similarity: Compares a predicted spectrum to the target spectrum | |
Your task: | |
1. Choose candidate layer orders and thickness options | |
2. Call the appropriate agent to simulate the spectrum | |
3. Use cosine_similarity to compare with the target | |
4. Stop when similarity exceeds 0.999 | |
5. Report the matching order, thickness, and number of attempts | |
Begin. | |
Target spectrum: {target_val} | |
""" | |
chat_history = [] | |
yield gr.update(value=true_order_display), gr.update(value=prompt), gr.update(value=[]) | |
for msg in stream_to_gradio(coordinator, task=prompt): | |
if isinstance(msg, gr.ChatMessage): | |
chat_history.append(("", msg.content)) | |
elif isinstance(msg, str): | |
if chat_history: | |
chat_history[-1] = ("", msg) | |
else: | |
chat_history.append(("", msg)) | |
yield gr.update(), gr.update(), gr.update(value=chat_history) | |
run_btn.click(fn=run_agent_streaming, inputs=[], outputs=[true_order, prompt_box, chatbot]) | |
demo.launch(debug=True) | |