Exemplo n.º 1
0
def test_heat_transfer():
    #print(os.path.dirname(__file__))  # __file__ is absolute path for python 3.4+
    df = '../data/TestHeatTransfer.json'
    settings = load_settings(df)  # load FreeCAD GUI generated json data
    #settings['case_folder'] = os.path.abspath(os.path.dirname(__file__)) + os.path.sep + "data"
    ###########################################
    """ here lot of customization can be done, e.g. 
    settings['body_source'] = dolfin.Expression('', degree=1)
    anisotropic material, convective velociyt, see examples/test_*.py for more details
    """
    ###########################################
    solver = ScalarTransportSolver.ScalarTransportSolver(settings)
    solver.print()
    solver.solve()
    solver.plot()
Exemplo n.º 2
0
def test_incompressible(using_elbow=True,
                        coupling_energy_equation=True,
                        Newtonian=True):
    s = setup(using_elbow, using_3D=False, compressible=False)
    s['solving_temperature'] = coupling_energy_equation
    # nonNewtonian diverges!

    if using_elbow:
        Re = 1e0  # see pressure change,     # stabilization_method seems make no difference
    else:
        s['fe_degree'] = 1  # test failed, can not finish JIT
        Re = 10  # mesh is good enough to simulate higher Re

    fluid = {
        'name': 'gas',
        'kinematic_viscosity': (length_scale * max_vel) / Re,
        'density': 1,
        'specific_heat_capacity': 420,
        'thermal_conductivity': 0.1,
        'Newtonian': Newtonian
    }
    s['material'] = fluid

    # Re=10 is working without G2, when Re=1e-3 got NaN error, it is not clear why
    #s['advection_settings'] = {'Re': Re, 'stabilization_method': 'G2' , 'kappa1': 4, 'kappa2': 2}
    print("Reynolds number = ", Re)

    from FenicsSolver import CoupledNavierStokesSolver
    solver = CoupledNavierStokesSolver.CoupledNavierStokesSolver(
        s)  # set a very large viscosity for the large inlet width
    #solver.using_nonlinear_solver = False
    if coupling_energy_equation:
        u, p, T = split(solver.solve())
    else:
        u, p = split(solver.solve())

    if interactively:
        solver.plot()

    if not coupling_energy_equation and False:  # not fully tested
        from FenicsSolver import ScalarTransportSolver
        solver_T = ScalarTransportSolver.ScalarTransportSolver(s)
        #Q = solver.function_space.sub(1)  # seem it is hard to share vel between. u is vectorFunction
        Q = solver_T.function_space
        cvel = VectorFunction(Q)
        cvel.interpolate(u)  # degree ?
        selver_T.convective_velocity = cvel
        T = solver_T.solve()
Exemplo n.º 3
0
    def generate_thermal_form(self, time_iter_, trial_function, test_function,
                              up_current, up_prev):
        # temperature related items for the coupled vel, pressure and temperature form
        assert not self.compressible
        u, p, T = split(trial_function)
        v, q, Tq = split(test_function)
        u_current, p_current, T_current = split(up_current)
        u_prev, p_prev, T_prev = split(up_prev)

        #translate_setting, set the FunctionSpace
        Tsettings = copy.copy(self.settings)
        Tsettings['scalar_name'] = 'temperature'
        Tsettings['mesh'] = None
        Tsettings['function_space'] = self.function_space.sub(2)
        #Tsettings['convective_velocity'] = u_current  # can not use u_trial_function
        #Tsettings['advection_settings'] = {'stabilization_method': 'SPUG', 'Pe': 1.0/(15.0/(4200*1000))}
        # it is possible to use SPUG (not rotating velocity),  with and without body source
        Tsettings['advection_settings'] = {
            'stabilization_method': 'IP',
            'alpha': 0.1
        }
        #Tsettings['body_source'] = # incomplate form?
        import pprint
        pprint.pprint(Tsettings)
        from FenicsSolver import ScalarTransportSolver
        Tsolver = ScalarTransportSolver.ScalarTransportSolver(Tsettings)
        #Tsover.is_mixed_function_space = False

        #print('type(u_current)', u_current, type(u_current))  # ufl.tensors.ListTensor
        #print('type(u)', u, type(u))
        Tsolver.convective_velocity = u_current
        #ds should be passed to Tsolver? but they are actually same
        #convection stab can be a problem!
        F_T, T_bc = Tsolver.generate_form(time_iter_, T, Tq, T_current, T_prev)
        #inner() ufl.log.UFLException: Shapes do not match: <Sum id=140066973439888> and <ListTensor id=140066973395664>.
        tau = self.viscosity() * (grad(u) + grad(u).T)
        #sigma = tau- p*Identity(self.dimension)  # u_current for both nonlinear and picard loop
        epsdot = 0.5 * (grad(u) + grad(u).T)
        viscous_heating = inner(epsdot, tau)  # method 1
        #viscous_heating =  2 * self.viscosity(up_current) * tr(dot(epsdot, epsdot))
        print('type of viscous heating:', type(viscous_heating))
        ## sigma * u:  heat flux,  sigma * grad(u) heat source
        #F_T -= viscous_heating *Tq*dx  # need sum to scalar!
        return F_T, T_bc
