Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/networkx/utils/misc.py: 18%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

243 statements  

1""" 

2Miscellaneous Helpers for NetworkX. 

3 

4These are not imported into the base networkx namespace but 

5can be accessed, for example, as 

6 

7>>> import networkx as nx 

8>>> nx.utils.make_list_of_ints({1, 2, 3}) 

9[1, 2, 3] 

10>>> nx.utils.arbitrary_element({5, 1, 7}) # doctest: +SKIP 

111 

12""" 

13 

14import itertools 

15import random 

16import warnings 

17from collections import defaultdict 

18from collections.abc import Iterable, Iterator, Sized 

19from itertools import chain, tee, zip_longest 

20 

21import networkx as nx 

22 

23__all__ = [ 

24 "flatten", 

25 "make_list_of_ints", 

26 "dict_to_numpy_array", 

27 "arbitrary_element", 

28 "pairwise", 

29 "groups", 

30 "create_random_state", 

31 "create_py_random_state", 

32 "PythonRandomInterface", 

33 "PythonRandomViaNumpyBits", 

34 "nodes_equal", 

35 "edges_equal", 

36 "graphs_equal", 

37 "_clear_cache", 

38] 

39 

40 

41# some cookbook stuff 

42# used in deciding whether something is a bunch of nodes, edges, etc. 

43# see G.add_nodes and others in Graph Class in networkx/base.py 

44 

45 

46def flatten(obj, result=None): 

47 """Return flattened version of (possibly nested) iterable object.""" 

48 if not isinstance(obj, Iterable | Sized) or isinstance(obj, str): 

49 return obj 

50 if result is None: 

51 result = [] 

52 for item in obj: 

53 if not isinstance(item, Iterable | Sized) or isinstance(item, str): 

54 result.append(item) 

55 else: 

56 flatten(item, result) 

57 return tuple(result) 

58 

59 

60def make_list_of_ints(sequence): 

61 """Return list of ints from sequence of integral numbers. 

62 

63 All elements of the sequence must satisfy int(element) == element 

64 or a ValueError is raised. Sequence is iterated through once. 

65 

66 If sequence is a list, the non-int values are replaced with ints. 

67 So, no new list is created 

68 """ 

69 if not isinstance(sequence, list): 

70 result = [] 

71 for i in sequence: 

72 errmsg = f"sequence is not all integers: {i}" 

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 result.append(ii) 

80 return result 

81 # original sequence is a list... in-place conversion to ints 

82 for indx, i in enumerate(sequence): 

83 errmsg = f"sequence is not all integers: {i}" 

84 if isinstance(i, int): 

85 continue 

86 try: 

87 ii = int(i) 

88 except ValueError: 

89 raise nx.NetworkXError(errmsg) from None 

90 if ii != i: 

91 raise nx.NetworkXError(errmsg) 

92 sequence[indx] = ii 

93 return sequence 

94 

95 

96def dict_to_numpy_array(d, mapping=None): 

97 """Convert a dictionary of dictionaries to a numpy array 

98 with optional mapping.""" 

99 try: 

100 return _dict_to_numpy_array2(d, mapping) 

101 except (AttributeError, TypeError): 

102 # AttributeError is when no mapping was provided and v.keys() fails. 

103 # TypeError is when a mapping was provided and d[k1][k2] fails. 

104 return _dict_to_numpy_array1(d, mapping) 

105 

106 

107def _dict_to_numpy_array2(d, mapping=None): 

108 """Convert a dictionary of dictionaries to a 2d numpy array 

109 with optional mapping. 

110 

111 """ 

112 import numpy as np 

113 

114 if mapping is None: 

115 s = set(d.keys()) 

116 for k, v in d.items(): 

117 s.update(v.keys()) 

118 mapping = dict(zip(s, range(len(s)))) 

119 n = len(mapping) 

120 a = np.zeros((n, n)) 

121 for k1, i in mapping.items(): 

122 for k2, j in mapping.items(): 

123 try: 

124 a[i, j] = d[k1][k2] 

125 except KeyError: 

126 pass 

127 return a 

128 

129 

130def _dict_to_numpy_array1(d, mapping=None): 

131 """Convert a dictionary of numbers to a 1d numpy array with optional mapping.""" 

132 import numpy as np 

133 

134 if mapping is None: 

