while True: # As long as we find devices of the current type
        pair = select_device(devtype)
        if pair is None:
          break
        select.append(pair)
        if len(select) == count:
          return mark(select)

 # @file   cluster.py
 # @author Sébastien Rouault <example@nnthink.com>
 #         Georgios Damaskinos <example@nnthink.com>
 # @section LICENSE
 # @section DESCRIPTION
 # Cluster-related management module for 'runner.py'.

if __name__ == "__main__":
  print("This script is not to be used as the main module")
  exit(1)

import tensorflow as tf

import config
import tools

# Cluster manager

class Manager:
  """ Cluster manager class.
  """

  def __init__(self, target, devs=None, reuse=None):
    """ Cluster structure available at the target constructor.
    Args:
      target Target cluster server to use
      devs   Iterable of device types to allocate from, by order of preference (optional)
      reuse  Set of device types for first reallocation is allowed (optional, None for no reallocation)
    """
    # Cluster query
    with tf.Session(graph=tf.Graph(), target=target) as sess:
      devices = sess.list_devices()
    # Structure extraction
    has_replicas = False
    structure = {}
    for device in devices:
      # Extract field values
      def extract_field(text, name, conv):
        lim = "/" + name + ":"
        pos = text.find(lim)
        if pos < 0:
          raise tools.UserException("Missing field " + repr(name) + " in device name " + repr(device.name))
        text = text[pos + len(lim):]
        pos = text.find("/")
        if pos < 0:
          return conv(text), ""
        else:
          return conv(text[:pos]), text[pos:]
      devname = device.name
      jobname, devname = extract_field(devname, "job", str)
      replica, devname = extract_field(devname, "replica", int)
      if replica != 0:
        has_replicas = True
        continue
      taskid, devname = extract_field(devname, "task", int)
      # Get or create task list for this job
      if jobname in structure:
        tasks = structure[jobname]
      else:
        tasks = {}
        structure[jobname] = tasks
      # Get or create task for this task list
      if taskid in tasks:
        task = tasks[taskid]
      else:
        task = {}
        tasks[taskid] = task
      # Get or create device list
      if device.device_type in task:
        devlist = task[device.device_type]
      else:
        devlist = []
        task[device.device_type] = devlist
      # Insert device
      devlist.append({"attr": device, "used": []})
    # If needed, warn about what is currently unsupported
    if has_replicas:
      tools.warning("Replicas are unsupported and will be ignored.")
    # Filtered, special copy of all available (i.e. allowed and free) devices
    def restricted(iterator, restrict=None):
      if restrict is None:
        for item in iterator:
          yield item
      else:
        for item in iterator:
          if item in restrict:
            yield item
    if reuse is None:
      reuse = {}
    tasks = []
    for job, taskdevs in structure.items():
      for taskid, devtypes in taskdevs.items():
        taskcopy = []
        for devtype in restricted(devtypes.keys(), restrict=devs):
          for entry in devtypes[devtype]:
            if devtype in reuse or len(entry["used"]) == 0: # Reusable or unallocated
              devname = entry["attr"].name
              taskcopy.append((entry["used"], (job, str(taskid), devtype, devname[devname.rindex(":") + 1:])))
        if len(taskcopy) > 0:
          tasks.append(taskcopy)
    # Finalization
    self.__structure = structure
    self.__tasks     = tasks
    self.__devs      = devs
    self.__reuse     = reuse

  def report(self):
    """ Report on the cluster structure and allocations.
    """
    print("Cluster structure and allocation report:")
    for job, tasks in self.__structure.items():
      print(" · Job " + repr(job))
      for taskid, devtypes in tasks.items():
        print("   · Task " + repr(taskid))
        for devtype, devices in devtypes.items():
          for i in range(len(devices)):
            used = devices[i]["used"]
            print("     · " + devtype + " " + str(i) + ": " + ("<unallocated>" if len(used) == 0 else (", ").join(used)))

  def allocate(self, name, count, jobs=None, partial=False):
    """ Allocate (i.e. reserve) free, allowed devices from the cluster.
    Args:
      name    Identifier (for informational purpose) of the recipient of the allocation
      count   Number of devices to reserve (NB: devices of reused types can be reserved any number of times)
      jobs    Set of jobs to restrict allocation from (optional, None for no restriction)
      partial Allows partial allocation (=> len(<return list>) may be below count)
    Returns:
      List of tuples of strings (job name, task ID, device type, device ID) or None if non-partial allocation failed
    """
    # Quick path and assertions
    if count == 0:
      tools.warning("Successfully allocated 0 device")
      return []
    elif count < 0:
      raise tools.UserException("Expected non-negative number of devices to reserve, got " + repr(count))
    if jobs is not None:
      for job in jobs:
        if job not in self.__structure:
          tools.warning("Job " + repr(job) + " does not exist in the cluster")
    # Select devices by order of preference, maximizing task spread
    tasks = self.__tasks
    reuse = self.__reuse
    def select_device(devtype):
      nonlocal tasks
      nonlocal reuse
      for taskid in range(len(tasks)): # Look into next task
        devs = tasks[taskid]
        for devid in range(len(devs)): # Look into next available device (perhaps of wrong type)
          pair = devs[devid]
          if jobs is not None and pair[1][0] not in jobs: # Disallowed job
            continue
          if pair[1][2] != devtype: # Wrong type
            continue
          devs = devs[:devid] + devs[devid + 1:]
          if devtype in reuse: # Device is reusable: keep it in the list
            devs.append(pair)
          tasks = tasks[:taskid] + tasks[taskid + 1:]
          if len(devs) > 0: # Push back remaining devices
            tasks.append(devs)
          # Select device
          return pair
      # No available device of the requested type
      return None
    def mark(select):
      nonlocal self
      nonlocal tasks
      nonlocal name
      # Update the new set of tasks
      self.__tasks = tasks
      # Mark the selection as used
      res = []
      count = 0
      for used, info in select:
        res.append(info)
        used.append(name + "[" + str(count) + "]")
        count += 1
      # Return only the list of device tuples
      return res
    select = []
    for devtype in self.__devs:

    # Full allocation failed...
    if partial:
      return mark(select)
    # ...and partial allocation was forbidden
    return None


# This file is a command-module for Dragonfly.

"""
Command-module for **taskbar** and icon tray access
===================================================
This file implements commands for controlling tasks on the
taskbar and icons in the icon tray.

Commands
--------

Command: **"[open | switch to] task <number>"**
    Open the specified task on the taskbar.
    The *<number>* extra is a number (1, 2, ...) designating which
    task to activate.  The application on the taskbar which is
    closest to the "Start" button is task *number 1*.  The next
    is *number 2*, and so on.  This works for both horizontal and
    vertical taskbars.

Command: **"<action> task <number>"**
    Similar to the command above, but performs some action
    under specified task.  The following actions are available:

     - **"(menu | pop up)"** -- show the pop-up menu of the task,
       as if a right-click was done on the taskbar.
     - **"(maximize | max)"** -- maximize the task.
     - **"(minimize | min)"** -- minimize the task.
     - **"restore"** -- restore the task.
     - **"close"** -- close the task.

Command: **"[open] icon <number>"**
    Open the specified icon in the icon tray.
    The *<number>* extra is a number (1, 2, ...) designating which
    icon to activate.  The numbering is similar to that explained
    above for tasks on the taskbar.

Command: **"(menu | pop up) icon <number>"**
    Pop-up the menu for the specified icon in the icon tray.

Usage examples
--------------

Several concrete usage examples of the commands described above:

 - Say **"task 4"** to bring the fourth application on the taskbar
   to the foreground.
 - Say **"close task 2"** to close the second application on
   the taskbar.
 - Say **"icon 1"** to activate the first icon visible in the
   icon tray, as if it was double-clicked.
 - Say **"pop up icon 3"** to bring up the menu of the third
   icon visible in the icon tray, as if it was right-clicked.

"""

from dragonfly import *  # @UnusedWildImport

# This rule controls tasks on the taskbar.

class TaskRule(MappingRule):

    mapping = {
               "[open | switch to] task <n>":  Key("space"),
               "(menu | pop up) task <n>":     Key("apps"),
               "close task <n>":               Key("apps/10, c"),
               "restore task <n>":             Key("apps/10, r"),
               "(minimize | min) task <n>":    Key("apps/10, n"),
               "(maximize | max) task <n>":    Key("apps/10, x"),
              }
    extras = [IntegerRef("n", 1, 50)]

    def _process_recognition(self, value, extras):
        count = extras["n"] - 1
        action = Key("w-b/10, s-tab/10, right:%d/10" % count) + value
        action.execute()

# This rule controls icons in the icon tray.

class IconRule(MappingRule):

    mapping = {
               "[open] icon <n>":              Key("enter"),
               "(menu | pop up) icon <n>":     Key("apps"),
              }
    extras = [IntegerRef("n", 1, 12)]

count = extras["n"] - 1
        action = Key("w-b/10, right:%d/10" % count) + value
        action.execute()

# Load the grammar instance and define how to unload it.

grammar = Grammar("taskbar")
grammar.add_rule(TaskRule())
grammar.add_rule(IconRule())
grammar.load()

# Unload function which will be called by natlink at unload time.
def unload():
    global grammar
    if grammar:
        grammar.unload()
    grammar = None


count = extras["n"] - 1
        action = Key("w-b/10, s-tab/10, right:%d/10" % count) + value
        action.execute()

# This file is a command-module for Dragonfly.

"""
Command-module for **taskbar** and icon tray access
===================================================
This file implements commands for controlling tasks on the
taskbar and icons in the icon tray.

Commands
--------

Command: **"[open | switch to] task <number>"**
    Open the specified task on the taskbar.
    The *<number>* extra is a number (1, 2, ...) designating which
    task to activate.  The application on the taskbar which is
    closest to the "Start" button is task *number 1*.  The next
    is *number 2*, and so on.  This works for both horizontal and
    vertical taskbars.

Command: **"<action> task <number>"**
    Similar to the command above, but performs some action
    under specified task.  The following actions are available:

     - **"(menu | pop up)"** -- show the pop-up menu of the task,
       as if a right-click was done on the taskbar.
     - **"(maximize | max)"** -- maximize the task.
     - **"(minimize | min)"** -- minimize the task.
     - **"restore"** -- restore the task.
     - **"close"** -- close the task.

Command: **"[open] icon <number>"**
    Open the specified icon in the icon tray.
    The *<number>* extra is a number (1, 2, ...) designating which
    icon to activate.  The numbering is similar to that explained
    above for tasks on the taskbar.

Command: **"(menu | pop up) icon <number>"**
    Pop-up the menu for the specified icon in the icon tray.

Usage examples
--------------

Several concrete usage examples of the commands described above:

 - Say **"task 4"** to bring the fourth application on the taskbar
   to the foreground.
 - Say **"close task 2"** to close the second application on
   the taskbar.
 - Say **"icon 1"** to activate the first icon visible in the
   icon tray, as if it was double-clicked.
 - Say **"pop up icon 3"** to bring up the menu of the third
   icon visible in the icon tray, as if it was right-clicked.

"""

from dragonfly import *  # @UnusedWildImport

# This rule controls tasks on the taskbar.

class TaskRule(MappingRule):

    mapping = {
               "[open | switch to] task <n>":  Key("space"),
               "(menu | pop up) task <n>":     Key("apps"),
               "close task <n>":               Key("apps/10, c"),
               "restore task <n>":             Key("apps/10, r"),
               "(minimize | min) task <n>":    Key("apps/10, n"),
               "(maximize | max) task <n>":    Key("apps/10, x"),
              }
    extras = [IntegerRef("n", 1, 50)]

    def _process_recognition(self, value, extras):

