Esempio n. 1
0
    def __init__(self, physical_system, performance_test_flag = False):
        """
        Constructor for the nonlinear_solver object. It takes the physical
        system object as an argument and uses it in intialization and
        evolution of the system in consideration. 

        Additionally, a performance test flag is also passed which when true,
        stores time which is consumed by each of the major solver routines.
        This proves particularly useful in analyzing performance bottlenecks 
        and obtaining benchmarks.
        
        Parameters:
        -----------

        physical_system: object
                         The defined physical system object which holds
                         all the simulation information such as the initial
                         conditions, and the domain info is passed as an
                         argument in defining an instance of the
                         nonlinear_solver. This system is then evolved, and
                         monitored using the various methods under the
                         nonlinear_solver class.

        performance_test_flag: bool
                               When set to true, the time elapsed in each of the 
                               solver routines is measured. These performance 
                               stats can be obtained at the end of the run using
                               the command print_performance_timings, which summarizes
                               the results in a table.
        """
        self.physical_system = physical_system

        # Holding Domain Info:
        self.q1_start, self.q1_end = physical_system.q1_start,\
                                     physical_system.q1_end
        self.q2_start, self.q2_end = physical_system.q2_start,\
                                     physical_system.q2_end
        self.p1_start, self.p1_end = physical_system.p1_start,\
                                     physical_system.p1_end
        self.p2_start, self.p2_end = physical_system.p2_start,\
                                     physical_system.p2_end
        self.p3_start, self.p3_end = physical_system.p3_start,\
                                     physical_system.p3_end

        # Holding Domain Resolution:
        self.N_q1, self.dq1 = physical_system.N_q1, physical_system.dq1
        self.N_q2, self.dq2 = physical_system.N_q2, physical_system.dq2
        self.N_p1, self.dp1 = physical_system.N_p1, physical_system.dp1
        self.N_p2, self.dp2 = physical_system.N_p2, physical_system.dp2
        self.N_p3, self.dp3 = physical_system.N_p3, physical_system.dp3

        # Getting number of ghost zones, and the boundary 
        # conditions that are utilized:
        N_g = self.N_ghost       = physical_system.N_ghost
        self.boundary_conditions = physical_system.boundary_conditions

        # MPI Communicator:
        self._comm = self.physical_system.mpi_communicator        
        
        if(self.physical_system.params.num_devices>1):
            rank = self._comm.rank
            if (self.physical_system.params.manual_device_allocation == True):
                af.set_device(self.physical_system.params.device_allocation[rank])
            else:
                af.set_device(rank%self.physical_system.params.num_devices)
        

        # Getting number of species:
        N_s = self.N_species = self.physical_system.N_species

        # TODO: Remove mass and charge from lib
        if(type(physical_system.params.mass) == list):
            # Having a temporary copy of the lists to copy to af.Array:
            list_mass   = physical_system.params.mass.copy()
            list_charge = physical_system.params.charge.copy()

            # Initializing af.Arrays for mass and charge:
            # Having the mass and charge along axis 1:
            self.physical_system.params.mass   = af.constant(0, 1, N_s, dtype = af.Dtype.f64)
            self.physical_system.params.charge = af.constant(0, 1, N_s, dtype = af.Dtype.f64)

            for i in range(N_s):
                self.physical_system.params.mass[0, i]   = list_mass[i]
                self.physical_system.params.charge[0, i] = list_charge[i]

        self.physical_system.params.rank = self._comm.rank

        PETSc.Sys.Print('\nBackend Details for Nonlinear Solver:')
        # Printing the backend details for each rank/device/node:
        PETSc.Sys.syncPrint(indent('Rank ' + str(self._comm.rank) + ' of ' + str(self._comm.size-1)))
        PETSc.Sys.syncPrint(indent('On Node: '+ socket.gethostname()))
        PETSc.Sys.syncPrint(indent('Device Details:'))
        PETSc.Sys.syncPrint(indent(af.info_str(), 2))
        PETSc.Sys.syncPrint(indent('Device Bandwidth = ' + str(bandwidth_test(100)) + ' GB / sec'))
        PETSc.Sys.syncPrint()
        PETSc.Sys.syncFlush()

        self.performance_test_flag = performance_test_flag
    
        # Initializing variables which are used to time the components of the solver: 
        if(performance_test_flag == True):
        
            self.time_ts = 0

            self.time_interp2  = 0
            self.time_sourcets = 0

            self.time_fvm_solver  = 0
            self.time_reconstruct = 0
            self.time_riemann     = 0
            
            self.time_fieldstep = 0
            self.time_interp3   = 0
            
            self.time_apply_bcs_f   = 0
            self.time_communicate_f = 0

        petsc_bc_in_q1 = 'ghosted'; self.N_g1 = self.N_ghost
        petsc_bc_in_q2 = 'ghosted'; self.N_g2 = self.N_ghost

        # Only for periodic boundary conditions or shearing-box boundary conditions 
        # do the boundary conditions passed to the DA need to be changed. PETSc
        # automatically handles the application of periodic boundary conditions when
        # running in parallel. For shearing box boundary conditions, an interpolation
        # operation needs to be applied on top of the periodic boundary conditions.
        # In all other cases, ghosted boundaries are used.
        
        if(   self.boundary_conditions.in_q1_left == 'periodic'
           or self.boundary_conditions.in_q1_left == 'shearing-box'
          ):
            petsc_bc_in_q1 = 'periodic'

        if(   self.boundary_conditions.in_q2_bottom == 'periodic'
           or self.boundary_conditions.in_q2_bottom == 'shearing-box'
          ):
            petsc_bc_in_q2 = 'periodic'

        if(self.boundary_conditions.in_q1_left == 'none'):
            petsc_bc_in_q1 = 'none'; self.N_g1 = 0

        if(self.boundary_conditions.in_q2_bottom == 'none'):
            petsc_bc_in_q2 = 'none'; self.N_g2 = 0

        if(self.boundary_conditions.in_q1_left == 'periodic'):
            try:
                assert(self.boundary_conditions.in_q1_right == 'periodic')
            except:
                raise Exception('Periodic boundary conditions need to be applied to \
                                 both the boundaries of a particular axis'
                               )
        
        if(self.boundary_conditions.in_q1_left == 'shearing-box'):
            try:
                assert(self.boundary_conditions.in_q1_right == 'shearing-box')
            except:
                raise Exception('Shearing box boundary conditions need to be applied to \
                                 both the boundaries of a particular axis'
                               )

        if(self.boundary_conditions.in_q2_bottom == 'periodic'):
            try:
                assert(self.boundary_conditions.in_q2_top == 'periodic')
            except:
                raise Exception('Periodic boundary conditions need to be applied to \
                                 both the boundaries of a particular axis'
                               )

        if(self.boundary_conditions.in_q2_bottom == 'shearing-box'):
            try:
                assert(self.boundary_conditions.in_q2_top == 'shearing-box')
            except:
                raise Exception('Shearing box boundary conditions need to be applied to \
                                 both the boundaries of a particular axis'
                               )

        if(self.boundary_conditions.in_q1_left == 'none'):
            try:
                assert(self.boundary_conditions.in_q1_right == 'none')
            except:
                raise Exception('NONE boundary conditions need to be applied to \
                                 both the boundaries of a particular axis'
                               )

        if(self.boundary_conditions.in_q2_bottom == 'none'):
            try:
                assert(self.boundary_conditions.in_q2_top == 'none')
            except:
                raise Exception('NONE boundary conditions need to be applied to \
                                 both the boundaries of a particular axis'
                               )
        self._nproc_in_q1 = PETSc.DECIDE
        self._nproc_in_q2 = PETSc.DECIDE

        # Break up the domain into manually defined portions
        self._ownership_ranges = None
        if self.physical_system.params.enable_manual_domain_decomposition:
            ownership_q1 = [self.N_q1*item for item in self.physical_system.params.q1_partition]
            ownership_q2 = [self.N_q2*item for item in self.physical_system.params.q2_partition]
            self._ownership_ranges = (ownership_q1, ownership_q2)
        # TODO : Implement error handling and give clean messages
        
        # Since shearing boundary conditions require interpolations which are non-local:
        if(self.boundary_conditions.in_q2_bottom == 'shearing-box'):
            self._nproc_in_q1 = 1
        
        if(self.boundary_conditions.in_q1_left == 'shearing-box'):
            self._nproc_in_q2 = 1

        # DMDA is a data structure to handle a distributed structure 
        # grid and its related core algorithms. It stores metadata of
        # how the grid is partitioned when run in parallel which is 
        # utilized by the various methods of the solver.
        self._da_f = PETSc.DMDA().create([self.N_q1, self.N_q2],
                                         dof           = (  self.N_species 
                                                          * self.N_p1 
                                                          * self.N_p2 
                                                          * self.N_p3
                                                         ),
                                         stencil_width = N_g,
                                         boundary_type = (petsc_bc_in_q1,
                                                          petsc_bc_in_q2
                                                         ),
                                         proc_sizes    = (self._nproc_in_q1, 
                                                          self._nproc_in_q2
                                                         ),
                                         ownership_ranges = self._ownership_ranges,
                                         stencil_type  = 1,
                                         comm          = self._comm
                                        )
        lx, ly = self._da_f.getOwnershipRanges()

        # This DA is used by the FileIO routine dump_moments():
        # Finding the number of definitions for the moments:
        attributes = [a for a in dir(self.physical_system.moments) if not a.startswith('_')]

        # Removing utility functions:
        if('integral_over_v' in attributes):
            attributes.remove('integral_over_v')

        self._da_dump_moments = PETSc.DMDA().create([self.N_q1, self.N_q2],
                                                    dof        =   self.N_species
                                                            * (len(attributes)-2), # don't countintegral_over_p, and params in moments.py
                                                    proc_sizes = (self._nproc_in_q1, 
                                                                  self._nproc_in_q2
                                                                 ),
                                                    ownership_ranges = self._ownership_ranges,
                                                    comm       = self._comm
                                                   )

        # For dumping aux arrays:
        self.dump_aux_arrays_initial_call = 1


        # Creation of the local and global vectors from the DA:
        # This is for the distribution function
        self._glob_f  = self._da_f.createGlobalVec()
        self._local_f = self._da_f.createLocalVec()

        # The following vector is used to dump the data to file:
        self._glob_moments = self._da_dump_moments.createGlobalVec()

        # Getting the arrays for the above vectors:
        self._glob_f_array       = self._glob_f.getArray()
        self._local_f_array      = self._local_f.getArray()
        self._glob_moments_array = self._glob_moments.getArray()

        # Setting names for the objects which will then be
        # used as the key identifiers for the HDF5 files:
        PETSc.Object.setName(self._glob_f, 'distribution_function')
        PETSc.Object.setName(self._glob_moments, 'moments')

        # Indexing vars used through out
        self.i_q1_start = self.N_g1; self.i_q1_end = -self.N_g1
        self.i_q2_start = self.N_g2; self.i_q2_end = -self.N_g2
        if (self.N_g1 == 0):
            self.i_q1_end = 1

        if (self.N_g2 == 0):
            self.i_q2_end = 1

        # Get start (corner) indices of the local zone wrt global ordering and its size
        ((_i_q1_start, _i_q2_start), (_N_q1_local, _N_q2_local)) = self._da_f.getCorners()

        # Coordinates of the local zone in a global coord system
        self.q1_start_local = self.q1_start + _i_q1_start * self.dq1
        self.q2_start_local = self.q2_start + _i_q2_start * self.dq2

        # TODO : Fix this. Passing into params for use in coords.py
        self.physical_system.params.q1_start_local_left   = self.q1_start_local
        self.physical_system.params.q2_start_local_bottom = self.q2_start_local
        

        print("nonlinear.py: rank = ", self._comm.rank,
              "(q1_start_local, q2_start_local) =  (", 
              self.q1_start_local, self.q2_start_local, ")"
             )
        print("nonlinear.py: rank = ", self._comm.rank,
              "(N_q1_local, N_q2_local) =  (", 
              _N_q1_local, _N_q2_local, ")"
             )
        print("nonlinear.py: rank = ", self._comm.rank,
              "ownership_ranges : lx = ", lx, "ly = ", ly
             )

        self.N_q1_local = _N_q1_local
        self.N_q2_local = _N_q2_local

        self.N_q1_local_with_Ng = _N_q1_local + 2*self.N_g1
        self.N_q2_local_with_Ng = _N_q2_local + 2*self.N_g2

        # Obtaining the array values of spatial coordinates:
        q_left_bot, q_center_bot, q_left_center, q_center = \
            calculate_q(self.q1_start_local, 
                        self.q2_start_local,
                        self.N_q1_local, self.N_q2_local, 
                        self.N_g1, self.N_g2,
                        self.dq1, self.dq2
                       )

        self.q1_left_bot    = q_left_bot[0]
        self.q2_left_bot    = q_left_bot[1]

        self.q1_center_bot  = q_center_bot[0]
        self.q2_center_bot  = q_center_bot[1]

        self.q1_left_center = q_left_center[0]
        self.q2_left_center = q_left_center[1]

        self.q1_center      = q_center[0]
        self.q2_center      = q_center[1]

        self.p1_center, self.p2_center, self.p3_center = \
            calculate_p_center(self.p1_start, self.p2_start, self.p3_start,
                               self.N_p1, self.N_p2, self.N_p3,
                               self.dp1, self.dp2, self.dp3, 
                              )

        self.p1_left, self.p2_bottom, self.p3_back = \
            calculate_p_corner(self.p1_start, self.p2_start, self.p3_start,
                               self.N_p1, self.N_p2, self.N_p3,
                               self.dp1, self.dp2, self.dp3, 
                              )

        # Need to convert the lists dp1, dp2, dp3 to af.Arrays for vector
        # computations to work
        self.dp1 = af.moddims(af.to_array(np.array(self.dp1)), 1, self.N_species)
        self.dp2 = af.moddims(af.to_array(np.array(self.dp2)), 1, self.N_species)
        self.dp3 = af.moddims(af.to_array(np.array(self.dp3)), 1, self.N_species)

        # Need to do the same for the p1_start/end lists.
        self.p1_start = af.moddims(af.to_array(self.p1_start), 1, self.N_species)
        self.p2_start = af.moddims(af.to_array(self.p2_start), 1, self.N_species)
        self.p3_start = af.moddims(af.to_array(self.p3_start), 1, self.N_species)

        self.p1_end = af.moddims(af.to_array(self.p1_end), 1, self.N_species)
        self.p2_end = af.moddims(af.to_array(self.p2_end), 1, self.N_species)
        self.p3_end = af.moddims(af.to_array(self.p3_end), 1, self.N_species)

        self.p2_left = self.p2_center
        self.p3_left = self.p3_center

        self.p1_bottom = self.p1_center
        self.p3_bottom = self.p3_center

        self.p1_back = self.p1_center
        self.p2_back = self.p2_center
        
        # Initialize according to initial condition provided by user:
        self._initialize(physical_system.params)
    
        # Initializing a variable to track time-elapsed:
        self.time_elapsed = 0

        # Assigning the function objects to methods of the solver:
        self._A_q = physical_system.A_q
        self._C_q = physical_system.C_q
        self._A_p = physical_system.A_p
        self._C_p = physical_system.C_p

        # Source/Sink term:
        self._source = physical_system.source
