001/*******************************************************************************
002 * Copyright (c) 2017 Pablo Pavon Marino and others.
003 * All rights reserved. This program and the accompanying materials
004 * are made available under the terms of the 2-clause BSD License 
005 * which accompanies this distribution, and is available at
006 * https://opensource.org/licenses/BSD-2-Clause
007 *
008 * Contributors:
009 *     Pablo Pavon Marino and others - initial API and implementation
010 *******************************************************************************/
011
012
013
014
015 
016
017
018
019
020
021package com.net2plan.examples.general.reports;
022
023import cern.colt.matrix.tdouble.DoubleMatrix1D;
024import com.net2plan.interfaces.networkDesign.IReport;
025import com.net2plan.interfaces.networkDesign.Link;
026import com.net2plan.interfaces.networkDesign.Net2PlanException;
027import com.net2plan.interfaces.networkDesign.NetPlan;
028import com.net2plan.utils.Constants.OrderingType;
029import com.net2plan.utils.DoubleUtils;
030import com.net2plan.utils.StringUtils;
031import com.net2plan.utils.Triple;
032
033import java.text.DecimalFormat;
034import java.util.*;
035
036/**
037 * <p>This report shows line engineering information for WDM links in the network.</p>
038 * 
039 * <p>The report assumes that the network links are WDM optical fibres with the following scheme:</p>
040 * <ul>
041 * <li>A transmitter per WDM channel with the specifications given by "tp__XXX". The number of 
042 * channels can vary from one to up to channels_maxNumChannels and, the design should be correct 
043 * in all the cases. Transmitter specifications are set in "tp__XXX" input parameters</li>
044 * <li>A multiplexer that receives the input power from the transmitters and with specifications 
045 * given by "mux__XXX" parameters</li>
046 * <li>A fiber link of a distance given by the link length, and with specifications given by 
047 * "fiber__XXX" parameters. The fiber can be split into spans if optical amplifers (EDFAs) 
048 * and/or dispersion compensating modules (DCMs) are placed along the fiber.</li>
049 * <li>A set of optical amplifiers (EDFAs) located in none, one or more positions in the 
050 * fiber link, separating them in different spans. EDFAs are supposed to operate in the 
051 * automatic gain control mode. Thus, the gain is the same, whatever the number of input 
052 * WDM channels. EDFA positions (as distance in km from the link start to the EDFA location) 
053 * and EDFA gains (assumed in dB) are read from the "edfaPositions_km" and "edfaGains_dB" 
054 * attributes of the links. The format of both attributes are the same: a string of numbers 
055 * separated by spaces. The <i>i</i>-th number corresponding to the position/gain of the 
056 * <i>i</i>-th EDFA. If the attributes do not exist, it is assumed that no EDFAs are placed 
057 * in this link. EDFA specifications given by "edfa__XXX" parameters</li>
058 * <li>A set of dispersion compensating modules (DCMs) located in none, one or more positions 
059 * in the fiber link, separating them in different spans. If a DCM and a EDFA have the same 
060 * location, it is assumed that the DCM is placed first, to reduce the non-linear effects. DCM 
061 * positions (as distance in km from the link start to the DCM location) are read from the 
062 * "dcmPositions_km" attribute of the link, and the same format as with "edfaPositions_km" 
063 * attribute is expected. If the attribute does not exist, it is assumed that no DCMs are 
064 * placed in this link. DCM specifications are given by "dcm__XXX" parameters</li>
065 * <li>At the receiver end, WDM channels in the links are separated using a demultiplexer, 
066 * with specifications given by "mux__XXX" parameters</li>
067 * <li>Each channel ends in a receiver, with specifications given by "tp__XXX" parameters</li>
068 * </ul>
069 * 
070 * <p>The basic checks performed are:</p>
071 * <ul>
072 * <li>Signal power levels are within operating ranges at the mux/demux/edfas/dcms and 
073 * receivers, both when the link has one single active channel, or when all the 
074 * "channels__maxNumChannels" are active</li>
075 * <li>Chromatic dispersion is within the operating ranges in every point of the fiber, 
076 * and at the receiver</li>
077 * <li>Optical Signal to Noise Ration (OSNR) is within the operating range at the receiver</li>
078 * <li>Polarization mode dispersion (PMD) is within the operating range at the receiver</li>
079 * </ul>
080 * @net2plan.keywords WDM
081 * @author Pablo Pavon-Marino, Jose-Luis Izquierdo-Zaragoza
082 * @version 1.1, May 2015
083 */
084public class Report_WDM_pointToPointLineEngineering implements IReport
085{
086        private final static double constant_c = 299792458; /* speed of light in m/s */
087        private final static double constant_h = 6.626E-34; /* Plank constant m^2 kg/sec */
088        private final static double precisionMargenForChecks_dB = 0.00001;
089        
090        private List<Link> links;
091        private DoubleMatrix1D d_e;
092
093        @Override
094        public String executeReport(NetPlan netPlan, Map<String, String> reportParameters, Map<String, String> net2planParameters)
095        {
096                links = netPlan.getLinks();
097                d_e = netPlan.getVectorLinkLengthInKm();
098
099                final boolean report__checkCDOnlyAtTheReceiver = Boolean.parseBoolean(reportParameters.get("report__checkCDOnlyAtTheReceiver"));
100
101                /* Usable wavelengths */
102                final double channels__minChannelLambda_nm = Double.parseDouble(reportParameters.get("channels__minChannelLambda_nm"));
103                final double channels__channelSpacing_GHz = Double.parseDouble(reportParameters.get("channels__channelSpacing_GHz"));
104                final int channels__maxNumChannels = Integer.parseInt(reportParameters.get("channels__maxNumChannels"));
105                final double channels__maxChannelLambda_nm = constant_c / ((constant_c / channels__minChannelLambda_nm) - (channels__maxNumChannels - 1) * channels__channelSpacing_GHz);
106
107                /* Fiber specifications  */
108                final double fiber__attenuation_dB_per_km = Double.parseDouble(reportParameters.get("fiber__attenuation_dB_per_km"));
109                final double fiber__worseChromaticDispersion_ps_per_nm_per_km = Double.parseDouble(reportParameters.get("fiber__worseChromaticDispersion_ps_per_nm_per_km"));
110                final double fiber__PMD_ps_per_sqroot_km = Double.parseDouble(reportParameters.get("fiber__PMD_ps_per_sqroot_km"));
111
112                /* Transponder specifications */
113                final double tp__outputPower_dBm = Double.parseDouble(reportParameters.get("tp__outputPower_dBm"));
114                final double tp__maxChromaticDispersionTolerance_ps_per_nm = Double.parseDouble(reportParameters.get("tp__maxChromaticDispersionTolerance_ps_per_nm"));
115                final double tp__minOSNR_dB = Double.parseDouble(reportParameters.get("tp__minOSNR_dB"));
116                final double tp__inputPowerSensitivityMin_dBm = Double.parseDouble(reportParameters.get("tp__inputPowerSensitivityMin_dBm"));
117                final double tp__inputPowerSensitivityMax_dBm = Double.parseDouble(reportParameters.get("tp__inputPowerSensitivityMax_dBm"));
118                final double tp__minWavelength_nm = Double.parseDouble(reportParameters.get("tp__minWavelength_nm"));
119                final double tp__maxWavelength_nm = Double.parseDouble(reportParameters.get("tp__maxWavelength_nm"));
120                final double tp__pmdTolerance_ps = Double.parseDouble(reportParameters.get("tp__pmdTolerance_ps"));
121
122                /* Optical amplifier specifications */
123                final double edfa__minWavelength_nm = Double.parseDouble(reportParameters.get("edfa__minWavelength_nm"));
124                final double edfa__maxWavelength_nm = Double.parseDouble(reportParameters.get("edfa__maxWavelength_nm"));
125                final double edfa__minInputPower_dBm = Double.parseDouble(reportParameters.get("edfa__minInputPower_dBm"));
126                final double edfa__maxInputPower_dBm = Double.parseDouble(reportParameters.get("edfa__maxInputPower_dBm"));
127                final double edfa__minOutputPower_dBm = Double.parseDouble(reportParameters.get("edfa__minOutputPower_dBm"));
128                final double edfa__maxOutputPower_dBm = Double.parseDouble(reportParameters.get("edfa__maxOutputPower_dBm"));
129                final double edfa__minGain_dB = Double.parseDouble(reportParameters.get("edfa__minGain_dB"));
130                final double edfa__maxGain_dB = Double.parseDouble(reportParameters.get("edfa__maxGain_dB"));
131                final double edfa__PMD_ps = Double.parseDouble(reportParameters.get("edfa__PMD_ps"));
132                final double edfa__noiseFactorMaximumGain_dB = Double.parseDouble(reportParameters.get("edfa__noiseFactorMaximumGain_dB"));
133                final double edfa__noiseFactorMinimumGain_dB = Double.parseDouble(reportParameters.get("edfa__noiseFactorMinimumGain_dB"));
134                final double edfa__noiseFactorReferenceBandwidth_nm = Double.parseDouble(reportParameters.get("edfa__noiseFactorReferenceBandwidth_nm"));
135
136                /* Dispersion compensation modules specifications */
137                final double dcm__worseCaseChannelDispersion_ps_per_nm = Double.parseDouble(reportParameters.get("dcm__worseCaseChannelDispersion_ps_per_nm"));
138                final double dcm__PMD_ps = Double.parseDouble(reportParameters.get("dcm__PMD_ps"));
139                final double dcm__insertionLoss_dB = Double.parseDouble(reportParameters.get("dcm__insertionLoss_dB"));
140
141                /* Mux/demux modules specifications */
142                final double mux__insertionLoss_dB = Double.parseDouble(reportParameters.get("mux__insertionLoss_dB"));
143                final double mux__PMD_ps = Double.parseDouble(reportParameters.get("mux__PMD_ps"));
144                final double mux__maxInputPower_dBm = Double.parseDouble(reportParameters.get("mux__maxInputPower_dBm"));
145
146                /* OSNR penalties */
147                final double osnrPenalty__CD_dB = Double.parseDouble(reportParameters.get("osnrPenalty__CD_dB"));
148                final double osnrPenalty__nonLinear_dB = Double.parseDouble(reportParameters.get("osnrPenalty__nonLinear_dB"));
149                final double osnrPenalty__PMD_dB = Double.parseDouble(reportParameters.get("osnrPenalty__PMD_dB"));
150                final double osnrPenalty__PDL_dB = Double.parseDouble(reportParameters.get("osnrPenalty__PDL_dB"));
151                final double osnrPenalty__transmitterChirp_dB = Double.parseDouble(reportParameters.get("osnrPenalty__transmitterChirp_dB"));
152                final double osnrPenalty__muxDemuxCrosstalk_dB = Double.parseDouble(reportParameters.get("osnrPenalty__muxDemuxCrosstalk_dB"));
153                final double osnrPenalty__unassignedMargin_dB = Double.parseDouble(reportParameters.get("osnrPenalty__unassignedMargin_dB"));
154                final double osnrPenalty__SUM_dB = osnrPenalty__CD_dB + osnrPenalty__nonLinear_dB + osnrPenalty__PMD_dB + osnrPenalty__PDL_dB + osnrPenalty__transmitterChirp_dB + osnrPenalty__muxDemuxCrosstalk_dB + osnrPenalty__unassignedMargin_dB;
155
156                Map<Long, List<Triple<Double, String, double[]>>> signalInfo = new LinkedHashMap<Long, List<Triple<Double, String, double[]>>>();
157                Map<Long, List<Triple<Double, String, String>>> warnings = new LinkedHashMap<Long, List<Triple<Double, String, String>>>();
158
159                for (Link link : links)
160                {
161                        final double d_e_thisLink = link.getLengthInKm();
162                        List<Triple<Double, String, double[]>> signalInfoThisLink = new ArrayList<Triple<Double, String, double[]>>();
163                        List<Triple<Double, String, String>> warningsThisLink = new ArrayList<Triple<Double, String, String>>();
164
165                        final String st_edfaPositions_km = (link.getAttribute("edfaPositions_km") == null) ? "" : link.getAttribute("edfaPositions_km");
166                        final String st_edfaGains_dB = (link.getAttribute("edfaGains_dB") == null) ? "" : link.getAttribute("edfaGains_dB");
167                        final String st_dcmPositions_km = (link.getAttribute("dcmPositions_km") == null) ? "" : link.getAttribute("dcmPositions_km");
168
169                        final double[] edfaPositions_km = StringUtils.toDoubleArray(StringUtils.split(st_edfaPositions_km));
170                        final double[] edfaGains_dB = StringUtils.toDoubleArray(StringUtils.split(st_edfaGains_dB));
171                        final double[] dcmPositions_km = StringUtils.toDoubleArray(StringUtils.split(st_dcmPositions_km));
172
173                        /* Put in order the elements in the WDM line */
174                        List<Triple<Double, String, Double>> elementPositions = getElementPositionsArray(link.getId (), d_e_thisLink, edfaPositions_km, edfaGains_dB, dcmPositions_km);
175                        int numDCMs = 0;
176                        int numEDFAs = 0;
177
178                        /* In the transmitter */
179                        final double numChannels_dB = linear2dB(channels__maxNumChannels);
180                        double current_powerPerChannel_dBm = tp__outputPower_dBm;
181                        double current_CD_ps_per_nm = 0;
182                        double current_PMDSquared_ps2 = 0;
183                        double current_OSNR_linear = Double.MAX_VALUE; /* no noise */
184                        double current_kmFromTransmitter = 0;
185
186                        /* Check the range of wavelengths of the transponder */
187                        if (channels__minChannelLambda_nm < tp__minWavelength_nm)
188                        {
189                                warningsThisLink.add(Triple.of(current_kmFromTransmitter, "Transmitter", "Wavelength " + channels__minChannelLambda_nm + " nm is outside the transponder range [" + tp__minWavelength_nm + " nm , " + tp__maxWavelength_nm + " nm]"));
190                        }
191                        if (channels__maxChannelLambda_nm > tp__maxWavelength_nm)
192                        {
193                                warningsThisLink.add(Triple.of(current_kmFromTransmitter, "Transmitter", "Wavelength " + channels__maxChannelLambda_nm + " nm is outside the transponder range [" + tp__minWavelength_nm + " nm , " + tp__maxWavelength_nm + " nm]"));
194                        }
195
196                        signalInfoThisLink.add(Triple.of(current_kmFromTransmitter, "After Transmitter", new double[]
197                        {
198                                current_CD_ps_per_nm, linear2dB(current_OSNR_linear), current_powerPerChannel_dBm, Math.sqrt(current_PMDSquared_ps2)
199                        }));
200
201                        /* At the output of the multiplexer */
202                        /* Check if the input power at the multiplexer does not exceed the limit */
203                        if (tp__outputPower_dBm + numChannels_dB > mux__maxInputPower_dBm + precisionMargenForChecks_dB)
204                        {
205                                warningsThisLink.add(Triple.of(current_kmFromTransmitter, "Mux", "Input power if all the WDM channels are active (" + (tp__outputPower_dBm + numChannels_dB) + " dBm) exceeds the maximum input power to the multiplexer (" + mux__maxInputPower_dBm + " dBm"));
206                        }
207                        current_powerPerChannel_dBm -= mux__insertionLoss_dB;
208                        current_PMDSquared_ps2 += Math.pow(mux__PMD_ps, 2);
209
210                        signalInfoThisLink.add(Triple.of(current_kmFromTransmitter, "After MUX", DoubleUtils.arrayOf(current_CD_ps_per_nm, linear2dB(current_OSNR_linear), current_powerPerChannel_dBm, Math.sqrt(current_PMDSquared_ps2))));
211
212                        /* Iterate along consecutive spans (ended by OA, DCM or DEMUX at the receiver) */
213                        for (Triple<Double, String, Double> span : elementPositions)
214                        {
215                                final double elementPosition_km = span.getFirst();
216                                final String elementType = span.getSecond();
217                                final double gainIfOA_dB = span.getThird();
218                                if (elementPosition_km < current_kmFromTransmitter) throw new RuntimeException("Unexpected error");
219
220                                /* The span of fiber */
221                                final double fiberLength_km = elementPosition_km - current_kmFromTransmitter;
222                                current_powerPerChannel_dBm -= fiber__attenuation_dB_per_km * fiberLength_km;
223                                current_CD_ps_per_nm += fiber__worseChromaticDispersion_ps_per_nm_per_km * fiberLength_km;
224                                current_PMDSquared_ps2 += fiberLength_km * Math.pow(fiber__PMD_ps_per_sqroot_km, 2);
225                                current_kmFromTransmitter = elementPosition_km;
226
227                                /* Check chromatic dispersion at the end of the fiber span */
228                                if (!report__checkCDOnlyAtTheReceiver)
229                                {
230                                        if (current_CD_ps_per_nm > tp__maxChromaticDispersionTolerance_ps_per_nm)
231                                        {
232                                                warningsThisLink.add(Triple.of(current_kmFromTransmitter, "End fiber span", "Chromatic dispersion at this point (" + current_CD_ps_per_nm + " ps/nm) is above the upper limit (" + tp__maxChromaticDispersionTolerance_ps_per_nm + " ps/nm)"));
233                                        }
234                                        if (current_CD_ps_per_nm < -tp__maxChromaticDispersionTolerance_ps_per_nm)
235                                        {
236                                                warningsThisLink.add(Triple.of(current_kmFromTransmitter, "End fiber span", "Chromatic dispersion at this point (" + current_CD_ps_per_nm + " ps/nm) is below the lower limit (" + (-tp__maxChromaticDispersionTolerance_ps_per_nm) + " ps/nm)"));
237                                        }
238                                }
239                                /* The element */
240                                switch (elementType)
241                                {
242                                        case "DCM":
243                                                current_powerPerChannel_dBm -= dcm__insertionLoss_dB;
244                                                current_CD_ps_per_nm += dcm__worseCaseChannelDispersion_ps_per_nm;
245                                                current_PMDSquared_ps2 += Math.pow(dcm__PMD_ps, 2);
246                                                /* Check chromatic dispersion at the end of the fiber */
247                                                if (!report__checkCDOnlyAtTheReceiver)
248                                                {
249                                                        if (current_CD_ps_per_nm > tp__maxChromaticDispersionTolerance_ps_per_nm)
250                                                        {
251                                                                warningsThisLink.add(Triple.of(current_kmFromTransmitter, "Output of DCM", "Chromatic dispersion at this point (" + current_CD_ps_per_nm + " ps/nm) is above the upper limit (" + tp__maxChromaticDispersionTolerance_ps_per_nm + " ps/nm)"));
252                                                        }
253                                                        if (current_CD_ps_per_nm < -tp__maxChromaticDispersionTolerance_ps_per_nm)
254                                                        {
255                                                                warningsThisLink.add(Triple.of(current_kmFromTransmitter, "Output of DCM", "Chromatic dispersion at this point (" + current_CD_ps_per_nm + " ps/nm) is below the lower limit (" + (-tp__maxChromaticDispersionTolerance_ps_per_nm) + " ps/nm)"));
256                                                        }
257                                                }
258                                                numDCMs++;
259                                                signalInfoThisLink.add(Triple.of(current_kmFromTransmitter, "After DCM #" + numDCMs, new double[]
260                                                {
261                                                        current_CD_ps_per_nm, linear2dB(current_OSNR_linear), current_powerPerChannel_dBm, Math.sqrt(current_PMDSquared_ps2)
262                                                }));
263                                                break;
264                                        case "OA":
265                                                signalInfoThisLink.add(Triple.of(current_kmFromTransmitter, "Before EDFA #" + (numEDFAs + 1), new double[]
266                                                {
267                                                        current_CD_ps_per_nm, linear2dB(current_OSNR_linear), current_powerPerChannel_dBm, Math.sqrt(current_PMDSquared_ps2)
268                                                }));
269                                                if (channels__minChannelLambda_nm < edfa__minWavelength_nm)
270                                                {
271                                                        warningsThisLink.add(Triple.of(current_kmFromTransmitter, "EDFA", "Wavelength " + channels__minChannelLambda_nm + " is outside EDFA range [" + edfa__minWavelength_nm + " nm , " + edfa__maxWavelength_nm + " nm]"));
272                                                }
273                                                if (channels__maxChannelLambda_nm > edfa__maxWavelength_nm)
274                                                {
275                                                        warningsThisLink.add(Triple.of(current_kmFromTransmitter, "EDFA", "Wavelength " + channels__minChannelLambda_nm + " is outside EDFA range [" + edfa__minWavelength_nm + " nm , " + edfa__maxWavelength_nm + " nm]"));
276                                                }
277                                                if (current_powerPerChannel_dBm + precisionMargenForChecks_dB < edfa__minInputPower_dBm)
278                                                {
279                                                        warningsThisLink.add(Triple.of(current_kmFromTransmitter, "EDFA", "Input power at the amplifier if only one WDM channel is active (" + current_powerPerChannel_dBm + " dBm) is outside the EDFA input power range [" + edfa__minInputPower_dBm + " dBm , " + edfa__maxInputPower_dBm + " dBm]"));
280                                                }
281                                                if (current_powerPerChannel_dBm + numChannels_dB > edfa__maxInputPower_dBm + precisionMargenForChecks_dB)
282                                                {
283                                                        warningsThisLink.add(Triple.of(current_kmFromTransmitter, "EDFA", "Input power at the amplifier if all WDM channels are active (" + (current_powerPerChannel_dBm + numChannels_dB) + " dBm) is outside the EDFA input power range [" + edfa__minInputPower_dBm + " dBm , " + edfa__maxInputPower_dBm + " dBm]"));
284                                                }
285                                                if ((gainIfOA_dB < edfa__minGain_dB - precisionMargenForChecks_dB) || (gainIfOA_dB > edfa__maxGain_dB + precisionMargenForChecks_dB))
286                                                {
287                                                        warningsThisLink.add(Triple.of(current_kmFromTransmitter, "EDFA", "EDFA gain (" + gainIfOA_dB + " dB) is outside the EDFA gain range [" + edfa__minGain_dB + " dB , " + edfa__maxGain_dB + " dB]"));
288                                                }
289                                                /* update the OSNR */
290                                                final double edfa_noiseFactorThisGain_dB = edfa__noiseFactorMinimumGain_dB + (gainIfOA_dB - edfa__minGain_dB) * (edfa__noiseFactorMaximumGain_dB - edfa__noiseFactorMinimumGain_dB) / (edfa__maxGain_dB - edfa__minGain_dB);
291                                                if ((edfa_noiseFactorThisGain_dB < Math.min(edfa__noiseFactorMinimumGain_dB, edfa__noiseFactorMaximumGain_dB)) || (edfa_noiseFactorThisGain_dB > Math.max(edfa__noiseFactorMinimumGain_dB, edfa__noiseFactorMaximumGain_dB)))
292                                                {
293                                                        throw new RuntimeException("Bad");
294                                                }
295                                                final double edfa_NF_linear = dB2Linear(edfa_noiseFactorThisGain_dB);
296                                                final double highestFrequencyChannel_Hz = constant_c / (channels__minChannelLambda_nm * 1e-9);
297                                                final double referenceBandwidthAtHighestFrequency_Hz = -highestFrequencyChannel_Hz + constant_c / ((channels__minChannelLambda_nm - edfa__noiseFactorReferenceBandwidth_nm) * 1e-9);
298                                                final double inputPower_linear = dB2Linear(current_powerPerChannel_dBm) * 1E-3;
299                                                final double thisEDFAAddedNoise_linear = edfa_NF_linear * constant_h * highestFrequencyChannel_Hz * referenceBandwidthAtHighestFrequency_Hz;
300                                                final double addedOSNRThisOA_linear = inputPower_linear / thisEDFAAddedNoise_linear;
301                                                current_OSNR_linear = current_OSNR_linear == Double.MAX_VALUE ? addedOSNRThisOA_linear : 1 / (1 / current_OSNR_linear + 1 / addedOSNRThisOA_linear);
302                                                /* update the signal power */
303                                                current_powerPerChannel_dBm += gainIfOA_dB;
304                                                if (current_powerPerChannel_dBm < edfa__minOutputPower_dBm - precisionMargenForChecks_dB)
305                                                {
306                                                        warningsThisLink.add(Triple.of(current_kmFromTransmitter, "EDFA", "Output power at the amplifier if only one WDM channel is active (" + current_powerPerChannel_dBm + " dBm) is outside the EDFA output power range [" + edfa__minOutputPower_dBm + " dBm , " + edfa__maxOutputPower_dBm + " dBm]"));
307                                                }
308                                                if (current_powerPerChannel_dBm + numChannels_dB > edfa__maxOutputPower_dBm + precisionMargenForChecks_dB)
309                                                {
310                                                        warningsThisLink.add(Triple.of(current_kmFromTransmitter, "EDFA", "Output power at the amplifier if all WDM channels are active (" + (current_powerPerChannel_dBm + numChannels_dB) + " dBm) is outside the EDFA output power range [" + edfa__minOutputPower_dBm + " dBm , " + edfa__maxOutputPower_dBm + " dBm]"));
311                                                }
312                                                /* update the PMD */
313                                                current_PMDSquared_ps2 += Math.pow(edfa__PMD_ps, 2);
314                                                numEDFAs++;
315                                                signalInfoThisLink.add(Triple.of(current_kmFromTransmitter, "After EDFA #" + numEDFAs, new double[]
316                                                {
317                                                        current_CD_ps_per_nm, linear2dB(current_OSNR_linear), current_powerPerChannel_dBm, Math.sqrt(current_PMDSquared_ps2)
318                                                }));
319                                                break;
320                                        default:
321                                                throw new RuntimeException("Unexpected error");
322                                }
323                        }
324
325                        /* Fiber span after the last element, to the receiver */
326                        final double spanLength_km = d_e_thisLink - current_kmFromTransmitter;
327                        current_powerPerChannel_dBm -= fiber__attenuation_dB_per_km * spanLength_km;
328                        current_CD_ps_per_nm += fiber__worseChromaticDispersion_ps_per_nm_per_km * spanLength_km;
329                        current_PMDSquared_ps2 += spanLength_km * Math.pow(fiber__PMD_ps_per_sqroot_km, 2);
330                        current_kmFromTransmitter = d_e_thisLink;
331
332                        /* Check chromatic dispersion at the end of the fiber (input of the demux) */
333                        if (!report__checkCDOnlyAtTheReceiver)
334                        {
335                                if (current_CD_ps_per_nm > tp__maxChromaticDispersionTolerance_ps_per_nm)
336                                {
337                                        warningsThisLink.add(Triple.of(current_kmFromTransmitter, "Input of demux", "Chromatic dispersion at this point (" + current_CD_ps_per_nm + " ps/nm) is above the upper limit (" + tp__maxChromaticDispersionTolerance_ps_per_nm + " ps/nm)"));
338                                }
339                                if (current_CD_ps_per_nm < -tp__maxChromaticDispersionTolerance_ps_per_nm)
340                                {
341                                        warningsThisLink.add(Triple.of(current_kmFromTransmitter, "Input of demux", "Chromatic dispersion at this point (" + current_CD_ps_per_nm + " ps/nm) is below the lower limit (" + (-tp__maxChromaticDispersionTolerance_ps_per_nm) + " ps/nm)"));
342                                }
343                        }
344                        signalInfoThisLink.add(Triple.of(current_kmFromTransmitter, "Before DEMUX", new double[]
345                        {
346                                current_CD_ps_per_nm, linear2dB(current_OSNR_linear), current_powerPerChannel_dBm, Math.sqrt(current_PMDSquared_ps2)
347                        }));
348
349                        /* The demultiplexer at the end of the line */
350                        /* Check if the input power at the multiplexer does not exceed the limit */
351                        if (current_powerPerChannel_dBm + numChannels_dB > mux__maxInputPower_dBm + precisionMargenForChecks_dB)
352                        {
353                                warningsThisLink.add(Triple.of(current_kmFromTransmitter, "DEMUX", "Input power at the demux if all WDM channels are active (" + (current_powerPerChannel_dBm + numChannels_dB) + " dBm) is ebove the DEMUX maximum input power (" + mux__maxInputPower_dBm + " dBm)"));
354                        }
355                        current_powerPerChannel_dBm -= mux__insertionLoss_dB;
356                        current_PMDSquared_ps2 += Math.pow(mux__PMD_ps, 2);
357
358                        /* In the receiver */
359                        /* CHECK 1: CHROMATIC DISPERSION WITHIN THE LIMITS */
360                        if (current_CD_ps_per_nm > tp__maxChromaticDispersionTolerance_ps_per_nm)
361                                warningsThisLink.add(Triple.of(current_kmFromTransmitter, "Receiver", "Chromatic dispersion at this point (" + current_CD_ps_per_nm + " ps/nm) is above the upper limit (" + tp__maxChromaticDispersionTolerance_ps_per_nm + " ps/nm)"));
362
363                        if (current_CD_ps_per_nm < -tp__maxChromaticDispersionTolerance_ps_per_nm)
364                                warningsThisLink.add(Triple.of(current_kmFromTransmitter, "Receiver", "Chromatic dispersion at this point (" + current_CD_ps_per_nm + " ps/nm) is below the lower limit (" + (-tp__maxChromaticDispersionTolerance_ps_per_nm) + " ps/nm)"));
365
366                        /* CHECK 2: OSNR WITHIN THE LIMITS */
367                        if (linear2dB(current_OSNR_linear) < tp__minOSNR_dB + osnrPenalty__SUM_dB)
368                                warningsThisLink.add(Triple.of(current_kmFromTransmitter, "Receiver", "OSNR at the receiver (" + linear2dB(current_OSNR_linear) + " dB) is below the minimum OSNR tolerance plus margin (" + tp__minOSNR_dB + " dB + " + osnrPenalty__SUM_dB + " dB = " + (tp__minOSNR_dB + osnrPenalty__SUM_dB) + " dB)"));
369
370                        /* CHECK 3: POWER BUDGET WITHIN LIMITS */
371                        if ((current_powerPerChannel_dBm > tp__inputPowerSensitivityMax_dBm + precisionMargenForChecks_dB) || (current_powerPerChannel_dBm < tp__inputPowerSensitivityMin_dBm - precisionMargenForChecks_dB))
372                                warningsThisLink.add(Triple.of(current_kmFromTransmitter, "Receiver", "Input power (" + current_powerPerChannel_dBm + " dBm) is outside the sensitivity range [" + tp__inputPowerSensitivityMin_dBm + " dBm , " + tp__inputPowerSensitivityMax_dBm + " dBm]"));
373
374                        /* CHECK 4: PMD TOLERANCE OF RECEIVER */
375                        if (Math.sqrt(current_PMDSquared_ps2) > tp__pmdTolerance_ps)
376                                warningsThisLink.add(Triple.of(current_kmFromTransmitter, "Receiver", "PMD at the receiver (" + Math.sqrt(current_PMDSquared_ps2) + " ps) is above the maximum PMD tolerance (" + tp__pmdTolerance_ps + " ps)"));
377
378                        signalInfoThisLink.add(Triple.of(current_kmFromTransmitter, "Receiver", DoubleUtils.arrayOf(current_CD_ps_per_nm, linear2dB(current_OSNR_linear), current_powerPerChannel_dBm, Math.sqrt(current_PMDSquared_ps2))));
379                        signalInfo.put(link.getId () , signalInfoThisLink);
380                        warnings.put(link.getId (), warningsThisLink);
381                }
382
383                return printReport(netPlan, reportParameters, signalInfo, warnings);
384        }
385
386        @Override
387        public String getDescription()
388        {
389                return "This report shows line engineering information for WDM links in the network. Further description, in the HTML generated";
390        }
391
392        @Override
393        public List<Triple<String, String, String>> getParameters()
394        {
395                List<Triple<String, String, String>> aux = new LinkedList<Triple<String, String, String>>();
396
397                aux.add(Triple.of("report__checkCDOnlyAtTheReceiver", "#boolean# true", "If true, the chromatic dispersion only is checked against the CD tolerance at the receiver. If false, it is checked against the same value, in all the fiber spans"));
398
399                /* Usable wavelengths */
400                aux.add(Triple.of("channels__minChannelLambda_nm", "1530.33", "Channel minimum wavelength in nm"));
401                aux.add(Triple.of("channels__channelSpacing_GHz", "100", "Channel spacing in GHz"));
402                aux.add(Triple.of("channels__maxNumChannels", "16", "Maximum number of WDM channels that will be used"));
403
404                /* Fiber specifications  */
405                aux.add(Triple.of("fiber__ituType", "G.655", "ITU-T fiber type"));
406                aux.add(Triple.of("fiber__attenuation_dB_per_km", "0.25", "Fiber attenuation in dB/km"));
407                aux.add(Triple.of("fiber__worseChromaticDispersion_ps_per_nm_per_km", "6", "Chromatic dispersion of the fiber in ps/nm/km"));
408                aux.add(Triple.of("fiber__PMD_ps_per_sqroot_km", "0.5", "Polarization mode dispersion per km^0.5 of fiber (PMD_Q link factor)"));
409
410                /* Transponder specifications */
411                aux.add(Triple.of("tp__outputPower_dBm", "6", "Output power of the transmitter in dBm"));
412                aux.add(Triple.of("tp__maxChromaticDispersionTolerance_ps_per_nm", "800", "Maximum chromatic dispersion tolerance in ps/nm at the receiver"));
413                aux.add(Triple.of("tp__minOSNR_dB", "10", "Minimum OSNR needed at the receiver"));
414                aux.add(Triple.of("tp__inputPowerSensitivityMin_dBm", "-18", "Minimum input power at the receiver in dBm"));
415                aux.add(Triple.of("tp__inputPowerSensitivityMax_dBm", "-8", "Maximum input power at the receiver in dBm"));
416                aux.add(Triple.of("tp__minWavelength_nm", "1529.55", "Minimum wavelength usable by the transponder"));
417                aux.add(Triple.of("tp__maxWavelength_nm", "1561.84", "Maximum wavelength usable by the transponder"));
418                aux.add(Triple.of("tp__pmdTolerance_ps", "10", "Maximum tolarance of polarizarion mode dispersion (mean of differential group delay) in ps at the receiver"));
419
420                /* Optical amplifier specifications */
421                aux.add(Triple.of("edfa__minWavelength_nm", "1530", "Minimum wavelength usable by the EDFA"));
422                aux.add(Triple.of("edfa__maxWavelength_nm", "1563", "Maximum wavelength usable by the EDFA"));
423                aux.add(Triple.of("edfa__minInputPower_dBm", "-29", "Minimum input power at the EDFA"));
424                aux.add(Triple.of("edfa__maxInputPower_dBm", "2", "Maximum input power at the EDFA"));
425                aux.add(Triple.of("edfa__minOutputPower_dBm", "-6", "Minimum output power at the EDFA"));
426                aux.add(Triple.of("edfa__maxOutputPower_dBm", "19", "Maximum output power at the EDFA"));
427                aux.add(Triple.of("edfa__minGain_dB", "17", "Minimum gain at the EDFA"));
428                aux.add(Triple.of("edfa__maxGain_dB", "23", "Maximum gain at the EDFA"));
429                aux.add(Triple.of("edfa__PMD_ps", "0.5", "Polarization mode dispersion in ps added by the EDFA"));
430                aux.add(Triple.of("edfa__noiseFactorMaximumGain_dB", "5.4", "Noise factor at the EDFA when the gain is in its upper limit (linear interpolation is used to estimate the noise figura at other gains)"));
431                aux.add(Triple.of("edfa__noiseFactorMinimumGain_dB", "6.4", "Noise factor at the EDFA when the gain is in its lower limit (linear interpolation is used to estimate the noise figura at other gains)"));
432                aux.add(Triple.of("edfa__noiseFactorReferenceBandwidth_nm", "0.5", "Reference bandwidth that measures the noise factor at the EDFA"));
433
434                /* Dispersion compensation modules specifications */
435                aux.add(Triple.of("dcm__worseCaseChannelDispersion_ps_per_nm", "-551", "Dispersion compensation (ps/nm) in the WDM channel with lower dispersion in absolute number"));
436                aux.add(Triple.of("dcm__PMD_ps", "0.7", "Polarization mode dispersion in ps added by the DCM"));
437                aux.add(Triple.of("dcm__insertionLoss_dB", "3.5", "Maximum insertion loss added by the DCM"));
438
439                /* Mux/demux modules specifications */
440                aux.add(Triple.of("mux__insertionLoss_dB", "5.1", "Maximum insertion loss in dB added by the mux/demux"));
441                aux.add(Triple.of("mux__PMD_ps", "0.5", "Polarization mode dispersion in ps added by the mux/demux"));
442                aux.add(Triple.of("mux__maxInputPower_dBm", "24", "Maximum input power in dBm at the mux/demux"));
443
444                /* OSNR penalties */
445                aux.add(Triple.of("osnrPenalty__CD_dB", "1", "OSNR penalty caused by residual chromatic dispersion (assumed within limits)"));
446                aux.add(Triple.of("osnrPenalty__nonLinear_dB", "2", "OSNR penalty caused by the non-linear effects SPM, XPM, FWM and Brillouin / Raman scattering"));
447                aux.add(Triple.of("osnrPenalty__PMD_dB", "0.5", "OSNR penalty caused by the polarization mode dispersion (assumed within limits)"));
448                aux.add(Triple.of("osnrPenalty__PDL_dB", "0.3", "OSNR penalty caused by polarization dispersion losses"));
449                aux.add(Triple.of("osnrPenalty__transmitterChirp_dB", "0.5", "OSNR penalty caused by transmitter chirp "));
450                aux.add(Triple.of("osnrPenalty__muxDemuxCrosstalk_dB", "0.2", "OSNR penalty caused by the crosstalk at the mux and the demux"));
451                aux.add(Triple.of("osnrPenalty__unassignedMargin_dB", "3", "OSNR penalty caused by not assigned margins (e.g. random effects, aging, ...)"));
452
453                return aux;
454        }
455
456        @Override
457        public String getTitle()
458        {
459                return "WDM line engineering";
460        }
461
462        private static double dB2Linear(double dB)
463        {
464                return Math.pow(10, dB / 10);
465        }
466
467        private List<Triple<Double, String, Double>> getElementPositionsArray(long linkId, double linkLength, double[] edfaPositions_km, double[] edfaGains_dB, double[] dcmPositions_km) throws Net2PlanException
468        {
469                if (edfaPositions_km.length != edfaGains_dB.length)
470                {
471                        throw new Net2PlanException("Link: " + linkId + ". Number of elements in edfaPositions_km is not equal to the number of elements in edfaGains_dB.");
472                }
473                for (double edfaPosition : edfaPositions_km)
474                {
475                        if ((edfaPosition < 0) || (edfaPosition > linkLength))
476                        {
477                                throw new Net2PlanException("Link: " + linkId + ". Wrong OA position: " + edfaPosition + ", link length = " + linkLength);
478                        }
479                }
480                for (double dcmPosition : dcmPositions_km)
481                {
482                        if ((dcmPosition < 0) || (dcmPosition > linkLength))
483                        {
484                                throw new Net2PlanException("Link: " + linkId + ". Wrong DCM position: " + ", link length = " + linkLength);
485                        }
486                }
487
488                List<Triple<Double, String, Double>> res = new ArrayList<Triple<Double, String, Double>>();
489
490                int[] sortedOAPositionsIndexes = (edfaPositions_km.length == 0) ? new int[0] : DoubleUtils.sortIndexes(edfaPositions_km, OrderingType.ASCENDING);
491                int[] sortedDCMPositionsIndexes = (dcmPositions_km.length == 0) ? new int[0] : DoubleUtils.sortIndexes(dcmPositions_km, OrderingType.ASCENDING);
492
493                int numOAInserted = 0;
494                int numDCMInserted = 0;
495                while (true)
496                {
497                        double positionNextOA = (numOAInserted < edfaPositions_km.length) ? edfaPositions_km[sortedOAPositionsIndexes[numOAInserted]] : -1;
498                        double positionNextDCM = (numDCMInserted < dcmPositions_km.length) ? dcmPositions_km[sortedDCMPositionsIndexes[numDCMInserted]] : -1;
499
500                        /* IS no more elements, it is done */
501                        if ((positionNextOA == -1) && (positionNextDCM == -1))
502                        {
503                                break;
504                        }
505
506                        /* If at the same position, we assume DCM goes first, to reduce non-linear impairments in the DCM */
507                        boolean nextElementIsOA = (positionNextDCM == -1) || ((positionNextOA != -1) && (positionNextOA < positionNextDCM));
508                        if (nextElementIsOA)
509                        {
510                                res.add(Triple.of(positionNextOA, "OA", edfaGains_dB[sortedOAPositionsIndexes[numOAInserted]]));
511                                numOAInserted++;
512                        }
513                        else
514                        {
515                                res.add(Triple.of(positionNextDCM, "DCM", -1.0));
516                                numDCMInserted++;
517                        }
518                }
519
520                return res;
521        }
522
523        private static double linear2dB(double num)
524        {
525                return 10 * Math.log10(num);
526        }
527
528        private String printReport(NetPlan netPlan, Map<String, String> reportParameters, Map<Long, List<Triple<Double, String, double[]>>> signalInfo, Map<Long, List<Triple<Double, String, String>>> warnings)
529        {
530                StringBuilder out = new StringBuilder();
531                DecimalFormat df_2 = new DecimalFormat("###.##");
532
533                out.append("<html>");
534                out.append("<head><title>Point-to-point WDM line engineering report</title></head>");
535                out.append("<html><body>");
536                out.append("<h1>Point-to-point WDM line engineering report</h1>");
537                out.append("<p>This report checks the correctness of the line engineering design of the point-to-point WDM links in an optical network, following the guidelines described in ITU-T Manual 2009 \"Optical fibres, cables and systems\" (chapter 7, section 2 <em>Worst case design for system with optical line amplifiers</em>). The report assumes that the network links are WDM optical fibres with the following scheme:</p>");
538                out.append("<ul>");
539                out.append("<li>A transmitter per WDM channel with the specifications given by \"tp__XXX\". The number of channels can vary from one to up to channels_maxNumChannels and, the design should be correct in all the cases. Transmitter specifications are set in \"tp__XXX\" input parameters</li>");
540                out.append("<li>A multiplexer that receives the input power from the transmitters and  with specifications given by \"mux__XXX\" parameters</li>");
541                out.append("<li>A fiber link of a distance given by the link length, and with specifications given by \"fiber__XXX\" parameters. The fiber can be split into spans if optical amplifers (EDFAs) and/or dispersion compensating modules (DCMs) are placed along the fibre.</li>");
542                out.append("<li>A set of optical amplifiers (EDFAs) located in none, one or more positions in the fiber link, separating them in different spans. EDFAs are supposed to operate in the automatic gain control mode. Thus, the gain is the same, whatever the number of input WDM channels. "
543                                + "EDFA positions (as distance in km from the link start to the EDFA location) and EDFA gains (assumed in dB) are read from the \"edfaPositions_km\" and \"edfaGains_dB\" attributes of the links. The format of both attributes are the same: a string of numbers separated by spaces. "
544                                + "The i-th number corresponding to the position/gain of the i-th EDFA. If the attributes do not exist, it is assumed that no EDFAs are placed in this link. EDFA specifications given by \"edfa__XXX\" parameters</li>");
545                out.append("<li>A set of dispersion compensating modules (DCMs) located in none, one or more positions in the fiber link, separating them in different spans. If a DCM and a EDFA have the same location, it is assumed that the DCM is placed first, to reduce the non-linear effects. "
546                                + "DCM positions (as distance in km from the link start to the DCM location) are read from the \"dcmPositions_km\" attribute of the link, and the same format as with \"edfaPositions_km\" attribute is expected. "
547                                + "If the attribute does not exist, it is assumed that no DCMs are placed in this link. DCM specifications are given by \"dcm__XXX\" parameters</li>");
548                out.append("<li>At the receiver end, WDM channels in the links are separated using a demultiplexer, with specifications given by \"mux__XXX\" parameters</li>");
549                out.append("<li>Each channel ends in a receiver, with specifications given by \"tp__XXX\" parameters</li>");
550                out.append("</ul>");
551                out.append("<p>The basic checks performed are:</p>");
552                out.append("<ul>");
553                out.append("<li>Signal power levels are within operating ranges at the mux/demux/edfas/dcms and receivers, both when the link has one single active channel, or when all the \"channels__maxNumChannels\" are active</li>");
554                out.append("<li>Chromatic dispersion is within the operating ranges in every point of the fiber, and at the receiver.</li>");
555                out.append("<li>Optical Signal to Noise Ration (OSNR) is within the operating range at the receiver.</li>");
556                out.append("<li>Polarization mode dispersion (PMD) is within the operating range at the receiver.</li>");
557                out.append("</ul>");
558
559                out.append("<h2>Input Parameters</h2>");
560                out.append("<table border='1'>");
561                out.append("<tr><th><b>Name</b></th><th><b>Value</b></th><th><b>Description</b></th>");
562
563                for (Triple<String, String, String> paramDef : this.getParameters())
564                {
565                        String name = paramDef.getFirst();
566                        String description = paramDef.getThird();
567                        String value = reportParameters.get(name);
568                        out.append("<tr><td>").append(name).append("</td><td>").append(value).append("</td><td>").append(description).append("</td></tr>");
569                }
570                out.append("</table>");
571
572                out.append("<h2>INFORMATION SUMMARY - Signal metrics at the receiver end</h2>");
573                out.append("<table border='1'>");
574                out.append("<tr><th><b>Link #</b></th><th><b>Length (km)</b></th><th><b># EDFAs</b></th><th><b># DCMs</b></th><th><b>Chromatic Dispersion (ps/nm)</b></th><th><b>OSNR (dB)</b></th><th><b>Power per WDM channel (dBm)</b></th><th><b>Polarization Mode Dispersion (ps)</b></th></tr>");
575                for (Link link : links)
576                {
577                        final double d_e_thisLink = link.getLengthInKm();
578                        final String st_edfaPositions_km = (link.getAttribute("edfaPositions_km") == null) ? "" : link.getAttribute("edfaPositions_km");
579                        final String st_dcmPositions_km = (link.getAttribute("dcmPositions_km") == null) ? "" : link.getAttribute("dcmPositions_km");
580                        final double[] edfaPositions_km = StringUtils.toDoubleArray(StringUtils.split(st_edfaPositions_km));
581                        final double[] dcmPositions_km = StringUtils.toDoubleArray(StringUtils.split(st_dcmPositions_km));
582
583                        Triple<Double, String, double[]> t = signalInfo.get(link.getId ()).get(signalInfo.get(link.getId ()).size() - 1);
584                        out.append("<tr><td>").append(link.getId ()).append("</td><td>").append(df_2.format(d_e_thisLink)).append("</td><td>").append(edfaPositions_km.length).append("</td><td>").append(dcmPositions_km.length).append("</td><td>").append(df_2.format(t.getThird()[0])).append("</td><td>").append(df_2.format(t.getThird()[1])).append("</td><td>").append(df_2.format(t.getThird()[2])).append("</td><td>").append((t.getThird()[3] == -1) ? "-" : df_2.format(t.getThird()[3])).append("</td>" + "</tr>");
585                }
586
587                out.append("</table>");
588
589                out.append("<h2>DESIGN WARNINGS</h2>");
590                out.append("<table border='1'>");
591                out.append("<tr><th><b>Link #</b></th><th><b>Length (km)</b></th><th><b>Warnings</b></th></tr>");
592                for (Link link : links)
593                {
594                        final double d_e_thisLink = link.getLengthInKm();
595                        out.append("<tr><td>").append(link.getId ()).append("</td><td>").append(d_e_thisLink).append("</td><td>");
596                        List<Triple<Double, String, String>> warningsThisLink = warnings.get(link.getId ());
597                        if (warningsThisLink.isEmpty())
598                        {
599                                out.append("<p>None</p>");
600                                continue;
601                        }
602                        for (Triple<Double, String, String> w : warningsThisLink)
603                        {
604                                out.append("<p>[").append(w.getSecond()).append(" (km ").append(df_2.format(w.getFirst())).append(") - ").append(w.getThird()).append("</p>");
605                        }
606                        out.append("</td></tr>");
607                }
608                out.append("</table>");
609
610                out.append("<h2>PER-LINK DETAILED INFORMATION </h2>");
611                out.append("<p>Number of links: ").append(d_e.size()).append("</p>");
612
613                for (Link link : links)
614                {
615                        final double d_e_thisLink = link.getLengthInKm();
616                        List<Triple<Double, String, double[]>> signalInfoThisLinks = signalInfo.get(link.getId ());
617
618                        final String st_edfaPositions_km = (link.getAttribute("edfaPositions_km") == null) ? "" : link.getAttribute("edfaPositions_km");
619                        final String st_edfaGains_dB = (link.getAttribute("edfaGains_dB") == null) ? "" : link.getAttribute("edfaGains_dB");
620                        final String st_dcmPositions_km = (link.getAttribute("dcmPositions_km") == null) ? "" : link.getAttribute("dcmPositions_km");
621
622                        out.append("<h3>LINK # ").append(link.getId ()).append("</h3>");
623                        out.append("<table border=\"1\">");
624                        out.append("<caption>Link input information</caption>");
625                        out.append("<tr><td>Link length (km)</td><td>").append(d_e_thisLink).append("</td></tr>");
626                        out.append("<tr><td>EDFA positions (km)</td><td>").append(st_edfaPositions_km).append("</td></tr>");
627                        out.append("<tr><td>EDFA gains (dB)</td><td>").append(st_edfaGains_dB).append("</td></tr>");
628                        out.append("<tr><td>DCM positions (km)</td><td>").append(st_dcmPositions_km).append("</td></tr>");
629                        out.append("</table>");
630
631                        out.append("<table border=\"1\">");
632                        out.append("<caption>Signal metrics evolution</caption>");
633                        out.append("<tr><th><b>Position (km)</b></th><th><b>Position (description)</b></th><th><b>Chromatic Dispersion (ps/nm)</b></th><th><b>OSNR (dB)</b></th><th><b>Power per WDM channel (dBm)</b></th><th><b>Polarization Mode Dispersion (ps)</b></th></tr>");
634                        for (Triple<Double, String, double[]> t : signalInfoThisLinks)
635                        {
636                                out.append("<tr><td>").append(df_2.format(t.getFirst())).append("</td><td>").append(t.getSecond()).append("</td><td>").append(df_2.format(t.getThird()[0])).append("</td><td>").append(df_2.format(t.getThird()[1])).append("</td><td>").append(df_2.format(t.getThird()[2])).append("</td><td>").append((t.getThird()[3] == -1) ? "-" : df_2.format(t.getThird()[3])).append("</td>" + "</tr>");
637                        }
638                        out.append("</table>");
639                }
640                
641                out.append("</body></html>");
642                return out.toString();
643        }
644}