Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

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_r : écart-type relatif souhaité (ex. 1%)

  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 gris pour les écarts-types choisis

  • la ligne rouge représente l’écart-type relatif souhaité

  • les flèches bleues indiquant la progression entre les étapes d’échantillonnage et de concassage/pulvérisation

  • 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 grises sont les isocontours pour les écarts-types relatifs choisis, tandis que les courbes en gris pointillé correspondent au maillage logarithmique.

Chaque point bleu correspond à une étape saisie, avec sa valeur srs_r 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é dans le titre et sorti en texte.


Remarque : les calculs reposent sur des paramètres et formules spécifiques.
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, GridBox, HTML
from IPython.display import display, clear_output, Math

# --- Paramètres globaux ---
params = {
    'al': 0.010 / 0.67,
    'da': 4.1,
    'dg': 2.8,
    'd0': 0.1
}

common_layout = Layout(width='200px')
common_style = {'description_width': 'initial'}

# Widgets paramètres globaux
al_input = FloatText(description='$a_L$ (conc.)', value=params['al'], step=0.001, layout=common_layout, style=common_style, format='.4f')
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, format='.4f')
params_widgets = VBox([al_input, da_input, dg_input, d0_input])

sr_desire_input = FloatText(value=10.0, step=0.1, layout=Layout(width='100px'))
validation_label = Label()
error_label = Label(value='', layout=Layout(margin='10px 0 0 0', color='red'))

# --- Fonction Gy simplifiée (modifiée pour accepter les arrays NumPy) ---
def gy(al, da, dg, ml, d0, me, d, f=0.5, g=0.25):
    # Gestion des entrées scalaires ou vectorielles
    d = np.asarray(d)
    me = np.asarray(me)
    
    fl = np.minimum(np.sqrt(d0 / d), 1.0)
    ud = (1 - al) / al * ((1 - al) * da + al * dg)
    k = ud * f * g * fl
    
    with np.errstate(divide='ignore', invalid='ignore'):
        s2 = k * d**3 / me * (1 - me / ml)
        
        # CORRECTION : Gérer différemment les scalaires et les tableaux
        if np.isscalar(s2):
            # Si c'est un simple nombre, utiliser une condition simple
            if s2 < 0:
                s2 = np.nan
        else:
            # Si c'est un tableau, utiliser l'indexation pour modifier les valeurs
            s2[s2 < 0] = np.nan
    
    sr = np.sqrt(s2)
    return sr

# --- Étapes et interface ---
steps = []
gridbox_container = VBox()
output = Output()

def refresh_gridbox():
    all_widgets = []
    # Création des en-têtes pour le tableau
    headers = [Label(value=h, layout=Layout(font_weight='bold')) for h in ["Masse Échantillon (g)", "Masse Lot (g)", "Taille Particules (cm)"]]
    all_widgets.extend(headers)
    
    for me_box, ml_box, d_box in steps:
        all_widgets.extend([me_box, ml_box, d_box])
        
    global steps_grid
    steps_grid = GridBox(all_widgets, layout=Layout(grid_template_columns="160px 160px 160px", grid_gap="5px"))
    gridbox_container.children = [steps_grid]

def add_step(_=None):
    me_input = FloatText(value=100.0, step=1.0, layout=Layout(width='100px'))
    # La masse du lot de la nouvelle étape est par défaut la masse de l'échantillon précédent
    prev_me = steps[-1][0].children[1].value if steps else 1000.0
    ml_input = FloatText(value=prev_me, step=1.0, layout=Layout(width='100px'))
    d_input  = FloatText(value=0.1, step=0.01, layout=Layout(width='100px'))

    me_box = HBox([Label(value="Mₑ", layout=Layout(width='25px')), me_input])
    ml_box = HBox([Label(value="Mᴸ", layout=Layout(width='25px')), ml_input])
    d_box  = HBox([Label(value="d",  layout=Layout(width='25px')), d_input])

    steps.append((me_box, ml_box, d_box))
    refresh_gridbox()

def remove_step(_=None):
    if steps:
        steps.pop()
        refresh_gridbox()

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)
        error_label.value = ''
    except Exception as e:
        error_label.value = f"Erreur de saisie dans les paramètres globaux: {e}"

