Skip to article frontmatterSkip to article content

Ce notebook permet de visualiser les lignes d’isocontours de l’écart-type relatif de Gy.

Vous pouvez entrer vos propres paramètres, les valider, et obtenir une procédure graphique adaptée à vos étapes.


✅ Étapes :

  1. Entrer vos paramètres globaux :
  • aLa_L : proportion massique du lot analysé
  • δa\delta_a, δg\delta_g : masse spécifique du constituant d’intérêt et masse spécifique de la gangue
  • d0d_0 : taille à laquelle le constituant d’intérêt est entièrement libéré (en cm)
  • MLM_L : masse totale du lot (en g)
  • s_vals : liste des écarts-types relatifs souhaités (ex. [0.01, 0.002])
  1. Ajouter une ou plusieurs étapes avec leurs paramètres spécifiques :
  • MeM_e : masse d’échantillon (en g)
  • MLM_L : masse totale à l’étape (en g)
  • dd : taille max des fragments (en cm)
  1. Visualiser l’abaque mis à jour avec :
  • les lignes d’isocontours en noir pour les écarts-types choisis
  • les points rouges représentant chaque étape saisie
  • les flèches bleues indiquant la progression entre étapes
  • le calcul et l’affichage de l’écart-type relatif global (srs_r global)

📊 Exemple de sortie :

L’abaque affiche une échelle logarithmique en abscisse (taille des fragments, cm) et en ordonnée (masse de l’échantillon, g).
Les courbes noires sont les isocontours pour les écarts-types relatifs choisis, tandis que les courbes en gris pointillé correspondent à au maillage logarithmique.

Chaque point rouge correspond à une étape saisie, avec sa valeur sr affichée en rouge à côté.
Les flèches bleues montrent le cheminement entre étapes.

Enfin, le srs_r global (écart-type total combiné) est indiqué en bleu en haut à gauche.


Remarque : les calculs reposent sur les paramètres et formules spécifiques à la géotechnique minière.
Assurez-vous de bien ajuster les valeurs selon votre contexte d’étude.

Source
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import FancyArrowPatch
from ipywidgets import VBox, HBox, Button, FloatText, Output, Label, Layout
from IPython.display import display, clear_output

# Variables globales initiales
params = {
    'al': 0.010 / 0.67,
    'da': 4.1,
    'dg': 2.8,
    'd0': 0.1
}

# Champs élargis avec styles lisibles
common_layout = Layout(width='220px')
common_style = {'description_width': 'initial'}

al_input = FloatText(description='$a_L$ (conc.)', value=params['al'], step=0.01,
                     layout=common_layout, style=common_style)
da_input = FloatText(description='$\delta_a$ (densité min.)', value=params['da'], step=0.1,
                     layout=common_layout, style=common_style)
dg_input = FloatText(description='$\delta_g$ (densité gangue)', value=params['dg'], step=0.1,
                     layout=common_layout, style=common_style)
d0_input = FloatText(description='$d_0$ (taille lib.)', value=params['d0'], step=0.0001,
                     layout=common_layout, style=common_style)

params_widgets = HBox([al_input, da_input, dg_input, d0_input])

# Widgets supplémentaires
sr_desire_input = FloatText(value=10.0, step=0.1, layout=Layout(width='100px'))
validation_label = Label()
status_label = Label(value='')
error_label = Label(value='', layout=Layout(margin='10px 0 0 0', color='red'))  # Label d'erreur

# Fonctions
def gy(al, da, dg, ml, d0, me=None, d=None, sr=None, f=0.5, g=0.25):
    if d is None:
        fl = 1.0
    else:
        fl = min(np.sqrt(d0 / d), 1.0)
    ud = (1 - al) / al * ((1 - al) * da + al * dg)
    k2 = ud * f * g
    k = ud * f * g * fl
    if me is None:
        ime = sr**2 / k / d**3 + 1 / ml
        sr = 1 / ime
    elif d is None:
        for _ in range(10):
            d3 = sr**2 / k / (1 / me - 1 / ml)
            d = d3 ** (1 / 3)
            fl = min(np.sqrt(d0 / d), 1.0)
            k = ud * f * g * fl
        sr = d
    else:
        s2 = k * d**3 / me * (1 - me / ml)
        sr = np.sqrt(s2)
    return sr

