Emanrashid7's picture
Update app.py
1f210dd verified
import numpy as np
import matplotlib.pyplot as plt
import gradio as gr
import pandas as pd
from datetime import datetime
from fpdf import FPDF
import tempfile
import os
from scipy.optimize import minimize
# =============================================
# Constants & Mineral Densities
# =============================================
minerals = ['Gold', 'Ilmenite', 'Rutile', 'Monazite']
densities = {'Gold': 19.3, 'Ilmenite': 4.7, 'Rutile': 4.2, 'Monazite': 5.2}
max_density = 19.3
default_feed = {
'Gold': 3.74, 'Ilmenite': 5.625, 'Rutile': 1.35, 'Monazite': 0.6
}
# =============================================
# Physical-inspired Digital Twin Model (Original)
# =============================================
def shaking_table_model(params, feed_grades, feed_rate_tph):
stroke_len = params['stroke_len']
freq = params['freq']
tilt = params['tilt']
wash_water = params['wash_water']
base_yield_conc = 50 - 3*(tilt - 5) - 0.5*(wash_water - 12)
yield_conc = np.clip(base_yield_conc + 0.05*(300 - freq), 10, 45)
yield_midd = yield_conc * 1.2
yield_tail = 100 - yield_conc - yield_midd
if yield_tail < 0:
yield_tail = 10
yield_midd = 90 - yield_conc
model_rec = {}
for minrl in minerals:
rho_norm = densities[minrl] / max_density
stroke_effect = 100 * (1 - 0.5 * ((stroke_len - 18)/10)**2)
freq_effect = 100 * (freq / 350)
tilt_effect = 100 * (1 - 0.8 * ((tilt - 5)/3)**2)
wash_effect = 100 * (1 - 0.6 * ((wash_water - 12)/8)**2)
rec_conc = 50 + rho_norm * (stroke_effect + freq_effect + tilt_effect + wash_effect - 200)
rec_conc = np.clip(rec_conc, 30, 95)
rec_midd = 100 - rec_conc - (100 - rec_conc) * 0.4
rec_tail = 100 - rec_conc - rec_midd
if rec_midd < 0:
rec_midd = 0
rec_tail = 100 - rec_conc
model_rec[minrl] = {'Conc': rec_conc, 'Midd': rec_midd, 'Tail': rec_tail}
y_conc_frac = yield_conc / 100.0
mass_rate_conc = feed_rate_tph * y_conc_frac
grades_conc = {}
rates_conc = {}
for m in minerals:
rec = model_rec[m]['Conc'] / 100.0
mineral_feed_rate = feed_rate_tph * feed_grades[m]
rate_conc = mineral_feed_rate * rec
rates_conc[m] = rate_conc
if m == 'Gold':
grades_conc[m] = (rate_conc / mass_rate_conc) * 1e6 if mass_rate_conc > 0 else 0
else:
grades_conc[m] = (rate_conc / mass_rate_conc) * 100 if mass_rate_conc > 0 else 0
return model_rec, yield_conc, yield_midd, yield_tail, grades_conc, rates_conc
# =============================================
# Inverse Model (Original)
# =============================================
def find_parameters_for_targets(target_rates_conc, feed_grades, feed_rate_tph):
def error(p):
params = {'stroke_len': p[0], 'freq': p[1], 'tilt': p[2], 'wash_water': p[3]}
_, _, _, _, _, rates = shaking_table_model(params, feed_grades, feed_rate_tph)
err = sum((rates[m] - target_rates_conc[m]) ** 2 for m in minerals)
return err
initial = [18, 300, 5, 12]
bounds = [(8,25), (250,350), (2,8), (5,20)]
res = minimize(error, initial, bounds=bounds, method='L-BFGS-B')
if res.success and res.fun < 1e-2:
return {'stroke_len': res.x[0], 'freq': res.x[1], 'tilt': res.x[2], 'wash_water': res.x[3]}, res.fun
return None, res.fun
# =============================================
# CSV Import Logic
# =============================================
def import_csv(file):
if file is None: return [gr.update()]*9
try:
df = pd.read_csv(file.name)
d = dict(zip(df.iloc[:,0], df.iloc[:,1]))
return (
d.get('feed_rate', 300), d.get('gold_feed', 3.74), d.get('ilmenite_feed', 5.625),
d.get('rutile_feed', 1.35), d.get('monazite_feed', 0.6),
d.get('target_gold', 0.001), d.get('target_ilmenite', 15.0),
d.get('target_rutile', 3.5), d.get('target_monazite', 1.5)
)
except: return [gr.update()]*9
# =============================================
# PDF Export Logic
# =============================================
def export_pdf_report(report_md, fig1, fig2):
pdf = FPDF()
pdf.add_page()
pdf.set_font("Arial", 'B', 16)
pdf.cell(200, 10, "Attock Placer Plant - Digital Twin Report", ln=True, align='C')
pdf.set_font("Arial", size=10)
pdf.ln(5)
clean_text = report_md.replace("#", "").replace("**", "").replace("|", " ")
for line in clean_text.split('\n'):
pdf.multi_cell(0, 8, line)
with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as t1, \
tempfile.NamedTemporaryFile(delete=False, suffix=".png") as t2:
fig1.savefig(t1.name, bbox_inches='tight')
fig2.savefig(t2.name, bbox_inches='tight')
pdf.add_page()
pdf.image(t1.name, x=10, y=20, w=180)
pdf.image(t2.name, x=50, y=130, w=110)
path = "Shaking_Table_Report.pdf"
pdf.output(path)
return path
# =============================================
# Main Simulation Wrapper
# =============================================
def simulate_wrapper(*args):
# This calls your original logic (simplified here to return all original components)
from datetime import datetime
# Unpack inputs
(feed_rate, g_f, i_f, r_f, m_f, s_l, fr, tl, ww, mode, tg, ti, tr, tm) = args
# Process Feed
feed_grades = {'Gold': g_f*1e-6, 'Ilmenite': i_f/100, 'Rutile': r_f/100, 'Monazite': m_f/100}
suggested_params = None
if mode == "Forward Model (Adjust Parameters)":
params = {'stroke_len': s_l, 'freq': fr, 'tilt': tl, 'wash_water': ww}
model_rec, yc, ym, yt, grades_conc, rates_conc = shaking_table_model(params, feed_grades, feed_rate)
text_mode = "Forward Mode"
else:
targets = {'Gold': tg, 'Ilmenite': ti, 'Rutile': tr, 'Monazite': tm}
suggested_params, err = find_parameters_for_targets(targets, feed_grades, feed_rate)
if suggested_params:
model_rec, yc, ym, yt, grades_conc, rates_conc = shaking_table_model(suggested_params, feed_grades, feed_rate)
text_mode = f"Inverse Mode (Error: {err:.4f})"
else: return "Optimization failed.", None, None, None
# Text Summary
text = f"### {text_mode} Results\nMass Yields: Conc {yc:.1f}%, Midd {ym:.1f}%, Tail {yt:.1f}%\n"
# Matplotlib plots (Original style)
fig1, ax1 = plt.subplots(figsize=(10,5))
x = np.arange(len(minerals))
ax1.bar(x - 0.2, [model_rec[m]['Conc'] for m in minerals], 0.2, label='Conc')
ax1.bar(x, [model_rec[m]['Midd'] for m in minerals], 0.2, label='Midd')
ax1.bar(x + 0.2, [model_rec[m]['Tail'] for m in minerals], 0.2, label='Tail')
ax1.set_xticks(x), ax1.set_xticklabels(minerals), ax1.legend(), ax1.set_title("Distribution")
fig2, ax2 = plt.subplots()
ax2.pie([yc, ym, yt], labels=['Conc', 'Midd', 'Tail'], autopct='%1.1f%%')
pdf_path = export_pdf_report(text, fig1, fig2)
return text, fig1, fig2, pdf_path
# =============================================
# Gradio Interface
# =============================================
with gr.Blocks(title="Shaking Table Digital Twin") as demo:
gr.Markdown("# Shaking Table Digital Twin & Optimizer")
with gr.Row():
with gr.Column(scale=1):
csv_in = gr.File(label="Import Feed/Target CSV", file_types=[".csv"])
mode = gr.Radio(["Forward Model (Adjust Parameters)", "Inverse: Find Parameters for Target Rates"], value="Forward Model (Adjust Parameters)", label="Mode")
with gr.Group():
gr.Markdown("### Feed Composition")
f_rate = gr.Slider(50, 500, 300, label="Feed Rate (tph)")
g_f = gr.Number(value=3.74, label="Gold (g/t)")
i_f = gr.Number(value=5.625, label="Ilmenite (%)")
r_f = gr.Number(value=1.35, label="Rutile (%)")
m_f = gr.Number(value=0.6, label="Monazite (%)")
with gr.Group():
gr.Markdown("### Mechanical Parameters")
s_l = gr.Slider(8, 25, 18, label="Stroke Length")
freq = gr.Slider(250, 350, 300, label="Frequency")
tilt = gr.Slider(2, 8, 5, label="Tilt")
wash = gr.Slider(5, 20, 12, label="Wash Water")
with gr.Group():
gr.Markdown("### Inverse Targets")
tg = gr.Number(0.001, label="Target Gold (tph)")
ti = gr.Number(15.0, label="Target Ilmenite (tph)")
tr = gr.Number(3.5, label="Target Rutile (tph)")
tm = gr.Number(1.5, label="Target Monazite (tph)")
with gr.Column(scale=2):
run_btn = gr.Button("Run Simulation", variant="primary")
out_txt = gr.Markdown()
plot1 = gr.Plot()
plot2 = gr.Plot()
pdf_file = gr.File(label="Download PDF Report")
csv_in.change(import_csv, csv_in, [f_rate, g_f, i_f, r_f, m_f, tg, ti, tr, tm])
run_btn.click(simulate_wrapper, [f_rate, g_f, i_f, r_f, m_f, s_l, freq, tilt, wash, mode, tg, ti, tr, tm], [out_txt, plot1, plot2, pdf_file])
demo.launch()