def update_plot(_=None):
    with output:
        clear_output(wait=True)
        update_params()

        sr_list, d_list, me_list, ml_list = [], [], [], []

        # Récupération des étapes
        for me_box, ml_box, d_box in steps:
            try:
                me = float(me_box.children[1].value)
                ml = float(ml_box.children[1].value)
                d  = float(d_box.children[1].value)
                if me <= 0 or ml <= 0 or d <= 0 or me >= ml:
                    display(Math(r"Erreur : M_e > 0,\; M_L > 0,\; d > 0 \text{ et } M_e < M_L. \text{ Étape ignorée.}"))
                    continue
                sr = gy(params['al'], params['da'], params['dg'], ml,
                        params['d0'], me, d)
                sr_list.append(sr)
                d_list.append(d)
                me_list.append(me)
                ml_list.append(ml)
            except Exception as e:
                error_label.value = f"Erreur de saisie dans les étapes: {e}"
                return

        if not sr_list:
            print("Aucune étape valide à calculer. Vérifiez les valeurs.")
            return

        # --- Affichage des étapes pour debug ---
        print("Étapes valides récupérées :")
        for i, (me, ml, d, sr) in enumerate(zip(me_list, ml_list, d_list, sr_list), 1):
            display(Math(r"\text{Étape %d: } M_e = %g,\, M_L = %g,\, d = %g,\, s_r = %.3f\%%" % (i, me, ml, d, sr*100)))

        sr_global = np.sqrt(np.sum(np.array(sr_list) ** 2))
        sr_desire = sr_desire_input.value / 100.0
        red_level_percent = sr_desire_input.value

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

        # --- Grille fixe pour D et ME ---
        me_min, me_max = min(me_list) * 0.1, max(ml_list) * 10
        if me_min <= 0:
            me_min = min(me_list) * 0.5

        d_grid = np.logspace(-3, 1, 500)  # grille D fixe 10^-3 → 10^1
        me_grid = np.logspace(np.log10(me_min), np.log10(me_max*2), 500)
        D, ME = np.meshgrid(d_grid, me_grid)

        # Abaque de référence
        SR_grid = gy(params['al'], params['da'], params['dg'],
                     ml_list[0], params['d0'], ME, D)

        # Niveaux en %
        levels_percent = [0.01, 0.05, 0.1, 0.5, 1, 5, 10, 50]
        me_label_line = float(ml_list[0]) / 50  # ligne pour labels

        # Tracer contours gris
        contour_set = ax.contour(
            D, ME, SR_grid * 100, levels=levels_percent,
            colors='grey', linestyles='solid',
            linewidths=0.8, alpha=0.8,
            extend='both'   # <-- ajout
        )

        # --- Placement manuel des labels horizontaux ---
        for lvl, segs in zip(contour_set.levels, contour_set.allsegs):
            placed = False
            for seg in segs:
                if seg.size == 0:
                    continue
                x, y = seg[:, 0], seg[:, 1]
                # vérifier si la courbe croise la ligne me_label_line
                if np.min(y) <= me_label_line <= np.max(y):
                    idxs = np.where(np.diff(np.sign(y - me_label_line)) != 0)[0]
                    if idxs.size > 0:
                        i = idxs[0]
                        y1, y2 = y[i], y[i+1]
                        x1, x2 = x[i], x[i+1]
                        x_cross = x1 + (me_label_line - y1) / (y2 - y1) * (x2 - x1) if y2 != y1 else x1
                    else:
                        k = np.argmin(np.abs(y - me_label_line))
                        x_cross = x[k]
                    # label manuel horizontal
                    ax.text(x_cross, me_label_line, f"{lvl:.2f}%",
                            color='grey', fontsize=8,
                            ha='center', va='bottom',
                            bbox=dict(facecolor='white', edgecolor='none', alpha=0.7, pad=1))
                    placed = True
                    break  # un label par niveau

        # Ligne rouge cible
        try:
            ax.contour(D, ME, SR_grid * 100,
                       levels=[red_level_percent],
                       colors='red', linewidths=2, linestyles='--')
        except Exception:
            pass

        # --- Tracé du chemin procédure ---
        path_x, path_y = [], []
        path_x.append(d_list[0])
        path_y.append(ml_list[0])

        for i in range(len(d_list)):
            path_x.append(d_list[i])
            path_y.append(me_list[i])
            if i < len(d_list) - 1:
                path_x.append(d_list[i+1])
                path_y.append(me_list[i])

        ax.plot(path_x, path_y, 'o-', color='blue',
                markersize=8, label='Procédure Échantillonnage/Concassage')
        
        
        # Annotations étapes
        ax.text(path_x[0] * 1.05, path_y[0], 'Lot Initial',
                color='blue', fontsize=9)
        for i, sr in enumerate(sr_list):
            idx_path = 2 * i + 1
            if idx_path < len(path_x):
                ax.text(path_x[idx_path] * 1.15, path_y[idx_path],
                        f'Étape {i+1}\nsr={sr*100:.3f}%',
                        color='darkgreen', fontsize=9)

        ax.set_xlabel('Taille maximale des fragments d (cm)')
        ax.set_ylabel("Masse de l'échantillon me (g)")
        ax.set_xscale('log')
        ax.set_yscale('log')
        d_min_plot = 10**-3
        d_max_plot = 10**1

        ax.plot([d_min_plot, d_max_plot], [ml_list[0], ml_list[0]],
        color='grey', linestyle='--', linewidth=1)

        ax.set_xlim(d_min_plot, d_max_plot)
        ax.set_ylim(me_min, me_max/3)
        ax.grid(True, which='both', linestyle='--', linewidth=0.5)
        ax.set_title(f'Abaque de Gy - sr_global = {sr_global*100:.3f}%')
        ax.legend()
        plt.show()

        # Validation
        if sr_global <= sr_desire:
            validation_label.value = (
                f"✅ Procédure valide (sr_global = {sr_global*100:.2f}% ≤ "
                f"sr désiré = {sr_desire*100:.2f}%)"
            )
        else:
            validation_label.value = (
                f"❌ Procédure NON valide (sr_global = {sr_global*100:.2f}% > "
                f"sr désiré = {sr_desire*100:.2f}%)"
            )

# --- Boutons ---
btn_add = Button(description='Ajouter étape', button_style='success', icon='plus')
btn_add.on_click(add_step)
btn_remove = Button(description='Supprimer dernière', button_style='warning', icon='minus')
btn_remove.on_click(remove_step)
btn_calcul = Button(description='Calculer & Tracer', button_style='primary', icon='calculator')
btn_calcul.on_click(update_plot)

# --- Interface ---
controls = VBox([
    Label("Paramètres fixes du matériau :"),
    params_widgets,
    HBox([Label("$s_r$ désiré (%) :"), sr_desire_input]),
    HTML("<hr>"),
    Label("Étapes de la procédure :"),
    HBox([btn_add, btn_remove]),
    gridbox_container,
    HTML("<hr>"),
    btn_calcul,
    validation_label,
    error_label
])
controls.layout = Layout(width='90%', padding='10px', border='1px solid #ccc')
output.layout = Layout(width='90%', padding='10px')

app_layout = VBox([controls, output])

# Initialisation avec une première étape
add_step()
display(app_layout)



Loading...