예제 #1
0
    def run(self, yieldstep=None, finaltime=None):

        if yieldstep is None:
            yieldstep = self.args.yieldstep

        if finaltime is None:
            finaltime = self.args.finaltime

        if myid == 0 and self.verbose:
            print 'EVOLVE(yieldstep = {}, finaltime = {})'.format(
                yieldstep, finaltime)

        domain = self.domain
        #import time
        t0 = time.time()

        for t in domain.evolve(yieldstep=yieldstep,
                               finaltime=finaltime):  #= 83700.):

            if myid == 0 and self.verbose:
                domain.write_time()

        barrier()
        for p in range(numprocs):
            if myid == p:
                print 'Processor %g ' % myid
                print 'That took %.2f seconds' % (time.time() - t0)
                print 'Communication time %.2f seconds' % domain.communication_time
                print 'Reduction Communication time %.2f seconds' % domain.communication_reduce_time
                print 'Broadcast time %.2f seconds' % domain.communication_broadcast_time
            else:
                pass

            barrier()


#         if myid == 0 and self.verbose:
#             print 'Number of processors %g ' %numprocs
#             print 'That took %.2f seconds' %(time.time()-t0)
#             print 'Communication time %.2f seconds'%domain.communication_time
#             print 'Reduction Communication time %.2f seconds'%domain.communication_reduce_time
#             print 'Broadcast time %.2f seconds'%domain.communication_broadcast_time

        finalize()
예제 #2
0
    def run(self, yieldstep = None, finaltime =None):
        
        if yieldstep is None:
            yieldstep = self.args.yieldstep
            
        if finaltime is None:
            finaltime = self.args.finaltime
            
        if myid == 0 and self.verbose: 
            print 'EVOLVE(yieldstep = {}, finaltime = {})'.format(yieldstep,finaltime)
        
        
        domain = self.domain
        #import time
        t0 = time.time()
            
        for t in domain.evolve(yieldstep = yieldstep, finaltime = finaltime):#= 83700.):
        
            if myid == 0 and self.verbose:
                domain.write_time()
        
        barrier()
        for p in range(numprocs):
            if myid == p:
                print 'Processor %g ' %myid
                print 'That took %.2f seconds' %(time.time()-t0)
                print 'Communication time %.2f seconds'%domain.communication_time
                print 'Reduction Communication time %.2f seconds'%domain.communication_reduce_time
                print 'Broadcast time %.2f seconds'%domain.communication_broadcast_time
            else:
                pass
        
            barrier()

#         if myid == 0 and self.verbose:
#             print 'Number of processors %g ' %numprocs
#             print 'That took %.2f seconds' %(time.time()-t0)
#             print 'Communication time %.2f seconds'%domain.communication_time
#             print 'Reduction Communication time %.2f seconds'%domain.communication_reduce_time
#             print 'Broadcast time %.2f seconds'%domain.communication_broadcast_time
        
    
        finalize()



#------------------------------------------------------------------------------
# Produce a documentation of parameters
#------------------------------------------------------------------------------
if myid == 0:
    parameter_file=open('parameters.tex', 'w')
    parameter_file.write('\\begin{verbatim}\n')
    from pprint import pprint
    pprint(domain.get_algorithm_parameters(),parameter_file,indent=4)
    parameter_file.write('\\end{verbatim}\n')
    parameter_file.close()

#------------------------------------------------------------------------------
# Evolve system through time
#------------------------------------------------------------------------------
for t in domain.evolve(yieldstep = 0.1, finaltime = 5.):
    #print domain.timestepping_statistics(track_speeds=True)
    if myid == 0: print domain.timestepping_statistics()


domain.sww_merge(delete_old=True)

finalize()


#

# setup and create operator within polys setting the scour base elevations for each poly
op0 = Sanddune_erosion_operator(domain,
                                base=nsbase_elev_c,
                                indices=indices_union,
                                Ra=45)  # both dunes
#op1 = sanddune_erosion_operator(domain, base=nsbase_elev_c, polygon=polygon1)   # first notched dune
#op2 = sanddune_erosion_operator(domain, base=nsbase_elev_c, polygon=polygon2)   # second plain dune

#------------------------------------------------------------------------------
# Evolve sanddune erosion simulation through time
#------------------------------------------------------------------------------
for t in domain.evolve(yieldstep=1, duration=600.0):
    if myid == 0: domain.print_timestepping_statistics()

