From 591c3483957bd0ceafe58818054fc20da2ce2aad Mon Sep 17 00:00:00 2001 From: adigitoleo Date: Thu, 3 Aug 2023 16:47:41 +1000 Subject: [PATCH 1/4] dev: Migrate from flake8 to ruff Faster linting and ruff also supports pyproject.toml configuration. Also moves all configuration for other dev tools to pyproject.toml. --- .pre-commit-config.yaml | 15 +++------------ pyproject.toml | 18 +++++++++++++++++- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b434773e..4a77b13b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -35,20 +35,11 @@ repos: rev: 5.12.0 hooks: - id: isort - args: ["--profile", "black", "--filter-files"] - repo: https://github.com/asottile/pyupgrade rev: v3.10.1 hooks: - id: pyupgrade -- repo: https://github.com/PyCQA/flake8 - rev: 6.1.0 +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.0.282 hooks: - - id: flake8 - args: ["--max-line-length", "88", "--extend-ignore", "E203,E501,E722,W503"] - exclude: | # https://pre-commit.com/#regular-expressions - (?x)^( - src/initial_implementation/AlternativeFunctions.py| - src/pydrex/__init__.py - )$ - -exclude: \.vtu$ + - id: ruff diff --git a/pyproject.toml b/pyproject.toml index da021052..37b9de59 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,7 +44,8 @@ dependencies = [ [project.optional-dependencies] test = ["pytest", "matplotlib"] doc = ["pdoc"] -dev = ["pytest", "matplotlib", "pdoc", "flake8", "black", "h5py", "isort", "ipython"] +dev = ["pytest", "matplotlib", "pdoc", "ruff", "black", "h5py", "isort", "ipython"] +lsp = ["python-lsp-server", "pyls-isort", "python-lsp-black", "python-lsp-ruff"] [project.urls] repository = "https://github.com/Patol75/PyDRex/" @@ -72,3 +73,18 @@ testpaths = ["tests"] # --show-capture=no : Don't show captured output (stdout/stderr/logs) for failed tests. # --doctest-modules : Run doctest snippets in docstrings addopts = "--tb=short --show-capture=no --doctest-modules" + +# Global linter and devtools settings. +[tool.isort] +profile = "black" +filter_files = true +# Ruff is faster than flake8 and supports pyproject.toml config, line length is 88 by default. +[tool.ruff] +exclude = [ + "src/initial_implementation", + ".scsv", + ".npz", + ".vtu", +] +[tool.ruff.per-file-ignores] +"src/pydrex/__init__.py" = ["F401"] # Don't complain about unused imports in __init__.py file. From 7c27e4b716ae3a3c3c66fc8a556473e24a871e30 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 3 Aug 2023 07:06:40 +0000 Subject: [PATCH 2/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../steadyflow/corner2d_2cmyr_5e5x1e5.vtu | Bin 120791 -> 120788 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/pydrex/data/steadyflow/corner2d_2cmyr_5e5x1e5.vtu b/src/pydrex/data/steadyflow/corner2d_2cmyr_5e5x1e5.vtu index 887be72d470e88b958db565dbde21c9d2d5bc4fa..3b3fa9f56e2abb28dc03068a0513893271887490 100644 GIT binary patch delta 3077 zcmZWrdt8m_7XGc>m3k?v(e5raCA*snNiHR#rbsoU*lJ5kc6Z$rl0<{P>Wkb(O5@U} zJ|nki9G7T}#x>WWVGbf0hLN*YIKSWdo&CqN_VcV~t@r)j%l`KD0~&1yG}bROVCLo? z!SPASi7AoEDT#`xNr^F0o@@gj0&K&J>A<-0u&WnsI3`K^^6f`S@qfQ*a!QqAA8bP( z4~M|TG4aXX@?^Q^SIJ8e7xzDc!~ZG0MM<7Cm@Su}y3Xut?dNuw`O=;$Kl=>1yK0P^ zsFu?^5n`*pe9KW^Y4X+IJJgqQoLh_{Qh}pbXy~JNgoYVnsK!jhLmC#qgsU2wDj3@P zYGdG@vI-8;;np>8xtjfjPz zfcTrC52CU338I=&Af`i%3Mu`-_zdE1lWasw(=fywQv*zwnLeZDE6n?-=&*p3S!H&c z8;=_Jrqw>RWWr`kfocP-XivJWn$Xx_okM;%n}w9wVcUUNFmxNn2e%~gCU+iRj$quA2~B9J@fu2F|K@Fr7?I12`x;yBHWBU-}ofjA)&|juGeYn5>=V0i{s*}JKpNM zI;S(GtNpdtlNJ_*Jxf!XQh~8-^aeLBe6(ma%F`csH5_!EcrXsG(_z5N7Oh$7I&7OoA zVcXJV3VOLF)W&)F3Mt!pGHYMVz~ZjG(tz#&Z}e(tTfVMH#HVSZd!MXH)LFDWhN%O zD(fk^ch5#c_A!%veB{w&)n?t%vtL!29;x{2s(Mx{<7QQPqjg(V4_UWX8zLU5#@oVm zRv$yGt)T-mZ{HMkHZ1hII^*j0T|s|dZ3oRPw(dNltMCX>dRS?|UM(#cq+XD8dEvng zANr+P2Hb7W*vq)>hw*lDP+x^uU5^XRMK-t~mNz`qVO37)bF<5v9M+tzs97~QNtw{Lu^9jw{y#DsY_3aIkm zxAd^GxZMKF2e-dQ#o&$`DuVxAd&+0sKS_J;@hF4_;2tZDSYgTZ6rD518}}Pp+jXT$ z#70luOvlse3woGf+A%}}ZC=k$VvWiRdurs|DWyjKuVzt)yH5foG4DFD#V*-vl5TuTBxE{R05Tp;|P%Y61AIjb~fM|&ZGYxvUX$|9j z4FT_r*kK4V9Dvs|fx}48n}7>>drW~|YW#F_a5882C&WPJ?W_+v!AkbCR^ozn<&Did zTw5ceQauv8mok2rImDv5zXc4^W>*D+%8~AihNSLi;&2P-pmtJApdUDX*$}{!A$AXe zX*8&h4NOB?ZUblOA?FN(0&+i*0lj(oSB|h1>3Sz{CwkEZEHQMt03KX%qAO(LDdn3+ zfh&f$qu?QiTkep8A#gNoLC=fPK(7o@a}4;?@FAYyg<+E?{D7+MSXhRA#eEZ?jygq5 zf{{r1Nl-!N;l3~lL#8jdquSsLBJEMUAK*>mpZEd3$;1)h}@#NZVM<56812G*q3&W1mc z^5HO)=6G5TgONU#V=JU{B0+|)5DR6hY^*aAqv9Zg4EGj6 z92)%MVKULu#jqc#w-T30g}q_?+yrPrL6!(~mH64ofG=J?KbejSZC{Nd#HR)7_T z%N6h=s#%-C6I=0(TWACMzqUXdsu#ERuADe=JCxBt?K|K-wYnS|AsA`^u)kup^!z%nG5~UZwfS%3i5;S4ZZUb`+dY9=c^YLvE z%GX{78~pyF4Q7h=tzbm0(Vfblx=PFOwM5qTwIm1IqWhwR{s?V`>Q01Wj delta 3014 zcmZWrc~p*R8$Z|ErkhMPQ&ZA5(MY8iAvBp5Es~IIWqIjEB&|w|3>l4O(4!uNV@XXh z#(3y4nz4tl2)Y94SqXZ6@FE3UkhPzW(@K@9%eQ_j6zO{XEtEU~>5flQmyi z3nwSv=)}~tl=SIo=_&fSkd*j1Ke1LGOJeQQ(Ig~)+R=_ZBr{FM`(NK{3jg`lK}s?x z3`QCL^Yt1zFFr9XNSCJb`yd7C6B7PM@cKWcJt@_Xo4HBJhN~SvPZUkI~h?|*L4lr>9jx(76{L#djgmTfO zg8|G9(FsO+WL5&Qv~xJYT=>WoM!If36vWvginVmhC<7xg$=^{ZwN}5gd&{Re(CuuD zm^j-iz)QBlz^+{$0*&lO!ade*IlJ$4{S~;=J{RcX5ChC}u!g(X;RzRC;$&)IE+UWQ z6uWP#K4cx1vHOvWP&T*&=7xA*G zZ&dd=#eYS&ZrK$Xy1+DT*>K(7;Kzf6)a;&wSe?fWpr7Y7&i0&&JWa2mTq@LS1+dPm zA5dsF086#~;l8dNZv^7qKM}a2e*mzdeK;nEgysV;1@yKMz0~T1&znE1NtNyE@1L72q=x~=5Kp}Dt5vFpyk9Rz~YHI;LC{vEQM94Yn_0cqsq^B_)BW`HQmrWUcWpO(oxQ>>rlC>l3BoavR^7Iri6xa&GYPX}-9yo=jj zSoQN-`9*G#P%LM3=SBIZ1O%{LT;)K6h-rr&cF3934Lj>n9 z<2{sK7es-K_~t+d5W_-e5ZA)?3#OGeL8_H1fEnw2f&14@hufxT4jxa*U;i8D58HSO z#IhuU58~9V4cG}KbX#ZYAPj#wUcN40NRzkQAtG!0MGhZPYS&R*HJLSiQi?OGZr4>E*lGJ z@2(4oU%b14*Ac((46v_q4`{PL$yz*HSYTsZka}+R-nDN!XShV(ypi=C-Vyt8$E3IW z%Yo$wu&t7=${Sc*b=O>!_g*kHcl|N16(>t7m)PJUs|(OTbFh)OxcN{TP+uJlsk(X< zOJRrkG4+ReR7&5YGqA_f#uI#r-<!Tfz12k0u%o$4j@yn z6mY#Q|BjkMJZ1-5bksYwsu7iRJ4?ruT=w~jY37QO6S{uavMmrW?K!~Hdv z^>~xOH)ul}rlFi`tA|#!o4z1%;4w9!p?uAN`WT6ba0j7U7?Ld{)!mr7B1sK3B^$)6 zIza}ovc4njgz4Cc215<$L`T?b(wU-+A;)||&tXPc(U(vQttgniSFLHL#zZ(o-QBoC z$RW0bo1osaB~LVvE$wMP=qY>h=Gea+h(AH{NGIy;ByI=9Q}*?oH|Ehv;Slzmsy4mbQR?+nWY4J>yL-Fpb`X1F8o0qimc; z`N%-(2lIR&-G#X_h|*z345rP9c`}&z1+N-^K@r@3j2{KUtoNgDp?*GuzCvBKB7pXB zrPvVi0WAul5)SSiMjsm} z(4tAylh=4uN1uWIM~70Nlctj==!)r-%hXg)_=c(L^wgJ+!Ra|vY$%5G5$d!A%Hn|f zxs-r_VTlyVRGLS7Km(GnO$Oq*kS8Zo4FvZT;;ST2Oe1_I<@_{0DzbAr;a*qGrw+(f zO*7h0qH2~&bv&KB*@VxznwUf3WGAK^jd*$bo93{Ed42ES8K0>+Y*W(G`tib#vxHig zOEKJBn@7EnFeQ&>a=lZFXg*KFFQ51&qJ}S}>~{TYDrK8cKs&gP`AT}rW)=BDjaWr* zu`g;wA$4KztRl3>w2J5%UYT-P33Z35DWUJ7W^E!rl#+ki%p;XsH&Y$dGh5pCQVrZn z#oXxHHhRq^FO|L91j7nGvvNiS;bSkqtf1X+?yRI2yxPs!KMwwGA1&sgx*wq7TqLuK z0-67Ou>C&C&wj!k6spf5iez^|HC6C9`y6fG;Mk*-3ibD+ILc6skJC4xYmU=0yqi>; zvt-TB=2c6_V4l^I6O83KzT9#`9gUMK&ruKjeo{vhRFCsy$E6WHUp{=1x8p;Kd) Date: Thu, 3 Aug 2023 17:16:34 +1000 Subject: [PATCH 3/4] dev: Fix some long line CI errors --- pyproject.toml | 3 ++- src/pydrex/diagnostics.py | 2 +- src/pydrex/minerals.py | 26 +++++++++++++++++--------- src/pydrex/stats.py | 3 ++- 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 37b9de59..3c0732ca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,4 +87,5 @@ exclude = [ ".vtu", ] [tool.ruff.per-file-ignores] -"src/pydrex/__init__.py" = ["F401"] # Don't complain about unused imports in __init__.py file. +"src/pydrex/__init__.py" = ["F401", "E501"] # Don't complain about unused imports or long lines in __init__.py file. +"tests/" = ["E501"] # Don't complain about long lines in test files. diff --git a/src/pydrex/diagnostics.py b/src/pydrex/diagnostics.py index 4e6a72c3..8c1e3ca2 100644 --- a/src/pydrex/diagnostics.py +++ b/src/pydrex/diagnostics.py @@ -156,7 +156,7 @@ def finite_strain(deformation_gradient, **kwargs): def symmetry(orientations, axis="a"): - r"""Compute texture symmetry eigenvalue diagnostics from olivine orientation matrices. + r"""Compute texture symmetry eigenvalue diagnostics from grain orientation matrices. Compute Point, Girdle and Random symmetry diagnostics for ternary texture classification. diff --git a/src/pydrex/minerals.py b/src/pydrex/minerals.py index 3ba8710d..60ea0edb 100644 --- a/src/pydrex/minerals.py +++ b/src/pydrex/minerals.py @@ -27,7 +27,8 @@ """Stiffness tensor for olivine (Voigt representation), with units of GPa. The source of the values used here is unknown, but they are copied -from the original DRex code: [88K download] +from the original DRex code: +[88K download] """ @@ -45,7 +46,8 @@ """Stiffness tensor for enstatite (Voigt representation), with units of GPa. The source of the values used here is unknown, but they are copied -from the original DRex code: [88K download] +from the original DRex code: +[88K download] """ @@ -83,7 +85,8 @@ def voigt_averages(minerals, weights): - `minerals` — list of `pydrex.minerals.Mineral` instances storing orientations and fractional volumes of the grains within each distinct mineral phase - `weights` (dict) — dictionary containing weights of each mineral - phase, as a fraction of 1, in keys named "_fraction", e.g. "olivine_fraction" + phase, as a fraction of 1, in keys named "_fraction", + e.g. "olivine_fraction" Raises a ValueError if the minerals contain an unequal number of grains or stored texture results. @@ -176,7 +179,7 @@ class Mineral: >>> regime=pydrex.DeformationRegime.dislocation, >>> n_grains=2000, >>> ) - Mineral(phase=0, fabric=0, regime=1, n_grains=2000, fractions=, orientations=) + Mineral(phase=0, fabric=0, regime=1, n_grains=2000, ...) Mineral with specified initial texture and default phase, fabric and regime settings which are for an olivine A-type mineral in the dislocation creep regime. @@ -190,18 +193,22 @@ class Mineral: >>> n_grains=n_grains, >>> fractions_init=np.full(n_grains, 1 / n_grains), >>> orientations_init=Rotation.from_euler( - >>> "zxz", [[x * np.pi / 2, np.pi / /2, np.pi / 2] for x in rng.random(n_grains)] + >>> "zxz", [ + >>> [x * np.pi / 2, np.pi / /2, np.pi / 2] for x in rng.random(n_grains) + >>> ] >>> ).inv().as_matrix(), >>> ) - Mineral(phase=0, fabric=0, regime=1, n_grains=2000, fractions=, orientations=) + Mineral(phase=0, fabric=0, regime=1, n_grains=2000, ...) **Attributes:** - `phase` (`pydrex.core.MineralPhase`) — ordinal number of the mineral phase - `fabric` (`pydrex.core.MineralFabric`) — ordinal number of the fabric type - - `regime` (`pydrex.core.DeformationRegime`) — ordinal number of the deformation regime + - `regime` (`pydrex.core.DeformationRegime`) — ordinal number of the deformation + regime - `n_grains` (int) — number of grains in the aggregate - `fractions` (list of arrays) — grain volume fractions for each texture snapshot - - `orientations` (list of arrays) — grain orientation matrices for each texture snapshot + - `orientations` (list of arrays) — grain orientation matrices for each texture + snapshot - `seed` (`int`) — seed used by the random number generator to set up the isotropic initial condition when `fractions_init` or `orientations_init` are not provided @@ -241,7 +248,8 @@ def __str__(self): + f"fractions=<{self.fractions.__class__.__qualname__}" + f" of {self.fractions[0].__class__.__qualname__} {shape_of_fractions}>, " + f"orientations=<{self.orientations.__class__.__qualname__}" - + f" of {self.orientations[0].__class__.__qualname__} {shape_of_orientations}>)" + + f" of {self.orientations[0].__class__.__qualname__}" + + f" {shape_of_orientations}>)" ) def _repr_pretty_(self, p, cycle): diff --git a/src/pydrex/stats.py b/src/pydrex/stats.py index 6ac42545..37222b11 100644 --- a/src/pydrex/stats.py +++ b/src/pydrex/stats.py @@ -157,7 +157,8 @@ def point_density( - `gridsteps` (int) — the number of steps, i.e. number of points along a diameter of the spherical counting grid - `weights` (array) — auxiliary weights for each data point - - `kernel` (string) — the name of the kernel function to use, see `SPHERICAL_COUNTING_KERNELS` + - `kernel` (string) — the name of the kernel function to use, see + `SPHERICAL_COUNTING_KERNELS` - `axial` (bool) — toggle axial versions of the kernel functions (for crystallographic data this should normally be kept as `True`) From f70f1e187e0f8e26b487c4ada605f6032a36157d Mon Sep 17 00:00:00 2001 From: adigitoleo Date: Thu, 3 Aug 2023 17:20:05 +1000 Subject: [PATCH 4/4] dev: Exclude vtu files from pre-commit fixes --- .pre-commit-config.yaml | 2 ++ pyproject.toml | 2 +- .../steadyflow/corner2d_2cmyr_5e5x1e5.vtu | Bin 120788 -> 120791 bytes 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4a77b13b..ee1a0df6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -43,3 +43,5 @@ repos: rev: v0.0.282 hooks: - id: ruff + +exclude: \.*vtu$ diff --git a/pyproject.toml b/pyproject.toml index 3c0732ca..9a85eed9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -88,4 +88,4 @@ exclude = [ ] [tool.ruff.per-file-ignores] "src/pydrex/__init__.py" = ["F401", "E501"] # Don't complain about unused imports or long lines in __init__.py file. -"tests/" = ["E501"] # Don't complain about long lines in test files. +"tests/test_scsv.py" = ["E501"] # Don't complain about long lines here. diff --git a/src/pydrex/data/steadyflow/corner2d_2cmyr_5e5x1e5.vtu b/src/pydrex/data/steadyflow/corner2d_2cmyr_5e5x1e5.vtu index 3b3fa9f56e2abb28dc03068a0513893271887490..887be72d470e88b958db565dbde21c9d2d5bc4fa 100644 GIT binary patch delta 3014 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