def __init__(self, number_of_dimensions: int, time_interval: Tuple[float, float], time_step: float = 1e-5, z_boundary: Optional[Tuple[float, float]] = None, random_seed=None, result_storage: Optional[ResultStorage] = None): """ Construct an Experiment to run. Sources, Devices, Detectors and Actions should be added after this construction, via the appropriate add_ methods, e.g. add_device(), on the constructed instance. :param number_of_dimensions: The number of spatial dimensions of this experiment. The phase space will accordingly have 2*number_of_dimensions dimensions. :param time_interval: The time interval to run the experiment in, as a 2-tuple of (`t_start`, `t_end`). :param time_step: The time step to use. If None is passed, a time step is picked based on the initial particle velocities. :param z_boundary: (Optional) If passed, the Z boundary of the entire experimental setup. Particles will not be simulated outside of this boundary. If this argument is not passed, the minimal interval enclosing all Devices and Detectors along the Z axis will be calculated and used as the Z boundary. :param random_seed: The random seed to use for random value generation, affecting things like initial positions and brownian motion steps. Note that when running with multiple processes, every process will start from its own random seed (this random_seed plus an integer offset). :param result_storage: The ResultStorage to use for the experiment's results. Can be None, in which case the results will not be stored and only returned. """ self.devices = [] self.sources = [] self.detectors = [] self.actions = [] self.result_storage = result_storage self.random_seed = random_seed self.time_interval = time_interval self.time_step = time_step self.z_boundary = z_boundary self.loglevel = 'warning' self._integrator_name = 'lsoda' self._integrator_params = {} self._simulate_particle = simulate_particle # TODO do we need to subscribe? self.number_of_dimensions = number_of_dimensions GlobalConfig().set(ConfigKey.NUMBER_OF_DIMENSIONS, number_of_dimensions) GlobalConfig().set(ConfigKey.TIME_STEP, time_step)
def __init__(self, z_position: float, identifier: str = None): """ Constructs a new SimpleZDetector. :param z_position: The z position to put the detector at. :param identifier: The identifier of the detector. If None, ("SimpleZ@" + str(z_position)) is used. """ self.z_position = z_position self._z_boundary = (z_position, z_position) self.particle_distances: Dict[Any, float] = {} self._hit_position = None self._number_of_dimensions = None super().__init__(identifier=identifier if identifier is not None else f'SimpleZ@{z_position}') GlobalConfig().subscribe(self, ConfigKey.NUMBER_OF_DIMENSIONS)
def __init__(self, fields: List[Field], boundary: Boundary, actions: Union[List[Action]] = None): """ Constructor for Device. :param fields: The Fields that are present in this device. :param boundary: The Boundary this device has. """ self._fields: List[Field] = [] self._auto_overwrite_calculate_acceleration() self._boundary = boundary for field in fields: self.add_field(field) self._actions: List[Action] = actions if actions is not None else [] self._number_of_dimensions = None super().__init__() GlobalConfig().subscribe(self, ConfigKey.NUMBER_OF_DIMENSIONS)
def __init__(self, number_of_particles: int, position: List[Distribution], velocity: List[Distribution], radius: Distribution, density: Distribution, seed=None, particle_class: Type[SphericalParticle] = SphericalParticle, particle_kwargs: Dict[Any, Any] = None): self.number_of_particles = number_of_particles self.position = position self.velocity = velocity self.radius = radius self.density = density self.seed = seed self.particle_class = particle_class self.particle_kwargs = particle_kwargs or {} self.number_of_dimensions = len(self.position) # Check that position and velocity descriptions are equal in length (dimensionality) m, n = len(self.position), len(self.velocity) assert m == n, f"Mismatching position ({m}) / velocity ({n}) dimensionalities!" GlobalConfig().subscribe(self, ConfigKey.NUMBER_OF_DIMENSIONS)
def __init__(self, filename: str, offset: np.array = None, *args, **kwargs): """ The constructor for RegularGridInterpolationField. :param filename: The filename of an HDF5 file in the format as constructed by `tools/txt_to_hdf5`. :param offset: The positional offset of the field as a (d,)-ndarray, where d is the number of spatial dimensions .. todo:: Provide a "formal" definition of the Field-file format in documentaiton, not only through an implementation And then refer to this file format by its name, not by its "implemented somewhere" specification... """ # Construct the interpolator from the HDF5 file passed by file name self.filename = filename data_index, data_grid = hdf5_to_data_grid(self.filename) if offset is not None: for i, off in enumerate(offset): data_index[i] += off self.number_of_dimensions = len(data_index) self._zero_acceleration = np.zeros(self.number_of_dimensions) self._nan_acceleration = np.full(self.number_of_dimensions, np.nan) self._interpolator = get_regular_grid_interpolator( tuple(data_index), data_grid) # Assuming that Z is always the last dimension, construct the Z boundary from it minima = list(np.min(d) for d in data_index) maxima = list(np.max(d) for d in data_index) self._z_boundary = (minima[self.number_of_dimensions - 1], maxima[self.number_of_dimensions - 1]) super().__init__(*args, **kwargs) GlobalConfig().subscribe(self, ConfigKey.NUMBER_OF_DIMENSIONS)
def _prepare(self): # Set values on global config, thereby cascading them throughout the setup to all subscribed objects conf = GlobalConfig() conf.set(ConfigKey.NUMBER_OF_DIMENSIONS, self.number_of_dimensions) conf.set(ConfigKey.TIME_STEP, self.time_step) # Initialise list of particles from all sources particle_types = set( ) # Remember a set of particle types to raise an error if there are multiple types self.particles = [] for source in self.sources: source_particles = source.generate_particles( self.time_interval[0]) # initialize with t_0 if source_particles: particle_types.add(type(source_particles[0])) self.particles += source_particles if len(particle_types) > 1: raise TypeError( "Cannot simulate particles of multiple types at the same time!" ) # Initialize random state if self.random_seed is None: seed = np.random.randint(2**31) self.random_seed = seed np.random.seed(self.random_seed) # Initialise the min/max Z values amongst all detectors and all devices self.detectors_z_boundary = self._gather_z_boundary(self.detectors) self.devices_z_boundary = self._gather_z_boundary(self.devices) self.z_lower, self.z_upper = self._gather_z_lower_upper( [x.z_boundary for x in self.detectors + self.devices]) # Initialise the min/max Z boundary of the whole experiment if self.z_boundary is None: # Use the global min/max from all devices and detectors, -/+ the end Z delta self.z_boundary = min(self.detectors_z_boundary[0], self.devices_z_boundary[0]), \ max(self.detectors_z_boundary[1], self.devices_z_boundary[1]) self._prepare_process()
def __init__(self, field: MolecularFlowDragForceField): self.field = field self.dt = None self.number_of_dimensions = None GlobalConfig().subscribe(self, [ConfigKey.NUMBER_OF_DIMENSIONS, ConfigKey.TIME_STEP])
def __init__(self, intervals: List[Tuple[float, float]]): self.intervals = np.array(intervals) super().__init__(intervals[-1]) # last dimension is assumed to be Z GlobalConfig().subscribe(self, ConfigKey.NUMBER_OF_DIMENSIONS)