PySDM.backends.numba
Multi-threaded CPU backend using LLVM-powered just-in-time compilation
1""" 2Multi-threaded CPU backend using LLVM-powered just-in-time compilation 3""" 4 5import os 6import platform 7import warnings 8 9import numba 10 11from PySDM.backends.impl_numba import methods 12from PySDM.backends.impl_numba.random import Random as ImportedRandom 13from PySDM.backends.impl_numba.storage import Storage as ImportedStorage 14from PySDM.formulae import Formulae 15from PySDM.backends.impl_numba.conf import JIT_FLAGS 16 17 18class Numba( # pylint: disable=too-many-ancestors,duplicate-code 19 methods.CollisionsMethods, 20 methods.FragmentationMethods, 21 methods.PairMethods, 22 methods.IndexMethods, 23 methods.PhysicsMethods, 24 methods.CondensationMethods, 25 methods.ChemistryMethods, 26 methods.MomentsMethods, 27 methods.FreezingMethods, 28 methods.DisplacementMethods, 29 methods.TerminalVelocityMethods, 30 methods.IsotopeMethods, 31 methods.SeedingMethods, 32 methods.DepositionMethods, 33): 34 Storage = ImportedStorage 35 Random = ImportedRandom 36 37 default_croupier = "local" 38 39 def __init__(self, formulae=None, double_precision=True, override_jit_flags=None): 40 if not double_precision: 41 raise NotImplementedError() 42 self.formulae = formulae or Formulae() 43 self.formulae_flattened = self.formulae.flatten 44 45 parallel_default = True 46 if platform.machine() == "arm64": 47 if "CI" not in os.environ: 48 warnings.warn( 49 "Disabling Numba threading due to ARM64 CPU (atomics do not work yet)" 50 ) 51 parallel_default = False # TODO #1183 - atomics don't work on ARM64! 52 53 try: 54 numba.parfors.parfor.ensure_parallel_support() 55 except numba.core.errors.UnsupportedParforsError: 56 if "CI" not in os.environ: 57 warnings.warn( 58 "Numba version used does not support parallel for (32 bits?)" 59 ) 60 parallel_default = False 61 62 assert "fastmath" not in (override_jit_flags or {}) 63 self.default_jit_flags = { 64 **JIT_FLAGS, # here parallel=False (for out-of-backend code) 65 **{"fastmath": self.formulae.fastmath, "parallel": parallel_default}, 66 **(override_jit_flags or {}), 67 } 68 69 methods.CollisionsMethods.__init__(self) 70 methods.FragmentationMethods.__init__(self) 71 methods.PairMethods.__init__(self) 72 methods.IndexMethods.__init__(self) 73 methods.PhysicsMethods.__init__(self) 74 methods.CondensationMethods.__init__(self) 75 methods.ChemistryMethods.__init__(self) 76 methods.MomentsMethods.__init__(self) 77 methods.FreezingMethods.__init__(self) 78 methods.DisplacementMethods.__init__(self) 79 methods.TerminalVelocityMethods.__init__(self) 80 methods.IsotopeMethods.__init__(self) 81 methods.SeedingMethods.__init__(self) 82 methods.DepositionMethods.__init__(self)
class
Numba(PySDM.backends.impl_numba.methods.collisions_methods.CollisionsMethods, PySDM.backends.impl_numba.methods.fragmentation_methods.FragmentationMethods, PySDM.backends.impl_numba.methods.pair_methods.PairMethods, PySDM.backends.impl_numba.methods.index_methods.IndexMethods, PySDM.backends.impl_numba.methods.physics_methods.PhysicsMethods, PySDM.backends.impl_numba.methods.condensation_methods.CondensationMethods, PySDM.backends.impl_numba.methods.chemistry_methods.ChemistryMethods, PySDM.backends.impl_numba.methods.moments_methods.MomentsMethods, PySDM.backends.impl_numba.methods.freezing_methods.FreezingMethods, PySDM.backends.impl_numba.methods.displacement_methods.DisplacementMethods, PySDM.backends.impl_numba.methods.terminal_velocity_methods.TerminalVelocityMethods, PySDM.backends.impl_numba.methods.isotope_methods.IsotopeMethods, PySDM.backends.impl_numba.methods.seeding_methods.SeedingMethods, PySDM.backends.impl_numba.methods.deposition_methods.DepositionMethods):
19class Numba( # pylint: disable=too-many-ancestors,duplicate-code 20 methods.CollisionsMethods, 21 methods.FragmentationMethods, 22 methods.PairMethods, 23 methods.IndexMethods, 24 methods.PhysicsMethods, 25 methods.CondensationMethods, 26 methods.ChemistryMethods, 27 methods.MomentsMethods, 28 methods.FreezingMethods, 29 methods.DisplacementMethods, 30 methods.TerminalVelocityMethods, 31 methods.IsotopeMethods, 32 methods.SeedingMethods, 33 methods.DepositionMethods, 34): 35 Storage = ImportedStorage 36 Random = ImportedRandom 37 38 default_croupier = "local" 39 40 def __init__(self, formulae=None, double_precision=True, override_jit_flags=None): 41 if not double_precision: 42 raise NotImplementedError() 43 self.formulae = formulae or Formulae() 44 self.formulae_flattened = self.formulae.flatten 45 46 parallel_default = True 47 if platform.machine() == "arm64": 48 if "CI" not in os.environ: 49 warnings.warn( 50 "Disabling Numba threading due to ARM64 CPU (atomics do not work yet)" 51 ) 52 parallel_default = False # TODO #1183 - atomics don't work on ARM64! 53 54 try: 55 numba.parfors.parfor.ensure_parallel_support() 56 except numba.core.errors.UnsupportedParforsError: 57 if "CI" not in os.environ: 58 warnings.warn( 59 "Numba version used does not support parallel for (32 bits?)" 60 ) 61 parallel_default = False 62 63 assert "fastmath" not in (override_jit_flags or {}) 64 self.default_jit_flags = { 65 **JIT_FLAGS, # here parallel=False (for out-of-backend code) 66 **{"fastmath": self.formulae.fastmath, "parallel": parallel_default}, 67 **(override_jit_flags or {}), 68 } 69 70 methods.CollisionsMethods.__init__(self) 71 methods.FragmentationMethods.__init__(self) 72 methods.PairMethods.__init__(self) 73 methods.IndexMethods.__init__(self) 74 methods.PhysicsMethods.__init__(self) 75 methods.CondensationMethods.__init__(self) 76 methods.ChemistryMethods.__init__(self) 77 methods.MomentsMethods.__init__(self) 78 methods.FreezingMethods.__init__(self) 79 methods.DisplacementMethods.__init__(self) 80 methods.TerminalVelocityMethods.__init__(self) 81 methods.IsotopeMethods.__init__(self) 82 methods.SeedingMethods.__init__(self) 83 methods.DepositionMethods.__init__(self)
Numba(formulae=None, double_precision=True, override_jit_flags=None)
40 def __init__(self, formulae=None, double_precision=True, override_jit_flags=None): 41 if not double_precision: 42 raise NotImplementedError() 43 self.formulae = formulae or Formulae() 44 self.formulae_flattened = self.formulae.flatten 45 46 parallel_default = True 47 if platform.machine() == "arm64": 48 if "CI" not in os.environ: 49 warnings.warn( 50 "Disabling Numba threading due to ARM64 CPU (atomics do not work yet)" 51 ) 52 parallel_default = False # TODO #1183 - atomics don't work on ARM64! 53 54 try: 55 numba.parfors.parfor.ensure_parallel_support() 56 except numba.core.errors.UnsupportedParforsError: 57 if "CI" not in os.environ: 58 warnings.warn( 59 "Numba version used does not support parallel for (32 bits?)" 60 ) 61 parallel_default = False 62 63 assert "fastmath" not in (override_jit_flags or {}) 64 self.default_jit_flags = { 65 **JIT_FLAGS, # here parallel=False (for out-of-backend code) 66 **{"fastmath": self.formulae.fastmath, "parallel": parallel_default}, 67 **(override_jit_flags or {}), 68 } 69 70 methods.CollisionsMethods.__init__(self) 71 methods.FragmentationMethods.__init__(self) 72 methods.PairMethods.__init__(self) 73 methods.IndexMethods.__init__(self) 74 methods.PhysicsMethods.__init__(self) 75 methods.CondensationMethods.__init__(self) 76 methods.ChemistryMethods.__init__(self) 77 methods.MomentsMethods.__init__(self) 78 methods.FreezingMethods.__init__(self) 79 methods.DisplacementMethods.__init__(self) 80 methods.TerminalVelocityMethods.__init__(self) 81 methods.IsotopeMethods.__init__(self) 82 methods.SeedingMethods.__init__(self) 83 methods.DepositionMethods.__init__(self)
Inherited Members
- PySDM.backends.impl_numba.methods.collisions_methods.CollisionsMethods
- adaptive_sdm_end
- scale_prob_for_adaptive_sdm_gamma
- cell_id
- collision_coalescence
- collision_coalescence_breakup
- compute_gamma
- make_cell_caretaker
- normalize
- remove_zero_n_or_flagged
- linear_collection_efficiency
- PySDM.backends.impl_numba.methods.fragmentation_methods.FragmentationMethods
- fragmentation_limiters
- slams_fragmentation
- exp_fragmentation
- feingold1988_fragmentation
- gauss_fragmentation
- straub_fragmentation
- ll82_fragmentation
- ll82_coalescence_check
- PySDM.backends.impl_numba.methods.pair_methods.PairMethods
- distance_pair
- find_pairs
- max_pair
- min_pair
- sort_pair
- sort_within_pair_by_attr
- sum_pair
- multiply_pair
- PySDM.backends.impl_numba.methods.index_methods.IndexMethods
- identity_index
- shuffle_global
- shuffle_local
- sort_by_key
- PySDM.backends.impl_numba.methods.physics_methods.PhysicsMethods
- critical_volume
- temperature_pressure_rh
- a_w_ice
- volume_of_water_mass
- mass_of_water_volume
- air_density
- air_dynamic_viscosity
- reynolds_number
- explicit_euler
- PySDM.backends.impl_numba.methods.condensation_methods.CondensationMethods
- condensation
- make_adapt_substeps
- make_step_fake
- make_step
- make_step_impl
- make_calculate_ml_old
- make_calculate_ml_new
- make_condensation_solver
- make_condensation_solver_impl
- PySDM.backends.impl_numba.methods.chemistry_methods.ChemistryMethods
- HENRY_CONST
- KINETIC_CONST
- EQUILIBRIUM_CONST
- specific_gravities
- dissolution
- dissolution_body
- oxidation
- oxidation_body
- chem_recalculate_drop_data
- chem_recalculate_cell_data
- equilibrate_H
- equilibrate_H_body
- PySDM.backends.impl_numba.methods.freezing_methods.FreezingMethods
- freeze_singular
- freeze_time_dependent
- freeze_time_dependent_homogeneous
- record_freezing_temperatures
- PySDM.backends.impl_numba.methods.displacement_methods.DisplacementMethods
- calculate_displacement_body_1d
- calculate_displacement_body_2d
- calculate_displacement_body_3d
- calculate_displacement
- flag_precipitated
- flag_out_of_column
- PySDM.backends.impl_numba.methods.terminal_velocity_methods.TerminalVelocityMethods
- interpolation
- terminal_velocity
- power_series
17class Storage(StorageBase): 18 FLOAT = np.float64 19 INT = np.int64 20 BOOL = np.bool_ 21 22 def __getitem__(self, item): 23 dim = len(self.shape) 24 if isinstance(item, slice): 25 step = item.step or 1 26 if step != 1: 27 raise NotImplementedError("step != 1") 28 start = item.start or 0 29 if dim == 1: 30 stop = item.stop or len(self) 31 result_data = self.data[item] 32 result_shape = (stop - start,) 33 elif dim == 2: 34 stop = item.stop or self.shape[0] 35 result_data = self.data[item] 36 result_shape = (stop - start, self.shape[1]) 37 else: 38 raise NotImplementedError( 39 "Only 2 or less dimensions array is supported." 40 ) 41 if stop > self.data.shape[0]: 42 raise IndexError( 43 f"requested a slice ({start}:{stop}) of Storage" 44 f" with first dim of length {self.data.shape[0]}" 45 ) 46 result = Storage(StorageSignature(result_data, result_shape, self.dtype)) 47 elif isinstance(item, tuple) and dim == 2 and isinstance(item[1], slice): 48 result = Storage( 49 StorageSignature(self.data[item[0]], (*self.shape[1:],), self.dtype) 50 ) 51 else: 52 result = self.data[item] 53 return result 54 55 def __setitem__(self, key, value): 56 if hasattr(value, "data"): 57 self.data[key] = value.data 58 else: 59 self.data[key] = value 60 return self 61 62 def __iadd__(self, other): 63 if isinstance(other, Storage): 64 impl.add(self.data, other.data) 65 elif ( 66 isinstance(other, tuple) 67 and len(other) == 3 68 and isinstance(other[0], float) 69 and other[1] == "*" 70 and isinstance(other[2], Storage) 71 ): 72 impl.add_with_multiplier(self.data, other[2].data, other[0]) 73 else: 74 impl.add(self.data, other) 75 return self 76 77 def __isub__(self, other): 78 impl.subtract(self.data, other.data) 79 return self 80 81 def __imul__(self, other): 82 if hasattr(other, "data"): 83 impl.multiply(self.data, other.data) 84 else: 85 impl.multiply(self.data, other) 86 return self 87 88 def __itruediv__(self, other): 89 if hasattr(other, "data"): 90 self.data[:] /= other.data[:] 91 else: 92 self.data[:] /= other 93 return self 94 95 def __imod__(self, other): 96 impl.row_modulo(self.data, other.data) 97 return self 98 99 def __ipow__(self, other): 100 impl.power(self.data, other) 101 return self 102 103 def __bool__(self): 104 if len(self) == 1: 105 result = bool(self.data[0] != 0) 106 else: 107 raise NotImplementedError("Logic value of array is ambiguous.") 108 return result 109 110 def detach(self): 111 if self.data.base is not None: 112 self.data = np.array(self.data) 113 114 def download(self, target, reshape=False): 115 if reshape: 116 data = self.data.reshape(target.shape) 117 else: 118 data = self.data 119 np.copyto(target, data, casting="safe") 120 121 @staticmethod 122 def _get_empty_data(shape, dtype): 123 if dtype in (float, Storage.FLOAT): 124 data = np.full(shape, np.nan, dtype=Storage.FLOAT) 125 dtype = Storage.FLOAT 126 elif dtype in (int, Storage.INT): 127 data = np.full(shape, -1, dtype=Storage.INT) 128 dtype = Storage.INT 129 elif dtype in (bool, Storage.BOOL): 130 data = np.full(shape, -1, dtype=Storage.BOOL) 131 dtype = Storage.BOOL 132 else: 133 raise NotImplementedError() 134 135 return StorageSignature(data, shape, dtype) 136 137 @staticmethod 138 def empty(shape, dtype): 139 return empty(shape, dtype, Storage) 140 141 @staticmethod 142 def _get_data_from_ndarray(array): 143 return get_data_from_ndarray( 144 array=array, 145 storage_class=Storage, 146 copy_fun=lambda array_astype: array_astype.copy(), 147 ) 148 149 def amin(self): 150 return impl.amin(self.data) 151 152 def amax(self): 153 return impl.amax(self.data) 154 155 def all(self): 156 return self.data.all() 157 158 @staticmethod 159 def from_ndarray(array): 160 result = Storage(Storage._get_data_from_ndarray(array)) 161 return result 162 163 def floor(self, other=None): 164 if other is None: 165 impl.floor(self.data) 166 else: 167 impl.floor_out_of_place(self.data, other.data) 168 return self 169 170 def product(self, multiplicand, multiplier): 171 if hasattr(multiplier, "data"): 172 impl.multiply_out_of_place(self.data, multiplicand.data, multiplier.data) 173 else: 174 impl.multiply_out_of_place(self.data, multiplicand.data, multiplier) 175 return self 176 177 def ratio(self, dividend, divisor): 178 impl.divide_out_of_place(self.data, dividend.data, divisor.data) 179 return self 180 181 def divide_if_not_zero(self, divisor): 182 impl.divide_if_not_zero(self.data, divisor.data) 183 return self 184 185 def sum(self, arg_a, arg_b): 186 impl.sum_out_of_place(self.data, arg_a.data, arg_b.data) 187 return self 188 189 def ravel(self, other): 190 if isinstance(other, Storage): 191 self.data[:] = other.data.ravel() 192 else: 193 self.data[:] = other.ravel() 194 195 def urand(self, generator): 196 generator(self) 197 198 def to_ndarray(self): 199 return self.data.copy() 200 201 def upload(self, data): 202 np.copyto(self.data, data, casting="safe") 203 204 def fill(self, other): 205 if isinstance(other, Storage): 206 self.data[:] = other.data 207 else: 208 self.data[:] = other 209 210 def exp(self): 211 self.data[:] = np.exp(self.data) 212 213 def abs(self): 214 self.data[:] = np.abs(self.data)