Skip to content

mnns.calculations

Functions to statically solve nanowire networks.

Functions:

Name Description
create_matrix

Create the Laplacian connectivity matrix.

get_connected_nodes

Returns a list of nodes which are connected to any of the given nodes.

scale_sol

Scale the voltage and current solutions by their characteristic values.

solve_drain_current

Solve for the current through each drain node of a NWN.

solve_edge_current

Solve for the current passing through each edge of a NWN. The direction is

solve_network

Solve for the voltages of each node in a given NWN. Each drain node will

solve_nodal_current

Solve for the current entering each node of a NWN. Only the entering

create_matrix

create_matrix(
    NWN: NanowireNetwork,
    value_type: str = "conductance",
    source_nodes: list[NWNNode] = None,
    drain_nodes: list[NWNNode] = None,
    ground_nodes: bool = False,
) -> scipy.sparse.csr_matrix

Create the Laplacian connectivity matrix.

Parameters:

Name Type Description Default
NWN Graph

Nanowire network.

required
value_type ('conductance', 'capacitance')

Weight to use for the Laplacian matrix. Default: "conductance".

"conductance"
source_nodes list of tuples

Only needed if ground_nodes is True to find which nodes are to be grounded. Default: None.

None
drain_nodes list of tuples

If a drain node is supplied, the row and column corresponding to the drain node are zeros and a one is placed at the row-column intersection. Default: None.

None
ground_nodes bool

If true, a small value is added to the main diagonal to avoid singular matrices. Default: False.

False

Returns:

Name Type Description
M csr_matrix

Resultant sparse Laplacian matrix.

Source code in mnns/calculations.py
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
def create_matrix(
    NWN: NanowireNetwork,
    value_type: str = "conductance",
    source_nodes: list[NWNNode] = None,
    drain_nodes: list[NWNNode] = None,
    ground_nodes: bool = False,
) -> scipy.sparse.csr_matrix:
    """
    Create the Laplacian connectivity matrix.

    Parameters
    ----------
    NWN : Graph
        Nanowire network.

    value_type : {"conductance", "capacitance"}, optional
        Weight to use for the Laplacian matrix. Default: "conductance".

    source_nodes : list of tuples, optional
        Only needed if ground_nodes is True to find which nodes are to be
        grounded. Default: None.

    drain_nodes : list of tuples, optional
        If a drain node is supplied, the row and column corresponding to
        the drain node are zeros and a one is placed at the row-column
        intersection. Default: None.

    ground_nodes : bool, optional
        If true, a small value is added to the main diagonal to avoid
        singular matrices. Default: False.

    Returns
    -------
    M : csr_matrix
        Resultant sparse Laplacian matrix.

    """
    # Error check
    TYPES = ["conductance", "capacitance"]
    if value_type not in TYPES:
        raise ValueError("Invalid matrix type.")

    # Default values
    if source_nodes is None:
        source_nodes = []
    if drain_nodes is None:
        drain_nodes = []

    # Get Laplacian matrix
    nodelist = NWN.graph["node_indices"].keys()
    nodelist_len = len(nodelist)
    M = laplacian_matrix(NWN, nodelist=nodelist, weight=value_type)

    # Ground every node with a huge resistor/tiny capacitor
    if ground_nodes:
        # Get list of node indices which are not connected to an electrode
        unconnected_indices = list(
            set(NWN.graph["node_indices"].values()).difference(
                set(
                    NWN.graph["node_indices"][node]
                    for node in get_connected_nodes(
                        NWN, [*source_nodes, *drain_nodes]
                    )
                )
            )
        )

        small = np.zeros(nodelist_len)
        small[unconnected_indices] = 1e-12

        # Add small value to diagonal, grounding all non-connected nodes
        M += scipy.sparse.dia_matrix(
            (small, [0]), shape=(nodelist_len, nodelist_len)
        )

    # Zero each of the drain nodes' row and column
    for drain in drain_nodes:
        # Change to lil since csr sparsity changes are expensive
        M = M.tolil()
        drain_index = NWN.graph["node_indices"][drain]
        M[drain_index] = 0
        M[:, drain_index] = 0
        M[drain_index, drain_index] = 1

    return M.tocsr()

