Skip to content

mnns.nanowire_network

Functions to create nanowire networks.

Classes:

Name Description
NanowireNetwork

Internal nanowire network object. Should not be instantiated directly.

ParameterNotSetError

Raised when an parameter that needs to used has not been set yet.

Functions:

Name Description
create_NWN

Create a nanowire network represented by a NetworkX graph. Wires are

NanowireNetwork

NanowireNetwork(incoming_graph_data=None, **attr)

Bases: Graph

Internal nanowire network object. Should not be instantiated directly. Use mnns.create_NWN instead.

Parameters:

Name Type Description Default
incoming_graph_data input graph

Data to initialize graph. Same as networkx.Graph object.

None
**attr

Keyword arguments. Same as networkx.Graph object.

{}

Methods:

Name Description
evolve

Evolve the nanowire network using the given model and state variables.

get_index

Return the unique index of a node in the network.

get_index_from_edge

Return the indices of the nodes in the edge as a tuple.

get_node

Return the node corresponding to the index.

get_state_var

Get the state variable of the memristors (nanowire network wire

set_state_var

Set the state variable of the memristors (nanowire network wire

to_MNR

Converts the NWN to the multi-nodal representation (MNR).

update_resistance

Update the resistance of the nanowire network based on the provided

wire_junction_indices

Return the start and end indices of the wire junctions in the network

Attributes:

Name Type Description
lines list[LineString]

List of LineStrings representing the nanowires. Includes electrodes.

loc dict[tuple[int, int], Point]

Dictionary of graph edge locations (nanowire junctions). Only

n_wires int

Number of wires in the network. Does not include electrodes.

resistance_function Callable[[NanowireNetwork, ArrayLike], ArrayLike]

Resistance function of the nanowire network. Should have the calling

wire_density float

Wire density in units of (l0)^-2. Does not include electrodes.

wire_junctions list[NWNEdge]

Return a list of edges with the "type" attribute set to "junction".

Source code in mnns/nanowire_network.py
66
67
68
69
70
71
def __init__(self, incoming_graph_data=None, **attr):
    super().__init__(incoming_graph_data, **attr)
    self._resist_func = None
    self._state_vars: list[str] = []
    self._state_vars_is_set: list[bool] = []
    self._wire_junctions = None

lines property

lines: list[LineString]

List of LineStrings representing the nanowires. Includes electrodes.

loc property

loc: dict[tuple[int, int], Point]

Dictionary of graph edge locations (nanowire junctions). Only represents anything meaningful for JDA NWNs. MNR NWNs do not update this value. Indexes with a NWNEdge but without the nested tuples.

Does not have a guaranteed ordering.

n_wires property

n_wires: int

Number of wires in the network. Does not include electrodes.

resistance_function property writable

resistance_function: Callable[
    [NanowireNetwork, ArrayLike], ArrayLike
]

Resistance function of the nanowire network. Should have the calling signature func(NWN, param1, [param2, ...]) where NWN is the nanowire network, and param1, param2, etc. are the state variables of the memristor element(s).

You can also pass the string "linear" to choose a default linear resistance function based on the state variable x in [0, 1].

The resistance should be nondimensionalized, i.e. the resistance should be in units of Ron.

wire_density property

wire_density: float

Wire density in units of (l0)^-2. Does not include electrodes.

wire_junctions deletable property

wire_junctions: list[NWNEdge]

Return a list of edges with the "type" attribute set to "junction". Once called, the list is cached so the ordering can be fixed. If wires are added, clear the cache by deleting the property.

evolve

evolve(
    model: Callable[..., ArrayLike],
    t_eval: NDArray,
    args: tuple[Any, ...] = (),
    state_vars: Optional[list[str]] = None,
    ivp_options: dict = {},
) -> OdeResult

Evolve the nanowire network using the given model and state variables. Returns the solution of the initial value problem solver from scipy at the given time points.

Parameters:

Name Type Description Default
model callable

Model to use for the evolution. Should be a function with the signature func(t, y, *args) where t is the time, y is the state variable(s), and args are any additional arguments. Pre-implemented models include: mnns.models.HP_model, mnns.models.decay_HP_model, and mnns.models.SLT_HP_model.

required
t_eval ndarray

Time points to evaluate the solution at.

required
state_vars list of str

List of state variables to evolve. If not provided, all state variables in self.state_vars will be used.

None
args tuple

Additional arguments to pass to the model function. Most likely, you will need to provide args for the source node(s), drain node(s) and voltage function.

()
ivp_options dict

Additional keyword arguments to pass to the IVP solver.

{}
Source code in mnns/nanowire_network.py
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
413
414
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
def evolve(
    self,
    model: Callable[..., npt.ArrayLike],
    t_eval: npt.NDArray,
    args: tuple[Any, ...] = (),
    state_vars: Optional[list[str]] = None,
    ivp_options: dict = {},
) -> OdeResult:
    """
    Evolve the nanowire network using the given model and state variables.
    Returns the solution of the initial value problem solver from scipy
    at the given time points.

    Parameters
    ----------
    model : callable
        Model to use for the evolution. Should be a function with the
        signature `func(t, y, *args)` where `t` is the time, `y` is the
        state variable(s), and `args` are any additional arguments.
        Pre-implemented models include:
        [`mnns.models.HP_model`](models.md#mnns.models.HP_model),
        [`mnns.models.decay_HP_model`](models.md#mnns.models.decay_HP_model),
        and [`mnns.models.SLT_HP_model`](models.md#mnns.models.SLT_HP_model).

    t_eval : ndarray
        Time points to evaluate the solution at.

    state_vars : list of str, optional
        List of state variables to evolve. If not provided, all
        state variables in `self.state_vars` will be used.

    args : tuple, optional
        Additional arguments to pass to the model function. Most likely,
        you will need to provide args for the source node(s), drain node(s)
        and voltage function.

    ivp_options : dict, optional
        Additional keyword arguments to pass to the IVP solver.

    """
    if state_vars is None:
        state_vars = self.state_vars

    # Check if state variables are set
    if not all([self._state_vars_is_set[var] for var in state_vars]):
        raise ParameterNotSetError(
            "Not all state variables have not been set yet."
        )

    # Get initial state variable, if there are more than one, they
    # will be concatenated.
    y0 = np.hstack([self.get_state_var(var) for var in state_vars])

    # Set the tolerance value for the IVP solver
    if "atol" not in ivp_options.keys():
        ivp_options["atol"] = 1e-7
    if "rtol" not in ivp_options.keys():
        ivp_options["rtol"] = 1e-7

    t_span = (t_eval[0], t_eval[-1])

    # Solve how the state variables change over time
    sol = solve_ivp(
        model, t_span, y0, "DOP853", t_eval, args=args, **ivp_options
    )

    # Update the state variables
    split = np.split(sol.y[:, -1], len(state_vars))
    for var, new_vals in zip(state_vars, split):
        self.set_state_var(var, new_vals)

    return sol

get_index

get_index(node: NWNNode | list[NWNNode]) -> NWNNodeIndex

Return the unique index of a node in the network.

Source code in mnns/nanowire_network.py
129
130
131
def get_index(self, node: NWNNode | list[NWNNode]) -> NWNNodeIndex:
    """Return the unique index of a node in the network."""
    return self.graph["node_indices"][node]

get_index_from_edge

get_index_from_edge(
    edge: NWNEdge | list[NWNEdge],
) -> NWNEdgeIndex | list[NWNEdgeIndex]

Return the indices of the nodes in the edge as a tuple.

Source code in mnns/nanowire_network.py
142
143
144
145
146
147
148
149
def get_index_from_edge(
    self, edge: NWNEdge | list[NWNEdge]
) -> NWNEdgeIndex | list[NWNEdgeIndex]:
    """Return the indices of the nodes in the edge as a tuple."""
    if isinstance(edge, list):
        return [tuple(map(self.get_index, e)) for e in edge]
    else:
        return tuple(map(self.get_index, edge))

get_node

get_node(index: NWNNodeIndex) -> NWNNode

Return the node corresponding to the index.

Source code in mnns/nanowire_network.py
133
134
135
136
137
138
139
140
def get_node(self, index: NWNNodeIndex) -> NWNNode:
    """Return the node corresponding to the index."""
    try:
        return next(
            k for k, v in self.graph["node_indices"].items() if v == index
        )
    except StopIteration as e:
        raise ValueError("given index does not have a node") from e

get_state_var

get_state_var(
    var_name: str, edge_list: list[NWNEdge] | None = None
) -> npt.ArrayLike

Get the state variable of the memristors (nanowire network wire junctions) in the network.

Parameters:

Name Type Description Default
var_name str

Name of the state variable(s) to get.

required
edge_list list of edges

List of edges to get the state variable from. If None, all wire junction edges will be used.

None

Returns:

Type Description
ndarray or scalar

Value of the state variable(s).

Source code in mnns/nanowire_network.py
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
def get_state_var(
    self, var_name: str, edge_list: list[NWNEdge] | None = None
) -> npt.ArrayLike:
    """
    Get the state variable of the memristors (nanowire network wire
    junctions) in the network.

    Parameters
    ----------
    var_name : str
        Name of the state variable(s) to get.

    edge_list : list of edges, optional
        List of edges to get the state variable from. If None, all wire
        junction edges will be used.

    Returns
    -------
    ndarray or scalar
        Value of the state variable(s).

    """
    if var_name not in self.state_vars:
        cls = self.__class__
        raise AttributeError(
            f"'{var_name}' is not in {cls.__qualname__}.state_vars (currently is {self.state_vars})."
            f"\nDid you set it using {cls.__qualname__}.state_vars = ['{var_name}', ...]?"
        )

    if edge_list is None:
        edge_list = self.wire_junctions

    try:
        return np.array(
            [self[edge[0]][edge[1]][var_name] for edge in edge_list]
        )
    except KeyError as e:
        raise ParameterNotSetError(
            f"'{var_name}' has not been set yet using `set_state_var`."
        ) from e

set_state_var

set_state_var(
    var_name: str,
    value: ArrayLike,
    edge_list: list[NWNEdge] | None = None,
) -> None

Set the state variable of the memristors (nanowire network wire junctions) in the network.

Parameters:

Name Type Description Default
var_name str

Name of the state variable(s) to set.

required
value ndarray or scalar

Value to set the state variable(s) to.

required
edge_list list of edges

List of edges to set the state variable for. If None, all wire junction edges will be used. Should be the same length as the value array.

None
Source code in mnns/nanowire_network.py
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
274
275
276
277
278
279
280
281
282
def set_state_var(
    self,
    var_name: str,
    value: npt.ArrayLike,
    edge_list: list[NWNEdge] | None = None,
) -> None:
    """
    Set the state variable of the memristors (nanowire network wire
    junctions) in the network.

    Parameters
    ----------
    var_name : str
        Name of the state variable(s) to set.

    value : ndarray or scalar
        Value to set the state variable(s) to.

    edge_list : list of edges, optional
        List of edges to set the state variable for. If None, all wire
        junction edges will be used. Should be the same length as the value
        array.

    """
    value = np.atleast_1d(value)

    if var_name not in self.state_vars:
        cls = self.__class__
        raise ParameterNotSetError(
            f"'{var_name}' is not in {cls.__qualname__}.state_vars (currently is {self.state_vars})."
            f"\nDid you set it using {cls.__qualname__}.state_vars = ['{var_name}', ...]?"
        )

    edge_list = self.wire_junctions if edge_list is None else edge_list

    # Set the state variable for the given edges to the same value...
    if value.size == 1:
        nx.set_edge_attributes(
            self, {edge: {var_name: value[0]} for edge in edge_list}
        )

    # or to different values
    elif value.size == len(edge_list):
        nx.set_edge_attributes(
            self,
            {
                edge: {var_name: value[i]}
                for i, edge in enumerate(edge_list)
            },
        )

    else:
        raise ValueError(
            f"Length of value array ({value.size}) does not match the number of edges ({len(edge_list)})."
        )

    self._state_vars_is_set[var_name] = True

to_MNR

to_MNR() -> None

Converts the NWN to the multi-nodal representation (MNR).

Source code in mnns/nanowire_network.py
151
152
153
def to_MNR(self) -> None:
    """Converts the NWN to the multi-nodal representation (MNR)."""
    convert_NWN_to_MNR(self)

update_resistance

update_resistance(
    state_var_vals: ArrayLike | list[ArrayLike],
    edge_list: list[NWNEdge] | None = None,
) -> None

Update the resistance of the nanowire network based on the provided state variable values. The resistance function should be set before calling this method.

Parameters:

Name Type Description Default
state_var_vals ndarray or list of ndarrays

An array of values of the state variables to use in the resistance function. The should be in the same order as the state variables. If the resistance function takes multiple state variables, pass a list of arrays in the same order as NanowireNetwork.state_vars.

required
edge_list list of edges

List of edges to update the resistance for. If None, all wire junction edges will be used. In this case, the length of the state_var_vals array should be the same as the number of wire junctions.

None

Returns:

Type Description
ndarray

Array of updated resistance values for each edge in the edge_list.

Source code in mnns/nanowire_network.py
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
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
def update_resistance(
    self,
    state_var_vals: npt.ArrayLike | list[npt.ArrayLike],
    edge_list: list[NWNEdge] | None = None,
) -> None:
    """
    Update the resistance of the nanowire network based on the provided
    state variable values. The resistance function should be set before
    calling this method.

    Parameters
    ----------
    state_var_vals : ndarray or list of ndarrays
        An array of values of the state variables to use in the resistance
        function. The should be in the same order as the state variables.
        If the resistance function takes multiple state variables, pass a
        list of arrays in the same order as `NanowireNetwork.state_vars`.

    edge_list : list of edges, optional
        List of edges to update the resistance for. If None, all wire
        junction edges will be used. In this case, the length of the
        `state_var_vals` array should be the same as the number of wire
        junctions.

    Returns
    -------
    ndarray
        Array of updated resistance values for each edge in the edge_list.

    """
    if self.resistance_function is None:
        raise ParameterNotSetError(
            "Resistance function attribute must be set before updating resistance."
        )

    if not isinstance(state_var_vals, list):
        state_var_vals = [state_var_vals]

    if edge_list is None:
        edge_list = self.wire_junctions

    R = self.resistance_function(self, *state_var_vals)
    attrs = {
        edge: {"conductance": 1 / R[i]} for i, edge in enumerate(edge_list)
    }
    nx.set_edge_attributes(self, attrs)

    return R

wire_junction_indices cached

wire_junction_indices() -> (
    tuple[list[NWNNodeIndex], list[NWNNodeIndex]]
)

Return the start and end indices of the wire junctions in the network as a tuple of lists.

Source code in mnns/nanowire_network.py
177
178
179
180
181
182
183
184
185
186
@lru_cache
def wire_junction_indices(
    self,
) -> tuple[list[NWNNodeIndex], list[NWNNodeIndex]]:
    """
    Return the start and end indices of the wire junctions in the network
    as a tuple of lists.

    """
    return np.asarray(self.get_index_from_edge(self.wire_junctions)).T

ParameterNotSetError

ParameterNotSetError(
    message: str, param: Any | None = None
)

Bases: Exception

Raised when an parameter that needs to used has not been set yet.

Parameters:

Name Type Description Default
param str

Name of the parameter that needs to be set.

None
message str

Explanation of the error.

required
Source code in mnns/nanowire_network.py
45
46
47
def __init__(self, message: str, param: Any | None = None):
    super().__init__(message)
    self.param = param

create_NWN

create_NWN(
    wire_length: float = 7.0 / 7,
    shape: tuple[float, float] = (50.0 / 7, 50.0 / 7),
    density: float = 0.3 * 7**2,
    seed: int = None,
    conductance: float = 0.1 / 0.1,
    capacitance: float = 1000,
    diameter: float = 50.0 / 50.0,
    resistivity: float = 22.6 / 22.6,
    units: dict[str, float] = None,
    angle_dist: str = "uniform",
    angle_kwargs: dict | None = None,
) -> NanowireNetwork

Create a nanowire network represented by a NetworkX graph. Wires are represented by the graph's vertices, while the wire junctions are represented by the graph's edges.

The nanowire network starts in the junction-dominated assumption (JDA), but can be converted to the multi-nodal representation (MNR) after creation.

The desired density may not be attainable with the given shape, as there can only be a integer number of wires. Thus, the closest density to an integer number of wires is used and stored as an attribute (see NWN Attributes for more information).

See mnns.NWNUnits for the units used by the parameters.

Parameters:

Name Type Description Default
wire_length float

Length of each nanowire. Given in units of l0.

7.0 / 7
shape 2-tuple of float

The shape of the nanowire network given in units of l0. Assumed to have the shape of (x-length, y-length).

The x direction is labeled length, while the y direction is labeled width.

(50.0 / 7, 50.0 / 7)
density float

Density of nanowires in the area determined by the width. Given in units of (l0)^-2.

0.3 * 7 ** 2
seed int

Seed for random nanowire generation.

None
conductance float

The junction conductance of the nanowires where they intersect. Given in units of (Ron)^-1.

0.1 / 0.1
capacitance float

The junction capacitance of the nanowires where they intersect. Given in microfarads. (Currently unused)

1000
diameter float

The diameter of each nanowire. Given in units of D0.

50.0 / 50.0
resistivity float

The resistivity of each nanowire. Given in units of rho0.

22.6 / 22.6
units dict

Dictionary of custom base units. Defaults to None which will use the default units given in units.py

None

Returns:

Name Type Description
NWN Graph

The created random nanowire network.

Source code in mnns/nanowire_network.py
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
def create_NWN(
    wire_length: float = (7.0 / 7),
    shape: tuple[float, float] = ((50.0 / 7), (50.0 / 7)),
    density: float = (0.3 * 7**2),
    seed: int = None,
    conductance: float = (0.1 / 0.1),
    capacitance: float = 1000,
    diameter: float = (50.0 / 50.0),
    resistivity: float = (22.6 / 22.6),
    units: dict[str, float] = None,
    angle_dist: str = "uniform",
    angle_kwargs: dict | None = None,
) -> NanowireNetwork:
    """
    Create a nanowire network represented by a NetworkX graph. Wires are
    represented by the graph's vertices, while the wire junctions are
    represented by the graph's edges.

    The nanowire network starts in the junction-dominated assumption (JDA), but
    can be converted to the multi-nodal representation (MNR) after creation.

    The desired density may not be attainable with the given shape, as there
    can only be a integer number of wires. Thus, the closest density to an
    integer number of wires is used and stored as an attribute (see
    [NWN Attributes](../../attributes.md) for more information).

    See [`mnns.NWNUnits`](units.md#mnns.units.NWNUnits) for the units used by
    the parameters.

    Parameters
    ----------
    wire_length : float, optional
        Length of each nanowire. Given in units of l0.

    shape : 2-tuple of float
        The shape of the nanowire network given in units of l0. Assumed to
        have the shape of (x-length, y-length).

        The x direction is labeled `length`, while the y direction is labeled
        `width`.

    density : float, optional
        Density of nanowires in the area determined by the width.
        Given in units of (l0)^-2.

    seed : int, optional
        Seed for random nanowire generation.

    conductance : float, optional
        The junction conductance of the nanowires where they intersect.
        Given in units of (Ron)^-1.

    capacitance : float, optional
        The junction capacitance of the nanowires where they intersect.
        Given in microfarads. (Currently unused)

    diameter : float, optional
        The diameter of each nanowire. Given in units of D0.

    resistivity : float, optional
        The resistivity of each nanowire. Given in units of rho0.

    units : dict, optional
        Dictionary of custom base units. Defaults to None which will use the
        default units given in `units.py`

    Returns
    -------
    NWN : Graph
        The created random nanowire network.

    """
    # Find total area in (l0)^2 of the nanowire network
    length = shape[0]
    width = shape[1]
    area = shape[0] * shape[1]

    # Get closest density with an integer number of wires.
    wire_num = round(area * density)
    density = wire_num / area

    # Get characteristic units
    units = NWNUnits(units)

    # Create NWN graph
    NWN = NanowireNetwork(
        wire_length=wire_length,
        length=length,
        width=width,
        shape=shape,
        wire_density=density,
        wire_num=0,
        junction_conductance=conductance,
        junction_capacitance=capacitance,
        wire_diameter=diameter,
        wire_resistivity=resistivity,
        electrode_list=[],
        lines=[],
        type="JDA",
        units=units,
        loc={},
        node_indices={},
    )

    # Create seeded random generator for testing
    rng = np.random.default_rng(seed)

    # Create the underlying line that represent the nanowires
    lines = []
    for _ in range(wire_num):
        lines.append(
            create_line(
                length=wire_length,
                xmax=length,
                ymax=width,
                rng=rng,
                angle_dist=angle_dist,
                angle_kwargs=angle_kwargs,
            )
        )

    # Create nanowire from lines
    add_wires(NWN, lines, [False] * len(lines))

    # Find junction density
    NWN.graph["junction_density"] = len(NWN.graph["loc"].keys()) / area

    return NWN