{"id":1057,"date":"2025-11-26T15:09:49","date_gmt":"2025-11-26T15:09:49","guid":{"rendered":"https:\/\/inventorics.com\/?post_type=portfolio&#038;p=1057"},"modified":"2026-07-15T12:26:57","modified_gmt":"2026-07-15T12:26:57","slug":"k-means-clustering","status":"publish","type":"portfolio","link":"https:\/\/inventorics.com\/?portfolio=k-means-clustering","title":{"rendered":"K-Means++ Clustering &#8211; Files"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Custom Python component for Grasshopper which gives a lot of freedom to tweak the parameters and customization of the standard algorithm, such as for example comparative studies of different feature vector set-ups.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Our OpenAccess eCAADe 2025 Paper &#8220;Condensing Complexity: K-means clustering of geometric data sets as a design tool&#8221; &#8211; documenting our approach and usage &#8211; can be found on:<br><br>ResearchGate:<br><a href=\"https:\/\/www.researchgate.net\/publication\/401344242_Condensing_Complexity_K-means_clustering_of_geometric_data_sets_as_a_design_tool\">https:\/\/www.researchgate.net\/publication\/401344242_Condensing_Complexity_K-means_clustering_of_geometric_data_sets_as_a_design_tool<\/a><br><br>CumInCAD:<br><a href=\"https:\/\/papers.cumincad.org\/cgi-bin\/works\/paper\/ecaade2025_521\">https:\/\/papers.cumincad.org\/cgi-bin\/works\/paper\/ecaade2025_521<\/a><\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"287\" src=\"https:\/\/inventorics.com\/wp-content\/uploads\/2025\/11\/grasshopper_component-1024x287.jpg\" alt=\"\" class=\"wp-image-1578\" srcset=\"https:\/\/inventorics.com\/wp-content\/uploads\/2025\/11\/grasshopper_component-1024x287.jpg 1024w, https:\/\/inventorics.com\/wp-content\/uploads\/2025\/11\/grasshopper_component-300x84.jpg 300w, https:\/\/inventorics.com\/wp-content\/uploads\/2025\/11\/grasshopper_component-768x215.jpg 768w, https:\/\/inventorics.com\/wp-content\/uploads\/2025\/11\/grasshopper_component-1536x431.jpg 1536w, https:\/\/inventorics.com\/wp-content\/uploads\/2025\/11\/grasshopper_component.jpg 1886w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Python 3 code component for Rhino 8+Grasshopper:<\/strong><\/p>\n\n\n\n<div class=\"wp-block-file\"><a id=\"wp-block-file--media-9e1a6c5e-f392-4a8f-b4ba-5731e3aee09d\" href=\"https:\/\/inventorics.com\/wp-content\/uploads\/2026\/07\/k-means_pp_grasshopper.zip\">k-means_pp.gh (as ZIP file)<\/a><a href=\"https:\/\/inventorics.com\/wp-content\/uploads\/2026\/07\/k-means_pp_grasshopper.zip\" class=\"wp-block-file__button wp-element-button\" download aria-describedby=\"wp-block-file--media-9e1a6c5e-f392-4a8f-b4ba-5731e3aee09d\">Download<\/a><\/div>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Python 3 code for Rhino 8+Grasshopper code as a .py file:<\/strong><\/p>\n\n\n\n<div class=\"wp-block-file\"><a id=\"wp-block-file--media-d36a8103-c312-41c4-a271-311d77e2000e\" href=\"https:\/\/inventorics.com\/wp-content\/uploads\/2025\/11\/k-means_pp.zip\">k-means_pp.py (as ZIP file)<\/a><a href=\"https:\/\/inventorics.com\/wp-content\/uploads\/2025\/11\/k-means_pp.zip\" class=\"wp-block-file__button wp-element-button\" download aria-describedby=\"wp-block-file--media-d36a8103-c312-41c4-a271-311d77e2000e\">Download<\/a><\/div>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Python 3 for Rhino 8+Grasshopper code as plain text:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import math\nimport random as rnd\nimport ghpythonlib.treehelpers as th\n\n# Function to remap a value from one range to another\ndef translate(value, leftMin, leftMax, rightMin, rightMax):\n    leftSpan = leftMax - leftMin\n    rightSpan = rightMax - rightMin\n    valueScaled = (value - leftMin) \/ leftSpan\n    return rightMin + (valueScaled * rightSpan)\n\n# Function to normalize values to a target range &#91;0, 1]\ndef normalize_values(lst, min_val, max_val):\n    return &#91;\n        &#91;translate(value, min_val, max_val, 0, 1) for value in sublist] \n        for sublist in lst\n    ]\n\n# Euclidean distance function for two vectors\ndef vector_dist(v1, v2):\n    return math.sqrt(sum((v2&#91;i] - v1&#91;i])**2 for i in range(len(v1))))\n\n# KMeans++ initialization: Select initial cluster centers\ndef kMeansPlusPlusInitialization(vals, cls_num, first_center_index):\n    if not ini:\n        rnd.seed(42)  # Set fixed seed for deterministic results\n        \n    cluster_centers = &#91;] \n    first_center = vals&#91;first_center_index]  # Choose first cluster center manually via component input\n    cluster_centers.append(first_center)\n    \n    for _ in range(1, cls_num):\n        distances = &#91;\n            min(vector_dist(val, center) for center in cluster_centers) \n            for val in vals\n        ]\n        total_distance = sum(distances)\n        probabilities = &#91;dist \/ total_distance for dist in distances]\n\n        cumulative_probabilities = &#91;]\n        cumulative_sum = 0\n        for p in probabilities:\n            cumulative_sum += p\n            cumulative_probabilities.append(cumulative_sum)\n        \n        r = rnd.random()\n        for i, cumulative_prob in enumerate(cumulative_probabilities):\n            if r &lt; cumulative_prob:\n                cluster_centers.append(vals&#91;i])\n                break\n    \n    return cluster_centers\n\n# Main K-Means clustering algorithm\ndef kMeanClustering(vals, pt_geometry, cls_num, iterations, first_center_index):\n    # Normalize the input values\n    norm_list = normalize_values(vals, min_val, max_val)\n    \n    # Initialize cluster centers using KMeans++\n    cluster_centers = kMeansPlusPlusInitialization(norm_list, cls_num, first_center_index)\n    \n    for _ in range(iterations):\n        clusters = &#91;&#91;] for _ in cluster_centers]\n        clusters_ids = &#91;&#91;] for _ in cluster_centers]\n        geom_clusters = &#91;&#91;] for _ in cluster_centers]\n        \n        for i, norm_val in enumerate(norm_list):\n            # Find the closest cluster center for each value\n            distances = &#91;vector_dist(norm_val, center) for center in cluster_centers]\n            best_center_id = distances.index(min(distances))\n            \n            clusters&#91;best_center_id].append(norm_val)\n            clusters_ids&#91;best_center_id].append(i)\n            geom_clusters&#91;best_center_id].append(pt_geometry&#91;i])\n        \n        # Update cluster centers as the mean of assigned points\n        for i, cluster in enumerate(clusters):\n            if cluster:\n                new_center = &#91;sum(dim) \/ len(cluster) for dim in zip(*cluster)]\n                cluster_centers&#91;i] = new_center\n            else:\n                cluster_centers&#91;i] = rnd.choice(norm_list)\n    \n    # Compute geometric averages\n    avg_geometry = &#91;]\n    for cluster in geom_clusters:\n        if cluster:\n            avg_geom = &#91;sum(dim) \/ len(cluster) for dim in zip(*cluster)]\n            avg_geometry.append(avg_geom)\n        else:\n            avg_geometry.append(&#91;])\n    \n    # Map the cluster centers back to the original domain\n    mapped_centers = &#91;\n        &#91;translate(center&#91;i], 0, 1, min_val, max_val) for i in range(len(center))]\n        for center in cluster_centers\n    ]\n    \n    # Return cluster IDs, mapped cluster centers, and geometric averages as Grasshopper DataTrees\n    return th.list_to_tree(clusters_ids), th.list_to_tree(mapped_centers), th.list_to_tree(avg_geometry)\n\n# Main script execution\n# Flattened input: Extract values from Grasshopper tree structure\nvalues = &#91;&#91;item for item in branch] for branch in feature_vector.Branches]\npt_geom = &#91;&#91;item for item in branch] for branch in pt_geometry.Branches]\n\n# Compute min_val and max_val for the entire dataset\nall_values = &#91;item for sublist in values for item in sublist]\nmin_val = min(all_values)\nmax_val = max(all_values)\n\n# Perform clustering\nids, avg_feature_vector, avg_geometry = kMeanClustering(values, pt_geom, clusters, iterations, first_center_index)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>This work and all files related to it are shared under the <a href=\"https:\/\/creativecommons.org\/licenses\/by-nc-sa\/4.0\/\" target=\"_blank\" rel=\"noopener\">Creative Commons: Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) Licence<\/a>.<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Custom Python component for Grasshopper which gives a lot of freedom to tweak the parameters and customization of the standard algorithm, such as for example comparative studies of different feature vector set-ups. Our OpenAccess eCAADe 2025 Paper &#8220;Condensing Complexity: K-means clustering of geometric data sets as a design tool&#8221; &#8211; documenting our approach and usage [&hellip;]<\/p>\n","protected":false},"featured_media":1077,"comment_status":"closed","ping_status":"closed","template":"","portfolio_cat":[46],"portfolio_skill":[],"class_list":["post-1057","portfolio","type-portfolio","status-publish","has-post-thumbnail","hentry","portfolio_cat-code"],"jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/inventorics.com\/index.php?rest_route=\/wp\/v2\/portfolio\/1057","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/inventorics.com\/index.php?rest_route=\/wp\/v2\/portfolio"}],"about":[{"href":"https:\/\/inventorics.com\/index.php?rest_route=\/wp\/v2\/types\/portfolio"}],"replies":[{"embeddable":true,"href":"https:\/\/inventorics.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1057"}],"version-history":[{"count":4,"href":"https:\/\/inventorics.com\/index.php?rest_route=\/wp\/v2\/portfolio\/1057\/revisions"}],"predecessor-version":[{"id":1829,"href":"https:\/\/inventorics.com\/index.php?rest_route=\/wp\/v2\/portfolio\/1057\/revisions\/1829"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/inventorics.com\/index.php?rest_route=\/wp\/v2\/media\/1077"}],"wp:attachment":[{"href":"https:\/\/inventorics.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1057"}],"wp:term":[{"taxonomy":"portfolio_cat","embeddable":true,"href":"https:\/\/inventorics.com\/index.php?rest_route=%2Fwp%2Fv2%2Fportfolio_cat&post=1057"},{"taxonomy":"portfolio_skill","embeddable":true,"href":"https:\/\/inventorics.com\/index.php?rest_route=%2Fwp%2Fv2%2Fportfolio_skill&post=1057"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}