# This rule controls icons in the icon tray.

class IconRule(MappingRule):

    mapping = {
               "[open] icon <n>":              Key("enter"),
               "(menu | pop up) icon <n>":     Key("apps"),
              }
    extras = [IntegerRef("n", 1, 12)]

    def _process_recognition(self, value, extras):
        count = extras["n"] - 1
        action = Key("w-b/10, right:%d/10" % count) + value
        action.execute()

# Load the grammar instance and define how to unload it.

grammar = Grammar("taskbar")
grammar.add_rule(TaskRule())
grammar.add_rule(IconRule())
grammar.load()

# Unload function which will be called by natlink at unload time.
def unload():
    global grammar
    if grammar:
        grammar.unload()
    grammar = None


# This file is a command-module for Dragonfly.

"""
Command-module for **taskbar** and icon tray access
===================================================
This file implements commands for controlling tasks on the
taskbar and icons in the icon tray.

Commands
--------

Command: **"[open | switch to] task <number>"**
    Open the specified task on the taskbar.
    The *<number>* extra is a number (1, 2, ...) designating which
    task to activate.  The application on the taskbar which is
    closest to the "Start" button is task *number 1*.  The next
    is *number 2*, and so on.  This works for both horizontal and
    vertical taskbars.

Command: **"<action> task <number>"**
    Similar to the command above, but performs some action
    under specified task.  The following actions are available:

     - **"(menu | pop up)"** -- show the pop-up menu of the task,
       as if a right-click was done on the taskbar.
     - **"(maximize | max)"** -- maximize the task.
     - **"(minimize | min)"** -- minimize the task.
     - **"restore"** -- restore the task.
     - **"close"** -- close the task.

Command: **"[open] icon <number>"**
    Open the specified icon in the icon tray.
    The *<number>* extra is a number (1, 2, ...) designating which
    icon to activate.  The numbering is similar to that explained
    above for tasks on the taskbar.

Command: **"(menu | pop up) icon <number>"**
    Pop-up the menu for the specified icon in the icon tray.

Usage examples
--------------

Several concrete usage examples of the commands described above:

 - Say **"task 4"** to bring the fourth application on the taskbar
   to the foreground.
 - Say **"close task 2"** to close the second application on
   the taskbar.
 - Say **"icon 1"** to activate the first icon visible in the
   icon tray, as if it was double-clicked.
 - Say **"pop up icon 3"** to bring up the menu of the third
   icon visible in the icon tray, as if it was right-clicked.

"""

from dragonfly import *  # @UnusedWildImport

# This rule controls tasks on the taskbar.

class TaskRule(MappingRule):

    mapping = {
               "[open | switch to] task <n>":  Key("space"),
               "(menu | pop up) task <n>":     Key("apps"),
               "close task <n>":               Key("apps/10, c"),
               "restore task <n>":             Key("apps/10, r"),
               "(minimize | min) task <n>":    Key("apps/10, n"),
               "(maximize | max) task <n>":    Key("apps/10, x"),
              }
    extras = [IntegerRef("n", 1, 50)]

    def _process_recognition(self, value, extras):
        count = extras["n"] - 1
        action = Key("w-b/10, s-tab/10, right:%d/10" % count) + value
        action.execute()

# This rule controls icons in the icon tray.

class IconRule(MappingRule):

    mapping = {
               "[open] icon <n>":              Key("enter"),
               "(menu | pop up) icon <n>":     Key("apps"),
              }
    extras = [IntegerRef("n", 1, 12)]

    def _process_recognition(self, value, extras):
        count = extras["n"] -

        action = Key("w-b/10, right:%d/10" % count) + value
        action.execute()

# Load the grammar instance and define how to unload it.

grammar = Grammar("taskbar")
grammar.add_rule(TaskRule())
grammar.add_rule(IconRule())
grammar.load()

# Unload function which will be called by natlink at unload time.
def unload():
    global grammar
    if grammar:
        grammar.unload()
    grammar = None


import time
import os
import fcntl
import mmap
import struct
import digitalio
import board
from PIL import Image, ImageDraw
import adafruit_rgb_display.st7789 as st7789

FBIOGET_VSCREENINFO = 0x4600
FBIOGET_FSCREENINFO = 0x4602
FBIOBLANK = 0x4611

FB_TYPE_PACKED_PIXELS = 0
FB_TYPE_PLANES = 1
FB_TYPE_INTERLEAVED_PLANES = 2
FB_TYPE_TEXT = 3
FB_TYPE_VGA_PLANES = 4
FB_TYPE_FOURCC = 5

FB_VISUAL_MONO01 = 0
FB_VISUAL_MONO10 = 1
FB_VISUAL_TRUECOLOR = 2
FB_VISUAL_PSEUDOCOLOR = 3
FB_VISUAL_DIRECTCOLOR = 4
FB_VISUAL_STATIC_PSEUDOCOLOR = 5
FB_VISUAL_FOURCC = 6

FB_BLANK_UNBLANK = 0
FB_BLANK_POWERDOWN = 4

class Bitfield:  # pylint: disable=too-few-public-methods
    def __init__(self, offset, length, msb_right):
        self.offset = offset
        self.length = length
        self.msb_right = msb_right

# Kind of like a pygame Surface object, or not!
class Framebuffer:  # pylint: disable=too-many-instance-attributes
    def __init__(self, dev):
        self.dev = dev
        self.fbfd = os.open(dev, os.O_RDWR)
        vinfo = struct.unpack(
            "8I12I16I4I",
            fcntl.ioctl(self.fbfd, FBIOGET_VSCREENINFO, " " * ((8 + 12 + 16 + 4) * 4)),
        )
        finfo = struct.unpack(
            "16cL4I3HI", fcntl.ioctl(self.fbfd, FBIOGET_FSCREENINFO, " " * 48)
        )

        bytes_per_pixel = (vinfo[6] + 7) // 8
        screensize = vinfo[0] * vinfo[1] * bytes_per_pixel

        fbp = mmap.mmap(
            self.fbfd, screensize, flags=mmap.MAP_SHARED, prot=mmap.PROT_READ
        )

        self.fbp = fbp
        self.xres = vinfo[0]
        self.yres = vinfo[1]
        self.xoffset = vinfo[4]
        self.yoffset = vinfo[5]
        self.bits_per_pixel = vinfo[6]
        self.bytes_per_pixel = bytes_per_pixel
        self.grayscale = vinfo[7]
        self.red = Bitfield(vinfo[8], vinfo[9], vinfo[10])
        self.green = Bitfield(vinfo[11], vinfo[12], vinfo[13])
        self.blue = Bitfield(vinfo[14], vinfo[15], vinfo[16])
        self.transp = Bitfield(vinfo[17], vinfo[18], vinfo[19])
        self.nonstd = vinfo[20]
        self.name = b"".join([x for x in finfo[0:15] if x != b"\x00"])
        self.type = finfo[18]
        self.visual = finfo[20]
        self.line_length = finfo[24]
        self.screensize = screensize

self.fbp.close()
        os.close(self.fbfd)

    def blank(self, blank):
        # Blanking is not supported by all drivers
        try:
            if blank:
                fcntl.ioctl(self.fbfd, FBIOBLANK, FB_BLANK_POWERDOWN)
            else:
                fcntl.ioctl(self.fbfd, FBIOBLANK, FB_BLANK_UNBLANK)
        except IOError:
            pass

    def __str__(self):
        visual_list = [
            "MONO01",
            "MONO10",
            "TRUECOLOR",
            "PSEUDOCOLOR",
            "DIRECTCOLOR",
            "STATIC PSEUDOCOLOR",
            "FOURCC",
        ]
        type_list = [
            "PACKED_PIXELS",
            "PLANES",
            "INTERLEAVED_PLANES",
            "TEXT",
            "VGA_PLANES",
            "FOURCC",
        ]
        visual_name = "unknown"
        if self.visual < len(visual_list):
            visual_name = visual_list[self.visual]
        type_name = "unknown"
        if self.type < len(type_list):
            type_name = type_list[self.type]

        return (
            'mode "%sx%s"\n' % (self.xres, self.yres)
            + "    nonstd %s\n" % self.nonstd
            + "    rgba %s/%s,%s/%s,%s/%s,%s/%s\n"
            % (
                self.red.length,
                self.red.offset,
                self.green.length,
                self.green.offset,
                self.blue.length,
                self.blue.offset,
                self.transp.length,
                self.transp.offset,
            )
            + "endmode\n"
            + "\n"
            + "Frame buffer device information:\n"
            + "    Device      : %s\n" % self.dev
            + "    Name        : %s\n" % self.name
            + "    Size        : (%d, %d)\n" % (self.xres, self.yres)
            + "    Length      : %s\n" % self.screensize
            + "    BPP         : %d\n" % self.bits_per_pixel
            + "    Type        : %s\n" % type_name
            + "    Visual      : %s\n" % visual_name
            + "    LineLength  : %s\n" % self.line_length
        )

device = "/dev/fb0"
fb = Framebuffer(device)
print(fb)

# Configuration for CS and DC pins (these are FeatherWing defaults on M0/M4):
cs_pin = digitalio.DigitalInOut(board.CE0)
dc_pin = digitalio.DigitalInOut(board.D25)
reset_pin = None

BAUDRATE = 64000000

# Setup SPI bus using hardware SPI:
spi = board.SPI()

# Create the ST7789 display:
disp = st7789.ST7789(
    spi,
    cs=cs_pin,
    dc=dc_pin,
    rst=reset_pin,
    baudrate=BAUDRATE,
    width=135,
    height=240,
    x_offset=53,
    y_offset=40,
)

height = disp.width  # we swap height/width to rotate it to landscape!
width = disp.height
image = Image.new("RGB", (width, height))
rotation = 90

# Get drawing object to draw on image.
draw = ImageDraw.Draw(image)

# Draw a black filled box to clear the image.
draw.rectangle((0, 0, width, height), outline=0, fill=(0, 0, 0))
disp.image(image, rotation)

while True:
    t = time.monotonic()
    fb.fbp.seek(0)
    b = fb.fbp.read(fb.screensize)
    fbimage = Image.frombytes("RGBA", (fb.xres, fb.yres), b, "raw")
    b, g, r, a = fbimage.split()
    fbimage = Image.merge("RGB", (r, g, b))
    fbimage = fbimage.resize((width, height))

    disp.image(fbimage, rotation)
    print(1.0 / (time.monotonic() - t))
fb.close()


self.offset = offset
        self.length = length
        self.msb_right = msb_right

import time
import os
import fcntl
import mmap
import struct
import digitalio
import board
from PIL import Image, ImageDraw
import adafruit_rgb_display.st7789 as st7789

FBIOGET_VSCREENINFO = 0x4600
FBIOGET_FSCREENINFO = 0x4602
FBIOBLANK = 0x4611

FB_TYPE_PACKED_PIXELS = 0
FB_TYPE_PLANES = 1
FB_TYPE_INTERLEAVED_PLANES = 2
FB_TYPE_TEXT = 3
FB_TYPE_VGA_PLANES = 4
FB_TYPE_FOURCC = 5

FB_VISUAL_MONO01 = 0
FB_VISUAL_MONO10 = 1
FB_VISUAL_TRUECOLOR = 2
FB_VISUAL_PSEUDOCOLOR = 3
FB_VISUAL_DIRECTCOLOR = 4
FB_VISUAL_STATIC_PSEUDOCOLOR = 5
FB_VISUAL_FOURCC = 6