Esempio n. 2
0
def build_and_backend() -> str:
    return af.info_str().split('\n')[0]
Esempio n. 3
0
    def __init__(self, physical_system):
        """
        Constructor for the linear_solver object. It takes the physical
        system object as an argument and uses it in intialization and
        evolution of the system in consideration.

        Parameters
        ----------
        
        physical_system: The defined physical system object which holds
                         all the simulation information such as the initial
                         conditions, and the domain info is passed as an
                         argument in defining an instance of the
                         nonlinear_solver. This system is then evolved, and
                         monitored using the various methods under the
                         nonlinear_solver class.
        """
        self.physical_system = physical_system

        # Storing Domain Information:
        self.q1_start, self.q1_end = physical_system.q1_start,\
                                     physical_system.q1_end
        self.q2_start, self.q2_end = physical_system.q2_start,\
                                     physical_system.q2_end
        self.p1_start, self.p1_end = physical_system.p1_start,\
                                     physical_system.p1_end
        self.p2_start, self.p2_end = physical_system.p2_start,\
                                     physical_system.p2_end
        self.p3_start, self.p3_end = physical_system.p3_start,\
                                     physical_system.p3_end

        # Getting Domain Resolution
        self.N_q1, self.dq1 = physical_system.N_q1, physical_system.dq1
        self.N_q2, self.dq2 = physical_system.N_q2, physical_system.dq2
        self.N_p1, self.dp1 = physical_system.N_p1, physical_system.dp1
        self.N_p2, self.dp2 = physical_system.N_p2, physical_system.dp2
        self.N_p3, self.dp3 = physical_system.N_p3, physical_system.dp3

        # Checking that periodic B.C's are utilized:
        if(    physical_system.boundary_conditions.in_q1_left   != 'periodic' 
           and physical_system.boundary_conditions.in_q1_right  != 'periodic'
           and physical_system.boundary_conditions.in_q2_bottom != 'periodic'
           and physical_system.boundary_conditions.in_q2_top    != 'periodic'
          ):
            raise Exception('Only systems with periodic boundary conditions \
                             can be solved using the linear solver'
                           )

        # Initializing DAs which will be used in file-writing:
        self._da_dump_f = PETSc.DMDA().create([self.N_q1, self.N_q2],
                                              dof=(  self.N_p1 
                                                   * self.N_p2 
                                                   * self.N_p3
                                                  )
                                             )

        self._da_dump_moments = PETSc.DMDA().create([self.N_q1, self.N_q2],
                                                    dof=len(self.physical_system.\
                                                            moment_exponents
                                                           )
                                                   )


        # Printing backend details:
        PETSc.Sys.Print('\nBackend Details for Linear Solver:')
        PETSc.Sys.Print(indent('On Node: '+ socket.gethostname()))
        PETSc.Sys.Print(indent('Device Details:'))
        PETSc.Sys.Print(indent(af.info_str(), 2))
        PETSc.Sys.Print(indent('Device Bandwidth = ' + str(bandwidth_test(100)) + ' GB / sec'))
        PETSc.Sys.Print()

        # Creating PETSc Vecs which are used in dumping to file:
        self._glob_f       = self._da_dump_f.createGlobalVec()
        self._glob_f_value = self._da_dump_f.getVecArray(self._glob_f)

        self._glob_moments       = self._da_dump_moments.createGlobalVec()
        self._glob_moments_value = self._da_dump_moments.\
                                   getVecArray(self._glob_moments)

        # Setting names for the objects which will then be
        # used as the key identifiers for the HDF5 files:
        PETSc.Object.setName(self._glob_f, 'distribution_function')
        PETSc.Object.setName(self._glob_moments, 'moments')

        # Intializing position, wavenumber and velocity arrays:
        self.q1_center, self.q2_center = self._calculate_q_center()
        self.k_q1, self.k_q2           = self._calculate_k()
        self.p1, self.p2, self.p3      = self._calculate_p_center()

        # Assigning the advection terms along q1 and q2
        self._A_q1 = \
            self.physical_system.A_q(self.q1_center, self.q2_center,
                                     self.p1, self.p2, self.p3, 
                                     physical_system.params
                                    )[0]

        self._A_q2 = \
            self.physical_system.A_q(self.q1_center, self.q2_center,
                                     self.p1, self.p2, self.p3,
                                     physical_system.params
                                    )[1]

        # Assigning the function objects to methods of the solver:
        self._A_p    = self.physical_system.A_p
        self._source = self.physical_system.source

        if(len(signature(self._source).parameters) == 6):
            self.single_mode_evolution = True
        else:
            self.single_mode_evolution = False

        # Initializing f, f_hat and the other EM field quantities:
        self._initialize(physical_system.params)
