Exemplo n.º 1
0
def test_compare_nf_models(gain, setup_edfa_variable_gain, si):
    """ compare the 2 amplifier models (polynomial and estimated from nf_min and max)
     => nf_model vs nf_poly_fit for intermediate gain values:
     between gain_min and gain_flatmax some discrepancy is expected but target < 0.5dB
     => unitary test for Edfa._calc_nf (and Edfa.interpol_params)"""
    edfa = setup_edfa_variable_gain
    frequencies = array([c.frequency for c in si.carriers])
    pin = array([c.power.signal+c.power.nli+c.power.ase for c in si.carriers])
    pin = pin/db2lin(gain)
    baud_rates = array([c.baud_rate for c in si.carriers])
    edfa.operational.gain_target = gain
    pref = Pref(0, -gain)
    edfa.interpol_params(frequencies, pin, baud_rates, pref)
    nf_model = edfa.nf[0]
    edfa.interpol_params(frequencies, pin, baud_rates, pref)
    nf_poly = edfa.nf[0]
    assert pytest.approx(nf_model, abs=0.5) == nf_poly
Exemplo n.º 2
0
 def propagate(self, pref, *carriers):
     #pin_target and loss are read from eqpt_config.json['Roadm']
     #all ingress channels in xpress are set to this power level
     #but add channels are not, so we define an effective loss
     #in the case of add channels
     self.effective_pch_out_db = min(pref.p_spani, self.params.target_pch_out_db)
     self.effective_loss = pref.p_spani - self.effective_pch_out_db
     carriers_power = array([c.power.signal +c.power.nli+c.power.ase for c in carriers])
     carriers_att = list(map(lambda x : lin2db(x*1e3)-self.params.target_pch_out_db, carriers_power))
     exceeding_att = -min(list(filter(lambda x: x < 0, carriers_att)), default = 0)
     carriers_att = list(map(lambda x: db2lin(x+exceeding_att), carriers_att))
     for carrier_att, carrier in zip(carriers_att, carriers) :
         pwr = carrier.power
         pwr = pwr._replace( signal = pwr.signal/carrier_att,
                             nli = pwr.nli/carrier_att,
                             ase = pwr.ase/carrier_att)
         yield carrier._replace(power=pwr)
Exemplo n.º 3
0
    def interpol_params(self, frequencies, pin, baud_rates, pref):
        """interpolate SI channel frequencies with the edfa dgt and gain_ripple frquencies from JSON
        """
        # TODO|jla: read amplifier actual frequencies from additional params in json
        self.channel_freq = frequencies

        amplifier_freq = arrange_frequencies(len(self.params.dgt),
                                             self.params.f_min,
                                             self.params.f_max)  # Hz
        self.interpol_dgt = interp(self.channel_freq, amplifier_freq,
                                   self.params.dgt)

        amplifier_freq = arrange_frequencies(len(self.params.gain_ripple),
                                             self.params.f_min,
                                             self.params.f_max)  # Hz
        self.interpol_gain_ripple = interp(self.channel_freq, amplifier_freq,
                                           self.params.gain_ripple)

        amplifier_freq = arrange_frequencies(len(self.params.nf_ripple),
                                             self.params.f_min,
                                             self.params.f_max)  # Hz
        self.interpol_nf_ripple = interp(self.channel_freq, amplifier_freq,
                                         self.params.nf_ripple)

        self.nch = frequencies.size
        self.pin_db = lin2db(sum(pin * 1e3))
        """in power mode: delta_p is defined and can be used to calculate the power target
        This power target is used calculate the amplifier gain"""
        if self.delta_p is not None:
            self.target_pch_out_db = round(self.delta_p + pref.p_span0, 2)
            self.effective_gain = self.target_pch_out_db - pref.p_spani
        """check power saturation and correct effective gain & power accordingly:"""
        self.effective_gain = min(
            self.effective_gain,
            self.params.p_max - (pref.p_spani + pref.neq_ch))
        #print(self.uid, self.effective_gain, self.operational.gain_target)
        self.effective_pch_out_db = round(pref.p_spani + self.effective_gain,
                                          2)
        """check power saturation and correct target_gain accordingly:"""
        #print(self.uid, self.effective_gain, self.pin_db, pref.p_spani)
        self.nf = self._calc_nf()
        self.gprofile = self._gain_profile(pin)

        pout = (pin + self.noise_profile(baud_rates)) * db2lin(self.gprofile)
        self.pout_db = lin2db(sum(pout * 1e3))
Exemplo n.º 4
0
def test_compare_nf_models(gain, setup_edfa_variable_gain, si):
    """ compare the 2 amplifier models (polynomial and estimated from nf_min and max)
     => nf_model vs nf_poly_fit for intermediate gain values:
     between gain_min and gain_flatmax some discrepancy is expected but target < 0.5dB
     => unitary test for Edfa._calc_nf (and Edfa.interpol_params)"""
    edfa = setup_edfa_variable_gain
    frequencies = array([c.frequency for c in si.carriers])
    pin = array(
        [c.power.signal + c.power.nli + c.power.ase for c in si.carriers])
    pin = pin / db2lin(gain)
    baud_rates = array([c.baud_rate for c in si.carriers])
    edfa.operational.gain_target = gain
    # edfa is variable gain type
    pref = Pref(0, -gain, lin2db(len(frequencies)))
    edfa.interpol_params(frequencies, pin, baud_rates, pref)
    nf_model = edfa.nf[0]

    # change edfa type variety to a polynomial
    el_config = {
        "uid": "Edfa1",
        "operational": {
            "gain_target": gain,
            "tilt_target": 0
        },
        "metadata": {
            "location": {
                "region": "",
                "latitude": 2,
                "longitude": 0
            }
        }
    }
    equipment = load_equipment(eqpt_library)
    extra_params = equipment['Edfa']['CienaDB_medium_gain']
    temp = el_config.setdefault('params', {})
    temp = merge_amplifier_restrictions(temp, extra_params.__dict__)
    el_config['params'] = temp
    edfa = Edfa(**el_config)

    # edfa is variable gain type
    edfa.interpol_params(frequencies, pin, baud_rates, pref)
    nf_poly = edfa.nf[0]
    print(nf_poly, nf_model)
    assert pytest.approx(nf_model, abs=0.5) == nf_poly
Exemplo n.º 5
0
    def noise_profile(self, df):
        """noise_profile(bw) computes amplifier ASE (W) in signal bandwidth (Hz)

        Noise is calculated at amplifier input

        :bw: signal bandwidth = baud rate in Hz
        :type bw: float

        :return: the asepower in W in the signal bandwidth bw for 96 channels
        :return type: numpy array of float

        ASE power using per channel gain profile inputs:

            NF_dB - Noise figure in dB, vector of length number of channels or
                    spectral slices
            G_dB  - Actual gain calculated for the EDFA, vector of length number of
                    channels or spectral slices
            ffs     - Center frequency grid of the channels or spectral slices in
                    THz, vector of length number of channels or spectral slices
            dF    - width of each channel or spectral slice in THz,
                    vector of length number of channels or spectral slices

        OUTPUT:

            ase_dBm - ase in dBm per channel or spectral slice

        NOTE:

            The output is the total ASE in the channel or spectral slice. For
            50GHz channels the ASE BW is effectively 0.4nm. To get to noise power
            in 0.1nm, subtract 6dB.

        ONSR is usually quoted as channel power divided by
        the ASE power in 0.1nm RBW, regardless of the width of the actual
        channel.  This is a historical convention from the days when optical
        signals were much smaller (155Mbps, 2.5Gbps, ... 10Gbps) than the
        resolution of the OSAs that were used to measure spectral power which
        were set to 0.1nm resolution for convenience.  Moving forward into
        flexible grid and high baud rate signals, it may be convenient to begin
        quoting power spectral density in the same BW for both signal and ASE,
        e.g. 12.5GHz."""

        ase = h * df * self.channel_freq * db2lin(self.nf)  # W
        return ase  # in W at amplifier input
Exemplo n.º 6
0
    def propagate(self, pref, *carriers):
        #pin_target and loss are read from eqpt_config.json['Roadm']
        #all ingress channels in xpress are set to this power level
        #but add channels are not, so we define an effective loss
        #in the case of add channels
        if self.target_pch_out_db:
            self.effective_loss = pref.pi - self.target_pch_out_db
        else:
            self.effective_loss = self.loss
        self.effective_pch_out_db = pref.pi - self.effective_loss
        attenuation = db2lin(self.effective_loss)

        for carrier in carriers:
            pwr = carrier.power
            pwr = pwr._replace(signal=pwr.signal / attenuation,
                               nonlinear_interference=pwr.nli / attenuation,
                               amplified_spontaneous_emission=pwr.ase /
                               attenuation)
            yield carrier._replace(power=pwr)
