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}