Repository: networkx/networkx

Issue:
ENH: Add function for Hyper-Wiener Index
### Proposal

New function, `networkx.hyper_wiener_index(G)`, to calculate the Hyper-Wiener Index of a graph.

The Hyper-Wiener Index ($WW$) is a well-known topological index used in chemical graph theory. It's a natural extension of the existing `nx.wiener_index`. Adding this function would enhance the library's utility for structure-property studies.

### Proposed Implementation

The index is defined by the formula:
$$WW(G) = \frac{1}{2} \sum_{u, v \in V(G)} [d(u,v) + d(u,v)^2]$$
where $d(u,v)$ is the shortest path distance between vertices $u$ and $v$.

This can be implemented efficiently by iterating through the results of `nx.all_pairs_shortest_path_length(G)`.

For consistency with `nx.wiener_index`, the function should raise a `NetworkXError` if the input graph is not connected.

I have already started working on this feature and will submit a pull request for review shortly.

### References
* [Wikipedia: Hyper-Wiener Index](https://en.wikipedia.org/wiki/Hyper-Wiener_index)
* Randić, M. (1993). Novel molecular descriptor for structure-property studies. *Chemical Physics Letters, 211*(4-5), 478-483.


Discussion:

Patch:
diff --git a/doc/reference/algorithms/wiener.rst b/doc/reference/algorithms/wiener.rst
index acd070a99aa..5c570131a05 100644
--- a/doc/reference/algorithms/wiener.rst
+++ b/doc/reference/algorithms/wiener.rst
@@ -9,3 +9,4 @@ Wiener Index
    wiener_index
    schultz_index
    gutman_index
+   hyper_wiener_index
diff --git a/networkx/algorithms/wiener.py b/networkx/algorithms/wiener.py
index ac3abe4ac12..d097aa62908 100644
--- a/networkx/algorithms/wiener.py
+++ b/networkx/algorithms/wiener.py
@@ -19,7 +19,7 @@
 
 import networkx as nx
 
-__all__ = ["wiener_index", "schultz_index", "gutman_index"]
+__all__ = ["wiener_index", "schultz_index", "gutman_index", "hyper_wiener_index"]
 
 
 @nx._dispatchable(edge_attrs="weight")
@@ -224,3 +224,55 @@ def gutman_index(G, weight=None):
     spl = nx.shortest_path_length(G, weight=weight)
     d = dict(G.degree, weight=weight)
     return sum(dist * d[u] * d[v] for u, vinfo in spl for v, dist in vinfo.items()) / 2
+
+
+@nx.utils.not_implemented_for("directed")
+@nx.utils.not_implemented_for("multigraph")
+@nx._dispatchable(edge_attrs="weight")
+def hyper_wiener_index(G, weight=None):
+    r"""Returns the Hyper-Wiener index of the graph `G`.
+
+    The Hyper-Wiener index of a connected graph `G` is defined as
+
+    .. math::
+        WW(G) = \frac{1}{2} \sum_{u,v \in V(G)} (d(u,v) + d(u,v)^2)
+
+    where ``d(u, v)`` is the shortest-path distance between nodes ``u`` and ``v``.
+
+    Parameters
+    ----------
+    G : NetworkX graph
+        An undirected, connected graph.
+
+    weight : string or None, optional (default: None)
+        The edge attribute to use for calculating shortest-path distances.
+        If None, all edges are considered to have a weight of 1.
+
+    Returns
+    -------
+    float
+        The Hyper-Wiener index of the graph G.
+        Returns float("inf") if the graph is not connected.
+
+    References
+    ----------
+    .. [1] M. Randić, "Novel molecular descriptor for structure-property studies,"
+           Chemical Physics Letters, vol. 211, pp. 478-483, 1993.
+    .. [2] `Wikipedia: Hyper-Wiener Index <https://en.wikipedia.org/wiki/Hyper-Wiener_index>`_
+
+    Examples
+    --------
+    >>> G = nx.path_graph(4)
+    >>> nx.hyper_wiener_index(G)
+    30.0
+
+    >>> G = nx.cycle_graph(4)
+    >>> nx.hyper_wiener_index(G)
+    20.0
+    """
+    if not nx.is_connected(G):
+        return float("inf")
+
+    spl = nx.shortest_path_length(G, weight=weight)
+    total = sum(dist + dist**2 for _, lengths in spl for dist in lengths.values())
+    return total / 2


Test patch:
diff --git a/networkx/algorithms/tests/test_wiener.py b/networkx/algorithms/tests/test_wiener.py
index aded95143ca..2bb1f85b1d7 100644
--- a/networkx/algorithms/tests/test_wiener.py
+++ b/networkx/algorithms/tests/test_wiener.py
@@ -121,3 +121,37 @@ def test_schultz_and_gutman_index_of_odd_cycle_graph():
 
     assert expected_1 == actual_1
     assert expected_2 == actual_2
+
+
+def test_hyper_wiener_of_complete_graph():
+    # In a complete graph K_n, the distance is always 1.
+    # For K_n, this term is always (1 + 1^2) = 2.
+    #
+    # The number of ordered pairs is n * (n - 1).
+    # The total sum before division is (n * (n - 1)) * 2.
+    # The final result is therefore ((n * (n - 1)) * 2) / 2, which
+    # simplifies to n * (n - 1).
+    n = 5
+    G = nx.complete_graph(n)
+    assert nx.hyper_wiener_index(G) == n * (n - 1)
+
+
+def test_hyper_wiener_of_path_graph():
+    G = nx.path_graph(4)
+    assert nx.hyper_wiener_index(G) == 30.0
+
+
+def test_hyper_wiener_of_cycle_graph():
+    G = nx.cycle_graph(4)
+    assert nx.hyper_wiener_index(G) == 20.0
+
+
+def test_hyper_wiener_of_disconnected_graph():
+    G = nx.Graph([(0, 1), (2, 3)])
+    assert nx.hyper_wiener_index(G) == float("inf")
+
+
+def test_hyper_wiener_of_weighted_graph():
+    G = nx.path_graph(3)
+    G.edges[0, 1]["weight"] = 2
+    assert nx.hyper_wiener_index(G, weight="weight") == 20.0