#  run completed - tidy up
barrier()
if myid == 0:
    print ' >>>>> Simulation completed succesfully '
    print ' >>>>> Merging the individual cpu sww files and deleting the individual swws once merged'

# Merge the individual parallel swws created by the n processors
barrier()  # wait foir all processors to complete
domain.sww_merge(delete_old=False)

if myid == 0:
    print ' >>>>> Finalising the run -- all done'

# Finaise the parallel code and this model run
barrier()  # wait for all processors to complete
finalize()
예제 #5
0
else:
    domain = None

domain = anuga.distribute(domain)

#-----------------------------------------------------------------------------
# Setup boundary conditions
#------------------------------------------------------------------------------
Br = anuga.Reflective_boundary(domain)      # Solid reflective wall

# Associate boundary tags with boundary objects
domain.set_boundary({'left': Br, 'right': Br, 'top': Br, 'bottom': Br})

#-------------------------------------------------------------------------
# Produce a documentation of parameters
#-------------------------------------------------------------------------
from anuga.validation_utilities import save_parameters_tex
save_parameters_tex(domain)

#------------------------------------------------------------------------------
# Evolve system through time
#------------------------------------------------------------------------------
for t in domain.evolve(yieldstep = 0.1, finaltime = 2.0):
     if anuga.myid == 0 and verbose:
        print domain.timestepping_statistics()

domain.sww_merge(delete_old=True)

anuga.finalize()    
예제 #6
0
    domain.set_quantity('stage', height)
else:
    domain = None

domain = anuga.distribute(domain)

#-----------------------------------------------------------------------------
# Setup boundary conditions
#------------------------------------------------------------------------------
Br = anuga.Reflective_boundary(domain)      # Solid reflective wall

# Associate boundary tags with boundary objects
domain.set_boundary({'left': Br, 'right': Br, 'top': Br, 'bottom': Br})

#-------------------------------------------------------------------------
# Produce a documentation of parameters
#-------------------------------------------------------------------------
from anuga.validation_utilities import save_parameters_tex
save_parameters_tex(domain)

#------------------------------------------------------------------------------
# Evolve system through time
#------------------------------------------------------------------------------
for t in domain.evolve(yieldstep = 0.1, finaltime = 2.0):
     if anuga.myid == 0 and verbose:
         print(domain.timestepping_statistics())

domain.sww_merge(delete_old=True)

