class LumiDumper: def __init__(self): # Control output level self.verbose = True # Channel to chose (preferred by default) self.lumiChan = 0 # Run list self.runList = [] # Luminosity tag self.lumiTag = 'OflLumi-7TeV-002' # Use online instead self.online = False # Instantiate the LumiDBHandler, so we can cleanup all COOL connections in the destructor self.dbHandler = LumiDBHandler() # Output file (default stdout) self.outFile = None # Output directory (default none - pwd) self.outDir = None # Write stable beams only self.checkStable = True # Predefine data readers self.lumi = None self.fill = None self.ardy = None self.lhc = None self.lblb = None # Explicitly clean up our DB connections def __del__(self): self.dbHandler.closeAllDB() # Called in command-line mode def execute(self): # Handle command-line switches self.parseOpts() # Process each run in the runlist for runnum in self.runList: # Read all COOL data self.readData(runnum) self.printData(runnum) def parseOpts(self): parser = OptionParser(usage="usage: %prog [options]", add_help_option=False) parser.add_option("-?", "--usage", action="store_true", default=False, dest="help", help="show this help message and exit") parser.add_option("-v", "--verbose", action="store_true", default=self.verbose, dest="verbose", help="turn on verbose output") parser.add_option( "-r", "--run", dest="runlist", metavar="RUN", help="Specific run, range, or comma-separated list of both") parser.add_option("--channel", dest='chan', metavar='N', default=self.lumiChan, help='specify luminosity channel (default: %d)' % self.lumiChan) parser.add_option("--lumiTag", dest='lumitag', metavar='TAG', default=self.lumiTag, help='specify luminosity tag (default: %s)' % self.lumiTag) parser.add_option("--online", action='store_true', default=self.online, dest='online', help='use online luminosity (default: use offline)') parser.add_option("--outDir", dest='outdir', metavar='DIR', default=self.outDir, help='change default output directory') parser.add_option( "--noStable", action="store_false", default=self.checkStable, dest="checkstable", help="write non-stable beam luminosity (default: stable only)") (options, args) = parser.parse_args() if options.help: parser.print_help() sys.exit() if options.verbose: self.verbose = options.verbose self.lumiChan = int(options.chan) self.lumiTag = options.lumitag self.online = options.online self.outDir = options.outdir self.checkStable = options.checkstable # Parse run list if options.runlist != None: # Clear the old one self.runList = [] # Can be comma-separated list of run ranges runlist = options.runlist.split(',') if len(runlist) == 0: print 'Invalid run list specified!' sys.exit() # Go through and check for run ranges for runstr in runlist: subrunlist = runstr.split('-') if len(subrunlist) == 1: # Single run self.runList.append(int(subrunlist[0])) elif len(subrunlist) == 2: # Range of runs for runnum in range(int(subrunlist[0]), int(subrunlist[1]) + 1): self.runList.append(runnum) else: # Too many parameters print 'Invalid run list segment found:', runstr sys.exit() self.runList.sort() if self.verbose: print 'Finished parsing run list:', for runnum in self.runList: print runnum, print def readData(self, runnum): # Luminosity folder if self.verbose: print 'Reading lumi for run %d' % runnum if self.lumi == None: if self.online: self.lumi = CoolDataReader('COOLONL_TRIGGER/COMP200', '/TRIGGER/LUMI/LBLESTONL') else: self.lumi = CoolDataReader('COOLOFL_TRIGGER/COMP200', '/TRIGGER/OFLLUMI/LBLESTOFL') self.lumi.setTag(self.lumiTag) self.lumi.setChannelId(self.lumiChan) self.lumi.setIOVRangeFromRun(runnum) self.lumi.readData() if self.verbose: print 'Read %d Luminosity records' % len(self.lumi.data) # Load stable beam flag ( if self.verbose: print 'Reading ATLAS ready flag' if self.ardy == None: self.ardy = LumiDBCache('COOLONL_TDAQ/COMP200', '/TDAQ/RunCtrl/DataTakingMode') self.ardy.reader.setIOVRangeFromRun(runnum) self.ardy.reader.readData() if self.verbose: print 'Read %d ATLAS ready records' % len(self.ardy.reader.data) # Load LBLB data (needed to convert to time) if self.verbose: print 'Reading LBLB data' if self.lblb == None: self.lblb = CoolDataReader('COOLONL_TRIGGER/COMP200', '/TRIGGER/LUMI/LBLB') self.lblb.setIOVRangeFromRun(runnum) self.lblb.readData() if self.verbose: print 'Read %d LBLB records' % len(self.lblb.data) if len(self.lblb.data) > 0: print 'First LB %d/%d' % (self.lblb.data[0].since() >> 32, self.lblb.data[0].since() & 0xFFFFFFFF) print 'Last LB %d/%d' % (self.lblb.data[-1].since() >> 32, self.lblb.data[-1].since() & 0xFFFFFFFF) # Now figure out starting/ending time if len(self.lblb.data) < 1: print 'No LBLB data found!' return # Nothing to work with tlo = self.lblb.data[0].payload()['StartTime'] thi = self.lblb.data[-1].payload()['EndTime'] # Fillparams (slow) if self.verbose: print 'Reading Fillparams' if self.fill == None: self.fill = LumiDBCache('COOLONL_TDAQ/COMP200', '/TDAQ/OLC/LHC/FILLPARAMS') self.fill.reader.setIOVRange(tlo, thi) self.fill.reader.readData() if self.verbose: print 'Read %d FILLPARAMS records' % len(self.fill.reader.data) # LHC information (for stable beams) if self.verbose: print 'Reading LHC information' if self.lhc == None: self.lhc = LumiDBCache('COOLOFL_DCS/COMP200', '/LHC/DCS/FILLSTATE') self.lhc.reader.setIOVRange(tlo, thi) self.lhc.reader.readData() if self.verbose: print 'Read %d LHC records' % len(self.lhc.reader.data) def printData(self, runnum): # Only proceed if we have actual lumi data if len(self.lumi.data) == 0: return f = None # Make time map lblbMap = dict() for obj in self.lblb.data: lblbMap[obj.since()] = (obj.payload()['StartTime'], obj.payload()['EndTime']) # OK, now we want to go through the luminosity records and match the other data for obj in self.lumi.data: run = obj.since() >> 32 lb = obj.since() & 0xFFFFFFFF startTime = lblbMap.get(obj.since(), (0., 0.))[0] endTime = lblbMap.get(obj.since(), (0., 0.))[1] lumi = obj.payload()['LBAvInstLumi'] mu = obj.payload()['LBAvEvtsPerBX'] payload = self.fill.getPayload(startTime) if payload == None: ncol = 0 else: ncol = payload['LuminousBunches'] payload = self.lhc.getPayload(startTime) if payload == None: stable = 0 else: stable = payload['StableBeams'] payload = self.ardy.getPayload(obj.since()) if self.ardy == None: ready = 0 else: ready = payload['ReadyForPhysics'] if self.checkStable and not stable: continue # Open file if not open already if f == None: # Open if self.outDir != None: self.outFile = '%s/run%d.txt' % (self.outDir, runnum) else: self.outFile = 'run%d.txt' % runnum print 'Writing file %s' % self.outFile f = open(self.outFile, 'w') # OK, print it out print >> f, run, lb, startTime / 1.E9, endTime / 1.E9, lumi, mu, ncol, ready # Close file if f != None: f.close()
class CompareLumi: def __init__(self): # Control output level self.verbose = False # Channels for comparision self.lumi1Channel = 0 self.lumi2Channel = 261 self.lumi1Tag = 'OflLumi-7TeV-002' self.lumi2Tag = 'OflLumi-7TeV-003' # Difference (in percent) to complain about self.threshold = 5. # Stable only self.stableOnly = True # List of IOVRanges with stable beams self.stableTime = [] # ATLAS Ready only self.readyOnly = True #Dict of (runlblo, runlblbhi) pairs giving extent of ATLAS ready self.readyTime = [] # List of (integer) run numbers specified on the command line self.runList = [] # Utlity routine for handling COOL connections self.dbHandler = LumiDBHandler() # Data readers self.lumi1Reader = None self.lumi2Reader = None self.lblbReader = None self.lhcReader = None self.rdyReader = None # Explicitly clean up our DB connections def __del__(self): self.dbHandler.closeAllDB() # Called in command-line mode def execute(self): # Handle command-line switches self.parseOpts() lumi1Sum = dict() lumi2Sum = dict() # Process each run in self.runList for run in self.runList: # Load all data from COOL self.loadCOOLData(run) # Skip if run doesn't exist if len(self.lblbReader.data) == 0: continue # Find stable beams, output goes to self.stableTime self.findStable() # Find ATLAS ready, result goes to self.readyTime self.findReady() if self.stableOnly and len(self.stableTime) == 0: continue if self.readyOnly and len(self.readyTime) == 0: continue # Storage objects keyed by [lb] lumi1Dict = dict() lumi2Dict = dict() lumi1Sum[run] = 0. lumi2Sum[run] = 0. # Keep track of LB range lbLo = 99999 lbHi = 0 # Loop over all objects and sort by runlb and algo for obj in self.lumi1Reader.data: runlb = obj.since() lumi1Dict[runlb] = obj.payload() for obj in self.lumi2Reader.data: runlb = obj.since() lumi2Dict[runlb] = obj.payload() # if self.verbose: # run = runlb >> 32 # lb = runlb & 0xFFFFFFFF # print 'Found Run %d LB %d chan %d' % (run, lb, channel) # Loop over defined data for obj in self.lblbReader.data: runlb = obj.since() run = runlb >> 32 lb = runlb & 0xFFFFFFFF startTime = obj.payload()['StartTime'] endTime = obj.payload()['EndTime'] dtime = (endTime - startTime) / 1E9 # Check whether this is in stable beams isStable = False for stable in self.stableTime: if not stable[0] <= startTime < stable[1]: continue isStable = True break if self.stableOnly and not isStable: continue # Check whether this in in ATLAS ready isReady = False for ready in self.readyTime: if not ready[0] <= runlb < ready[1]: continue isReady = True break #if self.verbose: # print 'Found stable Run %d LB %d' % (run, lb) if lb < lbLo: lbLo = lb if lb > lbHi: lbHi = lb # Check if lumi record exists missing1 = False missing2 = False if runlb not in lumi1Dict: missing1 = True if runlb not in lumi2Dict: missing2 = True if missing1 and missing2: if isStable: print 'Run: %d LB: %4d does not have any luminosity record in stable beams!' % ( run, lb) else: print 'Run: %d LB: %4d does not have any luminosity record!' % ( run, lb) continue elif missing1: if isStable: print 'Run: %d LB: %4d does not have first lumi record in stable beams!' % ( run, lb) else: print 'Run: %d LB: %4d does not have first lumi record!' % ( run, lb) elif missing2: if isStable: print 'Run: %d LB: %4d does not have second lumi record in stable beams!' % ( run, lb) else: print 'Run: %d LB: %4d does not have second lumi record!' % ( run, lb) # Get lumi lumi1 = 0. valid1 = 999 chan1 = self.lumi1Channel lumi2 = 0. valid2 = 999 chan2 = self.lumi2Channel if not missing1: lumi1 = lumi1Dict[runlb]['LBAvInstLumi'] valid1 = lumi1Dict[runlb]['Valid'] & 0x3FF if chan1 == 0: chan1 = lumi1Dict[runlb]['Valid'] >> 22 lumi1Sum[run] += (lumi1 * dtime) / 1.E6 if not missing2: lumi2 = lumi2Dict[runlb]['LBAvInstLumi'] valid2 = lumi2Dict[runlb]['Valid'] & 0x3FF if chan2 == 0: chan2 = lumi2Dict[runlb]['Valid'] >> 22 lumi2Sum[run] += (lumi2 * dtime) / 1.E6 try: ratio = 100 * (lumi2 / lumi1 - 1.) except ZeroDivisionError: ratio = -99. if self.verbose: print 'Run: %6d LB: %4d Lumi1: %6.1f Chan1: %d Valid1: %d Lumi2: %6.1f Chan2: %d Valid2: %d Ratio: %.3f%%' % ( run, lb, lumi1, chan1, valid1, lumi2, chan2, valid2, ratio) # Compare if (abs(ratio) > self.threshold and (lumi1 > 1. or lumi2 > 1.)): # or valid1 != 0 or valid2 != 0: print '>>> Run: %6d LB: %4d dTime: %f Lumi1: %6.1f Chan1: %d Valid1: %d Lumi2: %6.1f Chan2: %d Valid2: %d Ratio: %.3f%%' % ( run, lb, dtime, lumi1, chan1, valid1, lumi2, chan2, valid2, ratio) # End of loop over LBs try: ratio = 100 * (lumi2Sum[run] / lumi1Sum[run] - 1.) except ZeroDivisionError: ratio = -99. print 'Run: %6d LBLo: %4d LBHi: %4d IntLumi1: %8.3f IntLumi2: %8.3f Ratio: %.3f%%' % ( run, lbLo, lbHi, lumi1Sum[run], lumi2Sum[run], ratio) # End of loop over runs totalLumi1 = 0. totalLumi2 = 0. for lumi in lumi1Sum.itervalues(): totalLumi1 += lumi for lumi in lumi2Sum.itervalues(): totalLumi2 += lumi try: ratio = 100 * (totalLumi2 / totalLumi1 - 1.) except ZeroDivisionError: ratio = -99. print 'Total Luminosity1: %8.3f Luminosity2: %8.3f => 2/1 = %.3f%%' % ( totalLumi1, totalLumi2, ratio) # Load all data necessary to make bunch-by-bunch lumi for a single run def loadCOOLData(self, runnum): if self.verbose: print 'LumiChecker.loadCOOLData(%s) called' % runnum # Many folders are indexed by time stamp. Load RunLB -> time conversion here self.loadLBLB(runnum) if len(self.lblbReader.data) == 0: return # Determine start and end time of this run startTime = self.lblbReader.data[0].payload()['StartTime'] endTime = self.lblbReader.data[-1].payload()['EndTime'] iov = (startTime, endTime) # Read LHC Data (for stable beams) self.loadLHCData(iov) # Read Ready Data self.loadReadyData(runnum) # Read the luminosity self.loadLumi(runnum) def loadLBLB(self, run): if self.verbose: print 'Loading LBLB data' # Instantiate new COOL data reader if not already done if self.lblbReader == None: self.lblbReader = CoolDataReader('COOLONL_TRIGGER/COMP200', '/TRIGGER/LUMI/LBLB') self.lblbReader.setIOVRangeFromRun(run) self.lblbReader.readData() if self.verbose: print 'Read %d LBLB records' % len(self.lblbReader.data) if len(self.lblbReader.data) > 0: print 'First LB %d/%d' % ( self.lblbReader.data[0].since() >> 32, self.lblbReader.data[0].since() & 0xFFFFFFFF) print 'Last LB %d/%d' % ( self.lblbReader.data[-1].since() >> 32, self.lblbReader.data[-1].since() & 0xFFFFFFFF) def loadLumi(self, runnum): if self.verbose: print 'Loading luminosity values' # Instantiate new COOL data reader if not already done if self.lumi1Reader == None: self.lumi1Reader = CoolDataReader('COOLOFL_TRIGGER/COMP200', '/TRIGGER/OFLLUMI/LBLESTOFL') self.lumi1Reader.setIOVRangeFromRun(runnum) self.lumi1Reader.setChannel([self.lumi1Channel]) self.lumi1Reader.setTag(self.lumi1Tag) self.lumi1Reader.readData() if self.verbose: print 'Read %d Lumi records' % len(self.lumi1Reader.data) # Instantiate new COOL data reader if not already done if self.lumi2Reader == None: self.lumi2Reader = CoolDataReader('COOLOFL_TRIGGER/COMP200', '/TRIGGER/OFLLUMI/LBLESTOFL') self.lumi2Reader.setIOVRangeFromRun(runnum) self.lumi2Reader.setChannel([self.lumi2Channel]) self.lumi2Reader.setTag(self.lumi2Tag) self.lumi2Reader.readData() if self.verbose: print 'Read %d Lumi records' % len(self.lumi2Reader.data) # Information about stable beams def loadLHCData(self, iov): if self.verbose: print 'Loading LHC information' # Instantiate new COOL data reader if not already done if self.lhcReader == None: self.lhcReader = CoolDataReader('COOLOFL_DCS/COMP200', '/LHC/DCS/FILLSTATE') self.lhcReader.setIOVRange(iov[0], iov[1]) self.lhcReader.readData() if self.verbose: print 'Read %d LHC records' % len(self.lhcReader.data) # Information about ATLAS ready def loadReadyData(self, run): if self.verbose: print 'Loading ATLAS ready information' # Instantiate new COOL data reader if not already done if self.rdyReader == None: self.rdyReader = CoolDataReader('COOLONL_TDAQ/COMP200', '/TDAQ/RunCtrl/DataTakingMode') self.rdyReader.setIOVRangeFromRun(run) self.rdyReader.readData() if self.verbose: print 'Read %d ATLAS ready records' % len(self.rdyReader.data) # Fill the stable beam information in the OflLumiRunData object def findStable(self): if self.verbose: print 'Finding stable beam periods' # First, fill stable beam time periods for this run tlo = cool.ValidityKeyMax thi = cool.ValidityKeyMin self.stableTime = [] for obj in self.lhcReader.data: if obj.payload()['StableBeams'] == 0: continue if tlo > thi: # First stable beams tlo = obj.since() thi = obj.until() elif thi == obj.since(): # Extension of existing stable beams thi = obj.until() else: # Not contiguous, add old to list and start again self.stableTime.append((tlo, thi)) tlo = obj.since() thi = obj.until() if tlo < thi: self.stableTime.append((tlo, thi)) if self.verbose: print 'Stable beam periods found:', self.stableTime # Fill the stable beam information in the OflLumiRunData object def findReady(self): if self.verbose: print 'Finding ATLAS ready beam periods' # First, fill stable beam time periods for this run tlo = cool.ValidityKeyMax thi = cool.ValidityKeyMin self.readyTime = [] for obj in self.rdyReader.data: if obj.payload()['ReadyForPhysics'] == 0: continue if tlo > thi: # First ready tlo = obj.since() thi = obj.until() elif thi == obj.since(): # Extension of existing ready thi = obj.until() else: # Not contiguous, add old to list and start again self.readyTime.append((tlo, thi)) tlo = obj.since() thi = obj.until() if tlo < thi: self.readyTime.append((tlo, thi)) if self.verbose: print 'ATLAS ready periods found:', self.readyTime def parseOpts(self): parser = OptionParser(usage="usage: %prog [options]", add_help_option=False) parser.add_option("-?", "--usage", action="store_true", default=False, dest="help", help="show this help message and exit") parser.add_option("-v", "--verbose", action="store_true", default=self.verbose, dest="verbose", help="turn on verbose output") parser.add_option("-r", "--updateRun", dest="runlist", metavar="RUN", help="update specific run, or comma separated list") parser.add_option("--tag1", dest="lumi1tag", metavar="TAG", default=self.lumi1Tag, help='first luminosity tag (default: %s)' % self.lumi1Tag) parser.add_option("--chan1", dest="lumi1chan", metavar="CHAN", default=self.lumi1Channel, help='first luminosity channel (default: %d)' % self.lumi1Channel) parser.add_option("--tag2", dest="lumi2tag", metavar="TAG", default=self.lumi2Tag, help='first luminosity tag (default: %s)' % self.lumi2Tag) parser.add_option("--chan2", dest="lumi2chan", metavar="CHAN", default=self.lumi2Channel, help='second luminosity channel (default: %d)' % self.lumi2Channel) parser.add_option( "--noStable", action="store_false", default=self.stableOnly, dest="stable", help="turn off stable beams requirement (default: stable only)") parser.add_option( "--noReady", action="store_false", default=self.readyOnly, dest="ready", help="turn off ATLAS ready requirement (default: ready only)") (options, args) = parser.parse_args() if options.help: parser.print_help() sys.exit() self.verbose = options.verbose self.lumi1Tag = options.lumi1tag self.lumi2Tag = options.lumi2tag self.stableOnly = options.stable self.readyOnly = options.ready self.lumi1Channel = int(options.lumi1chan) self.lumi2Channel = int(options.lumi2chan) # Parse run list if options.runlist != None: # Clear the old one self.runList = [] # Can be comma-separated list of run ranges runlist = options.runlist.split(',') if len(runlist) == 0: print 'Invalid run list specified!' sys.exit() # Go through and check for run ranges for runstr in runlist: subrunlist = runstr.split('-') if len(subrunlist) == 1: # Single run self.runList.append(int(subrunlist[0])) elif len(subrunlist) == 2: # Range of runs for runnum in range(int(subrunlist[0]), int(subrunlist[1]) + 1): self.runList.append(runnum) else: # Too many parameters print 'Invalid run list segment found:', runstr sys.exit() self.runList.sort() if self.verbose: print 'Finished parsing run list:', for runnum in self.runList: print runnum, print
def execute(self): self.parseOpts() # Read offline luminosity lumiReader = CoolDataReader('COOLOFL_TRIGGER/COMP200', '/TRIGGER/OFLLUMI/LBLESTOFL') lumiReader.setChannelId(self.lumiChannel) lumiReader.setTag(self.lumiTag) # Read LAr noise bursts larReader = CoolDataReader('COOLOFL_LAR/COMP200', '/LAR/BadChannelsOfl/EventVeto') larReader.setTag('LARBadChannelsOflEventVeto-UPD4-04') # Time to use with LAr noise # lbtimeReader = CoolDataReaderCache('COOLONL_TRIGGER/COMP200', '/TRIGGER/LUMI/LBTIME') lblbReader = CoolDataReader('COOLONL_TRIGGER/COMP200', '/TRIGGER/LUMI/LBLB') # Read ATLAS ready flag readyReader = LumiDBCache('COOLONL_TDAQ/COMP200', '/TDAQ/RunCtrl/DataTakingMode') for run in self.runList: print print 'Generating run', run rootfile = self.outdir + '/run' + str(run) + '.root' # Define ntuple - will open existing file if present nt = TrigNtupleHandler() nt.fileName = rootfile nt.open(update=False) nt.initLBData() nt.initL1TrigData() # Load run information print 'Load trigger information' th = TriggerHandler() th.allL1Triggers = True th.trigList = [] th.loadDataByRun(run) # Load lumi information print 'Load lumi information' lumiReader.setIOVRangeFromRun(run) lumiReader.readData() # Read ATLAS ready information print 'Load ATLAS ready information' readyReader.reader.setIOVRangeFromRun(run) readyReader.reader.readData() # Load time stamps print 'Load LBLB information' lblbReader.setIOVRangeFromRun(run) lblbReader.readData() startTime = lblbReader.data[0].payload()["StartTime"] endTime = lblbReader.data[-1].payload()["EndTime"] # Read bad LAr periods print 'Load LAr information' larReader.setIOVRange(startTime, endTime) larReader.readData() # Now make a list of bad lumi blocks print 'Finding bad LBs' badLB = set() for larData in larReader.data: if larData.payload()["EventVeto"] == 0: continue tlo = larData.since() thi = larData.until() # Find all lumi blocks spanned by this range for lb in lblbReader.data: if lb.payload()["EndTime"] <= tlo: continue ss = lb.since() if lb.payload()["StartTime"] < tlo: badLB.add(ss) print runLBString(ss) if lb.payload()["StartTime"] < thi: badLB.add(ss) print runLBString(ss) if lb.payload()["StartTime"] > thi: break # Process for obj in lumiReader.data: ss = obj.since() lumi = obj.payload()["LBAvInstLumi"] mu = obj.payload()["LBAvEvtsPerBX"] if ss not in th.trigL1Dict: continue trigcount = th.trigL1Dict[ss].TBP[self.trigChan] dtime = th.trigL1Dict[ss].dtime if (dtime > 0.): trigrate = trigcount / dtime else: trigrate = 0. atlasReady = False readypay = readyReader.getPayload(obj.since()) if readypay != None: atlasReady = readypay['ReadyForPhysics'] print runLBString( ss), atlasReady, lumi, mu, trigcount, trigrate, if trigrate > 0.: print lumi / trigrate else: print # ATLAS Ready only if self.ready and (not atlasReady): continue nt.clear() nt.lbData.coolStartTime = th.trigL1Dict[ss].startTime nt.lbData.coolEndTime = th.trigL1Dict[ss].endTime nt.lbData.startTime = nt.lbData.coolStartTime / 1.E9 nt.lbData.endTime = nt.lbData.coolEndTime / 1.E9 nt.lbData.lbTime = dtime nt.lbData.run = obj.since() >> 32 nt.lbData.lb = obj.since() & 0xFFFFFFFF nt.lbData.onlInstLum = lumi nt.lbData.onlEvtsPerBX = mu nt.lbData.ready = atlasReady nt.lbData.larVeto = (ss in badLB) # And save trigger counts nt.fillL1Trig(th.trigL1Dict[ss], th.trigChan) nt.save() #for chan in range(256): # print chan, nt.l1TBP[chan] nt.close()