From 091313ef66ae97923fd970be160e01d46649a835 Mon Sep 17 00:00:00 2001 From: Martin Skorupski Date: Sun, 12 Nov 2023 18:11:41 +0100 Subject: [PATCH] linting ... - lint py files Issue-ID: OAM-388 Change-Id: I797cf34049e4765971ee67017ee06f78fedebddf Signed-off-by: Martin Skorupski --- code/network-generator/network_generation/base.py | 2 +- code/network-generator/network_generation/cli.py | 39 +-- .../network_generation/model/python/countries.py | 2 +- .../network_generation/model/python/cube.py | 8 +- .../model/python/geo_location.py | 92 +++--- .../network_generation/model/python/hexagon.py | 357 +++------------------ .../network_generation/model/python/nr_cell_du.py | 98 +++--- .../model/python/o_ran_cloud_du.py | 66 ++-- .../network_generation/model/python/o_ran_cu.py | 67 ++-- .../network_generation/model/python/o_ran_du.py | 59 ++-- .../model/python/o_ran_near_rt_ric.py | 73 +++-- .../model/python/o_ran_network.py | 75 ++++- .../network_generation/model/python/o_ran_node.py | 172 +++++----- .../model/python/o_ran_object.py | 42 ++- .../network_generation/model/python/o_ran_ru.py | 100 +++--- .../network_generation/model/python/o_ran_smo.py | 101 +++--- .../model/python/o_ran_spiral_radius_profile.py | 100 +++--- .../model/python/o_ran_termination_point.py | 140 +++++--- .../network_generation/model/python/point.py | 2 +- .../network_generation/model/python/top.py | 136 ++++---- .../network_generation/model/python/tower.py | 96 +++--- .../model/python/type_definitions.py | 49 ++- .../network_generation/parameter_validator.py | 14 +- .../network_generation/view/network_viewer.py | 20 +- 24 files changed, 956 insertions(+), 954 deletions(-) diff --git a/code/network-generator/network_generation/base.py b/code/network-generator/network_generation/base.py index 7fe3964..bf1dab0 100644 --- a/code/network-generator/network_generation/base.py +++ b/code/network-generator/network_generation/base.py @@ -35,7 +35,7 @@ class NetworkGenerator: __configuration: dict = {} # constructor - def __init__(self, configuration: dict): + def __init__(self, configuration: dict) -> None: self.__configuration = configuration # getters diff --git a/code/network-generator/network_generation/cli.py b/code/network-generator/network_generation/cli.py index ada37e1..ad4188a 100644 --- a/code/network-generator/network_generation/cli.py +++ b/code/network-generator/network_generation/cli.py @@ -14,29 +14,21 @@ # inspired by https://github.com/rochacbruno/python-project-template -"""CLI interface for network_generation project. - -Be creative! do whatever you want! - -- Install click or typer and create a CLI app -- Use builtin argparse -- Start a web application -- Import things from your .base module -""" - - -""" -Module as entry point to generate an ietf topology json -""" import os import sys from network_generation.base import NetworkGenerator +from network_generation.model.python.o_ran_network import ORanNetwork from network_generation.parameter_validator import ParameterValidator from network_generation.view.network_viewer import NetworkViewer +""" +CLI interface for network_generation project. +Module as entry point to generate an ietf topology json +""" + -def main(): # pragma: no cover +def main() -> None: # pragma: no cover """ The main function executes on commands: `python -m network_generation`. @@ -45,10 +37,12 @@ def main(): # pragma: no cover validator: ParameterValidator = ParameterValidator(sys.argv) if validator.is_valid(): - configuration = validator.configuration() - generator = NetworkGenerator(configuration["network"]) - network = generator.generate() - viewer = NetworkViewer(network) + configuration: dict = validator.configuration() + generator: NetworkGenerator = NetworkGenerator( + configuration["network"] + ) + network: ORanNetwork = generator.generate() + viewer: NetworkViewer = NetworkViewer(network) output_folder: str = configuration["output-folder"] # If folder doesn't exist, then create it. @@ -56,20 +50,21 @@ def main(): # pragma: no cover os.makedirs(output_folder) name: str = configuration["network"]["name"] + filename: str = "" # topology json if configuration["generation-tasks"]["topology"] is True: - filename: str = output_folder + "/" + name + "-operational.json" + filename = output_folder + "/" + name + "-operational.json" viewer.json().save(filename) # svg xml if configuration["generation-tasks"]["svg"] is True: - filename: str = output_folder + "/" + name + ".svg" + filename = output_folder + "/" + name + ".svg" viewer.svg(filename) # kml xml if configuration["generation-tasks"]["kml"] is True: - filename: str = output_folder + "/" + name + ".kml" + filename = output_folder + "/" + name + ".kml" viewer.kml(filename) else: diff --git a/code/network-generator/network_generation/model/python/countries.py b/code/network-generator/network_generation/model/python/countries.py index 44e4eab..0f08909 100644 --- a/code/network-generator/network_generation/model/python/countries.py +++ b/code/network-generator/network_generation/model/python/countries.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -#!/usr/bin/python +# !/usr/bin/python """ A TypeDefinition as enum for countries diff --git a/code/network-generator/network_generation/model/python/cube.py b/code/network-generator/network_generation/model/python/cube.py index 0d4e2a3..2c5ef06 100644 --- a/code/network-generator/network_generation/model/python/cube.py +++ b/code/network-generator/network_generation/model/python/cube.py @@ -14,7 +14,7 @@ # # inspired by http://www.redblobgames.com/grids/hexagons/ -#!/usr/bin/python +# !/usr/bin/python from network_generation.model.python.hexagon import Hex @@ -35,7 +35,8 @@ class Cube: def direction(direction: int) -> Hex: if direction < 0 or direction > 5: raise ValueError( - "Invalid direction. The direction value must be in the range of [0..5]." + "Invalid direction. The direction value must be" + + " in the range of [0..5]." ) return Cube.direction_vectors()[direction] @@ -55,7 +56,8 @@ class Cube: def ring(center: Hex, radius: int) -> list[Hex]: if not (radius > 0): raise ValueError( - "Invalid radius. The radius around the hex center must be greater than 0 rings." + "Invalid radius. The radius around the hex center must" + + " be greater than 0 rings." ) results: list[Hex] = [] hex: Hex = Cube.add(center, Cube.scale(Cube.direction(4), radius)) diff --git a/code/network-generator/network_generation/model/python/geo_location.py b/code/network-generator/network_generation/model/python/geo_location.py index 3b6793a..64449f8 100644 --- a/code/network-generator/network_generation/model/python/geo_location.py +++ b/code/network-generator/network_generation/model/python/geo_location.py @@ -12,50 +12,44 @@ # See the License for the specific language governing permissions and # limitations under the License. -#!/usr/bin/python +# !/usr/bin/python """ A collection of TypeDefinitions for a geographical location """ import math +from typing import Any, TypedDict, cast + +from typing_extensions import Required from network_generation.model.python.point import Point -class IGeoLocationData: - def __init__( - self, latitude: float, longitude: float, aboveMeanSeaLevel: float - ): - self.latitude = latitude - self.longitude = longitude - self.aboveMeanSeaLevel = aboveMeanSeaLevel +class IGeoLocation(TypedDict): + latitude: Required[float] + longitude: Required[float] + aboveMeanSeaLevel: Required[float] -class IGeoLocation: - def __init__( - self, - latitude: float = 0, - longitude: float = 0, - aboveMeanSeaLevel: float = 0, - ): - self.latitude = latitude - self.longitude = longitude - self.aboveMeanSeaLevel = aboveMeanSeaLevel +default_value: IGeoLocation = { + "latitude": 0, + "longitude": 0, + "aboveMeanSeaLevel": 0, +} - def __str__(self) -> str: - return f"lat : {self.latitude} : lon : {self.longitude} : amsl : {self.aboveMeanSeaLevel}" +class GeoLocation: + @staticmethod + def default() -> dict[str, Any]: + return cast(dict[str, Any], default_value) -class GeoLocation(IGeoLocation): _equatorialRadius = 6378137 # meters _polarRadius = 6356752 # meters - def __init__(self, geoLocation: IGeoLocationData = None): - super().__init__( - geoLocation["latitude"] if geoLocation else 0, - geoLocation["longitude"] if geoLocation else 0, - geoLocation["aboveMeanSeaLevel"] if geoLocation else 0, - ) + def __init__(self, data: IGeoLocation = default_value) -> None: + self.latitude = data["latitude"] + self.longitude = data["longitude"] + self.aboveMeanSeaLevel = data["aboveMeanSeaLevel"] @property def equatorialRadius(self) -> int: @@ -65,19 +59,40 @@ class GeoLocation(IGeoLocation): def polarRadius(self) -> int: return GeoLocation._polarRadius - def set_latitude(self, value: float): + @property + def latitude(self) -> float: + return self._latitude + + @latitude.setter + def latitude(self, value: float) -> None: if not (-90 <= value <= 90): + msg: str = "Invalid latitude. Latitude must be between -90 and 90." + raise ValueError(msg) + self._latitude = value + + @property + def longitude(self) -> float: + return self._longitude + + @longitude.setter + def longitude(self, value: float) -> None: + if not (-180 <= value <= 180): raise ValueError( - "Invalid latitude. Latitude must be between -90 and 90." + "Invalid longitude. Longitude must be between -180 and 180." ) - self.latitude = value + self._longitude = value + + @property + def aboveMeanSeaLevel(self) -> float: + return self._aboveMeanSeaLevel - def set_longitude(self, value: float): + @aboveMeanSeaLevel.setter + def aboveMeanSeaLevel(self, value: float) -> None: if not (-180 <= value <= 180): raise ValueError( "Invalid longitude. Longitude must be between -180 and 180." ) - self.longitude = value + self._aboveMeanSeaLevel = value def json(self) -> dict[str, float]: return { @@ -89,22 +104,21 @@ class GeoLocation(IGeoLocation): def __str__(self) -> str: return str(self.json()) - def point_to_geo_location(self, point: Point): + def point_to_geo_location(self, point: Point) -> Any: """ - A static function which converts a point in pixels into a geographical location - when the self is represented as Point(0,0) + A static function which converts a point in pixels into a geographical + location when the self is represented as Point(0,0) @param point : The point to be converted returns The converted GeoLocation object. """ - equatorialRadius = 6378137 # meters - new_lat = self.latitude + (point.y / equatorialRadius) * ( + new_lat = self.latitude + (point.y / self.equatorialRadius) * ( 180 / math.pi ) - new_lon = self.longitude + (point.x / equatorialRadius) * ( + new_lon = self.longitude + (point.x / self.equatorialRadius) * ( 180 / math.pi ) / math.cos(self.latitude * math.pi / 180) - geo_location: IGeoLocationData = { + geo_location: IGeoLocation = { "longitude": new_lon, "latitude": new_lat, "aboveMeanSeaLevel": self.aboveMeanSeaLevel, diff --git a/code/network-generator/network_generation/model/python/hexagon.py b/code/network-generator/network_generation/model/python/hexagon.py index 5a81c23..555c601 100644 --- a/code/network-generator/network_generation/model/python/hexagon.py +++ b/code/network-generator/network_generation/model/python/hexagon.py @@ -14,7 +14,7 @@ # # inspired by http://www.redblobgames.com/grids/hexagons/ -#!/usr/bin/python +# !/usr/bin/python from __future__ import division, print_function @@ -27,7 +27,7 @@ from network_generation.model.python.point import Point class Hex: - def __init__(self, q: int, r: int, s: int): + def __init__(self, q: float, r: float, s: float) -> None: if round(q + r + s) != 0: raise ValueError("The sum of q, r, and s must be 0.") self.q = q @@ -50,11 +50,11 @@ def hex_scale(a: Hex, k: int) -> Hex: return Hex(a.q * k, a.r * k, a.s * k) -def hex_rotate_left(a) -> Hex: +def hex_rotate_left(a: Hex) -> Hex: return Hex(-a.s, -a.q, -a.r) -def hex_rotate_right(a) -> Hex: +def hex_rotate_right(a: Hex) -> Hex: return Hex(-a.r, -a.s, -a.q) @@ -90,18 +90,18 @@ def hex_diagonal_neighbor(hex: Hex, direction: int) -> Hex: return hex_add(hex, hex_diagonals[direction]) -def hex_length(hex: Hex) -> int: +def hex_length(hex: Hex) -> float: return (abs(hex.q) + abs(hex.r) + abs(hex.s)) // 2 -def hex_distance(a: Hex, b: Hex) -> int: +def hex_distance(a: Hex, b: Hex) -> float: return hex_length(hex_subtract(a, b)) def hex_round(hex: Hex) -> Hex: - qi = int(round(hex.q)) - ri = int(round(hex.r)) - si = int(round(hex.s)) + qi = round(hex.q) + ri = round(hex.r) + si = round(hex.s) q_diff = abs(qi - hex.q) r_diff = abs(ri - hex.r) s_diff = abs(si - hex.s) @@ -115,21 +115,21 @@ def hex_round(hex: Hex) -> Hex: return Hex(qi, ri, si) -def hex_lerp(a: Hex, b: Hex, t: int) -> Hex: # linearly interpolation +def hex_lerp(a: Hex, b: Hex, t: float) -> Hex: # linearly interpolation return Hex( - a.q * (1.0 - t) + b.q * t, - a.r * (1.0 - t) + b.r * t, - a.s * (1.0 - t) + b.s * t, + a.q * (1 - t) + b.q * t, + a.r * (1 - t) + b.r * t, + a.s * (1 - t) + b.s * t, ) -def hex_linedraw(a: Hex, b: Hex) -> list[hex]: - N = hex_distance(a, b) - a_nudge = Hex(a.q + 1e-06, a.r + 1e-06, a.s - 2e-06) - b_nudge = Hex(b.q + 1e-06, b.r + 1e-06, b.s - 2e-06) - results: list[hex] = [] - step = 1.0 / max(N, 1) - for i in range(0, N + 1): +def hex_linedraw(a: Hex, b: Hex) -> list[Hex]: + N: float = hex_distance(a, b) + a_nudge: Hex = Hex(a.q + 1e-06, a.r + 1e-06, a.s - 2e-06) + b_nudge: Hex = Hex(b.q + 1e-06, b.r + 1e-06, b.s - 2e-06) + results: list[Hex] = [] + step: float = 1 / max(N, 1) + for i in range(0, int(N) + 1): results.append(hex_round(hex_lerp(a_nudge, b_nudge, step * i))) return results @@ -140,33 +140,36 @@ EVEN: int = 1 ODD: int = -1 -def qoffset_from_cube(offset: int, hex: Hex) -> OffsetCoord: +def qoffset_from_cube(offset: float, hex: Hex) -> OffsetCoord: col = hex.q - row = hex.r + (hex.q + offset * (hex.q & 1)) // 2 + row = hex.r + (hex.q + offset * (int(hex.q) & 1)) // 2 if offset != EVEN and offset != ODD: raise ValueError("offset must be EVEN (+1) or ODD (-1)") return OffsetCoord(col, row) -def qoffset_to_cube(offset: int, hex: Hex) -> Hex: - q = hex.col - r = hex.row - (hex.col + offset * (hex.col & 1)) // 2 +def qoffset_to_cube(offset: int, offsetCoord: OffsetCoord) -> Hex: + q = offsetCoord.col + r = ( + offsetCoord.row + - (offsetCoord.col + offset * (offsetCoord.col & 1)) // 2 + ) s = -q - r if offset != EVEN and offset != ODD: raise ValueError("offset must be EVEN (+1) or ODD (-1)") return Hex(q, r, s) -def roffset_from_cube(offset: int, hex: Hex) -> OffsetCoord: - col = hex.q + (hex.r + offset * (hex.r & 1)) // 2 +def roffset_from_cube(offset: float, hex: Hex) -> OffsetCoord: + col = hex.q + (hex.r + offset * (int(hex.r) & 1)) // 2 row = hex.r if offset != EVEN and offset != ODD: raise ValueError("offset must be EVEN (+1) or ODD (-1)") return OffsetCoord(col, row) -def roffset_to_cube(offset: int, hex: Hex) -> Hex: - q = hex.col - (hex.row + offset * (hex.row & 1)) // 2 +def roffset_to_cube(offset: float, hex: OffsetCoord) -> Hex: + q = hex.col - (hex.row + offset * (int(hex.row) & 1)) // 2 r = hex.row s = -q - r if offset != EVEN and offset != ODD: @@ -177,15 +180,15 @@ def roffset_to_cube(offset: int, hex: Hex) -> Hex: DoubledCoord = collections.namedtuple("DoubledCoord", ["col", "row"]) -def qdoubled_from_cube(hex: Hex): +def qdoubled_from_cube(hex: Hex) -> DoubledCoord: col = hex.q row = 2 * hex.r + hex.q return DoubledCoord(col, row) -def qdoubled_to_cube(hex: Hex) -> Hex: - q = hex.col - r = (hex.row - hex.col) // 2 +def qdoubled_to_cube(doubledCoord: DoubledCoord) -> Hex: + q = doubledCoord.col + r = (doubledCoord.row - doubledCoord.col) // 2 s = -q - r return Hex(q, r, s) @@ -196,9 +199,9 @@ def rdoubled_from_cube(hex: Hex) -> DoubledCoord: return DoubledCoord(col, row) -def rdoubled_to_cube(hex: Hex): - q = (hex.col - hex.row) // 2 - r = hex.row +def rdoubled_to_cube(doubledCoord: DoubledCoord) -> Hex: + q = (doubledCoord.col - doubledCoord.row) // 2 + r = doubledCoord.row s = -q - r return Hex(q, r, s) @@ -279,284 +282,4 @@ def hex_to_geo_location( layout: Layout, hex: Hex, reference: GeoLocation ) -> GeoLocation: hexPoint: Point = hex_to_pixel(layout, hex) - return GeoLocation(reference).point_to_geo_location(hexPoint) - - -# Tests - - -def complain(name): - print("FAIL {0}".format(name)) - - -def equal_hex(name, a, b): - if not (a.q == b.q and a.s == b.s and a.r == b.r): - complain(name) - - -def equal_offsetcoord(name, a, b): - if not (a.col == b.col and a.row == b.row): - complain(name) - - -def equal_doubledcoord(name, a, b): - if not (a.col == b.col and a.row == b.row): - complain(name) - - -def equal_int(name, a, b): - if not (a == b): - complain(name) - - -def equal_hex_array(name, a, b): - equal_int(name, len(a), len(b)) - for i in range(0, len(a)): - equal_hex(name, a[i], b[i]) - - -def test_hex_arithmetic(): - equal_hex("hex_add", Hex(4, -10, 6), hex_add(Hex(1, -3, 2), Hex(3, -7, 4))) - equal_hex( - "hex_subtract", - Hex(-2, 4, -2), - hex_subtract(Hex(1, -3, 2), Hex(3, -7, 4)), - ) - - -def test_hex_direction(): - equal_hex("hex_direction", Hex(0, -1, 1), hex_direction(2)) - - -def test_hex_neighbor(): - equal_hex("hex_neighbor", Hex(1, -3, 2), hex_neighbor(Hex(1, -2, 1), 2)) - - -def test_hex_diagonal(): - equal_hex( - "hex_diagonal", Hex(-1, -1, 2), hex_diagonal_neighbor(Hex(1, -2, 1), 3) - ) - - -def test_hex_distance(): - equal_int("hex_distance", 7, hex_distance(Hex(3, -7, 4), Hex(0, 0, 0))) - - -def test_hex_rotate_right(): - equal_hex( - "hex_rotate_right", hex_rotate_right(Hex(1, -3, 2)), Hex(3, -2, -1) - ) - - -def test_hex_rotate_left(): - equal_hex( - "hex_rotate_left", hex_rotate_left(Hex(1, -3, 2)), Hex(-2, -1, 3) - ) - - -def test_hex_round(): - a = Hex(0.0, 0.0, 0.0) - b = Hex(1.0, -1.0, 0.0) - c = Hex(0.0, -1.0, 1.0) - equal_hex( - "hex_round 1", - Hex(5, -10, 5), - hex_round(hex_lerp(Hex(0.0, 0.0, 0.0), Hex(10.0, -20.0, 10.0), 0.5)), - ) - equal_hex("hex_round 2", hex_round(a), hex_round(hex_lerp(a, b, 0.499))) - equal_hex("hex_round 3", hex_round(b), hex_round(hex_lerp(a, b, 0.501))) - equal_hex( - "hex_round 4", - hex_round(a), - hex_round( - Hex( - a.q * 0.4 + b.q * 0.3 + c.q * 0.3, - a.r * 0.4 + b.r * 0.3 + c.r * 0.3, - a.s * 0.4 + b.s * 0.3 + c.s * 0.3, - ) - ), - ) - equal_hex( - "hex_round 5", - hex_round(c), - hex_round( - Hex( - a.q * 0.3 + b.q * 0.3 + c.q * 0.4, - a.r * 0.3 + b.r * 0.3 + c.r * 0.4, - a.s * 0.3 + b.s * 0.3 + c.s * 0.4, - ) - ), - ) - - -def test_hex_linedraw(): - equal_hex_array( - "hex_linedraw", - [ - Hex(0, 0, 0), - Hex(0, -1, 1), - Hex(0, -2, 2), - Hex(1, -3, 2), - Hex(1, -4, 3), - Hex(1, -5, 4), - ], - hex_linedraw(Hex(0, 0, 0), Hex(1, -5, 4)), - ) - - -def test_layout(): - h = Hex(3, 4, -7) - flat = Layout(layout_flat, Point(10.0, 15.0), Point(35.0, 71.0)) - equal_hex( - "layout", h, hex_round(pixel_to_hex(flat, hex_to_pixel(flat, h))) - ) - pointy = Layout(layout_pointy, Point(10.0, 15.0), Point(35.0, 71.0)) - equal_hex( - "layout", h, hex_round(pixel_to_hex(pointy, hex_to_pixel(pointy, h))) - ) - - -def test_offset_roundtrip(): - a = Hex(3, 4, -7) - b = OffsetCoord(1, -3) - equal_hex( - "conversion_roundtrip even-q", - a, - qoffset_to_cube(EVEN, qoffset_from_cube(EVEN, a)), - ) - equal_offsetcoord( - "conversion_roundtrip even-q", - b, - qoffset_from_cube(EVEN, qoffset_to_cube(EVEN, b)), - ) - equal_hex( - "conversion_roundtrip odd-q", - a, - qoffset_to_cube(ODD, qoffset_from_cube(ODD, a)), - ) - equal_offsetcoord( - "conversion_roundtrip odd-q", - b, - qoffset_from_cube(ODD, qoffset_to_cube(ODD, b)), - ) - equal_hex( - "conversion_roundtrip even-r", - a, - roffset_to_cube(EVEN, roffset_from_cube(EVEN, a)), - ) - equal_offsetcoord( - "conversion_roundtrip even-r", - b, - roffset_from_cube(EVEN, roffset_to_cube(EVEN, b)), - ) - equal_hex( - "conversion_roundtrip odd-r", - a, - roffset_to_cube(ODD, roffset_from_cube(ODD, a)), - ) - equal_offsetcoord( - "conversion_roundtrip odd-r", - b, - roffset_from_cube(ODD, roffset_to_cube(ODD, b)), - ) - - -def test_offset_from_cube(): - equal_offsetcoord( - "offset_from_cube even-q", - OffsetCoord(1, 3), - qoffset_from_cube(EVEN, Hex(1, 2, -3)), - ) - equal_offsetcoord( - "offset_from_cube odd-q", - OffsetCoord(1, 2), - qoffset_from_cube(ODD, Hex(1, 2, -3)), - ) - - -def test_offset_to_cube(): - equal_hex( - "offset_to_cube even-", - Hex(1, 2, -3), - qoffset_to_cube(EVEN, OffsetCoord(1, 3)), - ) - equal_hex( - "offset_to_cube odd-q", - Hex(1, 2, -3), - qoffset_to_cube(ODD, OffsetCoord(1, 2)), - ) - - -def test_doubled_roundtrip(): - a = Hex(3, 4, -7) - b = DoubledCoord(1, -3) - equal_hex( - "conversion_roundtrip doubled-q", - a, - qdoubled_to_cube(qdoubled_from_cube(a)), - ) - equal_doubledcoord( - "conversion_roundtrip doubled-q", - b, - qdoubled_from_cube(qdoubled_to_cube(b)), - ) - equal_hex( - "conversion_roundtrip doubled-r", - a, - rdoubled_to_cube(rdoubled_from_cube(a)), - ) - equal_doubledcoord( - "conversion_roundtrip doubled-r", - b, - rdoubled_from_cube(rdoubled_to_cube(b)), - ) - - -def test_doubled_from_cube(): - equal_doubledcoord( - "doubled_from_cube doubled-q", - DoubledCoord(1, 5), - qdoubled_from_cube(Hex(1, 2, -3)), - ) - equal_doubledcoord( - "doubled_from_cube doubled-r", - DoubledCoord(4, 2), - rdoubled_from_cube(Hex(1, 2, -3)), - ) - - -def test_doubled_to_cube(): - equal_hex( - "doubled_to_cube doubled-q", - Hex(1, 2, -3), - qdoubled_to_cube(DoubledCoord(1, 5)), - ) - equal_hex( - "doubled_to_cube doubled-r", - Hex(1, 2, -3), - rdoubled_to_cube(DoubledCoord(4, 2)), - ) - - -def test_all(): - test_hex_arithmetic() - test_hex_direction() - test_hex_neighbor() - test_hex_diagonal() - test_hex_distance() - test_hex_rotate_right() - test_hex_rotate_left() - test_hex_round() - test_hex_linedraw() - test_layout() - test_offset_roundtrip() - test_offset_from_cube() - test_offset_to_cube() - test_doubled_roundtrip() - test_doubled_from_cube() - test_doubled_to_cube() - print("test finished") - - -if __name__ == "__main__": - test_all() + return reference.point_to_geo_location(hexPoint) diff --git a/code/network-generator/network_generation/model/python/nr_cell_du.py b/code/network-generator/network_generation/model/python/nr_cell_du.py index a0fb6e8..40fa66b 100644 --- a/code/network-generator/network_generation/model/python/nr_cell_du.py +++ b/code/network-generator/network_generation/model/python/nr_cell_du.py @@ -12,18 +12,20 @@ # See the License for the specific language governing permissions and # limitations under the License. -#!/usr/bin/python +# !/usr/bin/python """ A Class representing a 3GPP new radio cell du (NrCellDu) """ import xml.etree.ElementTree as ET -from typing import overload +from typing import Any, cast import network_generation.model.python.hexagon as Hexagon -from network_generation.model.python.geo_location import GeoLocation -from network_generation.model.python.o_ran_node import ORanNode -from network_generation.model.python.o_ran_object import IORanObject +from network_generation.model.python.geo_location import ( + GeoLocation, + IGeoLocation, +) +from network_generation.model.python.o_ran_node import IORanNode, ORanNode from network_generation.model.python.o_ran_termination_point import ( ORanTerminationPoint, ) @@ -31,42 +33,54 @@ from network_generation.model.python.point import Point # Define the "INrCellDu" interface -class INrCellDu(IORanObject): - def __init__(self, cell_angel: int, azimuth: int, **kwargs): - super().__init__(**kwargs) - self._cell_angle = cell_angel - self._azimuth = azimuth +class INrCellDu(IORanNode): + cell_angle: int + azimuth: int + + +default_value: INrCellDu = cast( + INrCellDu, + { + **ORanNode.default(), + **{"cellAngle": 120, "azimuth": 120}, + }, +) # Define an abstract O-RAN Node class -class NrCellDu(ORanNode, INrCellDu): - def __init__(self, cell_data: INrCellDu = None, **kwargs): - super().__init__(cell_data, **kwargs) - self._cell_angle = ( - cell_data["cellAngle"] - if cell_data and "cellAngle" in cell_data - else 120 - ) - self._azimuth = ( - cell_data["azimuth"] if cell_data and "azimuth" in cell_data else 0 - ) +class NrCellDu(ORanNode): + def __init__( + self, + data: dict[str, Any] = cast(dict[str, Any], default_value), + **kwargs: dict[str, Any] + ) -> None: + cell_data: INrCellDu = self._to_cell_data(data) + super().__init__(cast(dict[str, Any], cell_data), **kwargs) + self._cell_angle: int = int(str(cell_data["cellAngle"])) + self._azimuth: int = int(str(cell_data["azimuth"])) + + def _to_cell_data(self, data: dict[str, Any]) -> INrCellDu: + result: INrCellDu = default_value + for key, key_type in INrCellDu.__annotations__.items(): + if key in data: + result[key] = data[key] # type: ignore + return result - @property def termination_points(self) -> list[ORanTerminationPoint]: - result: list[ORanTerminationPoint] = super().termination_points + result: list[ORanTerminationPoint] = super().termination_points() result.append( ORanTerminationPoint({"id": self.name, "name": self.name}) ) return result - def to_topology_nodes(self) -> list[dict[str, dict]]: + def to_topology_nodes(self) -> list[dict[str, Any]]: # a cell is not a node it is a Termination Point - result: list[dict[str, dict]] = [] # super().to_topology_nodes() + result: list[dict[str, Any]] = [] # super().to_topology_nodes() return result - def to_topology_links(self) -> list[dict[str, dict]]: + def to_topology_links(self) -> list[dict[str, Any]]: # as a cell is not a node, it does not have links - result: list[dict[str, dict]] = [] # super().to_topology_links() + result: list[dict[str, Any]] = [] # super().to_topology_links() return result def toKml(self) -> ET.Element: @@ -84,15 +98,16 @@ class NrCellDu(ORanNode, INrCellDu): points: list[Point] = Hexagon.polygon_corners( self.layout, self.position ) - method = GeoLocation( - self.parent.parent.parent.parent.parent.parent.geoLocation - ).point_to_geo_location + method = ( + self.parent.parent.parent.parent.parent.parent + .geo_location.point_to_geo_location + ) geo_locations: list[GeoLocation] = list(map(method, points)) text: list[str] = [] index: int = 1 + int(self._azimuth / self._cell_angle) - network_center: GeoLocation = GeoLocation( - self.parent.parent.parent.parent.parent.parent.geoLocation + network_center: GeoLocation = ( + self.parent.parent.parent.parent.parent.parent.geo_location ) intersect1: Point = Point( @@ -115,7 +130,8 @@ class NrCellDu(ORanNode, INrCellDu): network_center.point_to_geo_location(intersect2) ) - tower: GeoLocation = GeoLocation(self.geoLocation) + tower: GeoLocation = GeoLocation(cast(IGeoLocation, self.geo_location)) + # TODO: Why a cast is required cell_polygon: list[GeoLocation] = [] cell_polygon.append(tower) @@ -126,13 +142,17 @@ class NrCellDu(ORanNode, INrCellDu): # close polygon cell_polygon.append(tower) - for geo_location in cell_polygon: - text.append( - f"{'%.6f' % geo_location.longitude},{'%.6f' % geo_location.latitude},{'%.6f' % geo_location.aboveMeanSeaLevel}" - ) + for gl in cell_polygon: + index += 1 + strs: list[str] = [ + str("%.6f" % float(gl.longitude)), + str("%.6f" % float(gl.latitude)), + str("%.6f" % float(gl.aboveMeanSeaLevel)), + ] + text.append(",".join(strs)) coordinates.text = " ".join(text) return placemark - def toSvg(self) -> None: - return None + def toSvg(self) -> ET.Element: + return ET.Element("to-be-implemented") diff --git a/code/network-generator/network_generation/model/python/o_ran_cloud_du.py b/code/network-generator/network_generation/model/python/o_ran_cloud_du.py index 0c5b782..b97f97e 100644 --- a/code/network-generator/network_generation/model/python/o_ran_cloud_du.py +++ b/code/network-generator/network_generation/model/python/o_ran_cloud_du.py @@ -12,43 +12,59 @@ # See the License for the specific language governing permissions and # limitations under the License. -#!/usr/bin/python +# !/usr/bin/python """ -A Class representing an O-RAN O-Cloud resource pool for O-RAN distributed units (ORanDu) +A Class representing an O-RAN O-Cloud resource pool for O-RAN distributed units +(ORanDu). By default all O-RAN-DUs associated with the towers around are deployed here. -Maybe dedicated hardware is required to host O-DUs, but it is expected +Maybe dedicated hardware is required to host O-DUs, but it is expected that the O-Cloud mechanism and concepts can be applied here. """ import xml.etree.ElementTree as ET -from typing import overload +from typing import Any, cast import network_generation.model.python.hexagon as Hexagon from network_generation.model.python.cube import Cube +from network_generation.model.python.geo_location import GeoLocation from network_generation.model.python.hexagon import Hex -from network_generation.model.python.o_ran_node import ORanNode -from network_generation.model.python.o_ran_object import IORanObject +from network_generation.model.python.o_ran_node import ( + IORanNode, + ORanNode, + default_value, +) from network_generation.model.python.o_ran_termination_point import ( ORanTerminationPoint, ) from network_generation.model.python.tower import Tower - # Define the "IORanDu" interface -class IORanCloudDu(IORanObject): - def __init__(self, **kwargs): - super().__init__(**kwargs) +IORanCloudDu = IORanNode # Implements a concrete O-RAN Node class -class ORanCloudDu(ORanNode, IORanCloudDu): - def __init__(self, o_ran_du_data: IORanCloudDu = None, **kwargs): - super().__init__(o_ran_du_data, **kwargs) +class ORanCloudDu(ORanNode): + def __init__( + self, + data: dict[str, Any] = cast(dict[str, Any], default_value), + **kwargs: dict[str, Any] + ) -> None: + o_ran_cloud_du_data: IORanCloudDu = self._to_o_ran_cloud_du_data(data) + + super().__init__(cast(dict[str, Any], o_ran_cloud_du_data), **kwargs) self._towers: list[Tower] = self._calculate_towers() + def _to_o_ran_cloud_du_data(self, data: dict[str, Any]) -> IORanCloudDu: + result: IORanCloudDu = default_value + for key, key_type in IORanCloudDu.__annotations__.items(): + if key in data: + result[key] = data[key] # type: ignore + return result + def _calculate_towers(self) -> list[Tower]: hex_ring_radius: int = ( - self.spiralRadiusProfile.oRanDuSpiralRadiusOfTowers + self.parent.parent.parent.parent + .spiral_radius_profile.oRanDuSpiralRadiusOfTowers ) hex_list: list[Hex] = Cube.spiral(self.position, hex_ring_radius) result: list[Tower] = [] @@ -57,10 +73,12 @@ class ORanCloudDu(ORanNode, IORanCloudDu): name: str = "-".join( [ self.name.replace("O-Cloud-DU", "Tower"), - s[len(s) - 2 : len(s)], + s[len(s) - 2: len(s)], ] ) - network_center: dict = self.parent.parent.parent.parent.center + network_center: GeoLocation = ( + self.parent.parent.parent.parent.center + ) newGeo = Hexagon.hex_to_geo_location( self.layout, hex, network_center ).json() @@ -71,7 +89,6 @@ class ORanCloudDu(ORanNode, IORanCloudDu): "geoLocation": newGeo, "position": hex, "layout": self.layout, - "spiralRadiusProfile": self.spiralRadiusProfile, "parent": self, } ) @@ -82,9 +99,8 @@ class ORanCloudDu(ORanNode, IORanCloudDu): def towers(self) -> list[Tower]: return self._towers - @property def termination_points(self) -> list[ORanTerminationPoint]: - result: list[ORanTerminationPoint] = super().termination_points + result: list[ORanTerminationPoint] = super().termination_points() phy_tp: str = "-".join([self.name, "phy".upper()]) result.append(ORanTerminationPoint({"id": phy_tp, "name": phy_tp})) for interface in ["o2"]: @@ -96,14 +112,14 @@ class ORanCloudDu(ORanNode, IORanCloudDu): ) return result - def to_topology_nodes(self) -> list[dict[str, dict]]: - result: list[dict[str, dict]] = super().to_topology_nodes() + def to_topology_nodes(self) -> list[dict[str, Any]]: + result: list[dict[str, Any]] = super().to_topology_nodes() for tower in self.towers: result.extend(tower.to_topology_nodes()) return result - def to_topology_links(self) -> list[dict[str, dict]]: - result: list[dict[str, dict]] = super().to_topology_links() + def to_topology_links(self) -> list[dict[str, Any]]: + result: list[dict[str, Any]] = super().to_topology_links() for tower in self.towers: result.extend(tower.to_topology_links()) return result @@ -118,5 +134,5 @@ class ORanCloudDu(ORanNode, IORanCloudDu): o_ran_cloud_du.append(tower.toKml()) return o_ran_cloud_du - def toSvg(self) -> None: - return None + def toSvg(self) -> ET.Element: + return ET.Element("to-be-implemented") diff --git a/code/network-generator/network_generation/model/python/o_ran_cu.py b/code/network-generator/network_generation/model/python/o_ran_cu.py index d3034b4..be0006d 100644 --- a/code/network-generator/network_generation/model/python/o_ran_cu.py +++ b/code/network-generator/network_generation/model/python/o_ran_cu.py @@ -12,53 +12,68 @@ # See the License for the specific language governing permissions and # limitations under the License. -#!/usr/bin/python +# !/usr/bin/python """ A Class representing an O-RAN centralized unit (ORanCu) and at the same time a location for an O-Cloud resource pool """ import xml.etree.ElementTree as ET -from typing import overload +from typing import Any, cast import network_generation.model.python.hexagon as Hexagon -from network_generation.model.python.cube import Cube +from network_generation.model.python.geo_location import GeoLocation from network_generation.model.python.hexagon import Hex from network_generation.model.python.o_ran_cloud_du import ORanCloudDu -from network_generation.model.python.o_ran_node import ORanNode -from network_generation.model.python.o_ran_object import IORanObject +from network_generation.model.python.o_ran_node import ( + IORanNode, + ORanNode, + default_value, +) from network_generation.model.python.o_ran_termination_point import ( ORanTerminationPoint, ) from network_generation.model.python.tower import Tower - # Define the "IORanCu" interface -class IORanCu(IORanObject): - def __init__(self, **kwargs): - super().__init__(**kwargs) +IORanCu = IORanNode # Define an abstract O-RAN Node class -class ORanCu(ORanNode, IORanCu): - def __init__(self, o_ran_cu_data: IORanCu = None, **kwargs): - super().__init__(o_ran_cu_data, **kwargs) - self._o_ran_cloud_dus: list[ORanCu] = self._calculate_o_ran_dus() +class ORanCu(ORanNode): + def __init__( + self, + data: dict[str, Any] = cast(dict[str, Any], default_value), + **kwargs: dict[str, Any] + ) -> None: + o_ran_cu_data: IORanCu = self._to_o_ran_cu_data(data) + super().__init__(cast(dict[str, Any], o_ran_cu_data), **kwargs) + self._o_ran_cloud_dus: list[ORanCloudDu] = self._calculate_o_ran_dus() + + def _to_o_ran_cu_data(self, data: dict[str, Any]) -> IORanCu: + result: IORanCu = default_value + for key, key_type in IORanCu.__annotations__.items(): + if key in data: + result[key] = data[key] # type: ignore + return result def _calculate_o_ran_dus(self) -> list[ORanCloudDu]: hex_ring_radius: int = ( - self.spiralRadiusProfile.oRanCuSpiralRadiusOfODus + self.parent.parent.parent + .spiral_radius_profile.oRanCuSpiralRadiusOfODus ) - hex_list: list[Hex] = self.spiralRadiusProfile.oRanDuSpiral( + hex_list: list[ + Hex + ] = self.parent.parent.parent.spiral_radius_profile.oRanDuSpiral( self.position, hex_ring_radius ) result: list[ORanCloudDu] = [] for index, hex in enumerate(hex_list): s: str = "00" + str(index) name: str = "-".join( - [self.name.replace("CU", "O-Cloud-DU"), s[len(s) - 2 : len(s)]] + [self.name.replace("CU", "O-Cloud-DU"), s[len(s) - 2: len(s)]] ) - network_center: dict = self.parent.parent.parent.center + network_center: GeoLocation = self.parent.parent.parent.center newGeo = Hexagon.hex_to_geo_location( self.layout, hex, network_center ).json() @@ -69,7 +84,6 @@ class ORanCu(ORanNode, IORanCu): "geoLocation": newGeo, "position": hex, "layout": self.layout, - "spiralRadiusProfile": self.spiralRadiusProfile, "parent": self, } ) @@ -88,11 +102,10 @@ class ORanCu(ORanNode, IORanCu): result.append(tower) return result - @property def termination_points(self) -> list[ORanTerminationPoint]: - result: list[ORanTerminationPoint] = super().termination_points + result: list[ORanTerminationPoint] = super().termination_points() phy_tp: str = "-".join([self.name, "phy".upper()]) - result.append({"tp-id": phy_tp, "name": phy_tp}) + result.append(ORanTerminationPoint({"tp-id": phy_tp, "name": phy_tp})) for interface in ["e2", "o1"]: id: str = "-".join([self.name, interface.upper()]) result.append( @@ -102,16 +115,16 @@ class ORanCu(ORanNode, IORanCu): ) return result - def to_topology_nodes(self) -> list[dict[str, dict]]: - result: list[dict[str, dict]] = super().to_topology_nodes() + def to_topology_nodes(self) -> list[dict[str, Any]]: + result: list[dict[str, Any]] = super().to_topology_nodes() # for o_ran_du in self.o_ran_dus: # TODO # result.extend(o_ran_du.to_topology_nodes()) for o_ran_cloud_du in self.o_ran_cloud_dus: result.extend(o_ran_cloud_du.to_topology_nodes()) return result - def to_topology_links(self) -> list[dict[str, dict]]: - result: list[dict[str, dict]] = super().to_topology_links() + def to_topology_links(self) -> list[dict[str, Any]]: + result: list[dict[str, Any]] = super().to_topology_links() # for o_ran_du in self.o_ran_dus: # result.extend(o_ran_du.to_topology_links()) for o_ran_cloud_du in self.o_ran_cloud_dus: @@ -128,5 +141,5 @@ class ORanCu(ORanNode, IORanCu): o_ran_cu.append(o_ran_cloud_du.toKml()) return o_ran_cu - def toSvg(self) -> None: - return None + def toSvg(self) -> ET.Element: + return ET.Element("to-be-implemented") diff --git a/code/network-generator/network_generation/model/python/o_ran_du.py b/code/network-generator/network_generation/model/python/o_ran_du.py index 6e5149b..0392d2c 100644 --- a/code/network-generator/network_generation/model/python/o_ran_du.py +++ b/code/network-generator/network_generation/model/python/o_ran_du.py @@ -12,41 +12,58 @@ # See the License for the specific language governing permissions and # limitations under the License. -#!/usr/bin/python +# !/usr/bin/python """ A Class representing an O-RAN distributed unit (ORanDu) """ import xml.etree.ElementTree as ET -from typing import overload +from typing import Any, cast -from network_generation.model.python.o_ran_node import ORanNode -from network_generation.model.python.o_ran_object import IORanObject +from network_generation.model.python.o_ran_node import IORanNode, ORanNode from network_generation.model.python.o_ran_termination_point import ( ORanTerminationPoint, ) # Define the "IORanDu" interface -class IORanDu(IORanObject): - def __init__(self, o_ran_ru_count: int, **kwargs): - super().__init__(**kwargs) - self._o_ran_ru_count = o_ran_ru_count +class IORanDu(IORanNode): + o_ran_ru_count: int + + +default_value: IORanDu = cast( + IORanDu, + { + **ORanNode.default(), + **{"oRanRuCount": 1}, + }, +) # Define an abstract O-RAN Node class -class ORanDu(ORanNode, IORanDu): - def __init__(self, o_ran_du_data: IORanDu = None, **kwargs): - super().__init__(o_ran_du_data, **kwargs) +class ORanDu(ORanNode): + def __init__( + self, + data: dict[str, Any] = cast(dict[str, Any], default_value), + **kwargs: dict[str, Any] + ) -> None: + o_ran_du_data: IORanDu = self._to_o_ran_du_data(data) + super().__init__(cast(dict[str, Any], o_ran_du_data), **kwargs) self._o_ran_ru_count = ( o_ran_du_data["oRanRuCount"] if o_ran_du_data and "oRanRuCount" in o_ran_du_data else 1 ) - @property + def _to_o_ran_du_data(self, data: dict[str, Any]) -> IORanDu: + result: IORanDu = default_value + for key, key_type in IORanDu.__annotations__.items(): + if key in data: + result[key] = data[key] # type: ignore + return result + def termination_points(self) -> list[ORanTerminationPoint]: - result: list[ORanTerminationPoint] = super().termination_points + result: list[ORanTerminationPoint] = super().termination_points() phy_tp: str = "-".join([self.name, "phy".upper()]) result.append(ORanTerminationPoint({"id": phy_tp, "name": phy_tp})) for interface in ["e2", "o1", "ofhm", "ofhc", "ofhu", "ofhs"]: @@ -58,12 +75,12 @@ class ORanDu(ORanNode, IORanDu): ) return result - def to_topology_nodes(self) -> list[dict[str, dict]]: - result: list[dict[str, dict]] = super().to_topology_nodes() + def to_topology_nodes(self) -> list[dict[str, Any]]: + result: list[dict[str, Any]] = super().to_topology_nodes() return result - def to_topology_links(self) -> list[dict[str, dict]]: - result: list[dict[str, dict]] = super().to_topology_links() + def to_topology_links(self) -> list[dict[str, Any]]: + result: list[dict[str, Any]] = super().to_topology_links() for interface in ["e2", "o1"]: link_id: str = "".join( [interface, ":", self.name, "<->", self.parent.name] @@ -91,9 +108,9 @@ class ORanDu(ORanNode, IORanDu): open.text = "1" name: ET.Element = ET.SubElement(o_ran_du, "name") name.text = self.name - for tower in self.towers: - o_ran_du.append(tower.toKml()) + # for tower in self.towers: + # o_ran_du.append(tower.toKml()) return o_ran_du - def toSvg(self) -> None: - return None + def toSvg(self) -> ET.Element: + return ET.Element("to-be-implemented") diff --git a/code/network-generator/network_generation/model/python/o_ran_near_rt_ric.py b/code/network-generator/network_generation/model/python/o_ran_near_rt_ric.py index 4e92a5b..5a44843 100644 --- a/code/network-generator/network_generation/model/python/o_ran_near_rt_ric.py +++ b/code/network-generator/network_generation/model/python/o_ran_near_rt_ric.py @@ -12,53 +12,74 @@ # See the License for the specific language governing permissions and # limitations under the License. -#!/usr/bin/python +# !/usr/bin/python """ -A Class representing an O-RAN Near real-time intelligent controller (ORanNearRtRic) +A Class representing an O-RAN Near real-time intelligent controller +(ORanNearRtRic) """ import xml.etree.ElementTree as ET -from typing import overload +from typing import Any, cast import network_generation.model.python.hexagon as Hexagon +from network_generation.model.python.geo_location import GeoLocation from network_generation.model.python.hexagon import Hex from network_generation.model.python.o_ran_cu import ORanCu -from network_generation.model.python.o_ran_node import ORanNode -from network_generation.model.python.o_ran_object import IORanObject +from network_generation.model.python.o_ran_node import ( + IORanNode, + ORanNode, + default_value, +) from network_generation.model.python.o_ran_termination_point import ( ORanTerminationPoint, ) from network_generation.model.python.tower import Tower - # Define the "IORanNearRtRic" interface -class IORanNearRtRic(IORanObject): - def __init__(self, **kwargs): - super().__init__(**kwargs) +IORanNearRtRic = IORanNode # Define an abstract O-RAN Node class -class ORanNearRtRic(ORanNode, IORanNearRtRic): +class ORanNearRtRic(ORanNode): def __init__( - self, o_ran_near_rt_ric_data: IORanNearRtRic = None, **kwargs - ): - super().__init__(o_ran_near_rt_ric_data, **kwargs) + self, + data: dict[str, Any] = cast(dict[str, Any], default_value), + **kwargs: dict[str, Any] + ) -> None: + o_ran_near_rt_ric_data: IORanNearRtRic = ( + self._to_o_ran_near_rt_ric_data(data) + ) + super().__init__( + cast(dict[str, Any], o_ran_near_rt_ric_data), **kwargs + ) self._o_ran_cus: list[ORanCu] = self._calculate_o_ran_cus() + def _to_o_ran_near_rt_ric_data( + self, data: dict[str, Any] + ) -> IORanNearRtRic: + result: IORanNearRtRic = default_value + for key, key_type in IORanNearRtRic.__annotations__.items(): + if key in data: + result[key] = data[key] # type: ignore + return result + def _calculate_o_ran_cus(self) -> list[ORanCu]: hex_ring_radius: int = ( - self.spiralRadiusProfile.oRanNearRtRicSpiralRadiusOfOCus + self.parent.parent + .spiral_radius_profile.oRanNearRtRicSpiralRadiusOfOCus ) - hex_list: list[Hex] = self.spiralRadiusProfile.oRanCuSpiral( + hex_list: list[ + Hex + ] = self.parent.parent.spiral_radius_profile.oRanCuSpiral( self.position, hex_ring_radius ) result: list[ORanCu] = [] for index, hex in enumerate(hex_list): s: str = "00" + str(index) name: str = "-".join( - [self.name.replace("NearRtRic", "CU"), s[len(s) - 2 : len(s)]] + [self.name.replace("NearRtRic", "CU"), s[len(s) - 2: len(s)]] ) - network_center: dict = self.parent.parent.center + network_center: GeoLocation = self.parent.parent.center newGeo = Hexagon.hex_to_geo_location( self.layout, hex, network_center ).json() @@ -69,7 +90,6 @@ class ORanNearRtRic(ORanNode, IORanNearRtRic): "geoLocation": newGeo, "position": hex, "layout": self.layout, - "spiralRadiusProfile": self.spiralRadiusProfile, "parent": self, } ) @@ -88,11 +108,10 @@ class ORanNearRtRic(ORanNode, IORanNearRtRic): result.append(tower) return result - @property def termination_points(self) -> list[ORanTerminationPoint]: - result: list[ORanTerminationPoint] = super().termination_points + result: list[ORanTerminationPoint] = super().termination_points() phy_tp: str = "-".join([self.name, "phy".upper()]) - result.append({"tp-id": phy_tp, "name": phy_tp}) + result.append(ORanTerminationPoint({"tp-id": phy_tp, "name": phy_tp})) for interface in ["a1", "o1", "o2", "e2"]: id: str = "-".join([self.name, interface.upper()]) result.append( @@ -102,14 +121,14 @@ class ORanNearRtRic(ORanNode, IORanNearRtRic): ) return result - def to_topology_nodes(self) -> list[dict[str, dict]]: - result: list[dict[str, dict]] = super().to_topology_nodes() + def to_topology_nodes(self) -> list[dict[str, Any]]: + result: list[dict[str, Any]] = super().to_topology_nodes() for o_ran_cu in self.o_ran_cus: result.extend(o_ran_cu.to_topology_nodes()) return result - def to_topology_links(self) -> list[dict[str, dict]]: - result: list[dict[str, dict]] = super().to_topology_links() + def to_topology_links(self) -> list[dict[str, Any]]: + result: list[dict[str, Any]] = super().to_topology_links() for o_ran_cu in self.o_ran_cus: result.extend(o_ran_cu.to_topology_links()) return result @@ -124,5 +143,5 @@ class ORanNearRtRic(ORanNode, IORanNearRtRic): ric.append(o_ran_cu.toKml()) return ric - def toSvg(self) -> None: - return None + def toSvg(self) -> ET.Element: + return ET.Element("to-be-implemented") diff --git a/code/network-generator/network_generation/model/python/o_ran_network.py b/code/network-generator/network_generation/model/python/o_ran_network.py index bb39b61..3f816b3 100644 --- a/code/network-generator/network_generation/model/python/o_ran_network.py +++ b/code/network-generator/network_generation/model/python/o_ran_network.py @@ -12,13 +12,18 @@ # See the License for the specific language governing permissions and # limitations under the License. -#!/usr/bin/python +# !/usr/bin/python """ Module for a class representing a O-RAN Network """ import xml.etree.ElementTree as ET +from typing import Any, cast import network_generation.model.python.hexagon as Hexagon +from network_generation.model.python.geo_location import ( + GeoLocation, + IGeoLocation, +) from network_generation.model.python.hexagon import Layout from network_generation.model.python.o_ran_object import ( IORanObject, @@ -30,25 +35,38 @@ from network_generation.model.python.o_ran_spiral_radius_profile import ( ) from network_generation.model.python.point import Point +# Define the "IORanNetwork" interface +IORanNetwork = IORanObject + class ORanNetwork(ORanObject): """ Class representing an O-RAN Network object. """ + __my_default_value: IORanNetwork = cast(IORanNetwork, ORanObject.default()) + # constructor def __init__( - self, configuration: dict[str, dict], of: IORanObject = None, **kwargs - ): - super().__init__(of, **kwargs) + self, + configuration: dict[str, dict], + data: dict[str, Any] = cast(dict[str, Any], __my_default_value), + **kwargs: dict[str, Any] + ) -> None: + o_ran_network_data: IORanNetwork = self._to_o_ran_network_data(data) + super().__init__(cast(dict[str, Any], o_ran_network_data), **kwargs) self.__configuration = configuration - self.name = configuration["name"] - self.center = configuration["center"] - size = configuration["pattern"]["nr-cell-du"]["max-reach"] + + self.name = str(configuration["name"]) + self._center: IGeoLocation = cast( + IGeoLocation, configuration["center"] + ) + + size: int = int(configuration["pattern"]["nr-cell-du"]["max-reach"]) layout = Layout( Hexagon.layout_flat, Point(size, size), Point(0, 0) ) # 1 pixel = 1 meter - spiral_radius_profile = SpiralRadiusProfile( + self._spiral_radius_profile: SpiralRadiusProfile = SpiralRadiusProfile( { "oRanSmoSpiralRadiusOfNearRtRics": configuration["pattern"][ "smo" @@ -69,22 +87,44 @@ class ORanNetwork(ORanObject): "name": "O-RAN-SMO", "geoLocation": self.center, "layout": layout, - "spiralRadiusProfile": spiral_radius_profile, "parent": self, } ) - # getter - def configuration(self) -> dict[str, dict]: + def _to_o_ran_network_data(self, data: dict[str, Any]) -> IORanNetwork: + result: IORanNetwork = self.__my_default_value + for key, key_type in IORanNetwork.__annotations__.items(): + if key in data: + result[key] = data[key] # type: ignore + return result + + @property + def center(self) -> GeoLocation: + """ + Getter for a json object representing the O-RAN Network. + :return O-RAN Network as json object. + """ + return GeoLocation(self._center) + + @property + def spiral_radius_profile(self) -> SpiralRadiusProfile: + """ + Getter for a json object representing the SpiralRadiusProfile. + :return SpiralRadiusProfile. + """ + return self._spiral_radius_profile + + @property + def configuration(self) -> dict[str, Any]: """ Getter for a json object representing the O-RAN Network. :return O-RAN Network as json object. """ return self.__configuration - def to_topology(self) -> dict[str, dict]: - nodes: dict[str, dict] = self._o_ran_smo.to_topology_nodes() - links: dict[str, dict] = self._o_ran_smo.to_topology_links() + def to_topology(self) -> dict[str, Any]: + nodes: list[dict[str, Any]] = self._o_ran_smo.to_topology_nodes() + links: list[dict[str, Any]] = self._o_ran_smo.to_topology_links() return { "ietf-network:networks": { "network": [ @@ -129,12 +169,15 @@ class ORanNetwork(ORanObject): xmlns="http://www.w3.org/2000/svg", ) desc = ET.Element("desc") - # desc.text="\n context: " + str(self.id()) + "\n name: " + str(self.name()) + desc.text = "\n context: " + self.id + "\n name: " + self.name root.append(desc) title = ET.Element("title") - title.text = self.configuration()["name"] + title.text = str(self.configuration["name"]) root.append(title) # root.append(self.__context.svg(x, y)) return root + + def json(self) -> dict[str, Any]: + return super().json() diff --git a/code/network-generator/network_generation/model/python/o_ran_node.py b/code/network-generator/network_generation/model/python/o_ran_node.py index 7e5ec63..e8471ab 100644 --- a/code/network-generator/network_generation/model/python/o_ran_node.py +++ b/code/network-generator/network_generation/model/python/o_ran_node.py @@ -12,17 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -#!/usr/bin/python +# !/usr/bin/python """ An abstract Class for O-RAN Node """ -import json import xml.etree.ElementTree as ET -from abc import abstractmethod, abstractproperty -from typing import Any +from abc import abstractmethod +from typing import Any, cast import network_generation.model.python.hexagon as Hexagon +from network_generation.model.python.countries import Country from network_generation.model.python.geo_location import GeoLocation from network_generation.model.python.hexagon import Hex, Layout from network_generation.model.python.o_ran_object import ( @@ -41,74 +41,98 @@ from network_generation.model.python.type_definitions import AddressType # Define the "IORanObject" interface class IORanNode(IORanObject): - def __init__( - self, - address: AddressType = None, - geoLocation: GeoLocation = None, - url: str = None, - position: Hex = None, - layout: Layout = None, - spiralRadiusProfile: SpiralRadiusProfile = None, - parent=None, - **kwargs - ): - super().__init__(**kwargs) - self.address = address - self.geoLocation = geoLocation - self.url = url - self.position = position - self.layout = layout - self.spiralRadiusProfile = (spiralRadiusProfile,) - self.parent = parent + address: AddressType + geoLocation: GeoLocation + url: str + position: Hex + layout: Layout + spiralRadiusProfile: SpiralRadiusProfile + parent: Any + + +default_address: AddressType = { + "street": "highstreet", + "building": "none", + "city": "heaven", + "room": "frist", + "zip": "12345", + "state": "none", + "country": Country.Germany, +} +default_value: IORanNode = cast( + IORanNode, + { + **ORanObject.default(), + **{ + "address": default_address, + "geoLocation": GeoLocation(), + "url": "non-url", + "position": Hex(0, 0, 0), + "layout": Layout(Hexagon.layout_flat, Point(1, 1), Point(0, 0)), + "spiralRadiusProfile": SpiralRadiusProfile(), + "parent": None, + }, + }, +) # Define an abstract O-RAN Node class -class ORanNode(ORanObject, IORanNode): - def __init__(self, of: IORanNode = None, **kwargs): - super().__init__(of, **kwargs) - self.address = of["address"] if of and "address" in of else None - self.geoLocation = ( - of["geoLocation"] if of and "geoLocation" in of else GeoLocation() - ) - self.url = of["url"] if of and "url" in of else self.id - self.position = ( - of["position"] if of and "position" in of else Hex(0, 0, 0) +class ORanNode(ORanObject): + @staticmethod + def default() -> dict[str, Any]: + return cast(dict[str, Any], default_value) + + def __init__( + self, + data: dict[str, Any] = cast(dict[str, Any], default_value), + **kwargs: dict[str, Any] + ) -> None: + o_ran_node_data: IORanNode = self._to_o_ran_node_data(data) + super().__init__(cast(dict[str, Any], data), **kwargs) + self._address: AddressType = cast( + AddressType, o_ran_node_data["address"] ) - self.layout = ( - of["layout"] - if of and "layout" in of - else Layout(Hexagon.layout_flat, Point(1, 1), Point(0, 0)) + self._geo_location: GeoLocation = cast( + GeoLocation, o_ran_node_data["geoLocation"] ) - self.spiralRadiusProfile = ( - of["spiralRadiusProfile"] - if of and "spiralRadiusProfile" in of - else SpiralRadiusProfile() + self._url: str = str(o_ran_node_data["url"]) + self._position: Hex = cast(Hex, o_ran_node_data["position"]) + self._layout: Layout = cast(Layout, o_ran_node_data["layout"]) + self._spiral_radius_profile: SpiralRadiusProfile = cast( + SpiralRadiusProfile, o_ran_node_data["spiralRadiusProfile"] ) - self.parent = of["parent"] if of and "parent" in of else None + self._parent: Any = o_ran_node_data["parent"] self._termination_points: list[ORanTerminationPoint] = [] + def _to_o_ran_node_data(self, data: dict[str, Any]) -> IORanNode: + result: IORanNode = default_value + for key, key_type in IORanNode.__annotations__.items(): + if key in data: + result[key] = data[key] # type: ignore + return result + @property - def address(self) -> str: + def address(self) -> AddressType: return self._address @address.setter - def address(self, value: str): + def address(self, value: AddressType) -> None: self._address = value @property - def geoLocation(self) -> GeoLocation: - return self._geographicalLocation + def geo_location(self) -> GeoLocation: + return self._geo_location - @geoLocation.setter - def geoLocation(self, value: GeoLocation): - self._geographicalLocation = value + @geo_location.setter + def geo_location(self, value: GeoLocation) -> None: + self._geo_location = value @property def url(self) -> str: return self._url @url.setter - def url(self, value: str): + def url(self, value: str) -> None: self._url = value @property @@ -116,7 +140,7 @@ class ORanNode(ORanObject, IORanNode): return self._position @position.setter - def position(self, value: Hex): + def position(self, value: Hex) -> None: self._position = value @property @@ -124,16 +148,16 @@ class ORanNode(ORanObject, IORanNode): return self._layout @layout.setter - def layout(self, value: Layout): + def layout(self, value: Layout) -> None: self._layout = value @property - def spiralRadiusProfile(self) -> SpiralRadiusProfile: - return self._spiralRadiusProfile + def spiral_radius_profile(self) -> SpiralRadiusProfile: + return self._spiral_radius_profile - @spiralRadiusProfile.setter - def spiralRadiusProfile(self, value: SpiralRadiusProfile): - self._spiralRadiusProfile = value + @spiral_radius_profile.setter + def spiral_radius_profile(self, value: SpiralRadiusProfile) -> None: + self._spiral_radius_profile = value @property def parent( @@ -142,34 +166,26 @@ class ORanNode(ORanObject, IORanNode): return self._parent @parent.setter - def parent(self, value: Any): + def parent(self, value: Any) -> None: self._parent = value - @abstractproperty + # @property + # @abstractmethod def termination_points(self) -> list[ORanTerminationPoint]: return self._termination_points - def json(self) -> dict[str, dict]: - result: dict = super().json() - result["address"] = self.address - result["geoLocation"] = self.geoLocation - result["url"] = self.url - result["layout"] = self.layout - result["spiralRadiusProfile"] = self.spiralRadiusProfile - result["parent"] = self.parent - return result - @abstractmethod - def to_topology_nodes(self) -> list[dict[str, dict]]: - tps: list[dict[str, dict]] = [] - for tp in self.termination_points: + def to_topology_nodes(self) -> list[dict[str, Any]]: + tps: list[dict[str, Any]] = [] + for tp in self.termination_points(): if ( str(type(tp)) - == "" + == "" ): tps.append(tp.to_topology()) - result: list[dict[str, dict]] = [] + result: list[dict[str, Any]] = [] result.append( { "node-id": self.name, @@ -179,11 +195,11 @@ class ORanNode(ORanObject, IORanNode): return result @abstractmethod - def to_topology_links(self) -> list[dict[str, dict]]: - result: list[dict[str, dict]] = [] + def to_topology_links(self) -> list[dict[str, Any]]: + result: list[dict[str, Any]] = [] source_tp: str = "-".join([self.name, "phy".upper()]) dest_tp: str = "-".join([self.parent.name, "phy".upper()]) - if self.parent and not "Tower" in source_tp and not "Tower" in dest_tp: + if self.parent and "Tower" not in source_tp and "Tower" not in dest_tp: link_id: str = "".join( ["phy", ":", self.name, "<->", self.parent.name] ) @@ -203,5 +219,5 @@ class ORanNode(ORanObject, IORanNode): pass @abstractmethod - def toSvg(self) -> ET.Element | None: + def toSvg(self) -> ET.Element: pass diff --git a/code/network-generator/network_generation/model/python/o_ran_object.py b/code/network-generator/network_generation/model/python/o_ran_object.py index 3e367a6..ef74b30 100644 --- a/code/network-generator/network_generation/model/python/o_ran_object.py +++ b/code/network-generator/network_generation/model/python/o_ran_object.py @@ -12,28 +12,40 @@ # See the License for the specific language governing permissions and # limitations under the License. -#!/usr/bin/python +# !/usr/bin/python """ An abstract Class for O-RAN Objects """ -from network_generation.model.python.top import ITop, Top +from typing import Any, cast +from network_generation.model.python.top import ITop, Top, default_value # Define the "IORanObject" interface -class IORanObject(ITop): - def __init__(self, **kwargs): - super().__init__(**kwargs) +# class IORanObject(ITop): +IORanObject = ITop # Define an abstract O-RAN Object class -class ORanObject(Top, IORanObject): - def __init__(self, of: IORanObject = None, **kwargs): - super().__init__(of, **kwargs) - - def json(self) -> dict[str, dict]: - result: dict[str, dict] = super().json() - return result - - def __str__(self) -> str: - return str(self.json()) +class ORanObject(Top): + @staticmethod + def default() -> dict[str, Any]: + return cast(dict[str, Any], default_value) + + def __init__( + self, data: dict[str, Any] = default(), **kwargs: dict[str, Any] + ) -> None: + super().__init__(data, **kwargs) + + def json(self) -> dict[str, Any]: + return { + **super().json(), + "id": self.id, + "name": self.name, + "administrativeState": self.administrativeState, + "operationalState": self.operationalState, + "lifeCycleState": self.lifeCycleState, + "alarmState": self.alarmState, + "usageState": self.usageState, + "utilization": self.utilization, + } diff --git a/code/network-generator/network_generation/model/python/o_ran_ru.py b/code/network-generator/network_generation/model/python/o_ran_ru.py index d9b119d..e8ca9cf 100644 --- a/code/network-generator/network_generation/model/python/o_ran_ru.py +++ b/code/network-generator/network_generation/model/python/o_ran_ru.py @@ -12,86 +12,101 @@ # See the License for the specific language governing permissions and # limitations under the License. -#!/usr/bin/python +# !/usr/bin/python """ A Class representing an O-RAN radio unit (ORanRu) """ import xml.etree.ElementTree as ET -from typing import overload +from typing import Any, cast from network_generation.model.python.nr_cell_du import NrCellDu from network_generation.model.python.o_ran_du import ORanDu -from network_generation.model.python.o_ran_node import ORanNode -from network_generation.model.python.o_ran_object import IORanObject +from network_generation.model.python.o_ran_node import IORanNode, ORanNode from network_generation.model.python.o_ran_termination_point import ( ORanTerminationPoint, ) # Define the "IORanRu" interface -class IORanRu(IORanObject): - def __init__( - self, cell_count: int, ru_angle: int, ru_azimuth: int, **kwargs - ): - super().__init__(**kwargs) - self._cell_count = cell_count - self._ru_angle = ru_angle - self._ru_azimuth = ru_azimuth +class IORanRu(IORanNode): + cellCount: int + ruAngle: int + ruAzimuth: int + + +default_value: IORanRu = cast( + IORanRu, + { + **ORanNode.default(), + **{"cellCount": 1, "ruAngle": 120, "ruAzimuth": 0}, + }, +) # Define an abstract O-RAN Node class -class ORanRu(ORanNode, IORanRu): - def __init__(self, o_ran_ru_data: IORanRu = None, **kwargs): - super().__init__(o_ran_ru_data, **kwargs) - self._cell_count = ( - o_ran_ru_data["cellCount"] +class ORanRu(ORanNode): + def __init__( + self, + data: dict[str, Any] = cast(dict[str, Any], default_value), + **kwargs: dict[str, Any] + ) -> None: + o_ran_ru_data: IORanRu = self._to_o_ran_ru_data(data) + super().__init__(cast(dict[str, Any], o_ran_ru_data), **kwargs) + self._cell_count: int = ( + int(str(o_ran_ru_data["cellCount"])) if o_ran_ru_data and "cellCount" in o_ran_ru_data else 1 ) - self._ru_angle = ( - o_ran_ru_data["ruAngle"] + self._ru_angle: int = ( + int(str(o_ran_ru_data["ruAngle"])) if o_ran_ru_data and "ruAngle" in o_ran_ru_data else 120 ) - self._ru_azimuth = ( - o_ran_ru_data["ruAzimuth"] + self._ru_azimuth: int = ( + int(str(o_ran_ru_data["ruAzimuth"])) if o_ran_ru_data and "ruAzimuth" in o_ran_ru_data else 0 ) self._cells: list[NrCellDu] = self._create_cells() name: str = self.name.replace("RU", "DU") - self._oRanDu: ORanDu = ORanDu( - { - "name": name, - "geoLocation": self.parent.geoLocation, - "position": self.parent.position, - "layout": self.layout, - "parent": self.parent.parent.parent, - } - ) + + o_ran_du_data: dict[str, Any] = { + "name": name, + "geoLocation": self.parent.geo_location, + "position": self.parent.position, + "layout": self.layout, + "parent": self.parent.parent.parent, + } + self._oRanDu: ORanDu = ORanDu(o_ran_du_data) + + def _to_o_ran_ru_data(self, data: dict[str, Any]) -> IORanRu: + result: IORanRu = default_value + for key, key_type in IORanRu.__annotations__.items(): + if key in data: + result[key] = data[key] # type: ignore + return result def _create_cells(self) -> list[NrCellDu]: result: list[NrCellDu] = [] cell_angle: int = ( - self.parent.parent.parent.parent.parent.parent.configuration()[ + self.parent.parent.parent.parent.parent.parent.configuration[ "pattern" ]["nr-cell-du"]["cell-angle"] ) for index in range(self._cell_count): s: str = "00" + str(index) name: str = "-".join( - [self.name.replace("RU", "NRCellDu"), s[len(s) - 2 : len(s)]] + [self.name.replace("RU", "NRCellDu"), s[len(s) - 2: len(s)]] ) azimuth: int = index * cell_angle + self._ru_azimuth result.append( NrCellDu( { "name": name, - "geoLocation": self.geoLocation, + "geoLocation": self.geo_location, "position": self.position, "layout": self.layout, - "spiralRadiusProfile": self.spiralRadiusProfile, "parent": self, "cellAngle": cell_angle, "azimuth": azimuth, @@ -108,9 +123,8 @@ class ORanRu(ORanNode, IORanRu): def oRanDu(self) -> ORanDu: return self._oRanDu - @property def termination_points(self) -> list[ORanTerminationPoint]: - result: list[ORanTerminationPoint] = super().termination_points + result: list[ORanTerminationPoint] = super().termination_points() phy_tp: str = "-".join([self.name, "phy".upper()]) result.append(ORanTerminationPoint({"id": phy_tp, "name": phy_tp})) for interface in ["ofhm", "ofhc", "ofhu", "ofhs"]: @@ -121,16 +135,16 @@ class ORanRu(ORanNode, IORanRu): ) ) for cell in self.cells: - result.extend(cell.termination_points) + result.extend(cell.termination_points()) return result - def to_topology_nodes(self) -> list[dict[str, dict]]: - result: list[dict[str, dict]] = super().to_topology_nodes() + def to_topology_nodes(self) -> list[dict[str, Any]]: + result: list[dict[str, Any]] = super().to_topology_nodes() result.extend(self.oRanDu.to_topology_nodes()) return result - def to_topology_links(self) -> list[dict[str, dict]]: - result: list[dict[str, dict]] = super().to_topology_links() + def to_topology_links(self) -> list[dict[str, Any]]: + result: list[dict[str, Any]] = super().to_topology_links() result.extend(self.oRanDu.to_topology_links()) for interface in ["phy", "ofhm", "ofhc", "ofhu", "ofhs"]: link_id: str = "".join( @@ -163,5 +177,5 @@ class ORanRu(ORanNode, IORanRu): o_ran_ru.append(cell.toKml()) return o_ran_ru - def toSvg(self) -> None: - return None + def toSvg(self) -> ET.Element: + return ET.Element("to-be-implemented") diff --git a/code/network-generator/network_generation/model/python/o_ran_smo.py b/code/network-generator/network_generation/model/python/o_ran_smo.py index 9b1be83..9308291 100644 --- a/code/network-generator/network_generation/model/python/o_ran_smo.py +++ b/code/network-generator/network_generation/model/python/o_ran_smo.py @@ -12,53 +12,69 @@ # See the License for the specific language governing permissions and # limitations under the License. -#!/usr/bin/python +# !/usr/bin/python """ -A Class representing an O-RAN Service Management and Orchestration Framework (SMO) +A Class representing an O-RAN Service Management and +Orchestration Framework (SMO) """ import xml.etree.ElementTree as ET -from typing import overload +from typing import Any, cast import network_generation.model.python.hexagon as Hexagon +from network_generation.model.python.geo_location import GeoLocation from network_generation.model.python.hexagon import Hex from network_generation.model.python.o_ran_near_rt_ric import ORanNearRtRic -from network_generation.model.python.o_ran_node import ORanNode -from network_generation.model.python.o_ran_object import IORanObject +from network_generation.model.python.o_ran_node import ( + IORanNode, + ORanNode, + default_value, +) from network_generation.model.python.o_ran_termination_point import ( ORanTerminationPoint, ) from network_generation.model.python.tower import Tower - # Define the "IORanSmo" interface -class IORanSmo(IORanObject): - def __init__(self, **kwargs): - super().__init__(**kwargs) +IORanSmo = IORanNode # Define an abstract O-RAN Node class -class ORanSmo(ORanNode, IORanSmo): - def __init__(self, o_ran_smo_data: IORanSmo = None, **kwargs): - super().__init__(o_ran_smo_data, **kwargs) - self._o_ran_near_rt_rics: list[ - ORanNearRtRic - ] = self._calculate_near_rt_rics() +class ORanSmo(ORanNode): + """ + Class representing an O-RAN Service Management and Operation object. + """ + + def __init__( + self, + data: dict[str, Any] = cast(dict[str, Any], default_value), + **kwargs: dict[str, Any] + ) -> None: + o_ran_smo_data: IORanSmo = self._to_o_ran_smo_data(data) + super().__init__(cast(dict[str, Any], o_ran_smo_data), **kwargs) + if self.parent is not None: + self._o_ran_near_rt_rics: list[ + ORanNearRtRic + ] = self._calculate_near_rt_rics() def _calculate_near_rt_rics(self) -> list[ORanNearRtRic]: hex_ring_radius: int = ( - self.spiralRadiusProfile.oRanSmoSpiralRadiusOfNearRtRics + self.parent.spiral_radius_profile.oRanSmoSpiralRadiusOfNearRtRics + if self.parent is not None + else 1 ) - hex_list: list[Hex] = self.spiralRadiusProfile.oRanNearRtRicSpiral( + hex_list: list[ + Hex + ] = self.parent.spiral_radius_profile.oRanNearRtRicSpiral( self.position, hex_ring_radius ) result: list[ORanNearRtRic] = [] for index, hex in enumerate(hex_list): s: str = "00" + str(index) name: str = "-".join( - [self.name.replace("SMO", "NearRtRic"), s[len(s) - 2 : len(s)]] + [self.name.replace("SMO", "NearRtRic"), s[len(s) - 2: len(s)]] ) - network_center: dict = self.parent.center + network_center: GeoLocation = self.parent.center newGeo = Hexagon.hex_to_geo_location( self.layout, hex, network_center ).json() @@ -69,47 +85,58 @@ class ORanSmo(ORanNode, IORanSmo): "geoLocation": newGeo, "position": hex, "layout": self.layout, - "spiralRadiusProfile": self.spiralRadiusProfile, "parent": self, - } + }, ) ) return result + def _to_o_ran_smo_data(self, data: dict[str, Any]) -> IORanSmo: + result: IORanSmo = default_value + for key, key_type in IORanSmo.__annotations__.items(): + if key in data: + result[key] = data[key] # type: ignore + return result + @property def o_ran_near_rt_rics(self) -> list[ORanNearRtRic]: return self._o_ran_near_rt_rics @property + def towers(self) -> list[Tower]: + result: list[Tower] = [] + for ric in self.o_ran_near_rt_rics: + for tower in ric.towers: + result.append(tower) + return result + + # @property def termination_points(self) -> list[ORanTerminationPoint]: - result: list[ORanTerminationPoint] = super().termination_points + result: list[ORanTerminationPoint] = super().termination_points() phy_tp: str = "-".join([self.name, "phy".upper()]) result.append(ORanTerminationPoint({"id": phy_tp, "name": phy_tp})) for interface in ["a1", "o1", "o2"]: id: str = "-".join([self.name, interface.upper()]) result.append( ORanTerminationPoint( - {"id": id, "name": id, "supporter": phy_tp, "parent": self} + { + "id": id, + "name": id, + "supporter": phy_tp, + "parent": self, + }, ) ) return result - @property - def towers(self) -> list[Tower]: - result: list[Tower] = [] - for ric in self.o_ran_near_rt_rics: - for tower in ric.towers: - result.append(tower) - return result - - def to_topology_nodes(self) -> list[dict[str, dict]]: - result: list[dict[str, dict]] = super().to_topology_nodes() + def to_topology_nodes(self) -> list[dict[str, Any]]: + result: list[dict[str, Any]] = super().to_topology_nodes() for ric in self.o_ran_near_rt_rics: result.extend(ric.to_topology_nodes()) return result - def to_topology_links(self) -> list[dict[str, dict]]: - result: list[dict[str, dict]] = [] # super().to_topology_links() + def to_topology_links(self) -> list[dict[str, Any]]: + result: list[dict[str, Any]] = [] # super().to_topology_links() for ric in self.o_ran_near_rt_rics: result.extend(ric.to_topology_links()) return result @@ -124,5 +151,5 @@ class ORanSmo(ORanNode, IORanSmo): smo.append(ric.toKml()) return smo - def toSvg(self) -> None: - return None + def toSvg(self) -> ET.Element: + return ET.Element("not-implemented-yet-TODO") diff --git a/code/network-generator/network_generation/model/python/o_ran_spiral_radius_profile.py b/code/network-generator/network_generation/model/python/o_ran_spiral_radius_profile.py index fd8c289..75d684c 100644 --- a/code/network-generator/network_generation/model/python/o_ran_spiral_radius_profile.py +++ b/code/network-generator/network_generation/model/python/o_ran_spiral_radius_profile.py @@ -12,15 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. -#!/usr/bin/python +# !/usr/bin/python + +from typing import Any import network_generation.model.python.hexagon as Hexagon -from network_generation.model.python.cube import Cube from network_generation.model.python.hexagon import Hex class SpiralRadiusProfile: - def __init__(self, data: [dict[str, dict] | None] = None): + def __init__(self, data: dict[str, Any] | None = None) -> None: self._oRanSmoSpiralRadiusOfNearRtRics = ( data.get("oRanSmoSpiralRadiusOfNearRtRics", 1) if data else 1 ) @@ -33,10 +34,20 @@ class SpiralRadiusProfile: self._oRanDuSpiralRadiusOfTowers = ( data.get("oRanDuSpiralRadiusOfTowers", 1) if data else 1 ) + # self._nrDuCellsPerSector: int = ( + # int(str(data.get("sectorCount"))) if data else 1 + # ) @property def id(self) -> str: - return f"{self._oRanDuSpiralRadiusOfTowers}{self._oRanCuSpiralRadiusOfODus}{self._oRanNearRtRicSpiralRadiusOfOCus}{self._oRanSmoSpiralRadiusOfNearRtRics}" + return "".join( + [ + str(self._oRanDuSpiralRadiusOfTowers), + str(self._oRanCuSpiralRadiusOfODus), + str(self._oRanNearRtRicSpiralRadiusOfOCus), + str(self._oRanSmoSpiralRadiusOfNearRtRics), + ] + ) @property def count(self) -> int: @@ -64,7 +75,7 @@ class SpiralRadiusProfile: return self._oRanSmoSpiralRadiusOfNearRtRics @oRanSmoSpiralRadiusOfNearRtRics.setter - def oRanSmoSpiralRadiusOfNearRtRics(self, value: int): + def oRanSmoSpiralRadiusOfNearRtRics(self, value: int) -> None: self._oRanSmoSpiralRadiusOfNearRtRics = value @property @@ -72,7 +83,7 @@ class SpiralRadiusProfile: return self._oRanNearRtRicSpiralRadiusOfOCus @oRanNearRtRicSpiralRadiusOfOCus.setter - def oRanNearRtRicSpiralRadiusOfOCus(self, value: int): + def oRanNearRtRicSpiralRadiusOfOCus(self, value: int) -> None: self._oRanNearRtRicSpiralRadiusOfOCus = value @property @@ -80,7 +91,7 @@ class SpiralRadiusProfile: return self._oRanCuSpiralRadiusOfODus @oRanCuSpiralRadiusOfODus.setter - def oRanCuSpiralRadiusOfODus(self, value: int): + def oRanCuSpiralRadiusOfODus(self, value: int) -> None: self._oRanCuSpiralRadiusOfODus = value @property @@ -88,7 +99,7 @@ class SpiralRadiusProfile: return self._oRanDuSpiralRadiusOfTowers @oRanDuSpiralRadiusOfTowers.setter - def oRanDuSpiralRadiusOfTowers(self, value: int): + def oRanDuSpiralRadiusOfTowers(self, value: int) -> None: self._oRanDuSpiralRadiusOfTowers = value @property @@ -96,7 +107,7 @@ class SpiralRadiusProfile: return self._sectors @sectors.setter - def sectors(self, value: int): + def sectors(self, value: int) -> None: self._sectors = value @property @@ -104,8 +115,8 @@ class SpiralRadiusProfile: return self._nrDuCellsPerSector @nrDuCellsPerSector.setter - def nrDuCellsPerSector(self, value: int): - self._nrDuCellsPerSector: int = value + def nrDuCellsPerSector(self, value: int) -> None: + self._nrDuCellsPerSector = value def oRanDuDirections(self) -> list[Hex]: q: int = 2 * self._oRanDuSpiralRadiusOfTowers + 1 @@ -120,13 +131,14 @@ class SpiralRadiusProfile: Hex(-r, -s, -q), ] - def oRanDuNeighbor(self, cube: Cube, direction: int): + def oRanDuNeighbor(self, cube: Hex, direction: int) -> Hex: return Hexagon.hex_add(cube, self.oRanDuDirections()[direction]) def oRanDuRing(self, center: Hex, radius: int) -> list[Hex]: if radius <= 0: raise ValueError( - "Invalid radius. The radius around the hex center must be greater than 0 rings." + "Invalid radius. The radius around the hex center " + + "must be greater than 0 rings." ) results: list[Hex] = [] hex: Hex = Hexagon.hex_add( @@ -166,13 +178,14 @@ class SpiralRadiusProfile: Hex(-r, -s, -q), ] - def oRanCuNeighbor(self, cube: Hex, direction: int) -> list[Hex]: + def oRanCuNeighbor(self, cube: Hex, direction: int) -> Hex: return Hexagon.hex_add(cube, self.oRanCuDirections()[direction]) - def oRanCuRing(self, center: Hex, radius: int): + def oRanCuRing(self, center: Hex, radius: int) -> list[Hex]: if not (radius > 0): raise ValueError( - "Invalid radius. The radius around the hex center must be greater than 0 rings." + "Invalid radius. The radius around the hex center " + + "must be greater than 0 rings." ) results: list[Hex] = [] @@ -207,45 +220,45 @@ class SpiralRadiusProfile: q: int = 3 * q0 - self.oRanNearRtRicSpiralRadiusOfOCus r: int = -r0 - self.oRanNearRtRicSpiralRadiusOfOCus - profile_id: str = self.id[0 : len(self.id) - 1] + profile_id: str = self.id[0: len(self.id) - 1] if profile_id in {"111", "112", "113", "114"}: - q: int = 21 + 14 * (self.oRanNearRtRicSpiralRadiusOfOCus - 1) - r: int = -7 * self.oRanNearRtRicSpiralRadiusOfOCus + q = 21 + 14 * (self.oRanNearRtRicSpiralRadiusOfOCus - 1) + r = -7 * self.oRanNearRtRicSpiralRadiusOfOCus elif profile_id in {"121", "122", "123", "124"}: - q: int = 25 + 13 * (self.oRanNearRtRicSpiralRadiusOfOCus - 1) - r: int = 9 + 10 * (self.oRanNearRtRicSpiralRadiusOfOCus - 1) + q = 25 + 13 * (self.oRanNearRtRicSpiralRadiusOfOCus - 1) + r = 9 + 10 * (self.oRanNearRtRicSpiralRadiusOfOCus - 1) elif profile_id in {"131", "132"}: - q: int = 49 + 30 * (self.oRanNearRtRicSpiralRadiusOfOCus - 1) - r: int = -21 - 34 * (self.oRanNearRtRicSpiralRadiusOfOCus - 1) + q = 49 + 30 * (self.oRanNearRtRicSpiralRadiusOfOCus - 1) + r = -21 - 34 * (self.oRanNearRtRicSpiralRadiusOfOCus - 1) elif profile_id == "133": - q: int = 74 - r: int = 37 + q = 74 + r = 37 elif profile_id == "134": - q: int = 93 - r: int = 50 + q = 93 + r = 50 elif profile_id in {"211", "212", "213", "214"}: - q: int = 34 + 23 * (self.oRanNearRtRicSpiralRadiusOfOCus - 1) - r: int = -10 * self.oRanNearRtRicSpiralRadiusOfOCus - 1 + q = 34 + 23 * (self.oRanNearRtRicSpiralRadiusOfOCus - 1) + r = -10 * self.oRanNearRtRicSpiralRadiusOfOCus - 1 elif profile_id in {"221", "222", "223", "224"}: - q: int = 57 + 38 * (self.oRanNearRtRicSpiralRadiusOfOCus - 1) - r: int = -19 * self.oRanNearRtRicSpiralRadiusOfOCus + q = 57 + 38 * (self.oRanNearRtRicSpiralRadiusOfOCus - 1) + r = -19 * self.oRanNearRtRicSpiralRadiusOfOCus elif profile_id in {"231", "232", "233", "234"}: - q: int = 80 + 53 * (self.oRanNearRtRicSpiralRadiusOfOCus - 1) - r: int = -28 * self.oRanNearRtRicSpiralRadiusOfOCus - 1 + q = 80 + 53 * (self.oRanNearRtRicSpiralRadiusOfOCus - 1) + r = -28 * self.oRanNearRtRicSpiralRadiusOfOCus - 1 elif profile_id in {"241", "242", "243", "244"}: - q: int = 103 + 68 * (self.oRanNearRtRicSpiralRadiusOfOCus - 1) - r: int = -39 * self.oRanNearRtRicSpiralRadiusOfOCus + 2 * ( + q = 103 + 68 * (self.oRanNearRtRicSpiralRadiusOfOCus - 1) + r = -39 * self.oRanNearRtRicSpiralRadiusOfOCus + 2 * ( self.oRanNearRtRicSpiralRadiusOfOCus - 1 ) elif profile_id in {"311", "312", "313", "314"}: - q: int = 47 + 32 * (self.oRanNearRtRicSpiralRadiusOfOCus - 1) - r: int = -11 - 13 * (self.oRanNearRtRicSpiralRadiusOfOCus - 1) + q = 47 + 32 * (self.oRanNearRtRicSpiralRadiusOfOCus - 1) + r = -11 - 13 * (self.oRanNearRtRicSpiralRadiusOfOCus - 1) elif profile_id in {"321", "322", "323", "324"}: - q: int = 79 + 53 * (self.oRanNearRtRicSpiralRadiusOfOCus - 1) - r: int = -24 - 25 * (self.oRanNearRtRicSpiralRadiusOfOCus - 1) + q = 79 + 53 * (self.oRanNearRtRicSpiralRadiusOfOCus - 1) + r = -24 - 25 * (self.oRanNearRtRicSpiralRadiusOfOCus - 1) elif profile_id in {"331", "332", "333", "334"}: - q: int = 111 + 75 * (self.oRanNearRtRicSpiralRadiusOfOCus - 1) - r: int = -37 - 37 * (self.oRanNearRtRicSpiralRadiusOfOCus - 1) + q = 111 + 75 * (self.oRanNearRtRicSpiralRadiusOfOCus - 1) + r = -37 - 37 * (self.oRanNearRtRicSpiralRadiusOfOCus - 1) else: # Handle the default case or raise a warning pass @@ -260,13 +273,14 @@ class SpiralRadiusProfile: Hex(-r, -s, -q), ] - def oRanNearRtRicNeighbor(self, cube: Hex, direction: int): + def oRanNearRtRicNeighbor(self, cube: Hex, direction: int) -> Hex: return Hexagon.hex_add(cube, self.oRanNearRtRicDirections()[direction]) def oRanNearRtRicRing(self, center: Hex, radius: int) -> list[Hex]: if not (radius > 0): raise ValueError( - "Invalid radius. The radius around the hex center must be greater than 0 rings." + "Invalid radius. The radius around the hex center " + + "must be greater than 0 rings." ) results: list[Hex] = [] diff --git a/code/network-generator/network_generation/model/python/o_ran_termination_point.py b/code/network-generator/network_generation/model/python/o_ran_termination_point.py index 6aba16d..034c49a 100644 --- a/code/network-generator/network_generation/model/python/o_ran_termination_point.py +++ b/code/network-generator/network_generation/model/python/o_ran_termination_point.py @@ -12,12 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -#!/usr/bin/python +# !/usr/bin/python """ An abstract Class for O-RAN TerminationPoint """ -from abc import abstractmethod +from typing import Any, cast from network_generation.model.python.o_ran_object import ( IORanObject, @@ -26,50 +26,94 @@ from network_generation.model.python.o_ran_object import ( # Define the "IORanObject" interface -class IORanTerminationPointData(IORanObject): - def __init__(self, supporter: str = None, parent=None, **kwargs): - super().__init__(**kwargs) - self.supporter = supporter - self.parent = parent - - -# Define an O-RAN Termination Point (ietf-interface, onf:logical-termination-point) class -class ORanTerminationPoint(ORanObject, IORanTerminationPointData): - def __init__(self, tp: IORanTerminationPointData = None, **kwargs): - super().__init__(tp, **kwargs) - self.supporter = tp["supporter"] if tp and "supporter" in tp else None - self.parent = tp["parent"] if tp and "parent" in tp else None - - def to_topology(self): - result: dict[str, dict] = {"tp-id": self.name} - if self.supporter: - network_ref: str = "" - match str(type(self.parent)): - case "": - network_ref = self.parent.parent.id - case "": - network_ref = self.parent.parent.parent.id - case "": - network_ref = self.parent.parent.parent.parent.id - case "": - network_ref = self.parent.parent.parent.parent.parent.id - case "": - network_ref = self.parent.parent.parent.parent.parent.id - case "": - network_ref = ( - self.parent.parent.parent.parent.parent.parent.id - ) - case _: - print("unknown: implement " + str(type(self.parent))) - network_ref = "unknown: implement " + str( - type(self.parent) - ) - - result["supporting-termination-point"] = [ - { - "network-ref": network_ref, - "node-ref": self.parent.name, - "tp-ref": self.supporter, - } - ] +class IORanTerminationPoint(IORanObject): + supporter: str + parent: Any + + +default_value: IORanTerminationPoint = cast( + IORanTerminationPoint, + { + **ORanObject.default(), + **{ + "supporter": "TerminationPointLayer", + "parent": None, + }, + }, +) + + +# Define an O-RAN Termination Point +# (ietf-interface, onf:logical-termination-point) class +class ORanTerminationPoint(ORanObject): + @staticmethod + def default() -> dict[str, Any]: + return cast(dict[str, Any], default_value) + + def __init__( + self, data: dict[str, Any] = default(), **kwargs: dict[str, Any] + ) -> None: + itp: IORanTerminationPoint = self._to_itp_data(data) + super().__init__(cast(dict[str, Any], itp), **kwargs) + self._supporter: str = str(itp["supporter"]) + self._parent: Any = itp["parent"] + + def _to_itp_data(self, data: dict[str, Any]) -> IORanTerminationPoint: + result: IORanTerminationPoint = default_value + for key, key_type in IORanTerminationPoint.__annotations__.items(): + if key in data: + result[key] = data[key] # type: ignore + return result + + @property + def supporter(self) -> str: + return self._supporter + + @supporter.setter + def supporter(self, value: str) -> None: + self._supporter = value + + @property + def parent(self) -> Any: + return self._utilization + + @parent.setter + def parent(self, value: Any) -> None: + self._parent = value + + def to_topology(self) -> dict[str, Any]: + result: dict[str, Any] = {"tp-id": self.name} + # TODO + # if self.supporter: + # network_ref: str = "" + # match self.parent.__qualname__: + # case ORanSmo.__qualname__: + # network_ref = self.parent.parent.id + # case "": + # network_ref = self.parent.parent.id + # case "": + # network_ref = self.parent.parent.parent.id + # case "": + # network_ref = self.parent.parent.parent.parent.id + # case "": + # network_ref = self.parent.parent.parent.parent.parent.id + # case "": + # network_ref = self.parent.parent.parent.parent.parent.id + # case "": + # network_ref = ( + # self.parent.parent.parent.parent.parent.parent.id + # ) + # case _: + # print("unknown: implement " + str(type(self.parent))) + # network_ref = "unknown: implement " + str( + # type(self.parent) + # ) + + # result["supporting-termination-point"] = [ + # { + # "network-ref": network_ref, + # "node-ref": self.parent.name, + # "tp-ref": self.supporter, + # } + # ] return result diff --git a/code/network-generator/network_generation/model/python/point.py b/code/network-generator/network_generation/model/python/point.py index 1a9b772..41cd8a0 100644 --- a/code/network-generator/network_generation/model/python/point.py +++ b/code/network-generator/network_generation/model/python/point.py @@ -14,7 +14,7 @@ # # inspired by http://www.redblobgames.com/grids/hexagons/ -#!/usr/bin/python +# !/usr/bin/python from __future__ import division, print_function diff --git a/code/network-generator/network_generation/model/python/top.py b/code/network-generator/network_generation/model/python/top.py index 5d6ac02..e68c615 100644 --- a/code/network-generator/network_generation/model/python/top.py +++ b/code/network-generator/network_generation/model/python/top.py @@ -12,13 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -#!/usr/bin/python +# !/usr/bin/python """ An abstract Class for all classes """ -import uuid -from abc import ABC +from abc import ABC, abstractmethod +from typing import Any, TypedDict, cast from network_generation.model.python.type_definitions import ( AdministrativeState, @@ -31,70 +31,65 @@ from network_generation.model.python.type_definitions import ( # Define the ITop interface -class ITop: - def __init__( - self, - id: str = None, - name: str = None, - administrativeState: AdministrativeState = None, - operationalState: OperationalState = None, - lifeCycleState: LifeCycleState = None, - alarmState: AlarmState = None, - usageState: UsageState = None, - utilization: Utilization = None, - ): - self.id = id - self.name = name - self.administrativeState = administrativeState - self.operationalState = operationalState - self.lifeCycleState = lifeCycleState - self.alarmState = alarmState - self.usageState = usageState - self.utilization = utilization +class ITop(TypedDict): + id: str + name: str + administrativeState: AdministrativeState + operationalState: OperationalState + lifeCycleState: LifeCycleState + alarmState: AlarmState + usageState: UsageState + utilization: Utilization + + +# define default value +default_value: ITop = { + "id": "be5229af-2660-4bae-8f2c-b9d0f788fad1", + "name": "NoName", + "administrativeState": AdministrativeState.LOCKED, + "operationalState": OperationalState.DISABLED, + "lifeCycleState": LifeCycleState.PLANNED, + "alarmState": 0, + "usageState": UsageState.UNUSED, + "utilization": 0, +} # Define the Top class -class Top(ABC, ITop): - def __init__(self, data: [dict[str, dict] | None] = None): - self._id = data["id"] if data and "id" in data else str(uuid.uuid4()) - self._name = ( - data["name"] - if data and "name" in data - else " ".join(["Name", "of", self._id]) - ) - self._administrativeState = ( - data["administrativeState"] - if data and "administrativeState" in data - else AdministrativeState.LOCKED - ) - self._operationalState = ( - data["operationalState"] - if data and "operationalState" in data - else OperationalState.DISABLED - ) - self._lifeCycleState = ( - data["lifeCycleState"] - if data and "lifeCycleState" in data - else LifeCycleState.PLANNED - ) - self._alarmState = ( - data["alarmState"] if data and "alarmState" in data else 0 - ) - self._usageState = ( - data["usageState"] - if data and "usageState" in data - else UsageState.UNUSED - ) - self._utilization = ( - data["utilization"] if data and "utilization" in data else 0 - ) +class Top(ABC): + @staticmethod + def default() -> dict[str, Any]: + return cast(dict[str, Any], default_value) + + def __init__( + self, data: dict[str, Any] = cast(dict[str, Any], default_value) + ) -> None: + super().__init__() + itop: ITop = self._to_itop_data(data) + self._id: str = itop["id"] + self._name: str = itop["name"] + self._administrativeState: AdministrativeState = itop[ + "administrativeState" + ] + self._operationalState: OperationalState = itop["operationalState"] + self._lifeCycleState: LifeCycleState = itop["lifeCycleState"] + self._alarmState: AlarmState = itop["alarmState"] + self._usageState: UsageState = itop["usageState"] + self._utilization: Utilization = itop["utilization"] + + def _to_itop_data(self, data: dict[str, Any]) -> ITop: + result: ITop = default_value + for key, key_type in ITop.__annotations__.items(): + if key in data: + result[key] = data[key] # type: ignore + return result @property def id(self) -> str: return self._id @id.setter - def id(self, value: str): + def id(self, value: str) -> None: self._id = value @property @@ -102,7 +97,7 @@ class Top(ABC, ITop): return self._name @name.setter - def name(self, value: str): + def name(self, value: str) -> None: self._name = value @property @@ -110,7 +105,7 @@ class Top(ABC, ITop): return self._administrativeState @administrativeState.setter - def administrativeState(self, value: AdministrativeState): + def administrativeState(self, value: AdministrativeState) -> None: self._administrativeState = value @property @@ -118,7 +113,7 @@ class Top(ABC, ITop): return self._operationalState @operationalState.setter - def operationalState(self, value: OperationalState): + def operationalState(self, value: OperationalState) -> None: self._operationalState = value @property @@ -126,7 +121,7 @@ class Top(ABC, ITop): return self._lifeCycleState @lifeCycleState.setter - def lifeCycleState(self, value: LifeCycleState): + def lifeCycleState(self, value: LifeCycleState) -> None: self._lifeCycleState = value @property @@ -134,7 +129,7 @@ class Top(ABC, ITop): return self._alarmState @alarmState.setter - def alarmState(self, value: AlarmState): + def alarmState(self, value: AlarmState) -> None: self._alarmState = value @property @@ -142,7 +137,7 @@ class Top(ABC, ITop): return self._usageState @usageState.setter - def usageState(self, value: UsageState): + def usageState(self, value: UsageState) -> None: self._usageState = value @property @@ -150,18 +145,19 @@ class Top(ABC, ITop): return self._utilization @utilization.setter - def utilization(self, value: Utilization): + def utilization(self, value: Utilization) -> None: self._utilization = value - def json(self) -> dict[str, dict]: + @abstractmethod + def json(self) -> dict[str, Any]: return { "id": self.id, "name": self.name, - "administrativeState": self.administrativeState.value, - "operationalState": self.operationalState.value, - "lifeCycleState": self.lifeCycleState.value, + "administrativeState": self.administrativeState, + "operationalState": self.operationalState, + "lifeCycleState": self.lifeCycleState, "alarmState": self.alarmState, - "usageState": self.usageState.value, + "usageState": self.usageState, "utilization": self.utilization, } diff --git a/code/network-generator/network_generation/model/python/tower.py b/code/network-generator/network_generation/model/python/tower.py index d708d5e..d650ee0 100644 --- a/code/network-generator/network_generation/model/python/tower.py +++ b/code/network-generator/network_generation/model/python/tower.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -#!/usr/bin/python +# !/usr/bin/python """ A Class representing a Tower to mount O-RAN RUs @@ -20,10 +20,9 @@ It can be interpreted as 'resource pool' for physical network functions. """ import xml.etree.ElementTree as ET -from typing import overload +from typing import Any, cast -from network_generation.model.python.o_ran_node import ORanNode -from network_generation.model.python.o_ran_object import IORanObject +from network_generation.model.python.o_ran_node import IORanNode, ORanNode from network_generation.model.python.o_ran_ru import ORanRu from network_generation.model.python.o_ran_termination_point import ( ORanTerminationPoint, @@ -31,37 +30,58 @@ from network_generation.model.python.o_ran_termination_point import ( # Define the "IORanDu" interface -class ITower(IORanObject): - def __init__(self, o_ran_ru_count: int, **kwargs): - super().__init__(**kwargs) - self._o_ran_ru_count = o_ran_ru_count +class ITower(IORanNode): + o_ran_ru_count: int + + +default_value: ITower = cast( + ITower, + { + **ORanNode.default(), + **{ + "o_ran_ru_count": 1, + }, + }, +) # Implement a concrete O-RAN Node class class Tower(ORanNode): - def __init__(self, tower_data: ITower = None, **kwargs): - super().__init__(tower_data, **kwargs) - self._o_ran_ru_count = ( - tower_data["oRanRuCount"] + def __init__( + self, + data: dict[str, Any] = cast(dict[str, Any], default_value), + **kwargs: dict[str, Any] + ) -> None: + tower_data: ITower = self._to_tower_data(data) + super().__init__(cast(dict[str, Any], tower_data), **kwargs) + self._o_ran_ru_count: int = ( + int(str(tower_data["oRanRuCount"])) if tower_data and "oRanRuCount" in tower_data else 3 ) self._o_ran_rus: list[ORanRu] = self._create_o_ran_rus() + def _to_tower_data(self, data: dict[str, Any]) -> ITower: + result: ITower = default_value + for key, key_type in ITower.__annotations__.items(): + if key in data: + result[key] = data[key] # type: ignore + return result + def _create_o_ran_rus(self) -> list[ORanRu]: result: list[ORanRu] = [] for index in range(self._o_ran_ru_count): s: str = "00" + str(index) name: str = "-".join( - [self.name.replace("Tower", "RU"), s[len(s) - 2 : len(s)]] + [self.name.replace("Tower", "RU"), s[len(s) - 2: len(s)]] ) cell_count: int = ( - self.parent.parent.parent.parent.parent.configuration()[ + self.parent.parent.parent.parent.parent.configuration[ "pattern" ]["o-ran-ru"]["nr-cell-du-count"] ) cell_angle: int = ( - self.parent.parent.parent.parent.parent.configuration()[ + self.parent.parent.parent.parent.parent.configuration[ "pattern" ]["nr-cell-du"]["cell-angle"] ) @@ -71,10 +91,9 @@ class Tower(ORanNode): ORanRu( { "name": name, - "geoLocation": self.geoLocation, + "geoLocation": self.geo_location, "position": self.position, "layout": self.layout, - "spiralRadiusProfile": self.spiralRadiusProfile, "parent": self, "cellCount": cell_count, "ruAngle": ru_angle, @@ -88,36 +107,37 @@ class Tower(ORanNode): def o_ran_rus(self) -> list[ORanRu]: return self._o_ran_rus - @property def termination_points(self) -> list[ORanTerminationPoint]: - result: list[ORanTerminationPoint] = super().termination_points + result: list[ORanTerminationPoint] = super().termination_points() phy_tp: str = "-".join([self.name, "phy".upper()]) - result.append({"tp-id": phy_tp}) + result.append(ORanTerminationPoint({"tp-id": phy_tp})) for interface in ["e2", "o1", "ofhm", "ofhc", "ofhu", "ofhs"]: result.append( - { - "tp-id": "-".join([self.name, interface.upper()]), - "supporting-termination-point": [ - { - "network-ref": type( - self.parent.parent.parent.parent - ), - "node-ref": self.name, - "tp-ref": phy_tp, - } - ], - } + ORanTerminationPoint( + { + "tp-id": "-".join([self.name, interface.upper()]), + "supporting-termination-point": [ + { + "network-ref": type( + self.parent.parent.parent.parent + ), + "node-ref": self.name, + "tp-ref": phy_tp, + } + ], + } + ) ) return result - def to_topology_nodes(self) -> list[dict[str, dict]]: - result: list[dict[str, dict]] = super().to_topology_nodes() + def to_topology_nodes(self) -> list[dict[str, Any]]: + result: list[dict[str, Any]] = super().to_topology_nodes() for o_ran_ru in self.o_ran_rus: result.extend(o_ran_ru.to_topology_nodes()) return result - def to_topology_links(self) -> list[dict[str, dict]]: - result: list[dict[str, dict]] = super().to_topology_links() + def to_topology_links(self) -> list[dict[str, Any]]: + result: list[dict[str, Any]] = super().to_topology_links() for o_ran_ru in self.o_ran_rus: result.extend(o_ran_ru.to_topology_links()) return result @@ -132,5 +152,5 @@ class Tower(ORanNode): tower.append(o_ran_ru.toKml()) return tower - def toSvg(self) -> None: - return None + def toSvg(self) -> ET.Element: + return ET.Element("to-be-implemented") diff --git a/code/network-generator/network_generation/model/python/type_definitions.py b/code/network-generator/network_generation/model/python/type_definitions.py index 1430c3c..9dc0c8a 100644 --- a/code/network-generator/network_generation/model/python/type_definitions.py +++ b/code/network-generator/network_generation/model/python/type_definitions.py @@ -12,15 +12,26 @@ # See the License for the specific language governing permissions and # limitations under the License. -#!/usr/bin/python +# !/usr/bin/python """ A collection of TypeDefinitions """ from enum import Enum +from typing import TypedDict from network_generation.model.python.countries import Country +# from typing import Any, TypedDict, TypeVar + + +# from network_generation.model.python.o_ran_node import IORanNode +# from network_generation.model.python.o_ran_object import IORanObject +# from network_generation.model.python.top import ITop + +# Generic Types based on inheritance +# IORanType = TypeVar("IORanType", ITop, IORanObject, IORanNode) + # Define AdministrativeState enum class AdministrativeState(Enum): @@ -32,16 +43,16 @@ class AdministrativeState(Enum): # Define AlarmState type AlarmState = int + # Define Address type -AddressType = { - "street": str, - "building": str, - "room": str, - "city": str, - "zip": str, - "state": str, - "country": Country, -} +class AddressType(TypedDict): + street: str + building: str + room: str + city: str + zip: str + state: str + country: Country # Define OperationalState enum @@ -66,20 +77,4 @@ class UsageState(Enum): UNUSED = "unused" -# Define Enumerate type -def Enumerate(N, Acc=None): - if Acc is None: - Acc = [] - if len(Acc) == N: - return Acc[-1] - return Enumerate(N, Acc + [len(Acc)]) - - -# Define Range type -def Range(F, T) -> list[int]: - return [i for i in range(F, T + 1)] - - -# Define Procent and Utilization types -Procent = Range(0, 100) -Utilization = Procent +Utilization = int diff --git a/code/network-generator/network_generation/parameter_validator.py b/code/network-generator/network_generation/parameter_validator.py index 2c47299..4c1dabe 100644 --- a/code/network-generator/network_generation/parameter_validator.py +++ b/code/network-generator/network_generation/parameter_validator.py @@ -12,13 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -#!/usr/bin/python +# !/usr/bin/python """ Module containing a class for parameter validation """ import json import os import os.path +from typing import Any import jsonschema @@ -39,9 +40,8 @@ class ParameterValidator: __is_valid: bool = False # constructor - def __init__(self, args): + def __init__(self, args: list[str]) -> None: self.args = args - if len(self.args) > 1: self.__config_file = args[1] @@ -83,15 +83,17 @@ class ParameterValidator: def error_message(self) -> str: """ - Getter for the error message after validation process or an empty sting, - when configuration is valid. + Getter for the error message after validation process or an + empty sting, when configuration is valid. :return Error message as string. """ return self.__error_message # private - def __is_json_valid(self, json_data, json_schema) -> bool: + def __is_json_valid( + self, json_data: dict[str, Any], json_schema: dict[str, Any] + ) -> bool: """ Method validating json against a schema """ diff --git a/code/network-generator/network_generation/view/network_viewer.py b/code/network-generator/network_generation/view/network_viewer.py index 54ba84f..fd8c4fa 100644 --- a/code/network-generator/network_generation/view/network_viewer.py +++ b/code/network-generator/network_generation/view/network_viewer.py @@ -12,26 +12,26 @@ # See the License for the specific language governing permissions and # limitations under the License. -#!/usr/bin/python +# !/usr/bin/python """ Provides functions to convert the Network into different formats """ import json import xml.etree.ElementTree as ET +from typing import Any from network_generation.model.python.o_ran_network import ORanNetwork class NetworkViewer: """ - This class contains all functions converting the Network into different formats + This class contains all functions converting the Network into + different formats """ - __network: ORanNetwork = None - # constructor - def __init__(self, network: ORanNetwork): + def __init__(self, network: ORanNetwork) -> None: self.__network = network # json format @@ -43,7 +43,7 @@ class NetworkViewer: """ return self - def show_as_json(self) -> dict[str, dict]: + def show_as_json(self) -> None: """ Method printing the class in json format. """ @@ -62,7 +62,7 @@ class NetworkViewer: :type filename: string """ with open(filename, "w", encoding="utf-8") as json_file: - output: dict[str, dict] = self.__network.to_topology() + output: dict[str, Any] = self.__network.to_topology() json.dump(output, json_file, ensure_ascii=False, indent=2) print("File '" + filename + "' saved!") @@ -106,12 +106,12 @@ class NetworkViewer: style = ET.Element("Style", {"id": key}) line_style = ET.SubElement(style, "LineStyle") color = ET.SubElement(line_style, "color") - color.text = value["stroke"]["color"] + color.text = str(value["stroke"]["color"]) width = ET.SubElement(line_style, "width") - width.text = value["stroke"]["width"] + width.text = str(value["stroke"]["width"]) poly_style = ET.SubElement(style, "PolyStyle") fill = ET.SubElement(poly_style, "color") - fill.text = value["fill"]["color"] + fill.text = str(value["fill"]["color"]) root.findall(".//Document")[0].append(style) ET.ElementTree(root).write( -- 2.16.6