Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
64ef94c
Added lumicontainer builder script
lassejsc Feb 27, 2026
dad08a1
Merge branch 'fmihpc:master' into lumi_container
lassejsc Mar 12, 2026
815de63
Added way to define python version with PY_VERSION env var, defaults …
lassejsc Mar 12, 2026
47abf57
typo in singularity exec
lassejsc Mar 23, 2026
0a53629
Added missing "
lassejsc Apr 22, 2026
d6122c7
Changed the file check such that it makes the container if the .sif (…
lassejsc Apr 22, 2026
9303ba5
CITATION.cff update
alhom Apr 23, 2026
4c0eda3
CITATION.cff update with orcids
alhom Apr 23, 2026
59413f8
Moving lumi_onctainer to a new folder "containerization", perhaps more
lassejsc May 26, 2026
af55983
added documentation about binding folders with a friendly warning for
lassejsc May 28, 2026
d990d35
Added documentation for the lumi container
lassejsc May 28, 2026
b27d106
Fixed the whatever sphinx complained about the underline
lassejsc May 28, 2026
731dd1d
logging config for obliqueshock.py
alhom May 28, 2026
9137438
Merge pull request #447 from lassejsc/lumi_container
alhom May 28, 2026
0517a48
prototype to ditch Delaunay alpha-hulls when we actually have usable …
alhom Sep 4, 2025
e844c9e
vg_-prefix to outputs
alhom Sep 4, 2025
43bb012
Add variable cache injection function
alhom Sep 17, 2025
238f401
Region VTK pipeline cleanup, temp variable cache injection for vlsvre…
alhom Sep 18, 2025
3cac100
Merge alhom branch 'regions-update' into region_update
jreimi Jun 2, 2026
5869fb2
corrected VlsvTInterpolator cached variable name (#481)
leeviloikkanen Jun 2, 2026
67cf19e
Strip filename string before creating linked readers
alhom Jun 4, 2026
ac845c2
Added a quick fix for vtk to file writer func to accept both connecti…
jreimi Jun 8, 2026
2ef557f
Import mp finding functions to calculations
JonasSuni Jun 15, 2026
6e4c0b4
magnetopause streamline creation condition fix
jreimi Jun 15, 2026
6d26228
Changed to --constraint="amd" to eliminate slow nodes
lassejsc Jun 16, 2026
569f7ad
Merge pull request #484 from lassejsc/amdConstraint
alhom Jun 16, 2026
a2542fb
Fixed logic for dayside and nightside x_points
JonasSuni Jun 17, 2026
edd8213
vlsvReader arg takes str filename and already initialized obj, update…
Gekbako Jun 20, 2026
eb60f78
updated np.histogram arguments -> unnormalized hist
Gekbako Jun 20, 2026
cdba3b5
Merge pull request #480 from alhom/script-loggings
alhom Jun 23, 2026
37f3f15
Merge pull request #476 from alhom/citations
alhom Jun 23, 2026
882de52
enforce str or VlsvReader
Gekbako Jun 23, 2026
a167075
Merge branch 'fmihpc:master' into master
Gekbako Jun 23, 2026
2c22e91
Merge pull request #485 from Gekbako/master
alhom Jun 23, 2026
7e791b6
Merge pull request #482 from jreimi/region_update
alhom Jun 23, 2026
bf332fd
Merge pull request #483 from JonasSuni/mp_function_import_fix
alhom Jun 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions CITATION.cff
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
cff-version: 1.2.0
message: "If you use this software, please cite it as below."
authors:
- family-names: "Battarbee"
given-names: "Markus"
orcid: "https://orcid.org/0000-0001-7055-551X"
- family-names: "Alho"
given-names: "Markku"
orcid: "https://orcid.org/0000-0001-9762-6795"
- family-names: "Battarbee"
given-names: "Markus"
orcid: "https://orcid.org/0000-0001-7055-551X"
- family-names: "Hannuksela"
given-names: "Otto"
- family-names: "Sihvonen"
given-names: "Lasse"
- family-names: "Pfau-Kempf"
given-names: "Yann"
orcid: "https://orcid.org/0000-0001-5793-7070"
Expand Down Expand Up @@ -48,6 +52,12 @@ authors:
- family-names: "Turc"
given-names: "Lucile"
orcid: "https://orcid.org/0000-0002-7576-3251"
- family-names: "Ala-Lahti"
given-names: "Matti"
orcid: "https://orcid.org/0000-0001-9574-339X"
- family-names: "Kilian"
given-names: "Patrick"
orcid: "https://orcid.org/0000-0002-8906-7783"
- family-names: "Tesema"
given-names: "Fasil Kebede"
orcid: "https://orcid.org/0000-0002-5978-6955"
Expand All @@ -57,6 +67,9 @@ authors:
- family-names: "Honkonen"
given-names: "Ilja"
orcid: "https://orcid.org/0000-0002-9542-5866"
- family-names: "Hoilijoki"
given-names: "Sanni"
orcid: "https://orcid.org/0000-0001-5709-2346"
- family-names: "Brito"
given-names: "Thiago"
orcid: "https://orcid.org/0000-0002-2531-5848"
Expand All @@ -67,8 +80,10 @@ authors:
given-names: "Sameli"
- family-names: "Reimi"
given-names: "Johanna"
- family-names: "Greguš"
given-names: "Jakub"
title: "Analysator"
version: 0.9.6
version: 0.9.8
doi: 10.5281/zenodo.4462514
date-released: 2024-11-26
url: "https://github.com/fmihpc/analysator"
14 changes: 14 additions & 0 deletions Documentation/sphinx/hpcusage.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Usage in HPC environment
========================


LUMI Container
--------------
Script for building and running a container on LUMI can be found in ``containerization/lumi_container.sh``.

Simply execute the bash script to build the container, afterwards the same script can be used for launching the container.

Before building the container set the python version for the container export the environment variable ``PY_VERSION``, which defaults to ``PY_VERSION="3.12"``.

For binding folders into the container, set the environment variable ``SINGULARITY_BIND`` before running the container. For example, ``export SINGULARITY_BIND="/opt,/data:/mnt"``, this will bind /opt to /opt and /data to /mnt in the container.
For more information on singularity usage, see https://docs.sylabs.io/guides/3.0/user-guide/index.html
1 change: 1 addition & 0 deletions Documentation/sphinx/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ analysator documentation
analysator
analysator_exercises
analysator_supported
hpcusage

.. toctree::
:maxdepth: 2
Expand Down
2 changes: 2 additions & 0 deletions analysator/calculations/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,5 @@
from .null_lines import LMN_null_lines_FOTE
from .interpolator_amr import AMRInterpolator, supported_amr_interpolators
from .virtual_observations import spacecraft_to_simulation_frame,simulation_to_spacecraft_frame,simulation_to_observation_frame
from .magnetopause_sw_streamline_2d import find_magnetopause_sw_streamline_2d
from .magnetopause_sw_streamline_3d import find_magnetopause_sw_streamline_3d
10 changes: 6 additions & 4 deletions analysator/calculations/magnetopause_sw_streamline_3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ def make_streamlines(vlsvfile, streamline_seeds=None, seeds_n=25, seeds_x0=20*63
f = pt.vlsvfile.VlsvReader(file_name=vlsvfile)

# Create streamline starting points if needed
if streamline_seeds == None:
if not np.any(streamline_seeds):
streamline_seeds = np.zeros([seeds_n**2, 3])

t = np.linspace(seeds_range[0], seeds_range[1], seeds_n)
Expand Down Expand Up @@ -301,7 +301,7 @@ def make_magnetopause(streams, end_x=-15*6371000, x_point_n=50, sector_n=36, ign
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_x_point_n = int((subsolar_x/np.abs(subsolar_x - end_x))*x_point_n)

### dayside magnetopause ###
# for x > 0, look for magnetopause radially
Expand Down Expand Up @@ -355,11 +355,13 @@ def grid_mid_point(theta_idx, phi_idx):

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
x_points = np.linspace(0.0, end_x, x_point_n-dayside_x_point_n)
if end_x < 0:
x_points = np.linspace(0.0, end_x, int((np.abs(end_x)/np.abs(subsolar_x - end_x))*x_point_n))
else:
x_points = np.array([],dtype=float)

## 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]
Expand Down
22 changes: 18 additions & 4 deletions analysator/calculations/spectra.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,15 @@ def get_spectrum_energy(vlsvReader,

EkinBinEdges = np.logspace(np.log10(EMin),np.log10(EMax),nBins+1, endpoint=True)
dE = EkinBinEdges[1:] - EkinBinEdges[:-1]
vlsvReader = analysator.vlsvfile.VlsvReader(vlsvReader)

# initialize a new vlsvReader if passed a str filename of a .vlsv
if isinstance(vlsvReader,str):
vlsvReader = analysator.vlsvfile.VlsvReader(vlsvReader)
# ensure it is a VlsvReader obj instance otherwise
elif not isinstance(vlsvReader,analysator.vlsvfile.VlsvReader):
raise TypeError("Supplied vlsvReader must be either a VlsvReader obj instance or a filename str")
# else: keep the already initialized VlsvReader obj

# check if velocity space exists in this cell
if not vlsvReader.check_variable("moments"): # restart files have VDFs everywhere
if vlsvReader.check_variable("fSaved"):
Expand Down Expand Up @@ -124,7 +132,7 @@ def get_spectrum_energy(vlsvReader,
#Ekin[Ekin > max(EkinBinEdges)] = max(EkinBinEdges)

# compute histogram
(nhist,edges) = np.histogram(Ekin,bins=EkinBinEdges,weights=fw,normed=0)
(nhist,edges) = np.histogram(Ekin,bins=EkinBinEdges,weights=fw,density=False)

if (bindifferential): # finish differential flux per d(eV)
nhist = np.divide(nhist,dE)
Expand All @@ -151,7 +159,13 @@ def get_spectrum_alongaxis_vel(vlsvReader,
bindifferential=False, # weigh by d(velocity)
restart=True):

vlsvReader = analysator.vlsvfile.VlsvReader(vlsvReader)
# initialize a new vlsvReader if passed a str filename of a .vlsv
if isinstance(vlsvReader,str):
vlsvReader = analysator.vlsvfile.VlsvReader(vlsvReader)
# ensure it is a VlsvReader obj instance otherwise
elif not isinstance(vlsvReader,analysator.vlsvfile.VlsvReader):
raise TypeError("Supplied vlsvReader must be either a VlsvReader obj instance or a filename str")
# else: keep the already initialized VlsvReader obj

if vectorVar is not None and vector is None:
if vlsvReader.check_variable(vectorVar):
Expand Down Expand Up @@ -230,7 +244,7 @@ def get_spectrum_alongaxis_vel(vlsvReader,
latex=r'$f(\vec{r},v)$'
weight = 'particles'

(nhist,edges) = np.histogram(Vproj,bins=VBinEdges,weights=fw,normed=0)
(nhist,edges) = np.histogram(Vproj,bins=VBinEdges,weights=fw,density=False)
# normalization
dv = abs(VBinEdges[1:] - VBinEdges[:-1])
if (differential): # differential flux per [m/s]
Expand Down
2 changes: 1 addition & 1 deletion analysator/calculations/timeevolution.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ def __call__(self, t, coordinates, variable, operator='pass', method="linear", e
self.activeReaders['low'] = self.readers[max(rti-1,0)]

if self.activeReaders['hi']:
if lower_t == self.activeReaders['hi'].read_parameter('time'):
if upper_t == self.activeReaders['hi'].read_parameter('time'):
pass
else:
self.activeReaders['hi'] = self.readers[min(rti,len(self.ts)-1)]
Expand Down
15 changes: 14 additions & 1 deletion analysator/vlsvfile/vlsvreader.py
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,7 @@ def add_linked_reader(self, fname):
for reader in self.__linked_readers:
if fname == reader.file_name:
return
self.__linked_readers.add(VlsvReader(fname))
self.__linked_readers.add(VlsvReader(fname.strip()))
else:
logging.warning("Could not link "+fname+" (path does not exist)")

Expand Down Expand Up @@ -2491,6 +2491,19 @@ def read_variable_to_cache(self, name, operator="pass"):
# self.__read_fileindex_for_cellid()
return self.__variable_cache[(name,operator)]

def add_cached_variable(self, data, name, operator="pass"):
''' Add a variable to cache, for the whole grid and after applying
operator. Works by injecting a (variable,operator) key and the data to cache.

:param data: data array - make sure it is of the correct shape and size.
:param name: Name of the variable (or datareducer)
:param operator: Datareduction operator. "pass" does no operation on data.

'''

# add data to dict, use a tuple of (name,operator) as the key [tuples are immutable and hashable]
self.__variable_cache[(name,operator)] = data

def read_variable(self, name, cellids=-1,operator="pass"):
''' Read variables from the open vlsv file.
Arguments:
Expand Down
18 changes: 13 additions & 5 deletions analysator/vlsvfile/vlsvvtkinterface.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,21 @@ def __init__(self):

def SetFileName(self, filename):
if filename != self.__FileName:
self.Modified()
self.__FileName = filename
if self.__FileName is not None:
self.__reader = pt.vlsvfile.VlsvReader(self.__FileName)
fn = os.path.basename(self.__FileName)
if filename is not None:
if self.__reader is None:
self.__reader = pt.vlsvfile.VlsvReader(self.__FileName)
else:
raise ValueError("Tried to change an existing reader ("+self.__reader.file_name+") to " + filename)
self.Modified()
self.__FileName = filename
self.__metafile = os.path.join(self.__reader.get_cache_folder(),"vlsvvtkcache.pkl")

def SetReader(self, reader):
self.__reader = reader
self.Modified()
self.__FileName = reader.file_name
self.__metafile = os.path.join(self.__reader.get_cache_folder(),"vlsvvtkcache.pkl")

def GetFileName(self):
return self.__FileName

Expand Down
56 changes: 56 additions & 0 deletions containerization/lumi_container.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/bin/bash
#Make container.def
#Usage:
# Simply run this bash script to build the container, be sure to export PY_VERSION to set the python version desired, for example PY_VERSION=3.12
# After building one can run the script again to launch the container, alternatively call the .sif file directly with singularity
# For binding folders export SINGULARITY_BIND see for example below in # Running:

if [[ "$PY_VERSION" ]]; then
PY_PACKAGE=$(echo "$PY_VERSION" | sed 's/\.//g' )
else
PY_VERSION="3.12"
PY_PACKAGE="312"
fi

if [ ! -f ./container.sif ]; then
cat << EOF > container.def
Bootstrap: docker
From: docker.io/opensuse/leap:15.6

%post
# Continue to install software into the container normally.
# Building:
# module load LUMI systools
# singularity build container.sif container.def
# Running:
# For example launching python (one can also launch bash for example)
# singularity exec container.sif python
# to bind folders once can use --bind /opt,/data:/mnt
# this will bind /opt to /opt and /data to /mnt, alternatively one can use environment variables
# export SINGULARITY_BIND="/opt,/data:/mnt"

zypper -n --no-gpg-checks install python$PY_PACKAGE python$PY_PACKAGE-pip python$PY_PACKAGE-setuptools python$PY_PACKAGE-devel git

update-alternatives --install /usr/bin/python python /usr/bin/python$PY_VERSION 3
update-alternatives --install /usr/bin/python3 python3 /usr/bin/python$PY_VERSION 3
update-alternatives --install /usr/bin/pip pip /usr/bin/pip$PY_VERSION 3
update-alternatives --install /usr/bin/pip3 pip3 /usr/bin/pip$PY_VERSION 3

export PYTHONNOUSERSITE=1
pip install --extra-index-url https://version.helsinki.fi/api/v4/projects/5244/packages/pypi/simple vlsvrs
pip install --editable git+"https://github.com/fmihpc/analysator#egg=analysator"
EOF

#Build the container
module load LUMI systools
singularity build container.sif container.def
else
export PYTHONNOUSERSITE=1
if [[ ! $SINGULARITY_BIND ]]; then
echo "Friendly reminder: no folders binded to the container, please use SINGULARITY_BIND environment variable"
fi
singularity exec container.sif bash
fi



80 changes: 63 additions & 17 deletions scripts/magnetopause.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,16 @@


def write_vtk_surface_to_file(vtkSurface, outfilen):
writer = vtk.vtkXMLPolyDataWriter()
writer.SetInputConnection(vtkSurface.GetOutputPort())

writer = vtk.vtkXMLPolyDataWriter()
writer.SetFileName(outfilen)

try:
writer.SetInputConnection(vtkSurface.GetOutputPort())
except:
writer.SetInputData(vtkSurface)


writer.Write()
logging.info("wrote ", outfilen)

Expand All @@ -67,7 +74,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
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.0, 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 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
Expand All @@ -79,7 +86,7 @@ def magnetopause(datafilen, method="beta_star_with_connectivity", own_tresholds=
:kwarg return_SDF: True/False, return array of distances in m to SDF_points in point input order, negative distance inside the surface
:kwarg 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
:kwarg Delaunay_alpha: alpha (float) to give to vtkDelaunay3d, None -> convex hull, alpha=__: surface egdes longer than __ will be excluded
:kwarg beta_star_range: [min, max] treshold rage to use with methods "beta_star" and "beta_star_with_connectivity"
:kwarg beta_star_range: [min, max] treshold range to use with methods "beta_star" and "beta_star_with_connectivity"
:kwarg 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
"""
Expand Down Expand Up @@ -118,20 +125,59 @@ def magnetopause(datafilen, method="beta_star_with_connectivity", own_tresholds=

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])
np.save("pointcloud.npy", contour_coords)
except:
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])
vg_beta_star =f.read_variable("vg_beta_star")
vg_conn = f.read_variable("vg_connection")
vg_classifier = vg_beta_star*np.minimum(vg_conn,1) # if closed-closed fieldlines, set var to zero
if True:

vtkreader = pt.vlsvfile.VlsvVtkReader()
vtkreader.SetReader(f)
f.add_cached_variable(vg_classifier, "vg_betastar_classifier")
vtkreader.Update()

# vars =vtkreader.findVariablesFromVlsv(getReducers=False)
# add here more/other datareducer outputs for downstream use if needed
vars = ["vg_betastar_classifier", "cellid"]

for var in [v for v in vars if ("vg_" in v.lower()) or (v.lower() == "cellid")]:
vtkreader.addArrayFromVlsv(var)

vtkreader.Modified()
vtkreader.Update()
dataport = vtkreader.GetOutputPort()

dual = vtk.vtkHyperTreeGridToDualGrid()
dual.SetInputConnection(dataport)
vtkreader.Update()
dual.Update()

data = dual.GetOutputDataObject(0)

threshold0 = vtk.vtkThreshold()
threshold0.SetInputArrayToProcess(0,0,0, vtk.vtkDataObject.FIELD_ASSOCIATION_POINTS, "vg_betastar_classifier")
threshold0.SetInputData(data)
threshold0.SetLowerThreshold(beta_star_range[0])
threshold0.SetUpperThreshold(beta_star_range[1])
threshold0.Update()

vtkSurface, SDF = regions.vtkSDF(query_points, threshold0.GetOutputDataObject(0))

else:
betastar_region = regions.treshold_mask(vg_classifier, 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])
np.save("pointcloud.npy", contour_coords)
except:
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])

# make a convex hull surface with vtk's Delaunay
vtkSurface, SDF = regions.vtkDelaunay3d_SDF(query_points, contour_coords, Delaunay_alpha)
# 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
Expand Down
Loading
Loading