135 s = set(d.keys()) 

136 mapping = dict(zip(s, range(len(s)))) 

137 n = len(mapping) 

138 a = np.zeros(n) 

139 for k1, i in mapping.items(): 

140 i = mapping[k1] 

141 a[i] = d[k1] 

142 return a 

143 

144 

145def arbitrary_element(iterable): 

146 """Returns an arbitrary element of `iterable` without removing it. 

147 

148 This is most useful for "peeking" at an arbitrary element of a set, 

149 but can be used for any list, dictionary, etc., as well. 

150 

151 Parameters 

152 ---------- 

153 iterable : `abc.collections.Iterable` instance 

154 Any object that implements ``__iter__``, e.g. set, dict, list, tuple, 

155 etc. 

156 

157 Returns 

158 ------- 

159 The object that results from ``next(iter(iterable))`` 

160 

161 Raises 

162 ------ 

163 ValueError 

164 If `iterable` is an iterator (because the current implementation of 

165 this function would consume an element from the iterator). 

166 

167 Examples 

168 -------- 

169 Arbitrary elements from common Iterable objects: 

170 

171 >>> nx.utils.arbitrary_element([1, 2, 3]) # list 

172 1 

173 >>> nx.utils.arbitrary_element((1, 2, 3)) # tuple 

174 1 

175 >>> nx.utils.arbitrary_element({1, 2, 3}) # set 

176 1 

177 >>> d = {k: v for k, v in zip([1, 2, 3], [3, 2, 1])} 

178 >>> nx.utils.arbitrary_element(d) # dict_keys 

179 1 

180 >>> nx.utils.arbitrary_element(d.values()) # dict values 

181 3 

182 

183 `str` is also an Iterable: 

184 

185 >>> nx.utils.arbitrary_element("hello") 

186 'h' 

187 

188 :exc:`ValueError` is raised if `iterable` is an iterator: 

189 

190 >>> iterator = iter([1, 2, 3]) # Iterator, *not* Iterable 

191 >>> nx.utils.arbitrary_element(iterator) 

192 Traceback (most recent call last): 

193 ... 

194 ValueError: cannot return an arbitrary item from an iterator 

195 

196 Notes 

197 ----- 

198 This function does not return a *random* element. If `iterable` is 

199 ordered, sequential calls will return the same value:: 

200 

201 >>> l = [1, 2, 3] 

202 >>> nx.utils.arbitrary_element(l) 

203 1 

204 >>> nx.utils.arbitrary_element(l) 

205 1 

206 

207 """ 

208 if isinstance(iterable, Iterator): 

209 raise ValueError("cannot return an arbitrary item from an iterator") 

210 # Another possible implementation is ``for x in iterable: return x``. 

211 return next(iter(iterable)) 

212 

213 

214def pairwise(iterable, cyclic=False): 

215 """Return successive overlapping pairs taken from an input iterable. 

216 

217 Parameters 

218 ---------- 

219 iterable : iterable 

220 An iterable from which to generate pairs. 

221 

222 cyclic : bool, optional (default=False) 

223 If `True`, a pair with the last and first items is included at the end. 

224 

225 Returns 

226 ------- 

227 iterator 

228 An iterator over successive overlapping pairs from the `iterable`. 

229 

230 See Also 

231 -------- 

232 itertools.pairwise 

233 

234 Examples 

235 -------- 

236 >>> list(nx.utils.pairwise([1, 2, 3, 4])) 

237 [(1, 2), (2, 3), (3, 4)] 

238 

239 >>> list(nx.utils.pairwise([1, 2, 3, 4], cyclic=True)) 

240 [(1, 2), (2, 3), (3, 4), (4, 1)] 

241 """ 

242 if not cyclic: 

243 return itertools.pairwise(iterable) 

244 a, b = tee(iterable) 

245 first = next(b, None) 

246 return zip(a, chain(b, (first,))) 

247 

248 

249def groups(many_to_one): 

250 """Converts a many-to-one mapping into a one-to-many mapping. 

251 

252 `many_to_one` must be a dictionary whose keys and values are all 

253 :term:`hashable`. 

254 

255 The return value is a dictionary mapping values from `many_to_one` 

256 to sets of keys from `many_to_one` that have that value. 

257 

258 Examples 

259 -------- 

260 >>> from networkx.utils import groups 

261 >>> many_to_one = {"a": 1, "b": 1, "c": 2, "d": 3, "e": 3} 

262 >>> groups(many_to_one) # doctest: +SKIP 

263 {1: {'a', 'b'}, 2: {'c'}, 3: {'e', 'd'}} 

264 """ 

