code: public static boolean equivalent(
        @Nullable ValueGraph<?, ?> graphA, @Nullable ValueGraph<?, ?> graphB) {
      if (graphA == graphB) {
        return true;
      }
      if (graphA == null || graphB == null) {
        return false;
      }
  
      if (graphA.isDirected() != graphB.isDirected()
          || !graphA.nodes().equals(graphB.nodes())
          || !graphA.edges().equals(graphB.edges())) {
        return false;
      }
  
      for (EndpointPair<?> edge : graphA.edges()) {
        if (!graphA
            .edgeValue(edge.nodeU(), edge.nodeV())
            .equals(graphB.edgeValue(edge.nodeU(), edge.nodeV()))) {
          return false;
        }
      }
  
      return true;
    }
docs: Returns {@code true} if {@code graphA} and {@code graphB} have the same elements (including
  edge values) and the same relationships between elements, as exposed via the {@link ValueGraph}
  interface.
  
  <p>Thus, two value graphs A and B are equivalent if both are null or <b>all</b> of the
  following are true:
  
  <ul>
  <li>A and B have equal {@link Graph#isDirected() directedness}.
  <li>A and B have equal {@link Graph#nodes() node sets}.
  <li>A and B have equal {@link Graph#edges() edge sets}.
  <li>Each edge in A has a {@link ValueGraph#edgeValue(Object, Object) value} equal to the {@link
  ValueGraph#edgeValue(Object, Object) value} of the corresponding edge in B.
  </ul>
  
  <p>Graph properties besides {@link Graph#isDirected() directedness} do <b>not</b> affect
  equivalence. For example, two graphs may be considered equivalent even if one allows self-loops
  and the other doesn't. Additionally, the order in which nodes or edges are added to the graph,
  and the order in which they are iterated over, are irrelevant.