osiris-utils 1.1.4__tar.gz → 1.1.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. {osiris_utils-1.1.4/osiris_utils.egg-info → osiris_utils-1.1.6}/PKG-INFO +1 -1
  2. {osiris_utils-1.1.4 → osiris_utils-1.1.6}/osiris_utils/__init__.py +8 -2
  3. {osiris_utils-1.1.4 → osiris_utils-1.1.6}/osiris_utils/data/data.py +316 -42
  4. {osiris_utils-1.1.4 → osiris_utils-1.1.6}/osiris_utils/data/diagnostic.py +691 -233
  5. {osiris_utils-1.1.4 → osiris_utils-1.1.6}/osiris_utils/data/simulation.py +30 -17
  6. {osiris_utils-1.1.4 → osiris_utils-1.1.6}/osiris_utils/postprocessing/derivative.py +29 -49
  7. {osiris_utils-1.1.4 → osiris_utils-1.1.6}/osiris_utils/postprocessing/fft.py +8 -14
  8. osiris_utils-1.1.6/osiris_utils/postprocessing/field_centering.py +168 -0
  9. osiris_utils-1.1.6/osiris_utils/postprocessing/heatflux_correction.py +193 -0
  10. {osiris_utils-1.1.4 → osiris_utils-1.1.6}/osiris_utils/postprocessing/mft.py +14 -28
  11. osiris_utils-1.1.6/osiris_utils/postprocessing/pressure_correction.py +171 -0
  12. osiris_utils-1.1.6/osiris_utils/utils.py +283 -0
  13. {osiris_utils-1.1.4 → osiris_utils-1.1.6/osiris_utils.egg-info}/PKG-INFO +1 -1
  14. {osiris_utils-1.1.4 → osiris_utils-1.1.6}/osiris_utils.egg-info/SOURCES.txt +4 -1
  15. {osiris_utils-1.1.4 → osiris_utils-1.1.6}/setup.py +1 -1
  16. osiris_utils-1.1.4/osiris_utils/utils.py +0 -144
  17. {osiris_utils-1.1.4 → osiris_utils-1.1.6}/LICENSE.txt +0 -0
  18. {osiris_utils-1.1.4 → osiris_utils-1.1.6}/MANIFEST.in +0 -0
  19. {osiris_utils-1.1.4 → osiris_utils-1.1.6}/README.rst +0 -0
  20. {osiris_utils-1.1.4 → osiris_utils-1.1.6}/osiris_utils/data/__init__.py +0 -0
  21. {osiris_utils-1.1.4 → osiris_utils-1.1.6}/osiris_utils/decks/__init__.py +0 -0
  22. {osiris_utils-1.1.4 → osiris_utils-1.1.6}/osiris_utils/decks/decks.py +0 -0
  23. {osiris_utils-1.1.4 → osiris_utils-1.1.6}/osiris_utils/decks/species.py +0 -0
  24. {osiris_utils-1.1.4 → osiris_utils-1.1.6}/osiris_utils/gui/__init__.py +0 -0
  25. {osiris_utils-1.1.4 → osiris_utils-1.1.6}/osiris_utils/gui/gui.py +0 -0
  26. {osiris_utils-1.1.4 → osiris_utils-1.1.6}/osiris_utils/postprocessing/__init__.py +0 -0
  27. {osiris_utils-1.1.4 → osiris_utils-1.1.6}/osiris_utils/postprocessing/mft_for_gridfile.py +0 -0
  28. {osiris_utils-1.1.4 → osiris_utils-1.1.6}/osiris_utils/postprocessing/postprocess.py +0 -0
  29. {osiris_utils-1.1.4 → osiris_utils-1.1.6}/osiris_utils.egg-info/dependency_links.txt +0 -0
  30. {osiris_utils-1.1.4 → osiris_utils-1.1.6}/osiris_utils.egg-info/requires.txt +0 -0
  31. {osiris_utils-1.1.4 → osiris_utils-1.1.6}/osiris_utils.egg-info/top_level.txt +0 -0
  32. {osiris_utils-1.1.4 → osiris_utils-1.1.6}/pyproject.toml +0 -0
  33. {osiris_utils-1.1.4 → osiris_utils-1.1.6}/requirements.txt +0 -0
  34. {osiris_utils-1.1.4 → osiris_utils-1.1.6}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: osiris_utils
3
- Version: 1.1.4
3
+ Version: 1.1.6
4
4
  Summary: Utilities to manipulate and visualize OSIRIS framework output data
