Compositación de Sondajes Mineros

LUIS ÁLVAREZ P.

(27 de Septiembre, 2024)

Tabla de contenidos

¿Qué es la compositación y en qué consiste?

La compositación es un proceso mediante el cual se ajustan las longitudes de las muestras obtenidas de sondajes para que sean iguales (regularización). Esto implica modificar las longitudes de los ensayos de manera que cada volumen de roca tenga una influencia equitativa en la estimación de la ley mineral de un área específica. La compositación busca facilitar una representación más precisa y equilibrada de las concentraciones de mineral en el yacimiento.

¿Cuál es la importancia de la compositación de sondajes en minería?

La compositación de sondajes es crucial en la minería por varias razones:

  • Equidad en la Estimación: Asegura que cada volumen de roca contribuya de manera equitativa a la estimación de la ley, evitando que algunas muestras, que pueden ser más abundantes, influyan desproporcionadamente en los resultados finales.

  • Mejora del Proceso de Exploración: Al estandarizar las longitudes de las muestras, se mejora la capacidad de análisis y se facilita la comparación entre diferentes sondajes, lo que resulta en una mejor toma de decisiones sobre la viabilidad económica de un proyecto.

  • Reducción de Errores: Minimiza el riesgo de sobrestimar o subestimar la concentración de mineral debido a la variabilidad de las longitudes de las muestras.

Ejemplo

Para este ejemplo, se utilizan 3 archivos (DESCARGAR AQUÍ):

  1. HEADER.TXT = Collares de sondajes
  2. SURVEY.TXT = Dirección de sondajes
  3. ASSAYS.TXT = Ley mineral por tramo de testigo

El siguiente código composita a 10 metros los sondajes de exploración minera. Esta longitud de compositación es una variable por lo que es una opción del usuario. 

Código Python

def fill_missing_intervals_all(assay_data, fill_value=-99):
    assay_data = assay_data.sort_values(by=['HOLE-ID', 'FROM']).reset_index(drop=True)
    filled_data = []
    for i in range(len(assay_data) - 1):
        current_row = assay_data.iloc[i]
        next_row = assay_data.iloc[i + 1]
        filled_data.append(current_row.to_dict())  # Convertir la fila en un dict
        if current_row['TO'] < next_row['FROM'] and current_row['HOLE-ID'] == next_row['HOLE-ID']:
            missing_interval = {'HOLE-ID': current_row['HOLE-ID'], 'FROM': current_row['TO'], 'TO': next_row['FROM']}
            for col in assay_data.columns:
                if col not in ['HOLE-ID', 'FROM', 'TO']:
                    missing_interval[col] = fill_value
            filled_data.append(missing_interval)
    filled_data.append(assay_data.iloc[-1].to_dict())
    filled_df = pd.DataFrame(filled_data).sort_values(by=['HOLE-ID', 'FROM', 'TO']).reset_index(drop=True)
    return filled_df
assay_data = pd.read_csv('ASSAY.TXT', delimiter=',')
filled_assay_data = fill_missing_intervals_all(assay_data)
filled_assay_data.to_csv('ASSAY-C.txt', sep=',', index=False)
print("El archivo ASSAY-C.txt ha sido generado correctamente y está ordenado por HOLE-ID, FROM y TO, separado por comas.")

# Cargar los datos
header_data = pd.read_csv('HEADER.txt', delimiter=',')
survey_data = pd.read_csv('SURVEY.txt', delimiter=',')
assay_data = pd.read_csv('ASSAY-C.TXT', delimiter=',')
def composite_sondaje(assay_data, composite_length=10):
    composite_data = []
    current_start = 0
    max_to = assay_data['TO'].max()
    if max_to % composite_length != 0:
        print(f"Advertencia: El último tramo no es múltiplo de {composite_length} y no se compositará.")
        max_to = max_to - (max_to % composite_length)  # Ajustar para no incluir el tramo final
    while current_start < max_to:
        current_end = current_start + composite_length
        subset = assay_data[(assay_data['FROM'] < current_end) & (assay_data['TO'] > current_start)]
        if not subset.empty:
            weighted_law = 0
            total_length = 0
            for index, row in subset.iterrows():
                overlap_from = max(current_start, row['FROM'])
                overlap_to = min(current_end, row['TO'])
                length = overlap_to - overlap_from
                weighted_law += length * row['CU']
                total_length += length
            composito_cu = weighted_law / total_length if total_length > 0 else None
            if composito_cu is not None and composito_cu < 0:
                composito_cu = -99
            composite_data.append({
                'HOLE-ID': assay_data['HOLE-ID'].iloc[0],
                'FROM': current_start,
                'TO': current_end,
                'CU': composito_cu
            })
        current_start = current_end
    return pd.DataFrame(composite_data)
