Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
8b09aa4
Change default AMST approx bound value to 1.1
KIwabuchi Aug 7, 2026
a8e6204
Use MFC by default
KIwabuchi Aug 7, 2026
805652b
Brush up scripts
KIwabuchi Aug 8, 2026
6932cc6
Update bench scripts
KIwabuchi Aug 8, 2026
3ad72d6
Update bench scripts
KIwabuchi Aug 8, 2026
e10e513
Minor code brush ups
KIwabuchi Aug 10, 2026
5c17de2
Update bench script
KIwabuchi Aug 11, 2026
8189ff9
Assign clusters to noise points
KIwabuchi Aug 13, 2026
495a903
Add copy_pm_datastore
KIwabuchi Aug 13, 2026
a7f510d
Fix build error in cluster_noise_points.cpp
KIwabuchi Aug 13, 2026
726511f
Improve run_clams_bench.py
KIwabuchi Aug 13, 2026
756c207
Enhance clustering evaluation and correlation analysis
KIwabuchi Sep 4, 2026
cc8f3e2
Merge remote-tracking branch 'upstream/develop' into feature/brush_up…
KIwabuchi Sep 4, 2026
0c57721
Enhance documentation for kNNG functions and correlation evaluation
KIwabuchi Sep 4, 2026
b4f47fd
Refactor clustering evaluation output and streamline echo statements …
KIwabuchi Sep 4, 2026
c200068
Refactor clustering execution logic into a dedicated function for imp…
KIwabuchi Sep 4, 2026
2f02c26
Minor bugfix in cluster_noise_points.cpp
KIwabuchi Sep 4, 2026
c030037
Minor bugfix in cluster_noise_points.cpp
KIwabuchi Sep 4, 2026
4dda3ca
Brush up benchmark scripts
KIwabuchi Sep 4, 2026
15acaa4
Brush up benchmark scripts
KIwabuchi Sep 4, 2026
b0b78d9
Brush up benchmark scripts
KIwabuchi Sep 4, 2026
17ec574
Update output header in extract_clams_clustering_time.sh
KIwabuchi Sep 5, 2026
c66d607
Merge remote-tracking branch 'upstream/develop' into feature/brush_up…
KIwabuchi Sep 5, 2026
3ab6ad5
Bugfix: fix to detect noise points correctly.
KIwabuchi Sep 6, 2026
1ca4060
Brush up on the HDBSCAN script
KIwabuchi Sep 6, 2026
13db1e7
Add knng and clusters correlation evaluation script
KIwabuchi Sep 11, 2026
0164955
Add MST-based clusetring complement v2
KIwabuchi Sep 11, 2026
be532ad
Merge remote-tracking branch 'upstream/develop' into feature/brush_up…
KIwabuchi Sep 13, 2026
b97dde9
Change to use parallel PM datastore copy program when back up KNNG
KIwabuchi Sep 16, 2026
f489b55
Bugfix: Boost component linking in CMakeLists.txt
KIwabuchi Sep 16, 2026
8c4c643
Use the distributed HDBSCAN by default
KIwabuchi Sep 16, 2026
6e6f2bd
Enhance KNNG evaluation scripts with no-distance option and improve o…
KIwabuchi Sep 16, 2026
918f0f8
Add support for optional distance reading in knng file processing
KIwabuchi Sep 16, 2026
b612336
Change the header format of clustering output file
KIwabuchi Sep 16, 2026
9d88646
Change noise clustering algorithm to consider distance
KIwabuchi Sep 16, 2026
bb50b72
Optimize noise point clustering code
KIwabuchi Sep 17, 2026
6f0efb8
Separate point reading time in build-knng-neo-dnnd
KIwabuchi Sep 17, 2026
dc864f4
Merge branch 'develop' into feature/brush_up_bench
KIwabuchi Sep 17, 2026
0ff31d9
Merge remote-tracking branch 'upstream/develop' into feature/brush_up…
KIwabuchi Sep 25, 2026
350e135
Merge remote-tracking branch 'upstream/develop' into feature/brush_up…
KIwabuchi Sep 26, 2026
fe41d18
Merge remote-tracking branch 'upstream/develop' into feature/brush_up…
KIwabuchi Sep 27, 2026
02237d8
Minor bugfix in benchmark utilities
KIwabuchi Oct 2, 2026
905cd52
Reorganizing python scripts
KIwabuchi Oct 4, 2026
77f9cfc
(WIP) Implementing unsupervised clustering quality evaluation
KIwabuchi Oct 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 1 addition & 63 deletions script/benchmark/bench_utilities.py
Original file line number Diff line number Diff line change
@@ -1,44 +1,10 @@
# Copyright 2023-2026 Lawrence Livermore National Security, LLC and other ClaMS
# Project Developers. See the top-level COPYRIGHT file for details.


