Numpy-like array slicing for fluids and elements added; get_n() function added for fluids and elements

This commit is contained in:
Nicolas 2025-09-03 15:52:38 +02:00
parent 4b12570dbe
commit f6444b1649
3 changed files with 92 additions and 34 deletions

View File

@ -7,7 +7,7 @@ from gaspype._phys_data import atomic_weights, db_reader
import re import re
import pkgutil import pkgutil
from .constants import R, epsy, p0 from .constants import R, epsy, p0
from .typing import FloatArray, NDFloat, Shape from .typing import FloatArray, NDFloat, Shape, ArrayIndices
T = TypeVar('T', 'fluid', 'elements') T = TypeVar('T', 'fluid', 'elements')
@ -483,6 +483,28 @@ class fluid:
assert set(species) <= set(self.fs.species), f'Species {", ".join([s for s in species if s not in self.fs.species])} is/are not part of the fluid system' assert set(species) <= set(self.fs.species), f'Species {", ".join([s for s in species if s not in self.fs.species])} is/are not part of the fluid system'
return self.array_fractions[..., [self.fs.species.index(k) for k in species]] return self.array_fractions[..., [self.fs.species.index(k) for k in species]]
def get_n(self, species: str | list[str] | None = None) -> FloatArray:
"""Get molar amount of fluid species
Args:
species: A single species name, a list of species names or None for
returning the amount of all species
Returns:
Returns an array of floats with the molar amount of the species.
If the a single species name is provided the return float array has
the same dimensions as the fluid type. If a list or None is provided
the return array has an additional dimension for the species.
"""
if not species:
return self.array_composition
elif isinstance(species, str):
assert species in self.fs.species, f'Species {species} is not part of the fluid system'
return self.array_composition[..., self.fs.species.index(species)]
else:
assert set(species) <= set(self.fs.species), f'Species {", ".join([s for s in species if s not in self.fs.species])} is/are not part of the fluid system'
return self.array_composition[..., [self.fs.species.index(k) for k in species]]
def __add__(self, other: T) -> T: def __add__(self, other: T) -> T:
return array_operation(self, other, np.add) return array_operation(self, other, np.add)
@ -510,16 +532,21 @@ class fluid:
# def __array__(self) -> FloatArray: # def __array__(self) -> FloatArray:
# return self.array_composition # return self.array_composition
def __getitem__(self, key: str | int | list[str] | list[int] | slice) -> FloatArray: @overload
def __getitem__(self, key: str) -> FloatArray:
pass
@overload
def __getitem__(self, key: ArrayIndices) -> 'fluid':
pass
def __getitem__(self, key: str | ArrayIndices) -> Any:
if isinstance(key, str): if isinstance(key, str):
assert key in self.fs.species, f'Species {key} is not part of the fluid system' assert key in self.fs.species, f'Species {key} is not part of the fluid system'
return self.array_composition[..., self.fs.species.index(key)] return self.array_composition[..., self.fs.species.index(key)]
elif isinstance(key, (slice, int)):
return self.array_composition[..., key]
else: else:
mset = set(self.fs.species) | set(range(len(self.fs.species))) key_tuple = key if isinstance(key, tuple) else (key,)
assert set(key) <= mset, f'Species {", ".join([str(s) for s in key if s not in mset])} is/are not part of the fluid system' return fluid(self.array_composition[(*key_tuple, slice(None))], self.fs)
return self.array_composition[..., [self.fs.species.index(k) if isinstance(k, str) else k for k in key]]
def __iter__(self) -> Iterator[dict[str, float]]: def __iter__(self) -> Iterator[dict[str, float]]:
assert len(self.shape) < 2, 'Cannot iterate over species with more than one dimension' assert len(self.shape) < 2, 'Cannot iterate over species with more than one dimension'
@ -614,6 +641,28 @@ class elements:
""" """
return np.sum(self.array_elemental_composition * self.fs.array_atomic_mass, axis=-1, dtype=NDFloat) return np.sum(self.array_elemental_composition * self.fs.array_atomic_mass, axis=-1, dtype=NDFloat)
def get_n(self, elemental_species: str | list[str] | None = None) -> FloatArray:
"""Get molar amount of elements
Args:
elemental_species: A single element name, a list of element names or None for
returning the amount of all element
Returns:
Returns an array of floats with the molar amount of the elements.
If the a single element name is provided the return float array has
the same dimensions as the fluid type. If a list or None is provided
the return array has an additional dimension for the elements.
"""
if not elemental_species:
return self.array_elemental_composition
elif isinstance(elemental_species, str):
assert elemental_species in self.fs.elements, f'Element {elemental_species} is not part of the fluid system'
return self.array_elemental_composition[..., self.fs.elements.index(elemental_species)]
else:
assert set(elemental_species) <= set(self.fs.elements), f'Elements {", ".join([s for s in elemental_species if s not in self.fs.elements])} is/are not part of the fluid system'
return self.array_elemental_composition[..., [self.fs.elements.index(k) for k in elemental_species]]
def __add__(self, other: 'fluid | elements') -> 'elements': def __add__(self, other: 'fluid | elements') -> 'elements':
return array_operation(self, other, np.add) return array_operation(self, other, np.add)
@ -639,16 +688,21 @@ class elements:
def __array__(self) -> FloatArray: def __array__(self) -> FloatArray:
return self.array_elemental_composition return self.array_elemental_composition
def __getitem__(self, key: str | int | list[str] | list[int] | slice) -> FloatArray: @overload
def __getitem__(self, key: str) -> FloatArray:
pass
@overload
def __getitem__(self, key: ArrayIndices) -> 'elements':
pass
def __getitem__(self, key: str | ArrayIndices) -> Any:
if isinstance(key, str): if isinstance(key, str):
assert key in self.fs.elements, f'Element {key} is not part of the fluid system' assert key in self.fs.elements, f'Element {key} is not part of the fluid system'
return self.array_elemental_composition[..., self.fs.elements.index(key)] return self.array_elemental_composition[..., self.fs.elements.index(key)]
elif isinstance(key, (slice, int)):
return self.array_elemental_composition[..., key]
else: else:
mset = set(self.fs.elements) | set(range(len(self.fs.elements))) key_tuple = key if isinstance(key, tuple) else (key,)
assert set(key) <= mset, f'Elements {", ".join([str(s) for s in key if s not in mset])} is/are not part of the fluid system' return elements(self.array_elemental_composition[(*key_tuple, slice(None))], self.fs)
return self.array_elemental_composition[..., [self.fs.elements.index(k) if isinstance(k, str) else k for k in key]]
def __iter__(self) -> Iterator[dict[str, float]]: def __iter__(self) -> Iterator[dict[str, float]]:
assert len(self.shape) < 2, 'Cannot iterate over elements with more than one dimension' assert len(self.shape) < 2, 'Cannot iterate over elements with more than one dimension'