get_connected_nodes

get_connected_nodes(
    NWN: NanowireNetwork, connected: list[NWNNode]
) -> set[NWNNode]

Returns a list of nodes which are connected to any of the given nodes.

Source code in mnns/calculations.py
26
27
28
29
30
31
32
33
34
35
36
37
def get_connected_nodes(
    NWN: NanowireNetwork, connected: list[NWNNode]
) -> set[NWNNode]:
    """
    Returns a list of nodes which are connected to any of the given nodes.

    """
    nodelist = set()
    for subset in nx.connected_components(NWN):
        if any(node in subset for node in connected):
            nodelist = nodelist.union(subset)
    return nodelist

scale_sol

scale_sol(
    NWN: NanowireNetwork, sol: NDArray
) -> npt.NDArray

Scale the voltage and current solutions by their characteristic values.

Source code in mnns/calculations.py
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
def scale_sol(NWN: NanowireNetwork, sol: npt.NDArray) -> npt.NDArray:
    """
    Scale the voltage and current solutions by their characteristic values.

    """
    # Get parameters
    out = np.copy(sol)
    v0 = NWN.graph["units"]["v0"]
    i0 = NWN.graph["units"]["i0"]
    node_num = len(NWN.graph["node_indices"])

    # Current sources
    if node_num == len(sol):
        out *= v0

    # Voltage sources
    else:
        out[:node_num] *= v0
        out[node_num:] *= i0

    return out

solve_drain_current

solve_drain_current(
    NWN: NanowireNetwork,
    source_node: NWNNode | list[NWNNode],
    drain_node: NWNNode | list[NWNNode],
    voltage: float,
    scaled: bool = False,
    solver: str = "spsolve",
    **kwargs
) -> npt.NDArray

Solve for the current through each drain node of a NWN.

Parameters:

Name Type Description Default
NWN Graph

Nanowire network.

required
source_node tuple, or list of tuples

Voltage source nodes.

required
drain_node tuple, or list of tuples

Grounded output nodes.

required
voltage float

Voltage of the source nodes.

required
scaled bool

Whether or not to scaled the output by i0. Default: False.

False
solver str

Name of sparse matrix solving algorithm to use. Default: "spsolve".

'spsolve'

Returns:

Name Type Description
current_array ndarray

Array containing the current flow through each drain node in the order passed.

Source code in mnns/calculations.py
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
def solve_drain_current(
    NWN: NanowireNetwork,
    source_node: NWNNode | list[NWNNode],
    drain_node: NWNNode | list[NWNNode],
    voltage: float,
    scaled: bool = False,
    solver: str = "spsolve",
    **kwargs,
) -> npt.NDArray:
    """
    Solve for the current through each drain node of a NWN.

    Parameters
    ----------
    NWN : Graph
        Nanowire network.

    source_node : tuple, or list of tuples
        Voltage source nodes.

    drain_node : tuple, or list of tuples
        Grounded output nodes.

    voltage : float
        Voltage of the source nodes.

    scaled : bool, optional
        Whether or not to scaled the output by i0. Default: False.

    solver: str, optional
        Name of sparse matrix solving algorithm to use. Default: "spsolve".

    Returns
    -------
    current_array : ndarray
        Array containing the current flow through each drain node in the order
        passed.

    """
    # Get lists of source and drain nodes
    if isinstance(source_node, tuple):
        source_node = [source_node]
    if isinstance(drain_node, tuple):
        drain_node = [drain_node]

    # Preallocate output
    current_array = np.zeros(len(drain_node))

    # Solve nodes
    out = solve_network(
        NWN, source_node, drain_node, voltage, "voltage", solver, **kwargs
    )

    # Find current through each drain node
    for i, drain in enumerate(drain_node):
        I = 0
        for node in NWN.neighbors(drain):
            V = out[NWN.get_index(node)]
            R = 1 / NWN.edges[(node, drain)]["conductance"]
            I += V / R
        current_array[i] = I

    # Scale the output if desired
    if scaled:
        current_array *= NWN.graph["units"]["i0"]

    return current_array.squeeze()

solve_edge_current