265 one_to_many = defaultdict(set) 

266 for v, k in many_to_one.items(): 

267 one_to_many[k].add(v) 

268 return dict(one_to_many) 

269 

270 

271def create_random_state(random_state=None): 

272 """Returns a numpy.random.RandomState or numpy.random.Generator instance 

273 depending on input. 

274 

275 Parameters 

276 ---------- 

277 random_state : int or NumPy RandomState or Generator instance, optional (default=None) 

278 If int, return a numpy.random.RandomState instance set with seed=int. 

279 if `numpy.random.RandomState` instance, return it. 

280 if `numpy.random.Generator` instance, return it. 

281 if None or numpy.random, return the global random number generator used 

282 by numpy.random. 

283 """ 

284 import numpy as np 

285 

286 if random_state is None or random_state is np.random: 

287 return np.random.mtrand._rand 

288 if isinstance(random_state, np.random.RandomState): 

289 return random_state 

290 if isinstance(random_state, int): 

291 return np.random.RandomState(random_state) 

292 if isinstance(random_state, np.random.Generator): 

293 return random_state 

294 msg = ( 

295 f"{random_state} cannot be used to create a numpy.random.RandomState or\n" 

296 "numpy.random.Generator instance" 

297 ) 

298 raise ValueError(msg) 

299 

300 

301class PythonRandomViaNumpyBits(random.Random): 

302 """Provide the random.random algorithms using a numpy.random bit generator 

303 

304 The intent is to allow people to contribute code that uses Python's random 

305 library, but still allow users to provide a single easily controlled random 

306 bit-stream for all work with NetworkX. This implementation is based on helpful 

307 comments and code from Robert Kern on NumPy's GitHub Issue #24458. 

308 

309 This implementation supersedes that of `PythonRandomInterface` which rewrote 

310 methods to account for subtle differences in API between `random` and 

311 `numpy.random`. Instead this subclasses `random.Random` and overwrites 

312 the methods `random`, `getrandbits`, `getstate`, `setstate` and `seed`. 

313 It makes them use the rng values from an input numpy `RandomState` or `Generator`. 

314 Those few methods allow the rest of the `random.Random` methods to provide 

315 the API interface of `random.random` while using randomness generated by 

316 a numpy generator. 

317 """ 

318 

319 def __init__(self, rng=None): 

320 try: 

321 import numpy as np 

322 except ImportError: 

323 msg = "numpy not found, only random.random available." 

324 warnings.warn(msg, ImportWarning) 

325 

326 if rng is None: 

327 self._rng = np.random.mtrand._rand 

328 else: 

329 self._rng = rng 

330 

331 # Not necessary, given our overriding of gauss() below, but it's 

332 # in the superclass and nominally public, so initialize it here. 

333 self.gauss_next = None 

334 

335 def random(self): 

336 """Get the next random number in the range 0.0 <= X < 1.0.""" 

337 return self._rng.random() 

338 

339 def getrandbits(self, k): 

340 """getrandbits(k) -> x. Generates an int with k random bits.""" 

341 if k < 0: 

342 raise ValueError("number of bits must be non-negative") 

343 numbytes = (k + 7) // 8 # bits / 8 and rounded up 

344 x = int.from_bytes(self._rng.bytes(numbytes), "big") 

345 return x >> (numbytes * 8 - k) # trim excess bits 

346 

347 def getstate(self): 

348 return self._rng.__getstate__() 

349 

350 def setstate(self, state): 

351 self._rng.__setstate__(state) 

352 

353 def seed(self, *args, **kwds): 

354 "Do nothing override method." 

355 raise NotImplementedError("seed() not implemented in PythonRandomViaNumpyBits") 

356 

357 

358################################################################## 

359class PythonRandomInterface: 

360 """PythonRandomInterface is included for backward compatibility 

361 New code should use PythonRandomViaNumpyBits instead. 

362 """ 

363 

364 def __init__(self, rng=None): 

365 try: 

366 import numpy as np 

367 except ImportError: 

368 msg = "numpy not found, only random.random available." 

