@@ -391,7 +391,124 @@ class SolverBaseClass(uw_object):
391391 print (f" {name}.constitutive_model = uw.constitutive_models..." )
392392
393393 return
394+
395+ def get_dof_partition (self ,
396+ section_type: str ,
397+ filename: Optional[str | None] = None ,
398+ outputPath: Optional[str] = ""):
399+ """
400+ Obtains how the degrees of freedom (DOF) are distributed/divided among the processors and saves them in an h5 file.
401+ Parameters
402+ ----------
403+ section_type:
404+ Can be: "local" which includes DOFs from ghost points or "global" which differentiates DOFs from ghost points by having negative values.
405+ filename:
406+ Output file name. If None, will print out results; if set to a string, the final output file will be <filename>_<section_type>.u.h5.
407+ outputPath:
408+ Path of directory where data is saved. If left empty it will save the data in the current working directory.
409+ """
410+
411+ self .validate_solver() # mainly check if self.u is properly set
412+
413+ u_id = self .Unknowns.u.field_id
414+ fname = None if filename is None else f" {filename}_{section_type}.u.h5"
415+
416+ self ._get_dof_partition_by_field_id(section_type = section_type,
417+ field_id = u_id,
418+ filename = fname,
419+ outputPath = outputPath)
420+
421+ return
422+
423+
424+ def _get_dof_partition_by_field_id (self ,
425+ section_type: str ,
426+ field_id: int ,
427+ filename: Optional[str | None] = None ,
428+ outputPath: Optional[str] = ""):
429+ """
430+ Private version of get_dof_partition with field_id as an additional parameter.
431+ Parameters
432+ ----------
433+ section_type:
434+ Can be: "local" which includes DOFs from ghost points or "global" which differentiates DOFs from ghost points by having negative values.
435+ field_id:
436+ The field id
437+ filename:
438+ Output file name. If None, will print out results; if set to a string, resulting h5 file has the following keys: field_id, rank, dof.
439+ outputPath:
440+ Path of directory where data is saved. If left empty it will save the data in the current working directory.
441+ """
442+
443+ import os
444+ import h5py
445+ import numpy as np
394446
447+ # check if section type is valid
448+ if section_type not in [' local' , ' global' ]:
449+ raise (" 'section_type' unknown. Value must be either 'local' or 'global'" )
450+
451+ # check if path exists
452+ if os.path.exists(os.path.abspath(outputPath)): # easier to debug abs
453+ pass
454+ else :
455+ raise RuntimeError (f" {os.path.abspath(outputPath)} does not exist" )
456+
457+ # check if we have write access
458+ if os.access(os.path.abspath(outputPath), os.W_OK):
459+ pass
460+ else :
461+ raise RuntimeError (f" No write access to {os.path.abspath(outputPath)}" )
462+
463+
464+ # get all points in the DAG of this partition
465+ if section_type == " local" :
466+ section = self .mesh.dm.getLocalSection()
467+ elif section_type == " global" :
468+ section = self .mesh.dm.getGlobalSection()
469+
470+ # NOTE: negative DOFs mean that these are ghost ones and owned by a different process
471+
472+ ptStart, ptEnd = section.getChart() # will give all DOFs including ghosts
473+
474+ fdofs = [section.getFieldDof(pt, field_id) for pt in range (ptStart, ptEnd)]
475+ fdofs = np.array(fdofs)
476+ pos_dof_data = np.array([field_id, uw.mpi.rank, fdofs[fdofs > 0 ].sum()])
477+
478+ if section_type == " global" :
479+ neg_dof_data = np.array([field_id, uw.mpi.rank, fdofs[fdofs < 0 ].sum()])
480+
481+ comm = uw.mpi.comm
482+
483+ # Gather the arrays on rank 0
484+ gath_pos_dof_data = comm.gather(pos_dof_data, root = 0 )
485+ if section_type == " global" :
486+ gath_neg_dof_data = comm.gather(neg_dof_data, root = 0 )
487+
488+ # pack data and save to a dataframe for formatted opening
489+ if uw.mpi.rank == 0 :
490+ gath_dof_data = np.vstack(gath_pos_dof_data)
491+ if section_type == " global" :
492+ gath_dof_data = np.vstack([gath_pos_dof_data, gath_neg_dof_data])
493+
494+ if filename is None : # print out
495+ print (f" Section type: {section_type}" )
496+ print (f" | Field ID | Rank | # DOFs |" )
497+ print (f" | ---------------------------------------------- |" )
498+ for i in range (gath_dof_data.shape[0 ]):
499+ print (
500+ f" | {gath_dof_data[i, 0]:<15}|{gath_dof_data[i, 1]:<15}|{gath_dof_data[i, 2]:<15}|"
501+ )
502+ print (f" | ---------------------------------------------- |" )
503+ print (" \n " , flush = True )
504+
505+ else : # save
506+ with h5py.File(f" {outputPath}/{filename}" , " w" ) as f:
507+ f.create_dataset(" field_id" , data = gath_dof_data[:, 0 ])
508+ f.create_dataset(" rank" , data = gath_dof_data[:, 1 ])
509+ f.create_dataset(" dof" , data = gath_dof_data[:, 2 ])
510+
511+ return
395512
396513# # Specific to dimensionality
397514
@@ -2077,9 +2194,45 @@ class SNES_Stokes_SaddlePt(SolverBaseClass):
20772194 raise RuntimeError (" Constitutive Model is required" )
20782195
20792196 return
2080-
2081-
2082-
2197+
2198+ def get_dof_partition (self ,
2199+ section_type: str ,
2200+ filename: Optional[str | None] = None ,
2201+ outputPath: Optional[str] = ""):
2202+ """
2203+ Obtains how the degrees of freedom (DOF) are distributed/divided among the processors and saves them in an h5 file.
2204+ Parameters
2205+ ----------
2206+ section_type:
2207+ Can be: "local" which includes DOFs from ghost points or "global" which differentiates DOFs from ghost points by having negative values.
2208+ filename:
2209+ Output file name. If None, will print out results; if set to a string, the output files will be <filename>_<section_type>.u.h5 and <filename>_<section_type>.p.h5.
2210+ outputPath:
2211+ Path of directory where data is saved. If left empty it will save the data in the current working directory.
2212+ """
2213+ # NOTE: supposed to inherit get_dof_partition from SolverBaseClass
2214+ # NOTE: _get_dof_partition_by_field_id is defined in SolverBaseClass
2215+
2216+ self .validate_solver()
2217+
2218+ u_id = self .Unknowns.u.field_id
2219+ fname = None if filename is None else f" {filename}_{section_type}.u.h5"
2220+
2221+ self ._get_dof_partition_by_field_id(section_type = section_type,
2222+ field_id = u_id,
2223+ filename = fname,
2224+ outputPath = outputPath)
2225+
2226+ p_id = self .Unknowns.p.field_id
2227+ fname = None if filename is None else f" {filename}_{section_type}.p.h5"
2228+
2229+ self ._get_dof_partition_by_field_id(section_type = section_type,
2230+ field_id = p_id,
2231+ filename = fname,
2232+ outputPath = outputPath)
2233+
2234+ return
2235+
20832236 @timing.routine_timer_decorator
20842237 def _setup_pointwise_functions (self , verbose = False , debug = False , debug_name = None ):
20852238 import sympy
0 commit comments