solve_edge_current(
    NWN: NanowireNetwork,
    source_node: NWNNode | list[NWNNode],
    drain_node: NWNNode | list[NWNNode],
    voltage: float,
    scaled: bool = False,
    solver: str = "spsolve",
    **kwargs
) -> npt.NDArray

Solve for the current passing through each edge of a NWN. The direction is not considered, only the magnitude.

Parameters:

Name Type Description Default
NWN Graph

Nanowire network.

required
source_node tuple, or list of tuples

Voltage source nodes.

required
drain_node tuple, or list of tuples

Grounded output nodes.

required
voltage float

Voltage of the source nodes.

required
scaled bool

Whether or not to scaled the output by i0. Default: False.

False
solver str

Name of sparse matrix solving algorithm to use. Default: "spsolve".

'spsolve'

Returns:

Name Type Description
I ndarray

Array containing the current flow through each edge.

Source code in mnns/calculations.py
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
def solve_edge_current(
    NWN: NanowireNetwork,
    source_node: NWNNode | list[NWNNode],
    drain_node: NWNNode | list[NWNNode],
    voltage: float,
    scaled: bool = False,
    solver: str = "spsolve",
    **kwargs,
) -> npt.NDArray:
    """
    Solve for the current passing through each edge of a NWN. The direction is
    not considered, only the magnitude.

    Parameters
    ----------
    NWN : Graph
        Nanowire network.

    source_node : tuple, or list of tuples
        Voltage source nodes.

    drain_node : tuple, or list of tuples
        Grounded output nodes.

    voltage : float
        Voltage of the source nodes.

    scaled : bool, optional
        Whether or not to scaled the output by i0. Default: False.

    solver: str, optional
        Name of sparse matrix solving algorithm to use. Default: "spsolve".

    Returns
    -------
    I : ndarray
        Array containing the current flow through each edge.

    """
    # Get nodal voltages
    sol = solve_network(
        NWN, source_node, drain_node, voltage, "voltage", solver, **kwargs
    )

    # Get edges and conductances
    edges, G = zip(*nx.get_edge_attributes(NWN, "conductance").items())

    # Find edges indices
    start_nodes, end_nodes = get_edge_indices(NWN, edges)

    # Find current through each edges
    v0 = sol[start_nodes]
    v1 = sol[end_nodes]
    V_delta = np.abs(v0 - v1)
    I = V_delta * G

    # Scale the output if desired
    if scaled:
        I *= NWN.graph["units"]["i0"]

    return I

solve_network

solve_network(
    NWN: NanowireNetwork,
    source_node: NWNNode | list[NWNNode],
    drain_node: NWNNode | list[NWNNode],
    input: float,
    type: str = "voltage",
    solver: str = "spsolve",
    **kwargs
) -> npt.NDArray

Solve for the voltages of each node in a given NWN. Each drain node will be grounded. If the type is "voltage", each source node will be at the specified input voltage. If the type is "current", current will be sourced from each source node.

Parameters:

Name Type Description Default
NWN Graph

Nanowire network.

required
source_node tuple, or list of tuples

Voltage/current source nodes.

required
drain_node tuple, or list of tuples

Grounded output nodes.

required
input float

Supplied voltage (current) in units of v0 (i0).

required
type ('voltage', 'current')

Input type. Default: "voltage".

"voltage"
solver str

Name of sparse matrix solving algorithm to use. Default: "spsolve".

'spsolve'
**kwargs

Keyword arguments passed to the solver.

{}

Returns:

Name Type Description
out ndarray

Output array containing the voltages of each node. If the input type is voltage, the current is also in this array as the last element.