FB_BLANK_UNBLANK = 0
FB_BLANK_POWERDOWN = 4

class Bitfield:  # pylint: disable=too-few-public-methods
    def __init__(self, offset, length, msb_right):

# Kind of like a pygame Surface object, or not!
class Framebuffer:  # pylint: disable=too-many-instance-attributes
    def __init__(self, dev):
        self.dev = dev
        self.fbfd = os.open(dev, os.O_RDWR)
        vinfo = struct.unpack(
            "8I12I16I4I",
            fcntl.ioctl(self.fbfd, FBIOGET_VSCREENINFO, " " * ((8 + 12 + 16 + 4) * 4)),
        )
        finfo = struct.unpack(
            "16cL4I3HI", fcntl.ioctl(self.fbfd, FBIOGET_FSCREENINFO, " " * 48)
        )

        bytes_per_pixel = (vinfo[6] + 7) // 8
        screensize = vinfo[0] * vinfo[1] * bytes_per_pixel

        fbp = mmap.mmap(
            self.fbfd, screensize, flags=mmap.MAP_SHARED, prot=mmap.PROT_READ
        )

        self.fbp = fbp
        self.xres = vinfo[0]
        self.yres = vinfo[1]
        self.xoffset = vinfo[4]
        self.yoffset = vinfo[5]
        self.bits_per_pixel = vinfo[6]
        self.bytes_per_pixel = bytes_per_pixel
        self.grayscale = vinfo[7]
        self.red = Bitfield(vinfo[8], vinfo[9], vinfo[10])
        self.green = Bitfield(vinfo[11], vinfo[12], vinfo[13])
        self.blue = Bitfield(vinfo[14], vinfo[15], vinfo[16])
        self.transp = Bitfield(vinfo[17], vinfo[18], vinfo[19])
        self.nonstd = vinfo[20]
        self.name = b"".join([x for x in finfo[0:15] if x != b"\x00"])
        self.type = finfo[18]
        self.visual = finfo[20]
        self.line_length = finfo[24]
        self.screensize = screensize

    def close(self):
        self.fbp.close()
        os.close(self.fbfd)

    def blank(self, blank):
        # Blanking is not supported by all drivers
        try:
            if blank:
                fcntl.ioctl(self.fbfd, FBIOBLANK, FB_BLANK_POWERDOWN)
            else:
                fcntl.ioctl(self.fbfd, FBIOBLANK, FB_BLANK_UNBLANK)
        except IOError:
            pass

    def __str__(self):
        visual_list = [
            "MONO01",
            "MONO10",
            "TRUECOLOR",
            "PSEUDOCOLOR",
            "DIRECTCOLOR",
            "STATIC PSEUDOCOLOR",
            "FOURCC",
        ]
        type_list = [
            "PACKED_PIXELS",
            "PLANES",
            "INTERLEAVED_PLANES",
            "TEXT",
            "VGA_PLANES",
            "FOURCC",
        ]
        visual_name = "unknown"
        if self.visual < len(visual_list):
            visual_name = visual_list[self.visual]
        type_name = "unknown"
        if self.type < len(type_list):
            type_name = type_list[self.type]

        return (
            'mode "%sx%s"\n' % (self.xres, self.yres)
            + "    nonstd %s\n" % self.nonstd
            + "    rgba %s/%s,%s/%s,%s/%s,%s/%s\n"
            % (
                self.red.length,
                self.red.offset,
                self.green.length,
                self.green.offset,
                self.blue.length,
                self.blue.offset,
                self.transp.length,
                self.transp.offset,
            )
            + "endmode\n"
            + "\n"
            + "Frame buffer device information:\n"
            + "    Device      : %s\n" % self.dev
            + "    Name        : %s\n" % self.name
            + "    Size        : (%d, %d)\n" % (self.xres, self.yres)
            + "    Length      : %s\n" % self.screensize
            + "    BPP         : %d\n" % self.bits_per_pixel
            + "    Type        : %s\n" % type_name
            + "    Visual      : %s\n" % visual_name
            + "    LineLength  : %s\n" % self.line_length
        )

device = "/dev/fb0"
fb = Framebuffer(device)
print(fb)

# Configuration for CS and DC pins (these are FeatherWing defaults on M0/M4):
cs_pin = digitalio.DigitalInOut(board.CE0)
dc_pin = digitalio.DigitalInOut(board.D25)
reset_pin = None

BAUDRATE = 64000000

# Setup SPI bus using hardware SPI:
spi = board.SPI()

# Create the ST7789 display:
disp = st7789.ST7789(
    spi,
    cs=cs_pin,
    dc=dc_pin,
    rst=reset_pin,
    baudrate=BAUDRATE,
    width=135,
    height=240,
    x_offset=53,
    y_offset=40,
)

height = disp.width  # we swap height/width to rotate it to landscape!
width = disp.height
image = Image.new("RGB", (width, height))
rotation = 90

# Get drawing object to draw on image.
draw = ImageDraw.Draw(image)

# Draw a black filled box to clear the image.
draw.rectangle((0, 0, width, height), outline=0, fill=(0, 0, 0))
disp.image(image, rotation)

while True:
    t = time.monotonic()
    fb.fbp.seek(0)
    b = fb.fbp.read(fb.screensize)
    fbimage = Image.frombytes("RGBA", (fb.xres, fb.yres), b, "raw")
    b, g, r, a = fbimage.split()
    fbimage = Image.merge("RGB", (r, g, b))
    fbimage = fbimage.resize((width, height))

    disp.image(fbimage, rotation)
    print(1.0 / (time.monotonic() - t))
fb.close()


import time
import os
import fcntl
import mmap
import struct
import digitalio
import board
from PIL import Image, ImageDraw
import adafruit_rgb_display.st7789 as st7789

FBIOGET_VSCREENINFO = 0x4600
FBIOGET_FSCREENINFO = 0x4602
FBIOBLANK = 0x4611

FB_TYPE_PACKED_PIXELS = 0
FB_TYPE_PLANES = 1
FB_TYPE_INTERLEAVED_PLANES = 2
FB_TYPE_TEXT = 3
FB_TYPE_VGA_PLANES = 4
FB_TYPE_FOURCC = 5

FB_VISUAL_MONO01 = 0
FB_VISUAL_MONO10 = 1
FB_VISUAL_TRUECOLOR = 2
FB_VISUAL_PSEUDOCOLOR = 3
FB_VISUAL_DIRECTCOLOR = 4
FB_VISUAL_STATIC_PSEUDOCOLOR = 5
FB_VISUAL_FOURCC = 6

FB_BLANK_UNBLANK = 0
FB_BLANK_POWERDOWN = 4

class Bitfield:  # pylint: disable=too-few-public-methods
    def __init__(self, offset, length, msb_right):
        self.offset = offset
        self.length = length
        self.msb_right = msb_right

# Kind of like a pygame Surface object, or not!
class Framebuffer:  # pylint: disable=too-many-instance-attributes
    def __init__(self, dev):
        self.dev = dev
        self.fbfd = os.open(dev, os.O_RDWR)
        vinfo = struct.unpack(
            "8I12I16I4I",
            fcntl.ioctl(self.fbfd, FBIOGET_VSCREENINFO, " " * ((8 + 12 + 16 + 4) * 4)),
        )
        finfo = struct.unpack(
            "16cL4I3HI", fcntl.ioctl(self.fbfd, FBIOGET_FSCREENINFO, " " * 48)
        )

        bytes_per_pixel = (vinfo[6] + 7) // 8
        screensize = vinfo[0] * vinfo[1] * bytes_per_pixel

        fbp = mmap.mmap(
            self.fbfd, screensize, flags=mmap.MAP_SHARED, prot=mmap.PROT_READ
        )

        self

        self.xres = vinfo[0]
        self.yres = vinfo[1]
        self.xoffset = vinfo[4]
        self.yoffset = vinfo[5]
        self.bits_per_pixel = vinfo[6]
        self.bytes_per_pixel = bytes_per_pixel
        self.grayscale = vinfo[7]
        self.red = Bitfield(vinfo[8], vinfo[9], vinfo[10])
        self.green = Bitfield(vinfo[11], vinfo[12], vinfo[13])
        self.blue = Bitfield(vinfo[14], vinfo[15], vinfo[16])
        self.transp = Bitfield(vinfo[17], vinfo[18], vinfo[19])
        self.nonstd = vinfo[20]
        self.name = b"".join([x for x in finfo[0:15] if x != b"\x00"])
        self.type = finfo[18]
        self.visual = finfo[20]
        self.line_length = finfo[24]
        self.screensize = screensize

    def close(self):
        self.fbp.close()
        os.close(self.fbfd)

    def blank(self, blank):
        # Blanking is not supported by all drivers
        try:
            if blank:
                fcntl.ioctl(self.fbfd, FBIOBLANK, FB_BLANK_POWERDOWN)
            else:
                fcntl.ioctl(self.fbfd, FBIOBLANK, FB_BLANK_UNBLANK)
        except IOError:
            pass

    def __str__(self):
        visual_list = [
            "MONO01",
            "MONO10",
            "TRUECOLOR",
            "PSEUDOCOLOR",
            "DIRECTCOLOR",
            "STATIC PSEUDOCOLOR",
            "FOURCC",
        ]
        type_list = [
            "PACKED_PIXELS",
            "PLANES",
            "INTERLEAVED_PLANES",
            "TEXT",
            "VGA_PLANES",
            "FOURCC",
        ]
        visual_name = "unknown"
        if self.visual < len(visual_list):
            visual_name = visual_list[self.visual]
        type_name = "unknown"
        if self.type < len(type_list):
            type_name = type_list[self.type]

        return (
            'mode "%sx%s"\n' % (self.xres, self.yres)
            + "    nonstd %s\n" % self.nonstd
            + "    rgba %s/%s,%s/%s,%s/%s,%s/%s\n"
            % (
                self.red.length,
                self.red.offset,
                self.green.length,
                self.green.offset,
                self.blue.length,
                self.blue.offset,
                self.transp.length,
                self.transp.offset,
            )
            + "endmode\n"
            + "\n"
            + "Frame buffer device information:\n"
            + "    Device      : %s\n" % self.dev
            + "    Name        : %s\n" % self.name
            + "    Size        : (%d, %d)\n" % (self.xres, self.yres)
            + "    Length      : %s\n" % self.screensize
            + "    BPP         : %d\n" % self.bits_per_pixel
            + "    Type        : %s\n" % type_name
            + "    Visual      : %s\n" % visual_name
            + "    LineLength  : %s\n" % self.line_length
        )

device = "/dev/fb0"
fb = Framebuffer(device)
print(fb)

# Configuration for CS and DC pins (these are FeatherWing defaults on M0/M4):
cs_pin = digitalio.DigitalInOut(board.CE0)
dc_pin = digitalio.DigitalInOut(board.D25)
reset_pin = None

BAUDRATE = 64000000

# Setup SPI bus using hardware SPI:
spi = board.SPI()

# Create the ST7789 display:
disp = st7789.ST7789(
    spi,
    cs=cs_pin,
    dc=dc_pin,
    rst=reset_pin,
    baudrate=BAUDRATE,
    width=135,
    height=240,
    x_offset=53,
    y_offset=40,
)

height = disp.width  # we swap height/width to rotate it to landscape!
width = disp.height
image = Image.new("RGB", (width, height))
rotation = 90

# Get drawing object to draw on image.
draw = ImageDraw.Draw(image)

