From c38b819884fc879718517b552e46f3336bac0f39 Mon Sep 17 00:00:00 2001 From: aamirq Date: Wed, 28 Apr 2021 07:42:47 -0700 Subject: [PATCH 01/12] I added a Bit Error module --- examples/singlelink.py | 1 - ofcdemo/net_simu_sawtooth.py | 1063 ++++++++++++++++++++++ ofcdemo/net_simu_sawtooth_dual_limit.py | 1074 +++++++++++++++++++++++ 3 files changed, 2137 insertions(+), 1 deletion(-) create mode 100644 ofcdemo/net_simu_sawtooth.py create mode 100644 ofcdemo/net_simu_sawtooth_dual_limit.py diff --git a/examples/singlelink.py b/examples/singlelink.py index e3322872..2e1b668e 100755 --- a/examples/singlelink.py +++ b/examples/singlelink.py @@ -63,7 +63,6 @@ def build(self): boost=boost, spans=spans) if __name__ == '__main__': - cleanup() # Just in case! setLogLevel('info') diff --git a/ofcdemo/net_simu_sawtooth.py b/ofcdemo/net_simu_sawtooth.py new file mode 100644 index 00000000..20145637 --- /dev/null +++ b/ofcdemo/net_simu_sawtooth.py @@ -0,0 +1,1063 @@ +#!/usr/bin/python +""" +single_link_test.py: test monitoring on a single link + +Note this version uses and depends on explicit port assignment! +""" + +from network import Network +from link import Span as Fiber, SpanTuple as Segment +from node import Transceiver +from units import * +from collections import defaultdict +import random +from collections import defaultdict +import numpy as np +import scipy as sp +import matplotlib.pyplot as plt +from scipy import signal + + +km = dB = dBm = 1.0 +m = .001 + +# Parameters + + +NUM_WAV = 90 +LINK_CAP = 200 +DOWN_LINK_CAP = 100 +CPRI_CAP = 25 +# ROADM port numbers (input and output) +LINE_PORT1, LINE_PORT2, LINE_PORT3, LINE_PORT4, LINE_PORT5, LINE_PORT6 = NUM_WAV, NUM_WAV+1, NUM_WAV+2, NUM_WAV+3, NUM_WAV+4, NUM_WAV+5 +NETLINKS = [] +GRAPH = defaultdict() +NODES = defaultdict() +NETLINK_INFO = defaultdict( defaultdict ) # ('node_name', 'node_name'): {channel_id: lightpath_id} +TRAFFIC_INFO = defaultdict( defaultdict ) # id : {'src':src, 'dst':dst, 'lightpath_id': lightpath_id, 'up_time':s_time, 'down_time': d_time, 'latency': 0} # id : {'path':path, 'channel':channel_id, 'up_time':s_time, 'down_time': d_time} +LIGHTPATH_INFO = defaultdict( defaultdict ) # id : {'path':path, 'channel_id': channel_id, 'traf_set': set(), 'up_time':s_time, 'down_time': d_time, 'OSNR': 25, 'GOSNR': 24.5 } +SRC_DST_TO_LIGHTPATH = defaultdict( set ) # (src, dst) : {set[lightpath_id]} +PATH_CH_TO_LIGHTPATH = defaultdict(defaultdict) # (src, hop, dst) : {'channel_id': lightpath_id} +TRAFFIC_ID = 0 +LIGHTPATH_ID = 0 +NUM_NODE = 6 +NAME_ROADM = [] +UP_TRAF_TIME_LIST = [] +UP_TRAF_ID_SET = set() +UP_LIGHTPATH_TIME_LIST = [] +UP_LIGHTPATH_ID_SET = set() +ALL_CHANNELS = [ i for i in range(1,NUM_WAV+1)] +RU_ROADMS = [] +DU_ROADMS = ['r1', 'r6'] +ROADM_TRAF = defaultdict(set) + +# Mininet-Optical +name_roadms = [] +name_terminals = [] +Roadm_Rule_ID_dict = {} +ROADM_TO_TERMINAL = {} +TERMINAL_TO_ROADM = {} +for i in range(NUM_NODE): + name_roadms.append('r%d'%(i+1)) + name_terminals.append('t%d'%(i+1)) + Roadm_Rule_ID_dict['r%d' % (i + 1)] = 1 + ROADM_TO_TERMINAL['r%d' % (i + 1)] = 't%d' % (i + 1) + TERMINAL_TO_ROADM['t%d' % (i + 1)] = 'r%d' % (i + 1) + node = 'r%d' %(i+1) + if node not in DU_ROADMS: + RU_ROADMS.append(node) + + +for i in range(NUM_NODE): + NAME_ROADM.append('r%d' % (i + 1)) + + + +# Physical model API helpers + +def Span( km, amp=None ): + "Return a fiber segment of length km with a compensating amp" + return Segment( span=Fiber( length=km ), amplifier=amp ) + +# Physical Network simulation, created out of base PHY model objects + +def RoadmPhyNetwork(): + + """ROADM network topo + """ + ############################### + # t1 - r1 ----- r2 - t2 + # | + # t4 - r4 ----- r3 - t3 + ################################ + + net = Network() + lengths = [15 * km] + + # Network nodes + transceivers = [('tx%d' % i, 0 * dBm, 'C') for i in range(1, NUM_WAV + 1)] + + # each terminal includes NUM_WAV transceivers + terminals = [ + net.add_lt(name, transceivers=transceivers, monitor_mode=mode) + for name, mode in [('t%d' % i, 'in') for i in range(1, NUM_NODE + 1)]] + roadms = [ + net.add_roadm(name, monitor_mode=mode) + for name, mode in [('r%d' % i, 'in') for i in range(1, NUM_NODE + 1)]] + + # roadms = [ net.add_roadm( 'r%d' % i ) for i in (1, 2, 3) ] + nodes = net.name_to_node + # Convenience alias + link = net.add_link + + for k in range(1, NUM_NODE): + print('==range=', k) + # Eastbound link consisting of a boost amplifier going into + # one or more segments of fiber with compensating amplifiers + boost = net.add_amplifier('boost{}{}'.format(k, k + 1), target_gain=17 * dB, boost=True) + spans = [] + for i, length in enumerate(lengths, start=1): + amp = net.add_amplifier( + 'amp{}{}-{}'.format(k, k + 1, i), target_gain=length * 0.22 * dB, monitor_mode='out') + span = Span(length, amp=amp) + spans.append(span) + + link(nodes['r%d' % k], nodes['r%d' % (k + 1)], src_out_port=LINE_PORT2, dst_in_port=LINE_PORT1, boost_amp=boost, + spans=spans) + NETLINKS.append(('r%d' % k, LINE_PORT2, 'r%d' % (k + 1), LINE_PORT1)) + NETLINK_INFO['r%d' % k, 'r%d' % (k + 1)] = {0: 0} + + # Westbound link consisting of a boost amplifier going into + # one or more segments of fiber with compensating amplifiers + boost = net.add_amplifier('boost{}{}'.format(k + 1, k), target_gain=17 * dB, boost=True) + spans = [] + for i, length in enumerate(lengths, start=1): + amp = net.add_amplifier( + 'amp{}{}-{}'.format(k + 1, k, i), target_gain=length * 0.22 * dB, monitor_mode='out') + span = Span(length, amp=amp) + spans.append(span) + + link(nodes['r%d' % (k + 1)], nodes['r%d' % k], src_out_port=LINE_PORT1, dst_in_port=LINE_PORT2, boost_amp=boost, + spans=spans) + NETLINKS.append(('r%d' % (k + 1), LINE_PORT1, 'r%d' % k, LINE_PORT2)) + NETLINK_INFO['r%d' % (k + 1), 'r%d' % k] = {0: 0} + + for k in range(1,NUM_NODE+1): + # Local add/drop links between terminals/transceivers and ROADMs + for add_drop_port in range(NUM_WAV): + link( nodes['t%d' %k], nodes['r%d' %k], src_out_port=add_drop_port, dst_in_port=add_drop_port, spans=[Span(1*m)] ) + link( nodes['r%d' %k], nodes['t%d' %k], src_out_port=add_drop_port, dst_in_port=add_drop_port, spans=[Span(1*m)] ) + + NETLINKS.append(('t%d' %k, add_drop_port, 'r%d' %k, add_drop_port)) + NETLINKS.append(('r%d' %k, add_drop_port, 't%d' %k, add_drop_port)) + + return net + +############# Mininet Optical############ + +def Mininet_installPath(lightpath_id, path, channels, graph, nodes): + "intall switch rules on roadms along a lightpath for some signal channels" + + # Install ROADM rules + print(graph, nodes) + for channel in channels: + #print(channel) + rule_path = {} + #print(path) + for i in range(1, len(path) - 1 ): + node1, roadm, node2 = path[i-1], path[i], path[i+1] + port1 = graph[ node1 ][ roadm ] + port2 = graph[ node2 ][ roadm ] + #print('==route port', i,(node1,roadm,port1), (node2,roadm,port2)) + if i == 1: + nodes[roadm].install_switch_rule(rule_id=Roadm_Rule_ID_dict[roadm], in_port=channel - 1, out_port=port2, + signal_indices=[channel]) + rule_path[roadm] = Roadm_Rule_ID_dict[roadm] + Roadm_Rule_ID_dict[roadm] += 1 + elif i == len(path) - 2: + nodes[roadm].install_switch_rule(rule_id=Roadm_Rule_ID_dict[roadm], in_port=port1, out_port=channel - 1, + signal_indices=[channel]) + rule_path[roadm] = Roadm_Rule_ID_dict[roadm] + Roadm_Rule_ID_dict[roadm] += 1 + else: + nodes[roadm].install_switch_rule(rule_id=Roadm_Rule_ID_dict[roadm], in_port=port1, out_port=port2, + signal_indices=[channel]) + rule_path[roadm] = Roadm_Rule_ID_dict[roadm] + Roadm_Rule_ID_dict[roadm] += 1 + LIGHTPATH_INFO[lightpath_id]['rule_path'] = rule_path + + +def Mininet_uninstallPath(lightpath_id, nodes): + "delete switch rules on roadms along a lightpath for some signal channels" + + + path = LIGHTPATH_INFO[lightpath_id]['path'] + rule_path = LIGHTPATH_INFO[lightpath_id]['rule_path'] + channel = LIGHTPATH_INFO[lightpath_id]['channel_id'] + Mininet_turnoffTerminalChannel(terminal=nodes[path[0]], channel=channel) + for i in range(1, len(path) - 1): + roadm = path[i] + nodes[roadm].delete_switch_rule(rule_path[roadm]) + + + +def Mininet_setupLightpath(lightpath_id, path, channel, power, graph, nodes): + channel = channel[0] + Mininet_installPath(lightpath_id, path, [channel], graph, nodes) + Mininet_configTerminalChannelPower(terminal= nodes[path[0]], channel=channel, power=power) + Mininet_voaPowerLeveling(path=path, channel=channel, power=power, graph=graph, nodes=nodes) + Mininet_configTerminalChannel(terminal=nodes[path[0]], channel=channel) + return True + + +def Mininet_teardownLightpath(lightpath_id, nodes): + + Mininet_uninstallPath(lightpath_id, nodes) + return True + + +def Mininet_voaPowerLeveling(path, channel, power, graph, nodes): + "Power control for a signal channel at a roadm using VOA leveling" + + for i in range(1, len(path) - 1): + node1, roadm, node2 = path[i - 1], path[i], path[i + 1] + if i == len(path) - 2: + nodes[roadm].configure_voa(channel_id=channel, output_port=channel - 1, operational_power_dB=power) + else: + nodes[roadm].configure_voa(channel_id=channel, output_port=graph[node2][roadm], operational_power_dB=power) + + +def Mininet_configTerminalChannelPower(terminal, channel, power): + "Congifure Terminal Launch power for a channel" + + terminal.name_to_transceivers['tx%d'% channel].operation_power = db_to_abs(power) + + +def Mininet_configTerminalChannel(terminal, channel): + "Turn on a Terminal with a given channel" + + terminal.configure_terminal(transceiver=terminal.transceivers[channel-1], channel=channel) + terminal.turn_on() + + +def Mininet_turnoffTerminalChannel(terminal, channel): + "Turn on a Terminal with a given channel" + + terminal.turn_off([channel-1]) + + +def Mininet_monitorAll(node): + "monitoring all data at a node" + + return node.monitor.get_dict_power(),node.monitor.get_dict_osnr(), node.monitor.get_dict_gosnr() + + +def Mininet_monitorLightpath(path, channel, nodes): + "monitoring a signal along a lightpath" + #print('monitor_path_ch', path, channel) + + powers = list() + osnrs = list() + gosnrs = list() + ase_noise = list() + nli_noise = list() + freq = round((191.30 + 0.05*channel)*10**12,1) + for i in range(1, len(path) - 1): + name = path[i] + node = nodes[name] + optical_signals = node.monitor.extract_optical_signal() + for sig in optical_signals: + if freq==sig[0].frequency: + if node.monitor.mode == 'out': + output_power = (sig[0].loc_out_to_state[node.monitor.component]['power']) + ase = (sig[0].loc_out_to_state[node.monitor.component]['ase_noise']) + nli = (sig[0].loc_out_to_state[node.monitor.component]['nli_noise']) + else: + output_power = (sig[0].loc_in_to_state[node.monitor.component]['power']) + ase = (sig[0].loc_in_to_state[node.monitor.component]['ase_noise']) + nli = (sig[0].loc_in_to_state[node.monitor.component]['nli_noise']) + gosnr_linear = output_power / (ase + nli * (12.5e9 / 32.0e9)) + gosnr = abs_to_db(gosnr_linear) + osnr_linear = output_power / ase + osnr = abs_to_db(osnr_linear) + powers.append(output_power) + osnrs.append(osnr) + gosnrs.append(gosnr) + ase_noise.append(ase) + nli_noise.append(nli) + #powers.append((name,output_power)) + #osnrs.append((name,osnr)) + #gosnrs.append((name,gosnr)) + return powers, osnrs, gosnrs, ase_noise, nli_noise + +################# END #################### + + +################ CONTROL PLANE ##################### + +def linkspec( link ): + "Return specifier dict(node1, port1, node2, port2) for link" + node1, node2, port1, port2 = link[0], link[2], link[1], link[3] + return { node1:port1, node2:port2 } + + +def getLinks(): + + return dict( links=[ linkspec( link ) for link in NETLINKS ] ) + + +def netGraph( links ): + "Return an adjacency dict for links" + # Note we only have to worry about single links between nodes + # We handle the terminals separately + neighbors = defaultdict( defaultdict ) + for link in links: + #print(link) + src, dst = link # link is a dict but order doesn't matter + srcport, dstport = link[ src ], link[ dst ] + neighbors.setdefault( src, {} ) + neighbors[ src ][ dst ] = dstport + neighbors[ dst ][ src ] = srcport + return dict( neighbors ) + + +def FindRoute( src, graph, destinations, k=10): + """Route from src to destinations + neighbors: adjacency list + returns: routes dict""" + routes, seen, paths = defaultdict(list), set( (src,) ), [ (src,) ] + while paths: + path = paths.pop( 0 ) + lastNode = path[ -1 ] + for neighbor in graph[ lastNode ]: + if neighbor not in path: + newPath = ( path + (neighbor, ) ) + paths.append( newPath ) + if neighbor in destinations and len(routes[ neighbor ]) < k: + routes[ neighbor ].append(newPath) + return routes + + +def shortestPath(): + return + +def pathSelection(paths, cur_time, waiting_time_threshold=5.0, short_duration=False): + new_paths = [] + for path in paths: + avai_channels = waveAvailibility(path) + occupied_channels = set(ALL_CHANNELS).difference(avai_channels) + max_waiting_time = defaultdict(lambda: 0) + for i in range(len(path) - 1): + for j in occupied_channels: + if j in NETLINK_INFO[path[i], path[i + 1]].keys(): + lightpath_id = NETLINK_INFO[path[i], path[i + 1]][j] + waiting_time = LIGHTPATH_INFO[lightpath_id]['down_time'] - cur_time + # print('waiting', j, waiting_time) + max_waiting_time[j] = max(waiting_time, max_waiting_time[j]) + possible_channels = set() + # print('waiting', path, max_waiting_time) + for ch in occupied_channels: + # print(max_waiting_time[ch]) + if max_waiting_time[ch] < waiting_time_threshold: + possible_channels.add(ch) + ## if this lightpath will be teared town soon + ## if this lightpath will be teared town soon + if short_duration: + new_paths.append((-len(possible_channels), -len(avai_channels), len(path), path)) + else: + new_paths.append((-len(possible_channels)-len(avai_channels), -len(avai_channels), len(path), path)) + new_paths.sort() + #print('sort_path', new_paths) + return new_paths + + +def waveAvailibility(path): + avai_channels = set([i for i in range(NUM_WAV + 1)]) + for i in range(len(path) - 1): + link_channels = set(NETLINK_INFO[path[i], path[i + 1]].keys()) + avai_channels = avai_channels.difference(link_channels) + return avai_channels + + +def waveSelection(channels): + channels = list(channels) + return random.choice(channels) + + +def install_Lightpath(path, channel, up_time=0.0, down_time = float('inf')): + "intall switch rules on roadms along a lightpath for some signal channels" + + ## Install ROADM rules + global LIGHTPATH_ID + LIGHTPATH_ID += 1 + for i in range(len(path) - 1): + NETLINK_INFO[path[i], path[i + 1]][channel] = LIGHTPATH_ID # channel with lightpath_id + NETLINK_INFO[path[i + 1], path[i]][channel] = LIGHTPATH_ID + # id : {'path':path, 'channel': channel_id, 'traf': set(), 'up_time':s_time, 'down_time': d_time, 'OSNR': 25, 'GOSNR': 24.5 } + Mininet_setupLightpath(lightpath_id=LIGHTPATH_ID, path=path, power=-1, channel=[channel], graph=GRAPH, nodes=NODES) + powers, osnrs, gosnrs, ase, nli = Mininet_monitorLightpath(path, channel, NODES) + LIGHTPATH_INFO[LIGHTPATH_ID]['path'] = path + LIGHTPATH_INFO[LIGHTPATH_ID]['channel_id'] = channel + LIGHTPATH_INFO[LIGHTPATH_ID]['link_cap'] = LINK_CAP + LIGHTPATH_INFO[LIGHTPATH_ID]['traf_set'] = set() + LIGHTPATH_INFO[LIGHTPATH_ID]['up_time'] = up_time + LIGHTPATH_INFO[LIGHTPATH_ID]['down_time'] = down_time + LIGHTPATH_INFO[LIGHTPATH_ID]['power'] = abs_to_db(powers[-1]) + LIGHTPATH_INFO[LIGHTPATH_ID]['OSNR'] = osnrs[-1] + LIGHTPATH_INFO[LIGHTPATH_ID]['GOSNR'] = gosnrs[-1] + # (src, dst) : {1,2,3,4,5} ##lightpath_id + SRC_DST_TO_LIGHTPATH[path[0], path[-1]].add(LIGHTPATH_ID) + # (src, hop, dst) : {'channel_id': lightpath_id} + PATH_CH_TO_LIGHTPATH[path][channel] = LIGHTPATH_ID + UP_LIGHTPATH_TIME_LIST.append((down_time, LIGHTPATH_ID)) + UP_LIGHTPATH_TIME_LIST.sort() + UP_LIGHTPATH_ID_SET.add(LIGHTPATH_ID) + + + return LIGHTPATH_ID + + +def check_lightpath_for_traf(src, dst): + ''' + check if there are some provisioned lighpaths for CPRI traf + ''' + lighpaths = SRC_DST_TO_LIGHTPATH[src,dst] + avai_lightpaths = set() + for lighpath_id in lighpaths: + if len(LIGHTPATH_INFO[lighpath_id]['traf_set']) < LIGHTPATH_INFO[lighpath_id]['link_cap']/CPRI_CAP: + avai_lightpaths.add(lighpath_id) + return avai_lightpaths + + +def select_lightpath_by_latency(avai_lightpaths, latency=0): + for lightpath_id in avai_lightpaths: + path = LIGHTPATH_INFO[lightpath_id]['path'] + GOSNR = LIGHTPATH_INFO[lightpath_id]['GOSNR'] + if latency == 0 or latency == 1: + if len(path) < 4 and GOSNR > 25: + return lightpath_id + else: + return lightpath_id + return False + + +def update_lightpath_down_time(lightpath_id, down_time): + for d_time, id in UP_LIGHTPATH_TIME_LIST: + if id == lightpath_id: + UP_LIGHTPATH_TIME_LIST.remove((d_time, id)) + UP_LIGHTPATH_TIME_LIST.append((down_time, lightpath_id)) + UP_LIGHTPATH_TIME_LIST.sort() + + +def traf_to_lightpah_Assignment(traf_id, lightpath_id, down_time = float('inf')): + # (src, hop, dst) : {'channel_id': lightpath_id} + LIGHTPATH_INFO[lightpath_id]['traf_set'].add(traf_id) + path = LIGHTPATH_INFO[lightpath_id]['path'] + if down_time > LIGHTPATH_INFO[lightpath_id]['down_time']: + LIGHTPATH_INFO[lightpath_id]['down_time'] = down_time + update_lightpath_down_time(lightpath_id, down_time) + # traf_id : {'src':src, 'dst':dst, 'lightpath_id': lightpath_id, 'up_time':s_time, 'down_time': d_time, 'latency': 0} + TRAFFIC_INFO[traf_id]['src'], TRAFFIC_INFO[traf_id]['dst'], TRAFFIC_INFO[traf_id]['lightpath_id'] = path[0], path[-1], lightpath_id + UP_TRAF_TIME_LIST.append((down_time, traf_id)) + UP_TRAF_TIME_LIST.sort() + UP_TRAF_ID_SET.add(traf_id) + return traf_id + + +def install_Traf(src, dst, routes, cur_time, down_time=float('inf'), latency = 0, RWA = True): + ''' + source RRH node to destination BBU node + latency: 0 for ultra-low: only use provisioned lightpaths with high BW and BER, + 1 low latency: can setup lightpath but need high BW and high BER/GOSNR, + 2 no latency requirement: any lightpath + ''' + global TRAFFIC_ID + avai_lightpaths = check_lightpath_for_traf(src, dst) + lightpath_id = select_lightpath_by_latency(avai_lightpaths, latency) + #print('---avai_lightpaths, select lightpath_id--', avai_lightpaths, lightpath_id) + if latency == 0 : + if lightpath_id: + TRAFFIC_ID += 1 + traf_id = traf_to_lightpah_Assignment(TRAFFIC_ID, lightpath_id, down_time=down_time) + LIGHTPATH_INFO[lightpath_id]['traf_set'].add(TRAFFIC_ID) + return traf_id + elif latency == 1: + if lightpath_id: + TRAFFIC_ID += 1 + traf_id = traf_to_lightpah_Assignment(TRAFFIC_ID, lightpath_id, down_time=down_time) + LIGHTPATH_INFO[lightpath_id]['traf_set'].add(TRAFFIC_ID) + return traf_id + else: + if RWA: + all_path_info = pathSelection(paths= routes[src][dst], cur_time = cur_time, waiting_time_threshold=5.0, short_duration=True) + else: + all_path_info = routes[src][dst] + # [ (len(possible_channels), len(ava_channls), len(path), paths), ... ] + for path_info in all_path_info: + if RWA: + path = path_info[3] + else: + path = path_info + if len(path)>=4: + continue + chs = waveAvailibility(path=path) + if chs: + count = 0 + while count < 5 and chs: + count += 1 + ch = waveSelection(chs) + chs.remove(ch) + lightpath_id = install_Lightpath(path=path, channel=ch, up_time=cur_time, down_time=down_time) + GOSNR = LIGHTPATH_INFO[lightpath_id]['GOSNR'] + if GOSNR > 25: + TRAFFIC_ID += 1 + traf_id = traf_to_lightpah_Assignment(TRAFFIC_ID, lightpath_id, down_time=down_time) + LIGHTPATH_INFO[lightpath_id]['traf_set'].add(TRAFFIC_ID) + return traf_id + else: + uninstall_Lightpath(lightpath_id) + elif latency == 2: + if lightpath_id: + TRAFFIC_ID += 1 + traf_id = traf_to_lightpah_Assignment(TRAFFIC_ID, lightpath_id, down_time=down_time) + LIGHTPATH_INFO[lightpath_id]['traf_set'].add(TRAFFIC_ID) + return traf_id + else: + if RWA: + all_path_info = pathSelection(paths=routes[src][dst], cur_time=cur_time, waiting_time_threshold=5.0, + short_duration=True) + else: + all_path_info = routes[src][dst] + # [ (len(possible_channels), len(ava_channls), len(path), paths), ... ] + for path_info in all_path_info: + if RWA: + path = path_info[3] + else: + path = path_info + chs = waveAvailibility(path=path) + if chs: + count = 0 + while count < 5 and chs: + count += 1 + ch = waveSelection(chs) + chs.remove(ch) + lightpath_id = install_Lightpath(path=path, channel=ch, up_time=cur_time, down_time=down_time) + TRAFFIC_ID += 1 + traf_id = traf_to_lightpah_Assignment(TRAFFIC_ID, lightpath_id, down_time=down_time) + LIGHTPATH_INFO[lightpath_id]['traf_set'].add(TRAFFIC_ID) + return traf_id + return False + + +def uninstall_Lightpath(lightpath_id): + "delete switch rules on roadms along a lightpath for some signal channels" + Mininet_uninstallPath(lightpath_id=lightpath_id, nodes=NODES) + path = LIGHTPATH_INFO[lightpath_id]['path'] + channel = LIGHTPATH_INFO[lightpath_id]['channel_id'] + for i in range(len(path) - 1): + del NETLINK_INFO[path[i], path[i + 1]][channel] + del NETLINK_INFO[path[i + 1], path[i]][channel] + #print(PATH_CH_TO_LIGHTPATH) + lightpath_id = PATH_CH_TO_LIGHTPATH[path][channel] + #print('==', lightpath_id) + del LIGHTPATH_INFO[lightpath_id] + del PATH_CH_TO_LIGHTPATH[path][channel] + SRC_DST_TO_LIGHTPATH[path[0], path[-1]].remove(lightpath_id) + UP_LIGHTPATH_ID_SET.remove(lightpath_id) + + return lightpath_id + + +def traf_to_lightpath_Release(traf_id): + lightpath_id = TRAFFIC_INFO[traf_id]['lightpath_id'] + LIGHTPATH_INFO[lightpath_id]['traf_set'].remove(traf_id) + del TRAFFIC_INFO[traf_id] + UP_TRAF_ID_SET.remove(traf_id) + return traf_id + +################# END ################### + +def analytic_traffic(time, shift=0, floor=0.0001, timelength=8, days=1, pattern = 'sawtooth', source='office'): + """Graphs a sawtooth traffic pattern as a means to analytically study the system. + _/\_/\_/\_/\_/\_/\_/\_/\ This allows is to examine analytical properties of the + system.""" + time = time - 0.000125 + if pattern == 'sawtooth': + if source == 'office': + if time % 24 < timelength: + factor = ((sp.signal.sawtooth(2 * np.pi * (time % 24) / timelength, 1) + 1) / 2) * (1-floor+.001) + floor + return factor + else: + return floor + if source == 'resident': + if shift < (float(time) % 24) and (float(time) % 24) < (timelength + shift): + factor = ((sp.signal.sawtooth(2 * np.pi * ((time % 24) -shift) / timelength, 1) + 1) / 2) * (1-floor+.001) + floor + return factor + else: + return floor + if pattern == 'triangle': + if source == 'office': + if time % 24 < timelength: + factor = ((sp.signal.sawtooth(2 * np.pi * (time % 24) / timelength, 0.5) + 1) / 2) * (1-floor) + floor + return factor + else: + return floor + if source == 'resident': + if shift < (float(time) % 24) and (float(time) % 24) < (timelength + shift): + factor = ((sp.signal.sawtooth(2 * np.pi * ((time % 24) -shift) / timelength, 0.5) + 1) / 2) * (1-floor) + floor + return factor + else: + return floor + + if pattern == 'impulse': + if source == 'office': + if time % 24 < timelength: + factor = 1 + return factor + else: + return floor + if source == 'resident': + if shift < (float(time) % 24) and (float(time) % 24) < (timelength + shift): + factor = 1 + return factor + else: + return floor + if pattern == 'step_down': + if source == 'office': + if time % (24*days) < timelength: + factor = 1 + return factor + else: + return floor + if source == 'resident': + if shift < (float(time) % (24*days)) and (float(time) % (24*days)) < (timelength + shift): + factor = 1 + return factor + else: + return floor + + else: + raise ValueError('Error: function not found') + + + +def TrafficTest(shift=0, floor=0.0001, timelength=8, pattern = 'sawtooth',load=35000, days=7, filename = 'sawtooth_results.txt'): + """Create an analytical study of Mininet Optical's behavious. This will be done using + a sawtooth simulation of relevent data to produce results.""" + net = RoadmPhyNetwork() + AllLinks = getLinks() + global GRAPH, NODES + GRAPH = netGraph(AllLinks['links']) + NODES = net.name_to_node + routes = {node: FindRoute(node, GRAPH, name_terminals) + for node in name_terminals} + + print('===links', AllLinks['links']) + print('===graph', GRAPH) + print('==link_info', NETLINK_INFO) + print('==route') + + for key in routes.keys(): + print(key, routes[key]) + + + Total_Rej = 0 + N = 24*days #Hours + file = open(filename, 'w') + # Overall Traffic information + Total_traf = load # Gbps + MAX_traf = {} + for i in range(2,NUM_NODE): #Includes ROADMs 2,3,4,5, ROADM 1,2 are BBU nodes + MAX_traf['r%d' %(i)] = 1.0*Total_traf/(len(RU_ROADMS)) + print('---max_traf',MAX_traf) + + + BBU_traf = {} + BBU_limit = {} + #BBU_limit['t1'] = 250 #* float('inf') + BBU_limit['t%d' % NUM_NODE] = 250 #* float('inf') + for node in DU_ROADMS: + BBU_traf[ROADM_TO_TERMINAL[node]] = 0 + RRH_traf = {} + for node in RU_ROADMS: + RRH_traf[ROADM_TO_TERMINAL[node]] = 0 + + ROADM_TYPE = {'r2':'office', 'r3':'resident', 'r4':'resident', 'r5':'office'} + + file.write( + 'time, r2_traf, r3_traf, r4_traf, r5_traf, ' + 'number_of_lightpath, avg_wav_per_link, r1_BBU_traf, r6_BBU_traf,' + ' r2_rej, r3_rej, r4_rej, r5_rej,' + ' 50G, 100G, 200G, underutilized, Total_cap\n') + + for i in range(N): + Rej = {} + for key in TERMINAL_TO_ROADM.keys(): + Rej[key] = 0 + print(i) #Prints the hour + factors = {} + for src in RU_ROADMS: + f = analytic_traffic(time=i, shift=shift, floor=floor, timelength=timelength, days=days, pattern=pattern, + source=ROADM_TYPE[src]) + factors[src] = f + factor = f + src_t = ROADM_TO_TERMINAL[src] + count = 0 + while factor*MAX_traf[src]/CPRI_CAP > len(ROADM_TRAF[src]): + ADD_TRAF = False + print(factor*MAX_traf[src]/CPRI_CAP, len(ROADM_TRAF[src]), Rej) + dst = random.choice(DU_ROADMS) + + RRH_traf[src_t] += 1 + path_first = routes[src_t]['t1'][0] + path_last = routes[src_t]['t%d' % NUM_NODE][0] + print('two_paths', path_first, path_last) + if len(path_last) DOWN_LINK_CAP/CPRI_CAP: + traf_id = random.choice(list(traf_set)) + s_t, d_t = TRAFFIC_INFO[traf_id]['src'], TRAFFIC_INFO[traf_id]['dst'] + ROADM_TRAF[TERMINAL_TO_ROADM[s_t]].remove(traf_id) + traf_to_lightpath_Release(traf_id=traf_id) + reassign_traf.append((s_t, d_t)) + elif gosnrs[-1] < 16: + LIGHTPATH_INFO[lightpath_id]['link_cap'] = 50 + traf_set = LIGHTPATH_INFO[lightpath_id]['traf_set'] + while len(traf_set) > DOWN_LINK_CAP/CPRI_CAP: + traf_id = random.choice(list(traf_set)) + s_t, d_t = TRAFFIC_INFO[traf_id]['src'], TRAFFIC_INFO[traf_id]['dst'] + ROADM_TRAF[TERMINAL_TO_ROADM[s_t]].remove(traf_id) + traf_to_lightpath_Release(traf_id=traf_id) + reassign_traf.append((s_t, d_t)) + else: + LIGHTPATH_INFO[lightpath_id]['link_cap'] = LINK_CAP + + + for s_t, d_t in reassign_traf: + traf_id = install_Traf(s_t, d_t, routes, cur_time=0, down_time=float('inf'), latency=2, + RWA=False) + if traf_id: + ROADM_TRAF[TERMINAL_TO_ROADM[s_t]].add(traf_id) + else: + Rej[s_t] += 1 + Total_Rej += 1 + BBU_traf[d_t] -= 1 + + elif dst_back: + dst = dst_back + dst_t = dst_t_back + print('try_backup_path', src, dst) + traf_id = install_Traf(src_t, dst_t, routes, cur_time=0, down_time=float('inf'), latency=2, + RWA=False) + if traf_id: + ADD_TRAF = True + ROADM_TRAF[src].add(traf_id) + BBU_traf[dst_t] += 1 + reassign_traf = [] + fail_lightpaths = [] + for lightpath_id, info in LIGHTPATH_INFO.items(): + powers, osnrs, gosnrs, ase, nli = Mininet_monitorLightpath(path=info['path'], + channel=info['channel_id'], + nodes=NODES) + LIGHTPATH_INFO[lightpath_id]['GOSNR'] = gosnrs[-1] + LIGHTPATH_INFO[lightpath_id]['OSNR'] = osnrs[-1] + if 18 < gosnrs[-1] < 24: + LIGHTPATH_INFO[lightpath_id]['link_cap'] = DOWN_LINK_CAP + traf_set = LIGHTPATH_INFO[lightpath_id]['traf_set'] + while len(traf_set) > DOWN_LINK_CAP / CPRI_CAP: + traf_id = random.choice(list(traf_set)) + s_t, d_t = TRAFFIC_INFO[traf_id]['src'], TRAFFIC_INFO[traf_id]['dst'] + ROADM_TRAF[TERMINAL_TO_ROADM[s_t]].remove(traf_id) + traf_to_lightpath_Release(traf_id=traf_id) + reassign_traf.append((s_t, d_t)) + elif gosnrs[-1] < 16: + LIGHTPATH_INFO[lightpath_id]['link_cap'] = 50 + traf_set = LIGHTPATH_INFO[lightpath_id]['traf_set'] + while len(traf_set) > DOWN_LINK_CAP / CPRI_CAP: + traf_id = random.choice(list(traf_set)) + s_t, d_t = TRAFFIC_INFO[traf_id]['src'], TRAFFIC_INFO[traf_id]['dst'] + ROADM_TRAF[TERMINAL_TO_ROADM[s_t]].remove(traf_id) + traf_to_lightpath_Release(traf_id=traf_id) + reassign_traf.append((s_t, d_t)) + else: + LIGHTPATH_INFO[lightpath_id]['link_cap'] = LINK_CAP + + for s_t, d_t in reassign_traf: + traf_id = install_Traf(s_t, d_t, routes, cur_time=0, down_time=float('inf'), latency=2, + RWA=False) + if traf_id: + ROADM_TRAF[TERMINAL_TO_ROADM[s_t]].add(traf_id) + else: + Rej[s_t] += 1 + Total_Rej += 1 + BBU_traf[d_t] -= 1 + + if not ADD_TRAF: + Rej[src_t] += 1 + Total_Rej += 1 + count += 1 + if count == 10: + fails = factor*MAX_traf[src]/CPRI_CAP - len(ROADM_TRAF[src]) + RRH_traf[src_t] += fails + Rej[src_t] += fails + Total_Rej += fails + break + + while factor * MAX_traf[src] / CPRI_CAP < len(ROADM_TRAF[src]): + traf_id = random.choice(list(ROADM_TRAF[src])) + dst = TRAFFIC_INFO[traf_id]['dst'] + BBU_traf[dst] -= 1 + lightpath_id = TRAFFIC_INFO[traf_id]['lightpath_id'] + traf_set = LIGHTPATH_INFO[lightpath_id]['traf_set'] + traf_to_lightpath_Release(traf_id=traf_id) + ROADM_TRAF[src].remove(traf_id) + if not traf_set: + uninstall_Lightpath(lightpath_id=lightpath_id) + + + + """while UP_LIGHTPATH_TIME_LIST and UP_LIGHTPATH_TIME_LIST[0][0]< time: + lightpath_id = UP_LIGHTPATH_TIME_LIST.pop(0)[1] + uninstall_Lightpath(lightpath_id=lightpath_id) + #""" + OneG = 0 + TwoG = 0 + FiftyG = 0 + UnderUse = 0 + for lightpath_id, info in LIGHTPATH_INFO.items(): + if LIGHTPATH_INFO[lightpath_id]['link_cap']/CPRI_CAP/2 > len(LIGHTPATH_INFO[lightpath_id]['traf_set']): + UnderUse += 1 + link_cap = LIGHTPATH_INFO[lightpath_id]['link_cap'] + if link_cap == 100: + OneG += 1 + elif link_cap ==200: + TwoG += 1 + elif link_cap ==50: + FiftyG += 1 + total_wav = 0 + for key in NETLINK_INFO.keys(): + total_wav += len(NETLINK_INFO[key].items()) + avg_wav = (1.0 * total_wav ) / (NUM_NODE-1) + + """file.write('{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\n'.format(i, factors['r2'] * MAX_traf['r2'], factors['r3'] * MAX_traf['r3'], + len(LIGHTPATH_INFO.keys()), avg_wav,BBU_traf['t1'], BBU_traf['t%d' % NUM_NODE], + 1.0*Rej['t2']/(factors['r2']*MAX_traf['r2']/CPRI_CAP), 1.0*Rej['t3']/(factors['r3']*MAX_traf['r3']/CPRI_CAP), + OneG, TwoG, UnderUse, OneG*100+TwoG*200)) + #""" + print(Rej['t2'], '/', factors['r2'], MAX_traf['r2'], CPRI_CAP) + file.write('{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}\n'.format( + i, factors['r2'] * MAX_traf['r2'], + factors['r3'] * MAX_traf['r3'], + factors['r4'] * MAX_traf['r4'], + factors['r5'] * MAX_traf['r5'], + len(LIGHTPATH_INFO.keys()), avg_wav, + BBU_traf['t1'], + BBU_traf['t%d' % NUM_NODE], + 1.0 * Rej['t2'] / (factors['r2'] * MAX_traf['r2'] / CPRI_CAP), + 1.0 * Rej['t3'] / (factors['r3'] * MAX_traf['r3'] / CPRI_CAP), + 1.0 * Rej['t4'] / (factors['r4'] * MAX_traf['r4'] / CPRI_CAP), + 1.0 * Rej['t5'] / (factors['r5'] * MAX_traf['r5'] / CPRI_CAP), + FiftyG, OneG, TwoG, UnderUse, + OneG * 100 + TwoG * 200 + FiftyG * 50) + ) + #""" + print('==traf') + for item in TRAFFIC_INFO.items(): + print(item) + print('==Lightpath') + fail_paths= [] + for item in LIGHTPATH_INFO.items(): + print(item) + path = item[1]['path'] + ch = item[1]['channel_id'] + gosnr = item[1]['GOSNR'] + powers, osnrs, gosnrs, ase, nli = Mininet_monitorLightpath(path=path, channel=ch, nodes=NODES) + print('gosnr', gosnr, gosnrs[-1]) + if gosnrs[-1]< 24: + fail_paths.append((gosnrs[-1],osnrs[-1], powers[-1], item)) + for gosnr, osnr, power, item in fail_paths: + print('fail', gosnr, osnr, abs_to_db(power), item) + #print('==rej', Rej, 1.0*Rej/N) + print('r2-r1, chs', len(NETLINK_INFO['r2', 'r1'].items())) + print('r2-r3, chs', len(NETLINK_INFO['r2', 'r3'].items())) + print('r3-r4, chs', len(NETLINK_INFO['r3', 'r4'].items())) + for roadm in ROADM_TRAF: + print(roadm, ROADM_TRAF[roadm]) + print('BBU_Processing_traf', BBU_traf) + print('rej', Rej) + print('RRH_TRAF', RRH_traf) + print('rej_ratio', 1.0*Rej['t2']/RRH_traf['t2'], 1.0*Rej['t3']/RRH_traf['t3']) + print('total_rej_rate', 1.0* Total_Rej/sum(RRH_traf.values()) ) + + # for lightpath_id, info in LIGHTPATH_INFO.items(): + # powers, osnrs, gosnrs, ase, nli = Mininet_monitorLightpath(path=info['path'], channel=info['channel_id'], + # nodes=NODES) + # print('id: {}, path: {}, power: {}'.format( lightpath_id, info['path'], powers ) ) + + +# Physical model test +def RoadmPhyTest(): + # ROADM port numbers (input and output) + LINE_PORT1 = NUM_WAV + LINE_PORT2 = NUM_WAV+1 + + "Create a single link and monitor its OSNR and gOSNR" + net = RoadmPhyNetwork() + AllLinks = getLinks() + global GRAPH, NODES + GRAPH = netGraph(AllLinks['links']) + NODES = net.name_to_node + routes = {node: FindRoute(node, GRAPH, name_terminals) + for node in name_terminals} + # routes = {node: FindRoute(node, GRAPH, NAME_ROADM) + # for node in NAME_ROADM} + print('===links', AllLinks['links']) + print('===graph', GRAPH) + print('==link_info', NETLINK_INFO) + print('==route') + for key in routes.keys(): + print(key, routes[key]) + + ## add traffic and delete traffic + Rej = 0 + time = 0 # 86400s for one day + setup_time = 0.5/60.0 # MINUTE + N = 2000 # 1000000 for one day + file = open('record.txt', 'w') + # CPRI Request + arrival_rate = 50 # request/minute + holding_time = 30 # minute + factor = 1 + for i in range(N): + print(i) + #factor = trafficPattern(time) + t_arrival_rate = arrival_rate*factor + s_time = 1.0/t_arrival_rate + time #random.uniform(0.01, 0.02) + time # + duration = random.choice(np.random.poisson(holding_time, 10000)) + #duration = random.uniform(30, 600) + d_time = s_time + duration + time = s_time + setup_time + src = random.choice(RU_ROADMS) + dst = random.choice(DU_ROADMS) + #src = random.choice(NAME_ROADM) + #dst = random.choice(NAME_ROADM) + while dst == src: + dst = random.choice(NAME_ROADM) + + latency = random.uniform(0, 1) + if latency<0.1: + latency = 0 + elif latency<0.4: + latency = 1 + else: + latency = 2 + src = ROADM_TO_TERMINAL[src] + dst = ROADM_TO_TERMINAL[dst] + if not install_Traf(src, dst, routes, cur_time= s_time, down_time=d_time, latency=2, RWA= False): + Rej += 1 + else: + ROADM_TRAF[src].add(TRAFFIC_ID) + while UP_TRAF_TIME_LIST and UP_TRAF_TIME_LIST[0][0]< time: + traf_id = UP_TRAF_TIME_LIST.pop(0)[1] + traf_to_lightpath_Release(traf_id=traf_id) + print('~~', traf_id) + ROADM_TRAF[src].remove(traf_id) + while UP_LIGHTPATH_TIME_LIST and UP_LIGHTPATH_TIME_LIST[0][0]< time: + lightpath_id = UP_LIGHTPATH_TIME_LIST.pop(0)[1] + uninstall_Lightpath(lightpath_id=lightpath_id) + #""" + file.write('{}\t{}\n'.format(time, 1.0*Rej/(i+1))) + print('==traf') + for item in TRAFFIC_INFO.items(): + print(item) + print('==Lightpath') + fail_paths= [] + for item in LIGHTPATH_INFO.items(): + print(item) + if item[-1]['GOSNR']< 24: + fail_paths.append(item) + print('==time', time) + for item in fail_paths: + print('fail',item) + print('==rej', Rej, 1.0*Rej/N) + print('r2-r1, chs', len(NETLINK_INFO['r2', 'r1'].items())) + print('r2-r3, chs', len(NETLINK_INFO['r2', 'r3'].items())) + print('r3-r4, chs', len(NETLINK_INFO['r3', 'r4'].items())) + print(ROADM_TRAF) + +def QuickTest(shift=0, floor=0.0001, timelength=8, pattern = 'sawtooth',load=35000, days=7, filename = 'sawtooth_results.txt'): + days = 7 + time = np.arange(0, 24 * days) + office = [] + resident = [] + for hour in time: + office.append(analytic_traffic(hour, shift=shift, floor=floor, timelength=timelength, days=days, + pattern = pattern, source='office')) + resident.append(analytic_traffic(hour, shift=shift, floor=floor, timelength=timelength, days=days, + pattern = pattern, source='resident')) + plt.plot(time, resident, office) + plt.show() +if __name__ == '__main__': + #TrafficTest(shift=0, floor=0.01, timelength=4, pattern='triangle', load=35000, days=7, filename='Control.txt') + #TrafficTest(shift=0, floor=0.01, timelength=8, pattern='triangle', load=35000, days=7, filename='Control_s1.txt') + #TrafficTest(shift=0, floor=0.01, timelength=10, pattern='triangle', load=35000, days=7, filename='Control_s2.txt') + #TrafficTest(shift=0, floor=0.01, timelength=12, pattern='triangle', load=35000, days=7, filename='Control_s4.txt') + #TrafficTest(shift=0, floor=0.01, timelength=20, pattern='sawtooth', load=35000, days=7, filename='Control_s8 (2).txt') #check for wierd traffic_load error + + #TrafficTest(shift=0, floor=0.1, timelength=1, pattern='step_down', load=35000, days=7, filename='step_1.txt') + #TrafficTest(shift=0, floor=0.1, timelength=8, pattern='step_down', load=35000, days=7, filename='step_8.txt') + #TrafficTest(shift=0, floor=0.1, timelength=24, pattern='step_down', load=35000, days=7, filename='step_24.txt') + #TrafficTest(shift=0, floor=0.1, timelength=48, pattern='step_down', load=35000, days=7, filename='step_48.txt') + #TrafficTest(shift=0, floor=0.1, timelength=96, pattern='step_down', load=35000, days=7, filename='step_96.txt') + + #TrafficTest(shift=2, floor=0.001, timelength=8, pattern='impulse', load=35000, days=7, filename='Control_f=e-32.txt') + #TrafficTest(shift=2, floor=0.01, timelength=8, pattern='impulse', load=35000, days=7, filename='Control_f=e-2.txt') + #TrafficTest(shift=2, floor=0.1, timelength=8, pattern='impulse', load=35000, days=7, filename='Control_f=e-1.txt') + #TrafficTest(shift=2, floor=0.2, timelength=8, pattern='impulse', load=35000, days=7, filename='Control_f=2e-1.txt') + + #TrafficTest(shift=0, floor=0.1, timelength=1, pattern='step_down', load=35000, days=7, filename='step_1.txt') + #TrafficTest(shift=0, floor=0.1, timelength=8, pattern='step_down', load=35000, days=7, filename='step_8.txt') + + #TrafficTest(shift=0, floor=0.1, timelength=24, pattern='step_down', load=35000, days=7, filename='step_24.txt') + #TrafficTest(shift=0, floor=0.1, timelength=48, pattern='step_down', load=35000, days=7, filename='step_48.txt') + #TrafficTest(shift=0, floor=0.1, timelength=96, pattern='step_down', load=35000, days=7, filename='step_96.txt') + + TrafficTest(shift=0, floor=0.001, timelength=8, pattern='sawtooth', load=35000, days=7, filename='Control.txt') + #TrafficTest(shift=0, floor=0.1, timelength=4, pattern='sawtooth', load=35000, days=7, filename='rev-sawtooth-4.txt') + #TrafficTest(shift=0, floor=0.1, timelength=6, pattern='sawtooth', load=35000, days=7, filename='rev-sawtooth-6.txt') + #TrafficTest(shift=0, floor=0.1, timelength=8, pattern='sawtooth', load=35000, days=7, filename='rev-sawtooth-8.txt') + #TrafficTest(shift=0, floor=0.1, timelength=10, pattern='sawtooth', load=35010, days=7, filename='rev-sawtooth-10.txt') + #TrafficTest(shift=0, floor=0.1, timelength=12, pattern='sawtooth', load=35000, days=7, filename='rev-sawtooth-12.txt') + + #TrafficTest(shift=0, floor=0.1, timelength=16, pattern='triangle', load=35010, days=2, filename='triangle-6-dual_limits[Test].txt') + #TrafficTest(shift=0, floor=0.1, timelength=6, pattern='triangle', load=35010, days=7, filename='triangle-6-dual_limits.txt') + #TrafficTest(shift=0, floor=0.1, timelength=8, pattern='triangle', load=35010, days=7, filename='triangle-8-dual_limits.txt') + #TrafficTest(shift=0, floor=0.1, timelength=10, pattern='triangle', load=35010, days=7, filename='triangle-10-dual_limits.txt') + #TrafficTest(shift=0, floor=0.1, timelength=12, pattern='triangle', load=35010, days=7, filename='triangle-12-dual_limits.txt') + #TrafficTest(shift=0, floor=0.1, timelength=14, pattern='triangle', load=35010, days=7, filename='triangle-14-dual_limits.txt') \ No newline at end of file diff --git a/ofcdemo/net_simu_sawtooth_dual_limit.py b/ofcdemo/net_simu_sawtooth_dual_limit.py new file mode 100644 index 00000000..05a4ea85 --- /dev/null +++ b/ofcdemo/net_simu_sawtooth_dual_limit.py @@ -0,0 +1,1074 @@ +#!/usr/bin/python +""" +single_link_test.py: test monitoring on a single link + +Note this version uses and depends on explicit port assignment! +""" + +from network import Network +from link import Span as Fiber, SpanTuple as Segment +from node import Transceiver +from units import * +from collections import defaultdict +import random +from collections import defaultdict +import numpy as np +import scipy as sp +import matplotlib.pyplot as plt +from scipy import signal + + +km = dB = dBm = 1.0 +m = .001 + +# Parameters + + +NUM_WAV = 90 +LINK_CAP = 200 +DOWN_LINK_CAP = 100 +CPRI_CAP = 25 +# ROADM port numbers (input and output) +LINE_PORT1, LINE_PORT2, LINE_PORT3, LINE_PORT4, LINE_PORT5, LINE_PORT6 = NUM_WAV, NUM_WAV+1, NUM_WAV+2, NUM_WAV+3, NUM_WAV+4, NUM_WAV+5 +NETLINKS = [] +GRAPH = defaultdict() +NODES = defaultdict() +NETLINK_INFO = defaultdict( defaultdict ) # ('node_name', 'node_name'): {channel_id: lightpath_id} +TRAFFIC_INFO = defaultdict( defaultdict ) # id : {'src':src, 'dst':dst, 'lightpath_id': lightpath_id, 'up_time':s_time, 'down_time': d_time, 'latency': 0} # id : {'path':path, 'channel':channel_id, 'up_time':s_time, 'down_time': d_time} +LIGHTPATH_INFO = defaultdict( defaultdict ) # id : {'path':path, 'channel_id': channel_id, 'traf_set': set(), 'up_time':s_time, 'down_time': d_time, 'OSNR': 25, 'GOSNR': 24.5 } +SRC_DST_TO_LIGHTPATH = defaultdict( set ) # (src, dst) : {set[lightpath_id]} +PATH_CH_TO_LIGHTPATH = defaultdict(defaultdict) # (src, hop, dst) : {'channel_id': lightpath_id} +TRAFFIC_ID = 0 +LIGHTPATH_ID = 0 +NUM_NODE = 7 +NAME_ROADM = [] +UP_TRAF_TIME_LIST = [] +UP_TRAF_ID_SET = set() +UP_LIGHTPATH_TIME_LIST = [] +UP_LIGHTPATH_ID_SET = set() +ALL_CHANNELS = [ i for i in range(1,NUM_WAV+1)] +RU_ROADMS = [] +DU_ROADMS = ['r1', 'r4', 'r7'] +ROADM_TRAF = defaultdict(set) + +# Mininet-Optical +name_roadms = [] +name_terminals = [] +Roadm_Rule_ID_dict = {} +ROADM_TO_TERMINAL = {} +TERMINAL_TO_ROADM = {} +for i in range(NUM_NODE): + name_roadms.append('r%d'%(i+1)) + name_terminals.append('t%d'%(i+1)) + Roadm_Rule_ID_dict['r%d' % (i + 1)] = 1 + ROADM_TO_TERMINAL['r%d' % (i + 1)] = 't%d' % (i + 1) + TERMINAL_TO_ROADM['t%d' % (i + 1)] = 'r%d' % (i + 1) + node = 'r%d' %(i+1) + if node not in DU_ROADMS: + RU_ROADMS.append(node) + + +for i in range(NUM_NODE): + NAME_ROADM.append('r%d' % (i + 1)) + + + +# Physical model API helpers + +def Span( km, amp=None ): + "Return a fiber segment of length km with a compensating amp" + return Segment( span=Fiber( length=km ), amplifier=amp ) + +# Physical Network simulation, created out of base PHY model objects + +def RoadmPhyNetwork(): + + """ROADM network topo + """ + ############################### + # t1 - r1 ----- r2 - t2 + # | + # r3 - t3 + # | + # t5 - r5 ----- r4 - t4 + ################################ + + net = Network() + lengths = [15 * km] + + # Network nodes + transceivers = [('tx%d' % i, 0 * dBm, 'C') for i in range(1, NUM_WAV + 1)] + + # each terminal includes NUM_WAV transceivers + terminals = [ + net.add_lt(name, transceivers=transceivers, monitor_mode=mode) + for name, mode in [('t%d' % i, 'in') for i in range(1, NUM_NODE + 1)]] + roadms = [ + net.add_roadm(name, monitor_mode=mode) + for name, mode in [('r%d' % i, 'in') for i in range(1, NUM_NODE + 1)]] + + # roadms = [ net.add_roadm( 'r%d' % i ) for i in (1, 2, 3) ] + nodes = net.name_to_node + # Convenience alias + link = net.add_link + + for k in range(1, NUM_NODE): + print('==range=', k) + # Eastbound link consisting of a boost amplifier going into + # one or more segments of fiber with compensating amplifiers + boost = net.add_amplifier('boost{}{}'.format(k, k + 1), target_gain=17 * dB, boost=True) + spans = [] + for i, length in enumerate(lengths, start=1): + amp = net.add_amplifier( + 'amp{}{}-{}'.format(k, k + 1, i), target_gain=length * 0.22 * dB, monitor_mode='out') + span = Span(length, amp=amp) + spans.append(span) + + link(nodes['r%d' % k], nodes['r%d' % (k + 1)], src_out_port=LINE_PORT2, dst_in_port=LINE_PORT1, boost_amp=boost, + spans=spans) + NETLINKS.append(('r%d' % k, LINE_PORT2, 'r%d' % (k + 1), LINE_PORT1)) + NETLINK_INFO['r%d' % k, 'r%d' % (k + 1)] = {0: 0} + + # Westbound link consisting of a boost amplifier going into + # one or more segments of fiber with compensating amplifiers + boost = net.add_amplifier('boost{}{}'.format(k + 1, k), target_gain=17 * dB, boost=True) + spans = [] + for i, length in enumerate(lengths, start=1): + amp = net.add_amplifier( + 'amp{}{}-{}'.format(k + 1, k, i), target_gain=length * 0.22 * dB, monitor_mode='out') + span = Span(length, amp=amp) + spans.append(span) + + link(nodes['r%d' % (k + 1)], nodes['r%d' % k], src_out_port=LINE_PORT1, dst_in_port=LINE_PORT2, boost_amp=boost, + spans=spans) + NETLINKS.append(('r%d' % (k + 1), LINE_PORT1, 'r%d' % k, LINE_PORT2)) + NETLINK_INFO['r%d' % (k + 1), 'r%d' % k] = {0: 0} + + for k in range(1,NUM_NODE+1): + # Local add/drop links between terminals/transceivers and ROADMs + for add_drop_port in range(NUM_WAV): + link( nodes['t%d' %k], nodes['r%d' %k], src_out_port=add_drop_port, dst_in_port=add_drop_port, spans=[Span(1*m)] ) + link( nodes['r%d' %k], nodes['t%d' %k], src_out_port=add_drop_port, dst_in_port=add_drop_port, spans=[Span(1*m)] ) + + NETLINKS.append(('t%d' %k, add_drop_port, 'r%d' %k, add_drop_port)) + NETLINKS.append(('r%d' %k, add_drop_port, 't%d' %k, add_drop_port)) + + return net + +############# Mininet Optical############ + +def Mininet_installPath(lightpath_id, path, channels, graph, nodes): + "intall switch rules on roadms along a lightpath for some signal channels" + + # Install ROADM rules + print(graph, nodes) + for channel in channels: + #print(channel) + rule_path = {} + #print(path) + for i in range(1, len(path) - 1 ): + node1, roadm, node2 = path[i-1], path[i], path[i+1] + port1 = graph[ node1 ][ roadm ] + port2 = graph[ node2 ][ roadm ] + #print('==route port', i,(node1,roadm,port1), (node2,roadm,port2)) + if i == 1: + nodes[roadm].install_switch_rule(rule_id=Roadm_Rule_ID_dict[roadm], in_port=channel - 1, out_port=port2, + signal_indices=[channel]) + rule_path[roadm] = Roadm_Rule_ID_dict[roadm] + Roadm_Rule_ID_dict[roadm] += 1 + elif i == len(path) - 2: + nodes[roadm].install_switch_rule(rule_id=Roadm_Rule_ID_dict[roadm], in_port=port1, out_port=channel - 1, + signal_indices=[channel]) + rule_path[roadm] = Roadm_Rule_ID_dict[roadm] + Roadm_Rule_ID_dict[roadm] += 1 + else: + nodes[roadm].install_switch_rule(rule_id=Roadm_Rule_ID_dict[roadm], in_port=port1, out_port=port2, + signal_indices=[channel]) + rule_path[roadm] = Roadm_Rule_ID_dict[roadm] + Roadm_Rule_ID_dict[roadm] += 1 + LIGHTPATH_INFO[lightpath_id]['rule_path'] = rule_path + + +def Mininet_uninstallPath(lightpath_id, nodes): + "delete switch rules on roadms along a lightpath for some signal channels" + + + path = LIGHTPATH_INFO[lightpath_id]['path'] + rule_path = LIGHTPATH_INFO[lightpath_id]['rule_path'] + channel = LIGHTPATH_INFO[lightpath_id]['channel_id'] + Mininet_turnoffTerminalChannel(terminal=nodes[path[0]], channel=channel) + for i in range(1, len(path) - 1): + roadm = path[i] + nodes[roadm].delete_switch_rule(rule_path[roadm]) + + + +def Mininet_setupLightpath(lightpath_id, path, channel, power, graph, nodes): + channel = channel[0] + Mininet_installPath(lightpath_id, path, [channel], graph, nodes) + Mininet_configTerminalChannelPower(terminal= nodes[path[0]], channel=channel, power=power) + Mininet_voaPowerLeveling(path=path, channel=channel, power=power, graph=graph, nodes=nodes) + Mininet_configTerminalChannel(terminal=nodes[path[0]], channel=channel) + return True + + +def Mininet_teardownLightpath(lightpath_id, nodes): + + Mininet_uninstallPath(lightpath_id, nodes) + return True + + +def Mininet_voaPowerLeveling(path, channel, power, graph, nodes): + "Power control for a signal channel at a roadm using VOA leveling" + + for i in range(1, len(path) - 1): + node1, roadm, node2 = path[i - 1], path[i], path[i + 1] + if i == len(path) - 2: + nodes[roadm].configure_voa(channel_id=channel, output_port=channel - 1, operational_power_dB=power) + else: + nodes[roadm].configure_voa(channel_id=channel, output_port=graph[node2][roadm], operational_power_dB=power) + + +def Mininet_configTerminalChannelPower(terminal, channel, power): + "Congifure Terminal Launch power for a channel" + + terminal.name_to_transceivers['tx%d'% channel].operation_power = db_to_abs(power) + + +def Mininet_configTerminalChannel(terminal, channel): + "Turn on a Terminal with a given channel" + + terminal.configure_terminal(transceiver=terminal.transceivers[channel-1], channel=channel) + terminal.turn_on() + + +def Mininet_turnoffTerminalChannel(terminal, channel): + "Turn on a Terminal with a given channel" + + terminal.turn_off([channel-1]) + + +def Mininet_monitorAll(node): + "monitoring all data at a node" + + return node.monitor.get_dict_power(),node.monitor.get_dict_osnr(), node.monitor.get_dict_gosnr() + + +def Mininet_monitorLightpath(path, channel, nodes): + "monitoring a signal along a lightpath" + #print('monitor_path_ch', path, channel) + + powers = list() + osnrs = list() + gosnrs = list() + ase_noise = list() + nli_noise = list() + freq = round((191.30 + 0.05*channel)*10**12,1) + for i in range(1, len(path) - 1): + name = path[i] + node = nodes[name] + optical_signals = node.monitor.extract_optical_signal() + for sig in optical_signals: + if freq==sig[0].frequency: + if node.monitor.mode == 'out': + output_power = (sig[0].loc_out_to_state[node.monitor.component]['power']) + ase = (sig[0].loc_out_to_state[node.monitor.component]['ase_noise']) + nli = (sig[0].loc_out_to_state[node.monitor.component]['nli_noise']) + else: + output_power = (sig[0].loc_in_to_state[node.monitor.component]['power']) + ase = (sig[0].loc_in_to_state[node.monitor.component]['ase_noise']) + nli = (sig[0].loc_in_to_state[node.monitor.component]['nli_noise']) + gosnr_linear = output_power / (ase + nli * (12.5e9 / 32.0e9)) + gosnr = abs_to_db(gosnr_linear) + osnr_linear = output_power / ase + osnr = abs_to_db(osnr_linear) + powers.append(output_power) + osnrs.append(osnr) + gosnrs.append(gosnr) + ase_noise.append(ase) + nli_noise.append(nli) + #powers.append((name,output_power)) + #osnrs.append((name,osnr)) + #gosnrs.append((name,gosnr)) + return powers, osnrs, gosnrs, ase_noise, nli_noise + +################# END #################### + + +################ CONTROL PLANE ##################### + +def linkspec( link ): + "Return specifier dict(node1, port1, node2, port2) for link" + node1, node2, port1, port2 = link[0], link[2], link[1], link[3] + return { node1:port1, node2:port2 } + + +def getLinks(): + + return dict( links=[ linkspec( link ) for link in NETLINKS ] ) + + +def netGraph( links ): + "Return an adjacency dict for links" + # Note we only have to worry about single links between nodes + # We handle the terminals separately + neighbors = defaultdict( defaultdict ) + for link in links: + #print(link) + src, dst = link # link is a dict but order doesn't matter + srcport, dstport = link[ src ], link[ dst ] + neighbors.setdefault( src, {} ) + neighbors[ src ][ dst ] = dstport + neighbors[ dst ][ src ] = srcport + return dict( neighbors ) + + +def FindRoute( src, graph, destinations, k=10): + """Route from src to destinations + neighbors: adjacency list + returns: routes dict""" + routes, seen, paths = defaultdict(list), set( (src,) ), [ (src,) ] + while paths: + path = paths.pop( 0 ) + lastNode = path[ -1 ] + for neighbor in graph[ lastNode ]: + if neighbor not in path: + newPath = ( path + (neighbor, ) ) + paths.append( newPath ) + if neighbor in destinations and len(routes[ neighbor ]) < k: + routes[ neighbor ].append(newPath) + return routes + + +def shortestPath(): + return + +def pathSelection(paths, cur_time, waiting_time_threshold=5.0, short_duration=False): + new_paths = [] + for path in paths: + avai_channels = waveAvailibility(path) + occupied_channels = set(ALL_CHANNELS).difference(avai_channels) + max_waiting_time = defaultdict(lambda: 0) + for i in range(len(path) - 1): + for j in occupied_channels: + if j in NETLINK_INFO[path[i], path[i + 1]].keys(): + lightpath_id = NETLINK_INFO[path[i], path[i + 1]][j] + waiting_time = LIGHTPATH_INFO[lightpath_id]['down_time'] - cur_time + # print('waiting', j, waiting_time) + max_waiting_time[j] = max(waiting_time, max_waiting_time[j]) + possible_channels = set() + # print('waiting', path, max_waiting_time) + for ch in occupied_channels: + # print(max_waiting_time[ch]) + if max_waiting_time[ch] < waiting_time_threshold: + possible_channels.add(ch) + ## if this lightpath will be teared town soon + ## if this lightpath will be teared town soon + if short_duration: + new_paths.append((-len(possible_channels), -len(avai_channels), len(path), path)) + else: + new_paths.append((-len(possible_channels)-len(avai_channels), -len(avai_channels), len(path), path)) + new_paths.sort() + #print('sort_path', new_paths) + return new_paths + + +def waveAvailibility(path): + avai_channels = set([i for i in range(NUM_WAV + 1)]) + for i in range(len(path) - 1): + link_channels = set(NETLINK_INFO[path[i], path[i + 1]].keys()) + avai_channels = avai_channels.difference(link_channels) + return avai_channels + + +def waveSelection(channels): + channels = list(channels) + return random.choice(channels) + + +def install_Lightpath(path, channel, up_time=0.0, down_time = float('inf')): + "intall switch rules on roadms along a lightpath for some signal channels" + + ## Install ROADM rules + global LIGHTPATH_ID + LIGHTPATH_ID += 1 + for i in range(len(path) - 1): + NETLINK_INFO[path[i], path[i + 1]][channel] = LIGHTPATH_ID # channel with lightpath_id + NETLINK_INFO[path[i + 1], path[i]][channel] = LIGHTPATH_ID + # id : {'path':path, 'channel': channel_id, 'traf': set(), 'up_time':s_time, 'down_time': d_time, 'OSNR': 25, 'GOSNR': 24.5 } + Mininet_setupLightpath(lightpath_id=LIGHTPATH_ID, path=path, power=-1, channel=[channel], graph=GRAPH, nodes=NODES) + powers, osnrs, gosnrs, ase, nli = Mininet_monitorLightpath(path, channel, NODES) + LIGHTPATH_INFO[LIGHTPATH_ID]['path'] = path + LIGHTPATH_INFO[LIGHTPATH_ID]['channel_id'] = channel + LIGHTPATH_INFO[LIGHTPATH_ID]['link_cap'] = LINK_CAP + LIGHTPATH_INFO[LIGHTPATH_ID]['traf_set'] = set() + LIGHTPATH_INFO[LIGHTPATH_ID]['up_time'] = up_time + LIGHTPATH_INFO[LIGHTPATH_ID]['down_time'] = down_time + LIGHTPATH_INFO[LIGHTPATH_ID]['power'] = abs_to_db(powers[-1]) + LIGHTPATH_INFO[LIGHTPATH_ID]['OSNR'] = osnrs[-1] + LIGHTPATH_INFO[LIGHTPATH_ID]['GOSNR'] = gosnrs[-1] + # (src, dst) : {1,2,3,4,5} ##lightpath_id + SRC_DST_TO_LIGHTPATH[path[0], path[-1]].add(LIGHTPATH_ID) + # (src, hop, dst) : {'channel_id': lightpath_id} + PATH_CH_TO_LIGHTPATH[path][channel] = LIGHTPATH_ID + UP_LIGHTPATH_TIME_LIST.append((down_time, LIGHTPATH_ID)) + UP_LIGHTPATH_TIME_LIST.sort() + UP_LIGHTPATH_ID_SET.add(LIGHTPATH_ID) + + + return LIGHTPATH_ID + + +def check_lightpath_for_traf(src, dst): + ''' + check if there are some provisioned lighpaths for CPRI traf + ''' + lighpaths = SRC_DST_TO_LIGHTPATH[src,dst] + avai_lightpaths = set() + for lighpath_id in lighpaths: + if len(LIGHTPATH_INFO[lighpath_id]['traf_set']) < LIGHTPATH_INFO[lighpath_id]['link_cap']/CPRI_CAP: + avai_lightpaths.add(lighpath_id) + return avai_lightpaths + + +def select_lightpath_by_latency(avai_lightpaths, latency=0): + for lightpath_id in avai_lightpaths: + path = LIGHTPATH_INFO[lightpath_id]['path'] + GOSNR = LIGHTPATH_INFO[lightpath_id]['GOSNR'] + if latency == 0 or latency == 1: + if len(path) < 4 and GOSNR > 25: + return lightpath_id + else: + return lightpath_id + return False + + +def update_lightpath_down_time(lightpath_id, down_time): + for d_time, id in UP_LIGHTPATH_TIME_LIST: + if id == lightpath_id: + UP_LIGHTPATH_TIME_LIST.remove((d_time, id)) + UP_LIGHTPATH_TIME_LIST.append((down_time, lightpath_id)) + UP_LIGHTPATH_TIME_LIST.sort() + + +def traf_to_lightpah_Assignment(traf_id, lightpath_id, down_time = float('inf')): + # (src, hop, dst) : {'channel_id': lightpath_id} + LIGHTPATH_INFO[lightpath_id]['traf_set'].add(traf_id) + path = LIGHTPATH_INFO[lightpath_id]['path'] + if down_time > LIGHTPATH_INFO[lightpath_id]['down_time']: + LIGHTPATH_INFO[lightpath_id]['down_time'] = down_time + update_lightpath_down_time(lightpath_id, down_time) + # traf_id : {'src':src, 'dst':dst, 'lightpath_id': lightpath_id, 'up_time':s_time, 'down_time': d_time, 'latency': 0} + TRAFFIC_INFO[traf_id]['src'], TRAFFIC_INFO[traf_id]['dst'], TRAFFIC_INFO[traf_id]['lightpath_id'] = path[0], path[-1], lightpath_id + UP_TRAF_TIME_LIST.append((down_time, traf_id)) + UP_TRAF_TIME_LIST.sort() + UP_TRAF_ID_SET.add(traf_id) + return traf_id + + +def install_Traf(src, dst, routes, cur_time, down_time=float('inf'), latency = 0, RWA = True): + ''' + source RRH node to destination BBU node + latency: 0 for ultra-low: only use provisioned lightpaths with high BW and BER, + 1 low latency: can setup lightpath but need high BW and high BER/GOSNR, + 2 no latency requirement: any lightpath + ''' + global TRAFFIC_ID + avai_lightpaths = check_lightpath_for_traf(src, dst) + lightpath_id = select_lightpath_by_latency(avai_lightpaths, latency) + #print('---avai_lightpaths, select lightpath_id--', avai_lightpaths, lightpath_id) + if latency == 0 : + if lightpath_id: + TRAFFIC_ID += 1 + traf_id = traf_to_lightpah_Assignment(TRAFFIC_ID, lightpath_id, down_time=down_time) + LIGHTPATH_INFO[lightpath_id]['traf_set'].add(TRAFFIC_ID) + return traf_id + elif latency == 1: + if lightpath_id: + TRAFFIC_ID += 1 + traf_id = traf_to_lightpah_Assignment(TRAFFIC_ID, lightpath_id, down_time=down_time) + LIGHTPATH_INFO[lightpath_id]['traf_set'].add(TRAFFIC_ID) + return traf_id + else: + if RWA: + all_path_info = pathSelection(paths= routes[src][dst], cur_time = cur_time, waiting_time_threshold=5.0, short_duration=True) + else: + all_path_info = routes[src][dst] + # [ (len(possible_channels), len(ava_channls), len(path), paths), ... ] + for path_info in all_path_info: + if RWA: + path = path_info[3] + else: + path = path_info + if len(path)>=4: + continue + chs = waveAvailibility(path=path) + if chs: + count = 0 + while count < 5 and chs: + count += 1 + ch = waveSelection(chs) + chs.remove(ch) + lightpath_id = install_Lightpath(path=path, channel=ch, up_time=cur_time, down_time=down_time) + GOSNR = LIGHTPATH_INFO[lightpath_id]['GOSNR'] + if GOSNR > 25: + TRAFFIC_ID += 1 + traf_id = traf_to_lightpah_Assignment(TRAFFIC_ID, lightpath_id, down_time=down_time) + LIGHTPATH_INFO[lightpath_id]['traf_set'].add(TRAFFIC_ID) + return traf_id + else: + uninstall_Lightpath(lightpath_id) + elif latency == 2: + if lightpath_id: + TRAFFIC_ID += 1 + traf_id = traf_to_lightpah_Assignment(TRAFFIC_ID, lightpath_id, down_time=down_time) + LIGHTPATH_INFO[lightpath_id]['traf_set'].add(TRAFFIC_ID) + return traf_id + else: + if RWA: + all_path_info = pathSelection(paths=routes[src][dst], cur_time=cur_time, waiting_time_threshold=5.0, + short_duration=True) + else: + all_path_info = routes[src][dst] + # [ (len(possible_channels), len(ava_channls), len(path), paths), ... ] + for path_info in all_path_info: + if RWA: + path = path_info[3] + else: + path = path_info + chs = waveAvailibility(path=path) + if chs: + count = 0 + while count < 5 and chs: + count += 1 + ch = waveSelection(chs) + chs.remove(ch) + lightpath_id = install_Lightpath(path=path, channel=ch, up_time=cur_time, down_time=down_time) + TRAFFIC_ID += 1 + traf_id = traf_to_lightpah_Assignment(TRAFFIC_ID, lightpath_id, down_time=down_time) + LIGHTPATH_INFO[lightpath_id]['traf_set'].add(TRAFFIC_ID) + return traf_id + return False + + +def uninstall_Lightpath(lightpath_id): + "delete switch rules on roadms along a lightpath for some signal channels" + Mininet_uninstallPath(lightpath_id=lightpath_id, nodes=NODES) + path = LIGHTPATH_INFO[lightpath_id]['path'] + channel = LIGHTPATH_INFO[lightpath_id]['channel_id'] + for i in range(len(path) - 1): + del NETLINK_INFO[path[i], path[i + 1]][channel] + del NETLINK_INFO[path[i + 1], path[i]][channel] + #print(PATH_CH_TO_LIGHTPATH) + lightpath_id = PATH_CH_TO_LIGHTPATH[path][channel] + #print('==', lightpath_id) + del LIGHTPATH_INFO[lightpath_id] + del PATH_CH_TO_LIGHTPATH[path][channel] + SRC_DST_TO_LIGHTPATH[path[0], path[-1]].remove(lightpath_id) + UP_LIGHTPATH_ID_SET.remove(lightpath_id) + + return lightpath_id + + +def traf_to_lightpath_Release(traf_id): + lightpath_id = TRAFFIC_INFO[traf_id]['lightpath_id'] + LIGHTPATH_INFO[lightpath_id]['traf_set'].remove(traf_id) + del TRAFFIC_INFO[traf_id] + UP_TRAF_ID_SET.remove(traf_id) + return traf_id + +################# END ################### + +def analytic_traffic(time, shift=0, floor=0.0001, timelength=8, days=1, pattern = 'sawtooth', source='office'): + """Graphs a sawtooth traffic pattern as a means to analytically study the system. + _/\_/\_/\_/\_/\_/\_/\_/\ This allows is to examine analytical properties of the + system.""" + time = time - 0.000125 + if pattern == 'sawtooth': + if source == 'office': + if time % 24 < timelength: + factor = ((sp.signal.sawtooth(2 * np.pi * (time % 24) / timelength, 0) + 1) / 2) * (1-floor) + floor + return factor + else: + return floor + if source == 'resident': + if shift < (float(time) % 24) and (float(time) % 24) < (timelength + shift): + factor = ((sp.signal.sawtooth(2 * np.pi * ((time % 24) -shift) / timelength, 0) + 1) / 2) * (1-floor) + floor + return factor + else: + return floor + if pattern == 'triangle': + if source == 'office': + if time % 24 < timelength: + factor = ((sp.signal.sawtooth(2 * np.pi * (time % 24) / timelength, 0.5) + 1) / 2) * (1-floor) + floor + return factor + else: + return floor + if source == 'resident': + if shift < (float(time) % 24) and (float(time) % 24) < (timelength + shift): + factor = ((sp.signal.sawtooth(2 * np.pi * ((time % 24) -shift) / timelength, 0.5) + 1) / 2) * (1-floor) + floor + return factor + else: + return floor + + if pattern == 'impulse': + if source == 'office': + if time % 24 < timelength: + factor = 1 + return factor + else: + return floor + if source == 'resident': + if shift < (float(time) % 24) and (float(time) % 24) < (timelength + shift): + factor = 1 + return factor + else: + return floor + if pattern == 'step_down': + if source == 'office': + if time % (24*days) < timelength: + factor = 1 + return factor + else: + return floor + if source == 'resident': + if shift < (float(time) % (24*days)) and (float(time) % (24*days)) < (timelength + shift): + factor = 1 + return factor + else: + return floor + + else: + raise ValueError('Error: function not found') + + + +def TrafficTest(shift=0, floor=0.0001, timelength=8, pattern = 'sawtooth',load=35000, days=7, filename = 'sawtooth_results.txt'): + """Create an analytical study of Mininet Optical's behavious. This will be done using + a sawtooth simulation of relevent data to produce results.""" + net = RoadmPhyNetwork() + AllLinks = getLinks() + global GRAPH, NODES + GRAPH = netGraph(AllLinks['links']) + NODES = net.name_to_node + routes = {node: FindRoute(node, GRAPH, name_terminals) + for node in name_terminals} + + print('===links', AllLinks['links']) + print('===graph', GRAPH) + print('==link_info', NETLINK_INFO) + print('==route') + + for key in routes.keys(): + print(key, routes[key]) + + + Total_Rej = 0 + N = 24*days #Hours + file = open(filename, 'w') + # Overall Traffic information + Total_traf = load # Gbps + MAX_traf = {} + for i in range(2,NUM_NODE): #Includes ROADMs 2,3,4,5, ROADM 1,2 are BBU nodes + MAX_traf['r%d' %(i)] = 1.0*Total_traf/(len(RU_ROADMS)) + print('---max_traf',MAX_traf) + + + BBU_traf = {} + BBU_limit = {} + BBU_limit['t1'] = 250 #* float('inf') + BBU_limit['t%d' % NUM_NODE] = 250 #* float('inf') + for node in DU_ROADMS: + BBU_traf[ROADM_TO_TERMINAL[node]] = 0 + RRH_traf = {} + for node in RU_ROADMS: + RRH_traf[ROADM_TO_TERMINAL[node]] = 0 + + ROADM_TYPE = {'r2':'office', 'r3':'resident', 'r5':'resident', 'r6':'office'} + + file.write( + 'time, r2-office_traf, r3-resident_traf, r5-resident_traf, r6-office_traf, ' + 'number_of_lightpath, avg_wav_per_link, r1_BBU_traf, r6_BBU_traf, r4_BBU_traf,' + ' r2-office_rej, r3-resident_rej, r5-resident_rej, r6-office_rej,' + ' 50G, 100G, 200G, underutilized, Total_cap, Total_traffic\n') + + for i in range(N): + Rej = {} + for key in TERMINAL_TO_ROADM.keys(): + Rej[key] = 0 + print(i) #Prints the hour + factors = {} + for src in RU_ROADMS: + f = analytic_traffic(time=i, shift=shift, floor=floor, timelength=timelength, days=days, pattern=pattern, + source=ROADM_TYPE[src]) + factors[src] = f + factor = f + src_t = ROADM_TO_TERMINAL[src] + count = 0 + while factor*MAX_traf[src]/CPRI_CAP > len(ROADM_TRAF[src]): + ADD_TRAF = False + print(factor*MAX_traf[src]/CPRI_CAP, len(ROADM_TRAF[src]), Rej) + dst = random.choice(DU_ROADMS) + + RRH_traf[src_t] += 1 + path_first = routes[src_t]['t1'][0] + path_last = routes[src_t]['t%d' % NUM_NODE][0] + print('two_paths', path_first, path_last) + if len(path_last) DOWN_LINK_CAP/CPRI_CAP: + traf_id = random.choice(list(traf_set)) + s_t, d_t = TRAFFIC_INFO[traf_id]['src'], TRAFFIC_INFO[traf_id]['dst'] + ROADM_TRAF[TERMINAL_TO_ROADM[s_t]].remove(traf_id) + traf_to_lightpath_Release(traf_id=traf_id) + reassign_traf.append((s_t, d_t)) + elif gosnrs[-1] < 16: + LIGHTPATH_INFO[lightpath_id]['link_cap'] = 50 + traf_set = LIGHTPATH_INFO[lightpath_id]['traf_set'] + while len(traf_set) > DOWN_LINK_CAP/CPRI_CAP: + traf_id = random.choice(list(traf_set)) + s_t, d_t = TRAFFIC_INFO[traf_id]['src'], TRAFFIC_INFO[traf_id]['dst'] + ROADM_TRAF[TERMINAL_TO_ROADM[s_t]].remove(traf_id) + traf_to_lightpath_Release(traf_id=traf_id) + reassign_traf.append((s_t, d_t)) + else: + LIGHTPATH_INFO[lightpath_id]['link_cap'] = LINK_CAP + + + for s_t, d_t in reassign_traf: + traf_id = install_Traf(s_t, d_t, routes, cur_time=0, down_time=float('inf'), latency=2, + RWA=False) + if traf_id: + ROADM_TRAF[TERMINAL_TO_ROADM[s_t]].add(traf_id) + else: + Rej[s_t] += 1 + Total_Rej += 1 + BBU_traf[d_t] -= 1 + + elif dst_back: + dst = dst_back + dst_t = dst_t_back + print('try_backup_path', src, dst) + traf_id = install_Traf(src_t, dst_t, routes, cur_time=0, down_time=float('inf'), latency=2, + RWA=False) + if traf_id: + ADD_TRAF = True + ROADM_TRAF[src].add(traf_id) + BBU_traf[dst_t] += 1 + reassign_traf = [] + fail_lightpaths = [] + for lightpath_id, info in LIGHTPATH_INFO.items(): + powers, osnrs, gosnrs, ase, nli = Mininet_monitorLightpath(path=info['path'], + channel=info['channel_id'], + nodes=NODES) + LIGHTPATH_INFO[lightpath_id]['GOSNR'] = gosnrs[-1] + LIGHTPATH_INFO[lightpath_id]['OSNR'] = osnrs[-1] + if 18 < gosnrs[-1] < 24: + LIGHTPATH_INFO[lightpath_id]['link_cap'] = DOWN_LINK_CAP + traf_set = LIGHTPATH_INFO[lightpath_id]['traf_set'] + while len(traf_set) > DOWN_LINK_CAP / CPRI_CAP: + traf_id = random.choice(list(traf_set)) + s_t, d_t = TRAFFIC_INFO[traf_id]['src'], TRAFFIC_INFO[traf_id]['dst'] + ROADM_TRAF[TERMINAL_TO_ROADM[s_t]].remove(traf_id) + traf_to_lightpath_Release(traf_id=traf_id) + reassign_traf.append((s_t, d_t)) + elif gosnrs[-1] < 16: + LIGHTPATH_INFO[lightpath_id]['link_cap'] = 50 + traf_set = LIGHTPATH_INFO[lightpath_id]['traf_set'] + while len(traf_set) > DOWN_LINK_CAP / CPRI_CAP: + traf_id = random.choice(list(traf_set)) + s_t, d_t = TRAFFIC_INFO[traf_id]['src'], TRAFFIC_INFO[traf_id]['dst'] + ROADM_TRAF[TERMINAL_TO_ROADM[s_t]].remove(traf_id) + traf_to_lightpath_Release(traf_id=traf_id) + reassign_traf.append((s_t, d_t)) + else: + LIGHTPATH_INFO[lightpath_id]['link_cap'] = LINK_CAP + + for s_t, d_t in reassign_traf: + traf_id = install_Traf(s_t, d_t, routes, cur_time=0, down_time=float('inf'), latency=2, + RWA=False) + if traf_id: + ROADM_TRAF[TERMINAL_TO_ROADM[s_t]].add(traf_id) + else: + Rej[s_t] += 1 + Total_Rej += 1 + BBU_traf[d_t] -= 1 + + if not ADD_TRAF: + Rej[src_t] += 1 + Total_Rej += 1 + count += 1 + if count == 10: + fails = factor*MAX_traf[src]/CPRI_CAP - len(ROADM_TRAF[src]) + RRH_traf[src_t] += fails + Rej[src_t] += fails + Total_Rej += fails + break + + while factor * MAX_traf[src] / CPRI_CAP < len(ROADM_TRAF[src]): + traf_id = random.choice(list(ROADM_TRAF[src])) + dst = TRAFFIC_INFO[traf_id]['dst'] + BBU_traf[dst] -= 1 + lightpath_id = TRAFFIC_INFO[traf_id]['lightpath_id'] + traf_set = LIGHTPATH_INFO[lightpath_id]['traf_set'] + traf_to_lightpath_Release(traf_id=traf_id) + ROADM_TRAF[src].remove(traf_id) + if not traf_set: + uninstall_Lightpath(lightpath_id=lightpath_id) + + + + """while UP_LIGHTPATH_TIME_LIST and UP_LIGHTPATH_TIME_LIST[0][0]< time: + lightpath_id = UP_LIGHTPATH_TIME_LIST.pop(0)[1] + uninstall_Lightpath(lightpath_id=lightpath_id) + #""" + OneG = 0 + TwoG = 0 + FiftyG = 0 + UnderUse = 0 + for lightpath_id, info in LIGHTPATH_INFO.items(): + if LIGHTPATH_INFO[lightpath_id]['link_cap']/CPRI_CAP/2 > len(LIGHTPATH_INFO[lightpath_id]['traf_set']): + UnderUse += 1 + link_cap = LIGHTPATH_INFO[lightpath_id]['link_cap'] + if link_cap == 100: + OneG += 1 + elif link_cap ==200: + TwoG += 1 + elif link_cap ==50: + FiftyG += 1 + total_wav = 0 + for key in NETLINK_INFO.keys(): + total_wav += len(NETLINK_INFO[key].items()) + avg_wav = (1.0 * total_wav ) / (NUM_NODE-1) + + """file.write('{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\n'.format(i, factors['r2'] * MAX_traf['r2'], factors['r3'] * MAX_traf['r3'], + len(LIGHTPATH_INFO.keys()), avg_wav,BBU_traf['t1'], BBU_traf['t%d' % NUM_NODE], + 1.0*Rej['t2']/(factors['r2']*MAX_traf['r2']/CPRI_CAP), 1.0*Rej['t3']/(factors['r3']*MAX_traf['r3']/CPRI_CAP), + OneG, TwoG, UnderUse, OneG*100+TwoG*200)) + #""" + print(Rej['t2'], '/', factors['r2'], MAX_traf['r2'], CPRI_CAP) + file.write('{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {} \n'.format( + i, + factors['r2'] * MAX_traf['r2'], + factors['r3'] * MAX_traf['r3'], + factors['r5'] * MAX_traf['r5'], + factors['r6'] * MAX_traf['r6'], + len(LIGHTPATH_INFO.keys()), avg_wav, + BBU_traf['t1'], + BBU_traf['t7'], + BBU_traf['t4'], + 1.0 * Rej['t2'] / (factors['r2']*MAX_traf['r2']/CPRI_CAP), + 1.0 * Rej['t3'] / (factors['r3']*MAX_traf['r3']/CPRI_CAP), + 1.0 * Rej['t5'] / (factors['r5']*MAX_traf['r5']/CPRI_CAP), + 1.0 * Rej['t6'] / (factors['r6']*MAX_traf['r6']/CPRI_CAP), + FiftyG, OneG,TwoG, UnderUse, + OneG * 100 + TwoG * 200 +FiftyG*50, + (factors['r2'] * MAX_traf['r2']) + (factors['r3'] * MAX_traf['r3']) + (factors['r5'] * MAX_traf['r5'])+(factors['r6'] * MAX_traf['r6'])) + ) + #""" + print('==traf') + for item in TRAFFIC_INFO.items(): + print(item) + print('==Lightpath') + fail_paths= [] + for item in LIGHTPATH_INFO.items(): + print(item) + path = item[1]['path'] + ch = item[1]['channel_id'] + gosnr = item[1]['GOSNR'] + powers, osnrs, gosnrs, ase, nli = Mininet_monitorLightpath(path=path, channel=ch, nodes=NODES) + print('gosnr', gosnr, gosnrs[-1]) + if gosnrs[-1]< 24: + fail_paths.append((gosnrs[-1],osnrs[-1], powers[-1], item)) + for gosnr, osnr, power, item in fail_paths: + print('fail', gosnr, osnr, abs_to_db(power), item) + #print('==rej', Rej, 1.0*Rej/N) + print('r2-r1, chs', len(NETLINK_INFO['r2', 'r1'].items())) + print('r2-r3, chs', len(NETLINK_INFO['r2', 'r3'].items())) + print('r3-r4, chs', len(NETLINK_INFO['r3', 'r4'].items())) + for roadm in ROADM_TRAF: + print(roadm, ROADM_TRAF[roadm]) + print('BBU_Processing_traf', BBU_traf) + print('rej', Rej) + print('RRH_TRAF', RRH_traf) + print('rej_ratio', 1.0*Rej['t2']/RRH_traf['t2'], 1.0*Rej['t3']/RRH_traf['t3']) + print('total_rej_rate', 1.0* Total_Rej/sum(RRH_traf.values()) ) + + # for lightpath_id, info in LIGHTPATH_INFO.items(): + # powers, osnrs, gosnrs, ase, nli = Mininet_monitorLightpath(path=info['path'], channel=info['channel_id'], + # nodes=NODES) + # print('id: {}, path: {}, power: {}'.format( lightpath_id, info['path'], powers ) ) + + +# Physical model test +def RoadmPhyTest(): + # ROADM port numbers (input and output) + LINE_PORT1 = NUM_WAV + LINE_PORT2 = NUM_WAV+1 + + "Create a single link and monitor its OSNR and gOSNR" + net = RoadmPhyNetwork() + AllLinks = getLinks() + global GRAPH, NODES + GRAPH = netGraph(AllLinks['links']) + NODES = net.name_to_node + routes = {node: FindRoute(node, GRAPH, name_terminals) + for node in name_terminals} + # routes = {node: FindRoute(node, GRAPH, NAME_ROADM) + # for node in NAME_ROADM} + print('===links', AllLinks['links']) + print('===graph', GRAPH) + print('==link_info', NETLINK_INFO) + print('==route') + for key in routes.keys(): + print(key, routes[key]) + + ## add traffic and delete traffic + Rej = 0 + time = 0 # 86400s for one day + setup_time = 0.5/60.0 # MINUTE + N = 2000 # 1000000 for one day + file = open('record.txt', 'w') + # CPRI Request + arrival_rate = 50 # request/minute + holding_time = 30 # minute + factor = 1 + for i in range(N): + print(i) + #factor = trafficPattern(time) + t_arrival_rate = arrival_rate*factor + s_time = 1.0/t_arrival_rate + time #random.uniform(0.01, 0.02) + time # + duration = random.choice(np.random.poisson(holding_time, 10000)) + #duration = random.uniform(30, 600) + d_time = s_time + duration + time = s_time + setup_time + src = random.choice(RU_ROADMS) + dst = random.choice(DU_ROADMS) + #src = random.choice(NAME_ROADM) + #dst = random.choice(NAME_ROADM) + while dst == src: + dst = random.choice(NAME_ROADM) + + latency = random.uniform(0, 1) + if latency<0.1: + latency = 0 + elif latency<0.4: + latency = 1 + else: + latency = 2 + src = ROADM_TO_TERMINAL[src] + dst = ROADM_TO_TERMINAL[dst] + if not install_Traf(src, dst, routes, cur_time= s_time, down_time=d_time, latency=2, RWA= False): + Rej += 1 + else: + ROADM_TRAF[src].add(TRAFFIC_ID) + while UP_TRAF_TIME_LIST and UP_TRAF_TIME_LIST[0][0]< time: + traf_id = UP_TRAF_TIME_LIST.pop(0)[1] + traf_to_lightpath_Release(traf_id=traf_id) + print('~~', traf_id) + ROADM_TRAF[src].remove(traf_id) + while UP_LIGHTPATH_TIME_LIST and UP_LIGHTPATH_TIME_LIST[0][0]< time: + lightpath_id = UP_LIGHTPATH_TIME_LIST.pop(0)[1] + uninstall_Lightpath(lightpath_id=lightpath_id) + #""" + file.write('{}\t{}\n'.format(time, 1.0*Rej/(i+1))) + print('==traf') + for item in TRAFFIC_INFO.items(): + print(item) + print('==Lightpath') + fail_paths= [] + for item in LIGHTPATH_INFO.items(): + print(item) + if item[-1]['GOSNR']< 24: + fail_paths.append(item) + print('==time', time) + for item in fail_paths: + print('fail',item) + print('==rej', Rej, 1.0*Rej/N) + print('r2-r1, chs', len(NETLINK_INFO['r2', 'r1'].items())) + print('r2-r3, chs', len(NETLINK_INFO['r2', 'r3'].items())) + print('r3-r4, chs', len(NETLINK_INFO['r3', 'r4'].items())) + print(ROADM_TRAF) + +def QuickTest(shift=0, floor=0.0001, timelength=8, pattern = 'sawtooth',load=35000, days=7, filename = 'sawtooth_results.txt'): + days = 7 + time = np.arange(0, 24 * days) + office = [] + resident = [] + for hour in time: + office.append(analytic_traffic(hour, shift=shift, floor=floor, timelength=timelength, days=days, + pattern = pattern, source='office')) + resident.append(analytic_traffic(hour, shift=shift, floor=floor, timelength=timelength, days=days, + pattern = pattern, source='resident')) + plt.plot(time, resident, office) + plt.show() +if __name__ == '__main__': + #TrafficTest(shift=0, floor=0.01, timelength=4, pattern='triangle', load=35000, days=7, filename='Control.txt') + #TrafficTest(shift=0, floor=0.01, timelength=8, pattern='triangle', load=35000, days=7, filename='Control_s1.txt') + #TrafficTest(shift=0, floor=0.01, timelength=10, pattern='triangle', load=35000, days=7, filename='Control_s2.txt') + #TrafficTest(shift=0, floor=0.01, timelength=12, pattern='triangle', load=35000, days=7, filename='Control_s4.txt') + #TrafficTest(shift=0, floor=0.01, timelength=20, pattern='sawtooth', load=35000, days=7, filename='Control_s8 (2).txt') #check for wierd traffic_load error + + #TrafficTest(shift=0, floor=0.1, timelength=1, pattern='step_down', load=35000, days=7, filename='step_1.txt') + #TrafficTest(shift=0, floor=0.1, timelength=8, pattern='step_down', load=35000, days=7, filename='step_8.txt') + #TrafficTest(shift=0, floor=0.1, timelength=24, pattern='step_down', load=35000, days=7, filename='step_24.txt') + #TrafficTest(shift=0, floor=0.1, timelength=48, pattern='step_down', load=35000, days=7, filename='step_48.txt') + #TrafficTest(shift=0, floor=0.1, timelength=96, pattern='step_down', load=35000, days=7, filename='step_96.txt') + + #TrafficTest(shift=2, floor=0.001, timelength=8, pattern='impulse', load=35000, days=7, filename='Control_f=e-32.txt') + #TrafficTest(shift=2, floor=0.01, timelength=8, pattern='impulse', load=35000, days=7, filename='Control_f=e-2.txt') + #TrafficTest(shift=2, floor=0.1, timelength=8, pattern='impulse', load=35000, days=7, filename='Control_f=e-1.txt') + #TrafficTest(shift=2, floor=0.2, timelength=8, pattern='impulse', load=35000, days=7, filename='Control_f=2e-1.txt') + + #TrafficTest(shift=0, floor=0.1, timelength=1, pattern='step_down', load=35000, days=7, filename='step_1.txt') + #TrafficTest(shift=0, floor=0.1, timelength=8, pattern='step_down', load=35000, days=7, filename='step_8.txt') + + #TrafficTest(shift=0, floor=0.1, timelength=24, pattern='step_down', load=35000, days=7, filename='step_24.txt') + #TrafficTest(shift=0, floor=0.1, timelength=48, pattern='step_down', load=35000, days=7, filename='step_48.txt') + #TrafficTest(shift=0, floor=0.1, timelength=96, pattern='step_down', load=35000, days=7, filename='step_96.txt') + + #TrafficTest(shift=0, floor=0.0001, timelength=8, pattern='sawtooth', load=35000, days=7, filename='Control.txt') + #TrafficTest(shift=0, floor=0.1, timelength=4, pattern='sawtooth', load=35000, days=7, filename='rev-sawtooth-4.txt') + #TrafficTest(shift=0, floor=0.1, timelength=6, pattern='sawtooth', load=35000, days=7, filename='rev-sawtooth-6.txt') + #TrafficTest(shift=0, floor=0.1, timelength=8, pattern='sawtooth', load=35000, days=7, filename='rev-sawtooth-8.txt') + #TrafficTest(shift=0, floor=0.1, timelength=10, pattern='sawtooth', load=35010, days=7, filename='rev-sawtooth-10.txt') + #TrafficTest(shift=0, floor=0.1, timelength=12, pattern='sawtooth', load=35000, days=7, filename='rev-sawtooth-12.txt') + + #TrafficTest(shift=0, floor=0.1, timelength=16, pattern='triangle', load=35010, days=2, filename='triangle-6-dual_limits[Test].txt') + #TrafficTest(shift=0, floor=0.1, timelength=6, pattern='triangle', load=35010, days=7, filename='triangle-6-dual_limits.txt') + #TrafficTest(shift=0, floor=0.1, timelength=8, pattern='triangle', load=35010, days=7, filename='triangle-8-dual_limits.txt') + #TrafficTest(shift=0, floor=0.1, timelength=10, pattern='triangle', load=35010, days=7, filename='triangle-10-dual_limits.txt') + #TrafficTest(shift=0, floor=0.1, timelength=12, pattern='triangle', load=35010, days=7, filename='triangle-12-dual_limits.txt') + TrafficTest(shift=0, floor=0.1, timelength=14, pattern='triangle', load=35010, days=7, filename='triangle-14-dual_limits.txt') \ No newline at end of file From a48438d97692dc7adc10aacddc2ff7637d9418d1 Mon Sep 17 00:00:00 2001 From: aamirq Date: Fri, 28 May 2021 15:54:35 -0700 Subject: [PATCH 02/12] Added method to calculate Bit Error Rate from Modulation method in the receiver() function. --- node.py | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/node.py b/node.py index f9a2153e..fe997606 100755 --- a/node.py +++ b/node.py @@ -357,6 +357,26 @@ def gosnr(power, ase_noise, nli_noise, baud_rate): gosnr_linear = power / (ase_noise + nli_noise) return abs_to_db(gosnr_linear) - (12.5e9 / baud_rate) + @staticmethod + def get_ber(modulation_method, gosnr): + """ + AD: We need to check this function + Get's the bit error rate based on gOSNR + :return: BitErrorRate at this OPM + Calculates Bit Error Rate based on equations from F. Forghieri + doi: 10.1109/JLT.1012.2.2189198 + """ + ber=None + if modulation_method == '2QAM': + ber = 0.5 * erfc(sqrt(gosnr)) + if modulation_method == '4QAM': + ber = 0.5 * erfc(sqrt(gosnr/ 2)) + if modulation_method == '8QAM': + ber = (2 / 3) * erfc(sqrt((3 / 14) * gosnr)) + if modulation_method == '16QAM': + ber = (3 / 8) * erfc(sqrt(gosnr) / 10) + return ber + def receiver(self, optical_signal, in_port): """ Will verify that the signal can be received, then compute @@ -368,15 +388,19 @@ def receiver(self, optical_signal, in_port): if in_port in self.rx_to_channel: if self.rx_to_channel[in_port]['channel_id'] is optical_signal.index: + rx_transceiver = self.rx_to_channel[in_port]['transceiver'] # Get signal info + modulation_format = rx_transceiver.modulation_format power = optical_signal.loc_in_to_state[self]['power'] ase_noise = optical_signal.loc_in_to_state[self]['ase_noise'] nli_noise = optical_signal.loc_in_to_state[self]['nli_noise'] + # Compute OSNR and gOSNR osnr = self.osnr(power, ase_noise) gosnr = self.gosnr(power, ase_noise, nli_noise, optical_signal.symbol_rate) + ber = self.get_ber(modulation_format, gosnr) signalInfoDict[optical_signal]['osnr'] = osnr signalInfoDict[optical_signal]['gosnr'] = gosnr @@ -390,10 +414,17 @@ def receiver(self, optical_signal, in_port): signalInfoDict[optical_signal]['success'] = False self.receiver_callback(in_port, signalInfoDict) else: - print("*** %s receiving %s at port %s: Success!\ngOSNR: %f dB" % - (self.name, optical_signal, in_port, gosnr)) - print("OSNR: %f dB" % osnr) + if ber!=None: + print("*** %s receiving %s at port %s: Success! \t modulation format: %s\n" + "gOSNR: %f dB | OSNR: %f db |ber: %e" % + (self.name, optical_signal, in_port, modulation_format, gosnr, osnr, ber)) + else: + print( + "*** %s receiving %s at port %s: Success! \t modulation format: %s\n" + "gOSNR: %f dB | OSNR: %f db | ber: None" % + (self.name, optical_signal, in_port, modulation_format, gosnr, osnr)) + print("OSNR: %f dB" % osnr) signalInfoDict[optical_signal]['success'] = True self.receiver_callback(in_port, signalInfoDict) else: From bf703dc9e7f35c03a364445217fcaf30a0e91c03 Mon Sep 17 00:00:00 2001 From: Alan Diaz Date: Mon, 24 May 2021 16:54:45 -0400 Subject: [PATCH 03/12] Roadm with amps (#60) * add preamp and boost to roadm; fix simulation tests * reverse enabling boost in link * fixing link * add emulation test with ROADM w/amps; enable Transceiver as param in Terminal constructor * add preamp and boost to ROADM; fix simulation tests; add emulation test with ROADM w/amps; enable Transceiver as param in Terminal constructor * change permissions * minor edit * minor edit * add preamp and boost to ROADM; fix simulation tests; add emulation test with ROADM w/amps; enable Transceiver as param in Terminal constructor --- dataplane.py | 3 + examples/config-roadm_with_amps.sh | 13 ++ examples/roadm_with_amps.py | 106 ++++++++++++++ link.py | 101 +++++-------- network.py | 3 +- node.py | 221 +++++++++++++++++------------ tests/loop_test.py | 40 ++++-- tests/loop_test_auto.py | 40 ++++-- tests/reroute_test.py | 45 ++++-- tests/tutorial.py | 15 +- topo/linear.py | 40 ++++-- 11 files changed, 415 insertions(+), 212 deletions(-) create mode 100755 examples/config-roadm_with_amps.sh create mode 100755 examples/roadm_with_amps.py diff --git a/dataplane.py b/dataplane.py index de57075f..02e604e6 100755 --- a/dataplane.py +++ b/dataplane.py @@ -292,6 +292,9 @@ def makeTransceiver( txid, args ): "Helper constructor for node.Transceiver" if isinstance( args, dict ): return Transceiver( txid, **args ) + if isinstance( args, Transceiver ): + # enable passing Transceiver as param + return args # Remove obsolete 'C' band parameter if any if len( args ) > 2 and args[ 2 ] == 'C': args = args[ :2 ] + args[ 3: ] diff --git a/examples/config-roadm_with_amps.sh b/examples/config-roadm_with_amps.sh new file mode 100755 index 00000000..eef961c9 --- /dev/null +++ b/examples/config-roadm_with_amps.sh @@ -0,0 +1,13 @@ +#!/bin/bash -x + +set -e # exit script on error + +# URL for REST server +url="localhost:8080"; t1=$url; t2=$url; r1=$url +curl="curl -s" + +$curl "$t1/connect?node=t1ðPort=1&wdmPort=2&channel=1" +$curl "$t2/connect?node=t2ðPort=1&wdmPort=2&channel=1" +$curl "$r1/connect?node=r1&port1=1&port2=2&channels=1" +$curl "$t1/turn_on?node=t1" +$curl "$t2/turn_on?node=t2" diff --git a/examples/roadm_with_amps.py b/examples/roadm_with_amps.py new file mode 100755 index 00000000..6be7678b --- /dev/null +++ b/examples/roadm_with_amps.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 + +""" + This script shows how to create Terminals by passing Transceiver + objects to their constructor method (lines 64-67); it also shows + how to create ROADMs by passing boost and preamp Amplifier objects + to their constructor method (lines 72-74) + + Simple linear topology: + h1 - s1 - t1 -- r1 -- t2 - s2 - h2 + + Transmitting 1 channel from t1 to t2 +""" + +from node import Transceiver, Amplifier +from dataplane import (Terminal, ROADM, OpticalLink, + OpticalNet as Mininet, km, m, dB, dBm) +from rest import RestServer +from ofcdemo.demolib import OpticalCLI as CLI + +from mininet.node import OVSBridge, Host +from mininet.topo import Topo +from mininet.log import setLogLevel, warning +from mininet.clean import cleanup + +from os.path import dirname, realpath, join +from subprocess import run +from sys import argv + + +def add_amp(node_name=None, type=None, gain_dB=None): + """ + Create an Amplifier object to add to a ROADM node + :param node_name: string + :param type: string ('boost' or 'preamp' + :param gain_dB: int or float + """ + label = '%s-%s' % (node_name, type) + if type == 'preamp': + return Amplifier(name=label, + target_gain=float(gain_dB), + boost=True, + monitor_mode='out') + else: + return Amplifier(name=label, + target_gain=float(gain_dB), + preamp=True, + monitor_mode='out') + + +class SingleROADMTopo(Topo): + """ + h1 - s1 - t1 -- r1 -- t2 - s2 - h2 + """ + def build(self): + "Build single ROADM topology" + # Packet network elements + hosts = [self.addHost(h) for h in ('h1', 'h2')] + switches = [self.addSwitch(s) + for s in ('s1', 's2')] + + line_terminals = [] + for i in range(2): + transceivers = [Transceiver(1, 'tr1', operation_power=0 * dBm)] + lt = self.addSwitch( + 't%s' % (i + 1), cls=Terminal, transceivers=transceivers, + monitor_mode='in') + line_terminals.append(lt) + t1 = line_terminals[0] + t2 = line_terminals[1] + + r1 = self.addSwitch('r1', cls=ROADM, + preamp=add_amp(node_name='r1', type='preamp', gain_dB=17.6), + boost=add_amp(node_name='r2', type='boost', gain_dB=17.0)) + + # Ethernet links + for h, s, t in zip(hosts, switches, line_terminals): + self.addLink(h, s) + self.addLink(s, t, port2=1) + + amp1 = ('amp1', {'target_gain': 25 * .22 * dB}) + amp2 = ('amp2', {'target_gain': 25 * .22 * dB}) + spans = [25 * km, amp1, 25 * km, amp2] + self.addLink(r1, t1, cls=OpticalLink, port1=1, port2=2, spans=spans) + self.addLink(r1, t2, cls=OpticalLink, port1=2, port2=2, spans=spans) + +def test(net): + "Run config script and simple test" + testdir = dirname(realpath(argv[0])) + script = join(testdir, 'config-roadm_with_amps.sh') + run(script) + assert net.pingPair() == 0 + +if __name__ == '__main__': + + cleanup() + setLogLevel('info') + + topo = SingleROADMTopo() + net = Mininet(topo=topo, switch=OVSBridge, controller=None) + restServer = RestServer(net) + net.start() + restServer.start() + test(net) if 'test' in argv else CLI(net) + restServer.stop() + net.stop() diff --git a/link.py b/link.py index c059eea4..01bb0021 100644 --- a/link.py +++ b/link.py @@ -2,6 +2,7 @@ from units import * from pprint import pprint from numpy import errstate +from node import LineTerminal, Roadm SpanTuple = namedtuple('Span', 'span amplifier') @@ -117,25 +118,22 @@ def propagate(self, is_last_port=False, safe_switch=False): :return: """ if self.propagate_simulation(): + in_port = self.dst_node.link_to_port_in[self] # use is instance instead of checking the class - if self.dst_node.__class__.__name__ == 'LineTerminal': + if isinstance(self.dst_node, LineTerminal): # we need to pass the signals individually and indicate # what port should match what signal for optical_signal in self.optical_signals: - in_port = self.dst_node.link_to_port_in[self] self.dst_node.include_optical_signal_in(optical_signal, in_port=in_port, src_node=self.src_node) self.dst_node.receiver(optical_signal, in_port) - else: - in_port = self.dst_node.link_to_port_in[self] + elif isinstance(self.dst_node, Roadm): for optical_signal in self.optical_signals: # if it's just one signal this enters just once. - # a single link could have multiple optical signals - # and the link only has an input port of reference for + # a single link could have multiple signals + # and a link only has an input port of reference for # the dst_node - self.dst_node.include_optical_signal_in(optical_signal, - in_port=in_port, src_node=self.src_node) - + self.dst_node.include_optical_signal_in_roadm(optical_signal, in_port, self.src_node) if is_last_port: self.dst_node.switch(in_port, self.src_node, safe_switch=safe_switch) @@ -146,28 +144,14 @@ def propagate_simulation(self): """ # get the output power of the signals at output boost port output_power_dict = {} - # If there is an amplifier compensating for the node - # attenuation, compute the physical effects + if self.boost_amp: for optical_signal in self.optical_signals: # associate boost_amp to optical signal at input interface self.boost_amp.include_optical_signal_in(optical_signal, in_port=0, src_node=self.src_node) - # Enabling amplifier system gain balancing check - while not (self.boost_amp.power_excursions_flag_1 and self.boost_amp.power_excursions_flag_2): - for optical_signal in self.optical_signals: - output_power_dict[optical_signal] = \ - self.boost_amp.output_amplified_power(optical_signal, dst_node=self.dst_node) - self.boost_amp.compute_power_excursions() - self.boost_amp.power_excursions_flags_off() + self.boost_amp.propagate(self.src_node, self.dst_node, self.optical_signals) - for optical_signal in self.optical_signals: - self.boost_amp.nli_compensation(optical_signal, dst_node=self.dst_node) - # Compute ASE noise generation - self.boost_amp.stage_amplified_spontaneous_emission_noise(optical_signal) - - # Needed for the subsequent computations - prev_amp = self.boost_amp for span, amplifier in self.spans: for optical_signal in self.optical_signals: # associate (Link, Span) to optical signal at input interface @@ -190,54 +174,41 @@ def propagate_simulation(self): # ase_noise=ase_noise_out, nli_noise=nli_noise_out, # tup_key=(self, span)) - if amplifier: + if not isinstance(self.src_node, LineTerminal): # Compute the nonlinear noise with the GN model self.output_nonlinear_noise(span) - # Compute SRS effects from the fibre - if self.srs_effect: - if len(self.optical_signals) > 1 and prev_amp: - self.zirngibl_srs(span) + # Compute SRS effects from the fibre + if self.srs_effect: + if len(self.optical_signals) > 1: + self.zirngibl_srs(span) - # Compute linear effects from the fibre - span_attenuation = db_to_abs(span.length * span.fibre_attenuation) - for optical_signal in self.optical_signals: - power_out = optical_signal.loc_out_to_state[(self, span)]['power'] / span_attenuation - ase_noise_out = optical_signal.loc_out_to_state[(self, span)]['ase_noise'] / span_attenuation - nli_noise_out = optical_signal.loc_out_to_state[(self, span)]['nli_noise'] / span_attenuation - - self.include_optical_signal_out(optical_signal, power=power_out, - ase_noise=ase_noise_out, nli_noise=nli_noise_out, - tup_key=(self, span)) - - # Compute amplifier compensation - if amplifier: - for optical_signal in self.optical_signals: - # associate amp to optical signal at input interface - amplifier.include_optical_signal_in(optical_signal, - in_port=0, src_node=self.src_node) - # Enabling balancing check - while not (amplifier.power_excursions_flag_1 and amplifier.power_excursions_flag_2): - for optical_signal in self.optical_signals: - amplifier.output_amplified_power(optical_signal, dst_node=self.dst_node) - amplifier.compute_power_excursions() - # Reset balancing flags to original settings - amplifier.power_excursions_flags_off() - - # Compute for the power + # Compute linear effects from the fibre + span_attenuation = db_to_abs(span.length * span.fibre_attenuation) for optical_signal in self.optical_signals: - amplifier.nli_compensation(optical_signal, dst_node=self.dst_node) - # Compute ASE noise generation - amplifier.stage_amplified_spontaneous_emission_noise(optical_signal, dst_node=self.dst_node) - - power_out = optical_signal.loc_out_to_state[amplifier]['power'] - ase_noise_out = optical_signal.loc_out_to_state[amplifier]['ase_noise'] - nli_noise_out = optical_signal.loc_out_to_state[amplifier]['nli_noise'] + power_out = optical_signal.loc_out_to_state[(self, span)]['power'] / span_attenuation + ase_noise_out = optical_signal.loc_out_to_state[(self, span)]['ase_noise'] / span_attenuation + nli_noise_out = optical_signal.loc_out_to_state[(self, span)]['nli_noise'] / span_attenuation self.include_optical_signal_out(optical_signal, power=power_out, - ase_noise=ase_noise_out, nli_noise=nli_noise_out) + ase_noise=ase_noise_out, nli_noise=nli_noise_out, + tup_key=(self, span)) + if amplifier: + amplifier.include_optical_signal_in(optical_signal, power=power_out, + ase_noise=ase_noise_out, nli_noise=nli_noise_out, + src_node=self.src_node) + + # Compute amplifier compensation + if amplifier: + amplifier.propagate(self.src_node, self.dst_node, self.optical_signals) + for optical_signal in self.optical_signals: + power_out = optical_signal.loc_out_to_state[amplifier]['power'] + ase_noise_out = optical_signal.loc_out_to_state[amplifier]['ase_noise'] + nli_noise_out = optical_signal.loc_out_to_state[amplifier]['nli_noise'] + + self.include_optical_signal_out(optical_signal, power=power_out, + ase_noise=ase_noise_out, nli_noise=nli_noise_out) - prev_amp = amplifier return True def zirngibl_srs(self, span): diff --git a/network.py b/network.py index b6211bb9..ab4f046c 100644 --- a/network.py +++ b/network.py @@ -69,7 +69,7 @@ def add_amplifier(self, name, amplifier_type='EDFA', **params): self.amplifiers.append(amplifier) return amplifier - def add_link(self, src_node, dst_node, src_out_port=-1, dst_in_port=-1, boost_amp=None, spans=None): + def add_link(self, src_node, dst_node, src_out_port=-1, dst_in_port=-1, spans=None): """ Add a uni-directional link :param src_node: source node in link @@ -83,7 +83,6 @@ def add_link(self, src_node, dst_node, src_out_port=-1, dst_in_port=-1, boost_am link = Link(src_node, dst_node, src_out_port=src_out_port, dst_in_port=dst_in_port, - boost_amp=boost_amp, spans=spans) self.links.append(link) diff --git a/node.py b/node.py index fe997606..f4be26af 100755 --- a/node.py +++ b/node.py @@ -604,12 +604,15 @@ class Roadm(Node): components (i.e., WSSs). """ - def __init__(self, name, insertion_loss_dB=17, reference_power_dBm=0, monitor_mode=None): + def __init__(self, name, insertion_loss_dB=17, reference_power_dBm=0, + preamp=None, boost=None, monitor_mode=None): """ :param name: string, name tag of ROADM :param insertion_loss_dB: int, linear insertion loss of ROADM (default 17 dB) :param reference_power_dBm: int, reference power for ROADM-variable optical attenuator (VOA) - (default 0 dBm) + :param preamp: Amplifier object + :param boost: Amplifier object :param monitor_mode: Monitor object """ Node.__init__(self, name) @@ -630,10 +633,18 @@ def __init__(self, name, insertion_loss_dB=17, reference_power_dBm=0, monitor_mo # expected output power of signals self.target_output_power_dBm = reference_power_dBm - insertion_loss_dB + self.preamp = preamp + self.boost = boost + def monitor_query(self): if self.monitor: return self.monitor + def include_optical_signal_in_roadm(self, optical_signal, in_port, src_node): + if self.preamp: + self.preamp.include_optical_signal_in(optical_signal, in_port=0, src_node=src_node) + self.include_optical_signal_in(optical_signal,in_port=in_port, src_node=src_node) + def install_switch_rule(self, in_port, out_port, signal_indices, src_node=None): """ Switching rule installation, accessible from a Control System @@ -818,6 +829,9 @@ def switch(self, in_port, src_node, safe_switch=False): port_to_optical_signal_out, port_out_to_port_in_signals = self.can_switch_from_lt(src_node, safe_switch) else: port_to_optical_signal_out, port_out_to_port_in_signals = self.can_switch(in_port, safe_switch) + + self.prepropagation(port_out_to_port_in_signals, src_node) + # we will propagate and route signals at each out port individually for out_port, in_port_signals in port_out_to_port_in_signals.items(): # we need to pass all the signals at a given in port to compute @@ -826,19 +840,41 @@ def switch(self, in_port, src_node, safe_switch=False): self.propagate(out_port, in_port, optical_signals) self.route(out_port, safe_switch) - def compute_carrier_attenuation(self, in_port): + def prepropagation(self, port_out_to_port_in_signals, src_node): + """ + Preparing structures for propagation + """ + for out_port, in_port_signals in port_out_to_port_in_signals.items(): + dst_node = self.port_to_node_out[out_port] + if isinstance(dst_node, LineTerminal) or \ + (self.preamp and not isinstance(src_node, LineTerminal) + and not isinstance(dst_node, LineTerminal)): + # we need to pass all the signals at a given in port to compute + # the carrier's attenuation in self.propagate() + for in_port, optical_signals in in_port_signals.items(): + if self.preamp: + # need to process signal before switch-based propagation + self.preamp.propagate(src_node, dst_node, optical_signals) + + def compute_carrier_attenuation(self, in_port, amp=None): """ Compute the total power at an input port, and use it to compute the carriers attenuation """ carriers_power = [] for optical_signal in self.port_to_optical_signal_in[in_port]: - power_in = optical_signal.loc_in_to_state[self]['power'] - ase_noise_in = optical_signal.loc_in_to_state[self]['ase_noise'] - nli_noise_in = optical_signal.loc_in_to_state[self]['nli_noise'] + if amp: + power_in = optical_signal.loc_out_to_state[amp]['power'] + ase_noise_in = optical_signal.loc_out_to_state[amp]['ase_noise'] + nli_noise_in = optical_signal.loc_out_to_state[amp]['nli_noise'] + else: + power_in = optical_signal.loc_in_to_state[self]['power'] + ase_noise_in = optical_signal.loc_in_to_state[self]['ase_noise'] + nli_noise_in = optical_signal.loc_in_to_state[self]['nli_noise'] total_power = power_in + ase_noise_in + nli_noise_in carriers_power.append(total_power) + carriers_att = list(map( lambda x: abs_to_db(x * 1e3) - self.target_output_power_dBm, carriers_power)) exceeding_att = -min(list(filter(lambda x: x < 0, carriers_att)), default=0) @@ -846,28 +882,72 @@ def compute_carrier_attenuation(self, in_port): return carriers_att - def propagate(self, out_port, in_port, optical_signals): - carriers_att = self.compute_carrier_attenuation(in_port) + def process_att(self, out_port, in_port, optical_signals, src_node, dst_node, link, amp=None): + """ + Compute the attenuation effects at the ROADM + """ + # Compute per carrier attenuation + carriers_att = self.compute_carrier_attenuation(in_port, amp=amp) - link = self.port_to_link_out[out_port] for i, optical_signal in enumerate(optical_signals): - # attenuate signal power - power_in = optical_signal.loc_in_to_state[self]['power'] - ase_noise_in = optical_signal.loc_in_to_state[self]['ase_noise'] - nli_noise_in = optical_signal.loc_in_to_state[self]['nli_noise'] - - power_out = power_in / carriers_att[i] - ase_noise_out = ase_noise_in / carriers_att[i] - nli_noise_out = nli_noise_in / carriers_att[i] - - # update the structures for that direction - # all these signals are going towards the same out port - link.include_optical_signal_in(optical_signal, power=power_out, - ase_noise=ase_noise_out, nli_noise=nli_noise_out) - dst_node = self.port_to_node_out[out_port] - self.include_optical_signal_out(optical_signal, power=power_out, - ase_noise=ase_noise_out, nli_noise=nli_noise_out, - out_port=out_port, dst_node=dst_node) + if amp: + # attenuate signals at output interface of amp + power_out = optical_signal.loc_out_to_state[amp]['power'] / carriers_att[i] + ase_noise_out = optical_signal.loc_out_to_state[amp]['ase_noise'] / carriers_att[i] + nli_noise_out = optical_signal.loc_out_to_state[amp]['nli_noise'] / carriers_att[i] + else: + # attenuate signals as they entered the ROADM (self) + power_out = optical_signal.loc_in_to_state[self]['power'] / carriers_att[i] + ase_noise_out = optical_signal.loc_in_to_state[self]['ase_noise'] / carriers_att[i] + nli_noise_out = optical_signal.loc_in_to_state[self]['nli_noise'] / carriers_att[i] + + if self.boost and not isinstance(dst_node, LineTerminal): + # need to pass signals to boost for processing + self.boost.include_optical_signal_in(optical_signal, power=power_out, + ase_noise=ase_noise_out, nli_noise=nli_noise_out, + in_port=0, src_node=src_node) + else: + # update the structures for that direction + # all these signals are going towards the same out port + link.include_optical_signal_in(optical_signal, power=power_out, + ase_noise=ase_noise_out, nli_noise=nli_noise_out) + self.include_optical_signal_out(optical_signal, power=power_out, + ase_noise=ase_noise_out, nli_noise=nli_noise_out, + out_port=out_port, dst_node=dst_node) + + if self.boost and not isinstance(dst_node, LineTerminal): + # process boost amp + self.boost.propagate(src_node, dst_node, optical_signals) + + # pass signals to link and update state at ROADM (self) + for i, optical_signal in enumerate(optical_signals): + power_out = optical_signal.loc_out_to_state[self.boost]['power'] + ase_noise_out = optical_signal.loc_out_to_state[self.boost]['ase_noise'] + nli_noise_out = optical_signal.loc_out_to_state[self.boost]['nli_noise'] + + # update the structures for that direction + # all these signals are going towards the same out port + link.include_optical_signal_in(optical_signal, power=power_out, + ase_noise=ase_noise_out, nli_noise=nli_noise_out) + self.include_optical_signal_out(optical_signal, power=power_out, + ase_noise=ase_noise_out, nli_noise=nli_noise_out, + out_port=out_port, dst_node=dst_node) + + def propagate(self, out_port, in_port, optical_signals): + """ + Compute physical layer simulation for one direction given by the out_port + """ + src_node = self.port_to_node_in[in_port] + dst_node = self.port_to_node_out[out_port] + link = self.port_to_link_out[out_port] + + if isinstance(dst_node, LineTerminal) or \ + (self.preamp and not isinstance(src_node, LineTerminal) + and not isinstance(dst_node, LineTerminal)): + self.process_att(out_port, in_port, optical_signals, src_node, dst_node, link, amp=self.preamp) + else: + self.process_att(out_port, in_port, optical_signals, src_node, dst_node, link) + def route(self, out_port, safe_switch): """Calling route will continue to propagate the signals in this link""" @@ -880,8 +960,7 @@ class Amplifier(Node): def __init__(self, name, amplifier_type='EDFA', target_gain=17.6, noise_figure=(5.5, 91), noise_figure_function=None, bandwidth=32.0e9, wavelength_dependent_gain_id=None, - boost=False, monitor_mode=None, equalization_function=None, - equalization_target_out_power=0): + preamp=False, boost=False, monitor_mode=None): """ :param target_gain: units: dB - float :param noise_figure: tuple with NF value in dB and number of channels (def. 90) @@ -890,13 +969,18 @@ def __init__(self, name, amplifier_type='EDFA', target_gain=17.6, :param wavelength_dependent_gain_id: file name id (see top of script) units: dB - string """ Node.__init__(self, name) + # FIXME: (AD) id and type are not needed self.id = id(self) self.type = amplifier_type self.target_gain = target_gain self.system_gain = target_gain + # FIXME: (AD) is there a better way of allowing + # the declaration of a noise figure function? self.noise_figure = self.get_noise_figure(noise_figure, noise_figure_function) self.bandwidth = bandwidth + # FIXME: (AD) wdgfunc does nothing self.wdgfunc = None + wavelength_dependent_gain_id = 'linear' self.wavelength_dependent_gain = ( self.load_wavelength_dependent_gain(wavelength_dependent_gain_id)) @@ -907,70 +991,14 @@ def __init__(self, name, amplifier_type='EDFA', target_gain=17.6, self.power_excursions_flag_1 = False self.power_excursions_flag_2 = False - # FIXME: (AD) is this needed? - # if equalization_function: - # self.equalization_attenuation = db_to_abs(3) - # self.equalization_function = equalization_function - # self.equalization_target_out_power = None - # self.equalization_compensation = \ - # self.equalization_safety_check(equalization_function, equalization_target_out_power) - # self.equalization_flag_1 = True - # self.equalization_flag_2 = True - # else: - # self.equalization_flag_1 = False - # self.equalization_flag_2 = False - + # FIXME: (AD) Will this be needed if booster placed in ROADM? self.boost = boost + self.preamp = preamp def monitor_query(self): if self.monitor: return self.monitor - # FIXME: (AD) is this needed? - # def equalization_safety_check(self, equalization_function, equalization_target_out_power): - # """ - # Safety check for the declaration of equalization reconfiguration parameters - # :param equalization_function: string (i.e., 'flatten') - # :param equalization_target_out_power: float - # :return: True equalization reconf False otherwise - # """ - # if equalization_target_out_power is not None: - # # This check is to avoid pythonic-responses - # # if equalization_target_out_power is set to zero - # equalization_target_out_power = db_to_abs(equalization_target_out_power) - # self.equalization_target_out_power = equalization_target_out_power - # try: - # err_msg = "Roadm.equalization_safety_check: inconsistent declaration of equalization params." - # # Either both are passed or None - # assert all([equalization_function, equalization_target_out_power]) or \ - # all(x is None for x in [equalization_function, equalization_target_out_power]), err_msg - # except AssertionError as err: - # raise err - # if all([equalization_function, equalization_target_out_power]): - # return True - # return False - # - # def equalization_reconf(self, link, output_power_dict): - # """ - # wavelength dependent attenuation - # """ - # pass - # # if self.equalization_function == 'flatten': - # # # compute equalization compensation and re-propagate only if there is a function - # # out_difference = {} - # # for k, out_power in output_power_dict.items(): - # # # From the boost-amp, compute the difference between output power levels - # # # and the target output power. Set this as the compensation function. - # # delta = self.equalization_target_out_power / out_power - # # out_difference[k] = delta - # # - # # for optical_signal, equalization_att in out_difference.items(): - # # power = optical_signal.loc_in_to_state[self]['power'] * equalization_att - # # ase_noise = optical_signal.loc_in_to_state[self]['ase_noise'] * equalization_att - # # nli_noise = optical_signal.loc_in_to_state[self]['nli_noise'] * equalization_att - # # self.include_optical_signal_in((optical_signal, optical_signal.uid), power=power, - # # ase_noise=ase_noise, nli_noise=nli_noise) - def reset_gain(self): self.system_gain = self.target_gain @@ -1106,13 +1134,32 @@ def compute_power_excursions(self): if not (self.power_excursions_flag_1 and self.power_excursions_flag_2): self.power_excursions_flag_1 = True - def clean_optical_signals(self): - return + def propagate(self, src_node, dst_node, optical_signals): + """ + Compute the amplification process + :param src_node: Node object + :param dst_node: Node object + :param optical_signals: list + """ + # Enabling balancing check + while not (self.power_excursions_flag_1 and self.power_excursions_flag_2): + for optical_signal in optical_signals: + self.output_amplified_power(optical_signal, dst_node=dst_node) + self.compute_power_excursions() + # Reset balancing flags to original settings + self.power_excursions_flags_off() + + # Compute for the power + for optical_signal in optical_signals: + self.nli_compensation(optical_signal, dst_node=dst_node) + # Compute ASE noise generation + self.stage_amplified_spontaneous_emission_noise(optical_signal, dst_node=dst_node) def __repr__(self): """String representation""" return '<%s %.1fdB>' % (self.name, self.target_gain) + # FIXME: (AD) This will change # ADDITIONS FOR OFC DEMO USE-CASES def mock_amp_gain_adjust(self, new_gain): self.target_gain = new_gain diff --git a/tests/loop_test.py b/tests/loop_test.py index a121a8e4..f7d784ed 100644 --- a/tests/loop_test.py +++ b/tests/loop_test.py @@ -28,7 +28,6 @@ def Span(km, amp=None): """Return a fiber segment of length km with a compensating amp""" return Segment(span=Fiber(length=km), amplifier=amp) - def build_spans(net, r1, r2): """ Helper function for building spans of @@ -43,25 +42,39 @@ def build_spans(net, r1, r2): for i in range(1, span_no + 1): # append all spans except last one - amp = net.add_amplifier( - '%s-%s-amp%d' % (r1, r2, i), target_gain=span_length * 0.22 * dB, monitor_mode='out') - span = Span(span_length, amp=amp) + # amp = net.add_amplifier( + # '%s-%s-amp%d' % (r1, r2, i), target_gain=span_length * 0.22 * dB, monitor_mode='out') + span = Span(span_length, amp=None) spans.append(span) return net, spans - -def build_link(net, r1, r2, gain=17.0): - # boost amplifier object - boost_l = '%s-%s-boost' % (r1, r2) # label boost amp - boost_amp = net.add_amplifier(name=boost_l, amplifier_type='EDFA', target_gain=float(gain), boost=True, monitor_mode='out') - +def build_link(net, r1, r2): net, spans = build_spans(net, r1, r2) for step, span in enumerate(spans, start=1): net.spans.append(span) # link object - net.add_link(r1, r2, boost_amp=boost_amp, spans=spans) + net.add_link(r1, r2, spans=spans) + +def add_amp(net, node_name=None, type=None, gain_dB=None): + """ + Create an Amplifier object to add to a ROADM node + :param node_name: string + :param type: string ('boost' or 'preamp' + :param gain_dB: int or float + """ + label = '%s-%s' % (node_name, type) + if type == 'preamp': + return net.add_amplifier(name=label, + target_gain=float(gain_dB), + boost=True, + monitor_mode='out') + else: + return net.add_amplifier(name=label, + target_gain=float(gain_dB), + preamp=True, + monitor_mode='out') # Create the network object @@ -82,7 +95,10 @@ def build_link(net, r1, r2, gain=17.0): roadms = [net.add_roadm('r%s' % (i + 1), insertion_loss_dB=17, - reference_power_dBm=operational_power) for i in range(non)] + reference_power_dBm=operational_power, + preamp=add_amp(net, node_name='r%s' % (i + 1), type='preamp', gain_dB=17.6), + boost=add_amp(net, node_name='r%s' % (i + 1), type='boost', gain_dB=17.0) + ) for i in range(non)] # Modelling Lumentum ROADM-20 port numbering roadm20_in_ports = [i + 1 for i in range(4100, 4120)] diff --git a/tests/loop_test_auto.py b/tests/loop_test_auto.py index 752fd889..5b6bc754 100644 --- a/tests/loop_test_auto.py +++ b/tests/loop_test_auto.py @@ -29,7 +29,6 @@ def Span(km, amp=None): """Return a fiber segment of length km with a compensating amp""" return Segment(span=Fiber(length=km), amplifier=amp) - def build_spans(net, r1, r2): """ Helper function for building spans of @@ -44,25 +43,39 @@ def build_spans(net, r1, r2): for i in range(1, span_no + 1): # append all spans except last one - amp = net.add_amplifier( - '%s-%s-amp%d' % (r1, r2, i), target_gain=span_length * 0.22 * dB, monitor_mode='out') - span = Span(span_length, amp=amp) + # amp = net.add_amplifier( + # '%s-%s-amp%d' % (r1, r2, i), target_gain=span_length * 0.22 * dB, monitor_mode='out') + span = Span(span_length, amp=None) spans.append(span) return net, spans - -def build_link(net, r1, r2, gain=17.00022): - # boost amplifier object - boost_l = '%s-%s-boost' % (r1, r2) # label boost amp - boost_amp = net.add_amplifier(name=boost_l, amplifier_type='EDFA', target_gain=float(gain), boost=True, monitor_mode='out') - +def build_link(net, r1, r2): net, spans = build_spans(net, r1, r2) for step, span in enumerate(spans, start=1): net.spans.append(span) # link object - net.add_link(r1, r2, boost_amp=boost_amp, spans=spans) + net.add_link(r1, r2, spans=spans) + +def add_amp(net, node_name=None, type=None, gain_dB=None): + """ + Create an Amplifier object to add to a ROADM node + :param node_name: string + :param type: string ('boost' or 'preamp' + :param gain_dB: int or float + """ + label = '%s-%s' % (node_name, type) + if type == 'preamp': + return net.add_amplifier(name=label, + target_gain=float(gain_dB), + boost=True, + monitor_mode='out') + else: + return net.add_amplifier(name=label, + target_gain=float(gain_dB), + preamp=True, + monitor_mode='out') def install_paths(nodes, channels, line_terminals): @@ -97,7 +110,10 @@ def install_paths(nodes, channels, line_terminals): # If you change the launch power of signals, remember to configure # the ROADM parameters: insertion_loss_dB and reference_power_dBm; # default reference_power_dBm is 0 dBm -roadms = [net.add_roadm('r%s' % (i + 1)) for i in range(non)] +roadms = [net.add_roadm('r%s' % (i + 1), + preamp=add_amp(net, node_name='r%s' % (i + 1), type='preamp', gain_dB=17.6), + boost=add_amp(net, node_name='r%s' % (i + 1), type='boost', gain_dB=17.0) + ) for i in range(non)] # Modelling Lumentum ROADM-20 port numbering roadm20_in_ports = [i + 1 for i in range(4100, 4120)] diff --git a/tests/reroute_test.py b/tests/reroute_test.py index 8917d176..5bacb86a 100644 --- a/tests/reroute_test.py +++ b/tests/reroute_test.py @@ -2,9 +2,11 @@ This script models a linear topology between two line terminals with two ROADMs in between and two links between ROADMS: - ---> (link1) + (link1) + ------> lt1 ---> r1 r2 ----> lt2 - ---> (link2) + ------> + (link2) lt1 will transmit channel 1 at 0 dBm launch power and r1 will switch from port 4101 out to port 5203 @@ -24,12 +26,10 @@ km = dB = dBm = 1.0 m = .001 - def Span(km, amp=None): """Return a fiber segment of length km with a compensating amp""" return Segment(span=Fiber(length=km), amplifier=amp) - def build_spans(net, r1, r2, _id): """ Helper function for building spans of @@ -51,27 +51,39 @@ def build_spans(net, r1, r2, _id): return net, spans - -def build_link(net, r1, r2, _id, gain=17.0): - # boost amplifier object - boost_l = '%s-%s-boost%s' % (r1, r2, _id) # label boost amp - boost_amp = net.add_amplifier(name=boost_l, amplifier_type='EDFA', - target_gain=float(gain), boost=True, - monitor_mode='out') - +def build_link(net, r1, r2, _id): net, spans = build_spans(net, r1, r2, _id) for step, span in enumerate(spans, start=1): net.spans.append(span) # link object - net.add_link(r1, r2, boost_amp=boost_amp, spans=spans) + net.add_link(r1, r2, spans=spans) + +def add_amp(net, node_name=None, type=None, gain_dB=None): + """ + Create an Amplifier object to add to a ROADM node + :param node_name: string + :param type: string ('boost' or 'preamp' + :param gain_dB: int or float + """ + label = '%s-%s' % (node_name, type) + if type == 'preamp': + return net.add_amplifier(name=label, + target_gain=float(gain_dB), + boost=True, + monitor_mode='out') + else: + return net.add_amplifier(name=label, + target_gain=float(gain_dB), + preamp=True, + monitor_mode='out') # Create the network object net = network.Network() # Create line terminals operational_power = 0 # power in dBm -non = 2 # only 3 nodes in this script! +non = 2 # only 2 nodes in this script! tr_no = [1, 2] # number of transceivers in a terminal tr_labels = ['tr%s' % str(x) for x in tr_no] @@ -86,7 +98,10 @@ def build_link(net, r1, r2, _id, gain=17.0): # add roadms to the network roadms = [net.add_roadm('r%s' % (i + 1), insertion_loss_dB=17, - reference_power_dBm=operational_power) for i in range(non)] + reference_power_dBm=operational_power, + preamp=add_amp(net, node_name='r%s' % (i + 1), type='preamp', gain_dB=17.6), + boost=add_amp(net, node_name='r%s' % (i + 1), type='boost', gain_dB=17.0) + ) for i in range(non)] # Modelling Lumentum ROADM-20 port numbering roadm20_in_ports = [i + 1 for i in range(4100, 4120)] diff --git a/tests/tutorial.py b/tests/tutorial.py index 806c7374..28248ac5 100644 --- a/tests/tutorial.py +++ b/tests/tutorial.py @@ -95,12 +95,13 @@ def write_files(osnrs, gosnrs, p): lt_1.turn_on() # print("*** Monitoring interfaces") - osnrs = [] - gosnrs = [] - # Iterate through monitoring nodes at each EDFA - for amp in net.amplifiers: - # print(amp.monitor.get_list_osnr()) - osnrs.append(amp.monitor.get_list_osnr()[cut][1]) - gosnrs.append(amp.monitor.get_list_gosnr()[cut][1]) + # osnrs = [] + # gosnrs = [] + # # Iterate through monitoring nodes at each EDFA + # for amp in net.amplifiers: + # # print(amp.monitor.get_list_osnr()) + # tmp = amp.monitor.get_list_osnr() + # osnrs.append(amp.monitor.get_list_osnr()[cut]) + # gosnrs.append(amp.monitor.get_list_gosnr()[cut]) # write_files(osnrs, gosnrs, p) diff --git a/topo/linear.py b/topo/linear.py index a2ab1a82..726468d7 100644 --- a/topo/linear.py +++ b/topo/linear.py @@ -25,26 +25,39 @@ def build_spans(net, r1, r2): for i in range(1, span_no + 1): # append all spans except last one - amp = net.add_amplifier( - '%s-%s-amp%d' % (r1, r2, i), target_gain=span_length * 0.22 * dB, monitor_mode='out') - span = Span(span_length, amp=amp) + # amp = net.add_amplifier( + # '%s-%s-amp%d' % (r1, r2, i), target_gain=span_length * 0.22 * dB, monitor_mode='out') + span = Span(span_length, amp=None) spans.append(span) return net, spans - -def build_link(net, r1, r2, gain=17.0): - # boost amplifier object - boost_l = '%s-%s-boost' % (r1, r2) # label boost amp - boost_amp = net.add_amplifier(name=boost_l, amplifier_type='EDFA', - target_gain=float(gain), boost=True, monitor_mode='out') - +def build_link(net, r1, r2): net, spans = build_spans(net, r1, r2) for step, span in enumerate(spans, start=1): net.spans.append(span) # link object - net.add_link(r1, r2, boost_amp=boost_amp, spans=spans) + net.add_link(r1, r2, spans=spans) + +def add_amp(net, node_name=None, type=None, gain_dB=None): + """ + Create an Amplifier object to add to a ROADM node + :param node_name: string + :param type: string ('boost' or 'preamp' + :param gain_dB: int or float + """ + label = '%s-%s' % (node_name, type) + if type == 'preamp': + return net.add_amplifier(name=label, + target_gain=float(gain_dB), + boost=True, + monitor_mode='out') + else: + return net.add_amplifier(name=label, + target_gain=float(gain_dB), + preamp=True, + monitor_mode='out') class LinearTopology: @@ -70,7 +83,10 @@ def build(op=0, non=3): roadms = [net.add_roadm('r%s' % (i + 1), insertion_loss_dB=17, - reference_power_dBm=op) for i in range(non)] + reference_power_dBm=op, + preamp=add_amp(net, node_name='r%s' % (i + 1), type='preamp', gain_dB=17.6), + boost=add_amp(net, node_name='r%s' % (i + 1), type='boost', gain_dB=17.0)) + for i in range(non)] name_to_roadm = {roadm.name: roadm for roadm in roadms} # Modelling Lumentum ROADM-20 port numbering From 74c0fb6b95025a6fa50df66545f68962badf2e7d Mon Sep 17 00:00:00 2001 From: Alan Diaz Date: Tue, 25 May 2021 19:34:38 -0400 Subject: [PATCH 04/12] decomposing link (#61) decomposed propagation algorithm @Link, moved Link phy-models to Span class and cleaned code --- link.py | 374 +++++++++++++++++++++---------------------- network.py | 4 +- node.py | 146 +++++++---------- tests/simple_link.py | 141 ++++++++++++++++ 4 files changed, 386 insertions(+), 279 deletions(-) create mode 100644 tests/simple_link.py diff --git a/link.py b/link.py index 01bb0021..4476c7ba 100644 --- a/link.py +++ b/link.py @@ -2,7 +2,7 @@ from units import * from pprint import pprint from numpy import errstate -from node import LineTerminal, Roadm +from node import LineTerminal, Roadm, Amplifier SpanTuple = namedtuple('Span', 'span amplifier') @@ -24,7 +24,6 @@ def __init__(self, src_node, dst_node, src_out_port=-1, dst_in_port=-1, if src_node == dst_node: raise ValueError("link.__init__ src_node must be different from dst_node!") # configuration attributes - self.id = id(self) self.src_node = src_node self.dst_node = dst_node self.boost_amp = boost_amp @@ -35,23 +34,40 @@ def __init__(self, src_node, dst_node, src_out_port=-1, dst_in_port=-1, # set connection ports for amps and the link if boost_amp: - boost_amp.set_output_port(self.dst_node, self, output_port=0) - boost_amp.set_input_port(self.src_node, self, input_port=0) - for span, amplifier in spans: + self.boost_amp.prev_component = src_node + self.boost_amp.next_component = spans[0][0] + + prev_amp = None + for i, span in enumerate(spans): + prev_span = span[0] + prev_span.link = self + amplifier = span[1] + + if i == 0: + prev_span.prev_component = src_node + else: + prev_span.prev_component = prev_amp + if amplifier: + amplifier.link = self amplifier.set_output_port(self.dst_node, self, output_port=0) amplifier.set_input_port(self.src_node, self, input_port=0) + + amplifier.prev_component = prev_span + if i + 1 < len(spans): + next_span = spans[i + 1][0] + amplifier.next_component = next_span + else: + amplifier.next_component = dst_node + + prev_amp = amplifier + prev_span.next_component = amplifier + else: + prev_span.next_component = dst_node + self.src_node.set_output_port(self.dst_node, self, output_port=src_out_port) self.dst_node.set_input_port(self.src_node, self, input_port=dst_in_port) - def add_span(self, span, amplifier): - """ - :param span: Span() object - :param amplifier: Amplifier() object - :return: appends a SpanTuple to the spans attribute - """ - self.spans.append(SpanTuple(span, amplifier)) - def length(self): """ :return: link length adding up span lengths in spans attribute @@ -92,12 +108,9 @@ def include_optical_signal_in(self, optical_signal, power=None, if optical_signal not in self.optical_signals: self.optical_signals.append(optical_signal) - if tup_key: - optical_signal.assoc_loc_in(tup_key, power, ase_noise, nli_noise) - else: - optical_signal.assoc_loc_in(self, power, ase_noise, nli_noise) + optical_signal.assoc_loc_in(self, power, ase_noise, nli_noise) - def include_optical_signal_out(self, optical_signal, power=None, ase_noise=None, nli_noise=None, tup_key=None): + def include_optical_signal_out(self, optical_signal, power=None, ase_noise=None, nli_noise=None): """ Include optical signal in optical_signals_out :param optical_signal: OpticalSignal object @@ -106,116 +119,154 @@ def include_optical_signal_out(self, optical_signal, power=None, ase_noise=None, :param nli_noise: nli noise level of OpticalSignal :param tup_key: tuple key composed of (Link, Span) """ - if tup_key: - optical_signal.assoc_loc_out(tup_key, power, ase_noise, nli_noise) - else: - optical_signal.assoc_loc_out(self, power, ase_noise, nli_noise) + optical_signal.assoc_loc_out(self, power, ase_noise, nli_noise) def propagate(self, is_last_port=False, safe_switch=False): """ Propagate the signals across the link - :param is_last_port: - :return: - """ - if self.propagate_simulation(): - in_port = self.dst_node.link_to_port_in[self] - # use is instance instead of checking the class - if isinstance(self.dst_node, LineTerminal): - # we need to pass the signals individually and indicate - # what port should match what signal - for optical_signal in self.optical_signals: - self.dst_node.include_optical_signal_in(optical_signal, - in_port=in_port, src_node=self.src_node) - self.dst_node.receiver(optical_signal, in_port) - elif isinstance(self.dst_node, Roadm): - for optical_signal in self.optical_signals: - # if it's just one signal this enters just once. - # a single link could have multiple signals - # and a link only has an input port of reference for - # the dst_node - self.dst_node.include_optical_signal_in_roadm(optical_signal, in_port, self.src_node) - if is_last_port: - self.dst_node.switch(in_port, self.src_node, safe_switch=safe_switch) - - def propagate_simulation(self): - """ - Compute the propagation of signals over this link + :param is_last_port: boolean, needed for propagation algorithm + :param safe_switch: boolean, needed for propagation algorithm :return: """ - # get the output power of the signals at output boost port - output_power_dict = {} - if self.boost_amp: for optical_signal in self.optical_signals: # associate boost_amp to optical signal at input interface self.boost_amp.include_optical_signal_in(optical_signal, - in_port=0, src_node=self.src_node) - self.boost_amp.propagate(self.src_node, self.dst_node, self.optical_signals) + in_port=0) + self.boost_amp.propagate(self.optical_signals, + is_last_port=is_last_port, + safe_switch=safe_switch) + else: + first_span = self.spans[0][0] + for optical_signal in self.optical_signals: + first_span.include_optical_signal_in(optical_signal) + first_span.propagate(is_last_port=is_last_port, + safe_switch=safe_switch) + +class Span(object): + + ids = 1 + + def __init__(self, fibre_type='SMF', length=20.0): + """ + :param length: optical fiber span length in km - float + :param fibre_type: optical fiber type - string + """ + self.span_id = Span.ids + Span.ids += 1 + self.fibre_type = fibre_type + self.length = length * km + self.fibre_attenuation = 0.22 / km # fiber attenuation in decibels/km + self.alpha = self.fibre_attenuation / (20 * np.log10(np.e)) # linear value fibre attenuation + self.effective_length = (1 - np.exp(-2 * self.alpha * self.length)) / (2 * self.alpha) + self.non_linear_coefficient = 0.78 / km # gamma fiber non-linearity coefficient [W^-1 km^-1] + self.dispersion = 2.1e-05 + self.dispersion_coefficient = self.beta2() # B_2 dispersion coefficient [ps^2 km^-1] + self.dispersion_slope = 0.1452 * (ps ** 3 / km) # B_3 dispersion slope in (ps^3 km^-1) + self.effective_area = 80 * um * um # Aeff - SMF effective area + self.raman_gain = 7.0 * 1e-12 * cm / W # r - Raman Gain in SMF + self.raman_amplification_band = 15 * THz # Raman amplification band ~15THz + # Raman coefficient + self.raman_coefficient = self.raman_gain / (2 * self.effective_area * self.raman_amplification_band) + + self.optical_signals = [] + self.link = None + self.prev_component = None + self.next_component = None + + def describe(self): + pprint(vars(self)) + + def __repr__(self): + """String representation""" + return '<%d %.1fkm>' % (self.span_id, self.length/km) + + def attenuation(self): + return db_to_abs(self.fibre_attenuation * self.length) + + def beta2(self, ref_wavelength=1550e-9): + """Returns beta2 from dispersion parameter. + Dispersion is entered in ps/nm/km. + Translated from the GNPy project source code + :param ref_wavelength: can be a numpy array; default: 1550nm + """ + D = abs(self.dispersion) + b2 = (ref_wavelength ** 2) * D / (2 * pi * c) # 10^21 scales [ps^2/km] + return b2 # s/Hz/m + + def include_optical_signal_in(self, optical_signal, power=None, + ase_noise=None, nli_noise=None): + """ + Include optical signal in optical_signals + :param optical_signal: OpticalSignal object + :param power: power level of OpticalSignal + :param ase_noise: ase noise level of OpticalSignal + :param nli_noise: nli noise level of OpticalSignal + """ + if optical_signal not in self.optical_signals: + self.optical_signals.append(optical_signal) + optical_signal.assoc_loc_in(self, power, ase_noise, nli_noise) + + def include_optical_signal_out(self, optical_signal, power=None, + ase_noise=None, nli_noise=None): + """ + Include optical signal in optical_signals_out + :param optical_signal: OpticalSignal object + :param power: power level of OpticalSignal + :param ase_noise: ase noise level of OpticalSignal + :param nli_noise: nli noise level of OpticalSignal + """ + optical_signal.assoc_loc_out(self, power, ase_noise, nli_noise) + + def propagate(self, is_last_port=False, safe_switch=False): + for optical_signal in self.optical_signals: + power_in = optical_signal.loc_in_to_state[self]['power'] + ase_noise_in = optical_signal.loc_in_to_state[self]['ase_noise'] + nli_noise_in = optical_signal.loc_in_to_state[self]['nli_noise'] + + self.include_optical_signal_out(optical_signal, power=power_in, + ase_noise=ase_noise_in, nli_noise=nli_noise_in) + self.link.include_optical_signal_out(optical_signal, power=power_in, + ase_noise=ase_noise_in, nli_noise=nli_noise_in) + + if not isinstance(self.prev_component, LineTerminal): + # Compute the nonlinear noise with the GN model + self.output_nonlinear_noise() + # Compute SRS effects from the fibre + if self.link.srs_effect: + if len(self.optical_signals) > 1: + self.zirngibl_srs() - for span, amplifier in self.spans: for optical_signal in self.optical_signals: - # associate (Link, Span) to optical signal at input interface - self.include_optical_signal_in(optical_signal, tup_key=(self, span)) - power_in = optical_signal.loc_in_to_state[(self, span)]['power'] - ase_noise_in = optical_signal.loc_in_to_state[(self, span)]['ase_noise'] - nli_noise_in = optical_signal.loc_in_to_state[(self, span)]['nli_noise'] - # this will initialize the output state of the signal - # that will enable the subsequent computations - self.include_optical_signal_out(optical_signal, tup_key=(self, span)) - - - # conn_loss_in = db_to_abs(span.conn_loss_in + span.att_in) - # for optical_signal in self.optical_signals: - # power_out = optical_signal.loc_out_to_state[(self, span)]['power'] / conn_loss_in - # ase_noise_out = optical_signal.loc_out_to_state[(self, span)]['ase_noise'] / conn_loss_in - # nli_noise_out = optical_signal.loc_out_to_state[(self, span)]['nli_noise'] / conn_loss_in - # - # self.include_optical_signal_out(optical_signal, power=power_out, - # ase_noise=ase_noise_out, nli_noise=nli_noise_out, - # tup_key=(self, span)) - - if not isinstance(self.src_node, LineTerminal): - # Compute the nonlinear noise with the GN model - self.output_nonlinear_noise(span) - - # Compute SRS effects from the fibre - if self.srs_effect: - if len(self.optical_signals) > 1: - self.zirngibl_srs(span) - - # Compute linear effects from the fibre - span_attenuation = db_to_abs(span.length * span.fibre_attenuation) - for optical_signal in self.optical_signals: - power_out = optical_signal.loc_out_to_state[(self, span)]['power'] / span_attenuation - ase_noise_out = optical_signal.loc_out_to_state[(self, span)]['ase_noise'] / span_attenuation - nli_noise_out = optical_signal.loc_out_to_state[(self, span)]['nli_noise'] / span_attenuation - - self.include_optical_signal_out(optical_signal, power=power_out, - ase_noise=ase_noise_out, nli_noise=nli_noise_out, - tup_key=(self, span)) - if amplifier: - amplifier.include_optical_signal_in(optical_signal, power=power_out, - ase_noise=ase_noise_out, nli_noise=nli_noise_out, - src_node=self.src_node) - - # Compute amplifier compensation - if amplifier: - amplifier.propagate(self.src_node, self.dst_node, self.optical_signals) - for optical_signal in self.optical_signals: - power_out = optical_signal.loc_out_to_state[amplifier]['power'] - ase_noise_out = optical_signal.loc_out_to_state[amplifier]['ase_noise'] - nli_noise_out = optical_signal.loc_out_to_state[amplifier]['nli_noise'] - - self.include_optical_signal_out(optical_signal, power=power_out, - ase_noise=ase_noise_out, nli_noise=nli_noise_out) - - return True - - def zirngibl_srs(self, span): + power_out = optical_signal.loc_out_to_state[self]['power'] / self.attenuation() + ase_noise_out = optical_signal.loc_out_to_state[self]['ase_noise'] / self.attenuation() + nli_noise_out = optical_signal.loc_out_to_state[self]['nli_noise'] / self.attenuation() + + self.include_optical_signal_out(optical_signal, power=power_out, + ase_noise=ase_noise_out, nli_noise=nli_noise_out) + + for optical_signal in self.optical_signals: + in_port = self.next_component.link_to_port_in[self.link] + if isinstance(self.next_component, LineTerminal): + self.next_component.include_optical_signal_in(optical_signal, + in_port=in_port) + self.next_component.receiver(optical_signal, in_port) + elif isinstance(self.next_component, Roadm): + self.next_component.include_optical_signal_in(optical_signal, + in_port=in_port) + elif isinstance(self.next_component, Amplifier): + self.next_component.include_optical_signal_in(optical_signal, in_port=0) + + if isinstance(self.next_component, Amplifier): + self.next_component.propagate(self.optical_signals) + elif isinstance(self.next_component, Roadm) and is_last_port: + in_port = self.next_component.link_to_port_in[self.link] + self.next_component.switch(in_port, self.link.src_node, safe_switch=safe_switch) + + def zirngibl_srs(self): """ Computation taken from : M. Zirngibl Analytical model of Raman gain effects in massive wavelength division multiplexed transmission systems, 1998. - Equations 7,8. - :param span: Span() object :return: """ min_wavelength_index = 90 @@ -232,12 +283,12 @@ def zirngibl_srs(self, span): frequency_min = min_signal.frequency # minimum frequency of longest wavelength frequency_max = max_signal.frequency # maximum frequency of shortest wavelength - effective_length = span.effective_length # SMF effective distance - beta = span.raman_coefficient + effective_length = self.effective_length # SMF effective distance + beta = self.raman_coefficient total_power = 0 # Total input power calculated by following loop for optical_signal in self.optical_signals: - total_power += optical_signal.loc_out_to_state[(self, span)]['power'] + total_power += optical_signal.loc_out_to_state[self]['power'] # Calculate delta P for each channel for optical_signal in self.optical_signals: @@ -248,28 +299,23 @@ def zirngibl_srs(self, span): r2 = math.e ** (beta * total_power * effective_length * (frequency_max - frequency_min)) - 1 # term 2 delta_p = float(r1 / r2) - power_out = optical_signal.loc_out_to_state[(self, span)]['power'] * delta_p - ase_noise_out = optical_signal.loc_out_to_state[(self, span)]['ase_noise'] * delta_p - nli_noise_out = optical_signal.loc_out_to_state[(self, span)]['nli_noise'] * delta_p + power_out = optical_signal.loc_out_to_state[self]['power'] * delta_p + ase_noise_out = optical_signal.loc_out_to_state[self]['ase_noise'] * delta_p + nli_noise_out = optical_signal.loc_out_to_state[self]['nli_noise'] * delta_p self.include_optical_signal_out(optical_signal, power=power_out, - ase_noise=ase_noise_out, nli_noise=nli_noise_out, - tup_key=(self, span)) + ase_noise=ase_noise_out, nli_noise=nli_noise_out) - def output_nonlinear_noise(self, span): - """ - :param span: Span() object - """ - nonlinear_noise = self.gn_model(span) + def output_nonlinear_noise(self): + nonlinear_noise = self.gn_model() for optical_signal in self.optical_signals: - nli_noise_in = optical_signal.loc_in_to_state[(self, span)]['nli_noise'] + nli_noise_in = optical_signal.loc_in_to_state[self]['nli_noise'] nli_noise_out = nli_noise_in + nonlinear_noise[optical_signal] - self.include_optical_signal_out(optical_signal, nli_noise=nli_noise_out, tup_key=(self, span)) + self.include_optical_signal_out(optical_signal, nli_noise=nli_noise_out) - def gn_model(self, span): + def gn_model(self): """ Computes the nonlinear interference power on a single carrier. Translated from the GNPy project source code The method uses eq. 120 from arXiv:1209.0394. - :param span: :return: carrier_nli: the amount of nonlinear interference in W on the carrier under analysis """ nonlinear_noise_struct = {} @@ -279,24 +325,24 @@ def gn_model(self, span): nonlinear_noise_struct[channel] = None channels_index.append(channel.index) index_to_signal[channel.index] = channel - alpha = span.alpha - beta2 = span.dispersion_coefficient - gamma = span.non_linear_coefficient - effective_length = span.effective_length + alpha = self.alpha + beta2 = self.dispersion_coefficient + gamma = self.non_linear_coefficient + effective_length = self.effective_length asymptotic_length = 1 / (2 * alpha) for optical_signal in self.optical_signals: channel_under_test = optical_signal.index symbol_rate_cut = optical_signal.symbol_rate bw_cut = symbol_rate_cut - pwr_cut = optical_signal.loc_out_to_state[(self, span)]['power'] + pwr_cut = optical_signal.loc_out_to_state[self]['power'] g_cut = pwr_cut / bw_cut # G is the flat PSD per channel power (per polarization) g_nli = 0 for ch in self.optical_signals: symbol_rate_ch = ch.symbol_rate bw_ch = symbol_rate_ch - pwr_ch = ch.loc_out_to_state[(self, span)]['power'] + pwr_ch = ch.loc_out_to_state[self]['power'] g_ch = pwr_ch / bw_ch # G is the flat PSD per channel power (per polarization) psi = self.psi_factor(optical_signal, ch, beta2=beta2, asymptotic_length=asymptotic_length) g_nli += g_ch ** 2 * g_cut * psi @@ -328,59 +374,3 @@ def psi_factor(carrier, interfering_carrier, beta2, asymptotic_length): psi -= np.arcsinh(np.pi ** 2 * asymptotic_length * abs(beta2) * bw_cut * (delta_f - 0.5 * bw_ch)) return psi - - -class Span(object): - - ids = 1 - - def __init__(self, fibre_type='SMF', length=20.0): - """ - :param length: optical fiber span length in km - float - :param fibre_type: optical fiber type - string - """ - self.span_id = Span.ids # was id(self) - Span.ids += 1 - self.fibre_type = fibre_type - self.length = length * km - self.fibre_attenuation = 0.22 / km # fiber attenuation in decibels/km - self.alpha = self.fibre_attenuation / (20 * np.log10(np.e)) # linear value fibre attenuation - self.effective_length = (1 - np.exp(-2 * self.alpha * self.length)) / (2 * self.alpha) - self.non_linear_coefficient = 0.78 / km # gamma fiber non-linearity coefficient [W^-1 km^-1] - self.dispersion = 2.1e-05 - self.dispersion_coefficient = self.beta2() # B_2 dispersion coefficient [ps^2 km^-1] - self.dispersion_slope = 0.1452 * (ps ** 3 / km) # B_3 dispersion slope in (ps^3 km^-1) - self.effective_area = 80 * um * um # Aeff - SMF effective area - self.raman_gain = 7.0 * 1e-12 * cm / W # r - Raman Gain in SMF - self.raman_amplification_band = 15 * THz # Raman amplification band ~15THz - # Raman coefficient - self.raman_coefficient = self.raman_gain / (2 * self.effective_area * self.raman_amplification_band) - - self.input_power = {} # dict signal to input power - self.output_power = {} # dict signal to output power - - # Parameters to add: - self.att_in = 0 - self.conn_loss_in = 0 - self.conn_loss_out = 0 - self.padding = 0 - - def describe(self): - pprint(vars(self)) - - def __repr__(self): - """String representation""" - return '<%d %.1fkm>' % (self.span_id, self.length/km) - - def attenuation(self): - return db_to_abs(self.fibre_attenuation * self.length) - - def beta2(self, ref_wavelength=1550e-9): - """Returns beta2 from dispersion parameter. - Dispersion is entered in ps/nm/km. - Translated from the GNPy project source code - :param ref_wavelength: can be a numpy array; default: 1550nm - """ - D = abs(self.dispersion) - b2 = (ref_wavelength ** 2) * D / (2 * pi * c) # 10^21 scales [ps^2/km] - return b2 # s/Hz/m diff --git a/network.py b/network.py index ab4f046c..cc5532ba 100644 --- a/network.py +++ b/network.py @@ -69,7 +69,8 @@ def add_amplifier(self, name, amplifier_type='EDFA', **params): self.amplifiers.append(amplifier) return amplifier - def add_link(self, src_node, dst_node, src_out_port=-1, dst_in_port=-1, spans=None): + def add_link(self, src_node, dst_node, src_out_port=-1, + dst_in_port=-1, boost_amp=None, spans=None): """ Add a uni-directional link :param src_node: source node in link @@ -83,6 +84,7 @@ def add_link(self, src_node, dst_node, src_out_port=-1, dst_in_port=-1, spans=No link = Link(src_node, dst_node, src_out_port=src_out_port, dst_in_port=dst_in_port, + boost_amp=boost_amp, spans=spans) self.links.append(link) diff --git a/node.py b/node.py index f4be26af..51edf152 100755 --- a/node.py +++ b/node.py @@ -19,34 +19,21 @@ def __init__(self, name): self.ports_in = [] self.port_to_node_in = {} self.node_to_port_in = {} - self.node_to_link_in = {} - self.link_to_node_in = {} - self.port_to_link_in = {} self.link_to_port_in = {} # static attributes - outputs self.ports_out = [] self.port_to_node_out = {} - # AD: used by helper functions in Network. - # Probably will need to be part of the Network - # class instead self.node_to_port_out = {} - self.node_to_link_out = {} - self.link_to_node_out = {} self.port_to_link_out = {} - self.link_to_port_out = {} # dynamic attributes - inputs self.port_to_optical_signal_in = {} self.optical_signal_to_port_in = {} - self.node_to_optical_signal_in = {} - self.optical_signal_to_node_in = {} # dynamic attributes - outputs self.port_to_optical_signal_out = {} self.optical_signal_to_port_out = {} - self.node_to_optical_signal_out = {} - self.optical_signal_to_node_out = {} def set_output_port(self, dst_node, link, output_port=-1): if output_port < 0: @@ -58,18 +45,14 @@ def set_output_port(self, dst_node, link, output_port=-1): # set static attributes self.ports_out.append(output_port) self.port_to_node_out[output_port] = dst_node - self.link_to_node_out[link] = dst_node self.port_to_link_out[output_port] = link - self.link_to_port_out[link] = output_port # a node can have multiple # output ports to another node self.node_to_port_out.setdefault(dst_node, []).append(output_port) - self.node_to_link_out.setdefault(dst_node, []).append(link) # initialize dynamic attributes self.port_to_optical_signal_out[output_port] = [] - self.node_to_optical_signal_out[dst_node] = [] return output_port def set_input_port(self, src_node, link, input_port=-1): @@ -82,29 +65,24 @@ def set_input_port(self, src_node, link, input_port=-1): # set static attributes self.ports_in.append(input_port) self.port_to_node_in[input_port] = src_node - self.port_to_link_in[input_port] = link self.link_to_port_in[link] = input_port - self.link_to_node_in[link] = src_node # a node can have multiple input # ports from another node self.node_to_port_in.setdefault(src_node, []).append(input_port) - self.node_to_link_in.setdefault(src_node, []).append(link) # initialize dynamic attributes self.port_to_optical_signal_in[input_port] = [] - self.node_to_optical_signal_in[src_node] = [] return input_port def include_optical_signal_in(self, optical_signal, power=None, ase_noise=None, - nli_noise=None, in_port=None, src_node=None): + nli_noise=None, in_port=None): """ :param optical_signal: OpticalSignal object, OpticalSignal uid :param power: power level of OpticalSignal :param ase_noise: ase noise level of OpticalSignal :param nli_noise: nli noise level of OpticalSignal :param in_port: input port of node (optional) - :param src_node: src node (optional) """ # update structures with the input ports of the current node self.port_to_optical_signal_in.setdefault(in_port, []) @@ -114,24 +92,17 @@ def include_optical_signal_in(self, optical_signal, power=None, ase_noise=None, # for symmetry: self.optical_signal_to_port_in[optical_signal] = in_port - # update the structures with the source nodes (where signals are coming from) - self.node_to_optical_signal_in.setdefault(src_node, []) - if optical_signal not in self.node_to_optical_signal_in[src_node]: - self.node_to_optical_signal_in[src_node].append(optical_signal) - self.optical_signal_to_node_in[optical_signal] = src_node - # but we need to associate a component with the state of the signal optical_signal.assoc_loc_in(self, power, ase_noise, nli_noise) def include_optical_signal_out(self, optical_signal, power=None, ase_noise=None, - nli_noise=None, out_port=None, dst_node=None): + nli_noise=None, out_port=None): """ :param optical_signal: OpticalSignal object :param power: power level of OpticalSignal :param ase_noise: ase noise level of OpticalSignal :param nli_noise: nli noise level of OpticalSignal :param out_port: output port of node (optional) - :param dst_node: dst node (optional) """ if out_port is not None or out_port == 0: self.port_to_optical_signal_out.setdefault(out_port, []) @@ -139,29 +110,11 @@ def include_optical_signal_out(self, optical_signal, power=None, ase_noise=None, self.port_to_optical_signal_out[out_port].append(optical_signal) self.optical_signal_to_port_out[optical_signal] = out_port - if dst_node is not None: - self.node_to_optical_signal_out.setdefault(dst_node, []) - if optical_signal not in self.node_to_optical_signal_out[dst_node]: - self.node_to_optical_signal_out[dst_node].append(optical_signal) - self.optical_signal_to_node_out[optical_signal] = dst_node - optical_signal.assoc_loc_out(self, power, ase_noise, nli_noise) def remove_optical_signal(self, optical_signal): print("*** %s removing: %s" % (self, optical_signal)) - if optical_signal in self.optical_signal_to_node_in: - src_node = self.optical_signal_to_node_in[optical_signal] - if optical_signal in self.node_to_optical_signal_in[src_node]: - self.node_to_optical_signal_in[src_node].remove(optical_signal) - del self.optical_signal_to_node_in[optical_signal] - - if optical_signal in self.optical_signal_to_node_out: - dst_node = self.optical_signal_to_node_out[optical_signal] - if optical_signal in self.node_to_optical_signal_out[dst_node]: - self.node_to_optical_signal_out[dst_node].remove(optical_signal) - del self.optical_signal_to_node_out[optical_signal] - if optical_signal in self.optical_signal_to_port_in: port_in = self.optical_signal_to_port_in[optical_signal] self.port_to_optical_signal_in[port_in].remove(optical_signal) @@ -186,12 +139,6 @@ def remove_signal_from_out_port(self, port_out, optical_signal): link = self.port_to_link_out[port_out] link.remove_optical_signal(optical_signal) - if optical_signal in self.optical_signal_to_node_out: - dst_node = self.optical_signal_to_node_out[optical_signal] - if optical_signal in self.node_to_optical_signal_out[dst_node]: - self.node_to_optical_signal_out[dst_node].remove(optical_signal) - del self.optical_signal_to_node_out[optical_signal] - def describe(self): pprint(vars(self)) @@ -300,7 +247,7 @@ def assoc_tx_to_channel(self, transceiver, channel, out_port=-1): dst_node = self.port_to_node_out[out_port] # the goal of this function - self.include_optical_signal_out(optical_signal, out_port=out_port, dst_node=dst_node) + self.include_optical_signal_out(optical_signal, out_port=out_port) self.tx_to_channel[out_port] = {'optical_signal': optical_signal, 'transceiver': transceiver} self.optical_signals_out += 1 @@ -640,10 +587,10 @@ def monitor_query(self): if self.monitor: return self.monitor - def include_optical_signal_in_roadm(self, optical_signal, in_port, src_node): + def include_optical_signal_in_roadm(self, optical_signal, in_port): if self.preamp: - self.preamp.include_optical_signal_in(optical_signal, in_port=0, src_node=src_node) - self.include_optical_signal_in(optical_signal,in_port=in_port, src_node=src_node) + self.preamp.include_optical_signal_in(optical_signal, in_port=0) + self.include_optical_signal_in(optical_signal,in_port=in_port) def install_switch_rule(self, in_port, out_port, signal_indices, src_node=None): """ @@ -849,12 +796,16 @@ def prepropagation(self, port_out_to_port_in_signals, src_node): if isinstance(dst_node, LineTerminal) or \ (self.preamp and not isinstance(src_node, LineTerminal) and not isinstance(dst_node, LineTerminal)): + for in_port, optical_signals in in_port_signals.items(): + if self.preamp: + for optical_signal in optical_signals: + self.preamp.include_optical_signal_in(optical_signal) # we need to pass all the signals at a given in port to compute # the carrier's attenuation in self.propagate() for in_port, optical_signals in in_port_signals.items(): if self.preamp: # need to process signal before switch-based propagation - self.preamp.propagate(src_node, dst_node, optical_signals) + self.preamp.propagate(optical_signals) def compute_carrier_attenuation(self, in_port, amp=None): """ @@ -905,7 +856,7 @@ def process_att(self, out_port, in_port, optical_signals, src_node, dst_node, li # need to pass signals to boost for processing self.boost.include_optical_signal_in(optical_signal, power=power_out, ase_noise=ase_noise_out, nli_noise=nli_noise_out, - in_port=0, src_node=src_node) + in_port=0) else: # update the structures for that direction # all these signals are going towards the same out port @@ -913,11 +864,11 @@ def process_att(self, out_port, in_port, optical_signals, src_node, dst_node, li ase_noise=ase_noise_out, nli_noise=nli_noise_out) self.include_optical_signal_out(optical_signal, power=power_out, ase_noise=ase_noise_out, nli_noise=nli_noise_out, - out_port=out_port, dst_node=dst_node) + out_port=out_port) if self.boost and not isinstance(dst_node, LineTerminal): # process boost amp - self.boost.propagate(src_node, dst_node, optical_signals) + self.boost.propagate(optical_signals) # pass signals to link and update state at ROADM (self) for i, optical_signal in enumerate(optical_signals): @@ -931,7 +882,7 @@ def process_att(self, out_port, in_port, optical_signals, src_node, dst_node, li ase_noise=ase_noise_out, nli_noise=nli_noise_out) self.include_optical_signal_out(optical_signal, power=power_out, ase_noise=ase_noise_out, nli_noise=nli_noise_out, - out_port=out_port, dst_node=dst_node) + out_port=out_port) def propagate(self, out_port, in_port, optical_signals): """ @@ -962,24 +913,22 @@ def __init__(self, name, amplifier_type='EDFA', target_gain=17.6, bandwidth=32.0e9, wavelength_dependent_gain_id=None, preamp=False, boost=False, monitor_mode=None): """ + :param amplifier_type: OBSOLETE; kept for backwards compatibility :param target_gain: units: dB - float :param noise_figure: tuple with NF value in dB and number of channels (def. 90) :param noise_figure_function: custom NF function with values in dB :param bandwidth: measurement optical bandwidth units: GHz - float :param wavelength_dependent_gain_id: file name id (see top of script) units: dB - string + :param preamp: OBSOLETE; kept for backwards compatibility + :param boost: OBSOLETE; kept for backwards compatibility """ Node.__init__(self, name) - # FIXME: (AD) id and type are not needed - self.id = id(self) - self.type = amplifier_type self.target_gain = target_gain self.system_gain = target_gain # FIXME: (AD) is there a better way of allowing # the declaration of a noise figure function? self.noise_figure = self.get_noise_figure(noise_figure, noise_figure_function) self.bandwidth = bandwidth - # FIXME: (AD) wdgfunc does nothing - self.wdgfunc = None wavelength_dependent_gain_id = 'linear' self.wavelength_dependent_gain = ( self.load_wavelength_dependent_gain(wavelength_dependent_gain_id)) @@ -991,9 +940,9 @@ def __init__(self, name, amplifier_type='EDFA', target_gain=17.6, self.power_excursions_flag_1 = False self.power_excursions_flag_2 = False - # FIXME: (AD) Will this be needed if booster placed in ROADM? - self.boost = boost - self.preamp = preamp + self.prev_component = None + self.next_component = None + self.link = None def monitor_query(self): if self.monitor: @@ -1049,11 +998,10 @@ def get_noise_figure(noise_figure, noise_figure_function): else: raise Exception("Amplifier.get_noise_figure: couldn't retrieve noise figure as a function.") - def output_amplified_power(self, optical_signal, dst_node=None): + def output_amplified_power(self, optical_signal): """ Compute the output power levels of each signal after amplification :param optical_signal: signal object - :param dst_node: dst_node """ # process output power wavelength_dependent_gain = self.get_wavelength_dependent_gain(optical_signal.index) @@ -1067,11 +1015,11 @@ def output_amplified_power(self, optical_signal, dst_node=None): # associate amp to optical signal at output interface # and update the optical signal state (power) self.include_optical_signal_out(optical_signal, power=output_power, - out_port=0, dst_node=dst_node) + out_port=0) return output_power - def nli_compensation(self, optical_signal, dst_node=None): + def nli_compensation(self, optical_signal): wavelength_dependent_gain = self.get_wavelength_dependent_gain(optical_signal.index) # Conversion from dB to linear system_gain_linear = db_to_abs(self.system_gain) @@ -1081,11 +1029,10 @@ def nli_compensation(self, optical_signal, dst_node=None): nli_noise_in = optical_signal.loc_in_to_state[self]['nli_noise'] nli_noise_out = nli_noise_in * system_gain_linear * wavelength_dependent_gain_linear - # print("attempt to update at amplifier with nli noise", nli_noise_out) self.include_optical_signal_out(optical_signal, - nli_noise=nli_noise_out, out_port=0, dst_node=dst_node) + nli_noise=nli_noise_out, out_port=0) - def stage_amplified_spontaneous_emission_noise(self, optical_signal, dst_node=None): + def stage_amplified_spontaneous_emission_noise(self, optical_signal): """ :return: Ch.5 Eqs. 4-16,18 in: Gumaste A, Antony T. DWDM network designs and engineering solutions. Cisco Press; 2003. @@ -1102,7 +1049,7 @@ def stage_amplified_spontaneous_emission_noise(self, optical_signal, dst_node=No # associate amp to optical signal at output interface # and update the optical signal state (power) self.include_optical_signal_out(optical_signal, - ase_noise=ase_noise_out, out_port=0, dst_node=dst_node) + ase_noise=ase_noise_out, out_port=0) def compute_power_excursions(self): """ @@ -1134,26 +1081,53 @@ def compute_power_excursions(self): if not (self.power_excursions_flag_1 and self.power_excursions_flag_2): self.power_excursions_flag_1 = True - def propagate(self, src_node, dst_node, optical_signals): + def propagate(self, optical_signals, is_last_port=False, safe_switch=False): """ Compute the amplification process - :param src_node: Node object - :param dst_node: Node object :param optical_signals: list """ # Enabling balancing check while not (self.power_excursions_flag_1 and self.power_excursions_flag_2): for optical_signal in optical_signals: - self.output_amplified_power(optical_signal, dst_node=dst_node) + self.output_amplified_power(optical_signal) self.compute_power_excursions() # Reset balancing flags to original settings self.power_excursions_flags_off() # Compute for the power for optical_signal in optical_signals: - self.nli_compensation(optical_signal, dst_node=dst_node) + self.nli_compensation(optical_signal) # Compute ASE noise generation - self.stage_amplified_spontaneous_emission_noise(optical_signal, dst_node=dst_node) + self.stage_amplified_spontaneous_emission_noise(optical_signal) + + if self.next_component: + power_out = optical_signal.loc_out_to_state[self]['power'] + ase_noise_out = optical_signal.loc_out_to_state[self]['ase_noise'] + nli_noise_out = optical_signal.loc_out_to_state[self]['nli_noise'] + + if isinstance(self.next_component, LineTerminal): + in_port = self.next_component.link_to_port_in[self.link] + self.next_component.include_optical_signal_in( + optical_signal, power=power_out, + ase_noise=ase_noise_out, nli_noise=nli_noise_out, + in_port=in_port) + self.next_component.receiver(optical_signal, in_port) + elif isinstance(self.next_component, Roadm): + in_port = self.next_component.link_to_port_in[self.link] + self.next_component.include_optical_signal_in( + optical_signal, power=power_out, + ase_noise=ase_noise_out, nli_noise=nli_noise_out, + in_port=in_port) + else: + self.next_component.include_optical_signal_in( + optical_signal, power=power_out, + ase_noise=ase_noise_out, nli_noise=nli_noise_out) + + if self.next_component: + if self.next_component.__class__.__name__ == 'Span': + self.next_component.propagate(is_last_port=is_last_port, safe_switch=safe_switch) + elif isinstance(self.next_component, Roadm): + self.next_component.switch(in_port, self.link.src_node, safe_switch=safe_switch) def __repr__(self): """String representation""" diff --git a/tests/simple_link.py b/tests/simple_link.py new file mode 100644 index 00000000..514d3afd --- /dev/null +++ b/tests/simple_link.py @@ -0,0 +1,141 @@ +""" + + This script models a linear topology between two line terminals + with two ROADMs in between: + lt1 ---> r1 ---> r2 ----> lt2 + + lt1 will transmit 3 channels at 0 dBm launch power +""" + + +import network +from link import Span as Fiber, SpanTuple as Segment +import numpy as np +from node import Transceiver + + +km = dB = dBm = 1.0 +m = .001 + +def Span(km, amp=None): + """Return a fiber segment of length km with a compensating amp""" + return Segment(span=Fiber(length=km), amplifier=amp) + +def build_spans(net, r1, r2): + """ + Helper function for building spans of + fixed length of 50km and handling those + that require different lengths + """ + # store all spans (sequentially) in a list + spans = [] + # get number of spans (int) + span_no = 3 + span_length = 25 + + for i in range(1, span_no + 1): + # append all spans except last one + amp = net.add_amplifier( + '%s-%s-amp%d' % (r1, r2, i), target_gain=span_length * 0.22 * dB, monitor_mode='out') + span = Span(span_length, amp=amp) + spans.append(span) + + return net, spans + +def build_link(net, r1, r2): + # boost amplifier object + boost_l = '%s-%s-boost' % (r1, r2) # label boost amp + boost_amp = net.add_amplifier(name=boost_l, amplifier_type='EDFA', + target_gain=17.0, monitor_mode='out') + net, spans = build_spans(net, r1, r2) + for step, span in enumerate(spans, start=1): + net.spans.append(span) + + # link object + net.add_link(r1, r2, boost_amp=boost_amp, spans=spans) + +class LinearTopology: + + @staticmethod + def build(op=0, non=3): + """ + :param op: operational power in dBm + :param non: number of nodes (integer) + :return: Network object + """ + # Create an optical-network object + net = network.Network() + tr_no = range(1, 11) + tr_labels = ['tr%s' % str(x) for x in tr_no] + line_terminals = [] + for i in range(non): + # plugging a Transceiver at the first 10 ports of the Terminal + transceivers = [Transceiver(id, tr, operation_power=op) + for id, tr in enumerate(tr_labels, start=1)] + lt = net.add_lt('lt_%s' % (i + 1), transceivers=transceivers) + line_terminals.append(lt) + + roadms = [net.add_roadm('r%s' % (i + 1), + insertion_loss_dB=17, + reference_power_dBm=op) + for i in range(non)] + name_to_roadm = {roadm.name: roadm for roadm in roadms} + + # Modelling Lumentum ROADM-20 port numbering + roadm20_in_ports = [i + 1 for i in range(4100, 4120)] + roadm20_out_ports = [i + 1 for i in range(5200, 5220)] + # Create bi-directional links between LTs and ROADMs + # Need to decide which ports are connected to the ROADM + # Port-1 from Terminals are connected to Port-4101 from ROADMs. + for lt, roadm in zip(line_terminals, roadms): + for i, tr in enumerate(transceivers): + roadm20_in_port = roadm20_in_ports[i] + net.add_link(lt, roadm, src_out_port=tr.id, dst_in_port=roadm20_in_port, spans=[Span(0 * m)]) + + roadm20_out_port = roadm20_out_ports[i] + net.add_link(roadm, lt, src_out_port=roadm20_out_port, dst_in_port=tr.id, spans=[Span(0 * m)]) + + for i in range(non-1): + # Iterate through the number of nodes linearly connected + r1 = name_to_roadm['r' + str(i + 1)] + r2 = name_to_roadm['r' + str(i + 2)] + build_link(net, r1, r2) + + return net + + +operational_power_dBm = 0 +net = LinearTopology.build(op=operational_power_dBm, non=2) + +# Retrieve line terminals (transceivers) from network +lt_1 = net.name_to_node['lt_1'] +lt_2 = net.name_to_node['lt_2'] + +num_wavelengths = 3 +ports = channel_indexes = list(range(1, num_wavelengths + 1)) + + +for c, p in zip(channel_indexes, ports): + # configure transmitter terminal + tx_transceiver = lt_1.id_to_transceivers[c] + lt_1.assoc_tx_to_channel(tx_transceiver, c, out_port=p) + + # configure receiver terminal + rx_transceiver = lt_2.id_to_transceivers[c] + lt_2.assoc_rx_to_channel(rx_transceiver, c, in_port=p) + +# Configure ROADM 1 +r1 = net.roadms[0] +for c, p in zip(channel_indexes, ports): + in_port = 4100 + p + out_port = 5211 + r1.install_switch_rule(in_port, out_port, [c]) + +# Configure ROADM 2 +r2 = net.roadms[1] +for c, p in zip(channel_indexes, ports): + in_port = 4111 + out_port = 5200 + p + r2.install_switch_rule(in_port, out_port, [c]) + +lt_1.turn_on() \ No newline at end of file From 79999753df7b0e477afea6fcfdacc36d8863c1a1 Mon Sep 17 00:00:00 2001 From: Alan Diaz Date: Thu, 27 May 2021 18:17:03 -0400 Subject: [PATCH 05/12] add api for dynamic config of gain and ref power (#62) --- link.py | 8 ++++- node.py | 73 ++++++++++++++++++++++++++++++--------- tests/update_gain.py | 53 ++++++++++++++++++++++++++++ tests/update_ref_power.py | 58 +++++++++++++++++++++++++++++++ topo/linear.py | 10 +++--- 5 files changed, 180 insertions(+), 22 deletions(-) create mode 100644 tests/update_gain.py create mode 100644 tests/update_ref_power.py diff --git a/link.py b/link.py index 4476c7ba..7b103089 100644 --- a/link.py +++ b/link.py @@ -89,6 +89,7 @@ def remove_optical_signal(self, optical_signal): self.optical_signals.remove(optical_signal) for span, amplifier in self.spans: + span.remove_optical_signal(optical_signal) if amplifier: amplifier.remove_optical_signal(optical_signal) @@ -194,6 +195,11 @@ def beta2(self, ref_wavelength=1550e-9): b2 = (ref_wavelength ** 2) * D / (2 * pi * c) # 10^21 scales [ps^2/km] return b2 # s/Hz/m + def remove_optical_signal(self, optical_signal): + print("*** %s removing: %s" % (self, optical_signal)) + if optical_signal in self.optical_signals: + self.optical_signals.remove(optical_signal) + def include_optical_signal_in(self, optical_signal, power=None, ase_noise=None, nli_noise=None): """ @@ -258,7 +264,7 @@ def propagate(self, is_last_port=False, safe_switch=False): self.next_component.include_optical_signal_in(optical_signal, in_port=0) if isinstance(self.next_component, Amplifier): - self.next_component.propagate(self.optical_signals) + self.next_component.propagate(self.optical_signals, is_last_port=is_last_port, safe_switch=safe_switch) elif isinstance(self.next_component, Roadm) and is_last_port: in_port = self.next_component.link_to_port_in[self.link] self.next_component.switch(in_port, self.link.src_node, safe_switch=safe_switch) diff --git a/node.py b/node.py index 51edf152..c19e8a18 100755 --- a/node.py +++ b/node.py @@ -223,7 +223,7 @@ def set_modulation_format(self, transceiver, modulation_format, tx=False): def tx_config(self, transceiver, operational_power_dBm): """ Configure the operational power of the transceiver """ - transceiver.operation_power = db_to_abs(operational_power_dBm) * 1e-3 # Watts, will change after cleanup + transceiver.operation_power = db_to_abs(operational_power_dBm) * 1e-3 def assoc_tx_to_channel(self, transceiver, channel, out_port=-1): """ @@ -578,7 +578,11 @@ def __init__(self, name, insertion_loss_dB=17, reference_power_dBm=0, self.monitor = Monitor(name + "-monitor", component=self, mode=monitor_mode) # expected output power of signals - self.target_output_power_dBm = reference_power_dBm - insertion_loss_dB + channel_no = 90 + self.insertion_loss_dB = {k: insertion_loss_dB for k in range(1, channel_no + 1)} + self.reference_power_dBm = {k: reference_power_dBm for k in range(1, channel_no + 1)} + self.target_output_power_dBm = {k: self.reference_power_dBm[k] - self.insertion_loss_dB[k] + for k in range(1, channel_no + 1)} self.preamp = preamp self.boost = boost @@ -812,7 +816,8 @@ def compute_carrier_attenuation(self, in_port, amp=None): Compute the total power at an input port, and use it to compute the carriers attenuation """ - carriers_power = [] + + carriers_att = {} for optical_signal in self.port_to_optical_signal_in[in_port]: if amp: power_in = optical_signal.loc_out_to_state[amp]['power'] @@ -824,12 +829,16 @@ def compute_carrier_attenuation(self, in_port, amp=None): nli_noise_in = optical_signal.loc_in_to_state[self]['nli_noise'] total_power = power_in + ase_noise_in + nli_noise_in - carriers_power.append(total_power) + carriers_att[optical_signal.index] = abs_to_db(total_power * 1e3) - \ + self.target_output_power_dBm[optical_signal.index] - carriers_att = list(map( - lambda x: abs_to_db(x * 1e3) - self.target_output_power_dBm, carriers_power)) - exceeding_att = -min(list(filter(lambda x: x < 0, carriers_att)), default=0) - carriers_att = list(map(lambda x: db_to_abs(x + exceeding_att), carriers_att)) + # carriers_att = list(map( + # lambda x: abs_to_db(x * 1e3) - self.target_output_power_dBm, carriers_power)) + # exceeding_att = -min(list(filter(lambda x: x < 0, carriers_att)), default=0) + exceeding_att = -min(list(filter(lambda x: x < 0, carriers_att.values())), default=0) + for k, x in carriers_att.items(): + carriers_att[k] = db_to_abs(x + exceeding_att) + # carriers_att = list(map(lambda x: db_to_abs(x + exceeding_att), carriers_att)) return carriers_att @@ -840,17 +849,17 @@ def process_att(self, out_port, in_port, optical_signals, src_node, dst_node, li # Compute per carrier attenuation carriers_att = self.compute_carrier_attenuation(in_port, amp=amp) - for i, optical_signal in enumerate(optical_signals): + for optical_signal in optical_signals: if amp: # attenuate signals at output interface of amp - power_out = optical_signal.loc_out_to_state[amp]['power'] / carriers_att[i] - ase_noise_out = optical_signal.loc_out_to_state[amp]['ase_noise'] / carriers_att[i] - nli_noise_out = optical_signal.loc_out_to_state[amp]['nli_noise'] / carriers_att[i] + power_out = optical_signal.loc_out_to_state[amp]['power'] / carriers_att[optical_signal.index] + ase_noise_out = optical_signal.loc_out_to_state[amp]['ase_noise'] / carriers_att[optical_signal.index] + nli_noise_out = optical_signal.loc_out_to_state[amp]['nli_noise'] / carriers_att[optical_signal.index] else: # attenuate signals as they entered the ROADM (self) - power_out = optical_signal.loc_in_to_state[self]['power'] / carriers_att[i] - ase_noise_out = optical_signal.loc_in_to_state[self]['ase_noise'] / carriers_att[i] - nli_noise_out = optical_signal.loc_in_to_state[self]['nli_noise'] / carriers_att[i] + power_out = optical_signal.loc_in_to_state[self]['power'] / carriers_att[optical_signal.index] + ase_noise_out = optical_signal.loc_in_to_state[self]['ase_noise'] / carriers_att[optical_signal.index] + nli_noise_out = optical_signal.loc_in_to_state[self]['nli_noise'] / carriers_att[optical_signal.index] if self.boost and not isinstance(dst_node, LineTerminal): # need to pass signals to boost for processing @@ -905,6 +914,29 @@ def route(self, out_port, safe_switch): link = self.port_to_link_out[out_port] link.propagate(is_last_port=True, safe_switch=safe_switch) + def set_boost_gain(self, gain_dB): + self.boost.set_gain(gain_dB) + self.fast_switch() + + def set_preamp_gain(self, gain_dB): + self.preamp.set_gain(gain_dB) + self.fast_switch() + + def set_reference_power(self, ref_power_dBm, ch_index=None): + if ch_index or ch_index == 1: + self.target_output_power_dBm[ch_index] = ref_power_dBm - self.insertion_loss_dB[ch_index] + else: + for i, x in self.target_output_power_dBm.items(): + self.target_output_power_dBm[i] = ref_power_dBm - self.insertion_loss_dB[i] + self.fast_switch() + + def fast_switch(self): + for component, rule_list in self.node_to_rule_id_in.items(): + # it's just necessary to pass one in_port to the switch + # function, since safe_switch is passed as True + in_port = rule_list[0][0] + self.switch(in_port, component, safe_switch=True) + class Amplifier(Node): @@ -925,8 +957,6 @@ def __init__(self, name, amplifier_type='EDFA', target_gain=17.6, Node.__init__(self, name) self.target_gain = target_gain self.system_gain = target_gain - # FIXME: (AD) is there a better way of allowing - # the declaration of a noise figure function? self.noise_figure = self.get_noise_figure(noise_figure, noise_figure_function) self.bandwidth = bandwidth wavelength_dependent_gain_id = 'linear' @@ -1129,6 +1159,15 @@ def propagate(self, optical_signals, is_last_port=False, safe_switch=False): elif isinstance(self.next_component, Roadm): self.next_component.switch(in_port, self.link.src_node, safe_switch=safe_switch) + def set_gain(self, gain_dB): + self.system_gain = gain_dB + self.target_gain = gain_dB + + if 0 in self.port_to_optical_signal_in: + optical_signals = self.port_to_optical_signal_in[0] + self.propagate(optical_signals, is_last_port=True, safe_switch=True) + + def __repr__(self): """String representation""" return '<%s %.1fdB>' % (self.name, self.target_gain) diff --git a/tests/update_gain.py b/tests/update_gain.py new file mode 100644 index 00000000..b561dd79 --- /dev/null +++ b/tests/update_gain.py @@ -0,0 +1,53 @@ + + +from topo.linear import LinearTopology + + +operational_power_dBm = 0 +net = LinearTopology.build(op=operational_power_dBm, non=2) + +# Retrieve line terminals (transceivers) from network +lt_1 = net.name_to_node['lt_1'] +lt_2 = net.name_to_node['lt_2'] + +num_wavelengths = 2 +ports = channel_indexes = list(range(1, num_wavelengths + 1)) + + +for c, p in zip(channel_indexes, ports): + # configure transmitter terminal + tx_transceiver = lt_1.id_to_transceivers[c] + lt_1.assoc_tx_to_channel(tx_transceiver, c, out_port=p) + + # configure receiver terminal + rx_transceiver = lt_2.id_to_transceivers[c] + lt_2.assoc_rx_to_channel(rx_transceiver, c, in_port=p) + +# Configure ROADM 1 +r1 = net.roadms[0] +for c, p in zip(channel_indexes, ports): + in_port = 4100 + p + out_port = 5211 + r1.install_switch_rule(in_port, out_port, [c], src_node=lt_1) + +# Configure ROADM 2 +r2 = net.roadms[1] +for c, p in zip(channel_indexes, ports): + in_port = 4111 + out_port = 5200 + p + r2.install_switch_rule(in_port, out_port, [c], src_node=r1) + +lt_1.turn_on() + +print("*** Updating gain of r1_r2_amp1 to 10 dB") +r1_r2_amp1 = net.name_to_node['r1-r2-amp1'] +gain = r1_r2_amp1.target_gain +r1_r2_amp1.set_gain(10) +print("*** Recover:") +r1_r2_amp1.set_gain(gain) + +print("*** Updating gain of r1 boost to 10 dB") +gain = r1.boost.target_gain +r1.set_boost_gain(10) +print("*** Recover:") +r1.set_boost_gain(gain) \ No newline at end of file diff --git a/tests/update_ref_power.py b/tests/update_ref_power.py new file mode 100644 index 00000000..9ff48cd1 --- /dev/null +++ b/tests/update_ref_power.py @@ -0,0 +1,58 @@ + + + +from topo.linear import LinearTopology + + +operational_power_dBm = 0 +net = LinearTopology.build(op=operational_power_dBm, non=2) + +# Retrieve line terminals (transceivers) from network +lt_1 = net.name_to_node['lt_1'] +lt_2 = net.name_to_node['lt_2'] + +num_wavelengths = 3 +ports = channel_indexes = list(range(1, num_wavelengths + 1)) + + +for c, p in zip(channel_indexes, ports): + # configure transmitter terminal + tx_transceiver = lt_1.id_to_transceivers[c] + lt_1.assoc_tx_to_channel(tx_transceiver, c, out_port=p) + + # configure receiver terminal + rx_transceiver = lt_2.id_to_transceivers[c] + lt_2.assoc_rx_to_channel(rx_transceiver, c, in_port=p) + +# Configure ROADM 1 +r1 = net.roadms[0] +for c, p in zip(channel_indexes, ports): + in_port = 4100 + p + out_port = 5211 + r1.install_switch_rule(in_port, out_port, [c], src_node=lt_1) + +# Configure ROADM 2 +r2 = net.roadms[1] +for c, p in zip(channel_indexes, ports): + in_port = 4111 + out_port = 5200 + p + r2.install_switch_rule(in_port, out_port, [c], src_node=r1) + +lt_1.turn_on() + +print("*** Turning off port 1 of lt_1") +out_ports = [1] +lt_1.turn_off(out_ports) + +new_ref_power_dBm = 4 +print("*** Setting reference power of r1 to %f dBm for ch-1", new_ref_power_dBm) +r1.set_reference_power(new_ref_power_dBm, ch_index=1) + +print("*** Reconfigure tx1 of lt_1 to use launch power %f dBm", new_ref_power_dBm) +tx1 = lt_1.id_to_transceivers[1] +lt_1.tx_config(tx1, new_ref_power_dBm) +print("*** Reconfigure lt_1 to transmit ch-1 with tx1") +lt_1.assoc_tx_to_channel(tx_transceiver, 1, out_port=1) +print("*** Turning on lt_1") +lt_1.turn_on() + diff --git a/topo/linear.py b/topo/linear.py index 726468d7..e9045832 100644 --- a/topo/linear.py +++ b/topo/linear.py @@ -20,14 +20,16 @@ def build_spans(net, r1, r2): # store all spans (sequentially) in a list spans = [] # get number of spans (int) - span_no = 1 + span_no = 2 span_length = 80 for i in range(1, span_no + 1): # append all spans except last one - # amp = net.add_amplifier( - # '%s-%s-amp%d' % (r1, r2, i), target_gain=span_length * 0.22 * dB, monitor_mode='out') - span = Span(span_length, amp=None) + amplifier = None + if span_no > 1 and i < span_no: + amplifier = net.add_amplifier( + '%s-%s-amp%d' % (r1, r2, i), target_gain=span_length * 0.22 * dB, monitor_mode='out') + span = Span(span_length, amp=amplifier) spans.append(span) return net, spans From 9fc4d0d58500cfbdf47977852e5a08c8b369a8e6 Mon Sep 17 00:00:00 2001 From: jiakaiyu Date: Thu, 27 May 2021 18:41:05 -0700 Subject: [PATCH 06/12] Update Control_Test_Lum.py --- ofcdemo/Control_Test_Lum.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ofcdemo/Control_Test_Lum.py b/ofcdemo/Control_Test_Lum.py index c6c0daf8..1717848c 100644 --- a/ofcdemo/Control_Test_Lum.py +++ b/ofcdemo/Control_Test_Lum.py @@ -84,7 +84,7 @@ def installPath(self, path, ruleID, channel): Lumentum_previous, Lumentum_this_in = NodeLink_to_LumentumLink[path[i - 1], path[i]] Lumentum_NETCONF_Agent._ConfigWSS(node_ip=LumentumName_to_IP[Lumentum_this_in], status='in-service', conn_id=ruleID, module_id=1, input_port=4100+THRUPORT, output_port=4201, - start_freq=start_freq, end_freq=end_freq, attenuation=0, + start_freq=start_freq, end_freq=end_freq, attenuation=10, block='false', name='CH' + str(channel)) Lumentum_NETCONF_Agent._ConfigWSS(node_ip=LumentumName_to_IP[Lumentum_this_in], status='in-service', conn_id=ruleID, module_id=2, input_port=5101, output_port=5200+THRUPORT, @@ -92,7 +92,7 @@ def installPath(self, path, ruleID, channel): block='false', name='CH' + str(channel)) Lumentum_NETCONF_Agent._ConfigWSS(node_ip=LumentumName_to_IP[Lumentum_this_out], status='in-service', conn_id=ruleID, module_id=1, input_port=4100 + THRUPORT, output_port=4201, - start_freq=start_freq, end_freq=end_freq, attenuation=0, + start_freq=start_freq, end_freq=end_freq, attenuation=10, block='false', name='CH' + str(channel)) Lumentum_NETCONF_Agent._ConfigWSS(node_ip=LumentumName_to_IP[Lumentum_this_out], status='in-service', conn_id=ruleID, module_id=2, input_port=5101, output_port=5200 + THRUPORT, From 30811e22a6b9d610c149e7ac5027125ad255fa7c Mon Sep 17 00:00:00 2001 From: jiakaiyu Date: Fri, 28 May 2021 06:33:49 -0700 Subject: [PATCH 07/12] Add files via upload --- ofcdemo/Control_Test_Mininet_REST.py | 266 +++++++++++++++++++++++++++ 1 file changed, 266 insertions(+) create mode 100644 ofcdemo/Control_Test_Mininet_REST.py diff --git a/ofcdemo/Control_Test_Mininet_REST.py b/ofcdemo/Control_Test_Mininet_REST.py new file mode 100644 index 00000000..a29fa34e --- /dev/null +++ b/ofcdemo/Control_Test_Mininet_REST.py @@ -0,0 +1,266 @@ +#!/usr/bin/env python3 + +""" +apsp.py: all-pairs-shortest-paths routing for ofc demo + +The goal is *not* to demonstrate an elaborate routing and +rebalancing algorithm, but to demonstrate how +mininet-optical enables packet-optical SDN controller +development and experimentation!! + +So our routing is extremely simple: + +1. Every pair of nodes gets a unique channel +2. Routes are shortest paths + +Since all the links are the same length, we don't even +have to use Dijkstra's algorithm - BFS works just fine! + +""" + +from ofcdemo.demolib import DemoTopo +from dataplane import OpticalLink, ROADM + +from ofcdemo.fakecontroller import ( + RESTProxy, TerminalProxy, ROADMProxy, OFSwitchProxy, + fetchNodes, fetchLinks, fetchPorts, fetchOSNR ) + +from collections import defaultdict +from datetime import datetime +from itertools import chain +from time import sleep + + + +class Mininet_Control_REST(object): + + def __init__(self): + self.net = RESTProxy() + # Fetch nodes + self.net.allNodes = fetchNodes(self.net) + self.net.switches = sorted(node for node, cls in self.net.allNodes.items() + if cls == 'OVSSwitch') + self.net.terminals = sorted(node for node, cls in self.net.allNodes.items() + if cls == 'Terminal') + self.net.roadms = sorted(node for node, cls in self.net.allNodes.items() + if cls == 'ROADM') + self.net.nodes = self.net.terminals + self.net.roadms + + # Fetch links + self.net.allLinks, self.net.roadmLinks, self.net.terminalLinks = fetchLinks(self.net) + + # Create adjacency dict + self.net.graph = self.buildGraph(self.net.allLinks) + + # Fetch ports + self.net.ports = fetchPorts(self.net, self.net.roadms + self.net.terminals + self.net.switches) + + # Calculate inter-pop routes + self.net.routes = {node: self.route(node, self.net.graph, self.net.terminals) + for node in self.net.terminals} + + + def monitorKey(self, monitor ): + "Key for sorting monitor names" + items = monitor.split( '-' ) + return items + + def monitorOSNR(self, gosnrThreshold=18.0 ): + """Monitor gOSNR continuously; if any monitored gOSNR drops + below threshold, return list of (monitor, channel, link)""" + monitors = self.net.get( 'monitors' ).json()['monitors'] + fmt = '%s:(%.0f,%.0f) ' + failures = [] + while not failures: + logtime = datetime.now().strftime("%H:%M:%S") + # print( logtime, 'OSNR, gOSNR from monitors:' ) + for monitor in sorted( monitors, key=monitorKey ): + response = self.net.get( 'monitor', params=dict( monitor=monitor ) ) + osnrdata = response.json()[ 'osnr' ] + # print( monitor + ':', end=' ' ) + for channel, data in osnrdata.items(): + THz = float( data['freq'] )/1e12 + osnr, gosnr = data['osnr'], data['gosnr'] + # print( fmt % ( channel, osnr, gosnr ), end='' ) + if gosnr < gosnrThreshold: + print( "WARNING! gOSNR %.2f below %.2f dB threshold:" % + ( gosnr, gosnrThreshold ) ) + link = monitors[ monitor ][ 'link' ] + print( monitor, '' % + (channel, THz, osnr, gosnr ) ) + failures.append( ( monitor, channel, link ) ) + # print() + sleep( 1) + return failures + + + def buildGraph(self, links): + "Return an adjacency dict for links" + neighbors = defaultdict( defaultdict ) + for link in links: + src, dst = link # link is a dict but order doesn't matter + srcport, dstport = link[ src ], link[ dst ] + neighbors.setdefault( src, {} ) + neighbors[ src ][ dst ] = dstport + neighbors[ dst ][ src ] = srcport + return dict( neighbors ) + + + def route(self, src, graph, destinations ): + """Route from src to destinations + neighbors: adjacency list + returns: routes dict""" + routes, seen, paths = {}, set( (src,) ), [ (src,) ] + while paths: + path = paths.pop( 0 ) + lastNode = path[ -1 ] + for neighbor in graph[ lastNode ]: + if neighbor not in seen: + newPath = ( path + (neighbor, ) ) + paths.append( newPath ) + if neighbor in destinations: + routes[ neighbor ] = newPath + seen.add( neighbor) + return routes + + + + def configureTerminal(self, terminal, channel, power=0.0): + "Configure terminals statically: ethN <-> wdmM:channel" + print("*** Configuring terminals") + proxies = { terminal: TerminalProxy(terminal) } + termProxy = proxies[ terminal ] + ethPorts = sorted( int(port) for port, intf in self.net.ports[ terminal ].items() + if 'eth' in intf ) + wdmPorts = sorted( int(port) for port, intf in self.net.ports[ terminal ].items() + if 'wdm' in intf ) + #print('ethports, wdmports', ethPorts, wdmPorts) + print(termProxy) + ethPort, wdmPort = ethPorts[channel-1], wdmPorts[channel-1] + #print('Pin-Pout-channel', ethPort, wdmPort, channel) + termProxy.connect( ethPort=ethPort, wdmPort=wdmPort, + channel=channel, power=power ) + print("*** Turning on terminals") + + + def turnonTerminal(self, terminal): + "turn on terminal" + proxies = {terminal: TerminalProxy(terminal)} + proxies[terminal].turn_on() + + + def configurePacketSwitch(self, src, dst, channel, router): + "Configure Open vSwitch 'routers' using OpenFlow" + + print( "*** Configuring Open vSwitch 'routers' remotely... " ) + + def subnet( pop ): + return '10.%d.0.0/24' % pop + + routerProxy = OFSwitchProxy( router ) + + # Initialize flow table + print( 'Configuring', router, 'at', routerProxy.remote, 'via OpenFlow...' ) + routerProxy.dpctl( 'del-flows' ) + + # Find local port + ethports = sorted( int(port) for port, intf in self.net.ports[ router ].items() + if 'eth' in intf ) + print('==router ethports==', ethports) + localport = ethports[ -1 ] + + # to local + for protocol in 'ip', 'icmp', 'arp': + print('add-flow, proto, dst, port', protocol, subnet(src), localport) + flow = ( protocol + ',ip_dst=' + subnet( src )+ + ',actions=dec_ttl,output:%d' % localport ) + # print( router, 'add-flow', flow ) + routerProxy.dpctl( 'add-flow', flow ) + # to destination + for protocol in 'ip', 'icmp', 'arp': + print('add-flow, proto, dst, port', protocol, subnet(dst), channel) + flow = ( protocol + ',ip_dst=' + subnet( dst )+ + ',actions=dec_ttl,output:%d' % channel ) + # print( router, 'add-flow', flow ) + routerProxy.dpctl( 'add-flow', flow ) + + + def installPath(self, path, channels): + "Program a lightpath into the network" + print("*** Installing path", path, "channels", channels) + # Install ROADM rules + for i in range(1, len(path) - 1 ): + node1, roadm, node2 = path[i-1], path[i], path[i+1] + port1 = self.net.graph[ node1 ][ roadm ] + port2 = self.net.graph[ node2 ][ roadm ] + # For terminal nodes, use the proper channel port(s) + if i == 1: + for channel in channels: + #print('pin-pout', channel, port2) + ROADMProxy( roadm ).connect( channel, port2, [channel] ) + elif i == len(path) - 2: + for channel in channels: + #print('pin-pout', port1, channel) + ROADMProxy( roadm ).connect( port1, channel, [channel] ) + # For roadm nodes, forward the channels en masse + else: + #print('pin-pout', port1, port2) + ROADMProxy( roadm ).connect( port1, port2, channels ) + + + def uninstallPath(self, path, channels): + "Program a lightpath into the network" + print("*** Installing path", path, "channels", channels) + # Uninnstall ROADM rules + for i in range(1, len(path) - 1 ): + node1, roadm, node2 = path[i-1], path[i], path[i+1] + + port1 = self.net.graph[ node1 ][ roadm ] + port2 = self.net.graph[ node2 ][ roadm ] + # For terminal nodes, use the proper channel port(s) + if i == 1: + for channel in channels: + print('pin-pout', channel, port2) + ROADMProxy( roadm ).disconnect( channel, port2, [channel] ) + elif i == len(path) - 2: + for channel in channels: + print('pin-pout', port1, channel) + ROADMProxy( roadm ).disconnect( port1, channel, [channel] ) + # For roadm nodes, forward the channels en masse + else: + print('pin-pout', port1, port2) + ROADMProxy( roadm ).disconnect( port1, port2, channels ) + + +def Test(): + "Configure and monitor network with N=3 channels for each path" + + + + control = Mininet_Control_REST() + net = control.net + # Print routes + print( '*** Routes:' ) + src, dst = net.terminals[0], net.terminals[3] + path = net.routes[src][dst] + print(src, '->', dst, path) + + channel = 10 + # Install a route + control.installPath(path=path, channels= [channel]) + # Configure terminals and start transmitting + terminal = net.terminals[0] + control.configureTerminal(terminal=terminal, channel=channel, power=0.0) + terminal2 = net.terminals[3] + control.configureTerminal(terminal=terminal2, channel=channel, power=0.0) + control.turnonTerminal(terminal=terminal) + control.turnonTerminal(terminal=terminal2) + # Configure routers + router = net.switches[0] + router2 = net.switches[3] + control.configurePacketSwitch(src=1, dst=4, channel=channel, router=router) + control.configurePacketSwitch(src=4, dst=1, channel=channel, router=router2) + + #uninstallPath(path=path, channels=[channel], net=net) + +Test() From 96dac6b511bf04d587874dca32f7cb652b125555 Mon Sep 17 00:00:00 2001 From: jiakaiyu Date: Fri, 28 May 2021 06:36:29 -0700 Subject: [PATCH 08/12] Add files via upload --- demo 2021.py | 29 ++++ demolib_2021.py | 416 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 445 insertions(+) create mode 100644 demo 2021.py create mode 100644 demolib_2021.py diff --git a/demo 2021.py b/demo 2021.py new file mode 100644 index 00000000..bc455cfc --- /dev/null +++ b/demo 2021.py @@ -0,0 +1,29 @@ +#!/usr/bin/python + +""" +demo.py: Start up Mininet with OFC Demo Topology +""" + +from dataplane import Mininet, ROADM, Terminal, disableIPv6 +from ofcdemo.demolib_2021 import DemoTopo, CLI +from rest import RestServer + +from mininet.topo import SingleSwitchTopo +from mininet.log import setLogLevel, info +from mininet.clean import cleanup +from mininet.node import RemoteController + +if __name__ == '__main__': + + setLogLevel( 'info' ) + cleanup() + info( '*** Creating Demo Topology \n' ) + net = Mininet( topo=DemoTopo( txCount=15), autoSetMacs=True, + controller=RemoteController ) + disableIPv6( net ) + restServer = RestServer( net ) + net.start() + restServer.start() + CLI( net ) + restServer.stop() + net.stop() diff --git a/demolib_2021.py b/demolib_2021.py new file mode 100644 index 00000000..7b88f7a9 --- /dev/null +++ b/demolib_2021.py @@ -0,0 +1,416 @@ +#!/usr/bin/env python + +""" + +demolib.py: OFC Demo Topology and CLI + +Our demo topology is a cross-connected mesh of 6 POPs. + +""" + +from dataplane import ( Terminal, ROADM, OpticalLink, + SwitchBase as OpticalSwitchBase, + dBm, dB, km, + cleanOptLinks, disableIPv6, Mininet ) +from rest import RestServer + +from mininet.topo import Topo +from mininet.log import setLogLevel, info +from mininet.clean import cleanup +from mininet.cli import CLI +from mininet.node import RemoteController +from mininet.util import natural + +from collections import namedtuple + + +# Routers start listening at 6654 +ListenPortBase = 6653 + +class OpticalCLI( CLI ): + "Extended CLI with optical network commands" + + prompt = 'mininet-optical> ' + + # XXX This should probably be abstracted better. + # Also print() should be output( ... '\n' ) + + def do_signals( self, nodename ): + "Print node signals " + if nodename: + try: + node = self.mn.get( nodename ) + if hasattr( node, 'model' ): + self.printSignals( node.model ) + except: + pass + return + for node in self.mn.switches: + if hasattr( node, 'model' ): + self.printSignals( node.model ) + + + @staticmethod + def formatSigState( state ): + "Return formatted signal state string" + pwr = state[ 'power' ] + ase = state[ 'ase_noise' ] + nli = state[ 'nli_noise' ] + return 'pwr:%.1e ase:%.1e nli:%.1e' % ( pwr, ase, nli ) + + def printSignals(self, model): + "Print signals from a node's model" + for port, sigs in model.port_to_optical_signal_in.items(): + if not sigs: continue + print( model, "in %d:" % port, end='' ) + for sig in sigs: + state = sig.loc_in_to_state.get( model, '' ) + print( sig, self.formatSigState( state ) ) + for port, sigs in model.port_to_optical_signal_out.items(): + if not sigs: continue + print( model, "out %d:" % port, end='' ) + for sig in sigs: + state = sig.loc_out_to_state.get( model, '' ) + print( sig, self.formatSigState( state ) ) + + def opticalLinks( self ): + "Return optical links" + return [ link for link in self.mn.links + if isinstance( link, OpticalLink ) ] + + def do_linksignals( self, _line ): + "Print signals for links between ROADMs" + for link in self.opticalLinks(): + if ( isinstance( link.intf1.node, ROADM ) and + isinstance( link.intf2.node, ROADM ) ): + link.phyLink1.print_signals() + link.phyLink2.print_signals() + + + def do_monitors( self, _line ): + "List monitors on optical links and nodes" + for node in self.mn.values(): + monitor = getattr( node, 'modelMonitor', None ) + if monitor: + print( '%s:' % node, monitor ) + for link in self.opticalLinks(): + if link.monitors: + print( '%s:' % link ) + for monitor in link.monitors: + print( ' ', monitor ) + + def do_osnr( self, _line ): + "List osnr for monitors" + for monitor in self.mn.monitors: + monitor = monitor.model + print( str(monitor) + ':' ) + osnr = monitor.get_dict_osnr() + gosnr = monitor.get_dict_gosnr() + for signal in sorted(osnr, key=lambda s:s.index): + print( '%s OSNR: %.2f dB' % ( signal, osnr[signal] ), end='' ) + print( ' gOSNR: %.2f dB' % gosnr.get(signal, float('nan') ) ) + + def spans( self, minlength=100): + "Span iterator" + links = self.opticalLinks() + phyLinks = sum( [ [link.phyLink1, link.phyLink2] for link in links], [] ) + for phyLink in sorted( phyLinks, key=natural ): + if not phyLink: + continue + if len( phyLink.spans ) == 1 and phyLink.spans[0].span.length < minlength: + # Skip short lengths of fiber + continue + yield( phyLink, phyLink.spans ) + + def do_spans( self, _line ): + "List spans between nodes" + for (phyLink, spans) in self.spans(): + print( phyLink, end=' ' ) + if phyLink.boost_amp: + print( phyLink.boost_amp, end=' ' ) + for span in spans: + print( span.span, span.amplifier if span.amplifier else '', end=' ' ) + print() + + def do_plot( self, line ): + "plot ROADM topology; 'plot save' to save to plot.png" + net = self.mn + try: + import networkx as nx + import matplotlib.pyplot as plt + except: + print( 'Could not import networkx and/or matplotlib.pyplot' ) + return + g = nx.Graph() + g.add_nodes_from( net.switches ) + color = {ROADM: 'red', Terminal: 'blue'} + colors = [color.get(type(node), 'orange') for node in g] + g.add_edges_from([(link.intf1.node, link.intf2.node) for link in net.links + if link.intf1.node in g + and link.intf2.node in g]) + self.mn.g = g + nx.draw_spring( g, node_color=colors, with_labels=True, font_weight='bold', + font_color='white', edgecolors='black', node_size=600 ) + if line: + fname = 'plot.png' + print( 'Saving to', fname, '...' ) + plt.savefig( fname ) + else: + plt.show() + + def do_propagate( self, _line ): + "Obsolete: propagate signals manually" + for node in self.mn.switches: + if isinstance( node, Terminal ): + node.propagate() + + def do_amppowers( self, _line ): + "Print out power for all amps on links" + for link, spans in self.spans(): + print( link, end=' ' ) + for span in spans: + amp = span.amplifier + if amp: + print('amp:', amp) + inputs = amp.port_to_optical_signal_in[0] + outputs = amp.port_to_optical_signal_out[0] + inputs = list( + '%s %.2f dBm' % ( signal[0], signal[0].loc_in_to_state[ amp ][ 'power'] ) + for signal in sorted( outputs, key=lambda s: s[0].index ) ) + outputs = list( + '%s %.2f dBm' % ( signal[0], signal[0].loc_out_to_state[ amp ]['power']) + for signal in sorted( outputs, key=lambda s: s[0].index ) ) + print('input', inputs) + print('output', outputs) + + def do_arp( self, _line ): + "Send gratuitous arps from every host" + print( 'Sending gratuitous ARPs...' ) + for host in self.mn.hosts: + host.cmdPrint( 'arping -bf -c1 -U -I', + host.defaultIntf().name, host.IP() ) + + # FIXME: This is ugly and also doesn't seem to work. + # The amplifier gain is updated but the signals + # don't seem to be updating properly. + # Translated from network.mock_amp_gain_adjust() + def do_setgain( self, line ): + """Set amplifier gain for demo/testing purposes + usage: setgain src dst amp gain""" + params = line.split() + if len( params ) != 2: + print( "usage: setgain src-dst-ampN gain" ) + return + ampName, gain = params + print( self.mn.setgainCmd( ampName, gain ) ) + +CLI = OpticalCLI + + +### Sanity tests + +class OpticalTopo( Topo ): + "Topo with convenience methods for optical links" + + def wdmLink( self, *args, **kwargs ): + "Convenience function to add an OpticalLink" + kwargs.update(cls=OpticalLink) + self.addLink( *args, **kwargs ) + + def ethLink( self, *args, **kwargs ): + "Clarifying alias for addLink" + self.addLink( *args, **kwargs ) + +SpanSpec = namedtuple( 'SpanSpec', 'length amp' ) +AmpSpec = namedtuple( 'AmpSpec', 'name params ') + +def spanSpec( length, amp, **ampParams): + "Return span specifier [length, (ampName, params)]" + ampSpec = AmpSpec(amp, ampParams) + return SpanSpec( length, ampSpec ) + + +class LinearRoadmTopo( OpticalTopo ): + """A linear network with a single ROADM and three POPs + + h1 - s1 - t1 = r1 --- r2 --- r3 = t3 - s3 - h3 + || + t2 - s2 - h2 + h1-h3: hosts + s1-s3: routers (downlink: eth0, uplink: eth1, eth2) + t1-t3: terminals (downlink: eth1, eth2, uplink: wdm3, wdm4) + r1-r3: ROADMs (add/drop: wdm1, wdm2, line: wdm3, wdm4)""" + + @staticmethod + def ip( pop, intfnum=0, template='10.%d.0.%d', subnet='/24' ): + "Return a local IP address or subnet for the given POP" + return template % ( pop, intfnum ) + subnet + + def buildPop( self, p, txCount=2 ): + "Build a POP; returns: ROADM" + # Network elements + hostname, hostip, subnet = 'h%d' % p, self.ip(p, 1), self.ip(p, 0) + host = self.addHost(hostname, ip=hostip, + defaultRoute='dev ' + hostname + '-eth0' ) + router = self.addSwitch('s%d' % p, subnet=subnet, + listenPort=(ListenPortBase + p)) + transceivers = [ + ('t%d' %t, 0*dBm, 'C') for t in range(1, txCount+1) ] + terminal = self.addSwitch( + 't%d' % p, cls=Terminal, transceivers=transceivers ) + roadm = self.addSwitch( 'r%d' % p, cls=ROADM ) + # Local links + for port in range( 1, txCount+1 ): + self.ethLink( router, terminal, port1=port, port2=port ) + self.ethLink( router, host, port1=txCount + 1 ) + for port in range( 1, txCount+1 ): + self.wdmLink( terminal, roadm, port1=txCount+port, port2=port ) + # Return ROADM so we can link it to other POPs as needed + return roadm + + def spans( self, spanLength=50*km, spanCount=4 ): + """Return a list of span specifiers (length, (amp, params)) + the compensation amplifiers are named prefix-ampN""" + entries = [ spanSpec( length=spanLength, amp='amp%d' % i, + target_gain=spanLength/km * .22 * dB, + monitor_mode='out' ) + for i in range(1, spanCount+1) ] + return sum( [ list(entry) for entry in entries ], [] ) + + def build( self, n=3, txCount=2 ): + "Add POPs and connect them in a line" + roadms = [ self.buildPop( p, txCount ) for p in range( 1, n+1 ) ] + + # Inter-POP links + for i in range( 0, n - 1 ): + src, dst = roadms[i], roadms[i+1] + boost = ( 'boost', dict(target_gain=17.0*dB) ) + spans = self.spans( spanCount=2 ) + self.wdmLink( src, dst, boost=boost, spans=spans ) + + +def configureLinearNet( net, packetOnly=False ): + """Configure linear network locally + Channel usage: + r1<->r2: 1 + r1<->r3: 2 + r2<->r3: 1""" + + info( '*** Configuring linear network \n' ) + + # Port numbering + eth1, eth2, eth3 = 1, 2, 3 + wdm1, wdm2, wdm3, wdm4 = 1, 2, 3, 4 + + # Configure routers + # eth0: local, eth1: dest1, eth2: dest2 + routers = s1, s2, s3 = net.get( 's1', 's2', 's3' ) + for pop, dests in enumerate([(s2,s3), (s1, s3), (s1,s2)], start=1): + router, dest1, dest2 = routers[ pop - 1], dests[0], dests[1] + # XXX Only one host for now + hostmac = net.get( 'h%d' % pop).MAC() + router.dpctl( 'del-flows' ) + for eth, dest in enumerate( [ dest1, dest2, router ], start=1 ) : + dstmod = ( 'mod_dl_dst=%s,' % hostmac ) if dest == router else '' + for protocol in 'ip', 'icmp', 'arp': + flow = ( protocol + ',ip_dst=' + dest.params['subnet'] + + 'actions=' + dstmod + + 'dec_ttl,output:%d' % eth ) + router.dpctl( 'add-flow', flow ) + + # Configure transceivers + t1, t2, t3 = net.get( 't1', 't2', 't3' ) + t1.connect( tx=0, ethPort=eth1, wdmPort=wdm3, channel=1) + t1.connect( tx=1, ethPort=eth2, wdmPort=wdm4, channel=2) + t2.connect( tx=0, ethPort=eth1, wdmPort=wdm3, channel=1) + t2.connect( tx=1, ethPort=eth2, wdmPort=wdm4, channel=1) + t3.connect( tx=0, ethPort=eth1, wdmPort=wdm3, channel=2) + t3.connect( tx=1, ethPort=eth2, wdmPort=wdm4, channel=1) + + # Configure roadms + r1, r2, r3 = net.get( 'r1', 'r2', 'r3' ) + local1, local2, line1, line2 = wdm1, wdm2, wdm3, wdm4 + + # r1: add/drop ch1<->r2, ch2<->r3 + r1.connect( port1=local1, port2=line1, channels=[1] ) + r1.connect( port1=local2, port2=line1, channels=[2] ) + + # r2: add/drop ch1<->r1, ch1<->r3 + r2.connect( port1=local1, port2=line1, channels=[1] ) + r2.connect( port1=local2, port2=line2, channels=[1] ) + # r2: pass through ch2 r1<->r3 + r2.connect( port1=line1, port2=line2, channels=[2] ) + + r3.connect( port1=local1, port2=line1, channels=[2] ) + r3.connect( port1=local2, port2=line1, channels=[1] ) + + #for roadm in r1, r2, r3: + # roadm.propagate() + + +def linearRoadmTest(): + "Test Linear ROADM topology" + + topo = LinearRoadmTopo( n=3 ) + net = Mininet( topo=topo ) + net.start() + configureLinearNet( net ) + CLI( net ) + net.stop() + + + +### OFC Demo Topology + + +class DemoTopo( LinearRoadmTopo ): + """OFC Demo Topology + + -------------Linear Topo---------------- + POP1 -- POP2 -- POP3 -- POP4 + + All of the links are bidirectional. + + Each POP consists of a host, router, optical terminal, and ROADM: + + h1 - s1 - t1 - r1 + h2 - s2 - t2 - r2 + etc. + """ + + # Link helper function + def addPopLink( self, src, dst ): + "Construct a link of four 50km fiber spans" + boost = ( 'boost', dict(target_gain=17.0*dB) ) + spans = self.spans( spanLength=50*km, spanCount=2 ) + self.wdmLink( src, dst, boost=boost, spans=spans ) + + def build( self, n=4, txCount=10 ): + "Add POPs and connect them in a ring with some cross-connects" + + # Build POPs + roadms = {p: self.buildPop( p, txCount=txCount ) for p in range( 1, n+1 ) } + print(roadms) + + + # print(ring) + # Linear links + for i in range( 1, n ): + src, dst = roadms[i], roadms[i+1] + self.addPopLink( src, dst ) + + +if __name__ == '__main__': + + # Test our demo topology + cleanup() + setLogLevel( 'info' ) + net = Mininet( topo=DemoTopo( txCount=15 ), autoSetMacs=True, + controller=RemoteController ) + disableIPv6( net ) + restServer = RestServer( net ) + net.start() + restServer.start() + CLI( net ) + restServer.stop() + net.stop() From 8851df23d618b86022acb23dc0d9c6ecaeec4d6f Mon Sep 17 00:00:00 2001 From: jiakaiyu Date: Fri, 28 May 2021 06:37:59 -0700 Subject: [PATCH 09/12] Update rest.py --- rest.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rest.py b/rest.py index cf012530..db1ac684 100755 --- a/rest.py +++ b/rest.py @@ -168,6 +168,12 @@ def connect(): return nodeHandler( 'restConnectHandler' ) +@get( '/disconnect' ) +def disconnect(): + "Configure (or install/remove) connection in optical node" + return nodeHandler( 'restDisconnectHandler' ) + + @get( '/ports' ) def ports(): "Return a node's ports" From abb5e143fc436b2e88c9dbcbe63ce897c331110c Mon Sep 17 00:00:00 2001 From: jiakaiyu Date: Fri, 28 May 2021 06:42:08 -0700 Subject: [PATCH 10/12] Update Control_Test_Mininet_REST.py --- ofcdemo/Control_Test_Mininet_REST.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ofcdemo/Control_Test_Mininet_REST.py b/ofcdemo/Control_Test_Mininet_REST.py index a29fa34e..b57ebbce 100644 --- a/ofcdemo/Control_Test_Mininet_REST.py +++ b/ofcdemo/Control_Test_Mininet_REST.py @@ -261,6 +261,6 @@ def Test(): control.configurePacketSwitch(src=1, dst=4, channel=channel, router=router) control.configurePacketSwitch(src=4, dst=1, channel=channel, router=router2) - #uninstallPath(path=path, channels=[channel], net=net) + control.uninstallPath(path=path, channels=[channel]) Test() From f97c8c87fa9f1defa0817397c1e5121d1ef1dbc5 Mon Sep 17 00:00:00 2001 From: aamirq Date: Fri, 28 May 2021 15:58:17 -0700 Subject: [PATCH 11/12] added a space for readility --- node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node.py b/node.py index c19e8a18..f1e56ea2 100755 --- a/node.py +++ b/node.py @@ -363,7 +363,7 @@ def receiver(self, optical_signal, in_port): else: if ber!=None: print("*** %s receiving %s at port %s: Success! \t modulation format: %s\n" - "gOSNR: %f dB | OSNR: %f db |ber: %e" % + "gOSNR: %f dB | OSNR: %f db | ber: %e" % (self.name, optical_signal, in_port, modulation_format, gosnr, osnr, ber)) else: From ca735cb41be78879f90bacfe24e6c58ff381a13a Mon Sep 17 00:00:00 2001 From: aamirq Date: Fri, 28 May 2021 16:18:54 -0700 Subject: [PATCH 12/12] Added bit_error_rate functionality in receiver() --- node.py | 1 + 1 file changed, 1 insertion(+) diff --git a/node.py b/node.py index 05f44333..22dcb3f5 100755 --- a/node.py +++ b/node.py @@ -336,6 +336,7 @@ def receiver(self, optical_signal, in_port): if in_port in self.rx_to_channel: if self.rx_to_channel[in_port]['channel_id'] is optical_signal.index: rx_transceiver = self.rx_to_channel[in_port]['transceiver'] + modulation_format = rx_transceiver.modulation_format # Get signal info power = optical_signal.loc_in_to_state[self]['power'] ase_noise = optical_signal.loc_in_to_state[self]['ase_noise']