Maintenance scheduled for Thursday, September 24th at 15:00 MDT. Expected downtime <1 hour.

Merge branch 'master' into 'master'

Merge private to public

Closes #18, #20, #21, #23, #26, #25, #24, #28, #29, #30, #31, #33, #34, #37, #38, #39, #40, #41, and #42

See merge request !49
parents 31480f46 7833b6e5
*.mat -crlf -diff -merge
\ No newline at end of file
*.mat -crlf -diff -merge
# Declare files that will always have CRLF line endings on checkout.
# boundary condition
*.b[[:digit:]][[:digit:]] text eol=crlf
# flow files
*.f[[:digit:]][[:digit:]] text eol=crlf
# plan
*.p[[:digit:]][[:digit:]] text eol=crlf
# project file
*.prj text eol=crlf
# geometry
*.g[[:digit:]][[:digit:]] text eol=crlf
# unsteady flow
*.u[[:digit:]][[:digit:]] text eol=crlf
# output file
*.O[[:digit:]][[:digit:]] binary
# hdf files
*.hdf binary
import sys
from PyQt5.QtWidgets import QApplication
from fluegggui.gui import AppWindow
if __name__ == '__main__':
# Initialize the UI
app = QApplication(sys.argv)
w = AppWindow()
w.show()
sys.exit(app.exec_())
......@@ -21,7 +21,7 @@ project = 'fluegg'
author = 'USGS'
# The full version, including alpha/beta/rc tags
release = '0.0.0'
release = '4.1.0'
# -- General configuration ---------------------------------------------------
......
......@@ -16,6 +16,7 @@ fluegg.asiancarpeggs module
:members:
:undoc-members:
:show-inheritance:
fluegg.drift module
-------------------
......@@ -23,6 +24,7 @@ fluegg.drift module
:members:
:undoc-members:
:show-inheritance:
fluegg.hydraulics module
------------------------
......@@ -30,6 +32,7 @@ fluegg.hydraulics module
:members:
:undoc-members:
:show-inheritance:
fluegg.kml module
-----------------
......@@ -37,6 +40,7 @@ fluegg.kml module
:members:
:undoc-members:
:show-inheritance:
fluegg.random module
--------------------
......@@ -44,6 +48,7 @@ fluegg.random module
:members:
:undoc-members:
:show-inheritance:
fluegg.ras module
-----------------
......@@ -51,6 +56,23 @@ fluegg.ras module
:members:
:undoc-members:
:show-inheritance:
fluegg.results module
-----------------
.. automodule:: fluegg.results
:members:
:undoc-members:
:show-inheritance:
fluegg.resultsrecorder module
-----------------------------
.. automodule:: fluegg.resultsrecorder
:members:
:undoc-members:
:show-inheritance:
fluegg.simclock module
----------------------
......@@ -58,6 +80,7 @@ fluegg.simclock module
:members:
:undoc-members:
:show-inheritance:
fluegg.simulation module
------------------------
......@@ -65,6 +88,7 @@ fluegg.simulation module
:members:
:undoc-members:
:show-inheritance:
fluegg.transporter module
-------------------------
......
......@@ -42,7 +42,7 @@ SSH key to your GitLab account**.
ssh -T git@code.usgs.gov
```
## 3. Clone the repository
## 4. Clone the repository
Cloning the repository downloads a copy of the Git repository to your
local machine. This repository contains a history of changes, and most
importantly, the FluEgg code. Cloning the repository will also create a
......@@ -58,7 +58,7 @@ C:\path-to-sources>` is the command prompt).
(base) C:\path-to-sources>git clone git@code.usgs.gov:FluEgg/fluegg.git
```
## 4. Create and activate the fluegg conda environment
## 5. Create and activate the fluegg conda environment
In this step, you will create an Anaconda environment that contains the version
of Python and versions of packages that are known to work with the FluEgg code
......@@ -87,7 +87,7 @@ development environment.
(fluegg) C:\path-to-sources\fluegg>
```
## 5. Install the fluegg environment
## 6. Install the fluegg environment
The fluegg environment contains Python packages that the fluegg package is
known to work with.
......@@ -107,7 +107,7 @@ Next, install the packages in the environment using pip.
(fluegg) C:\path-to-sources\fluegg>pip install -r requirements.txt
```
## 6. Install the fluegg package
## 7. Install the fluegg package
In order for the Python interpreter in the fluegg environment to have "global"
access to the fluegg package, you'll have to install the package within the
......@@ -121,7 +121,7 @@ fluegg environment.
The `-e` option tells pip to install the fluegg package in "editable" mode.
See ["Editable" installs](https://pip.pypa.io/en/stable/reference/pip_install/#editable-installs) for more info.
## 7. Run the FluEgg unit tests
## 8. Run the FluEgg unit tests
Running the unit tests will ensure the environment is set up and the FluEgg
code is working correctly.
......@@ -142,7 +142,7 @@ OK
The tests passing is a clear indicator that the FluEgg environment has been set
up correctly. You're ready to begin working with the code.
## 8. Build the code documentation
## 9. Build the code documentation
Currently, only the docstrings within the FluEgg code are available in the
documentation.
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
__version__ = '4.0.0'
__version__ = '4.1.0'
......@@ -13,22 +13,21 @@ class CarpEggs(DriftingParticle):
----------
initial_position : numpy.ndarray
Must be an n by 3 array, where n is the number of eggs
simulation_clock : simulation.SimulationClock
Simulation clock
random_numbers : fluegg.random.RandomNumbers, optional
Random number source
characteristic_temperature : float, optional
The default is None
temperature : float, optional
Temperature for computing biological component of carp egg
model. The default is None. If None, the reference
temperature of 22 is used.
"""
_reference_temperature = 22 # degrees Celsius
def __init__(self, initial_position, simulation_clock, random_numbers=None,
characteristic_temperature=None):
temperature=None):
if initial_position.shape[1] == 3 and initial_position.ndim == 2:
self._position = initial_position
......@@ -45,9 +44,9 @@ class CarpEggs(DriftingParticle):
self._reference_density_array = self._init_reference_density_array(
random_numbers)
self._hatching_time = self.hatching_time(characteristic_temperature)
self._hatching_time = self.hatching_time(temperature)
self._gas_bladder_inflation_time = self.gas_bladder_inflation_time(
characteristic_temperature)
temperature)
def _calc_density_std(self, a, b, c):
"""Returns an array of the density standard deviation (kg/m**3)
......@@ -67,13 +66,13 @@ class CarpEggs(DriftingParticle):
@classmethod
def _calc_gas_bladder_inflation_time(cls, tmin2, meanctu_gas_bladder,
characteristic_temperature):
temperature):
if characteristic_temperature is None:
characteristic_temperature = cls._reference_temperature
if temperature is None:
temperature = cls._reference_temperature
# gas bladder inflation time in hours to seconds
return meanctu_gas_bladder/(characteristic_temperature - tmin2) * 3600
return meanctu_gas_bladder/(temperature - tmin2) * 3600
@classmethod
def _calc_hatching_time(cls, a, b, c, temperature=None):
......
......@@ -98,8 +98,6 @@ class ConstantDriftingParticle(DriftingParticle):
def __init__(self, density, diameter, initial_position):
initial_position = np.array(initial_position)
density = np.array(density)
diameter = np.array(diameter)
if initial_position.shape[1] == 3 and initial_position.ndim == 2:
self._position = initial_position
......@@ -109,14 +107,20 @@ class ConstantDriftingParticle(DriftingParticle):
number_of_particles = initial_position.shape[0]
if density.shape[0] != number_of_particles \
or diameter.shape[0] != number_of_particles:
density = np.array(density)
diameter = np.array(diameter)
if density.shape is () or density.shape == (number_of_particles,):
self._density = density
else:
raise ValueError(
'Density must be scalar or array with number of particles length')
if diameter.shape is () or diameter.shape == (number_of_particles,):
self._diameter = diameter
else:
raise ValueError(
'The zero axis of density, diameter, and initial_position ' +
'must be consistent')
'Diameter must be scalar or array with number of particles length')
self._density = density
self._diameter = diameter
self._position = initial_position
def density(self, *args):
......
......@@ -389,7 +389,7 @@ class SeriesOfHydraulicCells:
b = 2.47
streamwise_velocity = log_law_velocity * \
beta.pdf(lateral_location/width, a, b)
beta.pdf((lateral_location + width/2)/width, a, b)
return streamwise_velocity
......@@ -596,6 +596,7 @@ class SeriesOfHydraulicCells:
# set the streamwise velocities above the water surface to nan
above_water_surface = depth < vertical_location
streamwise_velocity[above_water_surface] = np.nan
hydraulic_data = np.stack([depth, width, temperature, viscosity,
......@@ -643,8 +644,8 @@ class SeriesOfHydraulicCells:
frames = [cell.to_data_frame() for cell in self._cells]
df = pd.concat(frames,
keys=range(1, len(frames)+1)) \
.swaplevel() \
.sort_index()
.swaplevel() \
.sort_index()
else:
raise RuntimeError("Unknown subclass of HydraulicCell")
......@@ -691,7 +692,6 @@ class RoughBottomSeriesOfHydraulicCells(SeriesOfHydraulicCells):
"""
@staticmethod
def _calc_roughness_height(depth, mean_xs_velocity, shear_velocity):
"""Calculate roughness height (kc), in meters
......@@ -727,7 +727,6 @@ class SmoothBottomSeriesOfHydraulicCells(SeriesOfHydraulicCells):
def __init__(self, *args):
raise NotImplementedError("This class is not implemented.")
def _calc_log_law_velocity(self, distance_above_bed, shear_velocity, depth,
mean_xs_velocity, viscosity):
......
This diff is collapsed.
from abc import ABC, abstractmethod
import numpy as np
class ResultsRecorder(ABC):
def __init__(self, simclock, particles, configuration, hydraulic_model):
self._configuration = configuration
self._time_series = simclock.time_array()
hydraulic_cell_edges = hydraulic_model.cell_edges()
self._domain_length = hydraulic_cell_edges[-1]
self._positions = None
def configuration(self):
"""Returns the configuration dictionary.
Returns
-------
dict
"""
return self._configuration
def domain_length(self):
"""Returns the length of the simulation domain
Returns the longitudinal (x-axis) length of the simulation domain.
Returns
-------
float
"""
return self._domain_length
def positions(self, *args, **kwargs):
"""Returns particle position array
Returns
-------
numpy.ndarray
Position array
"""
return self._positions.copy()
@abstractmethod
def record_result(self, simclock, particles, hydraulic_results):
pass
@abstractmethod
def results(self):
pass
def time(self, index):
"""Simulation time
Parameters
----------
index : int or slice
Index of time to return
Returns
-------
float
Simulation time, in seconds
"""
return self._time_series[index]
class FullResultsRecorder(ResultsRecorder):
"""Data structure containing simulation results during a simulation run
Parameters
----------
simclock : SimulationClock
Representation of a simulation clock
particles : DriftingParticle
Particles used during simulation
configuration : dict
Simulation configuration
hydraulic_model : SeriesOfHydraulicCells
Hydraulic model
"""
def __init__(self, simclock, particles, configuration, hydraulic_model):
super().__init__(simclock, particles, configuration, hydraulic_model)
self._positions = np.tile(
np.nan, (simclock.number_of_times(),
particles.position().shape[0], 3))
self._depth = np.tile(
np.nan, (simclock.number_of_times(),
particles.position().shape[0], ))
self._width = np.tile(
np.nan, (simclock.number_of_times(),
particles.position().shape[0], ))
def _normalize_axis(self, position_axis):
if position_axis == 0:
positions = \
self._positions[:, :, position_axis] / self._domain_length
elif position_axis == 1:
positions = \
self._positions[:, :, position_axis] / self._width + 0.5
elif position_axis == 2:
positions = \
self._positions[:, :, position_axis] / self._depth
return positions
def depth(self):
""" returns depth array
"""
return self._depth
def positions(self, normalize=()):
"""Returns particle position array
Parameters
----------
normalize : tuple of int, optional
Normalize position array axes (the default is an empty,
tuple, which doesn't normalize any axes).
Returns
-------
numpy.ndarray
Position array
"""
axes = {0, 1, 2}
# keep these axes in absolute length
absolute = axes.difference(normalize)
positions = np.zeros_like(self._positions)
for a in normalize:
positions[:, :, a] = self._normalize_axis(a)
for a in absolute:
positions[:, :, a] = self._positions[:, :, a]
return positions
def record_result(self, simclock, particles, hydraulic_results):
"""Records results for current time step
Parameters
----------
simclock : SimulationClock
particles : DriftingParticle
hydraulic_results : HydraulicResults
"""
time_index = simclock.current_time_index()
self._positions[time_index] = particles.position()
self._depth[time_index] = hydraulic_results.depth()
self._width[time_index] = hydraulic_results.width()
def results(self):
"""Returns results
Returns
-------
results : FullResults
"""
from fluegg.results import FullResults
return FullResults(self)
def width(self):
""" returns depth array
"""
return self._width
class QuantileResultsRecorder(ResultsRecorder):
def __init__(self, simclock, particles, configuration, hydraulic_model):
super().__init__(simclock, particles, configuration, hydraulic_model)
self._quantiles = self.quantiles()
self._positions = np.tile(
np.nan, (self._quantiles.shape[0], simclock.number_of_times(), 3))
@staticmethod
def quantiles():
"""Returns the quantiles
"""
lo = np.insert(np.logspace(-3, 0, 50) / 2, 0, 0)
hi = 1 - lo[:-1]
return np.sort(np.append(lo, hi))
def record_result(self, simclock, particles, hydraulic_results):
"""Records results for current time step
Parameters
----------
simclock : SimulationClock
particles : DriftingParticle
hydraulic_results : HydraulicResults
"""
time_index = simclock.current_time_index()
current_position = particles.position()
normalized_positions = np.empty_like(current_position)
depth = hydraulic_results.depth()
width = hydraulic_results.width()
normalized_positions[:, 0] = current_position[:,
0] / self._domain_length
normalized_positions[:, 1] = current_position[:, 1] / width + 0.5
normalized_positions[:, 2] = current_position[:, 2] / depth
self._positions[:, time_index, :] = np.quantile(
normalized_positions, self._quantiles, axis=0)
def results(self):
"""Returns quantile results
Returns
-------
QuantileResults
"""
from fluegg.results import QuantileResults
return QuantileResults(self)
......@@ -17,11 +17,11 @@ class SimulationClock:
def __init__(self, time_step_size, total_simulation_time):
if total_simulation_time % time_step_size == 0:
time_array = np.arange(0, total_simulation_time + time_step_size,
time_step_size, dtype=float)
n_steps = total_simulation_time // time_step_size
else:
time_array = np.arange(0, total_simulation_time, time_step_size,
dtype=float)
n_steps = total_simulation_time // time_step_size + 1
time_array = time_step_size*np.arange(0, n_steps+1, dtype=float)
self._time_array = time_array
self._current_time_index = 0
......
This diff is collapsed.
......@@ -104,31 +104,24 @@ class LateralTransporter(Transporter):
boundary_length = width - diameter
shifted_next_lateral_position = next_position - diameter / 2
shifted_boundary_location = boundary_length
# 0 is center
r_boundary_location = -boundary_length/2 + diameter/2
l_boundary_location = boundary_length/2 - diameter/2
right_of_boundary = shifted_next_lateral_position < 0
left_of_boundary = \
shifted_next_lateral_position > shifted_boundary_location
out_of_bounds = right_of_boundary | left_of_boundary
right_of_boundary = next_position < r_boundary_location
left_of_boundary = next_position > l_boundary_location
if np.any(out_of_bounds):
reflections = np.floor_divide(shifted_next_lateral_position,
shifted_boundary_location