Exemplo n.º 7
0
def gsnr_01(data_path):
    config_path = data_path / 'config_file.json'
    config = json.load(open(config_path, 'r'))
    list_data_files = [
        file for file in os.listdir(data_path) if file.endswith('.mat')
    ]

    baud_rate = config['spectral_config']['general_si']['symbol_rates']
    ratio_01nm = lin2db(12.5e9 / baud_rate)

    for file in list_data_files:
        data = DataQot.load_qot_mat(data_path / file)

        gsnr_01_list = []
        for i, gsnr_lin in enumerate(data.gsnr):
            gsnr = lin2db(gsnr_lin) - ratio_01nm
            gsnr_01_list.append(db2lin(gsnr))

        data.gsnr = np.array(gsnr_01_list)
        data.save_data()
Exemplo n.º 8
0
 def update_snr(self, *args):
     """
     snr_added in 0.1nm
     compute SNR penalties such as transponder Tx_osnr or Roadm add_drop_osnr
     only applied in request.py / propagate on the last Trasceiver node of the path
     all penalties are added in a single call because to avoid uncontrolled cumul
     """
     # use raw_values so that the added SNR penalties are not cumulated
     snr_added = 0
     for s in args:
         snr_added += db2lin(-s)
     snr_added = -lin2db(snr_added)
     self.osnr_ase = list(map(lambda x, y: snr_sum(x, y, snr_added),
                              self.raw_osnr_ase, self.baud_rate))
     self.snr = list(map(lambda x, y: snr_sum(x, y, snr_added),
                         self.raw_snr, self.baud_rate))
     self.osnr_ase_01nm = list(map(lambda x: snr_sum(x, 12.5e9, snr_added),
                                   self.raw_osnr_ase_01nm))
     self.snr_01nm = list(map(lambda x: snr_sum(x, 12.5e9, snr_added),
                              self.raw_snr_01nm))
Exemplo n.º 9
0
def propagation(input_power, con_in, con_out,dest):
    equipment = load_equipment(eqpt_library_name)
    network = load_network(network_file_name,equipment)
    build_network(network, equipment, 0, 20)

    # parametrize the network elements with the con losses and adapt gain
    # (assumes all spans are identical)
    for e in network.nodes():
        if isinstance(e, Fiber):
            loss = e.loss_coef * e.length
            e.con_in = con_in
            e.con_out = con_out
        if isinstance(e, Edfa):
            e.operational.gain_target = loss + con_in + con_out

    transceivers = {n.uid: n for n in network.nodes() if isinstance(n, Transceiver)}

    p = input_power
    p = db2lin(p) * 1e-3
    spacing = 0.05 # THz
    si = SpectralInformation() # SI units: W, Hz
    si = si.update(carriers=[
        Channel(f, (191.3 + spacing * f) * 1e12, 32e9, 0.15, Power(p, 0, 0))
        for f in range(1,80)
    ])
    source = next(transceivers[uid] for uid in transceivers if uid == 'trx A')
    sink = next(transceivers[uid] for uid in transceivers if uid == dest)
    path = dijkstra_path(network, source, sink)
    for el in path:
        si = el(si)
        print(el) # remove this line when sweeping across several powers
    edfa_sample = next(el for el in path if isinstance(el, Edfa))
    nf = mean(edfa_sample.nf)

    print(f'pw: {input_power} conn in: {con_in} con out: {con_out}',
          f'[email protected]: {round(mean(sink.osnr_ase_01nm),2)}',
          f'SNR@bandwitdth: {round(mean(sink.snr),2)}')
    return sink , nf
Exemplo n.º 10
0
    def propagate(self, *carriers):
        """add ase noise to the propagating carriers of SpectralInformation"""
        i = 0
        pin = np.array([
            c.power.signal + c.power.nli + c.power.ase for c in carriers
        ])  #pin in W
        freq = np.array([c.frequency for c in carriers])
        brate = np.array([c.baud_rate for c in carriers])
        #interpolate the amplifier vectors with the carriers freq, calculate nf & gain profile
        self.interpol_params(freq, pin, brate)
        gain = db2lin(self.gprofile)
        carrier_ase = self.noise_profile(brate)

        for carrier in carriers:

            pwr = carrier.power
            bw = carrier.baud_rate
            pwr = pwr._replace(
                signal=pwr.signal * gain[i],
                nonlinear_interference=pwr.nli * gain[i],
                amplified_spontaneous_emission=(pwr.ase + carrier_ase[i]) *
                gain[i])
            i += 1
            yield carrier._replace(power=pwr)
Exemplo n.º 11
0
 def propagate(self, pref, *carriers, degree):
     # pin_target and loss are read from eqpt_config.json['Roadm']
     # all ingress channels in xpress are set to this power level
     # but add channels are not, so we define an effective loss
     # in the case of add channels
     # find the target power on this degree:
     # if a target power has been defined for this degree use it else use the global one.
     # if the input power is lower than the target one, use the input power instead because
     # a ROADM doesn't amplify, it can only attenuate
     # TODO maybe add a minimum loss for the ROADM
     per_degree_pch = self.per_degree_pch_out_db[degree] if degree in self.per_degree_pch_out_db.keys() else self.params.target_pch_out_db
     self.effective_pch_out_db = min(pref.p_spani, per_degree_pch)
     self.effective_loss = pref.p_spani - self.effective_pch_out_db
     carriers_power = array([c.power.signal + c.power.nli + c.power.ase for c in carriers])
     carriers_att = list(map(lambda x: lin2db(x * 1e3) - per_degree_pch, carriers_power))
     exceeding_att = -min(list(filter(lambda x: x < 0, carriers_att)), default=0)
     carriers_att = list(map(lambda x: db2lin(x + exceeding_att), carriers_att))
     for carrier_att, carrier in zip(carriers_att, carriers):
         pwr = carrier.power
         pwr = pwr._replace(signal=pwr.signal / carrier_att,
                            nli=pwr.nli / carrier_att,
                            ase=pwr.ase / carrier_att)
         pmd = sqrt(carrier.pmd**2 + self.params.pmd**2)
         yield carrier._replace(power=pwr, pmd=pmd)
Exemplo n.º 12
0
def estimate_nf_model(type_variety, gain_min, gain_max, nf_min, nf_max):
    if nf_min < -10:
        raise EquipmentConfigError(
            f'Invalid nf_min value {nf_min!r} for amplifier {type_variety}')
    if nf_max < -10:
        raise EquipmentConfigError(
            f'Invalid nf_max value {nf_max!r} for amplifier {type_variety}')

    # NF estimation model based on nf_min and nf_max
    # delta_p:  max power dB difference between first and second stage coils
    # dB g1a:   first stage gain - internal VOA attenuation
    # nf1, nf2: first and second stage coils
    #           calculated by solving nf_{min,max} = nf1 + nf2 / g1a{min,max}
    delta_p = 5
    g1a_min = gain_min - (gain_max - gain_min) - delta_p
    g1a_max = gain_max - delta_p
    nf2 = lin2db((db2lin(nf_min) - db2lin(nf_max)) /
                 (1 / db2lin(g1a_max) - 1 / db2lin(g1a_min)))
    nf1 = lin2db(db2lin(nf_min) - db2lin(nf2) / db2lin(g1a_max))

    if nf1 < 4:
        raise EquipmentConfigError(
            f'First coil value too low {nf1} for amplifier {type_variety}')

    # Check 1 dB < delta_p < 6 dB to ensure nf_min and nf_max values make sense.
    # There shouldn't be high nf differences between the two coils:
    #    nf2 should be nf1 + 0.3 < nf2 < nf1 + 2
    # If not, recompute and check delta_p
    if not nf1 + 0.3 < nf2 < nf1 + 2:
        nf2 = clip(nf2, nf1 + 0.3, nf1 + 2)
        g1a_max = lin2db(db2lin(nf2) / (db2lin(nf_min) - db2lin(nf1)))
        delta_p = gain_max - g1a_max
        g1a_min = gain_min - (gain_max - gain_min) - delta_p
        if not 1 < delta_p < 11:
            raise EquipmentConfigError(
                f'Computed \N{greek capital letter delta}P invalid \
                \n 1st coil vs 2nd coil calculated DeltaP {delta_p:.2f} for \
                \n amplifier {type_variety} is not valid: revise inputs \
                \n calculated 1st coil NF = {nf1:.2f}, 2nd coil NF = {nf2:.2f}'
            )
    # Check calculated values for nf1 and nf2
    calc_nf_min = lin2db(db2lin(nf1) + db2lin(nf2) / db2lin(g1a_max))
    if not isclose(nf_min, calc_nf_min, abs_tol=0.01):
        raise EquipmentConfigError(
            f'nf_min does not match calc_nf_min, {nf_min} vs {calc_nf_min} for amp {type_variety}'
        )
    calc_nf_max = lin2db(db2lin(nf1) + db2lin(nf2) / db2lin(g1a_min))
    if not isclose(nf_max, calc_nf_max, abs_tol=0.01):
        raise EquipmentConfigError(
            f'nf_max does not match calc_nf_max, {nf_max} vs {calc_nf_max} for amp {type_variety}'
        )

    return nf1, nf2, delta_p