import subprocess
import os


# Execute a shell command
# out_file: output file to write the stdout
# err_file: output file to write the stderr
# command: command to execute
# cwd: working directory
def execute_cmd(log_file, err_log_file, command, cwd='./'):
print(f'In {os.path.abspath(cwd)}')
print(f'Command: {command}')
result = subprocess.run(f'{command}', shell=True,
cwd=cwd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)

with open(log_file, 'a') as f:
f.write(f'Command: {command}')
f.write(result.stdout.decode("utf8"))
with open(err_log_file, 'a') as f:
f.write(result.stderr.decode("utf8"))

if result.returncode == 0:
print("Command executed successfully.\n")
else:
print(f"Error executing command. See {err_log_file}.\n")
exit(1)


def create_dir(dir_path):
os.makedirs(dir_path, exist_ok=True)
# Check if the directory was created
if not os.path.exists(dir_path):
print(f"Error: Could not create directory {dir_path}")
exit(1)

from script.utilities import *

def set_up_batch_header(script_file, job_name, job_dir, num_nodes):
# Make dir
Expand Down Expand Up @@ -76,31 +42,3 @@ def add_srun_cmd(num_tasks_per_node, command, script_file, echo=True,
check_return_code=True):
add_cmd(f"srun --ntasks-per-node={num_tasks_per_node} {command}",
script_file, echo, check_return_code)


def grep_file(file, text_pattern):
# Return the lines that contain the pattern
with open(file, 'r') as f:
lines = f.readlines()
return [line for line in lines if text_pattern in line]

# Parse a range string and return a list of integers
# e.g., "1-5" -> [1, 2, 3, 4, 5]
# e.g., "1,3,5" -> [1, 3, 5]
def parse_range(range_str):
if range_str is None:
return [None]

if '-' in range_str:
start, end = range_str.split('-')
return list(range(int(start), int(end) + 1))
else:
return [int(x) for x in range_str.split(',')]

# Parse a list of floats from a string and return a list of floats
# e.g., "1.0,2.5,3.14" -> [1.0, 2.5, 3.14]
def parse_float_list(float_list_str):
if float_list_str is None:
return [None]

return [float(x) for x in float_list_str.split(',')]
162 changes: 2 additions & 160 deletions script/benchmark/clustering_utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
from sklearn.metrics import adjusted_rand_score, adjusted_mutual_info_score
import datetime
import os

import sys
from script.utilities import *

# Modified from:
# https://gist.github.com/lmcinnes/24ed5c22c80125be5133811d677eae7b
Expand Down Expand Up @@ -75,162 +76,3 @@ def assign_singleton_cluster_to_noise_points(cluster_labels, noise_id_offset):
print(f"Assigned singleton clusters to {cnt_noise} noise points")

return new_labels


def show_time_now():
now = datetime.datetime.now()
print(now.strftime("%Y-%m-%d %H:%M:%S"))


def find_files_in_dir(dir_path, ext=''):
if not os.path.isdir(dir_path):
return [dir_path]

files = []
for file in os.listdir(dir_path):
if len(ext) == 0 or file.endswith(ext):
files.append(os.path.join(dir_path, file))
return files


# Read point(feature) data from file(s)
# If data_path is a directory, load all files in the directory.
# Return a dense numpy array of feature vectors.
# Each row is a feature vector.
# Input files can have point IDs in the first column.
# Point IDs are expected to be 0, 1, 2, ..., N-1,
# where N is the number of points.
# If the first column is not a point ID, set has_ids to False.
# If there are multiple files in a directory, the point IDs must be present.
def read_point_data(data_path, has_ids=True):
print(f"Loading data from {data_path}", flush=True)
files = find_files_in_dir(data_path)

