Source code for yt.frontends.art.data_structures

import glob
import os
import struct
import weakref

import numpy as np

import yt.utilities.fortran_utils as fpu
from yt.data_objects.index_subobjects.octree_subset import OctreeSubset
from yt.data_objects.static_output import Dataset, ParticleFile
from yt.data_objects.unions import ParticleUnion
from yt.frontends.art.definitions import (
    amr_header_struct,
    constants,
    dmparticle_header_struct,
    filename_pattern,
    fluid_fields,
    particle_fields,
    particle_header_struct,
    seek_extras,
)
from yt.frontends.art.fields import ARTFieldInfo
from yt.frontends.art.io import (
    _read_art_level_info,
    _read_child_level,
    _read_root_level,
    a2b,
    b2t,
)
from yt.funcs import mylog, setdefaultattr
from yt.geometry.geometry_handler import Index, YTDataChunk
from yt.geometry.oct_container import ARTOctreeContainer
from yt.geometry.oct_geometry_handler import OctreeIndex
from yt.geometry.particle_geometry_handler import ParticleIndex


[docs] class ARTIndex(OctreeIndex): def __init__(self, ds, dataset_type="art"): self.fluid_field_list = fluid_fields self.dataset_type = dataset_type self.dataset = weakref.proxy(ds) self.index_filename = self.dataset.parameter_filename self.directory = os.path.dirname(self.index_filename) self.max_level = ds.max_level self.float_type = np.float64 super().__init__(ds, dataset_type)
[docs] def get_smallest_dx(self): """ Returns (in code units) the smallest cell size in the simulation. """ # Overloaded ds = self.dataset return (ds.domain_width / ds.domain_dimensions / (2**self.max_level)).min()
def _initialize_oct_handler(self): """ Just count the number of octs per domain and allocate the requisite memory in the oct tree """ nv = len(self.fluid_field_list) self.oct_handler = ARTOctreeContainer( self.dataset.domain_dimensions / 2, # dd is # of root cells self.dataset.domain_left_edge, self.dataset.domain_right_edge, 1, ) # The 1 here refers to domain_id == 1 always for ARTIO. self.domains = [ARTDomainFile(self.dataset, nv, self.oct_handler, 1)] self.octs_per_domain = [dom.level_count.sum() for dom in self.domains] self.total_octs = sum(self.octs_per_domain) mylog.debug("Allocating %s octs", self.total_octs) self.oct_handler.allocate_domains(self.octs_per_domain) domain = self.domains[0] domain._read_amr_root(self.oct_handler) domain._read_amr_level(self.oct_handler) self.oct_handler.finalize() def _detect_output_fields(self): self.particle_field_list = list(particle_fields) self.field_list = [("art", f) for f in fluid_fields] # now generate all of the possible particle fields for ptype in self.dataset.particle_types_raw: for pfield in self.particle_field_list: pfn = (ptype, pfield) self.field_list.append(pfn) def _identify_base_chunk(self, dobj): """ Take the passed in data source dobj, and use its embedded selector to calculate the domain mask, build the reduced domain subsets and oct counts. Attach this information to dobj. """ if getattr(dobj, "_chunk_info", None) is None: # Get all octs within this oct handler domains = [dom for dom in self.domains if dom.included(dobj.selector)] base_region = getattr(dobj, "base_region", dobj) if len(domains) > 1: mylog.debug("Identified %s intersecting domains", len(domains)) subsets = [ ARTDomainSubset(base_region, domain, self.dataset) for domain in domains ] dobj._chunk_info = subsets dobj._current_chunk = list(self._chunk_all(dobj))[0] def _chunk_all(self, dobj): oobjs = getattr(dobj._current_chunk, "objs", dobj._chunk_info) # We pass the chunk both the current chunk and list of chunks, # as well as the referring data source yield YTDataChunk(dobj, "all", oobjs, None) def _chunk_spatial(self, dobj, ngz, sort=None, preload_fields=None): sobjs = getattr(dobj._current_chunk, "objs", dobj._chunk_info) for og in sobjs: if ngz > 0: g = og.retrieve_ghost_zones(ngz, [], smoothed=True) else: g = og yield YTDataChunk(dobj, "spatial", [g], None) def _chunk_io(self, dobj, cache=True, local_only=False): """ Since subsets are calculated per domain, i.e. per file, yield each domain at a time to organize by IO. We will eventually chunk out NMSU ART to be level-by-level. """ oobjs = getattr(dobj._current_chunk, "objs", dobj._chunk_info) for subset in oobjs: yield YTDataChunk(dobj, "io", [subset], None, cache=cache)
[docs] class ARTDataset(Dataset): _index_class: type[Index] = ARTIndex _field_info_class = ARTFieldInfo def __init__( self, filename, dataset_type="art", fields=None, storage_filename=None, skip_particles=False, skip_stars=False, limit_level=None, spread_age=True, force_max_level=None, file_particle_header=None, file_particle_data=None, file_particle_stars=None, units_override=None, unit_system="cgs", default_species_fields=None, ): self.fluid_types += ("art",) if fields is None: fields = fluid_fields filename = os.path.abspath(filename) self._fields_in_file = fields self._file_amr = filename self._file_particle_header = file_particle_header self._file_particle_data = file_particle_data self._file_particle_stars = file_particle_stars self._find_files(filename) self.skip_particles = skip_particles self.skip_stars = skip_stars self.limit_level = limit_level self.max_level = limit_level self.force_max_level = force_max_level self.spread_age = spread_age Dataset.__init__( self, filename, dataset_type, units_override=units_override, unit_system=unit_system, default_species_fields=default_species_fields, ) self.storage_filename = storage_filename def _find_files(self, file_amr): """ Given the AMR base filename, attempt to find the particle header, star files, etc. """ base_prefix, base_suffix = filename_pattern["amr"] numericstr = file_amr.rsplit("_", 1)[1].replace(base_suffix, "") possibles = glob.glob( os.path.join(os.path.dirname(os.path.abspath(file_amr)), "*") ) for filetype, (prefix, suffix) in filename_pattern.items(): # if this attribute is already set skip it if getattr(self, "_file_" + filetype, None) is not None: continue match = None for possible in possibles: if possible.endswith(numericstr + suffix): if os.path.basename(possible).startswith(prefix): match = possible if match is not None: mylog.info("discovered %s:%s", filetype, match) setattr(self, "_file_" + filetype, match) else: setattr(self, "_file_" + filetype, None) def __str__(self): return self._file_amr.split("/")[-1] def _set_code_unit_attributes(self): """ Generates the conversion to various physical units based on the parameters from the header """ # spatial units z = self.current_redshift h = self.hubble_constant boxcm_cal = self.parameters["boxh"] boxcm_uncal = boxcm_cal / h box_proper = boxcm_uncal / (1 + z) aexpn = self.parameters["aexpn"] # all other units Om0 = self.parameters["Om0"] ng = self.parameters["ng"] boxh = self.parameters["boxh"] aexpn = self.parameters["aexpn"] hubble = self.parameters["hubble"] r0 = boxh / ng v0 = 50.0 * r0 * np.sqrt(Om0) rho0 = 2.776e11 * hubble**2.0 * Om0 aM0 = rho0 * (boxh / hubble) ** 3.0 / ng**3.0 velocity = v0 / aexpn * 1.0e5 # proper cm/s mass = aM0 * 1.98892e33 self.cosmological_simulation = True setdefaultattr(self, "mass_unit", self.quan(mass, f"g*{ng ** 3}")) setdefaultattr(self, "length_unit", self.quan(box_proper, "Mpc")) setdefaultattr(self, "velocity_unit", self.quan(velocity, "cm/s")) setdefaultattr(self, "time_unit", self.length_unit / self.velocity_unit) def _parse_parameter_file(self): """ Get the various simulation parameters & constants. """ self.domain_left_edge = np.zeros(3, dtype="float") self.domain_right_edge = np.zeros(3, dtype="float") + 1.0 self.dimensionality = 3 self.refine_by = 2 self._periodicity = (True, True, True) self.cosmological_simulation = True self.parameters = {} self.parameters.update(constants) self.parameters["Time"] = 1.0 # read the amr header with open(self._file_amr, "rb") as f: amr_header_vals = fpu.read_attrs(f, amr_header_struct, ">") n_to_skip = len(("tl", "dtl", "tlold", "dtlold", "iSO")) fpu.skip(f, n_to_skip, endian=">") (self.ncell) = fpu.read_vector(f, "i", ">")[0] # Try to figure out the root grid dimensions est = int(np.rint(self.ncell ** (1.0 / 3.0))) # Note here: this is the number of *cells* on the root grid. # This is not the same as the number of Octs. # domain dimensions is the number of root *cells* self.domain_dimensions = np.ones(3, dtype="int64") * est self.root_grid_mask_offset = f.tell() self.root_nocts = self.domain_dimensions.prod() // 8 self.root_ncells = self.root_nocts * 8 mylog.debug( "Estimating %i cells on a root grid side, %i root octs", est, self.root_nocts, ) self.root_iOctCh = fpu.read_vector(f, "i", ">")[: self.root_ncells] self.root_iOctCh = self.root_iOctCh.reshape( self.domain_dimensions, order="F" ) self.root_grid_offset = f.tell() self.root_nhvar = fpu.skip(f, endian=">") self.root_nvar = fpu.skip(f, endian=">") # make sure that the number of root variables is a multiple of # rootcells assert self.root_nhvar % self.root_ncells == 0 assert self.root_nvar % self.root_ncells == 0 self.nhydro_variables = ( self.root_nhvar + self.root_nvar ) / self.root_ncells self.iOctFree, self.nOct = fpu.read_vector(f, "i", ">") self.child_grid_offset = f.tell() # lextra needs to be loaded as a string, but it's actually # array values. So pop it off here, and then re-insert. lextra = amr_header_vals.pop("lextra") amr_header_vals["lextra"] = np.frombuffer(lextra, ">f4") self.parameters.update(amr_header_vals) amr_header_vals = None # estimate the root level float_center, fl, iocts, nocts, root_level = _read_art_level_info( f, [0, self.child_grid_offset], 1, coarse_grid=self.domain_dimensions[0] ) del float_center, fl, iocts, nocts self.root_level = root_level mylog.info("Using root level of %02i", self.root_level) # read the particle header self.particle_types = [] self.particle_types_raw = () if not self.skip_particles and self._file_particle_header: with open(self._file_particle_header, "rb") as fh: particle_header_vals = fpu.read_attrs(fh, particle_header_struct, ">") fh.seek(seek_extras) n = particle_header_vals["Nspecies"] wspecies = np.fromfile(fh, dtype=">f", count=10) lspecies = np.fromfile(fh, dtype=">i", count=10) # extras needs to be loaded as a string, but it's actually # array values. So pop it off here, and then re-insert. extras = particle_header_vals.pop("extras") particle_header_vals["extras"] = np.frombuffer(extras, ">f4") self.parameters["wspecies"] = wspecies[:n] self.parameters["lspecies"] = lspecies[:n] for specie in range(n): self.particle_types.append("specie%i" % specie) self.particle_types_raw = tuple(self.particle_types) ls_nonzero = np.diff(lspecies)[: n - 1] ls_nonzero = np.append(lspecies[0], ls_nonzero) self.star_type = len(ls_nonzero) mylog.info("Discovered %i species of particles", len(ls_nonzero)) info_str = "Particle populations: " + "%9i " * len(ls_nonzero) mylog.info(info_str, *ls_nonzero) self._particle_type_counts = dict( zip(self.particle_types_raw, ls_nonzero, strict=True) ) for k, v in particle_header_vals.items(): if k in self.parameters.keys(): if not self.parameters[k] == v: mylog.info( "Inconsistent parameter %s %1.1e %1.1e", k, v, self.parameters[k], ) else: self.parameters[k] = v self.parameters_particles = particle_header_vals self.parameters.update(particle_header_vals) self.parameters["ng"] = self.parameters["Ngridc"] self.parameters["ncell0"] = self.parameters["ng"] ** 3 # setup standard simulation params yt expects to see self.current_redshift = self.parameters["aexpn"] ** -1.0 - 1.0 self.omega_lambda = self.parameters["Oml0"] self.omega_matter = self.parameters["Om0"] self.hubble_constant = self.parameters["hubble"] self.min_level = self.parameters["min_level"] self.max_level = self.parameters["max_level"] if self.limit_level is not None: self.max_level = min(self.limit_level, self.parameters["max_level"]) if self.force_max_level is not None: self.max_level = self.force_max_level self.hubble_time = 1.0 / (self.hubble_constant * 100 / 3.08568025e19) self.current_time = self.quan(b2t(self.parameters["t"]), "Gyr") self.gamma = self.parameters["gamma"] mylog.info("Max level is %02i", self.max_level)
[docs] def create_field_info(self): super().create_field_info() if "wspecies" in self.parameters: # We create dark_matter and stars unions. ptr = self.particle_types_raw pu = ParticleUnion("darkmatter", list(ptr[:-1])) self.add_particle_union(pu) pu = ParticleUnion("stars", list(ptr[-1:])) self.add_particle_union(pu)
@classmethod def _is_valid(cls, filename: str, *args, **kwargs) -> bool: """ Defined for the NMSU file naming scheme. This could differ for other formats. """ f = str(filename) prefix, suffix = filename_pattern["amr"] if not os.path.isfile(f): return False if not f.endswith(suffix): return False with open(f, "rb") as fh: try: fpu.read_attrs(fh, amr_header_struct, ">") return True except Exception: return False
[docs] class ARTParticleFile(ParticleFile): def __init__(self, ds, io, filename, file_id): super().__init__(ds, io, filename, file_id, range=None) self.total_particles = {} for ptype, count in zip( ds.particle_types_raw, ds.parameters["total_particles"], strict=True, ): self.total_particles[ptype] = count with open(filename, "rb") as f: f.seek(0, os.SEEK_END) self._file_size = f.tell()
[docs] class ARTParticleIndex(ParticleIndex): def _setup_filenames(self): # no need for template, all data in one file template = self.dataset.filename_template ndoms = self.dataset.file_count cls = self.dataset._file_class self.data_files = [] fi = 0 for i in range(int(ndoms)): df = cls(self.dataset, self.io, template % {"num": i}, fi) fi += 1 self.data_files.append(df)
[docs] class DarkMatterARTDataset(ARTDataset): _index_class = ARTParticleIndex _file_class = ARTParticleFile filter_bbox = False def __init__( self, filename, dataset_type="dm_art", fields=None, storage_filename=None, skip_particles=False, skip_stars=False, limit_level=None, spread_age=True, force_max_level=None, file_particle_header=None, file_particle_stars=None, units_override=None, unit_system="cgs", ): self.num_zones = 2 self.n_ref = 64 self.particle_types += ("all",) if fields is None: fields = particle_fields filename = os.path.abspath(filename) self._fields_in_file = fields self._file_particle = filename self._file_particle_header = file_particle_header self._find_files(filename) self.skip_stars = skip_stars self.spread_age = spread_age Dataset.__init__( self, filename, dataset_type, units_override=units_override, unit_system=unit_system, ) self.storage_filename = storage_filename def _find_files(self, file_particle): """ Given the particle base filename, attempt to find the particle header and star files. """ base_prefix, base_suffix = filename_pattern["particle_data"] aexpstr = file_particle.rsplit("s0", 1)[1].replace(base_suffix, "") possibles = glob.glob( os.path.join(os.path.dirname(os.path.abspath(file_particle)), "*") ) for filetype, (prefix, suffix) in filename_pattern.items(): # if this attribute is already set skip it if getattr(self, "_file_" + filetype, None) is not None: continue match = None for possible in possibles: if possible.endswith(aexpstr + suffix): if os.path.basename(possible).startswith(prefix): match = possible if match is not None: mylog.info("discovered %s:%s", filetype, match) setattr(self, "_file_" + filetype, match) else: setattr(self, "_file_" + filetype, None) def __str__(self): return self._file_particle.split("/")[-1] def _set_code_unit_attributes(self): """ Generates the conversion to various physical units based on the parameters from the header """ # spatial units z = self.current_redshift h = self.hubble_constant boxcm_cal = self.parameters["boxh"] boxcm_uncal = boxcm_cal / h box_proper = boxcm_uncal / (1 + z) aexpn = self.parameters["aexpn"] # all other units Om0 = self.parameters["Om0"] ng = self.parameters["ng"] boxh = self.parameters["boxh"] aexpn = self.parameters["aexpn"] hubble = self.parameters["hubble"] r0 = boxh / ng rho0 = 2.776e11 * hubble**2.0 * Om0 aM0 = rho0 * (boxh / hubble) ** 3.0 / ng**3.0 velocity = 100.0 * r0 / aexpn * 1.0e5 # proper cm/s mass = aM0 * 1.98892e33 self.cosmological_simulation = True self.mass_unit = self.quan(mass, f"g*{ng ** 3}") self.length_unit = self.quan(box_proper, "Mpc") self.velocity_unit = self.quan(velocity, "cm/s") self.time_unit = self.length_unit / self.velocity_unit def _parse_parameter_file(self): """ Get the various simulation parameters & constants. """ self.domain_left_edge = np.zeros(3, dtype="float") self.domain_right_edge = np.zeros(3, dtype="float") + 1.0 self.dimensionality = 3 self.refine_by = 2 self._periodicity = (True, True, True) self.cosmological_simulation = True self.parameters = {} self.parameters.update(constants) self.parameters["Time"] = 1.0 self.file_count = 1 self.filename_template = self.parameter_filename # read the particle header self.particle_types = [] self.particle_types_raw = () assert self._file_particle_header with open(self._file_particle_header, "rb") as fh: seek = 4 fh.seek(seek) headerstr = fh.read(45).decode("ascii") aexpn = np.fromfile(fh, count=1, dtype=">f4") aexp0 = np.fromfile(fh, count=1, dtype=">f4") amplt = np.fromfile(fh, count=1, dtype=">f4") astep = np.fromfile(fh, count=1, dtype=">f4") istep = np.fromfile(fh, count=1, dtype=">i4") partw = np.fromfile(fh, count=1, dtype=">f4") tintg = np.fromfile(fh, count=1, dtype=">f4") ekin = np.fromfile(fh, count=1, dtype=">f4") ekin1 = np.fromfile(fh, count=1, dtype=">f4") ekin2 = np.fromfile(fh, count=1, dtype=">f4") au0 = np.fromfile(fh, count=1, dtype=">f4") aeu0 = np.fromfile(fh, count=1, dtype=">f4") nrowc = np.fromfile(fh, count=1, dtype=">i4") ngridc = np.fromfile(fh, count=1, dtype=">i4") nspecs = np.fromfile(fh, count=1, dtype=">i4") nseed = np.fromfile(fh, count=1, dtype=">i4") Om0 = np.fromfile(fh, count=1, dtype=">f4") Oml0 = np.fromfile(fh, count=1, dtype=">f4") hubble = np.fromfile(fh, count=1, dtype=">f4") Wp5 = np.fromfile(fh, count=1, dtype=">f4") Ocurv = np.fromfile(fh, count=1, dtype=">f4") wspecies = np.fromfile(fh, count=10, dtype=">f4") lspecies = np.fromfile(fh, count=10, dtype=">i4") extras = np.fromfile(fh, count=79, dtype=">f4") boxsize = np.fromfile(fh, count=1, dtype=">f4") n = nspecs[0] particle_header_vals = {} tmp = [ headerstr, aexpn, aexp0, amplt, astep, istep, partw, tintg, ekin, ekin1, ekin2, au0, aeu0, nrowc, ngridc, nspecs, nseed, Om0, Oml0, hubble, Wp5, Ocurv, wspecies, lspecies, extras, boxsize, ] for i, arr in enumerate(tmp): a1 = dmparticle_header_struct[0][i] a2 = dmparticle_header_struct[1][i] if a2 == 1: particle_header_vals[a1] = arr[0] else: particle_header_vals[a1] = arr[:a2] for specie in range(n): self.particle_types.append("specie%i" % specie) self.particle_types_raw = tuple(self.particle_types) ls_nonzero = np.diff(lspecies)[: n - 1] ls_nonzero = np.append(lspecies[0], ls_nonzero) self.star_type = len(ls_nonzero) mylog.info("Discovered %i species of particles", len(ls_nonzero)) info_str = "Particle populations: " + "%9i " * len(ls_nonzero) mylog.info(info_str, *ls_nonzero) for k, v in particle_header_vals.items(): if k in self.parameters.keys(): if not self.parameters[k] == v: mylog.info( "Inconsistent parameter %s %1.1e %1.1e", k, v, self.parameters[k], ) else: self.parameters[k] = v self.parameters_particles = particle_header_vals self.parameters.update(particle_header_vals) self.parameters["wspecies"] = wspecies[:n] self.parameters["lspecies"] = lspecies[:n] self.parameters["ng"] = self.parameters["Ngridc"] self.parameters["ncell0"] = self.parameters["ng"] ** 3 self.parameters["boxh"] = self.parameters["boxsize"] self.parameters["total_particles"] = ls_nonzero self.domain_dimensions = np.ones(3, dtype="int64") * 2 # NOT ng # setup standard simulation params yt expects to see # Convert to float to please unyt self.current_redshift = float(self.parameters["aexpn"] ** -1.0 - 1.0) self.omega_lambda = float(particle_header_vals["Oml0"]) self.omega_matter = float(particle_header_vals["Om0"]) self.hubble_constant = float(particle_header_vals["hubble"]) self.min_level = 0 self.max_level = 0 # self.min_level = particle_header_vals['min_level'] # self.max_level = particle_header_vals['max_level'] # if self.limit_level is not None: # self.max_level = min( # self.limit_level, particle_header_vals['max_level']) # if self.force_max_level is not None: # self.max_level = self.force_max_level self.hubble_time = 1.0 / (self.hubble_constant * 100 / 3.08568025e19) self.parameters["t"] = a2b(self.parameters["aexpn"]) self.current_time = self.quan(b2t(self.parameters["t"]), "Gyr") self.gamma = self.parameters["gamma"] mylog.info("Max level is %02i", self.max_level)
[docs] def create_field_info(self): super(ARTDataset, self).create_field_info() ptr = self.particle_types_raw pu = ParticleUnion("darkmatter", list(ptr)) self.add_particle_union(pu) pass
@classmethod def _is_valid(cls, filename: str, *args, **kwargs) -> bool: """ Defined for the NMSU file naming scheme. This could differ for other formats. """ f = str(filename) prefix, suffix = filename_pattern["particle_data"] if not os.path.isfile(f): return False if not f.endswith(suffix): return False if "s0" not in f: # ATOMIC.DAT, for instance, passes the other tests, but then dies # during _find_files because it can't be split. return False with open(f, "rb") as fh: try: amr_prefix, amr_suffix = filename_pattern["amr"] possibles = glob.glob( os.path.join(os.path.dirname(os.path.abspath(f)), "*") ) for possible in possibles: if possible.endswith(amr_suffix): if os.path.basename(possible).startswith(amr_prefix): return False except Exception: pass try: seek = 4 fh.seek(seek) headerstr = np.fromfile(fh, count=1, dtype=(str, 45)) # NOQA aexpn = np.fromfile(fh, count=1, dtype=">f4") # NOQA aexp0 = np.fromfile(fh, count=1, dtype=">f4") # NOQA amplt = np.fromfile(fh, count=1, dtype=">f4") # NOQA astep = np.fromfile(fh, count=1, dtype=">f4") # NOQA istep = np.fromfile(fh, count=1, dtype=">i4") # NOQA partw = np.fromfile(fh, count=1, dtype=">f4") # NOQA tintg = np.fromfile(fh, count=1, dtype=">f4") # NOQA ekin = np.fromfile(fh, count=1, dtype=">f4") # NOQA ekin1 = np.fromfile(fh, count=1, dtype=">f4") # NOQA ekin2 = np.fromfile(fh, count=1, dtype=">f4") # NOQA au0 = np.fromfile(fh, count=1, dtype=">f4") # NOQA aeu0 = np.fromfile(fh, count=1, dtype=">f4") # NOQA nrowc = np.fromfile(fh, count=1, dtype=">i4") # NOQA ngridc = np.fromfile(fh, count=1, dtype=">i4") # NOQA nspecs = np.fromfile(fh, count=1, dtype=">i4") # NOQA nseed = np.fromfile(fh, count=1, dtype=">i4") # NOQA Om0 = np.fromfile(fh, count=1, dtype=">f4") # NOQA Oml0 = np.fromfile(fh, count=1, dtype=">f4") # NOQA hubble = np.fromfile(fh, count=1, dtype=">f4") # NOQA Wp5 = np.fromfile(fh, count=1, dtype=">f4") # NOQA Ocurv = np.fromfile(fh, count=1, dtype=">f4") # NOQA wspecies = np.fromfile(fh, count=10, dtype=">f4") # NOQA lspecies = np.fromfile(fh, count=10, dtype=">i4") # NOQA extras = np.fromfile(fh, count=79, dtype=">f4") # NOQA boxsize = np.fromfile(fh, count=1, dtype=">f4") # NOQA return True except Exception: return False
[docs] class ARTDomainSubset(OctreeSubset): @property def oct_handler(self): return self.domain.oct_handler
[docs] def fill(self, content, ftfields, selector): """ This is called from IOHandler. It takes content which is a binary stream, reads the requested field over this while domain. It then uses oct_handler fill to reorganize values from IO read index order to the order they are in in the octhandler. """ oct_handler = self.oct_handler all_fields = self.domain.ds.index.fluid_field_list fields = [f for ft, f in ftfields] field_idxs = [all_fields.index(f) for f in fields] source, tr = {}, {} cell_count = selector.count_oct_cells(self.oct_handler, self.domain_id) levels, cell_inds, file_inds = self.oct_handler.file_index_octs( selector, self.domain_id, cell_count ) for field in fields: tr[field] = np.zeros(cell_count, "float64") data = _read_root_level( content, self.domain.level_child_offsets, self.domain.level_count ) ns = (self.domain.ds.domain_dimensions.prod() // 8, 8) for field, fi in zip(fields, field_idxs, strict=True): source[field] = np.empty(ns, dtype="float64", order="C") dt = data[fi, :].reshape(self.domain.ds.domain_dimensions, order="F") for i in range(2): for j in range(2): for k in range(2): ii = ((k * 2) + j) * 2 + i # Note: C order because our index converts C to F. source[field][:, ii] = dt[i::2, j::2, k::2].ravel(order="C") oct_handler.fill_level(0, levels, cell_inds, file_inds, tr, source) del source # Now we continue with the additional levels. for level in range(1, self.ds.index.max_level + 1): no = self.domain.level_count[level] noct_range = [0, no] source = _read_child_level( content, self.domain.level_child_offsets, self.domain.level_offsets, self.domain.level_count, level, fields, self.domain.ds.domain_dimensions, self.domain.ds.parameters["ncell0"], noct_range=noct_range, ) oct_handler.fill_level(level, levels, cell_inds, file_inds, tr, source) return tr
[docs] class ARTDomainFile: """ Read in the AMR, left/right edges, fill out the octhandler """ # We already read in the header in static output, # and since these headers are defined in only a single file it's # best to leave them in the static output _last_mask = None _last_selector_id = None def __init__(self, ds, nvar, oct_handler, domain_id): self.nvar = nvar self.ds = ds self.domain_id = domain_id self._level_count = None self._level_oct_offsets = None self._level_child_offsets = None self._oct_handler = oct_handler @property def oct_handler(self): return self._oct_handler @property def level_count(self): # this is number of *octs* if self._level_count is not None: return self._level_count self.level_offsets return self._level_count @property def level_child_offsets(self): if self._level_count is not None: return self._level_child_offsets self.level_offsets return self._level_child_offsets @property def level_offsets(self): # this is used by the IO operations to find the file offset, # and then start reading to fill values # note that this is called hydro_offset in ramses if self._level_oct_offsets is not None: return self._level_oct_offsets # We now have to open the file and calculate it f = open(self.ds._file_amr, "rb") ( nhydrovars, inoll, _level_oct_offsets, _level_child_offsets, ) = self._count_art_octs( f, self.ds.child_grid_offset, self.ds.min_level, self.ds.max_level ) # remember that the root grid is by itself; manually add it back in inoll[0] = self.ds.domain_dimensions.prod() // 8 _level_child_offsets[0] = self.ds.root_grid_offset self.nhydrovars = nhydrovars self.inoll = inoll # number of octs self._level_oct_offsets = _level_oct_offsets self._level_child_offsets = _level_child_offsets self._level_count = inoll return self._level_oct_offsets def _count_art_octs(self, f, offset, MinLev, MaxLevelNow): level_oct_offsets = [ 0, ] level_child_offsets = [ 0, ] f.seek(offset) nchild, ntot = 8, 0 Level = np.zeros(MaxLevelNow + 1 - MinLev, dtype="int64") iNOLL = np.zeros(MaxLevelNow + 1 - MinLev, dtype="int64") iHOLL = np.zeros(MaxLevelNow + 1 - MinLev, dtype="int64") for Lev in range(MinLev + 1, MaxLevelNow + 1): level_oct_offsets.append(f.tell()) # Get the info for this level, skip the rest # print("Reading oct tree data for level", Lev) # print('offset:',f.tell()) Level[Lev], iNOLL[Lev], iHOLL[Lev] = fpu.read_vector(f, "i", ">") # print('Level %i : '%Lev, iNOLL) # print('offset after level record:',f.tell()) nLevel = iNOLL[Lev] ntot = ntot + nLevel # Skip all the oct hierarchy data ns = fpu.peek_record_size(f, endian=">") size = struct.calcsize(">i") + ns + struct.calcsize(">i") f.seek(f.tell() + size * nLevel) level_child_offsets.append(f.tell()) # Skip the child vars data ns = fpu.peek_record_size(f, endian=">") size = struct.calcsize(">i") + ns + struct.calcsize(">i") f.seek(f.tell() + size * nLevel * nchild) # find nhydrovars nhydrovars = 8 + 2 f.seek(offset) return nhydrovars, iNOLL, level_oct_offsets, level_child_offsets def _read_amr_level(self, oct_handler): """Open the oct file, read in octs level-by-level. For each oct, only the position, index, level and domain are needed - its position in the octree is found automatically. The most important is finding all the information to feed oct_handler.add """ self.level_offsets f = open(self.ds._file_amr, "rb") for level in range(1, self.ds.max_level + 1): unitary_center, fl, iocts, nocts, root_level = _read_art_level_info( f, self._level_oct_offsets, level, coarse_grid=self.ds.domain_dimensions[0], root_level=self.ds.root_level, ) nocts_check = oct_handler.add(self.domain_id, level, unitary_center) assert nocts_check == nocts mylog.debug( "Added %07i octs on level %02i, cumulative is %07i", nocts, level, oct_handler.nocts, ) def _read_amr_root(self, oct_handler): self.level_offsets # add the root *cell* not *oct* mesh root_octs_side = self.ds.domain_dimensions[0] / 2 NX = np.ones(3) * root_octs_side LE = np.array([0.0, 0.0, 0.0], dtype="float64") RE = np.array([1.0, 1.0, 1.0], dtype="float64") root_dx = (RE - LE) / NX LL = LE + root_dx / 2.0 RL = RE - root_dx / 2.0 # compute floating point centers of root octs root_fc = np.mgrid[ LL[0] : RL[0] : NX[0] * 1j, LL[1] : RL[1] : NX[1] * 1j, LL[2] : RL[2] : NX[2] * 1j, ] root_fc = np.vstack([p.ravel() for p in root_fc]).T oct_handler.add(self.domain_id, 0, root_fc) assert oct_handler.nocts == root_fc.shape[0] mylog.debug( "Added %07i octs on level %02i, cumulative is %07i", root_octs_side**3, 0, oct_handler.nocts, )
[docs] def included(self, selector): return True