Exemplo n.º 13
0
def propagate_raman_fiber(fiber, *carriers):
    simulation = Simulation.get_simulation()
    sim_params = simulation.sim_params
    raman_params = sim_params.raman_params
    nli_params = sim_params.nli_params
    # apply input attenuation to carriers
    attenuation_in = db2lin(fiber.params.con_in + fiber.params.att_in)
    chan = []
    for carrier in carriers:
        pwr = carrier.power
        pwr = pwr._replace(signal=pwr.signal / attenuation_in,
                           nli=pwr.nli / attenuation_in,
                           ase=pwr.ase / attenuation_in)
        carrier = carrier._replace(power=pwr)
        chan.append(carrier)
    carriers = tuple(f for f in chan)

    # evaluate fiber attenuation involving also SRS if required by sim_params
    raman_solver = fiber.raman_solver
    raman_solver.carriers = carriers
    raman_solver.raman_pumps = fiber.raman_pumps
    stimulated_raman_scattering = raman_solver.stimulated_raman_scattering

    fiber_attenuation = (stimulated_raman_scattering.rho[:, -1])**-2
    if not raman_params.flag_raman:
        fiber_attenuation = tuple(fiber.params.lin_attenuation
                                  for _ in carriers)

    # evaluate Raman ASE noise if required by sim_params and if raman pumps are present
    if raman_params.flag_raman and fiber.raman_pumps:
        raman_ase = raman_solver.spontaneous_raman_scattering.power[:, -1]
    else:
        raman_ase = tuple(0 for _ in carriers)

    # evaluate nli and propagate in fiber
    attenuation_out = db2lin(fiber.params.con_out)
    nli_solver = fiber.nli_solver
    nli_solver.stimulated_raman_scattering = stimulated_raman_scattering

    nli_frequencies = []
    computed_nli = []
    for carrier in (
            c for c in carriers
            if c.channel_number in sim_params.nli_params.computed_channels):
        resolution_param = frequency_resolution(carrier, carriers, sim_params,
                                                fiber)
        f_cut_resolution, f_pump_resolution, _, _ = resolution_param
        nli_params.f_cut_resolution = f_cut_resolution
        nli_params.f_pump_resolution = f_pump_resolution
        nli_frequencies.append(carrier.frequency)
        computed_nli.append(nli_solver.compute_nli(carrier, *carriers))

    new_carriers = []
    for carrier, attenuation, rmn_ase in zip(carriers, fiber_attenuation,
                                             raman_ase):
        carrier_nli = interp(carrier.frequency, nli_frequencies, computed_nli)
        pwr = carrier.power
        pwr = pwr._replace(
            signal=pwr.signal / attenuation / attenuation_out,
            nli=(pwr.nli + carrier_nli) / attenuation / attenuation_out,
            ase=((pwr.ase / attenuation) + rmn_ase) / attenuation_out)
        new_carriers.append(carrier._replace(power=pwr))
    return new_carriers
Exemplo n.º 14
0
def transmission_main_example(args=None):
    parser = argparse.ArgumentParser(
        description=
        'Send a full spectrum load through the network from point A to point B',
        epilog=_help_footer,
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
    )
    _add_common_options(parser,
                        network_default=_examples_dir /
                        'edfa_example_network.json')
    parser.add_argument('--show-channels',
                        action='store_true',
                        help='Show final per-channel OSNR summary')
    parser.add_argument('-pl', '--plot', action='store_true')
    parser.add_argument('-l',
                        '--list-nodes',
                        action='store_true',
                        help='list all transceiver nodes')
    parser.add_argument('-po',
                        '--power',
                        default=0,
                        help='channel ref power in dBm')
    parser.add_argument('source', nargs='?', help='source node')
    parser.add_argument('destination', nargs='?', help='destination node')

    args = parser.parse_args(args if args is not None else sys.argv[1:])
    _setup_logging(args)

    (equipment,
     network) = load_common_data(args.equipment, args.topology,
                                 args.sim_params,
                                 args.save_network_before_autodesign)

    if args.plot:
        plot_baseline(network)

    transceivers = {
        n.uid: n
        for n in network.nodes() if isinstance(n, Transceiver)
    }

    if not transceivers:
        sys.exit('Network has no transceivers!')
    if len(transceivers) < 2:
        sys.exit('Network has only one transceiver!')

    if args.list_nodes:
        for uid in transceivers:
            print(uid)
        sys.exit()

    # First try to find exact match if source/destination provided
    if args.source:
        source = transceivers.pop(args.source, None)
        valid_source = True if source else False
    else:
        source = None
        _logger.info('No source node specified: picking random transceiver')

    if args.destination:
        destination = transceivers.pop(args.destination, None)
        valid_destination = True if destination else False
    else:
        destination = None
        _logger.info(
            'No destination node specified: picking random transceiver')

    # If no exact match try to find partial match
    if args.source and not source:
        # TODO code a more advanced regex to find nodes match
        source = next(
            (transceivers.pop(uid)
             for uid in transceivers if args.source.lower() in uid.lower()),
            None)

    if args.destination and not destination:
        # TODO code a more advanced regex to find nodes match
        destination = next((transceivers.pop(uid) for uid in transceivers
                            if args.destination.lower() in uid.lower()), None)

    # If no partial match or no source/destination provided pick random
    if not source:
        source = list(transceivers.values())[0]
        del transceivers[source.uid]

    if not destination:
        destination = list(transceivers.values())[0]

    _logger.info(f'source = {args.source!r}')
    _logger.info(f'destination = {args.destination!r}')

    params = {}
    params['request_id'] = 0
    params['trx_type'] = ''
    params['trx_mode'] = ''
    params['source'] = source.uid
    params['destination'] = destination.uid
    params['bidir'] = False
    params['nodes_list'] = [destination.uid]
    params['loose_list'] = ['strict']
    params['format'] = ''
    params['path_bandwidth'] = 0
    trx_params = trx_mode_params(equipment)
    #     Randomly generate input power
    input_power = round(random.random(), 2)
    input_power = -2 + (input_power * (8))
    args.power = input_power
    if args.power:
        trx_params['power'] = db2lin(float(args.power)) * 1e-3
    params.update(trx_params)
    req = PathRequest(**params)

    power_mode = equipment['Span']['default'].power_mode
    print('\n'.join([
        f'Power mode is set to {power_mode}',
        f'=> it can be modified in eqpt_config.json - Span'
    ]))

    pref_ch_db = lin2db(req.power *
                        1e3)  # reference channel power / span (SL=20dB)
    pref_total_db = pref_ch_db + lin2db(
        req.nb_channel)  # reference total power / span (SL=20dB)
    try:
        build_network(network, equipment, pref_ch_db, pref_total_db)
    except exceptions.NetworkTopologyError as e:
        print(
            f'{ansi_escapes.red}Invalid network definition:{ansi_escapes.reset} {e}'
        )
        sys.exit(1)
    except exceptions.ConfigurationError as e:
        print(
            f'{ansi_escapes.red}Configuration error:{ansi_escapes.reset} {e}')
        sys.exit(1)
    path = compute_constrained_path(network, req)

    spans = [
        s.params.length for s in path
        if isinstance(s, RamanFiber) or isinstance(s, Fiber)
    ]
    print(
        f'\nThere are {len(spans)} fiber spans over {sum(spans)/1000:.0f} km between {source.uid} '
        f'and {destination.uid}')
    print(f'\nNow propagating between {source.uid} and {destination.uid}:')

    try:
        p_start, p_stop, p_step = equipment['SI']['default'].power_range_db
        p_num = abs(int(round(
            (p_stop - p_start) / p_step))) + 1 if p_step != 0 else 1
        power_range = list(linspace(p_start, p_stop, p_num))
    except TypeError:
        print(
            'invalid power range definition in eqpt_config, should be power_range_db: [lower, upper, step]'
        )
        power_range = [0]

    if not power_mode:
        # power cannot be changed in gain mode
        power_range = [0]
    for dp_db in power_range:
        req.power = db2lin(pref_ch_db + dp_db) * 1e-3
        if power_mode:
            print(
                f'\nPropagating with input power = {ansi_escapes.cyan}{lin2db(req.power*1e3):.2f} dBm{ansi_escapes.reset}:'
            )
        else:
            print(
                f'\nPropagating in {ansi_escapes.cyan}gain mode{ansi_escapes.reset}: power cannot be set manually'
            )
        infos = propagate(path, req, equipment)
        if len(power_range) == 1:
            for elem in path:
                print(elem)
            if power_mode:
                print(
                    f'\nTransmission result for input power = {lin2db(req.power*1e3):.2f} dBm:'
                )
            else:
                print(f'\nTransmission results:')
