misc.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703
  1. """
  2. Miscellaneous Helpers for NetworkX.
  3. These are not imported into the base networkx namespace but
  4. can be accessed, for example, as
  5. >>> import networkx as nx
  6. >>> nx.utils.make_list_of_ints({1, 2, 3})
  7. [1, 2, 3]
  8. >>> nx.utils.arbitrary_element({5, 1, 7}) # doctest: +SKIP
  9. 1
  10. """
  11. import itertools
  12. import random
  13. import warnings
  14. from collections import defaultdict
  15. from collections.abc import Iterable, Iterator, Sized
  16. from itertools import chain, tee, zip_longest
  17. import networkx as nx
  18. __all__ = [
  19. "flatten",
  20. "make_list_of_ints",
  21. "dict_to_numpy_array",
  22. "arbitrary_element",
  23. "pairwise",
  24. "groups",
  25. "create_random_state",
  26. "create_py_random_state",
  27. "PythonRandomInterface",
  28. "PythonRandomViaNumpyBits",
  29. "nodes_equal",
  30. "edges_equal",
  31. "graphs_equal",
  32. "_clear_cache",
  33. ]
  34. # some cookbook stuff
  35. # used in deciding whether something is a bunch of nodes, edges, etc.
  36. # see G.add_nodes and others in Graph Class in networkx/base.py
  37. def flatten(obj, result=None):
  38. """Return flattened version of (possibly nested) iterable object."""
  39. if not isinstance(obj, Iterable | Sized) or isinstance(obj, str):
  40. return obj
  41. if result is None:
  42. result = []
  43. for item in obj:
  44. if not isinstance(item, Iterable | Sized) or isinstance(item, str):
  45. result.append(item)
  46. else:
  47. flatten(item, result)
  48. return tuple(result)
  49. def make_list_of_ints(sequence):
  50. """Return list of ints from sequence of integral numbers.
  51. All elements of the sequence must satisfy int(element) == element
  52. or a ValueError is raised. Sequence is iterated through once.
  53. If sequence is a list, the non-int values are replaced with ints.
  54. So, no new list is created
  55. """
  56. if not isinstance(sequence, list):
  57. result = []
  58. for i in sequence:
  59. errmsg = f"sequence is not all integers: {i}"
  60. try:
  61. ii = int(i)
  62. except ValueError:
  63. raise nx.NetworkXError(errmsg) from None
  64. if ii != i:
  65. raise nx.NetworkXError(errmsg)
  66. result.append(ii)
  67. return result
  68. # original sequence is a list... in-place conversion to ints
  69. for indx, i in enumerate(sequence):
  70. errmsg = f"sequence is not all integers: {i}"
  71. if isinstance(i, int):
  72. continue
  73. try:
  74. ii = int(i)
  75. except ValueError:
  76. raise nx.NetworkXError(errmsg) from None
  77. if ii != i:
  78. raise nx.NetworkXError(errmsg)
  79. sequence[indx] = ii
  80. return sequence
  81. def dict_to_numpy_array(d, mapping=None):
  82. """Convert a dictionary of dictionaries to a numpy array
  83. with optional mapping."""
  84. try:
  85. return _dict_to_numpy_array2(d, mapping)
  86. except (AttributeError, TypeError):
  87. # AttributeError is when no mapping was provided and v.keys() fails.
  88. # TypeError is when a mapping was provided and d[k1][k2] fails.
  89. return _dict_to_numpy_array1(d, mapping)
  90. def _dict_to_numpy_array2(d, mapping=None):
  91. """Convert a dictionary of dictionaries to a 2d numpy array
  92. with optional mapping.
  93. """
  94. import numpy as np
  95. if mapping is None:
  96. s = set(d.keys())
  97. for k, v in d.items():
  98. s.update(v.keys())
  99. mapping = dict(zip(s, range(len(s))))
  100. n = len(mapping)
  101. a = np.zeros((n, n))
  102. for k1, i in mapping.items():
  103. for k2, j in mapping.items():
  104. try:
  105. a[i, j] = d[k1][k2]
  106. except KeyError:
  107. pass
  108. return a
  109. def _dict_to_numpy_array1(d, mapping=None):
  110. """Convert a dictionary of numbers to a 1d numpy array with optional mapping."""
  111. import numpy as np
  112. if mapping is None:
  113. s = set(d.keys())
  114. mapping = dict(zip(s, range(len(s))))
  115. n = len(mapping)
  116. a = np.zeros(n)
  117. for k1, i in mapping.items():
  118. i = mapping[k1]
  119. a[i] = d[k1]
  120. return a
  121. def arbitrary_element(iterable):
  122. """Returns an arbitrary element of `iterable` without removing it.
  123. This is most useful for "peeking" at an arbitrary element of a set,
  124. but can be used for any list, dictionary, etc., as well.
  125. Parameters
  126. ----------
  127. iterable : `abc.collections.Iterable` instance
  128. Any object that implements ``__iter__``, e.g. set, dict, list, tuple,
  129. etc.
  130. Returns
  131. -------
  132. The object that results from ``next(iter(iterable))``
  133. Raises
  134. ------
  135. ValueError
  136. If `iterable` is an iterator (because the current implementation of
  137. this function would consume an element from the iterator).
  138. Examples
  139. --------
  140. Arbitrary elements from common Iterable objects:
  141. >>> nx.utils.arbitrary_element([1, 2, 3]) # list
  142. 1
  143. >>> nx.utils.arbitrary_element((1, 2, 3)) # tuple
  144. 1
  145. >>> nx.utils.arbitrary_element({1, 2, 3}) # set
  146. 1
  147. >>> d = {k: v for k, v in zip([1, 2, 3], [3, 2, 1])}
  148. >>> nx.utils.arbitrary_element(d) # dict_keys
  149. 1
  150. >>> nx.utils.arbitrary_element(d.values()) # dict values
  151. 3
  152. `str` is also an Iterable:
  153. >>> nx.utils.arbitrary_element("hello")
  154. 'h'
  155. :exc:`ValueError` is raised if `iterable` is an iterator:
  156. >>> iterator = iter([1, 2, 3]) # Iterator, *not* Iterable
  157. >>> nx.utils.arbitrary_element(iterator)
  158. Traceback (most recent call last):
  159. ...
  160. ValueError: cannot return an arbitrary item from an iterator
  161. Notes
  162. -----
  163. This function does not return a *random* element. If `iterable` is
  164. ordered, sequential calls will return the same value::
  165. >>> l = [1, 2, 3]
  166. >>> nx.utils.arbitrary_element(l)
  167. 1
  168. >>> nx.utils.arbitrary_element(l)
  169. 1
  170. """
  171. if isinstance(iterable, Iterator):
  172. raise ValueError("cannot return an arbitrary item from an iterator")
  173. # Another possible implementation is ``for x in iterable: return x``.
  174. return next(iter(iterable))
  175. def pairwise(iterable, cyclic=False):
  176. """Return successive overlapping pairs taken from an input iterable.
  177. Parameters
  178. ----------
  179. iterable : iterable
  180. An iterable from which to generate pairs.
  181. cyclic : bool, optional (default=False)
  182. If `True`, a pair with the last and first items is included at the end.
  183. Returns
  184. -------
  185. iterator
  186. An iterator over successive overlapping pairs from the `iterable`.
  187. See Also
  188. --------
  189. itertools.pairwise
  190. Examples
  191. --------
  192. >>> list(nx.utils.pairwise([1, 2, 3, 4]))
  193. [(1, 2), (2, 3), (3, 4)]
  194. >>> list(nx.utils.pairwise([1, 2, 3, 4], cyclic=True))
  195. [(1, 2), (2, 3), (3, 4), (4, 1)]
  196. """
  197. if not cyclic:
  198. return itertools.pairwise(iterable)
  199. a, b = tee(iterable)
  200. first = next(b, None)
  201. return zip(a, chain(b, (first,)))
  202. def groups(many_to_one):
  203. """Converts a many-to-one mapping into a one-to-many mapping.
  204. `many_to_one` must be a dictionary whose keys and values are all
  205. :term:`hashable`.
  206. The return value is a dictionary mapping values from `many_to_one`
  207. to sets of keys from `many_to_one` that have that value.
  208. Examples
  209. --------
  210. >>> from networkx.utils import groups
  211. >>> many_to_one = {"a": 1, "b": 1, "c": 2, "d": 3, "e": 3}
  212. >>> groups(many_to_one) # doctest: +SKIP
  213. {1: {'a', 'b'}, 2: {'c'}, 3: {'e', 'd'}}
  214. """
  215. one_to_many = defaultdict(set)
  216. for v, k in many_to_one.items():
  217. one_to_many[k].add(v)
  218. return dict(one_to_many)
  219. def create_random_state(random_state=None):
  220. """Returns a numpy.random.RandomState or numpy.random.Generator instance
  221. depending on input.
  222. Parameters
  223. ----------
  224. random_state : int or NumPy RandomState or Generator instance, optional (default=None)
  225. If int, return a numpy.random.RandomState instance set with seed=int.
  226. if `numpy.random.RandomState` instance, return it.
  227. if `numpy.random.Generator` instance, return it.
  228. if None or numpy.random, return the global random number generator used
  229. by numpy.random.
  230. """
  231. import numpy as np
  232. if random_state is None or random_state is np.random:
  233. return np.random.mtrand._rand
  234. if isinstance(random_state, np.random.RandomState):
  235. return random_state
  236. if isinstance(random_state, int):
  237. return np.random.RandomState(random_state)
  238. if isinstance(random_state, np.random.Generator):
  239. return random_state
  240. msg = (
  241. f"{random_state} cannot be used to create a numpy.random.RandomState or\n"
  242. "numpy.random.Generator instance"
  243. )
  244. raise ValueError(msg)
  245. class PythonRandomViaNumpyBits(random.Random):
  246. """Provide the random.random algorithms using a numpy.random bit generator
  247. The intent is to allow people to contribute code that uses Python's random
  248. library, but still allow users to provide a single easily controlled random
  249. bit-stream for all work with NetworkX. This implementation is based on helpful
  250. comments and code from Robert Kern on NumPy's GitHub Issue #24458.
  251. This implementation supersedes that of `PythonRandomInterface` which rewrote
  252. methods to account for subtle differences in API between `random` and
  253. `numpy.random`. Instead this subclasses `random.Random` and overwrites
  254. the methods `random`, `getrandbits`, `getstate`, `setstate` and `seed`.
  255. It makes them use the rng values from an input numpy `RandomState` or `Generator`.
  256. Those few methods allow the rest of the `random.Random` methods to provide
  257. the API interface of `random.random` while using randomness generated by
  258. a numpy generator.
  259. """
  260. def __init__(self, rng=None):
  261. try:
  262. import numpy as np
  263. except ImportError:
  264. msg = "numpy not found, only random.random available."
  265. warnings.warn(msg, ImportWarning)
  266. if rng is None:
  267. self._rng = np.random.mtrand._rand
  268. else:
  269. self._rng = rng
  270. # Not necessary, given our overriding of gauss() below, but it's
  271. # in the superclass and nominally public, so initialize it here.
  272. self.gauss_next = None
  273. def random(self):
  274. """Get the next random number in the range 0.0 <= X < 1.0."""
  275. return self._rng.random()
  276. def getrandbits(self, k):
  277. """getrandbits(k) -> x. Generates an int with k random bits."""
  278. if k < 0:
  279. raise ValueError("number of bits must be non-negative")
  280. numbytes = (k + 7) // 8 # bits / 8 and rounded up
  281. x = int.from_bytes(self._rng.bytes(numbytes), "big")
  282. return x >> (numbytes * 8 - k) # trim excess bits
  283. def getstate(self):
  284. return self._rng.__getstate__()
  285. def setstate(self, state):
  286. self._rng.__setstate__(state)
  287. def seed(self, *args, **kwds):
  288. "Do nothing override method."
  289. raise NotImplementedError("seed() not implemented in PythonRandomViaNumpyBits")
  290. ##################################################################
  291. class PythonRandomInterface:
  292. """PythonRandomInterface is included for backward compatibility
  293. New code should use PythonRandomViaNumpyBits instead.
  294. """
  295. def __init__(self, rng=None):
  296. try:
  297. import numpy as np
  298. except ImportError:
  299. msg = "numpy not found, only random.random available."
  300. warnings.warn(msg, ImportWarning)
  301. if rng is None:
  302. self._rng = np.random.mtrand._rand
  303. else:
  304. self._rng = rng
  305. def random(self):
  306. return self._rng.random()
  307. def uniform(self, a, b):
  308. return a + (b - a) * self._rng.random()
  309. def randrange(self, a, b=None):
  310. import numpy as np
  311. if b is None:
  312. a, b = 0, a
  313. if b > 9223372036854775807: # from np.iinfo(np.int64).max
  314. tmp_rng = PythonRandomViaNumpyBits(self._rng)
  315. return tmp_rng.randrange(a, b)
  316. if isinstance(self._rng, np.random.Generator):
  317. return self._rng.integers(a, b)
  318. return self._rng.randint(a, b)
  319. # NOTE: the numpy implementations of `choice` don't support strings, so
  320. # this cannot be replaced with self._rng.choice
  321. def choice(self, seq):
  322. import numpy as np
  323. if isinstance(self._rng, np.random.Generator):
  324. idx = self._rng.integers(0, len(seq))
  325. else:
  326. idx = self._rng.randint(0, len(seq))
  327. return seq[idx]
  328. def gauss(self, mu, sigma):
  329. return self._rng.normal(mu, sigma)
  330. def shuffle(self, seq):
  331. return self._rng.shuffle(seq)
  332. # Some methods don't match API for numpy RandomState.
  333. # Commented out versions are not used by NetworkX
  334. def sample(self, seq, k):
  335. return self._rng.choice(list(seq), size=(k,), replace=False)
  336. def randint(self, a, b):
  337. import numpy as np
  338. if b > 9223372036854775807: # from np.iinfo(np.int64).max
  339. tmp_rng = PythonRandomViaNumpyBits(self._rng)
  340. return tmp_rng.randint(a, b)
  341. if isinstance(self._rng, np.random.Generator):
  342. return self._rng.integers(a, b + 1)
  343. return self._rng.randint(a, b + 1)
  344. # exponential as expovariate with 1/argument,
  345. def expovariate(self, scale):
  346. return self._rng.exponential(1 / scale)
  347. # pareto as paretovariate with argument,
  348. def paretovariate(self, shape):
  349. return self._rng.pareto(shape)
  350. # weibull as weibullvariate multiplied by beta,
  351. # def weibullvariate(self, alpha, beta):
  352. # return self._rng.weibull(alpha) * beta
  353. #
  354. # def triangular(self, low, high, mode):
  355. # return self._rng.triangular(low, mode, high)
  356. #
  357. # def choices(self, seq, weights=None, cum_weights=None, k=1):
  358. # return self._rng.choice(seq
  359. def create_py_random_state(random_state=None):
  360. """Returns a random.Random instance depending on input.
  361. Parameters
  362. ----------
  363. random_state : int or random number generator or None (default=None)
  364. - If int, return a `random.Random` instance set with seed=int.
  365. - If `random.Random` instance, return it.
  366. - If None or the `np.random` package, return the global random number
  367. generator used by `np.random`.
  368. - If an `np.random.Generator` instance, or the `np.random` package, or
  369. the global numpy random number generator, then return it.
  370. wrapped in a `PythonRandomViaNumpyBits` class.
  371. - If a `PythonRandomViaNumpyBits` instance, return it.
  372. - If a `PythonRandomInterface` instance, return it.
  373. - If a `np.random.RandomState` instance and not the global numpy default,
  374. return it wrapped in `PythonRandomInterface` for backward bit-stream
  375. matching with legacy code.
  376. Notes
  377. -----
  378. - A diagram intending to illustrate the relationships behind our support
  379. for numpy random numbers is called
  380. `NetworkX Numpy Random Numbers <https://excalidraw.com/#room=b5303f2b03d3af7ccc6a,e5ZDIWdWWCTTsg8OqoRvPA>`_.
  381. - More discussion about this support also appears in
  382. `gh-6869#comment <https://github.com/networkx/networkx/pull/6869#issuecomment-1944799534>`_.
  383. - Wrappers of numpy.random number generators allow them to mimic the Python random
  384. number generation algorithms. For example, Python can create arbitrarily large
  385. random ints, and the wrappers use Numpy bit-streams with CPython's random module
  386. to choose arbitrarily large random integers too.
  387. - We provide two wrapper classes:
  388. `PythonRandomViaNumpyBits` is usually what you want and is always used for
  389. `np.Generator` instances. But for users who need to recreate random numbers
  390. produced in NetworkX 3.2 or earlier, we maintain the `PythonRandomInterface`
  391. wrapper as well. We use it only used if passed a (non-default) `np.RandomState`
  392. instance pre-initialized from a seed. Otherwise the newer wrapper is used.
  393. """
  394. if random_state is None or random_state is random:
  395. return random._inst
  396. if isinstance(random_state, random.Random):
  397. return random_state
  398. if isinstance(random_state, int):
  399. return random.Random(random_state)
  400. try:
  401. import numpy as np
  402. except ImportError:
  403. pass
  404. else:
  405. if isinstance(random_state, PythonRandomInterface | PythonRandomViaNumpyBits):
  406. return random_state
  407. if isinstance(random_state, np.random.Generator):
  408. return PythonRandomViaNumpyBits(random_state)
  409. if random_state is np.random:
  410. return PythonRandomViaNumpyBits(np.random.mtrand._rand)
  411. if isinstance(random_state, np.random.RandomState):
  412. if random_state is np.random.mtrand._rand:
  413. return PythonRandomViaNumpyBits(random_state)
  414. # Only need older interface if specially constructed RandomState used
  415. return PythonRandomInterface(random_state)
  416. msg = f"{random_state} cannot be used to generate a random.Random instance"
  417. raise ValueError(msg)
  418. def nodes_equal(nodes1, nodes2):
  419. """Check if nodes are equal.
  420. Equality here means equal as Python objects.
  421. Node data must match if included.
  422. The order of nodes is not relevant.
  423. Parameters
  424. ----------
  425. nodes1, nodes2 : iterables of nodes, or (node, datadict) tuples
  426. Returns
  427. -------
  428. bool
  429. True if nodes are equal, False otherwise.
  430. """
  431. nlist1 = list(nodes1)
  432. nlist2 = list(nodes2)
  433. try:
  434. d1 = dict(nlist1)
  435. d2 = dict(nlist2)
  436. except (ValueError, TypeError):
  437. d1 = dict.fromkeys(nlist1)
  438. d2 = dict.fromkeys(nlist2)
  439. return d1 == d2
  440. def edges_equal(edges1, edges2, *, directed=False):
  441. """Return whether edgelists are equal.
  442. Equality here means equal as Python objects. Edge data must match
  443. if included. Ordering of edges in an edgelist is not relevant;
  444. ordering of nodes in an edge is only relevant if ``directed == True``.
  445. Parameters
  446. ----------
  447. edges1, edges2 : iterables of tuples
  448. Each tuple can be
  449. an edge tuple ``(u, v)``, or
  450. an edge tuple with data `dict` s ``(u, v, d)``, or
  451. an edge tuple with keys and data `dict` s ``(u, v, k, d)``.
  452. directed : bool, optional (default=False)
  453. If `True`, edgelists are treated as coming from directed
  454. graphs.
  455. Returns
  456. -------
  457. bool
  458. `True` if edgelists are equal, `False` otherwise.
  459. Examples
  460. --------
  461. >>> G1 = nx.complete_graph(3)
  462. >>> G2 = nx.cycle_graph(3)
  463. >>> edges_equal(G1.edges, G2.edges)
  464. True
  465. Edge order is not taken into account:
  466. >>> G1 = nx.Graph([(0, 1), (1, 2)])
  467. >>> G2 = nx.Graph([(1, 2), (0, 1)])
  468. >>> edges_equal(G1.edges, G2.edges)
  469. True
  470. The `directed` parameter controls whether edges are treated as
  471. coming from directed graphs.
  472. >>> DG1 = nx.DiGraph([(0, 1)])
  473. >>> DG2 = nx.DiGraph([(1, 0)])
  474. >>> edges_equal(DG1.edges, DG2.edges, directed=False) # Not recommended.
  475. True
  476. >>> edges_equal(DG1.edges, DG2.edges, directed=True)
  477. False
  478. This function is meant to be used on edgelists (i.e. the output of a
  479. ``G.edges()`` call), and can give unexpected results on unprocessed
  480. lists of edges:
  481. >>> l1 = [(0, 1)]
  482. >>> l2 = [(0, 1), (1, 0)]
  483. >>> edges_equal(l1, l2) # Not recommended.
  484. False
  485. >>> G1 = nx.Graph(l1)
  486. >>> G2 = nx.Graph(l2)
  487. >>> edges_equal(G1.edges, G2.edges)
  488. True
  489. >>> DG1 = nx.DiGraph(l1)
  490. >>> DG2 = nx.DiGraph(l2)
  491. >>> edges_equal(DG1.edges, DG2.edges, directed=True)
  492. False
  493. """
  494. d1 = defaultdict(list)
  495. d2 = defaultdict(list)
  496. for e1, e2 in zip_longest(edges1, edges2, fillvalue=None):
  497. if e1 is None or e2 is None:
  498. return False # One is longer.
  499. for e, d in [(e1, d1), (e2, d2)]:
  500. u, v, *data = e
  501. d[u, v].append(data)
  502. if not directed:
  503. d[v, u].append(data)
  504. # Can check one direction because lengths are the same.
  505. return all(d1[e].count(data) == d2[e].count(data) for e in d1 for data in d1[e])
  506. def graphs_equal(graph1, graph2):
  507. """Check if graphs are equal.
  508. Equality here means equal as Python objects (not isomorphism).
  509. Node, edge and graph data must match.
  510. Parameters
  511. ----------
  512. graph1, graph2 : graph
  513. Returns
  514. -------
  515. bool
  516. True if graphs are equal, False otherwise.
  517. """
  518. return (
  519. graph1.adj == graph2.adj
  520. and graph1.nodes == graph2.nodes
  521. and graph1.graph == graph2.graph
  522. )
  523. def _clear_cache(G):
  524. """Clear the cache of a graph (currently stores converted graphs).
  525. Caching is controlled via ``nx.config.cache_converted_graphs`` configuration.
  526. """
  527. if cache := getattr(G, "__networkx_cache__", None):
  528. cache.clear()
  529. def check_create_using(create_using, *, directed=None, multigraph=None, default=None):
  530. """Assert that create_using has good properties
  531. This checks for desired directedness and multi-edge properties.
  532. It returns `create_using` unless that is `None` when it returns
  533. the optionally specified default value.
  534. Parameters
  535. ----------
  536. create_using : None, graph class or instance
  537. The input value of create_using for a function.
  538. directed : None or bool
  539. Whether to check `create_using.is_directed() == directed`.
  540. If None, do not assert directedness.
  541. multigraph : None or bool
  542. Whether to check `create_using.is_multigraph() == multigraph`.
  543. If None, do not assert multi-edge property.
  544. default : None or graph class
  545. The graph class to return if create_using is None.
  546. Returns
  547. -------
  548. create_using : graph class or instance
  549. The provided graph class or instance, or if None, the `default` value.
  550. Raises
  551. ------
  552. NetworkXError
  553. When `create_using` doesn't match the properties specified by `directed`
  554. or `multigraph` parameters.
  555. """
  556. if default is None:
  557. default = nx.Graph
  558. G = create_using if create_using is not None else default
  559. G_directed = G.is_directed(None) if isinstance(G, type) else G.is_directed()
  560. G_multigraph = G.is_multigraph(None) if isinstance(G, type) else G.is_multigraph()
  561. if directed is not None:
  562. if directed and not G_directed:
  563. raise nx.NetworkXError("create_using must be directed")
  564. if not directed and G_directed:
  565. raise nx.NetworkXError("create_using must not be directed")
  566. if multigraph is not None:
  567. if multigraph and not G_multigraph:
  568. raise nx.NetworkXError("create_using must be a multi-graph")
  569. if not multigraph and G_multigraph:
  570. raise nx.NetworkXError("create_using must not be a multi-graph")
  571. return G