Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 29 additions & 7 deletions optical_rl_gym/envs/power_aware_rmsa_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import heapq
import logging
import functools
import sys
import numpy as np

from optical_rl_gym.utils import Service, Route
Expand Down Expand Up @@ -49,10 +50,11 @@ def __init__(self, topology=None,

self.bit_rate_lower_bound = bit_rate_lower_bound
self.bit_rate_higher_bound = bit_rate_higher_bound

self.spectrum_slots_allocation = np.full((self.topology.number_of_edges(), self.num_spectrum_resources),
fill_value=-1, dtype=np.int)

self.spectrum_power_allocation = np.full((self.topology.number_of_edges(), self.num_spectrum_resources),
fill_value=-1, dtype=np.int)
# do we allow proactive rejection or not?
self.reject_action = 1 if allow_rejection else 0

Expand Down Expand Up @@ -104,18 +106,35 @@ def step(self, action: [int]):
initial_slot, slots):
# compute OSNR and check if it's greater or equal to min_osnr, only then provision route, else service_accepted=False
sim_path = self.k_shortest_paths[self.service.source, self.service.destination][route].node_list
osnr = np.mean(propagation(launch_power, self.gnpy_network, sim_path, initial_slot, slots, self.eqpt_library))
propagate_output = propagation(
launch_power,
self.gnpy_network,
sim_path,
initial_slot,
slots,
self.eqpt_library,
self.spectrum_slots_allocation,
self.service,
self.topology
)
osnr = np.mean(propagate_output[0])
min_osnr = self.k_shortest_paths[self.service.source, self.service.destination][route].best_modulation[
"minimum_osnr"]
if osnr >= min_osnr:
self._provision_path(modulation, launch_power, # implementation of power into provision_path
self.k_shortest_paths[self.service.source, self.service.destination][route],
initial_slot, slots)
self._provision_path(
modulation,
launch_power, # implementation of power into provision_path
self.k_shortest_paths[self.service.source, self.service.destination][route],
initial_slot,
slots,
propagate_output[1]
)
self.service.accepted = True
self.actions_taken[route, modulation, initial_slot] += 1
self._add_release(self.service)
else:
self.service.accepted = False

else:
self.service.accepted = False

Expand Down Expand Up @@ -184,7 +203,7 @@ def reset(self, only_counters=True):
def render(self, mode='human'):
return