#             print('-------------')
#             print(destination.snr_01nm)
#             print('-------------')
            print(
                f'  Final SNR total (0.1 nm): {ansi_escapes.cyan}{mean(destination.snr_01nm):.02f} dB{ansi_escapes.reset}'
            )
        else:
            print(path[-1])

    if args.save_network is not None:
        save_network(network, args.save_network)
        print(
            f'{ansi_escapes.blue}Network (after autodesign) saved to {args.save_network}{ansi_escapes.reset}'
        )

    if args.show_channels:
        print('\nThe total SNR per channel at the end of the line is:')
        print('{:>5}{:>26}{:>26}{:>28}{:>28}{:>28}'.format(
            'Ch. #', 'Channel frequency (THz)', 'Channel power (dBm)',
            'OSNR ASE (signal bw, dB)', 'SNR NLI (signal bw, dB)',
            'SNR total (signal bw, dB)'))
        #         print(dir(info))
        for final_carrier, ch_osnr, ch_snr_nl, ch_snr in zip(
                infos.carriers, path[-1].osnr_ase, path[-1].osnr_nli,
                path[-1].snr):
            ch_freq = final_carrier.frequency * 1e-12
            ch_power = lin2db(final_carrier.power.signal * 1e3)
            print('{:5}{:26.2f}{:26.2f}{:28.2f}{:28.2f}{:28.2f}'.format(
                final_carrier.channel_number, round(ch_freq, 2),
                round(ch_power, 2), round(ch_osnr, 2), round(ch_snr_nl, 2),
                round(ch_snr, 2)))

    if not args.source:
        print(f'\n(No source node specified: picked {source.uid})')
    elif not valid_source:
        print(
            f'\n(Invalid source node {args.source!r} replaced with {source.uid})'
        )

    if not args.destination:
        print(f'\n(No destination node specified: picked {destination.uid})')
    elif not valid_destination:
        print(
            f'\n(Invalid destination node {args.destination!r} replaced with {destination.uid})'
        )

    if args.plot:
        plot_results(network, path, source, destination)

#     MY ADDITION
# just to see what the different contributions of ASE and NLI are
#     return input_power, path[-1].osnr_ase, path[-1].osnr_nli, path[-1].snr
# to test Raman
    return input_power, destination.snr_01nm, mean(destination.snr_01nm)
Exemplo n.º 15
0
    def __init__(self, Request, eqpt_filename):
        # request_id is str
        # excel has automatic number formatting that adds .0 on integer values
        # the next lines recover the pure int value, assuming this .0 is unwanted
        if not isinstance(Request.request_id, str):
            value = str(int(Request.request_id))
            if value.endswith('.0'):
                value = value[:-2]
            self.request_id = value
        else:
            self.request_id = Request.request_id
        self.source = Request.source
        self.destination = Request.destination
        self.srctpid = f'trx {Request.source}'
        self.dsttpid = f'trx {Request.destination}'
        # test that trx_type belongs to eqpt_config.json
        # if not replace it with a default
        equipment = load_equipment(eqpt_filename)
        try:
            if equipment['Transceiver'][Request.trx_type]:
                self.trx_type = Request.trx_type
            if [
                    mode
                    for mode in equipment['Transceiver'][Request.trx_type].mode
            ]:
                self.mode = Request.mode
        except KeyError:
            msg = f'could not find tsp : {Request.trx_type} with mode: {Request.mode} in eqpt library \nComputation stopped.'
            #print(msg)
            logger.critical(msg)
            exit()
        # excel input are in GHz and dBm
        self.spacing = Request.spacing * 1e9
        self.power = db2lin(Request.power) * 1e-3
        self.nb_channel = int(Request.nb_channel)
        if not isinstance(Request.disjoint_from, str):
            value = str(int(Request.disjoint_from))
            if value.endswith('.0'):
                value = value[:-2]
        else:
            value = Request.disjoint_from
        self.disjoint_from = [n for n in value.split()]
        self.nodes_list = []
        if Request.nodes_list:
            self.nodes_list = Request.nodes_list.split(' | ')
        try:
            self.nodes_list.remove(self.source)
            msg = f'{self.source} removed from explicit path node-list'
            logger.info(msg)
            # print(msg)
        except ValueError:
            msg = f'{self.source} already removed from explicit path node-list'
            logger.info(msg)
            # print(msg)
        try:
            self.nodes_list.remove(self.destination)
            msg = f'{self.destination} removed from explicit path node-list'
            logger.info(msg)
            # print(msg)
        except ValueError:
            msg = f'{self.destination} already removed from explicit path node-list'
            logger.info(msg)
            # print(msg)

        self.loose = 'loose'
        if Request.is_loose == 'no':
            self.loose = 'strict'
Exemplo n.º 16
0
    def _gain_profile(self, pin, err_tolerance=1.0e-11, simple_opt=True):
        """
        Pin : input power / channel in W

        :param gain_ripple: design flat gain
        :param dgt: design gain tilt
        :param Pin: total input power in W
        :param gp: Average gain setpoint in dB units (provisioned gain)
        :param gtp: gain tilt setting (provisioned tilt)
        :type gain_ripple: numpy.ndarray
        :type dgt: numpy.ndarray
        :type Pin: numpy.ndarray
        :type gp: float
        :type gtp: float
        :return: gain profile in dBm, per channel or spectral slice
        :rtype: numpy.ndarray

        Checking of output power clamping is implemented in interpol_params().


        Based on:

            R. di Muro, "The Er3+ fiber gain coefficient derived from a dynamic
            gain tilt technique", Journal of Lightwave Technology, Vol. 18,
            Iss. 3, Pp. 343-347, 2000.

            Ported from Matlab version written by David Boerges at Ciena.
        """

        # TODO|jla: check what param should be used (currently length(dgt))
        if len(self.interpol_dgt) == 1:
            return array([self.effective_gain])

        # TODO|jla: find a way to use these or lose them. Primarily we should have
        # a way to determine if exceeding the gain or output power of the amp
        tot_in_power_db = self.pin_db  # Pin in W

        # linear fit to get the
        p = polyfit(self.channel_freq, self.interpol_dgt, 1)
        dgt_slope = p[0]

        # Calculate the target slope
        targ_slope = -self.tilt_target / (self.params.f_max - self.params.f_min)

        # first estimate of DGT scaling
        try:
            dgts1 = targ_slope / dgt_slope
        except ZeroDivisionError:
            dgts1 = 0

        # when simple_opt is true, make 2 attempts to compute gain and
        # the internal voa value. This is currently here to provide direct
        # comparison with original Matlab code. Will be removed.
        # TODO|jla: replace with loop

        if not simple_opt:
            return

        # first estimate of Er gain & VOA loss
        g1st = array(self.interpol_gain_ripple) + self.params.gain_flatmax \
            + array(self.interpol_dgt) * dgts1
        voa = lin2db(mean(db2lin(g1st))) - self.effective_gain

        # second estimate of amp ch gain using the channel input profile
        g2nd = g1st - voa

        pout_db = lin2db(sum(pin * 1e3 * db2lin(g2nd)))
        dgts2 = self.effective_gain - (pout_db - tot_in_power_db)

        # center estimate of amp ch gain
        xcent = dgts2
        gcent = g1st - voa + array(self.interpol_dgt) * xcent
        pout_db = lin2db(sum(pin * 1e3 * db2lin(gcent)))
        gavg_cent = pout_db - tot_in_power_db

        # Lower estimate of amp ch gain
        deltax = max(g1st) - min(g1st)
        # if no ripple deltax = 0 and xlow = xcent: div 0
        # TODO|jla: add check for flat gain response
        if abs(deltax) <= 0.05:  # not enough ripple to consider calculation
            return g1st - voa

        xlow = dgts2 - deltax
        glow = g1st - voa + array(self.interpol_dgt) * xlow
        pout_db = lin2db(sum(pin * 1e3 * db2lin(glow)))
        gavg_low = pout_db - tot_in_power_db

        # upper gain estimate
        xhigh = dgts2 + deltax
        ghigh = g1st - voa + array(self.interpol_dgt) * xhigh
        pout_db = lin2db(sum(pin * 1e3 * db2lin(ghigh)))
        gavg_high = pout_db - tot_in_power_db

        # compute slope
        slope1 = (gavg_low - gavg_cent) / (xlow - xcent)
        slope2 = (gavg_cent - gavg_high) / (xcent - xhigh)

        if abs(self.effective_gain - gavg_cent) <= err_tolerance:
            dgts3 = xcent
        elif self.effective_gain < gavg_cent:
            dgts3 = xcent - (gavg_cent - self.effective_gain) / slope1
        else:
            dgts3 = xcent + (-gavg_cent + self.effective_gain) / slope2

        return g1st - voa + array(self.interpol_dgt) * dgts3