all_composites = pd.DataFrame()
for hole_id in assay_data['HOLE-ID'].unique():
    assay_sondaje = assay_data[assay_data['HOLE-ID'] == hole_id]
    composite_results = composite_sondaje(assay_sondaje)
    all_composites = pd.concat([all_composites, composite_results])
all_composites.to_csv('composite_results.csv', index=False)
print("El archivo CSV con los resultados de compositing ha sido generado.")
header_df = pd.read_csv("HEADER.txt")
survey_df = pd.read_csv("SURVEY.txt")
def calcular_nuevas_coordenadas(x1, y1, z1, distancia, azimut, dip):
    azimut_rad = np.radians(azimut)
    dip_rad = np.radians(dip)
    if azimut == 0 or azimut == 180:
        x2 = x1  
    elif azimut > 0 and azimut < 180:
        x2 = x1 + distancia * np.cos(dip_rad)
    else: 
        x2 = x1 - distancia * np.cos(dip_rad)
    if azimut == 90 or azimut == 270:
        y2 = y1  
    elif (azimut > 270 and azimut < 360) or (azimut > 0 and azimut < 90):
        y2 = y1 + distancia * np.cos(dip_rad)
    else: 
        y2 = y1 - distancia * np.cos(dip_rad)
    z2 = z1 + distancia * np.sin(dip_rad)
    return x2, y2, z2
def calcular_compositos_sondaje_sin_auxiliar(sondaje_id, collar, longitud, azimut, dip, interval=10):
    distancias, coords_x, coords_y, coords_z, tipo_composito = [], [], [], [], []
    primera_distancia = interval / 2
    distancias.append(primera_distancia)
    x2, y2, z2 = calcular_nuevas_coordenadas(collar[0], collar[1], collar[2], primera_distancia, azimut, dip)
    coords_x.append(x2)
    coords_y.append(y2)
    coords_z.append(z2)
    tipo_composito.append("Composito")
    collar = (x2, y2, z2)
    for i in range(int(primera_distancia), longitud, interval):
        if i + interval > longitud:
            break 
        x1, y1, z1 = collar
        x2, y2, z2 = calcular_nuevas_coordenadas(x1, y1, z1, interval, azimut, dip)
        distancia_actual = i + interval
        distancias.append(distancia_actual)
        coords_x.append(x2)
        coords_y.append(y2)
        coords_z.append(z2)
        tipo_composito.append("Composito")
        collar = (x2, y2, z2)
    return pd.DataFrame({
        'Sondaje': [sondaje_id] * len(distancias),
        'Distancia (m)': distancias,
        'X (m)': coords_x,
        'Y (m)': coords_y,
        'Z (m)': coords_z,
        'Tipo': tipo_composito
    })
def calcular_compositos_con_siguiente_tramo(collar, longitud, azimut, dip, interval=10, distancia_sobrante=0):
    distancias, coords_x, coords_y, coords_z, tipo_composito = [], [], [], [], []
    primera_distancia = interval - distancia_sobrante if distancia_sobrante > 0 else interval / 2
    distancias.append(primera_distancia)
    x2, y2, z2 = calcular_nuevas_coordenadas(collar[0], collar[1], collar[2], primera_distancia, azimut, dip)
    coords_x.append(x2)
    coords_y.append(y2)
    coords_z.append(z2)
    tipo_composito.append("Composito")
    collar = (x2, y2, z2)
    for i in range(int(primera_distancia), longitud, interval):
        if i + interval > longitud:
            break  
        x1, y1, z1 = collar
        x2, y2, z2 = calcular_nuevas_coordenadas(x1, y1, z1, interval, azimut, dip)
        distancias.append(i + interval)
        coords_x.append(x2)
        coords_y.append(y2)
        coords_z.append(z2)
        tipo_composito.append("Composito")
        collar = (x2, y2, z2)
    return pd.DataFrame({
        'Distancia (m)': distancias,
        'X (m)': coords_x,
        'Y (m)': coords_y,
        'Z (m)': coords_z,
        'Tipo': tipo_composito
    }), interval - (longitud % interval)