def update_ml_from_me():
    for i in range(len(steps) - 1):
        me_value = steps[i][0].value
        steps[i + 1][1].value = me_value

def plot_gy_iso_contours(ax, al, da, dg, d0, ml, s_vals=[0.1]):
    f, g = 0.5, 0.25
    d = np.exp(np.linspace(-7, 3, 100))  # tailles de fragments en cm
    flib = np.minimum(1, np.sqrt(d0 / d))
    ud = (1 - al) / al * ((1 - al) * da + al * dg)
    k = ud * f * g * flib * d**3

    for s in s_vals:
        me = 1 / (s**2 / k + 1 / ml)
        ax.loglog(d, me, 'k', linewidth=1.5)
        i = np.argmin(np.abs(me - 50))
        if i < len(d):
            ax.text(d[i]*1.4, me[i], f'{s * 100:.3f}%' , fontsize=10,
                    rotation=45, color='black', ha='right')

    ax.set_xlim([5e-3, 1])
    ax.set_ylim([1, 1e4])
    ax.grid(True, which='both', linestyle='-', alpha=0.75)
    ax.set_xlabel("Taille des plus gros fragments (cm)", fontsize=12)
    ax.set_ylabel("Masse de l'échantillon (g)", fontsize=12)
    ax.set_title(f"Abaque de Gy – $a_L$={al}, $\delta_a$={da}, $\delta_g$={dg}, $d_0$={d0}, $M_L$={ml:.0f}", fontsize=13)

steps = []
rows = VBox()
output = Output()

def add_step(_=None):
    me_input = FloatText(description='$M_e$ (g)', value=100.0, step=1.0,
                         layout=common_layout, style=common_style)
    ml_input = FloatText(description='$M_L$ (g)', value=1000.0, step=1.0,
                         layout=common_layout, style=common_style)
    d_input = FloatText(description='$d$ (cm)', value=0.1, step=0.01,
                        layout=common_layout, style=common_style)
    
    steps.append((me_input, ml_input, d_input))
    row = HBox([me_input, ml_input, d_input])
    rows.children += (row,)

def remove_step(_=None):
    if steps:
        steps.pop()
        rows.children = rows.children[:-1]

def finish_steps(_=None):
    btn_add.disabled = True
    btn_finish.disabled = True
    status_label.value = "Fin de la saisie des étapes. Plus aucune étape ne peut être ajoutée."

def update_params(_=None):
    try:
        params['al'] = float(al_input.value)
        params['da'] = float(da_input.value)
        params['dg'] = float(dg_input.value)
        params['d0'] = float(d0_input.value)
    except Exception as e:
        print("Erreur de saisie dans les paramètres globaux:", e)

