(27 de Septiembre, 2024)
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.
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.
Para este ejemplo, se utilizan 3 archivos (DESCARGAR AQUÍ):
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.
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}")
Archivo compositado y referenciado por las coordenadas X,Y,Z en el centro del composito.