devansh152's picture
Create app.py
ef1decc verified
raw
history blame
6.07 kB
from transformers import pipeline
import gradio as gr
import networkx as nx
import sympy as sp
from collections import defaultdict
import re
from dotenv import load_dotenv
import google.generativeai as genai
import os
from gradio.themes import Ocean
load_dotenv()
API_KEY = os.getenv("GEMINI_API")
genai.configure(api_key=API_KEY)
# Initialize the Gemini Flash Model
model = genai.GenerativeModel('gemini-2.5-flash-lite-preview-06-17')
# Gradio App with Support for Multi-Reactant Networks (e.g. A + B -> AB)
# --- Parsing Functions ---
def parse_species(expr):
# e.g., "A + B" -> ["A", "B"]
return [s.strip() for s in re.split(r'\s*[\+\-]\s*', expr)]
def parse_network(input_string):
edges = []
reversible_edges = []
for part in input_string.split(','):
part = part.strip()
if '<->' in part:
lhs, rhs = part.split('<->')
lhs_species = parse_species(lhs)
rhs_species = parse_species(rhs)
reversible_edges.append((lhs_species, rhs_species))
elif '->' in part:
lhs, rhs = part.split('->')
lhs_species = parse_species(lhs)
rhs_species = parse_species(rhs)
edges.append((lhs_species, rhs_species))
return edges, reversible_edges
def build_graph(edges, reversible_edges):
G = nx.DiGraph()
for a, b in edges:
lhs = " + ".join(a)
rhs = " + ".join(b)
G.add_edge(lhs, rhs)
for a, b in reversible_edges:
lhs = " + ".join(a)
rhs = " + ".join(b)
G.add_edge(lhs, rhs)
G.add_edge(rhs, lhs)
return G
def analyze_graph(G):
return {
"nodes": list(G.nodes),
"edges": list(G.edges),
"num_nodes": G.number_of_nodes(),
"num_edges": G.number_of_edges(),
"is_cyclic": not nx.is_directed_acyclic_graph(G)
}
# --- ODE Generator for Complex Reactions ---
def mass_action_odes(edges, reversible_edges):
species = set()
odes = defaultdict(lambda: 0)
rate_counter = 1
def term(species_list):
term_expr = 1
for s in species_list:
sym = sp.symbols(s)
species.add(sym)
term_expr *= sym
return term_expr
for lhs_species, rhs_species in edges:
k = sp.symbols(f'k{rate_counter}')
rate_counter += 1
flux = k * term(lhs_species)
for s in lhs_species:
sym = sp.symbols(s)
odes[sym] -= flux
for s in rhs_species:
sym = sp.symbols(s)
odes[sym] += flux
for lhs_species, rhs_species in reversible_edges:
kf = sp.symbols(f'k{rate_counter}')
rate_counter += 1
kr = sp.symbols(f'k{rate_counter}')
rate_counter += 1
forward_flux = kf * term(lhs_species)
reverse_flux = kr * term(rhs_species)
for s in lhs_species:
sym = sp.symbols(s)
odes[sym] -= forward_flux
odes[sym] += reverse_flux
for s in rhs_species:
sym = sp.symbols(s)
odes[sym] += forward_flux
odes[sym] -= reverse_flux
return dict(odes)
def format_odes(odes):
return "\n".join([f"d{var}/dt = {sp.simplify(expr)}" for var, expr in odes.items()])
def compute_jacobian(odes):
variables = list(odes.keys())
F = sp.Matrix([odes[var] for var in variables])
J = F.jacobian(variables)
return sp.pretty(J)
def extract_network_from_image(image):
prompt = (
"Analyze this network diagram and list the network only. "
"Use reaction format like 'A + B -> C' or 'X <-> Y'. "
"List multiple reactions separated by commas."
)
gemini_response = model.generate_content([prompt, image])
return gemini_response.text.strip()
def full_process(image, text_input, query):
if text_input.strip(): # If text is given, use it
network_description = text_input.strip()
elif image is not None: # Else if image is given, extract network from image
network_description = extract_network_from_image(image)
else:
return "❌ Please provide either a network image or a textual description."
# Step 2: Process extracted/generated network
return process_network(network_description, query)
qa = pipeline("text2text-generation", model="google/flan-t5-base")
def process_network(input_string, query):
edges, reversible_edges = parse_network(input_string)
G = build_graph(edges, reversible_edges)
info = analyze_graph(G)
if 'ode' in query.lower():
ode_sys = mass_action_odes(edges, reversible_edges)
return format_odes(ode_sys)
elif 'jacobian' in query.lower():
ode_sys = mass_action_odes(edges, reversible_edges)
return f"Jacobian Matrix:\n{compute_jacobian(ode_sys)}"
elif 'variables' in query.lower():
return f"There are {info['num_nodes']} variables: {info['nodes']}"
elif 'edges' in query.lower():
return f"Edges: {info['edges']}"
elif 'cyclic' or 'cycle' in query.lower():
cycles = list(nx.simple_cycles(G))
if cycles:
cycles_str = "\n".join([" -> ".join(cycle + [cycle[0]]) for cycle in cycles])
return f"Cycles found:\n{cycles_str}"
else:
return "No cycles found."
else:
prompt = f"Given the network with nodes: {info['nodes']} and edges: {info['edges']}, answer the query: {query}"
answer = qa(prompt, max_length=128)[0]['generated_text']
return answer
iface = gr.Interface(
fn=full_process,
inputs=[
gr.Image(type="pil", label="Upload Network Image (optional)"),
gr.Textbox(label="Text Input (optional)", placeholder="Or paste network: A + B -> C, X <-> Y"),
gr.Textbox(label="Query", placeholder="Ask about ODEs, Jacobian, edges, etc.")
],
outputs="text",
title="Biological Network Analyzer",
description="Upload an image or enter network text. Then ask a query like 'Give ODEs' or 'Is it cyclic?'.",
theme=Ocean()
)
iface.launch(share=True)