if len(files) == 0:
print(f"No files found in {data_path}")
exit(1)

if len(files) > 1 and not has_ids:
print("Multiple files are provided,"
"but has_ids is false.")

points_table = {}
dimensions = 0
for file in files:
for line in open(file, 'r'):
if len(line.strip()) == 0:
print(f"Empty line found in {file}")
continue
items = line.split()
if has_ids:
pid = int(items[0])
items = items[1:]
else:
pid = len(points_table)

if pid in points_table:
print(f"Duplicate ID in the feature file: {pid}")
exit(1)

if dimensions == 0:
dimensions = len(items)
elif dimensions != len(items):
print(
f"Dimension mismatch in {file}: {len(items)} != {dimensions}")
exit(1)

points_table[pid] = list(map(float, items))

print(f"Loaded {len(points_table)} items from {len(files)} files", flush=True)

# numpy array of feature vectors
# if the IDs are not continuous, fill the missing IDs with -1
max_id = max(points_table.keys())
points = np.full((max_id + 1, len(points_table[0])), 0.0)
for id, feature in points_table.items():
points[id] = feature

return points


# Load cluster label or ground truth label data
# If data_path is a directory, load all files in the directory
# Return a dense numpy array of labels
# Unclustered points are filled with -1
#
# There are two accepted file types:
#
# 1. Only cluster IDs:
# Each line contains a cluster ID.
# Point IDs are assumed to be 0, 1, 2, ..., N-1, where N is the number of points.
# This mode is not acceptable if there are multiple files in a directory.
#
# 2. Point IDs and cluster IDs:
# Contains two columns:
# the first column is for point IDs and the second column is for cluster IDs.
#
# Both File types can also contain comment lines, which must start from #.
def read_label_data(data_path):
print(f"Loading data from {data_path}", flush=True)
labels_dict = {}
files = []
if os.path.isdir(data_path):
files = [os.path.join(data_path, f) for f in os.listdir(data_path)]
else:
files.append(data_path)

if len(files) == 0:
print(f"No files found in {data_path}")
exit(1)

contains_ids = False
for line in open(files[0], 'r'):
if len(line.split()) >= 2:
contains_ids = True
break

if contains_ids:
print("Loading point IDs and labels", flush=True)
else:
print("Loading only labels", flush=True)

if len(files) > 1 and not contains_ids:
print("Multiple files are provided,"
"but no IDs are found in the first file.")

max_id = 0
for file in files:
for line in open(file, 'r'):

# Skip comments
if line.startswith("#"):
continue

if contains_ids:
items = line.split()
if len(items) < 2:
print(f"Invalid line: {line}")
exit(1)
pid = int(items[0])
label = int(items[1])
max_id = max(max_id, pid)
else:
pid = len(labels_dict)
max_id = pid
label = int(line)

if pid in labels_dict:
print(f"Duplicate ID in the ground truth file: {pid}")
exit(1)
labels_dict[pid] = label

print(f"Loaded {len(labels_dict)} items from {len(files)} files", flush=True)
print(f"Max ID: {max_id}", flush=True)

# numpy array of labels
# if the IDs are not continuous, fill the missing IDs with -1
labels = np.full(max_id + 1, -1)
for id, label in labels_dict.items():
labels[id] = label

return labels
6 changes: 3 additions & 3 deletions script/benchmark/evaluate_clustering_quality.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
import argparse
import numpy as np
from clustering_utilities import *

from script.utilities import *

def parse_options():
parser = argparse.ArgumentParser(
Expand All @@ -63,8 +63,8 @@ def parse_options():
def main():
opt = parse_options()

cluster_labels = read_label_data(opt.cluster_path)
true_labels = read_label_data(opt.gt_path)
cluster_labels = read_cluster_labels(opt.cluster_path, "*")
true_labels = read_cluster_labels(opt.gt_path, "*")

try:
eval_clusters(cluster_labels, true_labels, opt.singleton_noise_points, opt.ignore_true_noise_points)
Expand Down
Loading
Loading