5
5
  Author: ['João Pedro Ferreira Biu', 'João Cândido', 'Diogo Carvalho']
6
6
  Author-email: ['joaopedrofbiu@tecnico.ulisboa.pt']
@@ -8,7 +8,7 @@ from .utils import (
8
8
  courant2D,
9
9
  )
10
10
  from .gui.gui import LAVA_Qt, LAVA
11
- from .data.data import OsirisGridFile, OsirisRawFile, OsirisData, OsirisHIST
11
+ from .data.data import OsirisGridFile, OsirisRawFile, OsirisData, OsirisHIST, OsirisTrackFile
12
12
  from .data.simulation import Simulation, Species_Handler
13
13
  from .data.diagnostic import Diagnostic
14
14
 
@@ -25,4 +25,10 @@ from .postprocessing.mft import (
25
25
  MFT_Diagnostic,
26
26
  MFT_Diagnostic_Average,
27
27
  MFT_Diagnostic_Fluctuations,
28
- )
28
+ )
29
+
30
+ from .postprocessing.field_centering import FieldCentering_Simulation, FieldCentering_Diagnostic
31
+
32
+ from .postprocessing.pressure_correction import PressureCorrection_Simulation, PressureCorrection_Diagnostic
33
+
34
+ from .postprocessing.heatflux_correction import HeatfluxCorrection_Simulation, HeatfluxCorrection_Diagnostic
@@ -1,6 +1,8 @@
1
1
  import numpy as np
2
2
  import pandas as pd
3
3
  import h5py
4
+ from osiris_utils.utils import create_file_tags
5
+ from typing import Literal
4
6
 
5
7
  class OsirisData():