369 warnings.warn(msg, ImportWarning) 

370 

371 if rng is None: 

372 self._rng = np.random.mtrand._rand 

373 else: 

374 self._rng = rng 

375 

376 def random(self): 

377 return self._rng.random() 

378 

379 def uniform(self, a, b): 

380 return a + (b - a) * self._rng.random() 

381 

382 def randrange(self, a, b=None): 

383 import numpy as np 

384 

385 if b is None: 

386 a, b = 0, a 

387 if b > 9223372036854775807: # from np.iinfo(np.int64).max 

388 tmp_rng = PythonRandomViaNumpyBits(self._rng) 

389 return tmp_rng.randrange(a, b) 

390 

391 if isinstance(self._rng, np.random.Generator): 

392 return self._rng.integers(a, b) 

393 return self._rng.randint(a, b) 

394 

395 # NOTE: the numpy implementations of `choice` don't support strings, so 

396 # this cannot be replaced with self._rng.choice 

397 def choice(self, seq): 

398 import numpy as np 

399 

400 if isinstance(self._rng, np.random.Generator): 

401 idx = self._rng.integers(0, len(seq)) 

402 else: 

403 idx = self._rng.randint(0, len(seq)) 

404 return seq[idx] 

405 

406 def gauss(self, mu, sigma): 

407 return self._rng.normal(mu, sigma) 

408 

409 def shuffle(self, seq): 

410 return self._rng.shuffle(seq) 

411 

412 # Some methods don't match API for numpy RandomState. 

413 # Commented out versions are not used by NetworkX 

414 

415 def sample(self, seq, k): 

416 return self._rng.choice(list(seq), size=(k,), replace=False) 

417 

418 def randint(self, a, b): 

419 import numpy as np 

420 

421 if b > 9223372036854775807: # from np.iinfo(np.int64).max 

422 tmp_rng = PythonRandomViaNumpyBits(self._rng) 

423 return tmp_rng.randint(a, b) 

424 

425 if isinstance(self._rng, np.random.Generator): 

426 return self._rng.integers(a, b + 1) 

427 return self._rng.randint(a, b + 1) 

428 

429 # exponential as expovariate with 1/argument, 

430 def expovariate(self, scale): 

431 return self._rng.exponential(1 / scale) 

432 

433 # pareto as paretovariate with argument, 

434 def paretovariate(self, shape): 

435 return self._rng.pareto(shape) 

436 

437 

438# weibull as weibullvariate multiplied by beta, 

439# def weibullvariate(self, alpha, beta): 

440# return self._rng.weibull(alpha) * beta 

441# 

442# def triangular(self, low, high, mode): 

443# return self._rng.triangular(low, mode, high) 

444# 

445# def choices(self, seq, weights=None, cum_weights=None, k=1): 

446# return self._rng.choice(seq 

447 

448 

449def create_py_random_state(random_state=None): 

450 """Returns a random.Random instance depending on input. 

451 

452 Parameters 

453 ---------- 

454 random_state : int or random number generator or None (default=None) 

455 - If int, return a `random.Random` instance set with seed=int. 

456 - If `random.Random` instance, return it. 

457 - If None or the `np.random` package, return the global random number 

458 generator used by `np.random`. 

459 - If an `np.random.Generator` instance, or the `np.random` package, or 

460 the global numpy random number generator, then return it. 

461 wrapped in a `PythonRandomViaNumpyBits` class. 

462 - If a `PythonRandomViaNumpyBits` instance, return it. 

463 - If a `PythonRandomInterface` instance, return it. 

464 - If a `np.random.RandomState` instance and not the global numpy default, 

465 return it wrapped in `PythonRandomInterface` for backward bit-stream 

466 matching with legacy code. 

467 

468 Notes 

469 ----- 

470 - A diagram intending to illustrate the relationships behind our support 

471 for numpy random numbers is called 

472 `NetworkX Numpy Random Numbers <https://excalidraw.com/#room=b5303f2b03d3af7ccc6a,e5ZDIWdWWCTTsg8OqoRvPA>`_. 

473 - More discussion about this support also appears in 

474 `gh-6869#comment <https://github.com/networkx/networkx/pull/6869#issuecomment-1944799534>`_. 

475 - Wrappers of numpy.random number generators allow them to mimic the Python random 

476 number generation algorithms. For example, Python can create arbitrarily large 

477 random ints, and the wrappers use Numpy bit-streams with CPython's random module 

478 to choose arbitrarily large random integers too. 

479 - We provide two wrapper classes: 

480 `PythonRandomViaNumpyBits` is usually what you want and is always used for 

481 `np.Generator` instances. But for users who need to recreate random numbers 

482 produced in NetworkX 3.2 or earlier, we maintain the `PythonRandomInterface` 

483 wrapper as well. We use it only used if passed a (non-default) `np.RandomState` 

484 instance pre-initialized from a seed. Otherwise the newer wrapper is used. 

485 """ 

