def __init__(self, config, mpasTimeSeriesTask, controlConfig=None): # {{{ """ Construct the analysis task. Parameters ---------- config : ``MpasAnalysisConfigParser`` Configuration options mpasTimeSeriesTask : ``MpasTimeSeriesTask`` The task that extracts the time series from MPAS monthly output controlConfig : ``MpasAnalysisConfigParser``, optional Configuration options for a control run (if any) """ # Authors # ------- # Xylar Asay-Davis # first, call the constructor from the base class (AnalysisTask) super(TimeSeriesAntarcticMelt, self).__init__(config=config, taskName='timeSeriesAntarcticMelt', componentName='ocean', tags=['timeSeries', 'melt', 'landIceCavities']) regionMaskDirectory = build_config_full_path(config, 'diagnostics', 'regionMaskSubdirectory') self.iceShelfMasksFile = '{}/iceShelves.geojson'.format( regionMaskDirectory) iceShelvesToPlot = config.getExpression('timeSeriesAntarcticMelt', 'iceShelvesToPlot') if 'all' in iceShelvesToPlot: iceShelvesToPlot = get_feature_list(self.iceShelfMasksFile) parallelTaskCount = config.getWithDefault('execute', 'parallelTaskCount', default=1) masksSubtask = ComputeRegionMasksSubtask( self, self.iceShelfMasksFile, outFileSuffix='iceShelfMasks', featureList=iceShelvesToPlot, subprocessCount=parallelTaskCount) self.add_subtask(masksSubtask) computeMeltSubtask = ComputeMeltSubtask(self, mpasTimeSeriesTask, masksSubtask, iceShelvesToPlot) self.add_subtask(computeMeltSubtask) for index, iceShelf in enumerate(iceShelvesToPlot): plotMeltSubtask = PlotMeltSubtask(self, iceShelf, index, controlConfig) plotMeltSubtask.run_after(computeMeltSubtask) self.add_subtask(plotMeltSubtask)
def __init__(self, config, mpasTimeSeriesTask, regionMasksTask, controlConfig=None): # {{{ """ Construct the analysis task. Parameters ---------- config : ``MpasAnalysisConfigParser`` Configuration options mpasTimeSeriesTask : ``MpasTimeSeriesTask`` The task that extracts the time series from MPAS monthly output regionMasksTask : ``ComputeRegionMasks`` A task for computing region masks controlConfig : ``MpasAnalysisConfigParser``, optional Configuration options for a control run (if any) """ # Authors # ------- # Xylar Asay-Davis # first, call the constructor from the base class (AnalysisTask) super(TimeSeriesAntarcticMelt, self).__init__( config=config, taskName='timeSeriesAntarcticMelt', componentName='ocean', tags=['timeSeries', 'melt', 'landIceCavities', 'antarctic']) self.iceShelfMasksFile = get_region_mask(config, 'iceShelves20200621.geojson') iceShelvesToPlot = config.getExpression('timeSeriesAntarcticMelt', 'iceShelvesToPlot') if 'all' in iceShelvesToPlot: iceShelvesToPlot = get_feature_list(self.iceShelfMasksFile) masksSubtask = regionMasksTask.add_mask_subtask( self.iceShelfMasksFile, outFileSuffix='iceShelves20200621') computeMeltSubtask = ComputeMeltSubtask(self, mpasTimeSeriesTask, masksSubtask, iceShelvesToPlot) self.add_subtask(computeMeltSubtask) for index, iceShelf in enumerate(iceShelvesToPlot): plotMeltSubtask = PlotMeltSubtask(self, iceShelf, index, controlConfig) plotMeltSubtask.run_after(computeMeltSubtask) self.add_subtask(plotMeltSubtask)
def expand_transect_names(self, transectNames): """ If ``transectNames`` contains ``'all'``, make sure the geojson file exists and then return all the transect names found in the file. Parameters ---------- transectNames : list A list of transect names Returns ------- transectNames : list A list of transect names """ if 'all' in transectNames: self.make_transect_mask() transectNames = get_feature_list(self.geojsonFileName) return transectNames
def __init__(self, config, mpasClimatologyTask, regionMasksTask, controlConfig=None): # {{{ """ Construct the analysis task. Parameters ---------- config : ``MpasAnalysisConfigParser`` Configuration options mpasClimatologyTask : ``MpasClimatologyTask`` The task that produced the climatology to be remapped and plotted regionMasksTask : ``ComputeRegionMasks`` A task for computing region masks controlConfig : ``MpasAnalysisConfigParser``, optional Configuration options for a control run (if any) """ # Authors # ------- # Xylar Asay-Davis # first, call the constructor from the base class (AnalysisTask) super(RegionalTSDiagrams, self).__init__(config=config, taskName='regionalTSDiagrams', componentName='ocean', tags=['climatology', 'regions', 'publicObs']) self.run_after(mpasClimatologyTask) self.mpasClimatologyTask = mpasClimatologyTask regionGroups = config.getExpression(self.taskName, 'regionGroups') self.seasons = config.getExpression(self.taskName, 'seasons') obsDicts = { 'SOSE': { 'suffix': 'SOSE', 'gridName': 'SouthernOcean_0.167x0.167degree', 'gridFileName': 'SOSE/SOSE_2005-2010_monthly_pot_temp_' 'SouthernOcean_0.167x0.167degree_20180710.nc', 'TFileName': 'SOSE/SOSE_2005-2010_monthly_pot_temp_' 'SouthernOcean_0.167x0.167degree_20180710.nc', 'SFileName': 'SOSE/SOSE_2005-2010_monthly_salinity_' 'SouthernOcean_0.167x0.167degree_20180710.nc', 'volFileName': 'SOSE/SOSE_volume_' 'SouthernOcean_0.167x0.167degree_20190815.nc', 'preprocessedFileTemplate': 'SOSE/SOSE_{}_T_S_z_vol_' 'SouthernOcean_0.167x0.167degree_20200514.nc', 'lonVar': 'lon', 'latVar': 'lat', 'TVar': 'theta', 'SVar': 'salinity', 'volVar': 'volume', 'zVar': 'z', 'tVar': 'Time' }, 'WOA18': { 'suffix': 'WOA18', 'gridName': 'Global_0.25x0.25degree', 'gridFileName': 'WOA18/woa18_decav_04_TS_mon_20190829.nc', 'TFileName': 'WOA18/woa18_decav_04_TS_mon_20190829.nc', 'SFileName': 'WOA18/woa18_decav_04_TS_mon_20190829.nc', 'volFileName': None, 'preprocessedFileTemplate': 'WOA18/woa18_{}_T_S_z_vol_20200514.nc', 'lonVar': 'lon', 'latVar': 'lat', 'TVar': 't_an', 'SVar': 's_an', 'volVar': 'volume', 'zVar': 'depth', 'tVar': 'month' } } allObsUsed = [] for regionGroup in regionGroups: sectionSuffix = regionGroup[0].upper() + \ regionGroup[1:].replace(' ', '') sectionName = 'TSDiagramsFor{}'.format(sectionSuffix) obsList = config.getExpression(sectionName, 'obs') allObsUsed = allObsUsed + obsList allObsUsed = set(allObsUsed) for obsName in allObsUsed: obsDict = obsDicts[obsName] obsDicts[obsName]['climatologyTask'] = {} for season in self.seasons: climatologySubtask = ComputeObsTSClimatology(self, obsName, obsDict, season=season) obsDicts[obsName]['climatologyTask'][season] = \ climatologySubtask self.add_subtask(climatologySubtask) for regionGroup in regionGroups: sectionSuffix = regionGroup[0].upper() + \ regionGroup[1:].replace(' ', '') sectionName = 'TSDiagramsFor{}'.format(sectionSuffix) regionMaskSuffix = config.getExpression(sectionName, 'regionMaskSuffix') regionMaskFile = get_region_mask( config, '{}.geojson'.format(regionMaskSuffix)) regionNames = config.getExpression(sectionName, 'regionNames') if 'all' in regionNames and os.path.exists(regionMaskFile): regionNames = get_feature_list(regionMaskFile) mpasMasksSubtask = regionMasksTask.add_mask_subtask( regionMaskFile, outFileSuffix=regionMaskSuffix) obsList = config.getExpression(sectionName, 'obs') groupObsDicts = {} for obsName in obsList: localObsDict = dict(obsDicts[obsName]) obsFileName = build_obs_path( config, component=self.componentName, relativePath=localObsDict['gridFileName']) obsMasksSubtask = regionMasksTask.add_mask_subtask( regionMaskFile, outFileSuffix=regionMaskSuffix, obsFileName=obsFileName, lonVar=localObsDict['lonVar'], latVar=localObsDict['latVar'], meshName=localObsDict['gridName']) obsDicts[obsName]['maskTask'] = obsMasksSubtask localObsDict['maskTask'] = obsMasksSubtask groupObsDicts[obsName] = localObsDict for regionName in regionNames: fullSuffix = sectionSuffix + '_' + \ regionName[0].lower() + \ regionName[1:].replace(' ', '') for season in self.seasons: computeRegionSubtask = ComputeRegionTSSubtask( self, regionGroup, regionName, controlConfig, sectionName, fullSuffix, mpasClimatologyTask, mpasMasksSubtask, groupObsDicts, season) computeRegionSubtask.run_after(mpasMasksSubtask) for obsName in obsList: task = \ obsDicts[obsName]['climatologyTask'][season] computeRegionSubtask.run_after(task) task = obsDicts[obsName]['maskTask'] computeRegionSubtask.run_after(task) self.add_subtask(computeRegionSubtask) plotRegionSubtask = PlotRegionTSDiagramSubtask( self, regionGroup, regionName, controlConfig, sectionName, fullSuffix, mpasClimatologyTask, mpasMasksSubtask, groupObsDicts, season) plotRegionSubtask.run_after(computeRegionSubtask) self.add_subtask(plotRegionSubtask)
def __init__(self, config, regionMasksTask, controlConfig=None): # {{{ """ Construct the analysis task. Parameters ---------- config : ``MpasAnalysisConfigParser`` Configuration options regionMasksTask : ``ComputeRegionMasks`` A task for computing region masks controlConfig : ``MpasAnalysisConfigParser``, optional Configuration options for a control run (if any) """ # Authors # ------- # Xylar Asay-Davis # first, call the constructor from the base class (AnalysisTask) super(TimeSeriesOceanRegions, self).__init__(config=config, taskName='timeSeriesOceanRegions', componentName='ocean', tags=['timeSeries', 'regions', 'antarctic']) startYear = config.getint('timeSeries', 'startYear') endYear = config.getint('timeSeries', 'endYear') regionGroups = config.getExpression(self.taskName, 'regionGroups') for regionGroup in regionGroups: sectionSuffix = regionGroup[0].upper() + \ regionGroup[1:].replace(' ', '') sectionName = 'timeSeries{}'.format(sectionSuffix) regionMaskSuffix = config.getExpression(sectionName, 'regionMaskSuffix') regionMaskFile = get_region_mask( config, '{}.geojson'.format(regionMaskSuffix)) regionNames = config.getExpression(sectionName, 'regionNames') if 'all' in regionNames and os.path.exists(regionMaskFile): regionNames = get_feature_list(regionMaskFile) masksSubtask = regionMasksTask.add_mask_subtask( regionMaskFile, outFileSuffix=regionMaskSuffix) years = list(range(startYear, endYear + 1)) # in the end, we'll combine all the time series into one, but we # create this task first so it's easier to tell it to run after all # the compute tasks combineSubtask = CombineRegionalProfileTimeSeriesSubtask( self, startYears=years, endYears=years, regionGroup=regionGroup) depthMasksSubtask = ComputeRegionDepthMasksSubtask( self, masksSubtask=masksSubtask, regionGroup=regionGroup, regionNames=regionNames) depthMasksSubtask.run_after(masksSubtask) # run one subtask per year for year in years: computeSubtask = ComputeRegionTimeSeriesSubtask( self, startYear=year, endYear=year, masksSubtask=masksSubtask, regionGroup=regionGroup, regionNames=regionNames) self.add_subtask(computeSubtask) computeSubtask.run_after(depthMasksSubtask) computeSubtask.run_after(masksSubtask) combineSubtask.run_after(computeSubtask) self.add_subtask(combineSubtask) for index, regionName in enumerate(regionNames): fullSuffix = sectionSuffix + '_' + regionName[0].lower() + \ regionName[1:].replace(' ', '') plotRegionSubtask = PlotRegionTimeSeriesSubtask( self, regionGroup, regionName, index, controlConfig, sectionName, fullSuffix) plotRegionSubtask.run_after(combineSubtask) self.add_subtask(plotRegionSubtask)
def __init__(self, config, controlConfig=None): # {{{ ''' Construct the analysis task. Parameters ---------- config : instance of MpasAnalysisConfigParser Contains configuration options mpasClimatologyTask : ``MpasClimatologyTask`` The task that produced the climatology to be remapped and plotted controlConfig : ``MpasAnalysisConfigParser``, optional Configuration options for a control run (if any) ''' # Authors # ------- # Xylar Asay-Davis # first, call the constructor from the base class (AnalysisTask) super(OceanRegionalProfiles, self).__init__(config=config, taskName='oceanRegionalProfiles', componentName='ocean', tags=['profiles', 'climatology']) self.startYear = config.getint('climatology', 'startYear') self.endYear = config.getint('climatology', 'endYear') self.fields = config.getExpression('oceanRegionalProfiles', 'fields') self.seasons = config.getExpression('oceanRegionalProfiles', 'seasons') self.regionMaskSuffix = config.get('oceanRegionalProfiles', 'regionMaskSuffix') self.regionNames = config.getExpression('oceanRegionalProfiles', 'regionNames') plotHovmoller = config.getboolean('oceanRegionalProfiles', 'plotHovmoller') self.regionMaskSuffix = config.get('oceanRegionalProfiles', 'regionMaskSuffix') hovmollerGalleryGroup = config.get('oceanRegionalProfiles', 'hovmollerGalleryGroup') masksFile = get_region_mask(config, '{}.geojson'.format(self.regionMaskSuffix)) parallelTaskCount = config.getWithDefault('execute', 'parallelTaskCount', default=1) masksSubtask = ComputeRegionMasksSubtask( self, masksFile, outFileSuffix=self.regionMaskSuffix, featureList=self.regionNames, subprocessCount=parallelTaskCount) if 'all' in self.regionNames: self.regionNames = get_feature_list(config, masksFile) self.masksSubtask = masksSubtask years = range(self.startYear, self.endYear + 1) # in the end, we'll combine all the time series into one, but we create # this task first so it's easier to tell it to run after all the # compute tasks combineSubtask = CombineRegionalProfileTimeSeriesSubtask( self, startYears=years, endYears=years) # run one subtask per year for year in years: computeSubtask = ComputeRegionalProfileTimeSeriesSubtask( self, startYear=year, endYear=year) computeSubtask.run_after(masksSubtask) combineSubtask.run_after(computeSubtask) if plotHovmoller: for field in self.fields: prefix = field['prefix'] for regionName in self.regionNames: subtaskName = 'plotHovmoller_{}_{}'.format( prefix, regionName.replace(' ', '_')) inFileName = '{}/{}_{:04d}-{:04d}.nc'.format( self.regionMaskSuffix, self.regionMaskSuffix, self.startYear, self.endYear) titleName = field['titleName'] caption = 'Time series of {} {} vs ' \ 'depth'.format(regionName.replace('_', ' '), titleName) hovmollerSubtask = PlotHovmollerSubtask( parentTask=self, regionName=regionName, inFileName=inFileName, outFileLabel='{}_hovmoller'.format(prefix), fieldNameInTitle=titleName, mpasFieldName='{}_mean'.format(prefix), unitsLabel=field['units'], sectionName='{}OceanRegionalHovmoller'.format(prefix), thumbnailSuffix='', imageCaption=caption, galleryGroup=hovmollerGalleryGroup, groupSubtitle=None, groupLink='ocnreghovs', galleryName=titleName, subtaskName=subtaskName) hovmollerSubtask.run_after(combineSubtask) self.add_subtask(hovmollerSubtask) for field in self.fields: prefix = field['prefix'] for regionName in self.regionNames: for season in self.seasons: plotSubtask = PlotRegionalProfileTimeSeriesSubtask( self, season, regionName, field, controlConfig) plotSubtask.run_after(combineSubtask) self.add_subtask(plotSubtask)
def __init__(self, config, controlConfig=None): # {{{ """ Construct the analysis task. Parameters ---------- config : ``MpasAnalysisConfigParser`` Configuration options controlConfig : ``MpasAnalysisConfigParser``, optional Configuration options for a control run (if any) """ # Authors # ------- # Xylar Asay-Davis # first, call the constructor from the base class (AnalysisTask) super(TimeSeriesTransport, self).__init__(config=config, taskName='timeSeriesTransport', componentName='ocean', tags=['timeSeries', 'transport']) startYear = config.getint('timeSeries', 'startYear') endYear = config.getint('timeSeries', 'endYear') years = [year for year in range(startYear, endYear + 1)] transportTransectFileName = \ get_region_mask(config, 'transportTransects20200621.geojson') transectsToPlot = config.getExpression('timeSeriesTransport', 'transectsToPlot') if 'all' in transectsToPlot: transectsToPlot = get_feature_list(transportTransectFileName) masksSubtask = ComputeTransectMasksSubtask( self, transportTransectFileName, outFileSuffix='transportTransects20200621') self.add_subtask(masksSubtask) # in the end, we'll combine all the time series into one, but we # create this task first so it's easier to tell it to run after all # the compute tasks combineSubtask = CombineTransportSubtask(self, startYears=years, endYears=years) # run one subtask per year for year in years: computeSubtask = ComputeTransportSubtask( self, startYear=year, endYear=year, masksSubtask=masksSubtask, transectsToPlot=transectsToPlot) self.add_subtask(computeSubtask) computeSubtask.run_after(masksSubtask) combineSubtask.run_after(computeSubtask) for index, transect in enumerate(transectsToPlot): plotTransportSubtask = PlotTransportSubtask( self, transect, index, controlConfig, transportTransectFileName) plotTransportSubtask.run_after(combineSubtask) self.add_subtask(plotTransportSubtask)
def run_task(self): # {{{ """ Computes and plots table of Antarctic sub-ice-shelf melt rates. """ # Authors # ------- # Xylar Asay-Davis self.logger.info("Computing Antarctic melt rate table...") config = self.config sectionName = self.taskName iceShelvesInTable = config.getExpression(sectionName, 'iceShelvesInTable') if 'all' in iceShelvesInTable: iceShelvesInTable = get_feature_list(self.iceShelfMasksFile) meltRateFileName = get_masked_mpas_climatology_file_name( config, self.season, self.componentName, climatologyName='antarcticMeltTable') if not os.path.exists(meltRateFileName): with dask.config.set(schedular='threads', pool=ThreadPool(1)): # Load data: inFileName = self.mpasClimatologyTask.get_file_name( self.season) mpasFieldName = 'timeMonthly_avg_landIceFreshwaterFlux' dsIn = xr.open_dataset(inFileName) freshwaterFlux = dsIn[mpasFieldName] if 'Time' in freshwaterFlux.dims: freshwaterFlux.isel(Time=0) regionMaskFileName = self.masksSubtask.maskFileName dsRegionMask = xr.open_dataset(regionMaskFileName) # figure out the indices of the regions to plot regionNames = decode_strings(dsRegionMask.regionNames) regionIndices = [] for iceShelf in iceShelvesInTable: for index, regionName in enumerate(regionNames): if iceShelf == regionName: regionIndices.append(index) break # select only those regions we want to plot dsRegionMask = dsRegionMask.isel(nRegions=regionIndices) cellMasks = dsRegionMask.regionCellMasks.chunk( {'nRegions': 10}) restartFileName = \ self.runStreams.readpath('restart')[0] dsRestart = xr.open_dataset(restartFileName) areaCell = \ dsRestart.landIceFraction.isel(Time=0) * dsRestart.areaCell # convert from kg/s to kg/yr totalMeltFlux = constants.sec_per_year * \ (cellMasks * areaCell * freshwaterFlux).sum(dim='nCells') totalMeltFlux.compute() totalArea = (cellMasks * areaCell).sum(dim='nCells') # from kg/m^2/yr to m/yr meltRates = ((1. / constants.rho_fw) * (totalMeltFlux / totalArea)) meltRates.compute() # convert from kg/yr to GT/yr totalMeltFlux /= constants.kg_per_GT ds = xr.Dataset() ds['totalMeltFlux'] = totalMeltFlux ds.totalMeltFlux.attrs['units'] = 'GT a$^{-1}$' ds.totalMeltFlux.attrs['description'] = \ 'Total melt flux summed over each ice shelf or region' ds['meltRates'] = meltRates ds.meltRates.attrs['units'] = 'm a$^{-1}$' ds.meltRates.attrs['description'] = \ 'Melt rate averaged over each ice shelf or region' ds['area'] = 1e-6 * totalArea ds.meltRates.attrs['units'] = 'km$^2$' ds.meltRates.attrs['description'] = \ 'Region or ice shelf area' ds['regionNames'] = dsRegionMask.regionNames write_netcdf(ds, meltRateFileName) else: ds = xr.open_dataset(meltRateFileName) mainRunName = config.get('runs', 'mainRunName') fieldNames = ['Region', 'Area', mainRunName] controlConfig = self.controlConfig if controlConfig is not None: controlFileName = get_masked_mpas_climatology_file_name( controlConfig, self.season, self.componentName, climatologyName='antarcticMeltTable') dsControl = xr.open_dataset(controlFileName) controlRunName = controlConfig.get('runs', 'mainRunName') fieldNames.append(controlRunName) else: dsControl = None controlRunName = None regionNames = decode_strings(ds.regionNames) tableFileName = get_masked_mpas_climatology_file_name( config, self.season, self.componentName, climatologyName='antarcticMeltRateTable') tableFileName = tableFileName.replace('.nc', '.csv') with open(tableFileName, 'w', newline='') as csvfile: writer = csv.DictWriter(csvfile, fieldnames=fieldNames) writer.writeheader() for index, regionName in enumerate(regionNames): row = { 'Region': regionName, 'Area': '{}'.format(ds.area[index].values), mainRunName: '{}'.format(ds.meltRates[index].values) } if dsControl is not None: row[controlRunName] = \ '{}'.format(dsControl.meltRates[index].values) writer.writerow(row) tableFileName = get_masked_mpas_climatology_file_name( config, self.season, self.componentName, climatologyName='antarcticMeltFluxTable') tableFileName = tableFileName.replace('.nc', '.csv') with open(tableFileName, 'w', newline='') as csvfile: writer = csv.DictWriter(csvfile, fieldnames=fieldNames) writer.writeheader() for index, regionName in enumerate(regionNames): row = { 'Region': regionName, 'Area': '{}'.format(ds.area[index].values), mainRunName: '{}'.format(ds.totalMeltFlux[index].values) } if dsControl is not None: row[controlRunName] = \ '{}'.format(dsControl.totalMeltFlux[index].values) writer.writerow(row)
def __init__(self, config, controlConfig=None): # {{{ """ Construct the analysis task. Parameters ---------- config : ``MpasAnalysisConfigParser`` Configuration options controlConfig : ``MpasAnalysisConfigParser``, optional Configuration options for a control run (if any) """ # Authors # ------- # Xylar Asay-Davis # first, call the constructor from the base class (AnalysisTask) super(TimeSeriesOceanRegions, self).__init__(config=config, taskName='timeSeriesOceanRegions', componentName='ocean', tags=['timeSeries', 'regions']) startYear = config.getint('timeSeries', 'startYear') endYear = config.getint('timeSeries', 'endYear') regionMaskDirectory = build_config_full_path(config, 'diagnostics', 'regionMaskSubdirectory') regionGroups = config.getExpression(self.taskName, 'regionGroups') parallelTaskCount = config.getWithDefault('execute', 'parallelTaskCount', default=1) for regionGroup in regionGroups: sectionSuffix = regionGroup[0].upper() + \ regionGroup[1:].replace(' ', '') fileSuffix = sectionSuffix[0].lower() + sectionSuffix[1:] sectionName = 'timeSeries{}'.format(sectionSuffix) regionMaskFile = config.getExpression(sectionName, 'regionMask') regionMaskFile = '{}/{}'.format(regionMaskDirectory, regionMaskFile) regionNames = config.getExpression(sectionName, 'regionNames') if 'all' in regionNames and os.path.exists(regionMaskFile): regionNames = get_feature_list(regionMaskFile) masksSubtask = ComputeRegionMasksSubtask( self, regionMaskFile, outFileSuffix=fileSuffix, featureList=regionNames, subprocessCount=parallelTaskCount) self.add_subtask(masksSubtask) years = range(startYear, endYear + 1) # in the end, we'll combine all the time series into one, but we # create this task first so it's easier to tell it to run after all # the compute tasks combineSubtask = CombineRegionalProfileTimeSeriesSubtask( self, startYears=years, endYears=years, regionGroup=regionGroup) # run one subtask per year for year in years: computeSubtask = ComputeRegionTimeSeriesSubtask( self, startYear=year, endYear=year, masksSubtask=masksSubtask, regionGroup=regionGroup, regionNames=regionNames) self.add_subtask(computeSubtask) computeSubtask.run_after(masksSubtask) combineSubtask.run_after(computeSubtask) self.add_subtask(combineSubtask) for index, regionName in enumerate(regionNames): fullSuffix = sectionSuffix + '_' + regionName[0].lower() + \ regionName[1:].replace(' ', '') plotRegionSubtask = PlotRegionTimeSeriesSubtask( self, regionGroup, regionName, index, controlConfig, sectionName, fullSuffix) plotRegionSubtask.run_after(combineSubtask) self.add_subtask(plotRegionSubtask)