def update_plot(_=None):
    with output:
        clear_output(wait=True)
        error_label.value = ''  # reset erreur

        update_ml_from_me()

        # Vérification me[i-1] == ml[i]
        for i in range(1, len(steps)):
            me_prev = steps[i-1][0].value
            ml_curr = steps[i][1].value
            if me_prev != ml_curr:
                error_label.value = (f"❌ Erreur : me de la ligne {i} ({me_prev}) ≠ ml de la ligne {i+1} ({ml_curr}) ! "
                                     "Ces valeurs doivent être égales.")
                return  # Stoppe le calcul pour correction

        fig, ax = plt.subplots(figsize=(8, 5))

        d_vals = np.logspace(-2, 1, 100)
        me_vals = np.logspace(1, 4, 100)
        D, ME = np.meshgrid(d_vals, me_vals)

        SR = np.zeros_like(D)
        for i in range(D.shape[0]):
            for j in range(D.shape[1]):
                SR[i, j] = gy(params['al'], params['da'], params['dg'], ME[i, j], params['d0'], me=ME[i, j], d=D[i, j])

        cs = ax.contour(D, ME, SR * 100, levels=[5, 10, 20, 40, 60, 80], colors='gray', linestyles='dashed')
        ax.clabel(cs, inline=True, fontsize=8, fmt='%1.0f%%')

        sr_list = []
        valid_coords = []

        for (me_input, ml_input, d_input) in steps:
            me = me_input.value
            ml = ml_input.value
            d = d_input.value
            if me > 0 and ml > 0 and d > 0:
                sr = gy(params['al'], params['da'], params['dg'], ml, params['d0'], me, d)
                if np.isnan(sr):
                    continue
                sr_list.append(sr)
                ax.plot(d, me, 'ro')
                ax.text(d * 1.1, me * 1.1, f'{sr * 100:.3f}%', fontsize=9, color='red')
                valid_coords.append((d, me))

        for i in range(len(valid_coords) - 1):
            x1, y1 = valid_coords[i]
            x2, y2 = valid_coords[i+1]
            ax.add_patch(FancyArrowPatch((x1, y1), (x2, y1), arrowstyle='->', color='blue', mutation_scale=12, lw=1.5))
            ax.add_patch(FancyArrowPatch((x2, y1), (x2, y2), arrowstyle='->', color='blue', mutation_scale=12, lw=1.5))

        ax.set_xlabel('Taille max fragments d (cm)', fontsize=12)
        ax.set_ylabel('Masse échantillon me (g)', fontsize=12)
        ax.set_xscale('log')
        ax.set_yscale('log')
        ax.set_xlim(5e-3, 100)
        ax.set_ylim(10, 1e4)
        ax.grid(True, which='both')
        ax.set_title('Abaque de Gy - Écarts-types relatifs par étape', fontsize=13)

        sr_global = None
        if sr_list:
            sr_global = np.sqrt(np.sum(np.array(sr_list) ** 2))

        ml_values = [ml_input.value for _, ml_input, _ in steps]
        ml_moyen = np.max(ml_values) if ml_values else 1000

        plot_gy_iso_contours(ax, params['al'], params['da'], params['dg'], params['d0'],
                             ml=ml_moyen, s_vals=[0.001, 0.005, 0.01, 0.05, 0.1, 0.2])

        plt.show()

        if sr_list:
            print("\nValeurs des $s_r$ par étape :")
            for i, sr_val in enumerate(sr_list, 1):
                print(f"Sr{i} = {sr_val*100:.3f} %")
            print(f"\nSr global = {sr_global*100:.3f} %")

        if sr_global is not None:
            sr_desire = sr_desire_input.value / 100
            if sr_global <= sr_desire:
                validation_label.value = f"✅ Procédure valide (sr_global = {sr_global*100:.3f}% ≤ sr désiré = {sr_desire*100:.3f}%)"
            else:
                validation_label.value = f"❌ Procédure NON valide (sr_global = {sr_global*100:.3f}% > sr désiré = {sr_desire*100:.3f}%)"
        else:
            validation_label.value = ""

# Boutons
btn_add = Button(description='Ajouter étape', button_style='success')
btn_add.on_click(add_step)

btn_remove = Button(description='Supprimer dernière étape', button_style='warning')
btn_remove.on_click(remove_step)

btn_finish = Button(description='Fin', button_style='danger')
btn_finish.on_click(finish_steps)

btn_calcul = Button(description='Calculer', button_style='primary')
btn_calcul.on_click(update_plot)

# Observateurs
al_input.observe(update_params, names='value')
da_input.observe(update_params, names='value')
dg_input.observe(update_params, names='value')
d0_input.observe(update_params, names='value')

# Interface
ui = VBox([
    Label("Paramètres globaux :"), 
    params_widgets,
    HBox([Label("$s_r$ désiré (%) :"), sr_desire_input]),
    HBox([btn_add, btn_remove, btn_finish, btn_calcul]),
    rows, 
    status_label,
    validation_label,
    error_label,  # Affichage erreur me[i-1] vs ml[i]
    output
])

display(ui)
Loading...