486 if random_state is None or random_state is random: 

487 return random._inst 

488 if isinstance(random_state, random.Random): 

489 return random_state 

490 if isinstance(random_state, int): 

491 return random.Random(random_state) 

492 

493 try: 

494 import numpy as np 

495 except ImportError: 

496 pass 

497 else: 

498 if isinstance(random_state, PythonRandomInterface | PythonRandomViaNumpyBits): 

499 return random_state 

500 if isinstance(random_state, np.random.Generator): 

501 return PythonRandomViaNumpyBits(random_state) 

502 if random_state is np.random: 

503 return PythonRandomViaNumpyBits(np.random.mtrand._rand) 

504 

505 if isinstance(random_state, np.random.RandomState): 

506 if random_state is np.random.mtrand._rand: 

507 return PythonRandomViaNumpyBits(random_state) 

508 # Only need older interface if specially constructed RandomState used 

509 return PythonRandomInterface(random_state) 

510 

511 msg = f"{random_state} cannot be used to generate a random.Random instance" 

512 raise ValueError(msg) 

513 

514 

515def nodes_equal(nodes1, nodes2): 

516 """Check if nodes are equal. 

517 

518 Equality here means equal as Python objects. 

519 Node data must match if included. 

520 The order of nodes is not relevant. 

521 

522 Parameters 

523 ---------- 

524 nodes1, nodes2 : iterables of nodes, or (node, datadict) tuples 

525 

526 Returns 

527 ------- 

528 bool 

529 True if nodes are equal, False otherwise. 

530 """ 

531 nlist1 = list(nodes1) 

532 nlist2 = list(nodes2) 

533 try: 

534 d1 = dict(nlist1) 

535 d2 = dict(nlist2) 

536 except (ValueError, TypeError): 

537 d1 = dict.fromkeys(nlist1) 

538 d2 = dict.fromkeys(nlist2) 

539 return d1 == d2 

540 

541 

542def edges_equal(edges1, edges2, *, directed=False): 

543 """Return whether edgelists are equal. 

544 

545 Equality here means equal as Python objects. Edge data must match 

546 if included. Ordering of edges in an edgelist is not relevant; 

547 ordering of nodes in an edge is only relevant if ``directed == True``. 

548 

549 Parameters 

550 ---------- 

551 edges1, edges2 : iterables of tuples 

552 Each tuple can be 

553 an edge tuple ``(u, v)``, or 

554 an edge tuple with data `dict` s ``(u, v, d)``, or 

555 an edge tuple with keys and data `dict` s ``(u, v, k, d)``. 

556 

557 directed : bool, optional (default=False) 

558 If `True`, edgelists are treated as coming from directed 

559 graphs. 

560 

561 Returns 

562 ------- 

563 bool 

564 `True` if edgelists are equal, `False` otherwise. 

565 

566 Examples 

567 -------- 

568 >>> G1 = nx.complete_graph(3) 

569 >>> G2 = nx.cycle_graph(3) 

570 >>> edges_equal(G1.edges, G2.edges) 

571 True 

572 

573 Edge order is not taken into account: 

574 

575 >>> G1 = nx.Graph([(0, 1), (1, 2)]) 

576 >>> G2 = nx.Graph([(1, 2), (0, 1)]) 

577 >>> edges_equal(G1.edges, G2.edges) 

578 True 

579 

580 The `directed` parameter controls whether edges are treated as 

581 coming from directed graphs. 

582 

583 >>> DG1 = nx.DiGraph([(0, 1)]) 

584 >>> DG2 = nx.DiGraph([(1, 0)]) 

585 >>> edges_equal(DG1.edges, DG2.edges, directed=False) # Not recommended. 

586 True 

587 >>> edges_equal(DG1.edges, DG2.edges, directed=True) 

588 False 

589 

590 This function is meant to be used on edgelists (i.e. the output of a 

591 ``G.edges()`` call), and can give unexpected results on unprocessed 

592 lists of edges: 

593 

594 >>> l1 = [(0, 1)] 

595 >>> l2 = [(0, 1), (1, 0)] 

596 >>> edges_equal(l1, l2) # Not recommended. 

597 False 

598 >>> G1 = nx.Graph(l1) 

599 >>> G2 = nx.Graph(l2) 

600 >>> edges_equal(G1.edges, G2.edges) 

601 True 

602 >>> DG1 = nx.DiGraph(l1) 

603 >>> DG2 = nx.DiGraph(l2) 

604 >>> edges_equal(DG1.edges, DG2.edges, directed=True) 

605 False 

606 """ 