anuga.finalize()
def distibute_three_processors():
    """
    Do a parallel test of distributing a rectangle onto 3 processors

    """

    # FIXME: Need to update expected values on macos
    if sys.platform == 'darwin':
        return

    # FIXME: Need to update expected values on macos
    #if sys.platform == 'win32':
    #	return

    from anuga.utilities import parallel_abstraction as pypar

    myid = pypar.rank()
    numprocs = pypar.size()

    if not numprocs == 3:
        return

    try:
        import pymetis
        metis_version = 5
    except:
        metis_version = 4

    #print numprocs

    #barrier()

    if myid == 0:

        nodes_0, triangles_0, boundary_0 = rectangular_cross(2, 2)

        domain = Domain(nodes_0, triangles_0, boundary_0)

        domain.set_quantity('elevation',
                            topography)  # Use function for elevation
        domain.set_quantity('friction', 0.0)  # Constant friction
        domain.set_quantity('stage',
                            expression='elevation')  # Dry initial stage
        domain.set_quantity('xmomentum', expression='friction + 2.0')
        domain.set_quantity('ymomentum', ycoord)

        #----------------------------------------------------------------------------------
        # Test pmesh_divide_metis
        #----------------------------------------------------------------------------------
        vertices, triangles, boundary, triangles_per_proc, quantities = pmesh_divide_metis(
            domain, numprocs)

        if False:
            print_seq_values(vertices, triangles, triangles_per_proc)

        true_seq_values = get_true_seq_values(metis_version=metis_version)

        if False:
            print("True Seq Values = \\")
            pprint(true_seq_values)

        assert_allclose(vertices, true_seq_values['vertices'])
        assert_allclose(triangles, true_seq_values['triangles'])
        assert_allclose(triangles_per_proc,
                        true_seq_values['triangles_per_proc'])

        #----------------------------------------------------------------------------------
        # Test build_submesh
        #----------------------------------------------------------------------------------
        submesh = build_submesh(vertices, triangles, boundary, quantities,
                                triangles_per_proc)

        if False:
            print('submesh_values = \\')
            print_submesh_values(submesh)

        true_values = get_true_submesh_values(metis_version)

        assert_allclose(submesh['full_nodes'][0], true_values['full_nodes_0'])
        assert_allclose(submesh['full_nodes'][1], true_values['full_nodes_1'])
        assert_allclose(submesh['full_nodes'][2], true_values['full_nodes_2'])

        assert_allclose(submesh['ghost_nodes'][0],
                        true_values['ghost_nodes_0'])
        assert_allclose(submesh['ghost_nodes'][1],
                        true_values['ghost_nodes_1'])
        assert_allclose(submesh['ghost_nodes'][2],
                        true_values['ghost_nodes_2'])

        assert_allclose(submesh['full_triangles'][0],
                        true_values['full_triangles_0'])
        assert_allclose(submesh['full_triangles'][1],
                        true_values['full_triangles_1'])
        assert_allclose(submesh['full_triangles'][2],
                        true_values['full_triangles_2'])

        assert_allclose(submesh['ghost_triangles'][0],
                        true_values['ghost_triangles_0'])
        assert_allclose(submesh['ghost_triangles'][1],
                        true_values['ghost_triangles_1'])
        assert_allclose(submesh['ghost_triangles'][2],
                        true_values['ghost_triangles_2'])

        assert_allclose(submesh['ghost_commun'][0],
                        true_values['ghost_commun_0'])
        assert_allclose(submesh['ghost_commun'][1],
                        true_values['ghost_commun_1'])
        assert_allclose(submesh['ghost_commun'][2],
                        true_values['ghost_commun_2'])

        assert_(submesh['full_commun'] == true_values['full_commun'])

    barrier()
    #--------------------------------
    # Now do the comunnication part
    #--------------------------------

    if myid == 0:

        points, vertices, boundary, quantities, \
                    ghost_recv_dict, full_send_dict, tri_map, node_map, tri_l2g, node_l2g, \
                    ghost_layer_width =\
                    extract_submesh(submesh, triangles_per_proc)

        #----------------------------------------------------------------------------------
        # Test send_submesh
        #----------------------------------------------------------------------------------
        for p in range(1, numprocs):
            send_submesh(submesh, triangles_per_proc, p, verbose=False)
    else:
        #----------------------------------------------------------------------------------
        # Test rec_submesh
        #----------------------------------------------------------------------------------
        points, triangles, boundary, quantities, \
                ghost_recv_dict, full_send_dict, \
                no_full_nodes, no_full_trigs, tri_map, node_map, tri_l2g, node_l2g, \
                ghost_layer_width = \
                rec_submesh(0, verbose=False)

    barrier()

    #--------------------------------
    # Now do the test
    #--------------------------------
    if myid == 0:

        if False:
            print('extract_values = \\')
            print_extract_submesh(points, triangles, ghost_recv_dict, \
                                  full_send_dict, tri_map, node_map, ghost_layer_width)

        true_values = get_true_extract_submesh(metis_version)

        assert_allclose(points, true_values['points'])
        assert_allclose(triangles, true_values['triangles'])
        assert_allclose(ghost_recv_dict[1], true_values['ghost_recv_dict_1'])
        assert_allclose(ghost_recv_dict[2], true_values['ghost_recv_dict_2'])
        assert_allclose(full_send_dict[1], true_values['full_send_dict_1'])
        assert_allclose(full_send_dict[2], true_values['full_send_dict_2'])
        assert_allclose(tri_map, true_values['tri_map'])
        assert_allclose(node_map, true_values['node_map'])
        assert_allclose(ghost_layer_width, true_values['ghost_layer_width'])

    if myid == 1:

        if False:
            print("rec_submesh_1 = \\")
            print_rec_submesh_1(points, triangles, ghost_recv_dict, full_send_dict, \
                         tri_map, node_map, ghost_layer_width)

        true_values = get_true_rec_submesh_1(metis_version)

        if False:
            print('true_rec_values_1 = \\')
            pprint(true_values)

        assert_allclose(points, true_values['points'])
        assert_allclose(triangles, true_values['triangles'])
        assert_allclose(ghost_recv_dict[0], true_values['ghost_recv_dict_0'])
        assert_allclose(ghost_recv_dict[2], true_values['ghost_recv_dict_2'])
        assert_allclose(full_send_dict[0], true_values['full_send_dict_0'])
        assert_allclose(full_send_dict[2], true_values['full_send_dict_2'])
        assert_allclose(tri_map, true_values['tri_map'])
        assert_allclose(node_map, true_values['node_map'])
        assert_allclose(ghost_layer_width, true_values['ghost_layer_width'])

    if myid == 2:

        if False:
            print("rec_submesh_2 = \\")
            print_rec_submesh_2(points, triangles, ghost_recv_dict, full_send_dict, \
                         tri_map, node_map, ghost_layer_width)

        true_values = get_true_rec_submesh_2(metis_version)

        if False:
            print('true_rec_values_2 = \\')
            pprint(true_values)

        assert_allclose(points, true_values['points'])
        assert_allclose(triangles, true_values['triangles'])
        assert_allclose(ghost_recv_dict[0], true_values['ghost_recv_dict_0'])
        assert_allclose(ghost_recv_dict[1], true_values['ghost_recv_dict_1'])
        assert_allclose(full_send_dict[0], true_values['full_send_dict_0'])
        assert_allclose(full_send_dict[1], true_values['full_send_dict_1'])
        assert_allclose(tri_map, true_values['tri_map'])
        assert_allclose(node_map, true_values['node_map'])
        assert_allclose(ghost_layer_width, true_values['ghost_layer_width'])

    finalize()
