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