607 d1 = defaultdict(list) 

608 d2 = defaultdict(list) 

609 

610 for e1, e2 in zip_longest(edges1, edges2, fillvalue=None): 

611 if e1 is None or e2 is None: 

612 return False # One is longer. 

613 for e, d in [(e1, d1), (e2, d2)]: 

614 u, v, *data = e 

615 d[u, v].append(data) 

616 if not directed: 

617 d[v, u].append(data) 

618 

619 # Can check one direction because lengths are the same. 

620 return all(d1[e].count(data) == d2[e].count(data) for e in d1 for data in d1[e]) 

621 

622 

623def graphs_equal(graph1, graph2): 

624 """Check if graphs are equal. 

625 

626 Equality here means equal as Python objects (not isomorphism). 

627 Node, edge and graph data must match. 

628 

629 Parameters 

630 ---------- 

631 graph1, graph2 : graph 

632 

633 Returns 

634 ------- 

635 bool 

636 True if graphs are equal, False otherwise. 

637 """ 

638 return ( 

639 graph1.adj == graph2.adj 

640 and graph1.nodes == graph2.nodes 

641 and graph1.graph == graph2.graph 

642 ) 

643 

644 

645def _clear_cache(G): 

646 """Clear the cache of a graph (currently stores converted graphs). 

647 

648 Caching is controlled via ``nx.config.cache_converted_graphs`` configuration. 

649 """ 

650 if cache := getattr(G, "__networkx_cache__", None): 

651 cache.clear() 

652 

653 

654def check_create_using(create_using, *, directed=None, multigraph=None, default=None): 

655 """Assert that create_using has good properties 

656 

657 This checks for desired directedness and multi-edge properties. 

658 It returns `create_using` unless that is `None` when it returns 

659 the optionally specified default value. 

660 

661 Parameters 

662 ---------- 

663 create_using : None, graph class or instance 

664 The input value of create_using for a function. 

665 directed : None or bool 

666 Whether to check `create_using.is_directed() == directed`. 

667 If None, do not assert directedness. 

668 multigraph : None or bool 

669 Whether to check `create_using.is_multigraph() == multigraph`. 

670 If None, do not assert multi-edge property. 

671 default : None or graph class 

672 The graph class to return if create_using is None. 

673 

674 Returns 

675 ------- 

676 create_using : graph class or instance 

677 The provided graph class or instance, or if None, the `default` value. 

678 

679 Raises 

680 ------ 

681 NetworkXError 

682 When `create_using` doesn't match the properties specified by `directed` 

683 or `multigraph` parameters. 

684 """ 

685 if default is None: 

686 default = nx.Graph 

687 G = create_using if create_using is not None else default 

688 

689 G_directed = G.is_directed(None) if isinstance(G, type) else G.is_directed() 

690 G_multigraph = G.is_multigraph(None) if isinstance(G, type) else G.is_multigraph() 

691 

692 if directed is not None: 

693 if directed and not G_directed: 

694 raise nx.NetworkXError("create_using must be directed") 

695 if not directed and G_directed: 

696 raise nx.NetworkXError("create_using must not be directed") 

697 

698 if multigraph is not None: 

699 if multigraph and not G_multigraph: 

700 raise nx.NetworkXError("create_using must be a multi-graph") 

701 if not multigraph and G_multigraph: 

702 raise nx.NetworkXError("create_using must not be a multi-graph") 

703 return G