예제 #8
0
def start_sim(run_id, Runs, scenario_name, Scenario, session, **kwargs):
    yieldstep = kwargs['yieldstep']
    finaltime = kwargs['finaltime']
    logger = logging.getLogger(run_id)
    max_triangle_area = kwargs['max_triangle_area']
    logger.info('Starting hydrata_project')

    if run_id == 'local_run':
        base_dir = os.getcwd()
    else:
        base_dir = os.getcwd() + '/base_dir/%s/' % run_id

    outname = run_id
    meshname = base_dir + 'outputs/' + run_id + '.msh'

    def get_filename(data_type, file_type):
        files = os.listdir('%sinputs/%s' % (base_dir, data_type))
        filename = '%sinputs/%s/%s' % (
            base_dir, data_type, [f for f in files if f[-4:] == file_type][0])
        return filename

    boundary_data_filename = get_filename('boundary_data', '.shp')
    elevation_data_filename = get_filename('elevation_data', '.tif')
    try:
        structures_filename = get_filename('structures', '.shp')
    except OSError as e:
        structures_filename = None
    try:
        rain_data_filename = get_filename('rain_data', '.shp')
    except OSError as e:
        rain_data_filename = None
    try:
        inflow_data_filename = get_filename('inflow_data', '.shp')
    except OSError as e:
        inflow_data_filename = None
    try:
        friction_data_filename = get_filename('friction_data', '.shp')
    except OSError as e:
        friction_data_filename = None

    logger.info('boundary_data_filename: %s' % boundary_data_filename)
    logger.info('structures_filename: %s' % structures_filename)
    logger.info('rain_data_filename: %s' % rain_data_filename)
    logger.info('inflow_data_filename: %s' % inflow_data_filename)
    logger.info('friction_data_filename: %s' % friction_data_filename)
    logger.info('elevation_data_filename: %s' % elevation_data_filename)

    # create a list of project files
    vector_filenames = [
        boundary_data_filename, structures_filename, rain_data_filename,
        inflow_data_filename, friction_data_filename
    ]

    # set the projection system for ANUGA calculations from the geotiff elevation data
    elevation_data_gdal = gdal.Open(elevation_data_filename)
    project_spatial_ref = osr.SpatialReference()
    project_spatial_ref.ImportFromWkt(elevation_data_gdal.GetProjectionRef())
    project_spatial_ref_epsg_code = int(
        project_spatial_ref.GetAttrValue("AUTHORITY", 1))

    # check the spatial reference system of the project files matches that of the calculation
    for filename in vector_filenames:
        if filename:
            prj_text = open(filename[:-4] + '.prj').read()
            srs = osr.SpatialReference()
            srs.ImportFromESRI([prj_text])
            srs.AutoIdentifyEPSG()
            logger.info('filename is: %s' % filename)
            logger.info('EPSG is: %s' % srs.GetAuthorityCode(None))
            if str(srs.GetAuthorityCode(None)) != str(
                    project_spatial_ref_epsg_code):
                logger.warning('warning spatial refs are not maching: %s, %s' %
                               (srs.GetAuthorityCode(None),
                                project_spatial_ref_epsg_code))

    logger.info('Setting up structures...')
    if structures_filename:
        structures = []
        logger.info('processing structures from :%s' % structures_filename)
        ogr_shapefile = ogr.Open(structures_filename)
        ogr_layer = ogr_shapefile.GetLayer(0)
        ogr_layer_feature = ogr_layer.GetNextFeature()
        while ogr_layer_feature:
            structure = json.loads(ogr_layer_feature.GetGeometryRef().
                                   ExportToJson())['coordinates'][0]
            structures.append(structure)
            ogr_layer_feature = None
            ogr_layer_feature = ogr_layer.GetNextFeature()

        logger.info('structures: %s' % structures)
    else:
        logger.warning('warning: no structures found.')
        structures = None

    logger.info('Setting up friction...')
    frictions = []
    if friction_data_filename:
        logger.info('processing frictions from :%s' % friction_data_filename)
        ogr_shapefile = ogr.Open(friction_data_filename)
        ogr_layer = ogr_shapefile.GetLayer(0)
        ogr_layer_feature = ogr_layer.GetNextFeature()
        while ogr_layer_feature:
            friction_poly = json.loads(ogr_layer_feature.GetGeometryRef().
                                       ExportToJson())['coordinates'][0]
            friction_value = float(ogr_layer_feature.GetField('mannings'))
            friction_couple = [friction_poly, friction_value]
            frictions.append(friction_couple)
            ogr_layer_feature = None
            ogr_layer_feature = ogr_layer.GetNextFeature()

        frictions.append(['All', 0.04])
        logger.info('frictions: %s' % frictions)
    else:
        frictions.append(['All', 0.04])
        logger.info('warning: no frictions found.')

    logger.info('Setting up boundary conditions...')
    ogr_shapefile = ogr.Open(boundary_data_filename)
    ogr_layer = ogr_shapefile.GetLayer(0)
    ogr_layer_definition = ogr_layer.GetLayerDefn()
    logger.info('ogr_layer_definition.GetGeomType: %s' %
                ogr_layer_definition.GetGeomType())
    boundary_tag_index = 0
    bdy_tags = {}
    bdy = {}

    ogr_layer_feature = ogr_layer.GetNextFeature()
    while ogr_layer_feature:
        boundary_tag_key = ogr_layer_feature.GetField('bdy_tag_k')
        boundary_tag_value = ogr_layer_feature.GetField('bdy_tag_v')
        bdy_tags[boundary_tag_key] = [
            boundary_tag_index * 2, boundary_tag_index * 2 + 1
        ]
        bdy[boundary_tag_key] = boundary_tag_value
        geom = ogr_layer_feature.GetGeometryRef().GetPoints()
        ogr_layer_feature = None
        ogr_layer_feature = ogr_layer.GetNextFeature()
        boundary_tag_index = boundary_tag_index + 1
        logger.info('bdy_tags: %s' % bdy_tags)
    logger.info('bdy: %s' % bdy)

    boundary_data = su.read_polygon(boundary_data_filename)

    create_mesh_from_regions(boundary_data,
                             boundary_tags=bdy_tags,
                             maximum_triangle_area=max_triangle_area,
                             interior_regions=None,
                             interior_holes=structures,
                             filename=meshname,
                             use_cache=False,
                             verbose=True)

    domain = Domain(meshname, use_cache=False, verbose=True)
    domain.set_name(outname)
    domain.set_datadir(base_dir + '/outputs')
    logger.info(domain.statistics())
    poly_fun_pairs = [['Extent', elevation_data_filename.encode("utf-8")]]
    topography_function = qs.composite_quantity_setting_function(
        poly_fun_pairs,
        domain,
        nan_treatment='exception',
    )
    friction_function = qs.composite_quantity_setting_function(
        frictions, domain)
    domain.set_quantity('friction', friction_function, verbose=True)
    domain.set_quantity('stage', 0.0)
    domain.set_quantity('elevation',
                        topography_function,
                        verbose=True,
                        alpha=0.99)
    domain.set_minimum_storable_height(0.005)

    logger.info('Applying rainfall...')
    if rain_data_filename:
        ogr_shapefile = ogr.Open(rain_data_filename)
        ogr_layer = ogr_shapefile.GetLayer(0)
        rainfall = 0
        ogr_layer_feature = ogr_layer.GetNextFeature()
        while ogr_layer_feature:
            rainfall = float(ogr_layer_feature.GetField('rate_mm_hr'))
            polygon = su.read_polygon(rain_data_filename)
            logger.info("applying Polygonal_rate_operator with rate, polygon:")
            logger.info(rainfall)
            logger.info(polygon)
            Polygonal_rate_operator(domain,
                                    rate=rainfall,
                                    factor=1.0e-6,
                                    polygon=polygon,
                                    default_rate=0.0)
            ogr_layer_feature = None
            ogr_layer_feature = ogr_layer.GetNextFeature()

    logger.info('Applying surface inflows...')
    if inflow_data_filename:
        ogr_shapefile = ogr.Open(inflow_data_filename)
        ogr_layer = ogr_shapefile.GetLayer(0)
        ogr_layer_definition = ogr_layer.GetLayerDefn()
        ogr_layer_feature = ogr_layer.GetNextFeature()
        while ogr_layer_feature:
            in_fixed = float(ogr_layer_feature.GetField('in_fixed'))
            line = ogr_layer_feature.GetGeometryRef().GetPoints()
            logger.info("applying Inlet_operator with line, in_fixed:")
            logger.info(line)
            logger.info(in_fixed)
            Inlet_operator(domain, line, in_fixed, verbose=False)
            ogr_layer_feature = None
            ogr_layer_feature = ogr_layer.GetNextFeature()

    logger.info('Applying Boundary Conditions...')
    logger.info('Available boundary tags: %s' % domain.get_boundary_tags())

    Br = anuga.Reflective_boundary(domain)
    Bd = anuga.Dirichlet_boundary([0.0, 0.0, 0.0])
    Bt = anuga.Transmissive_boundary(domain)

    for key, value in bdy.iteritems():
        if value == 'Br':
            bdy[key] = Br
        elif value == 'Bd':
            bdy[key] = Bd
        elif value == 'Bt':
            bdy[key] = Bt
        else:
            logger.info(
                'No matching boundary condition exists - please check your shapefile attributes in: %s'
                % boundary_data_filename)

    # set a default value for exterior & interior boundary if it is not already set
    try:
        bdy['exterior']
    except KeyError:
        bdy['exterior'] = Br
    try:
        bdy['interior']
    except KeyError:
        bdy['interior'] = Br

    logger.info('bdy: %s' % bdy)

    domain.set_boundary(bdy)

    domain = distribute(domain)
    logger.info('Beginning evolve phase...')
    for t in domain.evolve(yieldstep, finaltime):
        domain.write_time()
        print domain.timestepping_statistics()
        logger.info(domain.timestepping_statistics(track_speeds=True))
        percentage_complete = round(domain.time / domain.finaltime, 3) * 100
        logger.info('%s percent complete' % percentage_complete)
        if run_id != 'local_run':
            write_percentage_complete(run_id, Runs, scenario_name, Scenario,
                                      session, percentage_complete)
    domain.sww_merge(delete_old=True)
    barrier()
    finalize()
    sww_file = base_dir + '/outputs/' + run_id + '.sww'
    sww_file = sww_file.encode(
        'utf-8',
        'ignore')  # sometimes run_id gets turned to a unicode object by celery
    util.Make_Geotif(swwFile=sww_file,
                     output_quantities=['depth', 'velocity'],
                     myTimeStep='max',
                     CellSize=max_triangle_area,
                     lower_left=None,
                     upper_right=None,
                     EPSG_CODE=project_spatial_ref_epsg_code,
                     proj4string=None,
                     velocity_extrapolation=True,
                     min_allowed_height=1.0e-05,
                     output_dir=(base_dir + '/outputs/'),
                     bounding_polygon=boundary_data,
                     internal_holes=structures,
                     verbose=False,
                     k_nearest_neighbours=3,
                     creation_options=[])
    logger.info("Done. Nice work.")