Spaces:
Sleeping
Sleeping
File size: 6,065 Bytes
ef1decc |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 |
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)
|