Exemplo n.º 17
0
ch_spacing = 0.05
fc = itufs(ch_spacing)
lc = freq2wavelength(fc) / 1000
nchan = np.arange(len(lc))
df = np.ones(len(lc)) * ch_spacing

edfa1 = [n for n in nw.nodes() if n.uid == 'Edfa1'][0]
edfa1.gain_target = 20.0
edfa1.tilt_target = -0.7
edfa1.calc_nf()

results = []
for Pin in pch2d:
    chgain = edfa1.gain_profile(Pin)
    pase = edfa1.noise_profile(chgain, fc, df)
    pout = lin2db(db2lin(Pin + chgain) + db2lin(pase))
    results.append(pout)

# Generate legend text

pch2d_legend = []
for ea in pch2d_legend_data:
    s = ''.join([chr(xx) for xx in ea.astype(dtype=int)]).strip()
    pch2d_legend.append(s)

# Plot
axis_font = {'fontname': 'Arial', 'size': '16', 'fontweight': 'bold'}
title_font = {'fontname': 'Arial', 'size': '17', 'fontweight': 'bold'}
tic_font = {'fontname': 'Arial', 'size': '12'}
plt.rcParams["font.family"] = "Arial"
plt.figure()
Exemplo n.º 18
0
    def _gain_profile(self, pin):
        """
        Pin : input power / channel in W

        :param gain_ripple: design flat gain
        :param dgt: design gain tilt
        :param Pin: total input power in W
        :param gp: Average gain setpoint in dB units
        :param gtp: gain tilt setting
        :type gain_ripple: numpy.ndarray
        :type dgt: numpy.ndarray
        :type Pin: numpy.ndarray
        :type gp: float
        :type gtp: float
        :return: gain profile in dBm
        :rtype: numpy.ndarray

        AMPLIFICATION USING INPUT PROFILE
        INPUTS:
            gain_ripple - vector of length number of channels or spectral slices
            DGT - vector of length number of channels or spectral slices
            Pin - input powers vector of length number of channels or
            spectral slices
            Gp  - provisioned gain length 1
            GTp - provisioned tilt length 1

        OUTPUT:
            amp gain per channel or spectral slice
        NOTE: there is no checking done for violations of the total output
            power capability of the amp.
        EDIT OF PREVIOUS NOTE: power violation now added in interpol_params
            Ported from Matlab version written by David Boerges at Ciena.
        Based on:
            R. di Muro, "The Er3+ fiber gain coefficient derived from a dynamic
            gain
            tilt technique", Journal of Lightwave Technology, Vol. 18, Iss. 3,
            Pp. 343-347, 2000.
        """
        err_tolerance = 1.0e-11
        simple_opt = True

        # TODO check what param should be used (currently length(dgt))
        nchan = np.arange(len(self.interpol_dgt))

        # TODO find a way to use these or lose them.  Primarily we should have
        # a way to determine if exceeding the gain or output power of the amp
        tot_in_power_db = lin2db(np.sum(pin * 1e3))  # ! Pin expressed in W

        # Linear fit to get the
        p = np.polyfit(nchan, self.interpol_dgt, 1)
        dgt_slope = p[0]

        # Calculate the target slope-  Currently assumes equal spaced channels
        # TODO make it so that supports arbitrary channel spacing.
        targ_slope = self.operational.tilt_target / (len(nchan) - 1)

        # 1st estimate of DGT scaling
        if abs(dgt_slope) > 0.001:  # add check for div 0 due to flat dgt
            dgts1 = targ_slope / dgt_slope
        else:
            dgts1 = 0
        # when simple_opt is true code makes 2 attempts to compute gain and
        # the internal voa value.  This is currently here to provide direct
        # comparison with original Matlab code.  Will be removed.
        # TODO replace with loop

        if simple_opt:

            # 1st estimate of Er gain & voa loss
            g1st = np.array(self.interpol_gain_ripple) + self.params.gain_flatmax + \
                np.array(self.interpol_dgt) * dgts1
            voa = lin2db(np.mean(db2lin(g1st))) - self.operational.gain_target

            # 2nd estimate of Amp ch gain using the channel input profile
            g2nd = g1st - voa

            pout_db = lin2db(np.sum(pin * 1e3 * db2lin(g2nd)))
            dgts2 = self.operational.gain_target - (pout_db - tot_in_power_db)

            # Center estimate of amp ch gain
            xcent = dgts2
            gcent = g1st - voa + np.array(self.interpol_dgt) * xcent
            pout_db = lin2db(np.sum(pin * 1e3 * db2lin(gcent)))
            gavg_cent = pout_db - tot_in_power_db

            # Lower estimate of Amp ch gain
            deltax = np.max(g1st) - np.min(g1st)
            # ! if no ripple deltax = 0 => xlow = xcent: div 0
            # add check for flat gain response :
            if abs(
                    deltax
            ) > 0.05:  #enough ripple to consider calculation and avoid div 0
                xlow = dgts2 - deltax
                glow = g1st - voa + np.array(self.interpol_dgt) * xlow
                pout_db = lin2db(np.sum(pin * 1e3 * db2lin(glow)))
                gavg_low = pout_db - tot_in_power_db

                # Upper gain estimate
                xhigh = dgts2 + deltax
                ghigh = g1st - voa + np.array(self.interpol_dgt) * xhigh
                pout_db = lin2db(np.sum(pin * 1e3 * db2lin(ghigh)))
                gavg_high = pout_db - tot_in_power_db

                # compute slope
                slope1 = (gavg_low - gavg_cent) / (xlow - xcent)
                slope2 = (gavg_cent - gavg_high) / (xcent - xhigh)

                if np.abs(self.operational.gain_target -
                          gavg_cent) <= err_tolerance:
                    dgts3 = xcent
                elif self.operational.gain_target < gavg_cent:
                    dgts3 = xcent - (gavg_cent -
                                     self.operational.gain_target) / slope1
                else:
                    dgts3 = xcent + (-gavg_cent +
                                     self.operational.gain_target) / slope2

                gprofile = g1st - voa + np.array(self.interpol_dgt) * dgts3
            else:  #not enough ripple
                gprofile = g1st - voa
        else:  #simple_opt
            gprofile = None

        return gprofile
Exemplo n.º 19
0
def test_roadm_target_power(prev_node_type, effective_pch_out_db):
    ''' Check that egress power of roadm is equal to target power if input power is greater
    than target power else, that it is equal to input power. Use a simple two hops A-B-C topology
    for the test where the prev_node in ROADM B is either an amplifier or a fused, so that the target
    power can not be met in this last case.
    '''
    equipment = load_equipment(EQPT_LIBRARY_NAME)
    json_network = load_json(TEST_DIR / 'data/twohops_roadm_power_test.json')
    prev_node = next(n for n in json_network['elements']
                     if n['uid'] == 'west edfa in node B to ila2')
    json_network['elements'].remove(prev_node)
    if prev_node_type == 'edfa':
        prev_node = {'uid': 'west edfa in node B to ila2', 'type': 'Edfa'}
    elif prev_node_type == 'fused':
        prev_node = {'uid': 'west edfa in node B to ila2', 'type': 'Fused'}
        prev_node['params'] = {'loss': 0}
    json_network['elements'].append(prev_node)
    network = network_from_json(json_network, equipment)
    # Build the network once using the default power defined in SI in eqpt config
    p_db = equipment['SI']['default'].power_dbm
    p_total_db = p_db + lin2db(
        automatic_nch(equipment['SI']['default'].f_min,
                      equipment['SI']['default'].f_max,
                      equipment['SI']['default'].spacing))

    build_network(network, equipment, p_db, p_total_db)

    params = {}
    params['request_id'] = 0
    params['trx_type'] = ''
    params['trx_mode'] = ''
    params['source'] = 'trx node A'
    params['destination'] = 'trx node C'
    params['bidir'] = False
    params['nodes_list'] = ['trx node C']
    params['loose_list'] = ['strict']
    params['format'] = ''
    params['path_bandwidth'] = 100e9
    trx_params = trx_mode_params(equipment)
    params.update(trx_params)
    req = PathRequest(**params)
    path = compute_constrained_path(network, req)
    si = create_input_spectral_information(req.f_min, req.f_max, req.roll_off,
                                           req.baud_rate, req.power,
                                           req.spacing)
    for i, el in enumerate(path):
        if isinstance(el, Roadm):
            carriers_power_in_roadm = min([
                c.power.signal + c.power.nli + c.power.ase for c in si.carriers
            ])
            si = el(si, degree=path[i + 1].uid)
            if el.uid == 'roadm node B':
                print('input', carriers_power_in_roadm)
                assert el.effective_pch_out_db == effective_pch_out_db
                for carrier in si.carriers:
                    print(carrier.power.signal + carrier.power.nli +
                          carrier.power.ase)
                    power = carrier.power.signal + carrier.power.nli + carrier.power.ase
                    if prev_node_type == 'edfa':
                        # edfa prev_node sets input power to roadm to a high enough value:
                        # Check that egress power of roadm is equal to target power
                        assert power == pytest.approx(
                            db2lin(effective_pch_out_db - 30), rel=1e-3)
                    elif prev_node_type == 'fused':
                        # fused prev_node does reamplfy power after fiber propagation, so input power
                        # to roadm is low.
                        # Check that egress power of roadm is equalized to the min carrier input power.
                        assert power == pytest.approx(carriers_power_in_roadm,
                                                      rel=1e-3)
        else:
            si = el(si)
