Spaces:
Build error
Build error
Delete app.py
Browse files
app.py
DELETED
@@ -1,299 +0,0 @@
|
|
1 |
-
|
2 |
-
import math
|
3 |
-
from dataclasses import asdict, dataclass
|
4 |
-
from typing import Dict, List, Optional, Tuple
|
5 |
-
|
6 |
-
import gradio as gr
|
7 |
-
import pandas as pd
|
8 |
-
import plotly.express as px
|
9 |
-
|
10 |
-
# External dependency:
|
11 |
-
# pip install periodictable
|
12 |
-
from periodictable import elements
|
13 |
-
|
14 |
-
# -----------------------------
|
15 |
-
# Data extraction helpers
|
16 |
-
# -----------------------------
|
17 |
-
NUMERIC_PROPS = [
|
18 |
-
("mass", "Atomic mass (u)"),
|
19 |
-
("density", "Density (g/cm^3)"),
|
20 |
-
("electronegativity", "Pauling electronegativity"),
|
21 |
-
("boiling_point", "Boiling point (K)"),
|
22 |
-
("melting_point", "Melting point (K)"),
|
23 |
-
("vdw_radius", "van der Waals radius (pm)"),
|
24 |
-
("covalent_radius", "Covalent radius (pm)"),
|
25 |
-
]
|
26 |
-
|
27 |
-
# Some curated quick facts. We'll augment with group-based facts so every element gets at least one.
|
28 |
-
CURATED_FACTS: Dict[str, List[str]] = {
|
29 |
-
"H": ["Lightest element; ~74% of the visible universe by mass is hydrogen in stars."],
|
30 |
-
"He": ["Inert, used in cryogenics and balloons; second lightest element."],
|
31 |
-
"Li": ["Batteries MVP: lithium-ion cells power phones and EVs."],
|
32 |
-
"C": ["Backbone of life; diamond and graphite are pure carbon with wildly different properties."],
|
33 |
-
"N": ["~78% of Earth's atmosphere is nitrogen (mostly N₂)."],
|
34 |
-
"O": ["Essential for respiration; ~21% of Earth's atmosphere."],
|
35 |
-
"Na": ["Sodium metal reacts violently with water—handle only under oil or inert gas."],
|
36 |
-
"Mg": ["Burns with a bright white flame; used in flares and fireworks."],
|
37 |
-
"Al": ["Light and strong; forms a protective oxide layer that resists corrosion."],
|
38 |
-
"Si": ["Silicon is the basis of modern electronics—hello, semiconductors."],
|
39 |
-
"Cl": ["Powerful disinfectant; elemental chlorine is toxic, compounds are widely useful."],
|
40 |
-
"Ar": ["Argon is used to provide inert atmospheres for welding and 3D printing."],
|
41 |
-
"Fe": ["Core of steel; iron is essential in hemoglobin for oxygen transport."],
|
42 |
-
"Cu": ["Excellent electrical conductor; iconic blue-green patina (verdigris)."],
|
43 |
-
"Ag": ["Highest electrical conductivity of all metals; historically used as currency."],
|
44 |
-
"Au": ["Very unreactive ('noble'); prized for electronics and jewelry."],
|
45 |
-
"Hg": ["Only metal that's liquid at room temperature; toxic—use with care."],
|
46 |
-
"Pb": ["Dense and malleable; toxicity led to phase-out from gasoline and paints."],
|
47 |
-
"U": ["Radioactive; used as nuclear reactor fuel (U-235)."],
|
48 |
-
"Pu": ["Man-made in quantity; key in certain nuclear technologies."],
|
49 |
-
"F": ["Most electronegative element; extremely reactive."],
|
50 |
-
"Ne": ["Neon glows striking red-orange in discharge tubes—classic signs."],
|
51 |
-
"Xe": ["Xenon makes bright camera flashes and high-intensity lamps."],
|
52 |
-
}
|
53 |
-
|
54 |
-
GROUP_FACTS = {
|
55 |
-
"alkali": "Alkali metal: very reactive soft metal; forms +1 cations and reacts with water.",
|
56 |
-
"alkaline-earth": "Alkaline earth metal: reactive (less than Group 1); forms +2 cations.",
|
57 |
-
"transition": "Transition metal: often good catalysts, colorful compounds, multiple oxidation states.",
|
58 |
-
"post-transition": "Post-transition metal: softer metals with lower melting points than transition metals.",
|
59 |
-
"metalloid": "Metalloid: properties between metals and nonmetals; often semiconductors.",
|
60 |
-
"nonmetal": "Nonmetal: tends to form covalent compounds; wide range of roles in biology and materials.",
|
61 |
-
"halogen": "Halogen: very reactive nonmetals; form salts with metals and −1 oxidation state.",
|
62 |
-
"noble-gas": "Noble gas: chemically inert under most conditions; monatomic gases.",
|
63 |
-
"lanthanide": "Lanthanide: f-block rare earths; notable for magnets, lasers, and phosphors.",
|
64 |
-
"actinide": "Actinide: radioactive f-block; includes nuclear fuel materials.",
|
65 |
-
}
|
66 |
-
|
67 |
-
# Map periodictable categories into the above buckets
|
68 |
-
def classify_category(el) -> str:
|
69 |
-
try:
|
70 |
-
if el.block == "s" and el.group == 1 and el.number != 1:
|
71 |
-
return "alkali"
|
72 |
-
if el.block == "s" and el.group == 2:
|
73 |
-
return "alkaline-earth"
|
74 |
-
if el.block == "p" and el.group in (13, 14, 15, 16) and el.metallic:
|
75 |
-
return "post-transition"
|
76 |
-
if el.block == "d":
|
77 |
-
return "transition"
|
78 |
-
if el.block == "p" and el.group == 17:
|
79 |
-
return "halogen"
|
80 |
-
if el.block == "p" and el.group == 18:
|
81 |
-
return "noble-gas"
|
82 |
-
if el.block == "p" and not el.metallic:
|
83 |
-
return "nonmetal"
|
84 |
-
if el.block == "f" and 57 <= el.number <= 71:
|
85 |
-
return "lanthanide"
|
86 |
-
if el.block == "f" and 89 <= el.number <= 103:
|
87 |
-
return "actinide"
|
88 |
-
except Exception:
|
89 |
-
pass
|
90 |
-
return "nonmetal" if not getattr(el, "metallic", False) else "post-transition"
|
91 |
-
|
92 |
-
# Build a dataframe of elements
|
93 |
-
def build_elements_df() -> pd.DataFrame:
|
94 |
-
rows = []
|
95 |
-
for Z in range(1, 119):
|
96 |
-
el = elements[Z]
|
97 |
-
if el is None:
|
98 |
-
continue
|
99 |
-
data = {
|
100 |
-
"Z": el.number,
|
101 |
-
"symbol": el.symbol,
|
102 |
-
"name": el.name.title(),
|
103 |
-
"period": getattr(el, "period", None),
|
104 |
-
"group": getattr(el, "group", None),
|
105 |
-
"block": getattr(el, "block", None),
|
106 |
-
"mass": getattr(el, "mass", None),
|
107 |
-
"density": getattr(el, "density", None),
|
108 |
-
"electronegativity": getattr(el, "electronegativity", None),
|
109 |
-
"boiling_point": getattr(el, "boiling_point", None),
|
110 |
-
"melting_point": getattr(el, "melting_point", None),
|
111 |
-
"vdw_radius": getattr(el, "vdw_radius", None),
|
112 |
-
"covalent_radius": getattr(el, "covalent_radius", None),
|
113 |
-
"category": classify_category(el),
|
114 |
-
"is_radioactive": bool(getattr(el, "radioactive", False)),
|
115 |
-
}
|
116 |
-
rows.append(data)
|
117 |
-
df = pd.DataFrame(rows).sort_values("Z").reset_index(drop=True)
|
118 |
-
return df
|
119 |
-
|
120 |
-
DF = build_elements_df()
|
121 |
-
|
122 |
-
# Layout positions: group (1-18) x period (1-7); f-block as two rows
|
123 |
-
MAX_GROUP = 18
|
124 |
-
MAX_PERIOD = 7
|
125 |
-
|
126 |
-
GRID: List[List[Optional[int]]] = [[None for _ in range(MAX_GROUP)] for _ in range(MAX_PERIOD)]
|
127 |
-
for _, row in DF.iterrows():
|
128 |
-
period, group, Z = int(row["period"]), row["group"], int(row["Z"])
|
129 |
-
if group is None:
|
130 |
-
continue
|
131 |
-
GRID[period-1][group-1] = Z
|
132 |
-
|
133 |
-
# f-block positions (lanthanides/actinides) - show in separate rows
|
134 |
-
LAN = [z for z in DF["Z"] if 57 <= z <= 71]
|
135 |
-
ACT = [z for z in DF["Z"] if 89 <= z <= 103]
|
136 |
-
|
137 |
-
# -----------------------------
|
138 |
-
# UI callbacks
|
139 |
-
# -----------------------------
|
140 |
-
|
141 |
-
def element_info(z_or_symbol: str):
|
142 |
-
# Accept atomic number or symbol
|
143 |
-
try:
|
144 |
-
if z_or_symbol.isdigit():
|
145 |
-
Z = int(z_or_symbol)
|
146 |
-
el = elements[Z]
|
147 |
-
else:
|
148 |
-
el = elements.symbol(z_or_symbol)
|
149 |
-
Z = el.number
|
150 |
-
except Exception:
|
151 |
-
return f\"Unknown element: {z_or_symbol}\", None, None
|
152 |
-
|
153 |
-
row = DF.loc[DF['Z'] == Z].iloc[0].to_dict()
|
154 |
-
symbol = row['symbol']
|
155 |
-
# Build facts
|
156 |
-
facts = []
|
157 |
-
facts.extend(CURATED_FACTS.get(symbol, []))
|
158 |
-
facts.append(GROUP_FACTS.get(row['category'], None))
|
159 |
-
facts = [f for f in facts if f]
|
160 |
-
|
161 |
-
# Properties text
|
162 |
-
props_lines = [
|
163 |
-
f\"{row['name']} ({symbol}), Z = {Z}\",
|
164 |
-
f\"Period {int(row['period'])}, Group {row['group']}, Block {row['block']} | Category: {row['category'].replace('-', ' ').title()}\",
|
165 |
-
f\"Atomic mass: {row['mass'] if row['mass'] else '—'} u\",
|
166 |
-
f\"Density: {row['density'] if row['density'] else '—'} g/cm³\",
|
167 |
-
f\"Electronegativity: {row['electronegativity'] if row['electronegativity'] else '—'} (Pauling)\",
|
168 |
-
f\"Melting point: {row['melting_point'] if row['melting_point'] else '—'} K | Boiling point: {row['boiling_point'] if row['boiling_point'] else '—'} K\",
|
169 |
-
f\"vdW radius: {row['vdw_radius'] if row['vdw_radius'] else '—'} pm | Covalent radius: {row['covalent_radius'] if row['covalent_radius'] else '—'} pm\",
|
170 |
-
f\"Radioactive: {'Yes' if row['is_radioactive'] else 'No'}\",
|
171 |
-
]
|
172 |
-
info_text = \"\\n\".join(props_lines)
|
173 |
-
facts_text = \"\\n• \".join([\"Interesting facts:\"] + facts) if facts else \"No fact on file—still cool though!\"
|
174 |
-
|
175 |
-
# Trend plot (Atomic number vs selected property)
|
176 |
-
# We'll default to electronegativity if available, else mass.
|
177 |
-
prop_key = 'electronegativity' if not pd.isna(row['electronegativity']) else 'mass'
|
178 |
-
label = dict(NUMERIC_PROPS)[prop_key]
|
179 |
-
trend_df = DF[['Z', 'symbol', prop_key]].dropna()
|
180 |
-
fig = px.scatter(
|
181 |
-
trend_df, x='Z', y=prop_key, hover_name='symbol', title=f'{label} across the periodic table',
|
182 |
-
)
|
183 |
-
# Highlight selected element
|
184 |
-
fig.add_scatter(x=[Z], y=[row[prop_key]] if row[prop_key] else [None],
|
185 |
-
mode='markers+text', text=[symbol], textposition='top center')
|
186 |
-
|
187 |
-
return info_text, facts_text, fig
|
188 |
-
|
189 |
-
def handle_button_click(z: int):
|
190 |
-
return element_info(str(z))
|
191 |
-
|
192 |
-
def search_element(query: str):
|
193 |
-
query = (query or '').strip()
|
194 |
-
if not query:
|
195 |
-
return gr.update(), gr.update(), gr.update()
|
196 |
-
return element_info(query)
|
197 |
-
|
198 |
-
def heatmap(property_key: str):
|
199 |
-
prop_label = dict(NUMERIC_PROPS)[property_key]
|
200 |
-
# Create a pseudo-2D matrix for the s/p/d blocks (7x18) with property values
|
201 |
-
import numpy as np
|
202 |
-
grid_vals = np.full((MAX_PERIOD, MAX_GROUP), None, dtype=object)
|
203 |
-
for r in range(MAX_PERIOD):
|
204 |
-
for c in range(MAX_GROUP):
|
205 |
-
z = GRID[r][c]
|
206 |
-
if z is None:
|
207 |
-
continue
|
208 |
-
val = DF.loc[DF['Z'] == z, property_key].values[0]
|
209 |
-
grid_vals[r, c] = val if not pd.isna(val) else None
|
210 |
-
|
211 |
-
fig = px.imshow(
|
212 |
-
grid_vals.astype(float),
|
213 |
-
origin='upper',
|
214 |
-
labels=dict(color=prop_label, x='Group', y='Period'),
|
215 |
-
x=list(range(1, MAX_GROUP+1)),
|
216 |
-
y=list(range(1, MAX_PERIOD+1)),
|
217 |
-
title=f'Periodic heatmap: {prop_label}',
|
218 |
-
aspect='auto',
|
219 |
-
color_continuous_scale='Viridis'
|
220 |
-
)
|
221 |
-
return fig
|
222 |
-
|
223 |
-
# -----------------------------
|
224 |
-
# Build UI
|
225 |
-
# -----------------------------
|
226 |
-
with gr.Blocks(title="Interactive Periodic Table", css=\"\"\"
|
227 |
-
.button-cell {min-width: 40px; height: 40px; padding: 0.25rem; font-weight: 600;}
|
228 |
-
.symbol {font-size: 0.95rem;}
|
229 |
-
.small {font-size: 0.7rem; opacity: 0.8;}
|
230 |
-
.grid {display: grid; grid-template-columns: repeat(18, 1fr); gap: 4px;}
|
231 |
-
.fgrid {display: grid; grid-template-columns: repeat(15, 1fr); gap: 4px;}
|
232 |
-
.header {text-align:center; font-weight:700; margin: 0.5rem 0;}
|
233 |
-
\"\"\") as demo:
|
234 |
-
gr.Markdown(\"# 🧪 Interactive Periodic Table\\nClick an element or search by symbol/name/atomic number.\")
|
235 |
-
|
236 |
-
with gr.Row():
|
237 |
-
with gr.Column(scale=2):
|
238 |
-
gr.Markdown(\"### Main Table\")
|
239 |
-
main_buttons = []
|
240 |
-
with gr.Group():
|
241 |
-
with gr.Row():
|
242 |
-
gr.HTML('<div class=\"grid\">' + ''.join([f'<div class=\"header\">{g}</div>' for g in range(1, 19)]) + '</div>')
|
243 |
-
# Build button grid
|
244 |
-
rows = []
|
245 |
-
for r in range(MAX_PERIOD):
|
246 |
-
with gr.Row():
|
247 |
-
cells = []
|
248 |
-
for c in range(MAX_GROUP):
|
249 |
-
z = GRID[r][c]
|
250 |
-
if z is None:
|
251 |
-
btn = gr.Button(\"\", elem_classes=[\"button-cell\"])
|
252 |
-
btn.click(lambda: (gr.update(), gr.update(), gr.update()))
|
253 |
-
else:
|
254 |
-
sym = DF.loc[DF['Z'] == z, 'symbol'].values[0]
|
255 |
-
btn = gr.Button(sym, elem_classes=[\"button-cell\"])
|
256 |
-
btn.click(handle_button_click, inputs=[gr.Number(z, visible=False)], outputs=[
|
257 |
-
gr.Textbox(interactive=False), gr.Markdown(), gr.Plot()])
|
258 |
-
cells.append(btn)
|
259 |
-
rows.append(cells)
|
260 |
-
gr.Markdown(\"### f-block (lanthanides & actinides)\")
|
261 |
-
with gr.Row():
|
262 |
-
# Lanthanides row
|
263 |
-
lan_buttons = []
|
264 |
-
for z in LAN:
|
265 |
-
sym = DF.loc[DF['Z'] == z, 'symbol'].values[0]
|
266 |
-
btn = gr.Button(sym, elem_classes=[\"button-cell\"])
|
267 |
-
btn.click(handle_button_click, inputs=[gr.Number(z, visible=False)], outputs=[
|
268 |
-
gr.Textbox(interactive=False), gr.Markdown(), gr.Plot()])
|
269 |
-
lan_buttons.append(btn)
|
270 |
-
with gr.Row():
|
271 |
-
# Actinides row
|
272 |
-
act_buttons = []
|
273 |
-
for z in ACT:
|
274 |
-
sym = DF.loc[DF['Z'] == z, 'symbol'].values[0]
|
275 |
-
btn = gr.Button(sym, elem_classes=[\"button-cell\"])
|
276 |
-
btn.click(handle_button_click, inputs=[gr.Number(z, visible=False)], outputs=[
|
277 |
-
gr.Textbox(interactive=False), gr.Markdown(), gr.Plot()])
|
278 |
-
act_buttons.append(btn)
|
279 |
-
|
280 |
-
with gr.Column(scale=1):
|
281 |
-
gr.Markdown(\"### Inspector\")
|
282 |
-
search = gr.Textbox(label=\"Search (symbol/name/Z)\", placeholder=\"e.g., C, Iron, 79\" )
|
283 |
-
info = gr.Textbox(label=\"Properties\", lines=10, interactive=False)
|
284 |
-
facts = gr.Markdown(\"Select an element to see fun facts.\")
|
285 |
-
trend = gr.Plot()
|
286 |
-
|
287 |
-
search.submit(search_element, inputs=[search], outputs=[info, facts, trend])
|
288 |
-
|
289 |
-
gr.Markdown(\"### Trend heatmap\")
|
290 |
-
prop = gr.Dropdown(choices=[k for k, _ in NUMERIC_PROPS], value=\"electronegativity\", label=\"Property\")
|
291 |
-
heat = gr.Plot()
|
292 |
-
prop.change(heatmap, inputs=[prop], outputs=[heat])
|
293 |
-
# Initialize
|
294 |
-
heat.update(heatmap(\"electronegativity\"))
|
295 |
-
|
296 |
-
gr.Markdown(\"---\\nBuilt with **Gradio** + **periodictable**. Data completeness varies by element; some values may be missing.\")
|
297 |
-
|
298 |
-
if __name__ == \"__main__\":
|
299 |
-
demo.launch()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|