# Draw a black filled box to clear the image.
draw.rectangle((0, 0, width, height), outline=0, fill=(0, 0, 0))
disp.image(image, rotation)

while True:
    t = time.monotonic()
    fb.fbp.seek(0)
    b = fb.fbp.read(fb.screensize)
    fbimage = Image.frombytes("RGBA", (fb.xres, fb.yres), b, "raw")
    b, g, r, a = fbimage.split()
    fbimage = Image.merge("RGB", (r, g, b))
    fbimage = fbimage.resize((width, height))

    disp.image(fbimage, rotation)
    print(1.0 / (time.monotonic() - t))
fb.close()


while True:
    t = time.monotonic()
    fb.fbp.seek(0)
    b = fb.fbp.read(fb.screensize)
    fbimage = Image.frombytes("RGBA", (fb.xres, fb.yres), b, "raw")
    b, g, r, a = fbimage.split()
    fbimage = Image.merge("RGB", (r, g, b))
    fbimage = fbimage.resize((width, height))

    disp.image(fbimage, rotation)
    print(1.0 / (time.monotonic() - t))

import time
import os
import fcntl
import mmap
import struct
import digitalio
import board
from PIL import Image, ImageDraw
import adafruit_rgb_display.st7789 as st7789

FBIOGET_VSCREENINFO = 0x4600
FBIOGET_FSCREENINFO = 0x4602
FBIOBLANK = 0x4611

FB_TYPE_PACKED_PIXELS = 0
FB_TYPE_PLANES = 1
FB_TYPE_INTERLEAVED_PLANES = 2
FB_TYPE_TEXT = 3
FB_TYPE_VGA_PLANES = 4
FB_TYPE_FOURCC = 5

FB_VISUAL_MONO01 = 0
FB_VISUAL_MONO10 = 1
FB_VISUAL_TRUECOLOR = 2
FB_VISUAL_PSEUDOCOLOR = 3
FB_VISUAL_DIRECTCOLOR = 4
FB_VISUAL_STATIC_PSEUDOCOLOR = 5
FB_VISUAL_FOURCC = 6

FB_BLANK_UNBLANK = 0
FB_BLANK_POWERDOWN = 4

class Bitfield:  # pylint: disable=too-few-public-methods
    def __init__(self, offset, length, msb_right):
        self.offset = offset
        self.length = length
        self.msb_right = msb_right

# Kind of like a pygame Surface object, or not!
class Framebuffer:  # pylint: disable=too-many-instance-attributes
    def __init__(self, dev):
        self.dev = dev
        self.fbfd = os.open(dev, os.O_RDWR)
        vinfo = struct.unpack(
            "8I12I16I4I",
            fcntl.ioctl(self.fbfd, FBIOGET_VSCREENINFO, " " * ((8 + 12 + 16 + 4) * 4)),
        )
        finfo = struct.unpack(
            "16cL4I3HI", fcntl.ioctl(self.fbfd, FBIOGET_FSCREENINFO, " " * 48)
        )

        bytes_per_pixel = (vinfo[6] + 7) // 8
        screensize = vinfo[0] * vinfo[1] * bytes_per_pixel

        fbp = mmap.mmap(
            self.fbfd, screensize, flags=mmap.MAP_SHARED, prot=mmap.PROT_READ
        )

        self.fbp = fbp
        self.xres = vinfo[0]
        self.yres = vinfo[1]
        self.xoffset = vinfo[4]
        self.yoffset = vinfo[5]
        self.bits_per_pixel = vinfo[6]
        self.bytes_per_pixel = bytes_per_pixel
        self.grayscale = vinfo[7]
        self.red = Bitfield(vinfo[8], vinfo[9], vinfo[10])
        self.green = Bitfield(vinfo[11], vinfo[12], vinfo[13])
        self.blue = Bitfield(vinfo[14], vinfo[15], vinfo[16])
        self.transp = Bitfield(vinfo[17], vinfo[18], vinfo[19])
        self.nonstd = vinfo[20]
        self.name = b"".join([x for x in finfo[0:15] if x != b"\x00"])
        self.type = finfo[18]
        self.visual = finfo[20]
        self.line_length = finfo[24]
        self.screensize = screensize

    def close(self):
        self.fbp.close()
        os.close(self.fbfd)

    def blank(self, blank):
        # Blanking is not supported by all drivers
        try:
            if blank:
                fcntl.ioctl(self.fbfd, FBIOBLANK, FB_BLANK_POWERDOWN)
            else:
                fcntl.ioctl(self.fbfd, FBIOBLANK, FB_BLANK_UNBLANK)
        except IOError:
            pass

    def __str__(self):
        visual_list = [
            "MONO01",
            "MONO10",
            "TRUECOLOR",
            "PSEUDOCOLOR",
            "DIRECTCOLOR",
            "STATIC PSEUDOCOLOR",
            "FOURCC",
        ]
        type_list = [
            "PACKED_PIXELS",
            "PLANES",
            "INTERLEAVED_PLANES",
            "TEXT",
            "VGA_PLANES",
            "FOURCC",
        ]
        visual_name = "unknown"
        if self.visual < len(visual_list):
            visual_name = visual_list[self.visual]
        type_name = "unknown"
        if self.type < len(type_list):
            type_name = type_list[self.type]

        return (
            'mode "%sx%s"\n' % (self.xres, self.yres)
            + "    nonstd %s\n" % self.nonstd
            + "    rgba %s/%s,%s/%s,%s/%s,%s/%s\n"
            % (
                self.red.length,
                self.red.offset,
                self.green.length,
                self.green.offset,
                self.blue.length,
                self.blue.offset,
                self.transp.length,
                self.transp.offset,
            )
            + "endmode\n"
            + "\n"
            + "Frame buffer device information:\n"
            + "    Device      : %s\n" % self.dev
            + "    Name        : %s\n" % self.name
            + "    Size        : (%d, %d)\n" % (self.xres, self.yres)
            + "    Length      : %s\n" % self.screensize
            + "    BPP         : %d\n" % self.bits_per_pixel
            + "    Type        : %s\n" % type_name
            + "    Visual      : %s\n" % visual_name
            + "    LineLength  : %s\n" % self.line_length
        )

device = "/dev/fb0"
fb = Framebuffer(device)
print(fb)

# Configuration for CS and DC pins (these are FeatherWing defaults on M0/M4):
cs_pin = digitalio.DigitalInOut(board.CE0)
dc_pin = digitalio.DigitalInOut(board.D25)
reset_pin = None

BAUDRATE = 64000000

# Setup SPI bus using hardware SPI:
spi = board.SPI()

# Create the ST7789 display:
disp = st7789.ST7789(
    spi,
    cs=cs_pin,
    dc=dc_pin,
    rst=reset_pin,
    baudrate=BAUDRATE,
    width=135,
    height=240,
    x_offset=53,
    y_offset=40,
)

height = disp.width  # we swap height/width to rotate it to landscape!
width = disp.height
image = Image.new("RGB", (width, height))
rotation = 90

# Get drawing object to draw on image.
draw = ImageDraw.Draw(image)

# Draw a black filled box to clear the image.
draw.rectangle((0, 0, width, height), outline=0, fill=(0, 0, 0))
disp.image(image, rotation)


try:
            if blank:
                fcntl.ioctl(self.fbfd, FBIOBLANK, FB_BLANK_POWERDOWN)
            else:
                fcntl.ioctl(self.fbfd, FBIOBLANK, FB_BLANK_UNBLANK)
        except IOError:
            pass

import time
import os
import fcntl
import mmap
import struct
import digitalio
import board
from PIL import Image, ImageDraw
import adafruit_rgb_display.st7789 as st7789

FBIOGET_VSCREENINFO = 0x4600
FBIOGET_FSCREENINFO = 0x4602
FBIOBLANK = 0x4611

FB_TYPE_PACKED_PIXELS = 0
FB_TYPE_PLANES = 1
FB_TYPE_INTERLEAVED_PLANES = 2
FB_TYPE_TEXT = 3
FB_TYPE_VGA_PLANES = 4
FB_TYPE_FOURCC = 5

FB_VISUAL_MONO01 = 0
FB_VISUAL_MONO10 = 1
FB_VISUAL_TRUECOLOR = 2
FB_VISUAL_PSEUDOCOLOR = 3
FB_VISUAL_DIRECTCOLOR = 4
FB_VISUAL_STATIC_PSEUDOCOLOR = 5
FB_VISUAL_FOURCC = 6

FB_BLANK_UNBLANK = 0
FB_BLANK_POWERDOWN = 4

class Bitfield:  # pylint: disable=too-few-public-methods
    def __init__(self, offset, length, msb_right):
        self.offset = offset
        self.length = length
        self.msb_right = msb_right

# Kind of like a pygame Surface object, or not!
class Framebuffer:  # pylint: disable=too-many-instance-attributes
    def __init__(self, dev):
        self.dev = dev
        self.fbfd = os.open(dev, os.O_RDWR)
        vinfo = struct.unpack(
            "8I12I16I4I",
            fcntl.ioctl(self.fbfd, FBIOGET_VSCREENINFO, " " * ((8 + 12 + 16 + 4) * 4)),
        )
        finfo = struct.unpack(
            "16cL4I3HI", fcntl.ioctl(self.fbfd, FBIOGET_FSCREENINFO, " " * 48)
        )

        bytes_per_pixel = (vinfo[6] + 7) // 8
        screensize = vinfo[0] * vinfo[1] * bytes_per_pixel

        fbp = mmap.mmap(
            self.fbfd, screensize, flags=mmap.MAP_SHARED, prot=mmap.PROT_READ
        )

        self.fbp = fbp
        self.xres = vinfo[0]
        self.yres = vinfo[1]
        self.xoffset = vinfo[4]
        self.yoffset = vinfo[5]
        self.bits_per_pixel = vinfo[6]
        self.bytes_per_pixel = bytes_per_pixel
        self.grayscale = vinfo[7]
        self.red = Bitfield(vinfo[8], vinfo[9], vinfo[10])
        self.green = Bitfield(vinfo[11], vinfo[12], vinfo[13])
        self.blue = Bitfield(vinfo[14], vinfo[15], vinfo[16])
        self.transp = Bitfield(vinfo[17], vinfo[18], vinfo[19])
        self.nonstd = vinfo[20]
        self.name = b"".join([x for x in finfo[0:15] if x != b"\x00"])
        self.type = finfo[18]
        self.visual = finfo[20]
        self.line_length = finfo[24]
        self.screensize = screensize

    def close(self):
        self.fbp.close()
        os.close(self.fbfd)

    def blank(self, blank):
        # Blanking is not supported by all drivers

    def __str__(self):
        visual_list = [
            "MONO01",
            "MONO10",
            "TRUECOLOR",
            "PSEUDOCOLOR",
            "DIRECTCOLOR",
            "STATIC PSEUDOCOLOR",
            "FOURCC",
        ]
        type_list = [
            "PACKED_PIXELS",
            "PLANES",
            "INTERLEAVED_PLANES",
            "TEXT",
            "VGA_PLANES",
            "FOURCC",
        ]
        visual_name = "unknown"
        if self.visual < len(visual_list):
            visual_name = visual_list[self.visual]
        type_name = "unknown"
        if self.type < len(type_list):
            type_name = type_list[self.type]

        return (
            'mode "%sx%s"\n' % (self.xres, self.yres)
            + "    nonstd %s\n" % self.nonstd
            + "    rgba %s/%s,%s/%s,%s/%s,%s/%s\n"
            % (
                self.red.length,
                self.red.offset,
                self.green.length,
                self.green.offset,
                self.blue.length,
                self.blue.offset,
                self.transp.length,
                self.transp.offset,
            )
            + "endmode\n"
            + "\n"
            + "Frame buffer device information:\n"
            + "    Device      : %s\n" % self.dev
            + "    Name        : %s\n" % self.name
            + "    Size        : (%d, %d)\n" % (self.xres, self.yres)
            + "    Length      : %s\n" % self.screensize
            + "    BPP         : %d\n" % self.bits_per_pixel
            + "    Type        : %s\n" % type_name
            + "    Visual      : %s\n" % visual_name
            + "    LineLength  : %s\n" % self.line_length
        )