Esempio n. 4
0
    def __init__(self, physical_system, performance_test_flag=False):
        """
        Constructor for the nonlinear_solver object. It takes the physical
        system object as an argument and uses it in intialization and
        evolution of the system in consideration. 

        Additionally, a performance test flag is also passed which when true,
        stores time which is consumed by each of the major solver routines.
        This proves particularly useful in analyzing performance bottlenecks 
        and obtaining benchmarks.
        
        Parameters:
        -----------

        physical_system: The defined physical system object which holds
                         all the simulation information such as the initial
                         conditions, and the domain info is passed as an
                         argument in defining an instance of the
                         nonlinear_solver. This system is then evolved, and
                         monitored using the various methods under the
                         nonlinear_solver class.
        """
        self.physical_system = physical_system

        # Holding Domain Info:
        self.q1_start, self.q1_end = physical_system.q1_start,\
                                     physical_system.q1_end
        self.q2_start, self.q2_end = physical_system.q2_start,\
                                     physical_system.q2_end
        self.p1_start, self.p1_end = physical_system.p1_start,\
                                     physical_system.p1_end
        self.p2_start, self.p2_end = physical_system.p2_start,\
                                     physical_system.p2_end
        self.p3_start, self.p3_end = physical_system.p3_start,\
                                     physical_system.p3_end

        # Holding Domain Resolution:
        self.N_q1, self.dq1 = physical_system.N_q1, physical_system.dq1
        self.N_q2, self.dq2 = physical_system.N_q2, physical_system.dq2
        self.N_p1, self.dp1 = physical_system.N_p1, physical_system.dp1
        self.N_p2, self.dp2 = physical_system.N_p2, physical_system.dp2
        self.N_p3, self.dp3 = physical_system.N_p3, physical_system.dp3

        # Getting number of ghost zones, and the boundary
        # conditions that are utilized:
        N_g_q = self.N_ghost_q = physical_system.N_ghost_q
        N_g_p = self.N_ghost_p = physical_system.N_ghost_p

        self.boundary_conditions = physical_system.boundary_conditions

        # Declaring the communicator:
        self._comm = PETSc.COMM_WORLD.tompi4py()
        if (self.physical_system.params.num_devices > 1):
            af.set_device(self._comm.rank %
                          self.physical_system.params.num_devices)

        # Getting number of species:
        self.N_species = len(physical_system.params.mass)

        # Having the mass and charge along axis 1:
        self.physical_system.params.mass  = \
            af.cast(af.moddims(af.to_array(physical_system.params.mass),
                               1, self.N_species
                              ),
                    af.Dtype.f64
                   )

        self.physical_system.params.charge  = \
            af.cast(af.moddims(af.to_array(physical_system.params.charge),
                               1, self.N_species
                              ),
                    af.Dtype.f64
                   )

        PETSc.Sys.Print('\nBackend Details for Nonlinear Solver:')

        # Printing the backend details for each rank/device/node:
        PETSc.Sys.syncPrint(
            indent('Rank ' + str(self._comm.rank) + ' of ' +
                   str(self._comm.size - 1)))
        PETSc.Sys.syncPrint(indent('On Node: ' + socket.gethostname()))
        PETSc.Sys.syncPrint(indent('Device Details:'))
        PETSc.Sys.syncPrint(indent(af.info_str(), 2))
        PETSc.Sys.syncPrint(
            indent('Device Bandwidth = ' + str(bandwidth_test(100)) +
                   ' GB / sec'))
        PETSc.Sys.syncPrint()
        PETSc.Sys.syncFlush()

        self.performance_test_flag = performance_test_flag

        # Initializing variables which are used to time the components of the solver:
        if (performance_test_flag == True):

            self.time_ts = 0

            self.time_interp2 = 0
            self.time_sourcets = 0

            self.time_fvm_solver = 0
            self.time_reconstruct = 0
            self.time_riemann = 0

            self.time_fieldstep = 0
            self.time_interp3 = 0

            self.time_apply_bcs_f = 0
            self.time_communicate_f = 0

        petsc_bc_in_q1 = 'ghosted'
        petsc_bc_in_q2 = 'ghosted'

        # Only for periodic boundary conditions or shearing-box boundary conditions
        # do the boundary conditions passed to the DA need to be changed. PETSc
        # automatically handles the application of periodic boundary conditions when
        # running in parallel. For shearing box boundary conditions, an interpolation
        # operation needs to be applied on top of the periodic boundary conditions.
        # In all other cases, ghosted boundaries are used.

        if (self.boundary_conditions.in_q1_left == 'periodic'
                or self.boundary_conditions.in_q1_left == 'shearing-box'):
            petsc_bc_in_q1 = 'periodic'

        if (self.boundary_conditions.in_q2_bottom == 'periodic'
                or self.boundary_conditions.in_q2_bottom == 'shearing-box'):
            petsc_bc_in_q2 = 'periodic'

        if (self.boundary_conditions.in_q1_left == 'periodic'):
            try:
                assert (self.boundary_conditions.in_q1_right == 'periodic')
            except:
                raise Exception(
                    'Periodic boundary conditions need to be applied to \
                                 both the boundaries of a particular axis')

        if (self.boundary_conditions.in_q1_left == 'shearing-box'):
            try:
                assert (self.boundary_conditions.in_q1_right == 'shearing-box')
            except:
                raise Exception(
                    'Shearing box boundary conditions need to be applied to \
                                 both the boundaries of a particular axis')

        if (self.boundary_conditions.in_q2_bottom == 'periodic'):
            try:
                assert (self.boundary_conditions.in_q2_top == 'periodic')
            except:
                raise Exception(
                    'Periodic boundary conditions need to be applied to \
                                 both the boundaries of a particular axis')

        if (self.boundary_conditions.in_q2_bottom == 'shearing-box'):
            try:
                assert (self.boundary_conditions.in_q2_top == 'shearing-box')
            except:
                raise Exception(
                    'Shearing box boundary conditions need to be applied to \
                                 both the boundaries of a particular axis')

        nproc_in_q1 = PETSc.DECIDE
        nproc_in_q2 = PETSc.DECIDE

        # Since shearing boundary conditions require interpolations which are non-local:
        if (self.boundary_conditions.in_q2_bottom == 'shearing-box'):
            nproc_in_q1 = 1

        if (self.boundary_conditions.in_q1_left == 'shearing-box'):
            nproc_in_q2 = 1

        # DMDA is a data structure to handle a distributed structure
        # grid and its related core algorithms. It stores metadata of
        # how the grid is partitioned when run in parallel which is
        # utilized by the various methods of the solver.
        self._da_f = PETSc.DMDA().create(
            [self.N_q1, self.N_q2],
            dof=(self.N_species * (self.N_p1 + 2 * N_g_p) *
                 (self.N_p2 + 2 * N_g_p) * (self.N_p3 + 2 * N_g_p)),
            stencil_width=N_g_q,
            boundary_type=(petsc_bc_in_q1, petsc_bc_in_q2),
            proc_sizes=(nproc_in_q1, nproc_in_q2),
            stencil_type=1,
            comm=self._comm)

        # This DA is used by the FileIO routine dump_distribution_function():
        self._da_dump_f = PETSc.DMDA().create(
            [self.N_q1, self.N_q2],
            dof=(self.N_species * self.N_p1 * self.N_p2 * self.N_p3),
            stencil_width=N_g_q,
            boundary_type=(petsc_bc_in_q1, petsc_bc_in_q2),
            proc_sizes=(nproc_in_q1, nproc_in_q2),
            stencil_type=1,
            comm=self._comm)

        # This DA is used by the FileIO routine dump_moments():
        # Finding the number of definitions for the moments:
        attributes = [
            a for a in dir(self.physical_system.moments)
            if not a.startswith('_')
        ]

        # Removing utility functions:
        if ('integral_over_v' in attributes):
            attributes.remove('integral_over_v')

        self._da_dump_moments = PETSc.DMDA().create(
            [self.N_q1, self.N_q2],
            dof=self.N_species * len(attributes),
            proc_sizes=(nproc_in_q1, nproc_in_q2),
            comm=self._comm)

        # Creation of the local and global vectors from the DA:
        # This is for the distribution function
        self._glob_f = self._da_f.createGlobalVec()
        self._local_f = self._da_f.createLocalVec()

        # The following vector is used to dump the data to file:
        self._glob_dump_f = self._da_dump_f.createGlobalVec()
        self._glob_moments = self._da_dump_moments.createGlobalVec()

        # Getting the arrays for the above vectors:
        self._glob_f_array = self._glob_f.getArray()
        self._local_f_array = self._local_f.getArray()

        self._glob_moments_array = self._glob_moments.getArray()
        self._glob_dump_f_array = self._glob_dump_f.getArray()

        # Setting names for the objects which will then be
        # used as the key identifiers for the HDF5 files:
        PETSc.Object.setName(self._glob_dump_f, 'distribution_function')
        PETSc.Object.setName(self._glob_moments, 'moments')

        # Obtaining the array values of the cannonical variables:
        self.q1_center, self.q2_center = self._calculate_q_center()
        self.p1_center, self.p2_center, self.p3_center = self._calculate_p_center(
        )

        # Initialize according to initial condition provided by user:
        self._initialize(physical_system.params)

        # Obtaining start coordinates for the local zone
        # Additionally, we also obtain the size of the local zone
        ((i_q1_start, i_q2_start), (N_q1_local,
                                    N_q2_local)) = self._da_f.getCorners()
        (i_q1_end, i_q2_end) = (i_q1_start + N_q1_local - 1,
                                i_q2_start + N_q2_local - 1)

        # Applying dirichlet boundary conditions:
        if (self.physical_system.boundary_conditions.in_q1_left == 'dirichlet'
            ):
            # If local zone includes the left physical boundary:
            if (i_q1_start == 0):
                self.f[:, :N_g_q] = self.boundary_conditions.\
                                    f_left(self.f, self.q1_center, self.q2_center,
                                           self.p1_center, self.p2_center, self.p3_center,
                                           self.physical_system.params
                                          )[:, :N_g_q]

        if (self.physical_system.boundary_conditions.in_q1_right == 'dirichlet'
            ):
            # If local zone includes the right physical boundary:
            if (i_q1_end == self.N_q1 - 1):
                self.f[:, -N_g_q:] = self.boundary_conditions.\
                                     f_right(self.f, self.q1_center, self.q2_center,
                                             self.p1_center, self.p2_center, self.p3_center,
                                             self.physical_system.params
                                            )[:, -N_g_q:]

        if (self.physical_system.boundary_conditions.in_q2_bottom ==
                'dirichlet'):
            # If local zone includes the bottom physical boundary:
            if (i_q2_start == 0):
                self.f[:, :, :N_g_q] = self.boundary_conditions.\
                                       f_bot(self.f, self.q1_center, self.q2_center,
                                             self.p1_center, self.p2_center, self.p3_center,
                                             self.physical_system.params
                                            )[:, :, :N_g_q]

        if (self.physical_system.boundary_conditions.in_q2_top == 'dirichlet'):
            # If local zone includes the top physical boundary:
            if (i_q2_end == self.N_q2 - 1):
                self.f[:, :, -N_g_q:] = self.boundary_conditions.\
                                        f_top(self.f, self.q1_center, self.q2_center,
                                              self.p1_center, self.p2_center, self.p3_center,
                                              self.physical_system.params
                                             )[:, :, -N_g_q:]

        # Assigning the value to the PETSc Vecs(for dump at t = 0):
        (af.flat(self.f)).to_ndarray(self._local_f_array)
        (af.flat(self.f[:, :, N_g_q:-N_g_q,
                        N_g_q:-N_g_q])).to_ndarray(self._glob_f_array)

        # Assigning the function objects to methods of the solver:
        self._A_q = physical_system.A_q
        self._C_q = physical_system.C_q
        self._A_p = physical_system.A_p
        self._C_p = physical_system.C_p

        # Source/Sink term:
        self._source = physical_system.source

        # Initializing a variable to track time-elapsed:
        self.time_elapsed = 0