Exemplo n.º 20
0
    def __init__(self, Request, eqpt_filename):
        # request_id is str
        # excel has automatic number formatting that adds .0 on integer values
        # the next lines recover the pure int value, assuming this .0 is unwanted
        self.request_id = correct_xlrd_int_to_str_reading(Request.request_id)
        self.source = Request.source
        self.destination = Request.destination
        # TODO: the automatic naming generated by excel parser requires that source and dest name
        # be a string starting with 'trx' : this is manually added here.
        self.srctpid = f'trx {Request.source}'
        self.dsttpid = f'trx {Request.destination}'
        # test that trx_type belongs to eqpt_config.json
        # if not replace it with a default
        equipment = load_equipment(eqpt_filename)
        try:
            if equipment['Transceiver'][Request.trx_type]:
                self.trx_type = correct_xlrd_int_to_str_reading(
                    Request.trx_type)
            if Request.mode is not None:
                Requestmode = correct_xlrd_int_to_str_reading(Request.mode)
                if [
                        mode for mode in equipment['Transceiver']
                    [Request.trx_type].mode if mode['format'] == Requestmode
                ]:
                    self.mode = Requestmode
                else:
                    msg = f'Request Id: {self.request_id} - could not find tsp : \'{Request.trx_type}\' with mode: \'{Requestmode}\' in eqpt library \nComputation stopped.'
                    #print(msg)
                    logger.critical(msg)
                    exit(1)
            else:
                Requestmode = None
                self.mode = Request.mode
        except KeyError:
            msg = f'Request Id: {self.request_id} - could not find tsp : \'{Request.trx_type}\' with mode: \'{Requestmode}\' in eqpt library \nComputation stopped.'
            #print(msg)
            logger.critical(msg)
            exit()
        # excel input are in GHz and dBm
        if Request.spacing is not None:
            self.spacing = Request.spacing * 1e9
        else:
            msg = f'Request {self.request_id} missing spacing: spacing is mandatory.\ncomputation stopped'
            logger.critical(msg)
            exit()
        if Request.power is not None:
            self.power = db2lin(Request.power) * 1e-3
        else:
            self.power = None
        if Request.nb_channel is not None:
            self.nb_channel = int(Request.nb_channel)
        else:
            self.nb_channel = None

        value = correct_xlrd_int_to_str_reading(Request.disjoint_from)
        self.disjoint_from = [n for n in value.split(' | ') if value]
        self.nodes_list = []
        if Request.nodes_list:
            self.nodes_list = Request.nodes_list.split(' | ')

        # cleaning the list of nodes to remove source and destination
        # (because the remaining of the program assumes that the nodes list are nodes
        # on the path and should not include source and destination)
        try:
            self.nodes_list.remove(self.source)
            msg = f'{self.source} removed from explicit path node-list'
            logger.info(msg)
        except ValueError:
            msg = f'{self.source} already removed from explicit path node-list'
            logger.info(msg)

        try:
            self.nodes_list.remove(self.destination)
            msg = f'{self.destination} removed from explicit path node-list'
            logger.info(msg)
        except ValueError:
            msg = f'{self.destination} already removed from explicit path node-list'
            logger.info(msg)

        # the excel parser applies the same hop-type to all nodes in the route nodes_list.
        # user can change this per node in the generated json
        self.loose = 'loose'
        if Request.is_loose == 'no':
            self.loose = 'strict'
        self.path_bandwidth = None
        if Request.path_bandwidth is not None:
            self.path_bandwidth = Request.path_bandwidth * 1e9
        else:
            self.path_bandwidth = 0
Exemplo n.º 21
0
def main(network, equipment, source, destination, req=None):
    result_dicts = {}
    network_data = [{
        'network_name': str(args.filename),
        'source': source.uid,
        'destination': destination.uid
    }]
    result_dicts.update({'network': network_data})
    design_data = [{
        'power_mode': equipment['Spans']['default'].power_mode,
        'span_power_range': equipment['Spans']['default'].delta_power_range_db,
        'design_pch': equipment['SI']['default'].power_dbm,
        'baud_rate': equipment['SI']['default'].baud_rate
    }]
    result_dicts.update({'design': design_data})
    simulation_data = []
    result_dicts.update({'simulation results': simulation_data})

    power_mode = equipment['Spans']['default'].power_mode
    print('\n'.join([
        f'Power mode is set to {power_mode}',
        f'=> it can be modified in eqpt_config.json - Spans'
    ]))

    pref_ch_db = lin2db(req.power *
                        1e3)  #reference channel power / span (SL=20dB)
    pref_total_db = pref_ch_db + lin2db(
        req.nb_channel)  #reference total power / span (SL=20dB)
    build_network(network, equipment, pref_ch_db, pref_total_db)
    path = compute_constrained_path(network, req)

    spans = [s.length for s in path if isinstance(s, Fiber)]
    print(
        f'\nThere are {len(spans)} fiber spans over {sum(spans):.0f}m between {source.uid} and {destination.uid}'
    )
    print(f'\nNow propagating between {source.uid} and {destination.uid}:')

    try:
        power_range = list(arange(*equipment['SI']['default'].power_range_db))
        last = equipment['SI']['default'].power_range_db[-2]
        if len(power_range) == 0:  #bad input that will lead to no simulation
            power_range = [0]  #better than an error message
        else:
            power_range.append(last)
    except TypeError:
        print(
            'invalid power range definition in eqpt_config, should be power_range_db: [lower, upper, step]'
        )
        power_range = [0]

    for dp_db in power_range:
        req.power = db2lin(pref_ch_db + dp_db) * 1e-3
        print(
            f'\nPropagating with input power = {lin2db(req.power*1e3):.2f}dBm :'
        )
        propagate(path, req, equipment, show=len(power_range) == 1)
        print(
            f'\nTransmission result for input power = {lin2db(req.power*1e3):.2f}dBm :'
        )
        print(destination)
        simulation_data.append({
            'Pch_dBm':
            pref_ch_db + dp_db,
            'OSNR_ASE_0.1nm':
            round(mean(destination.osnr_ase_01nm), 2),
            'OSNR_ASE_signal_bw':
            round(mean(destination.osnr_ase), 2),
            'SNR_nli_signal_bw':
            round(mean(destination.osnr_nli), 2),
            'SNR_total_signal_bw':
            round(mean(destination.snr), 2)
        })
    write_csv(result_dicts, 'simulation_result.csv')
    return path
Exemplo n.º 22
0
def test_db2lin():
    assert pytest.approx(10.0) == db2lin(10.0)