device = "/dev/fb0"
fb = Framebuffer(device)
print(fb)

# Configuration for CS and DC pins (these are FeatherWing defaults on M0/M4):
cs_pin = digitalio.DigitalInOut(board.CE0)
dc_pin = digitalio.DigitalInOut(board.D25)
reset_pin = None

BAUDRATE = 64000000

# Setup SPI bus using hardware SPI:
spi = board.SPI()

# Create the ST7789 display:
disp = st7789.ST7789(
    spi,
    cs=cs_pin,
    dc=dc_pin,
    rst=reset_pin,
    baudrate=BAUDRATE,
    width=135,
    height=240,
    x_offset=53,
    y_offset=40,
)

height = disp.width  # we swap height/width to rotate it to landscape!
width = disp.height
image = Image.new("RGB", (width, height))
rotation = 90

# Get drawing object to draw on image.
draw = ImageDraw.Draw(image)

# Draw a black filled box to clear the image.
draw.rectangle((0, 0, width, height), outline=0, fill=(0, 0, 0))
disp.image(image, rotation)

while True:
    t = time.monotonic()
    fb.fbp.seek(0)
    b = fb.fbp.read(fb.screensize)
    fbimage = Image.frombytes("RGBA", (fb.xres, fb.yres), b, "raw")
    b, g, r, a = fbimage.split()
    fbimage = Image.merge("RGB", (r, g, b))
    fbimage = fbimage.resize((width, height))

    disp.image(fbimage, rotation)
    print(1.0 / (time.monotonic() - t))
fb.close()


"""Edge class"""
# File      : edge.py -- Base class for an edge
# Author    : Jürgen Hackl <example@nnthink.com>
from typing import Any, Optional, Union

from singledispatchmethod import singledispatchmethod  # NOTE: not needed at 3.9

from pathpy import logger
from pathpy.core.core import PathPyObject, PathPyPath, PathPyCollection

# create logger for the Path class
LOG = logger(__name__)

class Edge(PathPyPath):
    """Base class for an edge.

    An edge is (together with nodes) one of the two basic units out of which
    networks are constructed. Each edge has two nodes to which it is attached,
    called its endpoints. Edges may be directed or undirected; undirected edges
    are also called lines and directed edges are also called arcs or arrows. In
    an undirected network, an edge may be represented as the set of its nodes,
    and in a directed network it may be represented as an ordered pair of its
    vertices.

    In ``pathpy`` the edge is referenced by its unique identifier (``uid``),
    connects two :py:class:`PathPyObjects` and can store any arbitrary python
    objects as attributes.

    .. note::

       To generate an edge, only two nodes have to be defined. ``pathpy`` will
       automatically create a unique identifier (``uid``) for the edge.

    Parameters
    ----------

    v : str or PathPyObject

        This parameter defines the source of the edge (if directed),
        i.e. v->w. Beside a py:class:`PathPyObject` object also a ``str`` node
        uid can be entered.

    w : str or PathPyObject

        This parameter defines the source of the edge (if directed),
        i.e. v->w. Beside a py:class:`PathPyObject` object also a ``str`` node
        uid can be entered.

    uid : str, optional (default = None)

        The parameter ``uid`` is the unique identifier for the edge. Every edge
        should have an uid. The uid is converted to a string value and is used
        as a key value for all dict which saving edge objects. If no edge uid
        is specified the edge ``uid`` will be defined by pathpy based on the
        internal object id.

    directed : bool, optional (default = True)

        If ``True`` the edge is directed, i.e. quantities can only transmited
        from the source node ``v`` to the traget node ``w``. If ``False`` the
        edge is undirected, i.e. quantities can be transmited in both
        directions. Per default edges in ``pathpy`` are directed.

    kwargs : Any

        Keyword arguments to store edge attributes. Attributes are added to the
        edge as ``key=value`` pairs.

    Examples
    --------
    From the ``pathpy`` import the :py:class:`Node` and :py:class:`Edge` classes.

    >>> from pathpy import Node, Edge

    Create an edge ``e`` with given nodes.

    >>> v = Node('w')
    >>> w = Node('v')
    >>> e = Edge(v, w, uid='e')
    >>> e.uid
    e

    Create an edge with given node uids and no edge uid.

    >>> ab = Edge('a', 'b', uid = 'a-b')
    >>> ab.uid
    a-b

    Show the associated node objects

    >>> ab.objects
    {'a': Node a, 'b': Node b}

    Create an edge with attached attribute.

    >>> ab = Edge('a','b', length=10)

    Add attribute to the edge.

    >>> ab['capacity'] = 5.5

    Show attached attributes

    >>> ab.attributes
    {'length': 10, 'capacity': 5}

    Change attribute.

    >>> ab['length'] = 5

    Update attributes (and add new).

    >>> ab.update(length = 2, capacity = 3, speed = 10)
    >>> ab.attributes
    {'length': 2, 'capacity': 3, 'speed': 10}

    Get the weight of the edge. Per default the attribute with the key 'weight'
    is used as weight. Should there be no such attribute, a new one will be
    crated with weight = 1.0.

    >>> ab.weight()
    1.0

    If an other attribute should be used as weight, the option weight has to be
    changed.

    >>> ab.weight('length')
    2

    If a weight is assigned but for calculation a weight of 1.0 is needed, the
    weight can be disabled with ``False`` or None.

    >>> ab['weight'] = 4
    >>> ab.weight()
    4.0
    >>> ab.weight(False)
    1.0

    Make copy of the edge.

    >>> ef = ab.copy()
    >>> ef.uid
    'a-b'

    Make a plot element and plot the edge as a png image.

    .. todo::

        Make a single plot command for plotting edges.
        The code below is not working yet!

    >>> plt = ab.plot()
    >>> plt.show('png')

    .. plot::

       import pathpy as pp
       ab = pp.Edge('a','b')
       net = pp.Network()
       net.add_edge(ab)
       plt = net.plot()
       plt.show('png')

    Create an undirected edge.

    >>> cd = Edge('c', 'd', directed=False)
    >>> plt = ab.plot()
    >>> plt.show('png')

    .. plot::

       import pathpy as pp
       cd = pp.Edge('c','d',directed=False)
       net = pp.Network(directed=False)
       net.add_edge(cd)
       plt = net.plot()
       plt.show('png')

    See Also
    --------
    Node

    """

    def __init__(self, v: Union[str, PathPyObject],
                 w: Union[str, PathPyObject],
                 uid: Optional[str] = None,
                 directed: bool = True,
                 **kwargs: Any) -> None:
        """Initialize the node object."""

        # initialize the parent class
        super().__init__(v, w, uid=uid, directed=directed, **kwargs)

    @property
    def v(self) -> str:
        """Return the uid of the source node v. """
        return self.objects[self.relations[0]]

    @property
    def w(self) -> str:
        """Return the uid of the target node w. """
        return self.objects[self.relations[-1]]

    @property
    def nodes(self) -> dict:
        """Return the nodes of the edge."""
        return self.objects

    def summary(self) -> str:
        """Returns a summary of the edge. """
        summary = [
            'Uid:\t\t{}\n'.format(self.uid),
            'Type:\t\t{}\n'.format(self.__class__.__name__),
            'Directed:\t{}\n'.format(self.directed),
            'Nodes:\t\t{}\n'.format(self.relations),
        ]

        return ''.join(summary)

class EdgeCollection(PathPyCollection):
    """A collection of edges"""
    # pylint: disable=too-many-ancestors

    def __init__(self, *args, **kwargs) -> None:
        """Initialize the EdgeCollection object."""

        # initialize the base class
        super().__init__(*args, **kwargs)

        # indicator whether the network has multi-edges
        self._multiple: bool = kwargs.pop('multiedges', False)

        # class of objects
        self._default_class: Any = Edge

    @property
    def multiedges(self) -> bool:
        """Return if edges are multiedges. """
        return self._multiple

    @singledispatchmethod
    def add(self, *args, **kwargs: Any) -> None:
        """Add multiple nodes. """
        raise NotImplementedError

    @add.register(Edge)  # type: ignore
    def _(self, *args: Edge, **kwargs: Any) -> None:
        super().add(args[0], **kwargs)

    @add.register(str)  # type: ignore
    @add.register(int)  # type: ignore
    @add.register(PathPyObject)  # type: ignore
    def _(self, *args: Union[int, str, PathPyObject], **kwargs: Any) -> None:

        # get additional parameters
        uid: Optional[str] = kwargs.pop('uid', None)
        count: int = kwargs.pop('count', 1)

        if len(args) == 2:
            obj = self._default_class(
                *args, uid=uid, directed=self.directed, **kwargs)
            super().add(obj, count=count, **kwargs)
        else:
            LOG.error('Only two objects can be linked with an edge')
            raise KeyError

    @add.register(tuple)  # type: ignore
    @add.register(list)  # type: ignore
    def _(self, *args: Union[tuple, list], **kwargs: Any) -> None:
        for arg in args:
            if len(arg) == 2:
                self.add(*arg, **kwargs)
            else:
                LOG.error('The provided edge "%s" is of the wrong format!', arg)
                raise AttributeError

    @singledispatchmethod
    def remove(self, *args, **kwargs):
        """Remove objects"""
        super().remove(*args, **kwargs)

    @remove.register(Edge)  # type: ignore
    def _(self, *args: Edge, **kwargs: Any) -> None:
        super().remove(*args, **kwargs)

    @remove.register(str)  # type: ignore
    @remove.register(int)  # type: ignore
    @remove.register(PathPyObject)  # type: ignore
    def _(self, *args: Union[int, str, PathPyObject], **kwargs: Any) -> None:

        # get additional parameters
        uid: Optional[str] = kwargs.pop('uid', None)

        if len(args) == 1 and args[0] in self:
            self.remove(self[args[0]])
        elif uid is not None:
            self.remove(uid)
        elif args in self and self.multiedges:
            for obj in list(self[args]):
                super().remove(obj)
        elif args in self and not self.multiedges:
            super().remove(self[args])
        else:
            LOG.warning('No edge was removed!')

    @remove.register(tuple)  # type: ignore
    @remove.register(list)  # type: ignore

for arg in args:
            self.remove(*arg, **kwargs)

# eof
# Local Variables:
# mode: python
# mode: linum
# mode: auto-fill


"""Return the uid of the target node w. """
        return self.objects[self.relations[-1]]

"""Edge class"""
# File      : edge.py -- Base class for an edge
# Author    : Jürgen Hackl <example@nnthink.com>
from typing import Any, Optional, Union

from singledispatchmethod import singledispatchmethod  # NOTE: not needed at 3.9

from pathpy import logger
from pathpy.core.core import PathPyObject, PathPyPath, PathPyCollection

# create logger for the Path class
LOG = logger(__name__)