View File

@ -1,6 +1,10 @@
from numpy import float64 from numpy import float64
from numpy.typing import NDArray from numpy.typing import NDArray
from typing import Sequence
from types import EllipsisType
Shape = tuple[int, ...] Shape = tuple[int, ...]
NDFloat = float64 NDFloat = float64
FloatArray = NDArray[NDFloat] FloatArray = NDArray[NDFloat]
ArrayIndex = int | slice | None | EllipsisType | Sequence[int]
ArrayIndices = ArrayIndex | tuple[ArrayIndex, ...]

View File

@ -12,28 +12,28 @@ def test_str_index():
assert el['C'].shape == (2, 3, 4) assert el['C'].shape == (2, 3, 4)
def test_str_list_index(): def test_single_axis_int_index():
assert fl[['CO2', 'H2', 'CO']].shape == (2, 3, 4, 3) assert fl[0].shape == (3, 4)
assert el[['C', 'H', 'O']].shape == (2, 3, 4, 3) assert fl[1].shape == (3, 4)
assert el[1].shape == (3, 4)
assert el[0].shape == (3, 4)
def test_int_list_index(): def test_single_axis_int_list():
assert fl[[1, 2, 0, 5]].shape == (2, 3, 4, 4) assert fl[:, [0, 1]].shape == (2, 2, 4)
assert el[[1, 2, 0, 3]].shape == (2, 3, 4, 4) assert el[:, [0, 1]].shape == (2, 2, 4)
def test_mixed_list_index(): def test_multi_axis_int_index():
assert el[[1, 'H', 0, 'O']].shape == (2, 3, 4, 4) assert fl[0, 1].shape == (4,)
assert fl[0, 1, 2].shape == tuple()
assert fl[0, 2].shape == (4,)
def test_int_index(): assert fl[:, 2, :].shape == (2, 4)
assert fl[5].shape == (2, 3, 4) assert fl[0, [1, 2]].shape == (2, 4)
assert el[-1].shape == (2, 3, 4) assert fl[..., 0].shape == (2, 3)
assert el[0, 1].shape == (4,)
assert el[0, 1, 2].shape == tuple()
def test_slice_index(): assert el[0, 2].shape == (4,)
assert fl[0:3].shape == (2, 3, 4, 3) assert el[:, 2, :].shape == (2, 4)
assert fl[:].shape == (2, 3, 4, 6) assert el[0, [1, 2]].shape == (2, 4)
assert el[..., 0].shape == (2, 3)
assert el[0:3].shape == (2, 3, 4, 3)
assert el[:].shape == (2, 3, 4, 4)