Esempio n. 5
0
    def __init__(self, physical_system):
        """
        Constructor for the linear_solver object. It takes the physical
        system object as an argument and uses it in intialization and
        evolution of the system in consideration.

        Parameters
        ----------
        
        physical_system: The defined physical system object which holds
                         all the simulation information such as the initial
                         conditions, and the domain info is passed as an
                         argument in defining an instance of the linear_solver.
                         This system is then evolved, and monitored using the
                         various methods under the linear_solver class.
        """
        self.physical_system = physical_system

        # Storing Domain Information:
        self.q1_start, self.q1_end = physical_system.q1_start,\
                                     physical_system.q1_end
        self.q2_start, self.q2_end = physical_system.q2_start,\
                                     physical_system.q2_end
        self.p1_start, self.p1_end = physical_system.p1_start,\
                                     physical_system.p1_end
        self.p2_start, self.p2_end = physical_system.p2_start,\
                                     physical_system.p2_end
        self.p3_start, self.p3_end = physical_system.p3_start,\
                                     physical_system.p3_end

        # Getting Domain Resolution
        self.N_q1, self.dq1 = physical_system.N_q1, physical_system.dq1
        self.N_q2, self.dq2 = physical_system.N_q2, physical_system.dq2
        self.N_p1, self.dp1 = physical_system.N_p1, physical_system.dp1
        self.N_p2, self.dp2 = physical_system.N_p2, physical_system.dp2
        self.N_p3, self.dp3 = physical_system.N_p3, physical_system.dp3

        # Getting number of species:
        self.N_species = len(physical_system.params.mass)

        # Having the mass and charge along axis 1:
        self.physical_system.params.mass  = \
            af.cast(af.moddims(af.to_array(physical_system.params.mass),
                               1, self.N_species
                              ),
                    af.Dtype.f64
                   )

        self.physical_system.params.charge  = \
            af.cast(af.moddims(af.to_array(physical_system.params.charge),
                               1, self.N_species
                              ),
                    af.Dtype.f64
                   )

        # Initializing variable to hold time elapsed:
        self.time_elapsed = 0

        # Checking that periodic B.C's are utilized:
        if (physical_system.boundary_conditions.in_q1_left != 'periodic' and
                physical_system.boundary_conditions.in_q1_right != 'periodic'
                and
                physical_system.boundary_conditions.in_q2_bottom != 'periodic'
                and
                physical_system.boundary_conditions.in_q2_top != 'periodic'):
            raise Exception('Only systems with periodic boundary conditions \
                             can be solved using the linear solver')

        # Initializing DAs which will be used in file-writing:
        # This is done so that the output format used by the linear solver matches
        # with the output format of the nonlinear solver:
        self._da_dump_f = PETSc.DMDA().create([self.N_q1, self.N_q2],
                                              dof=(self.N_species * self.N_p1 *
                                                   self.N_p2 * self.N_p3))

        # Getting the number of definitions in moments:
        attributes = [
            a for a in dir(self.physical_system.moments)
            if not a.startswith('_')
        ]
        self._da_dump_moments = PETSc.DMDA().create([self.N_q1, self.N_q2],
                                                    dof=self.N_species *
                                                    len(attributes))

        # Printing backend details:
        PETSc.Sys.Print('\nBackend Details for Linear Solver:')
        PETSc.Sys.Print(indent('On Node: ' + socket.gethostname()))
        PETSc.Sys.Print(indent('Device Details:'))
        PETSc.Sys.Print(indent(af.info_str(), 2))
        PETSc.Sys.Print(
            indent('Device Bandwidth = ' + str(bandwidth_test(100)) +
                   ' GB / sec'))
        PETSc.Sys.Print()

        # Creating PETSc Vecs which are used in dumping to file:
        self._glob_f = self._da_dump_f.createGlobalVec()
        self._glob_f_array = self._glob_f.getArray()

        self._glob_moments = self._da_dump_moments.createGlobalVec()
        self._glob_moments_array = self._glob_moments.getArray()

        # Setting names for the objects which will then be
        # used as the key identifiers for the HDF5 files:
        PETSc.Object.setName(self._glob_f, 'distribution_function')
        PETSc.Object.setName(self._glob_moments, 'moments')

        # Intializing position, wavenumber and velocity arrays:
        self.q1_center, self.q2_center = self._calculate_q_center()
        self.k_q1, self.k_q2 = self._calculate_k()
        self.p1, self.p2, self.p3 = self._calculate_p_center()

        # Assigning the function objects to methods of the solver:
        self._A_q = self.physical_system.A_q
        self._A_p = self.physical_system.A_p
        self._source = self.physical_system.source

        # Initializing f, f_hat and the other EM field quantities:
        self._initialize(physical_system.params)