6
8
  """
@@ -343,42 +345,65 @@ class OsirisRawFile(OsirisData):
343
345
  '''
344
346
  Class to read the raw data from an OSIRIS HDF5 file.
345
347
 
346
- Input:
347
- - filename: the path to the HDF5 file
348
+ Parameters
349
+ ----------
350
+ filename : str
351
+ Path to OSIRIS HDF5 track file (.h5 extension)
348
352
 
349
353
  Attributes:
350
- - axis - a dictionary where each key is a dataset name, and each value is another dictionary containing
351
- name (str): The name of the quantity (e.g., r'x1', r'ene').
352
- units (str): The units associated with that dataset in LaTeX (e.g., r'c/\\omega_p', r'm_e c^2').
353
- long_name (str): The name of the quantity in LaTeX (e.g., r'x_1', r'En2').
354
- dictionary of dictionaries
355
- - data - a dictionary where each key is a dataset name, and each value is the data
356
- dictionary of np.arrays
357
- - dim - the number of dimensions
358
- int
359
- - dt - the time step
360
- float
361
- - grid - maximum and minimum coordinates of the box, for each axis
362
- numpy.ndarray(dim,2)
363
- - iter - the iteration number
364
- int
365
- - name - the name of the species
366
- str
367
- - time - the time and its units
368
- list [time, units]
369
- list [float, str]
370
- - type - type of data (particles in the case of raw files)
371
- str
372
-
373
- '''
354
+ -----------
355
+ axis : dict[str, dict[str, str]]
356
+ Dictionary where each key is a dataset name, and each value is another dictionary containing:
357
+ - 'name' (str): Short name of the quantity (e.g., 'x1', 'ene')
358
+ - 'units' (str): Units (LaTeX formatted, e.g., 'c/\\omega_p', 'm_e c^2')
359
+ - 'long_name' (str): Descriptive name (LaTeX formatted, e.g., 'x_1', 'En2')
360
+ data : dict[str, np.ndarray]
361
+ Dataset values indexed by dataset name (quants).
362
+ dim : int
363
+ Number of spatial dimensions.
364
+ dt : float
365
+ Time step between iterations.
366
+ grid : np.ndarray
367
+ Grid boundaries as ((x1_min, x1_max), (x2_min, x2_max), ...)
368
+ iter : int
369
+ Iteration number corresponding to the data.
370
+ name : str
371
+ Name of the species.
372
+ time : list[float, str]
373
+ Simulation time and its units (e.g., [12.5, '1/\\omega_p']).
374
+ type : str
375
+ Type of data (e.g., 'particles' for raw files).
376
+ labels : list[str]
377
+ Field labels/names (LaTeX formatted, e.g., 'x_1')
378
+ quants : list[str]
379
+ field names of the data
380
+ units : list[str]
381
+ Units of each field of the data (LaTeX formatted, e.g., 'c/\\omega_p')
374
382
 
383
+ Example
384
+ -------
385
+ >>> import osiris_utils as ou
386
+ >>> raw = ou.raw = ou.OsirisRawFile("path/to/raw/file.h5")
387
+ >>> print(raw.data.keys())
388
+ >>> # Access x1 position of first 10 particles
389
+ >>> print(raw.data[\"x1\"][0:10])
390
+ >>> # Write beautiful labels and units
391
+ >>> print("${} = $".format(raw.labels[\"x1\"]) + "$[{}]$".format(track.units[\"x1\"]))
392
+ '''
393
+
375
394
  def __init__(self, filename):
376
395
  super().__init__(filename)
377
396
 
378
- self.grid = np.array([self._file['SIMULATION'].attrs['XMIN'], self._file['SIMULATION'].attrs['XMAX']]).T
397
+ self._grid = np.array([self._file['SIMULATION'].attrs['XMIN'], self._file['SIMULATION'].attrs['XMAX']]).T
379
398
 
380
- self.data = {}
381
- self.axis = {}
399
+ self._quants = [byte.decode('utf-8') for byte in self._file.attrs['QUANTS'][:]]
400
+ units_list = [byte.decode('utf-8') for byte in self._file.attrs['UNITS'][:]]
401
+ labels_list = [byte.decode('utf-8') for byte in self._file.attrs['LABELS'][:]]
402
+ self._units = dict(zip(self._quants, units_list))
403
+ self._labels = dict(zip(self._quants, labels_list))
404
+
405
+ self._data = {}
406
+ self._axis = {}
382
407
  for key in self._file.keys():
383
408
  if key == 'SIMULATION': continue
384
409
 
@@ -390,29 +415,278 @@ class OsirisRawFile(OsirisData):
390
415
  'units': self._file.attrs['UNITS'][idx][0].decode('utf-8'),
391
416
  'long_name': self._file.attrs['LABELS'][idx][0].decode('utf-8'),
392
417
  }
393
- self.axis[key] = axis_data
418
+ self._axis[key] = axis_data
419
+
420
+ def raw_to_file_tags(self, filename, type: Literal["all", "random"] = "all", n_tags=10, mask=None):
421
+ """
422
+ Function to write a file_tags file from raw data.
423
+ this file is used to choose particles for the OSIRIS track diagnostic.
424
+
425
+ Parameters
426
+ ----------
427
+ filename : str
428
+ Path to the output file where tags will be stored.
429
+ type : {'all', 'random'}, optional
430
+ Selection mode for tags:
431
+ - 'all': Includes all available tags.
432
+ - 'random': Randomly selects `n_tags` tags.
433
+ n_tags : int, optional
434
+ Number of tags to randomly select when `type` is 'random'. Default is 10.
435
+ mask : np.ndarray, optional
436
+ Boolean mask array applied to filter valid tags before selection.
437
+
438
+ Returns
439
+ ------
440
+ A file_tags file with path \"filename\" to be used for the OSIRIS track diagnostic.
441
+
442
+ Notes
443
+ -----
444
+ The first element of the tag of a particle that is already being tracked is negative,
445
+ so we apply the absolute function when generating the file
446
+
447
+ """
448
+
449
+ if mask is not None:
450
+ # Apply mask to select certain tags
451
+ if not isinstance(mask, np.ndarray) or mask.dtype != bool or mask.shape[0] != self.data["tag"].shape[0]:
452
+ raise ValueError("Mask must be a boolean NumPy array of the same length as 'tag'.")
453
+ filtered_indices = np.where(mask)[0]
454
+ filtered_tags = self.data["tag"][filtered_indices]
455
+ else:
456
+ filtered_tags = self.data["tag"]
457
+
458
+ if type == "all":
459
+ tags = filtered_tags
460
+ elif type == "random":
461
+ if len(filtered_tags) < n_tags:
462
+ raise ValueError("Not enough tags to sample from.")
463
+ random_indices = np.random.choice(len(filtered_tags), size=n_tags, replace=False)
464
+ tags = filtered_tags[random_indices]
465
+ else:
466
+ raise TypeError("Invalid type", type)
467
+
468
+ create_file_tags(filename, tags)
469
+ print("Tag_file created: ", filename)
470
+
471
+ # Getters
472
+ @property
473
+ def grid(self):
474
+ return self._grid
475
+ @property
476
+ def data(self):
477
+ return self._data
478
+ @property
479
+ def units(self):
480
+ return self._units
481
+ @property
482
+ def labels(self):
483
+ return self._labels
484
+ @property
485
+ def quants(self):
486
+ return self._quants
487
+ @property
488
+ def axis(self):
489
+ return self._axis
394
490
 
395
491
  class OsirisHIST(OsirisData):
396
492
  ''''
397
493
  Class to read the data from an OSIRIS HIST file.'
398
494
 
399
- Input:
400
- - filename: the path to the HIST file
495
+ Input
496
+ -----
497
+ filename: the path to the HIST file
401
498
 
402
- Attributes:
403
- - filename - the path to the file
404
- str
405
- - verbose - if True, the class will print messages
406
- bool
407
- - df - the data in a pandas DataFrame
408
- pandas.DataFrame
499
+ Attributes
500
+ ----------
501
+ filename: the path to the file
502
+ str
503
+ df: the data in a pandas DataFrame
504
+ pandas.DataFrame
409
505
  '''
410
506
  def __init__(self, filename):
411
507
  super().__init__(filename)
412
508
 
413
509
  @property
414
510
  def df(self):
415
- """
416
- Returns the data in a pandas DataFrame
417
- """
418
511
  return self._df
512
+
513
+ class OsirisTrackFile(OsirisData):
514
+ """
515
+ Handles structured track data from OSIRIS HDF5 simulations.
516
+
517
+ Parameters
518
+ ----------
519
+ filename : str
520
+ Path to OSIRIS HDF5 track file (.h5 extension)
521
+
522
+ Attributes
523
+ ----------
524
+ data: numpy.ndarray of shape (num_particles, num_time_iter),
525
+ dtype = [(field_name, float) for field_name in field_names]
526
+ A structured numpy array with the track data
527
+ Accessed as data[particles, time_iters][quant]
528
+ grid : np.ndarray
529
+ Grid boundaries as ((x1_min, x1_max), (x2_min, x2_max), ...)
530
+ labels : list[str]
531
+ Field labels/names (LaTeX formatted, e.g., 'x_1')
532
+ num_particles : int
533
+ Number of particlest tracked, they are accessed from 0 to num_particles-1
534
+ num_time_iters : int
535
+ Number of time iteratis, they are accessed from 0 to num_time_iters-1
536
+ quants : list[str]
537
+ field names of the data
538
+ units : list[str]
539
+ Units of each field of the data (LaTeX formatted, e.g., 'c/\\omega_p')
540
+
541
+ Example
542
+ -------
543
+ >>> import osiris_utils as ou
544
+ >>> track = ou.OsirisTrackFile(path/to/track_file.h5)
545
+ >>> print(track.data[0:10, :]["x1"]) # Access x1 position of first 10 particles over all time steps
546
+ """
547
+
548
+ def __init__(self, filename):
549
+ super().__init__(filename)
550
+
551
+ self._grid = np.array([self._file['SIMULATION'].attrs['XMIN'], self._file['SIMULATION'].attrs['XMAX']]).T
552
+
553
+ self._quants = [byte.decode('utf-8') for byte in self._file.attrs['QUANTS'][1:]]
554
+ units_list = [byte.decode('utf-8') for byte in self._file.attrs['UNITS'][1:]]
555
+ labels_list = [byte.decode('utf-8') for byte in self._file.attrs['LABELS'][1:]]
556
+ self._units = dict(zip(self._quants, units_list))
557
+ self._labels = dict(zip(self._quants, labels_list))
558
+
559
+ self._num_particles = self._file.attrs['NTRACKS'][0]
560
+
561
+ unordered_data = self._file['data'][:]
562
+ itermap = self._file['itermap'][:]
563
+
564
+ idxs = get_track_indexes(itermap, self._num_particles)
565
+ self._data = reorder_track_data(unordered_data, idxs, self._quants)
566
+ self._time = self._data[0][:]["t"]
567
+ self._num_time_iters = np.shape(self._time.shape)
568
+ self._close_file()
569
+
570
+ def _load_basic_attributes(self, f: h5py.File) -> None:
571
+ '''Load common attributes from HDF5 file'''
572
+ self._dt = float(f['SIMULATION'].attrs['DT'][0])
573
+ self._dim = int(f['SIMULATION'].attrs['NDIMS'][0])
574
+ self._time = None
575
+ self._iter = None
576
+ self._name = f.attrs['NAME'][0].decode('utf-8')
577
+ self._type = f.attrs['TYPE'][0].decode('utf-8')
578
+
579
+
580
+ # Getters
581
+ @property
582
+ def grid(self):
583
+ return self._grid
584
+ @property
585
+ def data(self):
586
+ return self._data
587
+ @property
588
+ def units(self):
589
+ return self._units
590
+ @property
591
+ def labels(self):
592
+ return self._labels
593
+ @property
594
+ def quants(self):
595
+ return self._quants
596
+ @property
597
+ def num_particles(self):
598
+ return self._num_particles
599
+ @property
600
+ def num_time_iters(self):
601
+ return self._num_time_iters
602
+
603
+
604
+ # Setters
605
+ @data.setter
606
+ def data(self, data):
607
+ self._data = data
608
+
609
+ def __str__(self):
610
+ # write me a template to print with the name, label, units, iter, grid, nx, dx, axis, dt, dim in a logical way
611
+ return rf'{self.name}' + f'\n' + f'Iteration: {self.iter}' + f'\n' + f'Grid: {self.grid}' + f'\n' + f'dx: {self.dx}' + f'\n' + f'Dimensions: {self.dim}D'
612
+
613
+ def __array__(self):
614
+ return np.asarray(self.data)
615
+
616
+
617
+
618
+ def reorder_track_data(unordered_data, indexes, field_names):
619
+ '''
620
+ Reorder data from HDF5 track file such data it can be accessed more intuitively
621
+
622
+ Parameters
623
+ ----------
624
+ unordered_data: np.array
625
+ The data from a HDF5 osiris track file
626
+
627
+ indexes : list[list[int]]
628
+ Output of get_track_indexes(), list with the indexes associated with each particle
629
+
630
+ field_names: list[str]
631
+ Names for the quantities on the output file.
632
+ Recommended: field_names = [byte.decode('utf-8') for byte in file.attrs['QUANTS'][1:]]
633
+
634
+ Returns
635
+ -------
636
+ data_sorted: numpy.ndarray of shape (num_particles, num_time_iter),
637
+ dtype = [(field_name, float) for field_name in field_names]
638
+ A structured numpy array where data is reordered according to indexes.
639
+
640
+ '''
641
+ # Initialize the sorted data structure
642
+ num_particles = len(indexes)
643
+ num_time_iter = len(indexes[0])
644
+ data_sorted = np.empty((num_particles, num_time_iter), dtype=[(name, float) for name in field_names])
645
+
646
+ # Fill the sorted data based on the indexes
647
+ for particle in range(num_particles):
648
+ for time_iter in range(num_time_iter):
649
+ index = indexes[particle][time_iter]
650
+ if len(unordered_data[index]) != len(field_names):
651
+ raise ValueError(f"Data at index {index} has {len(unordered_data[index])} elements, "
652
+ f"but {len(field_names)} are expected.")
653
+ data_sorted[particle, time_iter] = tuple(unordered_data[index])
654
+
655
+ return (data_sorted)
656
+
657
+
658
+ def get_track_indexes(itermap, num_particles):
659
+ '''
660
+ Returns the indexes for each particle to read track data directly from the hd5 file
661
+ (before it is ordered)
662
+
663
+ Parameters
664
+ ----------
665
+ itermap: np.array
666
+ Itermap from a HDF5 osiris track file
667
+ num_particles: int
668
+ num of particles tracked, recomended file.attrs['NTRACKS'][0]
669
+
670
+ Returns
671
+ -------
672
+ indexes : list[list[int]]
673
+ Returns a list with the indexes associated with each particle
674
+ shape(num_particles, num_time_iters)
675
+ '''
676
+
677
+ itermapshape = itermap.shape
678
+ for i in range(itermapshape[0]):
679
+ part_number,npoints,nstart = itermap[i,:]
680
+ track_indices = np.zeros(num_particles)
681
+
682
+ data_index = 0
683
+ indexes = [[] for _ in range(num_particles)]
684
+ for i in range(itermapshape[0]):
685
+ part_number,npoints,nstart = itermap[i,:]
686
+
687
+ indexes[part_number-1].extend(list(range(data_index, data_index + npoints)))
688
+
689
+ data_index += npoints
690
+ track_indices[part_number-1] += npoints
691
+
692
+ return indexes