Exemplo n.º 4
0
def solve_ht():
    #  error for inf temperature field if all BC are HTC
    bcs = {
            "work": { 'boundary_id': work_htc_boundary_id, 'values': {
                                'temperature': {'variable': "temperature", 'type': 'HTC', 'value': htc_work, 'ambient': T_ambient } } },
            "cutter":  {'boundary_id': cutter_htc_boundary_id, 'values': {
                                #'temperature': {'variable': "temperature", 'type': 'Dirichlet', 'value': T_ambient + 200} } },
                                'temperature': {'variable': "temperature", 'type': 'HTC', 'value': htc_cutter, 'ambient': T_ambient } } },
            "holder":  {'boundary_id': holder_htc_boundary_id, 'values': {
                                'temperature': {'variable': "temperature", 'type': 'HTC', 'value': htc_holder, 'ambient': T_ambient} } },
                                #'temperature': {'variable': "temperature", 'type': 'Dirichlet', 'value': T_ambient + 20} } },
            "chip":  { 'boundary_id': chip_htc_boundary_id, 'values': {
                                'temperature': {'variable': "temperature", 'type': 'HTC', 'value': htc_chip, 'ambient': T_ambient } } },
            #other are zero heat flux
    }

    settings = {'solver_name': 'ScalarEquationSolver',
                    'mesh': mesh_file, 'function_space': None,  'fe_degree': element_degree, 'periodic_boundary': pbc,
                    'boundary_conditions': bcs,
                    'body_source': None,
                    'initial_values': {'temperature': T_ambient},
                    'material': material_work,
                    'solver_settings': {
                        'transient_settings': {'transient': False, 'starting_time': 0, 'time_step': 0.01, 'ending_time': 0.03},
                        'reference_values': {'temperature': T_ambient},
                        'solver_parameters': {"relative_tolerance": 1e-8,  # mapping to solver.parameters of Fenics
                                                        'absolute_tolerance': 1E-9,
                                                        "maximum_iterations": 500,
                                                        "relaxation_parameter": 0.3,  # case 6 , case 8 failed to converge
                                                        "monitor_convergence": True,  # print to console
                                                        },
                        },
                    # solver specific settings
                    'scalar_name': 'temperature',
                    "convective_velocity": None,
                    #'radiation_settings': {'ambient_temperature': T_ambient-20, 'emissivity': 0.9}
                    }
    if using_stab:  # stab is not necessary if mapping boundary is used
        settings['advection_settings'] = {'stabilization_method': 'IP', 'alpha': IP_alpha, 'Pe':  Pe/1000.0}
    if considering_radiation:
        settings['radiation_settings']= {'ambient_temperature': T_ambient, 'emissivity': emissivity}

    import time  # dolfin has time function
    ts_before_preprocessing = time.time()
    solver = ScalarTransportSolver.ScalarTransportSolver(settings)

    Q = solver.function_space
    vector_degree = element_degree+1
    V = VectorFunctionSpace(solver.mesh, 'CG', vector_degree)
    v_e = Expression(cppcode=velocity_code, degree=vector_degree)  # degree, must match function space
    v_e.subdomain_id = solver.subdomains
    velocity = Function(V)
    velocity.interpolate(v_e)  # does not work for MPI
    if has_convective_velocity:
        #solver.convective_velocity = Constant((1, 1, 0))
        solver.convective_velocity = velocity

    #DG for conductivity and heat source, save to hdf5?
    DG0 = FunctionSpace(solver.mesh, 'DG', 0)  # can not mixed higher order CG and DG?
    unity = Function(DG0)
    unity.vector()[:] = 1 #interpolate(Expression('1', element_degree), DG0)


    # Numerical simulations of advection-dominated scalar mixing with applications to spinal CSF flow and drug transport
    if not using_nonlinear_thermal_properties:
        k_f = get_material_function(DG0, solver.subdomains, material_k_list)
        solver.material['thermal_conductivity'] = k_f
        capacity_f = get_material_function(DG0, solver.subdomains, material_capacity_list)
        solver.material['capacity'] = capacity_f

    #################################################
    shear_heat, friction_heat = get_heat_source()
    shear_heat_density = shear_heat / shear_heat_volume  # W/m3
    friction_heat_density = friction_heat / nominal_friction_heat_volume
    print("heating volume and intensity; ", shear_heat_volume, friction_heat_volume, shear_heat_density, friction_heat_density)
    if True:  # test passed, non-uniform heat source can be implemented later
        class HeatSourceExpression(Expression):
            def eval_cell(self, values, x, ufc_cell):
                cindex = ufc_cell.index
                did = solver.subdomains[cindex]
                if did == shear_subdomain_id:
                    values[0] = shear_heat_density
                elif did == friction_subdomain_id:
                    if nonuniform_friction_heat:
                        distance = math.sqrt(x[0]*x[0] + x[1]*x[1])
                        _start = (friction_heat_start + uniform_friction_heat_length)
                        if distance <= _start:
                            values[0] = friction_heat_density * uniform_heat_ratio
                        else:
                            _ratio_l = (distance -_start)/(actual_friction_heat_distance - _start)
                            _ratio_h = uniform_heat_ratio * (1 - _ratio_l)
                            values[0] = friction_heat_density * _ratio_h
                    if nonuniform_friction_thickness:
                        distance = math.sqrt(x[0]*x[0] + x[1]*x[1])
                        _start = friction_heat_start
                        _ratio_l = (distance -_start)/actual_friction_heat_length
                        _ratio_h = 1.0 / ( 1.0 - _ratio_l * ( 1.0 - friction_end_thickness/friction_heat_thickness))
                        values[0] = friction_heat_density * _ratio_h
                    else:
                        values[0] = friction_heat_density
                else:
                    values[0] = 0
        # FIXME: cause error of UFLException: Discontinuous type Coefficient must be restricted.
        v_s = HeatSourceExpression(degree = element_degree)  # degree, must match function space
        heat_source = Function(DG0)
        heat_source.interpolate(v_s)  # does not work for MPI
        solver.body_source = heat_source
    else:
        heat_source_list = [0] * subdomain_number
        heat_source_list[shear_subdomain_id] = shear_heat_density
        heat_source_list[friction_subdomain_id] = friction_heat_density
        heat_source = get_material_function(DG0, solver.subdomains, heat_source_list)  # does not work for MPI
        solver.body_source =  heat_source
    '''
    solver.boundary_facets.set_all(0)
    for facet in facets(solver.mesh):
        for cell in cells(facet):
            #print(f.index(), c.index())
            if facet.exterior() and solver.subdomains[cell.index()] < 5:
                solver.boundary_facets[facet.index()] = solver.subdomains[cell.index()]
    '''

    if exporting_foam:
        #preset boundary value
        #DG vector space
        #v_e = Expression(cppcode=velocity_code, degree=vector_degree)  # degree, must match function space?
        #v_e.subdomain_id = solver.subdomains
        #print(velocity_code)

        DGV = VectorFunctionSpace(solver.mesh, 'DG', 0)
        velocity = Function(DGV)
        velocity.interpolate(v_e)  # does not work for MPI
        print(' chip volocity = ({}, {}, {})'.format(chip_sliding_vel_x, chip_sliding_vel_y, 0))
        print(' work volocity = ({}, {}, {})'.format(cutting_speed, 0, 0))

        sys.path.append('/media/sf_OneDrive/gitrepo/VTKToFoam')
        from VTKToFoam import set_internal_field, write_field
        foam_data_folder = '0/'
        #foam_data_folder = '0.origin/'

        #print('size of DG0 vector numpy array', velocity.vector().array().shape)  # correct 1D array
        #print('size of DG0 scalar numpy array', heat_source.vector().array().shape)
        # need to regerate mesh and transform mesh even if boundary, subdomain id changed.
        foam_velocity_data_file = foam_data_folder + foam_data_folder + 'U'
        set_internal_field(foam_velocity_data_file, velocity.vector().array(), 'vector',  foam_data_folder + '0.origin/U')

        # icoThermoFoam TEqn  accepts  power/capacity as source
        #set_internal_field(foam_data_folder + 'S', heat_source.vector().array()/metal_capacity_0, 'scalar', foam_base_folder + 'S')  # if not existing, write_field()
        print("set heat source density in system/")
        sys.exit()

    #
    holder_top_boundary = AutoSubDomain(lambda x, on_boundary: \
                                        near(x[1], holder_top_y) and on_boundary)
    holder_top_boundary.mark(solver.boundary_facets, holder_top_boundary_id)
    solver.boundary_conditions['holder_top'] = { 'boundary_id': holder_top_boundary_id, 'values': {
                    'temperature': {'variable': "temperature", 'type': 'Dirichlet', 'value': T_holder_top } } }
    #
    if is_straight_chip:
        chip_end_boundary = AutoSubDomain(lambda x, on_boundary: \
                    near(x[1], chip_top_y) and on_boundary)
    else:
        chip_end_boundary = AutoSubDomain(lambda x, on_boundary: \
                    near(x[0], chip_center_x) and (x[1]>0) and on_boundary)

    chip_end_boundary.mark(solver.boundary_facets, chip_end_boundary_id)
    if not has_convective_velocity:
        solver.boundary_conditions['chipend_htc'] = { 'boundary_id': chip_end_boundary_id, 'values': {
                    'temperature': {'variable': "temperature", 'type': 'HTC', 'value':  htc_chip, 'ambient':T_ambient } } }

    if has_convective_velocity:
        if using_mapping_bc: # test if bc is found
            # those are used in post-processing, PeriodicBoundary should be done in function space creation
            mapping_periodic_boundary = PeriodicBoundary()
            mapping_periodic_boundary.mark(solver.boundary_facets, mapping_boundary_id, check_midpoint = True)
            #
            shear_interface = TurningInterface()  # chip side
            shear_interface.mark(solver.boundary_facets, mapped_shear_boundary_id, check_midpoint = True)
            #
            if using_chip_cutter_mapping_bc:
                friction_interface = FrictionalInterface()  # cutter side
                friction_interface.mark(solver.boundary_facets, mapped_friction_boundary_id, check_midpoint = True)

        if workpiece_type_id == 0 or workpiece_type_id == 2:  # rect work shape
            work_bottom_boundary = AutoSubDomain(lambda x, on_boundary: near(x[1], work_bottom_y) and on_boundary)
            work_bottom_boundary.mark(solver.boundary_facets, work_bottom_boundary_id)
            #
            work_left_boundary_id = work_bottom_boundary_id+1
            work_left_boundary = AutoSubDomain(lambda x, on_boundary: near(x[0], work_left_x) and x[1]<feed_thickness+DOLFIN_EPS and on_boundary)
            work_left_boundary.mark(solver.boundary_facets, work_left_boundary_id)
            #
            work_right_boundary = AutoSubDomain(lambda x, on_boundary: near(x[0], work_right_x) and x[1]<feed_thickness+DOLFIN_EPS and on_boundary)
            work_right_boundary.mark(solver.boundary_facets, work_bottom_boundary_id+2)
            #right are natural boundary, zero gradient, or it does not mater
            solver.boundary_conditions['work_inlet'] = { 'boundary_id': work_left_boundary_id, 'values': {
                                        'temperature': {'variable': "temperature", 'type': 'Dirichlet', 'value': T_work_inlet } } }
            #solver.boundary_conditions['work_bottom'] = { 'boundary_id': work_bottom_boundary_id, 'values': {
            #                            'temperature': {'variable': "temperature", 'type': 'Dirichlet', 'value': T_ambient } } }
        elif workpiece_type_id == 1:
            disc_center_y = - disc_R
            work_clamp_boundary_id = 8
            work_clamp_boundary = AutoSubDomain(lambda x, on_boundary: \
                between(x[0], (-disc_R*0.05, disc_R*0.05)) \
                and between(x[1], (disc_center_y-disc_R*0.1, disc_center_y+disc_R*0.1)) and on_boundary)
            work_clamp_boundary.mark(solver.boundary_facets, work_clamp_boundary_id)
            solver.boundary_conditions['work_clamp'] = { 'boundary_id': work_clamp_boundary_id, 'values': {
                                        'temperature': {'variable': "temperature", 'type': 'Dirichlet', 'value': T_ambient } } }
        else:
            raise NotImplementedError('this work piece shape id is not supported')


    def update_material_property(DG0, subdomains, material_values, T_DG0, cutter_f, metal_f):
        # it takes very long time to complete if set individually
        did = np.asarray(subdomains.array(), dtype=np.int32)
        u = Function(DG0)
        u.vector()[:]  = metal_f(T_DG0.vector().get_local())  # return numpy.array
        #print(type(u_new), u_new.size, u.vector().array().size)

        u.vector()[did == cutter_subdomain_id]  = cutter_f(T_DG0.vector().get_local())[did == cutter_subdomain_id]
        #u.vector()[did == cutter_subdomain_id] = cutter_value
        """
        for i, v in enumerate(did):
            if (v == cutter_subdomain_id):
                u.vector()[i] = cutter_value
            else:
                u.vector()[i] = metal_f(T_DG0.vector().array()[i])
        """
        return u

    ts_after_preprocessing = time.time()
    # my own nonliner loop, updating material property in each iteration
    if using_nonlinear_loop:  # 3D looping is possible
        assert not using_MPI  # setting material property may not working in parallel
        loop_i = 0
        loop_N = 10
        T_old = Function(solver.function_space)  #default to zero, Yes
        while loop_i < loop_N:
            T = solver.solve()
            T_mean_diff = np.mean(T_old.vector()[:] - T.vector()[:])
            print("=== nonlinear loop ", loop_i, " ======= ", T_mean_diff)
            if  math.fabs(T_mean_diff) < 0.1:
                break
            T_DG0 = project(T, DG0)
            capacity_field = update_material_property(DG0, solver.subdomains, material_capacity_list, T_DG0, cutter_capacity_f, metal_capacity_f)
            solver.material['capacity'] = capacity_field
            k_field = update_material_property(DG0, solver.subdomains, material_k_list, T_DG0, cutter_k_f, metal_k_f)
            solver.material['thermal_conductivity'] = k_field
            #solver.solve() has considered dc/dT        ignore dk/dT
            #to trigger this condition: if 'dc_dT' in self.material:

            T_old.vector()[:] = T.vector()[:]
            loop_i += 1
            #ofile = File(result_folder + "k.pvd")  # not for parallel
            #ofile << solver.material['thermal_conductivity']
    else:
        T = solver.solve()

    ts_before_postprocessing = time.time()
    if using_MPI:
        mf = HDF5File(mpi_comm_world(),  result_folder + "metal_cutting_result" +'.h5', 'w')
        #mf = XDMFFile(mpi_comm_world(), result_name+'.xdmf')
        mf.write(T, "T")
        mf.write(V.mesh(), "mesh")
        #post processing is not possible in parallel?
        sys.exit()
    else:
        #interpolate the higher order solution onto a finer linear space
        T.rename("Temperature (C)", "temperature contour")
        ofile = File(result_folder + "T.pvd")  # not for parallel
        ofile << T
    
    ############################### post-processing#####################

    is_simulation_valid = True
    error_message = ''
    dx= Measure("dx", subdomain_data=solver.subdomains)
    print("================= summary =================")
    print("shear_heat, friction_heat by definition", shear_heat, friction_heat)
    volume_shear_zone = assemble(unity*dx(shear_subdomain_id))
    volume_friction_zone = assemble(unity*dx(friction_subdomain_id))
    print("volume_shear_zone, volume_friction_zone by integral:", volume_shear_zone, volume_friction_zone)

    heat_source = solver.body_source
    heat1 = assemble(heat_source*dx(shear_subdomain_id))
    heat2 = assemble(heat_source*dx(friction_subdomain_id))
    heat_chip = assemble(heat_source*dx(chip_subdomain_id))
    print("shear_heat, friction_heat, heating by chip (should be zero) by integration", heat1, heat2, heat_chip)
    total_heat = assemble(heat_source*dx)
    if using_3D:
        heat_ratio = total_heat/(shear_heat + friction_heat)
    else:
        #total_heat = total_heat*cutter_thickness
        heat_ratio = total_heat*cutter_thickness/(shear_heat + friction_heat)
    print("total_heat, ratio of heat integral to the defined", total_heat,  heat_ratio)
    if math.fabs(heat_ratio - 1) > validation_tol:
        error_message += "heat generation != heat source integration"
        is_simulation_valid = False

    print("========== heat loss from HTC =================")
    ds= Measure("ds", subdomain_data=solver.boundary_facets)
    # radiation is not added into cooling
    cooling_frictinal_zone = assemble(htc_work*(T-T_ambient)*ds(friction_subdomain_id))
    cooling_shear_zone = assemble(htc_work*(T-T_ambient)*ds(shear_subdomain_id))
    print("cooling from friction and shear zone surface",  cooling_frictinal_zone, cooling_shear_zone)
    cooling_work = assemble(htc_work*(T-T_ambient)*ds(work_htc_boundary_id))
    cooling_chip = assemble(htc_chip*(T-T_ambient)*ds(chip_htc_boundary_id))
    cooling_tool = assemble(htc_cutter*(T-T_ambient)*ds(cutter_htc_boundary_id) +
                                          htc_holder*(T-T_ambient)*ds(holder_htc_boundary_id))
    cooling_total_HTC = cooling_work + cooling_chip + cooling_tool + cooling_frictinal_zone + cooling_shear_zone 
    cooling_HTC_all = assemble(htc_work*(T-T_ambient)*ds)  # not all surface are HTC
    print("convective cooling_work, cooling_chip, cooling_tool and holder, cooling_HTC_all",\
                cooling_work, cooling_chip, cooling_tool, cooling_HTC_all)
    print("ratio of HTC loss to generation", cooling_total_HTC/total_heat)
    total_heat_loss = cooling_total_HTC

    if considering_radiation:
        Stefan_constant = 5.670367e-8  # W/m-2/K-4
        m_ = emissivity * Stefan_constant
        radiation_outflux = - m_*(T_ambient**4 - pow(T, 4))
        R_work = assemble(radiation_outflux*ds(work_htc_boundary_id))
        R_chip = assemble(radiation_outflux*ds(chip_htc_boundary_id))
        R_tool = assemble(radiation_outflux*ds(cutter_htc_boundary_id) +
                                              radiation_outflux*ds(holder_htc_boundary_id))
        R_total = R_work + R_chip + R_tool
        R_all_surfaces = assemble(radiation_outflux*ds)
        total_heat_loss += R_total
        print("radiative: cooling_work, cooling_chip, cooling_tool and holder", R_work, R_chip, R_tool, R_all_surfaces)
        if math.fabs(R_total / R_all_surfaces- 1) > 0.3:
            error_message += "radiation sum != radiation integration"
            is_simulation_valid = False
        print("ratio of heat generation radiative cooling",  R_total/total_heat, R_all_surfaces/total_heat)
    else:
        R_total = 0


    T_DG0 = interpolate(T, DG0)
    did = np.asarray(solver.subdomains.array(), dtype=np.int32)
    T_DG0.vector()[did != shear_subdomain_id] = 0.0
    Tmax_shear_zone = np.max(T_DG0.vector().array())
    # defined in salome_parameter.py, failed in 2D for v2017.2
    try:
        p_probe = Point(*point_shear_surface_center_coordinates)
        T_probe = T(p_probe)
    except:
        T_probe = 0

    Tmean_shear_zone = assemble(T*dx(shear_subdomain_id))/volume_shear_zone
    Tmean_friction_zone = assemble(T*dx(friction_subdomain_id))/volume_friction_zone
    Tmean_chip_zone = assemble(T*dx(chip_subdomain_id))/chip_volume
    Tmax_friction_zone = np.max(T.vector().array())

    T_analytical_shear_zone,  T_analytical_friction_zone = get_analytical_T()
    print('================ temperature prediction by FEA ==================')
    print('Tmean_shear_zone = ', Tmean_shear_zone)
    print('Tmax_shear_zone = ', Tmax_shear_zone, "T_probe = ", T_probe)
    print('Tmean_chip_zone = ', Tmean_chip_zone)
    print('Tmean_friction_zone = ', Tmean_friction_zone)
    print('Tmax_friction_zone = ', Tmax_friction_zone)


    if has_convective_velocity:
        #chip material flow taking away from system, how to get T_chip_end, (chip_cross_area*chip_velocity) is known
        #capacity = metal_density*metal_cp
        chip_speed = cutting_speed * (feed_thickness/chip_thickness)
        #QC = capacity * chip_speed* (cutter_thickness * chip_thickness)
        #print("outflow heat = %f * delta_T"%QC)
        #T_chip_end_center = T[p_chip_end_xyz]
        surface_normal = FacetNormal(solver.mesh)

        #area_shear_zone = assemble(unity*ds(shear_subdomain_id))
        #area_friction_zone = assemble(unity*ds(friction_subdomain_id))
        #print('area_shear_zone = ', area_shear_zone, "should ==", 2*l_AB * shear_heat_thickness + shear_heat_thickness*cutter_thickness)
        #print('area_friction_zone = ', area_friction_zone, "should ==", 2*actual_friction_heat_length * friction_heat_thickness)

        if using_nonlinear_loop:
            _capacity = solver.material['capacity']
            _conductivity = solver.material['thermal_conductivity']
        elif using_nonlinear_thermal_properties:  #CG, cutter has same material with metal,  no difference
            #T_DG0 = interpolate(T, DG0)
            #_capacity = update_material_property(DG0, solver.subdomains, material_capacity_list, T_DG0, material_cutter['capacity'], metal_capacity_f)
            #_conductivity = update_material_property(DG0, solver.subdomains, material_k_list, T_DG0, material_cutter['capacity'], metal_k_f)
            _capacity = metal_capacity_f(T)
            _conductivity = metal_k_f(T)
        else:
            _capacity = Constant(metal_density*metal_cp_0)
            _conductivity = metal_k_0
        _density = metal_density

        system_heat_capacity = assemble((T-Constant(T_reference)) * _capacity*_density*dx)
        print("characteristic time = ", system_heat_capacity/total_heat)  # too big not quite usful

        if using_mapping_bc:
            #validation
            print('============ mapping bc validation ================')
            if using_3D:
                _area_theory = l_AB*cutter_thickness
            else:
                _area_theory = l_AB
            area_mapping = assemble(unity*ds(mapping_boundary_id))
            print('area mapping bounadry and theroetical (should be equal): ', area_mapping, _area_theory)
            area_mapped_shear = assemble(unity*ds(mapped_shear_boundary_id))

            if using_workpiece_extra_extusion:
                if math.fabs(area_mapping / (_area_theory) - 1) > validation_tol:
                    error_message += "\n mapping interface area is not equal\n"
                    is_simulation_valid = False

            M_mapping = assemble(dot(surface_normal, velocity)*ds(mapping_boundary_id))  # not equal, why?
            print('mass flux from mapping bounadries vs in theory (should be equal): ', M_mapping, cutting_speed*cutter_thickness*feed_thickness)
            Q_mapping_bottom = assemble(dot(surface_normal, velocity) * (T-T_reference)*_capacity*ds(mapping_boundary_id))
            Q_mapping_top = assemble(dot(surface_normal, velocity) * (T-T_reference)*_capacity*ds(mapped_shear_boundary_id))
            print('heat flux from mapping bounadries (should be equal): ', Q_mapping_top, Q_mapping_bottom)
            if using_workpiece_extra_extusion:
                if math.fabs(math.fabs(Q_mapping_top / Q_mapping_bottom) - 1) > validation_tol:
                    error_message += "\n heat flux on mapping interfaces area is not equal\n"
                    is_simulation_valid = False

            Q_mapping_work = assemble(_conductivity*dot(grad(T), surface_normal) * ds(mapping_boundary_id)) * -1
            Q_mapping_chip = assemble(_conductivity*dot(grad(T), surface_normal) * ds(mapped_shear_boundary_id)) * -1
            print("conductive heat transfer, Q_mapping_work, Q_mapping_chip", Q_mapping_work, Q_mapping_chip)
            if using_chip_cutter_mapping_bc:
                area_mapped_friction = assemble(unity*ds(mapped_friction_boundary_id))
                print('area for a chip-cutter fricitonal interface  and theroetical value (should be equal): ', \
                        area_mapped_friction, actual_friction_heat_length*cutter_thickness)  # 2D has diff area
                print('total area for mapping (should be equal to), and sum of cutter and work', \
                        area_mapping, area_mapped_friction+area_mapped_shear)

        print('============ heat with mass flow ================')
        # heat loss due to mass flow, should be zero
        _tmp = dot(surface_normal, velocity) * (T-Constant(T_reference)) * _capacity
        Q_friction = assemble(_tmp*ds(friction_subdomain_id))
        print("heat flow with mass out of system from friction zone is:", Q_friction)
        Q_shearing = assemble(_tmp*ds(shear_subdomain_id))
        print("heat flow with mass out of system from sheairing zone is:", Q_shearing)

        Q_chip = assemble(_tmp*ds(chip_htc_boundary_id))
        print("heat flow with mass out of system from chip htc boundary is:", Q_chip)
        Q_work = assemble(_tmp*ds(work_htc_boundary_id))
        print("heat flow with mass out of system from work htc boundary (should be zero)is:", Q_work)
        Q_cutter = assemble(_tmp*ds(cutter_htc_boundary_id))
        print("heat flow with mass out of system from cutter htc is:", Q_cutter)
        Q_holder = assemble(_tmp*ds(holder_htc_boundary_id))
        print("heat flow with mass out of system from holder htc is:", Q_holder)
        Q_chipend = assemble(_tmp*ds(chip_end_boundary_id))
        print("heat flow with mass from  chipend", Q_chipend)
        if has_friction_transition_zone:  # no using mapping bc for friction contact
            Q_friction_transition_subdomain = assemble(_tmp*ds(friction_transition_subdomain_id))
            print("heat flow with mass from friction_transition_subdomain", Q_friction_transition_subdomain)
        Q_ds0 = assemble(_tmp*ds(0))
        print("heat flow with mass from  ds(0), should be zero", Q_ds0)
        total_heat_loss += Q_chipend  # Q_material_out_sum,  excluding the workpiece outlet

        """
        print('============ mass flow ================')
        M_shearing = assemble(dot(surface_normal, velocity)*ds(shear_subdomain_id))
        print("mass flow out of system from shearing zone boundary  is:", M_shearing)
        M_friction = assemble(dot(surface_normal, velocity)*ds(friction_subdomain_id))
        print("mass flow out of system from friction boundary is:", M_friction)
        M_work = assemble(dot(surface_normal, velocity)*ds(work_htc_boundary_id))
        print("mass flow out of system from work  is:", M_work)
        M_chip = assemble(dot(surface_normal, velocity)*ds(chip_htc_boundary_id))
        print("mass flow out of system from chip HTC boundary is:", M_chip)
        M_chipend = assemble(dot(surface_normal, velocity)*ds(chip_end_boundary_id))
        print("mass flow from chip end in theory VS integral at chipend", chip_speed * (cutter_thickness * chip_thickness), M_chipend)
        """

        Q_material_out = assemble(_tmp*ds)
        if workpiece_type_id == 0:  #rectangle
            #Q_bottom = cutting_speed * capacity *ds(work_bottom_boundary_id)
            Q_inflow = assemble( (T-Constant(T_reference))* cutting_speed * _capacity * ds(work_bottom_boundary_id + 1))
            #* cutter_thickness * (chip_start_y - work_bottom_y)
            Q_outflow = assemble( (T-Constant(T_reference))* cutting_speed * _capacity * ds(work_bottom_boundary_id + 2))
            print('inflow from left boundary and outflow from right bounadry is', Q_inflow, Q_outflow)

            Q_bottom = assemble(_conductivity*dot(grad(T), surface_normal)*ds(work_bottom_boundary_id)) * -1 #flow out

            if using_workpiece_extra_extusion:
                Q_flowing_out_work = (Q_material_out  - Q_chipend) + Q_bottom
            else:
                Q_flowing_out_work = (Q_outflow - Q_inflow) + Q_bottom
            print('heat flow out from work oulet and bottom is:', Q_flowing_out_work)
            total_heat_loss += Q_flowing_out_work
        else:  # disc clamping
            Q_clamp = assemble(_conductivity*dot(grad(T), surface_normal)*ds(work_clamp_boundary_id)) * -1 #flow out
            print('heat flux from clamp bounadry is', Q_clamp)
            Q_flowing_out_work = Q_clamp + cooling_work
            total_heat_loss += Q_flowing_out_work

        Q_material_out_sum = Q_chip + Q_work + Q_cutter + Q_holder + Q_chipend + Q_outflow + Q_inflow
        print("heat flow with mass out of system from all boundary byintegral (ds) and sum:",  Q_material_out, Q_material_out_sum)
        if math.fabs(Q_material_out_sum/Q_material_out - 1.0) > validation_tol*0.2:
            error_message += "\n heat flow with mass out of system from all boundary byintegral (ds) and sum is not equal\n"
            is_simulation_valid = False

        print('=============== conduction heat loss ===========')
        Q_conduction_all = assemble(_conductivity*dot(grad(T), surface_normal) * ds) * -1  # surface_normal pointing out
        Qk_chipend = assemble(_conductivity*dot(grad(T), surface_normal)*ds(chip_end_boundary_id)) * -1# flow out
        print('conductive heat flux from bottom and chipend bounadries', Q_bottom, Qk_chipend)   #should be set zero-flux
        Qk_holder_top = assemble(_conductivity*dot(grad(T), surface_normal) * ds(holder_top_boundary_id)) * -1 #flow out
        Q_conduction_work = assemble(_conductivity*dot(grad(T), surface_normal) * ds(work_subdomain_id)) * -1
        Q_conduction_chip = assemble(_conductivity*dot(grad(T), surface_normal) * ds(chip_subdomain_id)) * -1
        Q_conduction_tool = assemble(_conductivity*dot(grad(T), surface_normal) * ds(holder_subdomain_id)) * -1 +\
                                        assemble(_conductivity*dot(grad(T), surface_normal) * ds(cutter_subdomain_id)) * -1
        Qk_bottom = assemble(_conductivity*dot(grad(T), surface_normal) * ds(work_bottom_boundary_id)) * -1
        Qk_inlet= assemble(_conductivity*dot(grad(T), surface_normal) * ds(work_bottom_boundary_id + 1)) * -1
        Qk_outlet= assemble(_conductivity*dot(grad(T), surface_normal) * ds(work_bottom_boundary_id + 2)) * -1
        print('conductive heat at holder top, Qk_bottom, Qk_inlet, Qk_outlet, conduction all is',\
                    Qk_holder_top, Qk_bottom, Qk_inlet, Qk_outlet, Q_conduction_all)
        print('Q_conduction_tool, Q_conduction_work, Q_conduction_chip',\
                  Q_conduction_tool, Q_conduction_work, Q_conduction_chip)
        total_heat_loss += Qk_holder_top

        print('=============== heat loss ratios ===========')
        Q_baseline = total_heat_loss
        print("convection, radiation, Q_flowing_out_work, Q_bottom, Q_chipend, Q_holder_top")
        _ratios = np.array([cooling_total_HTC, R_total, Q_flowing_out_work, Q_bottom, Q_chipend, Qk_holder_top])/Q_baseline
        print(_ratios)

        Q_total_passing_friction_interface = (Qk_holder_top + cooling_tool)
        partition_coeff_shear = 1.0 -  Q_flowing_out_work/ shear_heat
        partition_coeff_friction = 1.0 -  Q_total_passing_friction_interface/ friction_heat
        print("partition_coeff_shear, partition_coeff_friction = ", partition_coeff_shear, partition_coeff_friction)

    print("ratio of total heat loss {}, heat generation {}, ratio {}"\
            .format(total_heat_loss, total_heat, total_heat_loss/total_heat))
    if math.fabs(total_heat_loss/total_heat - 1.0) > validation_tol:
        error_message += "\n heat generaton and heat loss are not equal\n"
        is_simulation_valid = False

    ts_after_post_processing = time.time()
    print('=============== time consumption ===========')
    print('preprocessing time', ts_after_preprocessing - ts_before_preprocessing)
    print('assembling and LA solving time', ts_before_postprocessing - ts_after_preprocessing)

    if using_debug:
        #plot(solver.boundary_facets)
        bfile = File(result_folder + "boundary.pvd")  # not working for parallel
        bfile << solver.boundary_facets
    if using_debug:
        ofile = File(result_folder + "HeatSource.pvd")  # not for parallel
        ofile << solver.body_source   #it is possible to save DG0 data

    if has_convective_velocity and using_debug:
        ofile = File(result_folder + "U.pvd")  # not for parallel, using hdf5 for parallel
        ofile << velocity

    if not is_batch_mode:
        if using_VTK:
            plot(solver.boundary_facets, title = "boundary id")
            plot(solver.subdomains, title = "subdomain id")
            plot(heat_source, title = "heat source density (W/m3)")
            #plot(solver.material['specific_heat_capacity'], title = "specific_heat_capacity")  # can not plot DG
            plot(velocity, title = "pseudo convective velocity")
            interactive()
            solver.plot()  # plt.legend() does not work: warnings.warn("No labelled objects found. "
        else:
            import matplotlib.pyplot as plt
            solver.plot()
            plt.show()
            #`paraview --data=T.pvd`

    if is_simulation_valid == False:
        print("======== the simulation is INVALID ! ==================")
        print(error_message)
        #if not using_workpiece_extra_extusion:
        #    sys.exit()
    else:
        print("======== the simulation is completed sucessfully ==================")


    thermal_number = metal_density * metal_cp_f(T_analytical_shear_zone) * cutting_speed * feed_thickness / metal_k_f(T_analytical_shear_zone)
    partition_coeff_eq = get_heat_partition(thermal_number)
    #also save thermal_number
    from datetime import datetime
    time_stamp  = datetime.utcnow().isoformat()
    parameters = [time_stamp, case_id, workpiece_type_id, 
        feed_thickness, chip_thickness, cutter_angle_v, shear_angle, cutting_speed, F_cutting, F_thrush,
        shear_heat_thickness, friction_heat_thickness, tool_chip_interface_length,
        T_ambient,  T_analytical_shear_zone, T_analytical_friction_zone,
        Tmean_shear_zone, Tmax_shear_zone, Tmean_friction_zone, Tmax_friction_zone,
        thermal_number, partition_coeff_eq, partition_coeff_shear, partition_coeff_friction, is_simulation_valid]

    output = ",".join([str(v) for v in parameters])
    with open(result_filename, 'a') as wf:
        wf.write(output)
        wf.write('\n')

    if extracting_data:
        #sys.path.append('/opt/fenicstools/')  # this package must be installed
        from fenicstools import Probes
        dist = np.linspace(0, tool_chip_interface_length, 20)
        probing_pts = np.array([(math.sin(cutter_angle_v*pi/180)*l, math.cos(cutter_angle_v*pi/180)*l, 0) for l in dist])
        probes = Probes(probing_pts.flatten(), solver.function_space)
        probes(T)  # evaluate f at all probing points
        extracted_datafile = result_folder + datafile_root + '__' + param_name + '__' + str(param_value) + '.csv'
        np.savetxt(probes.array(), extracted_datafile)
        print(probes.array())