diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..1ebed5d --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,16 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: trailing-whitespace + # - id: end-of-file-fixer + - id: check-yaml + - id: check-json + types: [text] + files: \.(json|ipynb)$ + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.5.6 + hooks: + - id: ruff + args: [ --fix ] + # - id: ruff-format diff --git a/environment.yml b/environment.yml index 6c5b151..eac83eb 100644 --- a/environment.yml +++ b/environment.yml @@ -9,3 +9,4 @@ dependencies: # Testing - pytest - coverage + - pre_commit diff --git a/plasticparcels/constructors.py b/plasticparcels/constructors.py index a5f50b1..b521218 100644 --- a/plasticparcels/constructors.py +++ b/plasticparcels/constructors.py @@ -10,7 +10,7 @@ def create_hydrodynamic_fieldset(settings): - """ A constructor method to create a Parcels.Fieldset from hydrodynamic model data + """A constructor method to create a Parcels.Fieldset from hydrodynamic model data Parameters ---------- @@ -22,7 +22,6 @@ def create_hydrodynamic_fieldset(settings): fieldset A parcels.FieldSet object """ - # Location of hydrodynamic data dirread_model = os.path.join(settings['ocean']['directory'], settings['ocean']['filename_style']) @@ -87,7 +86,7 @@ def create_hydrodynamic_fieldset(settings): def create_fieldset(settings): - """ A constructor method to create a Parcels.Fieldset with all fields necessary for a plasticparcels simulation + """A constructor method to create a Parcels.Fieldset with all fields necessary for a plasticparcels simulation Parameters ---------- @@ -99,7 +98,6 @@ def create_fieldset(settings): fieldset A parcels.FieldSet object """ - # First create the hydrodynamic fieldset fieldset = create_hydrodynamic_fieldset(settings) @@ -203,7 +201,7 @@ def create_fieldset(settings): def create_particleset(fieldset, settings, release_locations): - """ A constructor method to create a Parcels.ParticleSet for a plasticparcels simulation + """A constructor method to create a Parcels.ParticleSet for a plasticparcels simulation Parameters ---------- @@ -219,7 +217,6 @@ def create_particleset(fieldset, settings, release_locations): particleset A parcels.ParticleSet object """ - # Set the longitude, latitude, and plastic amount per particle lons = np.array(release_locations['lons']) lats = np.array(release_locations['lats']) @@ -271,7 +268,7 @@ def create_particleset(fieldset, settings, release_locations): def create_particleset_from_map(fieldset, settings): - """ A constructor method to create a Parcels.ParticleSet for a plasticparcels simulation from one of the available initialisation maps + """A constructor method to create a Parcels.ParticleSet for a plasticparcels simulation from one of the available initialisation maps Parameters ---------- @@ -285,7 +282,6 @@ def create_particleset_from_map(fieldset, settings): particleset A parcels.ParticleSet object """ - # Load release type information release_type = settings['release']['initialisation_type'] @@ -326,7 +322,7 @@ def create_particleset_from_map(fieldset, settings): def create_kernel(fieldset): - """ A constructor method to create a list of kernels for a plasticparcels simulation + """A constructor method to create a list of kernels for a plasticparcels simulation Parameters ---------- diff --git a/plasticparcels/kernels.py b/plasticparcels/kernels.py index a78753d..e1b1acc 100644 --- a/plasticparcels/kernels.py +++ b/plasticparcels/kernels.py @@ -36,7 +36,6 @@ def StokesDrift(particle, fieldset, time): """ - # Sample the U / V components of Stokes drift stokes_U = fieldset.Stokes_U[time, particle.depth, particle.lat, particle.lon] stokes_V = fieldset.Stokes_V[time, particle.depth, particle.lat, particle.lon] @@ -96,7 +95,6 @@ def WindageDrift(particle, fieldset, time): None """ - # Sample ocean velocities (ocean_U, ocean_V) = fieldset.UV[particle] ocean_speed = math.sqrt(ocean_U**2 + ocean_V**2) @@ -226,14 +224,13 @@ def SettlingVelocity(particle, fieldset, time): - Must run before any kernel that uses the particle.settling_velocty variable - References: + References ---------- [1] Dietrich (1982) - https://doi.org/10.1029/WR018i006p01615 [2] Kooi et al. (2017) - https://doi.org/10.1021/acs.est.6b04702 TODO: Add units to each variable, and the TODO's below. """ - # Define constants and sample fieldset variables g = fieldset.G # gravitational acceleration [m s-2] seawater_density = particle.seawater_density # [kg m-3] @@ -276,7 +273,7 @@ def SettlingVelocity(particle, fieldset, time): def Biofouling(particle, fieldset, time): - """ Settling velocity due to biofouling kernel + """Settling velocity due to biofouling kernel Description ---------- @@ -339,14 +336,13 @@ def Biofouling(particle, fieldset, time): - References: + References ---------- [1] Dietrich (1982) - https://doi.org/10.1029/WR018i006p01615 [2] Kooi et al. (2017) - https://doi.org/10.1021/acs.est.6b04702 [3] Menden-Deuer and Lessard (2000) - https://doi.org/10.4319/lo.2000.45.3.0569 [4] Bernard and Remond (2012) - https://doi.org/10.1016/j.biortech.2012.07.022 """ - # Define constants and algal properties, and sample fieldset variables # g = fieldset.G # gravitational acceleration [m s-2] # k = fieldset.K # Boltzmann constant [m2 kg d-2 K-1] now [s-2] (=1.3804E-23) @@ -467,7 +463,7 @@ def Biofouling(particle, fieldset, time): def PolyTEOS10_bsq(particle, fieldset, time): - """ A seawater density kernel + """A seawater density kernel Description: ---------- @@ -487,7 +483,8 @@ def PolyTEOS10_bsq(particle, fieldset, time): Order of Operations: This kernel must be run before any kernel that requires particle.seawater_density - References: + References + ---------- Roquet, F., Madec, G., McDougall, T. J., Barker, P. M., 2014: Accurate polynomial expressions for the density and specific volume of seawater using the TEOS-10 standard. Ocean Modelling. @@ -496,7 +493,6 @@ def PolyTEOS10_bsq(particle, fieldset, time): temperature and density of seawater. Journal of Atmospheric and Oceanic Technology, 20, 730-741. """ - Z = - particle.depth # note: use negative depths! SA = fieldset.absolute_salinity[time, particle.depth, particle.lat, particle.lon] CT = fieldset.conservative_temperature[time, particle.depth, particle.lat, particle.lon] @@ -569,7 +565,7 @@ def PolyTEOS10_bsq(particle, fieldset, time): def VerticalMixing(particle, fieldset, time): - """ A markov-0 kernel for vertical mixing + """A markov-0 kernel for vertical mixing Description: A simple verticle mixing kernel that uses a markov-0 process to determine the vertical displacement of a particle. @@ -587,7 +583,6 @@ def VerticalMixing(particle, fieldset, time): NOTES: previously called markov_0_mixing """ - # Sample the ocean vertical eddy diffusivity field KZ delta_z = 0.5 # [m], used to compute the gradient of kz using a forward-difference kz = fieldset.mixing_kz[time, particle.depth, particle.lat, particle.lon] @@ -633,7 +628,7 @@ def VerticalMixing(particle, fieldset, time): def unbeaching(particle, fieldset, time): - """ A kernel to unbeach particles""" + """A kernel to unbeach particles""" # Measure the velocity field at the final particle location (vel_u, vel_v, vel_w) = fieldset.UVW[time, particle.depth + particle_ddepth, particle.lat + particle_dlat, particle.lon + particle_dlon] # noqa @@ -670,8 +665,8 @@ def checkThroughBathymetry(particle, fieldset, time): # @date: 2023-08-09 def periodicBC(particle, fieldset, time): - """ Kernel to keep the particle between [-180,180] longitude - To be run after all movement kernels + """Kernel to keep the particle between [-180,180] longitude + To be run after all movement kernels """ if particle.lon + particle_dlon <= -180.: # noqa particle_dlon += 360. # noqa @@ -680,7 +675,7 @@ def periodicBC(particle, fieldset, time): def checkErrorThroughSurface(particle, fieldset, time): - """ Kernel to set the particle depth to the particle surface if it goes through the surface + """Kernel to set the particle depth to the particle surface if it goes through the surface """ if particle.state == StatusCode.ErrorThroughSurface: # particle_ddepth = - particle.depth # Set so that final depth = 0 # TODO why not use this instead of delete? @@ -689,7 +684,7 @@ def checkErrorThroughSurface(particle, fieldset, time): def deleteParticle(particle, fieldset, time): - """ Kernel for deleting particles if they throw an error other than through the surface + """Kernel for deleting particles if they throw an error other than through the surface """ if particle.state >= 50 and particle.state != StatusCode.ErrorThroughSurface: particle.delete() diff --git a/plasticparcels/scripts/create_masks.py b/plasticparcels/scripts/create_masks.py index 5a87630..dbed1b1 100644 --- a/plasticparcels/scripts/create_masks.py +++ b/plasticparcels/scripts/create_masks.py @@ -2,12 +2,12 @@ """ - TODO: - Come back to this on the advice from Daan: +TODO: +Come back to this on the advice from Daan: - https://www.mercator-ocean.eu/static-files-description/ - Mask files can be found in /storage2/oceanparcels/data/input_data/MOi/domain_ORCA0083-N006/ - These are downloaded directly from MOi - where though? +https://www.mercator-ocean.eu/static-files-description/ +Mask files can be found in /storage2/oceanparcels/data/input_data/MOi/domain_ORCA0083-N006/ +These are downloaded directly from MOi - where though? """ @@ -37,10 +37,9 @@ def NEMO_select_section(extent, lon, lat, val): def to_netcdf(output_filename, data, data_name, lons, lats, explanation=''): - ''' + """ All data is written to netcdf files to speed up computations - ''' - + """ dict_data = {} for data_, name_ in zip(data, data_name): dict_data[name_] = (["x", "y"], data_) @@ -58,9 +57,9 @@ def to_netcdf(output_filename, data, data_name, lons, lats, explanation=''): def get_mask_land(field, lons, lats, outfile='./tmp_mask_land'): - ''' + """ Mask with true on land cells, false on ocean cells - ''' + """ if os.path.exists(outfile): ds = xr.open_dataset(outfile) mask_land = np.array(ds['mask_land'], dtype=bool) @@ -120,7 +119,6 @@ def create_displacement_field(landmask, lons, lats, double_cell=False, outfile=' Output: two 2D arrays, one for each camponent of the velocity. """ - if os.path.exists(outfile): ds = xr.open_dataset(outfile) v_x = np.array(ds['land_current_u'], dtype=float) @@ -165,9 +163,9 @@ def create_displacement_field(landmask, lons, lats, double_cell=False, outfile=' def get_mask_coast(mask_land, lons, lats, outfile='./tmp_mask_coast'): - ''' + """ calculate the coast mask. With coastal cells, we mean cells in the water, adjacent to land - ''' + """ if os.path.exists(outfile): ds = xr.open_dataset(outfile) mask_coast = np.array(ds['mask_coast'], dtype=bool) diff --git a/plasticparcels/scripts/create_release_maps.py b/plasticparcels/scripts/create_release_maps.py index 621daf8..cedacba 100644 --- a/plasticparcels/scripts/create_release_maps.py +++ b/plasticparcels/scripts/create_release_maps.py @@ -39,7 +39,6 @@ def create_coastal_mpw_jambeck_release_map(mask_coast_filepath, coords_filepath, Parameters ---------- - mask_coast_filepath : str File path to the coastal mask generated in ...py TODO coords_filepath : str @@ -66,7 +65,6 @@ def create_coastal_mpw_jambeck_release_map(mask_coast_filepath, coords_filepath, coastal_density_mpw_df A pandas dataframe containing the coastal grid cells, associated countries, and associated mismanaged plastic waste in kg/day. """ - # Open the Natural Earth coastline vector dataset shpfilename = shpreader.natural_earth(resolution='50m', category='cultural', @@ -406,7 +404,6 @@ def create_global_concentrations_kaandorp_release_map(mask_land_filepath, mask_c We match this data to the model coastal and ocean cells for a better coverage release. We use only the 2020 data for all plastic class sizes, and for the ocean cells we only use the surface 0-5m depth. """ - # Load in the data and select year 2020, all plastic sizes, and for ocean concentrations take the surface layer ds = xr.open_dataset(kaandorp_filepath) beach = ds['concentration_beach_mass_log10'].sel({'size_nominal': 'all', 'time': 2020}) diff --git a/plasticparcels/utils.py b/plasticparcels/utils.py index b516cdc..73e8735 100644 --- a/plasticparcels/utils.py +++ b/plasticparcels/utils.py @@ -57,7 +57,6 @@ def distance(lon1, lat1, lon2, lat2): Calculate the great circle distance between two points on the earth (specified in decimal degrees) """ - # Convert decimal degrees to Radians: lon1r = np.radians(lon1) lat1r = np.radians(lat1) @@ -100,7 +99,7 @@ def get_coords_from_polygon(shape): def load_settings(filename): - """ A function to load a settings file in json format""" + """A function to load a settings file in json format""" with open(filename, "r") as file: settings = json.load(file) return settings @@ -126,7 +125,6 @@ def download_plasticparcels_dataset(dataset: str, settings, data_home=None): dataset_folder : Path Path to the folder containing the downloaded dataset files. """ - plasticparcels_data_files = { "NEMO0083": [ (("release_maps", "coastal"), "coastal_population_MPW_NEMO0083.csv"), diff --git a/pyproject.toml b/pyproject.toml index 7758f11..023b977 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,21 +43,44 @@ packages = ["plasticparcels"] write_to = "plasticparcels/_version_setup.py" local_scheme = "no-local-version" -[project.optional-dependencies] -dev = [ - "black == 24.4.0", - "darglint == 1.8.1", - "flake8 == 7.0.0", - "Flake8-pyproject == 1.2.3", - "isort == 5.13.2", - "pydocstyle == 6.3.0", - "sort-all == 1.2.0", +[tool.ruff.lint] +select = [ + "E", # Error + "F", # pyflakes + # "I", # isort + "B", # Bugbear + # "UP", # pyupgrade + "LOG", # logging + "ICN", # import conventions + "G", # logging-format + "RUF", # ruff + "D", # pydocstyle + "DOC", # pydoclint ] -[tool.isort] -profile = "black" -skip_gitignore = true +# If updating to use a formatter, look at https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules for rules to add to ignore +ignore = [ + # line too long (82 > 79 characters) + "E501", + # ‘from module import *’ used; unable to detect undefined names + "F403", + # Mutable class attributes should be annotated with `typing.ClassVar` + "RUF012", + # Consider `(slice(2), *block)` instead of concatenation + "RUF005", + # Prefer `next(iter(variable.items()))` over single element slice + "RUF015", + # do not use bare except, specify exception instead + "E722", + # First line should be in imperative mood (requires writing of summaries) + "D401", + # 1 blank line required between summary line and description (requires writing of summaries) + "D205", + # Loop control variable `i` not used within loop body + "B007", + # Remove unused `noqa` directive + "RUF100", +] -[tool.flake8] -extend-ignore = "E501" # Don't check line length. -docstring_style = "sphinx" # Use sphinx docstring style for darglint plugin. +[tool.ruff.lint.pydocstyle] +convention = "numpy" diff --git a/tests/pyproject.toml b/tests/pyproject.toml new file mode 100644 index 0000000..cc9da81 --- /dev/null +++ b/tests/pyproject.toml @@ -0,0 +1,17 @@ +[tool.ruff] +extend = "../pyproject.toml" + +[tool.ruff.lint] +# Remove docstring checks +# select = [ +# "E", # Error +# "F", # pyflakes +# # "I", # isort +# "B", # Bugbear +# # "UP", # pyupgrade +# "LOG", # logging +# "ICN", # import conventions +# "G", # logging-format +# "RUF", # ruff +# ] +ignore = ["D"] \ No newline at end of file