def _provision_path(self, modulation, launch_power, path: Route, initial_slot, number_slots):
def _provision_path(self, modulation, launch_power, path: Route, initial_slot, number_slots, power_values):
# usage
if not self.is_path_free(path, initial_slot, number_slots):
raise ValueError("Path {} has not enough capacity on slots {}-{}".format(path.node_list, path, initial_slot,
Expand All @@ -207,6 +226,7 @@ def _provision_path(self, modulation, launch_power, path: Route, initial_slot, n
self.service.modulation = modulation
self.service.initial_slot = initial_slot
self.service.number_slots = number_slots
self.service.power_values = power_values
self._update_network_stats()

self.services_accepted += 1
Expand Down Expand Up @@ -458,10 +478,12 @@ def shortest_available_path_first_fit_fixed_power(env: PowerAwareRMSA) -> int:
:return: action of iteration (path, modulations, spectrum resources, power)
"""
modulations = len(env.topology.graph['modulations'])
power = 0 # Fixed power variable for validation method. Gets passed through simulator.
power = 6.3 # Fixed power variable for validation method. Gets passed through simulator.
for idp, path in enumerate(env.k_shortest_paths[env.service.source, env.service.destination]):
num_slots = env.get_number_slots(path)
# print("num slots: " + str(num_slots))
for initial_slot in range(0, env.topology.graph['num_spectrum_resources'] - num_slots):
# print("env.topology.graph[...] - num_slots: " + str(env.topology.graph['num_spectrum_resources'] - num_slots))
if env.is_path_free(path, initial_slot, num_slots):
# Returned is the best_modulation of the 'modulations' object
return [idp, env.topology.graph['modulations'].index(path.best_modulation), initial_slot, power]
Expand Down
160 changes: 138 additions & 22 deletions optical_rl_gym/gnpy_utils.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
try:
from gnpy.core.elements import Transceiver, Fiber, Edfa, Roadm
from gnpy.core.utils import db2lin, lin2db, automatic_nch
from gnpy.core.info import create_input_spectral_information
from gnpy.core.info import create_input_spectral_information, Channel, Power
from gnpy.core.network import build_network
from gnpy.tools.json_io import load_equipment, network_from_json
except:
pass
from networkx import neighbors
import math


def topology_to_json(topology):
Expand All @@ -28,21 +29,84 @@ def topology_to_json(topology):
}
},
"type": "Transceiver"})
data["elements"].append({"uid": f"Roadm_{j}",
"metadata": {
"location": {
"city": "",
"region": "",
"latitude": i,
"longitude": i
}
},
"type": "Roadm"})

for node in topology.adj:
for connected_node in topology.adj[node]:
data["elements"].append({"uid": f"Fiber ({node} \u2192 {connected_node})",
"type": "Fiber",
"type_variety": "SSMF",
"params": {
"length": topology.adj[node][connected_node]['length'],
"length_units": "km",
"loss_coef": 0.2,
"con_in": 1.00,
"con_out": 1.00
}})
data["connections"].append({"from_node": node,
"to_node": f"Fiber ({node} \u2192 {connected_node})"})
data["connections"].append({"from_node": f"Fiber ({node} \u2192 {connected_node})",

# add connections between nodes using fibers such that:
# node -> fiber -> Edfa -> connected_node
# the fiber is split if length > 80 and each smaller fiber is then followed by an Edfa as well

times = topology.adj[node][connected_node]['length'] / 80.0
times_ceil = math.ceil(times)
last_bit = times - math.floor(times)

if times_ceil > 1:
for i in range(times_ceil - 1):
data["elements"].append({"uid": f"Fiber ({node} \u2192 {connected_node} {i+1}/{times_ceil})",
"type": "Fiber",
"type_variety": "SSMF",
"params": {
"length": 80.0,
"length_units": "km",
"loss_coef": 0.2,
"con_in": 1.00,
"con_out": 1.00
}})
data["elements"].append({
"uid": f"Edfa_Fiber ({node} \u2192 {connected_node} {i+1}/{times_ceil})",
"type": "Edfa",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": 120,
"tilt_target": 0,
"out_voa": 0
},
"metadata": {
"location": {
"region": "",
"latitude": 2,
"longitude": 0
}
}
})

data["connections"].append({"from_node": node,
"to_node": f"Fiber ({node} \u2192 {connected_node} {i+1}/{times_ceil})"})

data["connections"].append({"from_node": f"Fiber ({node} \u2192 {connected_node} {i+1}/{times_ceil})",
"to_node": f"Edfa_Fiber ({node} \u2192 {connected_node} {i+1}/{times_ceil})"})

if times_ceil > 1:
data["connections"].append({"from_node": f"Edfa_Fiber ({node} \u2192 {connected_node} {i+1}/{times_ceil})",
"to_node": f"Fiber ({node} \u2192 {connected_node} {times_ceil}/{times_ceil})"})
else:
data["connections"].append({"from_node": node,
"to_node": f"Fiber ({node} \u2192 {connected_node} {times_ceil}/{times_ceil})"})

data["elements"].append({"uid": f"Fiber ({node} \u2192 {connected_node} {times_ceil}/{times_ceil})",
"type": "Fiber",
"type_variety": "SSMF",
"params": {
"length": last_bit,
"length_units": "km",
"loss_coef": 0.2,
"con_in": 1.00,
"con_out": 1.00
}})


data["connections"].append({"from_node": f"Fiber ({node} \u2192 {connected_node} {times_ceil}/{times_ceil})",
"to_node": connected_node})
return data

Expand All @@ -55,7 +119,7 @@ def load_files(gnpy_topology):
return eqpt_library, gnpy_network


def propagation(input_power, network, sim_path, initial_slot, num_slots, eqpt):
def propagation(input_power, network, sim_path, initial_slot, num_slots, eqpt, slots_allocation, service, topology):
""" Calculate and output SNR based on inputs
input_power: Power in decibels
network: Network created from GNPy topology
Expand All @@ -76,32 +140,84 @@ def propagation(input_power, network, sim_path, initial_slot, num_slots, eqpt):

# Store network elements
transceivers = {n.uid: n for n in network.nodes() if isinstance(n, Transceiver)}
roadms = {n.uid: n for n in network.nodes() if isinstance(n, Roadm)}
fibers = {n.uid: n for n in network.nodes() if isinstance(n, Fiber)}
edfas = {n.uid: n for n in network.nodes() if isinstance(n, Edfa)}

# Recreate path in the GNPy network using node list from simulator
path = []

for index, node in enumerate(sim_path):
# add transceiver to path
path.append(transceivers[node])
# add roadm to path
if index == len(sim_path) - 1 or index == 0:
path.append(transceivers[node])
else:
path.append(roadms[f"Roadm_{node}"])

# add fiber connecting transceivers to path, unless source transceiver is last in path
if index + 1 < len(sim_path):
fiber_str = f"Fiber ({node} \u2192 {sim_path[index+1]})"
fiber_str = f"Fiber ({node} \u2192 {sim_path[index+1]}"
for uid in fibers:
# add all fibers to path even if they are split up
if uid[0:len(fiber_str)] == fiber_str:
path.append(fibers[uid])
# add amplifier to path, if necessary
edfa = f"Edfa0_{uid}"
edfa = f"Edfa_{uid}"
fiber_neighbors = [n.uid for n in neighbors(network, fibers[uid])]
if edfa in edfas and edfa in fiber_neighbors:
path.append(edfas[edfa])
# if edfa in edfas and edfa in fiber_neighbors:
# path.append(edfas[edfa])

current_node = 0

# Initialize the structure that stores information about the channels from adjacent services.
# This is saved in the env if this service is approved.
sim_path_si = {}

# Calculate effects of physical layer impairments
for el in path:
if isinstance(el, Roadm) or isinstance(el, Transceiver):

sim_path_si[sim_path[current_node]] = si.carriers

if current_node < len(sim_path) - 1:
adjacent_services = {}

for rs in topology.graph['running_services']:
for i in range(len(sim_path)):
if sim_path[i] in rs.route.node_list:
rs_in = rs.route.node_list.index(sim_path[i])

# A running service is considered to be adjacent if it starts at the same node as sim_path[current_node]
if rs_in + 1 < len(rs.route.node_list) and i + 1 < len(sim_path) and rs.route.node_list[rs_in + 1] == sim_path[i + 1] and rs.source == sim_path[current_node]:
adjacent_services[rs.service_id] = rs

carriers = list(si.carriers[0:num_slots])

for sid, s in adjacent_services.items():
ref = s.power_values[sim_path[current_node]]
l = len(carriers)

for i in range(len(ref)):
carriers.append(
Channel(
channel_number=l + i + 1,
frequency=(min_freq + spacing * ref[i].channel_number),
baud_rate=32e9,
roll_off=0.15,
power=ref[i].power,
chromatic_dispersion=ref[i].chromatic_dispersion,
pmd=ref[i].pmd
)
)

# Update all channels. The new carriers include the signal of the service that is currently
# being evaluated + channels from adjacent services (which were saved when those services were provisioned)
si = si._replace(carriers=carriers)

current_node += 1

si = el(si)

destination_node = path[-1]

return destination_node.snr
return [destination_node.snr, sim_path_si]
3 changes: 2 additions & 1 deletion optical_rl_gym/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
import networkx as nx
import numpy as np


class Path:
pass
class Route:

def __init__(self, route_id, node_list, length, best_modulation=None):
Expand Down