class Edge(PathPyPath):
    """Base class for an edge.

    An edge is (together with nodes) one of the two basic units out of which
    networks are constructed. Each edge has two nodes to which it is attached,
    called its endpoints. Edges may be directed or undirected; undirected edges
    are also called lines and directed edges are also called arcs or arrows. In
    an undirected network, an edge may be represented as the set of its nodes,
    and in a directed network it may be represented as an ordered pair of its
    vertices.

    In ``pathpy`` the edge is referenced by its unique identifier (``uid``),
    connects two :py:class:`PathPyObjects` and can store any arbitrary python
    objects as attributes.

    .. note::

       To generate an edge, only two nodes have to be defined. ``pathpy`` will
       automatically create a unique identifier (``uid``) for the edge.

    Parameters
    ----------

    v : str or PathPyObject

        This parameter defines the source of the edge (if directed),
        i.e. v->w. Beside a py:class:`PathPyObject` object also a ``str`` node
        uid can be entered.

    w : str or PathPyObject

        This parameter defines the source of the edge (if directed),
        i.e. v->w. Beside a py:class:`PathPyObject` object also a ``str`` node
        uid can be entered.

    uid : str, optional (default = None)

        The parameter ``uid`` is the unique identifier for the edge. Every edge
        should have an uid. The uid is converted to a string value and is used
        as a key value for all dict which saving edge objects. If no edge uid
        is specified the edge ``uid`` will be defined by pathpy based on the
        internal object id.

    directed : bool, optional (default = True)

        If ``True`` the edge is directed, i.e. quantities can only transmited
        from the source node ``v`` to the traget node ``w``. If ``False`` the
        edge is undirected, i.e. quantities can be transmited in both
        directions. Per default edges in ``pathpy`` are directed.

    kwargs : Any

        Keyword arguments to store edge attributes. Attributes are added to the
        edge as ``key=value`` pairs.

    Examples
    --------
    From the ``pathpy`` import the :py:class:`Node` and :py:class:`Edge` classes.

    >>> from pathpy import Node, Edge

    Create an edge ``e`` with given nodes.

    >>> v = Node('w')
    >>> w = Node('v')
    >>> e = Edge(v, w, uid='e')
    >>> e.uid
    e

    Create an edge with given node uids and no edge uid.

    >>> ab = Edge('a', 'b', uid = 'a-b')
    >>> ab.uid
    a-b

    Show the associated node objects

    >>> ab.objects
    {'a': Node a, 'b': Node b}

    Create an edge with attached attribute.

    >>> ab = Edge('a','b', length=10)

    Add attribute to the edge.

    >>> ab['capacity'] = 5.5

    Show attached attributes

    >>> ab.attributes
    {'length': 10, 'capacity': 5}

    Change attribute.

    >>> ab['length'] = 5

    Update attributes (and add new).

    >>> ab.update(length = 2, capacity = 3, speed = 10)
    >>> ab.attributes
    {'length': 2, 'capacity': 3, 'speed': 10}

    Get the weight of the edge. Per default the attribute with the key 'weight'
    is used as weight. Should there be no such attribute, a new one will be
    crated with weight = 1.0.

    >>> ab.weight()
    1.0

    If an other attribute should be used as weight, the option weight has to be
    changed.

    >>> ab.weight('length')
    2

    If a weight is assigned but for calculation a weight of 1.0 is needed, the
    weight can be disabled with ``False`` or None.

    >>> ab['weight'] = 4
    >>> ab.weight()
    4.0
    >>> ab.weight(False)
    1.0

    Make copy of the edge.

    >>> ef = ab.copy()
    >>> ef.uid
    'a-b'

    Make a plot element and plot the edge as a png image.

    .. todo::

        Make a single plot command for plotting edges.
        The code below is not working yet!

    >>> plt = ab.plot()
    >>> plt.show('png')

    .. plot::

       import pathpy as pp
       ab = pp.Edge('a','b')
       net = pp.Network()
       net.add_edge(ab)
       plt = net.plot()
       plt.show('png')

    Create an undirected edge.

    >>> cd = Edge('c', 'd', directed=False)
    >>> plt = ab.plot()
    >>> plt.show('png')

    .. plot::

       import pathpy as pp
       cd = pp.Edge('c','d',directed=False)
       net = pp.Network(directed=False)
       net.add_edge(cd)
       plt = net.plot()
       plt.show('png')

    See Also
    --------
    Node

    """

    def __init__(self, v: Union[str, PathPyObject],
                 w: Union[str, PathPyObject],
                 uid: Optional[str] = None,
                 directed: bool = True,
                 **kwargs: Any) -> None:
        """Initialize the node object."""

        # initialize the parent class
        super().__init__(v, w, uid=uid, directed=directed, **kwargs)

    @property
    def v(self) -> str:
        """Return the uid of the source node v. """
        return self.objects[self.relations[0]]

    @property
    def w(self) -> str:

    @property
    def nodes(self) -> dict:
        """Return the nodes of the edge."""
        return self.objects

    def summary(self) -> str:
        """Returns a summary of the edge. """
        summary = [
            'Uid:\t\t{}\n'.format(self.uid),
            'Type:\t\t{}\n'.format(self.__class__.__name__),
            'Directed:\t{}\n'.format(self.directed),
            'Nodes:\t\t{}\n'.format(self.relations),
        ]

        return ''.join(summary)

class EdgeCollection(PathPyCollection):
    """A collection of edges"""
    # pylint: disable=too-many-ancestors

    def __init__(self, *args, **kwargs) -> None:
        """Initialize the EdgeCollection object."""

        # initialize the base class
        super().__init__(*args, **kwargs)

        # indicator whether the network has multi-edges
        self._multiple: bool = kwargs.pop('multiedges', False)

        # class of objects
        self._default_class: Any = Edge

    @property
    def multiedges(self) -> bool:
        """Return if edges are multiedges. """
        return self._multiple

    @singledispatchmethod
    def add(self, *args, **kwargs: Any) -> None:
        """Add multiple nodes. """
        raise NotImplementedError

    @add.register(Edge)  # type: ignore
    def _(self, *args: Edge, **kwargs: Any) -> None:
        super().add(args[0], **kwargs)

    @add.register(str)  # type: ignore
    @add.register(int)  # type: ignore
    @add.register(PathPyObject)  # type: ignore
    def _(self, *args: Union[int, str, PathPyObject], **kwargs: Any) -> None:

        # get additional parameters
        uid: Optional[str] = kwargs.pop('uid', None)
        count: int = kwargs.pop('count', 1)

        if len(args) == 2:
            obj = self._default_class(
                *args, uid=uid, directed=self.directed, **kwargs)
            super().add(obj, count=count, **kwargs)
        else:
            LOG.error('Only two objects can be linked with an edge')
            raise KeyError

    @add.register(tuple)  # type: ignore
    @add.register(list)  # type: ignore
    def _(self, *args: Union[tuple, list], **kwargs: Any) -> None:
        for arg in args:
            if len(arg) == 2:
                self.add(*arg, **kwargs)
            else:
                LOG.error('The provided edge "%s" is of the wrong format!', arg)
                raise AttributeError

    @singledispatchmethod
    def remove(self, *args, **kwargs):
        """Remove objects"""
        super().remove(*args, **kwargs)

    @remove.register(Edge)  # type: ignore
    def _(self, *args: Edge, **kwargs: Any) -> None:
        super().remove(*args, **kwargs)

    @remove.register(str)  # type: ignore
    @remove.register(int)  # type: ignore
    @remove.register(PathPyObject)  # type: ignore
    def _(self, *args: Union[int, str, PathPyObject], **kwargs: Any) -> None:

        # get additional parameters
        uid: Optional[str] = kwargs.pop('uid', None)

        if len(args) == 1 and args[0] in self:
            self.remove(self[args[0]])
        elif uid is not None:
            self.remove(uid)
        elif args in self and self.multiedges:
            for obj in list(self[args]):
                super().remove(obj)
        elif args in self and not self.multiedges:
            super().remove(self[args])
        else:
            LOG.warning('No edge was removed!')

    @remove.register(tuple)  # type: ignore
    @remove.register(list)  # type: ignore
    def _(self, *args: Union[tuple, list], **kwargs: Any) -> None:
        for arg in args:
            self.remove(*arg, **kwargs)

# eof
# Local Variables:
# mode: python
# mode: linum
# mode: auto-fill


"""Edge class"""
# File      : edge.py -- Base class for an edge
# Author    : Jürgen Hackl <example@nnthink.com>
from typing import Any, Optional, Union

from singledispatchmethod import singledispatchmethod  # NOTE: not needed at 3.9

from pathpy import logger
from pathpy.core.core import PathPyObject, PathPyPath, PathPyCollection

# create logger for the Path class
LOG = logger(__name__)

class Edge(PathPyPath):
    """Base class for an edge.

    An edge is (together with nodes) one of the two basic units out of which
    networks are constructed. Each edge has two nodes to which it is attached,
    called its endpoints. Edges may be directed or undirected; undirected edges
    are also called lines and directed edges are also called arcs or arrows. In
    an undirected network, an edge may be represented as the set of its nodes,
    and in a directed network it may be represented as an ordered pair of its
    vertices.

    In ``pathpy`` the edge is referenced by its unique identifier (``uid``),
    connects two :py:class:`PathPyObjects` and can store any arbitrary python
    objects as attributes.

    .. note::

       To generate an edge, only two nodes have to be defined. ``pathpy`` will
       automatically create a unique identifier (``uid``) for the edge.

    Parameters
    ----------

    v : str or PathPyObject

        This parameter defines the source of the edge (if directed),
        i.e. v->w. Beside a py:class:`PathPyObject` object also a ``str`` node
        uid can be entered.

    w : str or PathPyObject

        This parameter defines the source of the edge (if directed),
        i.e. v->w. Beside a py:class:`PathPyObject` object also a ``str`` node
        uid can be entered.

    uid : str, optional (default = None)

        The parameter ``uid`` is the unique identifier for the edge. Every edge
        should have an uid. The uid is converted to a string value and is used
        as a key value for all dict which saving edge objects. If no edge uid
        is specified the edge ``uid`` will be defined by pathpy based on the
        internal object id.

    directed : bool, optional (default = True)

        If ``True`` the edge is directed, i.e. quantities can only transmited
        from the source node ``v`` to the traget node ``w``. If ``False`` the
        edge is undirected, i.e. quantities can be transmited in both
        directions. Per default edges in ``pathpy`` are directed.

    kwargs : Any

        Keyword arguments to store edge attributes. Attributes are added to the
        edge as ``key=value`` pairs.

    Examples
    --------
    From the ``pathpy`` import the :py:class:`Node` and :py:class:`Edge` classes.

    >>> from pathpy import Node, Edge

    Create an edge ``e`` with given nodes.

    >>> v = Node('w')
    >>> w = Node('v')
    >>> e = Edge(v, w, uid='e')
    >>> e.uid
    e

    Create an edge with given node uids and no edge uid.

    >>> ab = Edge('a', 'b', uid = 'a-b')
    >>> ab.uid
    a-b

    Show the associated node objects

    >>> ab.objects
    {'a': Node a, 'b': Node b}

    Create an edge with attached attribute.

    >>> ab = Edge('a','b', length=10)

    Add attribute to the edge.

    >>> ab['capacity'] = 5.5

    Show attached attributes

    >>> ab.attributes
    {'length': 10, 'capacity': 5}

    Change attribute.

    >>> ab['length'] = 5

    Update attributes (and add new).

    >>> ab.update(length = 2, capacity = 3, speed = 10)
    >>> ab.attributes
    {'length': 2, 'capacity': 3, 'speed': 10}

    Get the weight of the edge. Per default the attribute with the key 'weight'
    is used as weight. Should there be no such attribute, a new one will be
    crated with weight = 1.0.

    >>> ab.weight()
    1.0

    If an other attribute should be used as weight, the option weight has to be
    changed.

    >>> ab.weight('length')
    2

    If a weight is assigned but for calculation a weight of 1.0 is needed, the
    weight can be disabled with ``False`` or None.

    >>> ab['weight'] = 4
    >>> ab.weight()
    4.0
    >>> ab.weight(False)
    1.0

    Make copy of the edge.

    >>> ef = ab.copy()
    >>> ef.uid
    'a-b'

    Make a plot element and plot the edge as a png image.

    .. todo::

        Make a single plot command for plotting edges.
        The code below is not working yet!

    >>> plt = ab.plot()
    >>> plt.show('png')

    .. plot::

       import pathpy as pp
       ab = pp.Edge('a','b')
       net = pp.Network()
       net.add_edge(ab)
       plt = net.plot()
       plt.show('png')

    Create an undirected edge.

    >>> cd = Edge('c', 'd', directed=False)
    >>> plt = ab.plot()
    >>> plt.show('png')

    .. plot::

       import pathpy as pp
       cd = pp.Edge('c','d',directed=False)
       net = pp.Network(directed=False)
       net.add_edge(cd)
       plt = net.plot()
       plt.show('png')

    See Also
    --------
    Node

    """

    def __init__(self, v: Union[str, PathPyObject],
                 w: Union[str, PathPyObject],
                 uid: Optional[str] = None,
                 directed: bool = True,
                 **kwargs: Any) -> None:
        """Initialize the node object."""

        # initialize the parent class
        super().__init__(v, w, uid=uid, directed=directed, **kwargs)

    @property
    def v(self) -> str:
        """Return the uid of the source node v. """
        return self.objects[self.relations[0]]

    @property
    def w(self) -> str:
        """Return the uid of the target node w. """
        return self.objects[self.relations[-1]]

    @property
    def nodes(self) -> dict:
        """Return the nodes of the edge."""
        return self.objects

    def summary(self) -> str:
        """Returns a summary of the edge. """
        summary = [
            'Uid:\t\t{}\n'.format(self.uid),
            'Type:\t\t{}\n'.format(self.__class__.__name__),
            'Directed:\t{}\n'.format(self.directed),
            'Nodes:\t\t{}\n'.format(self.relations),
        ]

        return ''.join(summary)

