Spaces:
Sleeping
Sleeping
| import numpy as np | |
| import matplotlib.pyplot as plt | |
| import gradio as gr | |
| # ============================================= | |
| # Constants & Mineral Densities (for physical influence) | |
| # ============================================= | |
| minerals = ['Gold', 'Ilmenite', 'Rutile', 'Monazite'] | |
| densities = {'Gold': 19.3, 'Ilmenite': 4.7, 'Rutile': 4.2, 'Monazite': 5.2} | |
| max_density = 19.3 # Gold for normalization | |
| # Fixed upstream feed to shaking table (as before) | |
| feed_rate_default = 300.0 # tph - but we'll scale with user input | |
| feed_to_shaking = { | |
| 'Gold': 3.744e-06, # enriched g/t fraction | |
| 'Ilmenite': 0.05625, | |
| 'Rutile': 0.0135, | |
| 'Monazite': 0.006, | |
| 'Gangue': 0.924 # approximate balance | |
| } | |
| # ============================================= | |
| # Physical-inspired Digital Twin Model | |
| # ============================================= | |
| def shaking_table_model(params, feed_grades, feed_rate_tph): | |
| stroke_len = params['stroke_len'] # mm, 8-25 | |
| freq = params['freq'] # strokes/min, 250-350 | |
| tilt = params['tilt'] # degrees, 2-8 | |
| wash_water = params['wash_water'] # arbitrary units 5-20 | |
| # Other params ignored for simplicity but can be extended | |
| # Base mass yields influenced by parameters (empirical) | |
| # Higher tilt & wash water → lower conc yield (sharper separation) | |
| 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) | |
| # Middlings ~ proportional | |
| yield_midd = yield_conc * 1.2 | |
| yield_tail = 100 - yield_conc - yield_midd | |
| if yield_tail < 0: | |
| yield_tail = 10 | |
| yield_midd = 100 - yield_conc - yield_tail | |
| model_rec = {} | |
| for minrl in minerals: | |
| rho_norm = densities[minrl] / max_density | |
| # Higher density → higher recovery to conc | |
| # Optimal around stroke 18mm, freq 300, tilt 5°, wash 12 | |
| 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_tail = 100 - rec_conc - 20 # midd approx | |
| rec_midd = 100 - rec_conc - rec_tail | |
| if rec_midd < 0: | |
| rec_midd = 0 | |
| rec_tail = 100 - rec_conc | |
| model_rec[minrl] = {'Conc': rec_conc, 'Midd': rec_midd, 'Tail': rec_tail} | |
| # Compute grades & rates | |
| 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 = feed_rate_tph * feed_grades[m] | |
| rate_conc = mineral_feed * 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 | |
| # ============================================= | |
| # Target Rates Mode (inverse: adjust params to hit user target rates) | |
| # ============================================= | |
| def find_parameters_for_targets(target_rates_conc, feed_grades, feed_rate_tph): | |
| # target_rates_conc dict mineral: tph to conc | |
| 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 = 0 | |
| for m in minerals: | |
| err += (rates[m] - target_rates_conc[m]) ** 2 | |
| return err | |
| from scipy.optimize import minimize | |
| 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: | |
| best_params = {'stroke_len': res.x[0], 'freq': res.x[1], 'tilt': res.x[2], 'wash_water': res.x[3]} | |
| return best_params, res.fun | |
| else: | |
| return None, 1e6 | |
| # ============================================= | |
| # Main Simulation Function | |
| # ============================================= | |
| def simulate( | |
| feed_rate_tph, | |
| gold_feed_gpt, ilmenite_feed_wt, rutile_feed_wt, monazite_feed_wt, | |
| stroke_len, freq, tilt, wash_water, | |
| mode, # "forward" or "target" | |
| target_gold_tph, target_ilmenite_tph, target_rutile_tph, target_monazite_tph | |
| ): | |
| # User-defined feed grades | |
| feed_grades = { | |
| 'Gold': gold_feed_gpt * 1e-6, | |
| 'Ilmenite': ilmenite_feed_wt / 100, | |
| 'Rutile': rutile_feed_wt / 100, | |
| 'Monazite': monazite_feed_wt / 100, | |
| 'Gangue': 1 - (ilmenite_feed_wt + rutile_feed_wt + monazite_feed_wt)/100 | |
| } | |
| if mode == "Forward Model (Adjust Parameters)": | |
| params = {'stroke_len': stroke_len, 'freq': freq, 'tilt': tilt, 'wash_water': wash_water} | |
| model_rec, yc, ym, yt, grades, rates = shaking_table_model(params, feed_grades, feed_rate_tph) | |
| text = f"### Forward Model Results\n\n" | |
| text += f"**Mass Yields:** Conc {yc:.1f}%, Midd {ym:.1f}%, Tail {yt:.1f}%\n\n" | |
| text += "| Mineral | Rec Conc (%) | Grade Conc | Rate to Conc (tph) |\n" | |
| text += "|-----------|--------------|----------------|--------------------|\n" | |
| for m in minerals: | |
| rec = model_rec[m]['Conc'] | |
| grade = grades[m] | |
| unit = "g/t" if m=="Gold" else "wt%" | |
| rate = rates[m] | |
| text += f"| {m:<9} | {rec:11.1f} | {grade:13.2f} {unit} | {rate:17.3f} |\n" | |
| else: # Target mode | |
| target_rates = { | |
| 'Gold': target_gold_tph, | |
| 'Ilmenite': target_ilmenite_tph, | |
| 'Rutile': target_rutile_tph, | |
| 'Monazite': target_monazite_tph | |
| } | |
| best_params, err = find_parameters_for_targets(target_rates, feed_grades, feed_rate_tph) | |
| if best_params: | |
| model_rec, yc, ym, yt, grades, rates = shaking_table_model(best_params, feed_grades, feed_rate_tph) | |
| text = f"### Suggested Parameters to Achieve Target Rates (Error: {err:.1f})\n\n" | |
| text += f"**Suggested:** Stroke {best_params['stroke_len']:.1f} mm, Freq {best_params['freq']:.0f}/min, Tilt {best_params['tilt']:.1f}°, Wash {best_params['wash_water']:.1f}\n\n" | |
| text += "| Mineral | Target Rate (tph) | Achieved Rate | Grade Conc |\n" | |
| text += "|-----------|-------------------|---------------|----------------|\n" | |
| for m in minerals: | |
| grade = grades[m] | |
| unit = "g/t" if m=="Gold" else "wt%" | |
| text += f"| {m:<9} | {target_rates[m]:17.3f} | {rates[m]:12.3f} | {grade:13.2f} {unit} |\n" | |
| else: | |
| text = "Could not find parameters to match targets." | |
| # Plots | |
| fig1 = plt.figure(figsize=(10,6)) | |
| x = np.arange(len(minerals)) | |
| width = 0.25 | |
| plt.bar(x, [model_rec[m]['Conc'] for m in minerals], width, label='Conc') | |
| plt.bar(x + width, [model_rec[m]['Midd'] for m in minerals], width, label='Midd') | |
| plt.bar(x + 2*width, [model_rec[m]['Tail'] for m in minerals], width, label='Tail') | |
| plt.xlabel('Mineral') | |
| plt.ylabel('Distribution (%)') | |
| plt.title('Mineral Distribution Across Products') | |
| plt.xticks(x + width, minerals) | |
| plt.legend() | |
| plt.grid(True, axis='y') | |
| fig2 = plt.figure(figsize=(8,8)) | |
| labels = ['Conc', 'Midd', 'Tail'] | |
| sizes = [yc, ym, yt] | |
| plt.pie(sizes, labels=labels, autopct='%1.1f%%', startangle=90) | |
| plt.title('Mass Yield Pie Chart') | |
| # Simple estimated "cost" (arbitrary: lower yield higher cost due to more processing) | |
| est_cost = 1000 + 500 * (yc / 30) # dummy | |
| text += f"\n\n**Estimated Relative Operating Cost:** ${est_cost:.0f} (arbitrary units)" | |
| return text, fig1, fig2 | |
| # ============================================= | |
| # Gradio Interface with Sidebar Sections | |
| # ============================================= | |
| with gr.Blocks(title="Advanced Shaking Table Digital Twin") as demo: | |
| gr.Markdown("# Advanced Shaking Table Digital Twin - Attock Placer Plant") | |
| gr.Markdown("Physical-inspired model with manual inputs for feed, operating parameters, and target rates.") | |
| mode = gr.Radio(["Forward Model (Adjust Parameters)", "Inverse: Find Parameters for Target Rates"], value="Forward Model (Adjust Parameters)", label="Mode") | |
| with gr.Row(): | |
| with gr.Column(scale=1): # Sidebar-like | |
| gr.Markdown("### 1. Feed Composition & Rate") | |
| feed_rate_tph = gr.Slider(50, 500, value=300, step=10, label="Feed Rate (tph)") | |
| gold_feed_gpt = gr.Number(value=3.74, label="Gold Feed Grade (g/t)") | |
| ilmenite_feed_wt = gr.Number(value=5.625, label="Ilmenite Feed Grade (wt%)") | |
| rutile_feed_wt = gr.Number(value=1.35, label="Rutile Feed Grade (wt%)") | |
| monazite_feed_wt = gr.Number(value=0.6, label="Monazite Feed Grade (wt%)") | |
| gr.Markdown("### 2. Operating Parameters") | |
| stroke_len = gr.Slider(8, 25, value=18, step=0.5, label="Stroke Length (mm)") | |
| freq = gr.Slider(250, 350, value=300, step=5, label="Stroke Frequency (/min)") | |
| tilt = gr.Slider(2, 8, value=5, step=0.1, label="Deck Inclination (degrees)") | |
| wash_water = gr.Slider(5, 20, value=12, step=0.5, label="Wash Water Flow (units)") | |
| gr.Markdown("### 3. Target Rates to Concentrate (tph) - for Inverse Mode") | |
| target_gold_tph = gr.Number(value=0.8, label="Target Gold Rate to Conc (tph)") | |
| target_ilmenite_tph = gr.Number(value=15, label="Target Ilmenite Rate (tph)") | |
| target_rutile_tph = gr.Number(value=3.5, label="Target Rutile Rate (tph)") | |
| target_monazite_tph = gr.Number(value=1.5, label="Target Monazite Rate (tph)") | |
| with gr.Column(scale=3): | |
| btn = gr.Button("Run Simulation", variant="primary") | |
| output_text = gr.Markdown() | |
| with gr.Row(): | |
| plot1 = gr.Plot(label="Mineral Distribution Bar Chart") | |
| plot2 = gr.Plot(label="Mass Yield Pie Chart") | |
| btn.click( | |
| fn=simulate, | |
| inputs=[feed_rate_tph, gold_feed_gpt, ilmenite_feed_wt, rutile_feed_wt, monazite_feed_wt, | |
| stroke_len, freq, tilt, wash_water, mode, | |
| target_gold_tph, target_ilmenite_tph, target_rutile_tph, target_monazite_tph], | |
| outputs=[output_text, plot1, plot2] | |
| ) | |
| demo.launch() |