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