Source code in mnns/calculations.py
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
def solve_network(
    NWN: NanowireNetwork,
    source_node: NWNNode | list[NWNNode],
    drain_node: NWNNode | list[NWNNode],
    input: float,
    type: str = "voltage",
    solver: str = "spsolve",
    **kwargs,
) -> npt.NDArray:
    """
    Solve for the voltages of each node in a given NWN. Each drain node will
    be grounded. If the type is "voltage", each source node will be at the
    specified input voltage. If the type is "current", current will be sourced
    from each source node.

    Parameters
    ----------
    NWN : Graph
        Nanowire network.

    source_node : tuple, or list of tuples
        Voltage/current source nodes.

    drain_node : tuple, or list of tuples
        Grounded output nodes.

    input : float
        Supplied voltage (current) in units of v0 (i0).

    type : {"voltage", "current"}, optional
        Input type. Default: "voltage".

    solver: str, optional
        Name of sparse matrix solving algorithm to use. Default: "spsolve".

    **kwargs
        Keyword arguments passed to the solver.

    Returns
    -------
    out : ndarray
        Output array containing the voltages of each node. If the input type
        is voltage, the current is also in this array as the last element.

    """
    # Get lists of source and drain nodes
    if isinstance(source_node, tuple):
        source_node = [source_node]
    if isinstance(drain_node, tuple):
        drain_node = [drain_node]

    # Pass to solvers
    if type == "voltage":
        out = _solve_voltage(
            NWN, input, source_node, drain_node, solver, **kwargs
        )
    elif type == "current":
        out = _solve_current(
            NWN, input, source_node, drain_node, solver, **kwargs
        )
    else:
        raise ValueError("Invalid source type.")

    return out

solve_nodal_current

solve_nodal_current(
    NWN: NanowireNetwork,
    source_node: NWNNode | list[NWNNode],
    drain_node: NWNNode | list[NWNNode],
    voltage: float,
    scaled: bool = False,
    solver: str = "spsolve",
    **kwargs
) -> npt.NDArray

Solve for the current entering each node of a NWN. Only the entering current is considered since by Kirchoff's law, the sum of currents entering a node must equal the sum of currents leaving a node.

Parameters:

Name Type Description Default
NWN Graph

Nanowire network.

required
source_node tuple, or list of tuples

Voltage source nodes.

required
drain_node tuple, or list of tuples

Grounded output nodes.

required
voltage float

Voltage of the source nodes.

required
scaled bool

Whether or not to scaled the output by i0. Default: False.

False
solver str

Name of sparse matrix solving algorithm to use. Default: "spsolve".

'spsolve'

Returns:

Name Type Description
current_array ndarray

Array containing the current flow entering each node.

Source code in mnns/calculations.py
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
def solve_nodal_current(
    NWN: NanowireNetwork,
    source_node: NWNNode | list[NWNNode],
    drain_node: NWNNode | list[NWNNode],
    voltage: float,
    scaled: bool = False,
    solver: str = "spsolve",
    **kwargs,
) -> npt.NDArray:
    """
    Solve for the current entering each node of a NWN. Only the entering
    current is considered since by Kirchoff's law, the sum of currents
    entering a node must equal the sum of currents leaving a node.

    Parameters
    ----------
    NWN : Graph
        Nanowire network.

    source_node : tuple, or list of tuples
        Voltage source nodes.

    drain_node : tuple, or list of tuples
        Grounded output nodes.

    voltage : float
        Voltage of the source nodes.

    scaled : bool, optional
        Whether or not to scaled the output by i0. Default: False.

    solver: str, optional
        Name of sparse matrix solving algorithm to use. Default: "spsolve".

    Returns
    -------
    current_array : ndarray
        Array containing the current flow entering each node.

    """
    # Get nodal voltages
    V_out = solve_network(
        NWN, source_node, drain_node, voltage, "voltage", solver, **kwargs
    )

    # Preallocate output
    current_array = np.zeros(len(NWN.nodes))

    # Calculate input current for each node
    for node in NWN.nodes:
        node_ind = NWN.graph["node_indices"][node]
        for edge in NWN.edges(node):
            edge0_ind = NWN.graph["node_indices"][edge[0]]
            edge1_ind = NWN.graph["node_indices"][edge[1]]
            V_delta = V_out[edge1_ind] - V_out[edge0_ind]

            # Only add current entering a node so we can see how much
            # current passes through. Else, we just get zero due to KCL.
            if V_delta > 0:
                current_array[node_ind] += (
                    V_delta * NWN.edges[edge]["conductance"] * np.sign(voltage)
                )

    # Scale the output if desired
    if scaled:
        current_array *= NWN.graph["units"]["i0"]

    return current_array