Beispiel #1
0
    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)
Beispiel #2
0
    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)
Beispiel #3
0
    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)
Beispiel #5
0
    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)
Beispiel #6
0
    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()
Beispiel #7
0
 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])
Beispiel #8
0
    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)