class EdgeCollection(PathPyCollection):
    """A collection of edges"""
    # pylint: disable=too-many-ancestors

    def __init__(self, *args, **kwargs) -> None:
        """Initialize the EdgeCollection object."""

        # initialize the base class
        super().__init__(*args, **kwargs)

        # indicator whether the network has multi-edges
        self._multiple: bool = kwargs.pop('multiedges', False)

        # class of objects
        self._default_class: Any = Edge

    @property
    def multiedges(self) -> bool:
        """Return if edges are multiedges. """
        ret

    @singledispatchmethod
    def add(self, *args, **kwargs: Any) -> None:
        """Add multiple nodes. """
        raise NotImplementedError

    @add.register(Edge)  # type: ignore
    def _(self, *args: Edge, **kwargs: Any) -> None:
        super().add(args[0], **kwargs)

    @add.register(str)  # type: ignore
    @add.register(int)  # type: ignore
    @add.register(PathPyObject)  # type: ignore
    def _(self, *args: Union[int, str, PathPyObject], **kwargs: Any) -> None:

        # get additional parameters
        uid: Optional[str] = kwargs.pop('uid', None)
        count: int = kwargs.pop('count', 1)

        if len(args) == 2:
            obj = self._default_class(
                *args, uid=uid, directed=self.directed, **kwargs)
            super().add(obj, count=count, **kwargs)
        else:
            LOG.error('Only two objects can be linked with an edge')
            raise KeyError

    @add.register(tuple)  # type: ignore
    @add.register(list)  # type: ignore
    def _(self, *args: Union[tuple, list], **kwargs: Any) -> None:
        for arg in args:
            if len(arg) == 2:
                self.add(*arg, **kwargs)
            else:
                LOG.error('The provided edge "%s" is of the wrong format!', arg)
                raise AttributeError

    @singledispatchmethod
    def remove(self, *args, **kwargs):
        """Remove objects"""
        super().remove(*args, **kwargs)

    @remove.register(Edge)  # type: ignore
    def _(self, *args: Edge, **kwargs: Any) -> None:
        super().remove(*args, **kwargs)

    @remove.register(str)  # type: ignore
    @remove.register(int)  # type: ignore
    @remove.register(PathPyObject)  # type: ignore
    def _(self, *args: Union[int, str, PathPyObject], **kwargs: Any) -> None:

        # get additional parameters
        uid: Optional[str] = kwargs.pop('uid', None)

        if len(args) == 1 and args[0] in self:
            self.remove(self[args[0]])
        elif uid is not None:
            self.remove(uid)
        elif args in self and self.multiedges:
            for obj in list(self[args]):
                super().remove(obj)
        elif args in self and not self.multiedges:
            super().remove(self[args])
        else:
            LOG.warning('No edge was removed!')

    @remove.register(tuple)  # type: ignore
    @remove.register(list)  # type: ignore
    def _(self, *args: Union[tuple, list], **kwargs: Any) -> None:
        for arg in args:
            self.remove(*arg, **kwargs)

# eof
# Local Variables:
# mode: python
# mode: linum
# mode: auto-fill


"""Edge class"""
# File      : edge.py -- Base class for an edge
# Author    : Jürgen Hackl <example@nnthink.com>
from typing import Any, Optional, Union

from singledispatchmethod import singledispatchmethod  # NOTE: not needed at 3.9

from pathpy import logger
from pathpy.core.core import PathPyObject, PathPyPath, PathPyCollection

# create logger for the Path class
LOG = logger(__name__)

class Edge(PathPyPath):
    """Base class for an edge.

    An edge is (together with nodes) one of the two basic units out of which
    networks are constructed. Each edge has two nodes to which it is attached,
    called its endpoints. Edges may be directed or undirected; undirected edges
    are also called lines and directed edges are also called arcs or arrows. In
    an undirected network, an edge may be represented as the set of its nodes,
    and in a directed network it may be represented as an ordered pair of its
    vertices.

    In ``pathpy`` the edge is referenced by its unique identifier (``uid``),
    connects two :py:class:`PathPyObjects` and can store any arbitrary python
    objects as attributes.

    .. note::

       To generate an edge, only two nodes have to be defined. ``pathpy`` will
       automatically create a unique identifier (``uid``) for the edge.

    Parameters
    ----------

    v : str or PathPyObject

        This parameter defines the source of the edge (if directed),
        i.e. v->w. Beside a py:class:`PathPyObject` object also a ``str`` node
        uid can be entered.

    w : str or PathPyObject

        This parameter defines the source of the edge (if directed),
        i.e. v->w. Beside a py:class:`PathPyObject` object also a ``str`` node
        uid can be entered.

    uid : str, optional (default = None)

        The parameter ``uid`` is the unique identifier for the edge. Every edge
        should have an uid. The uid is converted to a string value and is used
        as a key value for all dict which saving edge objects. If no edge uid
        is specified the edge ``uid`` will be defined by pathpy based on the
        internal object id.

    directed : bool, optional (default = True)

        If ``True`` the edge is directed, i.e. quantities can only transmited
        from the source node ``v`` to the traget node ``w``. If ``False`` the
        edge is undirected, i.e. quantities can be transmited in both
        directions. Per default edges in ``pathpy`` are directed.

    kwargs : Any

        Keyword arguments to store edge attributes. Attributes are added to the
        edge as ``key=value`` pairs.

    Examples
    --------
    From the ``pathpy`` import the :py:class:`Node` and :py:class:`Edge` classes.

    >>> from pathpy import Node, Edge

    Create an edge ``e`` with given nodes.

    >>> v = Node('w')
    >>> w = Node('v')
    >>> e = Edge(v, w, uid='e')
    >>> e.uid
    e

    Create an edge with given node uids and no edge uid.

    >>> ab = Edge('a', 'b', uid = 'a-b')
    >>> ab.uid
    a-b

    Show the associated node objects

    >>> ab.objects
    {'a': Node a, 'b': Node b}

    Create an edge with attached attribute.

    >>> ab = Edge('a','b', length=10)

    Add attribute to the edge.

    >>> ab['capacity'] = 5.5

    Show attached attributes

    >>> ab.attributes
    {'length': 10, 'capacity': 5}

    Change attribute.

    >>> ab['length'] = 5

    Update attributes (and add new).

    >>> ab.update(length = 2, capacity = 3, speed = 10)
    >>> ab.attributes
    {'length': 2, 'capacity': 3, 'speed': 10}

    Get the weight of the edge. Per default the attribute with the key 'weight'
    is used as weight. Should there be no such attribute, a new one will be
    crated with weight = 1.0.

    >>> ab.weight()
    1.0

    If an other attribute should be used as weight, the option weight has to be
    changed.

    >>> ab.weight('length')
    2

    If a weight is assigned but for calculation a weight of 1.0 is needed, the
    weight can be disabled with ``False`` or None.

    >>> ab['weight'] = 4
    >>> ab.weight()
    4.0
    >>> ab.weight(False)
    1.0

    Make copy of the edge.

    >>> ef = ab.copy()
    >>> ef.uid
    'a-b'

    Make a plot element and plot the edge as a png image.

    .. todo::

        Make a single plot command for plotting edges.
        The code below is not working yet!

    >>> plt = ab.plot()
    >>> plt.show('png')

    .. plot::

       import pathpy as pp
       ab = pp.Edge('a','b')
       net = pp.Network()
       net.add_edge(ab)
       plt = net.plot()
       plt.show('png')

    Create an undirected edge.

    >>> cd = Edge('c', 'd', directed=False)
    >>> plt = ab.plot()
    >>> plt.show('png')

    .. plot::

       import pathpy as pp
       cd = pp.Edge('c','d',directed=False)
       net = pp.Network(directed=False)
       net.add_edge(cd)
       plt = net.plot()
       plt.show('png')

    See Also
    --------
    Node

    """

    def __init__(self, v: Union[str, PathPyObject],
                 w: Union[str, PathPyObject],
                 uid: Optional[str] = None,
                 directed: bool = True,
                 **kwargs: Any) -> None:
        """Initialize the node object."""

        # initialize the parent class
        super().__init__(v, w, uid=uid, directed=directed, **kwargs)

    @property
    def v(self) -> str:
        """Return the uid of the source node v. """
        return self.objects[self.relations[0]]

    @property
    def w(self) -> str:
        """Return the uid of the target node w. """
        return self.objects[self.relations[-1]]

    @property
    def nodes(self) -> dict:
        """Return the nodes of the edge."""

    def summary(self) -> str:
        """Returns a summary of the edge. """
        summary = [
            'Uid:\t\t{}\n'.format(self.uid),
            'Type:\t\t{}\n'.format(self.__class__.__name__),
            'Directed:\t{}\n'.format(self.directed),
            'Nodes:\t\t{}\n'.format(self.relations),
        ]

        return ''.join(summary)