Exemplo n.º 23
0
def main(network, equipment, source, destination, sim_params, req=None):
    result_dicts = {}
    network_data = [{
                    'network_name'  : str(args.filename),
                    'source'        : source.uid,
                    'destination'   : destination.uid
                    }]
    result_dicts.update({'network': network_data})
    design_data = [{
                    'power_mode'        : equipment['Span']['default'].power_mode,
                    'span_power_range'  : equipment['Span']['default'].delta_power_range_db,
                    'design_pch'        : equipment['SI']['default'].power_dbm,
                    'baud_rate'         : equipment['SI']['default'].baud_rate
                    }]
    result_dicts.update({'design': design_data})
    simulation_data = []
    result_dicts.update({'simulation results': simulation_data})

    power_mode = equipment['Span']['default'].power_mode
    print('\n'.join([f'Power mode is set to {power_mode}',
                     f'=> it can be modified in eqpt_config.json - Span']))

    pref_ch_db = lin2db(req.power*1e3) #reference channel power / span (SL=20dB)
    pref_total_db = pref_ch_db + lin2db(req.nb_channel) #reference total power / span (SL=20dB)
    build_network(network, equipment, pref_ch_db, pref_total_db)
    path = compute_constrained_path(network, req)

    if len([s.length for s in path if isinstance(s, RamanFiber)]):
        if sim_params is None:
            print(f'{ansi_escapes.red}Invocation error:{ansi_escapes.reset} RamanFiber requires passing simulation params via --sim-params')
            exit(1)
        configure_network(network, sim_params)

    spans = [s.length for s in path if isinstance(s, RamanFiber) or isinstance(s, Fiber)]
    print(f'\nThere are {len(spans)} fiber spans over {sum(spans)/1000:.0f} km between {source.uid} and {destination.uid}')
    print(f'\nNow propagating between {source.uid} and {destination.uid}:')

    try:
        p_start, p_stop, p_step = equipment['SI']['default'].power_range_db
        p_num = abs(int(round((p_stop - p_start)/p_step))) + 1 if p_step != 0 else 1
        power_range = list(linspace(p_start, p_stop, p_num))
    except TypeError:
        print('invalid power range definition in eqpt_config, should be power_range_db: [lower, upper, step]')
        power_range = [0]

    if not power_mode:
        #power cannot be changed in gain mode
        power_range = [0]
    for dp_db in power_range:
        req.power = db2lin(pref_ch_db + dp_db)*1e-3
        if power_mode:
            print(f'\nPropagating with input power = {ansi_escapes.cyan}{lin2db(req.power*1e3):.2f} dBm{ansi_escapes.reset}:')
        else:
            print(f'\nPropagating in {ansi_escapes.cyan}gain mode{ansi_escapes.reset}: power cannot be set manually')
        infos = propagate2(path, req, equipment)
        if len(power_range) == 1:
            for elem in path:
                print(elem)
        if power_mode:
            print(f'\nTransmission result for input power = {lin2db(req.power*1e3):.2f} dBm:')
        else:
            print(f'\nTransmission results:')
        print(f'  Final SNR total (signal bw): {ansi_escapes.cyan}{mean(destination.snr):.02f} dB{ansi_escapes.reset}')

        #print(f'\n !!!!!!!!!!!!!!!!!     TEST POINT         !!!!!!!!!!!!!!!!!!!!!')
        #print(f'carriers ase output of {path[1]} =\n {list(path[1].carriers("out", "nli"))}')
        # => use "in" or "out" parameter
        # => use "nli" or "ase" or "signal" or "total" parameter        
        if power_mode:
            simulation_data.append({
                        'Pch_dBm'               : pref_ch_db + dp_db,
                        'OSNR_ASE_0.1nm'        : round(mean(destination.osnr_ase_01nm),2),
                        'OSNR_ASE_signal_bw'    : round(mean(destination.osnr_ase),2),
                        'SNR_nli_signal_bw'     : round(mean(destination.osnr_nli),2),
                        'SNR_total_signal_bw'   : round(mean(destination.snr),2)
                                })
        else:
            simulation_data.append({
                        'gain_mode'             : 'power canot be set',
                        'OSNR_ASE_0.1nm'        : round(mean(destination.osnr_ase_01nm),2),
                        'OSNR_ASE_signal_bw'    : round(mean(destination.osnr_ase),2),
                        'SNR_nli_signal_bw'     : round(mean(destination.osnr_nli),2),
                        'SNR_total_signal_bw'   : round(mean(destination.snr),2)
                                })
    write_csv(result_dicts, 'simulation_result.csv')
    return path, infos
Exemplo n.º 24
0
    logger.info(f'source = {args.source!r}')
    logger.info(f'destination = {args.destination!r}')

    params = {}
    params['request_id'] = 0
    params['trx_type'] = ''
    params['trx_mode'] = ''
    params['source'] = source.uid
    params['destination'] = destination.uid
    params['nodes_list'] = [destination.uid]
    params['loose_list'] = ['strict']
    params['format'] = ''
    params['path_bandwidth'] = 0
    trx_params = trx_mode_params(equipment)
    if args.power:
        trx_params['power'] = db2lin(float(args.power))*1e-3
    params.update(trx_params)
    req = Path_request(**params)
    path, infos = main(network, equipment, source, destination, sim_params, req)
    save_network(args.filename, network)

    if args.show_channels:
        print('\nThe total SNR per channel at the end of the line is:')
        print('{:>5}{:>26}{:>26}{:>28}{:>28}{:>28}' \
            .format('Ch. #', 'Channel frequency (THz)', 'Channel power (dBm)', 'OSNR ASE (signal bw, dB)', 'SNR NLI (signal bw, dB)', 'SNR total (signal bw, dB)'))
        for final_carrier, ch_osnr, ch_snr_nl, ch_snr in zip(infos[path[-1]][1].carriers, path[-1].osnr_ase, path[-1].osnr_nli, path[-1].snr):
            ch_freq = final_carrier.frequency * 1e-12
            ch_power = lin2db(final_carrier.power.signal*1e3)
            print('{:5}{:26.2f}{:26.2f}{:28.2f}{:28.2f}{:28.2f}' \
                .format(final_carrier.channel_number, round(ch_freq, 2), round(ch_power, 2), round(ch_osnr, 2), round(ch_snr_nl, 2), round(ch_snr, 2)))
Exemplo n.º 25
0
    def __init__(self, Request, equipment, bidir):
        # request_id is str
        # excel has automatic number formatting that adds .0 on integer values
        # the next lines recover the pure int value, assuming this .0 is unwanted
        self.request_id = correct_xlrd_int_to_str_reading(Request.request_id)
        self.source = f'trx {Request.source}'
        self.destination = f'trx {Request.destination}'
        # TODO: the automatic naming generated by excel parser requires that source and dest name
        # be a string starting with 'trx' : this is manually added here.
        self.srctpid = f'trx {Request.source}'
        self.dsttpid = f'trx {Request.destination}'
        self.bidir = bidir
        # test that trx_type belongs to eqpt_config.json
        # if not replace it with a default
        try:
            if equipment['Transceiver'][Request.trx_type]:
                self.trx_type = correct_xlrd_int_to_str_reading(
                    Request.trx_type)
            if Request.mode is not None:
                Requestmode = correct_xlrd_int_to_str_reading(Request.mode)
                if [
                        mode for mode in equipment['Transceiver']
                    [Request.trx_type].mode if mode['format'] == Requestmode
                ]:
                    self.mode = Requestmode
                else:
                    msg = f'Request Id: {self.request_id} - could not find tsp : \'{Request.trx_type}\' with mode: \'{Requestmode}\' in eqpt library \nComputation stopped.'
                    # print(msg)
                    logger.critical(msg)
                    raise ServiceError(msg)
            else:
                Requestmode = None
                self.mode = Request.mode
        except KeyError:
            msg = f'Request Id: {self.request_id} - could not find tsp : \'{Request.trx_type}\' with mode: \'{Request.mode}\' in eqpt library \nComputation stopped.'
            # print(msg)
            logger.critical(msg)
            raise ServiceError(msg)
        # excel input are in GHz and dBm
        if Request.spacing is not None:
            self.spacing = Request.spacing * 1e9
        else:
            msg = f'Request {self.request_id} missing spacing: spacing is mandatory.\ncomputation stopped'
            logger.critical(msg)
            raise ServiceError(msg)
        if Request.power is not None:
            self.power = db2lin(Request.power) * 1e-3
        else:
            self.power = None
        if Request.nb_channel is not None:
            self.nb_channel = int(Request.nb_channel)
        else:
            self.nb_channel = None

        value = correct_xlrd_int_to_str_reading(Request.disjoint_from)
        self.disjoint_from = [n for n in value.split(' | ') if value]
        self.nodes_list = []
        if Request.nodes_list:
            self.nodes_list = Request.nodes_list.split(' | ')
        self.loose = 'LOOSE'
        if Request.is_loose.lower() == 'no':
            self.loose = 'STRICT'
        self.path_bandwidth = None
        if Request.path_bandwidth is not None:
            self.path_bandwidth = Request.path_bandwidth * 1e9
        else:
            self.path_bandwidth = 0
Exemplo n.º 26
0
 def lin_attenuation(self):
     return db2lin(self.length * self.loss_coef)
Exemplo n.º 27
0
 def lin_attenuation(self):
     attenuation = self.length * self.loss_coef
     return db2lin(attenuation)
