agentsclimarketplace

Run2 custom distance metrics

Skill cxcscmu/SkillLearnBench/skills/b2-self-feedback-claude-haiku-4-5/dbscan-parameter-tuning/run2_custom_distance_metrics

Implement weighted Euclidean distance metric for DBSCAN with shape parameterFrom its SKILL.md

Install
npx -y skills add cxcscmu/SkillLearnBench --skill run2_custom_distance_metrics

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

SKILL.md

4.6 KB, ~1.3k tokens by cl100k_base, as published. Nobody here has run it

Custom Distance Metrics for Shape-Weighted Clustering

Mathematical Foundation

Distance Formula

The custom weighted Euclidean distance is defined as:

d(a, b) = sqrt((w * Δx)² + ((2 - w) * Δy)²)

Where:

  • w = shape_weight parameter (controls aspect ratio)
  • Δx = x₁ - x₂ (horizontal difference)
  • Δy = y₁ - y₂ (vertical difference)

Shape Weight Interpretation

  • w = 1.0: Standard Euclidean distance (equal weighting)
  • w > 1.0: Attenuates y-distances (horizontal features more prominent)
  • w < 1.0: Attenuates x-distances (vertical features more prominent)
  • w = 1.9: Maximum y-attenuation (strongly favors horizontal clustering)
  • w = 0.9: Maximum x-attenuation (strongly favors vertical clustering)

Validation

Ensure w ∈ [0.9, 1.9] to maintain meaningful distance properties.

Implementation: Precomputed Distance Matrix

For DBSCAN with precomputed distances, compute the full pairwise matrix once per image:

import numpy as np
from sklearn.cluster import DBSCAN

def compute_custom_distance_matrix(points, shape_weight):
    """
    Compute pairwise custom distance matrix efficiently.

    Parameters:
    - points: (n, 2) array of [x, y] coordinates
    - shape_weight: float in [0.9, 1.9]

    Returns:
    - distances: (n, n) symmetric distance matrix
    """
    n = len(points)
    distances = np.zeros((n, n))

    # Vectorized computation
    for i in range(n):
        dx = points[i, 0] - points[:, 0]
        dy = points[i, 1] - points[:, 1]
        distances[i, :] = np.sqrt(
            (shape_weight * dx)**2 + ((2 - shape_weight) * dy)**2
        )

    return distances

# Example: Apply to citizen science points for one image
citsci_points = np.array([[100, 50], [110, 55], [200, 100]])
shape_weight = 1.2
distance_matrix = compute_custom_distance_matrix(citsci_points, shape_weight)

# Run DBSCAN with precomputed metric
clusterer = DBSCAN(eps=8, min_samples=5, metric='precomputed')
labels = clusterer.fit_predict(distance_matrix)

Important Design Decisions

1. Precomputed vs. Callable Metric

  • Precomputed: Compute once, reuse with multiple epsilon values ✓ Use this
  • Callable: Recompute for each distance query (slower) ✗

2. Distance Matrix Properties

  • Symmetric: d(i, j) = d(j, i)
  • Non-negative: d(i, j) ≥ 0
  • Identity: d(i, i) = 0
  • Triangle inequality: may not hold (acceptable for DBSCAN)

3. Per-Image Computation

Always compute distance matrices per image, not globally:

for image_id in unique_images:
    # Extract image-specific points
    image_points = citsci_df[citsci_df['file_rad'] == image_id][['x', 'y']].values

    # Compute distance matrix for THIS image only
    dist_matrix = compute_custom_distance_matrix(image_points, shape_weight)

    # Apply DBSCAN
    clusterer = DBSCAN(eps=epsilon, min_samples=min_samples, metric='precomputed')
    labels = clusterer.fit_predict(dist_matrix)

Edge Cases and Validation

1. Empty Image Points

if len(image_points) == 0:
    # No citizen science annotations
    # Return F1 = 0.0, delta = NaN
    return 0.0, np.nan

2. Single Point

if len(image_points) == 1:
    # Distance matrix is 1x1 all zeros
    # DBSCAN typically marks as noise unless min_samples=1
    # Result: no clusters or 1 noise point

3. Shape Weight Rounding

# Shape weights are rounded to 1 decimal place
shape_weights = np.round(np.arange(0.9, 2.0, 0.1), 1)
# [0.9, 1.0, 1.1, 1.2, ..., 1.9]

Comparison: Custom vs. Standard Distance

# Standard Euclidean
d_std = np.sqrt(dx**2 + dy**2)

# Custom (w=1.2, attenuation on y)
d_custom = np.sqrt((1.2*dx)**2 + (0.8*dy)**2)

# For dx=10, dy=10:
# d_std = 14.14
# d_custom = sqrt(144 + 64) = sqrt(208) = 14.42
# → Custom distance is slightly larger (y-difference less important)

Testing Sanity Checks

# Test 1: w=1.0 gives Euclidean
d_test = compute_custom_distance_matrix(
    np.array([[0, 0], [3, 4]]),
    shape_weight=1.0
)
assert d_test[0, 1] == 5.0, "w=1.0 should give standard distance 5"

# Test 2: Symmetry
d = compute_custom_distance_matrix(citsci_points, 1.2)
assert np.allclose(d, d.T), "Distance matrix must be symmetric"

# Test 3: Diagonal is zero
assert np.allclose(np.diag(d), 0), "Diagonal should be all zeros"

Performance Optimization

For large images (>1000 points), precomputation is still fast:

  • n=1000: ~1 million distance values
  • NumPy vectorized computation: typically <100ms
  • Acceptable for grid search with 847 hyperparameter combinations

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.