class EdgeCollection(PathPyCollection):
    """A collection of edges"""
    # pylint: disable=too-many-ancestors

    def __init__(self, *args, **kwargs) -> None:
        """Initialize the EdgeCollection object."""

        # initialize the base class
        super().__init__(*args, **kwargs)

        # indicator whether the network has multi-edges
        self._multiple: bool = kwargs.pop('multiedges', False)

        # class of objects
        self._default_class: Any = Edge

    @property
    def multiedges(self) -> bool:
        """Return if edges are multiedges. """
        return self._multiple

    @singledispatchmethod
    def add(self, *args, **kwargs: Any) -> None:
        """Add multiple nodes. """
        raise NotImplementedError

    @add.register(Edge)  # type: ignore
    def _(self, *args: Edge, **kwargs: Any) -> None:
        super().add(args[0], **kwargs)

    @add.register(str)  # type: ignore
    @add.register(int)  # type: ignore
    @add.register(PathPyObject)  # type: ignore
    def _(self, *args: Union[int, str, PathPyObject], **kwargs: Any) -> None:

        # get additional parameters
        uid: Optional[str] = kwargs.pop('uid', None)
        count: int = kwargs.pop('count', 1)

        if len(args) == 2:
            obj = self._default_class(
                *args, uid=uid, directed=self.directed, **kwargs)
            super().add(obj, count=count, **kwargs)
        else:
            LOG.error('Only two objects can be linked with an edge')
            raise KeyError

    @add.register(tuple)  # type: ignore
    @add.register(list)  # type: ignore
    def _(self, *args: Union[tuple, list], **kwargs: Any) -> None:
        for arg in args:
            if len(arg) == 2:
                self.add(*arg, **kwargs)
            else:
                LOG.error('The provided edge "%s" is of the wrong format!', arg)
                raise AttributeError

    @singledispatchmethod
    def remove(self, *args, **kwargs):
        """Remove objects"""
        super().remove(*args, **kwargs)

    @remove.register(Edge)  # type: ignore
    def _(self, *args: Edge, **kwargs: Any) -> None:
        super().remove(*args, **kwargs)

    @remove.register(str)  # type: ignore
    @remove.register(int)  # type: ignore
    @remove.register(PathPyObject)  # type: ignore
    def _(self, *args: Union[int, str, PathPyObject], **kwargs: Any) -> None:

        # get additional parameters
        uid: Optional[str] = kwargs.pop('uid', None)

        if len(args) == 1 and args[0] in self:
            self.remove(self[args[0]])
        elif uid is not None:
            self.remove(uid)
        elif args in self and self.multiedges:
            for obj in list(self[args]):
                super().remove(obj)
        elif args in self and not self.multiedges:
            super().remove(self[args])
        else:
            LOG.warning('No edge was removed!')

    @remove.register(tuple)  # type: ignore
    @remove.register(list)  # type: ignore
    def _(self, *args: Union[tuple, list], **kwargs: Any) -> None:
        for arg in args:
            self.remove(*arg, **kwargs)

# eof
# Local Variables:
# mode: python
# mode: linum
# mode: auto-fill


for arg in args:
            if len(arg) == 2:
                self.add(*arg, **kwargs)
            else:
                LOG.error('The provided edge "%s" is of the wrong format!', arg)
                raise AttributeError

"""Edge class"""
# File      : edge.py -- Base class for an edge
# Author    : Jürgen Hackl <example@nnthink.com>
from typing import Any, Optional, Union

from singledispatchmethod import singledispatchmethod  # NOTE: not needed at 3.9

from pathpy import logger
from pathpy.core.core import PathPyObject, PathPyPath, PathPyCollection

# create logger for the Path class
LOG = logger(__name__)

class Edge(PathPyPath):
    """Base class for an edge.

    An edge is (together with nodes) one of the two basic units out of which
    networks are constructed. Each edge has two nodes to which it is attached,
    called its endpoints. Edges may be directed or undirected; undirected edges
    are also called lines and directed edges are also called arcs or arrows. In
    an undirected network, an edge may be represented as the set of its nodes,
    and in a directed network it may be represented as an ordered pair of its
    vertices.

    In ``pathpy`` the edge is referenced by its unique identifier (``uid``),
    connects two :py:class:`PathPyObjects` and can store any arbitrary python
    objects as attributes.

    .. note::

       To generate an edge, only two nodes have to be defined. ``pathpy`` will
       automatically create a unique identifier (``uid``) for the edge.

    Parameters
    ----------

    v : str or PathPyObject

        This parameter defines the source of the edge (if directed),
        i.e. v->w. Beside a py:class:`PathPyObject` object also a ``str`` node
        uid can be entered.

    w : str or PathPyObject

        This parameter defines the source of the edge (if directed),
        i.e. v->w. Beside a py:class:`PathPyObject` object also a ``str`` node
        uid can be entered.

    uid : str, optional (default = None)

        The parameter ``uid`` is the unique identifier for the edge. Every edge
        should have an uid. The uid is converted to a string value and is used
        as a key value for all dict which saving edge objects. If no edge uid
        is specified the edge ``uid`` will be defined by pathpy based on the
        internal object id.

    directed : bool, optional (default = True)

        If ``True`` the edge is directed, i.e. quantities can only transmited
        from the source node ``v`` to the traget node ``w``. If ``False`` the
        edge is undirected, i.e. quantities can be transmited in both
        directions. Per default edges in ``pathpy`` are directed.

    kwargs : Any

        Keyword arguments to store edge attributes. Attributes are added to the
        edge as ``key=value`` pairs.

    Examples
    --------
    From the ``pathpy`` import the :py:class:`Node` and :py:class:`Edge` classes.

    >>> from pathpy import Node, Edge

    Create an edge ``e`` with given nodes.

    >>> v = Node('w')
    >>> w = Node('v')
    >>> e = Edge(v, w, uid='e')
    >>> e.uid
    e

    Create an edge with given node uids and no edge uid.

    >>> ab = Edge('a', 'b', uid = 'a-b')
    >>> ab.uid
    a-b

    Show the associated node objects

    >>> ab.objects
    {'a': Node a, 'b': Node b}

    Create an edge with attached attribute.

    >>> ab = Edge('a','b', length=10)

    Add attribute to the edge.

    >>> ab['capacity'] = 5.5

    Show attached attributes

    >>> ab.attributes
    {'length': 10, 'capacity': 5}

    Change attribute.

    >>> ab['length'] = 5

    Update attributes (and add new).

    >>> ab.update(length = 2, capacity = 3, speed = 10)
    >>> ab.attributes
    {'length': 2, 'capacity': 3, 'speed': 10}

    Get the weight of the edge. Per default the attribute with the key 'weight'
    is used as weight. Should there be no such attribute, a new one will be
    crated with weight = 1.0.

    >>> ab.weight()
    1.0

    If an other attribute should be used as weight, the option weight has to be
    changed.

    >>> ab.weight('length')
    2

    If a weight is assigned but for calculation a weight of 1.0 is needed, the
    weight can be disabled with ``False`` or None.

    >>> ab['weight'] = 4
    >>> ab.weight()
    4.0
    >>> ab.weight(False)
    1.0

    Make copy of the edge.

    >>> ef = ab.copy()
    >>> ef.uid
    'a-b'

    Make a plot element and plot the edge as a png image.

    .. todo::

        Make a single plot command for plotting edges.
        The code below is not working yet!

    >>> plt = ab.plot()
    >>> plt.show('png')

    .. plot::

       import pathpy as pp
       ab = pp.Edge('a','b')
       net = pp.Network()
       net.add_edge(ab)
       plt = net.plot()
       plt.show('png')

    Create an undirected edge.

    >>> cd = Edge('c', 'd', directed=False)
    >>> plt = ab.plot()
    >>> plt.show('png')

    .. plot::

       import pathpy as pp
       cd = pp.Edge('c','d',directed=False)
       net = pp.Network(directed=False)
       net.add_edge(cd)
       plt = net.plot()
       plt.show('png')

    See Also
    --------
    Node

    """

    def __init__(self, v: Union[str, PathPyObject],
                 w: Union[str, PathPyObject],
                 uid: Optional[str] = None,
                 directed: bool = True,
                 **kwargs: Any) -> None:
        """Initialize the node object."""

        # initialize the parent class
        super().__init__(v, w, uid=uid, directed=directed, **kwargs)

    @property
    def v(self) -> str:
        """Return the uid of the source node v. """
        return self.objects[self.relations[0]]

    @property
    def w(self) -> str:
        """Return the uid of the target node w. """
        return self.objects[self.relations[-1]]

    @property
    def nodes(self) -> dict:
        """Return the nodes of the edge."""
        return self.objects

    def summary(self) -> str:
        """Returns a summary of the edge. """
        summary = [
            'Uid:\t\t{}\n'.format(self.uid),
            'Type:\t\t{}\n'.format(self.__class__.__name__),
            'Directed:\t{}\n'.format(self.directed),
            'Nodes:\t\t{}\n'.format(self.relations),
        ]

        return ''.join(summary)

class EdgeCollection(PathPyCollection):
    """A collection of edges"""
    # pylint: disable=too-many-ancestors

    def __init__(self, *args, **kwargs) -> None:
        """Initialize the EdgeCollection object."""

        # initialize the base class
        super().__init__(*args, **kwargs)

        # indicator whether the network has multi-edges
        self._multiple: bool = kwargs.pop('multiedges', False)

        # class of objects
        self._default_class: Any = Edge

    @property
    def multiedges(self) -> bool:
        """Return if edges are multiedges. """
        return self._multiple

    @singledispatchmethod
    def add(self, *args, **kwargs: Any) -> None:
        """Add multiple nodes. """
        raise NotImplementedError

    @add.register(Edge)  # type: ignore
    def _(self, *args: Edge, **kwargs: Any) -> None:
        super().add(args[0], **kwargs)

    @add.register(str)  # type: ignore
    @add.register(int)  # type: ignore
    @add.register(PathPyObject)  # type: ignore
    def _(self, *args: Union[int, str, PathPyObject], **kwargs: Any) -> None:

        # get additional parameters
        uid: Optional[str] = kwargs.pop('uid', None)
        count: int = kwargs.pop('count', 1)

        if len(args) == 2:
            obj = self._default_class(
                *args, uid=uid, directed=self.directed, **kwargs)
            super().add(obj, count=count, **kwargs)
        else:
            LOG.error('Only two objects can be linked with an edge')
            raise KeyError

    @add.register(tuple)  # type: ignore
    @add.register(list)  # type: ignore
    def _(self, *args: Union[tuple, list], **kwargs: Any) -> None:

    @singledispatchmethod
    def remove(self, *args, **kwargs):
        """Remove objects"""
        super().remove(*args, **kwargs)

    @remove.register(Edge)  # type: ignore
    def _(self, *args: Edge, **kwargs: Any) -> None:
        super().remove(*args, **kwargs)

    @remove.register(str)  # type: ignore
    @remove.register(int)  # type: ignore
    @remove.register(PathPyObject)  # type: ignore
    def _(self, *args: Union[int, str, PathPyObject], **kwargs: Any) -> None:

        # get additional parameters
        uid: Optional[str] = kwargs.pop('uid', None)

        if len(args) == 1 and args[0] in self:
            self.remove(self[args[0]])
        elif uid is not None:
            self.remove(uid)
        elif args in self and self.multiedges:
            for obj in list(self[args]):
                super().remove(obj)
        elif args in self and not self.multiedges:
            super().remove(self[args])
        else:
            LOG.warning('No edge was removed!')

    @remove.register(tuple)  # type: ignore
    @remove.register(list)  # type: ignore
    def _(self, *args: Union[tuple, list], **kwargs: Any) -> None:
        for arg in args:
            self.remove(*arg, **kwargs)

# eof
# Local Variables:
# mode: python
# mode: linum
# mode: auto-fill