Esempio n. 6
0
    def __init__(self, physical_system, performance_test_flag=False):
        """
        Constructor for the nonlinear_solver object. It takes the physical
        system object as an argument and uses it in intialization and
        evolution of the system in consideration. 

        Additionally, a performance test flag is also passed which when true 
        stores time which is consumed by each of the major solver routines.
        This proves particularly useful in analyzing performance bottlenecks 
        and obtaining benchmarks.
        
        Parameters:
        -----------

        physical_system: The defined physical system object which holds
                         all the simulation information such as the initial
                         conditions, and the domain info is passed as an
                         argument in defining an instance of the
                         nonlinear_solver. This system is then evolved, and
                         monitored using the various methods under the
                         nonlinear_solver class.
        """
        self.physical_system = physical_system

        # Holding Domain Info:
        self.q1_start, self.q1_end = physical_system.q1_start,\
                                     physical_system.q1_end
        self.q2_start, self.q2_end = physical_system.q2_start,\
                                     physical_system.q2_end
        self.p1_start, self.p1_end = physical_system.p1_start,\
                                     physical_system.p1_end
        self.p2_start, self.p2_end = physical_system.p2_start,\
                                     physical_system.p2_end
        self.p3_start, self.p3_end = physical_system.p3_start,\
                                     physical_system.p3_end

        # Holding Domain Resolution:
        self.N_q1, self.dq1 = physical_system.N_q1, physical_system.dq1
        self.N_q2, self.dq2 = physical_system.N_q2, physical_system.dq2
        self.N_p1, self.dp1 = physical_system.N_p1, physical_system.dp1
        self.N_p2, self.dp2 = physical_system.N_p2, physical_system.dp2
        self.N_p3, self.dp3 = physical_system.N_p3, physical_system.dp3

        # Getting number of ghost zones, and the boundary
        # conditions that are utilized:
        N_g = self.N_ghost = physical_system.N_ghost
        self.boundary_conditions = physical_system.boundary_conditions

        # Declaring the communicator:
        self._comm = PETSc.COMM_WORLD.tompi4py()

        if (self.physical_system.params.num_devices > 1):
            af.set_device(self._comm.rank %
                          self.physical_system.params.num_devices)

        PETSc.Sys.Print('\nBackend Details for Nonlinear Solver:')

        # Printing the backend details for each rank/device/node:
        PETSc.Sys.syncPrint(
            indent('Rank ' + str(self._comm.rank) + ' of ' +
                   str(self._comm.size - 1)))
        PETSc.Sys.syncPrint(indent('On Node: ' + socket.gethostname()))
        PETSc.Sys.syncPrint(indent('Device Details:'))
        PETSc.Sys.syncPrint(indent(af.info_str(), 2))
        PETSc.Sys.syncPrint(
            indent('Device Bandwidth = ' + str(bandwidth_test(100)) +
                   ' GB / sec'))
        PETSc.Sys.syncPrint()
        PETSc.Sys.syncFlush()

        self.performance_test_flag = performance_test_flag

        if (performance_test_flag == True):

            self.time_ts = 0

            self.time_interp2 = 0
            self.time_sourcets = 0

            self.time_fvm_solver = 0
            self.time_reconstruct = 0
            self.time_riemann = 0

            self.time_fieldstep = 0
            self.time_fieldsolver = 0
            self.time_interp3 = 0

            self.time_apply_bcs_f = 0
            self.time_apply_bcs_fields = 0

            self.time_communicate_f = 0
            self.time_communicate_fields = 0

        petsc_bc_in_q1 = 'ghosted'
        petsc_bc_in_q2 = 'ghosted'

        # Only for periodic boundary conditions do the boundary
        # conditions passed to the DA need to be changed. PETSc
        # automatically handles the application of periodic
        # boundary conditions when running in parallel. In all other
        # cases, ghosted boundaries are used.

        if (self.boundary_conditions.in_q1_left == 'periodic'):
            petsc_bc_in_q1 = 'periodic'

        if (self.boundary_conditions.in_q2_bottom == 'periodic'):
            petsc_bc_in_q2 = 'periodic'

        # DMDA is a data structure to handle a distributed structure
        # grid and its related core algorithms. It stores metadata of
        # how the grid is partitioned when run in parallel which is
        # utilized by the various methods of the solver.

        self._da_f = PETSc.DMDA().create(
            [self.N_q1, self.N_q2],
            dof=(self.N_p1 * self.N_p2 * self.N_p3),
            stencil_width=self.N_ghost,
            boundary_type=(petsc_bc_in_q1, petsc_bc_in_q2),
            proc_sizes=(PETSc.DECIDE, PETSc.DECIDE),
            stencil_type=1,
            comm=self._comm)

        # This DA object is used in the communication routines for the
        # EM field quantities. A DOF of 6 is taken so that the communications,
        # and application of B.C's may be carried out in a single call among
        # all the field quantities(E1, E2, E3, B1, B2, B3)
        self._da_fields = PETSc.DMDA().create([self.N_q1, self.N_q2],
                                              dof=6,
                                              stencil_width=self.N_ghost,
                                              boundary_type=(petsc_bc_in_q1,
                                                             petsc_bc_in_q2),
                                              proc_sizes=(PETSc.DECIDE,
                                                          PETSc.DECIDE),
                                              stencil_type=1,
                                              comm=self._comm)

        # Additionally, a DMDA object also needs to be created for
        # the KSP/SNES solver with a DOF of 1. This is used to solve for
        # the electrostatic case:

        self._da_ksp = PETSc.DMDA().create([self.N_q1, self.N_q2],
                                           stencil_width=self.N_ghost,
                                           boundary_type=(petsc_bc_in_q1,
                                                          petsc_bc_in_q2),
                                           proc_sizes=(PETSc.DECIDE,
                                                       PETSc.DECIDE),
                                           stencil_type=1,
                                           comm=self._comm)

        # This DA is used by the FileIO routine dump_moments():
        self._da_dump_moments = PETSc.DMDA().create(
            [self.N_q1, self.N_q2],
            dof=len(self.physical_system.moment_exponents),
            proc_sizes=(PETSc.DECIDE, PETSc.DECIDE),
            comm=self._comm)

        # Creation of the local and global vectors from the DA:
        # This is for the distribution function
        self._glob_f = self._da_f.createGlobalVec()
        self._local_f = self._da_f.createLocalVec()

        # The following global and local vectors are used in
        # the communication routines for EM fields
        self._glob_fields = self._da_fields.createGlobalVec()
        self._local_fields = self._da_fields.createLocalVec()

        # The following vector is used to dump the data to file:
        self._glob_moments = self._da_dump_moments.createGlobalVec()

        # Getting the arrays for the above vectors:
        self._glob_f_array = self._glob_f.getArray()
        self._local_f_array = self._local_f.getArray()

        self._glob_fields_array = self._glob_fields.getArray()
        self._local_fields_array = self._local_fields.getArray()

        self._glob_moments_array = self._glob_moments.getArray()

        # Setting names for the objects which will then be
        # used as the key identifiers for the HDF5 files:
        PETSc.Object.setName(self._glob_f, 'distribution_function')
        PETSc.Object.setName(self._glob_moments, 'moments')

        # Obtaining the array values of the cannonical variables:
        self.q1_center, self.q2_center = self._calculate_q_center()
        self.p1, self.p2, self.p3 = self._calculate_p_center()

        # Initialize according to initial condition provided by user:
        self._initialize(physical_system.params)

        # Obtaining start coordinates for the local zone
        # Additionally, we also obtain the size of the local zone
        ((i_q1_start, i_q2_start), (N_q1_local,
                                    N_q2_local)) = self._da_f.getCorners()
        (i_q1_end, i_q2_end) = (i_q1_start + N_q1_local - 1,
                                i_q2_start + N_q2_local - 1)

        # Applying dirichlet boundary conditions:
        if (self.physical_system.boundary_conditions.in_q1_left == 'dirichlet'
            ):
            # If local zone includes the left physical boundary:
            if (i_q1_start == 0):
                self.f[:, :N_g] = self.boundary_conditions.\
                                  f_left(self.f, self.q1_center, self.q2_center,
                                         self.p1, self.p2, self.p3,
                                         self.physical_system.params
                                        )[:, :N_g]

        if (self.physical_system.boundary_conditions.in_q1_right == 'dirichlet'
            ):
            # If local zone includes the right physical boundary:
            if (i_q1_end == self.N_q1 - 1):
                self.f[:, -N_g:] = self.boundary_conditions.\
                                   f_right(self.f, self.q1_center, self.q2_center,
                                           self.p1, self.p2, self.p3,
                                           self.physical_system.params
                                          )[:, -N_g:]

        if (self.physical_system.boundary_conditions.in_q2_bottom ==
                'dirichlet'):
            # If local zone includes the bottom physical boundary:
            if (i_q2_start == 0):
                self.f[:, :, :N_g] = self.boundary_conditions.\
                                     f_bot(self.f, self.q1_center, self.q2_center,
                                           self.p1, self.p2, self.p3,
                                           self.physical_system.params
                                          )[:, :, :N_g]

        if (self.physical_system.boundary_conditions.in_q2_top == 'dirichlet'):
            # If local zone includes the top physical boundary:
            if (i_q2_end == self.N_q2 - 1):
                self.f[:, :, -N_g:] = self.boundary_conditions.\
                                      f_top(self.f, self.q1_center, self.q2_center,
                                            self.p1, self.p2, self.p3,
                                            self.physical_system.params
                                           )[:, :, -N_g:]

        # Assigning the value to the PETSc Vecs(for dump at t = 0):
        (af.flat(self.f)).to_ndarray(self._local_f_array)
        (af.flat(self.f[:, N_g:-N_g, N_g:-N_g])).to_ndarray(self._glob_f_array)

        # Assigning the advection terms along q1 and q2
        self._A_q1 = physical_system.A_q(self.q1_center, self.q2_center,
                                         self.p1, self.p2, self.p3,
                                         physical_system.params)[0]
        self._A_q2 = physical_system.A_q(self.q1_center, self.q2_center,
                                         self.p1, self.p2, self.p3,
                                         physical_system.params)[1]

        # Assigning the conservative advection terms along q1 and q2
        self._C_q1 = physical_system.C_q(self.q1_center, self.q2_center,
                                         self.p1, self.p2, self.p3,
                                         physical_system.params)[0]
        self._C_q2 = physical_system.C_q(self.q1_center, self.q2_center,
                                         self.p1, self.p2, self.p3,
                                         physical_system.params)[1]

        # Assigning the function objects to methods of the solver:
        self._A_p = physical_system.A_p

        # Source/Sink term:
        self._source = physical_system.source

        # Initializing a variable to track time-elapsed:
        # This becomes necessary when applying shearing wall
        # boundary conditions(WIP):
        self.time_elapsed = 0