def procesar_sondajes():
    all_composites = pd.DataFrame() 
    for sondaje_id in header_df['HOLE-ID'].unique():
        print(f"Procesando sondaje {sondaje_id}...")
        header_row = header_df[header_df['HOLE-ID'] == sondaje_id]
        if header_row.empty:
            print(f"Advertencia: No se encontraron datos de collar para el sondaje {sondaje_id}.")
            continue
        collar = (header_row.iloc[0]['LOCATIONX'], header_row.iloc[0]['LOCATIONY'], header_row.iloc[0]['LOCATIONZ'])
        survey_rows = survey_df[survey_df['HOLE-ID'] == sondaje_id]
        if survey_rows.empty:
            print(f"Advertencia: No se encontraron datos de azimut o dip para el sondaje {sondaje_id}.")
            continue
        if len(survey_rows) == 1:
            # Sondaje de un solo tramo
            azimut = survey_rows.iloc[0]['AZIMUTH']
            dip = survey_rows.iloc[0]['DIP']
            longitud = header_row.iloc[0]['LENGTH']
            composites = calcular_compositos_sondaje_sin_auxiliar(sondaje_id, collar, longitud, azimut, dip)
        else:
            composites = pd.DataFrame()
            distancia_sobrante = 0  # Inicializar la distancia sobrante como 0
            for index, row in survey_rows.iterrows():
                from_depth = row["FROM"]
                to_depth = row["TO"]
                azimut = row["AZIMUTH"]
                dip = row["DIP"]
                longitud_tramo = to_depth - from_depth
                tramo_composites, distancia_sobrante = calcular_compositos_con_siguiente_tramo(
                    collar, longitud_tramo, azimut, dip, interval=10, distancia_sobrante=distancia_sobrante
                )
                composites = pd.concat([composites, tramo_composites], ignore_index=True)
                collar = tramo_composites.iloc[-1][["X (m)", "Y (m)", "Z (m)"]].values
            for index, row in survey_rows.iterrows():
                to_depth = row["TO"]
                if not composites[composites["Distancia (m)"] == to_depth].empty:
                    composites.loc[composites["Distancia (m)"] == to_depth, "Tipo"] = "Auxiliar"
        all_composites = pd.concat([all_composites, composites], ignore_index=True)
    all_composites.to_csv('composites_sondajes.csv', index=False)
    print("El archivo 'composites_sondajes.csv' ha sido generado con éxito.")
procesar_sondajes()
def graficar_compositos():
    fig = plt.figure()
    ax = fig.add_subplot(111, projection='3d')
    compositos_b_filtrados = all_composites[all_composites["Tipo"] == "Composito"]
    ax.scatter(compositos_b_filtrados['X (m)'], 
               compositos_b_filtrados['Y (m)'], 
               compositos_b_filtrados['Z (m)'], 
               c='blue', marker='o')
    ax.set_xlabel('X (m)')
    ax.set_ylabel('Y (m)')
    ax.set_zlabel('Z (m)')
    plt.show()
composite_results = pd.read_csv('composite_results.csv')
composites_sondajes = pd.read_csv('composites_sondajes.csv')
print("Columnas en composite_results:", composite_results.columns)
print("Columnas en composites_sondajes:", composites_sondajes.columns)
composite_results['Distancia_Promedio'] = (composite_results['FROM'] + composite_results['TO']) / 2
if 'Sondaje' in composites_sondajes.columns:
    composites_sondajes.rename(columns={'Sondaje': 'HOLE-ID'}, inplace=True)
if 'Distancia (m)' in composites_sondajes.columns:
    composites_sondajes.rename(columns={'Distancia (m)': 'Distancia_Promedio'}, inplace=True)
merged_data = pd.merge(composite_results, composites_sondajes, how='left', on=['HOLE-ID', 'Distancia_Promedio'])
merged_data_filtered = merged_data.drop(columns=['FROM', 'TO', 'Distancia_Promedio', 'Tipo'], errors='ignore')
output_file_path = 'Sondajes_Compositados_final.csv'
merged_data_filtered.to_csv(output_file_path, index=False)
print(f"El archivo final filtrado ha sido guardado en {output_file_path}")

Resultados

Archivo compositado y referenciado por las coordenadas X,Y,Z en el centro del composito. 

Compartir en facebook
Facebook
Compartir en twitter
Twitter
Compartir en linkedin
LinkedIn
Compartir en whatsapp
WhatsApp

Cursos relacionados

Recursos Minerales

12 Cursos
Cursos relativos al uso de geoestadística y otras ciencias aplicadas en la evaluación de recursos minerales.