Exemplo n.º 28
0
def nf_model(amp_dict):
    gain_min = amp_dict[GAIN_MIN_FIELD]
    gain_max = amp_dict[GAIN_MAX_FIELD]
    nf_min = amp_dict.get(NF_MIN_FIELD,-100)
    nf_max = amp_dict.get(NF_MAX_FIELD,-100)
    if nf_min<-10 or nf_max<-10:
        raise ValueError(f'invalid or missing nf_min or nf_max values in eqpt_config.json for {amp_dict["type_variety"]}')
    nf_min = amp_dict.get(NF_MIN_FIELD,-100)
    nf_max = amp_dict.get(NF_MAX_FIELD,-100)
    #use NF estimation model based on NFmin and NFmax in json OA file
    delta_p = 5 #max power dB difference between 1st and 2nd stage coils
    #dB g1a = (1st stage gain) - (internal voa attenuation)
    g1a_min = gain_min - (gain_max-gain_min) - delta_p
    g1a_max = gain_max - delta_p
    #nf1 and nf2 are the nf of the 1st and 2nd stage coils
    #calculate nf1 and nf2 values that solve nf_[min/max] = nf1 + nf2 / g1a[min/max]
    nf2 = lin2db((db2lin(nf_min) - db2lin(nf_max)) / (1/db2lin(g1a_max)-1/db2lin(g1a_min)))
    nf1 = lin2db(db2lin(nf_min)- db2lin(nf2)/db2lin(g1a_max)) #expression (1)

    #now checking and recalculating the results:
    #recalculate delta_p to check it is within [1-6] boundaries
    #This is to check that the nf_min and nf_max values from the json file
    #make sense. If not a warning is printed 
    if nf1 < 4:
        print('1st coil nf calculated value {} is too low: revise inputs'.format(nf1))
    if nf2 < nf1 + 0.3 or nf2 > nf1 + 2: 
        #nf2 should be with [nf1+0.5 - nf1 +2] boundaries
        #there shouldn't be very high nf differences between 2 coils
        #=> recalculate delta_p            
        nf2 = max(nf2, nf1+0.3)
        nf2 = min(nf2, nf1+2)
        g1a_max = lin2db(db2lin(nf2) / (db2lin(nf_min) - db2lin(nf1))) #use expression (1)
        delta_p = gain_max - g1a_max
        g1a_min = gain_min - (gain_max-gain_min) - delta_p
        if delta_p < 1 or delta_p > 6:
            #delta_p should be > 1dB and < 6dB => consider user warning if not
            print('!WARNING!: 1st coil vs 2nd coil calculated DeltaP {} is not valid: revise inputs'
                        .format(delta_p))
    #check the calculated values for nf1 & nf2:
    nf_min_calc = lin2db(db2lin(nf1) + db2lin(nf2)/db2lin(g1a_max))
    nf_max_calc = lin2db(db2lin(nf1) + db2lin(nf2)/db2lin(g1a_min))
    if (abs(nf_min_calc-nf_min) > 0.01) or (abs(nf_max_calc-nf_max) > 0.01):
        raise ValueError(f'nf model calculation failed with nf_min {nf_min_calc} and nf_max {nf_max_calc} calculated')
    return nf1, nf2, delta_p
Exemplo n.º 29
0
def trx_mode_params(equipment,
                    trx_type_variety='',
                    trx_mode='',
                    error_message=False):
    """return the trx and SI parameters from eqpt_config for a given type_variety and mode (ie format)"""
    trx_params = {}
    default_si_data = equipment['SI']['default']

    try:
        trxs = equipment['Transceiver']
        #if called from path_requests_run.py, trx_mode is filled with None when not specified by user
        #if called from transmission_main.py, trx_mode is ''
        if trx_mode is not None:
            mode_params = next(mode for trx in trxs \
                        if trx == trx_type_variety \
                        for mode in trxs[trx].mode \
                        if mode['format'] == trx_mode)
            trx_params = {**mode_params}
            # sanity check: spacing baudrate must be smaller than min spacing
            if trx_params['baud_rate'] > trx_params['min_spacing']:
                msg = f'Inconsistency in equipment library:\n Transpoder "{trx_type_variety}" mode "{trx_params["format"]}" '+\
                    f'has baud rate: {trx_params["baud_rate"]*1e-9} GHz greater than min_spacing {trx_params["min_spacing"]*1e-9}.'
                print(msg)
                exit()
        else:
            mode_params = {
                "format": "undetermined",
                "baud_rate": None,
                "OSNR": None,
                "bit_rate": None,
                "roll_off": None,
                "tx_osnr": None,
                "min_spacing": None,
                "cost": None
            }
            trx_params = {**mode_params}
        trx_params['f_min'] = equipment['Transceiver'][
            trx_type_variety].frequency['min']
        trx_params['f_max'] = equipment['Transceiver'][
            trx_type_variety].frequency['max']

        # TODO: novel automatic feature maybe unwanted if spacing is specified
        # trx_params['spacing'] = automatic_spacing(trx_params['baud_rate'])
        # temp = trx_params['spacing']
        # print(f'spacing {temp}')
    except StopIteration:
        if error_message:
            print(
                f'could not find tsp : {trx_type_variety} with mode: {trx_mode} in eqpt library'
            )
            print('Computation stopped.')
            exit()
        else:
            # default transponder charcteristics
            # mainly used with transmission_main_example.py
            trx_params['f_min'] = default_si_data.f_min
            trx_params['f_max'] = default_si_data.f_max
            trx_params['baud_rate'] = default_si_data.baud_rate
            trx_params['spacing'] = default_si_data.spacing
            trx_params['OSNR'] = None
            trx_params['bit_rate'] = None
            trx_params['cost'] = None
            trx_params['roll_off'] = default_si_data.roll_off
            trx_params['tx_osnr'] = default_si_data.tx_osnr
            trx_params['min_spacing'] = None
            nch = automatic_nch(trx_params['f_min'], trx_params['f_max'],
                                trx_params['spacing'])
            trx_params['nb_channel'] = nch
            print(f'There are {nch} channels propagating')

    trx_params['power'] = db2lin(default_si_data.power_dbm) * 1e-3

    return trx_params
Exemplo n.º 30
0
def test_roadm_target_power(prev_node_type, effective_pch_out_db, power_dbm):
    ''' Check that egress power of roadm is equal to target power if input power is greater
    than target power else, that it is equal to input power. Use a simple two hops A-B-C topology
    for the test where the prev_node in ROADM B is either an amplifier or a fused, so that the target
    power can not be met in this last case.
    '''
    equipment = load_equipment(EQPT_LIBRARY_NAME)
    json_network = load_json(TEST_DIR / 'data/twohops_roadm_power_test.json')
    prev_node = next(n for n in json_network['elements']
                     if n['uid'] == 'west edfa in node B to ila2')
    json_network['elements'].remove(prev_node)
    if prev_node_type == 'edfa':
        prev_node = {'uid': 'west edfa in node B to ila2', 'type': 'Edfa'}
    elif prev_node_type == 'fused':
        prev_node = {'uid': 'west edfa in node B to ila2', 'type': 'Fused'}
        prev_node['params'] = {'loss': 0}
    json_network['elements'].append(prev_node)
    network = network_from_json(json_network, equipment)
    p_total_db = power_dbm + lin2db(
        automatic_nch(equipment['SI']['default'].f_min,
                      equipment['SI']['default'].f_max,
                      equipment['SI']['default'].spacing))

    build_network(network, equipment, power_dbm, p_total_db)

    params = {
        'request_id': 0,
        'trx_type': '',
        'trx_mode': '',
        'source': 'trx node A',
        'destination': 'trx node C',
        'bidir': False,
        'nodes_list': ['trx node C'],
        'loose_list': ['strict'],
        'format': '',
        'path_bandwidth': 100e9,
        'effective_freq_slot': None,
    }
    trx_params = trx_mode_params(equipment)
    params.update(trx_params)
    req = PathRequest(**params)
    req.power = db2lin(power_dbm - 30)
    path = compute_constrained_path(network, req)
    si = create_input_spectral_information(req.f_min, req.f_max, req.roll_off,
                                           req.baud_rate, req.power,
                                           req.spacing)
    for i, el in enumerate(path):
        if isinstance(el, Roadm):
            carriers_power_in_roadm = min([
                c.power.signal + c.power.nli + c.power.ase for c in si.carriers
            ])
            si = el(si, degree=path[i + 1].uid)
            if el.uid == 'roadm node B':
                print('input', carriers_power_in_roadm)
                # if previous was an EDFA, power level at ROADM input is enough for the ROADM to apply its
                # target power (as specified in equipment ie -20 dBm)
                # if it is a Fused, the input power to the ROADM is smaller than the target power, and the
                # ROADM cannot apply this target. In this case, it is assumed that the ROADM has 0 dB loss
                # so the output power will be the same as the input power, which for this particular case
                # corresponds to -22dBm + power_dbm
                # next step (for ROADM modelling) will be to apply a minimum loss for ROADMs !
                if prev_node_type == 'edfa':
                    assert el.effective_pch_out_db == effective_pch_out_db
                if prev_node_type == 'fused':
                    # then output power == input_power == effective_pch_out_db + power_dbm
                    assert effective_pch_out_db + power_dbm == \
                        pytest.approx(lin2db(carriers_power_in_roadm * 1e3), rel=1e-3)
                    assert el.effective_pch_out_db == effective_pch_out_db + power_dbm
                for carrier in si.carriers:
                    print(carrier.power.signal + carrier.power.nli +
                          carrier.power.ase)
                    power = carrier.power.signal + carrier.power.nli + carrier.power.ase
                    if prev_node_type == 'edfa':
                        # edfa prev_node sets input power to roadm to a high enough value:
                        # Check that egress power of roadm is equal to target power
                        assert power == pytest.approx(
                            db2lin(effective_pch_out_db - 30), rel=1e-3)
                    elif prev_node_type == 'fused':
                        # fused prev_node does reamplfy power after fiber propagation, so input power
                        # to roadm is low.
                        # Check that egress power of roadm is equalized to the min carrier input power.
                        assert power == pytest.approx(carriers_power_in_roadm,
                                                      rel=1e-3)
        else:
            si = el(si)