From 2aa97bc055a5f639ffbd0eee9d8d21b3ca29c6f4 Mon Sep 17 00:00:00 2001 From: Markku Alho Date: Wed, 29 May 2024 12:59:26 +0300 Subject: [PATCH 001/124] Initial additions to write MESH_DOMAIN_EXTENTS --- pyVlsv/vlsvwriter.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/pyVlsv/vlsvwriter.py b/pyVlsv/vlsvwriter.py index c510ea32..5a93f341 100644 --- a/pyVlsv/vlsvwriter.py +++ b/pyVlsv/vlsvwriter.py @@ -76,19 +76,24 @@ def __initialize( self, vlsvReader, copy_meshes=None ): tags['MESH_OFFSETS'] = '' tags['MESH'] = '' tags['MESH_DOMAIN_SIZES'] = '' + tags['MESH_DOMAIN_EXTENTS'] = '' tags['MESH_GHOST_DOMAINS'] = '' tags['MESH_GHOST_LOCALIDS'] = '' tags['CellID'] = '' tags['MESH_BBOX'] = '' tags['COORDS'] = '' + xml_mesh_tags = {} # Copy the xml root for child in xml_root: if child.tag in tags: if 'name' in child.attrib: name = child.attrib['name'] else: name = '' - if 'mesh' in child.attrib: mesh = child.attrib['mesh'] - else: mesh = None + if 'mesh' in child.attrib: + mesh = child.attrib['mesh'] + xml_mesh_tags.setdefault(mesh, []).append(child.tag) + else: + mesh = None tag = child.tag if copy_meshes is not None: @@ -106,6 +111,11 @@ def __initialize( self, vlsvReader, copy_meshes=None ): #print("writing",name, tag) self.__write( data=data, name=name, tag=tag, mesh=mesh, extra_attribs=extra_attribs ) + + for mesh, tags in xml_mesh_tags.items(): + if "MESH_DOMAIN_EXTENTS" not in tags and mesh == "SpatialGrid": + self.__write( data = vlsvReader.get_mesh_domain_extents(mesh), name='', tag="MESH_DOMAIN_EXTENTS", mesh=mesh) + def copy_variables( self, vlsvReader, varlist=None ): From f230e3402483e89d8f9073a507593ddf12131ebd Mon Sep 17 00:00:00 2001 From: Markku Alho Date: Thu, 30 May 2024 12:26:20 +0300 Subject: [PATCH 002/124] First implementations --- pyVlsv/vlsvreader.py | 33 +++++++++++++++++++++++++++++++++ pyVlsv/vlsvwriter.py | 8 ++++++-- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/pyVlsv/vlsvreader.py b/pyVlsv/vlsvreader.py index b6ebae40..a0fef7bf 100644 --- a/pyVlsv/vlsvreader.py +++ b/pyVlsv/vlsvreader.py @@ -3151,4 +3151,37 @@ def optimize_clear_fileindex_for_cellid(self): ''' self.__fileindex_for_cellid = {} + def get_mesh_domain_extents(self, mesh): + if (mesh == 'fsgrid'): + raise NotImplementedError + elif (mesh == 'ionosphere'): + raise NotImplementedError + elif (mesh == 'SpatialGrid'): + MESH_DOMAIN_SIZES = self.read(name="", tag="MESH_DOMAIN_SIZES", mesh="SpatialGrid") + n_domain_cells = MESH_DOMAIN_SIZES[:,0]-MESH_DOMAIN_SIZES[:,1] + cids_all = self.read_variable("CellID") + lowcorners_all = self.read_variable("vg_cell_lowcorners") + dxs_all = self.read_variable("vg_dx") + + extents = np.zeros((MESH_DOMAIN_SIZES.shape[0],6)) + start = 0 + end = 0 + for i, ncells in enumerate(n_domain_cells): + end = start+ncells + cids = cids_all[start:end] + lowcorners = lowcorners_all[start:end] + dxs = dxs_all[start:end] + highcorners = lowcorners+dxs + + mins = np.min(lowcorners,axis=0) + maxs = np.max(highcorners,axis=0) + + extents[i,:] = [*mins, *maxs] + + start += end + + return extents.reshape((-1)) + + else: + raise ValueError diff --git a/pyVlsv/vlsvwriter.py b/pyVlsv/vlsvwriter.py index 5a93f341..c0e2532d 100644 --- a/pyVlsv/vlsvwriter.py +++ b/pyVlsv/vlsvwriter.py @@ -112,9 +112,13 @@ def __initialize( self, vlsvReader, copy_meshes=None ): self.__write( data=data, name=name, tag=tag, mesh=mesh, extra_attribs=extra_attribs ) + # Find out and write possibly nonexisting metadata for mesh, tags in xml_mesh_tags.items(): - if "MESH_DOMAIN_EXTENTS" not in tags and mesh == "SpatialGrid": - self.__write( data = vlsvReader.get_mesh_domain_extents(mesh), name='', tag="MESH_DOMAIN_EXTENTS", mesh=mesh) + if mesh == "SpatialGrid": + if "MESH_DOMAIN_EXTENTS" not in tags: + extents = vlsvReader.get_mesh_domain_extents(mesh) + print(extents) + self.__write( data = extents, name='', tag="MESH_DOMAIN_EXTENTS", mesh=mesh) From 9deae7505d43ff4c186eca8f20e69a95139782b5 Mon Sep 17 00:00:00 2001 From: Markku Alho Date: Mon, 10 Mar 2025 16:27:30 +0200 Subject: [PATCH 003/124] Initial commit for linking vlsvreaders together.. so that e.g. vg_j can be accessed from the original vlsv file that has a linked sidecar file --- analysator/pyVlsv/vlsvreader.py | 91 +++++++++++++++++++++++++++++---- 1 file changed, 82 insertions(+), 9 deletions(-) diff --git a/analysator/pyVlsv/vlsvreader.py b/analysator/pyVlsv/vlsvreader.py index 0c49fe3c..b0b73f4b 100644 --- a/analysator/pyVlsv/vlsvreader.py +++ b/analysator/pyVlsv/vlsvreader.py @@ -30,6 +30,7 @@ import sys import re import numbers +import pickle # for caching linked readers, swtich to VLSV/XML at some point import vlsvvariables from reduction import datareducers,multipopdatareducers,data_operators,v5reducers,multipopv5reducers,deprecated_datareducers @@ -185,6 +186,18 @@ def __init__(self, file_name, fsGridDecomposition=None): self.__xml_root = ET.fromstring("") self.__fileindex_for_cellid={} + self.__linked_files = set() + self.__linked_readers = set() + + if(os.path.isfile(self.get_linked_readers_filename())): + with open(self.get_linked_readers_filename(), 'rb') as f: + l = pickle.load(f) + logging.info("Loaded linked readers from "+self.get_linked_readers_filename()) + self.__linked_files.update(l) + for f in self.__linked_files: + self.add_linked_file(f) + pass + self.__max_spatial_amr_level = -1 self.__fsGridDecomposition = fsGridDecomposition @@ -299,6 +312,29 @@ def __init__(self, file_name, fsGridDecomposition=None): self.__fptr.close() + def get_linked_readers_filename(self): + pth, base = os.path.split(self.file_name) + + s = os.path.join(pth,"vlsvmeta",base[:-5]+"_linked_readers.pkl") + return s + + def add_linked_file(self, fname): + if os.path.exists(fname): + self.__linked_files.add(fname) + self.__linked_readers.add(VlsvReader(fname)) + else: + logging.warning("Could not link "+fname+" (path does not exist)") + + def save_linked_readers_file(self): + fn = self.get_linked_readers_filename() + logging.info("Saving linked readers to "+fn) + dn = os.path.dirname(fn) + if not os.path.isdir(dn): + os.mkdir(dn) + with open(fn,'wb') as f: + pickle.dump(self.__linked_files, f) + + def __popmesh(self, popname): ''' Get the population-specific vspace mesh info object, and initialize it if it does not exist @@ -657,18 +693,24 @@ def __check_datareducer(self, name, reducer): def get_variables(self): - varlist = [] + varlist = set() + + for reader in self.__linked_readers: + varlist.update(set(reader.get_variables())) for child in self.__xml_root: if child.tag == "VARIABLE" and "name" in child.attrib: name = child.attrib["name"] - varlist.append(name) + varlist.add(name) - return varlist + return list(varlist) def get_reducers(self): - varlist = [] + varlist = set() + + for reader in self.__linked_readers: + varlist.update(reader.get_reducers()) reducer_max_len = 0 @@ -684,11 +726,11 @@ def get_reducers(self): if self.__check_datareducer(name,reducer): if name[:3] == 'pop': for pop in self.active_populations: - varlist.append(pop+'/'+name[4:]) + varlist.add(pop+'/'+name[4:]) else: - varlist.append(name) + varlist.add(name) - return varlist + return list(varlist) def list(self, parameter=True, variable=True, mesh=False, datareducer=False, operator=False, other=False): @@ -773,6 +815,27 @@ def check_parameter( self, name ): return True return False + def linked_readers_check_variable(self, name): + ''' Test all linked variables if any of them returns True on test function + + :param fun: Function to pass to linked readers (VlsvReader member function/first param VlsvReader) + :param args: arguments to pass to fun + :param kwargs: keyword arguments to pass to fun + ''' + + ret = False + for reader in self.__linked_readers: + try: + ret = reader.check_variable(name) + except Exception as e: # Let's not care if a sidecar file does not contain something + pass + if ret: + return ret + else: + continue + + return False + def check_variable( self, name ): ''' Checks if a given variable is in the vlsv reader @@ -792,6 +855,9 @@ def check_variable( self, name ): # Variable not in the vlsv file plot_B_vol() ''' + if self.linked_readers_check_variable(name): + return True + for child in self.__xml_root: if child.tag == "VARIABLE" and "name" in child.attrib: if child.attrib["name"].lower() == name.lower(): @@ -1280,7 +1346,7 @@ def read(self, name="", tag="", mesh="", operator="pass", cellids=-1): fptr.close() if name!="": - raise ValueError("Error: variable "+name+"/"+tag+"/"+mesh+"/"+operator+" not found in .vlsv file or in data reducers!") + raise ValueError("Error: variable "+name+"/"+tag+"/"+mesh+"/"+operator+" not found in .vlsv file or in data reducers!\n Reader file "+self.file_name) @@ -1985,6 +2051,14 @@ def read_variable(self, name, cellids=-1,operator="pass"): if((name,operator) in self.variable_cache.keys()): return self.read_variable_from_cache(name,cellids,operator) + for reader in self.__linked_readers: + try: + res = reader.read_variable(name=name, cellids=cellids, operator=operator) + print(self.file_name, 'read_variable', name, res) + return res + except: + pass + # Passes the list of cell id's onwards - optimization for reading is done in the lower level read() method return self.read(mesh="SpatialGrid", name=name, tag="VARIABLE", operator=operator, cellids=cellids) @@ -3256,7 +3330,6 @@ def read_parameter(self, name): if name=="t": if self.check_parameter(name="time"): return self.read(name="time", tag="PARAMETER") - return self.read(name=name, tag="PARAMETER") From e14e6a7a3430486f7f562bdab2b832dab3148988 Mon Sep 17 00:00:00 2001 From: Markku Alho Date: Fri, 14 Mar 2025 16:35:59 +0200 Subject: [PATCH 004/124] Forays towards HDF5 storage (for read-write support without implementing it in VLSV) --- analysator/pyVlsv/vlsvreader.py | 170 ++++++++++++++++++++++++++------ pyproject.toml | 1 + requirements.txt | 1 + 3 files changed, 141 insertions(+), 31 deletions(-) diff --git a/analysator/pyVlsv/vlsvreader.py b/analysator/pyVlsv/vlsvreader.py index b0b73f4b..2c778ef5 100644 --- a/analysator/pyVlsv/vlsvreader.py +++ b/analysator/pyVlsv/vlsvreader.py @@ -30,7 +30,8 @@ import sys import re import numbers -import pickle # for caching linked readers, swtich to VLSV/XML at some point +import pickle # for caching linked readers, switch to VLSV/XML at some point - h5py? +import h5py import vlsvvariables from reduction import datareducers,multipopdatareducers,data_operators,v5reducers,multipopv5reducers,deprecated_datareducers @@ -186,17 +187,13 @@ def __init__(self, file_name, fsGridDecomposition=None): self.__xml_root = ET.fromstring("") self.__fileindex_for_cellid={} + self.__metadata_read = False + self.__metadata_dict = {} # Used for reading in and storing derived/other metadata such as linked file paths self.__linked_files = set() self.__linked_readers = set() - if(os.path.isfile(self.get_linked_readers_filename())): - with open(self.get_linked_readers_filename(), 'rb') as f: - l = pickle.load(f) - logging.info("Loaded linked readers from "+self.get_linked_readers_filename()) - self.__linked_files.update(l) - for f in self.__linked_files: - self.add_linked_file(f) - pass + + self.__max_spatial_amr_level = -1 self.__fsGridDecomposition = fsGridDecomposition @@ -217,7 +214,7 @@ def __init__(self, file_name, fsGridDecomposition=None): self.__unavailable_reducers = set() # Set of strings of datareducer names self.__current_reducer_tree_nodes = set() # Set of strings of datareducer names - self.__read_xml_footer() + # vertex-indices is a 3-tuple of integers self.__dual_cells = {(0,0,0):(1,1,1,1,1,1,1,1)} # vertex-indices : 8-tuple of cellids at each corner (for x for y for z) self.__dual_bboxes = {} # vertex-indices : 6-list of (xmin, ymin, zmin, xmax, ymax, zmax) for the bounding box of each dual cell @@ -227,6 +224,10 @@ def __init__(self, file_name, fsGridDecomposition=None): self.__cell_duals = {} # cellid : tuple of vertex-indices that span this cell self.__regular_neighbor_cache = {} # cellid-of-low-corner : (8,) np.array of cellids) + # Start calling functions only after initializing trivial members + self.get_linked_readers() + self.__read_xml_footer() + # Check if the file is using new or old vlsv format # Read parameters (Note: Reading the spatial cell locations and # storing them will anyway take the most time and memory): @@ -313,18 +314,114 @@ def __init__(self, file_name, fsGridDecomposition=None): self.__fptr.close() def get_linked_readers_filename(self): + '''Need to go to a consolidated metadata handler''' pth, base = os.path.split(self.file_name) s = os.path.join(pth,"vlsvmeta",base[:-5]+"_linked_readers.pkl") return s + + def get_linked_readers(self, reload=False): + self.__linked_files = self.get_reader_metadata("linked_reader_files", set()) + if len(self.__linked_files)==0 or reload: + if(os.path.isfile(self.get_linked_readers_filename())): + with open(self.get_linked_readers_filename(), 'rb') as f: + l = pickle.load(f) + logging.info("Loaded linked readers from "+self.get_linked_readers_filename()) + self.__linked_files.update(l) + print(l) + + else: + self.add_linked_readers() + + self.add_metadata("linked_reader_files",self.__linked_files) + + + + return self.__linked_readers + + def get_metadata_filename(self): + pth, base = os.path.split(self.file_name) + + s = os.path.join(pth,"vlsvmeta",base[:-5]+"_metadata.pkl") + return s + + def get_h5_metadata(self, key, default): + ''' Read metadata from hdf5 metadata file, and if not available, + return the given default value. + + :param data: str, a key to stored metadata. + :param default + ''' + if type(key) == type(("a tuple",)): + print("tuple reader not implemented") + elif type(key) == type("a string"): + print("Reading str-keyed data") + else: + raise TypeError("key must be str or tuple") + + if not self.__metadata_read: + try: + fn = self.get_metadata_filename() + with open(fn,'rb') as f: + self.__metadata_dict = pickle.load(f) + except: + logging.debug("No metadata file found.") + self.__metadata_read = True + + return self.__metadata_dict.get(key,default) + + + def get_reader_metadata(self, key, default): + ''' Read metadata from metadata file, and if not available, + return the given default value. + + :param data: str, a key to stored metadata. + :param default + ''' + + + if not self.__metadata_read: + try: + fn = self.get_metadata_filename() + with open(fn,'rb') as f: + self.__metadata_dict = pickle.load(f) + except: + logging.debug("No metadata file found.") + self.__metadata_read = True + return self.__metadata_dict.get(key,default) + + def add_metadata(self, key, value): + self.__metadata_dict[key] = value + self.save_metadata() + + def save_metadata(self): + fn = self.get_metadata_filename() + try: + with open(fn,'wb') as f: + pickle.dump(self.__metadata_dict,f) + except Exception as e: + logging.warning("Could not save metadata file, error: "+str(e)) + def add_linked_file(self, fname): if os.path.exists(fname): - self.__linked_files.add(fname) + self.__linked_files.add(VlsvReader(fname)) + else: + logging.warning("Could not link "+fname+" (path does not exist)") + + def add_linked_reader(self, fname): + if os.path.exists(fname): + for reader in self.__linked_readers: + if fname == reader.file_name: + return self.__linked_readers.add(VlsvReader(fname)) else: logging.warning("Could not link "+fname+" (path does not exist)") + def add_linked_readers(self): + for fname in self.__linked_files: + self.add_linked_reader(fname) + def save_linked_readers_file(self): fn = self.get_linked_readers_filename() logging.info("Saving linked readers to "+fn) @@ -1827,6 +1924,36 @@ def read_fsgrid_variable_cellid(self, name, cellids=-1, operator="pass"): else: return [self.downsample_fsgrid_subarray(cid, var) for cid in cellids] + def get_fsgrid_decomposition(self): + # Try if in metadata + self.__fsGridDecomposition = self.get_reader_metadata(("MESH_DECOMPOSITION","fsgrid"),None) + if(self.__fsGridDecomposition is not None): + print("read ",self.__fsGridDecomposition) + + if self.__fsGridDecomposition is None: + self.__fsGridDecomposition = self.read(tag="MESH_DECOMPOSITION",mesh='fsgrid') + if self.__fsGridDecomposition is not None: + logging.info("Found FsGrid decomposition from vlsv file: " + str(self.__fsGridDecomposition)) + else: + logging.info("Did not find FsGrid decomposition from vlsv file.") + + # If decomposition is None even after reading, we need to calculate it: + if self.__fsGridDecomposition is None: + logging.info("Calculating fsGrid decomposition from the file") + self.__fsGridDecomposition = fsDecompositionFromGlobalIds(self) + logging.info("Computed FsGrid decomposition to be: " + str(self.__fsGridDecomposition)) + else: + # Decomposition is a list (or fail assertions below) - use it instead + pass + + assert len(self.__fsGridDecomposition) == 3, "Manual FSGRID decomposition should have three elements, but is "+str(self.__fsGridDecomposition) + assert np.prod(self.__fsGridDecomposition) == self.read_parameter("numWritingRanks"), "Manual FSGRID decomposition should have a product of numWritingRanks ("+str(numWritingRanks)+"), but is " + str(np.prod(self.__fsGridDecomposition)) + " for decomposition "+str(self.__fsGridDecomposition) + + self.add_metadata(("MESH_DECOMPOSITION","fsgrid"), self.__fsGridDecomposition) + + return self.__fsGridDecomposition + + def read_fsgrid_variable(self, name, operator="pass"): ''' Reads fsgrid variables from the open vlsv file. Arguments: @@ -1867,26 +1994,7 @@ def calcLocalSize(globalCells, ntasks, my_n): return n_per_task currentOffset = 0; - if self.__fsGridDecomposition is None: - self.__fsGridDecomposition = self.read(tag="MESH_DECOMPOSITION",mesh='fsgrid') - if self.__fsGridDecomposition is not None: - logging.info("Found FsGrid decomposition from vlsv file: " + str(self.__fsGridDecomposition)) - else: - logging.info("Did not find FsGrid decomposition from vlsv file.") - - # If decomposition is None even after reading, we need to calculate it: - if self.__fsGridDecomposition is None: - logging.info("Calculating fsGrid decomposition from the file") - self.__fsGridDecomposition = fsDecompositionFromGlobalIds(self) - logging.info("Computed FsGrid decomposition to be: " + str(self.__fsGridDecomposition)) - else: - # Decomposition is a list (or fail assertions below) - use it instead - pass - - assert len(self.__fsGridDecomposition) == 3, "Manual FSGRID decomposition should have three elements, but is "+str(self.__fsGridDecomposition) - assert np.prod(self.__fsGridDecomposition) == numWritingRanks, "Manual FSGRID decomposition should have a product of numWritingRanks ("+str(numWritingRanks)+"), but is " + str(np.prod(self.__fsGridDecomposition)) + " for decomposition "+str(self.__fsGridDecomposition) - - + self.get_fsgrid_decomposition() for i in range(0,numWritingRanks): x = (i // self.__fsGridDecomposition[2]) // self.__fsGridDecomposition[1] diff --git a/pyproject.toml b/pyproject.toml index ceeb7f0d..fc8952c0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,6 +26,7 @@ dependencies = [ "matplotlib", "packaging", "scikit-image", + "h5py", ] [project.optional-dependencies] none = [ diff --git a/requirements.txt b/requirements.txt index 444e84df..ccb71e27 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,4 @@ matplotlib scikit-image packaging vtk>=9.2 +h5py \ No newline at end of file From 077b6c2b2260cebe7bb4cf9b16fc44d822539c8f Mon Sep 17 00:00:00 2001 From: jreimi Date: Thu, 5 Jun 2025 10:08:14 +0300 Subject: [PATCH 005/124] Added documentation strings to magnetopause scripts and fixed plotting function call. --- scripts/magnetopause2d.py | 29 ++++++++++--- scripts/magnetopause3d.py | 89 +++++++++++++++++++++++++-------------- 2 files changed, 81 insertions(+), 37 deletions(-) diff --git a/scripts/magnetopause2d.py b/scripts/magnetopause2d.py index 5bf4e5df..1e3d433f 100644 --- a/scripts/magnetopause2d.py +++ b/scripts/magnetopause2d.py @@ -1,17 +1,20 @@ ''' -Finds the magnetopause position by tracing steamines of the plasma flow for two-dimensional Vlasiator runs. Needs the yt package. +Finds the magnetopause position by tracing streamines of the plasma flow for two-dimensional Vlasiator runs. Needs the yt package. ''' import numpy as np import analysator as pt -import plot_colormap import yt from yt.visualization.api import Streamlines - - def interpolate(streamline, x_points): + """Interpolates a single streamline for make_magnetopause(). + + :param streamline: a single streamline to be interpolated + :param x_points: points in the x-axis to use for interpolation + :returns: the streamline as numpy array of x,z coordinate points where the x-axis coordinates are the points given to the function + """ arr = np.array(streamline) @@ -26,6 +29,11 @@ def interpolate(streamline, x_points): def make_streamlines(vlsvFileName): + """Traces streamlines of velocity field from outside the magnetosphere to magnetotail using the yt-package. + + :param vlsvFileName: directory and file name of vlsv file, to be given to VlsvReader + :returns: streamlines as numpy array + """ ## make streamlines boxre = [0,0] @@ -81,6 +89,11 @@ def to_Re(m): #meters to Re return np.array(streamlines_pos.streamlines) def make_magnetopause(streams): + """Finds the mangetopause location based on streamlines. + + :param streams: streamlines + :returns: magnetopause as coordinate points from tail on positive x-axis to tail on negative x-axis + """ streampoints = np.reshape(streams, (streams.shape[0]*streams.shape[1], 3)) #all the points in one array @@ -126,6 +139,12 @@ def make_magnetopause(streams): def polar_to_cartesian(r, phi): + """Converts polar coordinates to cartesian. + + :param r: radius + :param phi: angular coordinate in degrees + :returns: y, z -coordinates in cartesian system + """ phi = np.deg2rad(phi) y = r * np.cos(phi) z = r * np.sin(phi) @@ -166,7 +185,7 @@ def external_plot(ax,XmeshXY=None, YmeshXY=None, pass_maps=None): ax.plot(magnetopause[:,0], magnetopause[:,1], color='cyan', linewidth=1.0) - plot_colormap.plot_colormap( + pt.plot.plot_colormap( filename=fileLocation+fileN, outputdir=outdir, nooverwrite=None, diff --git a/scripts/magnetopause3d.py b/scripts/magnetopause3d.py index 906880d7..517fdd40 100644 --- a/scripts/magnetopause3d.py +++ b/scripts/magnetopause3d.py @@ -1,24 +1,27 @@ ''' -Finds the magnetopause position by tracing steamines of the plasma flow for three-dimensional Vlasiator runs. Needs the yt package. +Finds the magnetopause position by tracing streamlines of the plasma flow for three-dimensional Vlasiator runs. Needs the yt package. ''' from pyCalculations import ids3d import matplotlib.pyplot as plt import numpy as np import analysator as pt -import plot_colormap3dslice import yt import math from mpl_toolkits import mplot3d from yt.visualization.api import Streamlines - - -def to_Re(m): #meters to Re +def to_Re(m): + """Converts units from meters to R_E (Earth's radius, 6371000m)""" return m/6371000 def cartesian_to_polar(cartesian_coords): # for segments of plane + """Converts cartesian coordinates to polar (for the segments of the yz-planes). + + :param cartesian_coords: (y,z) coordinates as list or array + :returns: the polar coordinates r, phi (angle in degrees) + """ y,z = cartesian_coords[0], cartesian_coords[1] r = np.sqrt(z**2 + y**2) phi = np.arctan2(z, y) @@ -27,6 +30,12 @@ def cartesian_to_polar(cartesian_coords): # for segments of plane return(r, phi) def polar_to_cartesian(r, phi): + """Converts polar coordinates of the yz-plane to cartesian coordinates. + + :param r: radius of the segment (distance from the x-axis) + :param phi: the angle coordinate in degrees + :returns: y, z -coordinates + """ phi = np.deg2rad(phi) y = r * np.cos(phi) z = r * np.sin(phi) @@ -34,6 +43,12 @@ def polar_to_cartesian(r, phi): def interpolate(streamline, x_points): + """IInterpolates a single streamline for make_magnetopause(). + + :param streamline: a single streamline to be interpolated + :param x_points: points in the x-axis to use for interpolation + :returns: the streamline as numpy array of coordinate points where the x-axis coordinates are the points given to the function + """ arr = np.array(streamline) # set arrays for interpolation @@ -53,32 +68,31 @@ def interpolate(streamline, x_points): def make_surface(coords): + '''Defines a surface constructed of input coordinates as triangles. - ''' - Defines surface constructed of input coordinates as triangles - Returns list of verts and vert indices of surface triangles - - coordinates must be in form [...[c11, c21, c31, ... cn1],[c12, c22, c32, ... cn2],... - where cij = [xij, yij, zij], i marks slice, j marks yz-plane (x_coord) index - - How it works: - Three points make a triangle, triangles make the surface. - For every two planes next to each other: - - take every other point from plane1, every other from plane2 (in order!) - - from list of points: every three points closest to each other make a surface - - Example: - plane 1: [v1, v2, v3, v4] - plane 2: [v5, v6, v7, v8] - - -> list: [v1, v5, v2, v6, v3,...] - -> triangles: - v1 v5 v2 - v5 v2 v6 - v2 v6 v3 - . - . - . + :param coords: points that make the surface + :returns: list of verts and vert indices of surface triangles + + input coordinates must be in form [...[c11, c21, c31, ... cn1],[c12, c22, c32, ... cn2],... + where cij = [xij, yij, zij], i marks sector, j marks yz-plane (x_coord) index + + Three points make a triangle, triangles make the surface. + For every two planes next to each other: + - take every other point from plane1, every other from plane2 (in order!) + - from list of points: every three points closest to each other make a surface + + Example: + plane 1: [v1, v2, v3, v4] + plane 2: [v5, v6, v7, v8] + + -> list: [v1, v5, v2, v6, v3,...] + -> triangles: + v1 v5 v2 + v5 v2 v6 + v2 v6 v3 + . + . + . ''' verts = [] #points @@ -123,6 +137,11 @@ def make_surface(coords): def make_streamlines(vlsvFileName): + """Traces streamlines of velocity field from outside the magnetosphere to magnetotail using the yt-package. + + :param vlsvFileName: directory and file name of .vlsv data file to use for VlsvReader + :returns: streamlines as numpy array + """ ## make streamlines boxre = [0] @@ -206,6 +225,12 @@ def make_streamlines(vlsvFileName): def make_magnetopause(streams): + """Finds the mangetopause location based on streamlines. + + :param streams: streamlines + :returns: the magnetopause position as coordinate points in numpy array + """ + streampoints = np.reshape(streams, (streams.shape[0]*streams.shape[1], 3)) #all the points in one array ## find the subsolar dayside point in the x-axis @@ -322,7 +347,7 @@ def external_plot(ax,XmeshXY=None, YmeshXY=None, pass_maps=None, requestvariable ax.plot(xz_slice[:,0], xz_slice[:,2], color='limegreen', linewidth=1.5) - plot_colormap3dslice.plot_colormap3dslice( + pt.plot.plot_colormap3dslice( filename=fileLocation+fileN, outputdir=outdir, run=run, @@ -342,7 +367,7 @@ def external_plot(ax,XmeshXY=None, YmeshXY=None, pass_maps=None, requestvariable return ['vg_v'] ax.plot(xy_slice[:,0], xy_slice[:,1], color='limegreen', linewidth=1.5) - plot_colormap3dslice.plot_colormap3dslice( + pt.plot.plot_colormap3dslice( filename=fileLocation+fileN, outputdir=outdir, run=run, From b95d2c0b954e6005bd78b0894d853a35d2994e9f Mon Sep 17 00:00:00 2001 From: jreimi Date: Thu, 5 Jun 2025 15:16:18 +0300 Subject: [PATCH 006/124] Add checks for starting point input, change ragged arrays to lists, and fix a small docstring typo --- analysator/pyCalculations/fieldtracer.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/analysator/pyCalculations/fieldtracer.py b/analysator/pyCalculations/fieldtracer.py index 7825423e..195c7d05 100644 --- a/analysator/pyCalculations/fieldtracer.py +++ b/analysator/pyCalculations/fieldtracer.py @@ -51,7 +51,7 @@ def static_field_tracer( vlsvReader, x0, max_iterations, dx, direction='+', bvar ''' Field tracer in a static frame :param vlsvReader: An open vlsv file - :param x: Starting point for the field trace + :param x0: Starting point for the field trace :param max_iterations: The maximum amount of iteractions before the algorithm stops :param dx: One iteration step length :param direction: '+' or '-' or '+-' Follow field in the plus direction or minus direction @@ -61,6 +61,10 @@ def static_field_tracer( vlsvReader, x0, max_iterations, dx, direction='+', bvar :returns: List of coordinates ''' + # Check the starting point + if (np.shape(x0) != (3,)): + raise ValueError("Starting point should be a single [x,y,z] coordinate in 1-dimensional list or array") + if(bvar != 'B'): warnings.warn("User defined tracing variable detected. fg, volumetric variable results may not work as intended, use face-values instead.") @@ -117,7 +121,7 @@ def static_field_tracer( vlsvReader, x0, max_iterations, dx, direction='+', bvar x = np.arange(mins[0], maxs[0], dcell[0]) + 0.5*dcell[0] y = np.arange(mins[1], maxs[1], dcell[1]) + 0.5*dcell[1] z = np.arange(mins[2], maxs[2], dcell[2]) + 0.5*dcell[2] - coordinates = np.array([x,y,z]) + coordinates = [x,y,z] # Debug: if( len(x) != sizes[0] ): logging.info("SIZE WRONG: " + str(len(x)) + " " + str(sizes[0])) @@ -142,7 +146,7 @@ def static_field_tracer( vlsvReader, x0, max_iterations, dx, direction='+', bvar x = np.arange(mins[0], maxs[0], dcell[0]) + 0.5*dcell[0] y = np.arange(mins[1], maxs[1], dcell[1]) + 0.5*dcell[1] z = np.arange(mins[2], maxs[2], dcell[2]) + 0.5*dcell[2] - coordinates = np.array([x,y,z]) + coordinates = [x,y,z] # Debug: if( len(x) != sizes[0] ): logging.info("SIZE WRONG: " + str(len(x)) + " " + str(sizes[0])) @@ -359,6 +363,10 @@ def static_field_tracer_3d( vlsvReader, seed_coords, max_iterations, dx, directi # Standardize input: (N,3) np.array if type(seed_coords) != np.ndarray: raise TypeError("Please give a numpy array.") + + # Transform a single (3,) coordinate to (1,3) if needed + if np.shape(seed_coords)==(3,): + seed_coords = np.array([seed_coords]) # Cache and read variables: vg = None From de4f3f8abcc11e60da2a558eeccd7e1729fd4149 Mon Sep 17 00:00:00 2001 From: jreimi Date: Fri, 6 Jun 2025 09:55:11 +0300 Subject: [PATCH 007/124] Change the streamline tracing to use analysator's fieldtracer instead of yt, remove unnecessary imports and function --- scripts/magnetopause3d.py | 96 +++++++-------------------------------- 1 file changed, 17 insertions(+), 79 deletions(-) diff --git a/scripts/magnetopause3d.py b/scripts/magnetopause3d.py index 517fdd40..9469d929 100644 --- a/scripts/magnetopause3d.py +++ b/scripts/magnetopause3d.py @@ -1,20 +1,10 @@ ''' -Finds the magnetopause position by tracing streamlines of the plasma flow for three-dimensional Vlasiator runs. Needs the yt package. +Finds the magnetopause position by tracing streamlines of the plasma flow for three-dimensional Vlasiator runs. ''' -from pyCalculations import ids3d + import matplotlib.pyplot as plt import numpy as np import analysator as pt -import yt -import math -from mpl_toolkits import mplot3d -from yt.visualization.api import Streamlines - - -def to_Re(m): - """Converts units from meters to R_E (Earth's radius, 6371000m)""" - return m/6371000 - def cartesian_to_polar(cartesian_coords): # for segments of plane """Converts cartesian coordinates to polar (for the segments of the yz-planes). @@ -34,7 +24,7 @@ def polar_to_cartesian(r, phi): :param r: radius of the segment (distance from the x-axis) :param phi: the angle coordinate in degrees - :returns: y, z -coordinates + :returns: y, z -coordinates in cartesian system """ phi = np.deg2rad(phi) y = r * np.cos(phi) @@ -135,65 +125,19 @@ def make_surface(coords): return verts, faces - def make_streamlines(vlsvFileName): - """Traces streamlines of velocity field from outside the magnetosphere to magnetotail using the yt-package. + """Traces streamlines of velocity field from outside the magnetosphere to magnetotail. :param vlsvFileName: directory and file name of .vlsv data file to use for VlsvReader :returns: streamlines as numpy array """ - ## make streamlines - boxre = [0] - - # bulk file - f = pt.vlsvfile.VlsvReader(file_name=vlsvFileName) - - #get box coordinates from data - [xmin, ymin, zmin, xmax, ymax, zmax] = f.get_spatial_mesh_extent() - [xsize, ysize, zsize] = f.get_spatial_mesh_size() - [xsizefs, ysizefs, zsizefs] = f.get_fsgrid_mesh_size() - simext_m =[xmin,xmax,ymin,ymax,zmin,zmax] - sizes = np.array([xsize,ysize,zsize]) - sizesfs = np.array([xsizefs,ysizefs,zsizefs]) - - simext = list(map(to_Re, simext_m)) - boxcoords=list(simext) - - cellids = f.read_variable("CellID") - indexids = cellids.argsort() - cellids = cellids[indexids] - - reflevel = ids3d.refinement_level(xsize, ysize, zsize, cellids[-1]) - - #Read the data from vlsv-file - V = f.read_variable("vg_v") - - #from m to Re - V = np.array([[to_Re(i) for i in j ]for j in V]) - - - V = V[indexids] - - if np.ndim(V)==1: - shape = None - elif np.ndim(V)==2: # vector variable - shape = V.shape[1] - elif np.ndim(V)==3: # tensor variable - shape = (V.shape[1], V.shape[2]) - - - Vdpoints = ids3d.idmesh3d2(cellids, V, reflevel, xsize, ysize, zsize, shape) - - - Vxs = Vdpoints[:,:,:,0] - Vys = Vdpoints[:,:,:,1] - Vzs = Vdpoints[:,:,:,2] + f = pt.vlsvfile.VlsvReader(file_name=vlsvFileName) - data=dict(Vx=Vxs,Vy=Vys,Vz=Vzs) + RE = 6371000 #Create streamline seeds (starting points for streamlines) - seedN = 50 #seeds per row, final seed count will be seedN*seedN ! + seedN = 25 #seeds per row, final seed count will be seedN*seedN ! streamline_seeds = np.zeros([seedN**2, 3]) #range: np.arange(from, to, step) @@ -204,24 +148,18 @@ def make_streamlines(vlsvFileName): streamline_seeds[k] = [20, i, j] k = k+1 + dx = 0.4 + iterations = int(50/(dx)) # iterations so that final step is at max -30 RE (in practice less) - #dataset in yt-form - yt_dataset = yt.load_uniform_grid( - data, - sizesfs, - bbox=np.array([[boxcoords[0], boxcoords[1]], - [boxcoords[2],boxcoords[3]], - [boxcoords[4],boxcoords[5]]])) - - - # data, seeds, dictionary positions, length of streamlines - streamlines_pos = yt.visualization.api.Streamlines(yt_dataset, streamline_seeds, - "Vx", "Vy", "Vz", length=50, direction=1) - - # make the streamlines - streamlines_pos.integrate_through_volume() + streams = pt.calculations.static_field_tracer_3d( + vlsvReader=f, + seed_coords=streamline_seeds*RE, + max_iterations=iterations, + dx=dx*RE, + direction='+', + grid_var='vg_v') - return np.array(streamlines_pos.streamlines) + return streams*(1/RE) def make_magnetopause(streams): From 6ca082ede17e03e9d0a3983f31eb052e8dde2580 Mon Sep 17 00:00:00 2001 From: jreimi Date: Tue, 10 Jun 2025 11:20:19 +0300 Subject: [PATCH 008/124] 2d and 3d magnetopause scripts: Add function find_magnetopause() that takes a vlsvfile name and keyword arguments and returns magnetopause as triangle mesh vertices and faces (3d) or array (2d). Remove unnecessary functions, change units from RE to m. --- scripts/magnetopause2d.py | 146 +++++++++++-------------------- scripts/magnetopause3d.py | 177 +++++++++++++------------------------- 2 files changed, 108 insertions(+), 215 deletions(-) diff --git a/scripts/magnetopause2d.py b/scripts/magnetopause2d.py index 1e3d433f..de4bff7b 100644 --- a/scripts/magnetopause2d.py +++ b/scripts/magnetopause2d.py @@ -1,11 +1,10 @@ ''' -Finds the magnetopause position by tracing streamines of the plasma flow for two-dimensional Vlasiator runs. Needs the yt package. +Finds the magnetopause position by tracing streamlines of the plasma flow for two-dimensional Vlasiator runs. Needs the yt package. ''' import numpy as np import analysator as pt import yt -from yt.visualization.api import Streamlines def interpolate(streamline, x_points): @@ -28,28 +27,24 @@ def interpolate(streamline, x_points): return np.array([x_points, z_points]) -def make_streamlines(vlsvFileName): - """Traces streamlines of velocity field from outside the magnetosphere to magnetotail using the yt-package. +def make_streamlines(vlsvfile, streamline_seeds=None, streamline_length=40*6371000): + """Traces streamlines of velocity field using the yt package. + + :param vlsvfile: directory and file name of .vlsv data file to use for VlsvReader + :kword streamline_seeds: optional streamline starting points in numpy array (coordinates in meters including the y-coordinate 0.0) + :kword streamline_length: streamline length - :param vlsvFileName: directory and file name of vlsv file, to be given to VlsvReader :returns: streamlines as numpy array """ - ## make streamlines - boxre = [0,0] - + # bulk file - f = pt.vlsvfile.VlsvReader(file_name=vlsvFileName) + f = pt.vlsvfile.VlsvReader(file_name=vlsvfile) - #get box coordinates from data + # get box coordinates from data [xmin, ymin, zmin, xmax, ymax, zmax] = f.get_spatial_mesh_extent() [xsize, ysize, zsize] = f.get_spatial_mesh_size() - simext_m =[xmin,xmax,ymin,ymax,zmin,zmax] + simext =[xmin,xmax,ymin,ymax,zmin,zmax] sizes = np.array([xsize,ysize,zsize]) - - def to_Re(m): #meters to Re - return m/6371000 - - simext = list(map(to_Re, simext_m)) boxcoords=list(simext) cellids = f.read_variable("CellID") @@ -66,9 +61,9 @@ def to_Re(m): #meters to Re data=dict(Vx=Vxs,Vy=Vys,Vz=Vzs) - #Create streamline seeds (starting points for streamlines) - seedN = 200 # number of streamlines wanted - streamline_seeds = np.array([[20, 0 ,i] for i in np.linspace(-4, 4, seedN)]) + #Create starting points for streamlines if they are not given + if streamline_seeds == None: + streamline_seeds = np.array([[20*6371000, 0 ,i] for i in np.linspace(-5*6371000, 5*6371000, 200)]) #streamline_seeds = np.array(streamline_seeds) #dataset in yt-form @@ -80,40 +75,45 @@ def to_Re(m): #meters to Re [boxcoords[4],boxcoords[5]]])) - #data, seeds, dictionary positions, lenght of lines - streamlines_pos = yt.visualization.api.Streamlines(yt_dataset, streamline_seeds, - "Vx", "Vy", "Vz", length=40, direction=1) + #data, seeds, dictionary positions, step size + streamlines = yt.visualization.api.Streamlines(yt_dataset, streamline_seeds, + "Vx", "Vy", "Vz", length=streamline_length, direction=1) + + #trace the streamlines with yt + streamlines.integrate_through_volume() + # return streamline positions + return np.array(streamlines.streamlines) - #where the magic happens - streamlines_pos.integrate_through_volume() - return np.array(streamlines_pos.streamlines) -def make_magnetopause(streams): +def make_magnetopause(streamlines, end_x=-15*6371000, x_point_n=50): """Finds the mangetopause location based on streamlines. - :param streams: streamlines - :returns: magnetopause as coordinate points from tail on positive x-axis to tail on negative x-axis + :param streams: streamlines (coordinates in m) + :kword end_x: tail end x-coordinate (how far along the negative x-axis the magnetopause is calculated) + :kword x_point_n: integer, how many x-axis points the magnetopause will be divided in between the subsolar point and tail + + :returns: the magnetopause position as coordinate points in numpy array """ - streampoints = np.reshape(streams, (streams.shape[0]*streams.shape[1], 3)) #all the points in one array - + RE = 6371000 + + streampoints = np.reshape(streamlines, (streamlines.shape[0]*streamlines.shape[1], 3)) #all the points in one array + ## find the subsolar dayside point in the positive x-axis ## do this by finding a stremline point on positive x axis closest to the Earth - x_axis_points = streampoints[np.floor(streampoints[:,2])==0] - x_axis_points[x_axis_points<0] = 800 + x_axis_points = streampoints[(streampoints[:,2]< RE) & (streampoints[:,2]> -RE) & (streampoints[:,0]> 0)] subsolar_x =np.min(x_axis_points[:,0]) ## define points in the x axis where to find magnetopause points on the yz-plane - x_points = np.arange(subsolar_x, -10, -0.2) + x_points = np.linspace(subsolar_x, end_x, x_point_n) ## interpolate more exact points for streamlines at exery x_point - new_streampoints = np.zeros((len(x_points), len(streams), 1)) # new array for keeping interpolated streamlines in form streamlines_new[x_point, streamline, z-coordinate] - i=0 - for stream in streams: + new_streampoints = np.zeros((len(x_points), len(streamlines), 1)) # new array for keeping interpolated streamlines in form streamlines_new[x_point, streamline, z-coordinate] + + for i,stream in enumerate(streamlines): interpolated_streamline = interpolate(stream, x_points) for j in range(0, len(x_points)): new_streampoints[j, i,:] = interpolated_streamline[1,j] - i += 1 ## start making the magnetopause @@ -138,67 +138,19 @@ def make_magnetopause(streams): return magnetopause -def polar_to_cartesian(r, phi): - """Converts polar coordinates to cartesian. - - :param r: radius - :param phi: angular coordinate in degrees - :returns: y, z -coordinates in cartesian system - """ - phi = np.deg2rad(phi) - y = r * np.cos(phi) - z = r * np.sin(phi) - return(y, z) - - - -def main(): +def find_magnetopause(vlsvfile, streamline_seeds=None, streamline_length=45*6371000, end_x=-15*6371000, x_point_n=50): + """Finds the magnetopause position by tracing streamlines of the velocity field for 2d runs. - ## get bulk data - run = 'BFD' - num = '2000' - - fileLocation="/wrk-vakka/group/spacephysics/vlasiator/2D/"+run+"/bulk/" - fileN = "bulk.000"+num+".vlsv" - - - ## STREAMLINES - streams = make_streamlines(fileLocation+fileN) - - ## MAGNETOPAUSE - magnetopause = make_magnetopause(streams) - - - ## PLOTTING - outdir="" - - # plot the magnetopause (and streamlines if needed) on top of colormap - def external_plot(ax,XmeshXY=None, YmeshXY=None, pass_maps=None): - plot_magnetopause = True - plot_streamlines = False - - if plot_streamlines: - for stream in streams: - ax.plot(stream[:,0], stream[:,2], color='paleturquoise', alpha=0.2) - - if plot_magnetopause: - ax.plot(magnetopause[:,0], magnetopause[:,1], color='cyan', linewidth=1.0) - - - pt.plot.plot_colormap( - filename=fileLocation+fileN, - outputdir=outdir, - nooverwrite=None, - var="rho", #var="beta_star", - boxre = [-21,21,-21,21], - title=None, - draw=None, usesci=True, Earth=True, - external = external_plot, - run=run, - colormap='inferno', - ) + :param vlsvfile: directory and file name of .vlsv data file to use for VlsvReader + :kword streamline_seeds: optional streamline starting points in numpy array (coordinates in meters including the y-coordinate 0.0) + :kword streamline_length: streamline length for tracing + :kword end_x: tail end x-coordinate (how far along the negative x-axis the magnetopause is calculated) + :kword x_point_n: integer, how many x-axis points the magnetopause will be divided in between the subsolar point and tail + :returns: the magnetopause position as coordinate points in numpy array + """ + streamlines = make_streamlines(vlsvfile, streamline_seeds, streamline_length) + magnetopause = make_magnetopause(streamlines, end_x, x_point_n) -if __name__ == "__main__": - main() + return magnetopause diff --git a/scripts/magnetopause3d.py b/scripts/magnetopause3d.py index 9469d929..b97013fb 100644 --- a/scripts/magnetopause3d.py +++ b/scripts/magnetopause3d.py @@ -33,7 +33,7 @@ def polar_to_cartesian(r, phi): def interpolate(streamline, x_points): - """IInterpolates a single streamline for make_magnetopause(). + """Interpolates a single streamline for make_magnetopause(). :param streamline: a single streamline to be interpolated :param x_points: points in the x-axis to use for interpolation @@ -61,7 +61,7 @@ def make_surface(coords): '''Defines a surface constructed of input coordinates as triangles. :param coords: points that make the surface - :returns: list of verts and vert indices of surface triangles + :returns: list of verts and vert indices of surface triangles as numpy arrays input coordinates must be in form [...[c11, c21, c31, ... cn1],[c12, c22, c32, ... cn2],... where cij = [xij, yij, zij], i marks sector, j marks yz-plane (x_coord) index @@ -122,62 +122,73 @@ def make_surface(coords): next_triangles = [x + slices_in_plane*area_index for x in first_triangles] faces.extend(next_triangles) - return verts, faces + return np.array(verts), np.array(faces) -def make_streamlines(vlsvFileName): +def make_streamlines(vlsvfile, streamline_seeds=None, dl=2e6, iterations=200): """Traces streamlines of velocity field from outside the magnetosphere to magnetotail. - :param vlsvFileName: directory and file name of .vlsv data file to use for VlsvReader - :returns: streamlines as numpy array - """ + :param vlsvfile: directory and file name of .vlsv data file to use for VlsvReader - f = pt.vlsvfile.VlsvReader(file_name=vlsvFileName) + :kword streamline_seeds: optional streamline starting points in numpy array + :kword dl: streamline iteration step length in m + :kword iterations: int, number of iteration steps - RE = 6371000 + :returns: streamlines as numpy array + """ - #Create streamline seeds (starting points for streamlines) - seedN = 25 #seeds per row, final seed count will be seedN*seedN ! - streamline_seeds = np.zeros([seedN**2, 3]) + f = pt.vlsvfile.VlsvReader(file_name=vlsvfile) - #range: np.arange(from, to, step) - t = np.linspace(-5, 5, seedN) - k = 0 - for i in t: - for j in t: - streamline_seeds[k] = [20, i, j] - k = k+1 + # Create streamline starting points if they have not been given + if streamline_seeds == None: + streamline_seeds = np.zeros([25**2, 3]) - dx = 0.4 - iterations = int(50/(dx)) # iterations so that final step is at max -30 RE (in practice less) + t = np.linspace(-5*6371000, 5*6371000, 25) + k = 0 + for i in t: + for j in t: + streamline_seeds[k] = [20*6371000, i, j] + k = k+1 + # Trace the streamlines streams = pt.calculations.static_field_tracer_3d( vlsvReader=f, - seed_coords=streamline_seeds*RE, + seed_coords=streamline_seeds, max_iterations=iterations, - dx=dx*RE, + dx=dl, direction='+', grid_var='vg_v') - return streams*(1/RE) + return streams -def make_magnetopause(streams): +def make_magnetopause(streams, end_x=-15*6371000, x_point_n=50, sector_n=36): """Finds the mangetopause location based on streamlines. - :param streams: streamlines - :returns: the magnetopause position as coordinate points in numpy array + :param streams: streamlines (coordinates in m) + + :kword end_x: tail end x-coordinate (how far along the negative x-axis the magnetopause is calculated) + :kword x_point_n: integer, how many x-axis points the magnetopause will be divided in between the subsolar point and tail + :kword sector_n: integer, how many sectors the magnetopause will be divided in on each yz-plane + + :returns: the magnetopause position as coordinate points in numpy array, form [...[c11, c21, c31, ... cn1],[c12, c22, c32, ... cn2],... + where cij = [xij, yij, zij], i marks sector, j marks yz-plane (x-coordinate) index """ - streampoints = np.reshape(streams, (streams.shape[0]*streams.shape[1], 3)) #all the points in one array + RE = 6371000 + + #streams = streams*(1/RE) # streamlines in rE + streampoints = np.reshape(streams, (streams.shape[0]*streams.shape[1], 3)) #all the points in one array) ## find the subsolar dayside point in the x-axis - ## do this by finding a stremline point on positive x axis closest to the Earth - x_axis_points = streampoints[(np.floor(streampoints[:,1])==0) & (np.floor(streampoints[:,2])==0)] + ## do this by finding a streamline point on positive x axis closest to the Earth + # streampoints closer than ~1 rE to positive x-axis: + x_axis_points = streampoints[(streampoints[:,1]-RE) & (streampoints[:,2]>-RE) & (streampoints[:,0]>0) & (streampoints[:,0]>0)] subsolar_x =np.min(x_axis_points[:,0]) + ## define points in the x axis where to find magnetopause points on the yz-plane - x_points = np.arange(subsolar_x, -15, -0.5) + x_points = np.linspace(subsolar_x, end_x, x_point_n) ## interpolate more exact points for streamlines at exery x_point new_streampoints = np.zeros((len(x_points), len(streams), 2)) # new array for keeping interpolated streamlines in form new_streampoints[x_point, streamline, y and z -coordinates] @@ -202,7 +213,6 @@ def make_magnetopause(streams): ## now start making the magnetopause ## in each x_point, divide the plane into sectors and look for the closest streamline to x-axis in the sector - sector_n = 36 ## if given sector number isn't divisible by 4, make it so because we want to have magnetopause points at exactly y=0 and z=0 for 2d slices of the whole thing while sector_n%4 != 0: @@ -249,91 +259,22 @@ def make_magnetopause(streams): return magnetopause +def find_magnetopause(vlsvfile, streamline_seeds=None, dl=2e6, iterations=200, end_x=-15*6371000, x_point_n=50, sector_n=36): + """Finds the magnetopause position by tracing streamlines of the velocity field. + + :param vlsvfile: path to .vlsv bulk file to use for VlsvReader + :kword streamline_seeds: optional streamline starting points in numpy array + :kword dl: streamline iteration step length in m + :kword iterations: int, number of iteration steps + :kword end_x: tail end x-coordinate (how far along the x-axis the magnetopause is calculated) + :kword x_point_n: integer, how many x-axis points the magnetopause will be divided in between the subsolar point and tail + :kword sector_n: integer, how many sectors the magnetopause will be divided in on each yz-plane + + :returns: vertices, faces of the magnetopause triangle mesh as numpy arrays + """ + streams = make_streamlines(vlsvfile, streamline_seeds, dl, iterations) + magnetopause = make_magnetopause(streams, end_x, x_point_n, sector_n) + vertices, faces = make_surface(magnetopause) -def main(): - - ## get bulk data - run = 'EGI' - fileLocation="/wrk-vakka/group/spacephysics/vlasiator/3D/"+run+"/bulk/" - fileN = "bulk5.0000070.vlsv" - - ## STREAMLINES - streams = make_streamlines(fileLocation+fileN) - ## MAGNETOPAUSE - magnetopause = make_magnetopause(streams) - - - ## PLOTTING - outdir="" - - ## take separate arrays for different 2d slice plots - slices = magnetopause.shape[1] - quarter_slice = int(slices/4) - # xy plane: z=0 - xy_slice = np.concatenate((magnetopause[:,0][::-1], magnetopause[:,2*quarter_slice])) - # xz plane: y=0 - xz_slice = np.concatenate((magnetopause[:,quarter_slice][::-1], magnetopause[:,3*quarter_slice])) - - - #2D plots - # analysator 3dcolormapslice y=0 - if True: - def external_plot(ax,XmeshXY=None, YmeshXY=None, pass_maps=None, requestvariables=False): - if requestvariables==True: - return ['vg_v'] - ax.plot(xz_slice[:,0], xz_slice[:,2], color='limegreen', linewidth=1.5) - - - pt.plot.plot_colormap3dslice( - filename=fileLocation+fileN, - outputdir=outdir, - run=run, - nooverwrite=None, - boxre = [-21,21,-21,21], - title=None, - draw=None, usesci=True, Earth=True, - external = external_plot, - colormap='inferno', - normal='y' - ) - - # analysator 3dcolormapslice z=0 - if True: - def external_plot(ax,XmeshXY=None, YmeshXY=None, pass_maps=None, requestvariables=False): - if requestvariables==True: - return ['vg_v'] - ax.plot(xy_slice[:,0], xy_slice[:,1], color='limegreen', linewidth=1.5) - - pt.plot.plot_colormap3dslice( - filename=fileLocation+fileN, - outputdir=outdir, - run=run, - nooverwrite=None, - boxre = [-21,21,-21,21], - title=None, - draw=None, usesci=True, Earth=True, - external = external_plot, - colormap='inferno', - normal='z' - ) - - # 3D plot - # matplotlib 3d surface plot for single image - if False: - verts, faces = make_surface(magnetopause) - verts = np.array(verts) - - fig = plt.figure() - ax2 = fig.add_subplot(projection='3d') - ax2.plot_trisurf(verts[:, 0], verts[:,1], faces, verts[:, 2], linewidth=0.2, antialiased=True) - ax2.view_init(azim=-60, elev=5) - ax2.set_xlabel('X') - ax2.set_ylabel('Y') - ax2.set_zlabel('Z') - fig.tight_layout() - plt.savefig(outdir+run+'_3d_magnetopause.png') - - -if __name__ == "__main__": - main() + return vertices, faces From 79abb14b03d1e6b61ca257ade91b046eb13d6ba0 Mon Sep 17 00:00:00 2001 From: jreimi Date: Thu, 12 Jun 2025 12:01:19 +0300 Subject: [PATCH 009/124] Moved magnetopause functions to analysator/pyCalculations --- {scripts => analysator/pyCalculations}/magnetopause2d.py | 0 {scripts => analysator/pyCalculations}/magnetopause3d.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {scripts => analysator/pyCalculations}/magnetopause2d.py (100%) rename {scripts => analysator/pyCalculations}/magnetopause3d.py (100%) diff --git a/scripts/magnetopause2d.py b/analysator/pyCalculations/magnetopause2d.py similarity index 100% rename from scripts/magnetopause2d.py rename to analysator/pyCalculations/magnetopause2d.py diff --git a/scripts/magnetopause3d.py b/analysator/pyCalculations/magnetopause3d.py similarity index 100% rename from scripts/magnetopause3d.py rename to analysator/pyCalculations/magnetopause3d.py From a20058056a76f78633982d81e4293bd308386d22 Mon Sep 17 00:00:00 2001 From: jreimi Date: Thu, 12 Jun 2025 12:34:39 +0300 Subject: [PATCH 010/124] Add magnetopause functions and surface function to calculations.py --- analysator/pyCalculations/calculations.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/analysator/pyCalculations/calculations.py b/analysator/pyCalculations/calculations.py index 7f5e940f..1ca715dc 100644 --- a/analysator/pyCalculations/calculations.py +++ b/analysator/pyCalculations/calculations.py @@ -61,3 +61,5 @@ from non_maxwellianity import epsilon_M from null_lines import LMN_null_lines_FOTE from interpolator_amr import AMRInterpolator, supported_amr_interpolators +from magnetopause2d import find_magnetopause +from magnetopause3d import find_magnetopause, make_surface From 99e37c918bb1c8613d68d3f12fc366e5f7777fbe Mon Sep 17 00:00:00 2001 From: jreimi Date: Thu, 12 Jun 2025 13:59:32 +0300 Subject: [PATCH 011/124] Change surface triangles so that the normals have the same direction. --- analysator/pyCalculations/magnetopause3d.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/analysator/pyCalculations/magnetopause3d.py b/analysator/pyCalculations/magnetopause3d.py index b97013fb..d690abdb 100644 --- a/analysator/pyCalculations/magnetopause3d.py +++ b/analysator/pyCalculations/magnetopause3d.py @@ -122,7 +122,13 @@ def make_surface(coords): next_triangles = [x + slices_in_plane*area_index for x in first_triangles] faces.extend(next_triangles) - return np.array(verts), np.array(faces) + # Change every other face triangle normal direction so that all face the same way + faces = np.array(faces) + for i in range(len(faces)): + if i%2==0: + faces[i,1], faces[i,2] = faces[i,2], faces[i,1] + + return np.array(verts), faces def make_streamlines(vlsvfile, streamline_seeds=None, dl=2e6, iterations=200): From 35cf3fbabf134d0280e172abe7436bca28e9296e Mon Sep 17 00:00:00 2001 From: jreimi Date: Thu, 12 Jun 2025 15:26:55 +0300 Subject: [PATCH 012/124] Changed function names for clarity, added possible keyword argumets for streamline seeds. --- analysator/pyCalculations/calculations.py | 4 ++-- analysator/pyCalculations/magnetopause2d.py | 14 ++++++++++---- analysator/pyCalculations/magnetopause3d.py | 20 +++++++++++++------- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/analysator/pyCalculations/calculations.py b/analysator/pyCalculations/calculations.py index 1ca715dc..cb39b3d6 100644 --- a/analysator/pyCalculations/calculations.py +++ b/analysator/pyCalculations/calculations.py @@ -61,5 +61,5 @@ from non_maxwellianity import epsilon_M from null_lines import LMN_null_lines_FOTE from interpolator_amr import AMRInterpolator, supported_amr_interpolators -from magnetopause2d import find_magnetopause -from magnetopause3d import find_magnetopause, make_surface +from magnetopause2d import find_magnetopause_2d +from magnetopause3d import find_magnetopause_3d, make_surface diff --git a/analysator/pyCalculations/magnetopause2d.py b/analysator/pyCalculations/magnetopause2d.py index de4bff7b..203f6ebb 100644 --- a/analysator/pyCalculations/magnetopause2d.py +++ b/analysator/pyCalculations/magnetopause2d.py @@ -27,11 +27,14 @@ def interpolate(streamline, x_points): return np.array([x_points, z_points]) -def make_streamlines(vlsvfile, streamline_seeds=None, streamline_length=40*6371000): +def make_streamlines(vlsvfile, streamline_seeds=None,seeds_n=200, seeds_x0=20*6371000, seeds_range=[-5*6371000, 5*6371000], streamline_length=40*6371000): """Traces streamlines of velocity field using the yt package. :param vlsvfile: directory and file name of .vlsv data file to use for VlsvReader :kword streamline_seeds: optional streamline starting points in numpy array (coordinates in meters including the y-coordinate 0.0) + :kword seeds_n: instead of streamline_seeds provide a number of streamlines to be traced + :kword seeds_x0: instead of streamline_seeds provide an x-coordinate for streamline starting points + :kword seeds_range: instead of streamline_seeds provide [min, max] range to use for streamline starting point z-coordinates :kword streamline_length: streamline length :returns: streamlines as numpy array @@ -63,7 +66,7 @@ def make_streamlines(vlsvfile, streamline_seeds=None, streamline_length=40*63710 #Create starting points for streamlines if they are not given if streamline_seeds == None: - streamline_seeds = np.array([[20*6371000, 0 ,i] for i in np.linspace(-5*6371000, 5*6371000, 200)]) + streamline_seeds = np.array([[seeds_x0, 0 ,i] for i in np.linspace(seeds_range[0], seeds_range[1], seeds_n)]) #streamline_seeds = np.array(streamline_seeds) #dataset in yt-form @@ -138,11 +141,14 @@ def make_magnetopause(streamlines, end_x=-15*6371000, x_point_n=50): return magnetopause -def find_magnetopause(vlsvfile, streamline_seeds=None, streamline_length=45*6371000, end_x=-15*6371000, x_point_n=50): +def find_magnetopause_2d(vlsvfile, streamline_seeds=None, seeds_n=200, seeds_x0=20*6371000, seeds_range=[-5*6371000, 5*6371000], streamline_length=45*6371000, end_x=-15*6371000, x_point_n=50): """Finds the magnetopause position by tracing streamlines of the velocity field for 2d runs. :param vlsvfile: directory and file name of .vlsv data file to use for VlsvReader :kword streamline_seeds: optional streamline starting points in numpy array (coordinates in meters including the y-coordinate 0.0) + :kword seeds_n: instead of streamline_seeds provide a number of streamlines to be traced + :kword seeds_x0: instead of streamline_seeds provide an x-coordinate for streamline starting points + :kword seeds_range: instead of streamline_seeds provide [min, max] range to use for streamline starting point z-coordinates :kword streamline_length: streamline length for tracing :kword end_x: tail end x-coordinate (how far along the negative x-axis the magnetopause is calculated) :kword x_point_n: integer, how many x-axis points the magnetopause will be divided in between the subsolar point and tail @@ -150,7 +156,7 @@ def find_magnetopause(vlsvfile, streamline_seeds=None, streamline_length=45*6371 :returns: the magnetopause position as coordinate points in numpy array """ - streamlines = make_streamlines(vlsvfile, streamline_seeds, streamline_length) + streamlines = make_streamlines(vlsvfile, streamline_seeds, seeds_n, seeds_x0, seeds_range, streamline_length) magnetopause = make_magnetopause(streamlines, end_x, x_point_n) return magnetopause diff --git a/analysator/pyCalculations/magnetopause3d.py b/analysator/pyCalculations/magnetopause3d.py index d690abdb..d2505e85 100644 --- a/analysator/pyCalculations/magnetopause3d.py +++ b/analysator/pyCalculations/magnetopause3d.py @@ -131,12 +131,15 @@ def make_surface(coords): return np.array(verts), faces -def make_streamlines(vlsvfile, streamline_seeds=None, dl=2e6, iterations=200): +def make_streamlines(vlsvfile, streamline_seeds=None, seeds_n=25, seeds_x0=20*6371000, seeds_range=[-5*6371000, 5*6371000], dl=2e6, iterations=200): """Traces streamlines of velocity field from outside the magnetosphere to magnetotail. :param vlsvfile: directory and file name of .vlsv data file to use for VlsvReader :kword streamline_seeds: optional streamline starting points in numpy array + :kword seeds_n: instead of streamline_seeds provide a number of streamlines to be traced + :kword seeds_x0: instead of streamline_seeds provide an x-coordinate for streamline starting points + :kword seeds_range: instead of streamline_seeds provide [min, max] range to use for streamline starting point coordinates (both y- and z-directions use the same range) :kword dl: streamline iteration step length in m :kword iterations: int, number of iteration steps @@ -145,15 +148,15 @@ def make_streamlines(vlsvfile, streamline_seeds=None, dl=2e6, iterations=200): f = pt.vlsvfile.VlsvReader(file_name=vlsvfile) - # Create streamline starting points if they have not been given + # Create streamline starting points if needed if streamline_seeds == None: - streamline_seeds = np.zeros([25**2, 3]) + streamline_seeds = np.zeros([seeds_n**2, 3]) - t = np.linspace(-5*6371000, 5*6371000, 25) + t = np.linspace(seeds_range[0], seeds_range[1], seeds_n) k = 0 for i in t: for j in t: - streamline_seeds[k] = [20*6371000, i, j] + streamline_seeds[k] = [seeds_x0, i, j] k = k+1 # Trace the streamlines @@ -265,11 +268,14 @@ def make_magnetopause(streams, end_x=-15*6371000, x_point_n=50, sector_n=36): return magnetopause -def find_magnetopause(vlsvfile, streamline_seeds=None, dl=2e6, iterations=200, end_x=-15*6371000, x_point_n=50, sector_n=36): +def find_magnetopause_3d(vlsvfile, streamline_seeds=None, seeds_n=25, seeds_x0=20*6371000, seeds_range=[-5*6371000, 5*6371000], dl=2e6, iterations=200, end_x=-15*6371000, x_point_n=50, sector_n=36): """Finds the magnetopause position by tracing streamlines of the velocity field. :param vlsvfile: path to .vlsv bulk file to use for VlsvReader :kword streamline_seeds: optional streamline starting points in numpy array + :kword seeds_n: instead of streamline_seeds provide a number of streamlines to be traced + :kword seeds_x0: instead of streamline_seeds provide an x-coordinate for streamline starting points + :kword seeds_range: instead of streamline_seeds provide [min, max] range to use for streamline starting point coordinates (both y- and z-directions use the same range) :kword dl: streamline iteration step length in m :kword iterations: int, number of iteration steps :kword end_x: tail end x-coordinate (how far along the x-axis the magnetopause is calculated) @@ -279,7 +285,7 @@ def find_magnetopause(vlsvfile, streamline_seeds=None, dl=2e6, iterations=200, e :returns: vertices, faces of the magnetopause triangle mesh as numpy arrays """ - streams = make_streamlines(vlsvfile, streamline_seeds, dl, iterations) + streams = make_streamlines(vlsvfile, streamline_seeds, seeds_n, seeds_x0, seeds_range, dl, iterations) magnetopause = make_magnetopause(streams, end_x, x_point_n, sector_n) vertices, faces = make_surface(magnetopause) From 6225f0d82975ca9e325c0564a9e38ab39331790c Mon Sep 17 00:00:00 2001 From: jreimi Date: Fri, 13 Jun 2025 11:28:30 +0300 Subject: [PATCH 013/124] Added example files for using magnetopause functions and plotting their output. --- examples/plot_2d_magnetopause.py | 35 +++++++++++++ examples/plot_3d_magnetopause.py | 85 ++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 examples/plot_2d_magnetopause.py create mode 100644 examples/plot_3d_magnetopause.py diff --git a/examples/plot_2d_magnetopause.py b/examples/plot_2d_magnetopause.py new file mode 100644 index 00000000..71e92213 --- /dev/null +++ b/examples/plot_2d_magnetopause.py @@ -0,0 +1,35 @@ +""" +An example of using find_magnetopause_2d() and plotting the output over colormap. +""" + +import analysator as pt + +run = 'BFD' +file_name="/wrk-vakka/group/spacephysics/vlasiator/2D/"+run+"/bulk/bulk.0002000.vlsv" +outdir= "" + +# magnetopause points +magnetopause = pt.calculations.find_magnetopause_2d( + file_name, + streamline_seeds=None, + seeds_n=200, + seeds_x0=20*6371000, + seeds_range=[-5*6371000, 5*6371000], + streamline_length=45*6371000, + end_x=-15*6371000, + x_point_n=50) + +magnetopause= magnetopause*(1/6371000) # in RE + +def external_plot(ax,XmeshXY=None, YmeshXY=None, pass_maps=None): + ax.plot(magnetopause[:,0], magnetopause[:,1], color='cyan', linewidth=1.0) + +pt.plot.plot_colormap( + filename=file_name, + var="rho", + boxre = [-21,21,-21,21], + Earth=True, + external = external_plot, + run=run, + colormap='inferno', + ) diff --git a/examples/plot_3d_magnetopause.py b/examples/plot_3d_magnetopause.py new file mode 100644 index 00000000..3d1ac7d5 --- /dev/null +++ b/examples/plot_3d_magnetopause.py @@ -0,0 +1,85 @@ +""" +An example of using find_magnetopause_3d() and plotting the output. +""" + +import numpy as np +import matplotlib.pyplot as plt +import analysator as pt + +## get bulk data +run = 'EGI' # for plot output file name +file_name="/wrk-vakka/group/spacephysics/vlasiator/3D/"+run+"/bulk/bulk5.0000070.vlsv" + +## magnetopause +x_point_n = 50 # number needed for 2d slice plots, default is 50 + +vertices, faces = pt.calculations.find_magnetopause_3d( + file_name, + streamline_seeds=None, + seeds_n=25, + seeds_x0=20*6371000, + seeds_range=[-5*6371000, 5*6371000], + dl=2e6, + iterations=200, + end_x=-15*6371000, + x_point_n=x_point_n, + sector_n=36) + + +outdir="" # output plot save directory + +### 3D surface plot ### + +fig = plt.figure() +ax = fig.add_subplot(projection='3d') +ax.plot_trisurf(vertices[:, 0], vertices[:,1], faces, vertices[:,2], linewidth=0.2) +plt.savefig(outdir+run+'_magnetopause_3D_surface.png') +plt.close() + + +### 2D slice plots ### + +vertices = vertices/6371000 # to RE +magnetopause = np.array(np.split(vertices, x_point_n+1)) # magnetopause points grouped by x-axis coordinate + +## take separate arrays for different 2d slice plots + +quarter_slice = int(np.shape(magnetopause)[1]/4) +# xy plane: z=0 +xy_slice = np.concatenate((magnetopause[:,0][::-1], magnetopause[:,2*quarter_slice])) +# xz plane: y=0 +xz_slice = np.concatenate((magnetopause[:,quarter_slice][::-1], magnetopause[:,3*quarter_slice])) + + +# analysator 3dcolormapslice y=0 + +def external_plot(ax,XmeshXY=None, YmeshXY=None, pass_maps=None, requestvariables=False): + if requestvariables==True: + return ['vg_v'] + ax.plot(xz_slice[:,0], xz_slice[:,2], color='limegreen', linewidth=1.5) + +pt.plot.plot_colormap3dslice( +filename=file_name, +run=run, +outputdir=outdir, +boxre = [-21,21,-21,21], +external = external_plot, +colormap='inferno', +normal='y' +) + +# analysator 3dcolormapslice z=0 + +def external_plot(ax,XmeshXY=None, YmeshXY=None, pass_maps=None, requestvariables=False): + if requestvariables==True: + return ['vg_v'] + ax.plot(xy_slice[:,0], xy_slice[:,1], color='limegreen', linewidth=1.5) + +pt.plot.plot_colormap3dslice( +filename=file_name, +outputdir=outdir, +run=run, +boxre = [-21,21,-21,21], +external = external_plot, +colormap='inferno', +normal='z') From 58e84bf5f5fea5149dbdf42152d297e475cf4c92 Mon Sep 17 00:00:00 2001 From: Markku Alho Date: Mon, 16 Jun 2025 09:54:01 +0300 Subject: [PATCH 014/124] Added yt dependency for the 2d script --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index ceeb7f0d..a1c26867 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,6 +26,7 @@ dependencies = [ "matplotlib", "packaging", "scikit-image", + "yt" ] [project.optional-dependencies] none = [ From 7303815e50a6f97159b69642c5d67bbd478d940c Mon Sep 17 00:00:00 2001 From: jreimi Date: Mon, 14 Jul 2025 15:20:56 +0300 Subject: [PATCH 015/124] function and file name changes and removal of unnecessary surface making function from calculations --- analysator/pyCalculations/calculations.py | 4 ++-- .../{magnetopause2d.py => magnetopause_sw_streamline_2d.py} | 2 +- .../{magnetopause3d.py => magnetopause_sw_streamline_3d.py} | 2 +- ..._2d_magnetopause.py => plot_streamline_magnetopause_2d.py} | 2 +- ..._3d_magnetopause.py => plot_streamline_magnetopause_3d.py} | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) rename analysator/pyCalculations/{magnetopause2d.py => magnetopause_sw_streamline_2d.py} (97%) rename analysator/pyCalculations/{magnetopause3d.py => magnetopause_sw_streamline_3d.py} (98%) rename examples/{plot_2d_magnetopause.py => plot_streamline_magnetopause_2d.py} (92%) rename examples/{plot_3d_magnetopause.py => plot_streamline_magnetopause_3d.py} (96%) diff --git a/analysator/pyCalculations/calculations.py b/analysator/pyCalculations/calculations.py index cb39b3d6..d9ea5bfd 100644 --- a/analysator/pyCalculations/calculations.py +++ b/analysator/pyCalculations/calculations.py @@ -61,5 +61,5 @@ from non_maxwellianity import epsilon_M from null_lines import LMN_null_lines_FOTE from interpolator_amr import AMRInterpolator, supported_amr_interpolators -from magnetopause2d import find_magnetopause_2d -from magnetopause3d import find_magnetopause_3d, make_surface +from magnetopause_sw_streamline_2d import find_magnetopause_sw_streamline_2d +from magnetopause_sw_streamline_3d import find_magnetopause_sw_streamline_3d diff --git a/analysator/pyCalculations/magnetopause2d.py b/analysator/pyCalculations/magnetopause_sw_streamline_2d.py similarity index 97% rename from analysator/pyCalculations/magnetopause2d.py rename to analysator/pyCalculations/magnetopause_sw_streamline_2d.py index 203f6ebb..4cf2a3b2 100644 --- a/analysator/pyCalculations/magnetopause2d.py +++ b/analysator/pyCalculations/magnetopause_sw_streamline_2d.py @@ -141,7 +141,7 @@ def make_magnetopause(streamlines, end_x=-15*6371000, x_point_n=50): return magnetopause -def find_magnetopause_2d(vlsvfile, streamline_seeds=None, seeds_n=200, seeds_x0=20*6371000, seeds_range=[-5*6371000, 5*6371000], streamline_length=45*6371000, end_x=-15*6371000, x_point_n=50): +def find_magnetopause_sw_streamline_2d(vlsvfile, streamline_seeds=None, seeds_n=200, seeds_x0=20*6371000, seeds_range=[-5*6371000, 5*6371000], streamline_length=45*6371000, end_x=-15*6371000, x_point_n=50): """Finds the magnetopause position by tracing streamlines of the velocity field for 2d runs. :param vlsvfile: directory and file name of .vlsv data file to use for VlsvReader diff --git a/analysator/pyCalculations/magnetopause3d.py b/analysator/pyCalculations/magnetopause_sw_streamline_3d.py similarity index 98% rename from analysator/pyCalculations/magnetopause3d.py rename to analysator/pyCalculations/magnetopause_sw_streamline_3d.py index d2505e85..c6d1c6c1 100644 --- a/analysator/pyCalculations/magnetopause3d.py +++ b/analysator/pyCalculations/magnetopause_sw_streamline_3d.py @@ -268,7 +268,7 @@ def make_magnetopause(streams, end_x=-15*6371000, x_point_n=50, sector_n=36): return magnetopause -def find_magnetopause_3d(vlsvfile, streamline_seeds=None, seeds_n=25, seeds_x0=20*6371000, seeds_range=[-5*6371000, 5*6371000], dl=2e6, iterations=200, end_x=-15*6371000, x_point_n=50, sector_n=36): +def find_magnetopause_sw_streamline_3d(vlsvfile, streamline_seeds=None, seeds_n=25, seeds_x0=20*6371000, seeds_range=[-5*6371000, 5*6371000], dl=2e6, iterations=200, end_x=-15*6371000, x_point_n=50, sector_n=36): """Finds the magnetopause position by tracing streamlines of the velocity field. :param vlsvfile: path to .vlsv bulk file to use for VlsvReader diff --git a/examples/plot_2d_magnetopause.py b/examples/plot_streamline_magnetopause_2d.py similarity index 92% rename from examples/plot_2d_magnetopause.py rename to examples/plot_streamline_magnetopause_2d.py index 71e92213..7358c8b1 100644 --- a/examples/plot_2d_magnetopause.py +++ b/examples/plot_streamline_magnetopause_2d.py @@ -9,7 +9,7 @@ outdir= "" # magnetopause points -magnetopause = pt.calculations.find_magnetopause_2d( +magnetopause = pt.calculations.find_magnetopause_sw_streamline_2d( file_name, streamline_seeds=None, seeds_n=200, diff --git a/examples/plot_3d_magnetopause.py b/examples/plot_streamline_magnetopause_3d.py similarity index 96% rename from examples/plot_3d_magnetopause.py rename to examples/plot_streamline_magnetopause_3d.py index 3d1ac7d5..1cb8d1d9 100644 --- a/examples/plot_3d_magnetopause.py +++ b/examples/plot_streamline_magnetopause_3d.py @@ -13,7 +13,7 @@ ## magnetopause x_point_n = 50 # number needed for 2d slice plots, default is 50 -vertices, faces = pt.calculations.find_magnetopause_3d( +vertices, faces = pt.calculations.find_magnetopause_sw_streamline_3d( file_name, streamline_seeds=None, seeds_n=25, From 40842f09d55ae69c88908516cf1bc77eac945c68 Mon Sep 17 00:00:00 2001 From: jreimi Date: Tue, 15 Jul 2025 10:03:17 +0300 Subject: [PATCH 016/124] Changed magnetopause stuff names in the sphinx directory to (hopefully) fix import issues --- Documentation/sphinx/magnetopause2d.rst | 5 ----- Documentation/sphinx/magnetopause3d.rst | 5 ----- .../sphinx/magnetopause_sw_streamline_2d.rst | 5 +++++ .../sphinx/magnetopause_sw_streamline_3d.rst | 5 +++++ Documentation/sphinx/scripts.rst | 16 ++++++++-------- 5 files changed, 18 insertions(+), 18 deletions(-) delete mode 100644 Documentation/sphinx/magnetopause2d.rst delete mode 100644 Documentation/sphinx/magnetopause3d.rst create mode 100644 Documentation/sphinx/magnetopause_sw_streamline_2d.rst create mode 100644 Documentation/sphinx/magnetopause_sw_streamline_3d.rst diff --git a/Documentation/sphinx/magnetopause2d.rst b/Documentation/sphinx/magnetopause2d.rst deleted file mode 100644 index 8e1717f1..00000000 --- a/Documentation/sphinx/magnetopause2d.rst +++ /dev/null @@ -1,5 +0,0 @@ -magnetopause2d --------------- - -.. automodule:: magnetopause2d - :members: \ No newline at end of file diff --git a/Documentation/sphinx/magnetopause3d.rst b/Documentation/sphinx/magnetopause3d.rst deleted file mode 100644 index 7d90752f..00000000 --- a/Documentation/sphinx/magnetopause3d.rst +++ /dev/null @@ -1,5 +0,0 @@ -magnetopause3d --------------- - -.. automodule:: magnetopause3d - :members: \ No newline at end of file diff --git a/Documentation/sphinx/magnetopause_sw_streamline_2d.rst b/Documentation/sphinx/magnetopause_sw_streamline_2d.rst new file mode 100644 index 00000000..6822aac9 --- /dev/null +++ b/Documentation/sphinx/magnetopause_sw_streamline_2d.rst @@ -0,0 +1,5 @@ +magnetopause_sw_streamline_2d +----------------------------- + +.. automodule:: magnetopause_sw_streamline_2d + :members: \ No newline at end of file diff --git a/Documentation/sphinx/magnetopause_sw_streamline_3d.rst b/Documentation/sphinx/magnetopause_sw_streamline_3d.rst new file mode 100644 index 00000000..590ca38a --- /dev/null +++ b/Documentation/sphinx/magnetopause_sw_streamline_3d.rst @@ -0,0 +1,5 @@ +magnetopause_sw_streamline_3d +----------------------------- + +.. automodule:: magnetopause_sw_streamline_3d + :members: \ No newline at end of file diff --git a/Documentation/sphinx/scripts.rst b/Documentation/sphinx/scripts.rst index 080c0f90..baaa22ad 100644 --- a/Documentation/sphinx/scripts.rst +++ b/Documentation/sphinx/scripts.rst @@ -21,20 +21,20 @@ gics ------------ -magnetopause2d --------------- -:doc:`magnetopause2d` +magnetopause_sw_streamline_2d +----------------------------- +:doc:`magnetopause_sw_streamline_2d` -.. automodule:: magnetopause2d +.. automodule:: magnetopause_sw_streamline_2d :no-index: ------------ -magnetopause3d --------------- -:doc:`magnetopause3d` +magnetopause_sw_streamline_3d +----------------------------- +:doc:`magnetopause_sw_streamline_3d` -.. automodule:: magnetopause3d +.. automodule:: magnetopause_sw_streamline_3d :no-index: ------------ From 1a3502f6f2a3825847b828d0f82a98ca7e1f92b4 Mon Sep 17 00:00:00 2001 From: jreimi Date: Tue, 15 Jul 2025 10:07:32 +0300 Subject: [PATCH 017/124] Added fix for toctree --- Documentation/sphinx/scripts.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/sphinx/scripts.rst b/Documentation/sphinx/scripts.rst index baaa22ad..5f0f3698 100644 --- a/Documentation/sphinx/scripts.rst +++ b/Documentation/sphinx/scripts.rst @@ -81,8 +81,8 @@ tsyganenko biot_savart gics - magnetopause2d - magnetopause3d + magnetopause_sw_streamline_2d + magnetopause_sw_streamline_3d obliqueshock obliqueshock_nif shue From 6eb059be7338899414fe95d8a4ae5ba6a2c33683 Mon Sep 17 00:00:00 2001 From: jreimi Date: Mon, 16 Jun 2025 10:10:47 +0300 Subject: [PATCH 018/124] Function name changes, removed unused import --- analysator/pyCalculations/magnetopause_sw_streamline_3d.py | 1 - 1 file changed, 1 deletion(-) diff --git a/analysator/pyCalculations/magnetopause_sw_streamline_3d.py b/analysator/pyCalculations/magnetopause_sw_streamline_3d.py index c6d1c6c1..18dcd34b 100644 --- a/analysator/pyCalculations/magnetopause_sw_streamline_3d.py +++ b/analysator/pyCalculations/magnetopause_sw_streamline_3d.py @@ -2,7 +2,6 @@ Finds the magnetopause position by tracing streamlines of the plasma flow for three-dimensional Vlasiator runs. ''' -import matplotlib.pyplot as plt import numpy as np import analysator as pt From 6a69fac782f0e72727307052b9faa690cc40ae34 Mon Sep 17 00:00:00 2001 From: jreimi Date: Thu, 26 Jun 2025 09:38:36 +0300 Subject: [PATCH 019/124] Fix to surface making and an attempt to make the subsolar area nicer --- .../magnetopause_sw_streamline_3d.py | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/analysator/pyCalculations/magnetopause_sw_streamline_3d.py b/analysator/pyCalculations/magnetopause_sw_streamline_3d.py index 18dcd34b..8fa965bc 100644 --- a/analysator/pyCalculations/magnetopause_sw_streamline_3d.py +++ b/analysator/pyCalculations/magnetopause_sw_streamline_3d.py @@ -121,10 +121,18 @@ def make_surface(coords): next_triangles = [x + slices_in_plane*area_index for x in first_triangles] faces.extend(next_triangles) - # Change every other face triangle normal direction so that all face the same way - faces = np.array(faces) - for i in range(len(faces)): + # From last triangles remove every other triangle + # (a single subsolar point -> last triangles are actual triangles instead of rectangles sliced in two) + removed = 0 + for i in range(len(faces)-slices_in_plane, len(faces)): if i%2==0: + faces.pop(i-removed) + removed += 1 + + # Change every other face triangle (except for last slice triangles) normal direction so that all face the same way + faces = np.array(faces) + for i in range(len(faces)-int(slices_in_plane/2)): + if i%2!=0: faces[i,1], faces[i,2] = faces[i,2], faces[i,1] return np.array(verts), faces @@ -196,7 +204,10 @@ def make_magnetopause(streams, end_x=-15*6371000, x_point_n=50, sector_n=36): ## define points in the x axis where to find magnetopause points on the yz-plane - x_points = np.linspace(subsolar_x, end_x, x_point_n) + #dx = (subsolar_x-end_x)/x_point_n + next_from_subsolar_x = subsolar_x-1e3 # start making the magnetopause from a point slightly inwards from subsolar point + x_point_n = x_point_n-1 + x_points = np.linspace(next_from_subsolar_x, end_x, x_point_n) ## interpolate more exact points for streamlines at exery x_point new_streampoints = np.zeros((len(x_points), len(streams), 2)) # new array for keeping interpolated streamlines in form new_streampoints[x_point, streamline, y and z -coordinates] @@ -249,7 +260,7 @@ def make_magnetopause(streams, end_x=-15*6371000, x_point_n=50, sector_n=36): # discard 'points' with r=0 and check that there's at least one streamline point in the sector sector_points = sector_points[sector_points[:,0] != 0.0] if sector_points.size == 0: - raise ValueError('No streamlines found in the sector') + raise ValueError('No streamlines found in the sector, x_i=',i) # find the points closest to the x-axis closest_point_radius = sector_points[sector_points[:,0].argmin(), 0] # smallest radius From 4340cc92f51ef7a0ee13f801f1d9e51d35e26b77 Mon Sep 17 00:00:00 2001 From: jreimi Date: Tue, 15 Jul 2025 11:08:34 +0300 Subject: [PATCH 020/124] More possible fixes to surface making, function name changes also to examples --- .../magnetopause_sw_streamline_3d.py | 22 +++++++++++++++---- examples/plot_streamline_magnetopause_2d.py | 2 +- examples/plot_streamline_magnetopause_3d.py | 4 ++-- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/analysator/pyCalculations/magnetopause_sw_streamline_3d.py b/analysator/pyCalculations/magnetopause_sw_streamline_3d.py index 8fa965bc..b955dadf 100644 --- a/analysator/pyCalculations/magnetopause_sw_streamline_3d.py +++ b/analysator/pyCalculations/magnetopause_sw_streamline_3d.py @@ -124,14 +124,28 @@ def make_surface(coords): # From last triangles remove every other triangle # (a single subsolar point -> last triangles are actual triangles instead of rectangles sliced in two) removed = 0 - for i in range(len(faces)-slices_in_plane, len(faces)): - if i%2==0: + for i in range(len(faces)-slices_in_plane*2, len(faces)): + if i%2!=0: faces.pop(i-removed) removed += 1 - # Change every other face triangle (except for last slice triangles) normal direction so that all face the same way + # From last triangles remove every other triangle + # (a single subsolar point -> last triangles are actual triangles instead of rectangles sliced in two) + # Also fix the last triangles so that they only point to one subsolar point and have normals towards outside + subsolar_index = int(len(verts)-slices_in_plane) + + for i,triangle in enumerate(reversed(faces)): + if i > (slices_in_plane): # faces not in last plane (we're going backwards) + break + + faces[len(faces)-i-1] = np.clip(triangle, a_min=0, a_max=subsolar_index) + + # this would remove duplicate subsolar points from vertices but makes 2d slicing harder + #verts = verts[:int(len(verts)-slices_in_plane+1)] + + # Change every other face triangle (except for last slice triangles) normal direction so that all face the same way (hopefully) faces = np.array(faces) - for i in range(len(faces)-int(slices_in_plane/2)): + for i in range(len(faces)-int(slices_in_plane)): if i%2!=0: faces[i,1], faces[i,2] = faces[i,2], faces[i,1] diff --git a/examples/plot_streamline_magnetopause_2d.py b/examples/plot_streamline_magnetopause_2d.py index 7358c8b1..933daf23 100644 --- a/examples/plot_streamline_magnetopause_2d.py +++ b/examples/plot_streamline_magnetopause_2d.py @@ -1,5 +1,5 @@ """ -An example of using find_magnetopause_2d() and plotting the output over colormap. +An example of using find_magnetopause_2d_sw_streamline() and plotting the output over colormap. """ import analysator as pt diff --git a/examples/plot_streamline_magnetopause_3d.py b/examples/plot_streamline_magnetopause_3d.py index 1cb8d1d9..40b4e1a4 100644 --- a/examples/plot_streamline_magnetopause_3d.py +++ b/examples/plot_streamline_magnetopause_3d.py @@ -1,5 +1,5 @@ """ -An example of using find_magnetopause_3d() and plotting the output. +An example of using find_find_magnetopause_3d_sw_streamline() and plotting the output. """ import numpy as np @@ -40,7 +40,7 @@ ### 2D slice plots ### vertices = vertices/6371000 # to RE -magnetopause = np.array(np.split(vertices, x_point_n+1)) # magnetopause points grouped by x-axis coordinate +magnetopause = np.array(np.split(vertices, x_point_n)) # magnetopause points grouped by x-axis coordinate ## take separate arrays for different 2d slice plots From dc08ad275cb5c1641fe80a82abc77ec2ed4a2347 Mon Sep 17 00:00:00 2001 From: jreimi Date: Tue, 15 Jul 2025 11:55:31 +0300 Subject: [PATCH 021/124] Removed streamline magnetopause from scripts in sphinx --- .../sphinx/magnetopause_sw_streamline_2d.rst | 5 ----- .../sphinx/magnetopause_sw_streamline_3d.rst | 5 ----- Documentation/sphinx/scripts.rst | 19 ------------------- 3 files changed, 29 deletions(-) delete mode 100644 Documentation/sphinx/magnetopause_sw_streamline_2d.rst delete mode 100644 Documentation/sphinx/magnetopause_sw_streamline_3d.rst diff --git a/Documentation/sphinx/magnetopause_sw_streamline_2d.rst b/Documentation/sphinx/magnetopause_sw_streamline_2d.rst deleted file mode 100644 index 6822aac9..00000000 --- a/Documentation/sphinx/magnetopause_sw_streamline_2d.rst +++ /dev/null @@ -1,5 +0,0 @@ -magnetopause_sw_streamline_2d ------------------------------ - -.. automodule:: magnetopause_sw_streamline_2d - :members: \ No newline at end of file diff --git a/Documentation/sphinx/magnetopause_sw_streamline_3d.rst b/Documentation/sphinx/magnetopause_sw_streamline_3d.rst deleted file mode 100644 index 590ca38a..00000000 --- a/Documentation/sphinx/magnetopause_sw_streamline_3d.rst +++ /dev/null @@ -1,5 +0,0 @@ -magnetopause_sw_streamline_3d ------------------------------ - -.. automodule:: magnetopause_sw_streamline_3d - :members: \ No newline at end of file diff --git a/Documentation/sphinx/scripts.rst b/Documentation/sphinx/scripts.rst index 5f0f3698..d5a8d32d 100644 --- a/Documentation/sphinx/scripts.rst +++ b/Documentation/sphinx/scripts.rst @@ -21,23 +21,6 @@ gics ------------ -magnetopause_sw_streamline_2d ------------------------------ -:doc:`magnetopause_sw_streamline_2d` - -.. automodule:: magnetopause_sw_streamline_2d - :no-index: - ------------- - -magnetopause_sw_streamline_3d ------------------------------ -:doc:`magnetopause_sw_streamline_3d` - -.. automodule:: magnetopause_sw_streamline_3d - :no-index: - ------------- obliqueshock ------------ @@ -81,8 +64,6 @@ tsyganenko biot_savart gics - magnetopause_sw_streamline_2d - magnetopause_sw_streamline_3d obliqueshock obliqueshock_nif shue From da57da39c706215732c9c584ea4f1f027b5ba98d Mon Sep 17 00:00:00 2001 From: jreimi Date: Thu, 17 Jul 2025 13:17:28 +0300 Subject: [PATCH 022/124] Move streamline magnetopause script usage examples to scripts --- {examples => scripts}/plot_streamline_magnetopause_2d.py | 0 {examples => scripts}/plot_streamline_magnetopause_3d.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {examples => scripts}/plot_streamline_magnetopause_2d.py (100%) rename {examples => scripts}/plot_streamline_magnetopause_3d.py (100%) diff --git a/examples/plot_streamline_magnetopause_2d.py b/scripts/plot_streamline_magnetopause_2d.py similarity index 100% rename from examples/plot_streamline_magnetopause_2d.py rename to scripts/plot_streamline_magnetopause_2d.py diff --git a/examples/plot_streamline_magnetopause_3d.py b/scripts/plot_streamline_magnetopause_3d.py similarity index 100% rename from examples/plot_streamline_magnetopause_3d.py rename to scripts/plot_streamline_magnetopause_3d.py From 1fc418f4d75cbdfe3b9e423c440ec88824d7e4ca Mon Sep 17 00:00:00 2001 From: jreimi Date: Thu, 17 Jul 2025 13:30:09 +0300 Subject: [PATCH 023/124] Updated sphinx --- .../sphinx/plot_streamline_magnetopause_2d.rst | 5 +++++ .../sphinx/plot_streamline_magnetopause_3d.rst | 5 +++++ Documentation/sphinx/scripts.rst | 16 ++++++++++++++++ 3 files changed, 26 insertions(+) create mode 100644 Documentation/sphinx/plot_streamline_magnetopause_2d.rst create mode 100644 Documentation/sphinx/plot_streamline_magnetopause_3d.rst diff --git a/Documentation/sphinx/plot_streamline_magnetopause_2d.rst b/Documentation/sphinx/plot_streamline_magnetopause_2d.rst new file mode 100644 index 00000000..64ef079a --- /dev/null +++ b/Documentation/sphinx/plot_streamline_magnetopause_2d.rst @@ -0,0 +1,5 @@ +plot_streamline_magnetopause_2d +------------------------------- + +.. automodule:: plot_streamline_magnetopause_2d + :members: \ No newline at end of file diff --git a/Documentation/sphinx/plot_streamline_magnetopause_3d.rst b/Documentation/sphinx/plot_streamline_magnetopause_3d.rst new file mode 100644 index 00000000..153d948c --- /dev/null +++ b/Documentation/sphinx/plot_streamline_magnetopause_3d.rst @@ -0,0 +1,5 @@ +plot_streamline_magnetopause_3d +------------------------------- + +.. automodule:: plot_streamline_magnetopause_3d + :members: \ No newline at end of file diff --git a/Documentation/sphinx/scripts.rst b/Documentation/sphinx/scripts.rst index d5a8d32d..09c3ed44 100644 --- a/Documentation/sphinx/scripts.rst +++ b/Documentation/sphinx/scripts.rst @@ -21,6 +21,20 @@ gics ------------ +plot_streamline_magnetopause_2d +------------------------------- + +.. automodule:: plot_streamline_magnetopause_2d + :no-index: +------------ + +plot_streamline_magnetopause_3d +------------------------------- + +.. automodule:: plot_streamline_magnetopause_3d + :no-index: + +------------ obliqueshock ------------ @@ -64,6 +78,8 @@ tsyganenko biot_savart gics + plot_streamline_magnetopause_2d + plot_streamline_magnetopause_3d obliqueshock obliqueshock_nif shue From f6bff74bec41c0be30ab04084723fa7cea507ab4 Mon Sep 17 00:00:00 2001 From: jreimi Date: Thu, 17 Jul 2025 13:35:20 +0300 Subject: [PATCH 024/124] Added blank lines --- Documentation/sphinx/plot_streamline_magnetopause_2d.rst | 3 ++- Documentation/sphinx/plot_streamline_magnetopause_3d.rst | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Documentation/sphinx/plot_streamline_magnetopause_2d.rst b/Documentation/sphinx/plot_streamline_magnetopause_2d.rst index 64ef079a..38e7d4fc 100644 --- a/Documentation/sphinx/plot_streamline_magnetopause_2d.rst +++ b/Documentation/sphinx/plot_streamline_magnetopause_2d.rst @@ -2,4 +2,5 @@ plot_streamline_magnetopause_2d ------------------------------- .. automodule:: plot_streamline_magnetopause_2d - :members: \ No newline at end of file + :members: + \ No newline at end of file diff --git a/Documentation/sphinx/plot_streamline_magnetopause_3d.rst b/Documentation/sphinx/plot_streamline_magnetopause_3d.rst index 153d948c..f1794ffb 100644 --- a/Documentation/sphinx/plot_streamline_magnetopause_3d.rst +++ b/Documentation/sphinx/plot_streamline_magnetopause_3d.rst @@ -2,4 +2,5 @@ plot_streamline_magnetopause_3d ------------------------------- .. automodule:: plot_streamline_magnetopause_3d - :members: \ No newline at end of file + :members: + \ No newline at end of file From 35965fec7abb0d816e7e5d6572333a9f59ffb17c Mon Sep 17 00:00:00 2001 From: jreimi Date: Thu, 17 Jul 2025 14:00:48 +0300 Subject: [PATCH 025/124] fixes for sphinx --- Documentation/sphinx/scripts.rst | 1 + scripts/plot_streamline_magnetopause_2d.py | 66 ++++++----- scripts/plot_streamline_magnetopause_3d.py | 126 +++++++++++---------- 3 files changed, 103 insertions(+), 90 deletions(-) diff --git a/Documentation/sphinx/scripts.rst b/Documentation/sphinx/scripts.rst index 09c3ed44..6ec5ee9d 100644 --- a/Documentation/sphinx/scripts.rst +++ b/Documentation/sphinx/scripts.rst @@ -26,6 +26,7 @@ plot_streamline_magnetopause_2d .. automodule:: plot_streamline_magnetopause_2d :no-index: + ------------ plot_streamline_magnetopause_3d diff --git a/scripts/plot_streamline_magnetopause_2d.py b/scripts/plot_streamline_magnetopause_2d.py index 933daf23..4c17e8b0 100644 --- a/scripts/plot_streamline_magnetopause_2d.py +++ b/scripts/plot_streamline_magnetopause_2d.py @@ -1,35 +1,41 @@ """ -An example of using find_magnetopause_2d_sw_streamline() and plotting the output over colormap. +An example of using find_magnetopause_sw_streamline_2d() and plotting the output over colormap. """ import analysator as pt -run = 'BFD' -file_name="/wrk-vakka/group/spacephysics/vlasiator/2D/"+run+"/bulk/bulk.0002000.vlsv" -outdir= "" - -# magnetopause points -magnetopause = pt.calculations.find_magnetopause_sw_streamline_2d( - file_name, - streamline_seeds=None, - seeds_n=200, - seeds_x0=20*6371000, - seeds_range=[-5*6371000, 5*6371000], - streamline_length=45*6371000, - end_x=-15*6371000, - x_point_n=50) - -magnetopause= magnetopause*(1/6371000) # in RE - -def external_plot(ax,XmeshXY=None, YmeshXY=None, pass_maps=None): - ax.plot(magnetopause[:,0], magnetopause[:,1], color='cyan', linewidth=1.0) - -pt.plot.plot_colormap( - filename=file_name, - var="rho", - boxre = [-21,21,-21,21], - Earth=True, - external = external_plot, - run=run, - colormap='inferno', - ) +def main(): + + run = 'BFD' # for output file name + file_name="/wrk-vakka/group/spacephysics/vlasiator/2D/BFD/bulk/bulk.0002000.vlsv" + + # magnetopause points + magnetopause = pt.calculations.find_magnetopause_sw_streamline_2d( + file_name, + streamline_seeds=None, + seeds_n=200, + seeds_x0=20*6371000, + seeds_range=[-5*6371000, 5*6371000], + streamline_length=45*6371000, + end_x=-15*6371000, + x_point_n=50) + + magnetopause = magnetopause*(1/6371000) # in RE + + def external_plot(ax,XmeshXY=None, YmeshXY=None, pass_maps=None): + ax.plot(magnetopause[:,0], magnetopause[:,1], color='cyan', linewidth=1.0) + + pt.plot.plot_colormap( + filename=file_name, + var="rho", + boxre = [-21,21,-21,21], + Earth=True, + external = external_plot, + run=run, + colormap='inferno', + ) + + +if __name__ == "__main__": + main() + diff --git a/scripts/plot_streamline_magnetopause_3d.py b/scripts/plot_streamline_magnetopause_3d.py index 40b4e1a4..8c661b12 100644 --- a/scripts/plot_streamline_magnetopause_3d.py +++ b/scripts/plot_streamline_magnetopause_3d.py @@ -1,85 +1,91 @@ """ -An example of using find_find_magnetopause_3d_sw_streamline() and plotting the output. +An example of using find_magnetopause_sw_streamline_3d() and plotting the output. """ import numpy as np import matplotlib.pyplot as plt import analysator as pt -## get bulk data -run = 'EGI' # for plot output file name -file_name="/wrk-vakka/group/spacephysics/vlasiator/3D/"+run+"/bulk/bulk5.0000070.vlsv" +def main(): + ## get bulk data + run = 'EGI' # for plot output file name + file_name="/wrk-vakka/group/spacephysics/vlasiator/3D/EGI/bulk/bulk5.0000070.vlsv" -## magnetopause -x_point_n = 50 # number needed for 2d slice plots, default is 50 + ## magnetopause + x_point_n = 50 # number needed for 2d slice plots, default is 50 -vertices, faces = pt.calculations.find_magnetopause_sw_streamline_3d( - file_name, - streamline_seeds=None, - seeds_n=25, - seeds_x0=20*6371000, - seeds_range=[-5*6371000, 5*6371000], - dl=2e6, - iterations=200, - end_x=-15*6371000, - x_point_n=x_point_n, - sector_n=36) + vertices, faces = pt.calculations.find_magnetopause_sw_streamline_3d( + file_name, + streamline_seeds=None, + seeds_n=25, + seeds_x0=20*6371000, + seeds_range=[-5*6371000, 5*6371000], + dl=2e6, + iterations=200, + end_x=-15*6371000, + x_point_n=x_point_n, + sector_n=36) -outdir="" # output plot save directory + outdir="" # output plot save directory -### 3D surface plot ### + ### 3D surface plot ### -fig = plt.figure() -ax = fig.add_subplot(projection='3d') -ax.plot_trisurf(vertices[:, 0], vertices[:,1], faces, vertices[:,2], linewidth=0.2) -plt.savefig(outdir+run+'_magnetopause_3D_surface.png') -plt.close() + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + ax.plot_trisurf(vertices[:, 0], vertices[:,1], faces, vertices[:,2], linewidth=0.2) + plt.savefig(outdir+run+'_magnetopause_3D_surface.png') + plt.close() -### 2D slice plots ### + ### 2D slice plots ### -vertices = vertices/6371000 # to RE -magnetopause = np.array(np.split(vertices, x_point_n)) # magnetopause points grouped by x-axis coordinate + vertices = vertices/6371000 # to RE + magnetopause = np.array(np.split(vertices, x_point_n)) # magnetopause points grouped by x-axis coordinate -## take separate arrays for different 2d slice plots + ## take separate arrays for different 2d slice plots -quarter_slice = int(np.shape(magnetopause)[1]/4) -# xy plane: z=0 -xy_slice = np.concatenate((magnetopause[:,0][::-1], magnetopause[:,2*quarter_slice])) -# xz plane: y=0 -xz_slice = np.concatenate((magnetopause[:,quarter_slice][::-1], magnetopause[:,3*quarter_slice])) + quarter_slice = int(np.shape(magnetopause)[1]/4) + # xy plane: z=0 + xy_slice = np.concatenate((magnetopause[:,0][::-1], magnetopause[:,2*quarter_slice])) + # xz plane: y=0 + xz_slice = np.concatenate((magnetopause[:,quarter_slice][::-1], magnetopause[:,3*quarter_slice])) -# analysator 3dcolormapslice y=0 + # analysator 3dcolormapslice y=0 -def external_plot(ax,XmeshXY=None, YmeshXY=None, pass_maps=None, requestvariables=False): - if requestvariables==True: - return ['vg_v'] - ax.plot(xz_slice[:,0], xz_slice[:,2], color='limegreen', linewidth=1.5) + def external_plot(ax,XmeshXY=None, YmeshXY=None, pass_maps=None, requestvariables=False): + if requestvariables==True: + return ['vg_v'] + ax.plot(xz_slice[:,0], xz_slice[:,2], color='limegreen', linewidth=1.5) -pt.plot.plot_colormap3dslice( -filename=file_name, -run=run, -outputdir=outdir, -boxre = [-21,21,-21,21], -external = external_plot, -colormap='inferno', -normal='y' -) + pt.plot.plot_colormap3dslice( + filename=file_name, + run=run, + outputdir=outdir, + boxre = [-21,21,-21,21], + external = external_plot, + colormap='inferno', + normal='y' + ) -# analysator 3dcolormapslice z=0 + # analysator 3dcolormapslice z=0 -def external_plot(ax,XmeshXY=None, YmeshXY=None, pass_maps=None, requestvariables=False): - if requestvariables==True: - return ['vg_v'] - ax.plot(xy_slice[:,0], xy_slice[:,1], color='limegreen', linewidth=1.5) + def external_plot(ax,XmeshXY=None, YmeshXY=None, pass_maps=None, requestvariables=False): + if requestvariables==True: + return ['vg_v'] + ax.plot(xy_slice[:,0], xy_slice[:,1], color='limegreen', linewidth=1.5) -pt.plot.plot_colormap3dslice( -filename=file_name, -outputdir=outdir, -run=run, -boxre = [-21,21,-21,21], -external = external_plot, -colormap='inferno', -normal='z') + pt.plot.plot_colormap3dslice( + filename=file_name, + outputdir=outdir, + run=run, + boxre = [-21,21,-21,21], + external = external_plot, + colormap='inferno', + normal='z') + + + +if __name__ == "__main__": + main() From a5a68b533acc1c3b479144898667940f51db647f Mon Sep 17 00:00:00 2001 From: jreimi Date: Thu, 17 Jul 2025 14:04:37 +0300 Subject: [PATCH 026/124] more fixes for sphinx --- Documentation/sphinx/scripts.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Documentation/sphinx/scripts.rst b/Documentation/sphinx/scripts.rst index 6ec5ee9d..dffdadaf 100644 --- a/Documentation/sphinx/scripts.rst +++ b/Documentation/sphinx/scripts.rst @@ -23,14 +23,16 @@ gics plot_streamline_magnetopause_2d ------------------------------- +:doc:`plot_streamline_magnetopause_2d` .. automodule:: plot_streamline_magnetopause_2d :no-index: - + ------------ plot_streamline_magnetopause_3d ------------------------------- +:doc:`plot_streamline_magnetopause_3d` .. automodule:: plot_streamline_magnetopause_3d :no-index: From 71594a69cd022b3ddabbf0a20c54439ca852bc04 Mon Sep 17 00:00:00 2001 From: Markku Alho Date: Tue, 19 Aug 2025 12:27:01 +0300 Subject: [PATCH 027/124] Remove debug output --- analysator/pyVlsv/vlsvreader.py | 1 - 1 file changed, 1 deletion(-) diff --git a/analysator/pyVlsv/vlsvreader.py b/analysator/pyVlsv/vlsvreader.py index 4a96d6ff..075a562b 100644 --- a/analysator/pyVlsv/vlsvreader.py +++ b/analysator/pyVlsv/vlsvreader.py @@ -3919,7 +3919,6 @@ def get_mesh_domain_extents(self, mesh): lowcorners = lowcorners_all[start:end,:] dxs = dxs_all[start:end,:] highcorners = lowcorners+dxs - print(np.sum(n_domain_cells), ncells, start, end,lowcorners) mins = np.min(lowcorners,axis=0) maxs = np.max(highcorners,axis=0) From 52015d86aef64c131d2b7b8eb4328d9089085284 Mon Sep 17 00:00:00 2001 From: Markku Alho Date: Wed, 20 Aug 2025 13:41:09 +0300 Subject: [PATCH 028/124] vlsvReader.read_interpolated_variable test_variable to use the first set of coordinates to read the test var, instead of CellID 1 (which may not be read to fileindex otherwise) --- analysator/pyVlsv/vlsvreader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/analysator/pyVlsv/vlsvreader.py b/analysator/pyVlsv/vlsvreader.py index bd22e93e..44842514 100644 --- a/analysator/pyVlsv/vlsvreader.py +++ b/analysator/pyVlsv/vlsvreader.py @@ -1711,7 +1711,7 @@ def read_interpolated_variable(self, name, coords, operator="pass",periodic=[Tru coordinates = np.atleast_2d(coordinates) # Check one value for the length - test_variable = self.read_variable(name,cellids=[1],operator=operator) + test_variable = self.read_variable(name,cellids=[self.get_cellid(coordinates[0,:])],operator=operator) if isinstance(test_variable,np.ma.core.MaskedConstant): value_length=1 elif isinstance(test_variable, Iterable): From a70e0e4894f9ca0cc10de1b95c134fabc3250359 Mon Sep 17 00:00:00 2001 From: Markku Alho Date: Wed, 20 Aug 2025 14:23:55 +0300 Subject: [PATCH 029/124] Fix undeclared var --- analysator/pyVlsv/vlsvreader.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/analysator/pyVlsv/vlsvreader.py b/analysator/pyVlsv/vlsvreader.py index 44842514..47987451 100644 --- a/analysator/pyVlsv/vlsvreader.py +++ b/analysator/pyVlsv/vlsvreader.py @@ -1953,9 +1953,10 @@ def get_fsgrid_decomposition(self): else: # Decomposition is a list (or fail assertions below) - use it instead pass - + + numWritingRanks = self.read_parameter("numWritingRanks") assert len(self.__fsGridDecomposition) == 3, "Manual FSGRID decomposition should have three elements, but is "+str(self.__fsGridDecomposition) - assert np.prod(self.__fsGridDecomposition) == self.read_parameter("numWritingRanks"), "Manual FSGRID decomposition should have a product of numWritingRanks ("+str(numWritingRanks)+"), but is " + str(np.prod(self.__fsGridDecomposition)) + " for decomposition "+str(self.__fsGridDecomposition) + assert np.prod(self.__fsGridDecomposition) == numWritingRanks, "Manual FSGRID decomposition should have a product of numWritingRanks ("+str(numWritingRanks)+"), but is " + str(np.prod(self.__fsGridDecomposition)) + " for decomposition "+str(self.__fsGridDecomposition) self.add_metadata(("MESH_DECOMPOSITION","fsgrid"), self.__fsGridDecomposition) From cab54a9b829c02324b14332181fa3e50c7b4da12 Mon Sep 17 00:00:00 2001 From: Markku Alho Date: Wed, 20 Aug 2025 14:36:04 +0300 Subject: [PATCH 030/124] Fix docstrings --- analysator/pyVlsv/vlsvreader.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/analysator/pyVlsv/vlsvreader.py b/analysator/pyVlsv/vlsvreader.py index 47987451..7c707528 100644 --- a/analysator/pyVlsv/vlsvreader.py +++ b/analysator/pyVlsv/vlsvreader.py @@ -351,7 +351,9 @@ def get_h5_metadata(self, key, default): :param data: str, a key to stored metadata. :param default + ''' + if type(key) == type(("a tuple",)): print("tuple reader not implemented") elif type(key) == type("a string"): @@ -377,6 +379,7 @@ def get_reader_metadata(self, key, default): :param data: str, a key to stored metadata. :param default + ''' From 12c3820ac70361b2d55df3cbdf9f952f77add7be Mon Sep 17 00:00:00 2001 From: Markku Alho Date: Wed, 20 Aug 2025 14:48:27 +0300 Subject: [PATCH 031/124] Fix docstrings properly after fixing the thing that caused a wrong warning... --- analysator/pyVlsv/vlsvreader.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/analysator/pyVlsv/vlsvreader.py b/analysator/pyVlsv/vlsvreader.py index 7c707528..4c817ae4 100644 --- a/analysator/pyVlsv/vlsvreader.py +++ b/analysator/pyVlsv/vlsvreader.py @@ -350,8 +350,8 @@ def get_h5_metadata(self, key, default): return the given default value. :param data: str, a key to stored metadata. - :param default - + :param default: value to return if key does not exist + ''' if type(key) == type(("a tuple",)): @@ -378,7 +378,7 @@ def get_reader_metadata(self, key, default): return the given default value. :param data: str, a key to stored metadata. - :param default + :param default: value to return if key does not exist ''' From dbba6eb27962e8f91fbe4e1f5aa632f050be88a0 Mon Sep 17 00:00:00 2001 From: Markku Alho Date: Wed, 20 Aug 2025 14:54:40 +0300 Subject: [PATCH 032/124] Add vlsvcache.py, start moving cache functionalities there --- analysator/pyVlsv/vlsvcache.py | 27 +++++++++++++++++++++++++++ analysator/pyVlsv/vlsvreader.py | 3 ++- 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 analysator/pyVlsv/vlsvcache.py diff --git a/analysator/pyVlsv/vlsvcache.py b/analysator/pyVlsv/vlsvcache.py new file mode 100644 index 00000000..cd8fe3fb --- /dev/null +++ b/analysator/pyVlsv/vlsvcache.py @@ -0,0 +1,27 @@ +# +# This file is part of Analysator. +# Copyright 2013-2016 Finnish Meteorological Institute +# Copyright 2017-2024 University of Helsinki +# +# For details of usage, see the COPYING file and read the "Rules of the Road" +# at http://www.physics.helsinki.fi/vlasiator/ +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +import logging +import os +import sys +import warnings diff --git a/analysator/pyVlsv/vlsvreader.py b/analysator/pyVlsv/vlsvreader.py index 4c817ae4..060a55c6 100644 --- a/analysator/pyVlsv/vlsvreader.py +++ b/analysator/pyVlsv/vlsvreader.py @@ -1,4 +1,4 @@ -#s +# # This file is part of Analysator. # Copyright 2013-2016 Finnish Meteorological Institute # Copyright 2017-2024 University of Helsinki @@ -34,6 +34,7 @@ import h5py import vlsvvariables +import vlsvcache from reduction import datareducers,multipopdatareducers,data_operators,v5reducers,multipopv5reducers,deprecated_datareducers try: from collections.abc import Iterable From 131894fe1e32ce54c6c460fe00f82e3912275602 Mon Sep 17 00:00:00 2001 From: Markku Alho Date: Wed, 20 Aug 2025 17:09:23 +0300 Subject: [PATCH 033/124] Moving cache stuff to vlsvcache --- analysator/pyVlsv/vlsvcache.py | 84 +++++++++++++++++++++++++++++++++ analysator/pyVlsv/vlsvreader.py | 30 ++---------- 2 files changed, 88 insertions(+), 26 deletions(-) diff --git a/analysator/pyVlsv/vlsvcache.py b/analysator/pyVlsv/vlsvcache.py index cd8fe3fb..28cee4bd 100644 --- a/analysator/pyVlsv/vlsvcache.py +++ b/analysator/pyVlsv/vlsvcache.py @@ -25,3 +25,87 @@ import os import sys import warnings +import numbers +import numpy as np +from operator import itemgetter +import h5py + +class VariableCache: + ''' Class for handling in-memory variable/reducer caching. + ''' + def __init__(self, reader): + self.__varcache = {} # {(varname, operator):data} + self.__reader = reader + + def keys(self): + return self.__varcache.keys() + + def read_variable_from_cache(self, name, cellids, operator): + ''' Read variable from cache instead of the vlsv file. + :param name: Name of the variable + :param cellids: a value of -1 reads all data + :param operator: Datareduction operator. "pass" does no operation on data + :returns: numpy array with the data, same format as read_variable + + .. seealso:: :func:`read_variable` + ''' + + var_data = self.__varcache[(name,operator)] + if var_data.ndim == 2: + value_len = var_data.shape[1] + else: + value_len = 1 + + if isinstance(cellids, numbers.Number): + if cellids == -1: + return var_data + else: + return var_data[self.__reader.get_cellid_locations()[cellids]] + else: + if(len(cellids) > 0): + indices = np.array(itemgetter(*cellids)(self.__reader.get_cellid_locations()),dtype=np.int64) + else: + indices = np.array([],dtype=np.int64) + if value_len == 1: + return var_data[indices] + else: + return var_data[indices,:] + +class FileCache: + ''' Top-level class for caching to file. + ''' + pass + +class MetadataFileCache(FileCache): + pass + +class VariableFileCache(FileCache): + pass + +class PicklableFile(object): + ''' Picklable file pointer object. + ''' + def __init__(self, fileobj): + self.fileobj = fileobj + + def __getattr__(self, key): + return getattr(self.fileobj, key) + + def __getstate__(self): + ret = self.__dict__.copy() + ret['_file_name'] = self.fileobj.name + ret['_file_mode'] = self.fileobj.mode + if self.fileobj.closed: + ret['_file_pos'] = 0 + else: + ret['_file_pos'] = self.fileobj.tell() + del ret['fileobj'] + return ret + + def __setstate__(self, dict): + self.fileobj = open(dict['_file_name'], dict['_file_mode']) + self.fileobj.seek(dict['_file_pos']) + del dict['_file_name'] + del dict['_file_mode'] + del dict['_file_pos'] + self.__dict__.update(dict) diff --git a/analysator/pyVlsv/vlsvreader.py b/analysator/pyVlsv/vlsvreader.py index 060a55c6..351004fe 100644 --- a/analysator/pyVlsv/vlsvreader.py +++ b/analysator/pyVlsv/vlsvreader.py @@ -51,31 +51,7 @@ interp_method_aliases = {"trilinear":"linear"} -class PicklableFile(object): - def __init__(self, fileobj): - self.fileobj = fileobj - - def __getattr__(self, key): - return getattr(self.fileobj, key) - - def __getstate__(self): - ret = self.__dict__.copy() - ret['_file_name'] = self.fileobj.name - ret['_file_mode'] = self.fileobj.mode - if self.fileobj.closed: - ret['_file_pos'] = 0 - else: - ret['_file_pos'] = self.fileobj.tell() - del ret['fileobj'] - return ret - - def __setstate__(self, dict): - self.fileobj = open(dict['_file_name'], dict['_file_mode']) - self.fileobj.seek(dict['_file_pos']) - del dict['_file_name'] - del dict['_file_mode'] - del dict['_file_pos'] - self.__dict__.update(dict) + def dict_keys_exist(dictionary, query_keys, prune_unique=False): if query_keys.shape[0] == 0: @@ -180,7 +156,7 @@ def __init__(self, file_name, fsGridDecomposition=None): self.file_name = file_name try: - self.__fptr = PicklableFile(open(self.file_name,"rb")) + self.__fptr = vlsvcache.PicklableFile(open(self.file_name,"rb")) except FileNotFoundError as e: logging.info("File not found: " + self.file_name) raise e @@ -188,6 +164,8 @@ def __init__(self, file_name, fsGridDecomposition=None): self.__xml_root = ET.fromstring("") self.__fileindex_for_cellid={} + self.__metadata_cache = vlsvcache.MetadataFileCache() + self.__metadata_read = False self.__metadata_dict = {} # Used for reading in and storing derived/other metadata such as linked file paths self.__linked_files = set() From ed8e6ea3d2d35899f39d33a4403505567f6a1006 Mon Sep 17 00:00:00 2001 From: jreimi Date: Wed, 20 Aug 2025 17:31:23 +0300 Subject: [PATCH 034/124] Better SW magnetopause and region classification draft --- analysator/pyCalculations/fieldtracer.py | 4 +- .../magnetopause_sw_streamline_3d.py | 191 ++++++++++++++---- 2 files changed, 151 insertions(+), 44 deletions(-) diff --git a/analysator/pyCalculations/fieldtracer.py b/analysator/pyCalculations/fieldtracer.py index 195c7d05..f383c760 100644 --- a/analysator/pyCalculations/fieldtracer.py +++ b/analysator/pyCalculations/fieldtracer.py @@ -294,7 +294,7 @@ def find_unit_vector(vg, coord): points_traced_unique[mask_update,i,:] = next_points[mask_update,:] # distances = np.linalg.norm(points_traced_unique[:,i,:],axis = 1) - mask_update[stop_condition(vlsvReader, points_traced_unique[:,i,:])] = False + mask_update[stop_condition(vlsvReader, points_traced_unique[:,i,:], var_unit)] = False # points_traced_unique[~mask_update, i, :] = points_traced_unique[~mask_update, i-1, :] @@ -302,7 +302,7 @@ def find_unit_vector(vg, coord): return points_traced # Default stop tracing condition for the vg tracing, (No stop until max_iteration) -def default_stopping_condition(vlsvReader, points): +def default_stopping_condition(vlsvReader, points, last_unit_v): [xmin, ymin, zmin, xmax, ymax, zmax] = vlsvReader.get_spatial_mesh_extent() x = points[:, 0] y = points[:, 1] diff --git a/analysator/pyCalculations/magnetopause_sw_streamline_3d.py b/analysator/pyCalculations/magnetopause_sw_streamline_3d.py index b955dadf..d3acc149 100644 --- a/analysator/pyCalculations/magnetopause_sw_streamline_3d.py +++ b/analysator/pyCalculations/magnetopause_sw_streamline_3d.py @@ -2,8 +2,10 @@ Finds the magnetopause position by tracing streamlines of the plasma flow for three-dimensional Vlasiator runs. ''' +import matplotlib.pyplot as plt import numpy as np import analysator as pt +import vtk def cartesian_to_polar(cartesian_coords): # for segments of plane """Converts cartesian coordinates to polar (for the segments of the yz-planes). @@ -31,6 +33,25 @@ def polar_to_cartesian(r, phi): return(y, z) +def cartesian_to_spherical(cartesian_coords): + """ Cartesian to spherical coordinates (Note: axes are swapped!)""" + x, y, z = cartesian_coords[0], cartesian_coords[1], cartesian_coords[2] + r = np.sqrt(x**2+y**2+z**2) + theta = np.arctan2(np.sqrt(y**2+z**2), x) #inclination + phi = np.arctan2(z, y)+np.pi # azimuth + + return [r, theta, phi] + + + +def spherical_to_cartesian(sphe_coords): + r, theta, phi = sphe_coords[0], sphe_coords[1], sphe_coords[2] + y = r*np.sin(theta)*np.cos(phi) + z = r*np.sin(theta)*np.sin(phi) + x = r*np.cos(theta) + return [x, y, z] + + def interpolate(streamline, x_points): """Interpolates a single streamline for make_magnetopause(). @@ -46,9 +67,9 @@ def interpolate(streamline, x_points): zp = arr[:,2][::-1] #interpolate z from xz-plane - z_points = np.interp(x_points, xp, zp, left=np.NaN, right=np.NaN) + z_points = np.interp(x_points, xp, zp, left=np.nan, right=np.nan) #interpolate y from xy-plane - y_points = np.interp(x_points, xp, yp, left=np.NaN, right=np.NaN) + y_points = np.interp(x_points, xp, yp, left=np.nan, right=np.nan) return np.array([x_points, y_points, z_points]) @@ -121,35 +142,56 @@ def make_surface(coords): next_triangles = [x + slices_in_plane*area_index for x in first_triangles] faces.extend(next_triangles) - # From last triangles remove every other triangle - # (a single subsolar point -> last triangles are actual triangles instead of rectangles sliced in two) - removed = 0 - for i in range(len(faces)-slices_in_plane*2, len(faces)): - if i%2!=0: - faces.pop(i-removed) - removed += 1 + # Change every other face triangle normal direction so that all face the same way + faces = np.array(faces) + for i in range(len(faces)): + if i%2==0: + faces[i,1], faces[i,2] = faces[i,2], faces[i,1] + + return np.array(verts), faces - # From last triangles remove every other triangle - # (a single subsolar point -> last triangles are actual triangles instead of rectangles sliced in two) - # Also fix the last triangles so that they only point to one subsolar point and have normals towards outside - subsolar_index = int(len(verts)-slices_in_plane) - for i,triangle in enumerate(reversed(faces)): - if i > (slices_in_plane): # faces not in last plane (we're going backwards) - break +def make_vtk_surface(vertices, faces): + """Makes a vtk DataSetSurfaceFilter from vertices and faces of a triangulated surface. - faces[len(faces)-i-1] = np.clip(triangle, a_min=0, a_max=subsolar_index) + :param vertices: vertex points of triangles in shape [[x0, y0, z0], [x1, y1, z1],...] + :param faces: face connectivities as indices of vertices so that triangle normals point outwards + :returns: a vtkDataSetSurfaceFilter object + """ - # this would remove duplicate subsolar points from vertices but makes 2d slicing harder - #verts = verts[:int(len(verts)-slices_in_plane+1)] + points = vtk.vtkPoints() - # Change every other face triangle (except for last slice triangles) normal direction so that all face the same way (hopefully) - faces = np.array(faces) - for i in range(len(faces)-int(slices_in_plane)): - if i%2!=0: - faces[i,1], faces[i,2] = faces[i,2], faces[i,1] + for vert in vertices: + points.InsertNextPoint(vert) - return np.array(verts), faces + # make vtk PolyData object + + triangles = vtk.vtkCellArray() + for face in faces: + triangle = vtk.vtkTriangle() + triangle.GetPointIds().SetId(0, face[0]) + triangle.GetPointIds().SetId(1, face[1]) + triangle.GetPointIds().SetId(2, face[2]) + triangles.InsertNextCell(triangle) + + polydata = vtk.vtkPolyData() + polydata.SetPoints(points) + polydata.Modified() + polydata.SetPolys(triangles) + polydata.Modified() + + surface = vtk.vtkDataSetSurfaceFilter() + surface.SetInputData(polydata) + surface.Update() + + return surface + +def streamline_stopping_condition(vlsvReader, points, value): + [xmin, ymin, zmin, xmax, ymax, zmax] = vlsvReader.get_spatial_mesh_extent() + x = points[:, 0] + y = points[:, 1] + z = points[:, 2] + return (x < xmin)|(x > xmax) | (y < ymin)|(y > ymax) | (z < zmin)|(z > zmax)|(value[:,0] > 0) def make_streamlines(vlsvfile, streamline_seeds=None, seeds_n=25, seeds_x0=20*6371000, seeds_range=[-5*6371000, 5*6371000], dl=2e6, iterations=200): @@ -187,7 +229,9 @@ def make_streamlines(vlsvfile, streamline_seeds=None, seeds_n=25, seeds_x0=20*63 max_iterations=iterations, dx=dl, direction='+', - grid_var='vg_v') + grid_var='vg_v', + stop_condition=streamline_stopping_condition + ) return streams @@ -206,6 +250,11 @@ def make_magnetopause(streams, end_x=-15*6371000, x_point_n=50, sector_n=36): """ RE = 6371000 + ignore = 0 + + ## if given sector number isn't divisible by 4, make it so because we want to have magnetopause points at exactly y=0 and z=0 for 2d slices of the whole thing + while sector_n%4 != 0: + sector_n +=1 #streams = streams*(1/RE) # streamlines in rE streampoints = np.reshape(streams, (streams.shape[0]*streams.shape[1], 3)) #all the points in one array) @@ -214,14 +263,63 @@ def make_magnetopause(streams, end_x=-15*6371000, x_point_n=50, sector_n=36): ## do this by finding a streamline point on positive x axis closest to the Earth # streampoints closer than ~1 rE to positive x-axis: x_axis_points = streampoints[(streampoints[:,1]-RE) & (streampoints[:,2]>-RE) & (streampoints[:,0]>0) & (streampoints[:,0]>0)] - subsolar_x =np.min(x_axis_points[:,0]) + if ignore == 0: + subsolar_x =np.min(x_axis_points[:,0]) + else: + subsolar_x = np.partition(x_axis_points[:,0], ignore)[ignore] # take the nth point as subsolar point + + ### dayside magnetopause ### + # for x > 0, look for magnetopause radially + dayside_points = streampoints[streampoints[:,0] > 0] + + phi_slices = sector_n + theta_slices = 10 + phi_step = 2*np.pi/phi_slices + theta_step = np.pi/(2*theta_slices) # positive x-axis only + + def cartesian_to_spherical_grid(cartesian_coords): + # Only for x > 0 + r, theta, phi = cartesian_to_spherical(cartesian_coords) + theta_idx = int(theta/theta_step) + phi_idx = int(phi/phi_step) + return [theta_idx, phi_idx, r] + + def grid_mid_point(theta_idx, phi_idx): + return (theta_idx+0.5)*theta_step, (phi_idx+0.5)*phi_step + + # make a dictionary based on spherical areas by theta index and phi index + sph_points = {} + for point in dayside_points: + sph_gridpoint = cartesian_to_spherical_grid(point) + idxs = (sph_gridpoint[0],sph_gridpoint[1]) # key is (theta_i, phi_i) + if idxs not in sph_points: + sph_points[idxs] = [sph_gridpoint[2]] + else: + sph_points[idxs].append(sph_gridpoint[2]) + + # dayside magetopause from subsolar point towards origo + dayside_magnetopause = np.zeros((theta_slices, phi_slices, 3)) + for ring_idx in range(theta_slices): + ring_points = np.zeros((phi_slices, 3)) + for phi_idx in range(phi_slices): + if (ring_idx, phi_idx) in sph_points: + if ignore == 0: + nth_min_r = np.min(np.array(sph_points[(ring_idx, phi_idx)])) # point in area with smallest radius + else: + nth_min_r = np.partition(np.array(sph_points[(ring_idx, phi_idx)]), ignore)[ignore] + else: + print("no ", (ring_idx, phi_idx)) + exit() # something wrong + midpoint_theta, midpoint_phi = grid_mid_point(ring_idx, phi_idx) # the smallest radius will be assigned to a point in the middle-ish of the area + ring_points[phi_idx] = np.array(spherical_to_cartesian([nth_min_r, midpoint_theta, midpoint_phi])) + + dayside_magnetopause[ring_idx] = ring_points + ### x < 0 magnetopause ### + # rest: look for magnetopause in yz-planes ## define points in the x axis where to find magnetopause points on the yz-plane - #dx = (subsolar_x-end_x)/x_point_n - next_from_subsolar_x = subsolar_x-1e3 # start making the magnetopause from a point slightly inwards from subsolar point - x_point_n = x_point_n-1 - x_points = np.linspace(next_from_subsolar_x, end_x, x_point_n) + x_points = np.linspace(subsolar_x, end_x, x_point_n) ## interpolate more exact points for streamlines at exery x_point new_streampoints = np.zeros((len(x_points), len(streams), 2)) # new array for keeping interpolated streamlines in form new_streampoints[x_point, streamline, y and z -coordinates] @@ -247,10 +345,6 @@ def make_magnetopause(streams, end_x=-15*6371000, x_point_n=50, sector_n=36): ## now start making the magnetopause ## in each x_point, divide the plane into sectors and look for the closest streamline to x-axis in the sector - ## if given sector number isn't divisible by 4, make it so because we want to have magnetopause points at exactly y=0 and z=0 for 2d slices of the whole thing - while sector_n%4 != 0: - sector_n +=1 - sector_width = 360/sector_n magnetopause = np.zeros((len(x_points), sector_n, 3)) @@ -274,20 +368,29 @@ def make_magnetopause(streams, end_x=-15*6371000, x_point_n=50, sector_n=36): # discard 'points' with r=0 and check that there's at least one streamline point in the sector sector_points = sector_points[sector_points[:,0] != 0.0] if sector_points.size == 0: - raise ValueError('No streamlines found in the sector, x_i=',i) + raise ValueError('No streamlines found in the sector') # find the points closest to the x-axis - closest_point_radius = sector_points[sector_points[:,0].argmin(), 0] # smallest radius - + if ignore == 0: + nth_closest_point_radius = sector_points[sector_points[:,0].argmin(), 0] # smallest radius + else: + nth_closest_point_radius = np.partition(sector_points[:,0], ignore)[ignore] + + #closest_point_radius = np.median(sector_points[:,0]) # median radius + #if x_point < subsolar_x-2e6: + # closest_point_radius = np.median(sector_points[:,0]) # median radius + #else: + # closest_point_radius = sector_points[sector_points[:,0].argmin(), 0] # smallest radius + # return to cartesian coordinates and save as a magnetopause point at the middle of the sector - y,z = polar_to_cartesian(closest_point_radius, mean_sector_angle) + y,z = polar_to_cartesian(nth_closest_point_radius, mean_sector_angle) magnetopause[i,j,:] = [x_point, y, z] # make a tip point for the magnetopause for prettier 3d plots tip = np.array([subsolar_x, 0, 0]) tips = np.tile(tip, (magnetopause.shape[1],1)) - magnetopause = np.vstack(([tips], magnetopause)) + magnetopause = np.vstack(([tips], dayside_magnetopause, magnetopause)) return magnetopause @@ -306,11 +409,15 @@ def find_magnetopause_sw_streamline_3d(vlsvfile, streamline_seeds=None, seeds_n= :kword x_point_n: integer, how many x-axis points the magnetopause will be divided in between the subsolar point and tail :kword sector_n: integer, how many sectors the magnetopause will be divided in on each yz-plane - :returns: vertices, faces of the magnetopause triangle mesh as numpy arrays + :returns: vertices, surface where vertices are numpy arrays in shape [[x0,y0,z0], [x1,y1,z1],...] and surface is a vtk vtkDataSetSurfaceFilter object """ streams = make_streamlines(vlsvfile, streamline_seeds, seeds_n, seeds_x0, seeds_range, dl, iterations) magnetopause = make_magnetopause(streams, end_x, x_point_n, sector_n) vertices, faces = make_surface(magnetopause) + surface = make_vtk_surface(vertices, faces) + + return vertices, surface + - return vertices, faces + From 01836c3ffc6ca922762afcb5f80822eecf7a7d35 Mon Sep 17 00:00:00 2001 From: jreimi Date: Wed, 20 Aug 2025 17:33:47 +0300 Subject: [PATCH 035/124] Documentation drafts and regions script --- Documentation/sphinx/bowshock.rst | 69 ++ Documentation/sphinx/magnetopause.rst | 122 ++++ .../sphinx/magnetosphere_regions.rst | 140 +++++ scripts/regions.py | 594 ++++++++++++++++++ 4 files changed, 925 insertions(+) create mode 100644 Documentation/sphinx/bowshock.rst create mode 100644 Documentation/sphinx/magnetopause.rst create mode 100644 Documentation/sphinx/magnetosphere_regions.rst create mode 100644 scripts/regions.py diff --git a/Documentation/sphinx/bowshock.rst b/Documentation/sphinx/bowshock.rst new file mode 100644 index 00000000..365d9dc4 --- /dev/null +++ b/Documentation/sphinx/bowshock.rst @@ -0,0 +1,69 @@ +Bow shock: How to find +====================== + + +Plasma properties for estimating bow shock position: +---------------------------------------------------- + +* plasma compression + * :math:`n_p > 2n_{p, sw}` [Battarbee_et_al_2020]_ (Vlasiator) +* solar wind core heating + * :math:`T_{core} > 4T_{sw}` [Battarbee_et_al_2020]_ (Vlasiator) + * :math:`T_{core} = 3T_{sw}` [Suni_et_al_2021]_ (Vlasiator) +* magnetosonic Mach number + * :math:`M_{ms} < 1` [Battarbee_et_al_2020]_ (Vlasiator) + + + + +In analysator: +-------------- + +regions.py: + +*method*: + +Convex hull from cells where :math:`n_p > 1.5 n_{p, sw}` +criteria + +also velocity? + + +Foreshock: +---------- + +*properties:* + +* larger fluctuations in the magnetic field, plasma velocity and plasma density than unperturbed solar wind [Grison_et_al_2025]_ + + +Magnetosheath +------------- + +properties: + +* density: + * :math:`8 cm^{-3}` [Hudges_Introduction_to_space_physics_Ch_9]_ +* temperature: + * ion: :math:`150 eV` [Hudges_Introduction_to_space_physics_Ch_9]_ + * electron: :math:`25 eV` [Hudges_Introduction_to_space_physics_Ch_9]_ +* magnetic field: + * :math:`15 nT` [Hudges_Introduction_to_space_physics_Ch_9]_ +* plasma :math:`\beta`: + * 2.5 [Hudges_Introduction_to_space_physics_Ch_9]_ + + +Regions inside the bow shock: +----------------------------- + +* magnetosheath: area inside bow shock but outside the magnetopause, see ... +* magnetosphere: area inside magnetopause, see again ... and ... for magnetosphere regions + + +------------ + +References: + +.. [Battarbee_et_al_2020] Battarbee, M., Ganse, U., Pfau-Kempf, Y., Turc, L., Brito, T., Grandin, M., Koskela, T., and Palmroth, M.: Non-locality of Earth's quasi-parallel bow shock: injection of thermal protons in a hybrid-Vlasov simulation, Ann. Geophys., 38, 625-643, https://doi.org/10.5194/angeo-38-625-2020, 2020 +.. [Suni_et_al_2021] Suni, J., Palmroth, M., Turc, L., Battarbee, M., Johlander, A., Tarvus, V., et al. (2021). Connection between foreshock structures and the generation of magnetosheath jets: Vlasiator results. Geophysical Research Letters, 48, e2021GL095655. https://doi. org/10.1029/2021GL095655 +.. [Grison_et_al_2025] Grison, B., Darrouzet, F., Maggiolo, R. et al. Localization of the Cluster satellites in the geospace environment. Sci Data 12, 327 (2025). https://doi.org/10.1038/s41597-025-04639-z diff --git a/Documentation/sphinx/magnetopause.rst b/Documentation/sphinx/magnetopause.rst new file mode 100644 index 00000000..b5b68520 --- /dev/null +++ b/Documentation/sphinx/magnetopause.rst @@ -0,0 +1,122 @@ +Magnetopause: how to find +========================= + +Magnetopause + +Plasma beta, beta* +------------------ + + +Modified plasma beta + +.. math:: \beta * = \dfrac{P_{th}P_{dyn}}{B^2/2\mu_0} + +[Xu_et_al_2016]_, [Brenner_et_al_2021]_ + + +The beta* gets values below 1 inside the magnetosphere near magnetopause and can be used to create a magnetopause surface. + +Caveats: magnetotail current sheet has beta* :math:`>` 1 + +**in analysator:** + +datareducer: beta_star, vg_beta_star + +regions.py: cells with beta* value within a certain treshold are chosen, and a convex hull is constructed with vtk to represent the magnetopause. +Ideal values of beta* for magnetopause construction might be run-dependent, and the surface construction works best with a small-ish range of beta* but still big enough to gather cells evenly from all sides. + + +.. [Xu_et_al_2016] Xu, S., M. W. Liemohn, C. Dong, D. L. Mitchell, S. W. Bougher, and Y. Ma (2016), Pressure and ion composition boundaries at Mars, J. Geophys. Res. Space Physics, 121, 6417–6429, doi:10.1002/2016JA022644. +.. [Brenner_et_al_2021] Brenner A, Pulkkinen TI, Al Shidi Q and Toth G (2021) Stormtime Energetics: Energy Transport Across the Magnetopause in a Global MHD Simulation. Front. Astron. Space Sci. 8:756732. doi: 10.3389/fspas.2021.756732 + + + +Field line connectivity +----------------------- + + + + +Solar wind flow +--------------- + +Method used in e.g. [Parlmroth_et_al_2003_] + +Streamlines of velocity field that are traced from outside the bow shock curve around the magnetopause. + +Caveats: sometimes some streamlines can curve into the magnetotail + + +**In analysator:** + +magnetopause_sw_streamline_2d.py +magnetopause_sw_streamline_3d.py + +Streamlines are traced from outside the bow shock towards Earth. A subsolar point for the magnetopause is chosen to be where streamlines get closest to Earth in x-axis [y/z~0]. + +From subsolar point towards Earth, .... + + +From the Earth towards negative x the space is divided into yz-planes. +Each yz-plane is then divided into sectors and magnetopause is marked to be in the middle of the sector with the radius of the closest streamline to the x-axis. + +For subsolar point, radial dayside and -x planes the closest streamline point can be changed to be n:th closest by setting keyword "ignore", in which case n-1 closest streamline points are not taken into acocunt. + + +2d: +streamlines: [...] +magnetopause: [...] + +example usage in [...] + + +3d: +streamlines: [...] +magnetopause: [...] +surface triangulation: [...] + +example usage in [...] + + + + + + +.. [Parlmroth_et_al_2003] Palmroth, M., T. I. Pulkkinen, P. Janhunen, and C.-C. Wu (2003), Stormtime energy transfer in global MHD simulation, J. Geophys. Res., 108, 1048, doi:10.1029/2002JA009446, A1. + + + + +Shue et al. (1997) +------------------ + +The Shue et al. (1997) [Shue_et_al_1997]_ mangnetopause model: + +.. math:: + + r_0 &= (11.4 + 0.013 B_z)(D_p)^{-\frac{1}{6.6}}, for B_z \geq 0 \\ + &= (11.4 + 0.14 B_z)(D_p)^{-\frac{1}{6.6}}, for B_z \leq 0 + +.. math:: + + \alpha = (0.58-0.010B_z)(1+0.010 D_p) + + +where :math:`D_p` is the dynamic pressure of the solar wind and :math:`B_z` the magnetic field z-component magnitude. +:math:`r_0` is the magnetopause standoff distance and :math:`\alpha` is the level of tail flaring. + + +The magnetopause radius as a function of angle :math:`\theta` is + +.. math:: + r = r_0 (\frac{2}{1+\cos \theta})^\alpha + +**In analysator:** + +*shue.py* in *scripts*: + + + + + +.. [Shue_et_al_1997] Shue, J.-H., Chao, J. K., Fu, H. C., Russell, C. T., Song, P., Khurana, K. K., and Singer, H. J. (1997). A new functional form to study the solar wind control of the magnetopause size and shape. Journal of Geophysical Research: Space Physics, 102(A5):9497–9511. eprint: https://agupubs.onlinelibrary.wiley.com/doi/pdf/10.1029/97JA00196. \ No newline at end of file diff --git a/Documentation/sphinx/magnetosphere_regions.rst b/Documentation/sphinx/magnetosphere_regions.rst new file mode 100644 index 00000000..81f1ec60 --- /dev/null +++ b/Documentation/sphinx/magnetosphere_regions.rst @@ -0,0 +1,140 @@ +Magnetosphere regions: How to find +================================== + + + + +Cusps +----- + + +*Properties:* + +* plasma density: high density in comparison to solar wind + * Ion density :math:`\geq` solar wind ion density [Pitout_et_al_2006_] (Cluster spacecraft data) +* ion energy: + * mean ion energy :math:`~2-3 keV` [Pitout_et_al_2006_] /[Stenuit_et_al_2001_] +* energy flux: + * energy flux + + + +**In analysator:** + + +Tail lobes +---------- + +* plasma density: low + * below :math:`0.03 cm^{-3}` [Grison_et_al_2025_] (Cluster spacecraft data) + * :math:`0.01 cm^{-3}` [Koskinen_Space_Storms_ p.38] + * less than :math:`0.1 cm^{-3}` [Wolf_Introduction_to_space_physics_Ch_10_ p.291] +* plasma :math:`\beta`: low + * typically around :math:`0.05` [Grison_et_al_2025_] (Cluster spacecraft data) + * :math:`3e-3` [Koskinen_Space_Storms_ p.38] +* temperature: + * ion temperature :math:`300 eV` [Koskinen_Space_Storms_ p.38] + * electron temperature :math:`50 eV` [Koskinen_Space_Storms_ p.38] +* magnetic field: + * :math:`20 nT` [Koskinen_Space_Storms_ p.38] +* open magnetic field lines [Wolf_Introduction_to_space_physics_Ch_10_ p.291] +* strong and stable magnetic field towards the Earth (northern lobe) and away from the Earth (southern lobe) [Coxon_et_al_2016_] + +Separated from the plasma sheet by the plasma sheet boundary layer (PSBL) + + +**in analysator:** + +regions.py + +conditions: + +* inside the magnetosphere +* plasma :math:`\beta` .... + + + + +Low-latitude boundary layer (LLBL) +---------------------------------- + + + +Properties: + +* density: + * ion number densities between those of magnetosphere and magnetosheath [Hudges_Introduction_to_space_physics_Ch_9_ p.267] +* temperature + * ion temperatures between those of magnetosphere and magnetosheath [Hudges_Introduction_to_space_physics_Ch_9_ p.267] +* unknown field line configuration, probably a mix of open and closed field lines [Hudges_Introduction_to_space_physics_Ch_9_ p.262] + + + +High-latitude boundary layer (HLBL) +----------------------------------- + +Includes the plasma mantle on the tail side and the entry layer on the dayside [... cit.] + +Properties: + +* open magnetic field lines [Hudges_Introduction_to_space_physics_Ch_9_ p.261] + + + + + +Plasma sheet boundary layer (PSBL) +---------------------------------- + +The plasma sheet boundary layer is a very thin boundary layer separating the tail lobes from the tail plasma sheet [Koskinen_Johdatus_] + +*Properties:* + +* density: + * :math:`0.1 cm^{-3}` [Koskinen_Space_Storms_ p.38] +* temperature: + * ion temperature :math:`1000 eV` [Koskinen_Space_Storms_ p.38] + * electron temperature :math:`150 eV` [Koskinen_Space_Storms_ p.38] +* magnetic field: + * :math:`20 nT` [Koskinen_Space_Storms_ p.38] +* plasma :math:`\beta` : + * :math:`0.1` [Koskinen_Space_Storms_ p.38] +* probably closed magnetic field lines [Wolf_Introduction_to_space_physics_Ch_10_ p.291] + + + + +Central plasma sheet +-------------------- + + +*Properties:* + +* density: + * :math:`0.3 cm^{-3}` [Koskinen_Space_Storms_ p.38] + * :math:`0.1-1 cm^{-3}` [Wolf_Introduction_to_space_physics_Ch_10_ p.291] +* temperature: hot + * ion temperature :math:`4200 eV` [Koskinen_Space_Storms_ p.38] + * electron temperature :math:`600 eV` [Koskinen_Space_Storms_ p.38] +* magnetic field: + * :math:`10 nT` [Koskinen_Space_Storms_ p.38], [Hudges_Introduction_to_space_physics_Ch_9_] +* plasma :math:`\beta`: high + * :math:`6` [Koskinen_Space_Storms_ p.38] +* Mostly closed magnetic field lines [Wolf_Introduction_to_space_physics_Ch_10_] + + + + +------------ + +References + +.. [Grison_et_al_2025] Grison, B., Darrouzet, F., Maggiolo, R. et al. Localization of the Cluster satellites in the geospace environment. Sci Data 12, 327 (2025). https://doi.org/10.1038/s41597-025-04639-z +.. [Koskinen_Johdatus] Koskinen, H. E. J. (2011). Johdatus plasmafysiikkaan ja sen avaruussovellutuksiin. Limes ry. +.. [Koskinen_Space_Storms] Koskinen, H. E. J. (2011). Physics of Space Storms: From the Solar Surface to the Earth. Springer-Verlag. https://doi.org/10.1007/978-3-642-00319-6 +.. [Pitout_et_al_2006] Pitout, F., Escoubet, C. P., Klecker, B., and Rème, H.: Cluster survey of the mid-altitude cusp: 1. size, location, and dynamics, Ann. Geophys., 24, 3011–3026, https://doi.org/10.5194/angeo-24-3011-2006, 2006. +.. [Coxon_et_al_2016] Coxon,J.C.,C.M.Jackman, M. P. Freeman, C. Forsyth, and I. J. Rae (2016), Identifying the magnetotail lobes with Cluster magnetometer data, J. Geophys. Res. Space Physics, 121, 1436–1446, doi:10.1002/2015JA022020. +.. [Hudges_Introduction_to_space_physics_Ch_9] Hudges, W. J. (1995) The magnetopause, magnetotail and magnetic reconnection. In Kivelson, M. G., & Russell, C. T. (Eds.), Introduction to space physics (pp.227-287). Cambridge University Press. +.. [Wolf_Introduction_to_space_physics_Ch_10] Wolf, R. A. (1995) Magnetospheric configuration. In Kivelson, M. G., & Russell, C. T. (Eds.), Introduction to space physics (pp.288-329). Cambridge University Press. +.. [Sckopke_et_al_1981] Sckopke, N., Paschmann, G., Haerendel, G., Sonnerup, B. U. , Bame, S. J., Forbes, T. G., Hones Jr., E. W., and Russell, C. T. (1981). Structure of the low-latitude boundary layer. Journal of Geophysical Research: Space Physics, 86(A4):2099–2110. eprint: https://agupubs.onlinelibrary.wiley.com/doi/pdf/10.1029/JA086iA04p02099 +.. [Boakes_et_al_2014] Boakes, P. D., Nakamura, R., Volwerk, M., and Milan, S. E. (2014). ECLAT Cluster Spacecraft Magnetotail Plasma Region Identifications (2001–2009). Dataset Papers in Science, 2014(1):684305. eprint: https://onlinelibrary.wiley.com/doi/pdf/10.1155/2014/684305 \ No newline at end of file diff --git a/scripts/regions.py b/scripts/regions.py new file mode 100644 index 00000000..e81eefb6 --- /dev/null +++ b/scripts/regions.py @@ -0,0 +1,594 @@ +"""Script and functions for creating sidecar files with SDF/region/boundary tags of plasma regions. + + variables used to find regions/boundary regions: + rho, temperature, beta, beta_star, + + + +""" + +import analysator as pt +import numpy as np +import vtk +#from .analysator.scripts import shue + + +R_E = 6371000 + +### Signed distance field functions: ### + + +def vtkDelaunay3d_SDF(all_points, coordinates): + """Gives a signed distance to a convex hull surface created from given coordinates. + + :param all_points: points ([x, y, z] coordinates in m) for which a signed distance to surface will be calculated + :param coordinates: coordinates (array of [x, y, z]:s in m) that are used to make a surface. + :returns: array of signed distances, negative sign: inside the surface, positive sign: outside the surface + + """ + + points =vtk.vtkPoints()#.NewInstance() + for i in range(len(coordinates)): + points.InsertNextPoint(coordinates[i,0],coordinates[i,1],coordinates[i,2]) + polydata = vtk.vtkPolyData() + polydata.SetPoints(points) + polydata.Modified() + + # Delaunay convex hull + delaunay_3d = vtk.vtkDelaunay3D() + delaunay_3d.SetInputData(polydata) + delaunay_3d.Update() + + + data = delaunay_3d.GetOutput() + + surface = vtk.vtkDataSetSurfaceFilter() + surface.SetInputData(data) + surface.Update() + + implicitPolyDataDistance = vtk.vtkImplicitPolyDataDistance() + implicitPolyDataDistance.SetInput(surface.GetOutput()) + + convexhull_sdf = np.zeros(len(all_points)) + + # SDFs + for i,coord in enumerate(all_points): + convexhull_sdf[i] = implicitPolyDataDistance.EvaluateFunction(coord) + + return convexhull_sdf + + + + +def treshold_mask(data_array, value): + """Chooses and flags cells where variable values match those given. If value is given as float, relative tolerance can be given + + :param data_array: array to mask, e.g. output of f.read_variable(name="proton/vg_rho", cellids=-1) + :param variable: str, variable name + :param value: value/values to use for masking; a float or int for exact match, a (value, relative tolerance) tuple, or [min value, max value] list pair where either can be None for less than or eq./more than or eq. value + :returns: 0/1 mask in same order as cellids, 1: variable value in array inside treshold values, 0: outside + """ + + if data_array is None or value is None: # either variable isn't usable or treshold has not been given + return None + + mask = np.zeros((len(data_array))) + + if isinstance(value, float) or isinstance(value, int): # single value, exact + mask = np.where(np.isclose(data_array, value), 1, 0) + elif isinstance(value, tuple): # single value with tolerance + mask = np.where(np.isclose(data_array, value[0], rtol=value[1]), 1, 0) + else: + if value[0] is None: + mask = np.where((data_array <= value[1]), 1, 0) # anywhere where value is less than or equal to + elif value[1] is None: + mask = np.where((value[0] <= data_array), 1, 0) # anywhere where value is more than or equal to + else: + mask = np.where((value[0] <= data_array) & (data_array <= value[1]), 1, 0) # min/max + + if np.sum(mask[mask>0]) == 0: + print("Treshold mask didn't match any values in array") + return None + + return mask + + +def box_mask(f, coordpoints=None, cells=None, marginal=[150e6, 50e6, 50e6, 50e6, 50e6, 50e6]): + """Crops simulation box for calculations, output flags outside of cropped box will be 0 + + :param f: a VlsvReader + :kword coordpoints: coordinate points of cells to be masked + :kword cells: cellIDs to be masked + :kword marginal: 6-length list of wanted marginal lengths from mesh edges in meters [negx, negy, negz, posx, posy, posz] + :returns: Boolean mask in input order of coordinates/cells, -1 if no coorpoints or cells were given and mask could not be done + """ + [xmin, ymin, zmin, xmax, ymax, zmax] = f.get_spatial_mesh_extent() + + def is_inside(coords): + res = np.zeros((len(coords)), dtype=bool) + + for i,coord in enumerate(coords): + if np.any((coord[0] < xmin+marginal[0], coord[0] > xmax-marginal[3], + coord[1] < ymin+marginal[1], coord[1] > ymax-marginal[4], + coord[2] < zmin+marginal[2], coord[2] > zmax-marginal[5])): + res[i] = False + else: + res[i] =True + return res + + if coordpoints is not None: + return is_inside(coordpoints) + + elif cells is not None: + coords = f.get_cell_coordinates(cells) + return is_inside(coords) + + return -1 + +def write_flags(writer, flags, flag_name, mask=None): + """Writes flags into .vlsv-file with writer + + :param writer: a vlsvwriter + :param flags: an array of flags in order of cellids + :param flag_name: string, name of the flag variable + :kword mask: if flags are made from cropped cells, give mask used for cropping + """ + + if mask is not None: # flags exist only in cropped cells + new_flags = mask.astype(float) + new_flags[mask] = flags + writer.write_variable_info(pt.calculations.VariableInfo(new_flags, flag_name, "-", latex="",latexunits=""),"SpatialGrid",1) + + else: + #writer.write(flags,flag_name,'VARIABLE','SpatialGrid') + writer.write_variable_info(pt.calculations.VariableInfo(flags, flag_name, "-", latex="",latexunits=""),"SpatialGrid",1) + + print(flag_name+" written to file") + + + +def make_region_flags(variable_dict, condition_dict, flag_type="01", mask=None): + """Makes region flags + + example: + make_region_flags(variable_dict = {"density": f.read_variable(name="proton/vg_rho", cellids=-1)}, condition_dict = {"density": [None, 1e7]}) + + results in flag array in shape of read_variable output where cells where "proton/vg_rho" is less or equal to 1e7 are 1 and others are 0 + + + :param variable_dict: dictionary containing names and data arrays of variables, names must match those in condition_dict + :param condition_dict: dictionary containing names and conditions for variable data in variable_dict + :kword flag_type: default "01", optionally "fraction". "01" gives flags 1: all variables fill all conditions, 0: everything else; "fraction" flags are rounded fractions of varibles that fulfill conditions (e.g. flag 0.5 means one of two conditions was filled) + :kword mask: deafault None, optionally boolean array to use for variable dict arrays to search for a region from subset of cells (cells False in mask will be automatically flagged as 0) + + """ + + variable_flags = [] + + if mask is None: # search all cells + for key in condition_dict.keys(): + + try: # if key in variables + region = treshold_mask(variable_dict[key], condition_dict[key]) + if region is not None: variable_flags.append(region) + except: + print(key, "ignored") + + else: # search only masked area + for key in condition_dict.keys(): + try: # if key in variables + region = treshold_mask(variable_dict[key][mask], condition_dict[key]) + if region is not None: variable_flags.append(region) + except: + print(key, "ignored") + + + variable_flags = np.array(variable_flags) + + if len(variable_flags)==0: # no cells that fullfill conditions, hope that density is a key + if mask is None: + return np.zeros((len(variable_dict["density"]))) + else: + return np.zeros((len(variable_dict["density"][mask]))) + + + if flag_type == "01": # 1 only if all viable variables are 1, 0 otherwise + flags = np.where(np.sum(variable_flags, axis=0)==len(variable_flags), 1, 0) + return flags + + elif flag_type == "fraction": # rounded fraction of viable variables that are 1 + flags = np.sum(variable_flags, axis=0)/len(variable_flags) + return flags.round(decimals=2) + + + +def bowshock_SDF(f, variable_dict, query_points, own_condition_dict=None): + """Finds the bow shock by making a convex hull of either default variables/values based on upstream values or a user-defined variable/value dictionary. Returns signed distances from the convex hull to given query_points. + + :param f: a VlsvReader + :param variable_dict: dictionary containing names and variable arrays needed + :param query_points: xyz-coordinates of all points where the SDF will be calculated ([x1, y1, z1], [x2, y2, z2], ...) + :kword own_condition_dict: optional, dictionary with string variable names as keys and tresholds as values to pass to treshold_mask()-function + """ + + cellids =f.read_variable("CellID") + + if own_condition_dict is None: + # bow shock from upstream rho + # upstream point + upstream_point = [150e6, 0.0, 0.0] + upstream_cellid = f.get_cellid(upstream_point) + upstream_rho = f.read_variable(name="proton/vg_rho", cellids=upstream_cellid) + + bowshock_conditions = {"density": (1.5*upstream_rho, 0.1)} + bowshock_rho_flags = make_region_flags(variable_dict, bowshock_conditions, flag_type="01") + bowshock_rho_SDF = vtkDelaunay3d_SDF(query_points, f.get_cell_coordinates(cellids[bowshock_rho_flags!=0])) + return bowshock_rho_SDF + + else: # bowshock from user-defined variable and values + bowshock_rho_flags = make_region_flags(variable_dict, own_condition_dict, flag_type="01") + bowshock_rho_SDF = vtkDelaunay3d_SDF(query_points, f.get_cell_coordinates(cellids[bowshock_rho_flags!=0])) + dict_sdf = vtkDelaunay3d_SDF(query_points, f.get_cell_coordinates(cellids[bowshock_rho_flags!=0])) + + return dict_sdf + + +def magnetopause_SDF(f, datafile, vtpoutfile, variable_dict, query_points, method="beta_star_with_connectivity", own_variable_dict=None): # TODO: remove vlsvReader AND datafile name need (streamline magnetopause to only use f?) + """Finds the magnetopause by making a convex hull of either streamlines, beta*, or user-defined variables, and returns signed distances from surface to query_points. + Note: constructing the magnetopause using solar wind streamlines is slow and needs more memory than e.g. the beta*-method + + :param query_points: xyz-coordinates of all points where the SDF will be calculated ([x1, y1, z1], [x2, y2, z2], ...) + :kword method: str, specifies the method used to find magnetopause, options: "beta_star", "streamlines", "shue", "dict". If "dict" is used, own_variable_dict dictionary must be given. If "shue", run needs to be specified (TODO kwarg) + :kword own_variable_dict: when using "dict"-method, dictionary with string variable names as keys and treshold pairs as values to pass to treshold_mask()-function + """ + + if method != "streamlines": cellids =f.read_variable("CellID") + + if method == "streamlines": + + [xmin, ymin, zmin, xmax, ymax, zmax] = f.get_spatial_mesh_extent() + seeds_x0=150e6 + dl=5e5 + iters = int(((seeds_x0-xmin)/dl)+100) + sector_n = 36*2 + vertices, vtkSurface = pt.calculations.find_magnetopause_sw_streamline_3d(datafile, seeds_n=200, seeds_x0=seeds_x0, seeds_range=[-15*6371000, 15*6371000], + dl=dl, iterations=iters, end_x=xmin+15*6371000, x_point_n=100, sector_n=sector_n) + + # make the magnetopause surface from vertice points + np.random.shuffle(vertices) # helps Delaunay triangulation + Delaunay_SDF = vtkDelaunay3d_SDF(query_points, vertices) + + writer = vtk.vtkXMLPolyDataWriter() + writer.SetInputConnection(vtkSurface.GetOutputPort()) + writer.SetFileName(vtpoutfile) + writer.Write() + return Delaunay_SDF + + elif method == "beta_star": + # magnetopause from beta_star only + condition_dict = {"beta_star": [0.4, 0.5]} # FIC: [0.4, 0.5]) # EGE: [0.9, 1.0]) # max 0.6 in FHA to not take flyaways from outside magnetopause + mpause_flags = make_region_flags(variable_dict, condition_dict, flag_type="01") + contour_coords = f.get_cell_coordinates(cellids[mpause_flags!=0]) + + # make a convex hull surface with vtk's Delaunay + magnetopause_sdf = vtkDelaunay3d_SDF(query_points, contour_coords) + return magnetopause_sdf + + + elif method == "beta_star_with_connectivity": + # magnetopause from beta_star, with connectivity if possible + try: + connectivity_region = treshold_mask(variable_dict["connection"], 0) + betastar_region = treshold_mask(variable_dict["beta_star"], [0.6, 0.7]) + magnetosphere_proper = np.where((connectivity_region==1) | (betastar_region==1), 1, 0) + contour_coords = f.get_cell_coordinates(cellids[magnetosphere_proper==1]) + except: + print("using field line connectivity for magnetosphere did not work, using only beta*") + condition_dict = {"beta_star": [0.5, 0.6]} # FIC: [0.4, 0.5]) # EGE: [0.9, 1.0]) # max 0.6 in FHA to not take flyaways from outside magnetopause + mpause_flags = make_region_flags(variable_dict, condition_dict, flag_type="01") + contour_coords = f.get_cell_coordinates(cellids[mpause_flags!=0]) + + # make a convex hull surface with vtk's Delaunay + magnetopause_sdf = vtkDelaunay3d_SDF(query_points, contour_coords) + return magnetopause_sdf + + elif method == "dict": + # same method as beta* but from user-defined variables as initial contour + flags = make_region_flags(variable_dict, own_variable_dict, flag_type="01") + treshold_coords = f.get_cell_coordinates(cellids[flags!=0]) + dict_sdf = vtkDelaunay3d_SDF(query_points, treshold_coords) + + return dict_sdf + + elif method == "shue": + return 0 + theta = np.linspace(0, 2*np.pi/3 , 200) # magnetotail length decided here by trial and error: [0, 5*np.pi/6] ~ -350e6 m, [0, 2*np.pi/3] ~ -100e6 m in EGE + r, __, __ = shue.f_shue(theta, run='EGI') # EGI~EGE, for runs not in shue.py B_z, n_p, and v_sw need to be specified and run=None + + # 2d one-sided magnetopause + xs = r*np.cos(theta) + ys = r*np.sin(theta) + + # 3d projection for complete magnetopause + psis = np.linspace(0, 2*np.pi, 100) + coords = np.zeros((len(theta)*len(psis), 3)) + i=0 + for x,y in zip(xs,ys): + for psi in psis: + coords[i] = np.array([x, y*np.sin(psi), y*np.cos(psi)]) + i += 1 + + coords = coords*R_E + + # surface and SDF + np.random.shuffle(coords) # helps Delaunay triangulation + shue_SDF = vtkDelaunay3d_SDF(query_points, coords) + + return shue_SDF + + else: + print("Magnetopause method not recognized. Use one of the options: \"beta_star\", \"beta_star_with_connectivity\", \"streamlines\", \"shue\", \"dict\"") + +def RegionFlags(datafile, outfilen, vtpoufile, ignore_boundaries=True, + magnetopause_method="beta_star", magnetopause_dict=None): + + """Creates a sidecar .vlsv file with flagged cells for regions and boundaries in near-Earth plasma environment. + Region flags (start with flag_, flags are fractions of filled conditions or 1/0): magnetosheath, magnetosphere, cusps, lobe_N, lobe_S, central_plasma_sheet, PSBL + Boundary signed distance flags (start with SDF_, flags are signed distances to boundary in m with inside being negative distance): magnetopause, bowshock + + :param datafile: vlsv file name (and path) + :param outdir: sidecar file save directory path + :param outfilen: sidecar file name + :kword ignore_boundaries: True: do not take cells in the inner/outer boundaries of the simulation into account when looking for regions + :kword magnetopause_method: default "beta_star", + """ + + + f = pt.vlsvfile.VlsvReader(file_name=datafile) + cellids =f.read_variable("CellID") + [xmin, ymin, zmin, xmax, ymax, zmax] = f.get_spatial_mesh_extent() + all_points = f.get_cell_coordinates(cellids) #centre_points_of_cells(f) + cellIDdict = f.get_cellid_locations() + + + + ## writer ## + writer = pt.vlsvfile.VlsvWriter(f, outfilen) + writer.copy_variables_list(f, ["CellID"]) + writer.copy_variables(f, varlist=["proton/vg_rho" , "vg_beta_star", "vg_temperature", "vg_b_vol", "vg_J", "vg_beta", "vg_connection", "vg_boundarytype"]) + + + # variable data dictionary + variables = {} + + if f.check_variable("proton/vg_rho"): # vg-grid + + # dict {varible call name : variable vlsvreader name or varible call name : (variable vlsvreader name, operator)} + varnames = {"density": "proton/vg_rho", + "temperature": "vg_temperature", + "beta": "vg_beta", + "beta_star": "vg_beta_star", + "B_magnitude": ("vg_B_vol", "magnitude"), + "B_x": ("vg_B_vol", "x"), + "B_y": ("vg_B_vol", "y"), + "B_z": ("vg_B_vol", "z"), + "connection": "vg_connection", + "J_magnitude": ("vg_J", "magnitude")} + + boundaryname = "vg_boundarytype" + + + def errormsg(varstr): print("{} could not be read, will be ignored".format(varstr)) + + for varname, filevarname in varnames.items(): + if isinstance(filevarname, str): # no operator + try: + variables[varname] = f.read_variable(name=filevarname, cellids=-1) + except: + errormsg(filevarname) + else: + try: + variables[varname] = f.read_variable(name=filevarname[0], cellids=-1, operator=filevarname[1]) + except: + errormsg(filevarname) + + ## cellid testing + #testmesh = np.where(cellids < 10000, 1, 0) + #write_flags(writer, testmesh, 'testmesh') + + + + # upstream point + upstream_point = [xmax-10*R_E,0.0,0.0] + upstream_cellid = f.get_cellid(upstream_point) + upstream_index = cellIDdict[upstream_cellid] + + ## MAGNETOPAUSE ## + magnetopause = magnetopause_SDF(f, datafile, vtpoufile, variables, all_points, method=magnetopause_method, own_variable_dict=magnetopause_dict) + write_flags(writer, magnetopause, 'SDF_magnetopause') + write_flags(writer, np.where(np.abs(magnetopause) < 5e6, 1, 0), "flag_magnetopause") + + # save some magnetopause values for later + magnetopause_density = np.mean(variables["density"][np.abs(magnetopause) < 5e6]) + print(f"{magnetopause_density=}") + magnetopause_temperature = np.mean(variables["temperature"][np.abs(magnetopause) < 5e6]) + print(f"{magnetopause_temperature=}") + + ## MAGNETOSPHERE ## + # magnetosphere from magentopause SDF + magnetosphere = np.where(magnetopause<0, 1, 0) + write_flags(writer, magnetosphere, 'flag_magnetosphere_convex') + + # magnetospshere from beta* and field line connectivity if possible # TODO + try: + connectivity_region = treshold_mask(variables["connection"], 0) + betastar_region = treshold_mask(variables["beta_star"], [0.01, 0.5]) + magnetosphere_proper = np.where((connectivity_region==1) | (betastar_region==1), 1, 0) + write_flags(writer, magnetosphere_proper, 'flag_magnetosphere') + except: + print("Non-convex magnetosphere could not be made") + + ## BOW SHOCK ## + # bow shock from rho + bowshock = bowshock_SDF(f, variables, all_points) + write_flags(writer, bowshock, 'SDF_bowshock') + write_flags(writer, np.where(np.abs(bowshock) < 5e6, 1, 0), "flag_bowshock") + + # magnetosphere+magnetosheath -area + inside_bowshock = np.where(bowshock<0, 1, 0) + + ## MAGNETOSHEATH ## + # magnetosheath from bow shock-magnetosphere difference + magnetosheath_flags = np.where((inside_bowshock & 1-magnetosphere), 1, 0) + write_flags(writer, magnetosheath_flags, 'flag_magnetosheath') + + # save magentosheath density and temperature for further use + magnetosheath_density = np.mean(variables["density"][magnetosheath_flags == 1]) + print(f"{magnetosheath_density=}") + magnetosheath_temperature = np.mean(variables["temperature"][magnetosheath_flags == 1]) + print(f"{magnetosheath_temperature=}") + + ## UPSTREAM ## + # upstream from !bowshock + write_flags(writer, 1-inside_bowshock, 'flag_upstream') + + write_flags(writer, inside_bowshock, 'flag_inside_bowshock') + + + + ## INNER MAGNETOSPHERE REGIONS ## + + if ignore_boundaries: + noBoundaries = np.where(f.read_variable(name=boundaryname, cellids=-1) == 1, 1, 0) # boundarytype 1: not a boundary + mask_inBowshock= np.where(((inside_bowshock == 1) & (noBoundaries == 1)), 1, 0).astype(bool) # only search inner regions from inside the magnetosheath and magnetosphere + mask_inMagnetosphere = np.where(((magnetosphere == 1) & (noBoundaries == 1)), 1, 0).astype(bool) # + + else: + mask_inBowshock = inside_bowshock.astype(bool) # only search inner regions from inside the magnetosheath and magnetosphere + mask_inMagnetosphere = magnetosphere.astype(bool) # + + #print("upstream B:", variables["B_magnitude"][upstream_index]) + + # cusps + try: + cusp_conditions = {"density": [variables["density"][upstream_index], None], + #"beta_star": [0.1, None], + "connection": [0.0, 2.5], # either closed, or open-closed/closed-open + "B_magnitude":[2*variables["B_magnitude"][upstream_index], None], + "J_magnitude": [variables["J_magnitude"][upstream_index], None] + } + except: + cusp_conditions = {"density": [variables["density"][upstream_index], None], + #"beta_star": [0.1, None], + "connection": [0.0, 2.5], # either closed, or open-closed/closed-open + "B_magnitude":[2*variables["B_magnitude"][upstream_index], None] + } + + cusp_flags = make_region_flags(variables, cusp_conditions, flag_type="fraction", mask=mask_inMagnetosphere) + write_flags(writer, cusp_flags, 'flag_cusps', mask_inMagnetosphere) + + + # magnetotail lobes + lobes_conditions = {"beta": [None, 0.1], # Koskinen: 0.003 + "connection": [0.5, 2.5], + "density": [None, variables["density"][upstream_index]], # Koskinen: 1e-8 + "temperature": [None, 3.5e6], # Koskinen: 3.5e6 K + } + lobes_flags = make_region_flags(variables, lobes_conditions, flag_type="fraction", mask=mask_inBowshock) + write_flags(writer, lobes_flags, 'flag_lobes', mask_inBowshock) + + # lobes slightly other way + lobe_N_conditions = {"beta": [None, 0.1], + "connection": [0.5, 2.5], + "B_x":[0, None], + "B_magnitude":[None, 10*variables["B_magnitude"][upstream_index]] + } + lobe_N_flags = make_region_flags(variables, lobe_N_conditions, flag_type="fraction", mask=mask_inBowshock) + write_flags(writer, lobe_N_flags, 'flag_lobe_N', mask_inBowshock) + + lobe_S_conditions = {"beta": [None, 0.1], + "connection": [0.5, 2.5], + "B_x":[None, 0], + "B_magnitude":[None, 10*variables["B_magnitude"][upstream_index]] + } + lobe_S_flags = make_region_flags(variables, lobe_S_conditions, flag_type="fraction", mask=mask_inBowshock) + write_flags(writer, lobe_S_flags, 'flag_lobe_S', mask_inBowshock) + + # lobe density from median densities? + lobes_mask = np.where((lobes_flags > 0.9), 1, 0).astype(bool) + lobes_allcells = mask_inBowshock.astype(float) + lobes_allcells[mask_inBowshock] = lobes_mask + lobe_density = np.mean(variables["density"][lobes_allcells>0.9]) + print(f"{lobe_density=}") + + # Central plasma sheet + central_plasma_sheet_conditions = {#"density": [None, variables["density"][upstream_index]], + "density": [None, 1e6], # Wolf intro + #"density" : [1e7, None], # Koskinen: 3e-7 + #"density": [lobe_density, None], + "beta": [1.0, None], # Koskinen: 6 + #"connection": 0, # only closed-closed + #"temperature": [2*variables["temperature"][upstream_index], None]#, + "temperature": [2e6, None], # 5e7 from Koskinen + #"B_magnitude":[10*variables["B_magnitude"][upstream_index], None] + #"J_magnitude": [2*variables["J_magnitude"][upstream_index], None], + "J_magnitude": [1e-9, None], + } + + central_plasma_sheet_flags = make_region_flags(variables, central_plasma_sheet_conditions,flag_type="fraction", mask=mask_inMagnetosphere) + write_flags(writer, central_plasma_sheet_flags, 'flag_central_plasma_sheet', mask_inMagnetosphere) + + + # Plasma sheet boundary layer (PSBL) + #PSBL_conditions = {#"density": (1e7, 1.0), # Koskinen: 1e5 + # "density": [None, 1e7], + #"temperature": [0.5e6, 1e6],#(1e7, 2.0), # from Koskinen + #"temperature": [2e6, None], + # "beta": [0.1, 1.0], + #"B_magnitude":[10*variables["B_magnitude"][upstream_index], None] + # "J_magnitude": [2*variables["J_magnitude"][upstream_index], None], + # } + + + #PSBL_flags = make_region_flags(variables, PSBL_conditions,flag_type="fraction", mask=mask_inMagnetosphere) + #write_flags(writer, PSBL_flags, "flag_PSBL", mask_inMagnetosphere) + + + # Low-Latitude boundary layer (LLBL) + #LLBL_conditions = {"density": [magnetopause_density, magnetosheath_density], + # "temperature": [magnetosheath_temperature, magnetopause_temperature] + # } + #LLBL_flags = make_region_flags(variables, LLBL_conditions,flag_type="fraction", mask=mask_inBowshock) + #write_flags(writer, LLBL_flags, "flag_LLBL", mask_inBowshock) + + + + + + # High-Latitude boundary layer (HLBL) + HLBL_conditions = { "density": [magnetopause_density, magnetosheath_density], + "temperature": [magnetosheath_temperature, magnetopause_temperature], + "beta": [1.0, None], + "connection": 0, + "J_magnitude": [2*variables["J_magnitude"][upstream_index], None], + } + + HLBL_flags = make_region_flags(variables, HLBL_conditions,flag_type="fraction", mask=mask_inBowshock) + write_flags(writer, HLBL_flags, "flag_HLBL", mask_inBowshock) + + + +def main(): + + datafile = "/wrk-vakka/group/spacephysics/vlasiator/3D/FHA/bulk1/bulk1.0001400.vlsv" + outfilen = "FHA_regions_t0001400.vlsv" + vtp_outfilen = "FHA_regions_t0001400.vtp" + + + for infile, outfile, vtp_outfile in zip(datafile, outfilen, vtp_outfilen): + RegionFlags(infile, outfile, vtp_outfile, magnetopause_method="streamlines") + + +if __name__ == "__main__": + + main() From abcdbd4b9e24a8e6fb40cf368200d223c86b64f3 Mon Sep 17 00:00:00 2001 From: jreimi Date: Thu, 21 Aug 2025 13:04:28 +0300 Subject: [PATCH 036/124] fix to dayside slicing --- analysator/pyCalculations/magnetopause_sw_streamline_3d.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/analysator/pyCalculations/magnetopause_sw_streamline_3d.py b/analysator/pyCalculations/magnetopause_sw_streamline_3d.py index d3acc149..27d44df4 100644 --- a/analysator/pyCalculations/magnetopause_sw_streamline_3d.py +++ b/analysator/pyCalculations/magnetopause_sw_streamline_3d.py @@ -268,6 +268,9 @@ def make_magnetopause(streams, end_x=-15*6371000, x_point_n=50, sector_n=36): else: subsolar_x = np.partition(x_axis_points[:,0], ignore)[ignore] # take the nth point as subsolar point + # divide the x point numbers between x > 0 (radial) an x < 0 (yz-planes) by ratio + dayside_x_point_n = int((subsolar_x/np.abs(end_x))*x_point_n) + ### dayside magnetopause ### # for x > 0, look for magnetopause radially dayside_points = streampoints[streampoints[:,0] > 0] @@ -319,7 +322,7 @@ def grid_mid_point(theta_idx, phi_idx): ### x < 0 magnetopause ### # rest: look for magnetopause in yz-planes ## define points in the x axis where to find magnetopause points on the yz-plane - x_points = np.linspace(subsolar_x, end_x, x_point_n) + x_points = np.linspace(0.0, end_x, x_point_n-dayside_x_point_n) ## interpolate more exact points for streamlines at exery x_point new_streampoints = np.zeros((len(x_points), len(streams), 2)) # new array for keeping interpolated streamlines in form new_streampoints[x_point, streamline, y and z -coordinates] From 3252d5a712bf713626835bc53bb515e9f410872b Mon Sep 17 00:00:00 2001 From: jreimi Date: Fri, 22 Aug 2025 16:11:16 +0300 Subject: [PATCH 037/124] Field values are passed to stopping condition function instead of unit vectors --- analysator/pyCalculations/fieldtracer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/analysator/pyCalculations/fieldtracer.py b/analysator/pyCalculations/fieldtracer.py index f383c760..1250a387 100644 --- a/analysator/pyCalculations/fieldtracer.py +++ b/analysator/pyCalculations/fieldtracer.py @@ -294,7 +294,7 @@ def find_unit_vector(vg, coord): points_traced_unique[mask_update,i,:] = next_points[mask_update,:] # distances = np.linalg.norm(points_traced_unique[:,i,:],axis = 1) - mask_update[stop_condition(vlsvReader, points_traced_unique[:,i,:], var_unit)] = False + mask_update[stop_condition(vlsvReader, points_traced_unique[:,i,:], vlsvReader.read_interpolated_variable(vg,points_traced_unique[:,i,:]))] = False # points_traced_unique[~mask_update, i, :] = points_traced_unique[~mask_update, i-1, :] @@ -302,7 +302,7 @@ def find_unit_vector(vg, coord): return points_traced # Default stop tracing condition for the vg tracing, (No stop until max_iteration) -def default_stopping_condition(vlsvReader, points, last_unit_v): +def default_stopping_condition(vlsvReader, points, vars): [xmin, ymin, zmin, xmax, ymax, zmax] = vlsvReader.get_spatial_mesh_extent() x = points[:, 0] y = points[:, 1] From 7eeb1bd5b5d431143c686f532f4ad77be6ae6d4b Mon Sep 17 00:00:00 2001 From: jreimi Date: Mon, 25 Aug 2025 14:26:26 +0300 Subject: [PATCH 038/124] Magnetopause script additional notes --- Documentation/sphinx/magnetopause.rst | 74 +++++++++++++++++++-------- 1 file changed, 52 insertions(+), 22 deletions(-) diff --git a/Documentation/sphinx/magnetopause.rst b/Documentation/sphinx/magnetopause.rst index b5b68520..7bf6f54b 100644 --- a/Documentation/sphinx/magnetopause.rst +++ b/Documentation/sphinx/magnetopause.rst @@ -34,17 +34,19 @@ Ideal values of beta* for magnetopause construction might be run-dependent, and Field line connectivity ----------------------- +Magnetic field lines that are closed at least from one end ... + Solar wind flow --------------- -Method used in e.g. [Parlmroth_et_al_2003_] +Method used in e.g. [Palmroth_et_al_2003]_ Streamlines of velocity field that are traced from outside the bow shock curve around the magnetopause. -Caveats: sometimes some streamlines can curve into the magnetotail +Caveats: sometimes some streamlines can curve into the magnetotail or dayside magnetoshpere **In analysator:** @@ -54,35 +56,25 @@ magnetopause_sw_streamline_3d.py Streamlines are traced from outside the bow shock towards Earth. A subsolar point for the magnetopause is chosen to be where streamlines get closest to Earth in x-axis [y/z~0]. -From subsolar point towards Earth, .... - +From subsolar point towards Earth, space is divided radially by spherical coordiante (theta from x-axis) angles theta and phi, and magnetopause is located by looking at streamline point distances from origo and marked to be at middle of the sector From the Earth towards negative x the space is divided into yz-planes. -Each yz-plane is then divided into sectors and magnetopause is marked to be in the middle of the sector with the radius of the closest streamline to the x-axis. - -For subsolar point, radial dayside and -x planes the closest streamline point can be changed to be n:th closest by setting keyword "ignore", in which case n-1 closest streamline points are not taken into acocunt. +Each yz-plane is then divided into 2d sectors and magnetopause is marked to be in the middle of the sector with the radius of the n:th closest streamline to the x-axis. -2d: -streamlines: [...] -magnetopause: [...] +For subsolar point, radial dayside and -x planes the closest streamline point can be changed to be n:th closest by setting keyword *ignore*, in which case *ignore* closest streamline points are not taken into acocunt. -example usage in [...] +2d: +Note: no radial dayside 3d: -streamlines: [...] -magnetopause: [...] -surface triangulation: [...] - -example usage in [...] +After the magnetopause points are chosen, they are made into a surface by setting connnection lines between vertices (magnetopause points) to make surface triangles. +This surface is then made into a vtkPolyData and returned as vtkDataSetSurfaceFilter that can be written into e.g. .vtp file. - - - -.. [Parlmroth_et_al_2003] Palmroth, M., T. I. Pulkkinen, P. Janhunen, and C.-C. Wu (2003), Stormtime energy transfer in global MHD simulation, J. Geophys. Res., 108, 1048, doi:10.1029/2002JA009446, A1. +.. [Palmroth_et_al_2003] Palmroth, M., T. I. Pulkkinen, P. Janhunen, and C.-C. Wu (2003), Stormtime energy transfer in global MHD simulation, J. Geophys. Res., 108, 1048, doi:10.1029/2002JA009446, A1. @@ -113,10 +105,48 @@ The magnetopause radius as a function of angle :math:`\theta` is **In analysator:** -*shue.py* in *scripts*: +*shue.py* in *scripts* + + +.. [Shue_et_al_1997] Shue, J.-H., Chao, J. K., Fu, H. C., Russell, C. T., Song, P., Khurana, K. K., and Singer, H. J. (1997). A new functional form to study the solar wind control of the magnetopause size and shape. Journal of Geophysical Research: Space Physics, 102(A5):9497–9511. eprint: https://agupubs.onlinelibrary.wiley.com/doi/pdf/10.1029/97JA00196. + + + + + +**In analysator:** +------------------ + +*magnetopause.py* in scripts for 3d runs +Constructs the magentopause surface with vtk's vtkDelaunay3d triangulation with optional alpha to make the surface non-convex. +Uses regions.py functions. + +Important: SDF of non-convex surface will (most likely) not work, use convex hull (alpha=None) for SDFs! + +options (magnetopause() method keyword) and some notes: +* solar wind flow ("streamlines") + * uses *magnetopause_sw_streamline_3d.py* from pyCalculations + * if streamlines make a turn so that the velocity points sunwards (to pos. x), the streamline is ignored from that point onwards + * this fixes some issues with funky and inwards-turning streamlines but not all + * very dependent on how solar wind streamlines behave + * streamline seeds and other options can greatly affect the resulting magnetopause +* beta* ("beta_star") + * beta* treshold might need tweaking as sometimes there are small low beta* areas in the magnetosheath that get taken in distorting the magnetopause shape at nose + * convex hull (Delaunay_alpha=None) usually makes a nice rough magnetopause but goes over any inward dips (like polar cusps) + * alpha shape (Delaunay_alpha= e.g. 1*R_E) does a better job at cusps and delicate shapes like vortices but can fail at magnetotail due to central plasma sheet and won't produce a correct SDF + * Delaynay3d has an easier time if the treshold is something like [0.4, 0.5] and not [0.1, 0.5] +* beta* with magnetic field line connectivity ("beta_star_with_connectivity") + * includes closed-closed magnetic field line areas if available, otherwise like "beta_star" + * can help with nose shape as beta* can be set lower to exclude magnetosheath low beta* areas while still getting the full dayside from field lines +* Shue et al. 1997 ("shue") + * uses *shue.py* from scripts + * a rough theoretical magnetopause using Shue et al. 1997 method based on B_z, solar wind density, and solar wind velocity -.. [Shue_et_al_1997] Shue, J.-H., Chao, J. K., Fu, H. C., Russell, C. T., Song, P., Khurana, K. K., and Singer, H. J. (1997). A new functional form to study the solar wind control of the magnetopause size and shape. Journal of Geophysical Research: Space Physics, 102(A5):9497–9511. eprint: https://agupubs.onlinelibrary.wiley.com/doi/pdf/10.1029/97JA00196. \ No newline at end of file +* user-defined parameter tresholds ("dict") + * creates a magnetopause (or other area) using the Delaunay3d triangulation of some area where user-defined tresholds given as dictionary + * dictionary key is data name in datafile and value is treshold used, if dictionary has multiple conditions, they all need to be fulfilled + * dictionary example: {"vg_rho": [None, 1e5]} makes a magnetopause using cells where density is less than 1e5 \ No newline at end of file From 3effe84038fe73e30ca4823797eb432f6e536de4 Mon Sep 17 00:00:00 2001 From: jreimi Date: Mon, 25 Aug 2025 14:35:33 +0300 Subject: [PATCH 039/124] attempted citation fixes --- .../sphinx/magnetosphere_regions.rst | 65 ++++++++++--------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/Documentation/sphinx/magnetosphere_regions.rst b/Documentation/sphinx/magnetosphere_regions.rst index 81f1ec60..1693d545 100644 --- a/Documentation/sphinx/magnetosphere_regions.rst +++ b/Documentation/sphinx/magnetosphere_regions.rst @@ -3,7 +3,6 @@ Magnetosphere regions: How to find - Cusps ----- @@ -11,9 +10,9 @@ Cusps *Properties:* * plasma density: high density in comparison to solar wind - * Ion density :math:`\geq` solar wind ion density [Pitout_et_al_2006_] (Cluster spacecraft data) + * Ion density :math:`\geq` solar wind ion density [Pitout_et_al_2006]_ (Cluster spacecraft data) * ion energy: - * mean ion energy :math:`~2-3 keV` [Pitout_et_al_2006_] /[Stenuit_et_al_2001_] + * mean ion energy :math:`~2-3 keV` [Pitout_et_al_2006]_ /[Stenuit_et_al_2001]_ * energy flux: * energy flux @@ -26,19 +25,19 @@ Tail lobes ---------- * plasma density: low - * below :math:`0.03 cm^{-3}` [Grison_et_al_2025_] (Cluster spacecraft data) - * :math:`0.01 cm^{-3}` [Koskinen_Space_Storms_ p.38] - * less than :math:`0.1 cm^{-3}` [Wolf_Introduction_to_space_physics_Ch_10_ p.291] + * below :math:`0.03 cm^{-3}` [Grison_et_al_2025]_ (Cluster spacecraft data) + * :math:`0.01 cm^{-3}` [Koskinen_Space_Storms]_ p.38 + * less than :math:`0.1 cm^{-3}` [Wolf_Introduction_to_space_physics_Ch_10]_ p.291 * plasma :math:`\beta`: low - * typically around :math:`0.05` [Grison_et_al_2025_] (Cluster spacecraft data) - * :math:`3e-3` [Koskinen_Space_Storms_ p.38] + * typically around :math:`0.05` [Grison_et_al_2025]_ (Cluster spacecraft data) + * :math:`3e-3` [Koskinen_Space_Storms]_ p.38 * temperature: - * ion temperature :math:`300 eV` [Koskinen_Space_Storms_ p.38] - * electron temperature :math:`50 eV` [Koskinen_Space_Storms_ p.38] + * ion temperature :math:`300 eV` [Koskinen_Space_Storms]_ p.38 + * electron temperature :math:`50 eV` [Koskinen_Space_Storms]_ p.38 * magnetic field: - * :math:`20 nT` [Koskinen_Space_Storms_ p.38] -* open magnetic field lines [Wolf_Introduction_to_space_physics_Ch_10_ p.291] -* strong and stable magnetic field towards the Earth (northern lobe) and away from the Earth (southern lobe) [Coxon_et_al_2016_] + * :math:`20 nT` [Koskinen_Space_Storms]_ p.38 +* open magnetic field lines [Wolf_Introduction_to_space_physics_Ch_10]_ p.291 +* strong and stable magnetic field towards the Earth (northern lobe) and away from the Earth (southern lobe) [Coxon_et_al_2016]_ Separated from the plasma sheet by the plasma sheet boundary layer (PSBL) @@ -63,10 +62,10 @@ Low-latitude boundary layer (LLBL) Properties: * density: - * ion number densities between those of magnetosphere and magnetosheath [Hudges_Introduction_to_space_physics_Ch_9_ p.267] + * ion number densities between those of magnetosphere and magnetosheath [Hudges_Introduction_to_space_physics_Ch_9]_ p.267 * temperature - * ion temperatures between those of magnetosphere and magnetosheath [Hudges_Introduction_to_space_physics_Ch_9_ p.267] -* unknown field line configuration, probably a mix of open and closed field lines [Hudges_Introduction_to_space_physics_Ch_9_ p.262] + * ion temperatures between those of magnetosphere and magnetosheath [Hudges_Introduction_to_space_physics_Ch_9]_ p.267 +* unknown field line configuration, probably a mix of open and closed field lines [Hudges_Introduction_to_space_physics_Ch_9]_ p.262 @@ -77,7 +76,7 @@ Includes the plasma mantle on the tail side and the entry layer on the dayside [ Properties: -* open magnetic field lines [Hudges_Introduction_to_space_physics_Ch_9_ p.261] +* open magnetic field lines [Hudges_Introduction_to_space_physics_Ch_9]_ p.261 @@ -86,20 +85,20 @@ Properties: Plasma sheet boundary layer (PSBL) ---------------------------------- -The plasma sheet boundary layer is a very thin boundary layer separating the tail lobes from the tail plasma sheet [Koskinen_Johdatus_] +The plasma sheet boundary layer is a very thin boundary layer separating the tail lobes from the tail plasma sheet [Koskinen_Johdatus]_ *Properties:* * density: - * :math:`0.1 cm^{-3}` [Koskinen_Space_Storms_ p.38] + * :math:`0.1 cm^{-3}` [Koskinen_Space_Storms]_ p.38 * temperature: - * ion temperature :math:`1000 eV` [Koskinen_Space_Storms_ p.38] - * electron temperature :math:`150 eV` [Koskinen_Space_Storms_ p.38] + * ion temperature :math:`1000 eV` [Koskinen_Space_Storms]_ p.38 + * electron temperature :math:`150 eV` [Koskinen_Space_Storms]_ p.38 * magnetic field: - * :math:`20 nT` [Koskinen_Space_Storms_ p.38] + * :math:`20 nT` [Koskinen_Space_Storms]_ p.38 * plasma :math:`\beta` : - * :math:`0.1` [Koskinen_Space_Storms_ p.38] -* probably closed magnetic field lines [Wolf_Introduction_to_space_physics_Ch_10_ p.291] + * :math:`0.1` [Koskinen_Space_Storms]_ p.38 +* probably closed magnetic field lines [Wolf_Introduction_to_space_physics_Ch_10]_ p.291 @@ -111,17 +110,18 @@ Central plasma sheet *Properties:* * density: - * :math:`0.3 cm^{-3}` [Koskinen_Space_Storms_ p.38] - * :math:`0.1-1 cm^{-3}` [Wolf_Introduction_to_space_physics_Ch_10_ p.291] + * :math:`0.3 cm^{-3}` [Koskinen_Space_Storms]_ p.38 + * :math:`0.1-1 cm^{-3}` [Wolf_Introduction_to_space_physics_Ch_10]_ p.291 * temperature: hot - * ion temperature :math:`4200 eV` [Koskinen_Space_Storms_ p.38] - * electron temperature :math:`600 eV` [Koskinen_Space_Storms_ p.38] + * ion temperature :math:`4200 eV` [Koskinen_Space_Storms]_ p.38 + * electron temperature :math:`600 eV` [Koskinen_Space_Storms]_ p.38 * magnetic field: - * :math:`10 nT` [Koskinen_Space_Storms_ p.38], [Hudges_Introduction_to_space_physics_Ch_9_] + * :math:`10 nT` [Koskinen_Space_Storms]_ p.38, [Hudges_Introduction_to_space_physics_Ch_9]_ * plasma :math:`\beta`: high - * :math:`6` [Koskinen_Space_Storms_ p.38] -* Mostly closed magnetic field lines [Wolf_Introduction_to_space_physics_Ch_10_] + * :math:`6` [Koskinen_Space_Storms]_ p.38 +* Mostly closed magnetic field lines [Wolf_Introduction_to_space_physics_Ch_10]_ +Inner plasma sheet: unusually low plasma beta may exist (e.g., cold tenuous plasma near the neutral sheet after long periods of northward IMF) [Boakes_et_al_2014]_, (Cluster spacecraft data) @@ -137,4 +137,5 @@ References .. [Hudges_Introduction_to_space_physics_Ch_9] Hudges, W. J. (1995) The magnetopause, magnetotail and magnetic reconnection. In Kivelson, M. G., & Russell, C. T. (Eds.), Introduction to space physics (pp.227-287). Cambridge University Press. .. [Wolf_Introduction_to_space_physics_Ch_10] Wolf, R. A. (1995) Magnetospheric configuration. In Kivelson, M. G., & Russell, C. T. (Eds.), Introduction to space physics (pp.288-329). Cambridge University Press. .. [Sckopke_et_al_1981] Sckopke, N., Paschmann, G., Haerendel, G., Sonnerup, B. U. , Bame, S. J., Forbes, T. G., Hones Jr., E. W., and Russell, C. T. (1981). Structure of the low-latitude boundary layer. Journal of Geophysical Research: Space Physics, 86(A4):2099–2110. eprint: https://agupubs.onlinelibrary.wiley.com/doi/pdf/10.1029/JA086iA04p02099 -.. [Boakes_et_al_2014] Boakes, P. D., Nakamura, R., Volwerk, M., and Milan, S. E. (2014). ECLAT Cluster Spacecraft Magnetotail Plasma Region Identifications (2001–2009). Dataset Papers in Science, 2014(1):684305. eprint: https://onlinelibrary.wiley.com/doi/pdf/10.1155/2014/684305 \ No newline at end of file +.. [Boakes_et_al_2014] Boakes, P. D., Nakamura, R., Volwerk, M., and Milan, S. E. (2014). ECLAT Cluster Spacecraft Magnetotail Plasma Region Identifications (2001–2009). Dataset Papers in Science, 2014(1):684305. eprint: https://onlinelibrary.wiley.com/doi/pdf/10.1155/2014/684305 +.. [Stenuit_et_al_2001] Stenuit, H., Sauvaud, J.-A., Delcourt, D. C., Mukai, T., Kokubun, S., Fujimoto, M., Buzulukova, N. Y., Kovrazhkin, R. A., Lin, R. P., and Lepping, R. P. (2001). A study of ion injections at the dawn and dusk polar edges of the auroral oval. Journal of Geophysical Research: Space Physics, 106(A12):29619–29631. eprint: https://agupubs.onlinelibrary.wiley.com/doi/pdf/10.1029/2001JA900060. From 88099fb878f51281030f23e221a5adaf5512f754 Mon Sep 17 00:00:00 2001 From: jreimi Date: Tue, 26 Aug 2025 11:22:52 +0300 Subject: [PATCH 040/124] Magnetopause search file --- scripts/magnetopause.py | 175 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 scripts/magnetopause.py diff --git a/scripts/magnetopause.py b/scripts/magnetopause.py new file mode 100644 index 00000000..2af4b7ae --- /dev/null +++ b/scripts/magnetopause.py @@ -0,0 +1,175 @@ +"""Functions for finding the magnetopause from .vlsv data +""" + +import analysator as pt +import numpy as np +import vtk +import time +from scripts import shue, regions + + +R_E = 6371000 + + +def write_vtk_surface_to_file(vtkSurface, outfilen): + writer = vtk.vtkXMLPolyDataWriter() + writer.SetInputConnection(vtkSurface.GetOutputPort()) + writer.SetFileName(outfilen) + writer.Write() + print("wrote ", outfilen) + +def write_SDF_to_file(SDF, datafilen, outfilen): + f = pt.vlsvfile.VlsvReader(file_name=datafilen) + writer = pt.vlsvfile.VlsvWriter(f, outfilen) + writer.copy_variables_list(f, ["CellID"]) + writer.write_variable_info(pt.calculations.VariableInfo(SDF, "SDF_magnetopause", "-", latex="",latexunits=""),"SpatialGrid",1) + print("wrote ", outfilen) + + + + +def magnetopause(datafilen, method="beta_star_with_connectivity", own_tresholds=None, return_surface=True, return_SDF=True, SDF_points=None, Delaunay_alpha=None, beta_star_range=[0.1, 1.0]): # TODO: separate streamline suface and vtkDelaunay3d surface in streamline method + """Finds the magnetopause using the specified method. Surface is constructed using vtk's Delaunay3d triangulation which results in a convex hull if no Delaunay_alpha is given. + + :param datafilen: a .vlsv bulk file name (and path) + :kword method: str, default "beta_star_with_connectivity", other options "beta_star", "streamlines", "shue", "dict" + :kword own_tresholds: if using method "dict", a dictionary with conditions for the magnetopause/magnetosphere must be given where key is data name in datafile and value is treshold used (see treshold_mask()) + :kword return_surface: True/False, return vtkDataSetSurfaceFilter object + :kword return_SDF: True/False, return array of distances in m to SDF_points in point input order, negative distance inside the surface + :kword SDF_points: optionally give array of own points to calculate signed distances to. If not given, distances will be to cell centres in the order of f.read_variable("CellID") output + :kword Delaunay_alpha: alpha (float) to give to vtkDelaunay3d, None -> convex hull, alpha=__: surface egdes longer than __ will be excluded (-> concave hull) + :kword beta_star_range: [min, max] treshold rage to use with methods "beta_star" and "beta_star_with_connectivity" + :returns: vtkDataSetSurfaceFilter object of convex hull or alpha shape if return_surface=True, signed distance field of convex hull or alpha shape of magnetopause if return_SDF=True + """ + + + start_t = time.time() + f = pt.vlsvfile.VlsvReader(file_name=datafilen) + cellids = f.read_variable("CellID") + [xmin, ymin, zmin, xmax, ymax, zmax] = f.get_spatial_mesh_extent() + query_points = f.get_cell_coordinates(cellids) # for SDF, all centre points of cells + + if method == "streamlines": + + [xmin, ymin, zmin, xmax, ymax, zmax] = f.get_spatial_mesh_extent() + seeds_x0=150e6 + dl=5e5 + iters = int(((seeds_x0-xmin)/dl)+100) + sector_n = 36*4 + vertices, manual_vtkSurface = pt.calculations.find_magnetopause_sw_streamline_3d(datafilen, seeds_n=200, seeds_x0=seeds_x0, seeds_range=[-5*6371000, 5*6371000], + dl=dl, iterations=iters, end_x=xmin+10*6371000, x_point_n=200, sector_n=sector_n) + # good parameters example: seeds_x0=150e6, dl=5e5, iters = int(((seeds_x0-xmin)/dl)+100), sector_n = 36*3, seeds_n=100, seeds_range=[-5*6371000, 5*6371000], end_x=xmin+10*6371000, x_point_n=200 + + write_vtk_surface_to_file(manual_vtkSurface, "/wrk-vakka/users/jreimi/magnetosphere_classification/FID/FID_magnetopause_SW_manual_t1100.vtp") + # make the magnetopause surface from vertice points + np.random.shuffle(vertices) # helps Delaunay triangulation + vtkSurface, SDF = regions.vtkDelaunay3d_SDF(query_points, vertices, Delaunay_alpha) + + elif method == "beta_star": + # magnetopause from beta_star only + mpause_flags = regions.treshold_mask(f.read_variable("vg_beta_star"), beta_star_range) + contour_coords = f.get_cell_coordinates(cellids[mpause_flags!=0]) + + # make a convex hull surface with vtk's Delaunay + np.random.shuffle(contour_coords) + vtkSurface, SDF = regions.vtkDelaunay3d_SDF(query_points, contour_coords, Delaunay_alpha) + + + + elif method == "beta_star_with_connectivity": + # magnetopause from beta_star, with connectivity if possible + betastar_region = regions.treshold_mask(f.read_variable("vg_beta_star"), beta_star_range) + try: + connectivity_region = regions.treshold_mask(f.read_variable("vg_connection"), 0) # closed-closed magnetic field lines + magnetosphere_proper = np.where((connectivity_region==1) | (betastar_region==1), 1, 0) + contour_coords = f.get_cell_coordinates(cellids[magnetosphere_proper==1]) + except: + print("using field line connectivity for magnetosphere did not work, using only beta*") + #condition_dict = {"beta_star": [0.5, 0.6]} # FIC: [0.4, 0.5]) # EGE: [0.9, 1.0]) # max 0.6 in FHA to not take flyaways from outside magnetopause + mpause_flags = np.where(betastar_region==1, 1, 0) + contour_coords = f.get_cell_coordinates(cellids[mpause_flags!=0]) + + # make a convex hull surface with vtk's Delaunay + vtkSurface, SDF = regions.vtkDelaunay3d_SDF(query_points, contour_coords, Delaunay_alpha) + + + elif method == "dict": + variable_dict = {} + for key in own_tresholds: + variable_dict[key] = f.read_variable(key, cellids=-1) + + # same method as beta* but from user-defined variables as initial contour + flags = regions.make_region_flags(variable_dict, own_tresholds, flag_type="01") + treshold_coords = f.get_cell_coordinates(cellids[flags!=0]) + vtkSurface, SDF = regions.vtkDelaunay3d_SDF(query_points, treshold_coords, Delaunay_alpha) + + + elif method == "shue": + # note: might not be correct but should produce something, should recheck the projection coordinates + + theta = np.linspace(0, 2*np.pi/3 , 200) # magnetotail length decided here by trial and error: [0, 5*np.pi/6] ~ -350e6 m, [0, 2*np.pi/3] ~ -100e6 m in EGE + r, __, __ = shue.f_shue(theta, B_z = -10, n_p = 1, v_sw = 750) # for runs not in shue.py B_z, n_p, and v_sw need to be specified and run=None + + # 2d one-sided magnetopause + xs = r*np.cos(theta) + ys = r*np.sin(theta) + + # 3d projection for complete magnetopause + psis = np.linspace(0, 2*np.pi, 100) + coords = np.zeros((len(theta)*len(psis), 3)) + i=0 + for x,y in zip(xs,ys): + for psi in psis: + coords[i] = np.array([x, y*np.sin(psi), y*np.cos(psi)]) + i += 1 + + coords = coords*R_E + + # surface and SDF + np.random.shuffle(coords) # helps Delaunay triangulation + vtkSurface, SDF = regions.vtkDelaunay3d_SDF(query_points, coords) # alpha does nothing here + + + + else: + print("Magnetopause method not recognized. Use one of the options: \"beta_star\", \"beta_star_with_connectivity\", \"streamlines\", \"shue\", \"dict\"") + exit() + + if return_surface and return_SDF: + return vtkSurface, SDF + elif return_surface: + return vtkSurface, None + elif return_SDF: + return None, SDF + + # write the surface to a file + #writer = vtk.vtkXMLPolyDataWriter() + #writer.SetInputConnection(vtkSurface.GetOutputPort()) + #writer.SetFileName(outfilen) + #writer.Write() + #return vtkSurface, SDF + + + +def main(): + + + datafile = "/wrk-vakka/group/spacephysics/vlasiator/3D/FID/bulk1/bulk1.0001100.vlsv" + vtpoutfilen = "FID_magnetopause_BS_noalpha_t1100.vtp" + vlsvoutfilen = "FID_magnetopause_BS_noalpha_t1100.vlsv" + + + surface, SDF = magnetopause(datafile, + #method="streamlines", + method="beta_star_with_connectivity", + beta_star_range=[0.3, 0.4], + #Delaunay_alpha=2*R_E, + return_SDF=True, + return_surface=True) + + write_vtk_surface_to_file(surface, vtpoutfilen) + write_SDF_to_file(SDF, datafile, vlsvoutfilen) + +if __name__ == "__main__": + + main() From 0283802631101c865b6f8610145a52220ee3ee78 Mon Sep 17 00:00:00 2001 From: jreimi Date: Tue, 26 Aug 2025 11:26:22 +0300 Subject: [PATCH 041/124] regions format changes draft --- scripts/regions.py | 300 +++++++++++++++++---------------------------- 1 file changed, 111 insertions(+), 189 deletions(-) diff --git a/scripts/regions.py b/scripts/regions.py index e81eefb6..a911bb2c 100644 --- a/scripts/regions.py +++ b/scripts/regions.py @@ -10,6 +10,7 @@ import analysator as pt import numpy as np import vtk +from scripts import magnetopause #from .analysator.scripts import shue @@ -18,16 +19,18 @@ ### Signed distance field functions: ### -def vtkDelaunay3d_SDF(all_points, coordinates): - """Gives a signed distance to a convex hull surface created from given coordinates. +def vtkDelaunay3d_SDF(query_points, coordinates, alpha=None): + """Gives a signed distance to a convex hull or alpha shape surface created from given coordinates. + Note: if using alpha, SDF most likely won't work! :param all_points: points ([x, y, z] coordinates in m) for which a signed distance to surface will be calculated :param coordinates: coordinates (array of [x, y, z]:s in m) that are used to make a surface. - :returns: array of signed distances, negative sign: inside the surface, positive sign: outside the surface + :kword alpha: alpha to be given to vtkDelaunay3D (e.g. R_E), removes surface edges that have length more than alpha so that the resulting surface is not convex. None -> convex hull + :returns: vtkDataSetSurfaceFilter() object, array of signed distances (negative sign: inside the surface, positive sign: outside the surface) """ - points =vtk.vtkPoints()#.NewInstance() + points = vtk.vtkPoints()#.NewInstance() for i in range(len(coordinates)): points.InsertNextPoint(coordinates[i,0],coordinates[i,1],coordinates[i,2]) polydata = vtk.vtkPolyData() @@ -37,6 +40,8 @@ def vtkDelaunay3d_SDF(all_points, coordinates): # Delaunay convex hull delaunay_3d = vtk.vtkDelaunay3D() delaunay_3d.SetInputData(polydata) + if alpha is not None: + delaunay_3d.SetAlpha(alpha) delaunay_3d.Update() @@ -49,13 +54,13 @@ def vtkDelaunay3d_SDF(all_points, coordinates): implicitPolyDataDistance = vtk.vtkImplicitPolyDataDistance() implicitPolyDataDistance.SetInput(surface.GetOutput()) - convexhull_sdf = np.zeros(len(all_points)) + convexhull_sdf = np.zeros(len(query_points)) # SDFs - for i,coord in enumerate(all_points): + for i,coord in enumerate(query_points): convexhull_sdf[i] = implicitPolyDataDistance.EvaluateFunction(coord) - return convexhull_sdf + return surface, convexhull_sdf @@ -69,7 +74,7 @@ def treshold_mask(data_array, value): :returns: 0/1 mask in same order as cellids, 1: variable value in array inside treshold values, 0: outside """ - if data_array is None or value is None: # either variable isn't usable or treshold has not been given + if (data_array is None) or (value is None) or (np.isnan(data_array[0])): # either variable isn't usable or treshold has not been given return None mask = np.zeros((len(data_array))) @@ -222,132 +227,39 @@ def bowshock_SDF(f, variable_dict, query_points, own_condition_dict=None): bowshock_conditions = {"density": (1.5*upstream_rho, 0.1)} bowshock_rho_flags = make_region_flags(variable_dict, bowshock_conditions, flag_type="01") - bowshock_rho_SDF = vtkDelaunay3d_SDF(query_points, f.get_cell_coordinates(cellids[bowshock_rho_flags!=0])) + __, bowshock_rho_SDF = vtkDelaunay3d_SDF(query_points, f.get_cell_coordinates(cellids[bowshock_rho_flags!=0])) return bowshock_rho_SDF else: # bowshock from user-defined variable and values - bowshock_rho_flags = make_region_flags(variable_dict, own_condition_dict, flag_type="01") - bowshock_rho_SDF = vtkDelaunay3d_SDF(query_points, f.get_cell_coordinates(cellids[bowshock_rho_flags!=0])) - dict_sdf = vtkDelaunay3d_SDF(query_points, f.get_cell_coordinates(cellids[bowshock_rho_flags!=0])) + bowshock_dict_flags = make_region_flags(variable_dict, own_condition_dict, flag_type="01") + __, dict_sdf = vtkDelaunay3d_SDF(query_points, f.get_cell_coordinates(cellids[bowshock_dict_flags!=0])) return dict_sdf -def magnetopause_SDF(f, datafile, vtpoutfile, variable_dict, query_points, method="beta_star_with_connectivity", own_variable_dict=None): # TODO: remove vlsvReader AND datafile name need (streamline magnetopause to only use f?) - """Finds the magnetopause by making a convex hull of either streamlines, beta*, or user-defined variables, and returns signed distances from surface to query_points. - Note: constructing the magnetopause using solar wind streamlines is slow and needs more memory than e.g. the beta*-method - - :param query_points: xyz-coordinates of all points where the SDF will be calculated ([x1, y1, z1], [x2, y2, z2], ...) - :kword method: str, specifies the method used to find magnetopause, options: "beta_star", "streamlines", "shue", "dict". If "dict" is used, own_variable_dict dictionary must be given. If "shue", run needs to be specified (TODO kwarg) - :kword own_variable_dict: when using "dict"-method, dictionary with string variable names as keys and treshold pairs as values to pass to treshold_mask()-function - """ - - if method != "streamlines": cellids =f.read_variable("CellID") - - if method == "streamlines": - - [xmin, ymin, zmin, xmax, ymax, zmax] = f.get_spatial_mesh_extent() - seeds_x0=150e6 - dl=5e5 - iters = int(((seeds_x0-xmin)/dl)+100) - sector_n = 36*2 - vertices, vtkSurface = pt.calculations.find_magnetopause_sw_streamline_3d(datafile, seeds_n=200, seeds_x0=seeds_x0, seeds_range=[-15*6371000, 15*6371000], - dl=dl, iterations=iters, end_x=xmin+15*6371000, x_point_n=100, sector_n=sector_n) - - # make the magnetopause surface from vertice points - np.random.shuffle(vertices) # helps Delaunay triangulation - Delaunay_SDF = vtkDelaunay3d_SDF(query_points, vertices) - - writer = vtk.vtkXMLPolyDataWriter() - writer.SetInputConnection(vtkSurface.GetOutputPort()) - writer.SetFileName(vtpoutfile) - writer.Write() - return Delaunay_SDF - - elif method == "beta_star": - # magnetopause from beta_star only - condition_dict = {"beta_star": [0.4, 0.5]} # FIC: [0.4, 0.5]) # EGE: [0.9, 1.0]) # max 0.6 in FHA to not take flyaways from outside magnetopause - mpause_flags = make_region_flags(variable_dict, condition_dict, flag_type="01") - contour_coords = f.get_cell_coordinates(cellids[mpause_flags!=0]) - - # make a convex hull surface with vtk's Delaunay - magnetopause_sdf = vtkDelaunay3d_SDF(query_points, contour_coords) - return magnetopause_sdf - - - elif method == "beta_star_with_connectivity": - # magnetopause from beta_star, with connectivity if possible - try: - connectivity_region = treshold_mask(variable_dict["connection"], 0) - betastar_region = treshold_mask(variable_dict["beta_star"], [0.6, 0.7]) - magnetosphere_proper = np.where((connectivity_region==1) | (betastar_region==1), 1, 0) - contour_coords = f.get_cell_coordinates(cellids[magnetosphere_proper==1]) - except: - print("using field line connectivity for magnetosphere did not work, using only beta*") - condition_dict = {"beta_star": [0.5, 0.6]} # FIC: [0.4, 0.5]) # EGE: [0.9, 1.0]) # max 0.6 in FHA to not take flyaways from outside magnetopause - mpause_flags = make_region_flags(variable_dict, condition_dict, flag_type="01") - contour_coords = f.get_cell_coordinates(cellids[mpause_flags!=0]) - - # make a convex hull surface with vtk's Delaunay - magnetopause_sdf = vtkDelaunay3d_SDF(query_points, contour_coords) - return magnetopause_sdf - - elif method == "dict": - # same method as beta* but from user-defined variables as initial contour - flags = make_region_flags(variable_dict, own_variable_dict, flag_type="01") - treshold_coords = f.get_cell_coordinates(cellids[flags!=0]) - dict_sdf = vtkDelaunay3d_SDF(query_points, treshold_coords) - return dict_sdf - - elif method == "shue": - return 0 - theta = np.linspace(0, 2*np.pi/3 , 200) # magnetotail length decided here by trial and error: [0, 5*np.pi/6] ~ -350e6 m, [0, 2*np.pi/3] ~ -100e6 m in EGE - r, __, __ = shue.f_shue(theta, run='EGI') # EGI~EGE, for runs not in shue.py B_z, n_p, and v_sw need to be specified and run=None - - # 2d one-sided magnetopause - xs = r*np.cos(theta) - ys = r*np.sin(theta) - - # 3d projection for complete magnetopause - psis = np.linspace(0, 2*np.pi, 100) - coords = np.zeros((len(theta)*len(psis), 3)) - i=0 - for x,y in zip(xs,ys): - for psi in psis: - coords[i] = np.array([x, y*np.sin(psi), y*np.cos(psi)]) - i += 1 - - coords = coords*R_E - - # surface and SDF - np.random.shuffle(coords) # helps Delaunay triangulation - shue_SDF = vtkDelaunay3d_SDF(query_points, coords) - - return shue_SDF - - else: - print("Magnetopause method not recognized. Use one of the options: \"beta_star\", \"beta_star_with_connectivity\", \"streamlines\", \"shue\", \"dict\"") - -def RegionFlags(datafile, outfilen, vtpoufile, ignore_boundaries=True, - magnetopause_method="beta_star", magnetopause_dict=None): +def RegionFlags(datafile, outfilen, regions=["all"], ignore_boundaries=True, magnetopause_kwargs={}): """Creates a sidecar .vlsv file with flagged cells for regions and boundaries in near-Earth plasma environment. Region flags (start with flag_, flags are fractions of filled conditions or 1/0): magnetosheath, magnetosphere, cusps, lobe_N, lobe_S, central_plasma_sheet, PSBL Boundary signed distance flags (start with SDF_, flags are signed distances to boundary in m with inside being negative distance): magnetopause, bowshock - :param datafile: vlsv file name (and path) - :param outdir: sidecar file save directory path - :param outfilen: sidecar file name + possilbe regions: "all", "boundaries" (magnetopause, bow shock), "large_areas" (boundaries + upstream, magnetosheath, magnetosphere), "magnetosphere", "bowshock", + "cusps", "lobes", "central_plasma_sheet", "boundary_layers" (incl. central plasma sheet BL, HLBL, LLBL; note: not reliable/working atm) + + Note that different runs may need different tresholds for region parameters and region accuracy should be verified visually + + :param datafile: .vlsv bulk file name (and path) + :param outfilen: sidecar .vlsv file name (and path) :kword ignore_boundaries: True: do not take cells in the inner/outer boundaries of the simulation into account when looking for regions - :kword magnetopause_method: default "beta_star", + :kword magnetopause_method: default "beta_star", other options: "beta_star_with_connectivity", "streamlines", "shue" """ f = pt.vlsvfile.VlsvReader(file_name=datafile) cellids =f.read_variable("CellID") [xmin, ymin, zmin, xmax, ymax, zmax] = f.get_spatial_mesh_extent() - all_points = f.get_cell_coordinates(cellids) #centre_points_of_cells(f) + all_points = f.get_cell_coordinates(cellids) cellIDdict = f.get_cellid_locations() @@ -385,18 +297,23 @@ def errormsg(varstr): print("{} could not be read, will be ignored".format(varst try: variables[varname] = f.read_variable(name=filevarname, cellids=-1) except: + variables[varname] = np.full((len(cellids)), np.nan) errormsg(filevarname) else: try: variables[varname] = f.read_variable(name=filevarname[0], cellids=-1, operator=filevarname[1]) except: + variables[varname] = np.full((len(cellids)), np.nan) errormsg(filevarname) - ## cellid testing - #testmesh = np.where(cellids < 10000, 1, 0) - #write_flags(writer, testmesh, 'testmesh') + #connectivity_region = treshold_mask(variables["connection"], 0) + #betastar_region = treshold_mask(variables["beta_star"], [0.0, 0.5]) + #magnetosphere_proper = np.where((connectivity_region==1) | (betastar_region==1), 1, 0) + #write_flags(writer, magnetosphere_proper, 'flag_magnetosphere') + #return 0 + # upstream point upstream_point = [xmax-10*R_E,0.0,0.0] @@ -404,32 +321,36 @@ def errormsg(varstr): print("{} could not be read, will be ignored".format(varst upstream_index = cellIDdict[upstream_cellid] ## MAGNETOPAUSE ## - magnetopause = magnetopause_SDF(f, datafile, vtpoufile, variables, all_points, method=magnetopause_method, own_variable_dict=magnetopause_dict) - write_flags(writer, magnetopause, 'SDF_magnetopause') - write_flags(writer, np.where(np.abs(magnetopause) < 5e6, 1, 0), "flag_magnetopause") + if magnetopause_kwargs: + __, magnetopause_SDF = magnetopause.magnetopause(datafile, **magnetopause_kwargs) + else: + __, magnetopause_SDF = magnetopause.magnetopause(datafile, method="beta_star_with_connectivity", Delaunay_alpha=None) # default magnetopause: beta*+ B connectivity convex hull + write_flags(writer, magnetopause_SDF, 'SDF_magnetopause') + write_flags(writer, np.where(np.abs(magnetopause_SDF) < 5e6, 1, 0), "flag_magnetopause") # save some magnetopause values for later - magnetopause_density = np.mean(variables["density"][np.abs(magnetopause) < 5e6]) + magnetopause_density = np.mean(variables["density"][np.abs(magnetopause_SDF) < 5e6]) print(f"{magnetopause_density=}") - magnetopause_temperature = np.mean(variables["temperature"][np.abs(magnetopause) < 5e6]) + magnetopause_temperature = np.mean(variables["temperature"][np.abs(magnetopause_SDF) < 5e6]) print(f"{magnetopause_temperature=}") ## MAGNETOSPHERE ## # magnetosphere from magentopause SDF - magnetosphere = np.where(magnetopause<0, 1, 0) + magnetosphere = np.where(magnetopause_SDF<0, 1, 0) write_flags(writer, magnetosphere, 'flag_magnetosphere_convex') # magnetospshere from beta* and field line connectivity if possible # TODO try: connectivity_region = treshold_mask(variables["connection"], 0) - betastar_region = treshold_mask(variables["beta_star"], [0.01, 0.5]) + betastar_region = treshold_mask(variables["beta_star"], [0.0, 0.5]) magnetosphere_proper = np.where((connectivity_region==1) | (betastar_region==1), 1, 0) write_flags(writer, magnetosphere_proper, 'flag_magnetosphere') except: - print("Non-convex magnetosphere could not be made") + print("Non-Delaunay beta* magnetosphere could not be made") - ## BOW SHOCK ## - # bow shock from rho + + ## BOW SHOCK ## #TODO: similar kwargs system as magnetopause? + # bow shock from rho bowshock = bowshock_SDF(f, variables, all_points) write_flags(writer, bowshock, 'SDF_bowshock') write_flags(writer, np.where(np.abs(bowshock) < 5e6, 1, 0), "flag_bowshock") @@ -470,73 +391,78 @@ def errormsg(varstr): print("{} could not be read, will be ignored".format(varst #print("upstream B:", variables["B_magnitude"][upstream_index]) # cusps - try: - cusp_conditions = {"density": [variables["density"][upstream_index], None], - #"beta_star": [0.1, None], - "connection": [0.0, 2.5], # either closed, or open-closed/closed-open - "B_magnitude":[2*variables["B_magnitude"][upstream_index], None], - "J_magnitude": [variables["J_magnitude"][upstream_index], None] - } - except: - cusp_conditions = {"density": [variables["density"][upstream_index], None], - #"beta_star": [0.1, None], - "connection": [0.0, 2.5], # either closed, or open-closed/closed-open - "B_magnitude":[2*variables["B_magnitude"][upstream_index], None] - } + cusp_conditions = {"density": [variables["density"][upstream_index], None], + #"beta_star": [0.1, None], + "connection": [0.0, 2.5], # either closed, or open-closed/closed-open + "B_magnitude":[2*variables["B_magnitude"][upstream_index], None], + "J_magnitude": [variables["J_magnitude"][upstream_index], None] + } cusp_flags = make_region_flags(variables, cusp_conditions, flag_type="fraction", mask=mask_inMagnetosphere) write_flags(writer, cusp_flags, 'flag_cusps', mask_inMagnetosphere) - + # magnetotail lobes - lobes_conditions = {"beta": [None, 0.1], # Koskinen: 0.003 - "connection": [0.5, 2.5], - "density": [None, variables["density"][upstream_index]], # Koskinen: 1e-8 - "temperature": [None, 3.5e6], # Koskinen: 3.5e6 K - } - lobes_flags = make_region_flags(variables, lobes_conditions, flag_type="fraction", mask=mask_inBowshock) - write_flags(writer, lobes_flags, 'flag_lobes', mask_inBowshock) - - # lobes slightly other way - lobe_N_conditions = {"beta": [None, 0.1], - "connection": [0.5, 2.5], - "B_x":[0, None], - "B_magnitude":[None, 10*variables["B_magnitude"][upstream_index]] - } - lobe_N_flags = make_region_flags(variables, lobe_N_conditions, flag_type="fraction", mask=mask_inBowshock) - write_flags(writer, lobe_N_flags, 'flag_lobe_N', mask_inBowshock) - - lobe_S_conditions = {"beta": [None, 0.1], - "connection": [0.5, 2.5], - "B_x":[None, 0], - "B_magnitude":[None, 10*variables["B_magnitude"][upstream_index]] - } - lobe_S_flags = make_region_flags(variables, lobe_S_conditions, flag_type="fraction", mask=mask_inBowshock) - write_flags(writer, lobe_S_flags, 'flag_lobe_S', mask_inBowshock) + def lobes(): + lobes_conditions = {"beta": [None, 0.1], # Koskinen: 0.003 + "connection": [0.5, 2.5], + "density": [None, variables["density"][upstream_index]], # Koskinen: 1e-8 + "temperature": [None, 3.5e6], # Koskinen: 3.5e6 K + } + lobes_flags = make_region_flags(variables, lobes_conditions, flag_type="fraction") + + + # lobes slightly other way + lobe_N_conditions = {"beta": [None, 0.1], + "connection": [0.5, 2.5], + "B_x":[0, None], + "B_magnitude":[None, 10*variables["B_magnitude"][upstream_index]] + } + lobe_N_flags = make_region_flags(variables, lobe_N_conditions, flag_type="fraction") + + + lobe_S_conditions = {"beta": [None, 0.1], + "connection": [0.5, 2.5], + "B_x":[None, 0], + "B_magnitude":[None, 10*variables["B_magnitude"][upstream_index]] + } + lobe_S_flags = make_region_flags(variables, lobe_S_conditions, flag_type="fraction") + + + return lobes_flags, lobe_N_flags, lobe_S_flags + + if "all" in regions or "lobes" in regions: + mask = mask_inBowshock + lobes_flags, N_lobe_flags, S_lobe_flags = lobes() + write_flags(writer, lobes_flags, 'flag_lobes') + write_flags(writer, N_lobe_flags, 'flag_lobe_N') + write_flags(writer, S_lobe_flags, 'flag_lobe_S') + # lobe density from median densities? lobes_mask = np.where((lobes_flags > 0.9), 1, 0).astype(bool) - lobes_allcells = mask_inBowshock.astype(float) - lobes_allcells[mask_inBowshock] = lobes_mask - lobe_density = np.mean(variables["density"][lobes_allcells>0.9]) + #lobes_allcells = mask_inBowshock.astype(float) + #lobes_allcells[mask_inBowshock] = lobes_mask + lobe_density = np.mean(variables["density"][lobes_mask])#[lobes_allcells>0.9]) print(f"{lobe_density=}") # Central plasma sheet - central_plasma_sheet_conditions = {#"density": [None, variables["density"][upstream_index]], - "density": [None, 1e6], # Wolf intro - #"density" : [1e7, None], # Koskinen: 3e-7 - #"density": [lobe_density, None], - "beta": [1.0, None], # Koskinen: 6 - #"connection": 0, # only closed-closed - #"temperature": [2*variables["temperature"][upstream_index], None]#, - "temperature": [2e6, None], # 5e7 from Koskinen - #"B_magnitude":[10*variables["B_magnitude"][upstream_index], None] - #"J_magnitude": [2*variables["J_magnitude"][upstream_index], None], - "J_magnitude": [1e-9, None], - } + def CPS(): + central_plasma_sheet_conditions = {"density": [None, 1e6], # Wolf intro ?should not be + #"density" : [1e7, None], # Koskinen: 3e-7 + #"density": [lobe_density, None], + "beta": [1.0, None], # Koskinen: 6 + #"connection": 0, # only closed-closed + "temperature": [2e6, None], # 5e7 from Koskinen + "J_magnitude": [1e-9, None], + } + + central_plasma_sheet_flags = make_region_flags(variables, central_plasma_sheet_conditions,flag_type="fraction", mask=mask_inMagnetosphere) + return central_plasma_sheet_flags - central_plasma_sheet_flags = make_region_flags(variables, central_plasma_sheet_conditions,flag_type="fraction", mask=mask_inMagnetosphere) - write_flags(writer, central_plasma_sheet_flags, 'flag_central_plasma_sheet', mask_inMagnetosphere) + if "all" in regions or "central_plasma_sheet" in regions: + CPS_flags = CPS() + write_flags(writer, CPS_flags, 'flag_central_plasma_sheet', mask_inMagnetosphere) # Plasma sheet boundary layer (PSBL) @@ -563,8 +489,6 @@ def errormsg(varstr): print("{} could not be read, will be ignored".format(varst - - # High-Latitude boundary layer (HLBL) HLBL_conditions = { "density": [magnetopause_density, magnetosheath_density], "temperature": [magnetosheath_temperature, magnetopause_temperature], @@ -580,13 +504,11 @@ def errormsg(varstr): print("{} could not be read, will be ignored".format(varst def main(): - datafile = "/wrk-vakka/group/spacephysics/vlasiator/3D/FHA/bulk1/bulk1.0001400.vlsv" - outfilen = "FHA_regions_t0001400.vlsv" - vtp_outfilen = "FHA_regions_t0001400.vtp" + datafile = "/wrk-vakka/group/spacephysics/vlasiator/3D/EGE/bulk/bulk.0002000.vlsv" + outfilen = "EGE_regions_t2000.vlsv" - for infile, outfile, vtp_outfile in zip(datafile, outfilen, vtp_outfilen): - RegionFlags(infile, outfile, vtp_outfile, magnetopause_method="streamlines") + RegionFlags(datafile, outfilen, regions=["all"], magnetopause_kwargs={"method":"beta_star_with_connectivity", "beta_star_range":[0.3, 0.4]}) if __name__ == "__main__": From 6f9a9fc3dde960532803cf8efa85400754bb92ea Mon Sep 17 00:00:00 2001 From: jreimi Date: Tue, 26 Aug 2025 14:46:26 +0300 Subject: [PATCH 042/124] non-fix to surface making and added outline to how to get beta* + connectivity magnetosphere to magnetopause script --- .../magnetopause_sw_streamline_3d.py | 45 ++++++++++++++++--- scripts/magnetopause.py | 26 +++++------ 2 files changed, 50 insertions(+), 21 deletions(-) diff --git a/analysator/pyCalculations/magnetopause_sw_streamline_3d.py b/analysator/pyCalculations/magnetopause_sw_streamline_3d.py index 27d44df4..abbc2ea5 100644 --- a/analysator/pyCalculations/magnetopause_sw_streamline_3d.py +++ b/analysator/pyCalculations/magnetopause_sw_streamline_3d.py @@ -142,6 +142,37 @@ def make_surface(coords): next_triangles = [x + slices_in_plane*area_index for x in first_triangles] faces.extend(next_triangles) + #### test area #### + # From last triangles remove every other triangle + # (a single subsolar point -> last triangles are actual triangles instead of rectangles sliced in two) + #removed = 0 + #for i in range(len(faces)-slices_in_plane*2, len(faces)): + # if i%2!=0: + # faces.pop(i-removed) + # removed += 1 + + # From last triangles remove every other triangle + # (a single subsolar point -> last triangles are actual triangles instead of rectangles sliced in two) + # Also fix the last triangles so that they only point to one subsolar point and have normals towards outside + #subsolar_index = int(len(verts)-slices_in_plane) + + #for i,triangle in enumerate(reversed(faces)): + # if i > (slices_in_plane): # faces not in last plane (we're going backwards) + # break + + # faces[len(faces)-i-1] = np.clip(triangle, a_min=0, a_max=subsolar_index) + + # this would remove duplicate subsolar points from vertices but makes 2d slicing harder + #verts = verts[:int(len(verts)-slices_in_plane+1)] + + # Change every other face triangle (except for last slice triangles) normal direction so that all face the same way (hopefully) + #faces = np.array(faces) + #for i in range(len(faces)-int(slices_in_plane)): + # if i%2!=0: + # faces[i,1], faces[i,2] = faces[i,2], faces[i,1] + + ################### + # Change every other face triangle normal direction so that all face the same way faces = np.array(faces) for i in range(len(faces)): @@ -236,7 +267,7 @@ def make_streamlines(vlsvfile, streamline_seeds=None, seeds_n=25, seeds_x0=20*63 return streams -def make_magnetopause(streams, end_x=-15*6371000, x_point_n=50, sector_n=36): +def make_magnetopause(streams, end_x=-15*6371000, x_point_n=50, sector_n=36, ignore=0): """Finds the mangetopause location based on streamlines. :param streams: streamlines (coordinates in m) @@ -250,7 +281,6 @@ def make_magnetopause(streams, end_x=-15*6371000, x_point_n=50, sector_n=36): """ RE = 6371000 - ignore = 0 ## if given sector number isn't divisible by 4, make it so because we want to have magnetopause points at exactly y=0 and z=0 for 2d slices of the whole thing while sector_n%4 != 0: @@ -276,7 +306,7 @@ def make_magnetopause(streams, end_x=-15*6371000, x_point_n=50, sector_n=36): dayside_points = streampoints[streampoints[:,0] > 0] phi_slices = sector_n - theta_slices = 10 + theta_slices = dayside_x_point_n phi_step = 2*np.pi/phi_slices theta_step = np.pi/(2*theta_slices) # positive x-axis only @@ -398,7 +428,7 @@ def grid_mid_point(theta_idx, phi_idx): return magnetopause -def find_magnetopause_sw_streamline_3d(vlsvfile, streamline_seeds=None, seeds_n=25, seeds_x0=20*6371000, seeds_range=[-5*6371000, 5*6371000], dl=2e6, iterations=200, end_x=-15*6371000, x_point_n=50, sector_n=36): +def find_magnetopause_sw_streamline_3d(vlsvfile, streamline_seeds=None, seeds_n=25, seeds_x0=20*6371000, seeds_range=[-5*6371000, 5*6371000], dl=2e6, iterations=200, end_x=-15*6371000, x_point_n=50, sector_n=36, ignore=0): """Finds the magnetopause position by tracing streamlines of the velocity field. :param vlsvfile: path to .vlsv bulk file to use for VlsvReader @@ -409,14 +439,15 @@ def find_magnetopause_sw_streamline_3d(vlsvfile, streamline_seeds=None, seeds_n= :kword dl: streamline iteration step length in m :kword iterations: int, number of iteration steps :kword end_x: tail end x-coordinate (how far along the x-axis the magnetopause is calculated) - :kword x_point_n: integer, how many x-axis points the magnetopause will be divided in between the subsolar point and tail - :kword sector_n: integer, how many sectors the magnetopause will be divided in on each yz-plane + :kword x_point_n: integer, how many parts the magnetopause will be divided in between the subsolar point and tail end + :kword sector_n: integer, how many sectors the magnetopause will be divided in on each yz-plane/radial sector + :kword ignore: how many inner streamlines will be ignored when calculating the magnetopause :returns: vertices, surface where vertices are numpy arrays in shape [[x0,y0,z0], [x1,y1,z1],...] and surface is a vtk vtkDataSetSurfaceFilter object """ streams = make_streamlines(vlsvfile, streamline_seeds, seeds_n, seeds_x0, seeds_range, dl, iterations) - magnetopause = make_magnetopause(streams, end_x, x_point_n, sector_n) + magnetopause = make_magnetopause(streams, end_x, x_point_n, sector_n, ignore) vertices, faces = make_surface(magnetopause) surface = make_vtk_surface(vertices, faces) diff --git a/scripts/magnetopause.py b/scripts/magnetopause.py index 2af4b7ae..3a4f9a12 100644 --- a/scripts/magnetopause.py +++ b/scripts/magnetopause.py @@ -37,7 +37,7 @@ def magnetopause(datafilen, method="beta_star_with_connectivity", own_tresholds= :kword return_surface: True/False, return vtkDataSetSurfaceFilter object :kword return_SDF: True/False, return array of distances in m to SDF_points in point input order, negative distance inside the surface :kword SDF_points: optionally give array of own points to calculate signed distances to. If not given, distances will be to cell centres in the order of f.read_variable("CellID") output - :kword Delaunay_alpha: alpha (float) to give to vtkDelaunay3d, None -> convex hull, alpha=__: surface egdes longer than __ will be excluded (-> concave hull) + :kword Delaunay_alpha: alpha (float) to give to vtkDelaunay3d, None -> convex hull, alpha=__: surface egdes longer than __ will be excluded (won't be a proper surface, SDF won't work) :kword beta_star_range: [min, max] treshold rage to use with methods "beta_star" and "beta_star_with_connectivity" :returns: vtkDataSetSurfaceFilter object of convex hull or alpha shape if return_surface=True, signed distance field of convex hull or alpha shape of magnetopause if return_SDF=True """ @@ -56,11 +56,10 @@ def magnetopause(datafilen, method="beta_star_with_connectivity", own_tresholds= dl=5e5 iters = int(((seeds_x0-xmin)/dl)+100) sector_n = 36*4 - vertices, manual_vtkSurface = pt.calculations.find_magnetopause_sw_streamline_3d(datafilen, seeds_n=200, seeds_x0=seeds_x0, seeds_range=[-5*6371000, 5*6371000], + vertices, manual_vtkSurface = pt.calculations.find_magnetopause_sw_streamline_3d(datafilen, seeds_n=300, seeds_x0=seeds_x0, seeds_range=[-5*6371000, 5*6371000], dl=dl, iterations=iters, end_x=xmin+10*6371000, x_point_n=200, sector_n=sector_n) - # good parameters example: seeds_x0=150e6, dl=5e5, iters = int(((seeds_x0-xmin)/dl)+100), sector_n = 36*3, seeds_n=100, seeds_range=[-5*6371000, 5*6371000], end_x=xmin+10*6371000, x_point_n=200 - - write_vtk_surface_to_file(manual_vtkSurface, "/wrk-vakka/users/jreimi/magnetosphere_classification/FID/FID_magnetopause_SW_manual_t1100.vtp") + + write_vtk_surface_to_file(manual_vtkSurface, "/wrk-vakka/users/jreimi/magnetosphere_classification/FID/FID_magnetopause_SWnf_manual_t1100.vtp") # make the magnetopause surface from vertice points np.random.shuffle(vertices) # helps Delaunay triangulation vtkSurface, SDF = regions.vtkDelaunay3d_SDF(query_points, vertices, Delaunay_alpha) @@ -141,13 +140,12 @@ def magnetopause(datafilen, method="beta_star_with_connectivity", own_tresholds= return vtkSurface, None elif return_SDF: return None, SDF + + # beta* + B connectivity: + #connectivity_region = regions.treshold_mask(f.read_variable("vg_connection"), 0) + #betastar_region = regions.treshold_mask(f.read_variable("beta_star"), [0.0, 0.5]) + #magnetosphere_proper = np.where((connectivity_region==1) | (betastar_region==1), 1, 0) - # write the surface to a file - #writer = vtk.vtkXMLPolyDataWriter() - #writer.SetInputConnection(vtkSurface.GetOutputPort()) - #writer.SetFileName(outfilen) - #writer.Write() - #return vtkSurface, SDF @@ -155,15 +153,15 @@ def main(): datafile = "/wrk-vakka/group/spacephysics/vlasiator/3D/FID/bulk1/bulk1.0001100.vlsv" - vtpoutfilen = "FID_magnetopause_BS_noalpha_t1100.vtp" - vlsvoutfilen = "FID_magnetopause_BS_noalpha_t1100.vlsv" + vtpoutfilen = "FID_magnetopause_BS_t1100.vtp" + vlsvoutfilen = "FID_magnetopause_BS_t1100.vlsv" surface, SDF = magnetopause(datafile, #method="streamlines", method="beta_star_with_connectivity", beta_star_range=[0.3, 0.4], - #Delaunay_alpha=2*R_E, + Delaunay_alpha=1*R_E, return_SDF=True, return_surface=True) From 46fe573640b57de068496f8bc3f69391c0a98d93 Mon Sep 17 00:00:00 2001 From: Markku Alho Date: Wed, 9 Jul 2025 19:11:04 +0300 Subject: [PATCH 043/124] 1) Add caching of neihgbor stencils (yes, it's via Pickle, it's ugly) 2) Removed unnecessary reconstruction of some of the neighbor stencils 3) Cleanups in VlsvWriter 4) Cleanups in VlsvVtkReader --- analysator/pyVlsv/vlsvreader.py | 109 +++++++++++++++++++++----- analysator/pyVlsv/vlsvvtkinterface.py | 37 +++++++-- analysator/pyVlsv/vlsvwriter.py | 3 +- 3 files changed, 121 insertions(+), 28 deletions(-) diff --git a/analysator/pyVlsv/vlsvreader.py b/analysator/pyVlsv/vlsvreader.py index 351004fe..df3ee2dc 100644 --- a/analysator/pyVlsv/vlsvreader.py +++ b/analysator/pyVlsv/vlsvreader.py @@ -51,6 +51,7 @@ interp_method_aliases = {"trilinear":"linear"} +neighbors_cache_file = "neighbors_cache.pkl" def dict_keys_exist(dictionary, query_keys, prune_unique=False): @@ -144,17 +145,19 @@ def __del__(self): if (hasattr(self, "__fptr")) and self.__fptr is not None: self.__fptr.close() - def __init__(self, file_name, fsGridDecomposition=None): + def __init__(self, file_name, fsGridDecomposition=None, file_cache = 0): ''' Initializes the vlsv file (opens the file, reads the file footer and reads in some parameters) :param file_name: Name of the vlsv file - :param fsGridDecomposition: Either None or a len-3 list of ints. + :kword fsGridDecomposition: Either None or a len-3 list of ints [None]. List (length 3): Use this as the decomposition directly. Product needs to match numWritingRanks. + :kword file_cache: Boolean, [False]: cache slow-to-compute data to disk (:seealso get_cache_folder) ''' # Make sure the path is set in file name: file_name = os.path.abspath(file_name) self.file_name = file_name + self.file_cache = file_cache try: self.__fptr = vlsvcache.PicklableFile(open(self.file_name,"rb")) except FileNotFoundError as e: @@ -202,6 +205,9 @@ def __init__(self, file_name, fsGridDecomposition=None): self.__cell_neighbours = {} # cellid : set of cellids (all neighbors sharing a vertex) self.__cell_duals = {} # cellid : tuple of vertex-indices that span this cell self.__regular_neighbor_cache = {} # cellid-of-low-corner : (8,) np.array of cellids) + self.__neighbors_cache_len = 0 + self.__neighbors_cache_available = os.path.isfile(os.path.join(self.get_cache_folder(),neighbors_cache_file)) + self.__neighbors_cache_loaded = False # Start calling functions only after initializing trivial members self.get_linked_readers() @@ -2120,6 +2126,7 @@ def read_variable_to_cache(self, name, operator="pass"): self.variable_cache[(name,operator)] = self.read_variable(name, cellids=-1,operator=operator) # Also initialize the fileindex dict at the same go because it is very likely something you want to have for accessing cached values self.__read_fileindex_for_cellid() + return self.variable_cache[(name,operator)] def read_variable(self, name, cellids=-1,operator="pass"): ''' Read variables from the open vlsv file. @@ -2132,6 +2139,8 @@ def read_variable(self, name, cellids=-1,operator="pass"): .. seealso:: :func:`read` :func:`read_variable_info` ''' cellids = get_data(cellids) + if((name,operator) in self.variable_cache.keys()): + return self.read_variable_from_cache(name,cellids,operator) # Wrapper, check if requesting an fsgrid variable if (self.check_variable(name) and (name.lower()[0:3]=="fg_")): @@ -2147,8 +2156,6 @@ def read_variable(self, name, cellids=-1,operator="pass"): return False return self.read_ionosphere_variable(name=name, operator=operator) - if((name,operator) in self.variable_cache.keys()): - return self.read_variable_from_cache(name,cellids,operator) for reader in self.__linked_readers: try: @@ -2830,12 +2837,14 @@ def get_cell_corner_vertices(self, cids): ''' - mask = ~dict_keys_exist(self.__cell_vertices,cids,prune_unique=False) - coords = self.get_cell_coordinates(cids[mask]) - vertices = np.zeros((len(cids[mask]), 8, 3),dtype=int) + mask = ~dict_keys_exist(self.__cell_corner_vertices,cids,prune_unique=False) + cell_vertex_sets = {} if(len(cids[mask]) > 0): + coords = self.get_cell_coordinates(cids[mask]) + vertices = np.zeros((len(cids[mask]), 8, 3),dtype=int) + ii = 0 for x in [-1,1]: for y in [-1,1]: @@ -2852,7 +2861,7 @@ def get_cell_corner_vertices(self, cids): self.__cell_corner_vertices.update(cell_vertex_sets) - for i, c in enumerate(cids[~mask]): + for c in cids[~mask]: cell_vertex_sets[c] = self.__cell_corner_vertices[c] return cell_vertex_sets @@ -2862,20 +2871,23 @@ def get_cell_corner_vertices(self, cids): def build_cell_neighborhoods(self, cids): mask = ~dict_keys_exist(self.__cell_neighbours, cids, prune_unique=False) - cell_vertex_sets = self.get_cell_corner_vertices(cids[mask]) # these are enough to fetch the neighbours - - cell_neighbor_sets = {c: set() for c in cell_vertex_sets.keys()} - vertices_todo = set().union(*cell_vertex_sets.values()) - neighbor_tuples_dict = self.build_dual_from_vertices(list(vertices_todo)) - for c,verts in cell_vertex_sets.items(): - # neighbor_tuples = self.build_dual_from_vertices(verts) - # cell_neighbor_sets[c].update(set().union(*neighbor_tuples.values())) - cell_neighbor_sets[c].update(set().union(*itemgetter(*cell_vertex_sets[c])(neighbor_tuples_dict))) - self.__cell_neighbours.update(cell_neighbor_sets) + cell_neighbor_sets = {} + + if len(cids[mask]) > 0: + cell_vertex_sets = self.get_cell_corner_vertices(cids[mask]) # these are enough to fetch the neighbours + cell_neighbor_sets = {c: set() for c in cell_vertex_sets.keys()} + vertices_todo = set().union(*cell_vertex_sets.values()) + neighbor_tuples_dict = self.build_dual_from_vertices(list(vertices_todo)) + for c in cell_vertex_sets.keys(): + # neighbor_tuples = self.build_dual_from_vertices(verts) + # cell_neighbor_sets[c].update(set().union(*neighbor_tuples.values())) + cell_neighbor_sets[c].update(set().union(*itemgetter(*cell_vertex_sets[c])(neighbor_tuples_dict))) + + self.__cell_neighbours.update(cell_neighbor_sets) for c in cids[~mask]: - cell_neighbor_sets[c] = self.__cell_neighbors[c] + cell_neighbor_sets[c] = self.__cell_neighbours[c] return cell_neighbor_sets @@ -3921,4 +3933,63 @@ def get_mesh_domain_extents(self, mesh): else: raise ValueError + + def get_cache_folder(self): + + fn = self.file_name + + head,tail = os.path.split(fn) + path = head + numslist = re.findall(r'\d+(?=\.vlsv)', tail) + + if(len(numslist) == 0): + path = os.path.join(path,"vlsvcache",tail[:-5]) + else: + nums = numslist[-1] + head, tail = tail.split(nums) + + leading_zero = True + path = os.path.join(path,"vlsvcache",head[:-1]) + for i,n in enumerate(nums): + if n == '0' and leading_zero: continue + if leading_zero: + fmt = "{:07d}" + else: + fmt = "{:0"+str(7-i)+"d}" + path = os.path.join(path, fmt.format(int(n)*10**(len(nums)-i-1))) + leading_zero = False + + # print(path) + return path + + def cache_neighbor_stencils(self): + self.load_neighbor_stencils_from_filecache() + path = self.get_cache_folder() + os.makedirs(path,exist_ok=True) + cache_file_neighbors = "neighbors_cache.pkl" + with open(os.path.join(path,cache_file_neighbors),'wb') as cache: + pickle.dump({ + "cell_neighbours": self._VlsvReader__cell_neighbours, + "cell_vertices":self._VlsvReader__cell_vertices, + "cell_corner_vertices":self._VlsvReader__cell_corner_vertices} + ,cache) + + def load_neighbor_stencils_from_filecache(self): + if self.__neighbors_cache_available and not self.__neighbors_cache_loaded: + path = self.get_cache_folder() + cache_file_neighbors = "neighbors_cache.pkl" + if(os.path.isfile(os.path.join(path,cache_file_neighbors))): + with open(os.path.join(path,cache_file_neighbors),'rb') as cache: + loaded = pickle.load(cache) + + self._VlsvReader__cell_neighbours.update(loaded["cell_neighbours"]) + self._VlsvReader__cell_vertices.update(loaded["cell_vertices"]) + self._VlsvReader__cell_corner_vertices.update(loaded["cell_corner_vertices"]) + self.__neighbors_cache_len = len(self._VlsvReader__cell_neighbours) + else: + self.__neighbors_cache_loaded = True + def clear_cache_folder(self): + path = self.get_cache_folder() + import shutil + shutil.rmtree(path) \ No newline at end of file diff --git a/analysator/pyVlsv/vlsvvtkinterface.py b/analysator/pyVlsv/vlsvvtkinterface.py index f54a0612..8663e692 100644 --- a/analysator/pyVlsv/vlsvvtkinterface.py +++ b/analysator/pyVlsv/vlsvvtkinterface.py @@ -24,8 +24,10 @@ import logging import numpy as np import cProfile -import os.path +import os import pickle +from operator import itemgetter +import numbers import analysator as pt try: @@ -627,6 +629,7 @@ def __init__(self): self.__cellarrays = [] self._arrayselection = vtk.vtkDataArraySelection() self._arrayselection.AddObserver("ModifiedEvent", createModifiedCallback(self)) + self.__cellIDtoIdx = {} def SetFileName(self, filename): if filename != self.__FileName: @@ -635,7 +638,7 @@ def SetFileName(self, filename): if self.__FileName is not None: self.__reader = pt.vlsvfile.VlsvReader(self.__FileName) fn = os.path.basename(self.__FileName) - self.__metafile = os.path.join(os.path.dirname(self.__FileName),"vlsvmeta",fn[:-5]+"_meta") + self.__metafile = os.path.join(self.__reader.get_cache_folder(),"vlsvvtkcache.pkl") def GetFileName(self): return self.__FileName @@ -716,6 +719,8 @@ def children(cid, level): def getDescriptor(self, reinit=False): + if hasattr(self, "__descriptor") and hasattr(self, "__idxToFileIndex"): + return self.__descriptor, self.__idxToFileIndex try: if reinit: raise Exception("reinit = True") @@ -728,13 +733,24 @@ def getDescriptor(self, reinit=False): print("Re-initializing HTG, no metadata accessible because of ", e) self.__descriptor, self.__idxToFileIndex = self.buildDescriptor() if not os.path.isdir(os.path.dirname(self.__metafile)): - os.mkdir(os.path.dirname(self.__metafile)) + os.makedirs(os.path.dirname(self.__metafile), exist_ok=True) with open(self.__metafile,'wb') as mfile: pickle.dump({"descr":self.__descriptor, "idxToFileIndexMap":self.__idxToFileIndex}, mfile) return self.__descriptor, self.__idxToFileIndex + + def getCellIDtoIdxMap(self): + if len(self.__cellIDtoIdx) == 0: + FileIndexToID = {v:k for k,v in self.getDescriptor()[1].items()} + for c,fi in self.__reader._VlsvReader__fileindex_for_cellid.items(): + self.__cellIDtoIdx[c] = FileIndexToID[fi] + + return self.__cellIDtoIdx def getHTG(self): + if self.__htg is not None: + return self.__htg + f = self.__reader descr, idxToFileIndex = self.getDescriptor() @@ -838,7 +854,7 @@ def RequestUpdateExtent(self, request, inInfo, outInfo): ''' def addArrayFromVlsv(self, varname): - htg = self.__htg + htg = self.getHTG() # Do not re-add an already existing array if htg.GetCellData().HasArray(varname): print("skipped existing array") @@ -919,9 +935,16 @@ def ReadAllScalarsOn(self): # self.__htg.addArrayFromVlsv(name) - - def GetDataArraySelection(self): - return self._arrayselection +# TODO + # def GetDataArraySelection(self): + # return self._arrayselection + + # def GetIdx(self, cellid): + # cidToIdx = self.getCellIDtoIdxMap() + # if isinstance(cellid,numbers.Number): + # return cidToIdx[cellid] + # else: + # return itemgetter(*cellid)(cidToIdx) def __main__(): diff --git a/analysator/pyVlsv/vlsvwriter.py b/analysator/pyVlsv/vlsvwriter.py index 24e4f883..330e3e51 100644 --- a/analysator/pyVlsv/vlsvwriter.py +++ b/analysator/pyVlsv/vlsvwriter.py @@ -29,7 +29,6 @@ import os import warnings from reduction import datareducers,data_operators -import warnings class VlsvWriter(object): ''' Class for reading VLSV files @@ -155,7 +154,7 @@ def __initialize( self, vlsvReader, copy_meshes=None ): if mesh == "SpatialGrid": if "MESH_DOMAIN_EXTENTS" not in tags: extents = vlsvReader.get_mesh_domain_extents(mesh) - print(extents) + # print(extents) self.__write( data = extents, name='', tag="MESH_DOMAIN_EXTENTS", mesh=mesh) From 47809fbb0565edcbe248bcabc46612d0fc1d4669 Mon Sep 17 00:00:00 2001 From: Markku Alho Date: Wed, 20 Aug 2025 17:09:23 +0300 Subject: [PATCH 044/124] Moving cache stuff to vlsvcache --- analysator/pyVlsv/vlsvreader.py | 1 - 1 file changed, 1 deletion(-) diff --git a/analysator/pyVlsv/vlsvreader.py b/analysator/pyVlsv/vlsvreader.py index df3ee2dc..1b46a3f4 100644 --- a/analysator/pyVlsv/vlsvreader.py +++ b/analysator/pyVlsv/vlsvreader.py @@ -53,7 +53,6 @@ neighbors_cache_file = "neighbors_cache.pkl" - def dict_keys_exist(dictionary, query_keys, prune_unique=False): if query_keys.shape[0] == 0: return np.array([],dtype=bool) From bd3e207905c224f1293d02f0b9da74894d316e4d Mon Sep 17 00:00:00 2001 From: Markku Alho Date: Thu, 21 Aug 2025 16:22:56 +0300 Subject: [PATCH 045/124] Partial fileindex reading update --- analysator/pyVlsv/vlsvcache.py | 51 +++++++++- analysator/pyVlsv/vlsvreader.py | 167 +++++++++++++++++++++++++------- 2 files changed, 180 insertions(+), 38 deletions(-) diff --git a/analysator/pyVlsv/vlsvcache.py b/analysator/pyVlsv/vlsvcache.py index 28cee4bd..cdd44611 100644 --- a/analysator/pyVlsv/vlsvcache.py +++ b/analysator/pyVlsv/vlsvcache.py @@ -21,6 +21,9 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # +''' Utilities for caching VLSV data and metadata. +''' + import logging import os import sys @@ -29,6 +32,7 @@ import numpy as np from operator import itemgetter import h5py +import re class VariableCache: ''' Class for handling in-memory variable/reducer caching. @@ -74,13 +78,58 @@ def read_variable_from_cache(self, name, cellids, operator): class FileCache: ''' Top-level class for caching to file. ''' - pass + + def __init__(self, reader) -> None: + self.__reader = reader + + def get_cache_folder(self): + fn = self.__reader.file_name + + head,tail = os.path.split(fn) + path = head + numslist = re.findall(r'\d+(?=\.vlsv)', tail) + + if(len(numslist) == 0): + path = os.path.join(path,"vlsvcache",tail[:-5]) + else: + nums = numslist[-1] + head, tail = tail.split(nums) + + leading_zero = True + path = os.path.join(path,"vlsvcache",head[:-1]) + for i,n in enumerate(nums): + if n == '0' and leading_zero: continue + if leading_zero: + fmt = "{:07d}" + else: + fmt = "{:0"+str(7-i)+"d}" + path = os.path.join(path, fmt.format(int(n)*10**(len(nums)-i-1))) + leading_zero = False + + return path + + def clear_cache_folder(self): + path = self.get_cache_folder() + import shutil + shutil.rmtree(path) class MetadataFileCache(FileCache): + ''' File caching class for storing "lightweight" metadata. + ''' pass + # superclass constructor called instead if no __init__ here + # def __init__(self, reader) -> None: + # super(MetadataFileCache, self).__init__(reader) class VariableFileCache(FileCache): + ''' File caching class for storing intermediate data, such as + gradient terms that are more expensive to compute, over whole grids with + more HDD footprint. + ''' pass + # superclass constructor called instead if no __init__ here + # def __init__(self, reader) -> None: + # super(MetadataFileCache, self).__init__(reader) class PicklableFile(object): ''' Picklable file pointer object. diff --git a/analysator/pyVlsv/vlsvreader.py b/analysator/pyVlsv/vlsvreader.py index 1b46a3f4..5245212a 100644 --- a/analysator/pyVlsv/vlsvreader.py +++ b/analysator/pyVlsv/vlsvreader.py @@ -165,8 +165,9 @@ def __init__(self, file_name, fsGridDecomposition=None, file_cache = 0): self.__xml_root = ET.fromstring("") self.__fileindex_for_cellid={} + self.__rankwise_fileindex_for_cellid = {} # { : {cellid: offset}} - self.__metadata_cache = vlsvcache.MetadataFileCache() + self.__metadata_cache = vlsvcache.MetadataFileCache(self) self.__metadata_read = False self.__metadata_dict = {} # Used for reading in and storing derived/other metadata such as linked file paths @@ -175,7 +176,7 @@ def __init__(self, file_name, fsGridDecomposition=None, file_cache = 0): - + self.__mesh_domain_sizes = {} self.__max_spatial_amr_level = -1 self.__fsGridDecomposition = fsGridDecomposition @@ -188,6 +189,7 @@ def __init__(self, file_name, fsGridDecomposition=None, file_cache = 0): self.__vg_indexes_on_fg = np.array([]) # SEE: map_vg_onto_fg(self) self.variable_cache = {} # {(varname, operator):data} + self.__params_cache = {} # {name:data} self.__pops_init = False @@ -1004,6 +1006,49 @@ def get_cellid_locations(self): self.__read_fileindex_for_cellid() return self.__fileindex_for_cellid + def get_mesh_domain_sizes(self, mesh): + + if mesh not in self.__mesh_domain_sizes.keys(): + self.__mesh_domain_sizes[mesh] = self.read(name="", tag="MESH_DOMAIN_SIZES", mesh=mesh) + return self.__mesh_domain_sizes[mesh] + + + def __read_fileindex_for_cellid_rank(self, rank): + """ Read in the cell ids and create an internal dictionary to give the index of an arbitrary cellID + """ + # print("filling rank",rank) + + if rank > self.read_parameter("numWritingRanks"): + raise ValueError("Tried to read rank "+rank+" out of "+self.read_parameter("numWritingRanks")) + + if not self.__rankwise_fileindex_for_cellid.get(rank,{}) == {}: + return + + mesh_domain_sizes = self.get_mesh_domain_sizes("SpatialGrid") + n_domain_cells = mesh_domain_sizes[:,0]-mesh_domain_sizes[:,1] + domain_offsets = np.cumsum(np.hstack((np.array([0]),n_domain_cells[:-1]))) + + cellids = self.read_cellids_with_offset(domain_offsets[rank],n_domain_cells[rank]) + + #Check if it is not iterable. If it is a scale then make it a list + if(not isinstance(cellids, Iterable)): + cellids=[ cellids ] + self.__rankwise_fileindex_for_cellid[rank] = {cellid: domain_offsets[rank]+index for index,cellid in enumerate(cellids)} + + + def get_cellid_locations_rank(self, rank): + ''' Returns a dictionary with cell id as the key and the index of the cell id as the value. + + :param rank: Find (and initialize) fileindex for cells contained by this rank + + ''' + # if len( self.__fileindex_for_cellid ) == 0: + self.__read_fileindex_for_cellid_rank(rank) + return self.__rankwise_fileindex_for_cellid[rank] + + + + def print_version(self): ''' Prints version information from VLSV file. @@ -1173,6 +1218,33 @@ def read_attribute(self, name="", mesh="", attribute="", tag=""): raise ValueError("Variable or attribute not found") + def read_with_offset(self, datatype,variable_offset, read_size, read_offsets, element_size, vector_size): + if self.__fptr.closed: + fptr = open(self.file_name,"rb") + else: + fptr = self.__fptr + if len(read_offsets) !=1: + arraydata = [] + for r_offset in read_offsets: + use_offset = int(variable_offset + r_offset) + fptr.seek(use_offset) + if datatype == "float" and element_size == 4: + data = np.fromfile(fptr, dtype = np.float32, count=vector_size*read_size) + if datatype == "float" and element_size == 8: + data = np.fromfile(fptr, dtype=np.float64, count=vector_size*read_size) + if datatype == "int" and element_size == 4: + data = np.fromfile(fptr, dtype=np.int32, count=vector_size*read_size) + if datatype == "int" and element_size == 8: + data = np.fromfile(fptr, dtype=np.int64, count=vector_size*read_size) + if datatype == "uint" and element_size == 4: + data = np.fromfile(fptr, dtype=np.uint32, count=vector_size*read_size) + if datatype == "uint" and element_size == 8: + data = np.fromfile(fptr, dtype=np.uint64, count=vector_size*read_size) + if len(read_offsets)!=1: + arraydata.append(data) + if len(read_offsets) !=1: + data = np.array(arraydata) + return data def read(self, name="", tag="", mesh="", operator="pass", cellids=-1): ''' Read data from the open vlsv file. @@ -1254,7 +1326,6 @@ def read(self, name="", tag="", mesh="", operator="pass", cellids=-1): # Define efficient method to read data in reorder_data = False - arraydata = [] try: # try-except to see how many cellids were given lencellids=len(cellids) # Read multiple specified cells @@ -1280,30 +1351,7 @@ def read(self, name="", tag="", mesh="", operator="pass", cellids=-1): read_size = 1 read_offsets = [self.__fileindex_for_cellid[cellids]*element_size*vector_size] - if self.__fptr.closed: - fptr = open(self.file_name,"rb") - else: - fptr = self.__fptr - - for r_offset in read_offsets: - use_offset = int(variable_offset + r_offset) - fptr.seek(use_offset) - if datatype == "float" and element_size == 4: - data = np.fromfile(fptr, dtype = np.float32, count=vector_size*read_size) - if datatype == "float" and element_size == 8: - data = np.fromfile(fptr, dtype=np.float64, count=vector_size*read_size) - if datatype == "int" and element_size == 4: - data = np.fromfile(fptr, dtype=np.int32, count=vector_size*read_size) - if datatype == "int" and element_size == 8: - data = np.fromfile(fptr, dtype=np.int64, count=vector_size*read_size) - if datatype == "uint" and element_size == 4: - data = np.fromfile(fptr, dtype=np.uint32, count=vector_size*read_size) - if datatype == "uint" and element_size == 8: - data = np.fromfile(fptr, dtype=np.uint64, count=vector_size*read_size) - if len(read_offsets)!=1: - arraydata.append(data) - - fptr.close() + data = self.read_with_offset(datatype, variable_offset, read_size, read_offsets, element_size, vector_size) if len(read_offsets)==1 and reorder_data: # Many single cell id's requested @@ -1318,7 +1366,7 @@ def read(self, name="", tag="", mesh="", operator="pass", cellids=-1): if len(read_offsets)!=1: # Not-so-many single cell id's requested - data = np.squeeze(np.array(arraydata)) + data = np.squeeze(np.array(data)) if vector_size > 1: data=data.reshape(result_size, vector_size) @@ -1440,7 +1488,47 @@ def read(self, name="", tag="", mesh="", operator="pass", cellids=-1): if name!="": raise ValueError("Error: variable "+name+"/"+tag+"/"+mesh+"/"+operator+" not found in .vlsv file or in data reducers!\n Reader file "+self.file_name) + def read_cellids_with_offset(self, start, ncells): + import ast + tag="VARIABLE" + name="cellid" + mesh="SpatialGrid" + if self.__fptr.closed: + fptr = open(self.file_name,"rb") + else: + fptr = self.__fptr + # Seek for requested data in VLSV file + for child in self.__xml_root: + if tag != "": + if child.tag != tag: + continue + # Verify that any requested name or mesh matches those of the data + if name != "": + if not "name" in child.attrib: + continue + if child.attrib["name"].lower() != name: + continue + if mesh != "": + if not "mesh" in child.attrib: + continue + if child.attrib["mesh"] != mesh: + continue + if child.tag == tag: + # Found the requested data entry in the file + vector_size = ast.literal_eval(child.attrib["vectorsize"]) + array_size = ast.literal_eval(child.attrib["arraysize"]) + element_size = ast.literal_eval(child.attrib["datasize"]) + datatype = child.attrib["datatype"] + variable_offset = ast.literal_eval(child.text) + + result_size = ncells + read_size = ncells + read_offsets = [start*element_size*vector_size] + + data = self.read_with_offset(datatype, variable_offset, read_size, read_offsets, element_size, vector_size) + + return data def read_metadata(self, name="", tag="", mesh=""): ''' Read variable metadata from the open vlsv file. @@ -3432,15 +3520,20 @@ def read_parameter(self, name): .. seealso:: :func:`read_variable` :func:`read_variable_info` ''' - - # Special handling for time - if name=="time": - if self.check_parameter(name="t"): - return self.read(name="t", tag="PARAMETER") - if name=="t": - if self.check_parameter(name="time"): - return self.read(name="time", tag="PARAMETER") - return self.read(name=name, tag="PARAMETER") + + if name in self.__params_cache.keys(): + return self.__params_cache[name] + else: + # Special handling for time + if name=="time": + if self.check_parameter(name="t"): + return self.read(name="t", tag="PARAMETER") + if name=="t": + if self.check_parameter(name="time"): + return self.read(name="time", tag="PARAMETER") + val = self.read(name=name, tag="PARAMETER") + self.__params_cache[name] = val + return val def read_velocity_cells(self, cellid, pop="proton"): From 4255e69ca3a290a25ae4b94c23c1b7635a198dcf Mon Sep 17 00:00:00 2001 From: Markku Alho Date: Mon, 25 Aug 2025 19:07:46 +0300 Subject: [PATCH 046/124] Rtree working and has some mildly tested thresholds for when to use the spatial index (small read volumes or few-enough ranks to read) if available. --- analysator/pyVlsv/vlsvcache.py | 51 ++++- analysator/pyVlsv/vlsvreader.py | 339 ++++++++++++++++++++++---------- pyproject.toml | 1 + 3 files changed, 281 insertions(+), 110 deletions(-) diff --git a/analysator/pyVlsv/vlsvcache.py b/analysator/pyVlsv/vlsvcache.py index cdd44611..a870c5d4 100644 --- a/analysator/pyVlsv/vlsvcache.py +++ b/analysator/pyVlsv/vlsvcache.py @@ -31,8 +31,8 @@ import numbers import numpy as np from operator import itemgetter -import h5py import re +import rtree class VariableCache: ''' Class for handling in-memory variable/reducer caching. @@ -43,6 +43,13 @@ def __init__(self, reader): def keys(self): return self.__varcache.keys() + + def __getitem__(self, key): + return self.__varcache[key] + + def __setitem__(self, key, value): + self.__varcache[key] = value + def read_variable_from_cache(self, name, cellids, operator): ''' Read variable from cache instead of the vlsv file. @@ -82,6 +89,15 @@ class FileCache: def __init__(self, reader) -> None: self.__reader = reader + self.__rtree_index_files = [] + self.__rtree_index = None + self.__rtree_idxfile = os.path.join(self.get_cache_folder(),"rtree.idx") + self.__rtree_datfile = os.path.join(self.get_cache_folder(),"rtree.dat") + self.__rtree_properties = rtree.index.Property() + self.__rtree_properties.dimension = 3 + self.__rtree_properties.overwrite=True + + def get_cache_folder(self): fn = self.__reader.file_name @@ -113,6 +129,37 @@ def clear_cache_folder(self): import shutil shutil.rmtree(path) + def set_cellid_spatial_index(self, force = False): + if not os.path.exists(self.get_cache_folder()): + os.makedirs(self.get_cache_folder()) + + if(force or (not os.path.isfile(self.__rtree_idxfile) or not os.path.isfile(self.__rtree_datfile))): + + + + bboxes = self.__reader.get_mesh_domain_extents("SpatialGrid") + bboxes = bboxes.reshape((-1,6), order='C') + print(bboxes.shape) + + self.__rtree_index = rtree.index.Index(self.__rtree_idxfile[:-4],properties=rtree_properties, interleaved=False) + for rank, bbox in enumerate(bboxes): + print(rank, bbox) + self.__rtree_index.insert(rank, bbox) + + print("index set") + else: + print("index exists") + + def get_cellid_spatial_index(self, force = False): + if self.__rtree_index == None: + if(force or (not os.path.isfile(self.__rtree_idxfile) or not os.path.isfile(self.__rtree_datfile))): + self.set_cellid_spatial_index(force) + else: + self.__rtree_index = rtree.index.Index(self.__rtree_idxfile[:-4], properties=self.__rtree_properties, interleaved=False) + + return self.__rtree_index + + class MetadataFileCache(FileCache): ''' File caching class for storing "lightweight" metadata. ''' @@ -121,6 +168,8 @@ class MetadataFileCache(FileCache): # def __init__(self, reader) -> None: # super(MetadataFileCache, self).__init__(reader) + + class VariableFileCache(FileCache): ''' File caching class for storing intermediate data, such as gradient terms that are more expensive to compute, over whole grids with diff --git a/analysator/pyVlsv/vlsvreader.py b/analysator/pyVlsv/vlsvreader.py index 5245212a..e6b4dc7e 100644 --- a/analysator/pyVlsv/vlsvreader.py +++ b/analysator/pyVlsv/vlsvreader.py @@ -31,7 +31,7 @@ import re import numbers import pickle # for caching linked readers, switch to VLSV/XML at some point - h5py? -import h5py +# import h5py import vlsvvariables import vlsvcache @@ -165,7 +165,10 @@ def __init__(self, file_name, fsGridDecomposition=None, file_cache = 0): self.__xml_root = ET.fromstring("") self.__fileindex_for_cellid={} + self.__full_fileindex_for_cellid = False + self.__cellid_spatial_index=None self.__rankwise_fileindex_for_cellid = {} # { : {cellid: offset}} + self.__loaded_fileindex_ranks = set() self.__metadata_cache = vlsvcache.MetadataFileCache(self) @@ -178,6 +181,7 @@ def __init__(self, file_name, fsGridDecomposition=None, file_cache = 0): self.__mesh_domain_sizes = {} self.__max_spatial_amr_level = -1 + self.__grid_epsilon = None self.__fsGridDecomposition = fsGridDecomposition self.use_dict_for_blocks = False @@ -188,7 +192,7 @@ def __init__(self, file_name, fsGridDecomposition=None, file_cache = 0): self.__order_for_cellid_blocks = {} # per-pop self.__vg_indexes_on_fg = np.array([]) # SEE: map_vg_onto_fg(self) - self.variable_cache = {} # {(varname, operator):data} + self.__variable_cache = vlsvcache.VariableCache(self) # {(varname, operator):data} self.__params_cache = {} # {name:data} self.__pops_init = False @@ -299,6 +303,12 @@ def __init__(self, file_name, fsGridDecomposition=None, file_cache = 0): self.__fptr.close() + def get_grid_epsilon(self): + if self.__grid_epsilon is None: + # one-thousandth of the max refined cell; self.get_max_refinement_level() however reads all cellids, so just temp here by assuming 8 refinement levels.. which is plenty for now + self.__grid_epsilon = 1e-3*np.array([self.__dx, self.__dy, self.__dz])/2**8 + return self.__grid_epsilon + def get_linked_readers_filename(self): '''Need to go to a consolidated metadata handler''' pth, base = os.path.split(self.file_name) @@ -331,32 +341,32 @@ def get_metadata_filename(self): s = os.path.join(pth,"vlsvmeta",base[:-5]+"_metadata.pkl") return s - def get_h5_metadata(self, key, default): - ''' Read metadata from hdf5 metadata file, and if not available, - return the given default value. - - :param data: str, a key to stored metadata. - :param default: value to return if key does not exist - - ''' - - if type(key) == type(("a tuple",)): - print("tuple reader not implemented") - elif type(key) == type("a string"): - print("Reading str-keyed data") - else: - raise TypeError("key must be str or tuple") - - if not self.__metadata_read: - try: - fn = self.get_metadata_filename() - with open(fn,'rb') as f: - self.__metadata_dict = pickle.load(f) - except: - logging.debug("No metadata file found.") - self.__metadata_read = True + # def get_h5_metadata(self, key, default): + # ''' Read metadata from hdf5 metadata file, and if not available, + # return the given default value. + + # :param data: str, a key to stored metadata. + # :param default: value to return if key does not exist + + # ''' + + # if type(key) == type(("a tuple",)): + # print("tuple reader not implemented") + # elif type(key) == type("a string"): + # print("Reading str-keyed data") + # else: + # raise TypeError("key must be str or tuple") + + # if not self.__metadata_read: + # try: + # fn = self.get_metadata_filename() + # with open(fn,'rb') as f: + # self.__metadata_dict = pickle.load(f) + # except: + # logging.debug("No metadata file found.") + # self.__metadata_read = True - return self.__metadata_dict.get(key,default) + # return self.__metadata_dict.get(key,default) def get_reader_metadata(self, key, default): @@ -576,9 +586,10 @@ def __read_xml_footer(self): def __read_fileindex_for_cellid(self): """ Read in the cell ids and create an internal dictionary to give the index of an arbitrary cellID """ - if not self.__fileindex_for_cellid == {}: + if self.__full_fileindex_for_cellid: return - + + # print("fileindex!") cellids=self.read(mesh="SpatialGrid",name="CellID", tag="VARIABLE") #Check if it is not iterable. If it is a scale then make it a list @@ -587,6 +598,7 @@ def __read_fileindex_for_cellid(self): # self.__fileindex_for_cellid = {cellid:index for index,cellid in enumerate(cellids)} for index,cellid in enumerate(cellids): self.__fileindex_for_cellid[cellid] = index + self.__full_fileindex_for_cellid = True def __read_blocks(self, cellid, pop="proton"): ''' Read raw velocity block data from the open file. @@ -1034,10 +1046,14 @@ def __read_fileindex_for_cellid_rank(self, rank): if(not isinstance(cellids, Iterable)): cellids=[ cellids ] self.__rankwise_fileindex_for_cellid[rank] = {cellid: domain_offsets[rank]+index for index,cellid in enumerate(cellids)} + self.__loaded_fileindex_ranks.add(rank) + self.__fileindex_for_cellid.update(self.__rankwise_fileindex_for_cellid[rank]) def get_cellid_locations_rank(self, rank): ''' Returns a dictionary with cell id as the key and the index of the cell id as the value. + So far this pipeline will update the main fileindex, as it is not too heavy an operation - but + moving towards handling data more rankwise may be nice some time in the future. :param rank: Find (and initialize) fileindex for cells contained by this rank @@ -1244,8 +1260,80 @@ def read_with_offset(self, datatype,variable_offset, read_size, read_offsets, el arraydata.append(data) if len(read_offsets) !=1: data = np.array(arraydata) + return data + def get_read_offsets(self, cellids, array_size, element_size, vector_size): + ''' Figure out somewhat optimal offsets for read operations + ''' + + # if isinstance(cellids, numbers.Number): + # if cellids >= 0: + # read_filelayout = True + # else: # cellids = -1 + # read_filelayout = False + # else: + # read_filelayout = True + # # Here could be a conditional optimization for unique CellIDs, + # # but it actually makes a very large query slower. + + # if (len( self.__fileindex_for_cellid ) == 0) and read_filelayout: + # # Do we need to construct the cellid index? + # self.__read_fileindex_for_cellid() + # Define efficient method to read data in + reorder_data = False + if not isinstance(cellids, numbers.Number): + # Read multiple specified cells + # If we're reading a large amount of single cells, it'll be faster to just read all + # data from the file system and sort through it. For the CSC disk system, this + # becomes more efficient for over ca. 5000 cellids. + if len(cellids) >5000: # TODO re-evaluate threshold + reorder_data = True + result_size = len(cellids) + read_size = array_size + read_offsets = [0] + else: # Read multiple cell ids one-by-one + try: + result_size = len(cellids) + read_size = 1 + read_offsets = [self.__fileindex_for_cellid[cid]*element_size*vector_size for cid in cellids] + except: + self.__read_fileindex_for_cellid() + result_size = len(cellids) + read_size = 1 + read_offsets = [self.__fileindex_for_cellid[cid]*element_size*vector_size for cid in cellids] + else: # single cell or all cells + if cellids < 0: # -1, read all cells + result_size = array_size + read_size = array_size + read_offsets = [0] + else: # single cell id + if self.__full_fileindex_for_cellid: # Fileindex already exists, we might as well use it + result_size = 1 + read_size = 1 + read_offsets = [self.__fileindex_for_cellid[cellids]*element_size*vector_size] + elif self.__cellid_spatial_index != None: + # Here a faster alternative + result_size = 1 + read_size = 1 + eps = self.get_grid_epsilon() + qp = self.get_cell_coordinates(cellids) + qqp = np.hstack((qp-eps,qp+eps)) + pq = np.array([qqp[0],qqp[3],qqp[1],qqp[4],qqp[2],qqp[5]]) + rankids = self.__cellid_spatial_index.intersection() + read_offsets = None + for rankid in rankids: + indexdict = self.get_cellid_locations_rank(rankid) + if cellids in indexdict.keys(): + read_offsets = [indexdict[cellids]*element_size*vector_size] + else: + self.__read_fileindex_for_cellid() + result_size = 1 + read_size = 1 + read_offsets = [self.__fileindex_for_cellid[cellids]*element_size*vector_size] + + return read_size, read_offsets, result_size, reorder_data + def read(self, name="", tag="", mesh="", operator="pass", cellids=-1): ''' Read data from the open vlsv file. @@ -1268,24 +1356,9 @@ def read(self, name="", tag="", mesh="", operator="pass", cellids=-1): # Force lowercase name for internal checks name = name.lower() - if tag == "VARIABLE": - if (name,operator) in self.variable_cache.keys(): + if (name,operator) in self.__variable_cache.keys(): return self.read_variable_from_cache(name, cellids, operator) - - if isinstance(cellids, numbers.Number): - if cellids >= 0: - read_filelayout = True - else: # cellids = -1 - read_filelayout = False - else: - read_filelayout = True - # Here could be a conditional optimization for unique CellIDs, - # but it actually makes a very large query slower. - - if (len( self.__fileindex_for_cellid ) == 0) and read_filelayout: - # Do we need to construct the cellid index? - self.__read_fileindex_for_cellid() # Get population and variable names from data array name if '/' in name: @@ -1324,32 +1397,13 @@ def read(self, name="", tag="", mesh="", operator="pass", cellids=-1): datatype = child.attrib["datatype"] variable_offset = ast.literal_eval(child.text) - # Define efficient method to read data in - reorder_data = False - try: # try-except to see how many cellids were given - lencellids=len(cellids) - # Read multiple specified cells - # If we're reading a large amount of single cells, it'll be faster to just read all - # data from the file system and sort through it. For the CSC disk system, this - # becomes more efficient for over ca. 5000 cellids. - if lencellids>5000: - reorder_data = True - result_size = len(cellids) - read_size = array_size - read_offsets = [0] - else: # Read multiple cell ids one-by-one - result_size = len(cellids) - read_size = 1 - read_offsets = [self.__fileindex_for_cellid[cid]*element_size*vector_size for cid in cellids] - except: # single cell or all cells - if cellids < 0: # -1, read all cells - result_size = array_size - read_size = array_size - read_offsets = [0] - else: # single cell id - result_size = 1 - read_size = 1 - read_offsets = [self.__fileindex_for_cellid[cellids]*element_size*vector_size] + if not isinstance(cellids, numbers.Number): + cellids = np.array(cellids, dtype=np.int64) + cellids_nonzero = cellids[cellids!=0] + else: + cellids_nonzero = cellids + + read_size, read_offsets, result_size, reorder_data = self.get_read_offsets(cellids_nonzero, array_size, element_size, vector_size) data = self.read_with_offset(datatype, variable_offset, read_size, read_offsets, element_size, vector_size) @@ -1359,8 +1413,12 @@ def read(self, name="", tag="", mesh="", operator="pass", cellids=-1): arraydata = np.array(data) if vector_size > 1: arraydata=arraydata.reshape(-1, vector_size) - - append_offsets = [self.__fileindex_for_cellid[cid] for cid in cellids] + + mask = ~dict_keys_exist(self.__fileindex_for_cellid, cellids_nonzero) + + self.do_partial_fileindex_update(self.get_cell_coordinates(cellids_nonzero[mask])) + + append_offsets = [self.__fileindex_for_cellid[cid] for cid in cellids_nonzero] data = arraydata[append_offsets,...] data = np.squeeze(data) @@ -1370,6 +1428,11 @@ def read(self, name="", tag="", mesh="", operator="pass", cellids=-1): if vector_size > 1: data=data.reshape(result_size, vector_size) + + if not isinstance(cellids, numbers.Number): + data_out = np.full_like(data, np.nan, shape=(len(cellids),*data.shape[1:])) + data_out[cellids!=0,:] = data + data = data_out # If variable vector size is 1, and requested magnitude, change it to "absolute" if vector_size == 1 and operator=="magnitude": @@ -1800,7 +1863,10 @@ def read_interpolated_variable(self, name, coords, operator="pass",periodic=[Tru closest_cell_ids = self.get_cellid(coordinates) if method.lower() == "nearest": - final_values = self.read_variable(name, cellids=closest_cell_ids, operator=operator) + if(coordinates.shape[0] > 1): + final_values = self.read_variable(name, cellids=closest_cell_ids, operator=operator) + else: # no need to re-read if we did the test_variable already! + final_values = test_variable if stack: return final_values.squeeze() else: @@ -2179,26 +2245,7 @@ def read_variable_from_cache(self, name, cellids, operator): .. seealso:: :func:`read_variable` ''' - var_data = self.variable_cache[(name,operator)] - if var_data.ndim == 2: - value_len = var_data.shape[1] - else: - value_len = 1 - - if isinstance(cellids, numbers.Number): - if cellids == -1: - return var_data - else: - return var_data[self.__fileindex_for_cellid[cellids]] - else: - if(len(cellids) > 0): - indices = np.array(itemgetter(*cellids)(self.__fileindex_for_cellid),dtype=np.int64) - else: - indices = np.array([],dtype=np.int64) - if value_len == 1: - return var_data[indices] - else: - return var_data[indices,:] + return self.__variable_cache.read_variable_from_cache(name,cellids,operator) def read_variable_to_cache(self, name, operator="pass"): @@ -2210,10 +2257,10 @@ def read_variable_to_cache(self, name, operator="pass"): ''' # add data to dict, use a tuple of (name,operator) as the key [tuples are immutable and hashable] - self.variable_cache[(name,operator)] = self.read_variable(name, cellids=-1,operator=operator) + self.__variable_cache[(name,operator)] = self.read_variable(name, cellids=-1,operator=operator) # Also initialize the fileindex dict at the same go because it is very likely something you want to have for accessing cached values self.__read_fileindex_for_cellid() - return self.variable_cache[(name,operator)] + return self.__variable_cache[(name,operator)] def read_variable(self, name, cellids=-1,operator="pass"): ''' Read variables from the open vlsv file. @@ -2226,8 +2273,8 @@ def read_variable(self, name, cellids=-1,operator="pass"): .. seealso:: :func:`read` :func:`read_variable_info` ''' cellids = get_data(cellids) - if((name,operator) in self.variable_cache.keys()): - return self.read_variable_from_cache(name,cellids,operator) + if((name,operator) in self.__variable_cache.keys()): + return self.__variable_cache.read_variable_from_cache(name,cellids,operator) # Wrapper, check if requesting an fsgrid variable if (self.check_variable(name) and (name.lower()[0:3]=="fg_")): @@ -2344,16 +2391,20 @@ def get_max_refinement_level(self): ''' Returns the maximum refinement level of the AMR ''' if self.__max_spatial_amr_level < 0: - # Read the file index for cellid - cellids=self.read(mesh="SpatialGrid",name="CellID", tag="VARIABLE") - maxcellid = np.int64(np.amax([cellids])) - - AMR_count = np.int64(0) - while (maxcellid > 0): - maxcellid -= 2**(3*(AMR_count))*(self.__xcells*self.__ycells*self.__zcells) - AMR_count += 1 - - self.__max_spatial_amr_level = AMR_count - 1 + try: + self.__max_spatial_amr_level = self.read_attribute(name="SpatialGrid", attribute="max_refinement_level",tag="MESH") + except: + # Read the file index for cellid - should maybe rather be runtime? + cellids=self.read(mesh="SpatialGrid",name="CellID", tag="VARIABLE") + maxcellid = np.int64(np.amax([cellids])) + + AMR_count = np.int64(0) + while (maxcellid > 0): + maxcellid -= 2**(3*(AMR_count))*(self.__xcells*self.__ycells*self.__zcells) + AMR_count += 1 + + self.__max_spatial_amr_level = AMR_count - 1 + return self.__max_spatial_amr_level def get_amr_level(self,cellid): @@ -2586,6 +2637,68 @@ def get_unique_cellids(self, coords): cidsout = np.array(list(OrderedDict.fromkeys(cids))) return cidsout + def do_partial_fileindex_update(self, coords): + ''' Ensure the cellids corresponding to coords or within query_window are mapped in the __fileindex_for_cellid dict. + + Tries to minimize the additional construction of the fileindex hashtable by spatial indexing. + ''' + if hasattr(self,"skipread"): + return + if coords.shape[0] == 0: + return + + if self.__cellid_spatial_index == None: + self.__read_fileindex_for_cellid() + return + + # If the query bounding box volume is small, we really should use spatial indexing instead of loading the whole + # domain + volume_threshold = 0.01 + + mins = np.min(coords, axis=0)-self.get_grid_epsilon() + maxs = np.max(coords, axis=0)+self.get_grid_epsilon() + query_window = np.array([mins[0],maxs[0],mins[1],maxs[1],mins[2],maxs[2]]) + + + if(coords.shape[0] == 1): + rankids = set(self.__cellid_spatial_index.intersection(query_window)) + rankids = rankids - self.__loaded_fileindex_ranks + for rankid in rankids: + self.get_cellid_locations_rank(rankid) + + + query_volume = np.prod(maxs-mins) + full_domain_extents = self.get_fsgrid_mesh_extent() + full_domain_mins = full_domain_extents[0:3] + full_domain_maxs = full_domain_extents[3:6] + full_domain_volume = np.prod(full_domain_maxs-full_domain_mins) + + if query_volume < volume_threshold*full_domain_volume: # If we are querying a small volume, it is fast to do a partial update. + rankids = set(self.__cellid_spatial_index.intersection(query_window)) + rankids = rankids - self.__loaded_fileindex_ranks + if(len(rankids)>0): + # print("single box") + for rankid in rankids: + self.get_cellid_locations_rank(rankid) + else: + # print("small boxes") + query_windows = np.zeros((coords.shape[0],6)) + query_windows[:,0::2] = coords - self.get_grid_epsilon() + query_windows[:,1::2] = coords + self.get_grid_epsilon() + rankids = set() + for qw in query_windows: + rankids.update(self.__cellid_spatial_index.intersection(qw)) + rankids = rankids - self.__loaded_fileindex_ranks + # print(len(rankids), "ranks to load") + if len(rankids) < self.read_parameter("numWritingRanks")/8: + # print("few ranks") + for rankid in rankids: + self.get_cellid_locations_rank(rankid) + else: # Fallback: read everything + # print("Fallback") + self.__read_fileindex_for_cellid() + + def get_cellid(self, coords): ''' Returns the cell ids at given coordinates @@ -2606,7 +2719,12 @@ def get_cellid(self, coords): # If needed, read the file index for cellid # if len(self.__fileindex_for_cellid) == 0: - self.__read_fileindex_for_cellid() + if hasattr(self,"skipread"): + pass + else: + self.do_partial_fileindex_update(coordinates) + + #good_ids = self.read_variable("CellID") # good_ids = np.array(list(self.__fileindex_for_cellid.keys())) # good_ids.sort() @@ -4081,6 +4199,9 @@ def load_neighbor_stencils_from_filecache(self): else: self.__neighbors_cache_loaded = True + def set_cellid_spatial_index(self, force=False): + self.__cellid_spatial_index = self.__metadata_cache.get_cellid_spatial_index(force) + def clear_cache_folder(self): path = self.get_cache_folder() import shutil diff --git a/pyproject.toml b/pyproject.toml index fc8952c0..88980bff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,7 @@ dependencies = [ "packaging", "scikit-image", "h5py", + "tree" ] [project.optional-dependencies] none = [ From 7ed31f4d690001c519c427ff3c0b3df3a508de1a Mon Sep 17 00:00:00 2001 From: Markku Alho Date: Tue, 26 Aug 2025 11:01:45 +0300 Subject: [PATCH 047/124] Housekeeping functions (clear, build some caches), revert neighbor stencil caching to work from VlsvReader since it is stillth ere --- analysator/pyVlsv/vlsvreader.py | 138 +++++++++++++++++++------------- 1 file changed, 82 insertions(+), 56 deletions(-) diff --git a/analysator/pyVlsv/vlsvreader.py b/analysator/pyVlsv/vlsvreader.py index e6b4dc7e..d64ecc6e 100644 --- a/analysator/pyVlsv/vlsvreader.py +++ b/analysator/pyVlsv/vlsvreader.py @@ -309,31 +309,6 @@ def get_grid_epsilon(self): self.__grid_epsilon = 1e-3*np.array([self.__dx, self.__dy, self.__dz])/2**8 return self.__grid_epsilon - def get_linked_readers_filename(self): - '''Need to go to a consolidated metadata handler''' - pth, base = os.path.split(self.file_name) - - s = os.path.join(pth,"vlsvmeta",base[:-5]+"_linked_readers.pkl") - return s - - def get_linked_readers(self, reload=False): - self.__linked_files = self.get_reader_metadata("linked_reader_files", set()) - if len(self.__linked_files)==0 or reload: - if(os.path.isfile(self.get_linked_readers_filename())): - with open(self.get_linked_readers_filename(), 'rb') as f: - l = pickle.load(f) - logging.info("Loaded linked readers from "+self.get_linked_readers_filename()) - self.__linked_files.update(l) - print(l) - - else: - self.add_linked_readers() - - self.add_metadata("linked_reader_files",self.__linked_files) - - - - return self.__linked_readers def get_metadata_filename(self): pth, base = os.path.split(self.file_name) @@ -402,6 +377,31 @@ def save_metadata(self): except Exception as e: logging.warning("Could not save metadata file, error: "+str(e)) + def get_linked_readers_filename(self): + '''Need to go to a consolidated metadata handler''' + pth, base = os.path.split(self.file_name) + + s = os.path.join(self.__metadata_cache.get_cache_folder(),base[:-5]+"_linked_readers.txt") + return s + + def get_linked_readers(self, reload=False): + self.__linked_files = self.get_reader_metadata("linked_reader_files", set()) + if len(self.__linked_files)==0 or reload: + if(os.path.isfile(self.get_linked_readers_filename())): + with open(self.get_linked_readers_filename(), 'r') as f: + l = f.readlines() + logging.info("Loaded linked readers from "+self.get_linked_readers_filename()) + self.__linked_files.update(l) + print(l) + + else: + self.add_linked_readers() + + self.add_metadata("linked_reader_files",self.__linked_files) + + return self.__linked_readers + + def add_linked_file(self, fname): if os.path.exists(fname): self.__linked_files.add(VlsvReader(fname)) @@ -421,15 +421,31 @@ def add_linked_readers(self): for fname in self.__linked_files: self.add_linked_reader(fname) - def save_linked_readers_file(self): + def save_linked_readers_file(self, overwrite = False): fn = self.get_linked_readers_filename() + if not overwrite: # Load existing linked reader to not overwrite everything + self.get_linked_readers() + logging.info("Saving linked readers to "+fn) dn = os.path.dirname(fn) if not os.path.isdir(dn): os.mkdir(dn) - with open(fn,'wb') as f: - pickle.dump(self.__linked_files, f) + with open(fn,'w') as f: + lines = [] + for line in self.__linked_files: + lines += (line+os.linesep) # gather from set and insert line swap + f.writelines(lines) + def clear_linked_readers(self): + self.__linked_files.clear() + self.__linked_readers.clear() + + def clear_linked_readers_file(self): + fn = self.get_linked_readers_filename() + if os.path.exists(fn): + os.remove(fn) + else: + logging.info("Tried to remove nonexisting linked reader file "+fn) def __popmesh(self, popname): ''' Get the population-specific vspace mesh info object, and initialize it if it does not exist @@ -2654,6 +2670,7 @@ def do_partial_fileindex_update(self, coords): # If the query bounding box volume is small, we really should use spatial indexing instead of loading the whole # domain volume_threshold = 0.01 + rank_ratio_threshold = 1./8. mins = np.min(coords, axis=0)-self.get_grid_epsilon() maxs = np.max(coords, axis=0)+self.get_grid_epsilon() @@ -2681,22 +2698,25 @@ def do_partial_fileindex_update(self, coords): for rankid in rankids: self.get_cellid_locations_rank(rankid) else: - # print("small boxes") - query_windows = np.zeros((coords.shape[0],6)) - query_windows[:,0::2] = coords - self.get_grid_epsilon() - query_windows[:,1::2] = coords + self.get_grid_epsilon() - rankids = set() - for qw in query_windows: - rankids.update(self.__cellid_spatial_index.intersection(qw)) - rankids = rankids - self.__loaded_fileindex_ranks - # print(len(rankids), "ranks to load") - if len(rankids) < self.read_parameter("numWritingRanks")/8: - # print("few ranks") - for rankid in rankids: - self.get_cellid_locations_rank(rankid) - else: # Fallback: read everything - # print("Fallback") + if coords.shape[0] > 0.001*self.read_attribute(name="CellID",mesh="SpatialGrid",attribute="arraysize",tag="VARIABLE"): + # Guess that there are too many cellids to handle even the intersection search sensibly self.__read_fileindex_for_cellid() + else: + query_windows = np.zeros((coords.shape[0],6)) + query_windows[:,0::2] = coords - self.get_grid_epsilon() + query_windows[:,1::2] = coords + self.get_grid_epsilon() + rankids = set() + for qw in query_windows: # There is a bulk intersection_v function but it does not work? + rankids.update(self.__cellid_spatial_index.intersection(qw)) + rankids = rankids - self.__loaded_fileindex_ranks + # print(len(rankids), "ranks to load") + if len(rankids) < self.read_parameter("numWritingRanks")*rank_ratio_threshold: + # print("few ranks") + for rankid in rankids: + self.get_cellid_locations_rank(rankid) + else: # Fallback: read everything + # print("Fallback") + self.__read_fileindex_for_cellid() def get_cellid(self, coords): @@ -4176,26 +4196,26 @@ def cache_neighbor_stencils(self): self.load_neighbor_stencils_from_filecache() path = self.get_cache_folder() os.makedirs(path,exist_ok=True) - cache_file_neighbors = "neighbors_cache.pkl" - with open(os.path.join(path,cache_file_neighbors),'wb') as cache: + cache_file_neighbors = os.path.join(path, "neighbors_cache.pkl") + with open(cache_file_neighbors,'wb') as cache: pickle.dump({ - "cell_neighbours": self._VlsvReader__cell_neighbours, - "cell_vertices":self._VlsvReader__cell_vertices, - "cell_corner_vertices":self._VlsvReader__cell_corner_vertices} + "cell_neighbours": self.__cell_neighbours, + "cell_vertices":self.__cell_vertices, + "cell_corner_vertices":self.__cell_corner_vertices} ,cache) def load_neighbor_stencils_from_filecache(self): if self.__neighbors_cache_available and not self.__neighbors_cache_loaded: path = self.get_cache_folder() - cache_file_neighbors = "neighbors_cache.pkl" - if(os.path.isfile(os.path.join(path,cache_file_neighbors))): - with open(os.path.join(path,cache_file_neighbors),'rb') as cache: + cache_file_neighbors = os.path.join(path, "neighbors_cache.pkl") + if(os.path.isfile(cache_file_neighbors)): + with open(cache_file_neighbors,'rb') as cache: loaded = pickle.load(cache) - self._VlsvReader__cell_neighbours.update(loaded["cell_neighbours"]) - self._VlsvReader__cell_vertices.update(loaded["cell_vertices"]) - self._VlsvReader__cell_corner_vertices.update(loaded["cell_corner_vertices"]) - self.__neighbors_cache_len = len(self._VlsvReader__cell_neighbours) + self.__cell_neighbours.update(loaded["cell_neighbours"]) + self.__cell_vertices.update(loaded["cell_vertices"]) + self.__cell_corner_vertices.update(loaded["cell_corner_vertices"]) + self.__neighbors_cache_len = len(self.__cell_neighbours) else: self.__neighbors_cache_loaded = True @@ -4205,4 +4225,10 @@ def set_cellid_spatial_index(self, force=False): def clear_cache_folder(self): path = self.get_cache_folder() import shutil - shutil.rmtree(path) \ No newline at end of file + shutil.rmtree(path) + + def cache_optimization_files(self, force=False): + ''' Create cached optimization files for this reader object (e.g. spatial index) + + ''' + self.__metadata_cache.get_cellid_spatial_index(force) \ No newline at end of file From cc9fa74c881800f317a25d8ed73988aef9d52763 Mon Sep 17 00:00:00 2001 From: Markku Alho Date: Tue, 26 Aug 2025 11:18:23 +0300 Subject: [PATCH 048/124] Dependency cleaning --- analysator/pyVlsv/vlsvcache.py | 12 +++++------- pyproject.toml | 1 - requirements.txt | 2 +- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/analysator/pyVlsv/vlsvcache.py b/analysator/pyVlsv/vlsvcache.py index a870c5d4..3ce02fc2 100644 --- a/analysator/pyVlsv/vlsvcache.py +++ b/analysator/pyVlsv/vlsvcache.py @@ -33,6 +33,7 @@ from operator import itemgetter import re import rtree +import time class VariableCache: ''' Class for handling in-memory variable/reducer caching. @@ -134,21 +135,18 @@ def set_cellid_spatial_index(self, force = False): os.makedirs(self.get_cache_folder()) if(force or (not os.path.isfile(self.__rtree_idxfile) or not os.path.isfile(self.__rtree_datfile))): - - + t0 = time.time() bboxes = self.__reader.get_mesh_domain_extents("SpatialGrid") bboxes = bboxes.reshape((-1,6), order='C') - print(bboxes.shape) - self.__rtree_index = rtree.index.Index(self.__rtree_idxfile[:-4],properties=rtree_properties, interleaved=False) + self.__rtree_index = rtree.index.Index(self.__rtree_idxfile[:-4],properties=self.__rtree_properties, interleaved=False) for rank, bbox in enumerate(bboxes): - print(rank, bbox) self.__rtree_index.insert(rank, bbox) - print("index set") + logging.info("index set in "+str(time.time()-t0)+" seconds") else: - print("index exists") + pass def get_cellid_spatial_index(self, force = False): if self.__rtree_index == None: diff --git a/pyproject.toml b/pyproject.toml index 88980bff..4f36381d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,6 @@ dependencies = [ "matplotlib", "packaging", "scikit-image", - "h5py", "tree" ] [project.optional-dependencies] diff --git a/requirements.txt b/requirements.txt index ccb71e27..98c72d07 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,4 @@ matplotlib scikit-image packaging vtk>=9.2 -h5py \ No newline at end of file +rtree \ No newline at end of file From 4df5e36cb91a955e2ea9f0eae710333a41f056e6 Mon Sep 17 00:00:00 2001 From: Markku Alho Date: Tue, 26 Aug 2025 13:26:17 +0300 Subject: [PATCH 049/124] Separate SpatialIndex get/set --- analysator/pyVlsv/vlsvcache.py | 4 +++- analysator/pyVlsv/vlsvreader.py | 9 ++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/analysator/pyVlsv/vlsvcache.py b/analysator/pyVlsv/vlsvcache.py index 3ce02fc2..fa21991d 100644 --- a/analysator/pyVlsv/vlsvcache.py +++ b/analysator/pyVlsv/vlsvcache.py @@ -150,8 +150,10 @@ def set_cellid_spatial_index(self, force = False): def get_cellid_spatial_index(self, force = False): if self.__rtree_index == None: - if(force or (not os.path.isfile(self.__rtree_idxfile) or not os.path.isfile(self.__rtree_datfile))): + if(force): self.set_cellid_spatial_index(force) + elif not os.path.isfile(self.__rtree_idxfile) or not os.path.isfile(self.__rtree_datfile): + self.__rtree_index = None else: self.__rtree_index = rtree.index.Index(self.__rtree_idxfile[:-4], properties=self.__rtree_properties, interleaved=False) diff --git a/analysator/pyVlsv/vlsvreader.py b/analysator/pyVlsv/vlsvreader.py index d64ecc6e..29b4257d 100644 --- a/analysator/pyVlsv/vlsvreader.py +++ b/analysator/pyVlsv/vlsvreader.py @@ -1447,7 +1447,7 @@ def read(self, name="", tag="", mesh="", operator="pass", cellids=-1): if not isinstance(cellids, numbers.Number): data_out = np.full_like(data, np.nan, shape=(len(cellids),*data.shape[1:])) - data_out[cellids!=0,:] = data + data_out[cellids!=0,...] = data data = data_out # If variable vector size is 1, and requested magnitude, change it to "absolute" @@ -4220,7 +4220,10 @@ def load_neighbor_stencils_from_filecache(self): self.__neighbors_cache_loaded = True def set_cellid_spatial_index(self, force=False): - self.__cellid_spatial_index = self.__metadata_cache.get_cellid_spatial_index(force) + self.__cellid_spatial_index = self.__metadata_cache.set_cellid_spatial_index(force) + + def get_cellid_spatial_index(self, force=False): + self.__cellid_spatial_index = self.__metadata_cache.set_cellid_spatial_index(force) def clear_cache_folder(self): path = self.get_cache_folder() @@ -4231,4 +4234,4 @@ def cache_optimization_files(self, force=False): ''' Create cached optimization files for this reader object (e.g. spatial index) ''' - self.__metadata_cache.get_cellid_spatial_index(force) \ No newline at end of file + self.__metadata_cache.set_cellid_spatial_index(force) \ No newline at end of file From f3e6ad1552bf06350c44a915ede2695e873e1238 Mon Sep 17 00:00:00 2001 From: Markku Alho Date: Tue, 26 Aug 2025 14:16:16 +0300 Subject: [PATCH 050/124] Guard against missing numWritingRanks, adjusted spatialindex getter --- analysator/pyCalculations/timeevolution.py | 1 + analysator/pyVlsv/vlsvcache.py | 5 ++- analysator/pyVlsv/vlsvreader.py | 40 +++++++++++++++++----- 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/analysator/pyCalculations/timeevolution.py b/analysator/pyCalculations/timeevolution.py index 9006c1bf..9421d12a 100644 --- a/analysator/pyCalculations/timeevolution.py +++ b/analysator/pyCalculations/timeevolution.py @@ -189,6 +189,7 @@ def point_time_evolution( vlsvReader_list, variables, coordinates, units="", met # For optimization purposes we are now freeing vlsvReader's memory # Note: Upon reading data vlsvReader created an internal hash map that takes a lot of memory vlsvReader.optimize_clear_fileindex_for_cellid() + vlsvReader.optimize_clear_fileindex_for_cellid_blocks() # Close the vlsv reader's file: vlsvReader.optimize_close_file() from output import output_1d diff --git a/analysator/pyVlsv/vlsvcache.py b/analysator/pyVlsv/vlsvcache.py index fa21991d..d7f23132 100644 --- a/analysator/pyVlsv/vlsvcache.py +++ b/analysator/pyVlsv/vlsvcache.py @@ -134,7 +134,10 @@ def set_cellid_spatial_index(self, force = False): if not os.path.exists(self.get_cache_folder()): os.makedirs(self.get_cache_folder()) - if(force or (not os.path.isfile(self.__rtree_idxfile) or not os.path.isfile(self.__rtree_datfile))): + if force: + os.remove(self.__rtree_idxfile) + os.remove(self.__rtree_datfile) + if(not os.path.isfile(self.__rtree_idxfile) or not os.path.isfile(self.__rtree_datfile)): t0 = time.time() bboxes = self.__reader.get_mesh_domain_extents("SpatialGrid") diff --git a/analysator/pyVlsv/vlsvreader.py b/analysator/pyVlsv/vlsvreader.py index 29b4257d..fb87e094 100644 --- a/analysator/pyVlsv/vlsvreader.py +++ b/analysator/pyVlsv/vlsvreader.py @@ -93,7 +93,7 @@ def fsGlobalIdToGlobalIndex(globalids, bbox): # Read in the global ids and indices for FsGrid cells, returns # min and max corners of the fsGrid chunk by rank def fsReadGlobalIdsPerRank(reader): - numWritingRanks = reader.read_parameter("numWritingRanks") + numWritingRanks = reader.get_numWritingRanks("fsgrid") rawData = reader.read(tag="MESH", name="fsgrid") bbox = reader.read(tag="MESH_BBOX", mesh="fsgrid") sizes = reader.read(tag="MESH_DOMAIN_SIZES", mesh="fsgrid") @@ -1040,14 +1040,29 @@ def get_mesh_domain_sizes(self, mesh): self.__mesh_domain_sizes[mesh] = self.read(name="", tag="MESH_DOMAIN_SIZES", mesh=mesh) return self.__mesh_domain_sizes[mesh] + def get_numWritingRanks(self, mesh): + ''' Get the number of writing ranks. This does not exist for all outputfiles eerywhere. + ''' + ranks = None + if "numWritingRanks" in self.__params_cache.keys(): + return self.__params_cache["numWritingRanks"] + else: + try: + ranks = self.read_parameter("numWritingRanks") + except: + ranks = self.read_attribute(mesh=mesh, attribute="arraysize", tag="MESH_DOMAIN_SIZES") + self.__params_cache["numWritingRanks"] = ranks + + return ranks + def __read_fileindex_for_cellid_rank(self, rank): """ Read in the cell ids and create an internal dictionary to give the index of an arbitrary cellID """ # print("filling rank",rank) - if rank > self.read_parameter("numWritingRanks"): - raise ValueError("Tried to read rank "+rank+" out of "+self.read_parameter("numWritingRanks")) + if rank > self.get_numWritingRanks("SpatialGrid"): + raise ValueError("Tried to read rank "+rank+" out of "+self.get_numWritingRanks("SpatialGrid")) if not self.__rankwise_fileindex_for_cellid.get(rank,{}) == {}: return @@ -2111,7 +2126,7 @@ def get_fsgrid_decomposition(self): # Decomposition is a list (or fail assertions below) - use it instead pass - numWritingRanks = self.read_parameter("numWritingRanks") + numWritingRanks = self.get_numWritingRanks("SpatialGrid") assert len(self.__fsGridDecomposition) == 3, "Manual FSGRID decomposition should have three elements, but is "+str(self.__fsGridDecomposition) assert np.prod(self.__fsGridDecomposition) == numWritingRanks, "Manual FSGRID decomposition should have a product of numWritingRanks ("+str(numWritingRanks)+"), but is " + str(np.prod(self.__fsGridDecomposition)) + " for decomposition "+str(self.__fsGridDecomposition) @@ -2138,7 +2153,7 @@ def read_fsgrid_variable(self, name, operator="pass"): rawData = self.read(mesh='fsgrid', name=name, tag="VARIABLE", operator=operator) # Determine fsgrid domain decomposition - numWritingRanks = self.read_parameter("numWritingRanks") + numWritingRanks = self.get_numWritingRanks("SpatialGrid") if len(rawData.shape) > 1: orderedData = np.zeros([bbox[0],bbox[1],bbox[2],rawData.shape[1]]) else: @@ -2663,7 +2678,8 @@ def do_partial_fileindex_update(self, coords): if coords.shape[0] == 0: return - if self.__cellid_spatial_index == None: + if self.get_cellid_spatial_index() == None: + self.__read_fileindex_for_cellid() return @@ -2710,7 +2726,7 @@ def do_partial_fileindex_update(self, coords): rankids.update(self.__cellid_spatial_index.intersection(qw)) rankids = rankids - self.__loaded_fileindex_ranks # print(len(rankids), "ranks to load") - if len(rankids) < self.read_parameter("numWritingRanks")*rank_ratio_threshold: + if len(rankids) < self.get_numWritingRanks("SpatialGrid")*rank_ratio_threshold: # print("few ranks") for rankid in rankids: self.get_cellid_locations_rank(rankid) @@ -4223,7 +4239,15 @@ def set_cellid_spatial_index(self, force=False): self.__cellid_spatial_index = self.__metadata_cache.set_cellid_spatial_index(force) def get_cellid_spatial_index(self, force=False): - self.__cellid_spatial_index = self.__metadata_cache.set_cellid_spatial_index(force) + if not force: + if self.__cellid_spatial_index is None: + self.__cellid_spatial_index = self.__metadata_cache.get_cellid_spatial_index(force) + else: + pass + else: + self.__cellid_spatial_index = self.__metadata_cache.set_cellid_spatial_index(force) + + return self.__cellid_spatial_index def clear_cache_folder(self): path = self.get_cache_folder() From 17dccb873e9db8299664c64bc2571ce37f58df0b Mon Sep 17 00:00:00 2001 From: jreimi Date: Tue, 26 Aug 2025 14:56:27 +0300 Subject: [PATCH 051/124] removed extra vtp save that shouldn't be there yet --- scripts/magnetopause.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/magnetopause.py b/scripts/magnetopause.py index 3a4f9a12..92220821 100644 --- a/scripts/magnetopause.py +++ b/scripts/magnetopause.py @@ -59,7 +59,6 @@ def magnetopause(datafilen, method="beta_star_with_connectivity", own_tresholds= vertices, manual_vtkSurface = pt.calculations.find_magnetopause_sw_streamline_3d(datafilen, seeds_n=300, seeds_x0=seeds_x0, seeds_range=[-5*6371000, 5*6371000], dl=dl, iterations=iters, end_x=xmin+10*6371000, x_point_n=200, sector_n=sector_n) - write_vtk_surface_to_file(manual_vtkSurface, "/wrk-vakka/users/jreimi/magnetosphere_classification/FID/FID_magnetopause_SWnf_manual_t1100.vtp") # make the magnetopause surface from vertice points np.random.shuffle(vertices) # helps Delaunay triangulation vtkSurface, SDF = regions.vtkDelaunay3d_SDF(query_points, vertices, Delaunay_alpha) From 23b4db18670505c4cbc3180ea902f657cc3e62a0 Mon Sep 17 00:00:00 2001 From: jreimi Date: Wed, 27 Aug 2025 15:33:24 +0300 Subject: [PATCH 052/124] Better optional arguments for regions --- scripts/regions.py | 298 ++++++++++++++++++++++----------------------- 1 file changed, 145 insertions(+), 153 deletions(-) diff --git a/scripts/regions.py b/scripts/regions.py index a911bb2c..a7e05f92 100644 --- a/scripts/regions.py +++ b/scripts/regions.py @@ -1,24 +1,14 @@ """Script and functions for creating sidecar files with SDF/region/boundary tags of plasma regions. - variables used to find regions/boundary regions: - rho, temperature, beta, beta_star, - - - """ import analysator as pt import numpy as np import vtk from scripts import magnetopause -#from .analysator.scripts import shue - R_E = 6371000 -### Signed distance field functions: ### - - def vtkDelaunay3d_SDF(query_points, coordinates, alpha=None): """Gives a signed distance to a convex hull or alpha shape surface created from given coordinates. Note: if using alpha, SDF most likely won't work! @@ -42,6 +32,10 @@ def vtkDelaunay3d_SDF(query_points, coordinates, alpha=None): delaunay_3d.SetInputData(polydata) if alpha is not None: delaunay_3d.SetAlpha(alpha) + delaunay_3d.SetAlphaTets(1) + delaunay_3d.SetAlphaLines(0) + delaunay_3d.SetAlphaTris(0) + delaunay_3d.SetAlphaVerts(0) delaunay_3d.Update() @@ -71,13 +65,13 @@ def treshold_mask(data_array, value): :param data_array: array to mask, e.g. output of f.read_variable(name="proton/vg_rho", cellids=-1) :param variable: str, variable name :param value: value/values to use for masking; a float or int for exact match, a (value, relative tolerance) tuple, or [min value, max value] list pair where either can be None for less than or eq./more than or eq. value - :returns: 0/1 mask in same order as cellids, 1: variable value in array inside treshold values, 0: outside + :returns: 0/1 mask in same order as data_array, 1: variable value in array inside treshold values, 0: outside """ if (data_array is None) or (value is None) or (np.isnan(data_array[0])): # either variable isn't usable or treshold has not been given return None - mask = np.zeros((len(data_array))) + #mask = np.zeros((len(data_array))) if isinstance(value, float) or isinstance(value, int): # single value, exact mask = np.where(np.isclose(data_array, value), 1, 0) @@ -238,23 +232,40 @@ def bowshock_SDF(f, variable_dict, query_points, own_condition_dict=None): -def RegionFlags(datafile, outfilen, regions=["all"], ignore_boundaries=True, magnetopause_kwargs={}): - +def RegionFlags(datafile, outfilen, regions=["all"], ignore_boundaries=True, region_flag_type="01", magnetopause_kwargs={}, region_conditions={}): """Creates a sidecar .vlsv file with flagged cells for regions and boundaries in near-Earth plasma environment. - Region flags (start with flag_, flags are fractions of filled conditions or 1/0): magnetosheath, magnetosphere, cusps, lobe_N, lobe_S, central_plasma_sheet, PSBL + Region flags (start with flag_, flags are fractions of filled conditions or 1/0): magnetosheath, magnetosphere, cusps, lobe_N, lobe_S, central_plasma_sheet Boundary signed distance flags (start with SDF_, flags are signed distances to boundary in m with inside being negative distance): magnetopause, bowshock possilbe regions: "all", "boundaries" (magnetopause, bow shock), "large_areas" (boundaries + upstream, magnetosheath, magnetosphere), "magnetosphere", "bowshock", - "cusps", "lobes", "central_plasma_sheet", "boundary_layers" (incl. central plasma sheet BL, HLBL, LLBL; note: not reliable/working atm) + "cusps", "lobes", "central_plasma_sheet" - Note that different runs may need different tresholds for region parameters and region accuracy should be verified visually + Note that different runs may need different tresholds for region parameters and region accuracy should be verified visually + Beta* convex hull is most likely the best magnetopause method for regions, for just magnetopause with different options use magnetopause.py - :param datafile: .vlsv bulk file name (and path) - :param outfilen: sidecar .vlsv file name (and path) - :kword ignore_boundaries: True: do not take cells in the inner/outer boundaries of the simulation into account when looking for regions - :kword magnetopause_method: default "beta_star", other options: "beta_star_with_connectivity", "streamlines", "shue" + :param datafile: .vlsv bulk file name (and path) + :param outfilen: sidecar .vlsv file name (and path) + :kword ignore_boundaries: True: do not take cells in the inner/outer boundaries of the simulation into account when looking for regions (applicable for cusps and CPS for now) + :kword region_flag_type: "01" or "fraction", whether flags are binary (all conditions must be satisfied) or fractions of how many given conditions are met + :kword magnetopause_method: default "beta_star", other options: see magnetopause.py, use alpha=None + :kword region_conditions: optional dict where keys are str region names and values are condition dictionaries, for setting own conditions for bow shock, cusps, lobes or central plasma sheet """ + if "all" in regions: + regions.extend(["magnetopause", "bowshock", "upstream", "magnetosheath", "magnetosphere", "cusps", "lobes", "central_plasma_sheet"]) + else: + if "large_areas" in regions: + regions.extend(["magnetopause", "bowshock", "upstream", "magnetosheath", "magnetosphere"]) + if "boundaries" in regions: + regions.extend(["magnetopause", "bowshock"]) + if "cusps" in regions or "central_plasma_sheet" in regions: # for cusps and central plasma sheet we need the magnetoshpere + regions.extend(["magnetosphere"]) + if "magnetosphere" in regions or "magnetosheath" in regions: + regions.extend(["magnetopause"]) + if "bowshock" in regions or "magnetosheath" in regions or "upstream" in regions: + regions.extend(["bowshock"]) + + f = pt.vlsvfile.VlsvReader(file_name=datafile) cellids =f.read_variable("CellID") @@ -262,12 +273,10 @@ def RegionFlags(datafile, outfilen, regions=["all"], ignore_boundaries=True, mag all_points = f.get_cell_coordinates(cellids) cellIDdict = f.get_cellid_locations() - - ## writer ## writer = pt.vlsvfile.VlsvWriter(f, outfilen) writer.copy_variables_list(f, ["CellID"]) - writer.copy_variables(f, varlist=["proton/vg_rho" , "vg_beta_star", "vg_temperature", "vg_b_vol", "vg_J", "vg_beta", "vg_connection", "vg_boundarytype"]) + #writer.copy_variables(f, varlist=["proton/vg_rho" , "vg_beta_star", "vg_temperature", "vg_b_vol", "vg_J", "vg_beta", "vg_connection", "vg_boundarytype"]) # variable data dictionary @@ -315,157 +324,153 @@ def errormsg(varstr): print("{} could not be read, will be ignored".format(varst #return 0 - # upstream point + # upstream point # this could probably be done better than just a random point in sw upstream_point = [xmax-10*R_E,0.0,0.0] upstream_cellid = f.get_cellid(upstream_point) upstream_index = cellIDdict[upstream_cellid] ## MAGNETOPAUSE ## - if magnetopause_kwargs: - __, magnetopause_SDF = magnetopause.magnetopause(datafile, **magnetopause_kwargs) - else: - __, magnetopause_SDF = magnetopause.magnetopause(datafile, method="beta_star_with_connectivity", Delaunay_alpha=None) # default magnetopause: beta*+ B connectivity convex hull - write_flags(writer, magnetopause_SDF, 'SDF_magnetopause') - write_flags(writer, np.where(np.abs(magnetopause_SDF) < 5e6, 1, 0), "flag_magnetopause") - - # save some magnetopause values for later - magnetopause_density = np.mean(variables["density"][np.abs(magnetopause_SDF) < 5e6]) - print(f"{magnetopause_density=}") - magnetopause_temperature = np.mean(variables["temperature"][np.abs(magnetopause_SDF) < 5e6]) - print(f"{magnetopause_temperature=}") + if "magnetopause" in regions: + if magnetopause_kwargs: + __, magnetopause_SDF = magnetopause.magnetopause(datafile, **magnetopause_kwargs) + else: + __, magnetopause_SDF = magnetopause.magnetopause(datafile, method="beta_star_with_connectivity", Delaunay_alpha=None) # default magnetopause: beta*+ B connectivity convex hull + write_flags(writer, magnetopause_SDF, 'SDF_magnetopause') + write_flags(writer, np.where(np.abs(magnetopause_SDF) < 5e6, 1, 0), "flag_magnetopause") + + # save some magnetopause values for later + magnetopause_density = np.mean(variables["density"][np.abs(magnetopause_SDF) < 5e6]) + print(f"{magnetopause_density=}") + magnetopause_temperature = np.mean(variables["temperature"][np.abs(magnetopause_SDF) < 5e6]) + print(f"{magnetopause_temperature=}") ## MAGNETOSPHERE ## # magnetosphere from magentopause SDF - magnetosphere = np.where(magnetopause_SDF<0, 1, 0) - write_flags(writer, magnetosphere, 'flag_magnetosphere_convex') - - # magnetospshere from beta* and field line connectivity if possible # TODO - try: - connectivity_region = treshold_mask(variables["connection"], 0) - betastar_region = treshold_mask(variables["beta_star"], [0.0, 0.5]) - magnetosphere_proper = np.where((connectivity_region==1) | (betastar_region==1), 1, 0) - write_flags(writer, magnetosphere_proper, 'flag_magnetosphere') - except: - print("Non-Delaunay beta* magnetosphere could not be made") + if "magnetosphere" in regions: + magnetosphere = np.where(magnetopause_SDF<0, 1, 0) + write_flags(writer, magnetosphere, 'flag_magnetosphere') ## BOW SHOCK ## #TODO: similar kwargs system as magnetopause? # bow shock from rho - bowshock = bowshock_SDF(f, variables, all_points) - write_flags(writer, bowshock, 'SDF_bowshock') - write_flags(writer, np.where(np.abs(bowshock) < 5e6, 1, 0), "flag_bowshock") + if "bowshock" in regions: + if "bowshock" in region_conditions: + bowshock = bowshock_SDF(f, variables, all_points, own_condition_dict=region_conditions["bowshock"]) + else: + bowshock = bowshock_SDF(f, variables, all_points) # default upstream rho method, might fail with foreshock + write_flags(writer, bowshock, 'SDF_bowshock') + write_flags(writer, np.where(np.abs(bowshock) < 5e6, 1, 0), "flag_bowshock") - # magnetosphere+magnetosheath -area - inside_bowshock = np.where(bowshock<0, 1, 0) + # magnetosphere+magnetosheath -area + inside_bowshock = np.where(bowshock<0, 1, 0) ## MAGNETOSHEATH ## - # magnetosheath from bow shock-magnetosphere difference - magnetosheath_flags = np.where((inside_bowshock & 1-magnetosphere), 1, 0) - write_flags(writer, magnetosheath_flags, 'flag_magnetosheath') + if "magnetosheath" in regions: + # magnetosheath from bow shock-magnetosphere difference + magnetosheath_flags = np.where((inside_bowshock & 1-magnetosphere), 1, 0) + write_flags(writer, magnetosheath_flags, 'flag_magnetosheath') - # save magentosheath density and temperature for further use - magnetosheath_density = np.mean(variables["density"][magnetosheath_flags == 1]) - print(f"{magnetosheath_density=}") - magnetosheath_temperature = np.mean(variables["temperature"][magnetosheath_flags == 1]) - print(f"{magnetosheath_temperature=}") + # save magentosheath density and temperature for further use + #magnetosheath_density = np.mean(variables["density"][magnetosheath_flags == 1]) + #print(f"{magnetosheath_density=}") + #magnetosheath_temperature = np.mean(variables["temperature"][magnetosheath_flags == 1]) + #print(f"{magnetosheath_temperature=}") ## UPSTREAM ## - # upstream from !bowshock - write_flags(writer, 1-inside_bowshock, 'flag_upstream') - - write_flags(writer, inside_bowshock, 'flag_inside_bowshock') + if "upstream" in regions: + # upstream from !bowshock + write_flags(writer, 1-inside_bowshock, 'flag_upstream') + #write_flags(writer, inside_bowshock, 'flag_inside_bowshock') ## INNER MAGNETOSPHERE REGIONS ## - if ignore_boundaries: - noBoundaries = np.where(f.read_variable(name=boundaryname, cellids=-1) == 1, 1, 0) # boundarytype 1: not a boundary - mask_inBowshock= np.where(((inside_bowshock == 1) & (noBoundaries == 1)), 1, 0).astype(bool) # only search inner regions from inside the magnetosheath and magnetosphere - mask_inMagnetosphere = np.where(((magnetosphere == 1) & (noBoundaries == 1)), 1, 0).astype(bool) # - - else: - mask_inBowshock = inside_bowshock.astype(bool) # only search inner regions from inside the magnetosheath and magnetosphere - mask_inMagnetosphere = magnetosphere.astype(bool) # + if "magnetosphere" in regions: + if ignore_boundaries: + noBoundaries = np.where(f.read_variable(name=boundaryname, cellids=-1) == 1, 1, 0) # boundarytype 1: not a boundary + #mask_inBowshock= np.where(((inside_bowshock == 1) & (noBoundaries == 1)), 1, 0).astype(bool) # only search inner regions from inside the magnetosheath and magnetosphere + mask_inMagnetosphere = np.where(((magnetosphere == 1) & (noBoundaries == 1)), 1, 0).astype(bool) # + else: + #mask_inBowshock = inside_bowshock.astype(bool) # only search inner regions from inside the magnetosheath and magnetosphere + mask_inMagnetosphere = magnetosphere.astype(bool) # #print("upstream B:", variables["B_magnitude"][upstream_index]) # cusps - cusp_conditions = {"density": [variables["density"][upstream_index], None], - #"beta_star": [0.1, None], - "connection": [0.0, 2.5], # either closed, or open-closed/closed-open - "B_magnitude":[2*variables["B_magnitude"][upstream_index], None], - "J_magnitude": [variables["J_magnitude"][upstream_index], None] - } + if "cusps" in regions: + if "cusps" in region_conditions: + cusp_conditions = region_conditions["cusps"] + else: + cusp_conditions = {"density": [variables["density"][upstream_index], None], + #"beta_star": [0.1, None], + "connection": [0.0, 2.5], # either closed, or open-closed/closed-open + "B_magnitude":[2*variables["B_magnitude"][upstream_index], None], + "J_magnitude": [variables["J_magnitude"][upstream_index], None] + } - cusp_flags = make_region_flags(variables, cusp_conditions, flag_type="fraction", mask=mask_inMagnetosphere) - write_flags(writer, cusp_flags, 'flag_cusps', mask_inMagnetosphere) + cusp_flags = make_region_flags(variables, cusp_conditions, flag_type=region_flag_type, mask=mask_inMagnetosphere) + write_flags(writer, cusp_flags, 'flag_cusps', mask_inMagnetosphere) # magnetotail lobes - def lobes(): - lobes_conditions = {"beta": [None, 0.1], # Koskinen: 0.003 - "connection": [0.5, 2.5], - "density": [None, variables["density"][upstream_index]], # Koskinen: 1e-8 - "temperature": [None, 3.5e6], # Koskinen: 3.5e6 K - } - lobes_flags = make_region_flags(variables, lobes_conditions, flag_type="fraction") - - - # lobes slightly other way - lobe_N_conditions = {"beta": [None, 0.1], - "connection": [0.5, 2.5], - "B_x":[0, None], - "B_magnitude":[None, 10*variables["B_magnitude"][upstream_index]] - } - lobe_N_flags = make_region_flags(variables, lobe_N_conditions, flag_type="fraction") - - - lobe_S_conditions = {"beta": [None, 0.1], - "connection": [0.5, 2.5], - "B_x":[None, 0], - "B_magnitude":[None, 10*variables["B_magnitude"][upstream_index]] - } - lobe_S_flags = make_region_flags(variables, lobe_S_conditions, flag_type="fraction") - - - return lobes_flags, lobe_N_flags, lobe_S_flags - - if "all" in regions or "lobes" in regions: - mask = mask_inBowshock - lobes_flags, N_lobe_flags, S_lobe_flags = lobes() + if "lobes" in regions: + if "lobes" in region_conditions: + lobes_conditions = region_conditions["lobes"] + else: + lobes_conditions = {"beta": [None, 0.1], # Koskinen: 0.003 + "connection": [0.5, 2.5], + "density": [None, variables["density"][upstream_index]], # Koskinen: 1e-8 + "temperature": [None, 3.5e6], # Koskinen: 3.5e6 K + } + + # lobes slightly other way + lobe_N_conditions = {"beta": [None, 0.1], + "connection": [0.5, 2.5], + "B_x":[0, None], + "B_magnitude":[None, 10*variables["B_magnitude"][upstream_index]] + } + lobe_N_flags = make_region_flags(variables, lobe_N_conditions, flag_type=region_flag_type) + + + lobe_S_conditions = {"beta": [None, 0.1], + "connection": [0.5, 2.5], + "B_x":[None, 0], + "B_magnitude":[None, 10*variables["B_magnitude"][upstream_index]] + } + lobe_S_flags = make_region_flags(variables, lobe_S_conditions, flag_type=region_flag_type) + + write_flags(writer, lobe_N_flags, 'flag_lobe_N') + write_flags(writer, lobe_S_flags, 'flag_lobe_S') + + lobes_flags = make_region_flags(variables, lobes_conditions, flag_type=region_flag_type) write_flags(writer, lobes_flags, 'flag_lobes') - write_flags(writer, N_lobe_flags, 'flag_lobe_N') - write_flags(writer, S_lobe_flags, 'flag_lobe_S') - # lobe density from median densities? - lobes_mask = np.where((lobes_flags > 0.9), 1, 0).astype(bool) - #lobes_allcells = mask_inBowshock.astype(float) - #lobes_allcells[mask_inBowshock] = lobes_mask - lobe_density = np.mean(variables["density"][lobes_mask])#[lobes_allcells>0.9]) - print(f"{lobe_density=}") + # lobe density from median densities? + #lobes_mask = np.where((lobes_flags > 0.9), 1, 0).astype(bool) + #lobe_density = np.mean(variables["density"][lobes_mask])#[lobes_allcells>0.9]) + #print(f"{lobe_density=}") # Central plasma sheet - def CPS(): - central_plasma_sheet_conditions = {"density": [None, 1e6], # Wolf intro ?should not be - #"density" : [1e7, None], # Koskinen: 3e-7 - #"density": [lobe_density, None], + if "central_plasma_sheet" in regions: + if "central_plasma_sheet" in region_conditions: + central_plasma_sheet_conditions = region_conditions["central_plasma_sheet"] + else: + central_plasma_sheet_conditions = {"density": [None, 1e6], # Wolf intro ?should not be but seems to work "beta": [1.0, None], # Koskinen: 6 - #"connection": 0, # only closed-closed "temperature": [2e6, None], # 5e7 from Koskinen "J_magnitude": [1e-9, None], } - central_plasma_sheet_flags = make_region_flags(variables, central_plasma_sheet_conditions,flag_type="fraction", mask=mask_inMagnetosphere) - return central_plasma_sheet_flags - - if "all" in regions or "central_plasma_sheet" in regions: - CPS_flags = CPS() - write_flags(writer, CPS_flags, 'flag_central_plasma_sheet', mask_inMagnetosphere) + central_plasma_sheet_flags = make_region_flags(variables, central_plasma_sheet_conditions,flag_type=region_flag_type, mask=mask_inMagnetosphere) + write_flags(writer, central_plasma_sheet_flags, 'flag_central_plasma_sheet', mask_inMagnetosphere) + + ## Other boundary layers, PSBL sometimes works # Plasma sheet boundary layer (PSBL) + # if "PSBL" in regions or "all" in regions: #PSBL_conditions = {#"density": (1e7, 1.0), # Koskinen: 1e5 # "density": [None, 1e7], #"temperature": [0.5e6, 1e6],#(1e7, 2.0), # from Koskinen @@ -476,36 +481,23 @@ def CPS(): # } - #PSBL_flags = make_region_flags(variables, PSBL_conditions,flag_type="fraction", mask=mask_inMagnetosphere) - #write_flags(writer, PSBL_flags, "flag_PSBL", mask_inMagnetosphere) - # Low-Latitude boundary layer (LLBL) - #LLBL_conditions = {"density": [magnetopause_density, magnetosheath_density], - # "temperature": [magnetosheath_temperature, magnetopause_temperature] - # } - #LLBL_flags = make_region_flags(variables, LLBL_conditions,flag_type="fraction", mask=mask_inBowshock) - #write_flags(writer, LLBL_flags, "flag_LLBL", mask_inBowshock) +def main(): + #datafile = "/wrk-vakka/group/spacephysics/vlasiator/3D/FHA/bulk1/bulk1.0001400.vlsv" + #outfilen = "FHA_regions_t0001400.vlsv" + #datafile = "/wrk-vakka/group/spacephysics/vlasiator/3D/EGE/bulk/bulk.0002000.vlsv" + #outfilen = "/wrk-vakka/users/jreimi/magnetosphere_classification/Results/EGE_regions_t2000.vlsv" - # High-Latitude boundary layer (HLBL) - HLBL_conditions = { "density": [magnetopause_density, magnetosheath_density], - "temperature": [magnetosheath_temperature, magnetopause_temperature], - "beta": [1.0, None], - "connection": 0, - "J_magnitude": [2*variables["J_magnitude"][upstream_index], None], - } - - HLBL_flags = make_region_flags(variables, HLBL_conditions,flag_type="fraction", mask=mask_inBowshock) - write_flags(writer, HLBL_flags, "flag_HLBL", mask_inBowshock) + datafile = "/wrk-vakka/group/spacephysics/vlasiator/3D/FID/bulk1/bulk1.0001100.vlsv" + outfilen = "/wrk-vakka/users/jreimi/magnetosphere_classification/FID/FID_magnetopause_BSC_t1100.vlsv" + #datafile = "/wrk-vakka/group/spacephysics/vlasiator/3D/EGE/bulk/bulk.0002000.vlsv" + #outfilen = "EGE_regions_t2000.vlsv" -def main(): - - datafile = "/wrk-vakka/group/spacephysics/vlasiator/3D/EGE/bulk/bulk.0002000.vlsv" - outfilen = "EGE_regions_t2000.vlsv" RegionFlags(datafile, outfilen, regions=["all"], magnetopause_kwargs={"method":"beta_star_with_connectivity", "beta_star_range":[0.3, 0.4]}) From eb43f48df7db958a18b03f135f1ff5b2692be490 Mon Sep 17 00:00:00 2001 From: jreimi Date: Wed, 27 Aug 2025 15:48:12 +0300 Subject: [PATCH 053/124] removed main function and moved usage example to docstring --- scripts/regions.py | 34 +++++++++------------------------- 1 file changed, 9 insertions(+), 25 deletions(-) diff --git a/scripts/regions.py b/scripts/regions.py index a7e05f92..d15fca62 100644 --- a/scripts/regions.py +++ b/scripts/regions.py @@ -1,5 +1,14 @@ """Script and functions for creating sidecar files with SDF/region/boundary tags of plasma regions. + Usage example where bow shock conditions are given to replace default conditions: + + .. code-block:: python + + datafile = "/wrk-vakka/group/spacephysics/vlasiator/3D/EGE/bulk/bulk.0002000.vlsv" + outfilen = "EGE_regions_t2000.vlsv" + RegionFlags(datafile, outfilen, regions=["all"], + region_conditions = {"bowshock": {"density": [2e6, None]}} + """ import analysator as pt @@ -481,28 +490,3 @@ def errormsg(varstr): print("{} could not be read, will be ignored".format(varst # } - - -def main(): - - #datafile = "/wrk-vakka/group/spacephysics/vlasiator/3D/FHA/bulk1/bulk1.0001400.vlsv" - #outfilen = "FHA_regions_t0001400.vlsv" - - #datafile = "/wrk-vakka/group/spacephysics/vlasiator/3D/EGE/bulk/bulk.0002000.vlsv" - #outfilen = "/wrk-vakka/users/jreimi/magnetosphere_classification/Results/EGE_regions_t2000.vlsv" - - datafile = "/wrk-vakka/group/spacephysics/vlasiator/3D/FID/bulk1/bulk1.0001100.vlsv" - outfilen = "/wrk-vakka/users/jreimi/magnetosphere_classification/FID/FID_magnetopause_BSC_t1100.vlsv" - - #datafile = "/wrk-vakka/group/spacephysics/vlasiator/3D/EGE/bulk/bulk.0002000.vlsv" - #outfilen = "EGE_regions_t2000.vlsv" - - - - - RegionFlags(datafile, outfilen, regions=["all"], magnetopause_kwargs={"method":"beta_star_with_connectivity", "beta_star_range":[0.3, 0.4]}) - - -if __name__ == "__main__": - - main() From afee9e911c4b5c5ef9fcd04500f4abba87efb72c Mon Sep 17 00:00:00 2001 From: jreimi Date: Wed, 27 Aug 2025 16:12:07 +0300 Subject: [PATCH 054/124] Sphinx updates: bowshock and scripts toctree --- .../sphinx/magnetosphere_regions.rst | 73 ++++++++++++++++--- Documentation/sphinx/scripts.rst | 20 +++++ 2 files changed, 84 insertions(+), 9 deletions(-) diff --git a/Documentation/sphinx/magnetosphere_regions.rst b/Documentation/sphinx/magnetosphere_regions.rst index 1693d545..76ff2d4a 100644 --- a/Documentation/sphinx/magnetosphere_regions.rst +++ b/Documentation/sphinx/magnetosphere_regions.rst @@ -1,11 +1,40 @@ -Magnetosphere regions: How to find -================================== +Magnetosphere regions and bow shock: How to find +================================================ +Bow shock +--------- -Cusps ------ +Plasma properties for estimating bow shock position: +* plasma compression: + * :math:`n_p > 2n_{p, sw}` [Battarbee_et_al_2020]_ (Vlasiator) +* solar wind core heating: + * :math:`T_{core} > 4T_{sw}` [Battarbee_et_al_2020]_ (Vlasiator) + * :math:`T_{core} = 3T_{sw}` [Suni_et_al_2021]_ (Vlasiator) +* magnetosonic Mach number: + * :math:`M_{ms} < 1` [Battarbee_et_al_2020]_ (Vlasiator) + + + +Magnetosheath +------------- + +properties: + +* density: + * :math:`8 cm^{-3}` [Hudges_Introduction_to_space_physics_Ch_9]_ +* temperature: + * ion: :math:`150 eV` [Hudges_Introduction_to_space_physics_Ch_9]_ + * electron: :math:`25 eV` [Hudges_Introduction_to_space_physics_Ch_9]_ +* magnetic field: + * :math:`15 nT` [Hudges_Introduction_to_space_physics_Ch_9]_ +* plasma :math:`\beta`: + * 2.5 [Hudges_Introduction_to_space_physics_Ch_9]_ + + +Polar cusps +----------- *Properties:* @@ -20,6 +49,15 @@ Cusps **In analysator:** +*regions.py* in *scripts* has an option to find cusps using convex hull of the magnetosphere. +Usage example: + +.. code-block:: [python] + + datafile = "vlsvbulkfile.vlsv" + outfilen = "cusps.vlsv" + RegionFlags(datafile, outfilen, regions=["cusps"]) + Tail lobes ---------- @@ -42,14 +80,16 @@ Tail lobes Separated from the plasma sheet by the plasma sheet boundary layer (PSBL) -**in analysator:** +**In analysator:** -regions.py +*regions.py* in *scripts* has an option to find tail lobes. +Usage example: -conditions: +.. code-block:: [python] -* inside the magnetosphere -* plasma :math:`\beta` .... + datafile = "vlsvbulkfile.vlsv" + outfilen = "lobes.vlsv" + RegionFlags(datafile, outfilen, regions=["lobes"]) @@ -124,11 +164,26 @@ Central plasma sheet Inner plasma sheet: unusually low plasma beta may exist (e.g., cold tenuous plasma near the neutral sheet after long periods of northward IMF) [Boakes_et_al_2014]_, (Cluster spacecraft data) +**In analysator:** + +*regions.py* in *scripts* has an option to find tail lobes. +Usage example: + +.. code-block:: [python] + + datafile = "vlsvbulkfile.vlsv" + outfilen = "CPS.vlsv" + RegionFlags(datafile, outfilen, regions=["central_plasma_sheet"]) + + + ------------ References +.. [Battarbee_et_al_2020] Battarbee, M., Ganse, U., Pfau-Kempf, Y., Turc, L., Brito, T., Grandin, M., Koskela, T., and Palmroth, M.: Non-locality of Earth's quasi-parallel bow shock: injection of thermal protons in a hybrid-Vlasov simulation, Ann. Geophys., 38, 625-643, https://doi.org/10.5194/angeo-38-625-2020, 2020 +.. [Suni_et_al_2021] Suni, J., Palmroth, M., Turc, L., Battarbee, M., Johlander, A., Tarvus, V., et al. (2021). Connection between foreshock structures and the generation of magnetosheath jets: Vlasiator results. Geophysical Research Letters, 48, e2021GL095655. https://doi. org/10.1029/2021GL095655 .. [Grison_et_al_2025] Grison, B., Darrouzet, F., Maggiolo, R. et al. Localization of the Cluster satellites in the geospace environment. Sci Data 12, 327 (2025). https://doi.org/10.1038/s41597-025-04639-z .. [Koskinen_Johdatus] Koskinen, H. E. J. (2011). Johdatus plasmafysiikkaan ja sen avaruussovellutuksiin. Limes ry. .. [Koskinen_Space_Storms] Koskinen, H. E. J. (2011). Physics of Space Storms: From the Solar Surface to the Earth. Springer-Verlag. https://doi.org/10.1007/978-3-642-00319-6 diff --git a/Documentation/sphinx/scripts.rst b/Documentation/sphinx/scripts.rst index dffdadaf..98547d68 100644 --- a/Documentation/sphinx/scripts.rst +++ b/Documentation/sphinx/scripts.rst @@ -75,6 +75,24 @@ tsyganenko ------------ +magnetopause +------------ +:doc:`magnetopause` + +.. automodule:: magnetopause + :no-index: + +------------ + +regions +------- +:doc:`magnetosphere_regions` + +.. automodule:: regions + :no-index: + +------------ + .. toctree:: :maxdepth: 2 :caption: Scripts: @@ -87,4 +105,6 @@ tsyganenko obliqueshock_nif shue tsyganenko + magnetopause + magnetosphere_regions From db3be40af163bf562a2d583f92a5a2f19d49c640 Mon Sep 17 00:00:00 2001 From: jreimi Date: Wed, 27 Aug 2025 16:27:43 +0300 Subject: [PATCH 055/124] removed bowshock after moving contents to regions doc before --- Documentation/sphinx/bowshock.rst | 69 ------------------------------- 1 file changed, 69 deletions(-) delete mode 100644 Documentation/sphinx/bowshock.rst diff --git a/Documentation/sphinx/bowshock.rst b/Documentation/sphinx/bowshock.rst deleted file mode 100644 index 365d9dc4..00000000 --- a/Documentation/sphinx/bowshock.rst +++ /dev/null @@ -1,69 +0,0 @@ -Bow shock: How to find -====================== - - -Plasma properties for estimating bow shock position: ----------------------------------------------------- - -* plasma compression - * :math:`n_p > 2n_{p, sw}` [Battarbee_et_al_2020]_ (Vlasiator) -* solar wind core heating - * :math:`T_{core} > 4T_{sw}` [Battarbee_et_al_2020]_ (Vlasiator) - * :math:`T_{core} = 3T_{sw}` [Suni_et_al_2021]_ (Vlasiator) -* magnetosonic Mach number - * :math:`M_{ms} < 1` [Battarbee_et_al_2020]_ (Vlasiator) - - - - -In analysator: --------------- - -regions.py: - -*method*: - -Convex hull from cells where :math:`n_p > 1.5 n_{p, sw}` -criteria - -also velocity? - - -Foreshock: ----------- - -*properties:* - -* larger fluctuations in the magnetic field, plasma velocity and plasma density than unperturbed solar wind [Grison_et_al_2025]_ - - -Magnetosheath -------------- - -properties: - -* density: - * :math:`8 cm^{-3}` [Hudges_Introduction_to_space_physics_Ch_9]_ -* temperature: - * ion: :math:`150 eV` [Hudges_Introduction_to_space_physics_Ch_9]_ - * electron: :math:`25 eV` [Hudges_Introduction_to_space_physics_Ch_9]_ -* magnetic field: - * :math:`15 nT` [Hudges_Introduction_to_space_physics_Ch_9]_ -* plasma :math:`\beta`: - * 2.5 [Hudges_Introduction_to_space_physics_Ch_9]_ - - -Regions inside the bow shock: ------------------------------ - -* magnetosheath: area inside bow shock but outside the magnetopause, see ... -* magnetosphere: area inside magnetopause, see again ... and ... for magnetosphere regions - - ------------- - -References: - -.. [Battarbee_et_al_2020] Battarbee, M., Ganse, U., Pfau-Kempf, Y., Turc, L., Brito, T., Grandin, M., Koskela, T., and Palmroth, M.: Non-locality of Earth's quasi-parallel bow shock: injection of thermal protons in a hybrid-Vlasov simulation, Ann. Geophys., 38, 625-643, https://doi.org/10.5194/angeo-38-625-2020, 2020 -.. [Suni_et_al_2021] Suni, J., Palmroth, M., Turc, L., Battarbee, M., Johlander, A., Tarvus, V., et al. (2021). Connection between foreshock structures and the generation of magnetosheath jets: Vlasiator results. Geophysical Research Letters, 48, e2021GL095655. https://doi. org/10.1029/2021GL095655 -.. [Grison_et_al_2025] Grison, B., Darrouzet, F., Maggiolo, R. et al. Localization of the Cluster satellites in the geospace environment. Sci Data 12, 327 (2025). https://doi.org/10.1038/s41597-025-04639-z From fcd718c819a14b4a102e9a5c07ad0cd71a9117c9 Mon Sep 17 00:00:00 2001 From: jreimi Date: Wed, 27 Aug 2025 16:56:26 +0300 Subject: [PATCH 056/124] sphinx documentation fixes --- .../sphinx/magnetosphere_regions.rst | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/Documentation/sphinx/magnetosphere_regions.rst b/Documentation/sphinx/magnetosphere_regions.rst index 76ff2d4a..b99ffac4 100644 --- a/Documentation/sphinx/magnetosphere_regions.rst +++ b/Documentation/sphinx/magnetosphere_regions.rst @@ -16,6 +16,17 @@ Plasma properties for estimating bow shock position: * :math:`M_{ms} < 1` [Battarbee_et_al_2020]_ (Vlasiator) +**In analysator:** + +*regions.py* in *scripts* has an option to find the bow shock. Default method uses 1.5*solar wind density as limit. +Usage example: + +.. code-block:: python + + datafile = "vlsvbulkfile.vlsv" + outfilen = "bowshock.vlsv" + RegionFlags(datafile, outfilen, regions=["bowshock"]) + Magnetosheath ------------- @@ -32,6 +43,17 @@ properties: * plasma :math:`\beta`: * 2.5 [Hudges_Introduction_to_space_physics_Ch_9]_ +**In analysator:** + +*regions.py* in *scripts* has an option to find the magnetosheath using bow shock and magnetopause: +Usage example: + +.. code-block:: python + + datafile = "vlsvbulkfile.vlsv" + outfilen = "magnetosheath.vlsv" + RegionFlags(datafile, outfilen, regions=["magnetosheath"]) + Polar cusps ----------- @@ -52,7 +74,7 @@ Polar cusps *regions.py* in *scripts* has an option to find cusps using convex hull of the magnetosphere. Usage example: -.. code-block:: [python] +.. code-block:: python datafile = "vlsvbulkfile.vlsv" outfilen = "cusps.vlsv" @@ -85,7 +107,7 @@ Separated from the plasma sheet by the plasma sheet boundary layer (PSBL) *regions.py* in *scripts* has an option to find tail lobes. Usage example: -.. code-block:: [python] +.. code-block:: python datafile = "vlsvbulkfile.vlsv" outfilen = "lobes.vlsv" @@ -166,10 +188,10 @@ Inner plasma sheet: unusually low plasma beta may exist (e.g., cold tenuous plas **In analysator:** -*regions.py* in *scripts* has an option to find tail lobes. +*regions.py* in *scripts* has an option to find the central plasma sheet. Usage example: -.. code-block:: [python] +.. code-block:: python datafile = "vlsvbulkfile.vlsv" outfilen = "CPS.vlsv" @@ -191,6 +213,5 @@ References .. [Coxon_et_al_2016] Coxon,J.C.,C.M.Jackman, M. P. Freeman, C. Forsyth, and I. J. Rae (2016), Identifying the magnetotail lobes with Cluster magnetometer data, J. Geophys. Res. Space Physics, 121, 1436–1446, doi:10.1002/2015JA022020. .. [Hudges_Introduction_to_space_physics_Ch_9] Hudges, W. J. (1995) The magnetopause, magnetotail and magnetic reconnection. In Kivelson, M. G., & Russell, C. T. (Eds.), Introduction to space physics (pp.227-287). Cambridge University Press. .. [Wolf_Introduction_to_space_physics_Ch_10] Wolf, R. A. (1995) Magnetospheric configuration. In Kivelson, M. G., & Russell, C. T. (Eds.), Introduction to space physics (pp.288-329). Cambridge University Press. -.. [Sckopke_et_al_1981] Sckopke, N., Paschmann, G., Haerendel, G., Sonnerup, B. U. , Bame, S. J., Forbes, T. G., Hones Jr., E. W., and Russell, C. T. (1981). Structure of the low-latitude boundary layer. Journal of Geophysical Research: Space Physics, 86(A4):2099–2110. eprint: https://agupubs.onlinelibrary.wiley.com/doi/pdf/10.1029/JA086iA04p02099 .. [Boakes_et_al_2014] Boakes, P. D., Nakamura, R., Volwerk, M., and Milan, S. E. (2014). ECLAT Cluster Spacecraft Magnetotail Plasma Region Identifications (2001–2009). Dataset Papers in Science, 2014(1):684305. eprint: https://onlinelibrary.wiley.com/doi/pdf/10.1155/2014/684305 .. [Stenuit_et_al_2001] Stenuit, H., Sauvaud, J.-A., Delcourt, D. C., Mukai, T., Kokubun, S., Fujimoto, M., Buzulukova, N. Y., Kovrazhkin, R. A., Lin, R. P., and Lepping, R. P. (2001). A study of ion injections at the dawn and dusk polar edges of the auroral oval. Journal of Geophysical Research: Space Physics, 106(A12):29619–29631. eprint: https://agupubs.onlinelibrary.wiley.com/doi/pdf/10.1029/2001JA900060. From 09a4d543e030a14e1312ef4ca7d94b0c5a61a2b0 Mon Sep 17 00:00:00 2001 From: jreimi Date: Thu, 28 Aug 2025 14:16:11 +0300 Subject: [PATCH 057/124] something wrong eith te beta* stop condition here --- .../magnetopause_sw_streamline_3d.py | 17 +++++-- scripts/magnetopause.py | 49 +++++++++++++------ 2 files changed, 49 insertions(+), 17 deletions(-) diff --git a/analysator/pyCalculations/magnetopause_sw_streamline_3d.py b/analysator/pyCalculations/magnetopause_sw_streamline_3d.py index abbc2ea5..ad329f6d 100644 --- a/analysator/pyCalculations/magnetopause_sw_streamline_3d.py +++ b/analysator/pyCalculations/magnetopause_sw_streamline_3d.py @@ -222,11 +222,13 @@ def streamline_stopping_condition(vlsvReader, points, value): x = points[:, 0] y = points[:, 1] z = points[:, 2] - return (x < xmin)|(x > xmax) | (y < ymin)|(y > ymax) | (z < zmin)|(z > zmax)|(value[:,0] > 0) + beta_star = vlsvReader.read_interpolated_variable("vg_beta_star", points) + return (x < xmin)|(x > xmax) | (y < ymin)|(y > ymax) | (z < zmin)|(z > zmax)|(value[:,0] > 0)| (beta_star < 0.4) def make_streamlines(vlsvfile, streamline_seeds=None, seeds_n=25, seeds_x0=20*6371000, seeds_range=[-5*6371000, 5*6371000], dl=2e6, iterations=200): """Traces streamlines of velocity field from outside the magnetosphere to magnetotail. + Stopping condition for when streamlines turn sunwards or go out of box :param vlsvfile: directory and file name of .vlsv data file to use for VlsvReader @@ -314,11 +316,16 @@ def cartesian_to_spherical_grid(cartesian_coords): # Only for x > 0 r, theta, phi = cartesian_to_spherical(cartesian_coords) theta_idx = int(theta/theta_step) - phi_idx = int(phi/phi_step) + #phi_idx = int(phi/phi_step) # off by half grid cell in relation to x<0 grid + if (phi (0-phi_step*0.5)): + phi_idx = 0 + else: + phi_idx = round(phi/phi_step) + return [theta_idx, phi_idx, r] def grid_mid_point(theta_idx, phi_idx): - return (theta_idx+0.5)*theta_step, (phi_idx+0.5)*phi_step + return (theta_idx+0.5)*theta_step, (phi_idx)*phi_step # make a dictionary based on spherical areas by theta index and phi index sph_points = {} @@ -431,6 +438,10 @@ def grid_mid_point(theta_idx, phi_idx): def find_magnetopause_sw_streamline_3d(vlsvfile, streamline_seeds=None, seeds_n=25, seeds_x0=20*6371000, seeds_range=[-5*6371000, 5*6371000], dl=2e6, iterations=200, end_x=-15*6371000, x_point_n=50, sector_n=36, ignore=0): """Finds the magnetopause position by tracing streamlines of the velocity field. + Note: there may be a slight jump at x=0. This may be due to difference in methods (pointcloud vs. interpolation). + If the subsolar point area looks off then more streamlines near the x-axis are needed. + + :param vlsvfile: path to .vlsv bulk file to use for VlsvReader :kword streamline_seeds: optional streamline starting points in numpy array :kword seeds_n: instead of streamline_seeds provide a number of streamlines to be traced diff --git a/scripts/magnetopause.py b/scripts/magnetopause.py index 92220821..03dd14b4 100644 --- a/scripts/magnetopause.py +++ b/scripts/magnetopause.py @@ -28,22 +28,22 @@ def write_SDF_to_file(SDF, datafilen, outfilen): -def magnetopause(datafilen, method="beta_star_with_connectivity", own_tresholds=None, return_surface=True, return_SDF=True, SDF_points=None, Delaunay_alpha=None, beta_star_range=[0.1, 1.0]): # TODO: separate streamline suface and vtkDelaunay3d surface in streamline method +def magnetopause(datafilen, method="beta_star_with_connectivity", own_tresholds=None, return_surface=True, return_SDF=True, SDF_points=None, Delaunay_alpha=None, beta_star_range=[0.4, 0.5]): # TODO: separate streamline suface and vtkDelaunay3d surface in streamline method """Finds the magnetopause using the specified method. Surface is constructed using vtk's Delaunay3d triangulation which results in a convex hull if no Delaunay_alpha is given. - + Returns vtk.vtkDataSetSurfaceFilter object and/or signed distances (negative -> inside mangetopause) (=SDF) to all cells + Note that using alpha for Delaunay might make SDF not so accurate inside the magnetosphere, especially if surface is constructed with points not everywhere in the magnetosphere (e.g. beta* 0.4-0.5) + :param datafilen: a .vlsv bulk file name (and path) :kword method: str, default "beta_star_with_connectivity", other options "beta_star", "streamlines", "shue", "dict" :kword own_tresholds: if using method "dict", a dictionary with conditions for the magnetopause/magnetosphere must be given where key is data name in datafile and value is treshold used (see treshold_mask()) :kword return_surface: True/False, return vtkDataSetSurfaceFilter object :kword return_SDF: True/False, return array of distances in m to SDF_points in point input order, negative distance inside the surface :kword SDF_points: optionally give array of own points to calculate signed distances to. If not given, distances will be to cell centres in the order of f.read_variable("CellID") output - :kword Delaunay_alpha: alpha (float) to give to vtkDelaunay3d, None -> convex hull, alpha=__: surface egdes longer than __ will be excluded (won't be a proper surface, SDF won't work) + :kword Delaunay_alpha: alpha (float) to give to vtkDelaunay3d, None -> convex hull, alpha=__: surface egdes longer than __ will be excluded :kword beta_star_range: [min, max] treshold rage to use with methods "beta_star" and "beta_star_with_connectivity" :returns: vtkDataSetSurfaceFilter object of convex hull or alpha shape if return_surface=True, signed distance field of convex hull or alpha shape of magnetopause if return_SDF=True """ - - start_t = time.time() f = pt.vlsvfile.VlsvReader(file_name=datafilen) cellids = f.read_variable("CellID") [xmin, ymin, zmin, xmax, ymax, zmax] = f.get_spatial_mesh_extent() @@ -51,14 +51,13 @@ def magnetopause(datafilen, method="beta_star_with_connectivity", own_tresholds= if method == "streamlines": - [xmin, ymin, zmin, xmax, ymax, zmax] = f.get_spatial_mesh_extent() seeds_x0=150e6 dl=5e5 iters = int(((seeds_x0-xmin)/dl)+100) - sector_n = 36*4 + sector_n = 36*3 vertices, manual_vtkSurface = pt.calculations.find_magnetopause_sw_streamline_3d(datafilen, seeds_n=300, seeds_x0=seeds_x0, seeds_range=[-5*6371000, 5*6371000], dl=dl, iterations=iters, end_x=xmin+10*6371000, x_point_n=200, sector_n=sector_n) - + # make the magnetopause surface from vertice points np.random.shuffle(vertices) # helps Delaunay triangulation vtkSurface, SDF = regions.vtkDelaunay3d_SDF(query_points, vertices, Delaunay_alpha) @@ -81,6 +80,7 @@ def magnetopause(datafilen, method="beta_star_with_connectivity", own_tresholds= connectivity_region = regions.treshold_mask(f.read_variable("vg_connection"), 0) # closed-closed magnetic field lines magnetosphere_proper = np.where((connectivity_region==1) | (betastar_region==1), 1, 0) contour_coords = f.get_cell_coordinates(cellids[magnetosphere_proper==1]) + np.save("pointcloud.npy", contour_coords) except: print("using field line connectivity for magnetosphere did not work, using only beta*") #condition_dict = {"beta_star": [0.5, 0.6]} # FIC: [0.4, 0.5]) # EGE: [0.9, 1.0]) # max 0.6 in FHA to not take flyaways from outside magnetopause @@ -90,6 +90,25 @@ def magnetopause(datafilen, method="beta_star_with_connectivity", own_tresholds= # make a convex hull surface with vtk's Delaunay vtkSurface, SDF = regions.vtkDelaunay3d_SDF(query_points, contour_coords, Delaunay_alpha) + #elif method == "beta_star_with_fieldlines": # either incredibly slow or does not work, don't use without fixing #TODO proprer measure of actual field line backwall point + # # magnetopause from beta_star, with field lines connecting to back wall if possible + # betastar_region = regions.treshold_mask(f.read_variable("vg_beta_star"), beta_star_range) + # try: + # fw_region = regions.treshold_mask(f.read_variable("vg_connection_coordinates_fw")[:,0], [None, xmin+5e7]) + # bw_region = regions.treshold_mask(f.read_variable("vg_connection_coordinates_bw")[:,0], [None, xmin+5e7]) + # #connectivity_region = regions.treshold_mask(f.read_variable("vg_connection"), 0) # closed-closed magnetic field lines + # magnetosphere_proper = np.where(((fw_region==1) | (betastar_region==1) | (bw_region==1)), 1, 0) + # contour_coords = f.get_cell_coordinates(cellids[magnetosphere_proper==1]) + # except: + # print("using field lines for magnetosphere did not work, using only beta*") + # #condition_dict = {"beta_star": [0.5, 0.6]} # FIC: [0.4, 0.5]) # EGE: [0.9, 1.0]) # max 0.6 in FHA to not take flyaways from outside magnetopause + # mpause_flags = np.where(betastar_region==1, 1, 0) + # contour_coords = f.get_cell_coordinates(cellids[mpause_flags!=0]) + # + # # make a convex hull surface with vtk's Delaunay + # vtkSurface, SDF = regions.vtkDelaunay3d_SDF(query_points, contour_coords, Delaunay_alpha) + + elif method == "dict": variable_dict = {} @@ -151,15 +170,17 @@ def magnetopause(datafilen, method="beta_star_with_connectivity", own_tresholds= def main(): - datafile = "/wrk-vakka/group/spacephysics/vlasiator/3D/FID/bulk1/bulk1.0001100.vlsv" - vtpoutfilen = "FID_magnetopause_BS_t1100.vtp" - vlsvoutfilen = "FID_magnetopause_BS_t1100.vlsv" + datafile = "/wrk-vakka/group/spacephysics/vlasiator/3D/EGE/bulk/bulk.0002000.vlsv" + vtpoutfilen = "EGE_magnetopause_SWtest_t2000.vtp" + vlsvoutfilen = "EGE_magnetopause_SWtest_t2000.vlsv" + + surface, SDF = magnetopause(datafile, - #method="streamlines", - method="beta_star_with_connectivity", - beta_star_range=[0.3, 0.4], + method="streamlines", + #method="beta_star_with_connectivity", + #beta_star_range=[0.0, 0.4], Delaunay_alpha=1*R_E, return_SDF=True, return_surface=True) From 1dc40facd733d3645c2051c73e1b8df4469bf173 Mon Sep 17 00:00:00 2001 From: jreimi Date: Thu, 28 Aug 2025 14:56:23 +0300 Subject: [PATCH 058/124] added automodules for regions and magnetopause documentations, some references still broken --- Documentation/sphinx/magnetopause.rst | 26 ++++++++++--------- .../sphinx/magnetosphere_regions.rst | 4 +++ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/Documentation/sphinx/magnetopause.rst b/Documentation/sphinx/magnetopause.rst index 7bf6f54b..0df6cdcb 100644 --- a/Documentation/sphinx/magnetopause.rst +++ b/Documentation/sphinx/magnetopause.rst @@ -20,11 +20,9 @@ Caveats: magnetotail current sheet has beta* :math:`>` 1 **in analysator:** -datareducer: beta_star, vg_beta_star - -regions.py: cells with beta* value within a certain treshold are chosen, and a convex hull is constructed with vtk to represent the magnetopause. -Ideal values of beta* for magnetopause construction might be run-dependent, and the surface construction works best with a small-ish range of beta* but still big enough to gather cells evenly from all sides. +*magnetopause.py* in *scripts*: see :func:`magnetopause.magnetopause` +datareducer: beta_star, vg_beta_star .. [Xu_et_al_2016] Xu, S., M. W. Liemohn, C. Dong, D. L. Mitchell, S. W. Bougher, and Y. Ma (2016), Pressure and ion composition boundaries at Mars, J. Geophys. Res. Space Physics, 121, 6417–6429, doi:10.1002/2016JA022644. .. [Brenner_et_al_2021] Brenner A, Pulkkinen TI, Al Shidi Q and Toth G (2021) Stormtime Energetics: Energy Transport Across the Magnetopause in a Global MHD Simulation. Front. Astron. Space Sci. 8:756732. doi: 10.3389/fspas.2021.756732 @@ -34,7 +32,6 @@ Ideal values of beta* for magnetopause construction might be run-dependent, and Field line connectivity ----------------------- -Magnetic field lines that are closed at least from one end ... @@ -51,8 +48,9 @@ Caveats: sometimes some streamlines can curve into the magnetotail or dayside ma **In analysator:** -magnetopause_sw_streamline_2d.py -magnetopause_sw_streamline_3d.py +see +:mod:`calculations.magnetopause_sw_streamline_2d.py` +:mod:`calculations.magnetopause_sw_streamline_3d.py` Streamlines are traced from outside the bow shock towards Earth. A subsolar point for the magnetopause is chosen to be where streamlines get closest to Earth in x-axis [y/z~0]. @@ -62,7 +60,7 @@ From the Earth towards negative x the space is divided into yz-planes. Each yz-plane is then divided into 2d sectors and magnetopause is marked to be in the middle of the sector with the radius of the n:th closest streamline to the x-axis. -For subsolar point, radial dayside and -x planes the closest streamline point can be changed to be n:th closest by setting keyword *ignore*, in which case *ignore* closest streamline points are not taken into acocunt. +For subsolar point, radial dayside, and -x planes the closest streamline point can be changed to be n:th closest by setting keyword *ignore*, in which case *ignore* closest streamline points are not taken into account. 2d: @@ -105,7 +103,7 @@ The magnetopause radius as a function of angle :math:`\theta` is **In analysator:** -*shue.py* in *scripts* +see :mod:`shue` .. [Shue_et_al_1997] Shue, J.-H., Chao, J. K., Fu, H. C., Russell, C. T., Song, P., Khurana, K. K., and Singer, H. J. (1997). A new functional form to study the solar wind control of the magnetopause size and shape. Journal of Geophysical Research: Space Physics, 102(A5):9497–9511. eprint: https://agupubs.onlinelibrary.wiley.com/doi/pdf/10.1029/97JA00196. @@ -121,7 +119,7 @@ The magnetopause radius as a function of angle :math:`\theta` is Constructs the magentopause surface with vtk's vtkDelaunay3d triangulation with optional alpha to make the surface non-convex. Uses regions.py functions. -Important: SDF of non-convex surface will (most likely) not work, use convex hull (alpha=None) for SDFs! +Important: SDF of non-convex surface might not always work options (magnetopause() method keyword) and some notes: @@ -135,7 +133,7 @@ options (magnetopause() method keyword) and some notes: * beta* ("beta_star") * beta* treshold might need tweaking as sometimes there are small low beta* areas in the magnetosheath that get taken in distorting the magnetopause shape at nose * convex hull (Delaunay_alpha=None) usually makes a nice rough magnetopause but goes over any inward dips (like polar cusps) - * alpha shape (Delaunay_alpha= e.g. 1*R_E) does a better job at cusps and delicate shapes like vortices but can fail at magnetotail due to central plasma sheet and won't produce a correct SDF + * alpha shape (Delaunay_alpha= e.g. 1*R_E) does a better job at cusps and delicate shapes like vortices but might fail at SDF inside the magnetosphere * Delaynay3d has an easier time if the treshold is something like [0.4, 0.5] and not [0.1, 0.5] * beta* with magnetic field line connectivity ("beta_star_with_connectivity") @@ -149,4 +147,8 @@ options (magnetopause() method keyword) and some notes: * user-defined parameter tresholds ("dict") * creates a magnetopause (or other area) using the Delaunay3d triangulation of some area where user-defined tresholds given as dictionary * dictionary key is data name in datafile and value is treshold used, if dictionary has multiple conditions, they all need to be fulfilled - * dictionary example: {"vg_rho": [None, 1e5]} makes a magnetopause using cells where density is less than 1e5 \ No newline at end of file + * dictionary example: {"vg_rho": [None, 1e5]} makes a magnetopause using cells where density is less than 1e5 + + +.. automodule:: magnetopause + :members: \ No newline at end of file diff --git a/Documentation/sphinx/magnetosphere_regions.rst b/Documentation/sphinx/magnetosphere_regions.rst index b99ffac4..3080321f 100644 --- a/Documentation/sphinx/magnetosphere_regions.rst +++ b/Documentation/sphinx/magnetosphere_regions.rst @@ -200,6 +200,10 @@ Usage example: +.. automodule:: regions + :members: + + ------------ References From e8f2754a3f0244bebfe1242dd6fcd91bde758874 Mon Sep 17 00:00:00 2001 From: jreimi Date: Thu, 5 Jun 2025 10:08:14 +0300 Subject: [PATCH 059/124] Added documentation strings to magnetopause scripts and fixed plotting function call. --- scripts/magnetopause2d.py | 29 ++++++++++--- scripts/magnetopause3d.py | 89 +++++++++++++++++++++++++-------------- 2 files changed, 81 insertions(+), 37 deletions(-) diff --git a/scripts/magnetopause2d.py b/scripts/magnetopause2d.py index 5bf4e5df..1e3d433f 100644 --- a/scripts/magnetopause2d.py +++ b/scripts/magnetopause2d.py @@ -1,17 +1,20 @@ ''' -Finds the magnetopause position by tracing steamines of the plasma flow for two-dimensional Vlasiator runs. Needs the yt package. +Finds the magnetopause position by tracing streamines of the plasma flow for two-dimensional Vlasiator runs. Needs the yt package. ''' import numpy as np import analysator as pt -import plot_colormap import yt from yt.visualization.api import Streamlines - - def interpolate(streamline, x_points): + """Interpolates a single streamline for make_magnetopause(). + + :param streamline: a single streamline to be interpolated + :param x_points: points in the x-axis to use for interpolation + :returns: the streamline as numpy array of x,z coordinate points where the x-axis coordinates are the points given to the function + """ arr = np.array(streamline) @@ -26,6 +29,11 @@ def interpolate(streamline, x_points): def make_streamlines(vlsvFileName): + """Traces streamlines of velocity field from outside the magnetosphere to magnetotail using the yt-package. + + :param vlsvFileName: directory and file name of vlsv file, to be given to VlsvReader + :returns: streamlines as numpy array + """ ## make streamlines boxre = [0,0] @@ -81,6 +89,11 @@ def to_Re(m): #meters to Re return np.array(streamlines_pos.streamlines) def make_magnetopause(streams): + """Finds the mangetopause location based on streamlines. + + :param streams: streamlines + :returns: magnetopause as coordinate points from tail on positive x-axis to tail on negative x-axis + """ streampoints = np.reshape(streams, (streams.shape[0]*streams.shape[1], 3)) #all the points in one array @@ -126,6 +139,12 @@ def make_magnetopause(streams): def polar_to_cartesian(r, phi): + """Converts polar coordinates to cartesian. + + :param r: radius + :param phi: angular coordinate in degrees + :returns: y, z -coordinates in cartesian system + """ phi = np.deg2rad(phi) y = r * np.cos(phi) z = r * np.sin(phi) @@ -166,7 +185,7 @@ def external_plot(ax,XmeshXY=None, YmeshXY=None, pass_maps=None): ax.plot(magnetopause[:,0], magnetopause[:,1], color='cyan', linewidth=1.0) - plot_colormap.plot_colormap( + pt.plot.plot_colormap( filename=fileLocation+fileN, outputdir=outdir, nooverwrite=None, diff --git a/scripts/magnetopause3d.py b/scripts/magnetopause3d.py index 906880d7..517fdd40 100644 --- a/scripts/magnetopause3d.py +++ b/scripts/magnetopause3d.py @@ -1,24 +1,27 @@ ''' -Finds the magnetopause position by tracing steamines of the plasma flow for three-dimensional Vlasiator runs. Needs the yt package. +Finds the magnetopause position by tracing streamlines of the plasma flow for three-dimensional Vlasiator runs. Needs the yt package. ''' from pyCalculations import ids3d import matplotlib.pyplot as plt import numpy as np import analysator as pt -import plot_colormap3dslice import yt import math from mpl_toolkits import mplot3d from yt.visualization.api import Streamlines - - -def to_Re(m): #meters to Re +def to_Re(m): + """Converts units from meters to R_E (Earth's radius, 6371000m)""" return m/6371000 def cartesian_to_polar(cartesian_coords): # for segments of plane + """Converts cartesian coordinates to polar (for the segments of the yz-planes). + + :param cartesian_coords: (y,z) coordinates as list or array + :returns: the polar coordinates r, phi (angle in degrees) + """ y,z = cartesian_coords[0], cartesian_coords[1] r = np.sqrt(z**2 + y**2) phi = np.arctan2(z, y) @@ -27,6 +30,12 @@ def cartesian_to_polar(cartesian_coords): # for segments of plane return(r, phi) def polar_to_cartesian(r, phi): + """Converts polar coordinates of the yz-plane to cartesian coordinates. + + :param r: radius of the segment (distance from the x-axis) + :param phi: the angle coordinate in degrees + :returns: y, z -coordinates + """ phi = np.deg2rad(phi) y = r * np.cos(phi) z = r * np.sin(phi) @@ -34,6 +43,12 @@ def polar_to_cartesian(r, phi): def interpolate(streamline, x_points): + """IInterpolates a single streamline for make_magnetopause(). + + :param streamline: a single streamline to be interpolated + :param x_points: points in the x-axis to use for interpolation + :returns: the streamline as numpy array of coordinate points where the x-axis coordinates are the points given to the function + """ arr = np.array(streamline) # set arrays for interpolation @@ -53,32 +68,31 @@ def interpolate(streamline, x_points): def make_surface(coords): + '''Defines a surface constructed of input coordinates as triangles. - ''' - Defines surface constructed of input coordinates as triangles - Returns list of verts and vert indices of surface triangles - - coordinates must be in form [...[c11, c21, c31, ... cn1],[c12, c22, c32, ... cn2],... - where cij = [xij, yij, zij], i marks slice, j marks yz-plane (x_coord) index - - How it works: - Three points make a triangle, triangles make the surface. - For every two planes next to each other: - - take every other point from plane1, every other from plane2 (in order!) - - from list of points: every three points closest to each other make a surface - - Example: - plane 1: [v1, v2, v3, v4] - plane 2: [v5, v6, v7, v8] - - -> list: [v1, v5, v2, v6, v3,...] - -> triangles: - v1 v5 v2 - v5 v2 v6 - v2 v6 v3 - . - . - . + :param coords: points that make the surface + :returns: list of verts and vert indices of surface triangles + + input coordinates must be in form [...[c11, c21, c31, ... cn1],[c12, c22, c32, ... cn2],... + where cij = [xij, yij, zij], i marks sector, j marks yz-plane (x_coord) index + + Three points make a triangle, triangles make the surface. + For every two planes next to each other: + - take every other point from plane1, every other from plane2 (in order!) + - from list of points: every three points closest to each other make a surface + + Example: + plane 1: [v1, v2, v3, v4] + plane 2: [v5, v6, v7, v8] + + -> list: [v1, v5, v2, v6, v3,...] + -> triangles: + v1 v5 v2 + v5 v2 v6 + v2 v6 v3 + . + . + . ''' verts = [] #points @@ -123,6 +137,11 @@ def make_surface(coords): def make_streamlines(vlsvFileName): + """Traces streamlines of velocity field from outside the magnetosphere to magnetotail using the yt-package. + + :param vlsvFileName: directory and file name of .vlsv data file to use for VlsvReader + :returns: streamlines as numpy array + """ ## make streamlines boxre = [0] @@ -206,6 +225,12 @@ def make_streamlines(vlsvFileName): def make_magnetopause(streams): + """Finds the mangetopause location based on streamlines. + + :param streams: streamlines + :returns: the magnetopause position as coordinate points in numpy array + """ + streampoints = np.reshape(streams, (streams.shape[0]*streams.shape[1], 3)) #all the points in one array ## find the subsolar dayside point in the x-axis @@ -322,7 +347,7 @@ def external_plot(ax,XmeshXY=None, YmeshXY=None, pass_maps=None, requestvariable ax.plot(xz_slice[:,0], xz_slice[:,2], color='limegreen', linewidth=1.5) - plot_colormap3dslice.plot_colormap3dslice( + pt.plot.plot_colormap3dslice( filename=fileLocation+fileN, outputdir=outdir, run=run, @@ -342,7 +367,7 @@ def external_plot(ax,XmeshXY=None, YmeshXY=None, pass_maps=None, requestvariable return ['vg_v'] ax.plot(xy_slice[:,0], xy_slice[:,1], color='limegreen', linewidth=1.5) - plot_colormap3dslice.plot_colormap3dslice( + pt.plot.plot_colormap3dslice( filename=fileLocation+fileN, outputdir=outdir, run=run, From fed215397272d3d2398b41ce85911b8b397a153e Mon Sep 17 00:00:00 2001 From: jreimi Date: Thu, 5 Jun 2025 15:16:18 +0300 Subject: [PATCH 060/124] Add checks for starting point input, change ragged arrays to lists, and fix a small docstring typo --- analysator/pyCalculations/fieldtracer.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/analysator/pyCalculations/fieldtracer.py b/analysator/pyCalculations/fieldtracer.py index 7825423e..195c7d05 100644 --- a/analysator/pyCalculations/fieldtracer.py +++ b/analysator/pyCalculations/fieldtracer.py @@ -51,7 +51,7 @@ def static_field_tracer( vlsvReader, x0, max_iterations, dx, direction='+', bvar ''' Field tracer in a static frame :param vlsvReader: An open vlsv file - :param x: Starting point for the field trace + :param x0: Starting point for the field trace :param max_iterations: The maximum amount of iteractions before the algorithm stops :param dx: One iteration step length :param direction: '+' or '-' or '+-' Follow field in the plus direction or minus direction @@ -61,6 +61,10 @@ def static_field_tracer( vlsvReader, x0, max_iterations, dx, direction='+', bvar :returns: List of coordinates ''' + # Check the starting point + if (np.shape(x0) != (3,)): + raise ValueError("Starting point should be a single [x,y,z] coordinate in 1-dimensional list or array") + if(bvar != 'B'): warnings.warn("User defined tracing variable detected. fg, volumetric variable results may not work as intended, use face-values instead.") @@ -117,7 +121,7 @@ def static_field_tracer( vlsvReader, x0, max_iterations, dx, direction='+', bvar x = np.arange(mins[0], maxs[0], dcell[0]) + 0.5*dcell[0] y = np.arange(mins[1], maxs[1], dcell[1]) + 0.5*dcell[1] z = np.arange(mins[2], maxs[2], dcell[2]) + 0.5*dcell[2] - coordinates = np.array([x,y,z]) + coordinates = [x,y,z] # Debug: if( len(x) != sizes[0] ): logging.info("SIZE WRONG: " + str(len(x)) + " " + str(sizes[0])) @@ -142,7 +146,7 @@ def static_field_tracer( vlsvReader, x0, max_iterations, dx, direction='+', bvar x = np.arange(mins[0], maxs[0], dcell[0]) + 0.5*dcell[0] y = np.arange(mins[1], maxs[1], dcell[1]) + 0.5*dcell[1] z = np.arange(mins[2], maxs[2], dcell[2]) + 0.5*dcell[2] - coordinates = np.array([x,y,z]) + coordinates = [x,y,z] # Debug: if( len(x) != sizes[0] ): logging.info("SIZE WRONG: " + str(len(x)) + " " + str(sizes[0])) @@ -359,6 +363,10 @@ def static_field_tracer_3d( vlsvReader, seed_coords, max_iterations, dx, directi # Standardize input: (N,3) np.array if type(seed_coords) != np.ndarray: raise TypeError("Please give a numpy array.") + + # Transform a single (3,) coordinate to (1,3) if needed + if np.shape(seed_coords)==(3,): + seed_coords = np.array([seed_coords]) # Cache and read variables: vg = None From 2da0e0e844e02a9d0b1741914e5474477f9bc433 Mon Sep 17 00:00:00 2001 From: jreimi Date: Fri, 6 Jun 2025 09:55:11 +0300 Subject: [PATCH 061/124] Change the streamline tracing to use analysator's fieldtracer instead of yt, remove unnecessary imports and function --- scripts/magnetopause3d.py | 96 +++++++-------------------------------- 1 file changed, 17 insertions(+), 79 deletions(-) diff --git a/scripts/magnetopause3d.py b/scripts/magnetopause3d.py index 517fdd40..9469d929 100644 --- a/scripts/magnetopause3d.py +++ b/scripts/magnetopause3d.py @@ -1,20 +1,10 @@ ''' -Finds the magnetopause position by tracing streamlines of the plasma flow for three-dimensional Vlasiator runs. Needs the yt package. +Finds the magnetopause position by tracing streamlines of the plasma flow for three-dimensional Vlasiator runs. ''' -from pyCalculations import ids3d + import matplotlib.pyplot as plt import numpy as np import analysator as pt -import yt -import math -from mpl_toolkits import mplot3d -from yt.visualization.api import Streamlines - - -def to_Re(m): - """Converts units from meters to R_E (Earth's radius, 6371000m)""" - return m/6371000 - def cartesian_to_polar(cartesian_coords): # for segments of plane """Converts cartesian coordinates to polar (for the segments of the yz-planes). @@ -34,7 +24,7 @@ def polar_to_cartesian(r, phi): :param r: radius of the segment (distance from the x-axis) :param phi: the angle coordinate in degrees - :returns: y, z -coordinates + :returns: y, z -coordinates in cartesian system """ phi = np.deg2rad(phi) y = r * np.cos(phi) @@ -135,65 +125,19 @@ def make_surface(coords): return verts, faces - def make_streamlines(vlsvFileName): - """Traces streamlines of velocity field from outside the magnetosphere to magnetotail using the yt-package. + """Traces streamlines of velocity field from outside the magnetosphere to magnetotail. :param vlsvFileName: directory and file name of .vlsv data file to use for VlsvReader :returns: streamlines as numpy array """ - ## make streamlines - boxre = [0] - - # bulk file - f = pt.vlsvfile.VlsvReader(file_name=vlsvFileName) - - #get box coordinates from data - [xmin, ymin, zmin, xmax, ymax, zmax] = f.get_spatial_mesh_extent() - [xsize, ysize, zsize] = f.get_spatial_mesh_size() - [xsizefs, ysizefs, zsizefs] = f.get_fsgrid_mesh_size() - simext_m =[xmin,xmax,ymin,ymax,zmin,zmax] - sizes = np.array([xsize,ysize,zsize]) - sizesfs = np.array([xsizefs,ysizefs,zsizefs]) - - simext = list(map(to_Re, simext_m)) - boxcoords=list(simext) - - cellids = f.read_variable("CellID") - indexids = cellids.argsort() - cellids = cellids[indexids] - - reflevel = ids3d.refinement_level(xsize, ysize, zsize, cellids[-1]) - - #Read the data from vlsv-file - V = f.read_variable("vg_v") - - #from m to Re - V = np.array([[to_Re(i) for i in j ]for j in V]) - - - V = V[indexids] - - if np.ndim(V)==1: - shape = None - elif np.ndim(V)==2: # vector variable - shape = V.shape[1] - elif np.ndim(V)==3: # tensor variable - shape = (V.shape[1], V.shape[2]) - - - Vdpoints = ids3d.idmesh3d2(cellids, V, reflevel, xsize, ysize, zsize, shape) - - - Vxs = Vdpoints[:,:,:,0] - Vys = Vdpoints[:,:,:,1] - Vzs = Vdpoints[:,:,:,2] + f = pt.vlsvfile.VlsvReader(file_name=vlsvFileName) - data=dict(Vx=Vxs,Vy=Vys,Vz=Vzs) + RE = 6371000 #Create streamline seeds (starting points for streamlines) - seedN = 50 #seeds per row, final seed count will be seedN*seedN ! + seedN = 25 #seeds per row, final seed count will be seedN*seedN ! streamline_seeds = np.zeros([seedN**2, 3]) #range: np.arange(from, to, step) @@ -204,24 +148,18 @@ def make_streamlines(vlsvFileName): streamline_seeds[k] = [20, i, j] k = k+1 + dx = 0.4 + iterations = int(50/(dx)) # iterations so that final step is at max -30 RE (in practice less) - #dataset in yt-form - yt_dataset = yt.load_uniform_grid( - data, - sizesfs, - bbox=np.array([[boxcoords[0], boxcoords[1]], - [boxcoords[2],boxcoords[3]], - [boxcoords[4],boxcoords[5]]])) - - - # data, seeds, dictionary positions, length of streamlines - streamlines_pos = yt.visualization.api.Streamlines(yt_dataset, streamline_seeds, - "Vx", "Vy", "Vz", length=50, direction=1) - - # make the streamlines - streamlines_pos.integrate_through_volume() + streams = pt.calculations.static_field_tracer_3d( + vlsvReader=f, + seed_coords=streamline_seeds*RE, + max_iterations=iterations, + dx=dx*RE, + direction='+', + grid_var='vg_v') - return np.array(streamlines_pos.streamlines) + return streams*(1/RE) def make_magnetopause(streams): From cf3f82d2e83bd00acbc1cb099c8216549e6e794e Mon Sep 17 00:00:00 2001 From: jreimi Date: Tue, 10 Jun 2025 11:20:19 +0300 Subject: [PATCH 062/124] 2d and 3d magnetopause scripts: Add function find_magnetopause() that takes a vlsvfile name and keyword arguments and returns magnetopause as triangle mesh vertices and faces (3d) or array (2d). Remove unnecessary functions, change units from RE to m. --- scripts/magnetopause2d.py | 146 +++++++++++-------------------- scripts/magnetopause3d.py | 177 +++++++++++++------------------------- 2 files changed, 108 insertions(+), 215 deletions(-) diff --git a/scripts/magnetopause2d.py b/scripts/magnetopause2d.py index 1e3d433f..de4bff7b 100644 --- a/scripts/magnetopause2d.py +++ b/scripts/magnetopause2d.py @@ -1,11 +1,10 @@ ''' -Finds the magnetopause position by tracing streamines of the plasma flow for two-dimensional Vlasiator runs. Needs the yt package. +Finds the magnetopause position by tracing streamlines of the plasma flow for two-dimensional Vlasiator runs. Needs the yt package. ''' import numpy as np import analysator as pt import yt -from yt.visualization.api import Streamlines def interpolate(streamline, x_points): @@ -28,28 +27,24 @@ def interpolate(streamline, x_points): return np.array([x_points, z_points]) -def make_streamlines(vlsvFileName): - """Traces streamlines of velocity field from outside the magnetosphere to magnetotail using the yt-package. +def make_streamlines(vlsvfile, streamline_seeds=None, streamline_length=40*6371000): + """Traces streamlines of velocity field using the yt package. + + :param vlsvfile: directory and file name of .vlsv data file to use for VlsvReader + :kword streamline_seeds: optional streamline starting points in numpy array (coordinates in meters including the y-coordinate 0.0) + :kword streamline_length: streamline length - :param vlsvFileName: directory and file name of vlsv file, to be given to VlsvReader :returns: streamlines as numpy array """ - ## make streamlines - boxre = [0,0] - + # bulk file - f = pt.vlsvfile.VlsvReader(file_name=vlsvFileName) + f = pt.vlsvfile.VlsvReader(file_name=vlsvfile) - #get box coordinates from data + # get box coordinates from data [xmin, ymin, zmin, xmax, ymax, zmax] = f.get_spatial_mesh_extent() [xsize, ysize, zsize] = f.get_spatial_mesh_size() - simext_m =[xmin,xmax,ymin,ymax,zmin,zmax] + simext =[xmin,xmax,ymin,ymax,zmin,zmax] sizes = np.array([xsize,ysize,zsize]) - - def to_Re(m): #meters to Re - return m/6371000 - - simext = list(map(to_Re, simext_m)) boxcoords=list(simext) cellids = f.read_variable("CellID") @@ -66,9 +61,9 @@ def to_Re(m): #meters to Re data=dict(Vx=Vxs,Vy=Vys,Vz=Vzs) - #Create streamline seeds (starting points for streamlines) - seedN = 200 # number of streamlines wanted - streamline_seeds = np.array([[20, 0 ,i] for i in np.linspace(-4, 4, seedN)]) + #Create starting points for streamlines if they are not given + if streamline_seeds == None: + streamline_seeds = np.array([[20*6371000, 0 ,i] for i in np.linspace(-5*6371000, 5*6371000, 200)]) #streamline_seeds = np.array(streamline_seeds) #dataset in yt-form @@ -80,40 +75,45 @@ def to_Re(m): #meters to Re [boxcoords[4],boxcoords[5]]])) - #data, seeds, dictionary positions, lenght of lines - streamlines_pos = yt.visualization.api.Streamlines(yt_dataset, streamline_seeds, - "Vx", "Vy", "Vz", length=40, direction=1) + #data, seeds, dictionary positions, step size + streamlines = yt.visualization.api.Streamlines(yt_dataset, streamline_seeds, + "Vx", "Vy", "Vz", length=streamline_length, direction=1) + + #trace the streamlines with yt + streamlines.integrate_through_volume() + # return streamline positions + return np.array(streamlines.streamlines) - #where the magic happens - streamlines_pos.integrate_through_volume() - return np.array(streamlines_pos.streamlines) -def make_magnetopause(streams): +def make_magnetopause(streamlines, end_x=-15*6371000, x_point_n=50): """Finds the mangetopause location based on streamlines. - :param streams: streamlines - :returns: magnetopause as coordinate points from tail on positive x-axis to tail on negative x-axis + :param streams: streamlines (coordinates in m) + :kword end_x: tail end x-coordinate (how far along the negative x-axis the magnetopause is calculated) + :kword x_point_n: integer, how many x-axis points the magnetopause will be divided in between the subsolar point and tail + + :returns: the magnetopause position as coordinate points in numpy array """ - streampoints = np.reshape(streams, (streams.shape[0]*streams.shape[1], 3)) #all the points in one array - + RE = 6371000 + + streampoints = np.reshape(streamlines, (streamlines.shape[0]*streamlines.shape[1], 3)) #all the points in one array + ## find the subsolar dayside point in the positive x-axis ## do this by finding a stremline point on positive x axis closest to the Earth - x_axis_points = streampoints[np.floor(streampoints[:,2])==0] - x_axis_points[x_axis_points<0] = 800 + x_axis_points = streampoints[(streampoints[:,2]< RE) & (streampoints[:,2]> -RE) & (streampoints[:,0]> 0)] subsolar_x =np.min(x_axis_points[:,0]) ## define points in the x axis where to find magnetopause points on the yz-plane - x_points = np.arange(subsolar_x, -10, -0.2) + x_points = np.linspace(subsolar_x, end_x, x_point_n) ## interpolate more exact points for streamlines at exery x_point - new_streampoints = np.zeros((len(x_points), len(streams), 1)) # new array for keeping interpolated streamlines in form streamlines_new[x_point, streamline, z-coordinate] - i=0 - for stream in streams: + new_streampoints = np.zeros((len(x_points), len(streamlines), 1)) # new array for keeping interpolated streamlines in form streamlines_new[x_point, streamline, z-coordinate] + + for i,stream in enumerate(streamlines): interpolated_streamline = interpolate(stream, x_points) for j in range(0, len(x_points)): new_streampoints[j, i,:] = interpolated_streamline[1,j] - i += 1 ## start making the magnetopause @@ -138,67 +138,19 @@ def make_magnetopause(streams): return magnetopause -def polar_to_cartesian(r, phi): - """Converts polar coordinates to cartesian. - - :param r: radius - :param phi: angular coordinate in degrees - :returns: y, z -coordinates in cartesian system - """ - phi = np.deg2rad(phi) - y = r * np.cos(phi) - z = r * np.sin(phi) - return(y, z) - - - -def main(): +def find_magnetopause(vlsvfile, streamline_seeds=None, streamline_length=45*6371000, end_x=-15*6371000, x_point_n=50): + """Finds the magnetopause position by tracing streamlines of the velocity field for 2d runs. - ## get bulk data - run = 'BFD' - num = '2000' - - fileLocation="/wrk-vakka/group/spacephysics/vlasiator/2D/"+run+"/bulk/" - fileN = "bulk.000"+num+".vlsv" - - - ## STREAMLINES - streams = make_streamlines(fileLocation+fileN) - - ## MAGNETOPAUSE - magnetopause = make_magnetopause(streams) - - - ## PLOTTING - outdir="" - - # plot the magnetopause (and streamlines if needed) on top of colormap - def external_plot(ax,XmeshXY=None, YmeshXY=None, pass_maps=None): - plot_magnetopause = True - plot_streamlines = False - - if plot_streamlines: - for stream in streams: - ax.plot(stream[:,0], stream[:,2], color='paleturquoise', alpha=0.2) - - if plot_magnetopause: - ax.plot(magnetopause[:,0], magnetopause[:,1], color='cyan', linewidth=1.0) - - - pt.plot.plot_colormap( - filename=fileLocation+fileN, - outputdir=outdir, - nooverwrite=None, - var="rho", #var="beta_star", - boxre = [-21,21,-21,21], - title=None, - draw=None, usesci=True, Earth=True, - external = external_plot, - run=run, - colormap='inferno', - ) + :param vlsvfile: directory and file name of .vlsv data file to use for VlsvReader + :kword streamline_seeds: optional streamline starting points in numpy array (coordinates in meters including the y-coordinate 0.0) + :kword streamline_length: streamline length for tracing + :kword end_x: tail end x-coordinate (how far along the negative x-axis the magnetopause is calculated) + :kword x_point_n: integer, how many x-axis points the magnetopause will be divided in between the subsolar point and tail + :returns: the magnetopause position as coordinate points in numpy array + """ + streamlines = make_streamlines(vlsvfile, streamline_seeds, streamline_length) + magnetopause = make_magnetopause(streamlines, end_x, x_point_n) -if __name__ == "__main__": - main() + return magnetopause diff --git a/scripts/magnetopause3d.py b/scripts/magnetopause3d.py index 9469d929..b97013fb 100644 --- a/scripts/magnetopause3d.py +++ b/scripts/magnetopause3d.py @@ -33,7 +33,7 @@ def polar_to_cartesian(r, phi): def interpolate(streamline, x_points): - """IInterpolates a single streamline for make_magnetopause(). + """Interpolates a single streamline for make_magnetopause(). :param streamline: a single streamline to be interpolated :param x_points: points in the x-axis to use for interpolation @@ -61,7 +61,7 @@ def make_surface(coords): '''Defines a surface constructed of input coordinates as triangles. :param coords: points that make the surface - :returns: list of verts and vert indices of surface triangles + :returns: list of verts and vert indices of surface triangles as numpy arrays input coordinates must be in form [...[c11, c21, c31, ... cn1],[c12, c22, c32, ... cn2],... where cij = [xij, yij, zij], i marks sector, j marks yz-plane (x_coord) index @@ -122,62 +122,73 @@ def make_surface(coords): next_triangles = [x + slices_in_plane*area_index for x in first_triangles] faces.extend(next_triangles) - return verts, faces + return np.array(verts), np.array(faces) -def make_streamlines(vlsvFileName): +def make_streamlines(vlsvfile, streamline_seeds=None, dl=2e6, iterations=200): """Traces streamlines of velocity field from outside the magnetosphere to magnetotail. - :param vlsvFileName: directory and file name of .vlsv data file to use for VlsvReader - :returns: streamlines as numpy array - """ + :param vlsvfile: directory and file name of .vlsv data file to use for VlsvReader - f = pt.vlsvfile.VlsvReader(file_name=vlsvFileName) + :kword streamline_seeds: optional streamline starting points in numpy array + :kword dl: streamline iteration step length in m + :kword iterations: int, number of iteration steps - RE = 6371000 + :returns: streamlines as numpy array + """ - #Create streamline seeds (starting points for streamlines) - seedN = 25 #seeds per row, final seed count will be seedN*seedN ! - streamline_seeds = np.zeros([seedN**2, 3]) + f = pt.vlsvfile.VlsvReader(file_name=vlsvfile) - #range: np.arange(from, to, step) - t = np.linspace(-5, 5, seedN) - k = 0 - for i in t: - for j in t: - streamline_seeds[k] = [20, i, j] - k = k+1 + # Create streamline starting points if they have not been given + if streamline_seeds == None: + streamline_seeds = np.zeros([25**2, 3]) - dx = 0.4 - iterations = int(50/(dx)) # iterations so that final step is at max -30 RE (in practice less) + t = np.linspace(-5*6371000, 5*6371000, 25) + k = 0 + for i in t: + for j in t: + streamline_seeds[k] = [20*6371000, i, j] + k = k+1 + # Trace the streamlines streams = pt.calculations.static_field_tracer_3d( vlsvReader=f, - seed_coords=streamline_seeds*RE, + seed_coords=streamline_seeds, max_iterations=iterations, - dx=dx*RE, + dx=dl, direction='+', grid_var='vg_v') - return streams*(1/RE) + return streams -def make_magnetopause(streams): +def make_magnetopause(streams, end_x=-15*6371000, x_point_n=50, sector_n=36): """Finds the mangetopause location based on streamlines. - :param streams: streamlines - :returns: the magnetopause position as coordinate points in numpy array + :param streams: streamlines (coordinates in m) + + :kword end_x: tail end x-coordinate (how far along the negative x-axis the magnetopause is calculated) + :kword x_point_n: integer, how many x-axis points the magnetopause will be divided in between the subsolar point and tail + :kword sector_n: integer, how many sectors the magnetopause will be divided in on each yz-plane + + :returns: the magnetopause position as coordinate points in numpy array, form [...[c11, c21, c31, ... cn1],[c12, c22, c32, ... cn2],... + where cij = [xij, yij, zij], i marks sector, j marks yz-plane (x-coordinate) index """ - streampoints = np.reshape(streams, (streams.shape[0]*streams.shape[1], 3)) #all the points in one array + RE = 6371000 + + #streams = streams*(1/RE) # streamlines in rE + streampoints = np.reshape(streams, (streams.shape[0]*streams.shape[1], 3)) #all the points in one array) ## find the subsolar dayside point in the x-axis - ## do this by finding a stremline point on positive x axis closest to the Earth - x_axis_points = streampoints[(np.floor(streampoints[:,1])==0) & (np.floor(streampoints[:,2])==0)] + ## do this by finding a streamline point on positive x axis closest to the Earth + # streampoints closer than ~1 rE to positive x-axis: + x_axis_points = streampoints[(streampoints[:,1]-RE) & (streampoints[:,2]>-RE) & (streampoints[:,0]>0) & (streampoints[:,0]>0)] subsolar_x =np.min(x_axis_points[:,0]) + ## define points in the x axis where to find magnetopause points on the yz-plane - x_points = np.arange(subsolar_x, -15, -0.5) + x_points = np.linspace(subsolar_x, end_x, x_point_n) ## interpolate more exact points for streamlines at exery x_point new_streampoints = np.zeros((len(x_points), len(streams), 2)) # new array for keeping interpolated streamlines in form new_streampoints[x_point, streamline, y and z -coordinates] @@ -202,7 +213,6 @@ def make_magnetopause(streams): ## now start making the magnetopause ## in each x_point, divide the plane into sectors and look for the closest streamline to x-axis in the sector - sector_n = 36 ## if given sector number isn't divisible by 4, make it so because we want to have magnetopause points at exactly y=0 and z=0 for 2d slices of the whole thing while sector_n%4 != 0: @@ -249,91 +259,22 @@ def make_magnetopause(streams): return magnetopause +def find_magnetopause(vlsvfile, streamline_seeds=None, dl=2e6, iterations=200, end_x=-15*6371000, x_point_n=50, sector_n=36): + """Finds the magnetopause position by tracing streamlines of the velocity field. + + :param vlsvfile: path to .vlsv bulk file to use for VlsvReader + :kword streamline_seeds: optional streamline starting points in numpy array + :kword dl: streamline iteration step length in m + :kword iterations: int, number of iteration steps + :kword end_x: tail end x-coordinate (how far along the x-axis the magnetopause is calculated) + :kword x_point_n: integer, how many x-axis points the magnetopause will be divided in between the subsolar point and tail + :kword sector_n: integer, how many sectors the magnetopause will be divided in on each yz-plane + + :returns: vertices, faces of the magnetopause triangle mesh as numpy arrays + """ + streams = make_streamlines(vlsvfile, streamline_seeds, dl, iterations) + magnetopause = make_magnetopause(streams, end_x, x_point_n, sector_n) + vertices, faces = make_surface(magnetopause) -def main(): - - ## get bulk data - run = 'EGI' - fileLocation="/wrk-vakka/group/spacephysics/vlasiator/3D/"+run+"/bulk/" - fileN = "bulk5.0000070.vlsv" - - ## STREAMLINES - streams = make_streamlines(fileLocation+fileN) - ## MAGNETOPAUSE - magnetopause = make_magnetopause(streams) - - - ## PLOTTING - outdir="" - - ## take separate arrays for different 2d slice plots - slices = magnetopause.shape[1] - quarter_slice = int(slices/4) - # xy plane: z=0 - xy_slice = np.concatenate((magnetopause[:,0][::-1], magnetopause[:,2*quarter_slice])) - # xz plane: y=0 - xz_slice = np.concatenate((magnetopause[:,quarter_slice][::-1], magnetopause[:,3*quarter_slice])) - - - #2D plots - # analysator 3dcolormapslice y=0 - if True: - def external_plot(ax,XmeshXY=None, YmeshXY=None, pass_maps=None, requestvariables=False): - if requestvariables==True: - return ['vg_v'] - ax.plot(xz_slice[:,0], xz_slice[:,2], color='limegreen', linewidth=1.5) - - - pt.plot.plot_colormap3dslice( - filename=fileLocation+fileN, - outputdir=outdir, - run=run, - nooverwrite=None, - boxre = [-21,21,-21,21], - title=None, - draw=None, usesci=True, Earth=True, - external = external_plot, - colormap='inferno', - normal='y' - ) - - # analysator 3dcolormapslice z=0 - if True: - def external_plot(ax,XmeshXY=None, YmeshXY=None, pass_maps=None, requestvariables=False): - if requestvariables==True: - return ['vg_v'] - ax.plot(xy_slice[:,0], xy_slice[:,1], color='limegreen', linewidth=1.5) - - pt.plot.plot_colormap3dslice( - filename=fileLocation+fileN, - outputdir=outdir, - run=run, - nooverwrite=None, - boxre = [-21,21,-21,21], - title=None, - draw=None, usesci=True, Earth=True, - external = external_plot, - colormap='inferno', - normal='z' - ) - - # 3D plot - # matplotlib 3d surface plot for single image - if False: - verts, faces = make_surface(magnetopause) - verts = np.array(verts) - - fig = plt.figure() - ax2 = fig.add_subplot(projection='3d') - ax2.plot_trisurf(verts[:, 0], verts[:,1], faces, verts[:, 2], linewidth=0.2, antialiased=True) - ax2.view_init(azim=-60, elev=5) - ax2.set_xlabel('X') - ax2.set_ylabel('Y') - ax2.set_zlabel('Z') - fig.tight_layout() - plt.savefig(outdir+run+'_3d_magnetopause.png') - - -if __name__ == "__main__": - main() + return vertices, faces From efbf1c0a24629d9909dcad25ba373c0666054319 Mon Sep 17 00:00:00 2001 From: jreimi Date: Thu, 12 Jun 2025 12:01:19 +0300 Subject: [PATCH 063/124] Moved magnetopause functions to analysator/pyCalculations --- {scripts => analysator/pyCalculations}/magnetopause2d.py | 0 {scripts => analysator/pyCalculations}/magnetopause3d.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {scripts => analysator/pyCalculations}/magnetopause2d.py (100%) rename {scripts => analysator/pyCalculations}/magnetopause3d.py (100%) diff --git a/scripts/magnetopause2d.py b/analysator/pyCalculations/magnetopause2d.py similarity index 100% rename from scripts/magnetopause2d.py rename to analysator/pyCalculations/magnetopause2d.py diff --git a/scripts/magnetopause3d.py b/analysator/pyCalculations/magnetopause3d.py similarity index 100% rename from scripts/magnetopause3d.py rename to analysator/pyCalculations/magnetopause3d.py From 83826df8fb0b83dda911f27f1ecf5bacd03a327b Mon Sep 17 00:00:00 2001 From: jreimi Date: Thu, 12 Jun 2025 12:34:39 +0300 Subject: [PATCH 064/124] Add magnetopause functions and surface function to calculations.py --- analysator/pyCalculations/calculations.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/analysator/pyCalculations/calculations.py b/analysator/pyCalculations/calculations.py index 7f5e940f..1ca715dc 100644 --- a/analysator/pyCalculations/calculations.py +++ b/analysator/pyCalculations/calculations.py @@ -61,3 +61,5 @@ from non_maxwellianity import epsilon_M from null_lines import LMN_null_lines_FOTE from interpolator_amr import AMRInterpolator, supported_amr_interpolators +from magnetopause2d import find_magnetopause +from magnetopause3d import find_magnetopause, make_surface From c67f732f874a2b4ea62af0cab72f59302a4c3b65 Mon Sep 17 00:00:00 2001 From: jreimi Date: Thu, 12 Jun 2025 13:59:32 +0300 Subject: [PATCH 065/124] Change surface triangles so that the normals have the same direction. --- analysator/pyCalculations/magnetopause3d.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/analysator/pyCalculations/magnetopause3d.py b/analysator/pyCalculations/magnetopause3d.py index b97013fb..d690abdb 100644 --- a/analysator/pyCalculations/magnetopause3d.py +++ b/analysator/pyCalculations/magnetopause3d.py @@ -122,7 +122,13 @@ def make_surface(coords): next_triangles = [x + slices_in_plane*area_index for x in first_triangles] faces.extend(next_triangles) - return np.array(verts), np.array(faces) + # Change every other face triangle normal direction so that all face the same way + faces = np.array(faces) + for i in range(len(faces)): + if i%2==0: + faces[i,1], faces[i,2] = faces[i,2], faces[i,1] + + return np.array(verts), faces def make_streamlines(vlsvfile, streamline_seeds=None, dl=2e6, iterations=200): From 10c0f412a8b5e8e1d711100c46a89b151a7ad7c6 Mon Sep 17 00:00:00 2001 From: jreimi Date: Thu, 12 Jun 2025 15:26:55 +0300 Subject: [PATCH 066/124] Changed function names for clarity, added possible keyword argumets for streamline seeds. --- analysator/pyCalculations/calculations.py | 4 ++-- analysator/pyCalculations/magnetopause2d.py | 14 ++++++++++---- analysator/pyCalculations/magnetopause3d.py | 20 +++++++++++++------- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/analysator/pyCalculations/calculations.py b/analysator/pyCalculations/calculations.py index 1ca715dc..cb39b3d6 100644 --- a/analysator/pyCalculations/calculations.py +++ b/analysator/pyCalculations/calculations.py @@ -61,5 +61,5 @@ from non_maxwellianity import epsilon_M from null_lines import LMN_null_lines_FOTE from interpolator_amr import AMRInterpolator, supported_amr_interpolators -from magnetopause2d import find_magnetopause -from magnetopause3d import find_magnetopause, make_surface +from magnetopause2d import find_magnetopause_2d +from magnetopause3d import find_magnetopause_3d, make_surface diff --git a/analysator/pyCalculations/magnetopause2d.py b/analysator/pyCalculations/magnetopause2d.py index de4bff7b..203f6ebb 100644 --- a/analysator/pyCalculations/magnetopause2d.py +++ b/analysator/pyCalculations/magnetopause2d.py @@ -27,11 +27,14 @@ def interpolate(streamline, x_points): return np.array([x_points, z_points]) -def make_streamlines(vlsvfile, streamline_seeds=None, streamline_length=40*6371000): +def make_streamlines(vlsvfile, streamline_seeds=None,seeds_n=200, seeds_x0=20*6371000, seeds_range=[-5*6371000, 5*6371000], streamline_length=40*6371000): """Traces streamlines of velocity field using the yt package. :param vlsvfile: directory and file name of .vlsv data file to use for VlsvReader :kword streamline_seeds: optional streamline starting points in numpy array (coordinates in meters including the y-coordinate 0.0) + :kword seeds_n: instead of streamline_seeds provide a number of streamlines to be traced + :kword seeds_x0: instead of streamline_seeds provide an x-coordinate for streamline starting points + :kword seeds_range: instead of streamline_seeds provide [min, max] range to use for streamline starting point z-coordinates :kword streamline_length: streamline length :returns: streamlines as numpy array @@ -63,7 +66,7 @@ def make_streamlines(vlsvfile, streamline_seeds=None, streamline_length=40*63710 #Create starting points for streamlines if they are not given if streamline_seeds == None: - streamline_seeds = np.array([[20*6371000, 0 ,i] for i in np.linspace(-5*6371000, 5*6371000, 200)]) + streamline_seeds = np.array([[seeds_x0, 0 ,i] for i in np.linspace(seeds_range[0], seeds_range[1], seeds_n)]) #streamline_seeds = np.array(streamline_seeds) #dataset in yt-form @@ -138,11 +141,14 @@ def make_magnetopause(streamlines, end_x=-15*6371000, x_point_n=50): return magnetopause -def find_magnetopause(vlsvfile, streamline_seeds=None, streamline_length=45*6371000, end_x=-15*6371000, x_point_n=50): +def find_magnetopause_2d(vlsvfile, streamline_seeds=None, seeds_n=200, seeds_x0=20*6371000, seeds_range=[-5*6371000, 5*6371000], streamline_length=45*6371000, end_x=-15*6371000, x_point_n=50): """Finds the magnetopause position by tracing streamlines of the velocity field for 2d runs. :param vlsvfile: directory and file name of .vlsv data file to use for VlsvReader :kword streamline_seeds: optional streamline starting points in numpy array (coordinates in meters including the y-coordinate 0.0) + :kword seeds_n: instead of streamline_seeds provide a number of streamlines to be traced + :kword seeds_x0: instead of streamline_seeds provide an x-coordinate for streamline starting points + :kword seeds_range: instead of streamline_seeds provide [min, max] range to use for streamline starting point z-coordinates :kword streamline_length: streamline length for tracing :kword end_x: tail end x-coordinate (how far along the negative x-axis the magnetopause is calculated) :kword x_point_n: integer, how many x-axis points the magnetopause will be divided in between the subsolar point and tail @@ -150,7 +156,7 @@ def find_magnetopause(vlsvfile, streamline_seeds=None, streamline_length=45*6371 :returns: the magnetopause position as coordinate points in numpy array """ - streamlines = make_streamlines(vlsvfile, streamline_seeds, streamline_length) + streamlines = make_streamlines(vlsvfile, streamline_seeds, seeds_n, seeds_x0, seeds_range, streamline_length) magnetopause = make_magnetopause(streamlines, end_x, x_point_n) return magnetopause diff --git a/analysator/pyCalculations/magnetopause3d.py b/analysator/pyCalculations/magnetopause3d.py index d690abdb..d2505e85 100644 --- a/analysator/pyCalculations/magnetopause3d.py +++ b/analysator/pyCalculations/magnetopause3d.py @@ -131,12 +131,15 @@ def make_surface(coords): return np.array(verts), faces -def make_streamlines(vlsvfile, streamline_seeds=None, dl=2e6, iterations=200): +def make_streamlines(vlsvfile, streamline_seeds=None, seeds_n=25, seeds_x0=20*6371000, seeds_range=[-5*6371000, 5*6371000], dl=2e6, iterations=200): """Traces streamlines of velocity field from outside the magnetosphere to magnetotail. :param vlsvfile: directory and file name of .vlsv data file to use for VlsvReader :kword streamline_seeds: optional streamline starting points in numpy array + :kword seeds_n: instead of streamline_seeds provide a number of streamlines to be traced + :kword seeds_x0: instead of streamline_seeds provide an x-coordinate for streamline starting points + :kword seeds_range: instead of streamline_seeds provide [min, max] range to use for streamline starting point coordinates (both y- and z-directions use the same range) :kword dl: streamline iteration step length in m :kword iterations: int, number of iteration steps @@ -145,15 +148,15 @@ def make_streamlines(vlsvfile, streamline_seeds=None, dl=2e6, iterations=200): f = pt.vlsvfile.VlsvReader(file_name=vlsvfile) - # Create streamline starting points if they have not been given + # Create streamline starting points if needed if streamline_seeds == None: - streamline_seeds = np.zeros([25**2, 3]) + streamline_seeds = np.zeros([seeds_n**2, 3]) - t = np.linspace(-5*6371000, 5*6371000, 25) + t = np.linspace(seeds_range[0], seeds_range[1], seeds_n) k = 0 for i in t: for j in t: - streamline_seeds[k] = [20*6371000, i, j] + streamline_seeds[k] = [seeds_x0, i, j] k = k+1 # Trace the streamlines @@ -265,11 +268,14 @@ def make_magnetopause(streams, end_x=-15*6371000, x_point_n=50, sector_n=36): return magnetopause -def find_magnetopause(vlsvfile, streamline_seeds=None, dl=2e6, iterations=200, end_x=-15*6371000, x_point_n=50, sector_n=36): +def find_magnetopause_3d(vlsvfile, streamline_seeds=None, seeds_n=25, seeds_x0=20*6371000, seeds_range=[-5*6371000, 5*6371000], dl=2e6, iterations=200, end_x=-15*6371000, x_point_n=50, sector_n=36): """Finds the magnetopause position by tracing streamlines of the velocity field. :param vlsvfile: path to .vlsv bulk file to use for VlsvReader :kword streamline_seeds: optional streamline starting points in numpy array + :kword seeds_n: instead of streamline_seeds provide a number of streamlines to be traced + :kword seeds_x0: instead of streamline_seeds provide an x-coordinate for streamline starting points + :kword seeds_range: instead of streamline_seeds provide [min, max] range to use for streamline starting point coordinates (both y- and z-directions use the same range) :kword dl: streamline iteration step length in m :kword iterations: int, number of iteration steps :kword end_x: tail end x-coordinate (how far along the x-axis the magnetopause is calculated) @@ -279,7 +285,7 @@ def find_magnetopause(vlsvfile, streamline_seeds=None, dl=2e6, iterations=200, e :returns: vertices, faces of the magnetopause triangle mesh as numpy arrays """ - streams = make_streamlines(vlsvfile, streamline_seeds, dl, iterations) + streams = make_streamlines(vlsvfile, streamline_seeds, seeds_n, seeds_x0, seeds_range, dl, iterations) magnetopause = make_magnetopause(streams, end_x, x_point_n, sector_n) vertices, faces = make_surface(magnetopause) From 59a6c21aa2e40ed5b800b165a39fb1abcd02a5cf Mon Sep 17 00:00:00 2001 From: jreimi Date: Fri, 13 Jun 2025 11:28:30 +0300 Subject: [PATCH 067/124] Added example files for using magnetopause functions and plotting their output. --- examples/plot_2d_magnetopause.py | 35 +++++++++++++ examples/plot_3d_magnetopause.py | 85 ++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 examples/plot_2d_magnetopause.py create mode 100644 examples/plot_3d_magnetopause.py diff --git a/examples/plot_2d_magnetopause.py b/examples/plot_2d_magnetopause.py new file mode 100644 index 00000000..71e92213 --- /dev/null +++ b/examples/plot_2d_magnetopause.py @@ -0,0 +1,35 @@ +""" +An example of using find_magnetopause_2d() and plotting the output over colormap. +""" + +import analysator as pt + +run = 'BFD' +file_name="/wrk-vakka/group/spacephysics/vlasiator/2D/"+run+"/bulk/bulk.0002000.vlsv" +outdir= "" + +# magnetopause points +magnetopause = pt.calculations.find_magnetopause_2d( + file_name, + streamline_seeds=None, + seeds_n=200, + seeds_x0=20*6371000, + seeds_range=[-5*6371000, 5*6371000], + streamline_length=45*6371000, + end_x=-15*6371000, + x_point_n=50) + +magnetopause= magnetopause*(1/6371000) # in RE + +def external_plot(ax,XmeshXY=None, YmeshXY=None, pass_maps=None): + ax.plot(magnetopause[:,0], magnetopause[:,1], color='cyan', linewidth=1.0) + +pt.plot.plot_colormap( + filename=file_name, + var="rho", + boxre = [-21,21,-21,21], + Earth=True, + external = external_plot, + run=run, + colormap='inferno', + ) diff --git a/examples/plot_3d_magnetopause.py b/examples/plot_3d_magnetopause.py new file mode 100644 index 00000000..3d1ac7d5 --- /dev/null +++ b/examples/plot_3d_magnetopause.py @@ -0,0 +1,85 @@ +""" +An example of using find_magnetopause_3d() and plotting the output. +""" + +import numpy as np +import matplotlib.pyplot as plt +import analysator as pt + +## get bulk data +run = 'EGI' # for plot output file name +file_name="/wrk-vakka/group/spacephysics/vlasiator/3D/"+run+"/bulk/bulk5.0000070.vlsv" + +## magnetopause +x_point_n = 50 # number needed for 2d slice plots, default is 50 + +vertices, faces = pt.calculations.find_magnetopause_3d( + file_name, + streamline_seeds=None, + seeds_n=25, + seeds_x0=20*6371000, + seeds_range=[-5*6371000, 5*6371000], + dl=2e6, + iterations=200, + end_x=-15*6371000, + x_point_n=x_point_n, + sector_n=36) + + +outdir="" # output plot save directory + +### 3D surface plot ### + +fig = plt.figure() +ax = fig.add_subplot(projection='3d') +ax.plot_trisurf(vertices[:, 0], vertices[:,1], faces, vertices[:,2], linewidth=0.2) +plt.savefig(outdir+run+'_magnetopause_3D_surface.png') +plt.close() + + +### 2D slice plots ### + +vertices = vertices/6371000 # to RE +magnetopause = np.array(np.split(vertices, x_point_n+1)) # magnetopause points grouped by x-axis coordinate + +## take separate arrays for different 2d slice plots + +quarter_slice = int(np.shape(magnetopause)[1]/4) +# xy plane: z=0 +xy_slice = np.concatenate((magnetopause[:,0][::-1], magnetopause[:,2*quarter_slice])) +# xz plane: y=0 +xz_slice = np.concatenate((magnetopause[:,quarter_slice][::-1], magnetopause[:,3*quarter_slice])) + + +# analysator 3dcolormapslice y=0 + +def external_plot(ax,XmeshXY=None, YmeshXY=None, pass_maps=None, requestvariables=False): + if requestvariables==True: + return ['vg_v'] + ax.plot(xz_slice[:,0], xz_slice[:,2], color='limegreen', linewidth=1.5) + +pt.plot.plot_colormap3dslice( +filename=file_name, +run=run, +outputdir=outdir, +boxre = [-21,21,-21,21], +external = external_plot, +colormap='inferno', +normal='y' +) + +# analysator 3dcolormapslice z=0 + +def external_plot(ax,XmeshXY=None, YmeshXY=None, pass_maps=None, requestvariables=False): + if requestvariables==True: + return ['vg_v'] + ax.plot(xy_slice[:,0], xy_slice[:,1], color='limegreen', linewidth=1.5) + +pt.plot.plot_colormap3dslice( +filename=file_name, +outputdir=outdir, +run=run, +boxre = [-21,21,-21,21], +external = external_plot, +colormap='inferno', +normal='z') From 6a802562c5461b108d5c54a7334b8194305152ee Mon Sep 17 00:00:00 2001 From: Markku Alho Date: Mon, 16 Jun 2025 09:54:01 +0300 Subject: [PATCH 068/124] Added yt dependency for the 2d script --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index ceeb7f0d..a1c26867 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,6 +26,7 @@ dependencies = [ "matplotlib", "packaging", "scikit-image", + "yt" ] [project.optional-dependencies] none = [ From 8f51471f4a746ee1a493790155038da56cac96d3 Mon Sep 17 00:00:00 2001 From: jreimi Date: Mon, 14 Jul 2025 15:20:56 +0300 Subject: [PATCH 069/124] function and file name changes and removal of unnecessary surface making function from calculations --- analysator/pyCalculations/calculations.py | 4 ++-- .../{magnetopause2d.py => magnetopause_sw_streamline_2d.py} | 2 +- .../{magnetopause3d.py => magnetopause_sw_streamline_3d.py} | 2 +- ..._2d_magnetopause.py => plot_streamline_magnetopause_2d.py} | 2 +- ..._3d_magnetopause.py => plot_streamline_magnetopause_3d.py} | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) rename analysator/pyCalculations/{magnetopause2d.py => magnetopause_sw_streamline_2d.py} (97%) rename analysator/pyCalculations/{magnetopause3d.py => magnetopause_sw_streamline_3d.py} (98%) rename examples/{plot_2d_magnetopause.py => plot_streamline_magnetopause_2d.py} (92%) rename examples/{plot_3d_magnetopause.py => plot_streamline_magnetopause_3d.py} (96%) diff --git a/analysator/pyCalculations/calculations.py b/analysator/pyCalculations/calculations.py index cb39b3d6..d9ea5bfd 100644 --- a/analysator/pyCalculations/calculations.py +++ b/analysator/pyCalculations/calculations.py @@ -61,5 +61,5 @@ from non_maxwellianity import epsilon_M from null_lines import LMN_null_lines_FOTE from interpolator_amr import AMRInterpolator, supported_amr_interpolators -from magnetopause2d import find_magnetopause_2d -from magnetopause3d import find_magnetopause_3d, make_surface +from magnetopause_sw_streamline_2d import find_magnetopause_sw_streamline_2d +from magnetopause_sw_streamline_3d import find_magnetopause_sw_streamline_3d diff --git a/analysator/pyCalculations/magnetopause2d.py b/analysator/pyCalculations/magnetopause_sw_streamline_2d.py similarity index 97% rename from analysator/pyCalculations/magnetopause2d.py rename to analysator/pyCalculations/magnetopause_sw_streamline_2d.py index 203f6ebb..4cf2a3b2 100644 --- a/analysator/pyCalculations/magnetopause2d.py +++ b/analysator/pyCalculations/magnetopause_sw_streamline_2d.py @@ -141,7 +141,7 @@ def make_magnetopause(streamlines, end_x=-15*6371000, x_point_n=50): return magnetopause -def find_magnetopause_2d(vlsvfile, streamline_seeds=None, seeds_n=200, seeds_x0=20*6371000, seeds_range=[-5*6371000, 5*6371000], streamline_length=45*6371000, end_x=-15*6371000, x_point_n=50): +def find_magnetopause_sw_streamline_2d(vlsvfile, streamline_seeds=None, seeds_n=200, seeds_x0=20*6371000, seeds_range=[-5*6371000, 5*6371000], streamline_length=45*6371000, end_x=-15*6371000, x_point_n=50): """Finds the magnetopause position by tracing streamlines of the velocity field for 2d runs. :param vlsvfile: directory and file name of .vlsv data file to use for VlsvReader diff --git a/analysator/pyCalculations/magnetopause3d.py b/analysator/pyCalculations/magnetopause_sw_streamline_3d.py similarity index 98% rename from analysator/pyCalculations/magnetopause3d.py rename to analysator/pyCalculations/magnetopause_sw_streamline_3d.py index d2505e85..c6d1c6c1 100644 --- a/analysator/pyCalculations/magnetopause3d.py +++ b/analysator/pyCalculations/magnetopause_sw_streamline_3d.py @@ -268,7 +268,7 @@ def make_magnetopause(streams, end_x=-15*6371000, x_point_n=50, sector_n=36): return magnetopause -def find_magnetopause_3d(vlsvfile, streamline_seeds=None, seeds_n=25, seeds_x0=20*6371000, seeds_range=[-5*6371000, 5*6371000], dl=2e6, iterations=200, end_x=-15*6371000, x_point_n=50, sector_n=36): +def find_magnetopause_sw_streamline_3d(vlsvfile, streamline_seeds=None, seeds_n=25, seeds_x0=20*6371000, seeds_range=[-5*6371000, 5*6371000], dl=2e6, iterations=200, end_x=-15*6371000, x_point_n=50, sector_n=36): """Finds the magnetopause position by tracing streamlines of the velocity field. :param vlsvfile: path to .vlsv bulk file to use for VlsvReader diff --git a/examples/plot_2d_magnetopause.py b/examples/plot_streamline_magnetopause_2d.py similarity index 92% rename from examples/plot_2d_magnetopause.py rename to examples/plot_streamline_magnetopause_2d.py index 71e92213..7358c8b1 100644 --- a/examples/plot_2d_magnetopause.py +++ b/examples/plot_streamline_magnetopause_2d.py @@ -9,7 +9,7 @@ outdir= "" # magnetopause points -magnetopause = pt.calculations.find_magnetopause_2d( +magnetopause = pt.calculations.find_magnetopause_sw_streamline_2d( file_name, streamline_seeds=None, seeds_n=200, diff --git a/examples/plot_3d_magnetopause.py b/examples/plot_streamline_magnetopause_3d.py similarity index 96% rename from examples/plot_3d_magnetopause.py rename to examples/plot_streamline_magnetopause_3d.py index 3d1ac7d5..1cb8d1d9 100644 --- a/examples/plot_3d_magnetopause.py +++ b/examples/plot_streamline_magnetopause_3d.py @@ -13,7 +13,7 @@ ## magnetopause x_point_n = 50 # number needed for 2d slice plots, default is 50 -vertices, faces = pt.calculations.find_magnetopause_3d( +vertices, faces = pt.calculations.find_magnetopause_sw_streamline_3d( file_name, streamline_seeds=None, seeds_n=25, From cddf3af7280a2ab182f1fec20b1c091bc7dc7609 Mon Sep 17 00:00:00 2001 From: jreimi Date: Tue, 15 Jul 2025 10:03:17 +0300 Subject: [PATCH 070/124] Changed magnetopause stuff names in the sphinx directory to (hopefully) fix import issues --- Documentation/sphinx/magnetopause2d.rst | 5 ----- Documentation/sphinx/magnetopause3d.rst | 5 ----- .../sphinx/magnetopause_sw_streamline_2d.rst | 5 +++++ .../sphinx/magnetopause_sw_streamline_3d.rst | 5 +++++ Documentation/sphinx/scripts.rst | 16 ++++++++-------- 5 files changed, 18 insertions(+), 18 deletions(-) delete mode 100644 Documentation/sphinx/magnetopause2d.rst delete mode 100644 Documentation/sphinx/magnetopause3d.rst create mode 100644 Documentation/sphinx/magnetopause_sw_streamline_2d.rst create mode 100644 Documentation/sphinx/magnetopause_sw_streamline_3d.rst diff --git a/Documentation/sphinx/magnetopause2d.rst b/Documentation/sphinx/magnetopause2d.rst deleted file mode 100644 index 8e1717f1..00000000 --- a/Documentation/sphinx/magnetopause2d.rst +++ /dev/null @@ -1,5 +0,0 @@ -magnetopause2d --------------- - -.. automodule:: magnetopause2d - :members: \ No newline at end of file diff --git a/Documentation/sphinx/magnetopause3d.rst b/Documentation/sphinx/magnetopause3d.rst deleted file mode 100644 index 7d90752f..00000000 --- a/Documentation/sphinx/magnetopause3d.rst +++ /dev/null @@ -1,5 +0,0 @@ -magnetopause3d --------------- - -.. automodule:: magnetopause3d - :members: \ No newline at end of file diff --git a/Documentation/sphinx/magnetopause_sw_streamline_2d.rst b/Documentation/sphinx/magnetopause_sw_streamline_2d.rst new file mode 100644 index 00000000..6822aac9 --- /dev/null +++ b/Documentation/sphinx/magnetopause_sw_streamline_2d.rst @@ -0,0 +1,5 @@ +magnetopause_sw_streamline_2d +----------------------------- + +.. automodule:: magnetopause_sw_streamline_2d + :members: \ No newline at end of file diff --git a/Documentation/sphinx/magnetopause_sw_streamline_3d.rst b/Documentation/sphinx/magnetopause_sw_streamline_3d.rst new file mode 100644 index 00000000..590ca38a --- /dev/null +++ b/Documentation/sphinx/magnetopause_sw_streamline_3d.rst @@ -0,0 +1,5 @@ +magnetopause_sw_streamline_3d +----------------------------- + +.. automodule:: magnetopause_sw_streamline_3d + :members: \ No newline at end of file diff --git a/Documentation/sphinx/scripts.rst b/Documentation/sphinx/scripts.rst index ebb57f90..01c308d9 100644 --- a/Documentation/sphinx/scripts.rst +++ b/Documentation/sphinx/scripts.rst @@ -30,20 +30,20 @@ gics ------------ -magnetopause2d --------------- -:doc:`magnetopause2d` +magnetopause_sw_streamline_2d +----------------------------- +:doc:`magnetopause_sw_streamline_2d` -.. automodule:: magnetopause2d +.. automodule:: magnetopause_sw_streamline_2d :no-index: ------------ -magnetopause3d --------------- -:doc:`magnetopause3d` +magnetopause_sw_streamline_3d +----------------------------- +:doc:`magnetopause_sw_streamline_3d` -.. automodule:: magnetopause3d +.. automodule:: magnetopause_sw_streamline_3d :no-index: ------------ From b253e11d14a8f8fefeffb78711ca00551575b868 Mon Sep 17 00:00:00 2001 From: jreimi Date: Tue, 15 Jul 2025 10:07:32 +0300 Subject: [PATCH 071/124] Added fix for toctree --- Documentation/sphinx/scripts.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/sphinx/scripts.rst b/Documentation/sphinx/scripts.rst index 01c308d9..e7df0ef8 100644 --- a/Documentation/sphinx/scripts.rst +++ b/Documentation/sphinx/scripts.rst @@ -91,8 +91,8 @@ tsyganenko biot_savart cutthrough_timeseries gics - magnetopause2d - magnetopause3d + magnetopause_sw_streamline_2d + magnetopause_sw_streamline_3d obliqueshock obliqueshock_nif shue From 6daa976bc2ffea579129ad50f8047a3ff6670cd7 Mon Sep 17 00:00:00 2001 From: jreimi Date: Mon, 16 Jun 2025 10:10:47 +0300 Subject: [PATCH 072/124] Function name changes, removed unused import --- analysator/pyCalculations/magnetopause_sw_streamline_3d.py | 1 - 1 file changed, 1 deletion(-) diff --git a/analysator/pyCalculations/magnetopause_sw_streamline_3d.py b/analysator/pyCalculations/magnetopause_sw_streamline_3d.py index c6d1c6c1..18dcd34b 100644 --- a/analysator/pyCalculations/magnetopause_sw_streamline_3d.py +++ b/analysator/pyCalculations/magnetopause_sw_streamline_3d.py @@ -2,7 +2,6 @@ Finds the magnetopause position by tracing streamlines of the plasma flow for three-dimensional Vlasiator runs. ''' -import matplotlib.pyplot as plt import numpy as np import analysator as pt From 61b4fb224d89f201376f4f12f9fc227f14dc529a Mon Sep 17 00:00:00 2001 From: jreimi Date: Thu, 26 Jun 2025 09:38:36 +0300 Subject: [PATCH 073/124] Fix to surface making and an attempt to make the subsolar area nicer --- .../magnetopause_sw_streamline_3d.py | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/analysator/pyCalculations/magnetopause_sw_streamline_3d.py b/analysator/pyCalculations/magnetopause_sw_streamline_3d.py index 18dcd34b..8fa965bc 100644 --- a/analysator/pyCalculations/magnetopause_sw_streamline_3d.py +++ b/analysator/pyCalculations/magnetopause_sw_streamline_3d.py @@ -121,10 +121,18 @@ def make_surface(coords): next_triangles = [x + slices_in_plane*area_index for x in first_triangles] faces.extend(next_triangles) - # Change every other face triangle normal direction so that all face the same way - faces = np.array(faces) - for i in range(len(faces)): + # From last triangles remove every other triangle + # (a single subsolar point -> last triangles are actual triangles instead of rectangles sliced in two) + removed = 0 + for i in range(len(faces)-slices_in_plane, len(faces)): if i%2==0: + faces.pop(i-removed) + removed += 1 + + # Change every other face triangle (except for last slice triangles) normal direction so that all face the same way + faces = np.array(faces) + for i in range(len(faces)-int(slices_in_plane/2)): + if i%2!=0: faces[i,1], faces[i,2] = faces[i,2], faces[i,1] return np.array(verts), faces @@ -196,7 +204,10 @@ def make_magnetopause(streams, end_x=-15*6371000, x_point_n=50, sector_n=36): ## define points in the x axis where to find magnetopause points on the yz-plane - x_points = np.linspace(subsolar_x, end_x, x_point_n) + #dx = (subsolar_x-end_x)/x_point_n + next_from_subsolar_x = subsolar_x-1e3 # start making the magnetopause from a point slightly inwards from subsolar point + x_point_n = x_point_n-1 + x_points = np.linspace(next_from_subsolar_x, end_x, x_point_n) ## interpolate more exact points for streamlines at exery x_point new_streampoints = np.zeros((len(x_points), len(streams), 2)) # new array for keeping interpolated streamlines in form new_streampoints[x_point, streamline, y and z -coordinates] @@ -249,7 +260,7 @@ def make_magnetopause(streams, end_x=-15*6371000, x_point_n=50, sector_n=36): # discard 'points' with r=0 and check that there's at least one streamline point in the sector sector_points = sector_points[sector_points[:,0] != 0.0] if sector_points.size == 0: - raise ValueError('No streamlines found in the sector') + raise ValueError('No streamlines found in the sector, x_i=',i) # find the points closest to the x-axis closest_point_radius = sector_points[sector_points[:,0].argmin(), 0] # smallest radius From 2d65f545c91982d9f9902d9f0f1c0cc2e179aa4c Mon Sep 17 00:00:00 2001 From: jreimi Date: Tue, 15 Jul 2025 11:08:34 +0300 Subject: [PATCH 074/124] More possible fixes to surface making, function name changes also to examples --- .../magnetopause_sw_streamline_3d.py | 22 +++++++++++++++---- examples/plot_streamline_magnetopause_2d.py | 2 +- examples/plot_streamline_magnetopause_3d.py | 4 ++-- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/analysator/pyCalculations/magnetopause_sw_streamline_3d.py b/analysator/pyCalculations/magnetopause_sw_streamline_3d.py index 8fa965bc..b955dadf 100644 --- a/analysator/pyCalculations/magnetopause_sw_streamline_3d.py +++ b/analysator/pyCalculations/magnetopause_sw_streamline_3d.py @@ -124,14 +124,28 @@ def make_surface(coords): # From last triangles remove every other triangle # (a single subsolar point -> last triangles are actual triangles instead of rectangles sliced in two) removed = 0 - for i in range(len(faces)-slices_in_plane, len(faces)): - if i%2==0: + for i in range(len(faces)-slices_in_plane*2, len(faces)): + if i%2!=0: faces.pop(i-removed) removed += 1 - # Change every other face triangle (except for last slice triangles) normal direction so that all face the same way + # From last triangles remove every other triangle + # (a single subsolar point -> last triangles are actual triangles instead of rectangles sliced in two) + # Also fix the last triangles so that they only point to one subsolar point and have normals towards outside + subsolar_index = int(len(verts)-slices_in_plane) + + for i,triangle in enumerate(reversed(faces)): + if i > (slices_in_plane): # faces not in last plane (we're going backwards) + break + + faces[len(faces)-i-1] = np.clip(triangle, a_min=0, a_max=subsolar_index) + + # this would remove duplicate subsolar points from vertices but makes 2d slicing harder + #verts = verts[:int(len(verts)-slices_in_plane+1)] + + # Change every other face triangle (except for last slice triangles) normal direction so that all face the same way (hopefully) faces = np.array(faces) - for i in range(len(faces)-int(slices_in_plane/2)): + for i in range(len(faces)-int(slices_in_plane)): if i%2!=0: faces[i,1], faces[i,2] = faces[i,2], faces[i,1] diff --git a/examples/plot_streamline_magnetopause_2d.py b/examples/plot_streamline_magnetopause_2d.py index 7358c8b1..933daf23 100644 --- a/examples/plot_streamline_magnetopause_2d.py +++ b/examples/plot_streamline_magnetopause_2d.py @@ -1,5 +1,5 @@ """ -An example of using find_magnetopause_2d() and plotting the output over colormap. +An example of using find_magnetopause_2d_sw_streamline() and plotting the output over colormap. """ import analysator as pt diff --git a/examples/plot_streamline_magnetopause_3d.py b/examples/plot_streamline_magnetopause_3d.py index 1cb8d1d9..40b4e1a4 100644 --- a/examples/plot_streamline_magnetopause_3d.py +++ b/examples/plot_streamline_magnetopause_3d.py @@ -1,5 +1,5 @@ """ -An example of using find_magnetopause_3d() and plotting the output. +An example of using find_find_magnetopause_3d_sw_streamline() and plotting the output. """ import numpy as np @@ -40,7 +40,7 @@ ### 2D slice plots ### vertices = vertices/6371000 # to RE -magnetopause = np.array(np.split(vertices, x_point_n+1)) # magnetopause points grouped by x-axis coordinate +magnetopause = np.array(np.split(vertices, x_point_n)) # magnetopause points grouped by x-axis coordinate ## take separate arrays for different 2d slice plots From 42a425efadfe5027ee7c4bad9c01df59681dc9aa Mon Sep 17 00:00:00 2001 From: jreimi Date: Tue, 15 Jul 2025 11:55:31 +0300 Subject: [PATCH 075/124] Removed streamline magnetopause from scripts in sphinx --- .../sphinx/magnetopause_sw_streamline_2d.rst | 5 ----- .../sphinx/magnetopause_sw_streamline_3d.rst | 5 ----- Documentation/sphinx/scripts.rst | 19 ------------------- 3 files changed, 29 deletions(-) delete mode 100644 Documentation/sphinx/magnetopause_sw_streamline_2d.rst delete mode 100644 Documentation/sphinx/magnetopause_sw_streamline_3d.rst diff --git a/Documentation/sphinx/magnetopause_sw_streamline_2d.rst b/Documentation/sphinx/magnetopause_sw_streamline_2d.rst deleted file mode 100644 index 6822aac9..00000000 --- a/Documentation/sphinx/magnetopause_sw_streamline_2d.rst +++ /dev/null @@ -1,5 +0,0 @@ -magnetopause_sw_streamline_2d ------------------------------ - -.. automodule:: magnetopause_sw_streamline_2d - :members: \ No newline at end of file diff --git a/Documentation/sphinx/magnetopause_sw_streamline_3d.rst b/Documentation/sphinx/magnetopause_sw_streamline_3d.rst deleted file mode 100644 index 590ca38a..00000000 --- a/Documentation/sphinx/magnetopause_sw_streamline_3d.rst +++ /dev/null @@ -1,5 +0,0 @@ -magnetopause_sw_streamline_3d ------------------------------ - -.. automodule:: magnetopause_sw_streamline_3d - :members: \ No newline at end of file diff --git a/Documentation/sphinx/scripts.rst b/Documentation/sphinx/scripts.rst index e7df0ef8..a304dd49 100644 --- a/Documentation/sphinx/scripts.rst +++ b/Documentation/sphinx/scripts.rst @@ -30,23 +30,6 @@ gics ------------ -magnetopause_sw_streamline_2d ------------------------------ -:doc:`magnetopause_sw_streamline_2d` - -.. automodule:: magnetopause_sw_streamline_2d - :no-index: - ------------- - -magnetopause_sw_streamline_3d ------------------------------ -:doc:`magnetopause_sw_streamline_3d` - -.. automodule:: magnetopause_sw_streamline_3d - :no-index: - ------------- obliqueshock ------------ @@ -91,8 +74,6 @@ tsyganenko biot_savart cutthrough_timeseries gics - magnetopause_sw_streamline_2d - magnetopause_sw_streamline_3d obliqueshock obliqueshock_nif shue From b0a2b24b0d6bd27a67ce5d23a9d647ef1bd4bfc1 Mon Sep 17 00:00:00 2001 From: jreimi Date: Thu, 17 Jul 2025 13:17:28 +0300 Subject: [PATCH 076/124] Move streamline magnetopause script usage examples to scripts --- {examples => scripts}/plot_streamline_magnetopause_2d.py | 0 {examples => scripts}/plot_streamline_magnetopause_3d.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {examples => scripts}/plot_streamline_magnetopause_2d.py (100%) rename {examples => scripts}/plot_streamline_magnetopause_3d.py (100%) diff --git a/examples/plot_streamline_magnetopause_2d.py b/scripts/plot_streamline_magnetopause_2d.py similarity index 100% rename from examples/plot_streamline_magnetopause_2d.py rename to scripts/plot_streamline_magnetopause_2d.py diff --git a/examples/plot_streamline_magnetopause_3d.py b/scripts/plot_streamline_magnetopause_3d.py similarity index 100% rename from examples/plot_streamline_magnetopause_3d.py rename to scripts/plot_streamline_magnetopause_3d.py From 0a602c6031fe5a583311d3ce1cb4dd43af88ce98 Mon Sep 17 00:00:00 2001 From: jreimi Date: Thu, 17 Jul 2025 13:30:09 +0300 Subject: [PATCH 077/124] Updated sphinx --- .../sphinx/plot_streamline_magnetopause_2d.rst | 5 +++++ .../sphinx/plot_streamline_magnetopause_3d.rst | 5 +++++ Documentation/sphinx/scripts.rst | 16 ++++++++++++++++ 3 files changed, 26 insertions(+) create mode 100644 Documentation/sphinx/plot_streamline_magnetopause_2d.rst create mode 100644 Documentation/sphinx/plot_streamline_magnetopause_3d.rst diff --git a/Documentation/sphinx/plot_streamline_magnetopause_2d.rst b/Documentation/sphinx/plot_streamline_magnetopause_2d.rst new file mode 100644 index 00000000..64ef079a --- /dev/null +++ b/Documentation/sphinx/plot_streamline_magnetopause_2d.rst @@ -0,0 +1,5 @@ +plot_streamline_magnetopause_2d +------------------------------- + +.. automodule:: plot_streamline_magnetopause_2d + :members: \ No newline at end of file diff --git a/Documentation/sphinx/plot_streamline_magnetopause_3d.rst b/Documentation/sphinx/plot_streamline_magnetopause_3d.rst new file mode 100644 index 00000000..153d948c --- /dev/null +++ b/Documentation/sphinx/plot_streamline_magnetopause_3d.rst @@ -0,0 +1,5 @@ +plot_streamline_magnetopause_3d +------------------------------- + +.. automodule:: plot_streamline_magnetopause_3d + :members: \ No newline at end of file diff --git a/Documentation/sphinx/scripts.rst b/Documentation/sphinx/scripts.rst index a304dd49..bcb8e10e 100644 --- a/Documentation/sphinx/scripts.rst +++ b/Documentation/sphinx/scripts.rst @@ -30,6 +30,20 @@ gics ------------ +plot_streamline_magnetopause_2d +------------------------------- + +.. automodule:: plot_streamline_magnetopause_2d + :no-index: +------------ + +plot_streamline_magnetopause_3d +------------------------------- + +.. automodule:: plot_streamline_magnetopause_3d + :no-index: + +------------ obliqueshock ------------ @@ -74,6 +88,8 @@ tsyganenko biot_savart cutthrough_timeseries gics + plot_streamline_magnetopause_2d + plot_streamline_magnetopause_3d obliqueshock obliqueshock_nif shue From b35f7fb0e7d7deacb7c514a096378a4f2c0f49f3 Mon Sep 17 00:00:00 2001 From: jreimi Date: Thu, 17 Jul 2025 13:35:20 +0300 Subject: [PATCH 078/124] Added blank lines --- Documentation/sphinx/plot_streamline_magnetopause_2d.rst | 3 ++- Documentation/sphinx/plot_streamline_magnetopause_3d.rst | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Documentation/sphinx/plot_streamline_magnetopause_2d.rst b/Documentation/sphinx/plot_streamline_magnetopause_2d.rst index 64ef079a..38e7d4fc 100644 --- a/Documentation/sphinx/plot_streamline_magnetopause_2d.rst +++ b/Documentation/sphinx/plot_streamline_magnetopause_2d.rst @@ -2,4 +2,5 @@ plot_streamline_magnetopause_2d ------------------------------- .. automodule:: plot_streamline_magnetopause_2d - :members: \ No newline at end of file + :members: + \ No newline at end of file diff --git a/Documentation/sphinx/plot_streamline_magnetopause_3d.rst b/Documentation/sphinx/plot_streamline_magnetopause_3d.rst index 153d948c..f1794ffb 100644 --- a/Documentation/sphinx/plot_streamline_magnetopause_3d.rst +++ b/Documentation/sphinx/plot_streamline_magnetopause_3d.rst @@ -2,4 +2,5 @@ plot_streamline_magnetopause_3d ------------------------------- .. automodule:: plot_streamline_magnetopause_3d - :members: \ No newline at end of file + :members: + \ No newline at end of file From 9217675eefacc029054793dae77a95754c98eb09 Mon Sep 17 00:00:00 2001 From: jreimi Date: Thu, 17 Jul 2025 14:00:48 +0300 Subject: [PATCH 079/124] fixes for sphinx --- Documentation/sphinx/scripts.rst | 1 + scripts/plot_streamline_magnetopause_2d.py | 66 ++++++----- scripts/plot_streamline_magnetopause_3d.py | 126 +++++++++++---------- 3 files changed, 103 insertions(+), 90 deletions(-) diff --git a/Documentation/sphinx/scripts.rst b/Documentation/sphinx/scripts.rst index bcb8e10e..ef837553 100644 --- a/Documentation/sphinx/scripts.rst +++ b/Documentation/sphinx/scripts.rst @@ -35,6 +35,7 @@ plot_streamline_magnetopause_2d .. automodule:: plot_streamline_magnetopause_2d :no-index: + ------------ plot_streamline_magnetopause_3d diff --git a/scripts/plot_streamline_magnetopause_2d.py b/scripts/plot_streamline_magnetopause_2d.py index 933daf23..4c17e8b0 100644 --- a/scripts/plot_streamline_magnetopause_2d.py +++ b/scripts/plot_streamline_magnetopause_2d.py @@ -1,35 +1,41 @@ """ -An example of using find_magnetopause_2d_sw_streamline() and plotting the output over colormap. +An example of using find_magnetopause_sw_streamline_2d() and plotting the output over colormap. """ import analysator as pt -run = 'BFD' -file_name="/wrk-vakka/group/spacephysics/vlasiator/2D/"+run+"/bulk/bulk.0002000.vlsv" -outdir= "" - -# magnetopause points -magnetopause = pt.calculations.find_magnetopause_sw_streamline_2d( - file_name, - streamline_seeds=None, - seeds_n=200, - seeds_x0=20*6371000, - seeds_range=[-5*6371000, 5*6371000], - streamline_length=45*6371000, - end_x=-15*6371000, - x_point_n=50) - -magnetopause= magnetopause*(1/6371000) # in RE - -def external_plot(ax,XmeshXY=None, YmeshXY=None, pass_maps=None): - ax.plot(magnetopause[:,0], magnetopause[:,1], color='cyan', linewidth=1.0) - -pt.plot.plot_colormap( - filename=file_name, - var="rho", - boxre = [-21,21,-21,21], - Earth=True, - external = external_plot, - run=run, - colormap='inferno', - ) +def main(): + + run = 'BFD' # for output file name + file_name="/wrk-vakka/group/spacephysics/vlasiator/2D/BFD/bulk/bulk.0002000.vlsv" + + # magnetopause points + magnetopause = pt.calculations.find_magnetopause_sw_streamline_2d( + file_name, + streamline_seeds=None, + seeds_n=200, + seeds_x0=20*6371000, + seeds_range=[-5*6371000, 5*6371000], + streamline_length=45*6371000, + end_x=-15*6371000, + x_point_n=50) + + magnetopause = magnetopause*(1/6371000) # in RE + + def external_plot(ax,XmeshXY=None, YmeshXY=None, pass_maps=None): + ax.plot(magnetopause[:,0], magnetopause[:,1], color='cyan', linewidth=1.0) + + pt.plot.plot_colormap( + filename=file_name, + var="rho", + boxre = [-21,21,-21,21], + Earth=True, + external = external_plot, + run=run, + colormap='inferno', + ) + + +if __name__ == "__main__": + main() + diff --git a/scripts/plot_streamline_magnetopause_3d.py b/scripts/plot_streamline_magnetopause_3d.py index 40b4e1a4..8c661b12 100644 --- a/scripts/plot_streamline_magnetopause_3d.py +++ b/scripts/plot_streamline_magnetopause_3d.py @@ -1,85 +1,91 @@ """ -An example of using find_find_magnetopause_3d_sw_streamline() and plotting the output. +An example of using find_magnetopause_sw_streamline_3d() and plotting the output. """ import numpy as np import matplotlib.pyplot as plt import analysator as pt -## get bulk data -run = 'EGI' # for plot output file name -file_name="/wrk-vakka/group/spacephysics/vlasiator/3D/"+run+"/bulk/bulk5.0000070.vlsv" +def main(): + ## get bulk data + run = 'EGI' # for plot output file name + file_name="/wrk-vakka/group/spacephysics/vlasiator/3D/EGI/bulk/bulk5.0000070.vlsv" -## magnetopause -x_point_n = 50 # number needed for 2d slice plots, default is 50 + ## magnetopause + x_point_n = 50 # number needed for 2d slice plots, default is 50 -vertices, faces = pt.calculations.find_magnetopause_sw_streamline_3d( - file_name, - streamline_seeds=None, - seeds_n=25, - seeds_x0=20*6371000, - seeds_range=[-5*6371000, 5*6371000], - dl=2e6, - iterations=200, - end_x=-15*6371000, - x_point_n=x_point_n, - sector_n=36) + vertices, faces = pt.calculations.find_magnetopause_sw_streamline_3d( + file_name, + streamline_seeds=None, + seeds_n=25, + seeds_x0=20*6371000, + seeds_range=[-5*6371000, 5*6371000], + dl=2e6, + iterations=200, + end_x=-15*6371000, + x_point_n=x_point_n, + sector_n=36) -outdir="" # output plot save directory + outdir="" # output plot save directory -### 3D surface plot ### + ### 3D surface plot ### -fig = plt.figure() -ax = fig.add_subplot(projection='3d') -ax.plot_trisurf(vertices[:, 0], vertices[:,1], faces, vertices[:,2], linewidth=0.2) -plt.savefig(outdir+run+'_magnetopause_3D_surface.png') -plt.close() + fig = plt.figure() + ax = fig.add_subplot(projection='3d') + ax.plot_trisurf(vertices[:, 0], vertices[:,1], faces, vertices[:,2], linewidth=0.2) + plt.savefig(outdir+run+'_magnetopause_3D_surface.png') + plt.close() -### 2D slice plots ### + ### 2D slice plots ### -vertices = vertices/6371000 # to RE -magnetopause = np.array(np.split(vertices, x_point_n)) # magnetopause points grouped by x-axis coordinate + vertices = vertices/6371000 # to RE + magnetopause = np.array(np.split(vertices, x_point_n)) # magnetopause points grouped by x-axis coordinate -## take separate arrays for different 2d slice plots + ## take separate arrays for different 2d slice plots -quarter_slice = int(np.shape(magnetopause)[1]/4) -# xy plane: z=0 -xy_slice = np.concatenate((magnetopause[:,0][::-1], magnetopause[:,2*quarter_slice])) -# xz plane: y=0 -xz_slice = np.concatenate((magnetopause[:,quarter_slice][::-1], magnetopause[:,3*quarter_slice])) + quarter_slice = int(np.shape(magnetopause)[1]/4) + # xy plane: z=0 + xy_slice = np.concatenate((magnetopause[:,0][::-1], magnetopause[:,2*quarter_slice])) + # xz plane: y=0 + xz_slice = np.concatenate((magnetopause[:,quarter_slice][::-1], magnetopause[:,3*quarter_slice])) -# analysator 3dcolormapslice y=0 + # analysator 3dcolormapslice y=0 -def external_plot(ax,XmeshXY=None, YmeshXY=None, pass_maps=None, requestvariables=False): - if requestvariables==True: - return ['vg_v'] - ax.plot(xz_slice[:,0], xz_slice[:,2], color='limegreen', linewidth=1.5) + def external_plot(ax,XmeshXY=None, YmeshXY=None, pass_maps=None, requestvariables=False): + if requestvariables==True: + return ['vg_v'] + ax.plot(xz_slice[:,0], xz_slice[:,2], color='limegreen', linewidth=1.5) -pt.plot.plot_colormap3dslice( -filename=file_name, -run=run, -outputdir=outdir, -boxre = [-21,21,-21,21], -external = external_plot, -colormap='inferno', -normal='y' -) + pt.plot.plot_colormap3dslice( + filename=file_name, + run=run, + outputdir=outdir, + boxre = [-21,21,-21,21], + external = external_plot, + colormap='inferno', + normal='y' + ) -# analysator 3dcolormapslice z=0 + # analysator 3dcolormapslice z=0 -def external_plot(ax,XmeshXY=None, YmeshXY=None, pass_maps=None, requestvariables=False): - if requestvariables==True: - return ['vg_v'] - ax.plot(xy_slice[:,0], xy_slice[:,1], color='limegreen', linewidth=1.5) + def external_plot(ax,XmeshXY=None, YmeshXY=None, pass_maps=None, requestvariables=False): + if requestvariables==True: + return ['vg_v'] + ax.plot(xy_slice[:,0], xy_slice[:,1], color='limegreen', linewidth=1.5) -pt.plot.plot_colormap3dslice( -filename=file_name, -outputdir=outdir, -run=run, -boxre = [-21,21,-21,21], -external = external_plot, -colormap='inferno', -normal='z') + pt.plot.plot_colormap3dslice( + filename=file_name, + outputdir=outdir, + run=run, + boxre = [-21,21,-21,21], + external = external_plot, + colormap='inferno', + normal='z') + + + +if __name__ == "__main__": + main() From 21a73d85009d5418589c80a1893f41726a65575c Mon Sep 17 00:00:00 2001 From: jreimi Date: Thu, 17 Jul 2025 14:04:37 +0300 Subject: [PATCH 080/124] more fixes for sphinx --- Documentation/sphinx/scripts.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Documentation/sphinx/scripts.rst b/Documentation/sphinx/scripts.rst index ef837553..1cfa7e01 100644 --- a/Documentation/sphinx/scripts.rst +++ b/Documentation/sphinx/scripts.rst @@ -32,14 +32,16 @@ gics plot_streamline_magnetopause_2d ------------------------------- +:doc:`plot_streamline_magnetopause_2d` .. automodule:: plot_streamline_magnetopause_2d :no-index: - + ------------ plot_streamline_magnetopause_3d ------------------------------- +:doc:`plot_streamline_magnetopause_3d` .. automodule:: plot_streamline_magnetopause_3d :no-index: From d2dda2e7fe0d0dd8c69112fc6f1fe84e69f5acb4 Mon Sep 17 00:00:00 2001 From: jreimi Date: Wed, 20 Aug 2025 17:31:23 +0300 Subject: [PATCH 081/124] Better SW magnetopause and region classification draft --- analysator/pyCalculations/fieldtracer.py | 4 +- .../magnetopause_sw_streamline_3d.py | 191 ++++++++++++++---- 2 files changed, 151 insertions(+), 44 deletions(-) diff --git a/analysator/pyCalculations/fieldtracer.py b/analysator/pyCalculations/fieldtracer.py index 195c7d05..f383c760 100644 --- a/analysator/pyCalculations/fieldtracer.py +++ b/analysator/pyCalculations/fieldtracer.py @@ -294,7 +294,7 @@ def find_unit_vector(vg, coord): points_traced_unique[mask_update,i,:] = next_points[mask_update,:] # distances = np.linalg.norm(points_traced_unique[:,i,:],axis = 1) - mask_update[stop_condition(vlsvReader, points_traced_unique[:,i,:])] = False + mask_update[stop_condition(vlsvReader, points_traced_unique[:,i,:], var_unit)] = False # points_traced_unique[~mask_update, i, :] = points_traced_unique[~mask_update, i-1, :] @@ -302,7 +302,7 @@ def find_unit_vector(vg, coord): return points_traced # Default stop tracing condition for the vg tracing, (No stop until max_iteration) -def default_stopping_condition(vlsvReader, points): +def default_stopping_condition(vlsvReader, points, last_unit_v): [xmin, ymin, zmin, xmax, ymax, zmax] = vlsvReader.get_spatial_mesh_extent() x = points[:, 0] y = points[:, 1] diff --git a/analysator/pyCalculations/magnetopause_sw_streamline_3d.py b/analysator/pyCalculations/magnetopause_sw_streamline_3d.py index b955dadf..d3acc149 100644 --- a/analysator/pyCalculations/magnetopause_sw_streamline_3d.py +++ b/analysator/pyCalculations/magnetopause_sw_streamline_3d.py @@ -2,8 +2,10 @@ Finds the magnetopause position by tracing streamlines of the plasma flow for three-dimensional Vlasiator runs. ''' +import matplotlib.pyplot as plt import numpy as np import analysator as pt +import vtk def cartesian_to_polar(cartesian_coords): # for segments of plane """Converts cartesian coordinates to polar (for the segments of the yz-planes). @@ -31,6 +33,25 @@ def polar_to_cartesian(r, phi): return(y, z) +def cartesian_to_spherical(cartesian_coords): + """ Cartesian to spherical coordinates (Note: axes are swapped!)""" + x, y, z = cartesian_coords[0], cartesian_coords[1], cartesian_coords[2] + r = np.sqrt(x**2+y**2+z**2) + theta = np.arctan2(np.sqrt(y**2+z**2), x) #inclination + phi = np.arctan2(z, y)+np.pi # azimuth + + return [r, theta, phi] + + + +def spherical_to_cartesian(sphe_coords): + r, theta, phi = sphe_coords[0], sphe_coords[1], sphe_coords[2] + y = r*np.sin(theta)*np.cos(phi) + z = r*np.sin(theta)*np.sin(phi) + x = r*np.cos(theta) + return [x, y, z] + + def interpolate(streamline, x_points): """Interpolates a single streamline for make_magnetopause(). @@ -46,9 +67,9 @@ def interpolate(streamline, x_points): zp = arr[:,2][::-1] #interpolate z from xz-plane - z_points = np.interp(x_points, xp, zp, left=np.NaN, right=np.NaN) + z_points = np.interp(x_points, xp, zp, left=np.nan, right=np.nan) #interpolate y from xy-plane - y_points = np.interp(x_points, xp, yp, left=np.NaN, right=np.NaN) + y_points = np.interp(x_points, xp, yp, left=np.nan, right=np.nan) return np.array([x_points, y_points, z_points]) @@ -121,35 +142,56 @@ def make_surface(coords): next_triangles = [x + slices_in_plane*area_index for x in first_triangles] faces.extend(next_triangles) - # From last triangles remove every other triangle - # (a single subsolar point -> last triangles are actual triangles instead of rectangles sliced in two) - removed = 0 - for i in range(len(faces)-slices_in_plane*2, len(faces)): - if i%2!=0: - faces.pop(i-removed) - removed += 1 + # Change every other face triangle normal direction so that all face the same way + faces = np.array(faces) + for i in range(len(faces)): + if i%2==0: + faces[i,1], faces[i,2] = faces[i,2], faces[i,1] + + return np.array(verts), faces - # From last triangles remove every other triangle - # (a single subsolar point -> last triangles are actual triangles instead of rectangles sliced in two) - # Also fix the last triangles so that they only point to one subsolar point and have normals towards outside - subsolar_index = int(len(verts)-slices_in_plane) - for i,triangle in enumerate(reversed(faces)): - if i > (slices_in_plane): # faces not in last plane (we're going backwards) - break +def make_vtk_surface(vertices, faces): + """Makes a vtk DataSetSurfaceFilter from vertices and faces of a triangulated surface. - faces[len(faces)-i-1] = np.clip(triangle, a_min=0, a_max=subsolar_index) + :param vertices: vertex points of triangles in shape [[x0, y0, z0], [x1, y1, z1],...] + :param faces: face connectivities as indices of vertices so that triangle normals point outwards + :returns: a vtkDataSetSurfaceFilter object + """ - # this would remove duplicate subsolar points from vertices but makes 2d slicing harder - #verts = verts[:int(len(verts)-slices_in_plane+1)] + points = vtk.vtkPoints() - # Change every other face triangle (except for last slice triangles) normal direction so that all face the same way (hopefully) - faces = np.array(faces) - for i in range(len(faces)-int(slices_in_plane)): - if i%2!=0: - faces[i,1], faces[i,2] = faces[i,2], faces[i,1] + for vert in vertices: + points.InsertNextPoint(vert) - return np.array(verts), faces + # make vtk PolyData object + + triangles = vtk.vtkCellArray() + for face in faces: + triangle = vtk.vtkTriangle() + triangle.GetPointIds().SetId(0, face[0]) + triangle.GetPointIds().SetId(1, face[1]) + triangle.GetPointIds().SetId(2, face[2]) + triangles.InsertNextCell(triangle) + + polydata = vtk.vtkPolyData() + polydata.SetPoints(points) + polydata.Modified() + polydata.SetPolys(triangles) + polydata.Modified() + + surface = vtk.vtkDataSetSurfaceFilter() + surface.SetInputData(polydata) + surface.Update() + + return surface + +def streamline_stopping_condition(vlsvReader, points, value): + [xmin, ymin, zmin, xmax, ymax, zmax] = vlsvReader.get_spatial_mesh_extent() + x = points[:, 0] + y = points[:, 1] + z = points[:, 2] + return (x < xmin)|(x > xmax) | (y < ymin)|(y > ymax) | (z < zmin)|(z > zmax)|(value[:,0] > 0) def make_streamlines(vlsvfile, streamline_seeds=None, seeds_n=25, seeds_x0=20*6371000, seeds_range=[-5*6371000, 5*6371000], dl=2e6, iterations=200): @@ -187,7 +229,9 @@ def make_streamlines(vlsvfile, streamline_seeds=None, seeds_n=25, seeds_x0=20*63 max_iterations=iterations, dx=dl, direction='+', - grid_var='vg_v') + grid_var='vg_v', + stop_condition=streamline_stopping_condition + ) return streams @@ -206,6 +250,11 @@ def make_magnetopause(streams, end_x=-15*6371000, x_point_n=50, sector_n=36): """ RE = 6371000 + ignore = 0 + + ## if given sector number isn't divisible by 4, make it so because we want to have magnetopause points at exactly y=0 and z=0 for 2d slices of the whole thing + while sector_n%4 != 0: + sector_n +=1 #streams = streams*(1/RE) # streamlines in rE streampoints = np.reshape(streams, (streams.shape[0]*streams.shape[1], 3)) #all the points in one array) @@ -214,14 +263,63 @@ def make_magnetopause(streams, end_x=-15*6371000, x_point_n=50, sector_n=36): ## do this by finding a streamline point on positive x axis closest to the Earth # streampoints closer than ~1 rE to positive x-axis: x_axis_points = streampoints[(streampoints[:,1]-RE) & (streampoints[:,2]>-RE) & (streampoints[:,0]>0) & (streampoints[:,0]>0)] - subsolar_x =np.min(x_axis_points[:,0]) + if ignore == 0: + subsolar_x =np.min(x_axis_points[:,0]) + else: + subsolar_x = np.partition(x_axis_points[:,0], ignore)[ignore] # take the nth point as subsolar point + + ### dayside magnetopause ### + # for x > 0, look for magnetopause radially + dayside_points = streampoints[streampoints[:,0] > 0] + + phi_slices = sector_n + theta_slices = 10 + phi_step = 2*np.pi/phi_slices + theta_step = np.pi/(2*theta_slices) # positive x-axis only + + def cartesian_to_spherical_grid(cartesian_coords): + # Only for x > 0 + r, theta, phi = cartesian_to_spherical(cartesian_coords) + theta_idx = int(theta/theta_step) + phi_idx = int(phi/phi_step) + return [theta_idx, phi_idx, r] + + def grid_mid_point(theta_idx, phi_idx): + return (theta_idx+0.5)*theta_step, (phi_idx+0.5)*phi_step + + # make a dictionary based on spherical areas by theta index and phi index + sph_points = {} + for point in dayside_points: + sph_gridpoint = cartesian_to_spherical_grid(point) + idxs = (sph_gridpoint[0],sph_gridpoint[1]) # key is (theta_i, phi_i) + if idxs not in sph_points: + sph_points[idxs] = [sph_gridpoint[2]] + else: + sph_points[idxs].append(sph_gridpoint[2]) + + # dayside magetopause from subsolar point towards origo + dayside_magnetopause = np.zeros((theta_slices, phi_slices, 3)) + for ring_idx in range(theta_slices): + ring_points = np.zeros((phi_slices, 3)) + for phi_idx in range(phi_slices): + if (ring_idx, phi_idx) in sph_points: + if ignore == 0: + nth_min_r = np.min(np.array(sph_points[(ring_idx, phi_idx)])) # point in area with smallest radius + else: + nth_min_r = np.partition(np.array(sph_points[(ring_idx, phi_idx)]), ignore)[ignore] + else: + print("no ", (ring_idx, phi_idx)) + exit() # something wrong + midpoint_theta, midpoint_phi = grid_mid_point(ring_idx, phi_idx) # the smallest radius will be assigned to a point in the middle-ish of the area + ring_points[phi_idx] = np.array(spherical_to_cartesian([nth_min_r, midpoint_theta, midpoint_phi])) + + dayside_magnetopause[ring_idx] = ring_points + ### x < 0 magnetopause ### + # rest: look for magnetopause in yz-planes ## define points in the x axis where to find magnetopause points on the yz-plane - #dx = (subsolar_x-end_x)/x_point_n - next_from_subsolar_x = subsolar_x-1e3 # start making the magnetopause from a point slightly inwards from subsolar point - x_point_n = x_point_n-1 - x_points = np.linspace(next_from_subsolar_x, end_x, x_point_n) + x_points = np.linspace(subsolar_x, end_x, x_point_n) ## interpolate more exact points for streamlines at exery x_point new_streampoints = np.zeros((len(x_points), len(streams), 2)) # new array for keeping interpolated streamlines in form new_streampoints[x_point, streamline, y and z -coordinates] @@ -247,10 +345,6 @@ def make_magnetopause(streams, end_x=-15*6371000, x_point_n=50, sector_n=36): ## now start making the magnetopause ## in each x_point, divide the plane into sectors and look for the closest streamline to x-axis in the sector - ## if given sector number isn't divisible by 4, make it so because we want to have magnetopause points at exactly y=0 and z=0 for 2d slices of the whole thing - while sector_n%4 != 0: - sector_n +=1 - sector_width = 360/sector_n magnetopause = np.zeros((len(x_points), sector_n, 3)) @@ -274,20 +368,29 @@ def make_magnetopause(streams, end_x=-15*6371000, x_point_n=50, sector_n=36): # discard 'points' with r=0 and check that there's at least one streamline point in the sector sector_points = sector_points[sector_points[:,0] != 0.0] if sector_points.size == 0: - raise ValueError('No streamlines found in the sector, x_i=',i) + raise ValueError('No streamlines found in the sector') # find the points closest to the x-axis - closest_point_radius = sector_points[sector_points[:,0].argmin(), 0] # smallest radius - + if ignore == 0: + nth_closest_point_radius = sector_points[sector_points[:,0].argmin(), 0] # smallest radius + else: + nth_closest_point_radius = np.partition(sector_points[:,0], ignore)[ignore] + + #closest_point_radius = np.median(sector_points[:,0]) # median radius + #if x_point < subsolar_x-2e6: + # closest_point_radius = np.median(sector_points[:,0]) # median radius + #else: + # closest_point_radius = sector_points[sector_points[:,0].argmin(), 0] # smallest radius + # return to cartesian coordinates and save as a magnetopause point at the middle of the sector - y,z = polar_to_cartesian(closest_point_radius, mean_sector_angle) + y,z = polar_to_cartesian(nth_closest_point_radius, mean_sector_angle) magnetopause[i,j,:] = [x_point, y, z] # make a tip point for the magnetopause for prettier 3d plots tip = np.array([subsolar_x, 0, 0]) tips = np.tile(tip, (magnetopause.shape[1],1)) - magnetopause = np.vstack(([tips], magnetopause)) + magnetopause = np.vstack(([tips], dayside_magnetopause, magnetopause)) return magnetopause @@ -306,11 +409,15 @@ def find_magnetopause_sw_streamline_3d(vlsvfile, streamline_seeds=None, seeds_n= :kword x_point_n: integer, how many x-axis points the magnetopause will be divided in between the subsolar point and tail :kword sector_n: integer, how many sectors the magnetopause will be divided in on each yz-plane - :returns: vertices, faces of the magnetopause triangle mesh as numpy arrays + :returns: vertices, surface where vertices are numpy arrays in shape [[x0,y0,z0], [x1,y1,z1],...] and surface is a vtk vtkDataSetSurfaceFilter object """ streams = make_streamlines(vlsvfile, streamline_seeds, seeds_n, seeds_x0, seeds_range, dl, iterations) magnetopause = make_magnetopause(streams, end_x, x_point_n, sector_n) vertices, faces = make_surface(magnetopause) + surface = make_vtk_surface(vertices, faces) + + return vertices, surface + - return vertices, faces + From cca622976b3ec3cc0e542828c7aedf7011926a9d Mon Sep 17 00:00:00 2001 From: jreimi Date: Wed, 20 Aug 2025 17:33:47 +0300 Subject: [PATCH 082/124] Documentation drafts and regions script --- Documentation/sphinx/bowshock.rst | 69 ++ Documentation/sphinx/magnetopause.rst | 122 ++++ .../sphinx/magnetosphere_regions.rst | 140 +++++ scripts/regions.py | 594 ++++++++++++++++++ 4 files changed, 925 insertions(+) create mode 100644 Documentation/sphinx/bowshock.rst create mode 100644 Documentation/sphinx/magnetopause.rst create mode 100644 Documentation/sphinx/magnetosphere_regions.rst create mode 100644 scripts/regions.py diff --git a/Documentation/sphinx/bowshock.rst b/Documentation/sphinx/bowshock.rst new file mode 100644 index 00000000..365d9dc4 --- /dev/null +++ b/Documentation/sphinx/bowshock.rst @@ -0,0 +1,69 @@ +Bow shock: How to find +====================== + + +Plasma properties for estimating bow shock position: +---------------------------------------------------- + +* plasma compression + * :math:`n_p > 2n_{p, sw}` [Battarbee_et_al_2020]_ (Vlasiator) +* solar wind core heating + * :math:`T_{core} > 4T_{sw}` [Battarbee_et_al_2020]_ (Vlasiator) + * :math:`T_{core} = 3T_{sw}` [Suni_et_al_2021]_ (Vlasiator) +* magnetosonic Mach number + * :math:`M_{ms} < 1` [Battarbee_et_al_2020]_ (Vlasiator) + + + + +In analysator: +-------------- + +regions.py: + +*method*: + +Convex hull from cells where :math:`n_p > 1.5 n_{p, sw}` +criteria + +also velocity? + + +Foreshock: +---------- + +*properties:* + +* larger fluctuations in the magnetic field, plasma velocity and plasma density than unperturbed solar wind [Grison_et_al_2025]_ + + +Magnetosheath +------------- + +properties: + +* density: + * :math:`8 cm^{-3}` [Hudges_Introduction_to_space_physics_Ch_9]_ +* temperature: + * ion: :math:`150 eV` [Hudges_Introduction_to_space_physics_Ch_9]_ + * electron: :math:`25 eV` [Hudges_Introduction_to_space_physics_Ch_9]_ +* magnetic field: + * :math:`15 nT` [Hudges_Introduction_to_space_physics_Ch_9]_ +* plasma :math:`\beta`: + * 2.5 [Hudges_Introduction_to_space_physics_Ch_9]_ + + +Regions inside the bow shock: +----------------------------- + +* magnetosheath: area inside bow shock but outside the magnetopause, see ... +* magnetosphere: area inside magnetopause, see again ... and ... for magnetosphere regions + + +------------ + +References: + +.. [Battarbee_et_al_2020] Battarbee, M., Ganse, U., Pfau-Kempf, Y., Turc, L., Brito, T., Grandin, M., Koskela, T., and Palmroth, M.: Non-locality of Earth's quasi-parallel bow shock: injection of thermal protons in a hybrid-Vlasov simulation, Ann. Geophys., 38, 625-643, https://doi.org/10.5194/angeo-38-625-2020, 2020 +.. [Suni_et_al_2021] Suni, J., Palmroth, M., Turc, L., Battarbee, M., Johlander, A., Tarvus, V., et al. (2021). Connection between foreshock structures and the generation of magnetosheath jets: Vlasiator results. Geophysical Research Letters, 48, e2021GL095655. https://doi. org/10.1029/2021GL095655 +.. [Grison_et_al_2025] Grison, B., Darrouzet, F., Maggiolo, R. et al. Localization of the Cluster satellites in the geospace environment. Sci Data 12, 327 (2025). https://doi.org/10.1038/s41597-025-04639-z diff --git a/Documentation/sphinx/magnetopause.rst b/Documentation/sphinx/magnetopause.rst new file mode 100644 index 00000000..b5b68520 --- /dev/null +++ b/Documentation/sphinx/magnetopause.rst @@ -0,0 +1,122 @@ +Magnetopause: how to find +========================= + +Magnetopause + +Plasma beta, beta* +------------------ + + +Modified plasma beta + +.. math:: \beta * = \dfrac{P_{th}P_{dyn}}{B^2/2\mu_0} + +[Xu_et_al_2016]_, [Brenner_et_al_2021]_ + + +The beta* gets values below 1 inside the magnetosphere near magnetopause and can be used to create a magnetopause surface. + +Caveats: magnetotail current sheet has beta* :math:`>` 1 + +**in analysator:** + +datareducer: beta_star, vg_beta_star + +regions.py: cells with beta* value within a certain treshold are chosen, and a convex hull is constructed with vtk to represent the magnetopause. +Ideal values of beta* for magnetopause construction might be run-dependent, and the surface construction works best with a small-ish range of beta* but still big enough to gather cells evenly from all sides. + + +.. [Xu_et_al_2016] Xu, S., M. W. Liemohn, C. Dong, D. L. Mitchell, S. W. Bougher, and Y. Ma (2016), Pressure and ion composition boundaries at Mars, J. Geophys. Res. Space Physics, 121, 6417–6429, doi:10.1002/2016JA022644. +.. [Brenner_et_al_2021] Brenner A, Pulkkinen TI, Al Shidi Q and Toth G (2021) Stormtime Energetics: Energy Transport Across the Magnetopause in a Global MHD Simulation. Front. Astron. Space Sci. 8:756732. doi: 10.3389/fspas.2021.756732 + + + +Field line connectivity +----------------------- + + + + +Solar wind flow +--------------- + +Method used in e.g. [Parlmroth_et_al_2003_] + +Streamlines of velocity field that are traced from outside the bow shock curve around the magnetopause. + +Caveats: sometimes some streamlines can curve into the magnetotail + + +**In analysator:** + +magnetopause_sw_streamline_2d.py +magnetopause_sw_streamline_3d.py + +Streamlines are traced from outside the bow shock towards Earth. A subsolar point for the magnetopause is chosen to be where streamlines get closest to Earth in x-axis [y/z~0]. + +From subsolar point towards Earth, .... + + +From the Earth towards negative x the space is divided into yz-planes. +Each yz-plane is then divided into sectors and magnetopause is marked to be in the middle of the sector with the radius of the closest streamline to the x-axis. + +For subsolar point, radial dayside and -x planes the closest streamline point can be changed to be n:th closest by setting keyword "ignore", in which case n-1 closest streamline points are not taken into acocunt. + + +2d: +streamlines: [...] +magnetopause: [...] + +example usage in [...] + + +3d: +streamlines: [...] +magnetopause: [...] +surface triangulation: [...] + +example usage in [...] + + + + + + +.. [Parlmroth_et_al_2003] Palmroth, M., T. I. Pulkkinen, P. Janhunen, and C.-C. Wu (2003), Stormtime energy transfer in global MHD simulation, J. Geophys. Res., 108, 1048, doi:10.1029/2002JA009446, A1. + + + + +Shue et al. (1997) +------------------ + +The Shue et al. (1997) [Shue_et_al_1997]_ mangnetopause model: + +.. math:: + + r_0 &= (11.4 + 0.013 B_z)(D_p)^{-\frac{1}{6.6}}, for B_z \geq 0 \\ + &= (11.4 + 0.14 B_z)(D_p)^{-\frac{1}{6.6}}, for B_z \leq 0 + +.. math:: + + \alpha = (0.58-0.010B_z)(1+0.010 D_p) + + +where :math:`D_p` is the dynamic pressure of the solar wind and :math:`B_z` the magnetic field z-component magnitude. +:math:`r_0` is the magnetopause standoff distance and :math:`\alpha` is the level of tail flaring. + + +The magnetopause radius as a function of angle :math:`\theta` is + +.. math:: + r = r_0 (\frac{2}{1+\cos \theta})^\alpha + +**In analysator:** + +*shue.py* in *scripts*: + + + + + +.. [Shue_et_al_1997] Shue, J.-H., Chao, J. K., Fu, H. C., Russell, C. T., Song, P., Khurana, K. K., and Singer, H. J. (1997). A new functional form to study the solar wind control of the magnetopause size and shape. Journal of Geophysical Research: Space Physics, 102(A5):9497–9511. eprint: https://agupubs.onlinelibrary.wiley.com/doi/pdf/10.1029/97JA00196. \ No newline at end of file diff --git a/Documentation/sphinx/magnetosphere_regions.rst b/Documentation/sphinx/magnetosphere_regions.rst new file mode 100644 index 00000000..81f1ec60 --- /dev/null +++ b/Documentation/sphinx/magnetosphere_regions.rst @@ -0,0 +1,140 @@ +Magnetosphere regions: How to find +================================== + + + + +Cusps +----- + + +*Properties:* + +* plasma density: high density in comparison to solar wind + * Ion density :math:`\geq` solar wind ion density [Pitout_et_al_2006_] (Cluster spacecraft data) +* ion energy: + * mean ion energy :math:`~2-3 keV` [Pitout_et_al_2006_] /[Stenuit_et_al_2001_] +* energy flux: + * energy flux + + + +**In analysator:** + + +Tail lobes +---------- + +* plasma density: low + * below :math:`0.03 cm^{-3}` [Grison_et_al_2025_] (Cluster spacecraft data) + * :math:`0.01 cm^{-3}` [Koskinen_Space_Storms_ p.38] + * less than :math:`0.1 cm^{-3}` [Wolf_Introduction_to_space_physics_Ch_10_ p.291] +* plasma :math:`\beta`: low + * typically around :math:`0.05` [Grison_et_al_2025_] (Cluster spacecraft data) + * :math:`3e-3` [Koskinen_Space_Storms_ p.38] +* temperature: + * ion temperature :math:`300 eV` [Koskinen_Space_Storms_ p.38] + * electron temperature :math:`50 eV` [Koskinen_Space_Storms_ p.38] +* magnetic field: + * :math:`20 nT` [Koskinen_Space_Storms_ p.38] +* open magnetic field lines [Wolf_Introduction_to_space_physics_Ch_10_ p.291] +* strong and stable magnetic field towards the Earth (northern lobe) and away from the Earth (southern lobe) [Coxon_et_al_2016_] + +Separated from the plasma sheet by the plasma sheet boundary layer (PSBL) + + +**in analysator:** + +regions.py + +conditions: + +* inside the magnetosphere +* plasma :math:`\beta` .... + + + + +Low-latitude boundary layer (LLBL) +---------------------------------- + + + +Properties: + +* density: + * ion number densities between those of magnetosphere and magnetosheath [Hudges_Introduction_to_space_physics_Ch_9_ p.267] +* temperature + * ion temperatures between those of magnetosphere and magnetosheath [Hudges_Introduction_to_space_physics_Ch_9_ p.267] +* unknown field line configuration, probably a mix of open and closed field lines [Hudges_Introduction_to_space_physics_Ch_9_ p.262] + + + +High-latitude boundary layer (HLBL) +----------------------------------- + +Includes the plasma mantle on the tail side and the entry layer on the dayside [... cit.] + +Properties: + +* open magnetic field lines [Hudges_Introduction_to_space_physics_Ch_9_ p.261] + + + + + +Plasma sheet boundary layer (PSBL) +---------------------------------- + +The plasma sheet boundary layer is a very thin boundary layer separating the tail lobes from the tail plasma sheet [Koskinen_Johdatus_] + +*Properties:* + +* density: + * :math:`0.1 cm^{-3}` [Koskinen_Space_Storms_ p.38] +* temperature: + * ion temperature :math:`1000 eV` [Koskinen_Space_Storms_ p.38] + * electron temperature :math:`150 eV` [Koskinen_Space_Storms_ p.38] +* magnetic field: + * :math:`20 nT` [Koskinen_Space_Storms_ p.38] +* plasma :math:`\beta` : + * :math:`0.1` [Koskinen_Space_Storms_ p.38] +* probably closed magnetic field lines [Wolf_Introduction_to_space_physics_Ch_10_ p.291] + + + + +Central plasma sheet +-------------------- + + +*Properties:* + +* density: + * :math:`0.3 cm^{-3}` [Koskinen_Space_Storms_ p.38] + * :math:`0.1-1 cm^{-3}` [Wolf_Introduction_to_space_physics_Ch_10_ p.291] +* temperature: hot + * ion temperature :math:`4200 eV` [Koskinen_Space_Storms_ p.38] + * electron temperature :math:`600 eV` [Koskinen_Space_Storms_ p.38] +* magnetic field: + * :math:`10 nT` [Koskinen_Space_Storms_ p.38], [Hudges_Introduction_to_space_physics_Ch_9_] +* plasma :math:`\beta`: high + * :math:`6` [Koskinen_Space_Storms_ p.38] +* Mostly closed magnetic field lines [Wolf_Introduction_to_space_physics_Ch_10_] + + + + +------------ + +References + +.. [Grison_et_al_2025] Grison, B., Darrouzet, F., Maggiolo, R. et al. Localization of the Cluster satellites in the geospace environment. Sci Data 12, 327 (2025). https://doi.org/10.1038/s41597-025-04639-z +.. [Koskinen_Johdatus] Koskinen, H. E. J. (2011). Johdatus plasmafysiikkaan ja sen avaruussovellutuksiin. Limes ry. +.. [Koskinen_Space_Storms] Koskinen, H. E. J. (2011). Physics of Space Storms: From the Solar Surface to the Earth. Springer-Verlag. https://doi.org/10.1007/978-3-642-00319-6 +.. [Pitout_et_al_2006] Pitout, F., Escoubet, C. P., Klecker, B., and Rème, H.: Cluster survey of the mid-altitude cusp: 1. size, location, and dynamics, Ann. Geophys., 24, 3011–3026, https://doi.org/10.5194/angeo-24-3011-2006, 2006. +.. [Coxon_et_al_2016] Coxon,J.C.,C.M.Jackman, M. P. Freeman, C. Forsyth, and I. J. Rae (2016), Identifying the magnetotail lobes with Cluster magnetometer data, J. Geophys. Res. Space Physics, 121, 1436–1446, doi:10.1002/2015JA022020. +.. [Hudges_Introduction_to_space_physics_Ch_9] Hudges, W. J. (1995) The magnetopause, magnetotail and magnetic reconnection. In Kivelson, M. G., & Russell, C. T. (Eds.), Introduction to space physics (pp.227-287). Cambridge University Press. +.. [Wolf_Introduction_to_space_physics_Ch_10] Wolf, R. A. (1995) Magnetospheric configuration. In Kivelson, M. G., & Russell, C. T. (Eds.), Introduction to space physics (pp.288-329). Cambridge University Press. +.. [Sckopke_et_al_1981] Sckopke, N., Paschmann, G., Haerendel, G., Sonnerup, B. U. , Bame, S. J., Forbes, T. G., Hones Jr., E. W., and Russell, C. T. (1981). Structure of the low-latitude boundary layer. Journal of Geophysical Research: Space Physics, 86(A4):2099–2110. eprint: https://agupubs.onlinelibrary.wiley.com/doi/pdf/10.1029/JA086iA04p02099 +.. [Boakes_et_al_2014] Boakes, P. D., Nakamura, R., Volwerk, M., and Milan, S. E. (2014). ECLAT Cluster Spacecraft Magnetotail Plasma Region Identifications (2001–2009). Dataset Papers in Science, 2014(1):684305. eprint: https://onlinelibrary.wiley.com/doi/pdf/10.1155/2014/684305 \ No newline at end of file diff --git a/scripts/regions.py b/scripts/regions.py new file mode 100644 index 00000000..e81eefb6 --- /dev/null +++ b/scripts/regions.py @@ -0,0 +1,594 @@ +"""Script and functions for creating sidecar files with SDF/region/boundary tags of plasma regions. + + variables used to find regions/boundary regions: + rho, temperature, beta, beta_star, + + + +""" + +import analysator as pt +import numpy as np +import vtk +#from .analysator.scripts import shue + + +R_E = 6371000 + +### Signed distance field functions: ### + + +def vtkDelaunay3d_SDF(all_points, coordinates): + """Gives a signed distance to a convex hull surface created from given coordinates. + + :param all_points: points ([x, y, z] coordinates in m) for which a signed distance to surface will be calculated + :param coordinates: coordinates (array of [x, y, z]:s in m) that are used to make a surface. + :returns: array of signed distances, negative sign: inside the surface, positive sign: outside the surface + + """ + + points =vtk.vtkPoints()#.NewInstance() + for i in range(len(coordinates)): + points.InsertNextPoint(coordinates[i,0],coordinates[i,1],coordinates[i,2]) + polydata = vtk.vtkPolyData() + polydata.SetPoints(points) + polydata.Modified() + + # Delaunay convex hull + delaunay_3d = vtk.vtkDelaunay3D() + delaunay_3d.SetInputData(polydata) + delaunay_3d.Update() + + + data = delaunay_3d.GetOutput() + + surface = vtk.vtkDataSetSurfaceFilter() + surface.SetInputData(data) + surface.Update() + + implicitPolyDataDistance = vtk.vtkImplicitPolyDataDistance() + implicitPolyDataDistance.SetInput(surface.GetOutput()) + + convexhull_sdf = np.zeros(len(all_points)) + + # SDFs + for i,coord in enumerate(all_points): + convexhull_sdf[i] = implicitPolyDataDistance.EvaluateFunction(coord) + + return convexhull_sdf + + + + +def treshold_mask(data_array, value): + """Chooses and flags cells where variable values match those given. If value is given as float, relative tolerance can be given + + :param data_array: array to mask, e.g. output of f.read_variable(name="proton/vg_rho", cellids=-1) + :param variable: str, variable name + :param value: value/values to use for masking; a float or int for exact match, a (value, relative tolerance) tuple, or [min value, max value] list pair where either can be None for less than or eq./more than or eq. value + :returns: 0/1 mask in same order as cellids, 1: variable value in array inside treshold values, 0: outside + """ + + if data_array is None or value is None: # either variable isn't usable or treshold has not been given + return None + + mask = np.zeros((len(data_array))) + + if isinstance(value, float) or isinstance(value, int): # single value, exact + mask = np.where(np.isclose(data_array, value), 1, 0) + elif isinstance(value, tuple): # single value with tolerance + mask = np.where(np.isclose(data_array, value[0], rtol=value[1]), 1, 0) + else: + if value[0] is None: + mask = np.where((data_array <= value[1]), 1, 0) # anywhere where value is less than or equal to + elif value[1] is None: + mask = np.where((value[0] <= data_array), 1, 0) # anywhere where value is more than or equal to + else: + mask = np.where((value[0] <= data_array) & (data_array <= value[1]), 1, 0) # min/max + + if np.sum(mask[mask>0]) == 0: + print("Treshold mask didn't match any values in array") + return None + + return mask + + +def box_mask(f, coordpoints=None, cells=None, marginal=[150e6, 50e6, 50e6, 50e6, 50e6, 50e6]): + """Crops simulation box for calculations, output flags outside of cropped box will be 0 + + :param f: a VlsvReader + :kword coordpoints: coordinate points of cells to be masked + :kword cells: cellIDs to be masked + :kword marginal: 6-length list of wanted marginal lengths from mesh edges in meters [negx, negy, negz, posx, posy, posz] + :returns: Boolean mask in input order of coordinates/cells, -1 if no coorpoints or cells were given and mask could not be done + """ + [xmin, ymin, zmin, xmax, ymax, zmax] = f.get_spatial_mesh_extent() + + def is_inside(coords): + res = np.zeros((len(coords)), dtype=bool) + + for i,coord in enumerate(coords): + if np.any((coord[0] < xmin+marginal[0], coord[0] > xmax-marginal[3], + coord[1] < ymin+marginal[1], coord[1] > ymax-marginal[4], + coord[2] < zmin+marginal[2], coord[2] > zmax-marginal[5])): + res[i] = False + else: + res[i] =True + return res + + if coordpoints is not None: + return is_inside(coordpoints) + + elif cells is not None: + coords = f.get_cell_coordinates(cells) + return is_inside(coords) + + return -1 + +def write_flags(writer, flags, flag_name, mask=None): + """Writes flags into .vlsv-file with writer + + :param writer: a vlsvwriter + :param flags: an array of flags in order of cellids + :param flag_name: string, name of the flag variable + :kword mask: if flags are made from cropped cells, give mask used for cropping + """ + + if mask is not None: # flags exist only in cropped cells + new_flags = mask.astype(float) + new_flags[mask] = flags + writer.write_variable_info(pt.calculations.VariableInfo(new_flags, flag_name, "-", latex="",latexunits=""),"SpatialGrid",1) + + else: + #writer.write(flags,flag_name,'VARIABLE','SpatialGrid') + writer.write_variable_info(pt.calculations.VariableInfo(flags, flag_name, "-", latex="",latexunits=""),"SpatialGrid",1) + + print(flag_name+" written to file") + + + +def make_region_flags(variable_dict, condition_dict, flag_type="01", mask=None): + """Makes region flags + + example: + make_region_flags(variable_dict = {"density": f.read_variable(name="proton/vg_rho", cellids=-1)}, condition_dict = {"density": [None, 1e7]}) + + results in flag array in shape of read_variable output where cells where "proton/vg_rho" is less or equal to 1e7 are 1 and others are 0 + + + :param variable_dict: dictionary containing names and data arrays of variables, names must match those in condition_dict + :param condition_dict: dictionary containing names and conditions for variable data in variable_dict + :kword flag_type: default "01", optionally "fraction". "01" gives flags 1: all variables fill all conditions, 0: everything else; "fraction" flags are rounded fractions of varibles that fulfill conditions (e.g. flag 0.5 means one of two conditions was filled) + :kword mask: deafault None, optionally boolean array to use for variable dict arrays to search for a region from subset of cells (cells False in mask will be automatically flagged as 0) + + """ + + variable_flags = [] + + if mask is None: # search all cells + for key in condition_dict.keys(): + + try: # if key in variables + region = treshold_mask(variable_dict[key], condition_dict[key]) + if region is not None: variable_flags.append(region) + except: + print(key, "ignored") + + else: # search only masked area + for key in condition_dict.keys(): + try: # if key in variables + region = treshold_mask(variable_dict[key][mask], condition_dict[key]) + if region is not None: variable_flags.append(region) + except: + print(key, "ignored") + + + variable_flags = np.array(variable_flags) + + if len(variable_flags)==0: # no cells that fullfill conditions, hope that density is a key + if mask is None: + return np.zeros((len(variable_dict["density"]))) + else: + return np.zeros((len(variable_dict["density"][mask]))) + + + if flag_type == "01": # 1 only if all viable variables are 1, 0 otherwise + flags = np.where(np.sum(variable_flags, axis=0)==len(variable_flags), 1, 0) + return flags + + elif flag_type == "fraction": # rounded fraction of viable variables that are 1 + flags = np.sum(variable_flags, axis=0)/len(variable_flags) + return flags.round(decimals=2) + + + +def bowshock_SDF(f, variable_dict, query_points, own_condition_dict=None): + """Finds the bow shock by making a convex hull of either default variables/values based on upstream values or a user-defined variable/value dictionary. Returns signed distances from the convex hull to given query_points. + + :param f: a VlsvReader + :param variable_dict: dictionary containing names and variable arrays needed + :param query_points: xyz-coordinates of all points where the SDF will be calculated ([x1, y1, z1], [x2, y2, z2], ...) + :kword own_condition_dict: optional, dictionary with string variable names as keys and tresholds as values to pass to treshold_mask()-function + """ + + cellids =f.read_variable("CellID") + + if own_condition_dict is None: + # bow shock from upstream rho + # upstream point + upstream_point = [150e6, 0.0, 0.0] + upstream_cellid = f.get_cellid(upstream_point) + upstream_rho = f.read_variable(name="proton/vg_rho", cellids=upstream_cellid) + + bowshock_conditions = {"density": (1.5*upstream_rho, 0.1)} + bowshock_rho_flags = make_region_flags(variable_dict, bowshock_conditions, flag_type="01") + bowshock_rho_SDF = vtkDelaunay3d_SDF(query_points, f.get_cell_coordinates(cellids[bowshock_rho_flags!=0])) + return bowshock_rho_SDF + + else: # bowshock from user-defined variable and values + bowshock_rho_flags = make_region_flags(variable_dict, own_condition_dict, flag_type="01") + bowshock_rho_SDF = vtkDelaunay3d_SDF(query_points, f.get_cell_coordinates(cellids[bowshock_rho_flags!=0])) + dict_sdf = vtkDelaunay3d_SDF(query_points, f.get_cell_coordinates(cellids[bowshock_rho_flags!=0])) + + return dict_sdf + + +def magnetopause_SDF(f, datafile, vtpoutfile, variable_dict, query_points, method="beta_star_with_connectivity", own_variable_dict=None): # TODO: remove vlsvReader AND datafile name need (streamline magnetopause to only use f?) + """Finds the magnetopause by making a convex hull of either streamlines, beta*, or user-defined variables, and returns signed distances from surface to query_points. + Note: constructing the magnetopause using solar wind streamlines is slow and needs more memory than e.g. the beta*-method + + :param query_points: xyz-coordinates of all points where the SDF will be calculated ([x1, y1, z1], [x2, y2, z2], ...) + :kword method: str, specifies the method used to find magnetopause, options: "beta_star", "streamlines", "shue", "dict". If "dict" is used, own_variable_dict dictionary must be given. If "shue", run needs to be specified (TODO kwarg) + :kword own_variable_dict: when using "dict"-method, dictionary with string variable names as keys and treshold pairs as values to pass to treshold_mask()-function + """ + + if method != "streamlines": cellids =f.read_variable("CellID") + + if method == "streamlines": + + [xmin, ymin, zmin, xmax, ymax, zmax] = f.get_spatial_mesh_extent() + seeds_x0=150e6 + dl=5e5 + iters = int(((seeds_x0-xmin)/dl)+100) + sector_n = 36*2 + vertices, vtkSurface = pt.calculations.find_magnetopause_sw_streamline_3d(datafile, seeds_n=200, seeds_x0=seeds_x0, seeds_range=[-15*6371000, 15*6371000], + dl=dl, iterations=iters, end_x=xmin+15*6371000, x_point_n=100, sector_n=sector_n) + + # make the magnetopause surface from vertice points + np.random.shuffle(vertices) # helps Delaunay triangulation + Delaunay_SDF = vtkDelaunay3d_SDF(query_points, vertices) + + writer = vtk.vtkXMLPolyDataWriter() + writer.SetInputConnection(vtkSurface.GetOutputPort()) + writer.SetFileName(vtpoutfile) + writer.Write() + return Delaunay_SDF + + elif method == "beta_star": + # magnetopause from beta_star only + condition_dict = {"beta_star": [0.4, 0.5]} # FIC: [0.4, 0.5]) # EGE: [0.9, 1.0]) # max 0.6 in FHA to not take flyaways from outside magnetopause + mpause_flags = make_region_flags(variable_dict, condition_dict, flag_type="01") + contour_coords = f.get_cell_coordinates(cellids[mpause_flags!=0]) + + # make a convex hull surface with vtk's Delaunay + magnetopause_sdf = vtkDelaunay3d_SDF(query_points, contour_coords) + return magnetopause_sdf + + + elif method == "beta_star_with_connectivity": + # magnetopause from beta_star, with connectivity if possible + try: + connectivity_region = treshold_mask(variable_dict["connection"], 0) + betastar_region = treshold_mask(variable_dict["beta_star"], [0.6, 0.7]) + magnetosphere_proper = np.where((connectivity_region==1) | (betastar_region==1), 1, 0) + contour_coords = f.get_cell_coordinates(cellids[magnetosphere_proper==1]) + except: + print("using field line connectivity for magnetosphere did not work, using only beta*") + condition_dict = {"beta_star": [0.5, 0.6]} # FIC: [0.4, 0.5]) # EGE: [0.9, 1.0]) # max 0.6 in FHA to not take flyaways from outside magnetopause + mpause_flags = make_region_flags(variable_dict, condition_dict, flag_type="01") + contour_coords = f.get_cell_coordinates(cellids[mpause_flags!=0]) + + # make a convex hull surface with vtk's Delaunay + magnetopause_sdf = vtkDelaunay3d_SDF(query_points, contour_coords) + return magnetopause_sdf + + elif method == "dict": + # same method as beta* but from user-defined variables as initial contour + flags = make_region_flags(variable_dict, own_variable_dict, flag_type="01") + treshold_coords = f.get_cell_coordinates(cellids[flags!=0]) + dict_sdf = vtkDelaunay3d_SDF(query_points, treshold_coords) + + return dict_sdf + + elif method == "shue": + return 0 + theta = np.linspace(0, 2*np.pi/3 , 200) # magnetotail length decided here by trial and error: [0, 5*np.pi/6] ~ -350e6 m, [0, 2*np.pi/3] ~ -100e6 m in EGE + r, __, __ = shue.f_shue(theta, run='EGI') # EGI~EGE, for runs not in shue.py B_z, n_p, and v_sw need to be specified and run=None + + # 2d one-sided magnetopause + xs = r*np.cos(theta) + ys = r*np.sin(theta) + + # 3d projection for complete magnetopause + psis = np.linspace(0, 2*np.pi, 100) + coords = np.zeros((len(theta)*len(psis), 3)) + i=0 + for x,y in zip(xs,ys): + for psi in psis: + coords[i] = np.array([x, y*np.sin(psi), y*np.cos(psi)]) + i += 1 + + coords = coords*R_E + + # surface and SDF + np.random.shuffle(coords) # helps Delaunay triangulation + shue_SDF = vtkDelaunay3d_SDF(query_points, coords) + + return shue_SDF + + else: + print("Magnetopause method not recognized. Use one of the options: \"beta_star\", \"beta_star_with_connectivity\", \"streamlines\", \"shue\", \"dict\"") + +def RegionFlags(datafile, outfilen, vtpoufile, ignore_boundaries=True, + magnetopause_method="beta_star", magnetopause_dict=None): + + """Creates a sidecar .vlsv file with flagged cells for regions and boundaries in near-Earth plasma environment. + Region flags (start with flag_, flags are fractions of filled conditions or 1/0): magnetosheath, magnetosphere, cusps, lobe_N, lobe_S, central_plasma_sheet, PSBL + Boundary signed distance flags (start with SDF_, flags are signed distances to boundary in m with inside being negative distance): magnetopause, bowshock + + :param datafile: vlsv file name (and path) + :param outdir: sidecar file save directory path + :param outfilen: sidecar file name + :kword ignore_boundaries: True: do not take cells in the inner/outer boundaries of the simulation into account when looking for regions + :kword magnetopause_method: default "beta_star", + """ + + + f = pt.vlsvfile.VlsvReader(file_name=datafile) + cellids =f.read_variable("CellID") + [xmin, ymin, zmin, xmax, ymax, zmax] = f.get_spatial_mesh_extent() + all_points = f.get_cell_coordinates(cellids) #centre_points_of_cells(f) + cellIDdict = f.get_cellid_locations() + + + + ## writer ## + writer = pt.vlsvfile.VlsvWriter(f, outfilen) + writer.copy_variables_list(f, ["CellID"]) + writer.copy_variables(f, varlist=["proton/vg_rho" , "vg_beta_star", "vg_temperature", "vg_b_vol", "vg_J", "vg_beta", "vg_connection", "vg_boundarytype"]) + + + # variable data dictionary + variables = {} + + if f.check_variable("proton/vg_rho"): # vg-grid + + # dict {varible call name : variable vlsvreader name or varible call name : (variable vlsvreader name, operator)} + varnames = {"density": "proton/vg_rho", + "temperature": "vg_temperature", + "beta": "vg_beta", + "beta_star": "vg_beta_star", + "B_magnitude": ("vg_B_vol", "magnitude"), + "B_x": ("vg_B_vol", "x"), + "B_y": ("vg_B_vol", "y"), + "B_z": ("vg_B_vol", "z"), + "connection": "vg_connection", + "J_magnitude": ("vg_J", "magnitude")} + + boundaryname = "vg_boundarytype" + + + def errormsg(varstr): print("{} could not be read, will be ignored".format(varstr)) + + for varname, filevarname in varnames.items(): + if isinstance(filevarname, str): # no operator + try: + variables[varname] = f.read_variable(name=filevarname, cellids=-1) + except: + errormsg(filevarname) + else: + try: + variables[varname] = f.read_variable(name=filevarname[0], cellids=-1, operator=filevarname[1]) + except: + errormsg(filevarname) + + ## cellid testing + #testmesh = np.where(cellids < 10000, 1, 0) + #write_flags(writer, testmesh, 'testmesh') + + + + # upstream point + upstream_point = [xmax-10*R_E,0.0,0.0] + upstream_cellid = f.get_cellid(upstream_point) + upstream_index = cellIDdict[upstream_cellid] + + ## MAGNETOPAUSE ## + magnetopause = magnetopause_SDF(f, datafile, vtpoufile, variables, all_points, method=magnetopause_method, own_variable_dict=magnetopause_dict) + write_flags(writer, magnetopause, 'SDF_magnetopause') + write_flags(writer, np.where(np.abs(magnetopause) < 5e6, 1, 0), "flag_magnetopause") + + # save some magnetopause values for later + magnetopause_density = np.mean(variables["density"][np.abs(magnetopause) < 5e6]) + print(f"{magnetopause_density=}") + magnetopause_temperature = np.mean(variables["temperature"][np.abs(magnetopause) < 5e6]) + print(f"{magnetopause_temperature=}") + + ## MAGNETOSPHERE ## + # magnetosphere from magentopause SDF + magnetosphere = np.where(magnetopause<0, 1, 0) + write_flags(writer, magnetosphere, 'flag_magnetosphere_convex') + + # magnetospshere from beta* and field line connectivity if possible # TODO + try: + connectivity_region = treshold_mask(variables["connection"], 0) + betastar_region = treshold_mask(variables["beta_star"], [0.01, 0.5]) + magnetosphere_proper = np.where((connectivity_region==1) | (betastar_region==1), 1, 0) + write_flags(writer, magnetosphere_proper, 'flag_magnetosphere') + except: + print("Non-convex magnetosphere could not be made") + + ## BOW SHOCK ## + # bow shock from rho + bowshock = bowshock_SDF(f, variables, all_points) + write_flags(writer, bowshock, 'SDF_bowshock') + write_flags(writer, np.where(np.abs(bowshock) < 5e6, 1, 0), "flag_bowshock") + + # magnetosphere+magnetosheath -area + inside_bowshock = np.where(bowshock<0, 1, 0) + + ## MAGNETOSHEATH ## + # magnetosheath from bow shock-magnetosphere difference + magnetosheath_flags = np.where((inside_bowshock & 1-magnetosphere), 1, 0) + write_flags(writer, magnetosheath_flags, 'flag_magnetosheath') + + # save magentosheath density and temperature for further use + magnetosheath_density = np.mean(variables["density"][magnetosheath_flags == 1]) + print(f"{magnetosheath_density=}") + magnetosheath_temperature = np.mean(variables["temperature"][magnetosheath_flags == 1]) + print(f"{magnetosheath_temperature=}") + + ## UPSTREAM ## + # upstream from !bowshock + write_flags(writer, 1-inside_bowshock, 'flag_upstream') + + write_flags(writer, inside_bowshock, 'flag_inside_bowshock') + + + + ## INNER MAGNETOSPHERE REGIONS ## + + if ignore_boundaries: + noBoundaries = np.where(f.read_variable(name=boundaryname, cellids=-1) == 1, 1, 0) # boundarytype 1: not a boundary + mask_inBowshock= np.where(((inside_bowshock == 1) & (noBoundaries == 1)), 1, 0).astype(bool) # only search inner regions from inside the magnetosheath and magnetosphere + mask_inMagnetosphere = np.where(((magnetosphere == 1) & (noBoundaries == 1)), 1, 0).astype(bool) # + + else: + mask_inBowshock = inside_bowshock.astype(bool) # only search inner regions from inside the magnetosheath and magnetosphere + mask_inMagnetosphere = magnetosphere.astype(bool) # + + #print("upstream B:", variables["B_magnitude"][upstream_index]) + + # cusps + try: + cusp_conditions = {"density": [variables["density"][upstream_index], None], + #"beta_star": [0.1, None], + "connection": [0.0, 2.5], # either closed, or open-closed/closed-open + "B_magnitude":[2*variables["B_magnitude"][upstream_index], None], + "J_magnitude": [variables["J_magnitude"][upstream_index], None] + } + except: + cusp_conditions = {"density": [variables["density"][upstream_index], None], + #"beta_star": [0.1, None], + "connection": [0.0, 2.5], # either closed, or open-closed/closed-open + "B_magnitude":[2*variables["B_magnitude"][upstream_index], None] + } + + cusp_flags = make_region_flags(variables, cusp_conditions, flag_type="fraction", mask=mask_inMagnetosphere) + write_flags(writer, cusp_flags, 'flag_cusps', mask_inMagnetosphere) + + + # magnetotail lobes + lobes_conditions = {"beta": [None, 0.1], # Koskinen: 0.003 + "connection": [0.5, 2.5], + "density": [None, variables["density"][upstream_index]], # Koskinen: 1e-8 + "temperature": [None, 3.5e6], # Koskinen: 3.5e6 K + } + lobes_flags = make_region_flags(variables, lobes_conditions, flag_type="fraction", mask=mask_inBowshock) + write_flags(writer, lobes_flags, 'flag_lobes', mask_inBowshock) + + # lobes slightly other way + lobe_N_conditions = {"beta": [None, 0.1], + "connection": [0.5, 2.5], + "B_x":[0, None], + "B_magnitude":[None, 10*variables["B_magnitude"][upstream_index]] + } + lobe_N_flags = make_region_flags(variables, lobe_N_conditions, flag_type="fraction", mask=mask_inBowshock) + write_flags(writer, lobe_N_flags, 'flag_lobe_N', mask_inBowshock) + + lobe_S_conditions = {"beta": [None, 0.1], + "connection": [0.5, 2.5], + "B_x":[None, 0], + "B_magnitude":[None, 10*variables["B_magnitude"][upstream_index]] + } + lobe_S_flags = make_region_flags(variables, lobe_S_conditions, flag_type="fraction", mask=mask_inBowshock) + write_flags(writer, lobe_S_flags, 'flag_lobe_S', mask_inBowshock) + + # lobe density from median densities? + lobes_mask = np.where((lobes_flags > 0.9), 1, 0).astype(bool) + lobes_allcells = mask_inBowshock.astype(float) + lobes_allcells[mask_inBowshock] = lobes_mask + lobe_density = np.mean(variables["density"][lobes_allcells>0.9]) + print(f"{lobe_density=}") + + # Central plasma sheet + central_plasma_sheet_conditions = {#"density": [None, variables["density"][upstream_index]], + "density": [None, 1e6], # Wolf intro + #"density" : [1e7, None], # Koskinen: 3e-7 + #"density": [lobe_density, None], + "beta": [1.0, None], # Koskinen: 6 + #"connection": 0, # only closed-closed + #"temperature": [2*variables["temperature"][upstream_index], None]#, + "temperature": [2e6, None], # 5e7 from Koskinen + #"B_magnitude":[10*variables["B_magnitude"][upstream_index], None] + #"J_magnitude": [2*variables["J_magnitude"][upstream_index], None], + "J_magnitude": [1e-9, None], + } + + central_plasma_sheet_flags = make_region_flags(variables, central_plasma_sheet_conditions,flag_type="fraction", mask=mask_inMagnetosphere) + write_flags(writer, central_plasma_sheet_flags, 'flag_central_plasma_sheet', mask_inMagnetosphere) + + + # Plasma sheet boundary layer (PSBL) + #PSBL_conditions = {#"density": (1e7, 1.0), # Koskinen: 1e5 + # "density": [None, 1e7], + #"temperature": [0.5e6, 1e6],#(1e7, 2.0), # from Koskinen + #"temperature": [2e6, None], + # "beta": [0.1, 1.0], + #"B_magnitude":[10*variables["B_magnitude"][upstream_index], None] + # "J_magnitude": [2*variables["J_magnitude"][upstream_index], None], + # } + + + #PSBL_flags = make_region_flags(variables, PSBL_conditions,flag_type="fraction", mask=mask_inMagnetosphere) + #write_flags(writer, PSBL_flags, "flag_PSBL", mask_inMagnetosphere) + + + # Low-Latitude boundary layer (LLBL) + #LLBL_conditions = {"density": [magnetopause_density, magnetosheath_density], + # "temperature": [magnetosheath_temperature, magnetopause_temperature] + # } + #LLBL_flags = make_region_flags(variables, LLBL_conditions,flag_type="fraction", mask=mask_inBowshock) + #write_flags(writer, LLBL_flags, "flag_LLBL", mask_inBowshock) + + + + + + # High-Latitude boundary layer (HLBL) + HLBL_conditions = { "density": [magnetopause_density, magnetosheath_density], + "temperature": [magnetosheath_temperature, magnetopause_temperature], + "beta": [1.0, None], + "connection": 0, + "J_magnitude": [2*variables["J_magnitude"][upstream_index], None], + } + + HLBL_flags = make_region_flags(variables, HLBL_conditions,flag_type="fraction", mask=mask_inBowshock) + write_flags(writer, HLBL_flags, "flag_HLBL", mask_inBowshock) + + + +def main(): + + datafile = "/wrk-vakka/group/spacephysics/vlasiator/3D/FHA/bulk1/bulk1.0001400.vlsv" + outfilen = "FHA_regions_t0001400.vlsv" + vtp_outfilen = "FHA_regions_t0001400.vtp" + + + for infile, outfile, vtp_outfile in zip(datafile, outfilen, vtp_outfilen): + RegionFlags(infile, outfile, vtp_outfile, magnetopause_method="streamlines") + + +if __name__ == "__main__": + + main() From 682199d068b66c5f2d79a21472fa6681dac2d75f Mon Sep 17 00:00:00 2001 From: jreimi Date: Thu, 21 Aug 2025 13:04:28 +0300 Subject: [PATCH 083/124] fix to dayside slicing --- analysator/pyCalculations/magnetopause_sw_streamline_3d.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/analysator/pyCalculations/magnetopause_sw_streamline_3d.py b/analysator/pyCalculations/magnetopause_sw_streamline_3d.py index d3acc149..27d44df4 100644 --- a/analysator/pyCalculations/magnetopause_sw_streamline_3d.py +++ b/analysator/pyCalculations/magnetopause_sw_streamline_3d.py @@ -268,6 +268,9 @@ def make_magnetopause(streams, end_x=-15*6371000, x_point_n=50, sector_n=36): else: subsolar_x = np.partition(x_axis_points[:,0], ignore)[ignore] # take the nth point as subsolar point + # divide the x point numbers between x > 0 (radial) an x < 0 (yz-planes) by ratio + dayside_x_point_n = int((subsolar_x/np.abs(end_x))*x_point_n) + ### dayside magnetopause ### # for x > 0, look for magnetopause radially dayside_points = streampoints[streampoints[:,0] > 0] @@ -319,7 +322,7 @@ def grid_mid_point(theta_idx, phi_idx): ### x < 0 magnetopause ### # rest: look for magnetopause in yz-planes ## define points in the x axis where to find magnetopause points on the yz-plane - x_points = np.linspace(subsolar_x, end_x, x_point_n) + x_points = np.linspace(0.0, end_x, x_point_n-dayside_x_point_n) ## interpolate more exact points for streamlines at exery x_point new_streampoints = np.zeros((len(x_points), len(streams), 2)) # new array for keeping interpolated streamlines in form new_streampoints[x_point, streamline, y and z -coordinates] From 37910d3c4aa81818df7b27c0f5240e673978ccde Mon Sep 17 00:00:00 2001 From: jreimi Date: Fri, 22 Aug 2025 16:11:16 +0300 Subject: [PATCH 084/124] Field values are passed to stopping condition function instead of unit vectors --- analysator/pyCalculations/fieldtracer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/analysator/pyCalculations/fieldtracer.py b/analysator/pyCalculations/fieldtracer.py index f383c760..1250a387 100644 --- a/analysator/pyCalculations/fieldtracer.py +++ b/analysator/pyCalculations/fieldtracer.py @@ -294,7 +294,7 @@ def find_unit_vector(vg, coord): points_traced_unique[mask_update,i,:] = next_points[mask_update,:] # distances = np.linalg.norm(points_traced_unique[:,i,:],axis = 1) - mask_update[stop_condition(vlsvReader, points_traced_unique[:,i,:], var_unit)] = False + mask_update[stop_condition(vlsvReader, points_traced_unique[:,i,:], vlsvReader.read_interpolated_variable(vg,points_traced_unique[:,i,:]))] = False # points_traced_unique[~mask_update, i, :] = points_traced_unique[~mask_update, i-1, :] @@ -302,7 +302,7 @@ def find_unit_vector(vg, coord): return points_traced # Default stop tracing condition for the vg tracing, (No stop until max_iteration) -def default_stopping_condition(vlsvReader, points, last_unit_v): +def default_stopping_condition(vlsvReader, points, vars): [xmin, ymin, zmin, xmax, ymax, zmax] = vlsvReader.get_spatial_mesh_extent() x = points[:, 0] y = points[:, 1] From 8bb1aff96f3bed7d3240f57839000ab850c78995 Mon Sep 17 00:00:00 2001 From: jreimi Date: Mon, 25 Aug 2025 14:26:26 +0300 Subject: [PATCH 085/124] Magnetopause script additional notes --- Documentation/sphinx/magnetopause.rst | 74 +++++++++++++++++++-------- 1 file changed, 52 insertions(+), 22 deletions(-) diff --git a/Documentation/sphinx/magnetopause.rst b/Documentation/sphinx/magnetopause.rst index b5b68520..7bf6f54b 100644 --- a/Documentation/sphinx/magnetopause.rst +++ b/Documentation/sphinx/magnetopause.rst @@ -34,17 +34,19 @@ Ideal values of beta* for magnetopause construction might be run-dependent, and Field line connectivity ----------------------- +Magnetic field lines that are closed at least from one end ... + Solar wind flow --------------- -Method used in e.g. [Parlmroth_et_al_2003_] +Method used in e.g. [Palmroth_et_al_2003]_ Streamlines of velocity field that are traced from outside the bow shock curve around the magnetopause. -Caveats: sometimes some streamlines can curve into the magnetotail +Caveats: sometimes some streamlines can curve into the magnetotail or dayside magnetoshpere **In analysator:** @@ -54,35 +56,25 @@ magnetopause_sw_streamline_3d.py Streamlines are traced from outside the bow shock towards Earth. A subsolar point for the magnetopause is chosen to be where streamlines get closest to Earth in x-axis [y/z~0]. -From subsolar point towards Earth, .... - +From subsolar point towards Earth, space is divided radially by spherical coordiante (theta from x-axis) angles theta and phi, and magnetopause is located by looking at streamline point distances from origo and marked to be at middle of the sector From the Earth towards negative x the space is divided into yz-planes. -Each yz-plane is then divided into sectors and magnetopause is marked to be in the middle of the sector with the radius of the closest streamline to the x-axis. - -For subsolar point, radial dayside and -x planes the closest streamline point can be changed to be n:th closest by setting keyword "ignore", in which case n-1 closest streamline points are not taken into acocunt. +Each yz-plane is then divided into 2d sectors and magnetopause is marked to be in the middle of the sector with the radius of the n:th closest streamline to the x-axis. -2d: -streamlines: [...] -magnetopause: [...] +For subsolar point, radial dayside and -x planes the closest streamline point can be changed to be n:th closest by setting keyword *ignore*, in which case *ignore* closest streamline points are not taken into acocunt. -example usage in [...] +2d: +Note: no radial dayside 3d: -streamlines: [...] -magnetopause: [...] -surface triangulation: [...] - -example usage in [...] +After the magnetopause points are chosen, they are made into a surface by setting connnection lines between vertices (magnetopause points) to make surface triangles. +This surface is then made into a vtkPolyData and returned as vtkDataSetSurfaceFilter that can be written into e.g. .vtp file. - - - -.. [Parlmroth_et_al_2003] Palmroth, M., T. I. Pulkkinen, P. Janhunen, and C.-C. Wu (2003), Stormtime energy transfer in global MHD simulation, J. Geophys. Res., 108, 1048, doi:10.1029/2002JA009446, A1. +.. [Palmroth_et_al_2003] Palmroth, M., T. I. Pulkkinen, P. Janhunen, and C.-C. Wu (2003), Stormtime energy transfer in global MHD simulation, J. Geophys. Res., 108, 1048, doi:10.1029/2002JA009446, A1. @@ -113,10 +105,48 @@ The magnetopause radius as a function of angle :math:`\theta` is **In analysator:** -*shue.py* in *scripts*: +*shue.py* in *scripts* + + +.. [Shue_et_al_1997] Shue, J.-H., Chao, J. K., Fu, H. C., Russell, C. T., Song, P., Khurana, K. K., and Singer, H. J. (1997). A new functional form to study the solar wind control of the magnetopause size and shape. Journal of Geophysical Research: Space Physics, 102(A5):9497–9511. eprint: https://agupubs.onlinelibrary.wiley.com/doi/pdf/10.1029/97JA00196. + + + + + +**In analysator:** +------------------ + +*magnetopause.py* in scripts for 3d runs +Constructs the magentopause surface with vtk's vtkDelaunay3d triangulation with optional alpha to make the surface non-convex. +Uses regions.py functions. + +Important: SDF of non-convex surface will (most likely) not work, use convex hull (alpha=None) for SDFs! + +options (magnetopause() method keyword) and some notes: +* solar wind flow ("streamlines") + * uses *magnetopause_sw_streamline_3d.py* from pyCalculations + * if streamlines make a turn so that the velocity points sunwards (to pos. x), the streamline is ignored from that point onwards + * this fixes some issues with funky and inwards-turning streamlines but not all + * very dependent on how solar wind streamlines behave + * streamline seeds and other options can greatly affect the resulting magnetopause +* beta* ("beta_star") + * beta* treshold might need tweaking as sometimes there are small low beta* areas in the magnetosheath that get taken in distorting the magnetopause shape at nose + * convex hull (Delaunay_alpha=None) usually makes a nice rough magnetopause but goes over any inward dips (like polar cusps) + * alpha shape (Delaunay_alpha= e.g. 1*R_E) does a better job at cusps and delicate shapes like vortices but can fail at magnetotail due to central plasma sheet and won't produce a correct SDF + * Delaynay3d has an easier time if the treshold is something like [0.4, 0.5] and not [0.1, 0.5] +* beta* with magnetic field line connectivity ("beta_star_with_connectivity") + * includes closed-closed magnetic field line areas if available, otherwise like "beta_star" + * can help with nose shape as beta* can be set lower to exclude magnetosheath low beta* areas while still getting the full dayside from field lines +* Shue et al. 1997 ("shue") + * uses *shue.py* from scripts + * a rough theoretical magnetopause using Shue et al. 1997 method based on B_z, solar wind density, and solar wind velocity -.. [Shue_et_al_1997] Shue, J.-H., Chao, J. K., Fu, H. C., Russell, C. T., Song, P., Khurana, K. K., and Singer, H. J. (1997). A new functional form to study the solar wind control of the magnetopause size and shape. Journal of Geophysical Research: Space Physics, 102(A5):9497–9511. eprint: https://agupubs.onlinelibrary.wiley.com/doi/pdf/10.1029/97JA00196. \ No newline at end of file +* user-defined parameter tresholds ("dict") + * creates a magnetopause (or other area) using the Delaunay3d triangulation of some area where user-defined tresholds given as dictionary + * dictionary key is data name in datafile and value is treshold used, if dictionary has multiple conditions, they all need to be fulfilled + * dictionary example: {"vg_rho": [None, 1e5]} makes a magnetopause using cells where density is less than 1e5 \ No newline at end of file From d5af3cadcba43a2b9e800d81f9104b0fa0d09c79 Mon Sep 17 00:00:00 2001 From: jreimi Date: Mon, 25 Aug 2025 14:35:33 +0300 Subject: [PATCH 086/124] attempted citation fixes --- .../sphinx/magnetosphere_regions.rst | 65 ++++++++++--------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/Documentation/sphinx/magnetosphere_regions.rst b/Documentation/sphinx/magnetosphere_regions.rst index 81f1ec60..1693d545 100644 --- a/Documentation/sphinx/magnetosphere_regions.rst +++ b/Documentation/sphinx/magnetosphere_regions.rst @@ -3,7 +3,6 @@ Magnetosphere regions: How to find - Cusps ----- @@ -11,9 +10,9 @@ Cusps *Properties:* * plasma density: high density in comparison to solar wind - * Ion density :math:`\geq` solar wind ion density [Pitout_et_al_2006_] (Cluster spacecraft data) + * Ion density :math:`\geq` solar wind ion density [Pitout_et_al_2006]_ (Cluster spacecraft data) * ion energy: - * mean ion energy :math:`~2-3 keV` [Pitout_et_al_2006_] /[Stenuit_et_al_2001_] + * mean ion energy :math:`~2-3 keV` [Pitout_et_al_2006]_ /[Stenuit_et_al_2001]_ * energy flux: * energy flux @@ -26,19 +25,19 @@ Tail lobes ---------- * plasma density: low - * below :math:`0.03 cm^{-3}` [Grison_et_al_2025_] (Cluster spacecraft data) - * :math:`0.01 cm^{-3}` [Koskinen_Space_Storms_ p.38] - * less than :math:`0.1 cm^{-3}` [Wolf_Introduction_to_space_physics_Ch_10_ p.291] + * below :math:`0.03 cm^{-3}` [Grison_et_al_2025]_ (Cluster spacecraft data) + * :math:`0.01 cm^{-3}` [Koskinen_Space_Storms]_ p.38 + * less than :math:`0.1 cm^{-3}` [Wolf_Introduction_to_space_physics_Ch_10]_ p.291 * plasma :math:`\beta`: low - * typically around :math:`0.05` [Grison_et_al_2025_] (Cluster spacecraft data) - * :math:`3e-3` [Koskinen_Space_Storms_ p.38] + * typically around :math:`0.05` [Grison_et_al_2025]_ (Cluster spacecraft data) + * :math:`3e-3` [Koskinen_Space_Storms]_ p.38 * temperature: - * ion temperature :math:`300 eV` [Koskinen_Space_Storms_ p.38] - * electron temperature :math:`50 eV` [Koskinen_Space_Storms_ p.38] + * ion temperature :math:`300 eV` [Koskinen_Space_Storms]_ p.38 + * electron temperature :math:`50 eV` [Koskinen_Space_Storms]_ p.38 * magnetic field: - * :math:`20 nT` [Koskinen_Space_Storms_ p.38] -* open magnetic field lines [Wolf_Introduction_to_space_physics_Ch_10_ p.291] -* strong and stable magnetic field towards the Earth (northern lobe) and away from the Earth (southern lobe) [Coxon_et_al_2016_] + * :math:`20 nT` [Koskinen_Space_Storms]_ p.38 +* open magnetic field lines [Wolf_Introduction_to_space_physics_Ch_10]_ p.291 +* strong and stable magnetic field towards the Earth (northern lobe) and away from the Earth (southern lobe) [Coxon_et_al_2016]_ Separated from the plasma sheet by the plasma sheet boundary layer (PSBL) @@ -63,10 +62,10 @@ Low-latitude boundary layer (LLBL) Properties: * density: - * ion number densities between those of magnetosphere and magnetosheath [Hudges_Introduction_to_space_physics_Ch_9_ p.267] + * ion number densities between those of magnetosphere and magnetosheath [Hudges_Introduction_to_space_physics_Ch_9]_ p.267 * temperature - * ion temperatures between those of magnetosphere and magnetosheath [Hudges_Introduction_to_space_physics_Ch_9_ p.267] -* unknown field line configuration, probably a mix of open and closed field lines [Hudges_Introduction_to_space_physics_Ch_9_ p.262] + * ion temperatures between those of magnetosphere and magnetosheath [Hudges_Introduction_to_space_physics_Ch_9]_ p.267 +* unknown field line configuration, probably a mix of open and closed field lines [Hudges_Introduction_to_space_physics_Ch_9]_ p.262 @@ -77,7 +76,7 @@ Includes the plasma mantle on the tail side and the entry layer on the dayside [ Properties: -* open magnetic field lines [Hudges_Introduction_to_space_physics_Ch_9_ p.261] +* open magnetic field lines [Hudges_Introduction_to_space_physics_Ch_9]_ p.261 @@ -86,20 +85,20 @@ Properties: Plasma sheet boundary layer (PSBL) ---------------------------------- -The plasma sheet boundary layer is a very thin boundary layer separating the tail lobes from the tail plasma sheet [Koskinen_Johdatus_] +The plasma sheet boundary layer is a very thin boundary layer separating the tail lobes from the tail plasma sheet [Koskinen_Johdatus]_ *Properties:* * density: - * :math:`0.1 cm^{-3}` [Koskinen_Space_Storms_ p.38] + * :math:`0.1 cm^{-3}` [Koskinen_Space_Storms]_ p.38 * temperature: - * ion temperature :math:`1000 eV` [Koskinen_Space_Storms_ p.38] - * electron temperature :math:`150 eV` [Koskinen_Space_Storms_ p.38] + * ion temperature :math:`1000 eV` [Koskinen_Space_Storms]_ p.38 + * electron temperature :math:`150 eV` [Koskinen_Space_Storms]_ p.38 * magnetic field: - * :math:`20 nT` [Koskinen_Space_Storms_ p.38] + * :math:`20 nT` [Koskinen_Space_Storms]_ p.38 * plasma :math:`\beta` : - * :math:`0.1` [Koskinen_Space_Storms_ p.38] -* probably closed magnetic field lines [Wolf_Introduction_to_space_physics_Ch_10_ p.291] + * :math:`0.1` [Koskinen_Space_Storms]_ p.38 +* probably closed magnetic field lines [Wolf_Introduction_to_space_physics_Ch_10]_ p.291 @@ -111,17 +110,18 @@ Central plasma sheet *Properties:* * density: - * :math:`0.3 cm^{-3}` [Koskinen_Space_Storms_ p.38] - * :math:`0.1-1 cm^{-3}` [Wolf_Introduction_to_space_physics_Ch_10_ p.291] + * :math:`0.3 cm^{-3}` [Koskinen_Space_Storms]_ p.38 + * :math:`0.1-1 cm^{-3}` [Wolf_Introduction_to_space_physics_Ch_10]_ p.291 * temperature: hot - * ion temperature :math:`4200 eV` [Koskinen_Space_Storms_ p.38] - * electron temperature :math:`600 eV` [Koskinen_Space_Storms_ p.38] + * ion temperature :math:`4200 eV` [Koskinen_Space_Storms]_ p.38 + * electron temperature :math:`600 eV` [Koskinen_Space_Storms]_ p.38 * magnetic field: - * :math:`10 nT` [Koskinen_Space_Storms_ p.38], [Hudges_Introduction_to_space_physics_Ch_9_] + * :math:`10 nT` [Koskinen_Space_Storms]_ p.38, [Hudges_Introduction_to_space_physics_Ch_9]_ * plasma :math:`\beta`: high - * :math:`6` [Koskinen_Space_Storms_ p.38] -* Mostly closed magnetic field lines [Wolf_Introduction_to_space_physics_Ch_10_] + * :math:`6` [Koskinen_Space_Storms]_ p.38 +* Mostly closed magnetic field lines [Wolf_Introduction_to_space_physics_Ch_10]_ +Inner plasma sheet: unusually low plasma beta may exist (e.g., cold tenuous plasma near the neutral sheet after long periods of northward IMF) [Boakes_et_al_2014]_, (Cluster spacecraft data) @@ -137,4 +137,5 @@ References .. [Hudges_Introduction_to_space_physics_Ch_9] Hudges, W. J. (1995) The magnetopause, magnetotail and magnetic reconnection. In Kivelson, M. G., & Russell, C. T. (Eds.), Introduction to space physics (pp.227-287). Cambridge University Press. .. [Wolf_Introduction_to_space_physics_Ch_10] Wolf, R. A. (1995) Magnetospheric configuration. In Kivelson, M. G., & Russell, C. T. (Eds.), Introduction to space physics (pp.288-329). Cambridge University Press. .. [Sckopke_et_al_1981] Sckopke, N., Paschmann, G., Haerendel, G., Sonnerup, B. U. , Bame, S. J., Forbes, T. G., Hones Jr., E. W., and Russell, C. T. (1981). Structure of the low-latitude boundary layer. Journal of Geophysical Research: Space Physics, 86(A4):2099–2110. eprint: https://agupubs.onlinelibrary.wiley.com/doi/pdf/10.1029/JA086iA04p02099 -.. [Boakes_et_al_2014] Boakes, P. D., Nakamura, R., Volwerk, M., and Milan, S. E. (2014). ECLAT Cluster Spacecraft Magnetotail Plasma Region Identifications (2001–2009). Dataset Papers in Science, 2014(1):684305. eprint: https://onlinelibrary.wiley.com/doi/pdf/10.1155/2014/684305 \ No newline at end of file +.. [Boakes_et_al_2014] Boakes, P. D., Nakamura, R., Volwerk, M., and Milan, S. E. (2014). ECLAT Cluster Spacecraft Magnetotail Plasma Region Identifications (2001–2009). Dataset Papers in Science, 2014(1):684305. eprint: https://onlinelibrary.wiley.com/doi/pdf/10.1155/2014/684305 +.. [Stenuit_et_al_2001] Stenuit, H., Sauvaud, J.-A., Delcourt, D. C., Mukai, T., Kokubun, S., Fujimoto, M., Buzulukova, N. Y., Kovrazhkin, R. A., Lin, R. P., and Lepping, R. P. (2001). A study of ion injections at the dawn and dusk polar edges of the auroral oval. Journal of Geophysical Research: Space Physics, 106(A12):29619–29631. eprint: https://agupubs.onlinelibrary.wiley.com/doi/pdf/10.1029/2001JA900060. From f6322ed9f94aac46c2bfb7fa9c01c45671b05cc9 Mon Sep 17 00:00:00 2001 From: jreimi Date: Tue, 26 Aug 2025 11:22:52 +0300 Subject: [PATCH 087/124] Magnetopause search file --- scripts/magnetopause.py | 175 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 scripts/magnetopause.py diff --git a/scripts/magnetopause.py b/scripts/magnetopause.py new file mode 100644 index 00000000..2af4b7ae --- /dev/null +++ b/scripts/magnetopause.py @@ -0,0 +1,175 @@ +"""Functions for finding the magnetopause from .vlsv data +""" + +import analysator as pt +import numpy as np +import vtk +import time +from scripts import shue, regions + + +R_E = 6371000 + + +def write_vtk_surface_to_file(vtkSurface, outfilen): + writer = vtk.vtkXMLPolyDataWriter() + writer.SetInputConnection(vtkSurface.GetOutputPort()) + writer.SetFileName(outfilen) + writer.Write() + print("wrote ", outfilen) + +def write_SDF_to_file(SDF, datafilen, outfilen): + f = pt.vlsvfile.VlsvReader(file_name=datafilen) + writer = pt.vlsvfile.VlsvWriter(f, outfilen) + writer.copy_variables_list(f, ["CellID"]) + writer.write_variable_info(pt.calculations.VariableInfo(SDF, "SDF_magnetopause", "-", latex="",latexunits=""),"SpatialGrid",1) + print("wrote ", outfilen) + + + + +def magnetopause(datafilen, method="beta_star_with_connectivity", own_tresholds=None, return_surface=True, return_SDF=True, SDF_points=None, Delaunay_alpha=None, beta_star_range=[0.1, 1.0]): # TODO: separate streamline suface and vtkDelaunay3d surface in streamline method + """Finds the magnetopause using the specified method. Surface is constructed using vtk's Delaunay3d triangulation which results in a convex hull if no Delaunay_alpha is given. + + :param datafilen: a .vlsv bulk file name (and path) + :kword method: str, default "beta_star_with_connectivity", other options "beta_star", "streamlines", "shue", "dict" + :kword own_tresholds: if using method "dict", a dictionary with conditions for the magnetopause/magnetosphere must be given where key is data name in datafile and value is treshold used (see treshold_mask()) + :kword return_surface: True/False, return vtkDataSetSurfaceFilter object + :kword return_SDF: True/False, return array of distances in m to SDF_points in point input order, negative distance inside the surface + :kword SDF_points: optionally give array of own points to calculate signed distances to. If not given, distances will be to cell centres in the order of f.read_variable("CellID") output + :kword Delaunay_alpha: alpha (float) to give to vtkDelaunay3d, None -> convex hull, alpha=__: surface egdes longer than __ will be excluded (-> concave hull) + :kword beta_star_range: [min, max] treshold rage to use with methods "beta_star" and "beta_star_with_connectivity" + :returns: vtkDataSetSurfaceFilter object of convex hull or alpha shape if return_surface=True, signed distance field of convex hull or alpha shape of magnetopause if return_SDF=True + """ + + + start_t = time.time() + f = pt.vlsvfile.VlsvReader(file_name=datafilen) + cellids = f.read_variable("CellID") + [xmin, ymin, zmin, xmax, ymax, zmax] = f.get_spatial_mesh_extent() + query_points = f.get_cell_coordinates(cellids) # for SDF, all centre points of cells + + if method == "streamlines": + + [xmin, ymin, zmin, xmax, ymax, zmax] = f.get_spatial_mesh_extent() + seeds_x0=150e6 + dl=5e5 + iters = int(((seeds_x0-xmin)/dl)+100) + sector_n = 36*4 + vertices, manual_vtkSurface = pt.calculations.find_magnetopause_sw_streamline_3d(datafilen, seeds_n=200, seeds_x0=seeds_x0, seeds_range=[-5*6371000, 5*6371000], + dl=dl, iterations=iters, end_x=xmin+10*6371000, x_point_n=200, sector_n=sector_n) + # good parameters example: seeds_x0=150e6, dl=5e5, iters = int(((seeds_x0-xmin)/dl)+100), sector_n = 36*3, seeds_n=100, seeds_range=[-5*6371000, 5*6371000], end_x=xmin+10*6371000, x_point_n=200 + + write_vtk_surface_to_file(manual_vtkSurface, "/wrk-vakka/users/jreimi/magnetosphere_classification/FID/FID_magnetopause_SW_manual_t1100.vtp") + # make the magnetopause surface from vertice points + np.random.shuffle(vertices) # helps Delaunay triangulation + vtkSurface, SDF = regions.vtkDelaunay3d_SDF(query_points, vertices, Delaunay_alpha) + + elif method == "beta_star": + # magnetopause from beta_star only + mpause_flags = regions.treshold_mask(f.read_variable("vg_beta_star"), beta_star_range) + contour_coords = f.get_cell_coordinates(cellids[mpause_flags!=0]) + + # make a convex hull surface with vtk's Delaunay + np.random.shuffle(contour_coords) + vtkSurface, SDF = regions.vtkDelaunay3d_SDF(query_points, contour_coords, Delaunay_alpha) + + + + elif method == "beta_star_with_connectivity": + # magnetopause from beta_star, with connectivity if possible + betastar_region = regions.treshold_mask(f.read_variable("vg_beta_star"), beta_star_range) + try: + connectivity_region = regions.treshold_mask(f.read_variable("vg_connection"), 0) # closed-closed magnetic field lines + magnetosphere_proper = np.where((connectivity_region==1) | (betastar_region==1), 1, 0) + contour_coords = f.get_cell_coordinates(cellids[magnetosphere_proper==1]) + except: + print("using field line connectivity for magnetosphere did not work, using only beta*") + #condition_dict = {"beta_star": [0.5, 0.6]} # FIC: [0.4, 0.5]) # EGE: [0.9, 1.0]) # max 0.6 in FHA to not take flyaways from outside magnetopause + mpause_flags = np.where(betastar_region==1, 1, 0) + contour_coords = f.get_cell_coordinates(cellids[mpause_flags!=0]) + + # make a convex hull surface with vtk's Delaunay + vtkSurface, SDF = regions.vtkDelaunay3d_SDF(query_points, contour_coords, Delaunay_alpha) + + + elif method == "dict": + variable_dict = {} + for key in own_tresholds: + variable_dict[key] = f.read_variable(key, cellids=-1) + + # same method as beta* but from user-defined variables as initial contour + flags = regions.make_region_flags(variable_dict, own_tresholds, flag_type="01") + treshold_coords = f.get_cell_coordinates(cellids[flags!=0]) + vtkSurface, SDF = regions.vtkDelaunay3d_SDF(query_points, treshold_coords, Delaunay_alpha) + + + elif method == "shue": + # note: might not be correct but should produce something, should recheck the projection coordinates + + theta = np.linspace(0, 2*np.pi/3 , 200) # magnetotail length decided here by trial and error: [0, 5*np.pi/6] ~ -350e6 m, [0, 2*np.pi/3] ~ -100e6 m in EGE + r, __, __ = shue.f_shue(theta, B_z = -10, n_p = 1, v_sw = 750) # for runs not in shue.py B_z, n_p, and v_sw need to be specified and run=None + + # 2d one-sided magnetopause + xs = r*np.cos(theta) + ys = r*np.sin(theta) + + # 3d projection for complete magnetopause + psis = np.linspace(0, 2*np.pi, 100) + coords = np.zeros((len(theta)*len(psis), 3)) + i=0 + for x,y in zip(xs,ys): + for psi in psis: + coords[i] = np.array([x, y*np.sin(psi), y*np.cos(psi)]) + i += 1 + + coords = coords*R_E + + # surface and SDF + np.random.shuffle(coords) # helps Delaunay triangulation + vtkSurface, SDF = regions.vtkDelaunay3d_SDF(query_points, coords) # alpha does nothing here + + + + else: + print("Magnetopause method not recognized. Use one of the options: \"beta_star\", \"beta_star_with_connectivity\", \"streamlines\", \"shue\", \"dict\"") + exit() + + if return_surface and return_SDF: + return vtkSurface, SDF + elif return_surface: + return vtkSurface, None + elif return_SDF: + return None, SDF + + # write the surface to a file + #writer = vtk.vtkXMLPolyDataWriter() + #writer.SetInputConnection(vtkSurface.GetOutputPort()) + #writer.SetFileName(outfilen) + #writer.Write() + #return vtkSurface, SDF + + + +def main(): + + + datafile = "/wrk-vakka/group/spacephysics/vlasiator/3D/FID/bulk1/bulk1.0001100.vlsv" + vtpoutfilen = "FID_magnetopause_BS_noalpha_t1100.vtp" + vlsvoutfilen = "FID_magnetopause_BS_noalpha_t1100.vlsv" + + + surface, SDF = magnetopause(datafile, + #method="streamlines", + method="beta_star_with_connectivity", + beta_star_range=[0.3, 0.4], + #Delaunay_alpha=2*R_E, + return_SDF=True, + return_surface=True) + + write_vtk_surface_to_file(surface, vtpoutfilen) + write_SDF_to_file(SDF, datafile, vlsvoutfilen) + +if __name__ == "__main__": + + main() From 0bf0cd1cec5cc7fab11d674cc19810c34ea53c24 Mon Sep 17 00:00:00 2001 From: jreimi Date: Tue, 26 Aug 2025 11:26:22 +0300 Subject: [PATCH 088/124] regions format changes draft --- scripts/regions.py | 300 +++++++++++++++++---------------------------- 1 file changed, 111 insertions(+), 189 deletions(-) diff --git a/scripts/regions.py b/scripts/regions.py index e81eefb6..a911bb2c 100644 --- a/scripts/regions.py +++ b/scripts/regions.py @@ -10,6 +10,7 @@ import analysator as pt import numpy as np import vtk +from scripts import magnetopause #from .analysator.scripts import shue @@ -18,16 +19,18 @@ ### Signed distance field functions: ### -def vtkDelaunay3d_SDF(all_points, coordinates): - """Gives a signed distance to a convex hull surface created from given coordinates. +def vtkDelaunay3d_SDF(query_points, coordinates, alpha=None): + """Gives a signed distance to a convex hull or alpha shape surface created from given coordinates. + Note: if using alpha, SDF most likely won't work! :param all_points: points ([x, y, z] coordinates in m) for which a signed distance to surface will be calculated :param coordinates: coordinates (array of [x, y, z]:s in m) that are used to make a surface. - :returns: array of signed distances, negative sign: inside the surface, positive sign: outside the surface + :kword alpha: alpha to be given to vtkDelaunay3D (e.g. R_E), removes surface edges that have length more than alpha so that the resulting surface is not convex. None -> convex hull + :returns: vtkDataSetSurfaceFilter() object, array of signed distances (negative sign: inside the surface, positive sign: outside the surface) """ - points =vtk.vtkPoints()#.NewInstance() + points = vtk.vtkPoints()#.NewInstance() for i in range(len(coordinates)): points.InsertNextPoint(coordinates[i,0],coordinates[i,1],coordinates[i,2]) polydata = vtk.vtkPolyData() @@ -37,6 +40,8 @@ def vtkDelaunay3d_SDF(all_points, coordinates): # Delaunay convex hull delaunay_3d = vtk.vtkDelaunay3D() delaunay_3d.SetInputData(polydata) + if alpha is not None: + delaunay_3d.SetAlpha(alpha) delaunay_3d.Update() @@ -49,13 +54,13 @@ def vtkDelaunay3d_SDF(all_points, coordinates): implicitPolyDataDistance = vtk.vtkImplicitPolyDataDistance() implicitPolyDataDistance.SetInput(surface.GetOutput()) - convexhull_sdf = np.zeros(len(all_points)) + convexhull_sdf = np.zeros(len(query_points)) # SDFs - for i,coord in enumerate(all_points): + for i,coord in enumerate(query_points): convexhull_sdf[i] = implicitPolyDataDistance.EvaluateFunction(coord) - return convexhull_sdf + return surface, convexhull_sdf @@ -69,7 +74,7 @@ def treshold_mask(data_array, value): :returns: 0/1 mask in same order as cellids, 1: variable value in array inside treshold values, 0: outside """ - if data_array is None or value is None: # either variable isn't usable or treshold has not been given + if (data_array is None) or (value is None) or (np.isnan(data_array[0])): # either variable isn't usable or treshold has not been given return None mask = np.zeros((len(data_array))) @@ -222,132 +227,39 @@ def bowshock_SDF(f, variable_dict, query_points, own_condition_dict=None): bowshock_conditions = {"density": (1.5*upstream_rho, 0.1)} bowshock_rho_flags = make_region_flags(variable_dict, bowshock_conditions, flag_type="01") - bowshock_rho_SDF = vtkDelaunay3d_SDF(query_points, f.get_cell_coordinates(cellids[bowshock_rho_flags!=0])) + __, bowshock_rho_SDF = vtkDelaunay3d_SDF(query_points, f.get_cell_coordinates(cellids[bowshock_rho_flags!=0])) return bowshock_rho_SDF else: # bowshock from user-defined variable and values - bowshock_rho_flags = make_region_flags(variable_dict, own_condition_dict, flag_type="01") - bowshock_rho_SDF = vtkDelaunay3d_SDF(query_points, f.get_cell_coordinates(cellids[bowshock_rho_flags!=0])) - dict_sdf = vtkDelaunay3d_SDF(query_points, f.get_cell_coordinates(cellids[bowshock_rho_flags!=0])) + bowshock_dict_flags = make_region_flags(variable_dict, own_condition_dict, flag_type="01") + __, dict_sdf = vtkDelaunay3d_SDF(query_points, f.get_cell_coordinates(cellids[bowshock_dict_flags!=0])) return dict_sdf -def magnetopause_SDF(f, datafile, vtpoutfile, variable_dict, query_points, method="beta_star_with_connectivity", own_variable_dict=None): # TODO: remove vlsvReader AND datafile name need (streamline magnetopause to only use f?) - """Finds the magnetopause by making a convex hull of either streamlines, beta*, or user-defined variables, and returns signed distances from surface to query_points. - Note: constructing the magnetopause using solar wind streamlines is slow and needs more memory than e.g. the beta*-method - - :param query_points: xyz-coordinates of all points where the SDF will be calculated ([x1, y1, z1], [x2, y2, z2], ...) - :kword method: str, specifies the method used to find magnetopause, options: "beta_star", "streamlines", "shue", "dict". If "dict" is used, own_variable_dict dictionary must be given. If "shue", run needs to be specified (TODO kwarg) - :kword own_variable_dict: when using "dict"-method, dictionary with string variable names as keys and treshold pairs as values to pass to treshold_mask()-function - """ - - if method != "streamlines": cellids =f.read_variable("CellID") - - if method == "streamlines": - - [xmin, ymin, zmin, xmax, ymax, zmax] = f.get_spatial_mesh_extent() - seeds_x0=150e6 - dl=5e5 - iters = int(((seeds_x0-xmin)/dl)+100) - sector_n = 36*2 - vertices, vtkSurface = pt.calculations.find_magnetopause_sw_streamline_3d(datafile, seeds_n=200, seeds_x0=seeds_x0, seeds_range=[-15*6371000, 15*6371000], - dl=dl, iterations=iters, end_x=xmin+15*6371000, x_point_n=100, sector_n=sector_n) - - # make the magnetopause surface from vertice points - np.random.shuffle(vertices) # helps Delaunay triangulation - Delaunay_SDF = vtkDelaunay3d_SDF(query_points, vertices) - - writer = vtk.vtkXMLPolyDataWriter() - writer.SetInputConnection(vtkSurface.GetOutputPort()) - writer.SetFileName(vtpoutfile) - writer.Write() - return Delaunay_SDF - - elif method == "beta_star": - # magnetopause from beta_star only - condition_dict = {"beta_star": [0.4, 0.5]} # FIC: [0.4, 0.5]) # EGE: [0.9, 1.0]) # max 0.6 in FHA to not take flyaways from outside magnetopause - mpause_flags = make_region_flags(variable_dict, condition_dict, flag_type="01") - contour_coords = f.get_cell_coordinates(cellids[mpause_flags!=0]) - - # make a convex hull surface with vtk's Delaunay - magnetopause_sdf = vtkDelaunay3d_SDF(query_points, contour_coords) - return magnetopause_sdf - - - elif method == "beta_star_with_connectivity": - # magnetopause from beta_star, with connectivity if possible - try: - connectivity_region = treshold_mask(variable_dict["connection"], 0) - betastar_region = treshold_mask(variable_dict["beta_star"], [0.6, 0.7]) - magnetosphere_proper = np.where((connectivity_region==1) | (betastar_region==1), 1, 0) - contour_coords = f.get_cell_coordinates(cellids[magnetosphere_proper==1]) - except: - print("using field line connectivity for magnetosphere did not work, using only beta*") - condition_dict = {"beta_star": [0.5, 0.6]} # FIC: [0.4, 0.5]) # EGE: [0.9, 1.0]) # max 0.6 in FHA to not take flyaways from outside magnetopause - mpause_flags = make_region_flags(variable_dict, condition_dict, flag_type="01") - contour_coords = f.get_cell_coordinates(cellids[mpause_flags!=0]) - - # make a convex hull surface with vtk's Delaunay - magnetopause_sdf = vtkDelaunay3d_SDF(query_points, contour_coords) - return magnetopause_sdf - - elif method == "dict": - # same method as beta* but from user-defined variables as initial contour - flags = make_region_flags(variable_dict, own_variable_dict, flag_type="01") - treshold_coords = f.get_cell_coordinates(cellids[flags!=0]) - dict_sdf = vtkDelaunay3d_SDF(query_points, treshold_coords) - return dict_sdf - - elif method == "shue": - return 0 - theta = np.linspace(0, 2*np.pi/3 , 200) # magnetotail length decided here by trial and error: [0, 5*np.pi/6] ~ -350e6 m, [0, 2*np.pi/3] ~ -100e6 m in EGE - r, __, __ = shue.f_shue(theta, run='EGI') # EGI~EGE, for runs not in shue.py B_z, n_p, and v_sw need to be specified and run=None - - # 2d one-sided magnetopause - xs = r*np.cos(theta) - ys = r*np.sin(theta) - - # 3d projection for complete magnetopause - psis = np.linspace(0, 2*np.pi, 100) - coords = np.zeros((len(theta)*len(psis), 3)) - i=0 - for x,y in zip(xs,ys): - for psi in psis: - coords[i] = np.array([x, y*np.sin(psi), y*np.cos(psi)]) - i += 1 - - coords = coords*R_E - - # surface and SDF - np.random.shuffle(coords) # helps Delaunay triangulation - shue_SDF = vtkDelaunay3d_SDF(query_points, coords) - - return shue_SDF - - else: - print("Magnetopause method not recognized. Use one of the options: \"beta_star\", \"beta_star_with_connectivity\", \"streamlines\", \"shue\", \"dict\"") - -def RegionFlags(datafile, outfilen, vtpoufile, ignore_boundaries=True, - magnetopause_method="beta_star", magnetopause_dict=None): +def RegionFlags(datafile, outfilen, regions=["all"], ignore_boundaries=True, magnetopause_kwargs={}): """Creates a sidecar .vlsv file with flagged cells for regions and boundaries in near-Earth plasma environment. Region flags (start with flag_, flags are fractions of filled conditions or 1/0): magnetosheath, magnetosphere, cusps, lobe_N, lobe_S, central_plasma_sheet, PSBL Boundary signed distance flags (start with SDF_, flags are signed distances to boundary in m with inside being negative distance): magnetopause, bowshock - :param datafile: vlsv file name (and path) - :param outdir: sidecar file save directory path - :param outfilen: sidecar file name + possilbe regions: "all", "boundaries" (magnetopause, bow shock), "large_areas" (boundaries + upstream, magnetosheath, magnetosphere), "magnetosphere", "bowshock", + "cusps", "lobes", "central_plasma_sheet", "boundary_layers" (incl. central plasma sheet BL, HLBL, LLBL; note: not reliable/working atm) + + Note that different runs may need different tresholds for region parameters and region accuracy should be verified visually + + :param datafile: .vlsv bulk file name (and path) + :param outfilen: sidecar .vlsv file name (and path) :kword ignore_boundaries: True: do not take cells in the inner/outer boundaries of the simulation into account when looking for regions - :kword magnetopause_method: default "beta_star", + :kword magnetopause_method: default "beta_star", other options: "beta_star_with_connectivity", "streamlines", "shue" """ f = pt.vlsvfile.VlsvReader(file_name=datafile) cellids =f.read_variable("CellID") [xmin, ymin, zmin, xmax, ymax, zmax] = f.get_spatial_mesh_extent() - all_points = f.get_cell_coordinates(cellids) #centre_points_of_cells(f) + all_points = f.get_cell_coordinates(cellids) cellIDdict = f.get_cellid_locations() @@ -385,18 +297,23 @@ def errormsg(varstr): print("{} could not be read, will be ignored".format(varst try: variables[varname] = f.read_variable(name=filevarname, cellids=-1) except: + variables[varname] = np.full((len(cellids)), np.nan) errormsg(filevarname) else: try: variables[varname] = f.read_variable(name=filevarname[0], cellids=-1, operator=filevarname[1]) except: + variables[varname] = np.full((len(cellids)), np.nan) errormsg(filevarname) - ## cellid testing - #testmesh = np.where(cellids < 10000, 1, 0) - #write_flags(writer, testmesh, 'testmesh') + #connectivity_region = treshold_mask(variables["connection"], 0) + #betastar_region = treshold_mask(variables["beta_star"], [0.0, 0.5]) + #magnetosphere_proper = np.where((connectivity_region==1) | (betastar_region==1), 1, 0) + #write_flags(writer, magnetosphere_proper, 'flag_magnetosphere') + #return 0 + # upstream point upstream_point = [xmax-10*R_E,0.0,0.0] @@ -404,32 +321,36 @@ def errormsg(varstr): print("{} could not be read, will be ignored".format(varst upstream_index = cellIDdict[upstream_cellid] ## MAGNETOPAUSE ## - magnetopause = magnetopause_SDF(f, datafile, vtpoufile, variables, all_points, method=magnetopause_method, own_variable_dict=magnetopause_dict) - write_flags(writer, magnetopause, 'SDF_magnetopause') - write_flags(writer, np.where(np.abs(magnetopause) < 5e6, 1, 0), "flag_magnetopause") + if magnetopause_kwargs: + __, magnetopause_SDF = magnetopause.magnetopause(datafile, **magnetopause_kwargs) + else: + __, magnetopause_SDF = magnetopause.magnetopause(datafile, method="beta_star_with_connectivity", Delaunay_alpha=None) # default magnetopause: beta*+ B connectivity convex hull + write_flags(writer, magnetopause_SDF, 'SDF_magnetopause') + write_flags(writer, np.where(np.abs(magnetopause_SDF) < 5e6, 1, 0), "flag_magnetopause") # save some magnetopause values for later - magnetopause_density = np.mean(variables["density"][np.abs(magnetopause) < 5e6]) + magnetopause_density = np.mean(variables["density"][np.abs(magnetopause_SDF) < 5e6]) print(f"{magnetopause_density=}") - magnetopause_temperature = np.mean(variables["temperature"][np.abs(magnetopause) < 5e6]) + magnetopause_temperature = np.mean(variables["temperature"][np.abs(magnetopause_SDF) < 5e6]) print(f"{magnetopause_temperature=}") ## MAGNETOSPHERE ## # magnetosphere from magentopause SDF - magnetosphere = np.where(magnetopause<0, 1, 0) + magnetosphere = np.where(magnetopause_SDF<0, 1, 0) write_flags(writer, magnetosphere, 'flag_magnetosphere_convex') # magnetospshere from beta* and field line connectivity if possible # TODO try: connectivity_region = treshold_mask(variables["connection"], 0) - betastar_region = treshold_mask(variables["beta_star"], [0.01, 0.5]) + betastar_region = treshold_mask(variables["beta_star"], [0.0, 0.5]) magnetosphere_proper = np.where((connectivity_region==1) | (betastar_region==1), 1, 0) write_flags(writer, magnetosphere_proper, 'flag_magnetosphere') except: - print("Non-convex magnetosphere could not be made") + print("Non-Delaunay beta* magnetosphere could not be made") - ## BOW SHOCK ## - # bow shock from rho + + ## BOW SHOCK ## #TODO: similar kwargs system as magnetopause? + # bow shock from rho bowshock = bowshock_SDF(f, variables, all_points) write_flags(writer, bowshock, 'SDF_bowshock') write_flags(writer, np.where(np.abs(bowshock) < 5e6, 1, 0), "flag_bowshock") @@ -470,73 +391,78 @@ def errormsg(varstr): print("{} could not be read, will be ignored".format(varst #print("upstream B:", variables["B_magnitude"][upstream_index]) # cusps - try: - cusp_conditions = {"density": [variables["density"][upstream_index], None], - #"beta_star": [0.1, None], - "connection": [0.0, 2.5], # either closed, or open-closed/closed-open - "B_magnitude":[2*variables["B_magnitude"][upstream_index], None], - "J_magnitude": [variables["J_magnitude"][upstream_index], None] - } - except: - cusp_conditions = {"density": [variables["density"][upstream_index], None], - #"beta_star": [0.1, None], - "connection": [0.0, 2.5], # either closed, or open-closed/closed-open - "B_magnitude":[2*variables["B_magnitude"][upstream_index], None] - } + cusp_conditions = {"density": [variables["density"][upstream_index], None], + #"beta_star": [0.1, None], + "connection": [0.0, 2.5], # either closed, or open-closed/closed-open + "B_magnitude":[2*variables["B_magnitude"][upstream_index], None], + "J_magnitude": [variables["J_magnitude"][upstream_index], None] + } cusp_flags = make_region_flags(variables, cusp_conditions, flag_type="fraction", mask=mask_inMagnetosphere) write_flags(writer, cusp_flags, 'flag_cusps', mask_inMagnetosphere) - + # magnetotail lobes - lobes_conditions = {"beta": [None, 0.1], # Koskinen: 0.003 - "connection": [0.5, 2.5], - "density": [None, variables["density"][upstream_index]], # Koskinen: 1e-8 - "temperature": [None, 3.5e6], # Koskinen: 3.5e6 K - } - lobes_flags = make_region_flags(variables, lobes_conditions, flag_type="fraction", mask=mask_inBowshock) - write_flags(writer, lobes_flags, 'flag_lobes', mask_inBowshock) - - # lobes slightly other way - lobe_N_conditions = {"beta": [None, 0.1], - "connection": [0.5, 2.5], - "B_x":[0, None], - "B_magnitude":[None, 10*variables["B_magnitude"][upstream_index]] - } - lobe_N_flags = make_region_flags(variables, lobe_N_conditions, flag_type="fraction", mask=mask_inBowshock) - write_flags(writer, lobe_N_flags, 'flag_lobe_N', mask_inBowshock) - - lobe_S_conditions = {"beta": [None, 0.1], - "connection": [0.5, 2.5], - "B_x":[None, 0], - "B_magnitude":[None, 10*variables["B_magnitude"][upstream_index]] - } - lobe_S_flags = make_region_flags(variables, lobe_S_conditions, flag_type="fraction", mask=mask_inBowshock) - write_flags(writer, lobe_S_flags, 'flag_lobe_S', mask_inBowshock) + def lobes(): + lobes_conditions = {"beta": [None, 0.1], # Koskinen: 0.003 + "connection": [0.5, 2.5], + "density": [None, variables["density"][upstream_index]], # Koskinen: 1e-8 + "temperature": [None, 3.5e6], # Koskinen: 3.5e6 K + } + lobes_flags = make_region_flags(variables, lobes_conditions, flag_type="fraction") + + + # lobes slightly other way + lobe_N_conditions = {"beta": [None, 0.1], + "connection": [0.5, 2.5], + "B_x":[0, None], + "B_magnitude":[None, 10*variables["B_magnitude"][upstream_index]] + } + lobe_N_flags = make_region_flags(variables, lobe_N_conditions, flag_type="fraction") + + + lobe_S_conditions = {"beta": [None, 0.1], + "connection": [0.5, 2.5], + "B_x":[None, 0], + "B_magnitude":[None, 10*variables["B_magnitude"][upstream_index]] + } + lobe_S_flags = make_region_flags(variables, lobe_S_conditions, flag_type="fraction") + + + return lobes_flags, lobe_N_flags, lobe_S_flags + + if "all" in regions or "lobes" in regions: + mask = mask_inBowshock + lobes_flags, N_lobe_flags, S_lobe_flags = lobes() + write_flags(writer, lobes_flags, 'flag_lobes') + write_flags(writer, N_lobe_flags, 'flag_lobe_N') + write_flags(writer, S_lobe_flags, 'flag_lobe_S') + # lobe density from median densities? lobes_mask = np.where((lobes_flags > 0.9), 1, 0).astype(bool) - lobes_allcells = mask_inBowshock.astype(float) - lobes_allcells[mask_inBowshock] = lobes_mask - lobe_density = np.mean(variables["density"][lobes_allcells>0.9]) + #lobes_allcells = mask_inBowshock.astype(float) + #lobes_allcells[mask_inBowshock] = lobes_mask + lobe_density = np.mean(variables["density"][lobes_mask])#[lobes_allcells>0.9]) print(f"{lobe_density=}") # Central plasma sheet - central_plasma_sheet_conditions = {#"density": [None, variables["density"][upstream_index]], - "density": [None, 1e6], # Wolf intro - #"density" : [1e7, None], # Koskinen: 3e-7 - #"density": [lobe_density, None], - "beta": [1.0, None], # Koskinen: 6 - #"connection": 0, # only closed-closed - #"temperature": [2*variables["temperature"][upstream_index], None]#, - "temperature": [2e6, None], # 5e7 from Koskinen - #"B_magnitude":[10*variables["B_magnitude"][upstream_index], None] - #"J_magnitude": [2*variables["J_magnitude"][upstream_index], None], - "J_magnitude": [1e-9, None], - } + def CPS(): + central_plasma_sheet_conditions = {"density": [None, 1e6], # Wolf intro ?should not be + #"density" : [1e7, None], # Koskinen: 3e-7 + #"density": [lobe_density, None], + "beta": [1.0, None], # Koskinen: 6 + #"connection": 0, # only closed-closed + "temperature": [2e6, None], # 5e7 from Koskinen + "J_magnitude": [1e-9, None], + } + + central_plasma_sheet_flags = make_region_flags(variables, central_plasma_sheet_conditions,flag_type="fraction", mask=mask_inMagnetosphere) + return central_plasma_sheet_flags - central_plasma_sheet_flags = make_region_flags(variables, central_plasma_sheet_conditions,flag_type="fraction", mask=mask_inMagnetosphere) - write_flags(writer, central_plasma_sheet_flags, 'flag_central_plasma_sheet', mask_inMagnetosphere) + if "all" in regions or "central_plasma_sheet" in regions: + CPS_flags = CPS() + write_flags(writer, CPS_flags, 'flag_central_plasma_sheet', mask_inMagnetosphere) # Plasma sheet boundary layer (PSBL) @@ -563,8 +489,6 @@ def errormsg(varstr): print("{} could not be read, will be ignored".format(varst - - # High-Latitude boundary layer (HLBL) HLBL_conditions = { "density": [magnetopause_density, magnetosheath_density], "temperature": [magnetosheath_temperature, magnetopause_temperature], @@ -580,13 +504,11 @@ def errormsg(varstr): print("{} could not be read, will be ignored".format(varst def main(): - datafile = "/wrk-vakka/group/spacephysics/vlasiator/3D/FHA/bulk1/bulk1.0001400.vlsv" - outfilen = "FHA_regions_t0001400.vlsv" - vtp_outfilen = "FHA_regions_t0001400.vtp" + datafile = "/wrk-vakka/group/spacephysics/vlasiator/3D/EGE/bulk/bulk.0002000.vlsv" + outfilen = "EGE_regions_t2000.vlsv" - for infile, outfile, vtp_outfile in zip(datafile, outfilen, vtp_outfilen): - RegionFlags(infile, outfile, vtp_outfile, magnetopause_method="streamlines") + RegionFlags(datafile, outfilen, regions=["all"], magnetopause_kwargs={"method":"beta_star_with_connectivity", "beta_star_range":[0.3, 0.4]}) if __name__ == "__main__": From 338daaaeecdcba6e66d2ffa9a87f104c8d61f229 Mon Sep 17 00:00:00 2001 From: jreimi Date: Tue, 26 Aug 2025 14:46:26 +0300 Subject: [PATCH 089/124] non-fix to surface making and added outline to how to get beta* + connectivity magnetosphere to magnetopause script --- .../magnetopause_sw_streamline_3d.py | 45 ++++++++++++++++--- scripts/magnetopause.py | 26 +++++------ 2 files changed, 50 insertions(+), 21 deletions(-) diff --git a/analysator/pyCalculations/magnetopause_sw_streamline_3d.py b/analysator/pyCalculations/magnetopause_sw_streamline_3d.py index 27d44df4..abbc2ea5 100644 --- a/analysator/pyCalculations/magnetopause_sw_streamline_3d.py +++ b/analysator/pyCalculations/magnetopause_sw_streamline_3d.py @@ -142,6 +142,37 @@ def make_surface(coords): next_triangles = [x + slices_in_plane*area_index for x in first_triangles] faces.extend(next_triangles) + #### test area #### + # From last triangles remove every other triangle + # (a single subsolar point -> last triangles are actual triangles instead of rectangles sliced in two) + #removed = 0 + #for i in range(len(faces)-slices_in_plane*2, len(faces)): + # if i%2!=0: + # faces.pop(i-removed) + # removed += 1 + + # From last triangles remove every other triangle + # (a single subsolar point -> last triangles are actual triangles instead of rectangles sliced in two) + # Also fix the last triangles so that they only point to one subsolar point and have normals towards outside + #subsolar_index = int(len(verts)-slices_in_plane) + + #for i,triangle in enumerate(reversed(faces)): + # if i > (slices_in_plane): # faces not in last plane (we're going backwards) + # break + + # faces[len(faces)-i-1] = np.clip(triangle, a_min=0, a_max=subsolar_index) + + # this would remove duplicate subsolar points from vertices but makes 2d slicing harder + #verts = verts[:int(len(verts)-slices_in_plane+1)] + + # Change every other face triangle (except for last slice triangles) normal direction so that all face the same way (hopefully) + #faces = np.array(faces) + #for i in range(len(faces)-int(slices_in_plane)): + # if i%2!=0: + # faces[i,1], faces[i,2] = faces[i,2], faces[i,1] + + ################### + # Change every other face triangle normal direction so that all face the same way faces = np.array(faces) for i in range(len(faces)): @@ -236,7 +267,7 @@ def make_streamlines(vlsvfile, streamline_seeds=None, seeds_n=25, seeds_x0=20*63 return streams -def make_magnetopause(streams, end_x=-15*6371000, x_point_n=50, sector_n=36): +def make_magnetopause(streams, end_x=-15*6371000, x_point_n=50, sector_n=36, ignore=0): """Finds the mangetopause location based on streamlines. :param streams: streamlines (coordinates in m) @@ -250,7 +281,6 @@ def make_magnetopause(streams, end_x=-15*6371000, x_point_n=50, sector_n=36): """ RE = 6371000 - ignore = 0 ## if given sector number isn't divisible by 4, make it so because we want to have magnetopause points at exactly y=0 and z=0 for 2d slices of the whole thing while sector_n%4 != 0: @@ -276,7 +306,7 @@ def make_magnetopause(streams, end_x=-15*6371000, x_point_n=50, sector_n=36): dayside_points = streampoints[streampoints[:,0] > 0] phi_slices = sector_n - theta_slices = 10 + theta_slices = dayside_x_point_n phi_step = 2*np.pi/phi_slices theta_step = np.pi/(2*theta_slices) # positive x-axis only @@ -398,7 +428,7 @@ def grid_mid_point(theta_idx, phi_idx): return magnetopause -def find_magnetopause_sw_streamline_3d(vlsvfile, streamline_seeds=None, seeds_n=25, seeds_x0=20*6371000, seeds_range=[-5*6371000, 5*6371000], dl=2e6, iterations=200, end_x=-15*6371000, x_point_n=50, sector_n=36): +def find_magnetopause_sw_streamline_3d(vlsvfile, streamline_seeds=None, seeds_n=25, seeds_x0=20*6371000, seeds_range=[-5*6371000, 5*6371000], dl=2e6, iterations=200, end_x=-15*6371000, x_point_n=50, sector_n=36, ignore=0): """Finds the magnetopause position by tracing streamlines of the velocity field. :param vlsvfile: path to .vlsv bulk file to use for VlsvReader @@ -409,14 +439,15 @@ def find_magnetopause_sw_streamline_3d(vlsvfile, streamline_seeds=None, seeds_n= :kword dl: streamline iteration step length in m :kword iterations: int, number of iteration steps :kword end_x: tail end x-coordinate (how far along the x-axis the magnetopause is calculated) - :kword x_point_n: integer, how many x-axis points the magnetopause will be divided in between the subsolar point and tail - :kword sector_n: integer, how many sectors the magnetopause will be divided in on each yz-plane + :kword x_point_n: integer, how many parts the magnetopause will be divided in between the subsolar point and tail end + :kword sector_n: integer, how many sectors the magnetopause will be divided in on each yz-plane/radial sector + :kword ignore: how many inner streamlines will be ignored when calculating the magnetopause :returns: vertices, surface where vertices are numpy arrays in shape [[x0,y0,z0], [x1,y1,z1],...] and surface is a vtk vtkDataSetSurfaceFilter object """ streams = make_streamlines(vlsvfile, streamline_seeds, seeds_n, seeds_x0, seeds_range, dl, iterations) - magnetopause = make_magnetopause(streams, end_x, x_point_n, sector_n) + magnetopause = make_magnetopause(streams, end_x, x_point_n, sector_n, ignore) vertices, faces = make_surface(magnetopause) surface = make_vtk_surface(vertices, faces) diff --git a/scripts/magnetopause.py b/scripts/magnetopause.py index 2af4b7ae..3a4f9a12 100644 --- a/scripts/magnetopause.py +++ b/scripts/magnetopause.py @@ -37,7 +37,7 @@ def magnetopause(datafilen, method="beta_star_with_connectivity", own_tresholds= :kword return_surface: True/False, return vtkDataSetSurfaceFilter object :kword return_SDF: True/False, return array of distances in m to SDF_points in point input order, negative distance inside the surface :kword SDF_points: optionally give array of own points to calculate signed distances to. If not given, distances will be to cell centres in the order of f.read_variable("CellID") output - :kword Delaunay_alpha: alpha (float) to give to vtkDelaunay3d, None -> convex hull, alpha=__: surface egdes longer than __ will be excluded (-> concave hull) + :kword Delaunay_alpha: alpha (float) to give to vtkDelaunay3d, None -> convex hull, alpha=__: surface egdes longer than __ will be excluded (won't be a proper surface, SDF won't work) :kword beta_star_range: [min, max] treshold rage to use with methods "beta_star" and "beta_star_with_connectivity" :returns: vtkDataSetSurfaceFilter object of convex hull or alpha shape if return_surface=True, signed distance field of convex hull or alpha shape of magnetopause if return_SDF=True """ @@ -56,11 +56,10 @@ def magnetopause(datafilen, method="beta_star_with_connectivity", own_tresholds= dl=5e5 iters = int(((seeds_x0-xmin)/dl)+100) sector_n = 36*4 - vertices, manual_vtkSurface = pt.calculations.find_magnetopause_sw_streamline_3d(datafilen, seeds_n=200, seeds_x0=seeds_x0, seeds_range=[-5*6371000, 5*6371000], + vertices, manual_vtkSurface = pt.calculations.find_magnetopause_sw_streamline_3d(datafilen, seeds_n=300, seeds_x0=seeds_x0, seeds_range=[-5*6371000, 5*6371000], dl=dl, iterations=iters, end_x=xmin+10*6371000, x_point_n=200, sector_n=sector_n) - # good parameters example: seeds_x0=150e6, dl=5e5, iters = int(((seeds_x0-xmin)/dl)+100), sector_n = 36*3, seeds_n=100, seeds_range=[-5*6371000, 5*6371000], end_x=xmin+10*6371000, x_point_n=200 - - write_vtk_surface_to_file(manual_vtkSurface, "/wrk-vakka/users/jreimi/magnetosphere_classification/FID/FID_magnetopause_SW_manual_t1100.vtp") + + write_vtk_surface_to_file(manual_vtkSurface, "/wrk-vakka/users/jreimi/magnetosphere_classification/FID/FID_magnetopause_SWnf_manual_t1100.vtp") # make the magnetopause surface from vertice points np.random.shuffle(vertices) # helps Delaunay triangulation vtkSurface, SDF = regions.vtkDelaunay3d_SDF(query_points, vertices, Delaunay_alpha) @@ -141,13 +140,12 @@ def magnetopause(datafilen, method="beta_star_with_connectivity", own_tresholds= return vtkSurface, None elif return_SDF: return None, SDF + + # beta* + B connectivity: + #connectivity_region = regions.treshold_mask(f.read_variable("vg_connection"), 0) + #betastar_region = regions.treshold_mask(f.read_variable("beta_star"), [0.0, 0.5]) + #magnetosphere_proper = np.where((connectivity_region==1) | (betastar_region==1), 1, 0) - # write the surface to a file - #writer = vtk.vtkXMLPolyDataWriter() - #writer.SetInputConnection(vtkSurface.GetOutputPort()) - #writer.SetFileName(outfilen) - #writer.Write() - #return vtkSurface, SDF @@ -155,15 +153,15 @@ def main(): datafile = "/wrk-vakka/group/spacephysics/vlasiator/3D/FID/bulk1/bulk1.0001100.vlsv" - vtpoutfilen = "FID_magnetopause_BS_noalpha_t1100.vtp" - vlsvoutfilen = "FID_magnetopause_BS_noalpha_t1100.vlsv" + vtpoutfilen = "FID_magnetopause_BS_t1100.vtp" + vlsvoutfilen = "FID_magnetopause_BS_t1100.vlsv" surface, SDF = magnetopause(datafile, #method="streamlines", method="beta_star_with_connectivity", beta_star_range=[0.3, 0.4], - #Delaunay_alpha=2*R_E, + Delaunay_alpha=1*R_E, return_SDF=True, return_surface=True) From 9ac7574cc31fc08986a8bf3f374b7ba70a62a9e7 Mon Sep 17 00:00:00 2001 From: jreimi Date: Tue, 26 Aug 2025 14:56:27 +0300 Subject: [PATCH 090/124] removed extra vtp save that shouldn't be there yet --- scripts/magnetopause.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/magnetopause.py b/scripts/magnetopause.py index 3a4f9a12..92220821 100644 --- a/scripts/magnetopause.py +++ b/scripts/magnetopause.py @@ -59,7 +59,6 @@ def magnetopause(datafilen, method="beta_star_with_connectivity", own_tresholds= vertices, manual_vtkSurface = pt.calculations.find_magnetopause_sw_streamline_3d(datafilen, seeds_n=300, seeds_x0=seeds_x0, seeds_range=[-5*6371000, 5*6371000], dl=dl, iterations=iters, end_x=xmin+10*6371000, x_point_n=200, sector_n=sector_n) - write_vtk_surface_to_file(manual_vtkSurface, "/wrk-vakka/users/jreimi/magnetosphere_classification/FID/FID_magnetopause_SWnf_manual_t1100.vtp") # make the magnetopause surface from vertice points np.random.shuffle(vertices) # helps Delaunay triangulation vtkSurface, SDF = regions.vtkDelaunay3d_SDF(query_points, vertices, Delaunay_alpha) From 91e8d83ad8f124d5fd6a861eadec552851ccafaf Mon Sep 17 00:00:00 2001 From: jreimi Date: Wed, 27 Aug 2025 15:33:24 +0300 Subject: [PATCH 091/124] Better optional arguments for regions --- scripts/regions.py | 298 ++++++++++++++++++++++----------------------- 1 file changed, 145 insertions(+), 153 deletions(-) diff --git a/scripts/regions.py b/scripts/regions.py index a911bb2c..a7e05f92 100644 --- a/scripts/regions.py +++ b/scripts/regions.py @@ -1,24 +1,14 @@ """Script and functions for creating sidecar files with SDF/region/boundary tags of plasma regions. - variables used to find regions/boundary regions: - rho, temperature, beta, beta_star, - - - """ import analysator as pt import numpy as np import vtk from scripts import magnetopause -#from .analysator.scripts import shue - R_E = 6371000 -### Signed distance field functions: ### - - def vtkDelaunay3d_SDF(query_points, coordinates, alpha=None): """Gives a signed distance to a convex hull or alpha shape surface created from given coordinates. Note: if using alpha, SDF most likely won't work! @@ -42,6 +32,10 @@ def vtkDelaunay3d_SDF(query_points, coordinates, alpha=None): delaunay_3d.SetInputData(polydata) if alpha is not None: delaunay_3d.SetAlpha(alpha) + delaunay_3d.SetAlphaTets(1) + delaunay_3d.SetAlphaLines(0) + delaunay_3d.SetAlphaTris(0) + delaunay_3d.SetAlphaVerts(0) delaunay_3d.Update() @@ -71,13 +65,13 @@ def treshold_mask(data_array, value): :param data_array: array to mask, e.g. output of f.read_variable(name="proton/vg_rho", cellids=-1) :param variable: str, variable name :param value: value/values to use for masking; a float or int for exact match, a (value, relative tolerance) tuple, or [min value, max value] list pair where either can be None for less than or eq./more than or eq. value - :returns: 0/1 mask in same order as cellids, 1: variable value in array inside treshold values, 0: outside + :returns: 0/1 mask in same order as data_array, 1: variable value in array inside treshold values, 0: outside """ if (data_array is None) or (value is None) or (np.isnan(data_array[0])): # either variable isn't usable or treshold has not been given return None - mask = np.zeros((len(data_array))) + #mask = np.zeros((len(data_array))) if isinstance(value, float) or isinstance(value, int): # single value, exact mask = np.where(np.isclose(data_array, value), 1, 0) @@ -238,23 +232,40 @@ def bowshock_SDF(f, variable_dict, query_points, own_condition_dict=None): -def RegionFlags(datafile, outfilen, regions=["all"], ignore_boundaries=True, magnetopause_kwargs={}): - +def RegionFlags(datafile, outfilen, regions=["all"], ignore_boundaries=True, region_flag_type="01", magnetopause_kwargs={}, region_conditions={}): """Creates a sidecar .vlsv file with flagged cells for regions and boundaries in near-Earth plasma environment. - Region flags (start with flag_, flags are fractions of filled conditions or 1/0): magnetosheath, magnetosphere, cusps, lobe_N, lobe_S, central_plasma_sheet, PSBL + Region flags (start with flag_, flags are fractions of filled conditions or 1/0): magnetosheath, magnetosphere, cusps, lobe_N, lobe_S, central_plasma_sheet Boundary signed distance flags (start with SDF_, flags are signed distances to boundary in m with inside being negative distance): magnetopause, bowshock possilbe regions: "all", "boundaries" (magnetopause, bow shock), "large_areas" (boundaries + upstream, magnetosheath, magnetosphere), "magnetosphere", "bowshock", - "cusps", "lobes", "central_plasma_sheet", "boundary_layers" (incl. central plasma sheet BL, HLBL, LLBL; note: not reliable/working atm) + "cusps", "lobes", "central_plasma_sheet" - Note that different runs may need different tresholds for region parameters and region accuracy should be verified visually + Note that different runs may need different tresholds for region parameters and region accuracy should be verified visually + Beta* convex hull is most likely the best magnetopause method for regions, for just magnetopause with different options use magnetopause.py - :param datafile: .vlsv bulk file name (and path) - :param outfilen: sidecar .vlsv file name (and path) - :kword ignore_boundaries: True: do not take cells in the inner/outer boundaries of the simulation into account when looking for regions - :kword magnetopause_method: default "beta_star", other options: "beta_star_with_connectivity", "streamlines", "shue" + :param datafile: .vlsv bulk file name (and path) + :param outfilen: sidecar .vlsv file name (and path) + :kword ignore_boundaries: True: do not take cells in the inner/outer boundaries of the simulation into account when looking for regions (applicable for cusps and CPS for now) + :kword region_flag_type: "01" or "fraction", whether flags are binary (all conditions must be satisfied) or fractions of how many given conditions are met + :kword magnetopause_method: default "beta_star", other options: see magnetopause.py, use alpha=None + :kword region_conditions: optional dict where keys are str region names and values are condition dictionaries, for setting own conditions for bow shock, cusps, lobes or central plasma sheet """ + if "all" in regions: + regions.extend(["magnetopause", "bowshock", "upstream", "magnetosheath", "magnetosphere", "cusps", "lobes", "central_plasma_sheet"]) + else: + if "large_areas" in regions: + regions.extend(["magnetopause", "bowshock", "upstream", "magnetosheath", "magnetosphere"]) + if "boundaries" in regions: + regions.extend(["magnetopause", "bowshock"]) + if "cusps" in regions or "central_plasma_sheet" in regions: # for cusps and central plasma sheet we need the magnetoshpere + regions.extend(["magnetosphere"]) + if "magnetosphere" in regions or "magnetosheath" in regions: + regions.extend(["magnetopause"]) + if "bowshock" in regions or "magnetosheath" in regions or "upstream" in regions: + regions.extend(["bowshock"]) + + f = pt.vlsvfile.VlsvReader(file_name=datafile) cellids =f.read_variable("CellID") @@ -262,12 +273,10 @@ def RegionFlags(datafile, outfilen, regions=["all"], ignore_boundaries=True, mag all_points = f.get_cell_coordinates(cellids) cellIDdict = f.get_cellid_locations() - - ## writer ## writer = pt.vlsvfile.VlsvWriter(f, outfilen) writer.copy_variables_list(f, ["CellID"]) - writer.copy_variables(f, varlist=["proton/vg_rho" , "vg_beta_star", "vg_temperature", "vg_b_vol", "vg_J", "vg_beta", "vg_connection", "vg_boundarytype"]) + #writer.copy_variables(f, varlist=["proton/vg_rho" , "vg_beta_star", "vg_temperature", "vg_b_vol", "vg_J", "vg_beta", "vg_connection", "vg_boundarytype"]) # variable data dictionary @@ -315,157 +324,153 @@ def errormsg(varstr): print("{} could not be read, will be ignored".format(varst #return 0 - # upstream point + # upstream point # this could probably be done better than just a random point in sw upstream_point = [xmax-10*R_E,0.0,0.0] upstream_cellid = f.get_cellid(upstream_point) upstream_index = cellIDdict[upstream_cellid] ## MAGNETOPAUSE ## - if magnetopause_kwargs: - __, magnetopause_SDF = magnetopause.magnetopause(datafile, **magnetopause_kwargs) - else: - __, magnetopause_SDF = magnetopause.magnetopause(datafile, method="beta_star_with_connectivity", Delaunay_alpha=None) # default magnetopause: beta*+ B connectivity convex hull - write_flags(writer, magnetopause_SDF, 'SDF_magnetopause') - write_flags(writer, np.where(np.abs(magnetopause_SDF) < 5e6, 1, 0), "flag_magnetopause") - - # save some magnetopause values for later - magnetopause_density = np.mean(variables["density"][np.abs(magnetopause_SDF) < 5e6]) - print(f"{magnetopause_density=}") - magnetopause_temperature = np.mean(variables["temperature"][np.abs(magnetopause_SDF) < 5e6]) - print(f"{magnetopause_temperature=}") + if "magnetopause" in regions: + if magnetopause_kwargs: + __, magnetopause_SDF = magnetopause.magnetopause(datafile, **magnetopause_kwargs) + else: + __, magnetopause_SDF = magnetopause.magnetopause(datafile, method="beta_star_with_connectivity", Delaunay_alpha=None) # default magnetopause: beta*+ B connectivity convex hull + write_flags(writer, magnetopause_SDF, 'SDF_magnetopause') + write_flags(writer, np.where(np.abs(magnetopause_SDF) < 5e6, 1, 0), "flag_magnetopause") + + # save some magnetopause values for later + magnetopause_density = np.mean(variables["density"][np.abs(magnetopause_SDF) < 5e6]) + print(f"{magnetopause_density=}") + magnetopause_temperature = np.mean(variables["temperature"][np.abs(magnetopause_SDF) < 5e6]) + print(f"{magnetopause_temperature=}") ## MAGNETOSPHERE ## # magnetosphere from magentopause SDF - magnetosphere = np.where(magnetopause_SDF<0, 1, 0) - write_flags(writer, magnetosphere, 'flag_magnetosphere_convex') - - # magnetospshere from beta* and field line connectivity if possible # TODO - try: - connectivity_region = treshold_mask(variables["connection"], 0) - betastar_region = treshold_mask(variables["beta_star"], [0.0, 0.5]) - magnetosphere_proper = np.where((connectivity_region==1) | (betastar_region==1), 1, 0) - write_flags(writer, magnetosphere_proper, 'flag_magnetosphere') - except: - print("Non-Delaunay beta* magnetosphere could not be made") + if "magnetosphere" in regions: + magnetosphere = np.where(magnetopause_SDF<0, 1, 0) + write_flags(writer, magnetosphere, 'flag_magnetosphere') ## BOW SHOCK ## #TODO: similar kwargs system as magnetopause? # bow shock from rho - bowshock = bowshock_SDF(f, variables, all_points) - write_flags(writer, bowshock, 'SDF_bowshock') - write_flags(writer, np.where(np.abs(bowshock) < 5e6, 1, 0), "flag_bowshock") + if "bowshock" in regions: + if "bowshock" in region_conditions: + bowshock = bowshock_SDF(f, variables, all_points, own_condition_dict=region_conditions["bowshock"]) + else: + bowshock = bowshock_SDF(f, variables, all_points) # default upstream rho method, might fail with foreshock + write_flags(writer, bowshock, 'SDF_bowshock') + write_flags(writer, np.where(np.abs(bowshock) < 5e6, 1, 0), "flag_bowshock") - # magnetosphere+magnetosheath -area - inside_bowshock = np.where(bowshock<0, 1, 0) + # magnetosphere+magnetosheath -area + inside_bowshock = np.where(bowshock<0, 1, 0) ## MAGNETOSHEATH ## - # magnetosheath from bow shock-magnetosphere difference - magnetosheath_flags = np.where((inside_bowshock & 1-magnetosphere), 1, 0) - write_flags(writer, magnetosheath_flags, 'flag_magnetosheath') + if "magnetosheath" in regions: + # magnetosheath from bow shock-magnetosphere difference + magnetosheath_flags = np.where((inside_bowshock & 1-magnetosphere), 1, 0) + write_flags(writer, magnetosheath_flags, 'flag_magnetosheath') - # save magentosheath density and temperature for further use - magnetosheath_density = np.mean(variables["density"][magnetosheath_flags == 1]) - print(f"{magnetosheath_density=}") - magnetosheath_temperature = np.mean(variables["temperature"][magnetosheath_flags == 1]) - print(f"{magnetosheath_temperature=}") + # save magentosheath density and temperature for further use + #magnetosheath_density = np.mean(variables["density"][magnetosheath_flags == 1]) + #print(f"{magnetosheath_density=}") + #magnetosheath_temperature = np.mean(variables["temperature"][magnetosheath_flags == 1]) + #print(f"{magnetosheath_temperature=}") ## UPSTREAM ## - # upstream from !bowshock - write_flags(writer, 1-inside_bowshock, 'flag_upstream') - - write_flags(writer, inside_bowshock, 'flag_inside_bowshock') + if "upstream" in regions: + # upstream from !bowshock + write_flags(writer, 1-inside_bowshock, 'flag_upstream') + #write_flags(writer, inside_bowshock, 'flag_inside_bowshock') ## INNER MAGNETOSPHERE REGIONS ## - if ignore_boundaries: - noBoundaries = np.where(f.read_variable(name=boundaryname, cellids=-1) == 1, 1, 0) # boundarytype 1: not a boundary - mask_inBowshock= np.where(((inside_bowshock == 1) & (noBoundaries == 1)), 1, 0).astype(bool) # only search inner regions from inside the magnetosheath and magnetosphere - mask_inMagnetosphere = np.where(((magnetosphere == 1) & (noBoundaries == 1)), 1, 0).astype(bool) # - - else: - mask_inBowshock = inside_bowshock.astype(bool) # only search inner regions from inside the magnetosheath and magnetosphere - mask_inMagnetosphere = magnetosphere.astype(bool) # + if "magnetosphere" in regions: + if ignore_boundaries: + noBoundaries = np.where(f.read_variable(name=boundaryname, cellids=-1) == 1, 1, 0) # boundarytype 1: not a boundary + #mask_inBowshock= np.where(((inside_bowshock == 1) & (noBoundaries == 1)), 1, 0).astype(bool) # only search inner regions from inside the magnetosheath and magnetosphere + mask_inMagnetosphere = np.where(((magnetosphere == 1) & (noBoundaries == 1)), 1, 0).astype(bool) # + else: + #mask_inBowshock = inside_bowshock.astype(bool) # only search inner regions from inside the magnetosheath and magnetosphere + mask_inMagnetosphere = magnetosphere.astype(bool) # #print("upstream B:", variables["B_magnitude"][upstream_index]) # cusps - cusp_conditions = {"density": [variables["density"][upstream_index], None], - #"beta_star": [0.1, None], - "connection": [0.0, 2.5], # either closed, or open-closed/closed-open - "B_magnitude":[2*variables["B_magnitude"][upstream_index], None], - "J_magnitude": [variables["J_magnitude"][upstream_index], None] - } + if "cusps" in regions: + if "cusps" in region_conditions: + cusp_conditions = region_conditions["cusps"] + else: + cusp_conditions = {"density": [variables["density"][upstream_index], None], + #"beta_star": [0.1, None], + "connection": [0.0, 2.5], # either closed, or open-closed/closed-open + "B_magnitude":[2*variables["B_magnitude"][upstream_index], None], + "J_magnitude": [variables["J_magnitude"][upstream_index], None] + } - cusp_flags = make_region_flags(variables, cusp_conditions, flag_type="fraction", mask=mask_inMagnetosphere) - write_flags(writer, cusp_flags, 'flag_cusps', mask_inMagnetosphere) + cusp_flags = make_region_flags(variables, cusp_conditions, flag_type=region_flag_type, mask=mask_inMagnetosphere) + write_flags(writer, cusp_flags, 'flag_cusps', mask_inMagnetosphere) # magnetotail lobes - def lobes(): - lobes_conditions = {"beta": [None, 0.1], # Koskinen: 0.003 - "connection": [0.5, 2.5], - "density": [None, variables["density"][upstream_index]], # Koskinen: 1e-8 - "temperature": [None, 3.5e6], # Koskinen: 3.5e6 K - } - lobes_flags = make_region_flags(variables, lobes_conditions, flag_type="fraction") - - - # lobes slightly other way - lobe_N_conditions = {"beta": [None, 0.1], - "connection": [0.5, 2.5], - "B_x":[0, None], - "B_magnitude":[None, 10*variables["B_magnitude"][upstream_index]] - } - lobe_N_flags = make_region_flags(variables, lobe_N_conditions, flag_type="fraction") - - - lobe_S_conditions = {"beta": [None, 0.1], - "connection": [0.5, 2.5], - "B_x":[None, 0], - "B_magnitude":[None, 10*variables["B_magnitude"][upstream_index]] - } - lobe_S_flags = make_region_flags(variables, lobe_S_conditions, flag_type="fraction") - - - return lobes_flags, lobe_N_flags, lobe_S_flags - - if "all" in regions or "lobes" in regions: - mask = mask_inBowshock - lobes_flags, N_lobe_flags, S_lobe_flags = lobes() + if "lobes" in regions: + if "lobes" in region_conditions: + lobes_conditions = region_conditions["lobes"] + else: + lobes_conditions = {"beta": [None, 0.1], # Koskinen: 0.003 + "connection": [0.5, 2.5], + "density": [None, variables["density"][upstream_index]], # Koskinen: 1e-8 + "temperature": [None, 3.5e6], # Koskinen: 3.5e6 K + } + + # lobes slightly other way + lobe_N_conditions = {"beta": [None, 0.1], + "connection": [0.5, 2.5], + "B_x":[0, None], + "B_magnitude":[None, 10*variables["B_magnitude"][upstream_index]] + } + lobe_N_flags = make_region_flags(variables, lobe_N_conditions, flag_type=region_flag_type) + + + lobe_S_conditions = {"beta": [None, 0.1], + "connection": [0.5, 2.5], + "B_x":[None, 0], + "B_magnitude":[None, 10*variables["B_magnitude"][upstream_index]] + } + lobe_S_flags = make_region_flags(variables, lobe_S_conditions, flag_type=region_flag_type) + + write_flags(writer, lobe_N_flags, 'flag_lobe_N') + write_flags(writer, lobe_S_flags, 'flag_lobe_S') + + lobes_flags = make_region_flags(variables, lobes_conditions, flag_type=region_flag_type) write_flags(writer, lobes_flags, 'flag_lobes') - write_flags(writer, N_lobe_flags, 'flag_lobe_N') - write_flags(writer, S_lobe_flags, 'flag_lobe_S') - # lobe density from median densities? - lobes_mask = np.where((lobes_flags > 0.9), 1, 0).astype(bool) - #lobes_allcells = mask_inBowshock.astype(float) - #lobes_allcells[mask_inBowshock] = lobes_mask - lobe_density = np.mean(variables["density"][lobes_mask])#[lobes_allcells>0.9]) - print(f"{lobe_density=}") + # lobe density from median densities? + #lobes_mask = np.where((lobes_flags > 0.9), 1, 0).astype(bool) + #lobe_density = np.mean(variables["density"][lobes_mask])#[lobes_allcells>0.9]) + #print(f"{lobe_density=}") # Central plasma sheet - def CPS(): - central_plasma_sheet_conditions = {"density": [None, 1e6], # Wolf intro ?should not be - #"density" : [1e7, None], # Koskinen: 3e-7 - #"density": [lobe_density, None], + if "central_plasma_sheet" in regions: + if "central_plasma_sheet" in region_conditions: + central_plasma_sheet_conditions = region_conditions["central_plasma_sheet"] + else: + central_plasma_sheet_conditions = {"density": [None, 1e6], # Wolf intro ?should not be but seems to work "beta": [1.0, None], # Koskinen: 6 - #"connection": 0, # only closed-closed "temperature": [2e6, None], # 5e7 from Koskinen "J_magnitude": [1e-9, None], } - central_plasma_sheet_flags = make_region_flags(variables, central_plasma_sheet_conditions,flag_type="fraction", mask=mask_inMagnetosphere) - return central_plasma_sheet_flags - - if "all" in regions or "central_plasma_sheet" in regions: - CPS_flags = CPS() - write_flags(writer, CPS_flags, 'flag_central_plasma_sheet', mask_inMagnetosphere) + central_plasma_sheet_flags = make_region_flags(variables, central_plasma_sheet_conditions,flag_type=region_flag_type, mask=mask_inMagnetosphere) + write_flags(writer, central_plasma_sheet_flags, 'flag_central_plasma_sheet', mask_inMagnetosphere) + + ## Other boundary layers, PSBL sometimes works # Plasma sheet boundary layer (PSBL) + # if "PSBL" in regions or "all" in regions: #PSBL_conditions = {#"density": (1e7, 1.0), # Koskinen: 1e5 # "density": [None, 1e7], #"temperature": [0.5e6, 1e6],#(1e7, 2.0), # from Koskinen @@ -476,36 +481,23 @@ def CPS(): # } - #PSBL_flags = make_region_flags(variables, PSBL_conditions,flag_type="fraction", mask=mask_inMagnetosphere) - #write_flags(writer, PSBL_flags, "flag_PSBL", mask_inMagnetosphere) - # Low-Latitude boundary layer (LLBL) - #LLBL_conditions = {"density": [magnetopause_density, magnetosheath_density], - # "temperature": [magnetosheath_temperature, magnetopause_temperature] - # } - #LLBL_flags = make_region_flags(variables, LLBL_conditions,flag_type="fraction", mask=mask_inBowshock) - #write_flags(writer, LLBL_flags, "flag_LLBL", mask_inBowshock) +def main(): + #datafile = "/wrk-vakka/group/spacephysics/vlasiator/3D/FHA/bulk1/bulk1.0001400.vlsv" + #outfilen = "FHA_regions_t0001400.vlsv" + #datafile = "/wrk-vakka/group/spacephysics/vlasiator/3D/EGE/bulk/bulk.0002000.vlsv" + #outfilen = "/wrk-vakka/users/jreimi/magnetosphere_classification/Results/EGE_regions_t2000.vlsv" - # High-Latitude boundary layer (HLBL) - HLBL_conditions = { "density": [magnetopause_density, magnetosheath_density], - "temperature": [magnetosheath_temperature, magnetopause_temperature], - "beta": [1.0, None], - "connection": 0, - "J_magnitude": [2*variables["J_magnitude"][upstream_index], None], - } - - HLBL_flags = make_region_flags(variables, HLBL_conditions,flag_type="fraction", mask=mask_inBowshock) - write_flags(writer, HLBL_flags, "flag_HLBL", mask_inBowshock) + datafile = "/wrk-vakka/group/spacephysics/vlasiator/3D/FID/bulk1/bulk1.0001100.vlsv" + outfilen = "/wrk-vakka/users/jreimi/magnetosphere_classification/FID/FID_magnetopause_BSC_t1100.vlsv" + #datafile = "/wrk-vakka/group/spacephysics/vlasiator/3D/EGE/bulk/bulk.0002000.vlsv" + #outfilen = "EGE_regions_t2000.vlsv" -def main(): - - datafile = "/wrk-vakka/group/spacephysics/vlasiator/3D/EGE/bulk/bulk.0002000.vlsv" - outfilen = "EGE_regions_t2000.vlsv" RegionFlags(datafile, outfilen, regions=["all"], magnetopause_kwargs={"method":"beta_star_with_connectivity", "beta_star_range":[0.3, 0.4]}) From 59e1dcaaff485b83018868d1cf9e3b2085d259eb Mon Sep 17 00:00:00 2001 From: jreimi Date: Wed, 27 Aug 2025 15:48:12 +0300 Subject: [PATCH 092/124] removed main function and moved usage example to docstring --- scripts/regions.py | 34 +++++++++------------------------- 1 file changed, 9 insertions(+), 25 deletions(-) diff --git a/scripts/regions.py b/scripts/regions.py index a7e05f92..d15fca62 100644 --- a/scripts/regions.py +++ b/scripts/regions.py @@ -1,5 +1,14 @@ """Script and functions for creating sidecar files with SDF/region/boundary tags of plasma regions. + Usage example where bow shock conditions are given to replace default conditions: + + .. code-block:: python + + datafile = "/wrk-vakka/group/spacephysics/vlasiator/3D/EGE/bulk/bulk.0002000.vlsv" + outfilen = "EGE_regions_t2000.vlsv" + RegionFlags(datafile, outfilen, regions=["all"], + region_conditions = {"bowshock": {"density": [2e6, None]}} + """ import analysator as pt @@ -481,28 +490,3 @@ def errormsg(varstr): print("{} could not be read, will be ignored".format(varst # } - - -def main(): - - #datafile = "/wrk-vakka/group/spacephysics/vlasiator/3D/FHA/bulk1/bulk1.0001400.vlsv" - #outfilen = "FHA_regions_t0001400.vlsv" - - #datafile = "/wrk-vakka/group/spacephysics/vlasiator/3D/EGE/bulk/bulk.0002000.vlsv" - #outfilen = "/wrk-vakka/users/jreimi/magnetosphere_classification/Results/EGE_regions_t2000.vlsv" - - datafile = "/wrk-vakka/group/spacephysics/vlasiator/3D/FID/bulk1/bulk1.0001100.vlsv" - outfilen = "/wrk-vakka/users/jreimi/magnetosphere_classification/FID/FID_magnetopause_BSC_t1100.vlsv" - - #datafile = "/wrk-vakka/group/spacephysics/vlasiator/3D/EGE/bulk/bulk.0002000.vlsv" - #outfilen = "EGE_regions_t2000.vlsv" - - - - - RegionFlags(datafile, outfilen, regions=["all"], magnetopause_kwargs={"method":"beta_star_with_connectivity", "beta_star_range":[0.3, 0.4]}) - - -if __name__ == "__main__": - - main() From 381c2562e815884e563751131bd9de1394cacd20 Mon Sep 17 00:00:00 2001 From: jreimi Date: Wed, 27 Aug 2025 16:12:07 +0300 Subject: [PATCH 093/124] Sphinx updates: bowshock and scripts toctree --- .../sphinx/magnetosphere_regions.rst | 73 ++++++++++++++++--- Documentation/sphinx/scripts.rst | 20 +++++ 2 files changed, 84 insertions(+), 9 deletions(-) diff --git a/Documentation/sphinx/magnetosphere_regions.rst b/Documentation/sphinx/magnetosphere_regions.rst index 1693d545..76ff2d4a 100644 --- a/Documentation/sphinx/magnetosphere_regions.rst +++ b/Documentation/sphinx/magnetosphere_regions.rst @@ -1,11 +1,40 @@ -Magnetosphere regions: How to find -================================== +Magnetosphere regions and bow shock: How to find +================================================ +Bow shock +--------- -Cusps ------ +Plasma properties for estimating bow shock position: +* plasma compression: + * :math:`n_p > 2n_{p, sw}` [Battarbee_et_al_2020]_ (Vlasiator) +* solar wind core heating: + * :math:`T_{core} > 4T_{sw}` [Battarbee_et_al_2020]_ (Vlasiator) + * :math:`T_{core} = 3T_{sw}` [Suni_et_al_2021]_ (Vlasiator) +* magnetosonic Mach number: + * :math:`M_{ms} < 1` [Battarbee_et_al_2020]_ (Vlasiator) + + + +Magnetosheath +------------- + +properties: + +* density: + * :math:`8 cm^{-3}` [Hudges_Introduction_to_space_physics_Ch_9]_ +* temperature: + * ion: :math:`150 eV` [Hudges_Introduction_to_space_physics_Ch_9]_ + * electron: :math:`25 eV` [Hudges_Introduction_to_space_physics_Ch_9]_ +* magnetic field: + * :math:`15 nT` [Hudges_Introduction_to_space_physics_Ch_9]_ +* plasma :math:`\beta`: + * 2.5 [Hudges_Introduction_to_space_physics_Ch_9]_ + + +Polar cusps +----------- *Properties:* @@ -20,6 +49,15 @@ Cusps **In analysator:** +*regions.py* in *scripts* has an option to find cusps using convex hull of the magnetosphere. +Usage example: + +.. code-block:: [python] + + datafile = "vlsvbulkfile.vlsv" + outfilen = "cusps.vlsv" + RegionFlags(datafile, outfilen, regions=["cusps"]) + Tail lobes ---------- @@ -42,14 +80,16 @@ Tail lobes Separated from the plasma sheet by the plasma sheet boundary layer (PSBL) -**in analysator:** +**In analysator:** -regions.py +*regions.py* in *scripts* has an option to find tail lobes. +Usage example: -conditions: +.. code-block:: [python] -* inside the magnetosphere -* plasma :math:`\beta` .... + datafile = "vlsvbulkfile.vlsv" + outfilen = "lobes.vlsv" + RegionFlags(datafile, outfilen, regions=["lobes"]) @@ -124,11 +164,26 @@ Central plasma sheet Inner plasma sheet: unusually low plasma beta may exist (e.g., cold tenuous plasma near the neutral sheet after long periods of northward IMF) [Boakes_et_al_2014]_, (Cluster spacecraft data) +**In analysator:** + +*regions.py* in *scripts* has an option to find tail lobes. +Usage example: + +.. code-block:: [python] + + datafile = "vlsvbulkfile.vlsv" + outfilen = "CPS.vlsv" + RegionFlags(datafile, outfilen, regions=["central_plasma_sheet"]) + + + ------------ References +.. [Battarbee_et_al_2020] Battarbee, M., Ganse, U., Pfau-Kempf, Y., Turc, L., Brito, T., Grandin, M., Koskela, T., and Palmroth, M.: Non-locality of Earth's quasi-parallel bow shock: injection of thermal protons in a hybrid-Vlasov simulation, Ann. Geophys., 38, 625-643, https://doi.org/10.5194/angeo-38-625-2020, 2020 +.. [Suni_et_al_2021] Suni, J., Palmroth, M., Turc, L., Battarbee, M., Johlander, A., Tarvus, V., et al. (2021). Connection between foreshock structures and the generation of magnetosheath jets: Vlasiator results. Geophysical Research Letters, 48, e2021GL095655. https://doi. org/10.1029/2021GL095655 .. [Grison_et_al_2025] Grison, B., Darrouzet, F., Maggiolo, R. et al. Localization of the Cluster satellites in the geospace environment. Sci Data 12, 327 (2025). https://doi.org/10.1038/s41597-025-04639-z .. [Koskinen_Johdatus] Koskinen, H. E. J. (2011). Johdatus plasmafysiikkaan ja sen avaruussovellutuksiin. Limes ry. .. [Koskinen_Space_Storms] Koskinen, H. E. J. (2011). Physics of Space Storms: From the Solar Surface to the Earth. Springer-Verlag. https://doi.org/10.1007/978-3-642-00319-6 diff --git a/Documentation/sphinx/scripts.rst b/Documentation/sphinx/scripts.rst index 1cfa7e01..bb0d05be 100644 --- a/Documentation/sphinx/scripts.rst +++ b/Documentation/sphinx/scripts.rst @@ -84,6 +84,24 @@ tsyganenko ------------ +magnetopause +------------ +:doc:`magnetopause` + +.. automodule:: magnetopause + :no-index: + +------------ + +regions +------- +:doc:`magnetosphere_regions` + +.. automodule:: regions + :no-index: + +------------ + .. toctree:: :maxdepth: 2 :caption: Scripts: @@ -97,4 +115,6 @@ tsyganenko obliqueshock_nif shue tsyganenko + magnetopause + magnetosphere_regions From c6559d9417f88739d4e7d5b6657923007bbb52f3 Mon Sep 17 00:00:00 2001 From: jreimi Date: Wed, 27 Aug 2025 16:27:43 +0300 Subject: [PATCH 094/124] removed bowshock after moving contents to regions doc before --- Documentation/sphinx/bowshock.rst | 69 ------------------------------- 1 file changed, 69 deletions(-) delete mode 100644 Documentation/sphinx/bowshock.rst diff --git a/Documentation/sphinx/bowshock.rst b/Documentation/sphinx/bowshock.rst deleted file mode 100644 index 365d9dc4..00000000 --- a/Documentation/sphinx/bowshock.rst +++ /dev/null @@ -1,69 +0,0 @@ -Bow shock: How to find -====================== - - -Plasma properties for estimating bow shock position: ----------------------------------------------------- - -* plasma compression - * :math:`n_p > 2n_{p, sw}` [Battarbee_et_al_2020]_ (Vlasiator) -* solar wind core heating - * :math:`T_{core} > 4T_{sw}` [Battarbee_et_al_2020]_ (Vlasiator) - * :math:`T_{core} = 3T_{sw}` [Suni_et_al_2021]_ (Vlasiator) -* magnetosonic Mach number - * :math:`M_{ms} < 1` [Battarbee_et_al_2020]_ (Vlasiator) - - - - -In analysator: --------------- - -regions.py: - -*method*: - -Convex hull from cells where :math:`n_p > 1.5 n_{p, sw}` -criteria - -also velocity? - - -Foreshock: ----------- - -*properties:* - -* larger fluctuations in the magnetic field, plasma velocity and plasma density than unperturbed solar wind [Grison_et_al_2025]_ - - -Magnetosheath -------------- - -properties: - -* density: - * :math:`8 cm^{-3}` [Hudges_Introduction_to_space_physics_Ch_9]_ -* temperature: - * ion: :math:`150 eV` [Hudges_Introduction_to_space_physics_Ch_9]_ - * electron: :math:`25 eV` [Hudges_Introduction_to_space_physics_Ch_9]_ -* magnetic field: - * :math:`15 nT` [Hudges_Introduction_to_space_physics_Ch_9]_ -* plasma :math:`\beta`: - * 2.5 [Hudges_Introduction_to_space_physics_Ch_9]_ - - -Regions inside the bow shock: ------------------------------ - -* magnetosheath: area inside bow shock but outside the magnetopause, see ... -* magnetosphere: area inside magnetopause, see again ... and ... for magnetosphere regions - - ------------- - -References: - -.. [Battarbee_et_al_2020] Battarbee, M., Ganse, U., Pfau-Kempf, Y., Turc, L., Brito, T., Grandin, M., Koskela, T., and Palmroth, M.: Non-locality of Earth's quasi-parallel bow shock: injection of thermal protons in a hybrid-Vlasov simulation, Ann. Geophys., 38, 625-643, https://doi.org/10.5194/angeo-38-625-2020, 2020 -.. [Suni_et_al_2021] Suni, J., Palmroth, M., Turc, L., Battarbee, M., Johlander, A., Tarvus, V., et al. (2021). Connection between foreshock structures and the generation of magnetosheath jets: Vlasiator results. Geophysical Research Letters, 48, e2021GL095655. https://doi. org/10.1029/2021GL095655 -.. [Grison_et_al_2025] Grison, B., Darrouzet, F., Maggiolo, R. et al. Localization of the Cluster satellites in the geospace environment. Sci Data 12, 327 (2025). https://doi.org/10.1038/s41597-025-04639-z From 0b659c49df730217db5eb8a2e7d67b1e3b9dc405 Mon Sep 17 00:00:00 2001 From: jreimi Date: Wed, 27 Aug 2025 16:56:26 +0300 Subject: [PATCH 095/124] sphinx documentation fixes --- .../sphinx/magnetosphere_regions.rst | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/Documentation/sphinx/magnetosphere_regions.rst b/Documentation/sphinx/magnetosphere_regions.rst index 76ff2d4a..b99ffac4 100644 --- a/Documentation/sphinx/magnetosphere_regions.rst +++ b/Documentation/sphinx/magnetosphere_regions.rst @@ -16,6 +16,17 @@ Plasma properties for estimating bow shock position: * :math:`M_{ms} < 1` [Battarbee_et_al_2020]_ (Vlasiator) +**In analysator:** + +*regions.py* in *scripts* has an option to find the bow shock. Default method uses 1.5*solar wind density as limit. +Usage example: + +.. code-block:: python + + datafile = "vlsvbulkfile.vlsv" + outfilen = "bowshock.vlsv" + RegionFlags(datafile, outfilen, regions=["bowshock"]) + Magnetosheath ------------- @@ -32,6 +43,17 @@ properties: * plasma :math:`\beta`: * 2.5 [Hudges_Introduction_to_space_physics_Ch_9]_ +**In analysator:** + +*regions.py* in *scripts* has an option to find the magnetosheath using bow shock and magnetopause: +Usage example: + +.. code-block:: python + + datafile = "vlsvbulkfile.vlsv" + outfilen = "magnetosheath.vlsv" + RegionFlags(datafile, outfilen, regions=["magnetosheath"]) + Polar cusps ----------- @@ -52,7 +74,7 @@ Polar cusps *regions.py* in *scripts* has an option to find cusps using convex hull of the magnetosphere. Usage example: -.. code-block:: [python] +.. code-block:: python datafile = "vlsvbulkfile.vlsv" outfilen = "cusps.vlsv" @@ -85,7 +107,7 @@ Separated from the plasma sheet by the plasma sheet boundary layer (PSBL) *regions.py* in *scripts* has an option to find tail lobes. Usage example: -.. code-block:: [python] +.. code-block:: python datafile = "vlsvbulkfile.vlsv" outfilen = "lobes.vlsv" @@ -166,10 +188,10 @@ Inner plasma sheet: unusually low plasma beta may exist (e.g., cold tenuous plas **In analysator:** -*regions.py* in *scripts* has an option to find tail lobes. +*regions.py* in *scripts* has an option to find the central plasma sheet. Usage example: -.. code-block:: [python] +.. code-block:: python datafile = "vlsvbulkfile.vlsv" outfilen = "CPS.vlsv" @@ -191,6 +213,5 @@ References .. [Coxon_et_al_2016] Coxon,J.C.,C.M.Jackman, M. P. Freeman, C. Forsyth, and I. J. Rae (2016), Identifying the magnetotail lobes with Cluster magnetometer data, J. Geophys. Res. Space Physics, 121, 1436–1446, doi:10.1002/2015JA022020. .. [Hudges_Introduction_to_space_physics_Ch_9] Hudges, W. J. (1995) The magnetopause, magnetotail and magnetic reconnection. In Kivelson, M. G., & Russell, C. T. (Eds.), Introduction to space physics (pp.227-287). Cambridge University Press. .. [Wolf_Introduction_to_space_physics_Ch_10] Wolf, R. A. (1995) Magnetospheric configuration. In Kivelson, M. G., & Russell, C. T. (Eds.), Introduction to space physics (pp.288-329). Cambridge University Press. -.. [Sckopke_et_al_1981] Sckopke, N., Paschmann, G., Haerendel, G., Sonnerup, B. U. , Bame, S. J., Forbes, T. G., Hones Jr., E. W., and Russell, C. T. (1981). Structure of the low-latitude boundary layer. Journal of Geophysical Research: Space Physics, 86(A4):2099–2110. eprint: https://agupubs.onlinelibrary.wiley.com/doi/pdf/10.1029/JA086iA04p02099 .. [Boakes_et_al_2014] Boakes, P. D., Nakamura, R., Volwerk, M., and Milan, S. E. (2014). ECLAT Cluster Spacecraft Magnetotail Plasma Region Identifications (2001–2009). Dataset Papers in Science, 2014(1):684305. eprint: https://onlinelibrary.wiley.com/doi/pdf/10.1155/2014/684305 .. [Stenuit_et_al_2001] Stenuit, H., Sauvaud, J.-A., Delcourt, D. C., Mukai, T., Kokubun, S., Fujimoto, M., Buzulukova, N. Y., Kovrazhkin, R. A., Lin, R. P., and Lepping, R. P. (2001). A study of ion injections at the dawn and dusk polar edges of the auroral oval. Journal of Geophysical Research: Space Physics, 106(A12):29619–29631. eprint: https://agupubs.onlinelibrary.wiley.com/doi/pdf/10.1029/2001JA900060. From 099de07393c6e1ac9a7922a1d95131f33f8b7ea0 Mon Sep 17 00:00:00 2001 From: jreimi Date: Thu, 28 Aug 2025 14:16:11 +0300 Subject: [PATCH 096/124] something wrong eith te beta* stop condition here --- .../magnetopause_sw_streamline_3d.py | 17 +++++-- scripts/magnetopause.py | 49 +++++++++++++------ 2 files changed, 49 insertions(+), 17 deletions(-) diff --git a/analysator/pyCalculations/magnetopause_sw_streamline_3d.py b/analysator/pyCalculations/magnetopause_sw_streamline_3d.py index abbc2ea5..ad329f6d 100644 --- a/analysator/pyCalculations/magnetopause_sw_streamline_3d.py +++ b/analysator/pyCalculations/magnetopause_sw_streamline_3d.py @@ -222,11 +222,13 @@ def streamline_stopping_condition(vlsvReader, points, value): x = points[:, 0] y = points[:, 1] z = points[:, 2] - return (x < xmin)|(x > xmax) | (y < ymin)|(y > ymax) | (z < zmin)|(z > zmax)|(value[:,0] > 0) + beta_star = vlsvReader.read_interpolated_variable("vg_beta_star", points) + return (x < xmin)|(x > xmax) | (y < ymin)|(y > ymax) | (z < zmin)|(z > zmax)|(value[:,0] > 0)| (beta_star < 0.4) def make_streamlines(vlsvfile, streamline_seeds=None, seeds_n=25, seeds_x0=20*6371000, seeds_range=[-5*6371000, 5*6371000], dl=2e6, iterations=200): """Traces streamlines of velocity field from outside the magnetosphere to magnetotail. + Stopping condition for when streamlines turn sunwards or go out of box :param vlsvfile: directory and file name of .vlsv data file to use for VlsvReader @@ -314,11 +316,16 @@ def cartesian_to_spherical_grid(cartesian_coords): # Only for x > 0 r, theta, phi = cartesian_to_spherical(cartesian_coords) theta_idx = int(theta/theta_step) - phi_idx = int(phi/phi_step) + #phi_idx = int(phi/phi_step) # off by half grid cell in relation to x<0 grid + if (phi (0-phi_step*0.5)): + phi_idx = 0 + else: + phi_idx = round(phi/phi_step) + return [theta_idx, phi_idx, r] def grid_mid_point(theta_idx, phi_idx): - return (theta_idx+0.5)*theta_step, (phi_idx+0.5)*phi_step + return (theta_idx+0.5)*theta_step, (phi_idx)*phi_step # make a dictionary based on spherical areas by theta index and phi index sph_points = {} @@ -431,6 +438,10 @@ def grid_mid_point(theta_idx, phi_idx): def find_magnetopause_sw_streamline_3d(vlsvfile, streamline_seeds=None, seeds_n=25, seeds_x0=20*6371000, seeds_range=[-5*6371000, 5*6371000], dl=2e6, iterations=200, end_x=-15*6371000, x_point_n=50, sector_n=36, ignore=0): """Finds the magnetopause position by tracing streamlines of the velocity field. + Note: there may be a slight jump at x=0. This may be due to difference in methods (pointcloud vs. interpolation). + If the subsolar point area looks off then more streamlines near the x-axis are needed. + + :param vlsvfile: path to .vlsv bulk file to use for VlsvReader :kword streamline_seeds: optional streamline starting points in numpy array :kword seeds_n: instead of streamline_seeds provide a number of streamlines to be traced diff --git a/scripts/magnetopause.py b/scripts/magnetopause.py index 92220821..03dd14b4 100644 --- a/scripts/magnetopause.py +++ b/scripts/magnetopause.py @@ -28,22 +28,22 @@ def write_SDF_to_file(SDF, datafilen, outfilen): -def magnetopause(datafilen, method="beta_star_with_connectivity", own_tresholds=None, return_surface=True, return_SDF=True, SDF_points=None, Delaunay_alpha=None, beta_star_range=[0.1, 1.0]): # TODO: separate streamline suface and vtkDelaunay3d surface in streamline method +def magnetopause(datafilen, method="beta_star_with_connectivity", own_tresholds=None, return_surface=True, return_SDF=True, SDF_points=None, Delaunay_alpha=None, beta_star_range=[0.4, 0.5]): # TODO: separate streamline suface and vtkDelaunay3d surface in streamline method """Finds the magnetopause using the specified method. Surface is constructed using vtk's Delaunay3d triangulation which results in a convex hull if no Delaunay_alpha is given. - + Returns vtk.vtkDataSetSurfaceFilter object and/or signed distances (negative -> inside mangetopause) (=SDF) to all cells + Note that using alpha for Delaunay might make SDF not so accurate inside the magnetosphere, especially if surface is constructed with points not everywhere in the magnetosphere (e.g. beta* 0.4-0.5) + :param datafilen: a .vlsv bulk file name (and path) :kword method: str, default "beta_star_with_connectivity", other options "beta_star", "streamlines", "shue", "dict" :kword own_tresholds: if using method "dict", a dictionary with conditions for the magnetopause/magnetosphere must be given where key is data name in datafile and value is treshold used (see treshold_mask()) :kword return_surface: True/False, return vtkDataSetSurfaceFilter object :kword return_SDF: True/False, return array of distances in m to SDF_points in point input order, negative distance inside the surface :kword SDF_points: optionally give array of own points to calculate signed distances to. If not given, distances will be to cell centres in the order of f.read_variable("CellID") output - :kword Delaunay_alpha: alpha (float) to give to vtkDelaunay3d, None -> convex hull, alpha=__: surface egdes longer than __ will be excluded (won't be a proper surface, SDF won't work) + :kword Delaunay_alpha: alpha (float) to give to vtkDelaunay3d, None -> convex hull, alpha=__: surface egdes longer than __ will be excluded :kword beta_star_range: [min, max] treshold rage to use with methods "beta_star" and "beta_star_with_connectivity" :returns: vtkDataSetSurfaceFilter object of convex hull or alpha shape if return_surface=True, signed distance field of convex hull or alpha shape of magnetopause if return_SDF=True """ - - start_t = time.time() f = pt.vlsvfile.VlsvReader(file_name=datafilen) cellids = f.read_variable("CellID") [xmin, ymin, zmin, xmax, ymax, zmax] = f.get_spatial_mesh_extent() @@ -51,14 +51,13 @@ def magnetopause(datafilen, method="beta_star_with_connectivity", own_tresholds= if method == "streamlines": - [xmin, ymin, zmin, xmax, ymax, zmax] = f.get_spatial_mesh_extent() seeds_x0=150e6 dl=5e5 iters = int(((seeds_x0-xmin)/dl)+100) - sector_n = 36*4 + sector_n = 36*3 vertices, manual_vtkSurface = pt.calculations.find_magnetopause_sw_streamline_3d(datafilen, seeds_n=300, seeds_x0=seeds_x0, seeds_range=[-5*6371000, 5*6371000], dl=dl, iterations=iters, end_x=xmin+10*6371000, x_point_n=200, sector_n=sector_n) - + # make the magnetopause surface from vertice points np.random.shuffle(vertices) # helps Delaunay triangulation vtkSurface, SDF = regions.vtkDelaunay3d_SDF(query_points, vertices, Delaunay_alpha) @@ -81,6 +80,7 @@ def magnetopause(datafilen, method="beta_star_with_connectivity", own_tresholds= connectivity_region = regions.treshold_mask(f.read_variable("vg_connection"), 0) # closed-closed magnetic field lines magnetosphere_proper = np.where((connectivity_region==1) | (betastar_region==1), 1, 0) contour_coords = f.get_cell_coordinates(cellids[magnetosphere_proper==1]) + np.save("pointcloud.npy", contour_coords) except: print("using field line connectivity for magnetosphere did not work, using only beta*") #condition_dict = {"beta_star": [0.5, 0.6]} # FIC: [0.4, 0.5]) # EGE: [0.9, 1.0]) # max 0.6 in FHA to not take flyaways from outside magnetopause @@ -90,6 +90,25 @@ def magnetopause(datafilen, method="beta_star_with_connectivity", own_tresholds= # make a convex hull surface with vtk's Delaunay vtkSurface, SDF = regions.vtkDelaunay3d_SDF(query_points, contour_coords, Delaunay_alpha) + #elif method == "beta_star_with_fieldlines": # either incredibly slow or does not work, don't use without fixing #TODO proprer measure of actual field line backwall point + # # magnetopause from beta_star, with field lines connecting to back wall if possible + # betastar_region = regions.treshold_mask(f.read_variable("vg_beta_star"), beta_star_range) + # try: + # fw_region = regions.treshold_mask(f.read_variable("vg_connection_coordinates_fw")[:,0], [None, xmin+5e7]) + # bw_region = regions.treshold_mask(f.read_variable("vg_connection_coordinates_bw")[:,0], [None, xmin+5e7]) + # #connectivity_region = regions.treshold_mask(f.read_variable("vg_connection"), 0) # closed-closed magnetic field lines + # magnetosphere_proper = np.where(((fw_region==1) | (betastar_region==1) | (bw_region==1)), 1, 0) + # contour_coords = f.get_cell_coordinates(cellids[magnetosphere_proper==1]) + # except: + # print("using field lines for magnetosphere did not work, using only beta*") + # #condition_dict = {"beta_star": [0.5, 0.6]} # FIC: [0.4, 0.5]) # EGE: [0.9, 1.0]) # max 0.6 in FHA to not take flyaways from outside magnetopause + # mpause_flags = np.where(betastar_region==1, 1, 0) + # contour_coords = f.get_cell_coordinates(cellids[mpause_flags!=0]) + # + # # make a convex hull surface with vtk's Delaunay + # vtkSurface, SDF = regions.vtkDelaunay3d_SDF(query_points, contour_coords, Delaunay_alpha) + + elif method == "dict": variable_dict = {} @@ -151,15 +170,17 @@ def magnetopause(datafilen, method="beta_star_with_connectivity", own_tresholds= def main(): - datafile = "/wrk-vakka/group/spacephysics/vlasiator/3D/FID/bulk1/bulk1.0001100.vlsv" - vtpoutfilen = "FID_magnetopause_BS_t1100.vtp" - vlsvoutfilen = "FID_magnetopause_BS_t1100.vlsv" + datafile = "/wrk-vakka/group/spacephysics/vlasiator/3D/EGE/bulk/bulk.0002000.vlsv" + vtpoutfilen = "EGE_magnetopause_SWtest_t2000.vtp" + vlsvoutfilen = "EGE_magnetopause_SWtest_t2000.vlsv" + + surface, SDF = magnetopause(datafile, - #method="streamlines", - method="beta_star_with_connectivity", - beta_star_range=[0.3, 0.4], + method="streamlines", + #method="beta_star_with_connectivity", + #beta_star_range=[0.0, 0.4], Delaunay_alpha=1*R_E, return_SDF=True, return_surface=True) From b3675d1cf4d0958d06dc6da731f42e285c880db2 Mon Sep 17 00:00:00 2001 From: jreimi Date: Thu, 28 Aug 2025 14:56:23 +0300 Subject: [PATCH 097/124] added automodules for regions and magnetopause documentations, some references still broken --- Documentation/sphinx/magnetopause.rst | 26 ++++++++++--------- .../sphinx/magnetosphere_regions.rst | 4 +++ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/Documentation/sphinx/magnetopause.rst b/Documentation/sphinx/magnetopause.rst index 7bf6f54b..0df6cdcb 100644 --- a/Documentation/sphinx/magnetopause.rst +++ b/Documentation/sphinx/magnetopause.rst @@ -20,11 +20,9 @@ Caveats: magnetotail current sheet has beta* :math:`>` 1 **in analysator:** -datareducer: beta_star, vg_beta_star - -regions.py: cells with beta* value within a certain treshold are chosen, and a convex hull is constructed with vtk to represent the magnetopause. -Ideal values of beta* for magnetopause construction might be run-dependent, and the surface construction works best with a small-ish range of beta* but still big enough to gather cells evenly from all sides. +*magnetopause.py* in *scripts*: see :func:`magnetopause.magnetopause` +datareducer: beta_star, vg_beta_star .. [Xu_et_al_2016] Xu, S., M. W. Liemohn, C. Dong, D. L. Mitchell, S. W. Bougher, and Y. Ma (2016), Pressure and ion composition boundaries at Mars, J. Geophys. Res. Space Physics, 121, 6417–6429, doi:10.1002/2016JA022644. .. [Brenner_et_al_2021] Brenner A, Pulkkinen TI, Al Shidi Q and Toth G (2021) Stormtime Energetics: Energy Transport Across the Magnetopause in a Global MHD Simulation. Front. Astron. Space Sci. 8:756732. doi: 10.3389/fspas.2021.756732 @@ -34,7 +32,6 @@ Ideal values of beta* for magnetopause construction might be run-dependent, and Field line connectivity ----------------------- -Magnetic field lines that are closed at least from one end ... @@ -51,8 +48,9 @@ Caveats: sometimes some streamlines can curve into the magnetotail or dayside ma **In analysator:** -magnetopause_sw_streamline_2d.py -magnetopause_sw_streamline_3d.py +see +:mod:`calculations.magnetopause_sw_streamline_2d.py` +:mod:`calculations.magnetopause_sw_streamline_3d.py` Streamlines are traced from outside the bow shock towards Earth. A subsolar point for the magnetopause is chosen to be where streamlines get closest to Earth in x-axis [y/z~0]. @@ -62,7 +60,7 @@ From the Earth towards negative x the space is divided into yz-planes. Each yz-plane is then divided into 2d sectors and magnetopause is marked to be in the middle of the sector with the radius of the n:th closest streamline to the x-axis. -For subsolar point, radial dayside and -x planes the closest streamline point can be changed to be n:th closest by setting keyword *ignore*, in which case *ignore* closest streamline points are not taken into acocunt. +For subsolar point, radial dayside, and -x planes the closest streamline point can be changed to be n:th closest by setting keyword *ignore*, in which case *ignore* closest streamline points are not taken into account. 2d: @@ -105,7 +103,7 @@ The magnetopause radius as a function of angle :math:`\theta` is **In analysator:** -*shue.py* in *scripts* +see :mod:`shue` .. [Shue_et_al_1997] Shue, J.-H., Chao, J. K., Fu, H. C., Russell, C. T., Song, P., Khurana, K. K., and Singer, H. J. (1997). A new functional form to study the solar wind control of the magnetopause size and shape. Journal of Geophysical Research: Space Physics, 102(A5):9497–9511. eprint: https://agupubs.onlinelibrary.wiley.com/doi/pdf/10.1029/97JA00196. @@ -121,7 +119,7 @@ The magnetopause radius as a function of angle :math:`\theta` is Constructs the magentopause surface with vtk's vtkDelaunay3d triangulation with optional alpha to make the surface non-convex. Uses regions.py functions. -Important: SDF of non-convex surface will (most likely) not work, use convex hull (alpha=None) for SDFs! +Important: SDF of non-convex surface might not always work options (magnetopause() method keyword) and some notes: @@ -135,7 +133,7 @@ options (magnetopause() method keyword) and some notes: * beta* ("beta_star") * beta* treshold might need tweaking as sometimes there are small low beta* areas in the magnetosheath that get taken in distorting the magnetopause shape at nose * convex hull (Delaunay_alpha=None) usually makes a nice rough magnetopause but goes over any inward dips (like polar cusps) - * alpha shape (Delaunay_alpha= e.g. 1*R_E) does a better job at cusps and delicate shapes like vortices but can fail at magnetotail due to central plasma sheet and won't produce a correct SDF + * alpha shape (Delaunay_alpha= e.g. 1*R_E) does a better job at cusps and delicate shapes like vortices but might fail at SDF inside the magnetosphere * Delaynay3d has an easier time if the treshold is something like [0.4, 0.5] and not [0.1, 0.5] * beta* with magnetic field line connectivity ("beta_star_with_connectivity") @@ -149,4 +147,8 @@ options (magnetopause() method keyword) and some notes: * user-defined parameter tresholds ("dict") * creates a magnetopause (or other area) using the Delaunay3d triangulation of some area where user-defined tresholds given as dictionary * dictionary key is data name in datafile and value is treshold used, if dictionary has multiple conditions, they all need to be fulfilled - * dictionary example: {"vg_rho": [None, 1e5]} makes a magnetopause using cells where density is less than 1e5 \ No newline at end of file + * dictionary example: {"vg_rho": [None, 1e5]} makes a magnetopause using cells where density is less than 1e5 + + +.. automodule:: magnetopause + :members: \ No newline at end of file diff --git a/Documentation/sphinx/magnetosphere_regions.rst b/Documentation/sphinx/magnetosphere_regions.rst index b99ffac4..3080321f 100644 --- a/Documentation/sphinx/magnetosphere_regions.rst +++ b/Documentation/sphinx/magnetosphere_regions.rst @@ -200,6 +200,10 @@ Usage example: +.. automodule:: regions + :members: + + ------------ References From 88a5076ced5969a2ee089ca34d9796a19354069a Mon Sep 17 00:00:00 2001 From: jreimi Date: Thu, 28 Aug 2025 16:12:27 +0300 Subject: [PATCH 098/124] added mention of beta* stop condition to docstring --- analysator/pyCalculations/magnetopause_sw_streamline_3d.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/analysator/pyCalculations/magnetopause_sw_streamline_3d.py b/analysator/pyCalculations/magnetopause_sw_streamline_3d.py index ad329f6d..c22078ce 100644 --- a/analysator/pyCalculations/magnetopause_sw_streamline_3d.py +++ b/analysator/pyCalculations/magnetopause_sw_streamline_3d.py @@ -228,7 +228,7 @@ def streamline_stopping_condition(vlsvReader, points, value): def make_streamlines(vlsvfile, streamline_seeds=None, seeds_n=25, seeds_x0=20*6371000, seeds_range=[-5*6371000, 5*6371000], dl=2e6, iterations=200): """Traces streamlines of velocity field from outside the magnetosphere to magnetotail. - Stopping condition for when streamlines turn sunwards or go out of box + Stopping condition for when streamlines turn sunwards, go out of box, or hit beta* below 0.4 region :param vlsvfile: directory and file name of .vlsv data file to use for VlsvReader From 49e45be1b16ec77ab2e4093e1aad6e8416a9d607 Mon Sep 17 00:00:00 2001 From: jreimi Date: Thu, 28 Aug 2025 16:23:48 +0300 Subject: [PATCH 099/124] docstring fixes --- scripts/regions.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/regions.py b/scripts/regions.py index d15fca62..44aace37 100644 --- a/scripts/regions.py +++ b/scripts/regions.py @@ -20,7 +20,7 @@ def vtkDelaunay3d_SDF(query_points, coordinates, alpha=None): """Gives a signed distance to a convex hull or alpha shape surface created from given coordinates. - Note: if using alpha, SDF most likely won't work! + Note: if using alpha, SDF might not work, especially if the point cloud used for Delaunay is not complete (missing low b) :param all_points: points ([x, y, z] coordinates in m) for which a signed distance to surface will be calculated :param coordinates: coordinates (array of [x, y, z]:s in m) that are used to make a surface. @@ -243,8 +243,8 @@ def bowshock_SDF(f, variable_dict, query_points, own_condition_dict=None): def RegionFlags(datafile, outfilen, regions=["all"], ignore_boundaries=True, region_flag_type="01", magnetopause_kwargs={}, region_conditions={}): """Creates a sidecar .vlsv file with flagged cells for regions and boundaries in near-Earth plasma environment. - Region flags (start with flag_, flags are fractions of filled conditions or 1/0): magnetosheath, magnetosphere, cusps, lobe_N, lobe_S, central_plasma_sheet - Boundary signed distance flags (start with SDF_, flags are signed distances to boundary in m with inside being negative distance): magnetopause, bowshock + Region flags (named flag_region, flags are fractions of filled conditions or 1/0): magnetosheath, magnetosphere, cusps, lobe_N, lobe_S, central_plasma_sheet + Boundary signed distance flags (named "SDF_boundary", flags are signed distances to boundary in m with inside being negative distance): magnetopause, bowshock possilbe regions: "all", "boundaries" (magnetopause, bow shock), "large_areas" (boundaries + upstream, magnetosheath, magnetosphere), "magnetosphere", "bowshock", "cusps", "lobes", "central_plasma_sheet" From 80fb347a1131e13e3258bb144d872588e0fe4939 Mon Sep 17 00:00:00 2001 From: jreimi Date: Thu, 28 Aug 2025 16:30:37 +0300 Subject: [PATCH 100/124] small sphinx changes --- Documentation/sphinx/magnetopause.rst | 6 +++--- Documentation/sphinx/magnetosphere_regions.rst | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Documentation/sphinx/magnetopause.rst b/Documentation/sphinx/magnetopause.rst index 0df6cdcb..56ea9c44 100644 --- a/Documentation/sphinx/magnetopause.rst +++ b/Documentation/sphinx/magnetopause.rst @@ -20,7 +20,7 @@ Caveats: magnetotail current sheet has beta* :math:`>` 1 **in analysator:** -*magnetopause.py* in *scripts*: see :func:`magnetopause.magnetopause` +see :func:`magnetopause.magnetopause` "method" keyword options "beta_star", "beta_star_with_connectivity" datareducer: beta_star, vg_beta_star @@ -49,8 +49,8 @@ Caveats: sometimes some streamlines can curve into the magnetotail or dayside ma **In analysator:** see -:mod:`calculations.magnetopause_sw_streamline_2d.py` -:mod:`calculations.magnetopause_sw_streamline_3d.py` +:func:`calculations.find_magnetopause_sw_streamline_2d` +:func:`calculations.find_magnetopause_sw_streamline_3d` Streamlines are traced from outside the bow shock towards Earth. A subsolar point for the magnetopause is chosen to be where streamlines get closest to Earth in x-axis [y/z~0]. diff --git a/Documentation/sphinx/magnetosphere_regions.rst b/Documentation/sphinx/magnetosphere_regions.rst index 3080321f..2d946f58 100644 --- a/Documentation/sphinx/magnetosphere_regions.rst +++ b/Documentation/sphinx/magnetosphere_regions.rst @@ -18,7 +18,7 @@ Plasma properties for estimating bow shock position: **In analysator:** -*regions.py* in *scripts* has an option to find the bow shock. Default method uses 1.5*solar wind density as limit. +:mod:`regions` has an option to find the bow shock. Default method uses 1.5*solar wind density as limit. Usage example: .. code-block:: python @@ -45,7 +45,7 @@ properties: **In analysator:** -*regions.py* in *scripts* has an option to find the magnetosheath using bow shock and magnetopause: +:mod:`regions` has an option to find the magnetosheath using bow shock and magnetopause: Usage example: .. code-block:: python @@ -71,7 +71,7 @@ Polar cusps **In analysator:** -*regions.py* in *scripts* has an option to find cusps using convex hull of the magnetosphere. +:mod:`regions` has an option to find cusps using convex hull of the magnetosphere. Usage example: .. code-block:: python @@ -104,7 +104,7 @@ Separated from the plasma sheet by the plasma sheet boundary layer (PSBL) **In analysator:** -*regions.py* in *scripts* has an option to find tail lobes. +:mod:`regions` has an option to find tail lobes. Usage example: .. code-block:: python @@ -134,7 +134,7 @@ Properties: High-latitude boundary layer (HLBL) ----------------------------------- -Includes the plasma mantle on the tail side and the entry layer on the dayside [... cit.] +Includes the plasma mantle on the tail side and the entry layer on the dayside Properties: @@ -188,7 +188,7 @@ Inner plasma sheet: unusually low plasma beta may exist (e.g., cold tenuous plas **In analysator:** -*regions.py* in *scripts* has an option to find the central plasma sheet. +:mod:`regions` has an option to find the central plasma sheet. Usage example: .. code-block:: python From 5f3c5569c67eb2f3d3928264ee17e4c2a246fcaa Mon Sep 17 00:00:00 2001 From: jreimi Date: Thu, 28 Aug 2025 17:00:39 +0300 Subject: [PATCH 101/124] added option to pass down keyword args to streamline and shue functions --- scripts/magnetopause.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/scripts/magnetopause.py b/scripts/magnetopause.py index 03dd14b4..5f423e24 100644 --- a/scripts/magnetopause.py +++ b/scripts/magnetopause.py @@ -4,7 +4,6 @@ import analysator as pt import numpy as np import vtk -import time from scripts import shue, regions @@ -28,7 +27,7 @@ def write_SDF_to_file(SDF, datafilen, outfilen): -def magnetopause(datafilen, method="beta_star_with_connectivity", own_tresholds=None, return_surface=True, return_SDF=True, SDF_points=None, Delaunay_alpha=None, beta_star_range=[0.4, 0.5]): # TODO: separate streamline suface and vtkDelaunay3d surface in streamline method +def magnetopause(datafilen, method="beta_star_with_connectivity", own_tresholds=None, return_surface=True, return_SDF=True, SDF_points=None, Delaunay_alpha=None, beta_star_range=[0.4, 0.5], method_args={}): # TODO: separate streamline suface and vtkDelaunay3d surface in streamline method """Finds the magnetopause using the specified method. Surface is constructed using vtk's Delaunay3d triangulation which results in a convex hull if no Delaunay_alpha is given. Returns vtk.vtkDataSetSurfaceFilter object and/or signed distances (negative -> inside mangetopause) (=SDF) to all cells Note that using alpha for Delaunay might make SDF not so accurate inside the magnetosphere, especially if surface is constructed with points not everywhere in the magnetosphere (e.g. beta* 0.4-0.5) @@ -41,6 +40,7 @@ def magnetopause(datafilen, method="beta_star_with_connectivity", own_tresholds= :kword SDF_points: optionally give array of own points to calculate signed distances to. If not given, distances will be to cell centres in the order of f.read_variable("CellID") output :kword Delaunay_alpha: alpha (float) to give to vtkDelaunay3d, None -> convex hull, alpha=__: surface egdes longer than __ will be excluded :kword beta_star_range: [min, max] treshold rage to use with methods "beta_star" and "beta_star_with_connectivity" + :kword method_args: dict of keyword arguments to be passed down to external functions (for streamlines and shue) :returns: vtkDataSetSurfaceFilter object of convex hull or alpha shape if return_surface=True, signed distance field of convex hull or alpha shape of magnetopause if return_SDF=True """ @@ -51,12 +51,15 @@ def magnetopause(datafilen, method="beta_star_with_connectivity", own_tresholds= if method == "streamlines": - seeds_x0=150e6 - dl=5e5 - iters = int(((seeds_x0-xmin)/dl)+100) - sector_n = 36*3 - vertices, manual_vtkSurface = pt.calculations.find_magnetopause_sw_streamline_3d(datafilen, seeds_n=300, seeds_x0=seeds_x0, seeds_range=[-5*6371000, 5*6371000], - dl=dl, iterations=iters, end_x=xmin+10*6371000, x_point_n=200, sector_n=sector_n) + if "streamline_seeds" in method_args or "seeds_n" in method_args: + vertices, manual_vtkSurface = pt.calculations.find_magnetopause_sw_streamline_3d(datafilen, **method_args) + else: + seeds_x0=150e6 + dl=5e5 + iters = int(((seeds_x0-xmin)/dl)+100) + sector_n = 36*3 + vertices, manual_vtkSurface = pt.calculations.find_magnetopause_sw_streamline_3d(datafilen, seeds_n=300, seeds_x0=seeds_x0, seeds_range=[-5*6371000, 5*6371000], + dl=dl, iterations=iters, end_x=xmin+10*6371000, x_point_n=200, sector_n=sector_n) # make the magnetopause surface from vertice points np.random.shuffle(vertices) # helps Delaunay triangulation @@ -125,7 +128,10 @@ def magnetopause(datafilen, method="beta_star_with_connectivity", own_tresholds= # note: might not be correct but should produce something, should recheck the projection coordinates theta = np.linspace(0, 2*np.pi/3 , 200) # magnetotail length decided here by trial and error: [0, 5*np.pi/6] ~ -350e6 m, [0, 2*np.pi/3] ~ -100e6 m in EGE - r, __, __ = shue.f_shue(theta, B_z = -10, n_p = 1, v_sw = 750) # for runs not in shue.py B_z, n_p, and v_sw need to be specified and run=None + if "run" in method_args or "B_z" in method_args: + r, __, __ = shue.f_shue(theta, **method_args) + else: + r, __, __ = shue.f_shue(theta, B_z = -10, n_p = 1, v_sw = 750) # for runs not in shue.py B_z, n_p, and v_sw need to be specified and run=None # 2d one-sided magnetopause xs = r*np.cos(theta) From fa0ee4ec67a332caeddea9e61e984c6c89f2005c Mon Sep 17 00:00:00 2001 From: jreimi Date: Fri, 29 Aug 2025 10:04:34 +0300 Subject: [PATCH 102/124] small changes to regions.py docstrings --- scripts/regions.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/scripts/regions.py b/scripts/regions.py index 44aace37..b23d6fc0 100644 --- a/scripts/regions.py +++ b/scripts/regions.py @@ -4,8 +4,8 @@ .. code-block:: python - datafile = "/wrk-vakka/group/spacephysics/vlasiator/3D/EGE/bulk/bulk.0002000.vlsv" - outfilen = "EGE_regions_t2000.vlsv" + datafile = "bulkfile.vlsv" + outfilen = "regions.vlsv" RegionFlags(datafile, outfilen, regions=["all"], region_conditions = {"bowshock": {"density": [2e6, None]}} @@ -20,7 +20,7 @@ def vtkDelaunay3d_SDF(query_points, coordinates, alpha=None): """Gives a signed distance to a convex hull or alpha shape surface created from given coordinates. - Note: if using alpha, SDF might not work, especially if the point cloud used for Delaunay is not complete (missing low b) + Note: if using alpha, SDF might not work, especially if the point cloud used for Delaunay does not fully cover e.g. lobes :param all_points: points ([x, y, z] coordinates in m) for which a signed distance to surface will be calculated :param coordinates: coordinates (array of [x, y, z]:s in m) that are used to make a surface. @@ -159,9 +159,12 @@ def make_region_flags(variable_dict, condition_dict, flag_type="01", mask=None): """Makes region flags example: + + .. code-block:: python + make_region_flags(variable_dict = {"density": f.read_variable(name="proton/vg_rho", cellids=-1)}, condition_dict = {"density": [None, 1e7]}) - results in flag array in shape of read_variable output where cells where "proton/vg_rho" is less or equal to 1e7 are 1 and others are 0 + results in flag array in shape of read_variable output where cells where "proton/vg_rho" is less or equal to 1e7 are 1 and others are 0 :param variable_dict: dictionary containing names and data arrays of variables, names must match those in condition_dict From 3c1f750f98967ecb7156552b37a73797536bcac9 Mon Sep 17 00:00:00 2001 From: jreimi Date: Fri, 29 Aug 2025 10:06:01 +0300 Subject: [PATCH 103/124] magetopause.py: removed main function, added usage examples to docstring --- scripts/magnetopause.py | 65 ++++++++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/scripts/magnetopause.py b/scripts/magnetopause.py index 5f423e24..a5b7d8df 100644 --- a/scripts/magnetopause.py +++ b/scripts/magnetopause.py @@ -1,4 +1,43 @@ """Functions for finding the magnetopause from .vlsv data + + Example use with own arguments passed to streamline function: + + .. code-block:: python + + datafile = "bulkfile.vlsv" + vtpoutfilen = "magnetopause.vtp" + vlsvoutfilen = "magnetopause.vlsv" + + SW_args = {"seeds_n":300, "seeds_x0":150e6, "seeds_range":[-5*6371000, 5*6371000], + "dl":5e5, "iterations":500, "end_x":-50*6371000, "x_point_n":100, "sector_n":36*2} + + surface, SDF = magnetopause(datafile, + method="streamlines", + return_SDF=True, + return_surface=True, + method_args=SW_args) + + write_vtk_surface_to_file(surface, vtpoutfilen) + write_SDF_to_file(SDF, datafile, vlsvoutfilen) + + + Example use for 0.0-0.4 beta* magnetopause with tetrahedrons with sides longer than 1Re excluded (aplha = 1Re): + + .. code-block:: python + + datafile = "bulkfile.vlsv" + vtpoutfilen = "magnetopause.vtp" + + surface, __ = magnetopause(datafile, + method="beta_star_with_connectivity", + beta_star_range=[0.0, 0.4], + Delaunay_alpha=1*6371000, + return_SDF=False, + return_surface=True) + + write_vtk_surface_to_file(surface, vtpoutfilen) + + """ import analysator as pt @@ -171,29 +210,3 @@ def magnetopause(datafilen, method="beta_star_with_connectivity", own_tresholds= #magnetosphere_proper = np.where((connectivity_region==1) | (betastar_region==1), 1, 0) - - -def main(): - - - datafile = "/wrk-vakka/group/spacephysics/vlasiator/3D/EGE/bulk/bulk.0002000.vlsv" - vtpoutfilen = "EGE_magnetopause_SWtest_t2000.vtp" - vlsvoutfilen = "EGE_magnetopause_SWtest_t2000.vlsv" - - - - - surface, SDF = magnetopause(datafile, - method="streamlines", - #method="beta_star_with_connectivity", - #beta_star_range=[0.0, 0.4], - Delaunay_alpha=1*R_E, - return_SDF=True, - return_surface=True) - - write_vtk_surface_to_file(surface, vtpoutfilen) - write_SDF_to_file(SDF, datafile, vlsvoutfilen) - -if __name__ == "__main__": - - main() From 685c2ac04c3076f98c9bf54a5ccd2f7fa23c2a9c Mon Sep 17 00:00:00 2001 From: jreimi Date: Fri, 29 Aug 2025 10:20:06 +0300 Subject: [PATCH 104/124] less print during regions --- scripts/regions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/regions.py b/scripts/regions.py index b23d6fc0..27574ec1 100644 --- a/scripts/regions.py +++ b/scripts/regions.py @@ -352,9 +352,9 @@ def errormsg(varstr): print("{} could not be read, will be ignored".format(varst # save some magnetopause values for later magnetopause_density = np.mean(variables["density"][np.abs(magnetopause_SDF) < 5e6]) - print(f"{magnetopause_density=}") + #print(f"{magnetopause_density=}") magnetopause_temperature = np.mean(variables["temperature"][np.abs(magnetopause_SDF) < 5e6]) - print(f"{magnetopause_temperature=}") + #print(f"{magnetopause_temperature=}") ## MAGNETOSPHERE ## # magnetosphere from magentopause SDF From 1d54dcb224964a116c4c7ccbbcfcc705c0756185 Mon Sep 17 00:00:00 2001 From: jreimi Date: Fri, 29 Aug 2025 11:11:29 +0300 Subject: [PATCH 105/124] fixed some typos --- Documentation/sphinx/magnetopause.rst | 2 +- analysator/pyCalculations/magnetopause_sw_streamline_3d.py | 2 +- scripts/magnetopause.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Documentation/sphinx/magnetopause.rst b/Documentation/sphinx/magnetopause.rst index 56ea9c44..055030e8 100644 --- a/Documentation/sphinx/magnetopause.rst +++ b/Documentation/sphinx/magnetopause.rst @@ -116,7 +116,7 @@ see :mod:`shue` ------------------ *magnetopause.py* in scripts for 3d runs -Constructs the magentopause surface with vtk's vtkDelaunay3d triangulation with optional alpha to make the surface non-convex. +Constructs the magnetopause surface with vtk's vtkDelaunay3d triangulation with optional alpha to make the surface non-convex. Uses regions.py functions. Important: SDF of non-convex surface might not always work diff --git a/analysator/pyCalculations/magnetopause_sw_streamline_3d.py b/analysator/pyCalculations/magnetopause_sw_streamline_3d.py index c22078ce..46520daf 100644 --- a/analysator/pyCalculations/magnetopause_sw_streamline_3d.py +++ b/analysator/pyCalculations/magnetopause_sw_streamline_3d.py @@ -270,7 +270,7 @@ def make_streamlines(vlsvfile, streamline_seeds=None, seeds_n=25, seeds_x0=20*63 def make_magnetopause(streams, end_x=-15*6371000, x_point_n=50, sector_n=36, ignore=0): - """Finds the mangetopause location based on streamlines. + """Finds the magnetopause location based on streamlines. :param streams: streamlines (coordinates in m) diff --git a/scripts/magnetopause.py b/scripts/magnetopause.py index a5b7d8df..5309e9ce 100644 --- a/scripts/magnetopause.py +++ b/scripts/magnetopause.py @@ -9,7 +9,7 @@ vlsvoutfilen = "magnetopause.vlsv" SW_args = {"seeds_n":300, "seeds_x0":150e6, "seeds_range":[-5*6371000, 5*6371000], - "dl":5e5, "iterations":500, "end_x":-50*6371000, "x_point_n":100, "sector_n":36*2} + "dl":5e5, "iterations":int(1500), "end_x":-50*6371000, "x_point_n":100, "sector_n":36*2} surface, SDF = magnetopause(datafile, method="streamlines", @@ -68,7 +68,7 @@ def write_SDF_to_file(SDF, datafilen, outfilen): def magnetopause(datafilen, method="beta_star_with_connectivity", own_tresholds=None, return_surface=True, return_SDF=True, SDF_points=None, Delaunay_alpha=None, beta_star_range=[0.4, 0.5], method_args={}): # TODO: separate streamline suface and vtkDelaunay3d surface in streamline method """Finds the magnetopause using the specified method. Surface is constructed using vtk's Delaunay3d triangulation which results in a convex hull if no Delaunay_alpha is given. - Returns vtk.vtkDataSetSurfaceFilter object and/or signed distances (negative -> inside mangetopause) (=SDF) to all cells + Returns vtk.vtkDataSetSurfaceFilter object and/or signed distances (negative -> inside magnetopause) (=SDF) to all cells Note that using alpha for Delaunay might make SDF not so accurate inside the magnetosphere, especially if surface is constructed with points not everywhere in the magnetosphere (e.g. beta* 0.4-0.5) :param datafilen: a .vlsv bulk file name (and path) From 453c932385e047aa341fb0cab0e4470652e705e1 Mon Sep 17 00:00:00 2001 From: jreimi Date: Fri, 29 Aug 2025 11:27:34 +0300 Subject: [PATCH 106/124] added main functions to magnetopause and regions scripts --- scripts/magnetopause.py | 20 ++++++++++++++++++++ scripts/regions.py | 14 ++++++++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/scripts/magnetopause.py b/scripts/magnetopause.py index 5309e9ce..f079b9be 100644 --- a/scripts/magnetopause.py +++ b/scripts/magnetopause.py @@ -210,3 +210,23 @@ def magnetopause(datafilen, method="beta_star_with_connectivity", own_tresholds= #magnetosphere_proper = np.where((connectivity_region==1) | (betastar_region==1), 1, 0) +def main(): + + datafile = "/wrk-vakka/group/spacephysics/vlasiator/3D/EGE/bulk/bulk.0002000.vlsv" + vtpoutfilen = "EGE_magnetopause_t2000.vtp" + vlsvoutfilen = "EGE_magnetopause_t2000.vlsv" + + surface, SDF = magnetopause(datafile, + method="beta_star", + beta_star_range=[0.9, 1.0], + return_SDF=True, + return_surface=True) + + write_vtk_surface_to_file(surface, vtpoutfilen) + write_SDF_to_file(SDF, datafile, vlsvoutfilen) + + +if __name__ == "__main__": + + main() + diff --git a/scripts/regions.py b/scripts/regions.py index 27574ec1..f074b2a1 100644 --- a/scripts/regions.py +++ b/scripts/regions.py @@ -357,7 +357,7 @@ def errormsg(varstr): print("{} could not be read, will be ignored".format(varst #print(f"{magnetopause_temperature=}") ## MAGNETOSPHERE ## - # magnetosphere from magentopause SDF + # magnetosphere from magnetopause SDF if "magnetosphere" in regions: magnetosphere = np.where(magnetopause_SDF<0, 1, 0) write_flags(writer, magnetosphere, 'flag_magnetosphere') @@ -382,7 +382,7 @@ def errormsg(varstr): print("{} could not be read, will be ignored".format(varst magnetosheath_flags = np.where((inside_bowshock & 1-magnetosphere), 1, 0) write_flags(writer, magnetosheath_flags, 'flag_magnetosheath') - # save magentosheath density and temperature for further use + # save magnetosheath density and temperature for further use #magnetosheath_density = np.mean(variables["density"][magnetosheath_flags == 1]) #print(f"{magnetosheath_density=}") #magnetosheath_temperature = np.mean(variables["temperature"][magnetosheath_flags == 1]) @@ -492,4 +492,14 @@ def errormsg(varstr): print("{} could not be read, will be ignored".format(varst # "J_magnitude": [2*variables["J_magnitude"][upstream_index], None], # } +def main(): + datafile = "/wrk-vakka/group/spacephysics/vlasiator/3D/EGE/bulk/bulk.0002000.vlsv" + outfilen = "EGE_regions_t2000.vlsv" + + RegionFlags(datafile, outfilen, regions=["all"]) + + +if __name__ == "__main__": + + main() From 74e6d7f27a9343e8cdc7c3337f77132335897b02 Mon Sep 17 00:00:00 2001 From: jreimi Date: Fri, 29 Aug 2025 12:00:44 +0300 Subject: [PATCH 107/124] logging instead of prints --- scripts/magnetopause.py | 11 ++++++----- scripts/regions.py | 11 ++++++----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/scripts/magnetopause.py b/scripts/magnetopause.py index f079b9be..242e3ed0 100644 --- a/scripts/magnetopause.py +++ b/scripts/magnetopause.py @@ -44,6 +44,7 @@ import numpy as np import vtk from scripts import shue, regions +import logging R_E = 6371000 @@ -54,14 +55,14 @@ def write_vtk_surface_to_file(vtkSurface, outfilen): writer.SetInputConnection(vtkSurface.GetOutputPort()) writer.SetFileName(outfilen) writer.Write() - print("wrote ", outfilen) + logging.info("wrote ", outfilen) def write_SDF_to_file(SDF, datafilen, outfilen): f = pt.vlsvfile.VlsvReader(file_name=datafilen) writer = pt.vlsvfile.VlsvWriter(f, outfilen) writer.copy_variables_list(f, ["CellID"]) writer.write_variable_info(pt.calculations.VariableInfo(SDF, "SDF_magnetopause", "-", latex="",latexunits=""),"SpatialGrid",1) - print("wrote ", outfilen) + logging.info("wrote ", outfilen) @@ -124,7 +125,7 @@ def magnetopause(datafilen, method="beta_star_with_connectivity", own_tresholds= contour_coords = f.get_cell_coordinates(cellids[magnetosphere_proper==1]) np.save("pointcloud.npy", contour_coords) except: - print("using field line connectivity for magnetosphere did not work, using only beta*") + logging.warning("using field line connectivity for magnetosphere did not work, using only beta*") #condition_dict = {"beta_star": [0.5, 0.6]} # FIC: [0.4, 0.5]) # EGE: [0.9, 1.0]) # max 0.6 in FHA to not take flyaways from outside magnetopause mpause_flags = np.where(betastar_region==1, 1, 0) contour_coords = f.get_cell_coordinates(cellids[mpause_flags!=0]) @@ -142,7 +143,7 @@ def magnetopause(datafilen, method="beta_star_with_connectivity", own_tresholds= # magnetosphere_proper = np.where(((fw_region==1) | (betastar_region==1) | (bw_region==1)), 1, 0) # contour_coords = f.get_cell_coordinates(cellids[magnetosphere_proper==1]) # except: - # print("using field lines for magnetosphere did not work, using only beta*") + # logging.warning("using field lines for magnetosphere did not work, using only beta*") # #condition_dict = {"beta_star": [0.5, 0.6]} # FIC: [0.4, 0.5]) # EGE: [0.9, 1.0]) # max 0.6 in FHA to not take flyaways from outside magnetopause # mpause_flags = np.where(betastar_region==1, 1, 0) # contour_coords = f.get_cell_coordinates(cellids[mpause_flags!=0]) @@ -194,7 +195,7 @@ def magnetopause(datafilen, method="beta_star_with_connectivity", own_tresholds= else: - print("Magnetopause method not recognized. Use one of the options: \"beta_star\", \"beta_star_with_connectivity\", \"streamlines\", \"shue\", \"dict\"") + logging.error("Magnetopause method not recognized. Use one of the options: \"beta_star\", \"beta_star_with_connectivity\", \"streamlines\", \"shue\", \"dict\"") exit() if return_surface and return_SDF: diff --git a/scripts/regions.py b/scripts/regions.py index f074b2a1..3776bf0e 100644 --- a/scripts/regions.py +++ b/scripts/regions.py @@ -15,6 +15,7 @@ import numpy as np import vtk from scripts import magnetopause +import logging R_E = 6371000 @@ -95,7 +96,7 @@ def treshold_mask(data_array, value): mask = np.where((value[0] <= data_array) & (data_array <= value[1]), 1, 0) # min/max if np.sum(mask[mask>0]) == 0: - print("Treshold mask didn't match any values in array") + logging.warning("Treshold mask didn't match any values in array") return None return mask @@ -151,7 +152,7 @@ def write_flags(writer, flags, flag_name, mask=None): #writer.write(flags,flag_name,'VARIABLE','SpatialGrid') writer.write_variable_info(pt.calculations.VariableInfo(flags, flag_name, "-", latex="",latexunits=""),"SpatialGrid",1) - print(flag_name+" written to file") + logging.info(flag_name+" written to file") @@ -183,7 +184,7 @@ def make_region_flags(variable_dict, condition_dict, flag_type="01", mask=None): region = treshold_mask(variable_dict[key], condition_dict[key]) if region is not None: variable_flags.append(region) except: - print(key, "ignored") + logging.warning(key, "ignored") else: # search only masked area for key in condition_dict.keys(): @@ -191,7 +192,7 @@ def make_region_flags(variable_dict, condition_dict, flag_type="01", mask=None): region = treshold_mask(variable_dict[key][mask], condition_dict[key]) if region is not None: variable_flags.append(region) except: - print(key, "ignored") + logging.warning(key, "ignored") variable_flags = np.array(variable_flags) @@ -311,7 +312,7 @@ def RegionFlags(datafile, outfilen, regions=["all"], ignore_boundaries=True, reg boundaryname = "vg_boundarytype" - def errormsg(varstr): print("{} could not be read, will be ignored".format(varstr)) + def errormsg(varstr): logging.warning("{} could not be read, will be ignored".format(varstr)) for varname, filevarname in varnames.items(): if isinstance(filevarname, str): # no operator From 207bf52265291734b12b80750fb5cba70d71af6f Mon Sep 17 00:00:00 2001 From: jreimi Date: Fri, 29 Aug 2025 12:06:55 +0300 Subject: [PATCH 108/124] added mention of dayside cells to CPS doc --- Documentation/sphinx/magnetosphere_regions.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/sphinx/magnetosphere_regions.rst b/Documentation/sphinx/magnetosphere_regions.rst index 2d946f58..7b96f1b7 100644 --- a/Documentation/sphinx/magnetosphere_regions.rst +++ b/Documentation/sphinx/magnetosphere_regions.rst @@ -189,6 +189,7 @@ Inner plasma sheet: unusually low plasma beta may exist (e.g., cold tenuous plas **In analysator:** :mod:`regions` has an option to find the central plasma sheet. +Note that the default parameters may catch some dayside cells in addition to plasma sheet. Usage example: .. code-block:: python From a9356b370db88d407660934645953646d9f6ce1a Mon Sep 17 00:00:00 2001 From: jreimi Date: Fri, 29 Aug 2025 13:13:42 +0300 Subject: [PATCH 109/124] small additions to magnetopause docs --- Documentation/sphinx/magnetopause.rst | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Documentation/sphinx/magnetopause.rst b/Documentation/sphinx/magnetopause.rst index 055030e8..111e0f0a 100644 --- a/Documentation/sphinx/magnetopause.rst +++ b/Documentation/sphinx/magnetopause.rst @@ -56,7 +56,7 @@ Streamlines are traced from outside the bow shock towards Earth. A subsolar poin From subsolar point towards Earth, space is divided radially by spherical coordiante (theta from x-axis) angles theta and phi, and magnetopause is located by looking at streamline point distances from origo and marked to be at middle of the sector -From the Earth towards negative x the space is divided into yz-planes. +From the Earth towards negative x the space is divided into yz-planes and streamlines are interpolated to certain x-points. Each yz-plane is then divided into 2d sectors and magnetopause is marked to be in the middle of the sector with the radius of the n:th closest streamline to the x-axis. @@ -64,7 +64,7 @@ For subsolar point, radial dayside, and -x planes the closest streamline point c 2d: -Note: no radial dayside +Note: no radial dayside, uses yt-package instead of analysator's fieldtracer 3d: After the magnetopause points are chosen, they are made into a surface by setting connnection lines between vertices (magnetopause points) to make surface triangles. @@ -103,7 +103,7 @@ The magnetopause radius as a function of angle :math:`\theta` is **In analysator:** -see :mod:`shue` +see :doc:`../shue` .. [Shue_et_al_1997] Shue, J.-H., Chao, J. K., Fu, H. C., Russell, C. T., Song, P., Khurana, K. K., and Singer, H. J. (1997). A new functional form to study the solar wind control of the magnetopause size and shape. Journal of Geophysical Research: Space Physics, 102(A5):9497–9511. eprint: https://agupubs.onlinelibrary.wiley.com/doi/pdf/10.1029/97JA00196. @@ -115,7 +115,7 @@ see :mod:`shue` **In analysator:** ------------------ -*magnetopause.py* in scripts for 3d runs +:mod:`magnetopause` in scripts for 3d runs Constructs the magnetopause surface with vtk's vtkDelaunay3d triangulation with optional alpha to make the surface non-convex. Uses regions.py functions. @@ -126,7 +126,9 @@ options (magnetopause() method keyword) and some notes: * solar wind flow ("streamlines") * uses *magnetopause_sw_streamline_3d.py* from pyCalculations * if streamlines make a turn so that the velocity points sunwards (to pos. x), the streamline is ignored from that point onwards - * this fixes some issues with funky and inwards-turning streamlines but not all + * streamlines that enter an area where beta* is below 0.4 are also stopped + * this can affect the far magnetotail as streamlines may turn inwards into lobes at some point and may result in magnetopause not forming + due to not enough streamline points where magnetopause is supposed to be * very dependent on how solar wind streamlines behave * streamline seeds and other options can greatly affect the resulting magnetopause @@ -134,7 +136,7 @@ options (magnetopause() method keyword) and some notes: * beta* treshold might need tweaking as sometimes there are small low beta* areas in the magnetosheath that get taken in distorting the magnetopause shape at nose * convex hull (Delaunay_alpha=None) usually makes a nice rough magnetopause but goes over any inward dips (like polar cusps) * alpha shape (Delaunay_alpha= e.g. 1*R_E) does a better job at cusps and delicate shapes like vortices but might fail at SDF inside the magnetosphere - * Delaynay3d has an easier time if the treshold is something like [0.4, 0.5] and not [0.1, 0.5] + * Delaynay3d has an easier time if the treshold is something like [0.4, 0.5] and not [0.0, 0.5], but this can affect the alpha shape if alpha is used * beta* with magnetic field line connectivity ("beta_star_with_connectivity") * includes closed-closed magnetic field line areas if available, otherwise like "beta_star" From f7104eb1af737512f913a61fd4a1906064fbeb01 Mon Sep 17 00:00:00 2001 From: jreimi Date: Fri, 29 Aug 2025 13:34:51 +0300 Subject: [PATCH 110/124] added comment about cusps catching extra cells on dayside to sphinx doc --- Documentation/sphinx/magnetosphere_regions.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/sphinx/magnetosphere_regions.rst b/Documentation/sphinx/magnetosphere_regions.rst index 7b96f1b7..e3e1f42e 100644 --- a/Documentation/sphinx/magnetosphere_regions.rst +++ b/Documentation/sphinx/magnetosphere_regions.rst @@ -72,6 +72,9 @@ Polar cusps **In analysator:** :mod:`regions` has an option to find cusps using convex hull of the magnetosphere. +Note that the default parameters may catch some dayside cells near the magnetopause and some non-cusp areas inside dayside in addition to cusps. +Areas near magnetopause can be excluded for example by only including cells where the SDF is less than e.g. -1 Re. + Usage example: .. code-block:: python From cc3ca7240916657f425ea48f6f35fc16664b4825 Mon Sep 17 00:00:00 2001 From: jreimi Date: Fri, 29 Aug 2025 14:08:20 +0300 Subject: [PATCH 111/124] documentation updates --- Documentation/sphinx/magnetopause.rst | 6 ++++-- Documentation/sphinx/magnetosphere_regions.rst | 1 + scripts/magnetopause.py | 2 +- scripts/regions.py | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Documentation/sphinx/magnetopause.rst b/Documentation/sphinx/magnetopause.rst index 111e0f0a..72d1a432 100644 --- a/Documentation/sphinx/magnetopause.rst +++ b/Documentation/sphinx/magnetopause.rst @@ -1,6 +1,8 @@ Magnetopause: how to find ========================= +Note: The analysator methods here are prototypes and should be adapted and modified as needed. + Magnetopause Plasma beta, beta* @@ -80,7 +82,7 @@ This surface is then made into a vtkPolyData and returned as vtkDataSetSurfaceFi Shue et al. (1997) ------------------ -The Shue et al. (1997) [Shue_et_al_1997]_ mangnetopause model: +The Shue et al. (1997) [Shue_et_al_1997]_ magnetopause model: .. math:: @@ -119,7 +121,7 @@ see :doc:`../shue` Constructs the magnetopause surface with vtk's vtkDelaunay3d triangulation with optional alpha to make the surface non-convex. Uses regions.py functions. -Important: SDF of non-convex surface might not always work +Important: SDF of non-convex surface might not always work as expected inside the magnetosphere if e.g. the lobes are hollow. options (magnetopause() method keyword) and some notes: diff --git a/Documentation/sphinx/magnetosphere_regions.rst b/Documentation/sphinx/magnetosphere_regions.rst index e3e1f42e..9883c7d7 100644 --- a/Documentation/sphinx/magnetosphere_regions.rst +++ b/Documentation/sphinx/magnetosphere_regions.rst @@ -1,6 +1,7 @@ Magnetosphere regions and bow shock: How to find ================================================ +Note: The analysator methods here are prototypes and should be adapted and modified as needed. Bow shock --------- diff --git a/scripts/magnetopause.py b/scripts/magnetopause.py index 242e3ed0..94ce29d2 100644 --- a/scripts/magnetopause.py +++ b/scripts/magnetopause.py @@ -70,7 +70,7 @@ def write_SDF_to_file(SDF, datafilen, outfilen): def magnetopause(datafilen, method="beta_star_with_connectivity", own_tresholds=None, return_surface=True, return_SDF=True, SDF_points=None, Delaunay_alpha=None, beta_star_range=[0.4, 0.5], method_args={}): # TODO: separate streamline suface and vtkDelaunay3d surface in streamline method """Finds the magnetopause using the specified method. Surface is constructed using vtk's Delaunay3d triangulation which results in a convex hull if no Delaunay_alpha is given. Returns vtk.vtkDataSetSurfaceFilter object and/or signed distances (negative -> inside magnetopause) (=SDF) to all cells - Note that using alpha for Delaunay might make SDF not so accurate inside the magnetosphere, especially if surface is constructed with points not everywhere in the magnetosphere (e.g. beta* 0.4-0.5) + Note that using alpha for Delaunay might make SDF different from expected inside the magnetosphere, especially if surface is constructed with points not everywhere in the magnetosphere (e.g. beta* 0.4-0.5) or if simulation grid size is larger than alpha :param datafilen: a .vlsv bulk file name (and path) :kword method: str, default "beta_star_with_connectivity", other options "beta_star", "streamlines", "shue", "dict" diff --git a/scripts/regions.py b/scripts/regions.py index 3776bf0e..7b7895f7 100644 --- a/scripts/regions.py +++ b/scripts/regions.py @@ -250,7 +250,7 @@ def RegionFlags(datafile, outfilen, regions=["all"], ignore_boundaries=True, reg Region flags (named flag_region, flags are fractions of filled conditions or 1/0): magnetosheath, magnetosphere, cusps, lobe_N, lobe_S, central_plasma_sheet Boundary signed distance flags (named "SDF_boundary", flags are signed distances to boundary in m with inside being negative distance): magnetopause, bowshock - possilbe regions: "all", "boundaries" (magnetopause, bow shock), "large_areas" (boundaries + upstream, magnetosheath, magnetosphere), "magnetosphere", "bowshock", + possible regions: "all", "boundaries" (magnetopause, bow shock), "large_areas" (boundaries + upstream, magnetosheath, magnetosphere), "magnetosphere", "bowshock", "cusps", "lobes", "central_plasma_sheet" Note that different runs may need different tresholds for region parameters and region accuracy should be verified visually From 68ac9f7de9351cee112ef5728e215004cec54860 Mon Sep 17 00:00:00 2001 From: jreimi Date: Fri, 29 Aug 2025 14:10:38 +0300 Subject: [PATCH 112/124] added note about lobes --- Documentation/sphinx/magnetosphere_regions.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/sphinx/magnetosphere_regions.rst b/Documentation/sphinx/magnetosphere_regions.rst index 9883c7d7..a4e7fa2d 100644 --- a/Documentation/sphinx/magnetosphere_regions.rst +++ b/Documentation/sphinx/magnetosphere_regions.rst @@ -109,6 +109,8 @@ Separated from the plasma sheet by the plasma sheet boundary layer (PSBL) **In analysator:** :mod:`regions` has an option to find tail lobes. +Note that the default parameters to find lobes may include some areas from the dayside. + Usage example: .. code-block:: python From 7007381af875f2be8d7b348fe31d40dbeea60b2a Mon Sep 17 00:00:00 2001 From: jreimi Date: Fri, 29 Aug 2025 14:26:55 +0300 Subject: [PATCH 113/124] removed redundant streamline magnetopause plot example file --- scripts/plot_streamline_magnetopause_3d.py | 91 ---------------------- 1 file changed, 91 deletions(-) delete mode 100644 scripts/plot_streamline_magnetopause_3d.py diff --git a/scripts/plot_streamline_magnetopause_3d.py b/scripts/plot_streamline_magnetopause_3d.py deleted file mode 100644 index 8c661b12..00000000 --- a/scripts/plot_streamline_magnetopause_3d.py +++ /dev/null @@ -1,91 +0,0 @@ -""" -An example of using find_magnetopause_sw_streamline_3d() and plotting the output. -""" - -import numpy as np -import matplotlib.pyplot as plt -import analysator as pt - -def main(): - ## get bulk data - run = 'EGI' # for plot output file name - file_name="/wrk-vakka/group/spacephysics/vlasiator/3D/EGI/bulk/bulk5.0000070.vlsv" - - ## magnetopause - x_point_n = 50 # number needed for 2d slice plots, default is 50 - - vertices, faces = pt.calculations.find_magnetopause_sw_streamline_3d( - file_name, - streamline_seeds=None, - seeds_n=25, - seeds_x0=20*6371000, - seeds_range=[-5*6371000, 5*6371000], - dl=2e6, - iterations=200, - end_x=-15*6371000, - x_point_n=x_point_n, - sector_n=36) - - - outdir="" # output plot save directory - - ### 3D surface plot ### - - fig = plt.figure() - ax = fig.add_subplot(projection='3d') - ax.plot_trisurf(vertices[:, 0], vertices[:,1], faces, vertices[:,2], linewidth=0.2) - plt.savefig(outdir+run+'_magnetopause_3D_surface.png') - plt.close() - - - ### 2D slice plots ### - - vertices = vertices/6371000 # to RE - magnetopause = np.array(np.split(vertices, x_point_n)) # magnetopause points grouped by x-axis coordinate - - ## take separate arrays for different 2d slice plots - - quarter_slice = int(np.shape(magnetopause)[1]/4) - # xy plane: z=0 - xy_slice = np.concatenate((magnetopause[:,0][::-1], magnetopause[:,2*quarter_slice])) - # xz plane: y=0 - xz_slice = np.concatenate((magnetopause[:,quarter_slice][::-1], magnetopause[:,3*quarter_slice])) - - - # analysator 3dcolormapslice y=0 - - def external_plot(ax,XmeshXY=None, YmeshXY=None, pass_maps=None, requestvariables=False): - if requestvariables==True: - return ['vg_v'] - ax.plot(xz_slice[:,0], xz_slice[:,2], color='limegreen', linewidth=1.5) - - pt.plot.plot_colormap3dslice( - filename=file_name, - run=run, - outputdir=outdir, - boxre = [-21,21,-21,21], - external = external_plot, - colormap='inferno', - normal='y' - ) - - # analysator 3dcolormapslice z=0 - - def external_plot(ax,XmeshXY=None, YmeshXY=None, pass_maps=None, requestvariables=False): - if requestvariables==True: - return ['vg_v'] - ax.plot(xy_slice[:,0], xy_slice[:,1], color='limegreen', linewidth=1.5) - - pt.plot.plot_colormap3dslice( - filename=file_name, - outputdir=outdir, - run=run, - boxre = [-21,21,-21,21], - external = external_plot, - colormap='inferno', - normal='z') - - - -if __name__ == "__main__": - main() From 3995c02f25fbd0c04c976678530c8809d8433e0f Mon Sep 17 00:00:00 2001 From: jreimi Date: Fri, 29 Aug 2025 14:32:39 +0300 Subject: [PATCH 114/124] also removed plotting example from sphinx --- .../sphinx/plot_streamline_magnetopause_3d.rst | 6 ------ Documentation/sphinx/scripts.rst | 10 ---------- 2 files changed, 16 deletions(-) delete mode 100644 Documentation/sphinx/plot_streamline_magnetopause_3d.rst diff --git a/Documentation/sphinx/plot_streamline_magnetopause_3d.rst b/Documentation/sphinx/plot_streamline_magnetopause_3d.rst deleted file mode 100644 index f1794ffb..00000000 --- a/Documentation/sphinx/plot_streamline_magnetopause_3d.rst +++ /dev/null @@ -1,6 +0,0 @@ -plot_streamline_magnetopause_3d -------------------------------- - -.. automodule:: plot_streamline_magnetopause_3d - :members: - \ No newline at end of file diff --git a/Documentation/sphinx/scripts.rst b/Documentation/sphinx/scripts.rst index bb0d05be..f9a7d8bd 100644 --- a/Documentation/sphinx/scripts.rst +++ b/Documentation/sphinx/scripts.rst @@ -39,15 +39,6 @@ plot_streamline_magnetopause_2d ------------ -plot_streamline_magnetopause_3d -------------------------------- -:doc:`plot_streamline_magnetopause_3d` - -.. automodule:: plot_streamline_magnetopause_3d - :no-index: - ------------- - obliqueshock ------------ :doc:`obliqueshock` @@ -110,7 +101,6 @@ regions cutthrough_timeseries gics plot_streamline_magnetopause_2d - plot_streamline_magnetopause_3d obliqueshock obliqueshock_nif shue From cc79359d68fbc17df0085f3eb465032c38505ded Mon Sep 17 00:00:00 2001 From: jreimi Date: Fri, 29 Aug 2025 14:58:39 +0300 Subject: [PATCH 115/124] added something about field lines to documentation --- Documentation/sphinx/magnetopause.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Documentation/sphinx/magnetopause.rst b/Documentation/sphinx/magnetopause.rst index 72d1a432..b1a225ac 100644 --- a/Documentation/sphinx/magnetopause.rst +++ b/Documentation/sphinx/magnetopause.rst @@ -34,8 +34,7 @@ datareducer: beta_star, vg_beta_star Field line connectivity ----------------------- - - +Bulkfiles from newer Vlasiator runs include connection (whether magnetic field lines are closed-closed, closed-open, open/closed or open-open) and coordinates for field line start and end points that can be used in finding the mangetopause location. Solar wind flow From e90b519c971637f7f43cc31f33a88eb4c312e32c Mon Sep 17 00:00:00 2001 From: Markku Alho Date: Mon, 1 Sep 2025 12:56:17 +0300 Subject: [PATCH 116/124] Update requirements.txt missing endline --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 98c72d07..7f8ffdcc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,4 @@ matplotlib scikit-image packaging vtk>=9.2 -rtree \ No newline at end of file +rtree From cf9e9cb77fe34e7f7712f640f36251285bf6e2ab Mon Sep 17 00:00:00 2001 From: Markku Alho Date: Mon, 1 Sep 2025 13:21:33 +0300 Subject: [PATCH 117/124] Update pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ece514a3..34dc2149 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,7 @@ dependencies = [ "matplotlib", "packaging", "scikit-image", - "yt" + "yt", "rtree" ] [project.optional-dependencies] From e4be4fbe5cbb0c5415e5c1c6a6e02dcd70ce6c6d Mon Sep 17 00:00:00 2001 From: Markku Alho Date: Mon, 1 Sep 2025 13:58:55 +0300 Subject: [PATCH 118/124] Guards for setting up cache files, cache setup script --- analysator/pyVlsv/vlsvcache.py | 6 ++++-- examples/setup-bulk-caches.py | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 examples/setup-bulk-caches.py diff --git a/analysator/pyVlsv/vlsvcache.py b/analysator/pyVlsv/vlsvcache.py index d7f23132..a20fc4b6 100644 --- a/analysator/pyVlsv/vlsvcache.py +++ b/analysator/pyVlsv/vlsvcache.py @@ -135,8 +135,10 @@ def set_cellid_spatial_index(self, force = False): os.makedirs(self.get_cache_folder()) if force: - os.remove(self.__rtree_idxfile) - os.remove(self.__rtree_datfile) + if os.path.exists(self.__rtree_idxfile): + os.remove(self.__rtree_idxfile) + if os.path.exists(self.__rtree_datfile): + os.remove(self.__rtree_datfile) if(not os.path.isfile(self.__rtree_idxfile) or not os.path.isfile(self.__rtree_datfile)): t0 = time.time() diff --git a/examples/setup-bulk-caches.py b/examples/setup-bulk-caches.py new file mode 100644 index 00000000..84dc79c0 --- /dev/null +++ b/examples/setup-bulk-caches.py @@ -0,0 +1,17 @@ +import analysator as pt +import sys +import glob + +if len(sys.argv) != 2: + print("Need globbable argument, eg. python setup-bulk-caches.py /wrk-vakka/group/spacephysics/vlasiator/3D/FHA/bulk1/bulk1.*.vlsv") + sys.exit(1) + +fns = glob.glob(sys.argv[1]) + +fns.sort() + + +for fn in fns: + f = pt.vlsvfile.VlsvReader(fn) + f.cache_optimization_files(True) + del f \ No newline at end of file From 847ca2ff3206f70d61abaf67fe9dcf477173e076 Mon Sep 17 00:00:00 2001 From: Markku Alho Date: Mon, 1 Sep 2025 14:02:07 +0300 Subject: [PATCH 119/124] Missing newline --- examples/setup-bulk-caches.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/setup-bulk-caches.py b/examples/setup-bulk-caches.py index 84dc79c0..6884f5e8 100644 --- a/examples/setup-bulk-caches.py +++ b/examples/setup-bulk-caches.py @@ -14,4 +14,4 @@ for fn in fns: f = pt.vlsvfile.VlsvReader(fn) f.cache_optimization_files(True) - del f \ No newline at end of file + del f From 0213d18a12e35d684516cf35fc53fa9b8d0df452 Mon Sep 17 00:00:00 2001 From: Markku Alho Date: Thu, 15 Jan 2026 11:30:05 +0200 Subject: [PATCH 120/124] Refactor vtk dependency in pyproject.toml Removed vtk dependency from optional dependencies and added it to the main dependencies. --- pyproject.toml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 4c9b7e52..890e3ca2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,15 +26,13 @@ dependencies = [ "matplotlib", "packaging", "scikit-image", + "vtk>=9.2", "yt", "rtree" ] [project.optional-dependencies] none = [ ] -vtk = [ - "vtk>=9.2", -] all = [ "analysator[vtk]", ] From 2c96697aff2bb2ad607a293f8bf4d9f59007ff7d Mon Sep 17 00:00:00 2001 From: Markku Alho Date: Sun, 18 Jan 2026 19:21:41 +0200 Subject: [PATCH 121/124] Try with carrington-constrained sbatch... ... if that helps with stability... --- testpackage/run_compare.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testpackage/run_compare.sh b/testpackage/run_compare.sh index 981445fe..244d1c4f 100755 --- a/testpackage/run_compare.sh +++ b/testpackage/run_compare.sh @@ -1,7 +1,7 @@ #!/bin/bash -l #SBATCH -t 00:30:00 #SBATCH -J analysator_testpackage_compare -#SBATCH --constraint="ukko|carrington" +#SBATCH --constraint="carrington" #SBATCH -p short #SBATCH -n 1 #SBATCH --array=1-10 From 95910d25230d5d755a6f9bcfa90e7aff81c3d014 Mon Sep 17 00:00:00 2001 From: Markku Alho Date: Sun, 18 Jan 2026 19:24:22 +0200 Subject: [PATCH 122/124] Update optional dependencies in pyproject.toml Removed 'analysator[vtk]' from optional dependencies. --- pyproject.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 890e3ca2..705e5e83 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,14 +34,12 @@ dependencies = [ none = [ ] all = [ - "analysator[vtk]", ] bvtk = [ "vtk==9.2.6", ] testpackage = [ "opencv-python", - "analysator[vtk]", ] [project.urls] Homepage = "https://github.com/fmihpc/analysator" From 414a44bf27f21efc514e3399eaca31a640314525 Mon Sep 17 00:00:00 2001 From: Markku Alho Date: Sun, 18 Jan 2026 19:40:23 +0200 Subject: [PATCH 123/124] Update SLURM constraint for testpackage workflow .. edit correct file. --- testpackage/run_testpackage_workflow.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testpackage/run_testpackage_workflow.sh b/testpackage/run_testpackage_workflow.sh index d401be0f..f134b55c 100755 --- a/testpackage/run_testpackage_workflow.sh +++ b/testpackage/run_testpackage_workflow.sh @@ -1,7 +1,7 @@ #!/bin/bash -l #SBATCH -t 00:45:00 #SBATCH -J analysator_testpackage -#SBATCH --constraint="carrington|ukko" +#SBATCH --constraint="carrington" #SBATCH -p short #SBATCH -n 1 #SBATCH --array=1-20 From 9ec0efaaa04456b8d0fc678aa142cbbe8621f6b8 Mon Sep 17 00:00:00 2001 From: lassjsc Date: Fri, 30 Jan 2026 14:19:50 +0200 Subject: [PATCH 124/124] Fixed the folder path --- analysator/pyVlsv/vlsvcache.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/analysator/pyVlsv/vlsvcache.py b/analysator/pyVlsv/vlsvcache.py index a20fc4b6..15f8ed98 100644 --- a/analysator/pyVlsv/vlsvcache.py +++ b/analysator/pyVlsv/vlsvcache.py @@ -115,7 +115,8 @@ def get_cache_folder(self): leading_zero = True path = os.path.join(path,"vlsvcache",head[:-1]) for i,n in enumerate(nums): - if n == '0' and leading_zero: continue + if n == '0' and leading_zero and i!=len(nums)-1: + continue if leading_zero: fmt = "{:07d}" else: