[ { "repo": "sympy/sympy", "pull_number": 26980, "instance_id": "sympy__sympy-26980", "issue_numbers": [ "14103", "22200" ], "base_commit": "bdc14c5b925389fcbfd92d08a000ba5527e6190e", "patch": "diff --git a/sympy/concrete/summations.py b/sympy/concrete/summations.py\nindex e167c77e462e..e093cc46d4e6 100644\n--- a/sympy/concrete/summations.py\n+++ b/sympy/concrete/summations.py\n@@ -536,7 +536,7 @@ def is_convergent(self):\n ratio = combsimp(powsimp(next_sequence_term/sequence_term))\n try:\n lim_ratio = limit_seq(ratio, sym)\n- if lim_ratio is not None and lim_ratio.is_number:\n+ if lim_ratio is not None and lim_ratio.is_number and lim_ratio is not S.NaN:\n if abs(lim_ratio) > 1:\n return S.false\n if abs(lim_ratio) < 1:\n", "test_patch": "diff --git a/sympy/concrete/tests/test_sums_products.py b/sympy/concrete/tests/test_sums_products.py\nindex 806f2edb1c1f..eeb923c24864 100644\n--- a/sympy/concrete/tests/test_sums_products.py\n+++ b/sympy/concrete/tests/test_sums_products.py\n@@ -1057,6 +1057,7 @@ def test_is_convergent():\n assert Sum((-1)**n*n, (n, 3, oo)).is_convergent() is S.false\n assert Sum((-1)**n, (n, 1, oo)).is_convergent() is S.false\n assert Sum(log(1/n), (n, 2, oo)).is_convergent() is S.false\n+ assert Sum(sin(n), (n, 1, oo)).is_convergent() is S.false\n \n # Raabe's test --\n assert Sum(Product((3*m),(m,1,n))/Product((3*m+4),(m,1,n)),(n,1,oo)).is_convergent() is S.true\n@@ -1157,6 +1158,11 @@ def test_issue_10973():\n assert Sum((-n + (n**3 + 1)**(S(1)/3))/log(n), (n, 1, oo)).is_convergent() is S.true\n \n \n+def test_issue_14103():\n+ assert Sum(sin(n)**2 + cos(n)**2 - 1, (n, 1, oo)).is_convergent() is S.true\n+ assert Sum(sin(pi*n), (n, 1, oo)).is_convergent() is S.true\n+\n+\n def test_issue_14129():\n x = Symbol('x', zero=False)\n assert Sum( k*x**k, (k, 0, n-1)).doit() == \\\n", "problem_statement": "Sum(sin(n), (n, 1, oo)).is_convergent() is not implemented\n`>>> Sum(sin(n), (n, 1, oo)).is_convergent()`\r\nTraceback (most recent call last):\r\n\r\n File \"\", line 1, in \r\n Sum(sin(n), (n, 1, oo)).is_convergent()\r\n\r\n File \"C:\\Users\\Roland\\Anaconda3\\lib\\site-packages\\sympy\\concrete\\summations.py\", line 528, in is_convergent\r\n \"is not yet implemented\" % (sequence_term))\r\n\r\nNotImplementedError: The algorithm to find the Sum convergence of sin(n) is not yet implemented\nFixes Ratio Test which wasn't checking whether the limit ratio was NaN or not leading into wrong results from basic Sums \n\r\n\r\n#### References to other Issues or PRs\r\n\r\nFixes #14103 \r\n#### Brief description of what is fixed or changed\r\nWhile going through the above issue ,I realized that the main point of the issue has been addressed which is `Sum(sin(n) , (n, 1, oo)).is_convergent()` returns `False` but a somewhat faulty PR attached to that issue which was eventually closed wasn't following few is_convergent() results . I just ended up trying those eg` Sum(sin(x)**2 + cos(x)**2 -1 , (x, 1, oo)).is_convergent()` etc and found that they weren't giving expected results . I addressed the issue in the comment section.\r\n\r\nSo the problem was in the ratio test ,where lim_ratio was `NaN` but was passing the is_number test and hence comparing `NaN `with 1 in abs(lim_ratio > 1) was giving the `TypeError` .The snippet below shows the debugging.\r\n```\r\n>>> sequence_term = s.function.simplify()\r\n>>> next_sequence_term = sequence_term.xreplace({sym: sym + 1})\r\n>>> next_sequence_term\r\n0\r\n>>> ratio = combsimp(powsimp(next_sequence_term/sequence_term))\r\n>>> ratio\r\nnan\r\n>>> lim_ratio = limit_seq(ratio, sym)\r\n>>> type(lim_ratio)\r\n\r\n>>> lim_ratio.is_number\r\nTrue\r\n```\r\nI tried to find the cause of this error but the `is_number` function seems to work flawlessly in the both expr.py and expr_with_limits.py and disturbing that by changing something there didn't seem correct as it even runs recursively.\r\n\r\nI also realized that the `doit()` won't work as expected for these as it checks for `f.is_zero `but something like `sin(x)**2 + cos(x)**2 -1 ,` `cos(x) * sec(x) -1 ` or `sin(pi*x)` may not return 0 without `f.simplify()` . Hence I also changed that and added couple of tests.\r\nSome suitable examples: ( The root test is now responsible for returning True for these)\r\n```\r\nx = Symbol('x', integer = True)\r\n>>> Sum(sin(x)**2 + cos(x)**2 -1 , (x, 1, oo)).doit()\r\n0\r\n>>> Sum(sin(x)**2 + cos(x)**2 -1 , (x, 1, oo)).is_convergent()\r\nTrue\r\n>>> Sum(cos(x) * sec(x) -1 , (x, 1, oo)).is_convergent()\r\nTrue\r\n>>> Sum(cos(x) * sec(x) -1 , (x, 1, oo)).doit()\r\n0\r\n>>> Sum(sin(pi*x) , (x, 1, oo)).is_convergent()\r\nTrue\r\n```\r\n\r\n#### Other comments\r\n\r\n\r\n#### Release Notes\r\n\r\n\r\n\r\n\r\n* concrete\r\n * Fixes Ratio Test which wasn't checking whether the limit ratio was NaN or not leading into wrong results from basic Sums.\r\n\r\n\n", "hints_text": "Related https://github.com/sympy/sympy/issues/8328\r\n\r\nWhat convergence check can be implemented to show that the sum does not converge? \nIn this case, it is sufficient to check the limit of `sin(n)` as `n` tends to `oo`. Since the limit does not exist (in particular is different from `0`), the sum can not be convergent.\nThis seems to be implemented as of now , but going through some cases as discussed in the above closed PR (wasn't the correct solution for this ) I discovered that `Sum(expr , (n, x, y))` where expr is 0 gives `TypeError` .\r\n\r\n![image](https://user-images.githubusercontent.com/87052487/134623100-bbb8e493-d1a7-4a01-aa7c-206a9ebb3809.png)\r\n\n@anutosh491 please use markdown code formatting rather than a screenshot so that it is copyable. A convenient way to make something that you can copy/paste straight into github is using isympy like this:\r\n```python\r\nIn [19]: Sum(sin(n), (n, 1, oo)).is_convergent()\r\nOut[19]: False\r\n\r\nIn [20]: Sum(sin(n)**2 + cos(n)**2 - 1, (n, 1, oo)).is_convergent()\r\n---------------------------------------------------------------------------\r\nTypeError Traceback (most recent call last)\r\n in \r\n----> 1 Sum(sin(n)**2 + cos(n)**2 - 1, (n, 1, oo)).is_convergent()\r\n\r\n~/current/sympy/sympy.git/sympy/concrete/summations.py in is_convergent(self)\r\n 519 lim_ratio = limit_seq(ratio, sym)\r\n 520 if lim_ratio is not None and lim_ratio.is_number:\r\n--> 521 if abs(lim_ratio) > 1:\r\n 522 return S.false\r\n 523 if abs(lim_ratio) < 1:\r\n\r\n~/current/sympy/sympy.git/sympy/core/decorators.py in _func(self, other)\r\n 264 if not isinstance(other, expectedcls):\r\n 265 return retval\r\n--> 266 return func(self, other)\r\n 267 \r\n 268 return _func\r\n\r\n~/current/sympy/sympy.git/sympy/core/expr.py in __gt__(self, other)\r\n 377 def __gt__(self, other):\r\n 378 from .relational import StrictGreaterThan\r\n--> 379 return StrictGreaterThan(self, other)\r\n 380 \r\n 381 @sympify_return([('other', 'Expr')], NotImplemented)\r\n\r\n~/current/sympy/sympy.git/sympy/core/relational.py in __new__(cls, lhs, rhs, **options)\r\n 706 raise TypeError(\"Invalid comparison of non-real %s\" % me)\r\n 707 if me is S.NaN:\r\n--> 708 raise TypeError(\"Invalid NaN comparison\")\r\n 709 # First we invoke the appropriate inequality method of `lhs`\r\n 710 # (e.g., `lhs.__lt__`). That method will try to reduce to\r\n\r\nTypeError: Invalid NaN comparison\r\n\r\n```\n:white_check_mark:\n\nHi, I am the [SymPy bot](https://github.com/sympy/sympy-bot) (v162). I'm here to help you write a release notes entry. Please read the [guide on how to write release notes](https://github.com/sympy/sympy/wiki/Writing-Release-Notes).\n\n\n\nYour release notes are in good order.\n\nHere is what the release notes will look like:\n* concrete\n * Fixes Ratio Test which wasn't checking whether the limit ratio was NaN or not leading into wrong results from basic Sums. ([#22200](https://github.com/sympy/sympy/pull/22200) by [@anutosh491](https://github.com/anutosh491))\n\nThis will be added to https://github.com/sympy/sympy/wiki/Release-Notes-for-1.11.\n\n
Click here to see the pull request description that was parsed.\n\n \r\n\r\n #### References to other Issues or PRs\r\n \r\n Fixes #14103 \r\n #### Brief description of what is fixed or changed\r\n While going through the above issue ,I realized that the main point of the issue has been addressed which is `Sum(sin(n) , (n, 1, oo)).is_convergent()` returns `False` but a somewhat faulty PR attached to that issue which was eventually closed wasn't following few is_convergent() results . I just ended up trying those eg` Sum(sin(x)**2 + cos(x)**2 -1 , (x, 1, oo)).is_convergent()` etc and found that they weren't giving expected results . I addressed the issue in the comment section.\r\n\r\n So the problem was in the ratio test ,where lim_ratio was `NaN` but was passing the is_number test and hence comparing `NaN `with 1 in abs(lim_ratio > 1) was giving the `TypeError` .The snippet below shows the debugging.\r\n ```\r\n >>> sequence_term = s.function.simplify()\r\n >>> next_sequence_term = sequence_term.xreplace({sym: sym + 1})\r\n >>> next_sequence_term\r\n 0\r\n >>> ratio = combsimp(powsimp(next_sequence_term/sequence_term))\r\n >>> ratio\r\n nan\r\n >>> lim_ratio = limit_seq(ratio, sym)\r\n >>> type(lim_ratio)\r\n \r\n >>> lim_ratio.is_number\r\n True\r\n ```\r\n I tried to find the cause of this error but the `is_number` function seems to work flawlessly in the both expr.py and expr_with_limits.py and disturbing that by changing something there didn't seem correct as it even runs recursively.\r\n\r\n I also realized that the `doit()` won't work as expected for these as it checks for `f.is_zero `but something like `sin(x)**2 + cos(x)**2 -1 ,` `cos(x) * sec(x) -1 ` or `sin(pi*x)` may not return 0 without `f.simplify()` . Hence I also changed that and added couple of tests.\r\n Some suitable examples: ( The root test is now responsible for returning True for these)\r\n ```\r\n x = Symbol('x', integer = True)\r\n >>> Sum(sin(x)**2 + cos(x)**2 -1 , (x, 1, oo)).doit()\r\n 0\r\n >>> Sum(sin(x)**2 + cos(x)**2 -1 , (x, 1, oo)).is_convergent()\r\n True\r\n >>> Sum(cos(x) * sec(x) -1 , (x, 1, oo)).is_convergent()\r\n True\r\n >>> Sum(cos(x) * sec(x) -1 , (x, 1, oo)).doit()\r\n 0\r\n >>> Sum(sin(pi*x) , (x, 1, oo)).is_convergent()\r\n True\r\n ```\r\n\r\n #### Other comments\r\n\r\n\r\n #### Release Notes\r\n\r\n \r\n\r\n \r\n * concrete\r\n * Fixes Ratio Test which wasn't checking whether the limit ratio was NaN or not leading into wrong results from basic Sums.\r\n \r\n\n\n

\n\nBenchmark results from GitHub Actions\n\nLower numbers are good, higher numbers are bad. A ratio less than 1\nmeans a speed up and greater than 1 means a slowdown. Green lines\nbeginning with `+` are slowdowns (the PR is slower then master or\nmaster is slower than the previous release). Red lines beginning\nwith `-` are speedups.\n\nSignificantly changed benchmark results (PR vs master)\n```diff\n before after ratio\n [4028a7f5] [e379dd4a]\n+ 114\u00b13ms 1.20\u00b10.01s 10.55 sum.TimeSum.time_doit\n\n```\nSignificantly changed benchmark results (master vs previous release)\n```diff\n\n```\nFull benchmark results can be found as artifacts in GitHub Actions\n(click on checks at the top of the PR).\nCan you change the title of the PR to something that describes what was actually changed? It's always useful to have a reference to other PRs or issues in the OP for further information but the title should communicate what is being changed at a high-level without a need to go look up some other issue to understand.\r\n\r\n\r\n\n> Can you change the title of the PR to something that describes what was actually changed? It's always useful to have a reference to other PRs or issues in the OP for further information but the title should communicate what is being changed at a high-level without a need to go look up some other issue to understand.\r\n\r\nThanks @oscarbenjamin , will keep in mind for future Pr's\nThe underlying issue causing the test failure is fixed in master, so if you can rebase based on that and push again (you may have to force push) is should be possible to try simplify again.\n> The underlying issue causing the test failure is fixed in master, so if you can rebase based on that and push again (you may have to force push) is should be possible to try simplify again.\r\n\r\nYeah , I've done the needful , hopefully this would work ! I even realized that this will also be fixing some basic standard examples that can be found in almost all text books saying divergent + divergent need not be divergent . Those examples are \r\n`Sum(1/x + (-1/x) , (x, 1, oo)).is_convergent()` ,` Sum((-1)**x + (-1)**(x+1) , (x, 1, oo)).is_convergent()`\r\nThese weren't working properly in sympy and I guess were failing due to the same reason\r\n```\r\nTraceback (most recent call last):\r\n File \"\", line 1, in \r\n File \"c:\\users\\anuto\\sympy\\sympy\\sympy\\concrete\\summations.py\", line 521, in is_convergent\r\n if abs(lim_ratio) > 1:\r\n File \"c:\\users\\anuto\\sympy\\sympy\\sympy\\core\\decorators.py\", line 266, in _func\r\n return func(self, other)\r\n File \"c:\\users\\anuto\\sympy\\sympy\\sympy\\core\\expr.py\", line 380, in __gt__\r\n return StrictGreaterThan(self, other)\r\n File \"c:\\users\\anuto\\sympy\\sympy\\sympy\\core\\relational.py\", line 708, in __new__\r\n raise TypeError(\"Invalid NaN comparison\")\r\nTypeError: Invalid NaN comparison\r\n```\r\nEDIT:\r\nHi @oscargus ,I have forced pushed the required changes and everything works fine . I feel this could be merged ,seems fine to me . Would be glad if you could revert back with your thoughts ! I write this edit as someone on the gitter channel had pointed out the exact same issue yesterday , so thought of asking you if we could get this fixed.Thank You !\nNew features introduced after latest commit :\r\non Master:\r\n```\r\n>>>Sum(sin(x)**2 + cos(x)**2 , (x, 1, 10)).doit()\r\nsin(3)**2 + cos(8)**2 + sin(6)**2 + cos(5)**2 + sin(9)**2 + cos(2)**2 + cos(1)**2 + sin(10)**2 + cos(4)**2 + sin(7)**2 + cos(7)**2 + sin(4)**2 + cos(10)**2 + sin(1)**2 + sin(2)**2 + cos(9)**2 + sin(5)**2 + cos(6)**2 + sin(8)**2 + cos(3)**2\r\n\r\n>>>Sum(sin(x)**2 + cos(x)**2 , (x, 1, 1000)).doit() # returns 1000 sin and 1000 cos terms \r\n\r\n>>>Sum(sin(x)**2 + cos(x)**2 - 1, (x, 1, 1000)).doit() # Similar as above \r\n```\r\n\r\nOn committed branch\r\n```\r\n>>> Sum(sin(x)**2 + cos(x)**2 , (x, 1, 10)).doit()\r\n10\r\n>>> Sum(sin(x)**2 + cos(x)**2 , (x, 1, 1000)).doit()\r\n1000\r\n>>> Sum(cos(pi*x) , (x, 1, 50)).doit()\r\n0\r\n>>> Sum(cos(pi*x) , (x, 1, 49)).doit()\r\n-1\r\n>>> Sum(sec(x)*cos(x) , (x, 1, 50)).doit()\r\n50\r\n```\nping @oscargus for review . I would be glad if you could have a look . This could maybe go in as the issue and some related things have been addressed !\n Hey @oscargus I am satisfied with the changes from my side . Please have a look and merge possibly if you don't have any concerns\n@oscargus I could spot that the ratio test fix here is the reason behind couple of prs being stalled hence I would be glad if you could help me get this in . The pr included some other fixes too which involve simplifying trig functions .Basically the motive behind that is \r\n\r\n```\r\nSum(sin(x)**2 + cos(x)**2 - 1, (x, 1, 100)).doit()\r\nOut[43]: \r\n-100 + cos(11)**2 + sin(22)**2 + cos(33)**2 + sin(44)**2 + cos(55)**2 + sin(66)**2 + cos(77)**2 + sin(88)**2 + cos(99)**2 + sin(91)**2 + cos(80)**2 + sin(69)**2 + cos(58)**2 + sin(47)**2 + cos(36)**2 + sin(25)**2 + cos(14)**2 + sin(3)**2 + cos(8)**2 + sin(19)**2 + cos(30)**2 + sin(41)**2 + cos(52)**2 + sin(63)**2 + cos(74)**2 + sin(85)**2 + cos(96)**2 + sin(94)**2 + cos(83)**2 + sin(72)**2 + cos(61)**2 + sin(50)**2 + cos(39)**2 + sin(28)**2 + cos(17)**2 + sin(6)**2 + cos(5)**2 + sin(16)**2 + cos(27)**2 + sin(38)**2 + cos(49)**2 + sin(60)**2 + cos(71)**2 + sin(82)**2 + cos(93)**2 + sin(97)**2 + cos(86)**2 + sin(75)**2 + cos(64)**2 + sin(53)**2 + cos(42)**2 + sin(31)**2 + cos(20)**2 + sin(9)**2 + cos(2)**2 + sin(13)**2 + cos(24)**2 + sin(35)**2 + cos(46)**2 + sin(57)**2 + cos(68)**2 + sin(79)**2 + cos(90)**2 + sin(100)**2 + cos(89)**2 + sin(78)**2 + cos(67)**2 + sin(56)**2 + cos(45)**2 + sin(34)**2 + cos(23)**2 + sin(12)**2 + cos(1)**2 + sin(10)**2 + cos(21)**2 + sin(32)**2 + cos(43)**2 + sin(54)**2 + cos(65)**2 + sin(76)**2 + cos(87)**2 + sin(98)**2 + cos(92)**2 + sin(81)**2 + cos(70)**2 + sin(59)**2 + cos(48)**2 + sin(37)**2 + cos(26)**2 + sin(15)**2 + cos(4)**2 + sin(7)**2 + cos(18)**2 + sin(29)**2 + cos(40)**2 + sin(51)**2 + cos(62)**2 + sin(73)**2 + cos(84)**2 + sin(95)**2 + cos(95)**2 + sin(84)**2 + cos(73)**2 + sin(62)**2 + cos(51)**2 + sin(40)**2 + cos(29)**2 + sin(18)**2 + cos(7)**2 + sin(4)**2 + cos(15)**2 + sin(26)**2 + cos(37)**2 + sin(48)**2 + cos(59)**2 + sin(70)**2 + cos(81)**2 + sin(92)**2 + cos(98)**2 + sin(87)**2 + cos(76)**2 + sin(65)**2 + cos(54)**2 + sin(43)**2 + cos(32)**2 + sin(21)**2 + cos(10)**2 + sin(1)**2 + cos(12)**2 + sin(23)**2 + cos(34)**2 + sin(45)**2 + cos(56)**2 + sin(67)**2 + cos(78)**2 + sin(89)**2 + cos(100)**2 + sin(90)**2 + cos(79)**2 + sin(68)**2 + cos(57)**2 + sin(46)**2 + cos(35)**2 + sin(24)**2 + cos(13)**2 + sin(2)**2 + cos(9)**2 + sin(20)**2 + cos(31)**2 + sin(42)**2 + cos(53)**2 + sin(64)**2 + cos(75)**2 + sin(86)**2 + cos(97)**2 + sin(93)**2 + cos(82)**2 + sin(71)**2 + cos(60)**2 + sin(49)**2 + cos(38)**2 + sin(27)**2 + cos(16)**2 + sin(5)**2 + cos(6)**2 + sin(17)**2 + cos(28)**2 + sin(39)**2 + cos(50)**2 + sin(61)**2 + cos(72)**2 + sin(83)**2 + cos(94)**2 + sin(96)**2 + cos(85)**2 + sin(74)**2 + cos(63)**2 + sin(52)**2 + cos(41)**2 + sin(30)**2 + cos(19)**2 + sin(8)**2 + cos(3)**2 + sin(14)**2 + cos(25)**2 + sin(36)**2 + cos(47)**2 + sin(58)**2 + cos(69)**2 + sin(80)**2 + cos(91)**2 + sin(99)**2 + cos(88)**2 + sin(77)**2 + cos(66)**2 + sin(55)**2 + cos(44)**2 + sin(33)**2 + cos(22)**2 + sin(11)**2\r\n```\r\nReturning this with random sin cosine terms to the user makes no sense to me . This was also raised in the issue reported above , it's much better to have `trigsimp` here and return 0 here !\r\n\r\n```\r\n>>> Sum(sin(x)**2 + cos(x)**2 - 1, (x, 1, 100)).doit()\r\n0\r\n```\r\nThe main part still being the ratio test and this issue has been raised a couple of time or more which I might have not noticed . Hence I would be glad to hear from you !\r\n\nPing @anutosh491 \r\nWhat is the status of this pull request ?One of my pull request #22809 is dependent on some wrong results which this pull request fixes .\r\nThanks . \n> Ping @anutosh491 @jksuom What is the status of this pull request ?One of my pull request #22809 is dependent on some wrong results which this pull request fixes . Thanks .\r\n\r\nI am satisfied with the changes but @smichr or @oscargus should review !\nping @oscargus for review !\ncc: @oscargus , would be glad to hear on this from your side ! it mainly addresses two issues which have been mentioned here https://github.com/sympy/sympy/pull/22200#issuecomment-1025344093", "created_at": "2024-08-19T07:47:09Z" }, { "repo": "sympy/sympy", "pull_number": 26955, "instance_id": "sympy__sympy-26955", "issue_numbers": [ "26856" ], "base_commit": "823065c6d82ef2ceb9c0c78ef19ae94685dbdfef", "patch": "diff --git a/sympy/core/expr.py b/sympy/core/expr.py\nindex c6be21344a60..a29b8298010f 100644\n--- a/sympy/core/expr.py\n+++ b/sympy/core/expr.py\n@@ -2967,6 +2967,11 @@ def series(self, x=None, x0=0, n=6, dir=\"+\", logx=None, cdir=0):\n if len(dir) != 1 or dir not in '+-':\n raise ValueError(\"Dir must be '+' or '-'\")\n \n+ if n is not None:\n+ n = int(n)\n+ if n < 0:\n+ raise ValueError(\"Number of terms should be nonnegative\")\n+\n x0 = sympify(x0)\n cdir = sympify(cdir)\n from sympy.functions.elementary.complexes import im, sign\n", "test_patch": "diff --git a/sympy/series/tests/test_series.py b/sympy/series/tests/test_series.py\nindex 2adeef40f8a2..6ae7ed2b848b 100644\n--- a/sympy/series/tests/test_series.py\n+++ b/sympy/series/tests/test_series.py\n@@ -402,3 +402,7 @@ def test_issue_24266():\n #type3: f(y)**g(x)\n assert ((y)**(I*pi*(2*x+1))).series(x, 0, 2) == exp(I*pi*log(y)) + 2*I*pi*x*exp(I*pi*log(y))*log(y) + O(x**2)\n assert ((I*y)**(I*pi*(2*x+1))).series(x, 0, 2) == exp(I*pi*log(I*y)) + 2*I*pi*x*exp(I*pi*log(I*y))*log(I*y) + O(x**2)\n+\n+\n+def test_issue_26856():\n+ raises(ValueError, lambda: (2**x).series(x, oo, -1))\n", "problem_statement": "very simple example of series bug\n```\r\n>>> series(2**n, n, oo, -1)\r\nO(n, (n, oo))\r\n```\r\n\r\nIsn't it wrong? Of course 2^n is not O(n) as n->inf...\n", "hints_text": "You are not supposed to put -1 as the number of terms I guess...\nAh, sorry.. I thought giving k here is saying that I need the expression up to O(n^{-k}) error\r\nBtw, is there a way to tell it that I need k leading terms instead of giving the absolute error? I often want to estimate something for theoretical computer science, where I need an expansion of a fast-growing function like binomial(n, n/3), but I often need only 1-2 leading terms of it, not up to O(1)", "created_at": "2024-08-13T09:36:12Z" }, { "repo": "sympy/sympy", "pull_number": 26954, "instance_id": "sympy__sympy-26954", "issue_numbers": [ "26916", "26937" ], "base_commit": "823065c6d82ef2ceb9c0c78ef19ae94685dbdfef", "patch": "diff --git a/sympy/functions/special/error_functions.py b/sympy/functions/special/error_functions.py\nindex fd616c49af6f..945bad4c4384 100644\n--- a/sympy/functions/special/error_functions.py\n+++ b/sympy/functions/special/error_functions.py\n@@ -1263,7 +1263,7 @@ def _eval_aseries(self, n, args0, x, logx):\n from sympy.series.order import Order\n point = args0[0]\n \n- if point is S.Infinity:\n+ if point in (S.Infinity, S.NegativeInfinity):\n z = self.args[0]\n s = [factorial(k) / (z)**k for k in range(n)] + \\\n [Order(1/z**n, x)]\n@@ -2766,8 +2766,8 @@ class _eis(Function):\n \n def _eval_aseries(self, n, args0, x, logx):\n from sympy.series.order import Order\n- if args0[0] != S.Infinity:\n- return super(_erfs, self)._eval_aseries(n, args0, x, logx)\n+ if args0[0] not in (S.Infinity, S.NegativeInfinity):\n+ return super()._eval_aseries(n, args0, x, logx)\n \n z = self.args[0]\n l = [factorial(k) * (1/z)**(k + 1) for k in range(n)]\n", "test_patch": "diff --git a/sympy/functions/special/tests/test_error_functions.py b/sympy/functions/special/tests/test_error_functions.py\nindex 051e1af4200b..b3085e8e92c1 100644\n--- a/sympy/functions/special/tests/test_error_functions.py\n+++ b/sympy/functions/special/tests/test_error_functions.py\n@@ -437,6 +437,10 @@ def test_ei():\n assert Ei(x).series(x, 1, 3) == Ei(1) + E*(x - 1) + O((x - 1)**3, (x, 1))\n assert Ei(x).series(x, oo) == \\\n (120/x**5 + 24/x**4 + 6/x**3 + 2/x**2 + 1/x + 1 + O(x**(-6), (x, oo)))*exp(x)/x\n+ assert Ei(x).series(x, -oo) == \\\n+ (120/x**5 + 24/x**4 + 6/x**3 + 2/x**2 + 1/x + 1 + O(x**(-6), (x, -oo)))*exp(x)/x\n+ assert Ei(-x).series(x, oo) == \\\n+ -((-120/x**5 + 24/x**4 - 6/x**3 + 2/x**2 - 1/x + 1 + O(x**(-6), (x, oo)))*exp(-x)/x)\n \n assert str(Ei(cos(2)).evalf(n=10)) == '-0.6760647401'\n raises(ArgumentIndexError, lambda: Ei(x).fdiff(2))\ndiff --git a/sympy/series/tests/test_limits.py b/sympy/series/tests/test_limits.py\nindex 21777c15e65c..ac28ad3eafbf 100644\n--- a/sympy/series/tests/test_limits.py\n+++ b/sympy/series/tests/test_limits.py\n@@ -1412,3 +1412,8 @@ def test_issue_26250():\n e1 = ((1-3*x**2)*e**2/2 - (x**2-2*x+1)*e*k/2)\n e2 = pi**2*(x**8 - 2*x**7 - x**6 + 4*x**5 - x**4 - 2*x**3 + x**2)\n assert limit(e1/e2, x, 0) == -S(1)/8\n+\n+\n+def test_issue_26916():\n+ assert limit(Ei(x)*exp(-x), x, +oo) == 0\n+ assert limit(Ei(x)*exp(-x), x, -oo) == 0\n", "problem_statement": "Ei(-x) * exp(x) (exponential integral) limit to infinity error out\nThe following limit computes fine:\r\n\r\n```\r\n(Ei(x) * exp(-x)).limit(x, oo)\r\n# Result: 0\r\n```\r\n\r\nBut the following doesn't:\r\n\r\n```\r\n(Ei(-x) * exp(x)).limit(x, oo)\r\n# Or equivalent: (Ei(x) * exp(-x)).limit(x, -oo)\r\n# Expected: 0\r\n```\r\n\r\nInstead, sympy will error out with\r\n\r\n```\r\n---------------------------------------------------------------------------\r\nValueError Traceback (most recent call last)\r\nFile ~\\AppData\\Roaming\\Python\\Python39\\site-packages\\sympy\\series\\gruntz.py:557, in mrv_leadterm(e, x)\r\n 556 try:\r\n--> 557 lt = f.leadterm(w, logx=logw)\r\n 558 except (NotImplementedError, PoleError, ValueError):\r\n\r\nFile ~\\AppData\\Roaming\\Python\\Python39\\site-packages\\sympy\\core\\expr.py:3533, in Expr.leadterm(self, x, logx, cdir)\r\n 3532 if x in c.free_symbols:\r\n-> 3533 raise ValueError(filldedent(\"\"\"\r\n 3534 cannot compute leadterm(%s, %s). The coefficient\r\n 3535 should have been free of %s but got %s\"\"\" % (self, x, x, c)))\r\n 3536 c = c.subs(d, log(x))\r\n\r\nValueError: \r\ncannot compute leadterm(_eis(-1/_w), _w). The coefficient should have\r\nbeen free of _w but got _eis(-1/_w)\r\n\r\nDuring handling of the above exception, another exception occurred:\r\n\r\nTypeError Traceback (most recent call last)\r\nCell In[84], line 2\r\n 1 (Ei(x) * exp(-x)).limit(x, oo)\r\n----> 2 (Ei(x) * exp(-x)).limit(x, -oo)\r\n\r\nFile ~\\AppData\\Roaming\\Python\\Python39\\site-packages\\sympy\\core\\expr.py:3418, in Expr.limit(self, x, xlim, dir)\r\n 3415 \"\"\" Compute limit x->xlim.\r\n 3416 \"\"\"\r\n 3417 from sympy.series.limits import limit\r\n-> 3418 return limit(self, x, xlim, dir)\r\n\r\nFile ~\\AppData\\Roaming\\Python\\Python39\\site-packages\\sympy\\series\\limits.py:64, in limit(e, z, z0, dir)\r\n 13 def limit(e, z, z0, dir=\"+\"):\r\n 14 \"\"\"Computes the limit of ``e(z)`` at the point ``z0``.\r\n 15 \r\n 16 Parameters\r\n (...)\r\n 61 limit_seq : returns the limit of a sequence.\r\n 62 \"\"\"\r\n---> 64 return Limit(e, z, z0, dir).doit(deep=False)\r\n\r\nFile ~\\AppData\\Roaming\\Python\\Python39\\site-packages\\sympy\\series\\limits.py:375, in Limit.doit(self, **hints)\r\n 372 l = None\r\n 374 try:\r\n--> 375 r = gruntz(e, z, z0, dir)\r\n 376 if r is S.NaN or l is S.NaN:\r\n 377 raise PoleError()\r\n\r\nFile ~\\AppData\\Roaming\\Python\\Python39\\site-packages\\sympy\\series\\gruntz.py:733, in gruntz(e, z, z0, dir)\r\n 730 else:\r\n 731 raise NotImplementedError(\"dir must be '+' or '-'\")\r\n--> 733 r = limitinf(e0, z)\r\n 735 # This is a bit of a heuristic for nice results... we always rewrite\r\n 736 # tractable functions in terms of familiar intractable ones.\r\n 737 # It might be nicer to rewrite the exactly to what they were initially,\r\n 738 # but that would take some work to implement.\r\n 739 return r.rewrite('intractable', deep=True)\r\n\r\nFile ~\\AppData\\Roaming\\Python\\Python39\\site-packages\\sympy\\core\\cache.py:72, in __cacheit..func_wrapper..wrapper(*args, **kwargs)\r\n 69 @wraps(func)\r\n 70 def wrapper(*args, **kwargs):\r\n 71 try:\r\n---> 72 retval = cfunc(*args, **kwargs)\r\n 73 except TypeError as e:\r\n 74 if not e.args or not e.args[0].startswith('unhashable type:'):\r\n\r\nFile ~\\AppData\\Roaming\\Python\\Python39\\site-packages\\sympy\\series\\gruntz.py:453, in limitinf(e, x)\r\n 451 c0, e0 = mrv_leadterm(e.min, x)\r\n 452 else:\r\n--> 453 c0, e0 = mrv_leadterm(e, x)\r\n 454 sig = sign(e0, x)\r\n 455 if sig == 1:\r\n\r\nFile ~\\AppData\\Roaming\\Python\\Python39\\site-packages\\sympy\\core\\cache.py:72, in __cacheit..func_wrapper..wrapper(*args, **kwargs)\r\n 69 @wraps(func)\r\n 70 def wrapper(*args, **kwargs):\r\n 71 try:\r\n---> 72 retval = cfunc(*args, **kwargs)\r\n 73 except TypeError as e:\r\n 74 if not e.args or not e.args[0].startswith('unhashable type:'):\r\n\r\nFile ~\\AppData\\Roaming\\Python\\Python39\\site-packages\\sympy\\series\\gruntz.py:563, in mrv_leadterm(e, x)\r\n 561 incr = S.One\r\n 562 while _series.is_Order:\r\n--> 563 _series = f._eval_nseries(w, n=n0+incr, logx=logw)\r\n 564 incr *= 2\r\n 565 series = _series.expand().removeO()\r\n\r\nFile ~\\AppData\\Roaming\\Python\\Python39\\site-packages\\sympy\\functions\\special\\error_functions.py:2782, in _eis._eval_nseries(self, x, n, logx, cdir)\r\n 2780 f = self._eval_rewrite_as_intractable(*self.args)\r\n 2781 return f._eval_nseries(x, n, logx)\r\n-> 2782 return super()._eval_nseries(x, n, logx)\r\n\r\nFile ~\\AppData\\Roaming\\Python\\Python39\\site-packages\\sympy\\core\\function.py:690, in Function._eval_nseries(self, x, n, logx, cdir)\r\n 688 a0 = [t.limit(x, 0) for t in a]\r\n 689 if any(t.has(oo, -oo, zoo, nan) for t in a0):\r\n--> 690 return self._eval_aseries(n, args0, x, logx)\r\n 691 # Careful: the argument goes to oo, but only logarithmically so. We\r\n 692 # are supposed to do a power series expansion \"around the\r\n 693 # logarithmic term\". e.g.\r\n 694 # f(1+x+log(x))\r\n 695 # -> f(1+logx) + x*f'(1+logx) + O(x**2)\r\n 696 # where 'logx' is given in the argument\r\n 697 a = [t._eval_nseries(x, n, logx) for t in args]\r\n\r\nFile ~\\AppData\\Roaming\\Python\\Python39\\site-packages\\sympy\\functions\\special\\error_functions.py:2751, in _eis._eval_aseries(self, n, args0, x, logx)\r\n 2749 from sympy.series.order import Order\r\n 2750 if args0[0] != S.Infinity:\r\n-> 2751 return super(_erfs, self)._eval_aseries(n, args0, x, logx)\r\n 2753 z = self.args[0]\r\n 2754 l = [factorial(k) * (1/z)**(k + 1) for k in range(n)]\r\n\r\nTypeError: super(type, obj): obj must be an instance or subtype of type\r\n```\nFix limit evaluation for Ei function\n\r\n\r\n#### References to other Issues or PRs\r\n\r\n\r\n\r\n#### Brief description of what is fixed or changed\r\n Fixes #26916 \r\nEnhanced handling of special function limits to correctly compute edge cases. and This fix aims to improve the library's reliability when working with special functions and limit calculations.\r\n#### Other comments\r\n\r\n\r\n#### Release Notes\r\n\r\n\r\n\r\n\r\n* functions\r\n * Fix limit evaluation for Ei function\r\n\r\n\n", "hints_text": "The immediate error message results from a bug that is easily fixed:\r\n```diff\r\ndiff --git a/sympy/functions/special/error_functions.py b/sympy/functions/special/error_functions.py\r\nindex 09279588b6..ba29c4d7d4 100644\r\n--- a/sympy/functions/special/error_functions.py\r\n+++ b/sympy/functions/special/error_functions.py\r\n@@ -2748,7 +2748,7 @@ class _eis(Function):\r\n def _eval_aseries(self, n, args0, x, logx):\r\n from sympy.series.order import Order\r\n if args0[0] != S.Infinity:\r\n- return super(_erfs, self)._eval_aseries(n, args0, x, logx)\r\n+ return super()._eval_aseries(n, args0, x, logx)\r\n \r\n z = self.args[0]\r\n l = [factorial(k) * (1/z)**(k + 1) for k in range(n)]\r\n```\r\nIn this case the limit does not compute but does not exit with the error:\r\n```python\r\nIn [1]: (Ei(-x) * exp(x)).limit(x, oo)\r\nOut[1]: \r\n \u239b x \u239e\r\nlim \u239d\u212f \u22c5Ei(-x)\u23a0\r\nx\u2500\u2192\u221e \r\n```\nGreat thanks! A little bit unfortunate that it can't compute the limit however. Luckily WolframAlpha didn't seem to have any issues with it.\nThe basic bug is easy to fix.\r\n\r\nEvaluating the limit would require having an asymptotic expansion for `Ei(x)` at `-oo`:\r\n```python\r\nIn [5]: Ei(x).series(x, oo)\r\nOut[5]: \r\n\u239b120 24 6 2 1 \u239b1 \u239e\u239e x\r\n\u239c\u2500\u2500\u2500 + \u2500\u2500 + \u2500\u2500 + \u2500\u2500 + \u2500 + 1 + O\u239c\u2500\u2500; x \u2192 \u221e\u239f\u239f\u22c5\u212f \r\n\u239c 5 4 3 2 x \u239c 6 \u239f\u239f \r\n\u239dx x x x \u239dx \u23a0\u23a0 \r\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n x \r\n\r\nIn [6]: Ei(x).series(x, -oo)\r\n...\r\nPoleError: \r\nAsymptotic expansion of Ei around [-oo] is not implemented.\r\n```\nThere is an asymptotic description on [Wikipedia about Exponential Integrals](https://en.wikipedia.org/wiki/Exponential_integral) which is asymptotic beyond all borders in the complex plain. Would that help?\nIt is possibly just a case of someone needing to implement the formula somewhere.\n:white_check_mark:\n\nHi, I am the [SymPy bot](https://github.com/sympy/sympy-bot). I'm here to help you write a release notes entry. Please read the [guide on how to write release notes](https://github.com/sympy/sympy/wiki/Writing-Release-Notes).\n\n\n\nYour release notes are in good order.\n\nHere is what the release notes will look like:\n* functions\n * Fix limit evaluation for Ei function ([#26937](https://github.com/sympy/sympy/pull/26937) by [@Abhishekjsr283204](https://github.com/Abhishekjsr283204))\n\nThis will be added to https://github.com/sympy/sympy/wiki/Release-Notes-for-1.14.\n\n

Click here to see the pull request description that was parsed.\n\n \r\n\r\n #### References to other Issues or PRs\r\n \r\n\r\n\r\n #### Brief description of what is fixed or changed\r\n Fixes #26916 \r\n Enhanced handling of special function limits to correctly compute edge cases. and This fix aims to improve the library's reliability when working with special functions and limit calculations.\r\n #### Other comments\r\n\r\n\r\n #### Release Notes\r\n\r\n \r\n\r\n \r\n * functions\r\n * Fix limit evaluation for Ei function\r\n \r\n\n\n

\n", "created_at": "2024-08-13T05:31:50Z" }, { "repo": "sympy/sympy", "pull_number": 26940, "instance_id": "sympy__sympy-26940", "issue_numbers": [ "26934" ], "base_commit": "5b2c92a8e024160a9b883cd3cfa95c0f5698bdab", "patch": "diff --git a/doc/src/modules/sets.rst b/doc/src/modules/sets.rst\nindex b59548756dff..c4cea3662d9c 100644\n--- a/doc/src/modules/sets.rst\n+++ b/doc/src/modules/sets.rst\n@@ -26,9 +26,6 @@ Elementary Sets\n Compound Sets\n -------------\n \n-.. module:: sympy.sets.sets\n- :noindex:\n-\n .. autoclass:: Union\n :members:\n \ndiff --git a/sympy/integrals/heurisch.py b/sympy/integrals/heurisch.py\nindex 5b9ce143bda9..a27e2700afd0 100644\n--- a/sympy/integrals/heurisch.py\n+++ b/sympy/integrals/heurisch.py\n@@ -504,15 +504,16 @@ def heurisch(f, x, rewrite=False, hints=None, mappings=None, retries=3,\n # optimizing the number of permutations of mapping #\n assert mapping[-1][0] == x # if not, find it and correct this comment\n unnecessary_permutations = [mapping.pop(-1)]\n- # only permute types of objects and let the ordering\n- # of types take care of the order of replacement\n+ # permute types of objects\n types = defaultdict(list)\n for i in mapping:\n- types[type(i)].append(i)\n+ e, _ = i\n+ types[type(e)].append(i)\n mapping = [types[i] for i in types]\n def _iter_mappings():\n for i in permutations(mapping):\n- yield [j for i in i for j in i]\n+ # make the expression of a given type be ordered\n+ yield [j for i in i for j in ordered(i)]\n mappings = _iter_mappings()\n else:\n unnecessary_permutations = unnecessary_permutations or []\n", "test_patch": "diff --git a/.github/workflows/runtests.yml b/.github/workflows/runtests.yml\nindex 0b2fe10b6b88..13535aa99902 100644\n--- a/.github/workflows/runtests.yml\n+++ b/.github/workflows/runtests.yml\n@@ -187,27 +187,27 @@ jobs:\n # Test modules with specific dependencies\n - run: bin/test_optional_dependencies.py\n \n- # -------------------- NumPy nightly ----------------------------- #\n+ # -------------------- Bleeding edge dependencies ----------------- #\n \n- numpy-nightly:\n+ bleeding-edge:\n needs: code-quality\n-\n- runs-on: ubuntu-20.04\n-\n- name: NumPy/SciPy nightly\n-\n+ name: Bleeding edge dependencies\n+ runs-on: ubuntu-24.04\n steps:\n - uses: actions/checkout@v4\n - uses: actions/setup-python@v5\n with:\n- python-version: 3.12\n-\n- - run: pip install -r requirements-dev.txt\n+ python-version: '3.12'\n+ - run: sudo apt-get update\n+ - run: sudo apt-get install libgmp-dev libmpfr-dev libmpc-dev libflint-dev\n+ - run: pip install git+https://github.com/flintlib/python-flint.git@master\n+ - run: pip install git+https://github.com/aleaxit/gmpy.git@master\n+ - run: pip install git+https://github.com/mpmath/mpmath.git@master\n - run: pip install -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy\n - run: pip install -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple scipy\n-\n- # Test modules with specific dependencies\n- - run: bin/test_optional_dependencies.py\n+ - run: pip install -r requirements-dev.txt\n+ - run: pip install .\n+ - run: pytest -n auto\n \n # -------------------- FLINT tests -------------------------------- #\n \ndiff --git a/sympy/integrals/tests/test_heurisch.py b/sympy/integrals/tests/test_heurisch.py\nindex 265186acd7fd..3a5236a942f5 100644\n--- a/sympy/integrals/tests/test_heurisch.py\n+++ b/sympy/integrals/tests/test_heurisch.py\n@@ -19,6 +19,8 @@\n from sympy.integrals.heurisch import components, heurisch, heurisch_wrapper\n from sympy.testing.pytest import XFAIL, slow\n from sympy.integrals.integrals import integrate\n+from sympy import S\n+\n x, y, z, nu = symbols('x,y,z,nu')\n f = Function('f')\n \n@@ -368,6 +370,8 @@ def test_heurisch_complex_erf_issue_26338():\n \n a = exp(-x**2/(2*(2 - I)**2))\n assert heurisch(a, x, hints=[]) is None # None, not a wrong soln\n+ a = exp(-r**2/(2*(2 - I)**2))\n+ assert heurisch(a, r, hints=[]) is None\n a = sqrt(pi)*erf((1 + I)/2)/2\n assert integrate(exp(-I*x**2/2), (x, 0, 1)) == a - I*a\n \n@@ -387,3 +391,29 @@ def test_issue_15498():\n integrand = m*m.subs(t, s)**-1*f_vec.subs(aif_eq.lhs, aif_eq.rhs).subs(t, s)\n solution = integrate(integrand[0], (s, 0, t))\n assert solution is not None # does not hang and takes less than 10 s\n+\n+\n+@slow\n+def test_heurisch_issue_26930():\n+ integrand = x**Rational(4, 3)*log(x)\n+ anti = 3*x**(S(7)/3)*log(x)/7 - 9*x**(S(7)/3)/49\n+ assert heurisch(integrand, x) == anti\n+ assert integrate(integrand, x) == anti\n+ assert integrate(integrand, (x, 0, 1)) == -S(9)/49\n+\n+\n+def test_heurisch_issue_26922():\n+\n+ a, b, x = symbols(\"a, b, x\", real=True, positive=True)\n+ C = symbols(\"C\", real=True)\n+ i1 = -C*x*exp(-a*x**2 - sqrt(b)*x)\n+ i2 = C*x*exp(-a*x**2 + sqrt(b)*x)\n+ i = Integral(i1, x) + Integral(i2, x)\n+ res = (\n+ -C*exp(-a*x**2)*exp(sqrt(b)*x)/(2*a)\n+ + C*exp(-a*x**2)*exp(-sqrt(b)*x)/(2*a)\n+ + sqrt(pi)*C*sqrt(b)*exp(b/(4*a))*erf(sqrt(a)*x - sqrt(b)/(2*sqrt(a)))/(4*a**(S(3)/2))\n+ + sqrt(pi)*C*sqrt(b)*exp(b/(4*a))*erf(sqrt(a)*x + sqrt(b)/(2*sqrt(a)))/(4*a**(S(3)/2))\n+ )\n+\n+ assert i.doit(heurisch=False).expand() == res\ndiff --git a/sympy/integrals/tests/test_integrals.py b/sympy/integrals/tests/test_integrals.py\nindex 8436d6127c97..97b4dd4b30bc 100644\n--- a/sympy/integrals/tests/test_integrals.py\n+++ b/sympy/integrals/tests/test_integrals.py\n@@ -1146,8 +1146,8 @@ def test_issue_3940():\n a, b, c, d = symbols('a:d', positive=True)\n assert integrate(exp(-x**2 + I*c*x), x) == \\\n -sqrt(pi)*exp(-c**2/4)*erf(I*c/2 - x)/2\n- assert integrate(exp(a*x**2 + b*x + c), x) == \\\n- sqrt(pi)*exp(c - b**2/(4*a))*erfi((2*a*x + b)/(2*sqrt(a)))/(2*sqrt(a))\n+ assert integrate(exp(a*x**2 + b*x + c), x).equals(\n+ sqrt(pi)*exp(c - b**2/(4*a))*erfi((2*a*x + b)/(2*sqrt(a)))/(2*sqrt(a)))\n \n from sympy.core.function import expand_mul\n from sympy.abc import k\ndiff --git a/sympy/stats/sampling/tests/test_sample_discrete_rv.py b/sympy/stats/sampling/tests/test_sample_discrete_rv.py\nindex 10029af647b9..df73eb24d0e7 100644\n--- a/sympy/stats/sampling/tests/test_sample_discrete_rv.py\n+++ b/sympy/stats/sampling/tests/test_sample_discrete_rv.py\n@@ -1,7 +1,7 @@\n from sympy.core.singleton import S\n-from sympy.core.symbol import Symbol\n+#from sympy.core.symbol import Symbol\n from sympy.external import import_module\n-from sympy.stats import Geometric, Poisson, Zeta, sample, Skellam, DiscreteRV, Logarithmic, NegativeBinomial, YuleSimon\n+from sympy.stats import Geometric, Poisson, Zeta, sample, Skellam, Logarithmic, NegativeBinomial, YuleSimon\n from sympy.testing.pytest import skip, raises, slow\n \n \n@@ -27,11 +27,13 @@ def test_sample_numpy():\n \n \n def test_sample_scipy():\n- p = S(2)/3\n- x = Symbol('x', integer=True, positive=True)\n- pdf = p*(1 - p)**(x - 1) # pdf of Geometric Distribution\n+ #p = S(2)/3\n+ #x = Symbol('x', integer=True, positive=True)\n+ #pdf = p*(1 - p)**(x - 1) # pdf of Geometric Distribution\n distribs_scipy = [\n- DiscreteRV(x, pdf, set=S.Naturals),\n+ # This one fails:\n+ # https://github.com/sympy/sympy/issues/26862\n+ # DiscreteRV(x, pdf, set=S.Naturals),\n Geometric('G', 0.5),\n Logarithmic('L', 0.5),\n NegativeBinomial('N', 5, 0.4),\n", "problem_statement": "(2nd) Revert gh-26385 (maintain ordering in heurisch mapping)\n\r\n\r\n#### References to other Issues or PRs\r\n\r\n\r\nReplaces gh-26931\r\n\r\n#### Brief description of what is fixed or changed\r\n\r\n\r\n#### Other comments\r\n\r\n\r\n#### Release Notes\r\n\r\n\r\n\r\n\r\nNO ENTRY\r\n\r\n\n", "hints_text": ":white_check_mark:\n\nHi, I am the [SymPy bot](https://github.com/sympy/sympy-bot). I'm here to help you write a release notes entry. Please read the [guide on how to write release notes](https://github.com/sympy/sympy/wiki/Writing-Release-Notes).\n\n\n\n* No release notes entry will be added for this pull request.\n\n

Click here to see the pull request description that was parsed.\n\n \r\n\r\n #### References to other Issues or PRs\r\n \r\n\r\n Replaces gh-26931\r\n\r\n #### Brief description of what is fixed or changed\r\n\r\n\r\n #### Other comments\r\n\r\n\r\n #### Release Notes\r\n\r\n \r\n\r\n \r\n NO ENTRY\r\n \r\n\n\n

\n", "created_at": "2024-08-09T20:58:42Z" }, { "repo": "sympy/sympy", "pull_number": 26935, "instance_id": "sympy__sympy-26935", "issue_numbers": [ "26934" ], "base_commit": "583993a9b99faa3f244b1863feddf4c2480ac640", "patch": "diff --git a/sympy/integrals/heurisch.py b/sympy/integrals/heurisch.py\nindex 5b9ce143bda9..a27e2700afd0 100644\n--- a/sympy/integrals/heurisch.py\n+++ b/sympy/integrals/heurisch.py\n@@ -504,15 +504,16 @@ def heurisch(f, x, rewrite=False, hints=None, mappings=None, retries=3,\n # optimizing the number of permutations of mapping #\n assert mapping[-1][0] == x # if not, find it and correct this comment\n unnecessary_permutations = [mapping.pop(-1)]\n- # only permute types of objects and let the ordering\n- # of types take care of the order of replacement\n+ # permute types of objects\n types = defaultdict(list)\n for i in mapping:\n- types[type(i)].append(i)\n+ e, _ = i\n+ types[type(e)].append(i)\n mapping = [types[i] for i in types]\n def _iter_mappings():\n for i in permutations(mapping):\n- yield [j for i in i for j in i]\n+ # make the expression of a given type be ordered\n+ yield [j for i in i for j in ordered(i)]\n mappings = _iter_mappings()\n else:\n unnecessary_permutations = unnecessary_permutations or []\n", "test_patch": "diff --git a/sympy/integrals/tests/test_heurisch.py b/sympy/integrals/tests/test_heurisch.py\nindex 265186acd7fd..bcdfa2b93a92 100644\n--- a/sympy/integrals/tests/test_heurisch.py\n+++ b/sympy/integrals/tests/test_heurisch.py\n@@ -19,6 +19,8 @@\n from sympy.integrals.heurisch import components, heurisch, heurisch_wrapper\n from sympy.testing.pytest import XFAIL, slow\n from sympy.integrals.integrals import integrate\n+from sympy import S\n+\n x, y, z, nu = symbols('x,y,z,nu')\n f = Function('f')\n \n@@ -361,13 +363,13 @@ def f(x):\n \n def test_heurisch_complex_erf_issue_26338():\n r = symbols('r', real=True)\n- a = exp(-r**2/(2*(2 - I)**2))\n- assert heurisch(a, r, hints=[]) is None # None, not a wrong soln\n a = sqrt(pi)*erf((1 + I)/2)/2\n assert integrate(exp(-I*r**2/2), (r, 0, 1)) == a - I*a\n \n a = exp(-x**2/(2*(2 - I)**2))\n assert heurisch(a, x, hints=[]) is None # None, not a wrong soln\n+ a = exp(-r**2/(2*(2 - I)**2))\n+ assert heurisch(a, r, hints=[]) is None\n a = sqrt(pi)*erf((1 + I)/2)/2\n assert integrate(exp(-I*x**2/2), (x, 0, 1)) == a - I*a\n \n@@ -387,3 +389,29 @@ def test_issue_15498():\n integrand = m*m.subs(t, s)**-1*f_vec.subs(aif_eq.lhs, aif_eq.rhs).subs(t, s)\n solution = integrate(integrand[0], (s, 0, t))\n assert solution is not None # does not hang and takes less than 10 s\n+\n+\n+@slow\n+def test_heurisch_issue_26930():\n+ integrand = x**Rational(4, 3)*log(x)\n+ anti = 3*x**(S(7)/3)*log(x)/7 - 9*x**(S(7)/3)/49\n+ assert heurisch(integrand, x) == anti\n+ assert integrate(integrand, x) == anti\n+ assert integrate(integrand, (x, 0, 1)) == -S(9)/49\n+\n+\n+def test_heurisch_issue_26922():\n+\n+ a, b, x = symbols(\"a, b, x\", real=True, positive=True)\n+ C = symbols(\"C\", real=True)\n+ i1 = -C*x*exp(-a*x**2 - sqrt(b)*x)\n+ i2 = C*x*exp(-a*x**2 + sqrt(b)*x)\n+ i = Integral(i1, x) + Integral(i2, x)\n+ res = (\n+ -C*exp(-a*x**2)*exp(sqrt(b)*x)/(2*a)\n+ + C*exp(-a*x**2)*exp(-sqrt(b)*x)/(2*a)\n+ + sqrt(pi)*C*sqrt(b)*exp(b/(4*a))*erf(sqrt(a)*x - sqrt(b)/(2*sqrt(a)))/(4*a**(S(3)/2))\n+ + sqrt(pi)*C*sqrt(b)*exp(b/(4*a))*erf(sqrt(a)*x + sqrt(b)/(2*sqrt(a)))/(4*a**(S(3)/2))\n+ )\n+\n+ assert i.doit(heurisch=False).expand() == res\ndiff --git a/sympy/integrals/tests/test_integrals.py b/sympy/integrals/tests/test_integrals.py\nindex 8436d6127c97..97b4dd4b30bc 100644\n--- a/sympy/integrals/tests/test_integrals.py\n+++ b/sympy/integrals/tests/test_integrals.py\n@@ -1146,8 +1146,8 @@ def test_issue_3940():\n a, b, c, d = symbols('a:d', positive=True)\n assert integrate(exp(-x**2 + I*c*x), x) == \\\n -sqrt(pi)*exp(-c**2/4)*erf(I*c/2 - x)/2\n- assert integrate(exp(a*x**2 + b*x + c), x) == \\\n- sqrt(pi)*exp(c - b**2/(4*a))*erfi((2*a*x + b)/(2*sqrt(a)))/(2*sqrt(a))\n+ assert integrate(exp(a*x**2 + b*x + c), x).equals(\n+ sqrt(pi)*exp(c - b**2/(4*a))*erfi((2*a*x + b)/(2*sqrt(a)))/(2*sqrt(a)))\n \n from sympy.core.function import expand_mul\n from sympy.abc import k\n", "problem_statement": "(2nd) Revert gh-26385 (maintain ordering in heurisch mapping)\n\r\n\r\n#### References to other Issues or PRs\r\n\r\n\r\nReplaces gh-26931\r\n\r\n#### Brief description of what is fixed or changed\r\n\r\n\r\n#### Other comments\r\n\r\n\r\n#### Release Notes\r\n\r\n\r\n\r\n\r\nNO ENTRY\r\n\r\n\n", "hints_text": ":white_check_mark:\n\nHi, I am the [SymPy bot](https://github.com/sympy/sympy-bot). I'm here to help you write a release notes entry. Please read the [guide on how to write release notes](https://github.com/sympy/sympy/wiki/Writing-Release-Notes).\n\n\n\n* No release notes entry will be added for this pull request.\n\n

Click here to see the pull request description that was parsed.\n\n \r\n\r\n #### References to other Issues or PRs\r\n \r\n\r\n Replaces gh-26931\r\n\r\n #### Brief description of what is fixed or changed\r\n\r\n\r\n #### Other comments\r\n\r\n\r\n #### Release Notes\r\n\r\n \r\n\r\n \r\n NO ENTRY\r\n \r\n\n\n

\n", "created_at": "2024-08-08T01:18:25Z" }, { "repo": "sympy/sympy", "pull_number": 26920, "instance_id": "sympy__sympy-26920", "issue_numbers": [ "26918" ], "base_commit": "0596a28f6ca118a8ebeeeb7c8f31ba8153596369", "patch": "diff --git a/.mailmap b/.mailmap\nindex 4478e0227663..03df810c1f5e 100644\n--- a/.mailmap\n+++ b/.mailmap\n@@ -474,6 +474,7 @@ Colleen Lee \n Comer Duncan \n Congxu Yang \n Constantin Mateescu \n+Corbet Elkins \n Corey Cerovsek \n Costor \n Craig A. Stoudt \ndiff --git a/sympy/liealgebras/type_e.py b/sympy/liealgebras/type_e.py\nindex c8271c29ebf6..d73defe53432 100644\n--- a/sympy/liealgebras/type_e.py\n+++ b/sympy/liealgebras/type_e.py\n@@ -106,8 +106,7 @@ def positive_roots(self):\n root[i] = 1\n posroots[k] = root\n \n- root = [Rational(1, 2), Rational(1, 2), Rational(1, 2), Rational(1, 2), Rational(1, 2),\n- Rational(-1, 2), Rational(-1, 2), Rational(1, 2)]\n+ root = [Rational(1, 2)]*5 + [Rational(-1, 2), Rational(-1, 2), Rational(1, 2)]\n for a in range(0, 2):\n for b in range(0, 2):\n for c in range(0, 2):\n@@ -125,7 +124,7 @@ def positive_roots(self):\n root[3] = Rational(-1, 2)\n if e == 1:\n root[4] = Rational(-1, 2)\n- posroots[k] = root\n+ posroots[k] = root[:]\n \n return posroots\n if n == 7:\n@@ -143,8 +142,7 @@ def positive_roots(self):\n \n k += 1\n posroots[k] = [0, 0, 0, 0, 0, 1, 1, 0]\n- root = [Rational(1, 2), Rational(1, 2), Rational(1, 2), Rational(1, 2), Rational(1, 2),\n- Rational(-1, 2), Rational(-1, 2), Rational(1, 2)]\n+ root = [Rational(1, 2)]*5 + [Rational(-1, 2), Rational(-1, 2), Rational(1, 2)]\n for a in range(0, 2):\n for b in range(0, 2):\n for c in range(0, 2):\n@@ -165,7 +163,7 @@ def positive_roots(self):\n root[4] = Rational(-1, 2)\n if f == 1:\n root[5] = Rational(1, 2)\n- posroots[k] = root\n+ posroots[k] = root[:]\n \n return posroots\n if n == 8:\n@@ -181,8 +179,7 @@ def positive_roots(self):\n root[i] = 1\n posroots[k] = root\n \n- root = [Rational(1, 2), Rational(1, 2), Rational(1, 2), Rational(1, 2), Rational(1, 2),\n- Rational(-1, 2), Rational(-1, 2), Rational(1, 2)]\n+ root = [Rational(1, 2)]*5 + [Rational(-1, 2), Rational(-1, 2), Rational(1, 2)]\n for a in range(0, 2):\n for b in range(0, 2):\n for c in range(0, 2):\n@@ -206,7 +203,7 @@ def positive_roots(self):\n root[5] = Rational(1, 2)\n if g == 1:\n root[6] = Rational(1, 2)\n- posroots[k] = root\n+ posroots[k] = root[:]\n \n return posroots\n \n", "test_patch": "diff --git a/sympy/liealgebras/tests/test_type_E.py b/sympy/liealgebras/tests/test_type_E.py\nindex 95bb23e205bd..bdb08342f41e 100644\n--- a/sympy/liealgebras/tests/test_type_E.py\n+++ b/sympy/liealgebras/tests/test_type_E.py\n@@ -1,5 +1,6 @@\n from sympy.liealgebras.cartan_type import CartanType\n from sympy.matrices import Matrix\n+from sympy.core.backend import Rational\n \n def test_type_E():\n c = CartanType(\"E6\")\n@@ -17,3 +18,5 @@ def test_type_E():\n assert c.dynkin_diagram() == diag\n posroots = c.positive_roots()\n assert posroots[8] == [1, 0, 0, 0, 1, 0, 0, 0]\n+ assert posroots[21] == [Rational(1,2),Rational(1,2),Rational(1,2),Rational(1,2),\n+ Rational(1,2),Rational(-1,2),Rational(-1,2),Rational(1,2)]\n", "problem_statement": "Incorrectly generated positive roots for E_n\nThe positive roots generated in the CartanType for type E_n are not correct. The same list object is being added to the dictionary causing all of the roots generated in the last section to be the same.\n", "hints_text": "", "created_at": "2024-08-06T02:22:03Z" }, { "repo": "sympy/sympy", "pull_number": 26919, "instance_id": "sympy__sympy-26919", "issue_numbers": [ "26903" ], "base_commit": "0596a28f6ca118a8ebeeeb7c8f31ba8153596369", "patch": "diff --git a/sympy/polys/numberfields/minpoly.py b/sympy/polys/numberfields/minpoly.py\nindex a3543339bfba..ac28ffb1257e 100644\n--- a/sympy/polys/numberfields/minpoly.py\n+++ b/sympy/polys/numberfields/minpoly.py\n@@ -572,11 +572,12 @@ def _minpoly_compose(ex, x, dom):\n \n if dom.is_QQ and _is_sum_surds(ex):\n # eliminate the square roots\n+ v = ex\n ex -= x\n while 1:\n ex1 = _separate_sq(ex)\n if ex1 is ex:\n- return ex\n+ return _choose_factor(factor_list(ex)[1], x, v)\n else:\n ex = ex1\n \n", "test_patch": "diff --git a/sympy/polys/numberfields/tests/test_minpoly.py b/sympy/polys/numberfields/tests/test_minpoly.py\nindex 18d786e1bd10..a04f418cd251 100644\n--- a/sympy/polys/numberfields/tests/test_minpoly.py\n+++ b/sympy/polys/numberfields/tests/test_minpoly.py\n@@ -8,6 +8,7 @@\n from sympy.functions.elementary.exponential import exp\n from sympy.functions.elementary.miscellaneous import (cbrt, sqrt)\n from sympy.functions.elementary.trigonometric import (cos, sin, tan)\n+from sympy.ntheory.generate import nextprime\n from sympy.polys.polytools import Poly\n from sympy.polys.rootoftools import CRootOf\n from sympy.solvers.solveset import nonlinsolve\n@@ -176,6 +177,15 @@ def test_minimal_polynomial():\n assert minimal_polynomial(phi, x) == x**2 - x - 1\n \n \n+def test_issue_26903():\n+ p1 = nextprime(10**16) # greater than 10**15\n+ p2 = nextprime(p1)\n+ assert sqrt(p1**2*p2).is_Pow # square not extracted\n+ zero = sqrt(p1**2*p2) - p1*sqrt(p2)\n+ assert minimal_polynomial(zero, x) == x\n+ assert minimal_polynomial(sqrt(2) - zero, x) == x**2 - 2\n+\n+\n def test_minimal_polynomial_issue_19732():\n # https://github.com/sympy/sympy/issues/19732\n expr = (-280898097948878450887044002323982963174671632174995451265117559518123750720061943079105185551006003416773064305074191140286225850817291393988597615/(-488144716373031204149459129212782509078221364279079444636386844223983756114492222145074506571622290776245390771587888364089507840000000*sqrt(238368341569)*sqrt(S(11918417078450)/63568729\n", "problem_statement": "minpoly returns a not irreducible polynomial\nCC @smichr \r\n\r\nIn this example minpoly returns `x**2` when it should return `x`:\r\n```python\r\nIn [1]: p1 = nextprime(10**20)\r\n\r\nIn [2]: p2 = nextprime(p1)\r\n\r\nIn [3]: minpoly(sqrt(p1**2*p2) - p1*sqrt(p2))\r\nOut[3]: \r\n 2\r\nx \r\n```\r\nI don't fully understand the `_separate_sq` function:\r\nhttps://github.com/sympy/sympy/blob/199aafdfb2c18fd18be47503a6aa93a1eb992187/sympy/polys/numberfields/minpoly.py#L577-L579\r\n\r\nApart from a degenerate case like this is there a reason to assume that it will generate an irreducible polynomial?\r\n\r\nOtherwise I guess we need to use the usual `choose_factor` method for this case as well.\n", "hints_text": "I don't know a lot about this routine, but the first failing example for two consecutive primes has `p1 = nextprime(2**15)` which makes it look suspiciously dependent on something that happens only for small ints.\r\n```python\r\n>>> p1=2\r\n>>> for p2 in primerange(3,100000):\r\n... if minpoly(sqrt(p1**2*p2) - p1*sqrt(p2)).is_Pow:\r\n... print(p1,p2)\r\n... stop\r\n... p1=p2\r\n...\r\n32771 32779\r\n>>> p1 == nextprime(2**15)\r\nTrue\r\n```\n```diff\r\ndiff --git a/sympy/polys/numberfields/minpoly.py b/sympy/polys/numberfields/minpoly.py\r\nindex a3543339bf..9eb02d765d 100644\r\n--- a/sympy/polys/numberfields/minpoly.py\r\n+++ b/sympy/polys/numberfields/minpoly.py\r\n@@ -576,7 +576,14 @@ def _minpoly_compose(ex, x, dom):\r\n while 1:\r\n ex1 = _separate_sq(ex)\r\n if ex1 is ex:\r\n- return ex\r\n+ if ex.is_Add:\r\n+ return ex\r\n+ ex = ex.as_independent(x, as_Add=False)[1]\r\n+ if ex.is_Pow and ex.exp.is_Integer:\r\n+ return ex.base\r\n+ if ex == x:\r\n+ return ex\r\n+ assert None, \"unhandled case\"\r\n else:\r\n ex = ex1\r\n```\n> the first failing example for two consecutive primes has `p1 = nextprime(2**15)` which makes it look suspiciously dependent on something that happens only for small ints.\r\n\r\nThat is the limit on prime factorisation in `Pow.__new__` I think:\r\n```python\r\nIn [3]: p1 = nextprime(100)\r\n\r\nIn [4]: p2 = nextprime(p1)\r\n\r\nIn [5]: sqrt(p1**2*p2) - p1*sqrt(p2)\r\nOut[5]: 0\r\n```\r\nI was deliberately testing primes large enough that Expr would not automatically simplify the expression to zero (by extracting `p1` from the first `sqrt`).\nI'm not sure about this:\r\n\r\n> ```diff\r\n> + if ex.is_Add:\r\n> + return ex\r\n> ```\r\n\r\nThe fact that this case returns a non-irreducible polynomial makes me wonder if there could be others where the polynomial is not just a simple power but is still not irreducible. It is unclear to me how the routine guarantees irreducibility without factorising the polynomial.\nWe can fix the problem while pondering the nature of the routine.... The suggested change will, at least, not hurt the behavior.\nIf our expectation is that the worst case is we get a polynomial like `x**2` then how bad is it to just call `factor` (`factor(x**2)` is not a slow operation) and if there is more than one factor go through `choose_factor`?\r\n\r\nAnother possibility could be to check for what we expect and otherwise `assert False`.\nWhat about\r\n```python\r\n if dom.is_QQ and _is_sum_surds(ex):\r\n # eliminate the square roots\r\n ex -= x\r\n while 1:\r\n ex1 = _separate_sq(ex)\r\n if ex1 is ex:\r\n if ex.is_Add:\r\n return ex\r\n if ex == -x**2: # original number simplified to 0, e.g. sqrt(4*3)-2*sqrt(3)\r\n return x\r\n assert None, 'unexpected result'\r\n else:\r\n ex = ex1\r\n```", "created_at": "2024-08-06T02:09:22Z" }, { "repo": "sympy/sympy", "pull_number": 26891, "instance_id": "sympy__sympy-26891", "issue_numbers": [ "26888" ], "base_commit": "258d78c44c367f713d47345f810dbeba41c27235", "patch": "diff --git a/sympy/printing/c.py b/sympy/printing/c.py\nindex 05549c7fe285..6fcc01777690 100644\n--- a/sympy/printing/c.py\n+++ b/sympy/printing/c.py\n@@ -331,9 +331,6 @@ def _print_Indexed(self, expr):\n return \"%s[%s]\" % (self._print(expr.base.label),\n self._print(flat_index))\n \n- def _print_Idx(self, expr):\n- return self._print(expr.label)\n-\n @_as_macro_if_defined\n def _print_NumberSymbol(self, expr):\n return super()._print_NumberSymbol(expr)\ndiff --git a/sympy/printing/codeprinter.py b/sympy/printing/codeprinter.py\nindex 346a801d3059..c036d9c06a16 100644\n--- a/sympy/printing/codeprinter.py\n+++ b/sympy/printing/codeprinter.py\n@@ -351,6 +351,9 @@ def _print_Dummy(self, expr):\n else:\n return '%s_%d' % (expr.name, expr.dummy_index)\n \n+ def _print_Idx(self, expr):\n+ return self._print(expr.label)\n+\n def _print_CodeBlock(self, expr):\n return '\\n'.join([self._print(i) for i in expr.args])\n \ndiff --git a/sympy/printing/fortran.py b/sympy/printing/fortran.py\nindex a647907a918c..7cea812d72dd 100644\n--- a/sympy/printing/fortran.py\n+++ b/sympy/printing/fortran.py\n@@ -376,9 +376,6 @@ def _print_Indexed(self, expr):\n inds = [ self._print(i) for i in expr.indices ]\n return \"%s(%s)\" % (self._print(expr.base.label), \", \".join(inds))\n \n- def _print_Idx(self, expr):\n- return self._print(expr.label)\n-\n def _print_AugmentedAssignment(self, expr):\n lhs_code = self._print(expr.lhs)\n rhs_code = self._print(expr.rhs)\ndiff --git a/sympy/printing/glsl.py b/sympy/printing/glsl.py\nindex 7bcb3bbff9a4..92c1aa3f53c7 100644\n--- a/sympy/printing/glsl.py\n+++ b/sympy/printing/glsl.py\n@@ -258,9 +258,6 @@ def _print_Piecewise(self, expr):\n last_line = \": (\\n%s\\n)\" % self._print(expr.args[-1].expr)\n return \": \".join(ecpairs) + last_line + \" \".join([\")\"*len(ecpairs)])\n \n- def _print_Idx(self, expr):\n- return self._print(expr.label)\n-\n def _print_Indexed(self, expr):\n # calculate index for 1d array\n dims = expr.shape\ndiff --git a/sympy/printing/jscode.py b/sympy/printing/jscode.py\nindex c0961857e30f..753eb3291dd7 100644\n--- a/sympy/printing/jscode.py\n+++ b/sympy/printing/jscode.py\n@@ -137,9 +137,6 @@ def _print_Indexed(self, expr):\n offset *= dims[i]\n return \"%s[%s]\" % (self._print(expr.base.label), self._print(elem))\n \n- def _print_Idx(self, expr):\n- return self._print(expr.label)\n-\n def _print_Exp1(self, expr):\n return \"Math.E\"\n \ndiff --git a/sympy/printing/julia.py b/sympy/printing/julia.py\nindex e5912570dad1..c0585b93578f 100644\n--- a/sympy/printing/julia.py\n+++ b/sympy/printing/julia.py\n@@ -384,11 +384,6 @@ def _print_Indexed(self, expr):\n inds = [ self._print(i) for i in expr.indices ]\n return \"%s[%s]\" % (self._print(expr.base.label), \",\".join(inds))\n \n-\n- def _print_Idx(self, expr):\n- return self._print(expr.label)\n-\n-\n def _print_Identity(self, expr):\n return \"eye(%s)\" % self._print(expr.shape[0])\n \ndiff --git a/sympy/printing/maple.py b/sympy/printing/maple.py\nindex f36a5b568857..2c937cd262ab 100644\n--- a/sympy/printing/maple.py\n+++ b/sympy/printing/maple.py\n@@ -183,9 +183,6 @@ def _print_NegativeInfinity(self, expr):\n def _print_Infinity(self, expr):\n return 'infinity'\n \n- def _print_Idx(self, expr):\n- return self._print(expr.label)\n-\n def _print_BooleanTrue(self, expr):\n return \"true\"\n \ndiff --git a/sympy/printing/octave.py b/sympy/printing/octave.py\nindex fe36308e1222..b7e599399322 100644\n--- a/sympy/printing/octave.py\n+++ b/sympy/printing/octave.py\n@@ -379,10 +379,6 @@ def _print_Indexed(self, expr):\n return \"%s(%s)\" % (self._print(expr.base.label), \", \".join(inds))\n \n \n- def _print_Idx(self, expr):\n- return self._print(expr.label)\n-\n-\n def _print_KroneckerDelta(self, expr):\n prec = PRECEDENCE[\"Pow\"]\n return \"double(%s == %s)\" % tuple(self.parenthesize(x, prec)\ndiff --git a/sympy/printing/rcode.py b/sympy/printing/rcode.py\nindex c497f1a6fff6..3107e6e94d5c 100644\n--- a/sympy/printing/rcode.py\n+++ b/sympy/printing/rcode.py\n@@ -157,9 +157,6 @@ def _print_Indexed(self, expr):\n inds = [ self._print(i) for i in expr.indices ]\n return \"%s[%s]\" % (self._print(expr.base.label), \", \".join(inds))\n \n- def _print_Idx(self, expr):\n- return self._print(expr.label)\n-\n def _print_Exp1(self, expr):\n return \"exp(1)\"\n \n", "test_patch": "diff --git a/sympy/utilities/tests/test_lambdify.py b/sympy/utilities/tests/test_lambdify.py\nindex 96203a3ccb3a..ee56ab5bcdc3 100644\n--- a/sympy/utilities/tests/test_lambdify.py\n+++ b/sympy/utilities/tests/test_lambdify.py\n@@ -33,7 +33,7 @@\n from sympy.simplify.cse_main import cse\n from sympy.tensor.array import derive_by_array, Array\n from sympy.tensor.array.expressions import ArraySymbol\n-from sympy.tensor.indexed import IndexedBase\n+from sympy.tensor.indexed import IndexedBase, Idx\n from sympy.utilities.lambdify import lambdify\n from sympy.utilities.iterables import numbered_symbols\n from sympy.vector import CoordSys3D\n@@ -1067,6 +1067,13 @@ def test_Indexed():\n b = numpy.array([[1, 2], [3, 4]])\n assert lambdify(a, Sum(a[x, y], (x, 0, 1), (y, 0, 1)))(b) == 10\n \n+def test_Idx():\n+ # Issue 26888\n+ a = IndexedBase('a')\n+ i = Idx('i')\n+ b = [1,2,3]\n+ assert lambdify([a, i], a[i])(b, 2) == 3\n+\n \n def test_issue_12173():\n #test for issue 12173\n", "problem_statement": "Cannot lambdify `sp.Idx` symbols in 1.13\nThe following code worked in 1.12.1:\r\n\r\n```python\r\nimport sympy as sp\r\n\r\nX = sp.IndexedBase('X')\r\ni = sp.Idx('i')\r\nsp.lambdify([X, i], X[i])\r\n```\r\n\r\nIt now fails with a `PrintMethodNotImplementedError` in 1.13. Full stack trace below the fold:\r\n\r\n

\r\n\r\nTraceback\r\n\r\n```shelll\r\nPrintMethodNotImplementedError Traceback (most recent call last)\r\n\r\n[](https://localhost:8080/#) in ()\r\n 2 X = sp.IndexedBase('X')\r\n 3 i = sp.Idx('i')\r\n----> 4 sp.lambdify([X, i], X[i])(np.arange(10), 3)\r\n\r\n9 frames\r\n\r\n[/usr/local/lib/python3.10/dist-packages/sympy/utilities/lambdify.py](https://localhost:8080/#) in lambdify(args, expr, modules, printer, use_imps, dummify, cse, docstring_limit)\r\n 878 else:\r\n 879 cses, _expr = (), expr\r\n--> 880 funcstr = funcprinter.doprint(funcname, iterable_args, _expr, cses=cses)\r\n 881 \r\n 882 # Collect the module imports from the code printers.\r\n\r\n[/usr/local/lib/python3.10/dist-packages/sympy/utilities/lambdify.py](https://localhost:8080/#) in doprint(self, funcname, args, expr, cses)\r\n 1169 funcbody.append('{} = {}'.format(self._exprrepr(s), self._exprrepr(e)))\r\n 1170 \r\n-> 1171 str_expr = _recursive_to_string(self._exprrepr, expr)\r\n 1172 \r\n 1173 if '\\n' in str_expr:\r\n\r\n[/usr/local/lib/python3.10/dist-packages/sympy/utilities/lambdify.py](https://localhost:8080/#) in _recursive_to_string(doprint, arg)\r\n 964 \r\n 965 if isinstance(arg, (Basic, MatrixBase)):\r\n--> 966 return doprint(arg)\r\n 967 elif iterable(arg):\r\n 968 if isinstance(arg, list):\r\n\r\n[/usr/local/lib/python3.10/dist-packages/sympy/printing/codeprinter.py](https://localhost:8080/#) in doprint(self, expr, assign_to)\r\n 170 self._number_symbols = set()\r\n 171 \r\n--> 172 lines = self._print(expr).splitlines()\r\n 173 \r\n 174 # format the output\r\n\r\n[/usr/local/lib/python3.10/dist-packages/sympy/printing/printer.py](https://localhost:8080/#) in _print(self, expr, **kwargs)\r\n 329 printmethod = getattr(self, printmethodname, None)\r\n 330 if printmethod is not None:\r\n--> 331 return printmethod(expr, **kwargs)\r\n 332 # Unknown object, fall back to the emptyPrinter.\r\n 333 return self.emptyPrinter(expr)\r\n\r\n[/usr/local/lib/python3.10/dist-packages/sympy/printing/pycode.py](https://localhost:8080/#) in _print_Indexed(self, expr)\r\n 565 base = expr.args[0]\r\n 566 index = expr.args[1:]\r\n--> 567 return \"{}[{}]\".format(str(base), \", \".join([self._print(ind) for ind in index]))\r\n 568 \r\n 569 def _print_Pow(self, expr, rational=False):\r\n\r\n[/usr/local/lib/python3.10/dist-packages/sympy/printing/pycode.py](https://localhost:8080/#) in (.0)\r\n 565 base = expr.args[0]\r\n 566 index = expr.args[1:]\r\n--> 567 return \"{}[{}]\".format(str(base), \", \".join([self._print(ind) for ind in index]))\r\n 568 \r\n 569 def _print_Pow(self, expr, rational=False):\r\n\r\n[/usr/local/lib/python3.10/dist-packages/sympy/printing/printer.py](https://localhost:8080/#) in _print(self, expr, **kwargs)\r\n 329 printmethod = getattr(self, printmethodname, None)\r\n 330 if printmethod is not None:\r\n--> 331 return printmethod(expr, **kwargs)\r\n 332 # Unknown object, fall back to the emptyPrinter.\r\n 333 return self.emptyPrinter(expr)\r\n\r\n[/usr/local/lib/python3.10/dist-packages/sympy/printing/codeprinter.py](https://localhost:8080/#) in _print_Function(self, expr)\r\n 459 return '%s(%s)' % (self._print(expr.func), ', '.join(map(self._print, expr.args)))\r\n 460 else:\r\n--> 461 return self._print_not_supported(expr)\r\n 462 \r\n 463 _print_Expr = _print_Function\r\n\r\n[/usr/local/lib/python3.10/dist-packages/sympy/printing/codeprinter.py](https://localhost:8080/#) in _print_not_supported(self, expr)\r\n 580 def _print_not_supported(self, expr):\r\n 581 if self._settings.get('strict', False):\r\n--> 582 raise PrintMethodNotImplementedError(\"Unsupported by %s: %s\" % (str(type(self)), str(type(expr))) + \\\r\n 583 \"\\nSet the printer option 'strict' to False in order to generate partially printed code.\")\r\n 584 try:\r\n\r\nPrintMethodNotImplementedError: Unsupported by : \r\nSet the printer option 'strict' to False in order to generate partially printed code.\r\n```\r\n\r\n
\r\n\r\nTested on a colab under `1.12.1`, `1.13.0`, and `1.13.1`\n", "hints_text": "", "created_at": "2024-07-31T21:08:29Z" }, { "repo": "sympy/sympy", "pull_number": 26882, "instance_id": "sympy__sympy-26882", "issue_numbers": [ "25173" ], "base_commit": "a9a6f150383de85a70a19e91e88bca40ceb093ba", "patch": "diff --git a/.mailmap b/.mailmap\nindex 0ae5a7d3954a..ec7fb6fdd208 100644\n--- a/.mailmap\n+++ b/.mailmap\n@@ -632,6 +632,7 @@ Gautam Menghani gum3ng \n Geetika Vadali Geetika V \n Geetika Vadali Geetika Vadali <123307246+geetHonve@users.noreply.github.com>\n Geoffry Song \n+George Frolov \n George Korepanov \n George Pittock \n George Waksman \ndiff --git a/doc/src/modules/printing.rst b/doc/src/modules/printing.rst\nindex c084f0762730..ce4b91ef9a4e 100644\n--- a/doc/src/modules/printing.rst\n+++ b/doc/src/modules/printing.rst\n@@ -392,7 +392,7 @@ Rust code printing\n \n .. autoattribute:: RustCodePrinter.printmethod\n \n-.. autofunction:: sympy.printing.rust.rust_code\n+.. autofunction:: sympy.printing.codeprinter.rust_code\n \n Aesara Code printing\n --------------------\ndiff --git a/sympy/printing/__init__.py b/sympy/printing/__init__.py\nindex 134785794d63..15dfaf70eb37 100644\n--- a/sympy/printing/__init__.py\n+++ b/sympy/printing/__init__.py\n@@ -12,7 +12,7 @@\n \n from .codeprinter import print_ccode, print_fcode\n \n-from .codeprinter import ccode, fcode, cxxcode # noqa:F811\n+from .codeprinter import ccode, fcode, cxxcode, rust_code # noqa:F811\n \n from .smtlib import smtlib_code\n \n@@ -28,8 +28,6 @@\n \n from .octave import octave_code\n \n-from .rust import rust_code\n-\n from .gtk import print_gtk\n \n from .preview import preview\n@@ -64,7 +62,7 @@\n 'pycode',\n \n # sympy.printing.codeprinter\n- 'ccode', 'print_ccode', 'cxxcode', 'fcode', 'print_fcode',\n+ 'ccode', 'print_ccode', 'cxxcode', 'fcode', 'print_fcode', 'rust_code',\n \n # sympy.printing.smtlib\n 'smtlib_code',\n@@ -87,9 +85,6 @@\n # sympy.printing.octave\n 'octave_code',\n \n- # sympy.printing.rust\n- 'rust_code',\n-\n # sympy.printing.gtk\n 'print_gtk',\n \ndiff --git a/sympy/printing/codeprinter.py b/sympy/printing/codeprinter.py\nindex 346a801d3059..0dd2431ccba5 100644\n--- a/sympy/printing/codeprinter.py\n+++ b/sympy/printing/codeprinter.py\n@@ -5,7 +5,7 @@\n \n from sympy.core import Add, Mul, Pow, S, sympify, Float\n from sympy.core.basic import Basic\n-from sympy.core.expr import UnevaluatedExpr\n+from sympy.core.expr import Expr, UnevaluatedExpr\n from sympy.core.function import Lambda\n from sympy.core.mul import _keep_coeff\n from sympy.core.sorting import default_sort_key\n@@ -892,3 +892,125 @@ def cxxcode(expr, assign_to=None, standard='c++11', **settings):\n \"\"\" C++ equivalent of :func:`~.ccode`. \"\"\"\n from sympy.printing.cxx import cxx_code_printers\n return cxx_code_printers[standard.lower()](settings).doprint(expr, assign_to)\n+\n+\n+def rust_code(expr, assign_to=None, **settings):\n+ \"\"\"Converts an expr to a string of Rust code\n+\n+ Parameters\n+ ==========\n+\n+ expr : Expr\n+ A SymPy expression to be converted.\n+ assign_to : optional\n+ When given, the argument is used as the name of the variable to which\n+ the expression is assigned. Can be a string, ``Symbol``,\n+ ``MatrixSymbol``, or ``Indexed`` type. This is helpful in case of\n+ line-wrapping, or for expressions that generate multi-line statements.\n+ precision : integer, optional\n+ The precision for numbers such as pi [default=15].\n+ user_functions : dict, optional\n+ A dictionary where the keys are string representations of either\n+ ``FunctionClass`` or ``UndefinedFunction`` instances and the values\n+ are their desired C string representations. Alternatively, the\n+ dictionary value can be a list of tuples i.e. [(argument_test,\n+ cfunction_string)]. See below for examples.\n+ dereference : iterable, optional\n+ An iterable of symbols that should be dereferenced in the printed code\n+ expression. These would be values passed by address to the function.\n+ For example, if ``dereference=[a]``, the resulting code would print\n+ ``(*a)`` instead of ``a``.\n+ human : bool, optional\n+ If True, the result is a single string that may contain some constant\n+ declarations for the number symbols. If False, the same information is\n+ returned in a tuple of (symbols_to_declare, not_supported_functions,\n+ code_text). [default=True].\n+ contract: bool, optional\n+ If True, ``Indexed`` instances are assumed to obey tensor contraction\n+ rules and the corresponding nested loops over indices are generated.\n+ Setting contract=False will not generate loops, instead the user is\n+ responsible to provide values for the indices in the code.\n+ [default=True].\n+\n+ Examples\n+ ========\n+\n+ >>> from sympy import rust_code, symbols, Rational, sin, ceiling, Abs, Function\n+ >>> x, tau = symbols(\"x, tau\")\n+ >>> rust_code((2*tau)**Rational(7, 2))\n+ '8.0*1.4142135623731*tau.powf(7_f64/2.0)'\n+ >>> rust_code(sin(x), assign_to=\"s\")\n+ 's = x.sin();'\n+\n+ Simple custom printing can be defined for certain types by passing a\n+ dictionary of {\"type\" : \"function\"} to the ``user_functions`` kwarg.\n+ Alternatively, the dictionary value can be a list of tuples i.e.\n+ [(argument_test, cfunction_string)].\n+\n+ >>> custom_functions = {\n+ ... \"ceiling\": \"CEIL\",\n+ ... \"Abs\": [(lambda x: not x.is_integer, \"fabs\", 4),\n+ ... (lambda x: x.is_integer, \"ABS\", 4)],\n+ ... \"func\": \"f\"\n+ ... }\n+ >>> func = Function('func')\n+ >>> rust_code(func(Abs(x) + ceiling(x)), user_functions=custom_functions)\n+ '(fabs(x) + x.ceil()).f()'\n+\n+ ``Piecewise`` expressions are converted into conditionals. If an\n+ ``assign_to`` variable is provided an if statement is created, otherwise\n+ the ternary operator is used. Note that if the ``Piecewise`` lacks a\n+ default term, represented by ``(expr, True)`` then an error will be thrown.\n+ This is to prevent generating an expression that may not evaluate to\n+ anything.\n+\n+ >>> from sympy import Piecewise\n+ >>> expr = Piecewise((x + 1, x > 0), (x, True))\n+ >>> print(rust_code(expr, tau))\n+ tau = if (x > 0.0) {\n+ x + 1\n+ } else {\n+ x\n+ };\n+\n+ Support for loops is provided through ``Indexed`` types. With\n+ ``contract=True`` these expressions will be turned into loops, whereas\n+ ``contract=False`` will just print the assignment expression that should be\n+ looped over:\n+\n+ >>> from sympy import Eq, IndexedBase, Idx\n+ >>> len_y = 5\n+ >>> y = IndexedBase('y', shape=(len_y,))\n+ >>> t = IndexedBase('t', shape=(len_y,))\n+ >>> Dy = IndexedBase('Dy', shape=(len_y-1,))\n+ >>> i = Idx('i', len_y-1)\n+ >>> e=Eq(Dy[i], (y[i+1]-y[i])/(t[i+1]-t[i]))\n+ >>> rust_code(e.rhs, assign_to=e.lhs, contract=False)\n+ 'Dy[i] = (y[i + 1] - y[i])/(t[i + 1] - t[i]);'\n+\n+ Matrices are also supported, but a ``MatrixSymbol`` of the same dimensions\n+ must be provided to ``assign_to``. Note that any expression that can be\n+ generated normally can also exist inside a Matrix:\n+\n+ >>> from sympy import Matrix, MatrixSymbol\n+ >>> mat = Matrix([x**2, Piecewise((x + 1, x > 0), (x, True)), sin(x)])\n+ >>> A = MatrixSymbol('A', 3, 1)\n+ >>> print(rust_code(mat, A))\n+ A = [x.powi(2), if (x > 0.0) {\n+ x + 1\n+ } else {\n+ x\n+ }, x.sin()];\n+ \"\"\"\n+ from sympy.printing.rust import RustCodePrinter\n+ printer = RustCodePrinter(settings)\n+ expr = printer._rewrite_known_functions(expr)\n+ if isinstance(expr, Expr):\n+ for src_func, dst_func in printer.function_overrides.values():\n+ expr = expr.replace(src_func, dst_func)\n+ return printer.doprint(expr, assign_to)\n+\n+\n+def print_rust_code(expr, **settings):\n+ \"\"\"Prints Rust representation of the given expression.\"\"\"\n+ print(rust_code(expr, **settings))\ndiff --git a/sympy/printing/rust.py b/sympy/printing/rust.py\nindex 1d3ca8f26ad3..c98222c259a8 100644\n--- a/sympy/printing/rust.py\n+++ b/sympy/printing/rust.py\n@@ -32,11 +32,20 @@\n # .. _BigRational: http://rust-num.github.io/num/num/rational/type.BigRational.html\n \n from __future__ import annotations\n+from functools import reduce\n+import operator\n from typing import Any\n \n+from sympy.codegen.ast import (\n+ float32, float64, int32,\n+ real, integer, bool_\n+)\n from sympy.core import S, Rational, Float, Lambda\n+from sympy.core.expr import Expr\n from sympy.core.numbers import equal_valued\n+from sympy.functions.elementary.integers import ceiling, floor\n from sympy.printing.codeprinter import CodePrinter\n+from sympy.printing.precedence import PRECEDENCE\n \n # Rust's methods for integer and float can be found at here :\n #\n@@ -53,6 +62,26 @@\n # dictionary mapping SymPy function to (argument_conditions, Rust_function).\n # Used in RustCodePrinter._print_Function(self)\n \n+class float_floor(floor):\n+ \"\"\"\n+ Same as `sympy.floor`, but mimics the Rust behavior of returning a float rather than an integer\n+ \"\"\"\n+ def _eval_is_integer(self):\n+ return False\n+\n+class float_ceiling(ceiling):\n+ \"\"\"\n+ Same as `sympy.ceiling`, but mimics the Rust behavior of returning a float rather than an integer\n+ \"\"\"\n+ def _eval_is_integer(self):\n+ return False\n+\n+\n+function_overrides = {\n+ \"floor\": (floor, float_floor),\n+ \"ceiling\": (ceiling, float_ceiling),\n+}\n+\n # f64 method in Rust\n known_functions = {\n # \"\": \"is_nan\",\n@@ -60,8 +89,8 @@\n # \"\": \"is_finite\",\n # \"\": \"is_normal\",\n # \"\": \"classify\",\n- \"floor\": \"floor\",\n- \"ceiling\": \"ceil\",\n+ \"float_floor\": \"floor\",\n+ \"float_ceiling\": \"ceil\",\n # \"\": \"round\",\n # \"\": \"trunc\",\n # \"\": \"fract\",\n@@ -216,11 +245,47 @@\n 'yield']\n \n \n+class TypeCast(Expr):\n+ \"\"\"\n+ The type casting operator of the Rust language.\n+ \"\"\"\n+\n+ def __init__(self, expr, type_) -> None:\n+ super().__init__()\n+ self.explicit = expr.is_integer and type_ is not integer\n+ self._assumptions = expr._assumptions\n+ if self.explicit:\n+ setattr(self, 'precedence', PRECEDENCE[\"Func\"] + 10)\n+\n+ @property\n+ def expr(self):\n+ return self.args[0]\n+\n+ @property\n+ def type_(self):\n+ return self.args[1]\n+\n+ def sort_key(self, order=None):\n+ return self.args[0].sort_key(order=order)\n+\n+\n class RustCodePrinter(CodePrinter):\n \"\"\"A printer to convert SymPy expressions to strings of Rust code\"\"\"\n printmethod = \"_rust_code\"\n language = \"Rust\"\n \n+ type_aliases = {\n+ integer: int32,\n+ real: float64,\n+ }\n+\n+ type_mappings = {\n+ int32: 'i32',\n+ float32: 'f32',\n+ float64: 'f64',\n+ bool_: 'bool'\n+ }\n+\n _default_settings: dict[str, Any] = dict(CodePrinter._default_settings, **{\n 'precision': 17,\n 'user_functions': {},\n@@ -235,6 +300,7 @@ def __init__(self, settings={}):\n self.known_functions.update(userfuncs)\n self._dereference = set(settings.get('dereference', []))\n self.reserved_words = set(reserved_words)\n+ self.function_overrides = function_overrides\n \n def _rate_index_position(self, p):\n return p*5\n@@ -246,7 +312,8 @@ def _get_comment(self, text):\n return \"// %s\" % text\n \n def _declare_number_const(self, name, value):\n- return \"const %s: f64 = %s;\" % (name, value)\n+ type_ = self.type_mappings[self.type_aliases[real]]\n+ return \"const %s: %s = %s;\" % (name, type_, value)\n \n def _format_code(self, lines):\n return self.indent_code(lines)\n@@ -327,41 +394,63 @@ def _print_Function(self, expr):\n elif hasattr(expr, '_imp_') and isinstance(expr._imp_, Lambda):\n # inlined function\n return self._print(expr._imp_(*expr.args))\n- elif expr.func.__name__ in self._rewriteable_functions:\n- # Simple rewrite to supported function possible\n- target_f, required_fs = self._rewriteable_functions[expr.func.__name__]\n- if self._can_print(target_f) and all(self._can_print(f) for f in required_fs):\n- return self._print(expr.rewrite(target_f))\n else:\n return self._print_not_supported(expr)\n \n+ def _print_Mul(self, expr):\n+ contains_floats = any(arg.is_real and not arg.is_integer for arg in expr.args)\n+ if contains_floats:\n+ expr = reduce(operator.mul,(self._cast_to_float(arg) if arg != -1 else arg for arg in expr.args))\n+\n+ return super()._print_Mul(expr)\n+\n+ def _print_Add(self, expr, order=None):\n+ contains_floats = any(arg.is_real and not arg.is_integer for arg in expr.args)\n+ if contains_floats:\n+ expr = reduce(operator.add, (self._cast_to_float(arg) for arg in expr.args))\n+\n+ return super()._print_Add(expr, order)\n+\n def _print_Pow(self, expr):\n if expr.base.is_integer and not expr.exp.is_integer:\n expr = type(expr)(Float(expr.base), expr.exp)\n return self._print(expr)\n return self._print_Function(expr)\n \n+ def _print_TypeCast(self, expr):\n+ if not expr.explicit:\n+ return self._print(expr.expr)\n+ else:\n+ return self._print(expr.expr) + ' as %s' % self.type_mappings[self.type_aliases[expr.type_]]\n+\n def _print_Float(self, expr, _type=False):\n ret = super()._print_Float(expr)\n if _type:\n- return ret + '_f64'\n+ return ret + '_%s' % self.type_mappings[self.type_aliases[real]]\n else:\n return ret\n \n def _print_Integer(self, expr, _type=False):\n ret = super()._print_Integer(expr)\n if _type:\n- return ret + '_i32'\n+ return ret + '_%s' % self.type_mappings[self.type_aliases[integer]]\n else:\n return ret\n \n def _print_Rational(self, expr):\n p, q = int(expr.p), int(expr.q)\n- return '%d_f64/%d.0' % (p, q)\n+ float_suffix = self.type_mappings[self.type_aliases[real]]\n+ return '%d_%s/%d.0' % (p, float_suffix, q)\n \n def _print_Relational(self, expr):\n- lhs_code = self._print(expr.lhs)\n- rhs_code = self._print(expr.rhs)\n+ if (expr.lhs.is_integer and not expr.rhs.is_integer) or (expr.rhs.is_integer and not expr.lhs.is_integer):\n+ lhs = self._cast_to_float(expr.lhs)\n+ rhs = self._cast_to_float(expr.rhs)\n+ else:\n+ lhs = expr.lhs\n+ rhs = expr.rhs\n+ lhs_code = self._print(lhs)\n+ rhs_code = self._print(rhs)\n op = expr.rel_op\n return \"{} {} {}\".format(lhs_code, op, rhs_code)\n \n@@ -473,6 +562,48 @@ def _print_Assignment(self, expr):\n rhs_code = self._print(rhs)\n return self._get_statement(\"%s = %s\" % (lhs_code, rhs_code))\n \n+ def _cast_to_float(self, expr):\n+ if not expr.is_number:\n+ return TypeCast(expr, real)\n+ elif expr.is_integer:\n+ return Float(expr)\n+ return expr\n+\n+ def _can_print(self, name):\n+ \"\"\" Check if function ``name`` is either a known function or has its own\n+ printing method. Used to check if rewriting is possible.\"\"\"\n+\n+ # since the whole point of function_overrides is to enable proper printing,\n+ # we presume they all are printable\n+\n+ return name in self.known_functions or name in function_overrides or getattr(self, '_print_{}'.format(name), False)\n+\n+ def _collect_functions(self, expr):\n+ functions = set()\n+ if isinstance(expr, Expr):\n+ if expr.is_Function:\n+ functions.add(expr.func)\n+ for arg in expr.args:\n+ functions = functions.union(self._collect_functions(arg))\n+ return functions\n+\n+ def _rewrite_known_functions(self, expr):\n+ if not isinstance(expr, Expr):\n+ return expr\n+\n+ expression_functions = self._collect_functions(expr)\n+ rewriteable_functions = {\n+ name: (target_f, required_fs)\n+ for name, (target_f, required_fs) in self._rewriteable_functions.items()\n+ if self._can_print(target_f)\n+ and all(self._can_print(f) for f in required_fs)\n+ }\n+ for func in expression_functions:\n+ target_f, _ = rewriteable_functions.get(func.__name__, (None, None))\n+ if target_f:\n+ expr = expr.rewrite(target_f)\n+ return expr\n+\n def indent_code(self, code):\n \"\"\"Accepts a string of code or a list of code lines\"\"\"\n \n@@ -500,120 +631,3 @@ def indent_code(self, code):\n pretty.append(\"%s%s\" % (tab*level, line))\n level += increase[n]\n return pretty\n-\n-\n-def rust_code(expr, assign_to=None, **settings):\n- \"\"\"Converts an expr to a string of Rust code\n-\n- Parameters\n- ==========\n-\n- expr : Expr\n- A SymPy expression to be converted.\n- assign_to : optional\n- When given, the argument is used as the name of the variable to which\n- the expression is assigned. Can be a string, ``Symbol``,\n- ``MatrixSymbol``, or ``Indexed`` type. This is helpful in case of\n- line-wrapping, or for expressions that generate multi-line statements.\n- precision : integer, optional\n- The precision for numbers such as pi [default=15].\n- user_functions : dict, optional\n- A dictionary where the keys are string representations of either\n- ``FunctionClass`` or ``UndefinedFunction`` instances and the values\n- are their desired C string representations. Alternatively, the\n- dictionary value can be a list of tuples i.e. [(argument_test,\n- cfunction_string)]. See below for examples.\n- dereference : iterable, optional\n- An iterable of symbols that should be dereferenced in the printed code\n- expression. These would be values passed by address to the function.\n- For example, if ``dereference=[a]``, the resulting code would print\n- ``(*a)`` instead of ``a``.\n- human : bool, optional\n- If True, the result is a single string that may contain some constant\n- declarations for the number symbols. If False, the same information is\n- returned in a tuple of (symbols_to_declare, not_supported_functions,\n- code_text). [default=True].\n- contract: bool, optional\n- If True, ``Indexed`` instances are assumed to obey tensor contraction\n- rules and the corresponding nested loops over indices are generated.\n- Setting contract=False will not generate loops, instead the user is\n- responsible to provide values for the indices in the code.\n- [default=True].\n-\n- Examples\n- ========\n-\n- >>> from sympy import rust_code, symbols, Rational, sin, ceiling, Abs, Function\n- >>> x, tau = symbols(\"x, tau\")\n- >>> rust_code((2*tau)**Rational(7, 2))\n- '8*1.4142135623731*tau.powf(7_f64/2.0)'\n- >>> rust_code(sin(x), assign_to=\"s\")\n- 's = x.sin();'\n-\n- Simple custom printing can be defined for certain types by passing a\n- dictionary of {\"type\" : \"function\"} to the ``user_functions`` kwarg.\n- Alternatively, the dictionary value can be a list of tuples i.e.\n- [(argument_test, cfunction_string)].\n-\n- >>> custom_functions = {\n- ... \"ceiling\": \"CEIL\",\n- ... \"Abs\": [(lambda x: not x.is_integer, \"fabs\", 4),\n- ... (lambda x: x.is_integer, \"ABS\", 4)],\n- ... \"func\": \"f\"\n- ... }\n- >>> func = Function('func')\n- >>> rust_code(func(Abs(x) + ceiling(x)), user_functions=custom_functions)\n- '(fabs(x) + x.CEIL()).f()'\n-\n- ``Piecewise`` expressions are converted into conditionals. If an\n- ``assign_to`` variable is provided an if statement is created, otherwise\n- the ternary operator is used. Note that if the ``Piecewise`` lacks a\n- default term, represented by ``(expr, True)`` then an error will be thrown.\n- This is to prevent generating an expression that may not evaluate to\n- anything.\n-\n- >>> from sympy import Piecewise\n- >>> expr = Piecewise((x + 1, x > 0), (x, True))\n- >>> print(rust_code(expr, tau))\n- tau = if (x > 0) {\n- x + 1\n- } else {\n- x\n- };\n-\n- Support for loops is provided through ``Indexed`` types. With\n- ``contract=True`` these expressions will be turned into loops, whereas\n- ``contract=False`` will just print the assignment expression that should be\n- looped over:\n-\n- >>> from sympy import Eq, IndexedBase, Idx\n- >>> len_y = 5\n- >>> y = IndexedBase('y', shape=(len_y,))\n- >>> t = IndexedBase('t', shape=(len_y,))\n- >>> Dy = IndexedBase('Dy', shape=(len_y-1,))\n- >>> i = Idx('i', len_y-1)\n- >>> e=Eq(Dy[i], (y[i+1]-y[i])/(t[i+1]-t[i]))\n- >>> rust_code(e.rhs, assign_to=e.lhs, contract=False)\n- 'Dy[i] = (y[i + 1] - y[i])/(t[i + 1] - t[i]);'\n-\n- Matrices are also supported, but a ``MatrixSymbol`` of the same dimensions\n- must be provided to ``assign_to``. Note that any expression that can be\n- generated normally can also exist inside a Matrix:\n-\n- >>> from sympy import Matrix, MatrixSymbol\n- >>> mat = Matrix([x**2, Piecewise((x + 1, x > 0), (x, True)), sin(x)])\n- >>> A = MatrixSymbol('A', 3, 1)\n- >>> print(rust_code(mat, A))\n- A = [x.powi(2), if (x > 0) {\n- x + 1\n- } else {\n- x\n- }, x.sin()];\n- \"\"\"\n-\n- return RustCodePrinter(settings).doprint(expr, assign_to)\n-\n-\n-def print_rust_code(expr, **settings):\n- \"\"\"Prints Rust representation of the given expression.\"\"\"\n- print(rust_code(expr, **settings))\n", "test_patch": "diff --git a/sympy/core/tests/test_args.py b/sympy/core/tests/test_args.py\nindex 8ec028503d0f..57b5cab25d6f 100644\n--- a/sympy/core/tests/test_args.py\n+++ b/sympy/core/tests/test_args.py\n@@ -5219,6 +5219,22 @@ def test_sympy__codegen__matrix_nodes__MatrixSolve():\n assert _test_args(MatrixSolve(A, v))\n \n \n+def test_sympy__printing__rust__TypeCast():\n+ from sympy.printing.rust import TypeCast\n+ from sympy.codegen.ast import real\n+ assert _test_args(TypeCast(x, real))\n+\n+\n+def test_sympy__printing__rust__float_floor():\n+ from sympy.printing.rust import float_floor\n+ assert _test_args(float_floor(x))\n+\n+\n+def test_sympy__printing__rust__float_ceiling():\n+ from sympy.printing.rust import float_ceiling\n+ assert _test_args(float_ceiling(x))\n+\n+\n def test_sympy__vector__coordsysrect__CoordSys3D():\n from sympy.vector.coordsysrect import CoordSys3D\n assert _test_args(CoordSys3D('C'))\ndiff --git a/sympy/printing/tests/test_rust.py b/sympy/printing/tests/test_rust.py\nindex 1c2a443422bb..c88489591833 100644\n--- a/sympy/printing/tests/test_rust.py\n+++ b/sympy/printing/tests/test_rust.py\n@@ -9,9 +9,10 @@\n from sympy.tensor import IndexedBase, Idx\n from sympy.matrices import MatrixSymbol, SparseMatrix, Matrix\n \n-from sympy.printing.rust import rust_code\n+from sympy.printing.codeprinter import rust_code\n \n-x, y, z = symbols('x,y,z')\n+x, y, z = symbols('x,y,z', integer=False, real=True)\n+k, m, n = symbols('k,m,n', integer=True)\n \n \n def test_Integer():\n@@ -41,9 +42,11 @@ def test_basic_ops():\n assert rust_code(x + y) == \"x + y\"\n assert rust_code(x - y) == \"x - y\"\n assert rust_code(x * y) == \"x*y\"\n- assert rust_code(x / y) == \"x/y\"\n+ assert rust_code(x / y) == \"x*y.recip()\"\n assert rust_code(-x) == \"-x\"\n-\n+ assert rust_code(2 * x) == \"2.0*x\"\n+ assert rust_code(y + 2) == \"y + 2.0\"\n+ assert rust_code(x + n) == \"n as f64 + x\"\n \n def test_printmethod():\n class fabs(Abs):\n@@ -61,7 +64,7 @@ def test_Functions():\n assert rust_code(floor(x)) == \"x.floor()\"\n \n # Automatic rewrite\n- assert rust_code(Mod(x, 3)) == 'x - 3*((1_f64/3.0)*x).floor()'\n+ assert rust_code(Mod(x, 3)) == 'x - 3.0*((1_f64/3.0)*x).floor()'\n \n \n def test_Pow():\n@@ -86,7 +89,7 @@ def test_Pow():\n \n g = implemented_function('g', Lambda(x, 2*x))\n assert rust_code(1/(g(x)*3.5)**(x - y**x)/(x**2 + y)) == \\\n- \"(3.5*2*x).powf(-x + y.powf(x))/(x.powi(2) + y)\"\n+ \"(3.5*2.0*x).powf(-x + y.powf(x))/(x.powi(2) + y)\"\n _cond_cfunc = [(lambda base, exp: exp.is_integer, \"dpowi\", 1),\n (lambda base, exp: not exp.is_integer, \"pow\", 1)]\n assert rust_code(x**3, user_functions={'Pow': _cond_cfunc}) == 'x.dpowi(3)'\n@@ -105,10 +108,10 @@ def test_constants():\n \n \n def test_constants_other():\n- assert rust_code(2*GoldenRatio) == \"const GoldenRatio: f64 = %s;\\n2*GoldenRatio\" % GoldenRatio.evalf(17)\n+ assert rust_code(2*GoldenRatio) == \"const GoldenRatio: f64 = %s;\\n2.0*GoldenRatio\" % GoldenRatio.evalf(17)\n assert rust_code(\n- 2*Catalan) == \"const Catalan: f64 = %s;\\n2*Catalan\" % Catalan.evalf(17)\n- assert rust_code(2*EulerGamma) == \"const EulerGamma: f64 = %s;\\n2*EulerGamma\" % EulerGamma.evalf(17)\n+ 2*Catalan) == \"const Catalan: f64 = %s;\\n2.0*Catalan\" % Catalan.evalf(17)\n+ assert rust_code(2*EulerGamma) == \"const EulerGamma: f64 = %s;\\n2.0*EulerGamma\" % EulerGamma.evalf(17)\n \n \n def test_boolean():\n@@ -116,50 +119,50 @@ def test_boolean():\n assert rust_code(S.true) == \"true\"\n assert rust_code(False) == \"false\"\n assert rust_code(S.false) == \"false\"\n- assert rust_code(x & y) == \"x && y\"\n- assert rust_code(x | y) == \"x || y\"\n- assert rust_code(~x) == \"!x\"\n- assert rust_code(x & y & z) == \"x && y && z\"\n- assert rust_code(x | y | z) == \"x || y || z\"\n- assert rust_code((x & y) | z) == \"z || x && y\"\n- assert rust_code((x | y) & z) == \"z && (x || y)\"\n+ assert rust_code(k & m) == \"k && m\"\n+ assert rust_code(k | m) == \"k || m\"\n+ assert rust_code(~k) == \"!k\"\n+ assert rust_code(k & m & n) == \"k && m && n\"\n+ assert rust_code(k | m | n) == \"k || m || n\"\n+ assert rust_code((k & m) | n) == \"n || k && m\"\n+ assert rust_code((k | m) & n) == \"n && (k || m)\"\n \n \n def test_Piecewise():\n expr = Piecewise((x, x < 1), (x + 2, True))\n assert rust_code(expr) == (\n- \"if (x < 1) {\\n\"\n+ \"if (x < 1.0) {\\n\"\n \" x\\n\"\n \"} else {\\n\"\n- \" x + 2\\n\"\n+ \" x + 2.0\\n\"\n \"}\")\n assert rust_code(expr, assign_to=\"r\") == (\n- \"r = if (x < 1) {\\n\"\n+ \"r = if (x < 1.0) {\\n\"\n \" x\\n\"\n \"} else {\\n\"\n- \" x + 2\\n\"\n+ \" x + 2.0\\n\"\n \"};\")\n assert rust_code(expr, assign_to=\"r\", inline=True) == (\n- \"r = if (x < 1) { x } else { x + 2 };\")\n+ \"r = if (x < 1.0) { x } else { x + 2.0 };\")\n expr = Piecewise((x, x < 1), (x + 1, x < 5), (x + 2, True))\n assert rust_code(expr, inline=True) == (\n- \"if (x < 1) { x } else if (x < 5) { x + 1 } else { x + 2 }\")\n+ \"if (x < 1.0) { x } else if (x < 5.0) { x + 1.0 } else { x + 2.0 }\")\n assert rust_code(expr, assign_to=\"r\", inline=True) == (\n- \"r = if (x < 1) { x } else if (x < 5) { x + 1 } else { x + 2 };\")\n+ \"r = if (x < 1.0) { x } else if (x < 5.0) { x + 1.0 } else { x + 2.0 };\")\n assert rust_code(expr, assign_to=\"r\") == (\n- \"r = if (x < 1) {\\n\"\n+ \"r = if (x < 1.0) {\\n\"\n \" x\\n\"\n- \"} else if (x < 5) {\\n\"\n- \" x + 1\\n\"\n+ \"} else if (x < 5.0) {\\n\"\n+ \" x + 1.0\\n\"\n \"} else {\\n\"\n- \" x + 2\\n\"\n+ \" x + 2.0\\n\"\n \"};\")\n expr = 2*Piecewise((x, x < 1), (x + 1, x < 5), (x + 2, True))\n assert rust_code(expr, inline=True) == (\n- \"2*if (x < 1) { x } else if (x < 5) { x + 1 } else { x + 2 }\")\n+ \"2.0*if (x < 1.0) { x } else if (x < 5.0) { x + 1.0 } else { x + 2.0 }\")\n expr = 2*Piecewise((x, x < 1), (x + 1, x < 5), (x + 2, True)) - 42\n assert rust_code(expr, inline=True) == (\n- \"2*if (x < 1) { x } else if (x < 5) { x + 1 } else { x + 2 } - 42\")\n+ \"2.0*if (x < 1.0) { x } else if (x < 5.0) { x + 1.0 } else { x + 2.0 } - 42.0\")\n # Check that Piecewise without a True (default) condition error\n expr = Piecewise((x, x < 1), (x**2, x > 1), (sin(x), x > 0))\n raises(ValueError, lambda: rust_code(expr))\n@@ -172,8 +175,8 @@ def test_dereference_printing():\n \n def test_sign():\n expr = sign(x) * y\n- assert rust_code(expr) == \"y*x.signum()\"\n- assert rust_code(expr, assign_to='r') == \"r = y*x.signum();\"\n+ assert rust_code(expr) == \"y*x.signum() as f64\"\n+ assert rust_code(expr, assign_to='r') == \"r = y*x.signum() as f64;\"\n \n expr = sign(x + y) + 42\n assert rust_code(expr) == \"(x + y).signum() + 42\"\n@@ -197,12 +200,12 @@ def test_reserved_words():\n \n \n def test_ITE():\n- expr = ITE(x < 1, y, z)\n- assert rust_code(expr) == (\n- \"if (x < 1) {\\n\"\n- \" y\\n\"\n+ ekpr = ITE(k < 1, m, n)\n+ assert rust_code(ekpr) == (\n+ \"if (k < 1) {\\n\"\n+ \" m\\n\"\n \"} else {\\n\"\n- \" z\\n\"\n+ \" n\\n\"\n \"}\")\n \n \n@@ -325,7 +328,7 @@ def test_inline_function():\n \n g = implemented_function('g', Lambda(x, 2*x/Catalan))\n assert rust_code(g(x)) == (\n- \"const Catalan: f64 = %s;\\n2*x/Catalan\" % Catalan.evalf(17))\n+ \"const Catalan: f64 = %s;\\n2.0*x/Catalan\" % Catalan.evalf(17))\n \n A = IndexedBase('A')\n i = Idx('i', symbols('n', integer=True))\ndiff --git a/sympy/utilities/tests/test_codegen_rust.py b/sympy/utilities/tests/test_codegen_rust.py\nindex 235cc6350e05..7d721f64e7f3 100644\n--- a/sympy/utilities/tests/test_codegen_rust.py\n+++ b/sympy/utilities/tests/test_codegen_rust.py\n@@ -228,11 +228,11 @@ def test_piecewise_():\n source = result[1]\n expected = (\n \"fn pwtest(x: f64) -> f64 {\\n\"\n- \" let out1 = if (x < -1) {\\n\"\n+ \" let out1 = if (x < -1.0) {\\n\"\n \" 0\\n\"\n- \" } else if (x <= 1) {\\n\"\n+ \" } else if (x <= 1.0) {\\n\"\n \" x.powi(2)\\n\"\n- \" } else if (x > 1) {\\n\"\n+ \" } else if (x > 1.0) {\\n\"\n \" 2 - x\\n\"\n \" } else {\\n\"\n \" 1\\n\"\n", "problem_statement": "Rust codegen is broken\nThe current rust printer is broken, it does not output valid rust code. It sometimes places floats where only integers are allowed and vice versa.\r\n\r\nIn rust, `2*arg[1].powi(2)` does not compile because 2 is not a float, 2.0 is. `2.0*arg[1].powi(2.0)` also does not compile, because `powi` expects an integer, not a float.\r\n\r\nCurrently, sympy does not do this correctly. For example:\r\n![afbeelding](https://github.com/sympy/sympy/assets/56522208/8b5079b6-2d7f-4811-8418-666f61d23489)\r\nis printed as:\r\n```rust\r\n2*m.powi(2)*(\u03c6 - \u03c60)*(4*m.powi(4)*\u03c6.powi(3)*(\u03c6 - \u03c60) + \u03c80.powi(2)*(\u03c6.powi(2) - 3)*(\u03c6.powi(2) + 1))(\u03c6*(4*m.powi(4)*\u03c6.powi(2)*(\u03c6 - \u03c60).powi(2) + \u03c80.powi(2)*(\u03c6.powi(2) + 1).powi(2))*(2*V0 + m.powi(2)*(\u03c6 - \u03c60).powi(2) - 2*\u03c8*\u03c80))\r\n```\r\n\r\nPython 3.11.3\r\nSympy 1.11.1\r\nFedora 38, virtual environment \n", "hints_text": "Thank you for reporting this @smups . I think some of the issues can be fixed in the RustCodePrinter itself, others might require a preprocessing step (which `rust_code` could perform by default). I don't have any real experience of writing in rust (beyond working my way through their tutorial a few years back). Would you mind providing a few (minimal) examples highlighting the different failure modes, and what you would have expected instead? Those examples will be useful as test cases when someone tries to fix this.\n@bjodah Yeah sure. The main issue that comes to mind is that rust does not support implicit conversion of numeric types. Sympy sometimes mixes floats and integer types in the output code. Things like: `2*f` where `f` is supposed to be a float.\r\n\r\nI believe it's currently ambiguous what type each parsed sympy symbol should have. I think it might be most straightforward to allow the user to specify the output type of the whole expression (with some sane default like `f64`) and work back from there, making sure that all the numeric constants are parsed as the appropriate type (either float or int) and that the correct functions are used.\r\n\r\nFor example, calling `rust_code` with `rust_type=\"f64\"` (or something like that) converts all constants to float literals (so `2.0` instead of `2`) etc.\r\n\r\nOne complication (perhaps for a later revision) is that rust has a `pow` function that mixes integers and floats and that not all math functions are implemented for all types.\r\n\r\nHere's an overview of which mathematics functions are available in rust's std:\r\n\r\n### Float only:\r\n(arguments and return type are all floats)\r\n- trig functions: `(a)cos(h)`, `(a)sin(h)`, `(a)tan(h)`, `sin_cos` and `atan2`\r\n- roots: `sqrt`, `cbrt`\r\n- exponents: `exp`, `exp2`, `exp_m1`, `powf`\r\n- logarithms: `ln`, `ln_1p`, `log`, `log10`, `log2`\r\n- misc: `signum`, `abs`, `round`, `recip`, `div_euclid`, `rem_euclid`, `hypot`, `floor`\r\n\r\n### Integer only:\r\n(arguments and return type are all integers)\r\n- exponents: `pow`\r\n- logarithms: `ilog`, `ilog10`, `ilog2`\r\n- misc: `signum`, `abs`, `div_euclid`, `rem_euclid`, `abs_diff`\r\n\r\n### Then there's `pow(i)`...\r\n- `pow(self, exp: u32) -> Self` calculates `self^exp` where `Self` can be any numerical type. It can be called like `2.0.pow(2)`. It is a lot faster than the `powf` function.\r\n- `powi(self, exp: i32) -> Self` calculates `self^exp` where `Self` is a float and `exp` is an `i32`. It is not implemented for integer types. It can be called like `2.0.powi(-2)`. This function is generally faster than `powf`, but slower than `pow`.\r\n\r\nIt might be necessary to do some type conversions because most math functions are only implemented for the floating-point types. In rust you can do this with `type1 as type2`. Some conversions might do surprising things, but all conversions are well-defined (`NaN` becomes `0`, values too large/small for the output type will be cropped to the max/min value of the output type).\r\n\r\n# Some examples of expected behaviour:\r\n```python\r\na, b = sympy.symbols(\"a b\")\r\nexpr1 = (2*a**2 - b).nsimplify()\r\nexpr2 = sympy.sqrt(sympy.sqrt(a) - b)\r\nsympy.printing.rust_code(expr1)\r\nsympy.printing.rust_code(expr1)\r\n```\r\n```rust\r\n2.0*a.pow(2) - b //expr1\r\n(a.sqrt() - b).sqrt() //expr2\r\n```\r\nwith the (proposed) `rust_type` option:\r\n```python\r\nsympy.printing.rust_code(expr1, rust_type=\"i64\")\r\nsympy.printing.rust_code(expr2, rust_type=\"i64\")\r\n```\r\n```rust\r\n2*a.pow(2) - b //expr1\r\n((a as f64).sqrt() - b as f64).sqrt().round() as i64\r\n```\nThank you for expanding on this. I think the proper way forward is to add `Function` subclasses (e.g. `powi`) in a new module (`sympy.codegen.rust`). That file would also be the proper destination for utility functions that transforms the expression tree to include these rust specific functions.\r\n\r\nAs for float vs. integer, I was hoping that using `Symbol('k', integer=True)` could be useful to indicate what variables are expected to be integers. Then the user would need to specify a choice of what floating point type to use for symbols where integer is not set to `True` (default would probably be `f64`) and what type to use for symbols with `Integer` set to `True`. The Fortran and C printer uses the printer setting `type_aliases` for this. Do you think that mechanism is appropriate for rust as well?\n> As for float vs. integer, I was hoping that using Symbol('k', integer=True) could be useful to indicate what variables are expected to be integers. Then the user would need to specify what floating point type to use for symbols where `integer` is not set to True (default would probably be f64) and what type to use for symbols with Integer set to True. The Fortran and C printer uses the printer setting type_aliases for this. Do you think that mechanism is appropriate for rust as well?\r\n\r\nYes, I think that would be a great solution. So to properly parse a sympy expression we would need:\r\n- the types of all the symbols (integer, float, complex - throw an error for matrix symbols?)\r\n- `type_aliases` to go from sympy symbol types -> rust types (integer -> i64, float -> f64 etc.)\r\n- a user-defined output type (perhaps derived from the types of the symbols? I.e. if all symbols are integers, then the output of the expression should be an integer as well)", "created_at": "2024-07-30T17:05:17Z" }, { "repo": "sympy/sympy", "pull_number": 26876, "instance_id": "sympy__sympy-26876", "issue_numbers": [ "26784" ], "base_commit": "1a7afca81a3b4bcf5c60a41f04c27eb677309d6d", "patch": "diff --git a/.mailmap b/.mailmap\nindex a42978e6a0ad..4ad82320bea6 100644\n--- a/.mailmap\n+++ b/.mailmap\n@@ -1555,6 +1555,7 @@ Zamrath Nizam \n Zaz Brown \n Zedmat <104870914+harshkasat@users.noreply.github.com>\n Zeel Shah \n+Zexuan Zhou (Bruce) \n Zhenxu Zhu xzdlj \n Zhi-Qiang Zhou zhouzq-thu \n Zhongshi \ndiff --git a/sympy/printing/tensorflow.py b/sympy/printing/tensorflow.py\nindex 5c3a342bef52..78b0df62b611 100644\n--- a/sympy/printing/tensorflow.py\n+++ b/sympy/printing/tensorflow.py\n@@ -1,3 +1,5 @@\n+import sympy.codegen\n+import sympy.codegen.cfunctions\n from sympy.external.importtools import version_tuple\n from collections.abc import Iterable\n \n@@ -203,6 +205,12 @@ def _print_CodeBlock(self, expr):\n ret.append(self._print(subexpr))\n return \"\\n\".join(ret)\n \n+ def _print_isnan(self, exp):\n+ return f'tensorflow.math.is_nan({self._print(*exp.args)})'\n+\n+ def _print_isinf(self, exp):\n+ return f'tensorflow.math.is_inf({self._print(*exp.args)})'\n+\n _module = \"tensorflow\"\n _einsum = \"linalg.einsum\"\n _add = \"math.add\"\n", "test_patch": "diff --git a/sympy/printing/tests/test_tensorflow.py b/sympy/printing/tests/test_tensorflow.py\nindex d511e3b6c7fc..e9c92cd17b13 100644\n--- a/sympy/printing/tests/test_tensorflow.py\n+++ b/sympy/printing/tests/test_tensorflow.py\n@@ -1,6 +1,7 @@\n import random\n from sympy.core.function import Derivative\n from sympy.core.symbol import symbols\n+from sympy import Piecewise\n from sympy.tensor.array.expressions.array_expressions import ArrayTensorProduct, ArrayAdd, \\\n PermuteDims, ArrayDiagonal\n from sympy.core.relational import Eq, Ne, Ge, Gt, Le, Lt\n@@ -9,6 +10,7 @@\n Abs, ceiling, exp, floor, sign, sin, asin, sqrt, cos, \\\n acos, tan, atan, atan2, cosh, acosh, sinh, asinh, tanh, atanh, \\\n re, im, arg, erf, loggamma, log\n+from sympy.codegen.cfunctions import isnan, isinf\n from sympy.matrices import Matrix, MatrixBase, eye, randMatrix\n from sympy.matrices.expressions import \\\n Determinant, HadamardProduct, Inverse, MatrixSymbol, Trace\n@@ -463,3 +465,29 @@ def test_tensorflow_Derivative():\n expr = Derivative(sin(x), x)\n assert tensorflow_code(expr) == \\\n \"tensorflow.gradients(tensorflow.math.sin(x), x)[0]\"\n+\n+def test_tensorflow_isnan_isinf():\n+ if not tf:\n+ skip(\"TensorFlow not installed\")\n+\n+ # Test for isnan\n+ x = symbols(\"x\")\n+ # Return 0 if x is of nan value, and 1 otherwise\n+ expression = Piecewise((0.0, isnan(x)), (1.0, True))\n+ printed_code = tensorflow_code(expression)\n+ expected_printed_code = \"tensorflow.where(tensorflow.math.is_nan(x), 0.0, 1.0)\"\n+ assert tensorflow_code(expression) == expected_printed_code, f\"Incorrect printed result {printed_code}, expected {expected_printed_code}\"\n+ for _input, _expected in [(float('nan'), 0.0), (float('inf'), 1.0), (float('-inf'), 1.0), (1.0, 1.0)]:\n+ _output = lambdify((x), expression, modules=\"tensorflow\")(x=tf.constant([_input]))\n+ assert (_output == _expected).numpy().all()\n+\n+ # Test for isinf\n+ x = symbols(\"x\")\n+ # Return 0 if x is of nan value, and 1 otherwise\n+ expression = Piecewise((0.0, isinf(x)), (1.0, True))\n+ printed_code = tensorflow_code(expression)\n+ expected_printed_code = \"tensorflow.where(tensorflow.math.is_inf(x), 0.0, 1.0)\"\n+ assert tensorflow_code(expression) == expected_printed_code, f\"Incorrect printed result {printed_code}, expected {expected_printed_code}\"\n+ for _input, _expected in [(float('inf'), 0.0), (float('-inf'), 0.0), (float('nan'), 1.0), (1.0, 1.0)]:\n+ _output = lambdify((x), expression, modules=\"tensorflow\")(x=tf.constant([_input]))\n+ assert (_output == _expected).numpy().all()\n", "problem_statement": "How to cast nan to not-nan in an expression\nHi, I'm new to Sympy and I wonder how to cast null value to a default value in a Sympy expression\r\nFor example\r\n```\r\nimport sympy as sp\r\nx = sp.symbols(\"x\")\r\n# Return 0 if x is of nan value, and 1 otherwise\r\nexp = sp.Piecewise((0.0, sp.Eq(x, sp.nan)), (1.0, True))\r\nsp.lambdify((x), exp)(x=float('nan'))\r\nOut[28]: 1.0\r\n```\r\nThe above expression returns 1.0 instead of 0.0\r\nI found that the problem comes from the expression\r\n```\r\nIn [29]: sp.Eq(x, sp.nan)\r\nOut[29]: False\r\n```\r\nIt seems that `sp.Eq` does symbolic evaluation on variable `x` instead of waiting for the input from the actual calculation\r\n\r\nMaybe I'm using sympy in a wrong way. But would appreciate someone to shed some light on how to do this\r\n\r\nThank you\n", "hints_text": "@oscarbenjamin If you could take a look. Thanks\n> In [29]: sp.Eq(x, sp.nan)\r\n> Out[29]: False\r\n\r\nI don't think this should give False but rather unknown or something.\nEven if Eq were fixed to not evaluate, `lambdify` would still convert the code to `equal(x, nan)`, which is wrong. It should create `isnan(x)`. \nThe simplest workaround for you is to make your own `isnan` and use that instead of `Eq(x, nan)`. Currently this requires importing and using BooleanFunction, since Piecewise will fail if the expression is not a `Boolean`\r\n\r\n```py\r\n>>> from sympy.logic.boolalg import BooleanFunction\r\n>>> class isnan(BooleanFunction):\r\n... pass\r\n>>> isnan(x)\r\nisnan(x)\r\n>>> expr = Piecewise((0.0, isnan(x)), (1.0, True))\r\n>>> lambdify(x, expr)(float('nan'))\r\narray(0.)\r\n```\r\n\r\nThis works because the function `isnan` uses the same name as the NumPy `isnan`, so when lambdified it just uses `np.isnan`. \r\n\r\nYou could also add some logic to the custom `isnan` to actually evaluate to True on `sympy.nan` if you wanted. \n> `lambdify` would still convert the code to `equal(x, nan)`, which is wrong. It should create `isnan(x)`.\r\n\r\nIt is not clear to me that `Eq(x, nan)` should be evaluated as `isnan(x)`. The nan can come from other operations:\r\n```\r\nEq(x, y/sin(y)).subs(y, 0)\r\n```\r\nShould substituting `x = nan` here return True?\nThe point is just that the semantics should remain the same after lambdifying. Sometimes SymPy and NumPy use different semantics and lambdify has to translate from one to the other. In this case, sympy.nan doesn't use the same equality logic as numpy.nan, so lambdify should translate Eq to the equivalent isnan when one of the operands is nan. \r\n\r\nsympy.nan is already the codegen version of numpy.nan, even if they don't necessarily represent the same things. \r\n\r\nNow lambdify could in principle always translate `Eq(x, y)` to `np.where(isnan(x) | isnan(y), False, x == y)` but I don't think we should do that. Trying to handle nan in every possible function would just result in making everything slower, but it should at least handle it when an explicit `sympy.nan` is present. \n> lambdify should translate Eq to the equivalent isnan when one of the operands is nan\r\n\r\nSympy's Eq does not behave like `isnan` so there is no equivalence.\nThe equivalent to `Piecewise((0.0, sp.Eq(x, sp.nan)), (1.0, True))` uses `isnan`. In SymPy, replacing `x` with `nan` produces `0.0`, so in NumPy it should do the same. Like I said, `sympy.nan` is the codegen version of `np.nan`, so if you want to add nan handling in your expression so that it would get translated to lambdify, that's how you would do it. \r\n\r\nI guess we could also add `isnan` to sympy.codegen, but I in general if some SymPy function has a certain behavior that they want to translate to NumPy we want people to just that. \n> The equivalent to `Piecewise((0.0, sp.Eq(x, sp.nan)), (1.0, True))` uses `isnan`\r\n\r\nCan you demonstrate the equivalence here? We have:\r\n```python\r\nIn [12]: Eq(nan, nan)\r\nOut[12]: False\r\n\r\nIn [13]: np.isnan(float('nan'))\r\nOut[13]: True\r\n```\r\nDo you propose that `Eq(nan, nan)` should evaluate to True?\n> The equivalent to `Piecewise((0.0, sp.Eq(x, sp.nan)), (1.0, True))` uses `isnan`. In SymPy, replacing `x` with `nan` produces `0.0`, so in NumPy it should do the same.\r\n\r\nThis isn't what happens:\r\n```python\r\nIn [15]: Piecewise((0.0, sp.Eq(x, sp.nan)), (1.0, True))\r\nOut[15]: 1.00000000000000\r\n\r\nIn [16]: Piecewise((0.0, sp.Eq(sp.nan, sp.nan)), (1.0, True))\r\nOut[16]: 1.00000000000000\r\n\r\nIn [17]: Piecewise((0.0, sp.Eq(x, sp.nan)), (1.0, True)).subs(x, sp.nan)\r\nOut[17]: 1.00000000000000\r\n```\r\nNo cases give 0.0.\nThanks guys. I looked at BooleanFunction and it seems not very trivial for me to implement a customized version of `isnan`\r\nAlso a FYI, my use case is with tensorflow as the backend compute module when using `lambdify` so having something that only works in numpy is not sufficient\nThere is a `isnan` function in `sympy.codegen.cfunctions`. But it looks like it should be subclassing booleanfunction becuase Piecewise currently does not seem to work with it:\r\n
\r\n
>>> f = lambdify(x, Piecewise((0.0, isnan(x)), (1.0, True)))
\r\n\r\n```\r\n---------------------------------------------------------------------------\r\nTypeError Traceback (most recent call last)\r\nCell In[3], line 1\r\n----> 1 f = lambdify(x, Piecewise((0.0, isnan(x)), (1.0, True)))\r\n\r\nFile ~/vc/sympy/sympy/functions/elementary/piecewise.py:137, in Piecewise.__new__(cls, *args, **options)\r\n 134 newargs = []\r\n 135 for ec in args:\r\n 136 # ec could be a ExprCondPair or a tuple\r\n--> 137 pair = ExprCondPair(*getattr(ec, 'args', ec))\r\n 138 cond = pair.cond\r\n 139 if cond is false:\r\n\r\nFile ~/vc/sympy/sympy/functions/elementary/piecewise.py:33, in ExprCondPair.__new__(cls, expr, cond)\r\n 30 cond = cond.rewrite(ITE)\r\n 32 if not isinstance(cond, Boolean):\r\n---> 33 raise TypeError(filldedent('''\r\n 34 Second argument must be a Boolean,\r\n 35 not `%s`''' % func_name(cond)))\r\n 36 return Tuple.__new__(cls, expr, cond)\r\n```\r\n\r\n
\r\n\r\n```\r\nTypeError:\r\nSecond argument must be a Boolean, not `isnan`\r\n```\r\n\n> Do you propose that Eq(nan, nan) should evaluate to True?\r\n\r\nOh I didn't realize Eq was implementing that behavior. That's why Eq(x, nan) evaluates to False apparently, which is what you suggested should be changed above. \r\n\r\nPersonally I would be OK with SymPy's nan not carrying over the weird IEEE nan equality stuff. Really SymPy's nan was a poor decision and it should have just been called \"undefined\" or something. \nWould it be possible to fix this soon? Thanks\nIt is not completely clear to me what the fix should be. I think probably the code in the OP should not be expected to work and should be written differently.\n@zexuan-zhou I wonder if you can try gh-26794 and see if that works for you.\n@bjodah Thanks. I tested it and it seems work now in regular mode but not in tensorflow\r\n\r\n```\r\nIn [4]: !git branch\r\n* add-isinf\r\n master\r\n\r\nIn [5]: from sympy.codegen.cfunctions import isnan\r\n\r\nIn [6]: import sympy as sp\r\n ...: x = sp.symbols(\"x\")\r\n ...: # Return 0 if x is of nan value, and 1 otherwise\r\n ...: exp = sp.Piecewise((0.0, isnan(x)), (1.0, True))\r\n ...: sp.lambdify((x), exp)(x=float('nan'))\r\nOut[6]: array(0.)\r\n\r\nIn [7]: sp.lambdify((x), exp)(x=1)\r\nOut[7]: array(1.)\r\n\r\nIn [13]: sp.lambdify((x), exp, modules=\"tensorflow\")(x=tf.constant([np.nan]))\r\n---------------------------------------------------------------------------\r\nNameError Traceback (most recent call last)\r\nCell In[13], line 1\r\n----> 1 sp.lambdify((x), exp, modules=\"tensorflow\")(x=tf.constant([np.nan]))\r\nFile :2, in _lambdifygenerated(x)\r\n 1 def _lambdifygenerated(x):\r\n----> 2 return where(isnan(x), 0.0, 1.0)\r\n\r\nNameError: name 'isnan' is not defined\r\n```\nYes, that's expected. Feel free to add a print method to the tensorflow printer (I'm not familiar with tensorflow). I can pull in a commit into this branch if you would like.\n@bjodah Would appreciate it if you could help. I'm not familiar with how to modify the printer but I can give it a try if there is a guide\nSure, this functions looks like it does what we want:\r\nhttps://www.tensorflow.org/api_docs/python/tf/math/is_nan\r\n\r\nThen if you search for \"def _print_isnan\" in the diff view of my PR: https://github.com/sympy/sympy/pull/26794/files\r\n\r\nYou'll see how to implement the print method. You will need to do this here:\r\nhttps://github.com/sympy/sympy/blob/master/sympy/printing/tensorflow.py\n@bjodah Thanks!\r\nAre you going to merge your PR? I can rebase once you merge\nTypically we do not self-merge PRs in the sympy project. So we'll have to wait until someone finds the time to review the PR. You can open a new PR once mine is merged, or you can even open a PR against my branch over at github.com/bjodah/sympy if you prefer. It's up to you. :)", "created_at": "2024-07-29T23:53:10Z" }, { "repo": "sympy/sympy", "pull_number": 26848, "instance_id": "sympy__sympy-26848", "issue_numbers": [ "15323" ], "base_commit": "9a3c4653658031a7323c4e58bd821acb8a4e8f43", "patch": "diff --git a/sympy/core/expr.py b/sympy/core/expr.py\nindex a29b8298010f..f502625c7ea9 100644\n--- a/sympy/core/expr.py\n+++ b/sympy/core/expr.py\n@@ -3241,7 +3241,8 @@ def aseries(self, x=None, n=6, bound=0, hir=False):\n logw = log(1/res)\n \n s = func.series(k, 0, n)\n-\n+ from sympy.core.function import expand_mul\n+ s = expand_mul(s)\n # Hierarchical series\n if hir:\n return s.subs(k, exp(logw))\ndiff --git a/sympy/series/gruntz.py b/sympy/series/gruntz.py\nindex 407df70fdc4d..0bf3bf3e50b1 100644\n--- a/sympy/series/gruntz.py\n+++ b/sympy/series/gruntz.py\n@@ -118,7 +118,7 @@\n \"\"\"\n from functools import reduce\n \n-from sympy.core import Basic, S, Mul, PoleError, expand_mul\n+from sympy.core import Basic, S, Mul, PoleError\n from sympy.core.cache import cacheit\n from sympy.core.intfunc import ilcm\n from sympy.core.numbers import I, oo\n@@ -550,6 +550,11 @@ def mrv_leadterm(e, x):\n #\n w = Dummy(\"w\", positive=True)\n f, logw = rewrite(exps, Omega, x, w)\n+\n+ # Ensure expressions of the form exp(log(...)) don't get simplified automatically in the previous steps.\n+ # see: https://github.com/sympy/sympy/issues/15323#issuecomment-478639399\n+ f = f.replace(lambda f: f.is_Pow and f.has(x), lambda f: exp(log(f.base)*f.exp))\n+\n try:\n lt = f.leadterm(w, logx=logw)\n except (NotImplementedError, PoleError, ValueError):\n@@ -657,7 +662,7 @@ def rewrite(e, Omega, x, wsym):\n if not isinstance(rewrites[var], exp):\n raise ValueError(\"Value should be exp\")\n arg = rewrites[var].args[0]\n- O2.append((var, exp((arg - c*g.exp).expand())*wsym**c))\n+ O2.append((var, exp((arg - c*g.exp))*wsym**c))\n \n # Remember that Omega contains subexpressions of \"e\". So now we find\n # them in \"e\" and substitute them for our rewriting, stored in O2\n@@ -688,7 +693,6 @@ def rewrite(e, Omega, x, wsym):\n # -exp(p/(p + 1)) + exp(-p**2/(p + 1) + p). No current simplification\n # methods reduce this to 0 while not expanding polynomials.\n f = bottom_up(f, lambda w: getattr(w, 'normal', lambda: w)())\n- f = expand_mul(f)\n \n return f, logw\n \n", "test_patch": "diff --git a/sympy/series/tests/test_aseries.py b/sympy/series/tests/test_aseries.py\nindex 055d6b8aef23..cae0ac0a43f2 100644\n--- a/sympy/series/tests/test_aseries.py\n+++ b/sympy/series/tests/test_aseries.py\n@@ -1,6 +1,5 @@\n from sympy.core.function import PoleError\n from sympy.core.numbers import oo\n-from sympy.core.singleton import S\n from sympy.core.symbol import Symbol\n from sympy.functions.elementary.exponential import (exp, log)\n from sympy.functions.elementary.miscellaneous import sqrt\n@@ -32,9 +31,9 @@ def test_simple():\n \n e3 = lambda x:exp(exp(exp(x)))\n e = e3(x)/e3(x - 1/e3(x))\n- assert e.aseries(x, n=3) == 1 + exp(x + exp(x))*exp(-exp(exp(x)))\\\n- + ((-exp(x)/2 - S.Half)*exp(x + exp(x))\\\n- + exp(2*x + 2*exp(x))/2)*exp(-2*exp(exp(x))) + O(exp(-3*exp(exp(x))), (x, oo))\n+ assert e.aseries(x, n=3) == 1 + exp(2*x + 2*exp(x))*exp(-2*exp(exp(x)))/2\\\n+ - exp(2*x + exp(x))*exp(-2*exp(exp(x)))/2 - exp(x + exp(x))*exp(-2*exp(exp(x)))/2\\\n+ + exp(x + exp(x))*exp(-exp(exp(x))) + O(exp(-3*exp(exp(x))), (x, oo))\n \n e = exp(exp(x)) * (exp(sin(1/x + 1/exp(exp(x)))) - exp(sin(1/x)))\n assert e.aseries(x, n=4) == -1/(2*x**3) + 1/x + 1 + O(x**(-4), (x, oo))\ndiff --git a/sympy/series/tests/test_gruntz.py b/sympy/series/tests/test_gruntz.py\nindex 4565c876085b..c11d243c7c1e 100644\n--- a/sympy/series/tests/test_gruntz.py\n+++ b/sympy/series/tests/test_gruntz.py\n@@ -5,7 +5,6 @@\n from sympy.functions.elementary.exponential import (exp, log)\n from sympy.functions.elementary.miscellaneous import sqrt\n from sympy.functions.elementary.trigonometric import (acot, atan, cos, sin)\n-from sympy.functions.elementary.complexes import sign as _sign\n from sympy.functions.special.error_functions import (Ei, erf)\n from sympy.functions.special.gamma_functions import (digamma, gamma, loggamma)\n from sympy.functions.special.zeta_functions import zeta\n@@ -314,7 +313,7 @@ def test_rewrite1():\n e = exp(x + 1/x)\n assert mrewrite(mrv(e, x), x, m) == (1/m, -x - 1/x)\n e = 1/exp(-x + exp(-x)) - exp(x)\n- assert mrewrite(mrv(e, x), x, m) == (1/(m*exp(m)) - 1/m, -x)\n+ assert mrewrite(mrv(e, x), x, m) == ((-m*exp(m) + m)*exp(-m)/m**2, -x)\n \n \n def test_rewrite2():\n@@ -329,7 +328,7 @@ def test_rewrite3():\n e = exp(-x + 1/x**2) - exp(x + 1/x)\n #both of these are correct and should be equivalent:\n assert mrewrite(mrv(e, x), x, m) in [(-1/m + m*exp(\n- 1/x + 1/x**2), -x - 1/x), (m - 1/m*exp(1/x + x**(-2)), x**(-2) - x)]\n+ (x**2 + x)/x**3), -x - 1/x), ((m**2 - exp((x**2 + x)/x**3))/m, x**(-2) - x)]\n \n \n def test_mrv_leadterm1():\n@@ -404,7 +403,7 @@ def test_gruntz_I():\n assert gruntz(I*x, x, oo) == I*oo\n assert gruntz(y*I*x, x, oo) == y*I*oo\n assert gruntz(y*3*I*x, x, oo) == y*I*oo\n- assert gruntz(y*3*sin(I)*x, x, oo).simplify().rewrite(_sign) == _sign(y)*I*oo\n+ assert gruntz(y*3*sin(I)*x, x, oo) == y*I*oo\n \n \n def test_issue_4814():\ndiff --git a/sympy/series/tests/test_limits.py b/sympy/series/tests/test_limits.py\nindex ac28ad3eafbf..4f2760f8e82a 100644\n--- a/sympy/series/tests/test_limits.py\n+++ b/sympy/series/tests/test_limits.py\n@@ -1417,3 +1417,10 @@ def test_issue_26250():\n def test_issue_26916():\n assert limit(Ei(x)*exp(-x), x, +oo) == 0\n assert limit(Ei(x)*exp(-x), x, -oo) == 0\n+\n+\n+def test_issue_22982_15323():\n+ assert limit((log(E + 1/x) - 1)**(1 - sqrt(E + 1/x)), x, oo) == oo\n+ assert limit((1 - 1/x)**x*(log(1 - 1/x) + 1/(x*(1 - 1/x))), x, 1, dir='+') == 1\n+ assert limit((log(E + 1/x) )**(1 - sqrt(E + 1/x)), x, oo) == 1\n+ assert limit((log(E + 1/x) - 1)**(- sqrt(E + 1/x)), x, oo) == oo\n", "problem_statement": "limit of the derivative of (1-1/x)^x as x --> 1+ gives wrong answer\n```python3\r\n>>> from sympy import *\r\n>>> var('x')\r\nx\r\n>>> wat = ((1 - 1/x)**x).diff(x)\r\n>>> limit(wat, x, 1, dir='+')\r\n0\r\n>>> wat.subs(x, 1.0000000001)\r\n0.999999995294830\r\n>>> wat\r\n(1 - 1/x)**x*(log(1 - 1/x) + 1/(x*(1 - 1/x)))\r\n>>> \r\n```\r\n\r\nthe answer should be 1, not 0\n", "hints_text": "Where can I look for code of limit function?\r\n\n@jksuom i would like to solve this issue. Can you just guide me a bit where to look for possible error. ?\r\nThankyou !!\r\n\nI would look into the computation of `mrv_leadterm()` in `limitinf()` of `sympy/series/gruntz.py`.\n@jksuom what is the logic behind the computation of mrv_leadterm() sympy/series/gruntz.py .\r\nI understood the purpose of e0,c0, but i am not able to understand how it is calculated ?\r\n\nThe function is rewritten in terms of a single most rapidly varying component function denoted by w (imitating omega) and then expanded in a series of its powers. The exponents ei are real numbers, not necessarily integers, and the coefficients ci are functions lower comparability class than w, not necessarily constants in general. The series is computed in the same way as customary power series by traversing the expression tree of the function. Generally only the leading terms of the series need be retained to get c0 and e0.\nThe algorithm computes the limit of `e = (1 - 1/x)**x` at `1+` by setting `1 + 1/x` for `x`\r\n```\r\n>>> f = e.subs(x, 1 + 1/x); f\r\n(1 - 1/(1 + 1/x))**(1 + 1/x)\r\n```\r\nand computing its limit `limitinf(f, x)` as `x` tends to `oo`. The algorithm first finds the set of most rapidly variable components of `f`:\r\n```\r\n>>> mrv(f, x)\r\n({x: _Dummy_432, exp((1 + 1/x)*log(1 - 1/(1 + 1/x))): _Dummy_431}, {_Dummy_431: exp((1 + 1/_Dummy_432)*log(1 - 1/(1 + 1/_Dummy_432)))}, _Dummy_431)\r\n```\r\nIt contains `x` and `f` rewritten in the form `exp((1 + 1/x)*log(1 - 1/(1 + 1/x)))` (which means that they are in the same comparability class). The algorithm then accelerates the convergence by substituting `exp(x)` for `x`. (It does not change the limit as `exp(x)` also tends to `oo`.)\r\n```\r\n>>> g = f.subs(x, exp(x)); g\r\n(1 - 1/(1 + exp(-x)))**(1 + exp(-x))\r\n```\r\nThe next task is to find the expansion of `g` as a series in `w = exp(-x)`.\r\n```\r\ng = (1 - 1/(1 + w))**(1 + w) = (w/(1 + w)**(1 + w)\r\n = exp((1 + w)*log(w/(1 + w)))\r\n = exp((1 + w)*(log(w) - log(1 + w)) = exp(h)\r\n```\r\nAs `log(w) (= -x)` is less rapidly moving than `w (= exp(-x)`, `h` will have the following expansion\r\n```\r\nh = (1 + w)*(log(w) - w + ) = log(w) + (log(w) - 1)*w + \r\n```\r\nIt follows that\r\n```\r\ng = exp(h) = exp(log(w))*exp((log(w) - 1)*w + )\r\n = w*(1 + (log(w) - 1)*w + ) = w + \r\n```\r\nand therefore the most rapidly variable term of `g` is `w = 1*w**1`. In other words, `mrv_leadterm(g, x)` is `(1, 1)`. However, SymPy will give us\r\n```\r\n>>> mrv_leadterm(g, x)\r\n(exp(x), 2)\r\n```\r\nwhich is wrong. It means that the code will see `g` as tending quadratically to zero. If that were true, then the derivative would vanish linearly. This explains why the the algorithm gives 0 as the limit of the derivative.\nThe result `(c0, e0) = (exp(x), 2)` is wrong because `c0 = exp(x)` is in the same comparability class as `w = exp(-x)`. However, the indicated value of the leading term, `c0*w**e0 = exp(x)*w**2 = w` is correct, only its form is wrong. This means that rewriting has failed.\r\nIn fact, running `mrv_leadterm((1/(x + 1))**((x + 1)/x), x)` with `SYMPY_DEBUG=True` (on Python 3) will give\r\n\r\n rewrite(_Dummy_230, {exp(x): _Dummy_231, exp((exp(x) + 1)*exp(-x)*log(1/(exp(x) + 1))): \r\n _Dummy_230}, {_Dummy_230: exp((_Dummy_231 + 1)*log(1/(_Dummy_231 + \r\n 1))/_Dummy_231)}, x, _w) = (_w*exp(_w*log(1/(1 + 1/_w)) + x)/(1 + 1/_w), -x)\r\n\r\nwhich says that\r\n\r\n exp((exp(x) + 1)*exp(-x)*log(1/(exp(x) + 1))) = exp((1/w + 1)*w*log(1/(1/w + 1)))\r\n\r\nis rewritten as `w*exp(w*log(1/(1 + 1/w)) + x)/(1 + 1/w)`. The leading terms of the three factors\r\n\r\n w, exp(w*log(1/(1 + 1/w)) + x), 1/(1 + 1/w) = w/(w + 1)\r\n\r\nare `w, exp(x)` and `w`, respectively, which explains the result `exp(x)*w**2`.\nI'll try to fix this.\nThe purpose of `rewrite` is to represent all expressions `f` in a comparability class in terms of a single, preferably simple, one `g`, in the form `f = h*g**c` where `c` is a constant and `h` is in a lower comparability class. The principal task is to transform `h = f*g**-c` into a form that does not involve `f` or `g`. The algorithm works by writing `f` and `g` in the form `exp(u)` and `exp(v)` for some expressions `u` and `v`. To make that possible, it is often necessary to replace the variable `x` by `exp(x)`. Then `h` can be written as `exp(u - c*v)` where the most rapidly variable terms of `u` and `c*v` will cancel leaving an expression belonging to a lower comparability class. The relevant line in the code is https://github.com/sympy/sympy/blob/fb98dbb5a50dc13489b365ea6558e1efc5e62377/sympy/series/gruntz.py#L597 where `arg = u` and `g.args[0] = v`.\r\n\r\nIn the case at hand, we have `u = (exp(x) + 1)*exp(-x)*log(1/(exp(x) + 1))` , `v = x` (after substituting `exp(x)` for `x`) and `c = limit(u/v, x, oo) = -1`. The argument of `exp` then becomes\r\n```\r\n>>> (u - c*v).expand()\r\nx + log(1/(exp(x) + 1)) + exp(-x)*log(1/(exp(x) + 1))\r\n```\r\nwhere `x` is expected to cancel the most rapidly variable part of `log(1/(exp(x) + 1) = log(exp(-x)) - log(1 + exp(-x))`. However, the exponent (with `w = exp(-x)` as above) actually becomes\r\n\r\n w*log(1/(1 + 1/w)) + x = x + exp(-x)*log(1/(1 + exp(-x)))\r\n\r\nbecause `exp(log(1/(exp(x) + 1)))` is automatically rewritten as the third factor\r\n\r\n 1/(exp(x) + 1) = 1/(1 + 1/w)\r\n\r\nin the result of `rewrite` above.\r\n\r\nThis can be fixed by [removing the automatic simplification](https://groups.google.com/forum/#!searchin/sympy/Removing$20exp%28a*log%28x%29%29$20auto-simplification%7Csort:relevance/sympy/ph1II18Whn8/GDXaSYXOL5IJ). It may also be possible to work around the simplification issue by suppressing the creation of vulnerable expressions (by deleting `expand()`).", "created_at": "2024-07-23T07:23:35Z" }, { "repo": "sympy/sympy", "pull_number": 26809, "instance_id": "sympy__sympy-26809", "issue_numbers": [ "26807" ], "base_commit": "530149cc7256a98c5963bcccc43cec19a9d04d09", "patch": "diff --git a/.mailmap b/.mailmap\nindex 4d622cd0e986..95f9c4e2f880 100644\n--- a/.mailmap\n+++ b/.mailmap\n@@ -308,6 +308,7 @@ Andre de Fortier Smit \n Andreas Kl\u00f6ckner Andreas Kloeckner \n Andrej Tokar\u010d\u00edk \n Andrew Docherty \n+Andrew Mosson \n Andrew Straw \n Andrew Taber \n Andrey Grozin \ndiff --git a/sympy/tensor/array/expressions/array_expressions.py b/sympy/tensor/array/expressions/array_expressions.py\nindex 60912c0a4e23..135576abc127 100644\n--- a/sympy/tensor/array/expressions/array_expressions.py\n+++ b/sympy/tensor/array/expressions/array_expressions.py\n@@ -54,6 +54,8 @@ class ArraySymbol(_ArrayExpr):\n Symbol representing an array expression\n \"\"\"\n \n+ _iterable = False\n+\n def __new__(cls, symbol, shape: typing.Iterable) -> \"ArraySymbol\":\n if isinstance(symbol, str):\n symbol = Symbol(symbol)\n", "test_patch": "diff --git a/sympy/utilities/tests/test_lambdify.py b/sympy/utilities/tests/test_lambdify.py\nindex 631cd7bfb67c..fecb84ae7aa5 100644\n--- a/sympy/utilities/tests/test_lambdify.py\n+++ b/sympy/utilities/tests/test_lambdify.py\n@@ -32,6 +32,7 @@\n from sympy.matrices.expressions.dotproduct import DotProduct\n from sympy.simplify.cse_main import cse\n from sympy.tensor.array import derive_by_array, Array\n+from sympy.tensor.array.expressions import ArraySymbol\n from sympy.tensor.indexed import IndexedBase\n from sympy.utilities.lambdify import lambdify\n from sympy.utilities.iterables import numbered_symbols\n@@ -1915,3 +1916,11 @@ def test_assoc_legendre_numerical_evaluation():\n \n assert all_close(sympy_result_integer, mpmath_result_integer, tol)\n assert all_close(sympy_result_complex, mpmath_result_complex, tol)\n+\n+\n+def test_array_symbol():\n+ if not numpy:\n+ skip(\"numpy not installed.\")\n+ a = ArraySymbol('a', (3,))\n+ f = lambdify((a), a)\n+ assert numpy.all(f(numpy.array([1,2,3])) == numpy.array([1,2,3]))\n", "problem_statement": "lambdify((). ArraySymbol(...)) causes a recursion error\nUsing lambdify with expressions that contain and ArraySymbol causes a recursion depth exceeded error.\r\n\r\n```python\r\n>>> from sympy import Array, lambdify, tensorproduct\r\n>>> from sympy.tensor.array.expressions import ArraySymbol\r\n>>> from sympy.utilities.iterables import iterable\r\n\r\n>>> a = ArraySymbol('a', (3,))\r\n\r\n>>> f = lambdify((a), a)\r\n\r\n ---------------------------------------------------------------------------\r\n\r\n RecursionError Traceback (most recent call last)\r\n\r\n Cell In[3], line 1\r\n ----> 1 f = lambdify((a), a)\r\n\r\n\r\n File ~/.pyenv/versions/3.11.9/envs/sym-model/lib/python3.11/site-packages/sympy/utilities/lambdify.py:784, in lambdify(args, expr, modules, printer, use_imps, dummify, cse, docstring_limit)\r\n 782 # First find any function implementations\r\n 783 if use_imps:\r\n --> 784 namespaces.append(_imp_namespace(expr))\r\n 785 # Check for dict before iterating\r\n 786 if isinstance(modules, (dict, str)) or not hasattr(modules, '__iter__'):\r\n\r\n\r\n File ~/.pyenv/versions/3.11.9/envs/sym-model/lib/python3.11/site-packages/sympy/utilities/lambdify.py:1337, in _imp_namespace(expr, namespace)\r\n 1335 if is_sequence(expr):\r\n 1336 for arg in expr:\r\n -> 1337 _imp_namespace(arg, namespace)\r\n 1338 return namespace\r\n 1339 elif isinstance(expr, dict):\r\n\r\n\r\n File ~/.pyenv/versions/3.11.9/envs/sym-model/lib/python3.11/site-packages/sympy/utilities/lambdify.py:1359, in _imp_namespace(expr, namespace)\r\n 1357 if hasattr(expr, 'args'):\r\n 1358 for arg in expr.args:\r\n -> 1359 _imp_namespace(arg, namespace)\r\n 1360 return namespace\r\n\r\n\r\n File ~/.pyenv/versions/3.11.9/envs/sym-model/lib/python3.11/site-packages/sympy/utilities/lambdify.py:1337, in _imp_namespace(expr, namespace)\r\n 1335 if is_sequence(expr):\r\n 1336 for arg in expr:\r\n -> 1337 _imp_namespace(arg, namespace)\r\n 1338 return namespace\r\n 1339 elif isinstance(expr, dict):\r\n\r\n\r\n File ~/.pyenv/versions/3.11.9/envs/sym-model/lib/python3.11/site-packages/sympy/utilities/lambdify.py:1359, in _imp_namespace(expr, namespace)\r\n 1357 if hasattr(expr, 'args'):\r\n 1358 for arg in expr.args:\r\n -> 1359 _imp_namespace(arg, namespace)\r\n 1360 return namespace\r\n\r\n\r\n [... skipping similar frames: _imp_namespace at line 1337 (1475 times), _imp_namespace at line 1359 (1475 times)]\r\n\r\n\r\n File ~/.pyenv/versions/3.11.9/envs/sym-model/lib/python3.11/site-packages/sympy/utilities/lambdify.py:1337, in _imp_namespace(expr, namespace)\r\n 1335 if is_sequence(expr):\r\n 1336 for arg in expr:\r\n -> 1337 _imp_namespace(arg, namespace)\r\n 1338 return namespace\r\n 1339 elif isinstance(expr, dict):\r\n\r\n\r\n File ~/.pyenv/versions/3.11.9/envs/sym-model/lib/python3.11/site-packages/sympy/utilities/lambdify.py:1359, in _imp_namespace(expr, namespace)\r\n 1357 if hasattr(expr, 'args'):\r\n 1358 for arg in expr.args:\r\n -> 1359 _imp_namespace(arg, namespace)\r\n 1360 return namespace\r\n\r\n\r\n File ~/.pyenv/versions/3.11.9/envs/sym-model/lib/python3.11/site-packages/sympy/utilities/lambdify.py:1336, in _imp_namespace(expr, namespace)\r\n 1334 # tuples, lists, dicts are valid expressions\r\n 1335 if is_sequence(expr):\r\n -> 1336 for arg in expr:\r\n 1337 _imp_namespace(arg, namespace)\r\n 1338 return namespace\r\n\r\n\r\n File ~/.pyenv/versions/3.11.9/envs/sym-model/lib/python3.11/site-packages/sympy/tensor/array/expressions/array_expressions.py:46, in _ArrayExpr.__getitem__(self, item)\r\n 44 item = (item,)\r\n 45 ArrayElement._check_shape(self, item)\r\n ---> 46 return self._get(item)\r\n\r\n\r\n File ~/.pyenv/versions/3.11.9/envs/sym-model/lib/python3.11/site-packages/sympy/tensor/array/expressions/array_expressions.py:49, in _ArrayExpr._get(self, item)\r\n 48 def _get(self, item):\r\n ---> 49 return _get_array_element_or_slice(self, item)\r\n\r\n\r\n File ~/.pyenv/versions/3.11.9/envs/sym-model/lib/python3.11/site-packages/sympy/tensor/array/expressions/array_expressions.py:1967, in _get_array_element_or_slice(expr, indices)\r\n 1966 def _get_array_element_or_slice(expr, indices):\r\n -> 1967 return ArrayElement(expr, indices)\r\n\r\n\r\n File ~/.pyenv/versions/3.11.9/envs/sym-model/lib/python3.11/site-packages/sympy/tensor/array/expressions/array_expressions.py:95, in ArrayElement.__new__(cls, name, indices)\r\n 93 if not isinstance(indices, collections.abc.Iterable):\r\n 94 indices = (indices,)\r\n ---> 95 indices = _sympify(tuple(indices))\r\n 96 cls._check_shape(name, indices)\r\n 97 obj = Expr.__new__(cls, name, indices)\r\n\r\n\r\n File ~/.pyenv/versions/3.11.9/envs/sym-model/lib/python3.11/site-packages/sympy/core/sympify.py:526, in _sympify(a)\r\n 500 def _sympify(a):\r\n 501 \"\"\"\r\n 502 Short version of :func:`~.sympify` for internal usage for ``__add__`` and\r\n 503 ``__eq__`` methods where it is ok to allow some things (like Python\r\n (...)\r\n 524 \r\n 525 \"\"\"\r\n --> 526 return sympify(a, strict=True)\r\n\r\n\r\n File ~/.pyenv/versions/3.11.9/envs/sym-model/lib/python3.11/site-packages/sympy/core/sympify.py:381, in sympify(a, locals, convert_xor, strict, rational, evaluate)\r\n 379 conv = _sympy_converter.get(superclass)\r\n 380 if conv is not None:\r\n --> 381 return conv(a)\r\n 383 if cls is type(None):\r\n 384 if strict:\r\n\r\n\r\n File ~/.pyenv/versions/3.11.9/envs/sym-model/lib/python3.11/site-packages/sympy/core/containers.py:175, in (tup)\r\n 150 \"\"\"\r\n 151 The kind of a Tuple instance.\r\n 152 \r\n (...)\r\n 171 sympy.core.kind.NumberKind\r\n 172 \"\"\"\r\n 173 return TupleKind(*(i.kind for i in self.args))\r\n --> 175 _sympy_converter[tuple] = lambda tup: Tuple(*tup)\r\n 181 def tuple_wrapper(method):\r\n 182 \"\"\"\r\n 183 Decorator that converts any tuple in the function arguments into a Tuple.\r\n 184 \r\n (...)\r\n 204 \r\n 205 \"\"\"\r\n\r\n\r\n File ~/.pyenv/versions/3.11.9/envs/sym-model/lib/python3.11/site-packages/sympy/core/containers.py:55, in Tuple.__new__(cls, *args, **kwargs)\r\n 53 if kwargs.get('sympify', True):\r\n 54 args = (sympify(arg) for arg in args)\r\n ---> 55 obj = Basic.__new__(cls, *args)\r\n 56 return obj\r\n\r\n\r\n File ~/.pyenv/versions/3.11.9/envs/sym-model/lib/python3.11/site-packages/sympy/core/containers.py:54, in (.0)\r\n 52 def __new__(cls, *args, **kwargs):\r\n 53 if kwargs.get('sympify', True):\r\n ---> 54 args = (sympify(arg) for arg in args)\r\n 55 obj = Basic.__new__(cls, *args)\r\n 56 return obj\r\n\r\n\r\n File ~/.pyenv/versions/3.11.9/envs/sym-model/lib/python3.11/site-packages/sympy/core/sympify.py:381, in sympify(a, locals, convert_xor, strict, rational, evaluate)\r\n 379 conv = _sympy_converter.get(superclass)\r\n 380 if conv is not None:\r\n --> 381 return conv(a)\r\n 383 if cls is type(None):\r\n 384 if strict:\r\n\r\n\r\n File ~/.pyenv/versions/3.11.9/envs/sym-model/lib/python3.11/site-packages/sympy/core/cache.py:72, in __cacheit..func_wrapper..wrapper(*args, **kwargs)\r\n 69 @wraps(func)\r\n 70 def wrapper(*args, **kwargs):\r\n 71 try:\r\n ---> 72 retval = cfunc(*args, **kwargs)\r\n 73 except TypeError as e:\r\n 74 if not e.args or not e.args[0].startswith('unhashable type:'):\r\n\r\n\r\n RecursionError: maximum recursion depth exceeded while calling a Python object\r\n```\r\n\r\nDigging a bit in the code, it appears to occur because an ArraySymbol is ```iterable(ArraySymbol)``` returns ```True``` despite the fact that you can not in fact iterate over an ```ArraySymbol```\r\n\r\n```python\r\n>>> iterable(a)\r\n\r\n True\r\n\r\n>>> [e for e in a]\r\n\r\n ---------------------------------------------------------------------------\r\n\r\n ValueError Traceback (most recent call last)\r\n\r\n Cell In[6], line 1\r\n ----> 1 [e for e in a]\r\n\r\n\r\n Cell In[6], line 1, in (.0)\r\n ----> 1 [e for e in a]\r\n\r\n\r\n File ~/.pyenv/versions/3.11.9/envs/sym-model/lib/python3.11/site-packages/sympy/tensor/array/expressions/array_expressions.py:45, in _ArrayExpr.__getitem__(self, item)\r\n 43 if not isinstance(item, collections.abc.Iterable):\r\n 44 item = (item,)\r\n ---> 45 ArrayElement._check_shape(self, item)\r\n 46 return self._get(item)\r\n\r\n\r\n File ~/.pyenv/versions/3.11.9/envs/sym-model/lib/python3.11/site-packages/sympy/tensor/array/expressions/array_expressions.py:108, in ArrayElement._check_shape(cls, name, indices)\r\n 106 raise index_error\r\n 107 if any((i >= s) == True for i, s in zip(indices, name.shape)):\r\n --> 108 raise ValueError(\"shape is out of bounds\")\r\n 109 if any((i < 0) == True for i in indices):\r\n 110 raise ValueError(\"shape contains negative values\")\r\n\r\n\r\n ValueError: shape is out of bounds\r\n```\r\n\n", "hints_text": "", "created_at": "2024-07-12T18:23:09Z" }, { "repo": "sympy/sympy", "pull_number": 26802, "instance_id": "sympy__sympy-26802", "issue_numbers": [ "26789" ], "base_commit": "5f58c25d470291d4cb7c8cf602529fa4bf4b495e", "patch": "diff --git a/.mailmap b/.mailmap\nindex 56bd59ae2747..9f3c9824da4b 100644\n--- a/.mailmap\n+++ b/.mailmap\n@@ -733,6 +733,7 @@ James Harrop \n James Pearson \n James Taylor \n James Whitehead jcwhitehead \n+Jan Jancar J08nY \n Jan Kruse \n Jan-Philipp Hoffmann Jan-Philipp Hoffmann <90828785+jan-philipp-hoffmann@users.noreply.github.com>\n Jared Lumpe Michael Jared Lumpe \ndiff --git a/AUTHORS b/AUTHORS\nindex 507928408d75..0057a5df5622 100644\n--- a/AUTHORS\n+++ b/AUTHORS\n@@ -4,7 +4,7 @@ those who explicitly didn't want to be mentioned. People with a * next\n to their names are not found in the metadata of the git history. This\n file is generated automatically by running `./bin/authors_update.py`.\n \n-There are a total of 1308 authors.\n+There are a total of 1309 authors.\n \n Ond\u0159ej \u010cert\u00edk \n Fabian Pedregosa \n@@ -1314,3 +1314,4 @@ Alberto Jim\u00e9nez Ruiz \n Jo\u00e3o Bravo \n Dean Price \n Hugo Kerstens \n+Jan Jancar \ndiff --git a/sympy/polys/domains/finitefield.py b/sympy/polys/domains/finitefield.py\nindex 8e0a139050a8..92ecbaeb52dd 100644\n--- a/sympy/polys/domains/finitefield.py\n+++ b/sympy/polys/domains/finitefield.py\n@@ -1,5 +1,7 @@\n \"\"\"Implementation of :class:`FiniteField` class. \"\"\"\n \n+import operator\n+\n from sympy.external.gmpy import GROUND_TYPES\n from sympy.utilities.decorator import doctest_depends_on\n \n@@ -33,20 +35,42 @@ def _modular_int_factory(mod, dom, symmetric, self):\n \n # Use flint if available\n if flint is not None:\n+\n+ nmod = flint.nmod\n+ fmpz_mod_ctx = flint.fmpz_mod_ctx\n+ index = operator.index\n+\n try:\n mod = dom.convert(mod)\n except CoercionFailed:\n raise ValueError('modulus must be an integer, got %s' % mod)\n \n+ # mod might be e.g. Integer\n+ try:\n+ fmpz_mod_ctx(mod)\n+ except TypeError:\n+ mod = index(mod)\n+\n # flint's nmod is only for moduli up to 2^64-1 (on a 64-bit machine)\n try:\n- flint.nmod(0, mod)\n+ nmod(0, mod)\n except OverflowError:\n # Use fmpz_mod\n- ctx = flint.fmpz_mod_ctx(mod)\n+ fctx = fmpz_mod_ctx(mod)\n+\n+ def ctx(x):\n+ try:\n+ return fctx(x)\n+ except TypeError:\n+ # x might be Integer\n+ return fctx(index(x))\n else:\n # Use nmod\n- ctx = lambda x: flint.nmod(x, mod)\n+ def ctx(x):\n+ try:\n+ return nmod(x, mod)\n+ except TypeError:\n+ return nmod(index(x), mod)\n \n return ctx\n \n", "test_patch": "diff --git a/sympy/polys/domains/tests/test_domains.py b/sympy/polys/domains/tests/test_domains.py\nindex 3fe40cdb8fab..13fc7940fc28 100644\n--- a/sympy/polys/domains/tests/test_domains.py\n+++ b/sympy/polys/domains/tests/test_domains.py\n@@ -1047,10 +1047,16 @@ def test_ModularInteger():\n raises(TypeError, lambda: F5(n1) > F5(n2))\n raises(TypeError, lambda: F5(n1) >= F5(n2))\n \n+ # https://github.com/sympy/sympy/issues/26789\n+ assert GF(Integer(5)) == F5\n+ assert F5(Integer(3)) == F5(3)\n+\n+\n def test_QQ_int():\n assert int(QQ(2**2000, 3**1250)) == 455431\n assert int(QQ(2**100, 3)) == 422550200076076467165567735125\n \n+\n def test_RR_double():\n assert RR(3.14) > 1e-50\n assert RR(1e-13) > 1e-50\n@@ -1059,6 +1065,7 @@ def test_RR_double():\n assert RR(1e-20) > 1e-50\n assert RR(1e-40) > 1e-50\n \n+\n def test_RR_Float():\n f1 = Float(\"1.01\")\n f2 = Float(\"1.0000000000000000000001\")\n", "problem_statement": "GF __call__ broken with Integer after fmpz_mod change\nAfter https://github.com/sympy/sympy/pull/25663 the following code is broken:\r\n\r\n```python\r\nfrom sympy import FF, Integer\r\nK = FF(11)\r\ni = Integer(3)\r\nK(i)\r\n```\r\nGives:\r\n```python-traceback\r\nTraceback (most recent call last):\r\n File \"virt/lib/python3.11/site-packages/IPython/core/interactiveshell.py\", line 3508, in run_code\r\n exec(code_obj, self.user_global_ns, self.user_ns)\r\n File \"\", line 4, in \r\n K(i)\r\n File \"virt/lib/python3.11/site-packages/sympy/polys/domains/domain.py\", line 381, in __call__\r\n return self.new(*args)\r\n ^^^^^^^^^^^^^^^\r\n File \"virt/lib/python3.11/site-packages/sympy/polys/domains/domain.py\", line 372, in new\r\n return self.dtype(*args)\r\n ^^^^^^^^^^^^^^^^^\r\n File \"virt/lib/python3.11/site-packages/sympy/polys/domains/finitefield.py\", line 49, in \r\n ctx = lambda x: flint.nmod(x, mod)\r\n ^^^^^^^^^^^^^^^^^^\r\n File \"src/flint/types/nmod.pyx\", line 55, in flint.types.nmod.nmod.__init__\r\nTypeError: cannot create nmod from object of type \r\n```\r\n\r\nWhile previously it worked and gave a `SymmetricModularIntegerMod11(3)`.\n", "hints_text": "", "created_at": "2024-07-12T12:10:30Z" }, { "repo": "sympy/sympy", "pull_number": 26796, "instance_id": "sympy__sympy-26796", "issue_numbers": [ "26783" ], "base_commit": "e2145b58f528b495d08e0670ad5c2a19c1d1bbe1", "patch": "diff --git a/sympy/solvers/diophantine/diophantine.py b/sympy/solvers/diophantine/diophantine.py\nindex d02d0689d814..8200ef6da21e 100644\n--- a/sympy/solvers/diophantine/diophantine.py\n+++ b/sympy/solvers/diophantine/diophantine.py\n@@ -1482,7 +1482,9 @@ def diophantine(eq, param=symbols(\"t\", integer=True), syms=None,\n null = tuple([0]*len(var))\n # if there is no solution, return trivial solution\n if not sols and eq.subs(zip(var, null)).is_zero:\n- sols.add(null)\n+ if all(check_assumptions(val, **s.assumptions0) is not False for val, s in zip(null, var)):\n+ sols.add(null)\n+\n final_soln = set()\n for sol in sols:\n if all(int_valued(s) for s in sol):\n@@ -1558,7 +1560,8 @@ def diop_solve(eq, param=symbols(\"t\", integer=True)):\n \n Use of ``diophantine()`` is recommended over other helper functions.\n ``diop_solve()`` can return either a set or a tuple depending on the\n- nature of the equation.\n+ nature of the equation. All non-trivial solutions are returned: assumptions\n+ on symbols are ignored.\n \n Usage\n =====\n", "test_patch": "diff --git a/sympy/solvers/diophantine/tests/test_diophantine.py b/sympy/solvers/diophantine/tests/test_diophantine.py\nindex a500903fc06f..e36c4c9ad165 100644\n--- a/sympy/solvers/diophantine/tests/test_diophantine.py\n+++ b/sympy/solvers/diophantine/tests/test_diophantine.py\n@@ -752,6 +752,18 @@ def test_assumptions():\n diof = diophantine(a*b + 2*a + 3*b - 6)\n assert diof == {(-15, -3), (-9, -4), (-7, -5), (-6, -6), (-5, -8), (-4, -14)}\n \n+ x, y = symbols('x y', integer=True)\n+ diof = diophantine(10*x**2 + 5*x*y - 3*y)\n+ assert diof == {(1, -5), (-3, 5), (0, 0)}\n+\n+ x, y = symbols('x y', integer=True, positive=True)\n+ diof = diophantine(10*x**2 + 5*x*y - 3*y)\n+ assert diof == set()\n+\n+ x, y = symbols('x y', integer=True, negative=False)\n+ diof = diophantine(10*x**2 + 5*x*y - 3*y)\n+ assert diof == {(0, 0)}\n+\n \n def check_solutions(eq):\n \"\"\"\n", "problem_statement": "diophantine and diop_solve ignore assumptions\nUsing `solvers.diophantine` `diophantine` and `diop_solve` functions with integer and positive assumed symbols, also `{(0, 0)}` and negative solutions are returned.\r\nFor example:\r\n```python\r\nfrom sympy import Symbol\r\nfrom sympy.solvers.diophantine import diophantine, diop_solve\r\n\r\nx = Symbol(\"x\", integer=True, positive=True)\r\ny = Symbol(\"y\", integer=True, positive=True)\r\neq = 10*x**2 + 5*x*y - 3*y\r\nprint(diophantine(eq))\r\nprint(diop_solve(eq))\r\n```\r\nprints\r\n```\r\n{(0, 0)}\r\n{(1, -5), (-3, 5), (0, 0)}\r\n```\r\nwhile it should print at least an empty solution set.\n", "hints_text": "It seems currently `diop_quadratic()` will always return a trivial solution if applied when no other solutions are found.\r\nhttps://github.com/sympy/sympy/blob/3a812204052176854bdfc1ea9d12097c3f9307ca/sympy/solvers/diophantine/diophantine.py#L1479-L1485\r\nMaybe we should rearrange the logic? By the way, you should use `diophantine()` rather than `diop_solve()` since the latter won't filter out the negative solutions. \nI'm not inside `sympy` logic, but if a symbol is subjected to certain assumptions, intuitively I expect that functions I call on that symbols are going to adhere to such assumptions. So yes, probably editing `diophantine()` to make it returns an empty set if the trivial solution is not compatible with assumptions will be a better choice. Regarding `diop_solve()`, as it is an internal use function only, maybe adding in the documentation the warning that it does not necessarily respect assumptions (except the obvious `integer=True`) is the way.", "created_at": "2024-07-11T15:50:59Z" }, { "repo": "sympy/sympy", "pull_number": 26794, "instance_id": "sympy__sympy-26794", "issue_numbers": [ "26784" ], "base_commit": "b6376e085a047b8bada988ff57fbb79a62546842", "patch": "diff --git a/sympy/codegen/cfunctions.py b/sympy/codegen/cfunctions.py\nindex 2eb3176ea8cd..7b79291f128a 100644\n--- a/sympy/codegen/cfunctions.py\n+++ b/sympy/codegen/cfunctions.py\n@@ -12,7 +12,7 @@\n from sympy.core.singleton import S\n from sympy.functions.elementary.exponential import exp, log\n from sympy.functions.elementary.miscellaneous import sqrt\n-\n+from sympy.logic.boolalg import BooleanFunction, true, false\n \n def _expm1(x):\n return exp(x) - S.One\n@@ -532,5 +532,27 @@ def _eval_rewrite_as_Pow(self, arg, **kwargs):\n _eval_rewrite_as_tractable = _eval_rewrite_as_Pow\n \n \n-class isnan(Function):\n+class isnan(BooleanFunction):\n nargs = 1\n+\n+ @classmethod\n+ def eval(cls, arg):\n+ if arg is S.NaN:\n+ return true\n+ elif arg.is_number:\n+ return false\n+ else:\n+ return None\n+\n+\n+class isinf(BooleanFunction):\n+ nargs = 1\n+\n+ @classmethod\n+ def eval(cls, arg):\n+ if arg.is_infinite:\n+ return true\n+ elif arg.is_finite:\n+ return false\n+ else:\n+ return None\ndiff --git a/sympy/printing/codeprinter.py b/sympy/printing/codeprinter.py\nindex d58c5f76b1bd..346a801d3059 100644\n--- a/sympy/printing/codeprinter.py\n+++ b/sympy/printing/codeprinter.py\n@@ -519,6 +519,12 @@ def _print_Not(self, expr):\n def _print_BooleanFunction(self, expr):\n return self._print(expr.to_nnf())\n \n+ def _print_isnan(self, arg):\n+ return 'isnan(%s)' % self._print(*arg.args)\n+\n+ def _print_isinf(self, arg):\n+ return 'isinf(%s)' % self._print(*arg.args)\n+\n def _print_Mul(self, expr):\n \n prec = precedence(expr)\ndiff --git a/sympy/printing/numpy.py b/sympy/printing/numpy.py\nindex 00713ebc67fb..37650b0163b9 100644\n--- a/sympy/printing/numpy.py\n+++ b/sympy/printing/numpy.py\n@@ -19,7 +19,9 @@\n 'sign': 'sign',\n 'logaddexp': 'logaddexp',\n 'logaddexp2': 'logaddexp2',\n- 'isnan': 'isnan'\n+ 'isinf': 'isinf',\n+ 'isnan': 'isnan',\n+\n })\n _known_constants_numpy = {\n 'Exp1': 'e',\ndiff --git a/sympy/printing/pycode.py b/sympy/printing/pycode.py\nindex 54e88e7e1012..7094e076602b 100644\n--- a/sympy/printing/pycode.py\n+++ b/sympy/printing/pycode.py\n@@ -41,6 +41,7 @@\n 'floor': 'floor',\n 'gamma': 'gamma',\n 'hypot': 'hypot',\n+ 'isinf': 'isinf',\n 'isnan': 'isnan',\n 'loggamma': 'lgamma',\n 'log': 'log',\n", "test_patch": "diff --git a/sympy/codegen/tests/test_cfunctions.py b/sympy/codegen/tests/test_cfunctions.py\nindex ca85758d3461..f2bc3992ec5f 100644\n--- a/sympy/codegen/tests/test_cfunctions.py\n+++ b/sympy/codegen/tests/test_cfunctions.py\n@@ -3,7 +3,7 @@\n from sympy.core.symbol import (Symbol, symbols)\n from sympy.functions.elementary.exponential import (exp, log)\n from sympy.codegen.cfunctions import (\n- expm1, log1p, exp2, log2, fma, log10, Sqrt, Cbrt, hypot\n+ expm1, log1p, exp2, log2, fma, log10, Sqrt, Cbrt, hypot, isnan, isinf\n )\n from sympy.core.function import expand_log\n \n@@ -163,3 +163,24 @@ def test_hypot():\n \n assert hypot(17*x, 42*y).diff(x).expand(func=True) - 2*17*17*x*((17*x)**2 + (42*y)**2)**Rational(-1, 2)/2 == 0\n assert hypot(17*x, 42*y).diff(y).expand(func=True) - 2*42*42*y*((17*x)**2 + (42*y)**2)**Rational(-1, 2)/2 == 0\n+\n+\n+def test_isnan_isinf():\n+ x = Symbol('x')\n+\n+ # isinf\n+ assert isinf(+S.Infinity) == True\n+ assert isinf(-S.Infinity) == True\n+ assert isinf(S.Pi) == False\n+ isinfx = isinf(x)\n+ assert isinfx not in (False, True)\n+ assert isinfx.func is isinf\n+ assert isinfx.args == (x,)\n+\n+ # isnan\n+ assert isnan(S.NaN) == True\n+ assert isnan(S.Pi) == False\n+ isnanx = isnan(x)\n+ assert isnanx not in (False, True)\n+ assert isnanx.func is isnan\n+ assert isnanx.args == (x,)\ndiff --git a/sympy/core/tests/test_args.py b/sympy/core/tests/test_args.py\nindex 6c69e8f5555d..8ec028503d0f 100644\n--- a/sympy/core/tests/test_args.py\n+++ b/sympy/core/tests/test_args.py\n@@ -5156,6 +5156,11 @@ def test_sympy__codegen__cfunctions__isnan():\n assert _test_args(isnan(x))\n \n \n+def test_sympy__codegen__cfunctions__isinf():\n+ from sympy.codegen.cfunctions import isinf\n+ assert _test_args(isinf(x))\n+\n+\n def test_sympy__codegen__fnodes__FFunction():\n from sympy.codegen.fnodes import FFunction\n assert _test_args(FFunction('f'))\ndiff --git a/sympy/printing/tests/test_c.py b/sympy/printing/tests/test_c.py\nindex 11836539f0b0..bd76af28cc32 100644\n--- a/sympy/printing/tests/test_c.py\n+++ b/sympy/printing/tests/test_c.py\n@@ -19,7 +19,7 @@\n While, Scope, Print, FunctionPrototype, FunctionDefinition, FunctionCall, Return,\n real, float32, float64, float80, float128, intc, Comment, CodeBlock, stderr, QuotedString\n )\n-from sympy.codegen.cfunctions import expm1, log1p, exp2, log2, fma, log10, Cbrt, hypot, Sqrt\n+from sympy.codegen.cfunctions import expm1, log1p, exp2, log2, fma, log10, Cbrt, hypot, Sqrt, isnan, isinf\n from sympy.codegen.cnodes import restrict\n from sympy.utilities.lambdify import implemented_function\n from sympy.tensor import IndexedBase, Idx\n@@ -881,3 +881,7 @@ def test_ccode_UnevaluatedExpr():\n def test_ccode_array_like_containers():\n assert ccode([2,3,4]) == \"{2, 3, 4}\"\n assert ccode((2,3,4)) == \"{2, 3, 4}\"\n+\n+def test_ccode__isinf_isnan():\n+ assert ccode(isinf(x)) == 'isinf(x)'\n+ assert ccode(isnan(x)) == 'isnan(x)'\ndiff --git a/sympy/utilities/tests/test_lambdify.py b/sympy/utilities/tests/test_lambdify.py\nindex fecb84ae7aa5..96203a3ccb3a 100644\n--- a/sympy/utilities/tests/test_lambdify.py\n+++ b/sympy/utilities/tests/test_lambdify.py\n@@ -14,7 +14,7 @@\n from sympy.core.symbol import (Dummy, symbols)\n from sympy.functions.combinatorial.factorials import (RisingFactorial, factorial)\n from sympy.functions.combinatorial.numbers import bernoulli, harmonic\n-from sympy.functions.elementary.complexes import Abs\n+from sympy.functions.elementary.complexes import Abs, sign\n from sympy.functions.elementary.exponential import exp, log\n from sympy.functions.elementary.hyperbolic import acosh\n from sympy.functions.elementary.integers import floor\n@@ -38,7 +38,7 @@\n from sympy.utilities.iterables import numbered_symbols\n from sympy.vector import CoordSys3D\n from sympy.core.expr import UnevaluatedExpr\n-from sympy.codegen.cfunctions import expm1, log1p, exp2, log2, log10, hypot\n+from sympy.codegen.cfunctions import expm1, log1p, exp2, log2, log10, hypot, isnan, isinf\n from sympy.codegen.numpy_nodes import logaddexp, logaddexp2\n from sympy.codegen.scipy_nodes import cosm1, powm1\n from sympy.functions.elementary.complexes import re, im, arg\n@@ -1918,6 +1918,30 @@ def test_assoc_legendre_numerical_evaluation():\n assert all_close(sympy_result_complex, mpmath_result_complex, tol)\n \n \n+def test_Piecewise():\n+\n+ modules = [math]\n+ if numpy:\n+ modules.append('numpy')\n+\n+ for mod in modules:\n+ # test isinf\n+ f = lambdify(x, Piecewise((7.0, isinf(x)), (3.0, True)), mod)\n+ assert f(+float('inf')) == +7.0\n+ assert f(-float('inf')) == +7.0\n+ assert f(42.) == 3.0\n+\n+ f2 = lambdify(x, Piecewise((7.0*sign(x), isinf(x)), (3.0, True)), mod)\n+ assert f2(+float('inf')) == +7.0\n+ assert f2(-float('inf')) == -7.0\n+ assert f2(42.) == 3.0\n+\n+ # test isnan (gh-26784)\n+ g = lambdify(x, Piecewise((7.0, isnan(x)), (3.0, True)), mod)\n+ assert g(float('nan')) == 7.0\n+ assert g(42.) == 3.0\n+\n+\n def test_array_symbol():\n if not numpy:\n skip(\"numpy not installed.\")\n", "problem_statement": "How to cast nan to not-nan in an expression\nHi, I'm new to Sympy and I wonder how to cast null value to a default value in a Sympy expression\r\nFor example\r\n```\r\nimport sympy as sp\r\nx = sp.symbols(\"x\")\r\n# Return 0 if x is of nan value, and 1 otherwise\r\nexp = sp.Piecewise((0.0, sp.Eq(x, sp.nan)), (1.0, True))\r\nsp.lambdify((x), exp)(x=float('nan'))\r\nOut[28]: 1.0\r\n```\r\nThe above expression returns 1.0 instead of 0.0\r\nI found that the problem comes from the expression\r\n```\r\nIn [29]: sp.Eq(x, sp.nan)\r\nOut[29]: False\r\n```\r\nIt seems that `sp.Eq` does symbolic evaluation on variable `x` instead of waiting for the input from the actual calculation\r\n\r\nMaybe I'm using sympy in a wrong way. But would appreciate someone to shed some light on how to do this\r\n\r\nThank you\n", "hints_text": "@oscarbenjamin If you could take a look. Thanks\n> In [29]: sp.Eq(x, sp.nan)\r\n> Out[29]: False\r\n\r\nI don't think this should give False but rather unknown or something.\nEven if Eq were fixed to not evaluate, `lambdify` would still convert the code to `equal(x, nan)`, which is wrong. It should create `isnan(x)`. \nThe simplest workaround for you is to make your own `isnan` and use that instead of `Eq(x, nan)`. Currently this requires importing and using BooleanFunction, since Piecewise will fail if the expression is not a `Boolean`\r\n\r\n```py\r\n>>> from sympy.logic.boolalg import BooleanFunction\r\n>>> class isnan(BooleanFunction):\r\n... pass\r\n>>> isnan(x)\r\nisnan(x)\r\n>>> expr = Piecewise((0.0, isnan(x)), (1.0, True))\r\n>>> lambdify(x, expr)(float('nan'))\r\narray(0.)\r\n```\r\n\r\nThis works because the function `isnan` uses the same name as the NumPy `isnan`, so when lambdified it just uses `np.isnan`. \r\n\r\nYou could also add some logic to the custom `isnan` to actually evaluate to True on `sympy.nan` if you wanted. \n> `lambdify` would still convert the code to `equal(x, nan)`, which is wrong. It should create `isnan(x)`.\r\n\r\nIt is not clear to me that `Eq(x, nan)` should be evaluated as `isnan(x)`. The nan can come from other operations:\r\n```\r\nEq(x, y/sin(y)).subs(y, 0)\r\n```\r\nShould substituting `x = nan` here return True?\nThe point is just that the semantics should remain the same after lambdifying. Sometimes SymPy and NumPy use different semantics and lambdify has to translate from one to the other. In this case, sympy.nan doesn't use the same equality logic as numpy.nan, so lambdify should translate Eq to the equivalent isnan when one of the operands is nan. \r\n\r\nsympy.nan is already the codegen version of numpy.nan, even if they don't necessarily represent the same things. \r\n\r\nNow lambdify could in principle always translate `Eq(x, y)` to `np.where(isnan(x) | isnan(y), False, x == y)` but I don't think we should do that. Trying to handle nan in every possible function would just result in making everything slower, but it should at least handle it when an explicit `sympy.nan` is present. \n> lambdify should translate Eq to the equivalent isnan when one of the operands is nan\r\n\r\nSympy's Eq does not behave like `isnan` so there is no equivalence.\nThe equivalent to `Piecewise((0.0, sp.Eq(x, sp.nan)), (1.0, True))` uses `isnan`. In SymPy, replacing `x` with `nan` produces `0.0`, so in NumPy it should do the same. Like I said, `sympy.nan` is the codegen version of `np.nan`, so if you want to add nan handling in your expression so that it would get translated to lambdify, that's how you would do it. \r\n\r\nI guess we could also add `isnan` to sympy.codegen, but I in general if some SymPy function has a certain behavior that they want to translate to NumPy we want people to just that. \n> The equivalent to `Piecewise((0.0, sp.Eq(x, sp.nan)), (1.0, True))` uses `isnan`\r\n\r\nCan you demonstrate the equivalence here? We have:\r\n```python\r\nIn [12]: Eq(nan, nan)\r\nOut[12]: False\r\n\r\nIn [13]: np.isnan(float('nan'))\r\nOut[13]: True\r\n```\r\nDo you propose that `Eq(nan, nan)` should evaluate to True?\n> The equivalent to `Piecewise((0.0, sp.Eq(x, sp.nan)), (1.0, True))` uses `isnan`. In SymPy, replacing `x` with `nan` produces `0.0`, so in NumPy it should do the same.\r\n\r\nThis isn't what happens:\r\n```python\r\nIn [15]: Piecewise((0.0, sp.Eq(x, sp.nan)), (1.0, True))\r\nOut[15]: 1.00000000000000\r\n\r\nIn [16]: Piecewise((0.0, sp.Eq(sp.nan, sp.nan)), (1.0, True))\r\nOut[16]: 1.00000000000000\r\n\r\nIn [17]: Piecewise((0.0, sp.Eq(x, sp.nan)), (1.0, True)).subs(x, sp.nan)\r\nOut[17]: 1.00000000000000\r\n```\r\nNo cases give 0.0.\nThanks guys. I looked at BooleanFunction and it seems not very trivial for me to implement a customized version of `isnan`\r\nAlso a FYI, my use case is with tensorflow as the backend compute module when using `lambdify` so having something that only works in numpy is not sufficient\nThere is a `isnan` function in `sympy.codegen.cfunctions`. But it looks like it should be subclassing booleanfunction becuase Piecewise currently does not seem to work with it:\r\n
\r\n
>>> f = lambdify(x, Piecewise((0.0, isnan(x)), (1.0, True)))
\r\n\r\n```\r\n---------------------------------------------------------------------------\r\nTypeError Traceback (most recent call last)\r\nCell In[3], line 1\r\n----> 1 f = lambdify(x, Piecewise((0.0, isnan(x)), (1.0, True)))\r\n\r\nFile ~/vc/sympy/sympy/functions/elementary/piecewise.py:137, in Piecewise.__new__(cls, *args, **options)\r\n 134 newargs = []\r\n 135 for ec in args:\r\n 136 # ec could be a ExprCondPair or a tuple\r\n--> 137 pair = ExprCondPair(*getattr(ec, 'args', ec))\r\n 138 cond = pair.cond\r\n 139 if cond is false:\r\n\r\nFile ~/vc/sympy/sympy/functions/elementary/piecewise.py:33, in ExprCondPair.__new__(cls, expr, cond)\r\n 30 cond = cond.rewrite(ITE)\r\n 32 if not isinstance(cond, Boolean):\r\n---> 33 raise TypeError(filldedent('''\r\n 34 Second argument must be a Boolean,\r\n 35 not `%s`''' % func_name(cond)))\r\n 36 return Tuple.__new__(cls, expr, cond)\r\n```\r\n\r\n
\r\n\r\n```\r\nTypeError:\r\nSecond argument must be a Boolean, not `isnan`\r\n```\r\n\n> Do you propose that Eq(nan, nan) should evaluate to True?\r\n\r\nOh I didn't realize Eq was implementing that behavior. That's why Eq(x, nan) evaluates to False apparently, which is what you suggested should be changed above. \r\n\r\nPersonally I would be OK with SymPy's nan not carrying over the weird IEEE nan equality stuff. Really SymPy's nan was a poor decision and it should have just been called \"undefined\" or something. \nWould it be possible to fix this soon? Thanks\nIt is not completely clear to me what the fix should be. I think probably the code in the OP should not be expected to work and should be written differently.", "created_at": "2024-07-11T06:33:01Z" }, { "repo": "sympy/sympy", "pull_number": 26790, "instance_id": "sympy__sympy-26790", "issue_numbers": [ "26789" ], "base_commit": "3a812204052176854bdfc1ea9d12097c3f9307ca", "patch": "diff --git a/.mailmap b/.mailmap\nindex 35219d735c5a..4d622cd0e986 100644\n--- a/.mailmap\n+++ b/.mailmap\n@@ -737,6 +737,7 @@ James Taylor \n James Titus \n James Titus \n James Whitehead jcwhitehead \n+Jan Jancar J08nY \n Jan Kruse \n Jan-Philipp Hoffmann Jan-Philipp Hoffmann <90828785+jan-philipp-hoffmann@users.noreply.github.com>\n Jared Lumpe Michael Jared Lumpe \ndiff --git a/sympy/polys/domains/finitefield.py b/sympy/polys/domains/finitefield.py\nindex 8e0a139050a8..92ecbaeb52dd 100644\n--- a/sympy/polys/domains/finitefield.py\n+++ b/sympy/polys/domains/finitefield.py\n@@ -1,5 +1,7 @@\n \"\"\"Implementation of :class:`FiniteField` class. \"\"\"\n \n+import operator\n+\n from sympy.external.gmpy import GROUND_TYPES\n from sympy.utilities.decorator import doctest_depends_on\n \n@@ -33,20 +35,42 @@ def _modular_int_factory(mod, dom, symmetric, self):\n \n # Use flint if available\n if flint is not None:\n+\n+ nmod = flint.nmod\n+ fmpz_mod_ctx = flint.fmpz_mod_ctx\n+ index = operator.index\n+\n try:\n mod = dom.convert(mod)\n except CoercionFailed:\n raise ValueError('modulus must be an integer, got %s' % mod)\n \n+ # mod might be e.g. Integer\n+ try:\n+ fmpz_mod_ctx(mod)\n+ except TypeError:\n+ mod = index(mod)\n+\n # flint's nmod is only for moduli up to 2^64-1 (on a 64-bit machine)\n try:\n- flint.nmod(0, mod)\n+ nmod(0, mod)\n except OverflowError:\n # Use fmpz_mod\n- ctx = flint.fmpz_mod_ctx(mod)\n+ fctx = fmpz_mod_ctx(mod)\n+\n+ def ctx(x):\n+ try:\n+ return fctx(x)\n+ except TypeError:\n+ # x might be Integer\n+ return fctx(index(x))\n else:\n # Use nmod\n- ctx = lambda x: flint.nmod(x, mod)\n+ def ctx(x):\n+ try:\n+ return nmod(x, mod)\n+ except TypeError:\n+ return nmod(index(x), mod)\n \n return ctx\n \n", "test_patch": "diff --git a/sympy/polys/domains/tests/test_domains.py b/sympy/polys/domains/tests/test_domains.py\nindex 3fe40cdb8fab..13fc7940fc28 100644\n--- a/sympy/polys/domains/tests/test_domains.py\n+++ b/sympy/polys/domains/tests/test_domains.py\n@@ -1047,10 +1047,16 @@ def test_ModularInteger():\n raises(TypeError, lambda: F5(n1) > F5(n2))\n raises(TypeError, lambda: F5(n1) >= F5(n2))\n \n+ # https://github.com/sympy/sympy/issues/26789\n+ assert GF(Integer(5)) == F5\n+ assert F5(Integer(3)) == F5(3)\n+\n+\n def test_QQ_int():\n assert int(QQ(2**2000, 3**1250)) == 455431\n assert int(QQ(2**100, 3)) == 422550200076076467165567735125\n \n+\n def test_RR_double():\n assert RR(3.14) > 1e-50\n assert RR(1e-13) > 1e-50\n@@ -1059,6 +1065,7 @@ def test_RR_double():\n assert RR(1e-20) > 1e-50\n assert RR(1e-40) > 1e-50\n \n+\n def test_RR_Float():\n f1 = Float(\"1.01\")\n f2 = Float(\"1.0000000000000000000001\")\n", "problem_statement": "GF __call__ broken with Integer after fmpz_mod change\nAfter https://github.com/sympy/sympy/pull/25663 the following code is broken:\r\n\r\n```python\r\nfrom sympy import FF, Integer\r\nK = FF(11)\r\ni = Integer(3)\r\nK(i)\r\n```\r\nGives:\r\n```python-traceback\r\nTraceback (most recent call last):\r\n File \"virt/lib/python3.11/site-packages/IPython/core/interactiveshell.py\", line 3508, in run_code\r\n exec(code_obj, self.user_global_ns, self.user_ns)\r\n File \"\", line 4, in \r\n K(i)\r\n File \"virt/lib/python3.11/site-packages/sympy/polys/domains/domain.py\", line 381, in __call__\r\n return self.new(*args)\r\n ^^^^^^^^^^^^^^^\r\n File \"virt/lib/python3.11/site-packages/sympy/polys/domains/domain.py\", line 372, in new\r\n return self.dtype(*args)\r\n ^^^^^^^^^^^^^^^^^\r\n File \"virt/lib/python3.11/site-packages/sympy/polys/domains/finitefield.py\", line 49, in \r\n ctx = lambda x: flint.nmod(x, mod)\r\n ^^^^^^^^^^^^^^^^^^\r\n File \"src/flint/types/nmod.pyx\", line 55, in flint.types.nmod.nmod.__init__\r\nTypeError: cannot create nmod from object of type \r\n```\r\n\r\nWhile previously it worked and gave a `SymmetricModularIntegerMod11(3)`.\n", "hints_text": "", "created_at": "2024-07-10T17:06:37Z" }, { "repo": "sympy/sympy", "pull_number": 26782, "instance_id": "sympy__sympy-26782", "issue_numbers": [ "26318" ], "base_commit": "72e62fb965bb371b78b0279521ae45f172728553", "patch": "diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml\nindex bdabdf4e131a..06a1f55539da 100644\n--- a/.github/workflows/release.yml\n+++ b/.github/workflows/release.yml\n@@ -10,17 +10,17 @@ name: release\n on:\n push:\n branches:\n- - '1.13'\n+ - '1.14'\n tags:\n- - 'sympy-1.13.*'\n+ - 'sympy-1.14.*'\n pull_request:\n branches:\n- - '1.13'\n+ - '1.14'\n env:\n- release_branch: '1.13'\n- release_version: '1.13.1'\n- final_release_version: '1.13.1'\n- previous_version: '1.13'\n+ release_branch: '1.14'\n+ release_version: '1.14.0-dev'\n+ final_release_version: '1.14.0'\n+ previous_version: '1.13.0'\n dev_version: '1.14-dev'\n \n jobs:\n@@ -39,7 +39,7 @@ jobs:\n - name: Setup Python\n uses: actions/setup-python@v5\n with:\n- python-version: '3.10'\n+ python-version: '3.12'\n \n - name: Build release files\n run: release/ci_release_script.sh ${{ env.release_version }} ${{ env.previous_version }}\n@@ -79,8 +79,11 @@ jobs:\n - name: Install wheel\n run: pip install sympy-${{ env.release_version }}-py3-none-any.whl\n \n+ - name: Install test-only dependencies\n+ run: pip install pytest pytest-xdist hypothesis\n+\n - name: Run tests after install\n- run: python -c 'import sympy; sympy.test()'\n+ run: python -c 'import sympy; sympy.test(parallel=True)'\n \n # -------------------- Upload to Test-PyPI ----------------------- #\n \n@@ -170,11 +173,22 @@ jobs:\n \n # -------------------- Update the docs repository ---------------- #\n \n+ # Dummy job to determine whether the release is final\n+ check-final:\n+ name: Check whether the release is final\n+ needs: [build, pypi-publish]\n+ runs-on: ubuntu-latest\n+ outputs:\n+ # The env context is not available in jobs..if so we set it here\n+ is-final: ${{ env.release_version == env.final_release_version }}\n+ steps:\n+ - run: echo \"null\"\n+\n update-docs:\n name: Update the docs repository\n- needs: [build, pypi-publish]\n+ needs: [check-final]\n # Only run for a final release\n- if: ${{ github.env.release_version == github.env.final_release_version }}\n+ if: ${{ needs.check-final.outputs.is-final == 'true' }}\n runs-on: ubuntu-latest\n permissions:\n contents: write\ndiff --git a/.mailmap b/.mailmap\nindex 86b8e0da17b8..8bf276673932 100644\n--- a/.mailmap\n+++ b/.mailmap\n@@ -691,6 +691,7 @@ Hong Xu \n Hou-Rui Hou-Rui <13244639785@163.com>\n Huangduirong Huangxiaodui \n Hubert Tsang \n+Hugo Kerstens Hugo Kerstens \n Hugo van Kemenade Hugo \n Huijun Mai \n Hwayeon Kang <97640870+eh111eh@users.noreply.github.com>\ndiff --git a/AUTHORS b/AUTHORS\nindex 3c87652807ef..507928408d75 100644\n--- a/AUTHORS\n+++ b/AUTHORS\n@@ -4,7 +4,7 @@ those who explicitly didn't want to be mentioned. People with a * next\n to their names are not found in the metadata of the git history. This\n file is generated automatically by running `./bin/authors_update.py`.\n \n-There are a total of 1306 authors.\n+There are a total of 1308 authors.\n \n Ond\u0159ej \u010cert\u00edk \n Fabian Pedregosa \n@@ -1312,3 +1312,5 @@ James A. Preiss \n Emile Fourcini \n Alberto Jim\u00e9nez Ruiz \n Jo\u00e3o Bravo \n+Dean Price \n+Hugo Kerstens \ndiff --git a/README.md b/README.md\nindex 99ec4ce5d0d7..b843e6712bbc 100644\n--- a/README.md\n+++ b/README.md\n@@ -88,12 +88,6 @@ if SymPy is installed.\n \n ## Installation\n \n-SymPy has a hard dependency on the [mpmath](http://mpmath.org/) library\n-(version \\>= 0.19). You should install it first, please refer to the\n-mpmath installation guide:\n-\n-\n-\n To install SymPy using PyPI, run the following command:\n \n $ pip install sympy\ndiff --git a/release/build_docs.py b/release/build_docs.py\nindex fa39bce571c4..7cb61b509ecf 100755\n--- a/release/build_docs.py\n+++ b/release/build_docs.py\n@@ -15,13 +15,14 @@\n \n def main(version, outputdir):\n os.makedirs(outputdir, exist_ok=True)\n+ run('bin/test_sphinx.sh')\n build_html(DOCSDIR, outputdir, version)\n build_latex(DOCSDIR, outputdir, version)\n \n \n def build_html(docsdir, outputdir, version):\n- run('make', 'clean', cwd=docsdir)\n- run('make', 'html', cwd=docsdir)\n+ #run('make', 'clean', cwd=docsdir)\n+ #run('make', 'html', cwd=docsdir)\n \n builddir = join(docsdir, '_build')\n docsname = 'sympy-docs-html-%s' % (version,)\n@@ -37,8 +38,8 @@ def build_html(docsdir, outputdir, version):\n \n \n def build_latex(docsdir, outputdir, version):\n- run('make', 'clean', cwd=docsdir)\n- run('make', 'latexpdf', cwd=docsdir)\n+ #run('make', 'clean', cwd=docsdir)\n+ #run('make', 'latexpdf', cwd=docsdir)\n \n srcfilename = 'sympy-%s.pdf' % (version,)\n dstfilename = 'sympy-docs-pdf-%s.pdf' % (version,)\ndiff --git a/sympy/external/gmpy.py b/sympy/external/gmpy.py\nindex 4c9e978ca186..b28da521a620 100644\n--- a/sympy/external/gmpy.py\n+++ b/sympy/external/gmpy.py\n@@ -92,13 +92,13 @@\n # Tested python-flint version. Future versions might work but we will only use\n # them if explicitly requested by SYMPY_GROUND_TYPES=flint.\n #\n-_PYTHON_FLINT_VERSION_NEEDED = \"0.6.*\"\n+_PYTHON_FLINT_VERSION_NEEDED = [\"0.6\", \"0.7\", \"0.8\", \"0.9\"]\n \n \n def _flint_version_okay(flint_version):\n- flint_ver = flint_version.split('.')[:2]\n- needed_ver = _PYTHON_FLINT_VERSION_NEEDED.split('.')[:2]\n- return flint_ver == needed_ver\n+ major, minor = flint_version.split('.')[:2]\n+ flint_ver = f'{major}.{minor}'\n+ return flint_ver in _PYTHON_FLINT_VERSION_NEEDED\n \n #\n # We will only use gmpy2 >= 2.0.0\n@@ -123,15 +123,11 @@ def _get_flint(sympy_ground_types):\n if _flint_version_okay(_flint_version):\n return flint\n elif sympy_ground_types == 'auto':\n- warn(f\"python-flint {_flint_version} is installed but only version \"\n- f\"{_PYTHON_FLINT_VERSION_NEEDED} will be used by default. \"\n- f\"Falling back to other ground types. Use \"\n- f\"SYMPY_GROUND_TYPES=flint to force the use of python-flint.\")\n return None\n else:\n warn(f\"Using python-flint {_flint_version} because SYMPY_GROUND_TYPES \"\n- f\"is set to flint but this version of SymPy has only been tested \"\n- f\"with python-flint {_PYTHON_FLINT_VERSION_NEEDED}.\")\n+ f\"is set to flint but this version of SymPy is only tested \"\n+ f\"with python-flint versions {_PYTHON_FLINT_VERSION_NEEDED}.\")\n return flint\n \n \ndiff --git a/sympy/polys/numberfields/galois_resolvents.py b/sympy/polys/numberfields/galois_resolvents.py\nindex f51781585a49..5d73b56870a4 100644\n--- a/sympy/polys/numberfields/galois_resolvents.py\n+++ b/sympy/polys/numberfields/galois_resolvents.py\n@@ -25,7 +25,6 @@\n from sympy.core.symbol import symbols, Dummy\n from sympy.polys.densetools import dup_eval\n from sympy.polys.domains import ZZ\n-from sympy.polys.numberfields.resolvent_lookup import resolvent_coeff_lambdas\n from sympy.polys.orderings import lex\n from sympy.polys.polyroots import preprocess_roots\n from sympy.polys.polytools import Poly\n@@ -659,6 +658,7 @@ def get_resolvent_by_lookup(T, number):\n dup\n \n \"\"\"\n+ from sympy.polys.numberfields.resolvent_lookup import resolvent_coeff_lambdas\n degree = T.degree()\n L = resolvent_coeff_lambdas[(degree, number)]\n T_coeffs = T.rep.to_list()[1:]\n", "test_patch": "diff --git a/.github/workflows/runtests.yml b/.github/workflows/runtests.yml\nindex 4f2991162d4d..1c563d807dfc 100644\n--- a/.github/workflows/runtests.yml\n+++ b/.github/workflows/runtests.yml\n@@ -16,9 +16,9 @@ jobs:\n \n runs-on: ubuntu-20.04\n steps:\n- - uses: actions/checkout@v3\n+ - uses: actions/checkout@v4\n \n- - uses: actions/setup-python@v4\n+ - uses: actions/setup-python@v5\n with:\n python-version: '3.12'\n - run: python -m pip install --upgrade pip\n@@ -64,8 +64,8 @@ jobs:\n \n runs-on: ubuntu-20.04\n steps:\n- - uses: actions/checkout@v3\n- - uses: actions/setup-python@v4\n+ - uses: actions/checkout@v4\n+ - uses: actions/setup-python@v5\n with:\n python-version: '3.12'\n - run: python -m pip install --upgrade pip\n@@ -81,8 +81,8 @@ jobs:\n \n runs-on: ubuntu-20.04\n steps:\n- - uses: actions/checkout@v3\n- - uses: actions/setup-python@v4\n+ - uses: actions/checkout@v4\n+ - uses: actions/setup-python@v5\n with:\n python-version: '3.12'\n - run: python -m pip install --upgrade pip\n@@ -97,11 +97,11 @@ jobs:\n needs: code-quality\n runs-on: ubuntu-20.04\n steps:\n- - uses: actions/checkout@v3\n+ - uses: actions/checkout@v4\n with:\n # Clone full git history (needed for detecting authors)\n fetch-depth: 0\n- - uses: actions/setup-python@v4\n+ - uses: actions/setup-python@v5\n with:\n python-version: '3.12'\n - run: python -m pip install --upgrade pip\n@@ -115,8 +115,8 @@ jobs:\n \n runs-on: ubuntu-20.04\n steps:\n- - uses: actions/checkout@v3\n- - uses: actions/setup-python@v4\n+ - uses: actions/checkout@v4\n+ - uses: actions/setup-python@v5\n with:\n python-version: '3.12'\n - run: python -m pip install --upgrade pip\n@@ -131,14 +131,11 @@ jobs:\n \n runs-on: ubuntu-20.04\n \n- strategy:\n- fail-fast: false\n-\n name: Tests\n \n steps:\n- - uses: actions/checkout@v3\n- - uses: actions/setup-python@v4\n+ - uses: actions/checkout@v4\n+ - uses: actions/setup-python@v5\n with:\n python-version: '3.12'\n - run: python -m pip install --upgrade pip\n@@ -160,8 +157,8 @@ jobs:\n name: ${{ matrix.python-version }} Optional Dendendencies\n \n steps:\n- - uses: actions/checkout@v3\n- - uses: actions/setup-python@v4\n+ - uses: actions/checkout@v4\n+ - uses: actions/setup-python@v5\n with:\n python-version: ${{ matrix.python-version }}\n \n@@ -200,8 +197,8 @@ jobs:\n name: NumPy/SciPy nightly\n \n steps:\n- - uses: actions/checkout@v3\n- - uses: actions/setup-python@v4\n+ - uses: actions/checkout@v4\n+ - uses: actions/setup-python@v5\n with:\n python-version: 3.12\n \n@@ -219,13 +216,13 @@ jobs:\n \n runs-on: ubuntu-20.04\n steps:\n- - uses: actions/checkout@v3\n- - uses: actions/setup-python@v4\n+ - uses: actions/checkout@v4\n+ - uses: actions/setup-python@v5\n with:\n python-version: '3.12'\n - run: python -m pip install --upgrade pip\n - run: pip install -r requirements-dev.txt\n- - run: pip install python-flint==0.6\n+ - run: pip install python-flint\n # Test the modules that most directly use python-flint\n - run: >-\n pytest\n@@ -260,14 +257,13 @@ jobs:\n \n runs-on: ubuntu-20.04\n steps:\n- - uses: actions/checkout@v3\n- - uses: actions/setup-python@v4\n+ - uses: actions/checkout@v4\n+ - uses: actions/setup-python@v5\n with:\n- # gmpy2 is not yet available for Python 3.12\n- python-version: '3.11'\n+ python-version: '3.12'\n - run: python -m pip install --upgrade pip\n - run: pip install -r requirements-dev.txt\n- - run: pip install python-flint==0.6 'gmpy2>=2.2.0rc1'\n+ - run: pip install python-flint gmpy2\n # Test the modules that most directly use python-flint\n - run: pytest sympy/polys sympy/ntheory sympy/matrices\n env:\n@@ -306,8 +302,8 @@ jobs:\n \n runs-on: ubuntu-20.04\n steps:\n- - uses: actions/checkout@v3\n- - uses: actions/setup-python@v4\n+ - uses: actions/checkout@v4\n+ - uses: actions/setup-python@v5\n with:\n # tensorflow not yet available for 3.12\n python-version: '3.11'\n@@ -324,8 +320,8 @@ jobs:\n \n runs-on: ubuntu-20.04\n steps:\n- - uses: actions/checkout@v3\n- - uses: actions/setup-python@v4\n+ - uses: actions/checkout@v4\n+ - uses: actions/setup-python@v5\n with:\n # symengine not yet available for 3.12\n python-version: '3.11'\n@@ -344,12 +340,9 @@ jobs:\n \n runs-on: ubuntu-20.04\n \n- strategy:\n- fail-fast: false\n-\n steps:\n- - uses: actions/checkout@v3\n- - uses: actions/setup-python@v4\n+ - uses: actions/checkout@v4\n+ - uses: actions/setup-python@v5\n with:\n python-version: '3.12'\n - run: python -m pip install --upgrade pip\n@@ -371,8 +364,8 @@ jobs:\n name: ${{ matrix.python-version }} Tests\n \n steps:\n- - uses: actions/checkout@v3\n- - uses: actions/setup-python@v4\n+ - uses: actions/checkout@v4\n+ - uses: actions/setup-python@v5\n with:\n python-version: ${{ matrix.python-version }}\n - run: python -m pip install --upgrade pip\n@@ -394,8 +387,8 @@ jobs:\n name: ${{ matrix.python-version }} Doctests\n \n steps:\n- - uses: actions/checkout@v3\n- - uses: actions/setup-python@v4\n+ - uses: actions/checkout@v4\n+ - uses: actions/setup-python@v5\n with:\n python-version: ${{ matrix.python-version }}\n - run: python -m pip install --upgrade pip\n@@ -413,8 +406,8 @@ jobs:\n name: mpmath-master Tests\n \n steps:\n- - uses: actions/checkout@v3\n- - uses: actions/setup-python@v4\n+ - uses: actions/checkout@v4\n+ - uses: actions/setup-python@v5\n with:\n python-version: '3.12'\n - run: python -m pip install --upgrade pip\n@@ -429,8 +422,8 @@ jobs:\n \n runs-on: ubuntu-20.04\n steps:\n- - uses: actions/checkout@v3\n- - uses: actions/setup-python@v4\n+ - uses: actions/checkout@v4\n+ - uses: actions/setup-python@v5\n with:\n python-version: '3.12'\n - run: doc/aptinstall.sh\n@@ -456,8 +449,8 @@ jobs:\n \n runs-on: ubuntu-20.04\n steps:\n- - uses: actions/checkout@v3\n- - uses: actions/setup-python@v4\n+ - uses: actions/checkout@v4\n+ - uses: actions/setup-python@v5\n with:\n python-version: 'pypy2.7'\n - run: bin/test_py2_import.py\n@@ -469,8 +462,8 @@ jobs:\n \n runs-on: ubuntu-20.04\n steps:\n- - uses: actions/checkout@v3\n- - uses: actions/setup-python@v4\n+ - uses: actions/checkout@v4\n+ - uses: actions/setup-python@v5\n with:\n python-version: '3.12'\n - run: python -m pip install --upgrade pip build\n@@ -484,11 +477,11 @@ jobs:\n \n runs-on: ubuntu-20.04\n steps:\n- - uses: actions/checkout@v3\n+ - uses: actions/checkout@v4\n with:\n # Checkout repo with full history\n fetch-depth: 0\n- - uses: actions/setup-python@v4\n+ - uses: actions/setup-python@v5\n with:\n python-version: '3.12'\n - run: pip install asv virtualenv packaging\ndiff --git a/bin/doctest b/bin/doctest\nindex 4a3b1bf95f30..a7c85de03757 100755\n--- a/bin/doctest\n+++ b/bin/doctest\n@@ -15,6 +15,7 @@ blacklist = [\n 'doc/src/tutorials/physics/biomechanics/biomechanical-model-example.rst',\n 'doc/src/explanation/modules/physics/biomechanics/biomechanics.rst',\n 'doc/src/explanation/modules/physics/mechanics/autolev_parser.rst',\n+ 'doc/src/tutorials/physics/biomechanics/biomechanics.rst',\n ]\n \n import sys\ndiff --git a/sympy/testing/runtests_pytest.py b/sympy/testing/runtests_pytest.py\nindex 0ca69aa52662..d63a8b1a283a 100644\n--- a/sympy/testing/runtests_pytest.py\n+++ b/sympy/testing/runtests_pytest.py\n@@ -421,15 +421,11 @@ def test(*paths, subprocess=True, rerun=0, **kwargs):\n raise ModuleNotFoundError(msg)\n args.extend(['--timeout', str(int(timeout))])\n \n- # The use of `bool | None` for the `slow` kwarg allows a configuration file\n- # to take precedence if found by pytest, but if one isn't present (e.g. in\n- # the case when used with Pyodide) then a user can still explicitly ensure\n- # that only the slow tests are run.\n- if slow := kwargs.get('slow', None) is not None:\n- if slow:\n- args.extend(['-m', 'slow'])\n- else:\n- args.extend(['-m', 'not slow'])\n+ # Skip slow tests by default and always skip tooslow tests\n+ if kwargs.get('slow', False):\n+ args.extend(['-m', 'slow and not tooslow'])\n+ else:\n+ args.extend(['-m', 'not slow and not tooslow'])\n \n if (split := kwargs.get('split')) is not None:\n if not pytest_plugin_manager.has_split:\n", "problem_statement": "Doctest failures due to line wrapping in the printers\nRealted to gh-26221.\r\n\r\nAfter gh-25673 the printers are changed to add extra characters when wrapping lines. This causes the doctests to fail when run outside of CI e.g.:\r\n```console\r\n$ bin/doctest sympy/categories/baseclasses.py\r\n================================================================================================ test process starts =================================================================================================\r\nexecutable: /Users/enojb/.pyenv/versions/sympy-3.12.git/bin/python (3.12.2-final-0) [CPython]\r\narchitecture: 64-bit\r\ncache: yes\r\nground types: python\r\nnumpy: 1.26.4\r\nhash randomization: on (PYTHONHASHSEED=3926478540)\r\n\r\nsympy/categories/baseclasses.py[23] .......F............... [FAIL]\r\n\r\n______________________________________________________________________________________________________________________________________________________________________________________________________________________\r\n________________________________________________________________________________________ sympy.categories.baseclasses.Diagram ________________________________________________________________________________________\r\nFile \"/Users/enojb/work/dev/sympy/sympy/categories/baseclasses.py\", line 611, in sympy.categories.baseclasses.Diagram\r\nFailed example:\r\n pprint(d.premises, use_unicode=False)\r\nExpected:\r\n {g*f:A-->C: EmptySet, id:A-->A: EmptySet, id:B-->B: EmptySet, id:C-->C: EmptyS >\r\n \r\n > et, f:A-->B: EmptySet, g:B-->C: EmptySet}\r\nGot:\r\n {g*f:A-->C: EmptySet, id:A-->A: EmptySet, id:B-->B: EmptySet, id:C-->C: EmptySet, f:A-->B: EmptySet, g:B-->C: EmptySet}\r\n\r\n================================================================================ tests finished: 22 passed, 1 failed, in 0.12 seconds ================================================================================\r\nDO *NOT* COMMIT!\r\n```\r\nThe problem seems to be that the doctests effectively hardcode a particular terminal width that works in CI but not anywhere else.\r\n\r\nI think that this printing change should be reverted but either way the doctests should be made to work.\r\n\r\nCC @smichr\n", "hints_text": "", "created_at": "2024-07-08T17:02:04Z" }, { "repo": "sympy/sympy", "pull_number": 26780, "instance_id": "sympy__sympy-26780", "issue_numbers": [ "18628" ], "base_commit": "16362cc09da946b518f549f249e81672a221030d", "patch": "diff --git a/.mailmap b/.mailmap\nindex 55b874e6f897..86b8e0da17b8 100644\n--- a/.mailmap\n+++ b/.mailmap\n@@ -1550,6 +1550,8 @@ Zeel Shah \n Zhenxu Zhu xzdlj \n Zhi-Qiang Zhou zhouzq-thu \n Zhongshi \n+Zhuoyuan Li zylipku <123136644+zylipku@users.noreply.github.com>\n+Zhuoyuan Li zylipku \n Zlatan Vasovi\u0107 \n Zoufin\u00e9 Lauer-Bar\u00e9 Zoufin\u00e9 Lauer-Bar\u00e9 <82505312+zolabar@users.noreply.github.com>\n Zoufin\u00e9 Lauer-Bar\u00e9 zolabar \ndiff --git a/sympy/solvers/diophantine/diophantine.py b/sympy/solvers/diophantine/diophantine.py\nindex 3df4fe9b0df1..d02d0689d814 100644\n--- a/sympy/solvers/diophantine/diophantine.py\n+++ b/sympy/solvers/diophantine/diophantine.py\n@@ -2174,6 +2174,25 @@ def cornacchia(a:int, b:int, m:int) -> set[tuple[int, int]]:\n \"\"\"\n # Assume gcd(a, b) = gcd(a, m) = 1 and a, b > 0 but no error checking\n sols = set()\n+\n+ if a + b > m:\n+ # xy = 0 must hold if there exists a solution\n+ if a == 1:\n+ # y = 0\n+ s, _exact = iroot(m // a, 2)\n+ if _exact:\n+ sols.add((int(s), 0))\n+ if a == b:\n+ # only keep one solution\n+ return sols\n+ if m % b == 0:\n+ # x = 0\n+ s, _exact = iroot(m // b, 2)\n+ if _exact:\n+ sols.add((0, int(s)))\n+ return sols\n+\n+ # the original cornacchia\n for t in sqrt_mod_iter(-b*invert(a, m), m):\n if t < m // 2:\n continue\n", "test_patch": "diff --git a/sympy/solvers/diophantine/tests/test_diophantine.py b/sympy/solvers/diophantine/tests/test_diophantine.py\nindex 094770b7bba7..a500903fc06f 100644\n--- a/sympy/solvers/diophantine/tests/test_diophantine.py\n+++ b/sympy/solvers/diophantine/tests/test_diophantine.py\n@@ -1049,3 +1049,11 @@ def test_quadratic_parameter_passing():\n # test that parameters are passed all the way to the final solution\n assert solution == {(t, 11*t), (t, -22*t)}\n assert solution(0, 0) == {(0, 0)}\n+\n+def test_issue_18628():\n+ eq1 = x**2 - 15*x + y**2 - 8*y\n+ sol = diophantine(eq1)\n+ assert sol == {(15, 0), (15, 8), (-1, 4), (0, 0), (0, 8), (16, 4)}\n+ eq2 = 2*x**2 - 9*x + 4*y**2 - 8*y + 14\n+ sol = diophantine(eq2)\n+ assert sol == {(2, 1)}\n", "problem_statement": "diophantine(), diop_quadratic() missing solutions\nWith sympy version 1.5.1:\r\n\r\n```\r\nfrom sympy import *\r\nfrom sympy.solvers.diophantine import diop_quadratic\r\n\r\na, b, x, y = symbols('a b x y', integer=True, positive=True)\r\nf = -a*x + x**2 - b*y + y**2\r\ng = f.subs([(a, 15), (b, 8)])\r\nprint('diophantine (', g, ') solutions:', diophantine(g))\r\nprint('diop_quadratic(', g, ') solutions:', diop_quadratic(g))\r\nh = f.subs([(a, 8), (b, 15)])\r\nprint('diophantine (', h, ') solutions:', diophantine(h))\r\nprint('diop_quadratic(', h, ') solutions:', diop_quadratic(h))\r\nprint(g.subs([(x, 16), (y, 4)]))\r\n\r\n```\r\nOutput:\r\n\r\n```\r\ndiophantine ( x**2 - 15*x + y**2 - 8*y ) solutions: {(15, 8)}\r\ndiop_quadratic( x**2 - 15*x + y**2 - 8*y ) solutions: {(15, 0), (0, 8), (15, 8), (0, 0)}\r\ndiophantine ( x**2 - 8*x + y**2 - 15*y ) solutions: {(8, 15), (4, 16)}\r\ndiop_quadratic( x**2 - 8*x + y**2 - 15*y ) solutions: {(0, 0), (0, 15), (8, 0), (4, 16), (4, -1), (8, 15)}\r\n0\r\n\r\n```\r\nFirst, I expect diophantine() to return the same solutions as diop_quadratic(), but it doesn't.\r\n\r\nSecond, the function f() is symmetric in x and y, so the solutions to g() and h() should be symmetric, but they aren't. For example, (16, 4) is a solution to g(), but is missing from the solution set.\n", "hints_text": "`diophantine` is doing additional filtering on the results to match the assumptions on `x` and `y` as positive integers. `diop_quadratic` has no such checks.\r\n\r\nThe boundary between public and private methods in the diophantine solver is extremely ill-defined.\r\n\r\nI agree that there are solutions missing from your example.\nI see, thanks. Zero isn't considered positive, so I should have:\r\n\r\n`a, b, x, y = symbols('a b x y', integer=True, negative=False)\r\n`\r\n\r\nThen the output is:\r\n\r\n```\r\ndiophantine ( x**2 - 15*x + y**2 - 8*y ) solutions: {(15, 0), (0, 8), (15, 8), (0, 0)}\r\ndiop_quadratic( x**2 - 15*x + y**2 - 8*y ) solutions: {(15, 0), (0, 8), (15, 8), (0, 0)}\r\ndiophantine ( x**2 - 8*x + y**2 - 15*y ) solutions: {(0, 0), (0, 15), (8, 0), (4, 16), (8, 15)}\r\ndiop_quadratic( x**2 - 8*x + y**2 - 15*y ) solutions: {(0, 0), (0, 15), (8, 0), (4, 16), (4, -1), (8, 15)}\r\n0\r\n\r\n```\r\nSo the issue is with diop_quadratic(g), which is missing the solution (16, 4).\nI've investigated this further. The problem appears to actually be in `transformation_to_DN` and `find_DN`:\r\n\r\n```\r\nfrom sympy import *\r\nfrom sympy.solvers.diophantine import transformation_to_DN, find_DN, diop_DN\r\n\r\nd, e, x, y = symbols('d e x y', integer=True)\r\n\r\nf = x**2 + y**2 + d*x + e*y \r\ng = f.subs([(d, -15), (e, -8)])\r\nh = f.subs([(d, -8), (e, -15)])\r\n\r\nprint(f'transformation_to_DN({g}) =')\r\nA, B = transformation_to_DN(g)\r\nprint('A:', A)\r\nprint('B:', B)\r\nD, N = find_DN(g)\r\nprint(f'find_DN({g}) = {D}, {N}')\r\nprint(f'diop_DN({D}, {N}) = {diop_DN(D, N)}')\r\nprint('')\r\n\r\nprint(f'transformation_to_DN({h}) =')\r\nA, B = transformation_to_DN(h)\r\nprint('A:', A)\r\nprint('B:', B)\r\nD, N = find_DN(h)\r\nprint(f'find_DN({h}) = {D}, {N}')\r\nprint(f'diop_DN({D}, {N}) = {diop_DN(D, N)}')\r\n```\r\n\r\nOutput:\r\n\r\n```\r\ntransformation_to_DN(x**2 - 15*x + y**2 - 8*y) =\r\nA: Matrix([[-1/2, 0], [0, -1]])\r\nB: Matrix([[15/2], [4]])\r\nfind_DN(x**2 - 15*x + y**2 - 8*y) = -4, 289\r\ndiop_DN(-4, 289) = [(15, 4)]\r\n\r\ntransformation_to_DN(x**2 - 8*x + y**2 - 15*y) =\r\nA: Matrix([[-1/4, 0], [0, -1/2]])\r\nB: Matrix([[4], [15/2]])\r\nfind_DN(x**2 - 8*x + y**2 - 15*y) = -4, 1156\r\ndiop_DN(-4, 1156) = [(16, 15), (30, 8), (0, 17)]\r\n\r\n```\r\nMy math isn't strong, but shouldn't `transformation_to_DN(x**2 - 15*x + y**2 - 8*y)` return:\r\n\r\n`A: Matrix([[-1/2, 0], [0, -1/4]])\r\n`\r\n\r\nand `find_DN(x**2 - 15*x + y**2 - 8*y)` also return `-4, 1156`?\r\n\r\nI don't understand the method used by these functions, and the link to the paper _Solving the equation ax^2 + bxy + cy^2 + dx + ey + f = 0, John P.Robertson, May 8, 2003_ ([http://www.jpr2718.org/ax2p.pdf](url)) is broken.\r\n\r\n[Here's a link that works: [https://vdocuments.mx/solving-the-equation-ax2-bxy-cy2-dx-ey-f-0.html](url)]\nThe transformation is not unique. It seems that the one defined by `Matrix([[-1/2, 0], [0, -1]])` would also be valid. However, `diop_DN(-4, 289)` does not return the non-primitive solution `(17, 0)` of `x**2 + 4*y**2 = 289`because `cornacchia` does not find the trivial solution `(1, 0)` of the equation `x**2 + 4*y**2 = 1`:\r\n```\r\n>>> from sympy.solvers.diophantine.diophantine import cornacchia\r\n>>> cornacchia(1, 4, 1)\r\nset()\r\n```\r\nThat looks like a bug in `cornacchia`.\nFrom [http://www.numbertheory.org/php/cornacchia.html](url), it appears that `cornacchia` isn't applicable because a + b > m.\nFrom the documentation for `cornacchia`:\r\n\r\nSolving the diophantine equation ax**2 + by**2 = m by Cornacchia\u2019s method, [online], Available: [http://www.numbertheory.org/php/cornacchia.html](http://www.numbertheory.org/php/cornacchia.html)\r\n\r\nThe page at that link states: _Here a > 0, b > 0,m \u2265 a+b, gcd(a,m)=1=gcd(a,b)_, so Cornacchia\u2019s method fails because `1 + 4 > 1`, making me think that `cornacchia `isn't applicable in this case.\n> From the documentation for `cornacchia`:\r\n> \r\n> Solving the diophantine equation ax**2 + by**2 = m by Cornacchia\u2019s method, [online], Available: http://www.numbertheory.org/php/cornacchia.html\r\n> \r\n> The page at that link states: _Here a > 0, b > 0,m \u2265 a+b, gcd(a,m)=1=gcd(a,b)_, so Cornacchia\u2019s method fails because `1 + 4 > 1`, making me think that `cornacchia `isn't applicable in this case.\r\n\r\nI've redacted my statements above since I now realize that I did not fully understand the constraints on the coefficients in the equation a*x^2 + b*y^2 = m when solving it using Cornacchia\u2019s method. Thank you for clarifying, I now understand the problem much better. \r\n\r\n> The transformation is not unique. It seems that the one defined by `Matrix([[-1/2, 0], [0, -1]])` would also be valid. However, `diop_DN(-4, 289)` does not return the non-primitive solution `(17, 0)` of `x**2 + 4*y**2 = 289`because `cornacchia` does not find the trivial solution `(1, 0)` of the equation `x**2 + 4*y**2 = 1`:\r\n> \r\n> ```\r\n> >>> from sympy.solvers.diophantine.diophantine import cornacchia\r\n> >>> cornacchia(1, 4, 1)\r\n> set()\r\n> ```\r\n> \r\n> That looks like a bug in `cornacchia`.\r\n\r\nThere is not a bug in the cornacchia method (at least not for this reason). @kgorlen is correct, we can not use Cornacchia\u2019s method in this instance since a + b > m.", "created_at": "2024-07-08T03:07:31Z" }, { "repo": "sympy/sympy", "pull_number": 26779, "instance_id": "sympy__sympy-26779", "issue_numbers": [ "26775" ], "base_commit": "84b114cbed65788e62f46cdd468456f2f876b74f", "patch": "diff --git a/sympy/integrals/laplace.py b/sympy/integrals/laplace.py\nindex 3c3ddfce3367..bba1d2c552b4 100644\n--- a/sympy/integrals/laplace.py\n+++ b/sympy/integrals/laplace.py\n@@ -2100,8 +2100,10 @@ def _inverse_laplace_rational(fn, s, t, plane, *, simplify):\n dc = [x/dc_lead for x in dc]\n nc = [x/dc_lead for x in n.as_poly(s).all_coeffs()]\n if len(dc) == 1:\n- r = nc[0]*DiracDelta(t)\n- terms_t.append(r)\n+ N = len(nc)-1\n+ for c in enumerate(nc):\n+ r = c[1]*DiracDelta(t, N-c[0])\n+ terms_t.append(r)\n elif len(dc) == 2:\n r = nc[0]*exp(-dc[1]*t)\n terms_t.append(Heaviside(t)*r)\n", "test_patch": "diff --git a/sympy/integrals/tests/test_laplace.py b/sympy/integrals/tests/test_laplace.py\nindex 5c42490b91f7..15118ccd88d3 100644\n--- a/sympy/integrals/tests/test_laplace.py\n+++ b/sympy/integrals/tests/test_laplace.py\n@@ -717,6 +717,9 @@ def simp_hyp(expr):\n Derivative(InverseLaplaceTransform(f(s), s, t, None), (t, 42)))\n assert ILT(cos(s), s, t) == InverseLaplaceTransform(cos(s), s, t, None)\n # Rules for testing different DiracDelta cases\n+ assert (\n+ ILT(1 + 2*s + 3*s**2 + 5*s**3, s, t) == DiracDelta(t) +\n+ 2*DiracDelta(t, 1) + 3*DiracDelta(t, 2) + 5*DiracDelta(t, 3))\n assert (ILT(2*exp(3*s) - 5*exp(-7*s), s, t) ==\n 2*InverseLaplaceTransform(exp(3*s), s, t, None) -\n 5*DiracDelta(t - 7))\n", "problem_statement": "`inverse_laplace_transform(F,s,t)` return wrong result when `F` is `s**n` where `n` is a positive integer\nsympy version: 1.12.1, the issue is found when using google colab.\r\nTo reproduce the issue:\r\n```\r\nimport sympy as sy\r\ns,t = sy.symbols('s,t')\r\n[sy.inverse_laplace_transform(F,s,t) for F in (1,s,s**2,s**3)]\r\n```\r\nit returns wrong result:\r\n```\r\n[\u03b4(t), \u03b4(t), \u03b4(t), \u03b4(t)]\r\n```\r\nexpected result:\r\n```\r\n[\u03b4(t), \u03b4'(t), \u03b4''(t), \u03b4'''(t)]\r\n```\r\nfor $s^n$ where $n$ is a positive integer, `inverse_laplace_transform` should return the n-th derivative of the Dirac Delta function `DiracDelta(t,n)` $\\delta^{(n)}(t)$, which Wolfram Alpha does.\n", "hints_text": "CC @hanspi42 \nThanks, this is indeed incorrect. I did not find it because the inverse Laplace transform of s**n for n>1 has no use in my field.", "created_at": "2024-07-07T13:46:45Z" }, { "repo": "sympy/sympy", "pull_number": 26729, "instance_id": "sympy__sympy-26729", "issue_numbers": [ "26728" ], "base_commit": "6d6e89caad6da38313a9174fc186b67904872a47", "patch": "diff --git a/sympy/physics/control/control_plots.py b/sympy/physics/control/control_plots.py\nindex 3742de329e61..e233d5d0a4e2 100644\n--- a/sympy/physics/control/control_plots.py\n+++ b/sympy/physics/control/control_plots.py\n@@ -2,12 +2,14 @@\n from sympy.functions.elementary.exponential import (exp, log)\n from sympy.polys.partfrac import apart\n from sympy.core.symbol import Dummy\n+from sympy.core.sympify import _sympify\n from sympy.external import import_module\n from sympy.functions import arg, Abs\n from sympy.integrals.laplace import _fast_inverse_laplace\n from sympy.physics.control.lti import SISOLinearTimeInvariant\n from sympy.plotting.series import LineOver1DRangeSeries\n from sympy.polys.polytools import Poly\n+from sympy.polys.polyutils import _nsort\n from sympy.printing.latex import latex\n \n __all__ = ['pole_zero_numerical_data', 'pole_zero_plot',\n@@ -88,8 +90,8 @@ def pole_zero_numerical_data(system):\n >>> from sympy.physics.control.lti import TransferFunction\n >>> from sympy.physics.control.control_plots import pole_zero_numerical_data\n >>> tf1 = TransferFunction(s**2 + 1, s**4 + 4*s**3 + 6*s**2 + 5*s + 2, s)\n- >>> pole_zero_numerical_data(tf1) # doctest: +SKIP\n- ([-0.+1.j 0.-1.j], [-2. +0.j -0.5+0.8660254j -0.5-0.8660254j -1. +0.j ])\n+ >>> pole_zero_numerical_data(tf1) # doctest: +SKIP\n+ ([-1j, 1j], [-2.0, (-1.5-0.8660254j), (-0.5+0.8660254j)])\n \n See Also\n ========\n@@ -109,7 +111,12 @@ def pole_zero_numerical_data(system):\n zeros = np.roots(num_poly)\n poles = np.roots(den_poly)\n \n- return zeros, poles\n+ # make ordering canonical\n+ def _sort(l):\n+ return [float(i) if i.is_real else complex(i) for i in\n+ _nsort([_sympify(i) for i in l])]\n+\n+ return _sort(zeros), _sort(poles)\n \n \n def pole_zero_plot(system, pole_color='blue', pole_markersize=10,\ndiff --git a/sympy/polys/polyutils.py b/sympy/polys/polyutils.py\nindex b5c49fcc9d81..6a2019d3b195 100644\n--- a/sympy/polys/polyutils.py\n+++ b/sympy/polys/polyutils.py\n@@ -39,6 +39,8 @@ def _nsort(roots, separated=False):\n \"\"\"\n if not all(r.is_number for r in roots):\n raise NotImplementedError\n+ if not len(roots):\n+ return [] if not separated else ([], [])\n # see issue 6137:\n # get the real part of the evaluated real and imaginary parts of each root\n key = [[i.n(2).as_real_imag()[0] for i in r.as_real_imag()] for r in roots]\n", "test_patch": "diff --git a/sympy/physics/control/tests/test_control_plots.py b/sympy/physics/control/tests/test_control_plots.py\nindex 673fcee6cfdb..dbb9126762ed 100644\n--- a/sympy/physics/control/tests/test_control_plots.py\n+++ b/sympy/physics/control/tests/test_control_plots.py\n@@ -1,8 +1,10 @@\n from math import isclose\n-from sympy.core.numbers import I\n+from sympy.core.numbers import I, all_close\n+from sympy.core.sympify import _sympify\n from sympy.core.symbol import Dummy\n from sympy.functions.elementary.complexes import (Abs, arg)\n from sympy.functions.elementary.exponential import log\n+from sympy.polys.polyutils import _nsort\n from sympy.abc import s, p, a\n from sympy.external import import_module\n from sympy.physics.control.control_plots import \\\n@@ -101,20 +103,28 @@ def test_pole_zero():\n skip(\"NumPy is required for this test\")\n \n def pz_tester(sys, expected_value):\n- z, p = pole_zero_numerical_data(sys)\n- z_check = numpy.allclose(z, expected_value[0])\n- p_check = numpy.allclose(p, expected_value[1])\n+ zp = pole_zero_numerical_data(sys)\n+ zp = [[_sympify(i).n(chop=1e-10) for i in j] for j in zp]\n+ z, p = [_nsort(i) for i in zp]\n+ def check(a, b):\n+ if isinstance(a, (list, tuple)):\n+ return all(check(i, j) for i,j in zip(a, b))\n+ if not b:\n+ return all_close(a, b, atol=1e-10, rtol=0)\n+ return all_close(a, b, atol=0, rtol=1e-6)\n+ z_check = check(z, expected_value[0])\n+ p_check = check(p, expected_value[1])\n return p_check and z_check\n \n- exp1 = [[], [-0.24999999999999994+1.3919410907075054j, -0.24999999999999994-1.3919410907075054j]]\n- exp2 = [[0.0], [-0.25+0.3227486121839514j, -0.25-0.3227486121839514j]]\n- exp3 = [[0.0], [-0.5000000000000004+0.8660254037844395j,\n- -0.5000000000000004-0.8660254037844395j, 0.9999999999999998+0j]]\n- exp4 = [[], [5.0, 0.0, 0.0, 0.0]]\n+ exp1 = [[], [-0.24999999999999994-1.3919410907075054j, -0.24999999999999994+1.3919410907075054j]]\n+ exp2 = [[0.0], [-0.25-0.3227486121839514j, -0.25+0.3227486121839514j]]\n+ exp3 = [[0.0], [0.9999999999999998+0j, -0.5000000000000004-0.8660254037844395j,\n+ -0.5000000000000004+0.8660254037844395j]]\n+ exp4 = [[], [0.0, 0.0, 0.0, 5.0]]\n exp5 = [[-5.645751311064592, -0.5000000000000008, -0.3542486889354093],\n- [-0.24999999999999986+1.3919410907075052j,\n- -0.24999999999999986-1.3919410907075052j, -0.2499999999999998+0.32274861218395134j,\n- -0.2499999999999998-0.32274861218395134j]]\n+ [-0.24999999999999986-1.3919410907075052j,\n+ -0.24999999999999986-0.32274861218395134j, -0.2499999999999998+0.32274861218395134j,\n+ -0.2499999999999998+1.3919410907075052j]]\n exp6 = [[], [-1.1641600331447917-3.545808351896439j,\n -0.8358399668552097+2.5458083518964383j]]\n \n", "problem_statement": "Control test fails with numpy 2.0\nCC @akshanshbhatt @faze-geek \r\n\r\nThis is the second time I have asked to fix these tests (gh-23128).\r\n\r\nThis test fails with numpy 2.0:\r\n```console\r\n$ pytest sympy/physics/control/tests/test_control_plots.py::test_pole_zero\r\n================================================================================================ test session starts =================================================================================================\r\nplatform darwin -- Python 3.12.2, pytest-8.1.1, pluggy-1.4.0\r\narchitecture: 64-bit\r\ncache: yes\r\nground types: flint (python-flint==0.6.0)\r\n\r\nrootdir: /Users/enojb/work/dev/sympy\r\nconfigfile: pyproject.toml\r\nplugins: instafail-0.5.0, doctestplus-1.2.2.dev5+g5176f32, hypothesis-6.99.9, xdist-3.5.0, timeout-2.3.1, split-0.8.2\r\ncollected 1 item\r\n\r\nsympy/physics/control/tests/test_control_plots.py F [100%]\r\n\r\n====================================================================================================== FAILURES ======================================================================================================\r\n___________________________________________________________________________________________________ test_pole_zero ___________________________________________________________________________________________________\r\n\r\n def test_pole_zero():\r\n if not numpy:\r\n skip(\"NumPy is required for this test\")\r\n\r\n def pz_tester(sys, expected_value):\r\n z, p = pole_zero_numerical_data(sys)\r\n z_check = numpy.allclose(z, expected_value[0])\r\n p_check = numpy.allclose(p, expected_value[1])\r\n return p_check and z_check\r\n\r\n exp1 = [[], [-0.24999999999999994+1.3919410907075054j, -0.24999999999999994-1.3919410907075054j]]\r\n exp2 = [[0.0], [-0.25+0.3227486121839514j, -0.25-0.3227486121839514j]]\r\n exp3 = [[0.0], [-0.5000000000000004+0.8660254037844395j,\r\n -0.5000000000000004-0.8660254037844395j, 0.9999999999999998+0j]]\r\n exp4 = [[], [5.0, 0.0, 0.0, 0.0]]\r\n exp5 = [[-5.645751311064592, -0.5000000000000008, -0.3542486889354093],\r\n [-0.24999999999999986+1.3919410907075052j,\r\n -0.24999999999999986-1.3919410907075052j, -0.2499999999999998+0.32274861218395134j,\r\n -0.2499999999999998-0.32274861218395134j]]\r\n exp6 = [[], [-1.1641600331447917-3.545808351896439j,\r\n -0.8358399668552097+2.5458083518964383j]]\r\n\r\n> assert pz_tester(tf1, exp1)\r\nE assert False\r\nE + where False = .pz_tester at 0x1148e6c00>(TransferFunction(1, p**2 + 0.5*p + 2, p), [[], [(-0.24999999999999994+1.3919410907075054j), (-0.24999999999999994-1.3919410907075054j)]])\r\n\r\nsympy/physics/control/tests/test_control_plots.py:121: AssertionError\r\n DO *NOT* COMMIT!\r\n============================================================================================== short test summary info ===============================================================================================\r\nFAILED sympy/physics/control/tests/test_control_plots.py::test_pole_zero - assert False\r\n================================================================================================= 1 failed in 0.29s ==================================================================================================\r\n```\n", "hints_text": "", "created_at": "2024-06-22T00:55:37Z" }, { "repo": "sympy/sympy", "pull_number": 26693, "instance_id": "sympy__sympy-26693", "issue_numbers": [ "25175" ], "base_commit": "73ae5e99aff9bf96675dd90670d637877a2d6041", "patch": "diff --git a/sympy/functions/elementary/exponential.py b/sympy/functions/elementary/exponential.py\nindex 3f4f462150e6..9f7c9ccce8de 100644\n--- a/sympy/functions/elementary/exponential.py\n+++ b/sympy/functions/elementary/exponential.py\n@@ -1002,7 +1002,7 @@ def coeff_exp(term, x):\n except ValueError:\n a, b = s.removeO().as_leading_term(t, cdir=1), S.Zero\n \n- p = (z/(a*t**b) - 1)._eval_nseries(t, n=n, logx=logx, cdir=1)\n+ p = (z/(a*t**b) - 1).cancel()._eval_nseries(t, n=n, logx=logx, cdir=1)\n if p.has(exp):\n p = logcombine(p)\n if isinstance(p, Order):\n@@ -1047,14 +1047,13 @@ def mul(d1, d2):\n while k*d < n:\n coeff = -S.NegativeOne**k/k\n for ex in pk:\n- _ = terms.get(ex, S.Zero) + coeff*pk[ex]\n- terms[ex] = _.nsimplify()\n+ terms[ex] = terms.get(ex, S.Zero) + coeff*pk[ex]\n pk = mul(pk, pterms)\n k += S.One\n \n res = log(a) - b*log(cdir) + b*logx\n for ex in terms:\n- res += terms[ex]*t**(ex)\n+ res += terms[ex].cancel()*t**(ex)\n \n if a.is_negative and im(z) != 0:\n from sympy.functions.special.delta_functions import Heaviside\n", "test_patch": "diff --git a/sympy/functions/elementary/tests/test_exponential.py b/sympy/functions/elementary/tests/test_exponential.py\nindex 686ae3b363f1..25d7f4e3431d 100644\n--- a/sympy/functions/elementary/tests/test_exponential.py\n+++ b/sympy/functions/elementary/tests/test_exponential.py\n@@ -524,7 +524,7 @@ def test_log_nseries():\n assert log(-2*x + (3 - I)*x**2)._eval_nseries(x, 3, None, -1) == -I*pi + log(2) + log(x) - \\\n x*(S(3)/2 - I/2) + x**2*(-1 + 3*I/4) + O(x**3)\n assert log(sqrt(-I*x**2 - 3)*sqrt(-I*x**2 - 1) - 2)._eval_nseries(x, 3, None, 1) == -I*pi + \\\n- log(sqrt(3) + 2) + I*x**2*(-2 + 4*sqrt(3)/3) + O(x**3)\n+ log(sqrt(3) + 2) + 2*sqrt(3)*I*x**2/(3*sqrt(3) + 6) + O(x**3)\n assert log(-1/(1 - x))._eval_nseries(x, 3, None, 1) == I*pi + x + x**2/2 + O(x**3)\n assert log(-1/(1 - x))._eval_nseries(x, 3, None, -1) == I*pi + x + x**2/2 + O(x**3)\n \ndiff --git a/sympy/functions/elementary/tests/test_hyperbolic.py b/sympy/functions/elementary/tests/test_hyperbolic.py\nindex 5b4365d8217d..1ad9f1d51598 100644\n--- a/sympy/functions/elementary/tests/test_hyperbolic.py\n+++ b/sympy/functions/elementary/tests/test_hyperbolic.py\n@@ -603,12 +603,12 @@ def test_asinh_series():\n def test_asinh_nseries():\n x = Symbol('x')\n # Tests concerning branch points\n- assert asinh(x + I)._eval_nseries(x, 4, None) == I*pi/2 + \\\n- sqrt(x)*(1 - I) + x**(S(3)/2)*(S(1)/12 + I/12) + x**(S(5)/2)*(-S(3)/160 + 3*I/160) + \\\n- x**(S(7)/2)*(-S(5)/896 - 5*I/896) + O(x**4)\n+ assert asinh(x + I)._eval_nseries(x, 4, None) == I*pi/2 - \\\n+ sqrt(2)*sqrt(I)*I*sqrt(x) + sqrt(2)*sqrt(I)*x**(S(3)/2)/12 + 3*sqrt(2)*sqrt(I)*I*x**(S(5)/2)/160 - \\\n+ 5*sqrt(2)*sqrt(I)*x**(S(7)/2)/896 + O(x**4)\n assert asinh(x - I)._eval_nseries(x, 4, None) == -I*pi/2 + \\\n- sqrt(x)*(1 + I) + x**(S(3)/2)*(S(1)/12 - I/12) + x**(S(5)/2)*(-S(3)/160 - 3*I/160) + \\\n- x**(S(7)/2)*(-S(5)/896 + 5*I/896) + O(x**4)\n+ sqrt(2)*I*sqrt(x)*sqrt(-I) + sqrt(2)*x**(S(3)/2)*sqrt(-I)/12 - \\\n+ 3*sqrt(2)*I*x**(S(5)/2)*sqrt(-I)/160 - 5*sqrt(2)*x**(S(7)/2)*sqrt(-I)/896 + O(x**4)\n # Tests concerning points lying on branch cuts\n assert asinh(x + 2*I)._eval_nseries(x, 4, None, cdir=1) == I*asin(2) - \\\n sqrt(3)*I*x/3 + sqrt(3)*x**2/9 + sqrt(3)*I*x**3/18 + O(x**4)\n@@ -619,8 +619,9 @@ def test_asinh_nseries():\n assert asinh(x - 2*I)._eval_nseries(x, 4, None, cdir=-1) == -I*asin(2) - \\\n sqrt(3)*I*x/3 - sqrt(3)*x**2/9 + sqrt(3)*I*x**3/18 + O(x**4)\n # Tests concerning re(ndir) == 0\n- assert asinh(2*I + I*x - x**2)._eval_nseries(x, 4, None) == I*pi/2 + log(2 - sqrt(3)) - \\\n- sqrt(3)*x/3 + x**2*(sqrt(3)/9 - sqrt(3)*I/3) + x**3*(-sqrt(3)/18 + 2*sqrt(3)*I/9) + O(x**4)\n+ assert asinh(2*I + I*x - x**2)._eval_nseries(x, 4, None) == I*pi/2 + log(2 - sqrt(3)) + \\\n+ x*(-3 + 2*sqrt(3))/(-6 + 3*sqrt(3)) + x**2*(12 - 36*I + sqrt(3)*(-7 + 21*I))/(-63 + \\\n+ 36*sqrt(3)) + x**3*(-168 + sqrt(3)*(97 - 388*I) + 672*I)/(-1746 + 1008*sqrt(3)) + O(x**4)\n \n \n def test_asinh_fdiff():\n@@ -757,8 +758,9 @@ def test_acosh_nseries():\n assert acosh(1/(I*x - 3))._eval_nseries(x, 4, None, cdir=-1) == acosh(-S(1)/3) - \\\n sqrt(2)*x/12 - 17*sqrt(2)*I*x**2/576 + 443*sqrt(2)*x**3/41472 + O(x**4)\n # Tests concerning im(ndir) == 0\n- assert acosh(-I*x**2 + x - 2)._eval_nseries(x, 4, None) == -I*pi + log(sqrt(3) + 2) - \\\n- sqrt(3)*x/3 + x**2*(-sqrt(3)/9 + sqrt(3)*I/3) + x**3*(-sqrt(3)/18 + 2*sqrt(3)*I/9) + O(x**4)\n+ assert acosh(-I*x**2 + x - 2)._eval_nseries(x, 4, None) == -I*pi + log(sqrt(3) + 2) + \\\n+ x*(-2*sqrt(3) - 3)/(3*sqrt(3) + 6) + x**2*(-12 + 36*I + sqrt(3)*(-7 + 21*I))/(36*sqrt(3) + \\\n+ 63) + x**3*(-168 + 672*I + sqrt(3)*(-97 + 388*I))/(1008*sqrt(3) + 1746) + O(x**4)\n \n \n def test_acosh_fdiff():\n@@ -880,8 +882,9 @@ def test_asech_nseries():\n assert asech(-I*x - 3)._eval_nseries(x, 4, None) == asech(-3) - sqrt(2)*x/12 + \\\n 17*sqrt(2)*I*x**2/576 + 443*sqrt(2)*x**3/41472 + O(x**4)\n # Tests concerning im(ndir) == 0\n- assert asech(-I*x**2 + x - 2)._eval_nseries(x, 3, None) == 2*I*pi/3 + sqrt(3)*I*x/6 + \\\n- x**2*(sqrt(3)/6 + 7*sqrt(3)*I/72) + O(x**3)\n+ assert asech(-I*x**2 + x - 2)._eval_nseries(x, 3, None) == 2*I*pi/3 + \\\n+ x*(-sqrt(3) + 3*I)/(6*sqrt(3) + 6*I) + x**2*(36 + sqrt(3)*(7 - 12*I) + 21*I)/(72*sqrt(3) - \\\n+ 72*I) + O(x**3)\n \n \n def test_asech_rewrite():\n@@ -1002,12 +1005,12 @@ def test_acsch_series():\n def test_acsch_nseries():\n x = Symbol('x')\n # Tests concerning branch points\n- assert acsch(x + I)._eval_nseries(x, 4, None) == -I*pi/2 + I*sqrt(x) + \\\n- sqrt(x) + 5*I*x**(S(3)/2)/12 - 5*x**(S(3)/2)/12 - 43*I*x**(S(5)/2)/160 - \\\n- 43*x**(S(5)/2)/160 - 177*I*x**(S(7)/2)/896 + 177*x**(S(7)/2)/896 + O(x**4)\n- assert acsch(x - I)._eval_nseries(x, 4, None) == I*pi/2 - I*sqrt(x) + \\\n- sqrt(x) - 5*I*x**(S(3)/2)/12 - 5*x**(S(3)/2)/12 + 43*I*x**(S(5)/2)/160 - \\\n- 43*x**(S(5)/2)/160 + 177*I*x**(S(7)/2)/896 + 177*x**(S(7)/2)/896 + O(x**4)\n+ assert acsch(x + I)._eval_nseries(x, 4, None) == -I*pi/2 + \\\n+ sqrt(2)*I*sqrt(x)*sqrt(-I) - 5*x**(S(3)/2)*(1 - I)/12 - \\\n+ 43*sqrt(2)*I*x**(S(5)/2)*sqrt(-I)/160 + 177*x**(S(7)/2)*(1 - I)/896 + O(x**4)\n+ assert acsch(x - I)._eval_nseries(x, 4, None) == I*pi/2 - \\\n+ sqrt(2)*sqrt(I)*I*sqrt(x) - 5*x**(S(3)/2)*(1 + I)/12 + \\\n+ 43*sqrt(2)*sqrt(I)*I*x**(S(5)/2)/160 + 177*x**(S(7)/2)*(1 + I)/896 + O(x**4)\n # Tests concerning points lying on branch cuts\n assert acsch(x + I/2)._eval_nseries(x, 4, None, cdir=1) == -acsch(I/2) - \\\n I*pi + 4*sqrt(3)*I*x/3 - 8*sqrt(3)*x**2/9 - 16*sqrt(3)*I*x**3/9 + O(x**4)\n@@ -1017,10 +1020,11 @@ def test_acsch_nseries():\n 4*sqrt(3)*I*x/3 - 8*sqrt(3)*x**2/9 + 16*sqrt(3)*I*x**3/9 + O(x**4)\n assert acsch(x - I/2)._eval_nseries(x, 4, None, cdir=-1) == I*pi + \\\n acsch(I/2) + 4*sqrt(3)*I*x/3 + 8*sqrt(3)*x**2/9 - 16*sqrt(3)*I*x**3/9 + O(x**4)\n- # TODO: Tests concerning re(ndir) == 0\n+ # Tests concerning re(ndir) == 0\n assert acsch(I/2 + I*x - x**2)._eval_nseries(x, 4, None) == -I*pi/2 + \\\n- log(2 - sqrt(3)) + 4*sqrt(3)*x/3 + x**2*(-8*sqrt(3)/9 + 4*sqrt(3)*I/3) + \\\n- x**3*(16*sqrt(3)/9 - 16*sqrt(3)*I/9) + O(x**4)\n+ log(2 - sqrt(3)) + x*(12 - 8*sqrt(3))/(-6 + 3*sqrt(3)) + x**2*(-96 + \\\n+ sqrt(3)*(56 - 84*I) + 144*I)/(-63 + 36*sqrt(3)) + x**3*(2688 - 2688*I + \\\n+ sqrt(3)*(-1552 + 1552*I))/(-873 + 504*sqrt(3)) + O(x**4)\n \n \n def test_acsch_rewrite():\n@@ -1540,3 +1544,10 @@ def test_issue_25847():\n #acsch\n assert acsch(sin(x)/x).as_leading_term(x) == log(1 + sqrt(2))\n raises(PoleError, lambda: acsch(exp(1/x)).as_leading_term(x))\n+\n+\n+def test_issue_25175():\n+ x = Symbol('x')\n+ g1 = 2*acosh(1 + 2*x/3) - acosh(S(5)/3 - S(8)/3/(x + 4))\n+ g2 = 2*log(sqrt((x + 4)/3)*(sqrt(x + 3)+sqrt(x))**2/(2*sqrt(x + 3) + sqrt(x)))\n+ assert (g1 - g2).series(x) == O(x**6)\n", "problem_statement": "nsimplify should not be used in series calculations\nThe use of `nsimplify` here leads to incorrect results:\r\nhttps://github.com/sympy/sympy/blob/e2cadc140cc969fea038240a39961a66a2f3dd6d/sympy/functions/elementary/exponential.py#L1047-L1048\r\n\r\nThis series should just be all zero terms:\r\n```python\r\nIn [3]: g1 = 2*acosh(1+2*z/3)-acosh(S(5)/3-S(8)/3/(z+4))\r\n\r\nIn [4]: g3 = 2*log(sqrt((z+4)/3)*(sqrt(z+3)+sqrt(z))**2/(2*sqrt(z+3)+sqrt(z)))\r\n\r\nIn [5]: s = (g1 - g3).series(z)\r\n\r\nIn [6]: s\r\nOut[6]: \r\n \u239b 11 31 61 47\u239e \u239b 372 19 443 193 17 \r\n \u239c \u2500\u2500 \u2500\u2500 \u2500\u2500\u2500 \u2500\u2500\u239f \u239c \u2500\u2500\u2500 \u2500\u2500 \u2500\u2500\u2500 \u2500\u2500\u2500 \u2500\u2500 \r\n \u239c 16 32 192 48\u239f \u239c 485 97 485 485 7/59 59 9/59 \r\n 9/2 \u239c 115963\u22c5\u221a3 1000\u22c52 \u22c53 \u22c55 \u22c57 \u239f 11/2 \u239c 2\u22c52 \u22c53 \u22c55 \u22c57 19131876\u22c52 \u22c53 \u22c55 \u22c57\r\nz \u22c5\u239c- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 + \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u239f + z \u22c5\u239c- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 + \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n \u239d 746496 194481 \u23a0 \u239d 45 820654296875 \r\n\r\n53 \u239e \r\n\u2500\u2500 \u239f \r\n59 \u239f \r\n 4194103\u22c5\u221a3\u239f \u239b 6\u239e\r\n\u2500\u2500 + \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u239f + O\u239dz \u23a0\r\n 8211456 \u23a0 \r\n```\r\nInternally `nsimplify` is called with:\r\n```python\r\nIn [1]: nsimplify(-403*sqrt(3)/2737152)\r\nOut[1]: \r\n 17 53 \r\n \u2500\u2500 \u2500\u2500 \r\n 7/59 59 9/59 59 \r\n-19131876\u22c52 \u22c53 \u22c55 \u22c57 \r\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n 820654296875 \r\n```\r\nThis output from `nsimplify` is not equal to the input which could be considered a bug in `nsimplify` but then again `nsimplify` is only supposed to be approximate. Really the problem is that the series code should not use nsimplify at all. This is presumably added to handle floats but that should be done elsewhere.\r\n\r\nThe call to `nsimplify` was added in gh-23798 (CC @anutosh491).\n", "hints_text": "", "created_at": "2024-06-11T20:18:16Z" }, { "repo": "sympy/sympy", "pull_number": 26683, "instance_id": "sympy__sympy-26683", "issue_numbers": [ "26602" ], "base_commit": "79d7b3ef4b5e2796da6458effa7fb3427a73f2c9", "patch": "diff --git a/sympy/physics/continuum_mechanics/beam.py b/sympy/physics/continuum_mechanics/beam.py\nindex 6b2e8013882c..bbf8c67fc77c 100644\n--- a/sympy/physics/continuum_mechanics/beam.py\n+++ b/sympy/physics/continuum_mechanics/beam.py\n@@ -136,7 +136,7 @@ class Beam:\n (-5*L**2*q1 + 7*L**2*q2 - 8*L*P1 + 4*L*P2 + 32*M1 - 32*M2)/(32*L)\n \"\"\"\n \n- def __init__(self, length, elastic_modulus, second_moment, area=Symbol('A'), variable=Symbol('x'), base_char='C'):\n+ def __init__(self, length, elastic_modulus, second_moment, area=Symbol('A'), variable=Symbol('x'), base_char='C', ild_variable=Symbol('a')):\n \"\"\"Initializes the class.\n \n Parameters\n@@ -174,6 +174,11 @@ def __init__(self, length, elastic_modulus, second_moment, area=Symbol('A'), var\n A String that will be used as base character to generate sequential\n symbols for integration constants in cases where boundary conditions\n are not sufficient to solve them.\n+\n+ ild_variable : Symbol, optional\n+ A Symbol object that will be used as the variable specifying the\n+ location of the moving load in ILD calculations. By default, it\n+ is set to ``Symbol('a')``.\n \"\"\"\n self.length = length\n self.elastic_modulus = elastic_modulus\n@@ -183,6 +188,7 @@ def __init__(self, length, elastic_modulus, second_moment, area=Symbol('A'), var\n self.cross_section = None\n self.second_moment = second_moment\n self.variable = variable\n+ self.ild_variable = ild_variable\n self._base_char = base_char\n self._boundary_conditions = {'deflection': [], 'slope': [], 'bending_moment': [], 'shear_force': []}\n self._load = 0\n@@ -241,6 +247,24 @@ def ild_reactions(self):\n \"\"\" Returns the I.L.D. reaction forces in a dictionary.\"\"\"\n return self._ild_reactions\n \n+ @property\n+ def ild_rotation_jumps(self):\n+ \"\"\"\n+ Returns the I.L.D. rotation jumps in rotation hinges in a dictionary.\n+ The rotation jump is the rotation (in radian) in a rotation hinge. This can\n+ be seen as a jump in the slope plot.\n+ \"\"\"\n+ return self._ild_rotations_jumps\n+\n+ @property\n+ def ild_deflection_jumps(self):\n+ \"\"\"\n+ Returns the I.L.D. deflection jumps in sliding hinges in a dictionary.\n+ The deflection jump is the deflection (in meters) in a sliding hinge.\n+ This can be seen as a jump in the deflection plot.\n+ \"\"\"\n+ return self._ild_deflection_jumps\n+\n @property\n def ild_moment(self):\n \"\"\" Returns the I.L.D. moment equation.\"\"\"\n@@ -1777,17 +1801,17 @@ def plot_loading_results(self, subs=None):\n \n return PlotGrid(4, 1, ax1, ax2, ax3, ax4)\n \n- def _solve_for_ild_equations(self):\n+ def _solve_for_ild_equations(self, value):\n \"\"\"\n \n Helper function for I.L.D. It takes the unsubstituted\n copy of the load equation and uses it to calculate shear force and bending\n moment equations.\n \"\"\"\n- if self._applied_rotation_hinges or self._applied_sliding_hinges:\n- raise NotImplementedError(\"I.L.D. calculations are not implemented for beams with hinges.\")\n x = self.variable\n- shear_force = -integrate(self._original_load, x)\n+ a = self.ild_variable\n+ load = self._load + value * SingularityFunction(x, a, -1)\n+ shear_force = -integrate(load, x)\n bending_moment = integrate(shear_force, x)\n \n return shear_force, bending_moment\n@@ -1805,6 +1829,11 @@ def solve_for_ild_reactions(self, value, *reactions):\n reactions :\n The reaction forces applied on the beam.\n \n+ Warning\n+ =======\n+ This method creates equations that can give incorrect results when\n+ substituting a = 0 or a = l, with l the length of the beam.\n+\n Examples\n ========\n \n@@ -1825,41 +1854,72 @@ def solve_for_ild_reactions(self, value, *reactions):\n >>> E, I = symbols('E, I')\n >>> R_0, R_10 = symbols('R_0, R_10')\n >>> b = Beam(10, E, I)\n- >>> p0 = b.apply_support(0, 'roller')\n+ >>> p0 = b.apply_support(0, 'pin')\n >>> p10 = b.apply_support(10, 'roller')\n >>> b.solve_for_ild_reactions(1,R_0,R_10)\n >>> b.ild_reactions\n- {R_0: x/10 - 1, R_10: -x/10}\n+ {R_0: -SingularityFunction(a, 0, 0) + SingularityFunction(a, 0, 1)/10 - SingularityFunction(a, 10, 1)/10,\n+ R_10: -SingularityFunction(a, 0, 1)/10 + SingularityFunction(a, 10, 0) + SingularityFunction(a, 10, 1)/10}\n \n \"\"\"\n- shear_force, bending_moment = self._solve_for_ild_equations()\n+ shear_force, bending_moment = self._solve_for_ild_equations(value)\n x = self.variable\n l = self.length\n+ a = self.ild_variable\n+\n+ rotation_jumps = tuple(self._rotation_hinge_symbols)\n+ deflection_jumps = tuple(self._sliding_hinge_symbols)\n+\n C3 = Symbol('C3')\n C4 = Symbol('C4')\n \n- shear_curve = limit(shear_force, x, l) - value\n- moment_curve = limit(bending_moment, x, l) - value*(l-x)\n+ shear_curve = limit(shear_force, x, l) - value*(SingularityFunction(a, 0, 0) - SingularityFunction(a, l, 0))\n+ moment_curve = (limit(bending_moment, x, l) - value * (l * SingularityFunction(a, 0, 0)\n+ - SingularityFunction(a, 0, 1)\n+ + SingularityFunction(a, l, 1)))\n \n+ shear_force_eqs = []\n+ bending_moment_eqs = []\n slope_eqs = []\n deflection_eqs = []\n \n+ for position, val in self._boundary_conditions['shear_force']:\n+ eqs = self.shear_force().subs(x, position) - val\n+ eqs_without_inf = sum(arg for arg in eqs.args if not any(num.is_infinite for num in arg.args))\n+ shear_sinc = value * (SingularityFunction(- a, - position, 0) - SingularityFunction(-a, 0, 0))\n+ eqs_with_shear_sinc = eqs_without_inf - shear_sinc\n+ shear_force_eqs.append(eqs_with_shear_sinc)\n+\n+ for position, val in self._boundary_conditions['bending_moment']:\n+ eqs = self.bending_moment().subs(x, position) - val\n+ eqs_without_inf = sum(arg for arg in eqs.args if not any(num.is_infinite for num in arg.args))\n+ moment_sinc = value * (position * SingularityFunction(a, 0, 0)\n+ - SingularityFunction(a, 0, 1) + SingularityFunction(a, position, 1))\n+ eqs_with_moment_sinc = eqs_without_inf - moment_sinc\n+ bending_moment_eqs.append(eqs_with_moment_sinc)\n+\n slope_curve = integrate(bending_moment, x) + C3\n- for position, value in self._boundary_conditions['slope']:\n- eqs = slope_curve.subs(x, position) - value\n+ for position, val in self._boundary_conditions['slope']:\n+ eqs = slope_curve.subs(x, position) - val + value * (SingularityFunction(-a, 0, 1) + position * SingularityFunction(-a, 0, 0))**2 / 2\n slope_eqs.append(eqs)\n \n deflection_curve = integrate(slope_curve, x) + C4\n- for position, value in self._boundary_conditions['deflection']:\n- eqs = deflection_curve.subs(x, position) - value\n+ for position, val in self._boundary_conditions['deflection']:\n+ eqs = deflection_curve.subs(x, position) - val + value * (SingularityFunction(-a, 0, 1) + position * SingularityFunction(-a, 0, 0)) ** 3 / 6\n deflection_eqs.append(eqs)\n \n- solution = list((linsolve([shear_curve, moment_curve] + slope_eqs\n- + deflection_eqs, (C3, C4) + reactions).args)[0])\n- solution = solution[2:]\n+ solution = list((linsolve([shear_curve, moment_curve] + shear_force_eqs + bending_moment_eqs + slope_eqs\n+ + deflection_eqs, (C3, C4) + reactions + rotation_jumps + deflection_jumps).args)[0])\n+\n+ reaction_index = 2 + len(reactions)\n+ rotation_index = reaction_index + len(rotation_jumps)\n+ reaction_solution = solution[2:reaction_index]\n+ rotation_solution = solution[reaction_index:rotation_index]\n+ deflection_solution = solution[rotation_index:]\n \n- # Determining the equations and solving them.\n- self._ild_reactions = dict(zip(reactions, solution))\n+ self._ild_reactions = dict(zip(reactions, reaction_solution))\n+ self._ild_rotations_jumps = dict(zip(rotation_jumps, rotation_solution))\n+ self._ild_deflection_jumps = dict(zip(deflection_jumps, deflection_solution))\n \n def plot_ild_reactions(self, subs=None):\n \"\"\"\n@@ -1875,6 +1935,11 @@ def plot_ild_reactions(self, subs=None):\n Python dictionary containing Symbols as key and their\n corresponding values.\n \n+ Warning\n+ =======\n+ The values for a = 0 and a = l, with l the length of the beam, in\n+ the plot can be incorrect.\n+\n Examples\n ========\n \n@@ -1903,19 +1968,23 @@ def plot_ild_reactions(self, subs=None):\n >>> b.apply_load(5,4,-1)\n >>> b.solve_for_ild_reactions(1,R_0,R_7)\n >>> b.ild_reactions\n- {R_0: x/7 - 22/7, R_7: -x/7 - 20/7}\n+ {R_0: -SingularityFunction(a, 0, 0) + SingularityFunction(a, 0, 1)/7\n+ - 3*SingularityFunction(a, 10, 0)/7 - SingularityFunction(a, 10, 1)/7 - 15/7,\n+ R_7: -SingularityFunction(a, 0, 1)/7 + 10*SingularityFunction(a, 10, 0)/7 + SingularityFunction(a, 10, 1)/7 - 20/7}\n >>> b.plot_ild_reactions()\n PlotGrid object containing:\n Plot[0]:Plot object containing:\n- [0]: cartesian line: x/7 - 22/7 for x over (0.0, 10.0)\n+ [0]: cartesian line: -SingularityFunction(a, 0, 0) + SingularityFunction(a, 0, 1)/7\n+ - 3*SingularityFunction(a, 10, 0)/7 - SingularityFunction(a, 10, 1)/7 - 15/7 for a over (0.0, 10.0)\n Plot[1]:Plot object containing:\n- [0]: cartesian line: -x/7 - 20/7 for x over (0.0, 10.0)\n+ [0]: cartesian line: -SingularityFunction(a, 0, 1)/7 + 10*SingularityFunction(a, 10, 0)/7\n+ + SingularityFunction(a, 10, 1)/7 - 20/7 for a over (0.0, 10.0)\n \n \"\"\"\n if not self._ild_reactions:\n raise ValueError(\"I.L.D. reaction equations not found. Please use solve_for_ild_reactions() to generate the I.L.D. reaction equations.\")\n \n- x = self.variable\n+ a = self.ild_variable\n ildplots = []\n \n if subs is None:\n@@ -1923,17 +1992,17 @@ def plot_ild_reactions(self, subs=None):\n \n for reaction in self._ild_reactions:\n for sym in self._ild_reactions[reaction].atoms(Symbol):\n- if sym != x and sym not in subs:\n+ if sym != a and sym not in subs:\n raise ValueError('Value of %s was not passed.' %sym)\n \n for sym in self._length.atoms(Symbol):\n- if sym != x and sym not in subs:\n+ if sym != a and sym not in subs:\n raise ValueError('Value of %s was not passed.' %sym)\n \n for reaction in self._ild_reactions:\n ildplots.append(plot(self._ild_reactions[reaction].subs(subs),\n- (x, 0, self._length.subs(subs)), title='I.L.D. for Reactions',\n- xlabel=x, ylabel=reaction, line_color='blue', show=False))\n+ (a, 0, self._length.subs(subs)), title='I.L.D. for Reactions',\n+ xlabel=a, ylabel=reaction, line_color='blue', show=False))\n \n return PlotGrid(len(ildplots), 1, *ildplots)\n \n@@ -1953,6 +2022,11 @@ def solve_for_ild_shear(self, distance, value, *reactions):\n reactions :\n The reaction forces applied on the beam.\n \n+ Warning\n+ =======\n+ This method creates equations that can give incorrect results when\n+ substituting a = 0 or a = l, with l the length of the beam.\n+\n Examples\n ========\n \n@@ -1978,14 +2052,17 @@ def solve_for_ild_shear(self, distance, value, *reactions):\n >>> b.solve_for_ild_reactions(1, R_0, R_8)\n >>> b.solve_for_ild_shear(4, 1, R_0, R_8)\n >>> b.ild_shear\n- Piecewise((x/8, x < 4), (x/8 - 1, x > 4))\n+ -(-SingularityFunction(a, 0, 0) + SingularityFunction(a, 12, 0) + 2)*SingularityFunction(a, 4, 0)\n+ - SingularityFunction(-a, 0, 0) - SingularityFunction(a, 0, 0) + SingularityFunction(a, 0, 1)/8\n+ + SingularityFunction(a, 12, 0)/2 - SingularityFunction(a, 12, 1)/8 + 1\n \n \"\"\"\n \n x = self.variable\n l = self.length\n+ a = self.ild_variable\n \n- shear_force, _ = self._solve_for_ild_equations()\n+ shear_force, _ = self._solve_for_ild_equations(value)\n \n shear_curve1 = value - limit(shear_force, x, distance)\n shear_curve2 = (limit(shear_force, x, l) - limit(shear_force, x, distance)) - value\n@@ -1994,7 +2071,8 @@ def solve_for_ild_shear(self, distance, value, *reactions):\n shear_curve1 = shear_curve1.subs(reaction,self._ild_reactions[reaction])\n shear_curve2 = shear_curve2.subs(reaction,self._ild_reactions[reaction])\n \n- shear_eq = Piecewise((shear_curve1, x < distance), (shear_curve2, x > distance))\n+ shear_eq = (shear_curve1 - (shear_curve1 - shear_curve2) * SingularityFunction(a, distance, 0)\n+ - value * SingularityFunction(-a, 0, 0) + value * SingularityFunction(a, l, 0))\n \n self._ild_shear = shear_eq\n \n@@ -2012,6 +2090,11 @@ def plot_ild_shear(self,subs=None):\n Python dictionary containing Symbols as key and their\n corresponding values.\n \n+ Warning\n+ =======\n+ The values for a = 0 and a = l, with l the lenght of the beam, in\n+ the plot can be incorrect.\n+\n Examples\n ========\n \n@@ -2037,32 +2120,36 @@ def plot_ild_shear(self,subs=None):\n >>> b.solve_for_ild_reactions(1, R_0, R_8)\n >>> b.solve_for_ild_shear(4, 1, R_0, R_8)\n >>> b.ild_shear\n- Piecewise((x/8, x < 4), (x/8 - 1, x > 4))\n+ -(-SingularityFunction(a, 0, 0) + SingularityFunction(a, 12, 0) + 2)*SingularityFunction(a, 4, 0)\n+ - SingularityFunction(-a, 0, 0) - SingularityFunction(a, 0, 0) + SingularityFunction(a, 0, 1)/8\n+ + SingularityFunction(a, 12, 0)/2 - SingularityFunction(a, 12, 1)/8 + 1\n >>> b.plot_ild_shear()\n Plot object containing:\n- [0]: cartesian line: Piecewise((x/8, x < 4), (x/8 - 1, x > 4)) for x over (0.0, 12.0)\n+ [0]: cartesian line: -(-SingularityFunction(a, 0, 0) + SingularityFunction(a, 12, 0) + 2)*SingularityFunction(a, 4, 0)\n+ - SingularityFunction(-a, 0, 0) - SingularityFunction(a, 0, 0) + SingularityFunction(a, 0, 1)/8\n+ + SingularityFunction(a, 12, 0)/2 - SingularityFunction(a, 12, 1)/8 + 1 for a over (0.0, 12.0)\n \n \"\"\"\n \n if not self._ild_shear:\n raise ValueError(\"I.L.D. shear equation not found. Please use solve_for_ild_shear() to generate the I.L.D. shear equations.\")\n \n- x = self.variable\n l = self._length\n+ a = self.ild_variable\n \n if subs is None:\n subs = {}\n \n for sym in self._ild_shear.atoms(Symbol):\n- if sym != x and sym not in subs:\n+ if sym != a and sym not in subs:\n raise ValueError('Value of %s was not passed.' %sym)\n \n for sym in self._length.atoms(Symbol):\n- if sym != x and sym not in subs:\n+ if sym != a and sym not in subs:\n raise ValueError('Value of %s was not passed.' %sym)\n \n- return plot(self._ild_shear.subs(subs), (x, 0, l), title='I.L.D. for Shear',\n- xlabel=r'$\\mathrm{X}$', ylabel=r'$\\mathrm{V}$', line_color='blue',show=True)\n+ return plot(self._ild_shear.subs(subs), (a, 0, l), title='I.L.D. for Shear',\n+ xlabel=r'$\\mathrm{a}$', ylabel=r'$\\mathrm{V}$', line_color='blue',show=True)\n \n def solve_for_ild_moment(self, distance, value, *reactions):\n \"\"\"\n@@ -2080,6 +2167,11 @@ def solve_for_ild_moment(self, distance, value, *reactions):\n reactions :\n The reaction forces applied on the beam.\n \n+ Warning\n+ =======\n+ This method creates equations that can give incorrect results when\n+ substituting a = 0 or a = l, with l the lenght of the beam.\n+\n Examples\n ========\n \n@@ -2105,23 +2197,30 @@ def solve_for_ild_moment(self, distance, value, *reactions):\n >>> b.solve_for_ild_reactions(1, R_0, R_8)\n >>> b.solve_for_ild_moment(4, 1, R_0, R_8)\n >>> b.ild_moment\n- Piecewise((-x/2, x < 4), (x/2 - 4, x > 4))\n+ -(4*SingularityFunction(a, 0, 0) - SingularityFunction(a, 0, 1) + SingularityFunction(a, 4, 1))*SingularityFunction(a, 4, 0)\n+ - SingularityFunction(a, 0, 1)/2 + SingularityFunction(a, 4, 1) - 2*SingularityFunction(a, 12, 0)\n+ - SingularityFunction(a, 12, 1)/2\n \n \"\"\"\n \n x = self.variable\n l = self.length\n+ a = self.ild_variable\n \n- _, moment = self._solve_for_ild_equations()\n+ _, moment = self._solve_for_ild_equations(value)\n \n- moment_curve1 = value*(distance-x) - limit(moment, x, distance)\n- moment_curve2= (limit(moment, x, l)-limit(moment, x, distance))-value*(l-x)\n+ moment_curve1 = value*(distance * SingularityFunction(a, 0, 0) - SingularityFunction(a, 0, 1)\n+ + SingularityFunction(a, distance, 1)) - limit(moment, x, distance)\n+ moment_curve2 = (limit(moment, x, l)-limit(moment, x, distance)\n+ - value * (l * SingularityFunction(a, 0, 0) - SingularityFunction(a, 0, 1)\n+ + SingularityFunction(a, l, 1)))\n \n for reaction in reactions:\n moment_curve1 = moment_curve1.subs(reaction, self._ild_reactions[reaction])\n moment_curve2 = moment_curve2.subs(reaction, self._ild_reactions[reaction])\n \n- moment_eq = Piecewise((moment_curve1, x < distance), (moment_curve2, x > distance))\n+ moment_eq = moment_curve1 - (moment_curve1 - moment_curve2) * SingularityFunction(a, distance, 0)\n+\n self._ild_moment = moment_eq\n \n def plot_ild_moment(self,subs=None):\n@@ -2138,6 +2237,11 @@ def plot_ild_moment(self,subs=None):\n Python dictionary containing Symbols as key and their\n corresponding values.\n \n+ Warning\n+ =======\n+ The values for a = 0 and a = l, with l the length of the beam, in\n+ the plot can be incorrect.\n+\n Examples\n ========\n \n@@ -2163,30 +2267,34 @@ def plot_ild_moment(self,subs=None):\n >>> b.solve_for_ild_reactions(1, R_0, R_8)\n >>> b.solve_for_ild_moment(4, 1, R_0, R_8)\n >>> b.ild_moment\n- Piecewise((-x/2, x < 4), (x/2 - 4, x > 4))\n+ -(4*SingularityFunction(a, 0, 0) - SingularityFunction(a, 0, 1) + SingularityFunction(a, 4, 1))*SingularityFunction(a, 4, 0)\n+ - SingularityFunction(a, 0, 1)/2 + SingularityFunction(a, 4, 1) - 2*SingularityFunction(a, 12, 0)\n+ - SingularityFunction(a, 12, 1)/2\n >>> b.plot_ild_moment()\n Plot object containing:\n- [0]: cartesian line: Piecewise((-x/2, x < 4), (x/2 - 4, x > 4)) for x over (0.0, 12.0)\n+ [0]: cartesian line: -(4*SingularityFunction(a, 0, 0) - SingularityFunction(a, 0, 1)\n+ + SingularityFunction(a, 4, 1))*SingularityFunction(a, 4, 0) - SingularityFunction(a, 0, 1)/2\n+ + SingularityFunction(a, 4, 1) - 2*SingularityFunction(a, 12, 0) - SingularityFunction(a, 12, 1)/2 for a over (0.0, 12.0)\n \n \"\"\"\n \n if not self._ild_moment:\n raise ValueError(\"I.L.D. moment equation not found. Please use solve_for_ild_moment() to generate the I.L.D. moment equations.\")\n \n- x = self.variable\n+ a = self.ild_variable\n \n if subs is None:\n subs = {}\n \n for sym in self._ild_moment.atoms(Symbol):\n- if sym != x and sym not in subs:\n+ if sym != a and sym not in subs:\n raise ValueError('Value of %s was not passed.' %sym)\n \n for sym in self._length.atoms(Symbol):\n- if sym != x and sym not in subs:\n+ if sym != a and sym not in subs:\n raise ValueError('Value of %s was not passed.' %sym)\n- return plot(self._ild_moment.subs(subs), (x, 0, self._length), title='I.L.D. for Moment',\n- xlabel=r'$\\mathrm{X}$', ylabel=r'$\\mathrm{M}$', line_color='blue', show=True)\n+ return plot(self._ild_moment.subs(subs), (a, 0, self._length), title='I.L.D. for Moment',\n+ xlabel=r'$\\mathrm{a}$', ylabel=r'$\\mathrm{M}$', line_color='blue', show=True)\n \n @doctest_depends_on(modules=('numpy',))\n def draw(self, pictorial=True):\n", "test_patch": "diff --git a/sympy/physics/continuum_mechanics/tests/test_beam.py b/sympy/physics/continuum_mechanics/tests/test_beam.py\nindex 9f226e4aa163..a6a36fb030f9 100644\n--- a/sympy/physics/continuum_mechanics/tests/test_beam.py\n+++ b/sympy/physics/continuum_mechanics/tests/test_beam.py\n@@ -741,6 +741,153 @@ def test_max_deflection():\n b.apply_load(-F, l/2, -1)\n assert b.max_deflection() == (l/2, F*l**3/(192*E*I))\n \n+def test_solve_for_ild_reactions():\n+ E = Symbol('E')\n+ I = Symbol('I')\n+ b = Beam(10, E, I)\n+ b.apply_support(0, type=\"pin\")\n+ b.apply_support(10, type=\"pin\")\n+ R_0, R_10 = symbols('R_0, R_10')\n+ b.solve_for_ild_reactions(1, R_0, R_10)\n+ a = b.ild_variable\n+ assert b.ild_reactions == {R_0: -SingularityFunction(a, 0, 0) + SingularityFunction(a, 0, 1)/10\n+ - SingularityFunction(a, 10, 1)/10,\n+ R_10: -SingularityFunction(a, 0, 1)/10 + SingularityFunction(a, 10, 0)\n+ + SingularityFunction(a, 10, 1)/10}\n+\n+ E = Symbol('E')\n+ I = Symbol('I')\n+ F = Symbol('F')\n+ L = Symbol('L', positive=True)\n+ b = Beam(L, E, I)\n+ b.apply_support(L, type=\"fixed\")\n+ b.apply_load(F, 0, -1)\n+ R_L, M_L = symbols('R_L, M_L')\n+ b.solve_for_ild_reactions(F, R_L, M_L)\n+ a = b.ild_variable\n+ assert b.ild_reactions == {R_L: -F*SingularityFunction(a, 0, 0) + F*SingularityFunction(a, L, 0) - F,\n+ M_L: -F*L*SingularityFunction(a, 0, 0) - F*L + F*SingularityFunction(a, 0, 1)\n+ - F*SingularityFunction(a, L, 1)}\n+\n+ E = Symbol('E')\n+ I = Symbol('I')\n+ b = Beam(20, E, I)\n+ r0 = b.apply_support(0, type=\"pin\")\n+ r5 = b.apply_support(5, type=\"pin\")\n+ r10 = b.apply_support(10, type=\"pin\")\n+ r20, m20 = b.apply_support(20, type=\"fixed\")\n+ b.solve_for_ild_reactions(1, r0, r5, r10, r20, m20)\n+ a = b.ild_variable\n+ assert b.ild_reactions[r0].subs(a, 4) == -Rational(59, 475)\n+ assert b.ild_reactions[r5].subs(a, 4) == -Rational(2296, 2375)\n+ assert b.ild_reactions[r10].subs(a, 4) == Rational(243, 2375)\n+ assert b.ild_reactions[r20].subs(a, 12) == -Rational(83, 475)\n+ assert b.ild_reactions[m20].subs(a, 12) == -Rational(264, 475)\n+\n+def test_solve_for_ild_shear():\n+ E = Symbol('E')\n+ I = Symbol('I')\n+ F = Symbol('F')\n+ L1 = Symbol('L1', positive=True)\n+ L2 = Symbol('L2', positive=True)\n+ b = Beam(L1 + L2, E, I)\n+ r0 = b.apply_support(0, type=\"pin\")\n+ rL = b.apply_support(L1 + L2, type=\"pin\")\n+ b.solve_for_ild_reactions(F, r0, rL)\n+ b.solve_for_ild_shear(L1, F, r0, rL)\n+ a = b.ild_variable\n+ expected_shear = (-F*L1*SingularityFunction(a, 0, 0)/(L1 + L2) - F*L2*SingularityFunction(a, 0, 0)/(L1 + L2)\n+ - F*SingularityFunction(-a, 0, 0) + F*SingularityFunction(a, L1 + L2, 0) + F\n+ + F*SingularityFunction(a, 0, 1)/(L1 + L2) - F*SingularityFunction(a, L1 + L2, 1)/(L1 + L2)\n+ - (-F*L1*SingularityFunction(a, 0, 0)/(L1 + L2) + F*L1*SingularityFunction(a, L1 + L2, 0)/(L1 + L2)\n+ - F*L2*SingularityFunction(a, 0, 0)/(L1 + L2) + F*L2*SingularityFunction(a, L1 + L2, 0)/(L1 + L2)\n+ + 2*F)*SingularityFunction(a, L1, 0))\n+ assert b.ild_shear.expand() == expected_shear.expand()\n+\n+ E = Symbol('E')\n+ I = Symbol('I')\n+ b = Beam(20, E, I)\n+ r0 = b.apply_support(0, type=\"pin\")\n+ r5 = b.apply_support(5, type=\"pin\")\n+ r10 = b.apply_support(10, type=\"pin\")\n+ r20, m20 = b.apply_support(20, type=\"fixed\")\n+ b.solve_for_ild_reactions(1, r0, r5, r10, r20, m20)\n+ b.solve_for_ild_shear(6, 1, r0, r5, r10, r20, m20)\n+ a = b.ild_variable\n+ assert b.ild_shear.subs(a, 12) == Rational(96, 475)\n+ assert b.ild_shear.subs(a, 4) == -Rational(216, 2375)\n+\n+def test_solve_for_ild_moment():\n+ E = Symbol('E')\n+ I = Symbol('I')\n+ F = Symbol('F')\n+ L1 = Symbol('L1', positive=True)\n+ L2 = Symbol('L2', positive=True)\n+ b = Beam(L1 + L2, E, I)\n+ r0 = b.apply_support(0, type=\"pin\")\n+ rL = b.apply_support(L1 + L2, type=\"pin\")\n+ a = b.ild_variable\n+ b.solve_for_ild_reactions(F, r0, rL)\n+ b.solve_for_ild_moment(L1, F, r0, rL)\n+ assert b.ild_moment.subs(a, 3).subs(L1, 5).subs(L2, 5) == -3*F/2\n+\n+ E = Symbol('E')\n+ I = Symbol('I')\n+ b = Beam(20, E, I)\n+ r0 = b.apply_support(0, type=\"pin\")\n+ r5 = b.apply_support(5, type=\"pin\")\n+ r10 = b.apply_support(10, type=\"pin\")\n+ r20, m20 = b.apply_support(20, type=\"fixed\")\n+ b.solve_for_ild_reactions(1, r0, r5, r10, r20, m20)\n+ b.solve_for_ild_moment(5, 1, r0, r5, r10, r20, m20)\n+ assert b.ild_moment.subs(a, 12) == -Rational(96, 475)\n+ assert b.ild_moment.subs(a, 4) == Rational(36, 95)\n+\n+def test_ild_with_rotation_hinge():\n+ E = Symbol('E')\n+ I = Symbol('I')\n+ F = Symbol('F')\n+ L1 = Symbol('L1', positive=True)\n+ L2 = Symbol('L2', positive=True)\n+ L3 = Symbol('L3', positive=True)\n+ b = Beam(L1 + L2 + L3, E, I)\n+ r0 = b.apply_support(0, type=\"pin\")\n+ r1 = b.apply_support(L1 + L2, type=\"pin\")\n+ r2 = b.apply_support(L1 + L2 + L3, type=\"pin\")\n+ b.apply_rotation_hinge(L1 + L2)\n+ b.solve_for_ild_reactions(F, r0, r1, r2)\n+ a = b.ild_variable\n+ assert b.ild_reactions[r0].subs(a, 4).subs(L1, 5).subs(L2, 5).subs(L3, 10) == -3*F/5\n+ assert b.ild_reactions[r0].subs(a, -10).subs(L1, 5).subs(L2, 5).subs(L3, 10) == 0\n+ assert b.ild_reactions[r0].subs(a, 25).subs(L1, 5).subs(L2, 5).subs(L3, 10) == 0\n+ assert b.ild_reactions[r1].subs(a, 4).subs(L1, 5).subs(L2, 5).subs(L3, 10) == -2*F/5\n+ assert b.ild_reactions[r2].subs(a, 18).subs(L1, 5).subs(L2, 5).subs(L3, 10) == -4*F/5\n+ b.solve_for_ild_shear(L1, F, r0, r1, r2)\n+ assert b.ild_shear.subs(a, 7).subs(L1, 5).subs(L2, 5).subs(L3, 10) == -3*F/10\n+ assert b.ild_shear.subs(a, 70).subs(L1, 5).subs(L2, 5).subs(L3, 10) == 0\n+ b.solve_for_ild_moment(L1, F, r0, r1, r2)\n+ assert b.ild_moment.subs(a, 1).subs(L1, 5).subs(L2, 5).subs(L3, 10) == -F/2\n+ assert b.ild_moment.subs(a, 8).subs(L1, 5).subs(L2, 5).subs(L3, 10) == -F\n+\n+def test_ild_with_sliding_hinge():\n+ b = Beam(13, 200, 200)\n+ r0 = b.apply_support(0, type=\"pin\")\n+ r6 = b.apply_support(6, type=\"pin\")\n+ r13, m13 = b.apply_support(13, type=\"fixed\")\n+ w3 = b.apply_sliding_hinge(3)\n+ b.solve_for_ild_reactions(1, r0, r6, r13, m13)\n+ a = b.ild_variable\n+ assert b.ild_reactions[r0].subs(a, 3) == -1\n+ assert b.ild_reactions[r6].subs(a, 3) == Rational(9, 14)\n+ assert b.ild_reactions[r13].subs(a, 9) == -Rational(207, 343)\n+ assert b.ild_reactions[m13].subs(a, 9) == -Rational(60, 49)\n+ assert b.ild_reactions[m13].subs(a, 15) == 0\n+ assert b.ild_reactions[m13].subs(a, -3) == 0\n+ assert b.ild_deflection_jumps[w3].subs(a, 9) == -Rational(9, 35000)\n+ b.solve_for_ild_shear(7, 1, r0, r6, r13, m13)\n+ assert b.ild_shear.subs(a, 8) == -Rational(200, 343)\n+ b.solve_for_ild_moment(8, 1, r0, r6, r13, m13)\n+ assert b.ild_moment.subs(a, 3) == -Rational(12, 7)\n \n def test_Beam3D():\n l, E, G, I, A = symbols('l, E, G, I, A')\n", "problem_statement": "Missing tests for Influence Line Diagrams\nWhile looking through the beam module and the tests for this module I saw that there are no tests for the Influence Line Diagram (I.L.D.) methods. I was looking for this in relation to issue #26601. In test_beam.py not a single I.L.D. method is tested. \r\n\r\nThe best way forward is probably to first add test for the current functionality of the method, before changing the method to be be able to handle hinges. \r\n\r\n\r\n\r\n\r\n\r\nAs this is for a school project I will tag my supervisors so they stay up-to-date: @Tom-van-Woudenberg @moorepants\n", "hints_text": "", "created_at": "2024-06-07T15:27:39Z" }, { "repo": "sympy/sympy", "pull_number": 26672, "instance_id": "sympy__sympy-26672", "issue_numbers": [ "26318" ], "base_commit": "3938a71ca9ac796e0cc0ebd893e07b5470a031ab", "patch": "diff --git a/doc/src/guides/solving/find-roots-polynomial.md b/doc/src/guides/solving/find-roots-polynomial.md\nindex 18f5cb3401ca..6ec9af206bb3 100644\n--- a/doc/src/guides/solving/find-roots-polynomial.md\n+++ b/doc/src/guides/solving/find-roots-polynomial.md\n@@ -504,11 +504,8 @@ symbolically.\n / 5 \\\n [CRootOf\\x - x + 1, 0/]\n >>> r = r0, r1, r2, r3, r4 = Poly(fifth_order, x).all_roots(); r\n- / 5 \\ / 5 \\ / 5 \\ >\n-[CRootOf\\x - x + 1, 0/, CRootOf\\x - x + 1, 1/, CRootOf\\x - x + 1, 2/, CRoot >\n-\n-> / 5 \\ / 5 \\\n-> Of\\x - x + 1, 3/, CRootOf\\x - x + 1, 4/]\n+ / 5 \\ / 5 \\ / 5 \\ / 5 \\ / 5 \\\n+[CRootOf\\x - x + 1, 0/, CRootOf\\x - x + 1, 1/, CRootOf\\x - x + 1, 2/, CRootOf\\x - x + 1, 3/, CRootOf\\x - x + 1, 4/]\n >>> r0\n / 5 \\\n CRootOf\\x - x + 1, 0/\ndiff --git a/doc/src/guides/solving/solve-matrix-equation.md b/doc/src/guides/solving/solve-matrix-equation.md\nindex 63cbdf449c4a..03317ba1d491 100644\n--- a/doc/src/guides/solving/solve-matrix-equation.md\n+++ b/doc/src/guides/solving/solve-matrix-equation.md\n@@ -227,17 +227,12 @@ symbolic matrix has 24 terms with four elements in each term:\n \u23a2 \u23a5\n \u23a3A\u2083\u2080 A\u2083\u2081 A\u2083\u2082 A\u2083\u2083\u23a6\n >>> A.det()\n-A\u2080\u2080\u22c5A\u2081\u2081\u22c5A\u2082\u2082\u22c5A\u2083\u2083 - A\u2080\u2080\u22c5A\u2081\u2081\u22c5A\u2082\u2083\u22c5A\u2083\u2082 - A\u2080\u2080\u22c5A\u2081\u2082\u22c5A\u2082\u2081\u22c5A\u2083\u2083 + A\u2080\u2080\u22c5A\u2081\u2082\u22c5A\u2082\u2083\u22c5A\u2083\u2081 + A\u2080\u2080\u22c5A\u2081 \u21aa\n-\n-\u21aa \u2083\u22c5A\u2082\u2081\u22c5A\u2083\u2082 - A\u2080\u2080\u22c5A\u2081\u2083\u22c5A\u2082\u2082\u22c5A\u2083\u2081 - A\u2080\u2081\u22c5A\u2081\u2080\u22c5A\u2082\u2082\u22c5A\u2083\u2083 + A\u2080\u2081\u22c5A\u2081\u2080\u22c5A\u2082\u2083\u22c5A\u2083\u2082 + A\u2080\u2081\u22c5A\u2081\u2082\u22c5A\u2082 \u21aa\n-\n-\u21aa \u2080\u22c5A\u2083\u2083 - A\u2080\u2081\u22c5A\u2081\u2082\u22c5A\u2082\u2083\u22c5A\u2083\u2080 - A\u2080\u2081\u22c5A\u2081\u2083\u22c5A\u2082\u2080\u22c5A\u2083\u2082 + A\u2080\u2081\u22c5A\u2081\u2083\u22c5A\u2082\u2082\u22c5A\u2083\u2080 + A\u2080\u2082\u22c5A\u2081\u2080\u22c5A\u2082\u2081\u22c5A\u2083 \u21aa\n-\n-\u21aa \u2083 - A\u2080\u2082\u22c5A\u2081\u2080\u22c5A\u2082\u2083\u22c5A\u2083\u2081 - A\u2080\u2082\u22c5A\u2081\u2081\u22c5A\u2082\u2080\u22c5A\u2083\u2083 + A\u2080\u2082\u22c5A\u2081\u2081\u22c5A\u2082\u2083\u22c5A\u2083\u2080 + A\u2080\u2082\u22c5A\u2081\u2083\u22c5A\u2082\u2080\u22c5A\u2083\u2081 - \u21aa\n-\n-\u21aa A\u2080\u2082\u22c5A\u2081\u2083\u22c5A\u2082\u2081\u22c5A\u2083\u2080 - A\u2080\u2083\u22c5A\u2081\u2080\u22c5A\u2082\u2081\u22c5A\u2083\u2082 + A\u2080\u2083\u22c5A\u2081\u2080\u22c5A\u2082\u2082\u22c5A\u2083\u2081 + A\u2080\u2083\u22c5A\u2081\u2081\u22c5A\u2082\u2080\u22c5A\u2083\u2082 - A\u2080\u2083\u22c5 \u21aa\n-\n-\u21aa A\u2081\u2081\u22c5A\u2082\u2082\u22c5A\u2083\u2080 - A\u2080\u2083\u22c5A\u2081\u2082\u22c5A\u2082\u2080\u22c5A\u2083\u2081 + A\u2080\u2083\u22c5A\u2081\u2082\u22c5A\u2082\u2081\u22c5A\u2083\u2080\n+A\u2080\u2080\u22c5A\u2081\u2081\u22c5A\u2082\u2082\u22c5A\u2083\u2083 - A\u2080\u2080\u22c5A\u2081\u2081\u22c5A\u2082\u2083\u22c5A\u2083\u2082 - A\u2080\u2080\u22c5A\u2081\u2082\u22c5A\u2082\u2081\u22c5A\u2083\u2083 + A\u2080\u2080\u22c5A\u2081\u2082\u22c5A\u2082\u2083\u22c5A\u2083\u2081 +\n+A\u2080\u2080\u22c5A\u2081\u2083\u22c5A\u2082\u2081\u22c5A\u2083\u2082 - A\u2080\u2080\u22c5A\u2081\u2083\u22c5A\u2082\u2082\u22c5A\u2083\u2081 - A\u2080\u2081\u22c5A\u2081\u2080\u22c5A\u2082\u2082\u22c5A\u2083\u2083 + A\u2080\u2081\u22c5A\u2081\u2080\u22c5A\u2082\u2083\u22c5A\u2083\u2082 +\n+A\u2080\u2081\u22c5A\u2081\u2082\u22c5A\u2082\u2080\u22c5A\u2083\u2083 - A\u2080\u2081\u22c5A\u2081\u2082\u22c5A\u2082\u2083\u22c5A\u2083\u2080 - A\u2080\u2081\u22c5A\u2081\u2083\u22c5A\u2082\u2080\u22c5A\u2083\u2082 + A\u2080\u2081\u22c5A\u2081\u2083\u22c5A\u2082\u2082\u22c5A\u2083\u2080 +\n+A\u2080\u2082\u22c5A\u2081\u2080\u22c5A\u2082\u2081\u22c5A\u2083\u2083 - A\u2080\u2082\u22c5A\u2081\u2080\u22c5A\u2082\u2083\u22c5A\u2083\u2081 - A\u2080\u2082\u22c5A\u2081\u2081\u22c5A\u2082\u2080\u22c5A\u2083\u2083 + A\u2080\u2082\u22c5A\u2081\u2081\u22c5A\u2082\u2083\u22c5A\u2083\u2080 +\n+A\u2080\u2082\u22c5A\u2081\u2083\u22c5A\u2082\u2080\u22c5A\u2083\u2081 - A\u2080\u2082\u22c5A\u2081\u2083\u22c5A\u2082\u2081\u22c5A\u2083\u2080 - A\u2080\u2083\u22c5A\u2081\u2080\u22c5A\u2082\u2081\u22c5A\u2083\u2082 + A\u2080\u2083\u22c5A\u2081\u2080\u22c5A\u2082\u2082\u22c5A\u2083\u2081 +\n+A\u2080\u2083\u22c5A\u2081\u2081\u22c5A\u2082\u2080\u22c5A\u2083\u2082 - A\u2080\u2083\u22c5A\u2081\u2081\u22c5A\u2082\u2082\u22c5A\u2083\u2080 - A\u2080\u2083\u22c5A\u2081\u2082\u22c5A\u2082\u2080\u22c5A\u2083\u2081 + A\u2080\u2083\u22c5A\u2081\u2082\u22c5A\u2082\u2081\u22c5A\u2083\u2080\n ```\n \n and solving a matrix equation of it takes about a minute, whereas the analogous\ndiff --git a/doc/src/modules/codegen.rst b/doc/src/modules/codegen.rst\nindex ee361481d1ae..873073097351 100644\n--- a/doc/src/modules/codegen.rst\n+++ b/doc/src/modules/codegen.rst\n@@ -164,9 +164,7 @@ An example of Mathematica code printer::\n \n >>> expr = summation(expr, (n, -1, 1))\n >>> mathematica_code(expr)\n- T*(x[-T]*Sin[(T + t)/T]/(T + t) + x[T]*Sin[(-T + t)/T]/(-T + t) + x[0]*Sin[t/T \u21aa\n- \n- \u21aa ]/t)\n+ T*(x[-T]*Sin[(T + t)/T]/(T + t) + x[T]*Sin[(-T + t)/T]/(-T + t) + x[0]*Sin[t/T]/t)\n \n We can go through a common expression in different languages we support and see\n how it works::\ndiff --git a/doc/src/modules/evalf.rst b/doc/src/modules/evalf.rst\nindex a5c7afd718ac..03dbb5f411b6 100644\n--- a/doc/src/modules/evalf.rst\n+++ b/doc/src/modules/evalf.rst\n@@ -286,7 +286,7 @@ Oscillatory quadrature requires an integrand containing a factor cos(ax+b) or\n sin(ax+b). Note that many other oscillatory integrals can be transformed to\n this form with a change of variables:\n \n- >>> init_printing(use_unicode=False, wrap_line=False)\n+ >>> init_printing(use_unicode=False)\n >>> intgrl = Integral(sin(1/x), (x, 0, 1)).transform(x, 1/x)\n >>> intgrl\n oo\ndiff --git a/doc/src/modules/integrals/integrals.rst b/doc/src/modules/integrals/integrals.rst\nindex 7c8aff8b9245..5efb1bc91d95 100644\n--- a/doc/src/modules/integrals/integrals.rst\n+++ b/doc/src/modules/integrals/integrals.rst\n@@ -16,7 +16,7 @@ Examples\n SymPy can integrate a vast array of functions. It can integrate polynomial functions::\n \n >>> from sympy import *\n- >>> init_printing(use_unicode=False, wrap_line=False)\n+ >>> init_printing(use_unicode=False)\n >>> x = Symbol('x')\n >>> integrate(x**2 + x + 1, x)\n 3 2\n@@ -266,7 +266,7 @@ For 2D Polygons\n Single Polynomial::\n \n >>> from sympy.integrals.intpoly import *\n- >>> init_printing(use_unicode=False, wrap_line=False)\n+ >>> init_printing(use_unicode=False)\n >>> polytope_integrate(Polygon((0, 0), (0, 1), (1, 0)), x)\n 1/6\n >>> polytope_integrate(Polygon((0, 0), (0, 1), (1, 0)), x + x*y + y**2)\ndiff --git a/doc/src/modules/matrices/matrices.rst b/doc/src/modules/matrices/matrices.rst\nindex 54a42fba2483..ad2fdfdddcde 100644\n--- a/doc/src/modules/matrices/matrices.rst\n+++ b/doc/src/modules/matrices/matrices.rst\n@@ -10,7 +10,7 @@ The linear algebra module is designed to be as simple as possible. First, we\n import and declare our first ``Matrix`` object:\n \n >>> from sympy.interactive.printing import init_printing\n- >>> init_printing(use_unicode=False, wrap_line=False)\n+ >>> init_printing(use_unicode=False)\n >>> from sympy.matrices import Matrix, eye, zeros, ones, diag, GramSchmidt\n >>> M = Matrix([[1,0,0], [0,0,0]]); M\n [1 0 0]\ndiff --git a/doc/src/modules/physics/continuum_mechanics/beam_problems.rst b/doc/src/modules/physics/continuum_mechanics/beam_problems.rst\nindex 39a2b90c04c0..307c6ea7107b 100644\n--- a/doc/src/modules/physics/continuum_mechanics/beam_problems.rst\n+++ b/doc/src/modules/physics/continuum_mechanics/beam_problems.rst\n@@ -11,7 +11,7 @@ To make this document easier to read, enable pretty printing:\n \n >>> from sympy import *\n >>> x, y, z = symbols('x y z')\n- >>> init_printing(use_unicode=True, wrap_line=False)\n+ >>> init_printing(use_unicode=True)\n \n Beam\n ====\ndiff --git a/doc/src/modules/polys/agca.rst b/doc/src/modules/polys/agca.rst\nindex 815078cdc3c9..1757d05d2c47 100644\n--- a/doc/src/modules/polys/agca.rst\n+++ b/doc/src/modules/polys/agca.rst\n@@ -52,7 +52,7 @@ All code examples assume::\n \n >>> from sympy import *\n >>> x, y, z = symbols('x,y,z')\n- >>> init_printing(use_unicode=True, wrap_line=False)\n+ >>> init_printing(use_unicode=True)\n \n Reference\n =========\ndiff --git a/doc/src/modules/polys/basics.rst b/doc/src/modules/polys/basics.rst\nindex 09a608c00251..9d3a9ebb1249 100644\n--- a/doc/src/modules/polys/basics.rst\n+++ b/doc/src/modules/polys/basics.rst\n@@ -12,7 +12,7 @@ polynomials within SymPy. All code examples assume::\n \n >>> from sympy import *\n >>> x, y, z = symbols('x,y,z')\n- >>> init_printing(use_unicode=False, wrap_line=False)\n+ >>> init_printing(use_unicode=False)\n \n Basic concepts\n ==============\ndiff --git a/doc/src/modules/polys/wester.rst b/doc/src/modules/polys/wester.rst\nindex e95c20a647af..6d8147857c66 100644\n--- a/doc/src/modules/polys/wester.rst\n+++ b/doc/src/modules/polys/wester.rst\n@@ -22,7 +22,7 @@ computations were done using the following setup::\n \n >>> from sympy import *\n \n- >>> init_printing(use_unicode=True, wrap_line=False)\n+ >>> init_printing(use_unicode=True)\n \n >>> var('x,y,z,s,c')\n (x, y, z, s, c)\ndiff --git a/sympy/categories/baseclasses.py b/sympy/categories/baseclasses.py\nindex 9c555b90fdec..e6ab5153ae4e 100644\n--- a/sympy/categories/baseclasses.py\n+++ b/sympy/categories/baseclasses.py\n@@ -609,9 +609,8 @@ class Diagram(Basic):\n >>> pprint(premises_keys, use_unicode=False)\n [g*f:A-->C, id:A-->A, id:B-->B, id:C-->C, f:A-->B, g:B-->C]\n >>> pprint(d.premises, use_unicode=False)\n- {g*f:A-->C: EmptySet, id:A-->A: EmptySet, id:B-->B: EmptySet, id:C-->C: EmptyS >\n- \n- > et, f:A-->B: EmptySet, g:B-->C: EmptySet}\n+ {g*f:A-->C: EmptySet, id:A-->A: EmptySet, id:B-->B: EmptySet,\n+ id:C-->C: EmptySet, f:A-->B: EmptySet, g:B-->C: EmptySet}\n >>> d = Diagram([f, g], {g * f: \"unique\"})\n >>> pprint(d.conclusions,use_unicode=False)\n {g*f:A-->C: {unique}}\ndiff --git a/sympy/physics/continuum_mechanics/beam.py b/sympy/physics/continuum_mechanics/beam.py\nindex e9303b7c991d..b89474a6b411 100644\n--- a/sympy/physics/continuum_mechanics/beam.py\n+++ b/sympy/physics/continuum_mechanics/beam.py\n@@ -2193,14 +2193,14 @@ def draw(self, pictorial=True):\n >>> p0, m0 = b.apply_support(0, \"fixed\")\n >>> p20 = b.apply_support(20, \"roller\")\n >>> p = b.draw() # doctest: +SKIP\n- >>> p # doctest: +ELLIPSIS\n+ >>> p # doctest: +ELLIPSIS,+SKIP\n Plot object containing:\n [0]: cartesian line: 25*SingularityFunction(x, 5, 0) - 25*SingularityFunction(x, 23, 0)\n + SingularityFunction(x, 30, 1) - 20*SingularityFunction(x, 50, 0)\n - SingularityFunction(x, 50, 1) + 5 for x over (0.0, 50.0)\n [1]: cartesian line: 5 for x over (0.0, 50.0)\n ...\n- >>> p.show()\n+ >>> p.show() # doctest: +SKIP\n \n \"\"\"\n if not numpy:\ndiff --git a/sympy/printing/llvmjitcode.py b/sympy/printing/llvmjitcode.py\nindex 5bba1003c87c..a7e68f34fb56 100644\n--- a/sympy/printing/llvmjitcode.py\n+++ b/sympy/printing/llvmjitcode.py\n@@ -400,6 +400,7 @@ def llvm_callable(args, expr, callback_type=None):\n \n \n Callbacks for integration functions can be JIT compiled.\n+\n >>> import sympy.printing.llvmjitcode as jit\n >>> from sympy.abc import a\n >>> from sympy import integrate\n@@ -427,6 +428,7 @@ def llvm_callable(args, expr, callback_type=None):\n expressions are given to cse, the compiled function returns a tuple.\n The 'cubature' callback handles multiple expressions (set `fdim`\n to match in the integration call.)\n+\n >>> import sympy.printing.llvmjitcode as jit\n >>> from sympy import cse\n >>> from sympy.abc import x,y\ndiff --git a/sympy/printing/pretty/stringpict.py b/sympy/printing/pretty/stringpict.py\nindex acbbba8834e4..b6055f09c83b 100644\n--- a/sympy/printing/pretty/stringpict.py\n+++ b/sympy/printing/pretty/stringpict.py\n@@ -17,6 +17,8 @@\n from .pretty_symbology import hobj, vobj, xsym, xobj, pretty_use_unicode, line_width, center\n from sympy.utilities.exceptions import sympy_deprecation_warning\n \n+_GLOBAL_WRAP_LINE = None\n+\n class stringPict:\n \"\"\"An ASCII picture.\n The pictures are represented as a list of equal length strings.\n@@ -251,6 +253,9 @@ def render(self, * args, **kwargs):\n break the expression in a form that can be printed\n on the terminal without being broken up.\n \"\"\"\n+ if _GLOBAL_WRAP_LINE is not None:\n+ kwargs[\"wrap_line\"] = _GLOBAL_WRAP_LINE\n+\n if kwargs[\"wrap_line\"] is False:\n return \"\\n\".join(self.picture)\n \ndiff --git a/sympy/solvers/ode/lie_group.py b/sympy/solvers/ode/lie_group.py\nindex 37a58ba85982..dd07f877c113 100644\n--- a/sympy/solvers/ode/lie_group.py\n+++ b/sympy/solvers/ode/lie_group.py\n@@ -191,16 +191,9 @@ def infinitesimals(eq, func=None, order=None, hint='default', match=None):\n >>> genform = Eq(eta.diff(x) + (eta.diff(y) - xi.diff(x))*h\n ... - (xi.diff(y))*h**2 - xi*(h.diff(x)) - eta*(h.diff(y)), 0)\n >>> pprint(genform)\n- /d d \\ d 2 d\n- >\n- |--(eta(x, y)) - --(xi(x, y))|*h(x, y) - eta(x, y)*--(h(x, y)) - h (x, y)*--(x\n- >\n- \\dy dx / dy dy\n- >\n- \n- > d d\n- > i(x, y)) - xi(x, y)*--(h(x, y)) + --(eta(x, y)) = 0\n- > dx dx\n+ /d d \\ d 2 d d d\n+ |--(eta(x, y)) - --(xi(x, y))|*h(x, y) - eta(x, y)*--(h(x, y)) - h (x, y)*--(xi(x, y)) - xi(x, y)*--(h(x, y)) + --(eta(x, y)) = 0\n+ \\dy dx / dy dy dx dx\n \n Solving the above mentioned PDE is not trivial, and can be solved only by\n making intelligent assumptions for `\\xi` and `\\eta` (heuristics). Once an\ndiff --git a/sympy/solvers/pde.py b/sympy/solvers/pde.py\nindex c46934fb128b..75e5503145af 100644\n--- a/sympy/solvers/pde.py\n+++ b/sympy/solvers/pde.py\n@@ -577,39 +577,22 @@ def pde_1st_linear_constant_coeff(eq, func, order, match, solvefun):\n a*--(f(x, y)) + b*--(f(x, y)) + c*f(x, y) - G(x, y)\n dx dy\n >>> pprint(pdsolve(genform, hint='1st_linear_constant_coeff_Integral'))\n- // a*x + b*y \\ >\n- || / | >\n- || | | >\n- || | c*xi | >\n- || | ------- | >\n- || | 2 2 | >\n- || | /a*xi + b*eta -a*eta + b*xi\\ a + b | >\n- || | G|------------, -------------|*e d(xi)| >\n- || | | 2 2 2 2 | | >\n- || | \\ a + b a + b / | >\n- || | | >\n- || / | >\n- || | >\n- f(x, y) = ||F(eta) + -------------------------------------------------------|* >\n- || 2 2 | >\n- \\\\ a + b / >\n- \n- > \\|\n- > ||\n- > ||\n- > ||\n- > ||\n- > ||\n- > ||\n- > ||\n- > ||\n- > -c*xi ||\n- > -------||\n- > 2 2||\n- > a + b ||\n- > e ||\n- > ||\n- > /|eta=-a*y + b*x, xi=a*x + b*y\n+ // a*x + b*y \\ \\|\n+ || / | ||\n+ || | | ||\n+ || | c*xi | ||\n+ || | ------- | ||\n+ || | 2 2 | ||\n+ || | /a*xi + b*eta -a*eta + b*xi\\ a + b | ||\n+ || | G|------------, -------------|*e d(xi)| ||\n+ || | | 2 2 2 2 | | ||\n+ || | \\ a + b a + b / | -c*xi ||\n+ || | | -------||\n+ || / | 2 2||\n+ || | a + b ||\n+ f(x, y) = ||F(eta) + -------------------------------------------------------|*e ||\n+ || 2 2 | ||\n+ \\\\ a + b / /|eta=-a*y + b*x, xi=a*x + b*y\n \n Examples\n ========\n", "test_patch": "diff --git a/conftest.py b/conftest.py\nindex 7e66cb185c76..7054ca679f9d 100644\n--- a/conftest.py\n+++ b/conftest.py\n@@ -16,7 +16,7 @@\n collect_ignore = _get_doctest_blacklist()\n \n # Set up printing for doctests\n-setup_pprint()\n+setup_pprint(disable_line_wrap=False)\n sys.__displayhook__ = sys.displayhook\n #from sympy import pprint_use_unicode\n #pprint_use_unicode(False)\ndiff --git a/sympy/testing/runtests.py b/sympy/testing/runtests.py\nindex 003e314e8062..47e24232c358 100644\n--- a/sympy/testing/runtests.py\n+++ b/sympy/testing/runtests.py\n@@ -153,20 +153,27 @@ def get_sympy_dir():\n return os.path.normcase(sympy_dir)\n \n \n-def setup_pprint():\n+def setup_pprint(disable_line_wrap=True):\n from sympy.interactive.printing import init_printing\n from sympy.printing.pretty.pretty import pprint_use_unicode\n import sympy.interactive.printing as interactive_printing\n+ from sympy.printing.pretty import stringpict\n+\n+ # Prevent init_printing() in doctests from affecting other doctests\n+ interactive_printing.NO_GLOBAL = True\n \n # force pprint to be in ascii mode in doctests\n use_unicode_prev = pprint_use_unicode(False)\n \n+ # disable line wrapping for pprint() outputs\n+ wrap_line_prev = stringpict._GLOBAL_WRAP_LINE\n+ if disable_line_wrap:\n+ stringpict._GLOBAL_WRAP_LINE = False\n+\n # hook our nice, hash-stable strprinter\n init_printing(pretty_print=False)\n \n- # Prevent init_printing() in doctests from affecting other doctests\n- interactive_printing.NO_GLOBAL = True\n- return use_unicode_prev\n+ return use_unicode_prev, wrap_line_prev\n \n \n @contextmanager\n@@ -787,6 +794,7 @@ def _doctest(*paths, **kwargs):\n ``doctest()`` and ``test()`` for more information.\n \"\"\"\n from sympy.printing.pretty.pretty import pprint_use_unicode\n+ from sympy.printing.pretty import stringpict\n \n normal = kwargs.get(\"normal\", False)\n verbose = kwargs.get(\"verbose\", False)\n@@ -887,7 +895,7 @@ def _doctest(*paths, **kwargs):\n continue\n old_displayhook = sys.displayhook\n try:\n- use_unicode_prev = setup_pprint()\n+ use_unicode_prev, wrap_line_prev = setup_pprint()\n out = sympytestfile(\n rst_file, module_relative=False, encoding='utf-8',\n optionflags=pdoctest.ELLIPSIS | pdoctest.NORMALIZE_WHITESPACE |\n@@ -901,6 +909,7 @@ def _doctest(*paths, **kwargs):\n import sympy.interactive.printing as interactive_printing\n interactive_printing.NO_GLOBAL = False\n pprint_use_unicode(use_unicode_prev)\n+ stringpict._GLOBAL_WRAP_LINE = wrap_line_prev\n \n rstfailed, tested = out\n if tested:\n@@ -1406,6 +1415,7 @@ def test_file(self, filename):\n from io import StringIO\n import sympy.interactive.printing as interactive_printing\n from sympy.printing.pretty.pretty import pprint_use_unicode\n+ from sympy.printing.pretty import stringpict\n \n rel_name = filename[len(self._root_dir) + 1:]\n dirname, file = os.path.split(filename)\n@@ -1473,7 +1483,7 @@ def test_file(self, filename):\n # comes by default with a \"from sympy import *\"\n #exec('from sympy import *') in test.globs\n old_displayhook = sys.displayhook\n- use_unicode_prev = setup_pprint()\n+ use_unicode_prev, wrap_line_prev = setup_pprint()\n \n try:\n f, t = runner.run(test,\n@@ -1489,6 +1499,7 @@ def test_file(self, filename):\n sys.displayhook = old_displayhook\n interactive_printing.NO_GLOBAL = False\n pprint_use_unicode(use_unicode_prev)\n+ stringpict._GLOBAL_WRAP_LINE = wrap_line_prev\n \n self._reporter.leaving_filename()\n \n", "problem_statement": "Doctest failures due to line wrapping in the printers\nRealted to gh-26221.\r\n\r\nAfter gh-25673 the printers are changed to add extra characters when wrapping lines. This causes the doctests to fail when run outside of CI e.g.:\r\n```console\r\n$ bin/doctest sympy/categories/baseclasses.py\r\n================================================================================================ test process starts =================================================================================================\r\nexecutable: /Users/enojb/.pyenv/versions/sympy-3.12.git/bin/python (3.12.2-final-0) [CPython]\r\narchitecture: 64-bit\r\ncache: yes\r\nground types: python\r\nnumpy: 1.26.4\r\nhash randomization: on (PYTHONHASHSEED=3926478540)\r\n\r\nsympy/categories/baseclasses.py[23] .......F............... [FAIL]\r\n\r\n______________________________________________________________________________________________________________________________________________________________________________________________________________________\r\n________________________________________________________________________________________ sympy.categories.baseclasses.Diagram ________________________________________________________________________________________\r\nFile \"/Users/enojb/work/dev/sympy/sympy/categories/baseclasses.py\", line 611, in sympy.categories.baseclasses.Diagram\r\nFailed example:\r\n pprint(d.premises, use_unicode=False)\r\nExpected:\r\n {g*f:A-->C: EmptySet, id:A-->A: EmptySet, id:B-->B: EmptySet, id:C-->C: EmptyS >\r\n \r\n > et, f:A-->B: EmptySet, g:B-->C: EmptySet}\r\nGot:\r\n {g*f:A-->C: EmptySet, id:A-->A: EmptySet, id:B-->B: EmptySet, id:C-->C: EmptySet, f:A-->B: EmptySet, g:B-->C: EmptySet}\r\n\r\n================================================================================ tests finished: 22 passed, 1 failed, in 0.12 seconds ================================================================================\r\nDO *NOT* COMMIT!\r\n```\r\nThe problem seems to be that the doctests effectively hardcode a particular terminal width that works in CI but not anywhere else.\r\n\r\nI think that this printing change should be reverted but either way the doctests should be made to work.\r\n\r\nCC @smichr\n", "hints_text": "Both the new and the old line wrapping methods aren't great for doctests. Most doctests should not be using pprint to begin with. For this particular one, the best option would be to disable wrapping in the doctests and manually wrap. ", "created_at": "2024-06-04T23:07:05Z" }, { "repo": "sympy/sympy", "pull_number": 26671, "instance_id": "sympy__sympy-26671", "issue_numbers": [ "26318" ], "base_commit": "ae3027b4ca682023dee36e7e8883311c62cc08e3", "patch": "diff --git a/doc/src/guides/solving/find-roots-polynomial.md b/doc/src/guides/solving/find-roots-polynomial.md\nindex 18f5cb3401ca..6ec9af206bb3 100644\n--- a/doc/src/guides/solving/find-roots-polynomial.md\n+++ b/doc/src/guides/solving/find-roots-polynomial.md\n@@ -504,11 +504,8 @@ symbolically.\n / 5 \\\n [CRootOf\\x - x + 1, 0/]\n >>> r = r0, r1, r2, r3, r4 = Poly(fifth_order, x).all_roots(); r\n- / 5 \\ / 5 \\ / 5 \\ >\n-[CRootOf\\x - x + 1, 0/, CRootOf\\x - x + 1, 1/, CRootOf\\x - x + 1, 2/, CRoot >\n-\n-> / 5 \\ / 5 \\\n-> Of\\x - x + 1, 3/, CRootOf\\x - x + 1, 4/]\n+ / 5 \\ / 5 \\ / 5 \\ / 5 \\ / 5 \\\n+[CRootOf\\x - x + 1, 0/, CRootOf\\x - x + 1, 1/, CRootOf\\x - x + 1, 2/, CRootOf\\x - x + 1, 3/, CRootOf\\x - x + 1, 4/]\n >>> r0\n / 5 \\\n CRootOf\\x - x + 1, 0/\ndiff --git a/doc/src/guides/solving/solve-matrix-equation.md b/doc/src/guides/solving/solve-matrix-equation.md\nindex 63cbdf449c4a..03317ba1d491 100644\n--- a/doc/src/guides/solving/solve-matrix-equation.md\n+++ b/doc/src/guides/solving/solve-matrix-equation.md\n@@ -227,17 +227,12 @@ symbolic matrix has 24 terms with four elements in each term:\n \u23a2 \u23a5\n \u23a3A\u2083\u2080 A\u2083\u2081 A\u2083\u2082 A\u2083\u2083\u23a6\n >>> A.det()\n-A\u2080\u2080\u22c5A\u2081\u2081\u22c5A\u2082\u2082\u22c5A\u2083\u2083 - A\u2080\u2080\u22c5A\u2081\u2081\u22c5A\u2082\u2083\u22c5A\u2083\u2082 - A\u2080\u2080\u22c5A\u2081\u2082\u22c5A\u2082\u2081\u22c5A\u2083\u2083 + A\u2080\u2080\u22c5A\u2081\u2082\u22c5A\u2082\u2083\u22c5A\u2083\u2081 + A\u2080\u2080\u22c5A\u2081 \u21aa\n-\n-\u21aa \u2083\u22c5A\u2082\u2081\u22c5A\u2083\u2082 - A\u2080\u2080\u22c5A\u2081\u2083\u22c5A\u2082\u2082\u22c5A\u2083\u2081 - A\u2080\u2081\u22c5A\u2081\u2080\u22c5A\u2082\u2082\u22c5A\u2083\u2083 + A\u2080\u2081\u22c5A\u2081\u2080\u22c5A\u2082\u2083\u22c5A\u2083\u2082 + A\u2080\u2081\u22c5A\u2081\u2082\u22c5A\u2082 \u21aa\n-\n-\u21aa \u2080\u22c5A\u2083\u2083 - A\u2080\u2081\u22c5A\u2081\u2082\u22c5A\u2082\u2083\u22c5A\u2083\u2080 - A\u2080\u2081\u22c5A\u2081\u2083\u22c5A\u2082\u2080\u22c5A\u2083\u2082 + A\u2080\u2081\u22c5A\u2081\u2083\u22c5A\u2082\u2082\u22c5A\u2083\u2080 + A\u2080\u2082\u22c5A\u2081\u2080\u22c5A\u2082\u2081\u22c5A\u2083 \u21aa\n-\n-\u21aa \u2083 - A\u2080\u2082\u22c5A\u2081\u2080\u22c5A\u2082\u2083\u22c5A\u2083\u2081 - A\u2080\u2082\u22c5A\u2081\u2081\u22c5A\u2082\u2080\u22c5A\u2083\u2083 + A\u2080\u2082\u22c5A\u2081\u2081\u22c5A\u2082\u2083\u22c5A\u2083\u2080 + A\u2080\u2082\u22c5A\u2081\u2083\u22c5A\u2082\u2080\u22c5A\u2083\u2081 - \u21aa\n-\n-\u21aa A\u2080\u2082\u22c5A\u2081\u2083\u22c5A\u2082\u2081\u22c5A\u2083\u2080 - A\u2080\u2083\u22c5A\u2081\u2080\u22c5A\u2082\u2081\u22c5A\u2083\u2082 + A\u2080\u2083\u22c5A\u2081\u2080\u22c5A\u2082\u2082\u22c5A\u2083\u2081 + A\u2080\u2083\u22c5A\u2081\u2081\u22c5A\u2082\u2080\u22c5A\u2083\u2082 - A\u2080\u2083\u22c5 \u21aa\n-\n-\u21aa A\u2081\u2081\u22c5A\u2082\u2082\u22c5A\u2083\u2080 - A\u2080\u2083\u22c5A\u2081\u2082\u22c5A\u2082\u2080\u22c5A\u2083\u2081 + A\u2080\u2083\u22c5A\u2081\u2082\u22c5A\u2082\u2081\u22c5A\u2083\u2080\n+A\u2080\u2080\u22c5A\u2081\u2081\u22c5A\u2082\u2082\u22c5A\u2083\u2083 - A\u2080\u2080\u22c5A\u2081\u2081\u22c5A\u2082\u2083\u22c5A\u2083\u2082 - A\u2080\u2080\u22c5A\u2081\u2082\u22c5A\u2082\u2081\u22c5A\u2083\u2083 + A\u2080\u2080\u22c5A\u2081\u2082\u22c5A\u2082\u2083\u22c5A\u2083\u2081 +\n+A\u2080\u2080\u22c5A\u2081\u2083\u22c5A\u2082\u2081\u22c5A\u2083\u2082 - A\u2080\u2080\u22c5A\u2081\u2083\u22c5A\u2082\u2082\u22c5A\u2083\u2081 - A\u2080\u2081\u22c5A\u2081\u2080\u22c5A\u2082\u2082\u22c5A\u2083\u2083 + A\u2080\u2081\u22c5A\u2081\u2080\u22c5A\u2082\u2083\u22c5A\u2083\u2082 +\n+A\u2080\u2081\u22c5A\u2081\u2082\u22c5A\u2082\u2080\u22c5A\u2083\u2083 - A\u2080\u2081\u22c5A\u2081\u2082\u22c5A\u2082\u2083\u22c5A\u2083\u2080 - A\u2080\u2081\u22c5A\u2081\u2083\u22c5A\u2082\u2080\u22c5A\u2083\u2082 + A\u2080\u2081\u22c5A\u2081\u2083\u22c5A\u2082\u2082\u22c5A\u2083\u2080 +\n+A\u2080\u2082\u22c5A\u2081\u2080\u22c5A\u2082\u2081\u22c5A\u2083\u2083 - A\u2080\u2082\u22c5A\u2081\u2080\u22c5A\u2082\u2083\u22c5A\u2083\u2081 - A\u2080\u2082\u22c5A\u2081\u2081\u22c5A\u2082\u2080\u22c5A\u2083\u2083 + A\u2080\u2082\u22c5A\u2081\u2081\u22c5A\u2082\u2083\u22c5A\u2083\u2080 +\n+A\u2080\u2082\u22c5A\u2081\u2083\u22c5A\u2082\u2080\u22c5A\u2083\u2081 - A\u2080\u2082\u22c5A\u2081\u2083\u22c5A\u2082\u2081\u22c5A\u2083\u2080 - A\u2080\u2083\u22c5A\u2081\u2080\u22c5A\u2082\u2081\u22c5A\u2083\u2082 + A\u2080\u2083\u22c5A\u2081\u2080\u22c5A\u2082\u2082\u22c5A\u2083\u2081 +\n+A\u2080\u2083\u22c5A\u2081\u2081\u22c5A\u2082\u2080\u22c5A\u2083\u2082 - A\u2080\u2083\u22c5A\u2081\u2081\u22c5A\u2082\u2082\u22c5A\u2083\u2080 - A\u2080\u2083\u22c5A\u2081\u2082\u22c5A\u2082\u2080\u22c5A\u2083\u2081 + A\u2080\u2083\u22c5A\u2081\u2082\u22c5A\u2082\u2081\u22c5A\u2083\u2080\n ```\n \n and solving a matrix equation of it takes about a minute, whereas the analogous\ndiff --git a/doc/src/modules/codegen.rst b/doc/src/modules/codegen.rst\nindex ee361481d1ae..873073097351 100644\n--- a/doc/src/modules/codegen.rst\n+++ b/doc/src/modules/codegen.rst\n@@ -164,9 +164,7 @@ An example of Mathematica code printer::\n \n >>> expr = summation(expr, (n, -1, 1))\n >>> mathematica_code(expr)\n- T*(x[-T]*Sin[(T + t)/T]/(T + t) + x[T]*Sin[(-T + t)/T]/(-T + t) + x[0]*Sin[t/T \u21aa\n- \n- \u21aa ]/t)\n+ T*(x[-T]*Sin[(T + t)/T]/(T + t) + x[T]*Sin[(-T + t)/T]/(-T + t) + x[0]*Sin[t/T]/t)\n \n We can go through a common expression in different languages we support and see\n how it works::\ndiff --git a/doc/src/modules/evalf.rst b/doc/src/modules/evalf.rst\nindex a5c7afd718ac..03dbb5f411b6 100644\n--- a/doc/src/modules/evalf.rst\n+++ b/doc/src/modules/evalf.rst\n@@ -286,7 +286,7 @@ Oscillatory quadrature requires an integrand containing a factor cos(ax+b) or\n sin(ax+b). Note that many other oscillatory integrals can be transformed to\n this form with a change of variables:\n \n- >>> init_printing(use_unicode=False, wrap_line=False)\n+ >>> init_printing(use_unicode=False)\n >>> intgrl = Integral(sin(1/x), (x, 0, 1)).transform(x, 1/x)\n >>> intgrl\n oo\ndiff --git a/doc/src/modules/integrals/integrals.rst b/doc/src/modules/integrals/integrals.rst\nindex 7c8aff8b9245..5efb1bc91d95 100644\n--- a/doc/src/modules/integrals/integrals.rst\n+++ b/doc/src/modules/integrals/integrals.rst\n@@ -16,7 +16,7 @@ Examples\n SymPy can integrate a vast array of functions. It can integrate polynomial functions::\n \n >>> from sympy import *\n- >>> init_printing(use_unicode=False, wrap_line=False)\n+ >>> init_printing(use_unicode=False)\n >>> x = Symbol('x')\n >>> integrate(x**2 + x + 1, x)\n 3 2\n@@ -266,7 +266,7 @@ For 2D Polygons\n Single Polynomial::\n \n >>> from sympy.integrals.intpoly import *\n- >>> init_printing(use_unicode=False, wrap_line=False)\n+ >>> init_printing(use_unicode=False)\n >>> polytope_integrate(Polygon((0, 0), (0, 1), (1, 0)), x)\n 1/6\n >>> polytope_integrate(Polygon((0, 0), (0, 1), (1, 0)), x + x*y + y**2)\ndiff --git a/doc/src/modules/matrices/matrices.rst b/doc/src/modules/matrices/matrices.rst\nindex 54a42fba2483..ad2fdfdddcde 100644\n--- a/doc/src/modules/matrices/matrices.rst\n+++ b/doc/src/modules/matrices/matrices.rst\n@@ -10,7 +10,7 @@ The linear algebra module is designed to be as simple as possible. First, we\n import and declare our first ``Matrix`` object:\n \n >>> from sympy.interactive.printing import init_printing\n- >>> init_printing(use_unicode=False, wrap_line=False)\n+ >>> init_printing(use_unicode=False)\n >>> from sympy.matrices import Matrix, eye, zeros, ones, diag, GramSchmidt\n >>> M = Matrix([[1,0,0], [0,0,0]]); M\n [1 0 0]\ndiff --git a/doc/src/modules/physics/continuum_mechanics/beam_problems.rst b/doc/src/modules/physics/continuum_mechanics/beam_problems.rst\nindex 39a2b90c04c0..307c6ea7107b 100644\n--- a/doc/src/modules/physics/continuum_mechanics/beam_problems.rst\n+++ b/doc/src/modules/physics/continuum_mechanics/beam_problems.rst\n@@ -11,7 +11,7 @@ To make this document easier to read, enable pretty printing:\n \n >>> from sympy import *\n >>> x, y, z = symbols('x y z')\n- >>> init_printing(use_unicode=True, wrap_line=False)\n+ >>> init_printing(use_unicode=True)\n \n Beam\n ====\ndiff --git a/doc/src/modules/polys/agca.rst b/doc/src/modules/polys/agca.rst\nindex 815078cdc3c9..1757d05d2c47 100644\n--- a/doc/src/modules/polys/agca.rst\n+++ b/doc/src/modules/polys/agca.rst\n@@ -52,7 +52,7 @@ All code examples assume::\n \n >>> from sympy import *\n >>> x, y, z = symbols('x,y,z')\n- >>> init_printing(use_unicode=True, wrap_line=False)\n+ >>> init_printing(use_unicode=True)\n \n Reference\n =========\ndiff --git a/doc/src/modules/polys/basics.rst b/doc/src/modules/polys/basics.rst\nindex 09a608c00251..9d3a9ebb1249 100644\n--- a/doc/src/modules/polys/basics.rst\n+++ b/doc/src/modules/polys/basics.rst\n@@ -12,7 +12,7 @@ polynomials within SymPy. All code examples assume::\n \n >>> from sympy import *\n >>> x, y, z = symbols('x,y,z')\n- >>> init_printing(use_unicode=False, wrap_line=False)\n+ >>> init_printing(use_unicode=False)\n \n Basic concepts\n ==============\ndiff --git a/doc/src/modules/polys/wester.rst b/doc/src/modules/polys/wester.rst\nindex e95c20a647af..6d8147857c66 100644\n--- a/doc/src/modules/polys/wester.rst\n+++ b/doc/src/modules/polys/wester.rst\n@@ -22,7 +22,7 @@ computations were done using the following setup::\n \n >>> from sympy import *\n \n- >>> init_printing(use_unicode=True, wrap_line=False)\n+ >>> init_printing(use_unicode=True)\n \n >>> var('x,y,z,s,c')\n (x, y, z, s, c)\ndiff --git a/sympy/categories/baseclasses.py b/sympy/categories/baseclasses.py\nindex 9c555b90fdec..e6ab5153ae4e 100644\n--- a/sympy/categories/baseclasses.py\n+++ b/sympy/categories/baseclasses.py\n@@ -609,9 +609,8 @@ class Diagram(Basic):\n >>> pprint(premises_keys, use_unicode=False)\n [g*f:A-->C, id:A-->A, id:B-->B, id:C-->C, f:A-->B, g:B-->C]\n >>> pprint(d.premises, use_unicode=False)\n- {g*f:A-->C: EmptySet, id:A-->A: EmptySet, id:B-->B: EmptySet, id:C-->C: EmptyS >\n- \n- > et, f:A-->B: EmptySet, g:B-->C: EmptySet}\n+ {g*f:A-->C: EmptySet, id:A-->A: EmptySet, id:B-->B: EmptySet,\n+ id:C-->C: EmptySet, f:A-->B: EmptySet, g:B-->C: EmptySet}\n >>> d = Diagram([f, g], {g * f: \"unique\"})\n >>> pprint(d.conclusions,use_unicode=False)\n {g*f:A-->C: {unique}}\ndiff --git a/sympy/physics/continuum_mechanics/beam.py b/sympy/physics/continuum_mechanics/beam.py\nindex e9303b7c991d..b89474a6b411 100644\n--- a/sympy/physics/continuum_mechanics/beam.py\n+++ b/sympy/physics/continuum_mechanics/beam.py\n@@ -2193,14 +2193,14 @@ def draw(self, pictorial=True):\n >>> p0, m0 = b.apply_support(0, \"fixed\")\n >>> p20 = b.apply_support(20, \"roller\")\n >>> p = b.draw() # doctest: +SKIP\n- >>> p # doctest: +ELLIPSIS\n+ >>> p # doctest: +ELLIPSIS,+SKIP\n Plot object containing:\n [0]: cartesian line: 25*SingularityFunction(x, 5, 0) - 25*SingularityFunction(x, 23, 0)\n + SingularityFunction(x, 30, 1) - 20*SingularityFunction(x, 50, 0)\n - SingularityFunction(x, 50, 1) + 5 for x over (0.0, 50.0)\n [1]: cartesian line: 5 for x over (0.0, 50.0)\n ...\n- >>> p.show()\n+ >>> p.show() # doctest: +SKIP\n \n \"\"\"\n if not numpy:\ndiff --git a/sympy/printing/llvmjitcode.py b/sympy/printing/llvmjitcode.py\nindex 5bba1003c87c..a7e68f34fb56 100644\n--- a/sympy/printing/llvmjitcode.py\n+++ b/sympy/printing/llvmjitcode.py\n@@ -400,6 +400,7 @@ def llvm_callable(args, expr, callback_type=None):\n \n \n Callbacks for integration functions can be JIT compiled.\n+\n >>> import sympy.printing.llvmjitcode as jit\n >>> from sympy.abc import a\n >>> from sympy import integrate\n@@ -427,6 +428,7 @@ def llvm_callable(args, expr, callback_type=None):\n expressions are given to cse, the compiled function returns a tuple.\n The 'cubature' callback handles multiple expressions (set `fdim`\n to match in the integration call.)\n+\n >>> import sympy.printing.llvmjitcode as jit\n >>> from sympy import cse\n >>> from sympy.abc import x,y\ndiff --git a/sympy/printing/pretty/stringpict.py b/sympy/printing/pretty/stringpict.py\nindex acbbba8834e4..b6055f09c83b 100644\n--- a/sympy/printing/pretty/stringpict.py\n+++ b/sympy/printing/pretty/stringpict.py\n@@ -17,6 +17,8 @@\n from .pretty_symbology import hobj, vobj, xsym, xobj, pretty_use_unicode, line_width, center\n from sympy.utilities.exceptions import sympy_deprecation_warning\n \n+_GLOBAL_WRAP_LINE = None\n+\n class stringPict:\n \"\"\"An ASCII picture.\n The pictures are represented as a list of equal length strings.\n@@ -251,6 +253,9 @@ def render(self, * args, **kwargs):\n break the expression in a form that can be printed\n on the terminal without being broken up.\n \"\"\"\n+ if _GLOBAL_WRAP_LINE is not None:\n+ kwargs[\"wrap_line\"] = _GLOBAL_WRAP_LINE\n+\n if kwargs[\"wrap_line\"] is False:\n return \"\\n\".join(self.picture)\n \ndiff --git a/sympy/solvers/ode/lie_group.py b/sympy/solvers/ode/lie_group.py\nindex 37a58ba85982..dd07f877c113 100644\n--- a/sympy/solvers/ode/lie_group.py\n+++ b/sympy/solvers/ode/lie_group.py\n@@ -191,16 +191,9 @@ def infinitesimals(eq, func=None, order=None, hint='default', match=None):\n >>> genform = Eq(eta.diff(x) + (eta.diff(y) - xi.diff(x))*h\n ... - (xi.diff(y))*h**2 - xi*(h.diff(x)) - eta*(h.diff(y)), 0)\n >>> pprint(genform)\n- /d d \\ d 2 d\n- >\n- |--(eta(x, y)) - --(xi(x, y))|*h(x, y) - eta(x, y)*--(h(x, y)) - h (x, y)*--(x\n- >\n- \\dy dx / dy dy\n- >\n- \n- > d d\n- > i(x, y)) - xi(x, y)*--(h(x, y)) + --(eta(x, y)) = 0\n- > dx dx\n+ /d d \\ d 2 d d d\n+ |--(eta(x, y)) - --(xi(x, y))|*h(x, y) - eta(x, y)*--(h(x, y)) - h (x, y)*--(xi(x, y)) - xi(x, y)*--(h(x, y)) + --(eta(x, y)) = 0\n+ \\dy dx / dy dy dx dx\n \n Solving the above mentioned PDE is not trivial, and can be solved only by\n making intelligent assumptions for `\\xi` and `\\eta` (heuristics). Once an\ndiff --git a/sympy/solvers/pde.py b/sympy/solvers/pde.py\nindex c46934fb128b..75e5503145af 100644\n--- a/sympy/solvers/pde.py\n+++ b/sympy/solvers/pde.py\n@@ -577,39 +577,22 @@ def pde_1st_linear_constant_coeff(eq, func, order, match, solvefun):\n a*--(f(x, y)) + b*--(f(x, y)) + c*f(x, y) - G(x, y)\n dx dy\n >>> pprint(pdsolve(genform, hint='1st_linear_constant_coeff_Integral'))\n- // a*x + b*y \\ >\n- || / | >\n- || | | >\n- || | c*xi | >\n- || | ------- | >\n- || | 2 2 | >\n- || | /a*xi + b*eta -a*eta + b*xi\\ a + b | >\n- || | G|------------, -------------|*e d(xi)| >\n- || | | 2 2 2 2 | | >\n- || | \\ a + b a + b / | >\n- || | | >\n- || / | >\n- || | >\n- f(x, y) = ||F(eta) + -------------------------------------------------------|* >\n- || 2 2 | >\n- \\\\ a + b / >\n- \n- > \\|\n- > ||\n- > ||\n- > ||\n- > ||\n- > ||\n- > ||\n- > ||\n- > ||\n- > -c*xi ||\n- > -------||\n- > 2 2||\n- > a + b ||\n- > e ||\n- > ||\n- > /|eta=-a*y + b*x, xi=a*x + b*y\n+ // a*x + b*y \\ \\|\n+ || / | ||\n+ || | | ||\n+ || | c*xi | ||\n+ || | ------- | ||\n+ || | 2 2 | ||\n+ || | /a*xi + b*eta -a*eta + b*xi\\ a + b | ||\n+ || | G|------------, -------------|*e d(xi)| ||\n+ || | | 2 2 2 2 | | ||\n+ || | \\ a + b a + b / | -c*xi ||\n+ || | | -------||\n+ || / | 2 2||\n+ || | a + b ||\n+ f(x, y) = ||F(eta) + -------------------------------------------------------|*e ||\n+ || 2 2 | ||\n+ \\\\ a + b / /|eta=-a*y + b*x, xi=a*x + b*y\n \n Examples\n ========\n", "test_patch": "diff --git a/conftest.py b/conftest.py\nindex 7e66cb185c76..7054ca679f9d 100644\n--- a/conftest.py\n+++ b/conftest.py\n@@ -16,7 +16,7 @@\n collect_ignore = _get_doctest_blacklist()\n \n # Set up printing for doctests\n-setup_pprint()\n+setup_pprint(disable_line_wrap=False)\n sys.__displayhook__ = sys.displayhook\n #from sympy import pprint_use_unicode\n #pprint_use_unicode(False)\ndiff --git a/sympy/testing/runtests.py b/sympy/testing/runtests.py\nindex 003e314e8062..47e24232c358 100644\n--- a/sympy/testing/runtests.py\n+++ b/sympy/testing/runtests.py\n@@ -153,20 +153,27 @@ def get_sympy_dir():\n return os.path.normcase(sympy_dir)\n \n \n-def setup_pprint():\n+def setup_pprint(disable_line_wrap=True):\n from sympy.interactive.printing import init_printing\n from sympy.printing.pretty.pretty import pprint_use_unicode\n import sympy.interactive.printing as interactive_printing\n+ from sympy.printing.pretty import stringpict\n+\n+ # Prevent init_printing() in doctests from affecting other doctests\n+ interactive_printing.NO_GLOBAL = True\n \n # force pprint to be in ascii mode in doctests\n use_unicode_prev = pprint_use_unicode(False)\n \n+ # disable line wrapping for pprint() outputs\n+ wrap_line_prev = stringpict._GLOBAL_WRAP_LINE\n+ if disable_line_wrap:\n+ stringpict._GLOBAL_WRAP_LINE = False\n+\n # hook our nice, hash-stable strprinter\n init_printing(pretty_print=False)\n \n- # Prevent init_printing() in doctests from affecting other doctests\n- interactive_printing.NO_GLOBAL = True\n- return use_unicode_prev\n+ return use_unicode_prev, wrap_line_prev\n \n \n @contextmanager\n@@ -787,6 +794,7 @@ def _doctest(*paths, **kwargs):\n ``doctest()`` and ``test()`` for more information.\n \"\"\"\n from sympy.printing.pretty.pretty import pprint_use_unicode\n+ from sympy.printing.pretty import stringpict\n \n normal = kwargs.get(\"normal\", False)\n verbose = kwargs.get(\"verbose\", False)\n@@ -887,7 +895,7 @@ def _doctest(*paths, **kwargs):\n continue\n old_displayhook = sys.displayhook\n try:\n- use_unicode_prev = setup_pprint()\n+ use_unicode_prev, wrap_line_prev = setup_pprint()\n out = sympytestfile(\n rst_file, module_relative=False, encoding='utf-8',\n optionflags=pdoctest.ELLIPSIS | pdoctest.NORMALIZE_WHITESPACE |\n@@ -901,6 +909,7 @@ def _doctest(*paths, **kwargs):\n import sympy.interactive.printing as interactive_printing\n interactive_printing.NO_GLOBAL = False\n pprint_use_unicode(use_unicode_prev)\n+ stringpict._GLOBAL_WRAP_LINE = wrap_line_prev\n \n rstfailed, tested = out\n if tested:\n@@ -1406,6 +1415,7 @@ def test_file(self, filename):\n from io import StringIO\n import sympy.interactive.printing as interactive_printing\n from sympy.printing.pretty.pretty import pprint_use_unicode\n+ from sympy.printing.pretty import stringpict\n \n rel_name = filename[len(self._root_dir) + 1:]\n dirname, file = os.path.split(filename)\n@@ -1473,7 +1483,7 @@ def test_file(self, filename):\n # comes by default with a \"from sympy import *\"\n #exec('from sympy import *') in test.globs\n old_displayhook = sys.displayhook\n- use_unicode_prev = setup_pprint()\n+ use_unicode_prev, wrap_line_prev = setup_pprint()\n \n try:\n f, t = runner.run(test,\n@@ -1489,6 +1499,7 @@ def test_file(self, filename):\n sys.displayhook = old_displayhook\n interactive_printing.NO_GLOBAL = False\n pprint_use_unicode(use_unicode_prev)\n+ stringpict._GLOBAL_WRAP_LINE = wrap_line_prev\n \n self._reporter.leaving_filename()\n \n", "problem_statement": "Doctest failures due to line wrapping in the printers\nRealted to gh-26221.\r\n\r\nAfter gh-25673 the printers are changed to add extra characters when wrapping lines. This causes the doctests to fail when run outside of CI e.g.:\r\n```console\r\n$ bin/doctest sympy/categories/baseclasses.py\r\n================================================================================================ test process starts =================================================================================================\r\nexecutable: /Users/enojb/.pyenv/versions/sympy-3.12.git/bin/python (3.12.2-final-0) [CPython]\r\narchitecture: 64-bit\r\ncache: yes\r\nground types: python\r\nnumpy: 1.26.4\r\nhash randomization: on (PYTHONHASHSEED=3926478540)\r\n\r\nsympy/categories/baseclasses.py[23] .......F............... [FAIL]\r\n\r\n______________________________________________________________________________________________________________________________________________________________________________________________________________________\r\n________________________________________________________________________________________ sympy.categories.baseclasses.Diagram ________________________________________________________________________________________\r\nFile \"/Users/enojb/work/dev/sympy/sympy/categories/baseclasses.py\", line 611, in sympy.categories.baseclasses.Diagram\r\nFailed example:\r\n pprint(d.premises, use_unicode=False)\r\nExpected:\r\n {g*f:A-->C: EmptySet, id:A-->A: EmptySet, id:B-->B: EmptySet, id:C-->C: EmptyS >\r\n \r\n > et, f:A-->B: EmptySet, g:B-->C: EmptySet}\r\nGot:\r\n {g*f:A-->C: EmptySet, id:A-->A: EmptySet, id:B-->B: EmptySet, id:C-->C: EmptySet, f:A-->B: EmptySet, g:B-->C: EmptySet}\r\n\r\n================================================================================ tests finished: 22 passed, 1 failed, in 0.12 seconds ================================================================================\r\nDO *NOT* COMMIT!\r\n```\r\nThe problem seems to be that the doctests effectively hardcode a particular terminal width that works in CI but not anywhere else.\r\n\r\nI think that this printing change should be reverted but either way the doctests should be made to work.\r\n\r\nCC @smichr\n", "hints_text": "Both the new and the old line wrapping methods aren't great for doctests. Most doctests should not be using pprint to begin with. For this particular one, the best option would be to disable wrapping in the doctests and manually wrap. ", "created_at": "2024-06-04T21:24:06Z" }, { "repo": "sympy/sympy", "pull_number": 26656, "instance_id": "sympy__sympy-26656", "issue_numbers": [ "26652" ], "base_commit": "08a2751ba399fd0651cfd2a3e3bdcf2ab8e6e9af", "patch": "diff --git a/sympy/printing/latex.py b/sympy/printing/latex.py\nindex fa81c58d26fa..9164065bc4d0 100644\n--- a/sympy/printing/latex.py\n+++ b/sympy/printing/latex.py\n@@ -1889,7 +1889,8 @@ def _print_NDimArray(self, expr: NDimArray):\n block_str = r'\\begin{%MATSTR%}%s\\end{%MATSTR%}'\n block_str = block_str.replace('%MATSTR%', mat_str)\n if mat_str == 'array':\n- block_str= block_str.replace('%s','{}%s')\n+ block_str = block_str.replace('%s', '{' + 'c'*expr.shape[0] + '}%s')\n+\n if self._settings['mat_delim']:\n left_delim: str = self._settings['mat_delim']\n right_delim = self._delim_dict[left_delim]\n", "test_patch": "diff --git a/sympy/printing/tests/test_latex.py b/sympy/printing/tests/test_latex.py\nindex f647a98f1750..28872fb3a7d1 100644\n--- a/sympy/printing/tests/test_latex.py\n+++ b/sympy/printing/tests/test_latex.py\n@@ -3134,8 +3134,8 @@ def test_Array():\n assert latex(arr) == r'\\left[\\begin{matrix}0 & 1 & 2 & 3 & 4 & 5 & 6 & 7 & 8 & 9\\end{matrix}\\right]'\n \n arr = Array(range(11))\n- # added empty arguments {}\n- assert latex(arr) == r'\\left[\\begin{array}{}0 & 1 & 2 & 3 & 4 & 5 & 6 & 7 & 8 & 9 & 10\\end{array}\\right]'\n+ # fill the empty argument with a bunch of 'c' to avoid latex errors\n+ assert latex(arr) == r'\\left[\\begin{array}{ccccccccccc}0 & 1 & 2 & 3 & 4 & 5 & 6 & 7 & 8 & 9 & 10\\end{array}\\right]'\n \n def test_latex_with_unevaluated():\n with evaluate(False):\n", "problem_statement": "calling `latex` on `Array` with less rows than columns, and at least 10 columns, emit bad latex\nHello,\r\n\r\nI stumble upon this odd thing: if I create an `Array` object with a number of rows that is less or equal to the number of columns, **and** the number of columns is at least 11, then the latex emitted by the `latex` function is wrong, because it is missing the preamble. A quick fix is to change the type to `Matrix` and then call the latex function. For array with more rows than columns there seems to be no issue.\r\n\r\n```py\r\nfrom sympy import Array, Matrix, latex, ones\r\n\r\na = Array(ones(2, 11)); \r\n\r\nfrom sympy import collect\r\n\r\nlatex(a)\r\n```\r\nwill produce a `\\begin{array}` with empty preamble `{}` which is not valid latex:\r\n```py\r\n'\\\\left[\\\\begin{array}{}1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1\\\\\\\\1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1\\\\end{array}\\\\right]'\r\n```\r\n\r\nfor reference:\r\n\r\n```py\r\na = Array(ones(2, 10)); \r\nlatex(a)\r\n```\r\nwill produce a `\\begin{matrix}`, which works:\r\n```py\r\n'\\\\left[\\\\begin{matrix}1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1\\\\\\\\1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1\\\\end{matrix}\\\\right]'\r\n```\r\n\r\nchanging the type to `Matrix` will give the correct preamble:\r\n```py\r\na = Matrix(ones(2, 11)); \r\nlatex(a)\r\n```\r\n```py\r\n'\\\\left[\\\\begin{array}{ccccccccccc}1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1\\\\\\\\1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1\\\\end{array}\\\\right]'\r\n```\r\n \n", "hints_text": "", "created_at": "2024-06-01T14:58:16Z" }, { "repo": "sympy/sympy", "pull_number": 26654, "instance_id": "sympy__sympy-26654", "issue_numbers": [ "26166" ], "base_commit": "06dbac029e785296ee3bbfb1001266806d94e98b", "patch": "diff --git a/sympy/physics/wigner.py b/sympy/physics/wigner.py\nindex adc198e036d8..5e67682b9f52 100644\n--- a/sympy/physics/wigner.py\n+++ b/sympy/physics/wigner.py\n@@ -197,7 +197,9 @@ def wigner_3j(j_1, j_2, j_3, m_1, m_2, m_3):\n - zero for `m_1 + m_2 + m_3 \\neq 0`\n \n - zero for violating any one of the conditions\n- `j_1 \\ge |m_1|`, `j_2 \\ge |m_2|`, `j_3 \\ge |m_3|`\n+ `m_1 \\in \\{-|j_1|, \\ldots, |j_1|\\}`,\n+ `m_2 \\in \\{-|j_2|, \\ldots, |j_2|\\}`,\n+ `m_3 \\in \\{-|j_3|, \\ldots, |j_3|\\}`\n \n Algorithm\n =========\n@@ -230,6 +232,10 @@ def wigner_3j(j_1, j_2, j_3, m_1, m_2, m_3):\n return S.Zero\n if (abs(m_1) > j_1) or (abs(m_2) > j_2) or (abs(m_3) > j_3):\n return S.Zero\n+ if not (int_valued(j_1 - m_1) and \\\n+ int_valued(j_2 - m_2) and \\\n+ int_valued(j_3 - m_3)):\n+ return S.Zero\n \n maxfact = max(j_1 + j_2 + j_3 + 1, j_1 + abs(m_1), j_2 + abs(m_2),\n j_3 + abs(m_3))\n", "test_patch": "diff --git a/sympy/physics/quantum/tests/test_cg.py b/sympy/physics/quantum/tests/test_cg.py\nindex e5ff16265f8e..384512aaac7a 100644\n--- a/sympy/physics/quantum/tests/test_cg.py\n+++ b/sympy/physics/quantum/tests/test_cg.py\n@@ -178,3 +178,6 @@ def test_doit():\n assert Wigner9j(\n 2, 1, 1, Rational(3, 2), S.Half, 1, S.Half, S.Half, 0).doit() == sqrt(2)/12\n assert CG(S.Half, S.Half, S.Half, Rational(-1, 2), 1, 0).doit() == sqrt(2)/2\n+ # J minus M is not integer\n+ assert Wigner3j(1, -1, S.Half, S.Half, 1, S.Half).doit() == 0\n+ assert CG(4, -1, S.Half, S.Half, 4, Rational(-1, 2)).doit() == 0\n", "problem_statement": "Issue with sympy.physics.quantum.cg.CG returning incorrect values\nI have encountered an issue where the `sympy.physics.quantum.cg.CG` function and the `py3nj.clebsch_gordan` function return different results for the same inputs. \r\nHere is a minimal reproducible example:\r\n```python3\r\nimport sympy.physics.quantum.cg as spy\r\nimport py3nj\r\n\r\ndef CGsmpy(j1, m1, j2, m2, j3, m3):\r\n return float(spy.CG(j1, m1, j2, m2, j3, m3).doit())\r\n\r\ndef CGpy3nj(j1, m1, j2, m2, j3, m3):\r\n j1_, m1_, j2_, m2_, j3_, m3_ = int(2*j1), int(2*m1), int(2*j2), int(2*m2), int(2*j3), int(2*m3)\r\n try:\r\n return py3nj.clebsch_gordan(j1_, j2_, j3_, m1_, m2_, m3_)\r\n except ValueError:\r\n return 0.0\r\n\r\nsR = 0.5\r\nprint(\"Calculating Clebsch-Gordan coefficients... \\n\")\r\nfor l in range(14):\r\n for j in range(14):\r\n for mi in range(-1, 2):\r\n for mip in range(-1, 2):\r\n for mR in range(1, 3, 2):\r\n for mRp in range(-1, 3, 2):\r\n j1, m1, j2, m2, j3, m3 = l, mi, sR, mR*0.5, j * 0.5, mi + mR*0.5\r\n cg_sympy = CGsmpy(j1, m1, j2, m2, j3, m3)\r\n cg_py3nj = CGpy3nj(j1, m1, j2, m2, j3, m3)\r\n if abs(cg_sympy - cg_py3nj) > 1e-10:\r\n print(\"j1, m1, j2, m2, j3, m3: \", l, mi, sR, mR*0.5, j * 0.5, mi + mR*0.5)\r\n print(\"sympy: \", cg_sympy)\r\n print(\"py3nj: \", cg_py3nj)\r\n print(\"--------------------\")\r\n```\r\n\r\nFor some inputs, such as j1, m1, j2, m2, j3, m3: 4 -1 0.5 0.5 4.0 -0.5, the sympy function returns 0.474341649025257 while the py3nj function returns -0.0. I checked with other libraries too (and Mathematica) and it seems like `sympy` returns wrong values. \r\n\r\nThe `python` and `sympy` versions are:\r\n```python3\r\nPython version\r\n3.10.13 (main, Sep 11 2023, 13:44:35) [GCC 11.2.0]\r\nVersion info.\r\nsys.version_info(major=3, minor=10, micro=13, releaselevel='final', serial=0)\r\nSympy version\r\n1.12\r\n```\r\n\r\nCould you please look into this?\n", "hints_text": "I tried the input you provided in **Mathematica** `ClebschGordan[{4, -1}, {0.5, 0.5}, {4, -0.5}]` and received the expected error: \r\n**\"ClebschGordan: ThreeJSymbol[{4,-1},{0.5,0.5},{4,0.5}] is not triangular.\"** \r\nThis error indicates that the input does not follow the selection rules of the Wigner 3j-Symbol. In other words, it does not satisfy the rules for angular momentum coupling.\r\n\r\nPlease check [Wigner3j-Symbol](https://mathworld.wolfram.com/Wigner3j-Symbol.html).\r\n\r\nI don't think Sympy should be blamed for giving incorrect answers in response to inputs that do not follow the rules.\r\nHowever, throwing an exception in such cases might be helpful.\n@xzdlj I completely agree! (That's exactly what I wanted to point to) In many software packages, when the selection rules for Wigner 3j-symbols aren't satisfied, they either return zero or raise an error. Opting for either approach can depend on the specific requirements of the calculation. \r\nThe developers must look into this issue.\n@dev-aditya Can you please explain or give more examples on what values were causing you issues . Although I think the issue arises cause \r\n\r\nInside the celbsch gordon method called with CG class `.doit()` method\r\n```python\r\n res = (-1) ** sympify(j_1 - j_2 + m_3) * sqrt(2 * j_3 + 1) * \\\r\n wigner_3j(j_1, j_2, j_3, m_1, m_2, -m_3)\r\n``` \r\n\r\nInside the wigner_3j() function\r\n```python\r\n m_3 = -m_3\r\n\r\n``` \r\n\r\nMeaning m1+m2 = M would be satisfied , the only remaining condition i.e. integer perimeter is not implemented which should probably also fail with your example but is not implemented since it is implied by the other three . ", "created_at": "2024-06-01T07:52:06Z" }, { "repo": "sympy/sympy", "pull_number": 26623, "instance_id": "sympy__sympy-26623", "issue_numbers": [ "26121" ], "base_commit": "ae3027b4ca682023dee36e7e8883311c62cc08e3", "patch": "diff --git a/sympy/vector/__init__.py b/sympy/vector/__init__.py\nindex 9228befbdb4b..f6757bbeb350 100644\n--- a/sympy/vector/__init__.py\n+++ b/sympy/vector/__init__.py\n@@ -17,11 +17,14 @@\n from sympy.vector.implicitregion import ImplicitRegion\n from sympy.vector.parametricregion import (ParametricRegion, parametric_region_list)\n from sympy.vector.integrals import (ParametricIntegral, vector_integrate)\n+from sympy.vector.kind import VectorKind\n \n __all__ = [\n 'Vector', 'VectorAdd', 'VectorMul', 'BaseVector', 'VectorZero', 'Cross',\n 'Dot', 'cross', 'dot',\n \n+ 'VectorKind',\n+\n 'Dyadic', 'DyadicAdd', 'DyadicMul', 'BaseDyadic', 'DyadicZero',\n \n 'BaseScalar',\ndiff --git a/sympy/vector/kind.py b/sympy/vector/kind.py\nnew file mode 100644\nindex 000000000000..c6c04896b34c\n--- /dev/null\n+++ b/sympy/vector/kind.py\n@@ -0,0 +1,67 @@\n+#sympy.vector.kind\n+\n+from sympy.core.kind import Kind, _NumberKind, NumberKind\n+from sympy.core.mul import Mul\n+\n+class VectorKind(Kind):\n+ \"\"\"\n+ Kind for all vector objects in SymPy.\n+\n+ Parameters\n+ ==========\n+\n+ element_kind : Kind\n+ Kind of the element. Default is\n+ :class:`sympy.core.kind.NumberKind`,\n+ which means that the vector contains only numbers.\n+\n+ Examples\n+ ========\n+\n+ Any instance of Vector class has kind ``VectorKind``:\n+\n+ >>> from sympy.vector.coordsysrect import CoordSys3D\n+ >>> Sys = CoordSys3D('Sys')\n+ >>> Sys.i.kind\n+ VectorKind(NumberKind)\n+\n+ Operations between instances of Vector keep also have the kind ``VectorKind``:\n+\n+ >>> from sympy.core.add import Add\n+ >>> v1 = Sys.i * 2 + Sys.j * 3 + Sys.k * 4\n+ >>> v2 = Sys.i * Sys.x + Sys.j * Sys.y + Sys.k * Sys.z\n+ >>> v1.kind\n+ VectorKind(NumberKind)\n+ >>> v2.kind\n+ VectorKind(NumberKind)\n+ >>> Add(v1, v2).kind\n+ VectorKind(NumberKind)\n+\n+ Subclasses of Vector also have the kind ``VectorKind``, such as\n+ Cross, VectorAdd, VectorMul or VectorZero.\n+\n+ See Also\n+ ========\n+\n+ sympy.core.kind.Kind\n+ sympy.matrices.kind.MatrixKind\n+\n+ \"\"\"\n+ def __new__(cls, element_kind=NumberKind):\n+ obj = super().__new__(cls, element_kind)\n+ obj.element_kind = element_kind\n+ return obj\n+\n+ def __repr__(self):\n+ return \"VectorKind(%s)\" % self.element_kind\n+\n+@Mul._kind_dispatcher.register(_NumberKind, VectorKind)\n+def num_vec_mul(k1, k2):\n+ \"\"\"\n+ The result of a multiplication between a number and a Vector should be of VectorKind.\n+ The element kind is selected by recursive dispatching.\n+ \"\"\"\n+ if not isinstance(k2, VectorKind):\n+ k1, k2 = k2, k1\n+ elemk = Mul._kind_dispatcher(k1, k2.element_kind)\n+ return VectorKind(elemk)\ndiff --git a/sympy/vector/scalar.py b/sympy/vector/scalar.py\nindex 42742b021ea5..bcfb56cf177b 100644\n--- a/sympy/vector/scalar.py\n+++ b/sympy/vector/scalar.py\n@@ -2,6 +2,7 @@\n from sympy.core.sympify import _sympify\n from sympy.printing.pretty.stringpict import prettyForm\n from sympy.printing.precedence import PRECEDENCE\n+from sympy.core.kind import NumberKind\n \n \n class BaseScalar(AtomicExpr):\n@@ -12,6 +13,8 @@ class BaseScalar(AtomicExpr):\n \n \"\"\"\n \n+ kind = NumberKind\n+\n def __new__(cls, index, system, pretty_str=None, latex_str=None):\n from sympy.vector.coordsysrect import CoordSys3D\n if pretty_str is None:\ndiff --git a/sympy/vector/vector.py b/sympy/vector/vector.py\nindex d64ea6d70890..7847a9f01f42 100644\n--- a/sympy/vector/vector.py\n+++ b/sympy/vector/vector.py\n@@ -1,7 +1,7 @@\n from __future__ import annotations\n from itertools import product\n \n-from sympy.core.add import Add\n+from sympy.core import Add, Basic\n from sympy.core.assumptions import StdFactKB\n from sympy.core.expr import AtomicExpr, Expr\n from sympy.core.power import Pow\n@@ -14,6 +14,7 @@\n BasisDependent, BasisDependentMul, BasisDependentAdd)\n from sympy.vector.coordsysrect import CoordSys3D\n from sympy.vector.dyadic import Dyadic, BaseDyadic, DyadicAdd\n+from sympy.vector.kind import VectorKind\n \n \n class Vector(BasisDependent):\n@@ -34,6 +35,8 @@ class Vector(BasisDependent):\n _base_func: type[Vector]\n zero: VectorZero\n \n+ kind: VectorKind = VectorKind()\n+\n @property\n def components(self):\n \"\"\"\n@@ -344,6 +347,24 @@ def _div_helper(one, other):\n else:\n raise TypeError(\"Invalid division involving a vector\")\n \n+# The following is adapted from the matrices.expressions.matexpr file\n+\n+def get_postprocessor(cls):\n+ def _postprocessor(expr):\n+ vec_class = {Add: VectorAdd}[cls]\n+ vectors = []\n+ for term in expr.args:\n+ if isinstance(term.kind, VectorKind):\n+ vectors.append(term)\n+\n+ if vec_class == VectorAdd:\n+ return VectorAdd(*vectors).doit(deep=False)\n+ return _postprocessor\n+\n+\n+Basic._constructor_postprocessor_mapping[Vector] = {\n+ \"Add\": [get_postprocessor(Add)],\n+}\n \n class BaseVector(Vector, AtomicExpr):\n \"\"\"\n", "test_patch": "diff --git a/sympy/vector/tests/test_vector.py b/sympy/vector/tests/test_vector.py\nindex b68fb9fb3efb..64cdc0cb8b59 100644\n--- a/sympy/vector/tests/test_vector.py\n+++ b/sympy/vector/tests/test_vector.py\n@@ -1,4 +1,4 @@\n-from sympy.core import Rational, S\n+from sympy.core import Rational, S, Add, Mul\n from sympy.simplify import simplify, trigsimp\n from sympy.core.function import (Derivative, Function, diff)\n from sympy.core.numbers import pi\n@@ -12,6 +12,8 @@\n from sympy.vector.coordsysrect import CoordSys3D\n from sympy.vector.vector import Cross, Dot, cross\n from sympy.testing.pytest import raises\n+from sympy.vector.kind import VectorKind\n+from sympy.core.kind import NumberKind\n \n C = CoordSys3D('C')\n \n@@ -52,6 +54,50 @@ def test_vector_sympy():\n assert v3.__hash__() == v2.__hash__()\n \n \n+def test_kind():\n+ assert C.i.kind is VectorKind(NumberKind)\n+ assert C.j.kind is VectorKind(NumberKind)\n+ assert C.k.kind is VectorKind(NumberKind)\n+\n+ assert C.x.kind is NumberKind\n+ assert C.y.kind is NumberKind\n+ assert C.z.kind is NumberKind\n+\n+ assert Mul._kind_dispatcher(NumberKind, VectorKind(NumberKind)) is VectorKind(NumberKind)\n+ assert Mul(2, C.i).kind is VectorKind(NumberKind)\n+\n+ v1 = C.x * i + C.z * C.z * j\n+ v2 = C.x * i + C.y * j + C.z * k\n+ assert v1.kind is VectorKind(NumberKind)\n+ assert v2.kind is VectorKind(NumberKind)\n+\n+ assert (v1 + v2).kind is VectorKind(NumberKind)\n+ assert Add(v1, v2).kind is VectorKind(NumberKind)\n+ assert Cross(v1, v2).doit().kind is VectorKind(NumberKind)\n+ assert VectorAdd(v1, v2).kind is VectorKind(NumberKind)\n+ assert VectorMul(2, v1).kind is VectorKind(NumberKind)\n+ assert VectorZero().kind is VectorKind(NumberKind)\n+\n+ assert v1.projection(v2).kind is VectorKind(NumberKind)\n+ assert v2.projection(v1).kind is VectorKind(NumberKind)\n+\n+\n+def test_vectoradd():\n+ assert isinstance(Add(C.i, C.j), VectorAdd)\n+ v1 = C.x * i + C.z * C.z * j\n+ v2 = C.x * i + C.y * j + C.z * k\n+ assert isinstance(Add(v1, v2), VectorAdd)\n+\n+ # https://github.com/sympy/sympy/issues/26121\n+\n+ E = Matrix([C.i, C.j, C.k]).T\n+ a = Matrix([1, 2, 3])\n+ av = E*a\n+\n+ assert av[0].kind == VectorKind()\n+ assert isinstance(av[0], VectorAdd)\n+\n+\n def test_vector():\n assert isinstance(i, BaseVector)\n assert i != j\n", "problem_statement": "matrix of vectors destroys vector type\n from sympy.matrices import Matrix\r\n from sympy.vector import CoordSys3D, Vector\r\n\r\n Sys = CoordSys3D('Sys')\r\n O = Sys.origin\r\n\r\n # The row matrix of unit vectors\r\n E = Matrix([Sys.i, Sys.j, Sys.k]).T\r\n\r\n # A Column matrix of coordinates\r\n a = Matrix([1, 2, 3])\r\n\r\n # Obtain the matrix product\r\n av = E*a\r\n\r\n # The following does not work! Why is av[0] not a vector anymore? \r\n # Is this a bug? Am I missing something important?\r\n A = O.locate_new('A', av[0])\r\n\r\n\n", "hints_text": "It is because:\r\n```python\r\nIn [61]: type(Add(Sys.i, Sys.j))\r\nOut[61]: sympy.core.add.Add\r\n\r\nIn [62]: type(Sys.i + Sys.j)\r\nOut[62]: sympy.vector.vector.VectorAdd\r\n```\r\nThe Matrix code assumes that it can add things with `Add` but there is no mechanism that makes this work with vectors.\r\n\r\nIdeally the `.kind` would be used for this so that `Add` can dispatch to `VectorAdd` or something like that. Currently `.kind` is undefined for vectors though.\nDo you think we can handle it any other way like checking the type and removing the vector part initially and adding them like scalars and adding them back later.\r\n\r\n\nThis sort of problem arises any time `Add(a, b)` is not the same as `a + b`. The vector code defines `__add__` and it is basically always a problem for an Expr class to define `__add__`. Instead what is needed is a way to make it so that `Add(a, b)` can do whatever should be done in the case of vectors.\n> Ideally the .kind would be used for this so that Add can dispatch to VectorAdd or something like that. Currently .kind is undefined for vectors though.\r\n\r\nWhat would you suggest on how to make these changes , I tried looking into it but it seems like it is connected to many different files and classes .\r\n\r\n```\r\n>>> E = Matrix([Sys.i, Sys.j, Sys.k]).T\r\n>>> E[0].kind\r\nUndefinedKind\r\n>>> E.kind\r\nMatrixKind(UndefinedKind)\r\n```\r\nis what we currently get . Do you think it should be changed so that `.kind` gives something like `VectorAdd` . \r\n\r\n> An expression representing a matrix may not be an instance of\r\n the Matrix class, but it will have kind ``MatrixKind``:\r\n\r\nSorry for any unnecessary questions but can't we make vectors work the same way ? So they can always have Something like `Vector` as its kind\r\n\nYes, ideally the kind of all vector objects would VectorKind.\nWhere would we need to make the changes to ensure the `.kind` gives the desired result for vectors .\nOnce one had implemented the VectorKind, what would be the next step to ensure that Add could dispatch to VectorAdd?\nI'm not sure. Maybe look at how it works for MatrixKind.", "created_at": "2024-05-26T21:35:44Z" }, { "repo": "sympy/sympy", "pull_number": 26612, "instance_id": "sympy__sympy-26612", "issue_numbers": [ "21463" ], "base_commit": "3cc42c49d62ff490ea1a57854781eb2196601b35", "patch": "diff --git a/.mailmap b/.mailmap\nindex fe494e21e738..b132e3745d7e 100644\n--- a/.mailmap\n+++ b/.mailmap\n@@ -799,6 +799,7 @@ Joseph Rance <56409230+Joseph-Rance@users.noreply.github.com>\n Joseph Redfern \n Josh Burkart \n Jos\u00e9 Senart \n+Jo\u00e3o Bravo \n Jo\u00e3o Moura \n Juan Barbosa \n Juan Felipe Osorio \ndiff --git a/sympy/physics/units/util.py b/sympy/physics/units/util.py\nindex e46b15cbb922..b3f5a004fe9f 100644\n--- a/sympy/physics/units/util.py\n+++ b/sympy/physics/units/util.py\n@@ -12,6 +12,7 @@\n from sympy.core.power import Pow\n from sympy.core.sorting import ordered\n from sympy.core.sympify import sympify\n+from sympy.core.function import Function\n from sympy.matrices.exceptions import NonInvertibleMatrixError\n from sympy.physics.units.dimensions import Dimension, DimensionSystem\n from sympy.physics.units.prefixes import Prefix\n@@ -110,6 +111,9 @@ def handle_Adds(expr):\n expr = sympify(expr)\n target_units = sympify(target_units)\n \n+ if isinstance(expr, Function):\n+ expr = expr.together()\n+\n if not isinstance(expr, Quantity) and expr.has(Quantity):\n expr = expr.replace(lambda x: isinstance(x, Quantity),\n lambda x: x.convert_to(target_units, unit_system))\n", "test_patch": "diff --git a/sympy/physics/units/tests/test_util.py b/sympy/physics/units/tests/test_util.py\nindex b4e1bd6284a8..3522af675d33 100644\n--- a/sympy/physics/units/tests/test_util.py\n+++ b/sympy/physics/units/tests/test_util.py\n@@ -76,6 +76,10 @@ def test_convert_to_quantities():\n \n \n def test_convert_to_tuples_of_quantities():\n+ from sympy.core.symbol import symbols\n+\n+ alpha, beta = symbols('alpha beta')\n+\n assert convert_to(speed_of_light, [meter, second]) == 299792458 * meter / second\n assert convert_to(speed_of_light, (meter, second)) == 299792458 * meter / second\n assert convert_to(speed_of_light, Tuple(meter, second)) == 299792458 * meter / second\n@@ -98,6 +102,9 @@ def test_convert_to_tuples_of_quantities():\n assert convert_to(sqrt(meter**2 + second**2.0), [meter, second]) == sqrt(meter**2 + second**2.0)\n assert convert_to((meter**2 + second**2.0)**2, [meter, second]) == (meter**2 + second**2.0)**2\n \n+ # similar to https://github.com/sympy/sympy/issues/21463\n+ assert convert_to(1/(beta*meter + meter), 1/meter) == 1/(beta*meter + meter)\n+ assert convert_to(1/(beta*meter + alpha*meter), 1/kilometer) == (1/(kilometer*beta/1000 + alpha*kilometer/1000))\n \n def test_eval_simplify():\n from sympy.physics.units import cm, mm, km, m, K, kilo\n", "problem_statement": "Unit conversion returns erroneous result for inverse of sum (bad Pow handling probably)\nPython version: 3.9.2\r\nSymPy version: 1.6.2\r\n\r\nI was able to isolate this unit conversion issue to inverse of sums of quantities. Maybe `conver_to` is not handling powers correctly, since the inverse function is implemented like `(expr)**(-1)`.\r\n\r\n```python\r\nfrom sympy.physics.units import m, convert_to\r\nfrom sympy.abc import beta\r\nfrom sympy import Tuple\r\n# suppose beta is a unitless ratio\r\n# units for this should be 1/m\r\nexpr = 1/(beta*m+m)\r\n\r\n# As expected, the simplified outer expression is a multiple of 1/m\r\nTuple(expr, expr.simplify())\r\n\r\n# Units are 1/m**2 for some reason, maybe because \r\nTuple(convert_to(expr, m**(-1)), convert_to(expr, m**(-1)).simplify())\r\n\r\n# If the expression is just a multiple of the unit then it works as expected\r\nTuple(convert_to(expr.simplify(), 1/m), convert_to(expr.simplify(), 1/m).simplify())\r\n```\r\n\r\nOn jupyter:\r\n![image](https://user-images.githubusercontent.com/15100012/118048030-0b1be600-b352-11eb-83d7-a7cb89b3c947.png)\r\n\n", "hints_text": "", "created_at": "2024-05-20T15:27:10Z" }, { "repo": "sympy/sympy", "pull_number": 26597, "instance_id": "sympy__sympy-26597", "issue_numbers": [ "26552" ], "base_commit": "986893a062bfd21a8063f7526f76d9c813d49934", "patch": "diff --git a/.mailmap b/.mailmap\nindex 2d81b1d8dbae..e5e40328d6ac 100644\n--- a/.mailmap\n+++ b/.mailmap\n@@ -718,6 +718,7 @@ Jaime R <38530589+Jaime02@users.noreply.github.com>\n Jaime Resano \n Jainul Vaghasia \n Jakub Wilk \n+James A. Preiss jpreiss \n James Abbatiello \n James Aspnes \n James Brandon Milam \ndiff --git a/sympy/algebras/quaternion.py b/sympy/algebras/quaternion.py\nindex ee03b7503f5a..3251d92af385 100644\n--- a/sympy/algebras/quaternion.py\n+++ b/sympy/algebras/quaternion.py\n@@ -972,22 +972,22 @@ def exp(self):\n \n return Quaternion(a, b, c, d)\n \n- def _ln(self):\n- \"\"\"Returns the natural logarithm of the quaternion (_ln(q)).\n+ def log(self):\n+ r\"\"\"Returns the logarithm of the quaternion, given by $\\log q$.\n \n Examples\n ========\n \n >>> from sympy import Quaternion\n >>> q = Quaternion(1, 2, 3, 4)\n- >>> q._ln()\n+ >>> q.log()\n log(sqrt(30))\n + 2*sqrt(29)*acos(sqrt(30)/30)/29*i\n + 3*sqrt(29)*acos(sqrt(30)/30)/29*j\n + 4*sqrt(29)*acos(sqrt(30)/30)/29*k\n \n \"\"\"\n- # _ln(q) = _ln||q|| + v/||v||*arccos(a/||q||)\n+ # log(q) = log||q|| + v/||v||*arccos(a/||q||)\n q = self\n vector_norm = sqrt(q.b**2 + q.c**2 + q.d**2)\n q_norm = q.norm()\n", "test_patch": "diff --git a/sympy/algebras/tests/test_quaternion.py b/sympy/algebras/tests/test_quaternion.py\nindex 8215635c833e..87b68f54b5f0 100644\n--- a/sympy/algebras/tests/test_quaternion.py\n+++ b/sympy/algebras/tests/test_quaternion.py\n@@ -172,7 +172,7 @@ def test_quaternion_functions():\n 2 * sqrt(29) * E * sin(sqrt(29)) / 29,\n 3 * sqrt(29) * E * sin(sqrt(29)) / 29,\n 4 * sqrt(29) * E * sin(sqrt(29)) / 29)\n- assert q1._ln() == \\\n+ assert q1.log() == \\\n Quaternion(log(sqrt(30)),\n 2 * sqrt(29) * acos(sqrt(30)/30) / 29,\n 3 * sqrt(29) * acos(sqrt(30)/30) / 29,\n", "problem_statement": "Why is the Quaternion logarithmic map given a private-looking name?\nWhy is `Quaternion._ln` not given a name like `Quaternion.log` and documented as part of the public interface?\r\n\r\nThis was asked a while back, but it was tangential to the main topic and thus ignored:\r\n\r\n> BTW, Is it any reason to avoid conventional `log` name for method. There is only `_ln`. Leading underscore in the method name may confuse since it looks like a private method. Maybe, adding `log = _ln` to the class definition makes sense.\r\n\r\n_Originally posted by @vr050714 in https://github.com/sympy/sympy/issues/21576#issuecomment-855421034_\r\n\r\nIt seems odd to me, especially since `Quaternion.exp` is public. There are many use cases of quaternions where we wish to go between the Lie group and Lie algebra in both directions.\n", "hints_text": "I think it could be made a public method. I'm not sure if more tidying up or something is needed.\nI can make a PR if desired!\nYes, go ahead.", "created_at": "2024-05-14T19:37:53Z" }, { "repo": "sympy/sympy", "pull_number": 26557, "instance_id": "sympy__sympy-26557", "issue_numbers": [ "26556" ], "base_commit": "7c4f580838612edea6dcd6ae7d5b8a75af2d8f10", "patch": "diff --git a/.mailmap b/.mailmap\nindex 4c0988c98177..aac350bcc3c5 100644\n--- a/.mailmap\n+++ b/.mailmap\n@@ -1188,6 +1188,7 @@ Riccardo Magliocchetti \n Rich LaSota \n Richard Otis \n Richard Otis \n+Richard Rodenbusch rrodenbusch \n Richard Samuel <98638849+samuelard7@users.noreply.github.com>\n Richard Samuel \n Rick Muller \ndiff --git a/sympy/simplify/simplify.py b/sympy/simplify/simplify.py\nindex c86eb1500131..af351a3c8478 100644\n--- a/sympy/simplify/simplify.py\n+++ b/sympy/simplify/simplify.py\n@@ -9,7 +9,7 @@\n from sympy.core.parameters import global_parameters\n from sympy.core.function import (expand_log, count_ops, _mexpand,\n nfloat, expand_mul, expand)\n-from sympy.core.numbers import Float, I, pi, Rational\n+from sympy.core.numbers import Float, I, pi, Rational, equal_valued\n from sympy.core.relational import Relational\n from sympy.core.rules import Transform\n from sympy.core.sorting import ordered\n@@ -1758,7 +1758,7 @@ def compare(s, alt_s):\n # get the non-commutative part\n c_args, args = expr.args_cnc()\n com_coeff = Mul(*c_args)\n- if com_coeff != 1:\n+ if not equal_valued(com_coeff, 1):\n return com_coeff*nc_simplify(expr/com_coeff, deep=deep)\n \n inv_tot, args = _reduce_inverses(args)\n", "test_patch": "diff --git a/sympy/simplify/tests/test_simplify.py b/sympy/simplify/tests/test_simplify.py\nindex a26e8e33a2eb..f4392b669375 100644\n--- a/sympy/simplify/tests/test_simplify.py\n+++ b/sympy/simplify/tests/test_simplify.py\n@@ -1080,3 +1080,8 @@ def test_reduce_inverses_nc_pow():\n x, y = symbols(\"x y\", positive=True)\n assert expand((x*y)**Z) == x**Z * y**Z\n assert simplify(x**Z * y**Z) == expand((x*y)**Z)\n+\n+def test_nc_recursion_coeff():\n+ X = symbols(\"X\", commutative = False)\n+ assert (2 * cos(pi/3) * X).simplify() == X\n+ assert (2.0 * cos(pi/3) * X).simplify() == X\n", "problem_statement": "RecursionError in simplify of non-commutative expressions with non-integer coefficient\nSimplifying expressions with non-commutative symbols and floating point coeefficients generates a recursion error.\r\n\r\n```python\r\nfrom sympy import Symbol, cos, pi\r\nX = Symbol('X', commutative = False)\r\n\r\ntry:\r\n display( (2 * cos(pi/3) * X).simplify() )\r\nexcept RecursionError as e:\r\n print(f'RecursionError 2*cos(pi/3): {e}')\r\n\r\ntry:\r\n display( (2.0 * cos(pi/3) * X).simplify() )\r\nexcept RecursionError as e:\r\n print(f'RecursionError 2.0*cos(pi/3): {e}')\r\n\r\ntry:\r\n display( (2.0 * cos(pi/6) * X).simplify() )\r\nexcept RecursionError as e:\r\n print(f'RecursionError 2.0*cos(pi/6): {e}')\r\n```\r\noutputs:\r\n\r\nX\r\nRecursionError 2.0*cos(pi/3): maximum recursion depth exceeded in comparison\r\nRecursionError 2.0*cos(pi/6): maximum recursion depth exceeded in comparison\r\n\r\n\r\n\n", "hints_text": "", "created_at": "2024-04-30T13:44:06Z" }, { "repo": "matplotlib/matplotlib", "pull_number": 28649, "instance_id": "matplotlib__matplotlib-28649", "issue_numbers": [ "28648" ], "base_commit": "adb97e848ca88f0b9abc5b1c5085f548847e3f8f", "patch": "diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py\nindex 981365d852be..24aaa349ee05 100644\n--- a/lib/matplotlib/artist.py\n+++ b/lib/matplotlib/artist.py\n@@ -1365,7 +1365,9 @@ def format_cursor_data(self, data):\n delta = np.diff(\n self.norm.boundaries[neigh_idx:cur_idx + 2]\n ).max()\n-\n+ elif self.norm.vmin == self.norm.vmax:\n+ # singular norms, use delta of 10% of only value\n+ delta = np.abs(self.norm.vmin * .1)\n else:\n # Midpoints of neighboring color intervals.\n neighbors = self.norm.inverse(\ndiff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py\nindex e4f60aac37a8..2411784af3ec 100644\n--- a/lib/matplotlib/cbook.py\n+++ b/lib/matplotlib/cbook.py\n@@ -2252,6 +2252,10 @@ def _g_sig_digits(value, delta):\n it is known with an error of *delta*.\n \"\"\"\n if delta == 0:\n+ if value == 0:\n+ # if both value and delta are 0, np.spacing below returns 5e-324\n+ # which results in rather silly results\n+ return 3\n # delta = 0 may occur when trying to format values over a tiny range;\n # in that case, replace it by the distance to the closest float.\n delta = abs(np.spacing(value))\n", "test_patch": "diff --git a/lib/matplotlib/tests/test_image.py b/lib/matplotlib/tests/test_image.py\nindex dfacfccb3e0e..6de1754f9ed7 100644\n--- a/lib/matplotlib/tests/test_image.py\n+++ b/lib/matplotlib/tests/test_image.py\n@@ -390,7 +390,8 @@ def test_cursor_data_nonuniform(xy, data):\n ([[.123, .987]], \"[0.123]\"),\n ([[np.nan, 1, 2]], \"[]\"),\n ([[1, 1+1e-15]], \"[1.0000000000000000]\"),\n- ([[-1, -1]], \"[-1.0000000000000000]\"),\n+ ([[-1, -1]], \"[-1.0]\"),\n+ ([[0, 0]], \"[0.00]\"),\n ])\n def test_format_cursor_data(data, text):\n from matplotlib.backend_bases import MouseEvent\n", "problem_statement": "[Bug]: format_image_data on an image of only zeros produces a large number of zeros\n### Bug summary\n\nIf you use imshow, and then mouseover the image, the value is listed at the bottom of the plot.\r\nHowever, if the image contains only zeros, the result is a comical number of zeros.\n\n### Code for reproduction\n\n```Python\nimport matplotlib\r\nimport numpy as np\r\nmatplotlib.use('TkAgg')\r\nimport matplotlib.pyplot as plt\r\nfig, ax = plt.subplots()\r\nim = ax.imshow(np.zeros((4, 4)))\r\nfig.show()\n```\n\n\n### Actual outcome\n\nWhen you mouse-over the image it shows a large number of zeros (see bottom of image).\r\n![image](https://github.com/user-attachments/assets/e1cb7d31-31a2-48f8-afd5-0dd57446ec38)\n\n### Expected outcome\n\n0.0 should be at the bottom of the page and the window should retain its original size:\r\n![image](https://github.com/user-attachments/assets/25f03995-ea55-4eea-b9ee-1763513c7cbf)\r\n\n\n### Additional information\n\ntest_image.py\u2192test_image_cursor_formatting() should test this perscise functionality, but there is a bug in this test where \r\n```py\r\n data = np.ma.masked_array([0], mask=[False])\r\n assert im.format_cursor_data(data) == '[0]'\r\n```\r\nshould be\r\n```py\r\n data = np.ma.masked_array(0, mask=[False])\r\n assert im.format_cursor_data(data) == '[0]'\r\n```\r\nAnd this causes `im.format_cursor_data` to fallback and '0' is returned. \r\nThis test should be updated as part of a PR to fix this bug. \r\n\n\n### Operating system\n\n_No response_\n\n### Matplotlib Version\n\nmatplotlib_dev, 3.8.2\n\n### Matplotlib Backend\n\nTkAgg\n\n### Python version\n\n_No response_\n\n### Jupyter version\n\n_No response_\n\n### Installation\n\ngit checkout\n", "hints_text": "The issue is singular norms. We do a bunch of extra logic it https://github.com/matplotlib/matplotlib/blob/adb97e848ca88f0b9abc5b1c5085f548847e3f8f/lib/matplotlib/artist.py#L1349-L1377 to estimate a \"good\" number of digits via https://github.com/matplotlib/matplotlib/blob/adb97e848ca88f0b9abc5b1c5085f548847e3f8f/lib/matplotlib/cbook.py#L2249-L2267\r\n\r\n\r\n```\r\nIn [10]: import matplotlib.cbook as mc\r\n\r\nIn [11]: mc._g_sig_digits(0, 0)\r\nOut[13]: 325\r\n\r\nIn [14]: mc._g_sig_digits(0, .1)\r\nOut[14]: 2\r\n\r\nIn [15]: mc._g_sig_digits(0, 0)\r\nOut[15]: 325\r\n\r\nIn [16]: mc._g_sig_digits(0, .00001)\r\nOut[16]: 6\r\n\r\nIn [17]: mc._g_sig_digits(0, .000001)\r\nOut[17]: 7\r\n\r\nIn [18]: mc._g_sig_digits(0, .0001)\r\nOut[18]: 5\r\n\r\nIn [19]: mc._g_sig_digits(.1, 0)\r\nOut[19]: 17\r\n```\r\n\r\nI have some ideas, will open a PR shortly.", "created_at": "2024-08-02T16:28:55Z" }, { "repo": "matplotlib/matplotlib", "pull_number": 28629, "instance_id": "matplotlib__matplotlib-28629", "issue_numbers": [ "28623", "0000" ], "base_commit": "30f803b2e9b5e237c5c31df57f657ae69bec240d", "patch": "diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py\nindex 483c9a3db15f..158d4a02ee61 100644\n--- a/lib/matplotlib/axis.py\n+++ b/lib/matplotlib/axis.py\n@@ -1362,7 +1362,7 @@ def get_tightbbox(self, renderer=None, *, for_layout_only=False):\n collapsed to near zero. This allows tight/constrained_layout to ignore\n too-long labels when doing their layout.\n \"\"\"\n- if not self.get_visible():\n+ if not self.get_visible() or for_layout_only and not self.get_in_layout():\n return\n if renderer is None:\n renderer = self.figure._get_renderer()\n", "test_patch": "diff --git a/lib/matplotlib/tests/test_axis.py b/lib/matplotlib/tests/test_axis.py\nindex 97b5f88dede1..33af30662a33 100644\n--- a/lib/matplotlib/tests/test_axis.py\n+++ b/lib/matplotlib/tests/test_axis.py\n@@ -8,3 +8,24 @@ def test_tick_labelcolor_array():\n # Smoke test that we can instantiate a Tick with labelcolor as array.\n ax = plt.axes()\n XTick(ax, 0, labelcolor=np.array([1, 0, 0, 1]))\n+\n+\n+def test_axis_not_in_layout():\n+ fig1, (ax1_left, ax1_right) = plt.subplots(ncols=2, layout='constrained')\n+ fig2, (ax2_left, ax2_right) = plt.subplots(ncols=2, layout='constrained')\n+\n+ # 100 label overlapping the end of the axis\n+ ax1_left.set_xlim([0, 100])\n+ # 100 label not overlapping the end of the axis\n+ ax2_left.set_xlim([0, 120])\n+\n+ for ax in ax1_left, ax2_left:\n+ ax.set_xticks([0, 100])\n+ ax.xaxis.set_in_layout(False)\n+\n+ for fig in fig1, fig2:\n+ fig.draw_without_rendering()\n+\n+ # Positions should not be affected by overlapping 100 label\n+ assert ax1_left.get_position().bounds == ax2_left.get_position().bounds\n+ assert ax1_right.get_position().bounds == ax2_right.get_position().bounds\n", "problem_statement": "[Bug]: `Axis.set_in_layout` not respected?\n### Bug summary\r\n\r\nIf a tick label appears right at the end of an axis, constrained layout increases the gap between the subplots to accommodate it. This is reasonable behaviour, but sometimes I would like to turn it off. I tried setting `xaxis.set_in_layout(False)`, but that doesn't seem to make a difference. Should it work?\r\n\r\n### Code for reproduction\r\n\r\n```Python\r\nimport matplotlib.pyplot as plt\r\n\r\n\r\ndef example_plot(xmax): \r\n fig, (ax1, ax2) = plt.subplots(ncols=2, layout='constrained')\r\n \r\n ax1.set_xlim([0, xmax])\r\n ax1.set_xticks([0, 100])\r\n ax1.xaxis.set_in_layout(False)\r\n \r\n fig.savefig(f'example_x{xmax}.png')\r\n \r\n plt.close(fig)\r\n \r\n \r\nfor xmax in 100, 120:\r\n example_plot(xmax)\r\n```\r\n\r\n\r\n### Actual outcome\r\n\r\n`example_x120.png`\r\n![example_x120](https://github.com/user-attachments/assets/986b6055-55c4-4c8e-beb0-10c61d36fcba)\r\n`example_x100.png`\r\n![example_x100](https://github.com/user-attachments/assets/4a3feefb-b26c-4384-b5ce-2d8ef99e200f)\r\n\r\nWhen the \"100\" tick label is at the end of the x-axis the gap between the subplots is bigger.\r\n\r\n### Expected outcome\r\n\r\nI would like the gap between the subplots to not depend on the \"100\" label.\r\n\r\n### Additional information\r\n\r\nI haven't tried, but suspect we could make this work by just returning `None` here if `for_layout_only` is `True` and `self.get_in_layout` is `False`.\r\nhttps://github.com/matplotlib/matplotlib/blob/040091e9ea35df756ee838fe8b83a9deabe16f26/lib/matplotlib/axis.py#L1365-L1366\r\n\r\n### Operating system\r\n\r\nRHEL7\r\n\r\n### Matplotlib Version\r\n\r\n3.9.1\r\n\r\n### Matplotlib Backend\r\n\r\nQtAgg\r\n\r\n### Python version\r\n\r\n3.12.4\r\n\r\n### Jupyter version\r\n\r\nN/A\r\n\r\n### Installation\r\n\r\nconda\n", "hints_text": "I don't see why it should not be allowed to work, and your propped change seems good.\nHaving semi-recently looked at #28574, I'm fairly certain that none of the `Axis`, `Spine`, or `Tick`s obey `set_in_layout`, but only because they were never set up that way.\nI assumed it would be futile to try to set it on the individual tick because of the way they are dynamically generated (my \"real\" example uses `MaxNLocator` on a colorbar).\r\n\r\nI'm unclear why `Spine`s wouldn't work properly as they appear to be handled within the general list of artists:\r\nhttps://github.com/matplotlib/matplotlib/blob/30f803b2e9b5e237c5c31df57f657ae69bec240d/lib/matplotlib/axes/_base.py#L4391-L4421", "created_at": "2024-07-30T16:54:25Z" }, { "repo": "matplotlib/matplotlib", "pull_number": 28609, "instance_id": "matplotlib__matplotlib-28609", "issue_numbers": [ "28595", "0000" ], "base_commit": "e6f2b0c72be421f3b324a55fca1dbc367405cce5", "patch": "diff --git a/lib/matplotlib/backends/backend_svg.py b/lib/matplotlib/backends/backend_svg.py\nindex 84e4f96ad4a7..623e1eb9ad82 100644\n--- a/lib/matplotlib/backends/backend_svg.py\n+++ b/lib/matplotlib/backends/backend_svg.py\n@@ -715,6 +715,8 @@ def draw_markers(\n self._markers[dictkey] = oid\n \n writer.start('g', **self._get_clip_attrs(gc))\n+ if gc.get_url() is not None:\n+ self.writer.start('a', {'xlink:href': gc.get_url()})\n trans_and_flip = self._make_flip_transform(trans)\n attrib = {'xlink:href': f'#{oid}'}\n clip = (0, 0, self.width*72, self.height*72)\n@@ -726,6 +728,8 @@ def draw_markers(\n attrib['y'] = _short_float_fmt(y)\n attrib['style'] = self._get_style(gc, rgbFace)\n writer.element('use', attrib=attrib)\n+ if gc.get_url() is not None:\n+ self.writer.end('a')\n writer.end('g')\n \n def draw_path_collection(self, gc, master_transform, paths, all_transforms,\n", "test_patch": "diff --git a/lib/matplotlib/tests/test_backend_svg.py b/lib/matplotlib/tests/test_backend_svg.py\nindex 689495eb31ac..b850a9ab6ff5 100644\n--- a/lib/matplotlib/tests/test_backend_svg.py\n+++ b/lib/matplotlib/tests/test_backend_svg.py\n@@ -343,13 +343,17 @@ def test_url():\n s.set_urls(['https://example.com/foo', 'https://example.com/bar', None])\n \n # Line2D\n- p, = plt.plot([1, 3], [6, 5])\n+ p, = plt.plot([2, 3, 4], [4, 5, 6])\n p.set_url('https://example.com/baz')\n \n+ # Line2D markers-only\n+ p, = plt.plot([3, 4, 5], [4, 5, 6], linestyle='none', marker='x')\n+ p.set_url('https://example.com/quux')\n+\n b = BytesIO()\n fig.savefig(b, format='svg')\n b = b.getvalue()\n- for v in [b'foo', b'bar', b'baz']:\n+ for v in [b'foo', b'bar', b'baz', b'quux']:\n assert b'https://example.com/' + v in b\n \n \n", "problem_statement": "[Bug]: set_url without effect for instances of Line2D with linestyle 'none'\n### Bug summary\n\nBug summary\r\nUsing SVG-backend, set_url does nothing for for instances of Line2D with linestyle='none', whereas it works for other objects.\r\nRelated to closed issue: https://github.com/matplotlib/matplotlib/issues/17336\n\n### Code for reproduction\n\n```Python\nfrom matplotlib import pyplot as plt\r\n\r\nf = plt.figure()\r\ns = plt.scatter([1, 2, 3], [4, 5, 6])\r\ns.set_urls(['http://www.bbc.co.uk/news', 'http://www.google.com', None])\r\np = plt.plot([1, 3], [6, 5], linestyle='none', color='green', marker='x')\r\np[0].set_url('http://www.duckduckgo.com')\r\n\r\nprint(s.get_urls())\r\nprint(p[0].get_url())\r\n\r\nimport io\r\nsvg = io.StringIO()\r\nf.savefig(svg, format='svg')\r\nf.savefig('test.svg', format='svg')\r\n\r\nassert \"http://www.google.com\" in svg.getvalue()\r\nassert \"http://www.duckduckgo.com\" in svg.getvalue()\n```\n\n\n### Actual outcome\n\nException has occurred: AssertionError\r\nexception: no description\r\n File \"/home/peunting/from matplotlib import pyplot as plt.py\", line 18, in \r\n assert \"http://www.duckduckgo.com\" in svg.getvalue()\r\nAssertionError: \n\n### Expected outcome\n\nBoth http://www.google.com and http://www.duckduckgo.com should be present in the svg. When the svg is opened in a browser, the plot markers of the line plot should be clickable as well.\n\n### Additional information\n\n_No response_\n\n### Operating system\n\nRocky\n\n### Matplotlib Version\n\n3.8.2\n\n### Matplotlib Backend\n\nQtAgg\n\n### Python version\n\n3.10.12\n\n### Jupyter version\n\n_No response_\n\n### Installation\n\npip\n", "hints_text": "I think in lines.py, line 812, the urls for the markers should be set:\r\nhttps://github.com/matplotlib/matplotlib/blob/ed17e00da9b8119c0a01290ebcace64bc0120b84/lib/matplotlib/lines.py#L809-L814\r\n@QuLogic : Do you have an idea why it is not working?\nYes, that code means the backend receives the URL, but doesn't necessarily mean the backend does anything with it. For SVG, I don't see any handling of URLs for plain markers:\r\nhttps://github.com/matplotlib/matplotlib/blob/e6f2b0c72be421f3b324a55fca1dbc367405cce5/lib/matplotlib/backends/backend_svg.py#L692-L729", "created_at": "2024-07-23T20:37:00Z" }, { "repo": "matplotlib/matplotlib", "pull_number": 28582, "instance_id": "matplotlib__matplotlib-28582", "issue_numbers": [ "28567", "0000" ], "base_commit": "9b8a8c7246c4a8e78095675f33e7f10c9d815dc4", "patch": "diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py\nindex 4606e5c01aec..a29583668a17 100644\n--- a/lib/matplotlib/axes/_base.py\n+++ b/lib/matplotlib/axes/_base.py\n@@ -2962,22 +2962,16 @@ def handle_single_axis(\n \n # Prevent margin addition from crossing a sticky value. A small\n # tolerance must be added due to floating point issues with\n- # streamplot; it is defined relative to x0, x1, x1-x0 but has\n+ # streamplot; it is defined relative to x1-x0 but has\n # no absolute term (e.g. \"+1e-8\") to avoid issues when working with\n # datasets where all values are tiny (less than 1e-8).\n- tol = 1e-5 * max(abs(x0), abs(x1), abs(x1 - x0))\n+ tol = 1e-5 * abs(x1 - x0)\n # Index of largest element < x0 + tol, if any.\n i0 = stickies.searchsorted(x0 + tol) - 1\n x0bound = stickies[i0] if i0 != -1 else None\n- # Ensure the boundary acts only if the sticky is the extreme value\n- if x0bound is not None and x0bound > x0:\n- x0bound = None\n # Index of smallest element > x1 - tol, if any.\n i1 = stickies.searchsorted(x1 - tol)\n x1bound = stickies[i1] if i1 != len(stickies) else None\n- # Ensure the boundary acts only if the sticky is the extreme value\n- if x1bound is not None and x1bound < x1:\n- x1bound = None\n \n # Add the margin in figure space and then transform back, to handle\n # non-linear scales.\n", "test_patch": "diff --git a/lib/matplotlib/tests/baseline_images/test_axes/sticky_tolerance_cf.png b/lib/matplotlib/tests/baseline_images/test_axes/sticky_tolerance_cf.png\nnew file mode 100644\nindex 000000000000..a2e185c2769d\nBinary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_axes/sticky_tolerance_cf.png differ\ndiff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py\nindex 13c181b68492..3791763f323f 100644\n--- a/lib/matplotlib/tests/test_axes.py\n+++ b/lib/matplotlib/tests/test_axes.py\n@@ -701,6 +701,16 @@ def test_sticky_tolerance():\n axs.flat[3].barh(y=1, width=width, left=-20000.1)\n \n \n+@image_comparison(['sticky_tolerance_cf.png'], remove_text=True, style=\"mpl20\")\n+def test_sticky_tolerance_contourf():\n+ fig, ax = plt.subplots()\n+\n+ x = y = [14496.71, 14496.75]\n+ data = [[0, 1], [2, 3]]\n+\n+ ax.contourf(x, y, data)\n+\n+\n def test_nargs_stem():\n with pytest.raises(TypeError, match='0 were given'):\n # stem() takes 1-3 arguments.\n", "problem_statement": "[Bug]: sticky edge related changes for datetime plots\n### Bug summary\r\n\r\nHaving just picked up v3.9.1, we are seeing downstream image test failures due to additional whitespace in `contourf` and `pcolor` plots that use datetimes. The change bisects to #28393.\r\n\r\n### Code for reproduction\r\n\r\n```Python\r\nimport datetime\r\n\r\nimport numpy as np\r\nimport matplotlib.pyplot as plt\r\n\r\ndts = [datetime.datetime(2009, 9, 9, 17, 0) + datetime.timedelta(minutes=n) for n in range(10, 61, 10)]\r\nfoo = range(5)\r\ndata = np.arange(30).reshape(6, 5)\r\n\r\nplt.contourf(foo, dts, data)\r\nplt.show()\r\n```\r\n\r\n\r\n### Actual outcome\r\n\r\n![image](https://github.com/user-attachments/assets/4f616f86-14d7-4335-b0c1-e5dfc637cdbc)\r\n\r\n\r\n### Expected outcome\r\n\r\nBefore #28393 I get\r\n![image](https://github.com/user-attachments/assets/e9dc1e03-2945-428b-bb71-eceb1114f5d4)\r\n\r\n\r\n### Additional information\r\n\r\n_No response_\r\n\r\n### Operating system\r\n\r\nUbuntu\r\n\r\n### Matplotlib Version\r\n\r\nv3.9.1 and `main`\r\n\r\n### Matplotlib Backend\r\n\r\n_No response_\r\n\r\n### Python version\r\n\r\n3.12\r\n\r\n### Jupyter version\r\n\r\n_No response_\r\n\r\n### Installation\r\n\r\nconda\n", "hints_text": "If I get hold of the contourset and the axes here, I have\r\n\r\n```python\r\nIn [4]: cs.sticky_edges.y[0] == ax.dataLim.ymin\r\nOut[4]: np.True_\r\n\r\nIn [5]: cs.sticky_edges.y[1] == ax.dataLim.ymax\r\nOut[5]: np.True_\r\n\r\nIn [6]: cs.sticky_edges.x[0] == ax.dataLim.xmin\r\nOut[6]: np.True_\r\n\r\nIn [7]: cs.sticky_edges.x[1] == ax.dataLim.xmax\r\nOut[7]: np.True_\r\n\r\n```\r\n:confused: \nIt seems like, by the time we get the the relevant comparison, somehow the sticky limits got flipped:\r\n\r\n```diff\r\ndiff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py\r\nindex 4606e5c01a..9d5274d9e6 100644\r\n--- a/lib/matplotlib/axes/_base.py\r\n+++ b/lib/matplotlib/axes/_base.py\r\n@@ -2969,12 +2969,14 @@ class _AxesBase(martist.Artist):\r\n # Index of largest element < x0 + tol, if any.\r\n i0 = stickies.searchsorted(x0 + tol) - 1\r\n x0bound = stickies[i0] if i0 != -1 else None\r\n+ print(f'{x0bound=}, {x0=}')\r\n # Ensure the boundary acts only if the sticky is the extreme value\r\n if x0bound is not None and x0bound > x0:\r\n x0bound = None\r\n # Index of smallest element > x1 - tol, if any.\r\n i1 = stickies.searchsorted(x1 - tol)\r\n x1bound = stickies[i1] if i1 != len(stickies) else None\r\n+ print(f'{x1bound=}, {x1=}')\r\n # Ensure the boundary acts only if the sticky is the extreme value\r\n if x1bound is not None and x1bound < x1:\r\n x1bound = None\r\n```\r\n```\r\nx0bound=np.float64(14496.75), x0=np.float64(14496.715277777777)\r\nx1bound=np.float64(14496.715277777777), x1=np.float64(14496.75)\r\n```\nSo I guess the problem is basically the same as #28223 in that the data range is less than the tolerance. If I make the tolerance only a function of the range\r\n\r\n```python\r\ntol = 1e-5 * abs(x1 - x0)\r\n```\r\nas suggested at https://github.com/matplotlib/matplotlib/pull/28393#issuecomment-2168453717, this is fixed and no existing tests break.\nYeah, I came to the same conclusion... I think that works, and makes more sense to me... I can't think of a case where the absolute magnitude is preferred to the range of the data for this computation.", "created_at": "2024-07-16T19:42:29Z" }, { "repo": "matplotlib/matplotlib", "pull_number": 28577, "instance_id": "matplotlib__matplotlib-28577", "issue_numbers": [ "28574", "0000" ], "base_commit": "c51103a068db018bad1152f78141d6c7360c29ff", "patch": "diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py\nindex 483c9a3db15f..d1bbd095da87 100644\n--- a/lib/matplotlib/axis.py\n+++ b/lib/matplotlib/axis.py\n@@ -33,12 +33,6 @@\n _gridline_param_names = ['grid_' + name\n for name in _line_param_names + _line_param_aliases]\n \n-_MARKER_DICT = {\n- 'out': (mlines.TICKDOWN, mlines.TICKUP),\n- 'in': (mlines.TICKUP, mlines.TICKDOWN),\n- 'inout': ('|', '|'),\n-}\n-\n \n class Tick(martist.Artist):\n \"\"\"\n@@ -204,18 +198,21 @@ def _set_labelrotation(self, labelrotation):\n _api.check_in_list(['auto', 'default'], labelrotation=mode)\n self._labelrotation = (mode, angle)\n \n+ @property\n+ def _pad(self):\n+ return self._base_pad + self.get_tick_padding()\n+\n def _apply_tickdir(self, tickdir):\n \"\"\"Set tick direction. Valid values are 'out', 'in', 'inout'.\"\"\"\n- # This method is responsible for updating `_pad`, and, in subclasses,\n- # for setting the tick{1,2}line markers as well. From the user\n- # perspective this should always be called through _apply_params, which\n- # further updates ticklabel positions using the new pads.\n+ # This method is responsible for verifying input and, in subclasses, for setting\n+ # the tick{1,2}line markers. From the user perspective this should always be\n+ # called through _apply_params, which further updates ticklabel positions using\n+ # the new pads.\n if tickdir is None:\n tickdir = mpl.rcParams[f'{self.__name__}.direction']\n else:\n _api.check_in_list(['in', 'out', 'inout'], tickdir=tickdir)\n self._tickdir = tickdir\n- self._pad = self._base_pad + self.get_tick_padding()\n \n def get_tickdir(self):\n return self._tickdir\n@@ -425,7 +422,11 @@ def _get_text2_transform(self):\n def _apply_tickdir(self, tickdir):\n # docstring inherited\n super()._apply_tickdir(tickdir)\n- mark1, mark2 = _MARKER_DICT[self._tickdir]\n+ mark1, mark2 = {\n+ 'out': (mlines.TICKDOWN, mlines.TICKUP),\n+ 'in': (mlines.TICKUP, mlines.TICKDOWN),\n+ 'inout': ('|', '|'),\n+ }[self._tickdir]\n self.tick1line.set_marker(mark1)\n self.tick2line.set_marker(mark2)\n \n@@ -1617,6 +1618,14 @@ def _copy_tick_props(self, src, dest):\n dest.tick1line.update_from(src.tick1line)\n dest.tick2line.update_from(src.tick2line)\n dest.gridline.update_from(src.gridline)\n+ dest.update_from(src)\n+ dest._loc = src._loc\n+ dest._size = src._size\n+ dest._width = src._width\n+ dest._base_pad = src._base_pad\n+ dest._labelrotation = src._labelrotation\n+ dest._zorder = src._zorder\n+ dest._tickdir = src._tickdir\n \n def get_label_text(self):\n \"\"\"Get the text of the label.\"\"\"\n", "test_patch": "diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py\nindex e5ae14c6e66b..2c10a93796fa 100644\n--- a/lib/matplotlib/tests/test_axes.py\n+++ b/lib/matplotlib/tests/test_axes.py\n@@ -5741,6 +5741,28 @@ def test_reset_ticks(fig_test, fig_ref):\n ax.yaxis.reset_ticks()\n \n \n+@mpl.style.context('mpl20')\n+def test_context_ticks():\n+ with plt.rc_context({\n+ 'xtick.direction': 'in', 'xtick.major.size': 30, 'xtick.major.width': 5,\n+ 'xtick.color': 'C0', 'xtick.major.pad': 12,\n+ 'xtick.bottom': True, 'xtick.top': True,\n+ 'xtick.labelsize': 14, 'xtick.labelcolor': 'C1'}):\n+ fig, ax = plt.subplots()\n+ # Draw outside the context so that all-but-first tick are generated with the normal\n+ # mpl20 style in place.\n+ fig.draw_without_rendering()\n+\n+ first_tick = ax.xaxis.majorTicks[0]\n+ for tick in ax.xaxis.majorTicks[1:]:\n+ assert tick._size == first_tick._size\n+ assert tick._width == first_tick._width\n+ assert tick._base_pad == first_tick._base_pad\n+ assert tick._labelrotation == first_tick._labelrotation\n+ assert tick._zorder == first_tick._zorder\n+ assert tick._tickdir == first_tick._tickdir\n+\n+\n def test_vline_limit():\n fig = plt.figure()\n ax = fig.gca()\n", "problem_statement": "[Bug]: Nondeterministic behavior with subplot spacing and constrained layout\n### Bug summary\r\n\r\nI'm trying to get constrained layout to remove all space between subplots when no artists should be preventing it. In this example I've set all necessary rc params accordingly and changed the axes limits to ensure the x-axis tick labels don't spill beyond the axis extent. The resulting space between the axes is nondeterministic.\r\n\r\n\r\n### Code for reproduction\r\n\r\n```Python\r\n%matplotlib inline\r\n\r\nimport matplotlib.pyplot as plt\r\nrc = {\r\n \"xtick.direction\": \"in\", \r\n \"ytick.direction\": \"in\",\r\n \"figure.constrained_layout.use\": True,\r\n \"figure.constrained_layout.h_pad\": 0,\r\n \"figure.constrained_layout.w_pad\": 0,\r\n \"figure.constrained_layout.wspace\": 0,\r\n \"figure.constrained_layout.hspace\": 0,\r\n \"axes.labelpad\": 0,\r\n}\r\n\r\nfor _ in range(2):\r\n with plt.style.context(rc):\r\n fig, axes = plt.subplots(1, 2, figsize=(6, 2), sharey=True)\r\n \r\n for ax in axes.flat:\r\n ax.set_xlim(-0.1, 1.1)\r\n```\r\n\r\n\r\n### Actual outcome\r\n\r\nExample output (ran a handful of times until one of each result showed):\r\n![image](https://github.com/user-attachments/assets/23564beb-f5c1-4a03-a2f5-35cfb4083907)\r\n\r\n### Expected outcome\r\n\r\nZero spacing between the axes, deterministically.\r\n\r\n### Additional information\r\n\r\nI noticed that the results became deterministic (with zero space between panels) when saving the figure (that is, the result that displayed in the notebook, not just the saved figure). Inserting a `fig.canvas.draw()` at the end likewise ensures zero space deterministically.\r\n\r\n### Operating system\r\n\r\nRocky Linux\r\n\r\n### Matplotlib Version\r\n\r\n3.9.1\r\n\r\n### Matplotlib Backend\r\n\r\ninline\r\n\r\n### Python version\r\n\r\n3.11.9\r\n\r\n### Jupyter version\r\n\r\n4.2.3\r\n\r\n### Installation\r\n\r\nconda\n", "hints_text": "", "created_at": "2024-07-15T03:28:04Z" }, { "repo": "matplotlib/matplotlib", "pull_number": 28573, "instance_id": "matplotlib__matplotlib-28573", "issue_numbers": [ "27878", "0000" ], "base_commit": "c4510cae31e37f5dd8fe7ac8c3843e72ec8492ee", "patch": "diff --git a/doc/users/next_whats_new/exception_prop_name.rst b/doc/users/next_whats_new/exception_prop_name.rst\nnew file mode 100644\nindex 000000000000..c887b879393c\n--- /dev/null\n+++ b/doc/users/next_whats_new/exception_prop_name.rst\n@@ -0,0 +1,26 @@\n+Exception handling control\n+~~~~~~~~~~~~~~~~~~~~~~~~~~\n+\n+The exception raised when an invalid keyword parameter is passed now includes\n+that parameter name as the exception's ``name`` property. This provides more\n+control for exception handling:\n+\n+\n+.. code-block:: python\n+\n+ import matplotlib.pyplot as plt\n+\n+ def wobbly_plot(args, **kwargs):\n+ w = kwargs.pop('wobble_factor', None)\n+\n+ try:\n+ plt.plot(args, **kwargs)\n+ except AttributeError as e:\n+ raise AttributeError(f'wobbly_plot does not take parameter {e.name}') from e\n+\n+\n+ wobbly_plot([0, 1], wibble_factor=5)\n+\n+.. code-block::\n+\n+ AttributeError: wobbly_plot does not take parameter wibble_factor\ndiff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py\nindex baf3b01ee6e5..345a61bfc16a 100644\n--- a/lib/matplotlib/artist.py\n+++ b/lib/matplotlib/artist.py\n@@ -1190,7 +1190,8 @@ def _update_props(self, props, errfmt):\n Helper for `.Artist.set` and `.Artist.update`.\n \n *errfmt* is used to generate error messages for invalid property\n- names; it gets formatted with ``type(self)`` and the property name.\n+ names; it gets formatted with ``type(self)`` for \"{cls}\" and the\n+ property name for \"{prop_name}\".\n \"\"\"\n ret = []\n with cbook._setattr_cm(self, eventson=False):\n@@ -1203,7 +1204,8 @@ def _update_props(self, props, errfmt):\n func = getattr(self, f\"set_{k}\", None)\n if not callable(func):\n raise AttributeError(\n- errfmt.format(cls=type(self), prop_name=k))\n+ errfmt.format(cls=type(self), prop_name=k),\n+ name=k)\n ret.append(func(v))\n if ret:\n self.pchanged()\n", "test_patch": "diff --git a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py\nindex be988c31ee75..f519b42098e5 100644\n--- a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py\n+++ b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py\n@@ -1501,8 +1501,9 @@ def test_calling_conventions(self):\n ax.voxels(x, y)\n # x, y, z are positional only - this passes them on as attributes of\n # Poly3DCollection\n- with pytest.raises(AttributeError):\n+ with pytest.raises(AttributeError, match=\"keyword argument 'x'\") as exec_info:\n ax.voxels(filled=filled, x=x, y=y, z=z)\n+ assert exec_info.value.name == 'x'\n \n \n def test_line3d_set_get_data_3d():\n", "problem_statement": "[ENH]: AttributeError('... got an unexpected keyword argument ...') should set the .name attribute to the keyword\n### Problem\n\nI am in the process to write a library that uses the matplotlib library for its graphics needs. As such, most of \"our\" graphics functions are wrappers around existing matplotlib functions such as tripcolor, tricontour\u2026 A typical function of ours would be:\r\n\r\n```\r\ndef fun_name_altered(lib_objects, **kwargs):\r\n args = prepare_args(lib_objects, **kwargs)\r\n plt.fun_name(*args, **kwargs)\r\n```\r\n\r\nIn this case the `kwargs` dictionnary may contain keywords aimed at the `prepare_args` process and keywords aimed at controlling the plotting. At the moment, `prepare_args` removes keywords from the dictionnary when used so that in principle, when `kwargs` arrive at `plt.fun_name`, it should only contain keywords known to `plt.fun_name`\r\n\r\nHowever, if the user makes a mistake while setting the keyword for OUR part, it is not properly handled by `prepare_args` and it does remain inside `kwargs` and then is passed to `plt.fun_name` which then raises an `AttributeError('... got an unexpected keyword argument ...')`.\r\n\r\nI would like to be able to try and recover if that happens.\n\n### Proposed solution\n\nFrom the [Built-in Exceptions documentation page](https://docs.python.org/3/library/exceptions.html#bltin-exceptions)\r\n\r\n> The name and obj attributes can be set using keyword-only arguments to the constructor. When set they represent the name of the attribute that was attempted to be accessed and the object that was accessed for said attribute, respectively.\r\nChanged in version 3.10: Added the name and obj attributes\r\n\r\nI looked at the matplotlib codebase but I am not an expert programmer so I may be wrong but I think what I would like was that `Artist._update_props` set the `name` attribute to `k` when raising the `AttributeError`.\r\n\r\nSince I am using Python 3.11 this would allow me to try and remove spurious keywords.\n", "hints_text": "This seems reasonable to me. My understanding is the same and it should just be a case of adding `name=k` here:\r\n\r\nhttps://github.com/matplotlib/matplotlib/blob/0da2da00d5a884db9c7991c31ee81d1b5dc57b63/lib/matplotlib/artist.py#L1193-L1194\r\n\r\nThis will break for python 3.9 but, according to the [NEP29 schedule](https://numpy.org/neps/nep-0029-deprecation_policy.html#support-table), we should be dropping support for that in April anyway. So if we target this for Matplotlib 3.10 there will be no problem.", "created_at": "2024-07-14T07:49:51Z" }, { "repo": "matplotlib/matplotlib", "pull_number": 28541, "instance_id": "matplotlib__matplotlib-28541", "issue_numbers": [ "28538", "0000" ], "base_commit": "830361d544ccca824e4df745eabf25dd4afdd26f", "patch": "diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py\nindex 813bee6eb623..d9560ec0cc0f 100644\n--- a/lib/matplotlib/font_manager.py\n+++ b/lib/matplotlib/font_manager.py\n@@ -965,11 +965,11 @@ def json_dump(data, filename):\n This function temporarily locks the output file to prevent multiple\n processes from overwriting one another's output.\n \"\"\"\n- with cbook._lock_path(filename), open(filename, 'w') as fh:\n- try:\n+ try:\n+ with cbook._lock_path(filename), open(filename, 'w') as fh:\n json.dump(data, fh, cls=_JSONEncoder, indent=2)\n- except OSError as e:\n- _log.warning('Could not save font_manager cache %s', e)\n+ except OSError as e:\n+ _log.warning('Could not save font_manager cache %s', e)\n \n \n def json_load(filename):\n", "test_patch": "diff --git a/lib/matplotlib/tests/test_font_manager.py b/lib/matplotlib/tests/test_font_manager.py\nindex 2dc530bf984b..c6fc422ca613 100644\n--- a/lib/matplotlib/tests/test_font_manager.py\n+++ b/lib/matplotlib/tests/test_font_manager.py\n@@ -16,7 +16,7 @@\n json_dump, json_load, get_font, is_opentype_cff_font,\n MSUserFontDirectories, _get_fontconfig_fonts, ttfFontProperty)\n from matplotlib import cbook, ft2font, pyplot as plt, rc_context, figure as mfigure\n-from matplotlib.testing import subprocess_run_helper\n+from matplotlib.testing import subprocess_run_helper, subprocess_run_for_testing\n \n \n has_fclist = shutil.which('fc-list') is not None\n@@ -287,6 +287,28 @@ def test_fontcache_thread_safe():\n subprocess_run_helper(_test_threading, timeout=10)\n \n \n+def test_lockfilefailure(tmp_path):\n+ # The logic here:\n+ # 1. get a temp directory from pytest\n+ # 2. import matplotlib which makes sure it exists\n+ # 3. get the cache dir (where we check it is writable)\n+ # 4. make it not writable\n+ # 5. try to write into it via font manager\n+ proc = subprocess_run_for_testing(\n+ [\n+ sys.executable,\n+ \"-c\",\n+ \"import matplotlib;\"\n+ \"import os;\"\n+ \"p = matplotlib.get_cachedir();\"\n+ \"os.chmod(p, 0o555);\"\n+ \"import matplotlib.font_manager;\"\n+ ],\n+ env={**os.environ, 'MPLCONFIGDIR': str(tmp_path)},\n+ check=True\n+ )\n+\n+\n def test_fontentry_dataclass():\n fontent = FontEntry(name='font-name')\n \n", "problem_statement": "[Bug]: Permission denied when importing matplotlib.pyplot\n### Bug summary\r\n\r\nOnce I type \" import matplotlib.pyplot as plt\" and run it, jupyter always returns\uff1a\r\n\uff02Matplotlib is building the font cache; this may take a moment.\uff02\r\n\uff03Then the return messages will end up as \uff1a\r\n \"PermissionError: [Errno 13] Permission denied: 'C:\\\\Users\\\\J\\\\.matplotlib\\\\fontlist-v330.json.matplotlib-lock'\r\n\r\nAnd the functions of matplotlib.pyplot can not be excuted.\r\n\r\n### Code for reproduction\r\n\r\n```Python\r\nimport matplotlib.pyplot as plt\r\n```\r\n\r\n\r\n### Actual outcome\r\n```\r\nMatplotlib is building the font cache; this may take a moment.\r\n\r\nPermissionError Traceback (most recent call last)\r\nCell In[3], line 1\r\n----> 1 import matplotlib.pyplot as plt\r\n\r\nFile ~\\anaconda3\\Lib\\site-packages\\matplotlib\\pyplot.py:56\r\n 54 from cycler import cycler\r\n 55 import matplotlib\r\n---> 56 import matplotlib.colorbar\r\n 57 import matplotlib.image\r\n 58 from matplotlib import _api\r\n\r\nFile ~\\anaconda3\\Lib\\site-packages\\matplotlib\\colorbar.py:19\r\n 16 import numpy as np\r\n 18 import matplotlib as mpl\r\n---> 19 from matplotlib import _api, cbook, collections, cm, colors, contour, ticker\r\n 20 import matplotlib.artist as martist\r\n 21 import matplotlib.patches as mpatches\r\n\r\nFile ~\\anaconda3\\Lib\\site-packages\\matplotlib\\contour.py:15\r\n 13 import matplotlib as mpl\r\n 14 from matplotlib import _api, _docstring\r\n---> 15 from matplotlib.backend_bases import MouseButton\r\n 16 from matplotlib.lines import Line2D\r\n 17 from matplotlib.path import Path\r\n\r\nFile ~\\anaconda3\\Lib\\site-packages\\matplotlib\\backend_bases.py:46\r\n 43 import numpy as np\r\n 45 import matplotlib as mpl\r\n---> 46 from matplotlib import (\r\n 47 _api, backend_tools as tools, cbook, colors, _docstring, text,\r\n 48 _tight_bbox, transforms, widgets, is_interactive, rcParams)\r\n 49 from matplotlib._pylab_helpers import Gcf\r\n 50 from matplotlib.backend_managers import ToolManager\r\n\r\nFile ~\\anaconda3\\Lib\\site-packages\\matplotlib\\text.py:16\r\n 14 from . import _api, artist, cbook, _docstring\r\n 15 from .artist import Artist\r\n---> 16 from .font_manager import FontProperties\r\n 17 from .patches import FancyArrowPatch, FancyBboxPatch, Rectangle\r\n 18 from .textpath import TextPath, TextToPath # noqa # Logically located here\r\n\r\nFile ~\\anaconda3\\Lib\\site-packages\\matplotlib\\font_manager.py:1582\r\n 1578 _log.info(\"generated new fontManager\")\r\n 1579 return fm\r\n-> 1582 fontManager = _load_fontmanager()\r\n 1583 findfont = fontManager.findfont\r\n 1584 get_font_names = fontManager.get_font_names\r\n\r\nFile ~\\anaconda3\\Lib\\site-packages\\matplotlib\\font_manager.py:1577, in _load_fontmanager(try_read_cache)\r\n 1575 return fm\r\n 1576 fm = FontManager()\r\n-> 1577 json_dump(fm, fm_path)\r\n 1578 _log.info(\"generated new fontManager\")\r\n 1579 return fm\r\n\r\nFile ~\\anaconda3\\Lib\\site-packages\\matplotlib\\font_manager.py:963, in json_dump(data, filename)\r\n 946 def json_dump(data, filename):\r\n 947 \"\"\"\r\n 948 Dump `FontManager` *data* as JSON to the file named *filename*.\r\n 949 \r\n (...)\r\n 961 processes from overwriting one another's output.\r\n 962 \"\"\"\r\n--> 963 with cbook._lock_path(filename), open(filename, 'w') as fh:\r\n 964 try:\r\n 965 json.dump(data, fh, cls=_JSONEncoder, indent=2)\r\n\r\nFile ~\\anaconda3\\Lib\\contextlib.py:137, in _GeneratorContextManager.__enter__(self)\r\n 135 del self.args, self.kwds, self.func\r\n 136 try:\r\n--> 137 return next(self.gen)\r\n 138 except StopIteration:\r\n 139 raise RuntimeError(\"generator didn't yield\") from None\r\n\r\nFile ~\\anaconda3\\Lib\\site-packages\\matplotlib\\cbook.py:1821, in _lock_path(path)\r\n 1819 for _ in range(retries):\r\n 1820 try:\r\n-> 1821 with lock_path.open(\"xb\"):\r\n 1822 break\r\n 1823 except FileExistsError:\r\n\r\nFile ~\\anaconda3\\Lib\\pathlib.py:1013, in Path.open(self, mode, buffering, encoding, errors, newline)\r\n 1011 if \"b\" not in mode:\r\n 1012 encoding = io.text_encoding(encoding)\r\n-> 1013 return io.open(self, mode, buffering, encoding, errors, newline)\r\n\r\nPermissionError: [Errno 13] Permission denied: 'C:\\\\Users\\\\happy\\\\.matplotlib\\\\fontlist-v330.json.matplotlib-lock'\r\n\r\n### Expected outcome\r\n\r\nThe expected outcome is to successful import matplotlib.pyplot. \r\n(\uff33\uff4f\uff52\uff52\uff59\u3000\uff54\uff4f\u3000\uff42\uff4f\uff54\uff48\uff45\uff52\u3000\uff59\uff4f\uff55\uff0e\r\nI have struggled with this for more than 2 weeks and searched different methods to resolve it. But, ...\r\nPlease help me to out of this, many many thanks!)\r\n\r\n### Additional information\r\n\r\n\uff0aWhat are the conditions under which this bug happens? input parameters, edge cases, etc?\r\nAns: When I type \" import matplotlib.pyplot as plt\" and run it in the Jupyter Notebook.\r\n\uff0aHas this worked in earlier versions? \r\nAns:\u3000No. \r\n\uff0aDo you know why this bug is happening?\r\n\uff21\uff4e\uff53\uff1a\u3000 No. \r\n\uff0aDo you maybe even know a fix? \r\n\uff21\uff4e\uff53\uff1aNo.\r\nI have tried a lot of times to figure out this import problem, including uninstall/reinstall anaconda, python, upgrade the version of matplotlib......\r\n Based on the google search results, I also tried to type\r\n \"import matplotlib matplotlib.use('TkAgg')\" \r\nbefore I import matplotlib.pyplot. \r\nBut it returns me \r\n\uff02Cell In[2], \r\nline 1 import matplotlib matplotlib.use('TkAgg') \r\n^ SyntaxError: invalid syntax\uff02\r\n```\r\n\r\n### Operating system\r\n\r\n\uff37\uff49\uff4e\uff44\uff4f\uff57\uff53\r\n\r\n### Matplotlib Version\r\n\r\nmatplotlib 3.8.4\r\n\r\n### Matplotlib Backend\r\n\r\nmatplotlib-inline 0.1.6\r\n\r\n### Python version\r\n\r\nPython 3.12.3\r\n\r\n### Jupyter version\r\n\r\n\uff2a\uff55\uff50\uff59\uff54\uff45\uff52\u30007.0.8\r\n\r\n### Installation\r\n\r\nconda\r\n\r\n\r\n[TAC edited to add markup]\n", "hints_text": "The error is that you don't have permission to create `C:\\Users\\J\\.matplotlib\\fontlist-v330.json.matplotlib-lock`. This is unusual, but I doubt there is a bug in matplotlib. You need to see what the permissions are for the `C:\\Users\\J\\.matplotlib\\` folder. I am assuming that you are logged in as the user `J`?\nAgree that this is a permissions issue and you should sort out why a process running as your user can not create the lock file. I would try removing `C:\\Users\\J\\.matplotlib` and letting us re-generate it on the next import. On startup we check that the path exists, is a directory, and is writable. It would be good to sort out how you got to this state so we can make our test more correct.\r\n\r\nhttps://github.com/matplotlib/matplotlib/blob/6b7b99927458de5980f8d5590073dd81e44e4cc5/lib/matplotlib/__init__.py#L520-L534\r\n\r\nis how we are currently checking that we can write to the cache directory.\r\n\r\nThat said, we do have the behavior that if the config directory is not writable we skip writing and continue on. \r\n\r\nhttps://github.com/matplotlib/matplotlib/blob/6b7b99927458de5980f8d5590073dd81e44e4cc5/lib/matplotlib/font_manager.py#L968-L972\r\n\r\nWe are being careful about the json writing failing, but not careful about the lock failing! \n> The error is that you don't have permission to create `C:\\Users\\J\\.matplotlib\\fontlist-v330.json.matplotlib-lock`. This is unusual, but I doubt there is a bug in matplotlib. You need to see what the permissions are for the `C:\\Users\\J\\.matplotlib\\` folder. I am assuming that you are logged in as the user `J`?\r\n\r\n\r\n\r\nDear WeatherGod and tacaswell,\r\nThank you so much for your suggestion.\r\nYes, I am login as \"J\". \r\nI will try what your suggestions to see whether it can be resolved or not.\r\n\r\nActually, when I bumped into that import problem, I had tried to figure it out by reinstall my Anaconda and Python, therefor I remove both of them through the \"Uninstall or change a program\" of \"Control panel\". \r\nAfter the removing, I did the re-install of Anaconda. But one message is shown up as:\r\n \"A version of Python 3.12(64-bit) is alreadt at C:\\Program Files\\WindowsApps\\PythonSoftwareFoundation.Python.3.12_3.12.1264.O_x64_qbz5n2kfra8pO\r\nWe recommend that if you want Anaconda3 registered as your system Python, you unregister this Python first. If you really know this is what you want, click OK, otherwise click cancel to continue.\"\r\nTherefor, I went back to check the existence of Python form the \"Uninstall or change a program\" , but there is nothing left.\r\nThen, I tried to reach the Python 3.12 mentioned in the above message to remove it. But I was prohibited from accessing the C:\\Program Files\\.\r\nSo I went back to the Anaconda installation and finish it.\r\nAfter that, I checked the versions of Anaconda, Python. Open the jupyter notebook and type the \"import matplotlib.pyplot as plt\" and run it, and, the Denied Message shown it self again to me.\r\nI am wondering whether this event being related with this import denied problem since before that, the use of jupyter and importing of matplotlib.pyplot were smooth.\r\n\r\nA lot of thanks for your great suggestions. I will try it! Thank you!\nIt is highly unlikely that the installation and uninstallation problems\r\nhave anything to do with the permission issues.\r\n\r\nOn Thu, Jul 11, 2024 at 12:24\u202fPM JCisCS ***@***.***> wrote:\r\n\r\n> The error is that you don't have permission to create\r\n> C:\\Users\\J\\.matplotlib\\fontlist-v330.json.matplotlib-lock. This is\r\n> unusual, but I doubt there is a bug in matplotlib. You need to see what the\r\n> permissions are for the C:\\Users\\J\\.matplotlib\\ folder. I am assuming\r\n> that you are logged in as the user J?\r\n>\r\n> Dear WeatherGod and tacaswell,\r\n> Thank you so much for your suggestion.\r\n> Yes, I am login as \"J\".\r\n> I will try what your suggestions to see whether it can be resolved or not.\r\n>\r\n> Actually, when I bumped into that import problem, I had tried to figure it\r\n> out by reinstall my Anaconda and Python, therefor I remove both of them\r\n> through the \"Uninstall or change a program\" of \"Control panel\".\r\n> After the removing, I did the re-install of Anaconda. But one message is\r\n> shown up as:\r\n> \"A version of Python 3.12(64-bit) is alreadt at C:\\Program\r\n> Files\\WindowsApps\\PythonSoftwareFoundation.Python.3.12_3.12.1264.O_x64_qbz5n2kfra8pO\r\n> We recommend that if you want Anaconda3 registered as your system Python,\r\n> you unregister this Python first. If you really know this is what you want,\r\n> click OK, otherwise click cancel to continue.\"\r\n> Therefor, I went back to check the existence of Python form the \"Uninstall\r\n> or change a program\" , but there is nothing left.\r\n> Then, I tried to reach the Python 3.12 mentioned in the above message to\r\n> remove it. But I was prohibited from accessing the C:\\Program Files.\r\n> So I went back to the Anaconda installation and finish it.\r\n> After that, I checked the versions of Anaconda, Python. Open the jupyter\r\n> notebook and type the \"import matplotlib.pyplot as plt\" and run it, and,\r\n> the Denied Message shown it self again to me.\r\n> I am wondering whether this event being related with this import denied\r\n> problem since before that, the use of jupyter and importing of\r\n> matplotlib.pyplot were smooth.\r\n>\r\n> A lot of thanks for your great suggestions. I will try it! Thank you!\r\n>\r\n> \u2014\r\n> Reply to this email directly, view it on GitHub\r\n> ,\r\n> or unsubscribe\r\n> \r\n> .\r\n> You are receiving this because you commented.Message ID:\r\n> ***@***.***>\r\n>\r\n\nI'm going to re-open this as we should not be failing to import due to failure to write the cache.", "created_at": "2024-07-11T20:43:11Z" }, { "repo": "matplotlib/matplotlib", "pull_number": 28503, "instance_id": "matplotlib__matplotlib-28503", "issue_numbers": [ "28489", "0000" ], "base_commit": "e5225b4a5d3486637852fff2d37dc129da12fce4", "patch": "diff --git a/.circleci/config.yml b/.circleci/config.yml\nindex 5b4cbf5570b8..7436698c8068 100644\n--- a/.circleci/config.yml\n+++ b/.circleci/config.yml\n@@ -216,9 +216,9 @@ commands:\n #\n \n jobs:\n- docs-python39:\n+ docs-python310:\n docker:\n- - image: cimg/python:3.9\n+ - image: cimg/python:3.10\n resource_class: large\n steps:\n - checkout\n@@ -259,4 +259,4 @@ workflows:\n jobs:\n # NOTE: If you rename this job, then you must update the `if` condition\n # and `circleci-jobs` option in `.github/workflows/circleci.yml`.\n- - docs-python39\n+ - docs-python310\ndiff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml\nindex 050ff16cfbbd..50adc91980de 100644\n--- a/.github/workflows/cibuildwheel.yml\n+++ b/.github/workflows/cibuildwheel.yml\n@@ -46,7 +46,7 @@ jobs:\n - uses: actions/setup-python@v5\n name: Install Python\n with:\n- python-version: 3.9\n+ python-version: '3.10'\n \n # Something changed somewhere that prevents the downloaded-at-build-time\n # licenses from being included in built wheels, so pre-download them so\n@@ -158,22 +158,14 @@ jobs:\n CIBW_BUILD: \"cp310-*\"\n CIBW_ARCHS: ${{ matrix.cibw_archs }}\n \n- - name: Build wheels for CPython 3.9\n- uses: pypa/cibuildwheel@7e5a838a63ac8128d71ab2dfd99e4634dd1bca09 # v2.19.2\n- with:\n- package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }}\n- env:\n- CIBW_BUILD: \"cp39-*\"\n- CIBW_ARCHS: ${{ matrix.cibw_archs }}\n-\n - name: Build wheels for PyPy\n uses: pypa/cibuildwheel@7e5a838a63ac8128d71ab2dfd99e4634dd1bca09 # v2.19.2\n with:\n package-dir: dist/${{ needs.build_sdist.outputs.SDIST_NAME }}\n env:\n- CIBW_BUILD: \"pp39-*\"\n+ CIBW_BUILD: \"pp310-*\"\n CIBW_ARCHS: ${{ matrix.cibw_archs }}\n- if: matrix.cibw_archs != 'aarch64'\n+ if: matrix.cibw_archs != 'aarch64' && matrix.os != 'windows-latest'\n \n - uses: actions/upload-artifact@v4\n with:\ndiff --git a/.github/workflows/circleci.yml b/.github/workflows/circleci.yml\nindex 3aead720cf20..c96dbecda7a1 100644\n--- a/.github/workflows/circleci.yml\n+++ b/.github/workflows/circleci.yml\n@@ -3,7 +3,7 @@ name: \"CircleCI artifact handling\"\n on: [status]\n jobs:\n circleci_artifacts_redirector_job:\n- if: \"${{ github.event.context == 'ci/circleci: docs-python39' }}\"\n+ if: \"${{ github.event.context == 'ci/circleci: docs-python310' }}\"\n permissions:\n statuses: write\n runs-on: ubuntu-latest\n@@ -16,11 +16,11 @@ jobs:\n repo-token: ${{ secrets.GITHUB_TOKEN }}\n api-token: ${{ secrets.CIRCLECI_TOKEN }}\n artifact-path: 0/doc/build/html/index.html\n- circleci-jobs: docs-python39\n+ circleci-jobs: docs-python310\n job-title: View the built docs\n \n post_warnings_as_review:\n- if: \"${{ github.event.context == 'ci/circleci: docs-python39' }}\"\n+ if: \"${{ github.event.context == 'ci/circleci: docs-python310' }}\"\n permissions:\n contents: read\n checks: write\ndiff --git a/.github/workflows/cygwin.yml b/.github/workflows/cygwin.yml\nindex 58c132315b6f..b8bb4400f2f3 100644\n--- a/.github/workflows/cygwin.yml\n+++ b/.github/workflows/cygwin.yml\n@@ -49,10 +49,12 @@ jobs:\n test-cygwin:\n runs-on: windows-latest\n name: Python 3.${{ matrix.python-minor-version }} on Cygwin\n+ # Enable these when Cygwin has Python 3.12.\n if: >-\n github.event_name == 'workflow_dispatch' ||\n- github.event_name == 'schedule' ||\n+ (false && github.event_name == 'schedule') ||\n (\n+ false &&\n github.repository == 'matplotlib/matplotlib' &&\n !contains(github.event.head_commit.message, '[ci skip]') &&\n !contains(github.event.head_commit.message, '[skip ci]') &&\n@@ -72,7 +74,7 @@ jobs:\n )\n strategy:\n matrix:\n- python-minor-version: [9]\n+ python-minor-version: [12]\n \n steps:\n - name: Fix line endings\ndiff --git a/.github/workflows/reviewdog.yml b/.github/workflows/reviewdog.yml\nindex fbd724571d80..12b59d866e42 100644\n--- a/.github/workflows/reviewdog.yml\n+++ b/.github/workflows/reviewdog.yml\n@@ -17,7 +17,7 @@ jobs:\n - name: Set up Python 3\n uses: actions/setup-python@v5\n with:\n- python-version: 3.9\n+ python-version: '3.10'\n \n - name: Install flake8\n run: pip3 install -r requirements/testing/flake8.txt\n@@ -42,7 +42,7 @@ jobs:\n - name: Set up Python 3\n uses: actions/setup-python@v5\n with:\n- python-version: 3.9\n+ python-version: '3.10'\n \n - name: Install mypy\n run: pip3 install -r requirements/testing/mypy.txt -r requirements/testing/all.txt\ndiff --git a/azure-pipelines.yml b/azure-pipelines.yml\nindex 91e653b033f2..35c95c3b1f94 100644\n--- a/azure-pipelines.yml\n+++ b/azure-pipelines.yml\n@@ -49,29 +49,20 @@ stages:\n - job: Pytest\n strategy:\n matrix:\n- Linux_py39:\n- vmImage: 'ubuntu-20.04' # keep one job pinned to the oldest image\n- python.version: '3.9'\n Linux_py310:\n- vmImage: 'ubuntu-latest'\n+ vmImage: 'ubuntu-20.04' # keep one job pinned to the oldest image\n python.version: '3.10'\n Linux_py311:\n vmImage: 'ubuntu-latest'\n python.version: '3.11'\n- macOS_py39:\n- vmImage: 'macOS-latest'\n- python.version: '3.9'\n macOS_py310:\n vmImage: 'macOS-latest'\n python.version: '3.10'\n macOS_py311:\n vmImage: 'macOS-latest'\n python.version: '3.11'\n- Windows_py39:\n- vmImage: 'windows-2019' # keep one job pinned to the oldest image\n- python.version: '3.9'\n Windows_py310:\n- vmImage: 'windows-latest'\n+ vmImage: 'windows-2019' # keep one job pinned to the oldest image\n python.version: '3.10'\n Windows_py311:\n vmImage: 'windows-latest'\ndiff --git a/doc/api/next_api_changes/development/28503-ES.rst b/doc/api/next_api_changes/development/28503-ES.rst\nnew file mode 100644\nindex 000000000000..e9b109cb8515\n--- /dev/null\n+++ b/doc/api/next_api_changes/development/28503-ES.rst\n@@ -0,0 +1,14 @@\n+Increase to minimum supported versions of dependencies\n+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n+\n+For Matplotlib 3.10, the :ref:`minimum supported versions ` are\n+being bumped:\n+\n++------------+-----------------+----------------+\n+| Dependency | min in mpl3.9 | min in mpl3.10 |\n++============+=================+================+\n+| Python | 3.9 | 3.10 |\n++------------+-----------------+----------------+\n+\n+This is consistent with our :ref:`min_deps_policy` and `SPEC0\n+`__\ndiff --git a/doc/install/dependencies.rst b/doc/install/dependencies.rst\nindex 8da22a16753b..3d921d2d10c9 100644\n--- a/doc/install/dependencies.rst\n+++ b/doc/install/dependencies.rst\n@@ -20,7 +20,7 @@ When installing through a package manager like ``pip`` or ``conda``, the\n mandatory dependencies are automatically installed. This list is mainly for\n reference.\n \n-* `Python `_ (>= 3.9)\n+* `Python `_ (>= 3.10)\n * `contourpy `_ (>= 1.0.1)\n * `cycler `_ (>= 0.10.0)\n * `dateutil `_ (>= 2.7)\n@@ -30,8 +30,6 @@ reference.\n * `packaging `_ (>= 20.0)\n * `Pillow `_ (>= 8.0)\n * `pyparsing `_ (>= 2.3.1)\n-* `importlib-resources `_\n- (>= 3.2.0; only required on Python < 3.10)\n \n \n .. _optional_dependencies:\ndiff --git a/doc/install/index.rst b/doc/install/index.rst\nindex 867e4600a77e..99ccc163a82e 100644\n--- a/doc/install/index.rst\n+++ b/doc/install/index.rst\n@@ -267,9 +267,9 @@ at the Terminal.app command line::\n \n You should see something like ::\n \n- 3.6.0 /Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/matplotlib/__init__.py\n+ 3.10.0 /Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/matplotlib/__init__.py\n \n-where ``3.6.0`` is the Matplotlib version you just installed, and the path\n+where ``3.10.0`` is the Matplotlib version you just installed, and the path\n following depends on whether you are using Python.org Python, Homebrew or\n Macports. If you see another version, or you get an error like ::\n \ndiff --git a/environment.yml b/environment.yml\nindex ccd5270e9149..264f02800690 100644\n--- a/environment.yml\n+++ b/environment.yml\n@@ -24,6 +24,7 @@ dependencies:\n - pygobject\n - pyparsing>=2.3.1\n - pyqt\n+ - python>=3.10\n - python-dateutil>=2.1\n - setuptools_scm\n - wxpython\ndiff --git a/galleries/users_explain/customizing.py b/galleries/users_explain/customizing.py\nindex b0aaee03239e..05b75ba7d0a4 100644\n--- a/galleries/users_explain/customizing.py\n+++ b/galleries/users_explain/customizing.py\n@@ -234,8 +234,8 @@ def plotting_function():\n #\n # 4. :file:`{INSTALL}/matplotlib/mpl-data/matplotlibrc`, where\n # :file:`{INSTALL}` is something like\n-# :file:`/usr/lib/python3.9/site-packages` on Linux, and maybe\n-# :file:`C:\\\\Python39\\\\Lib\\\\site-packages` on Windows. Every time you\n+# :file:`/usr/lib/python3.10/site-packages` on Linux, and maybe\n+# :file:`C:\\\\Python310\\\\Lib\\\\site-packages` on Windows. Every time you\n # install matplotlib, this file will be overwritten, so if you want\n # your customizations to be saved, please move this file to your\n # user-specific matplotlib directory.\ndiff --git a/lib/matplotlib/_api/__init__.pyi b/lib/matplotlib/_api/__init__.pyi\nindex 4baff7cd804c..8dbef9528a82 100644\n--- a/lib/matplotlib/_api/__init__.pyi\n+++ b/lib/matplotlib/_api/__init__.pyi\n@@ -1,5 +1,6 @@\n from collections.abc import Callable, Generator, Mapping, Sequence\n from typing import Any, Iterable, TypeVar, overload\n+from typing_extensions import Self # < Py 3.11\n \n from numpy.typing import NDArray\n \n@@ -25,9 +26,8 @@ class classproperty(Any):\n fdel: None = ...,\n doc: str | None = None,\n ): ...\n- # Replace return with Self when py3.9 is dropped\n @overload\n- def __get__(self, instance: None, owner: None) -> classproperty: ...\n+ def __get__(self, instance: None, owner: None) -> Self: ...\n @overload\n def __get__(self, instance: object, owner: type[object]) -> Any: ...\n @property\ndiff --git a/lib/matplotlib/_api/deprecation.pyi b/lib/matplotlib/_api/deprecation.pyi\nindex 9619d1b484fc..d0d04d987410 100644\n--- a/lib/matplotlib/_api/deprecation.pyi\n+++ b/lib/matplotlib/_api/deprecation.pyi\n@@ -1,8 +1,7 @@\n from collections.abc import Callable\n import contextlib\n-from typing import Any, TypedDict, TypeVar, overload\n+from typing import Any, ParamSpec, TypedDict, TypeVar, overload\n from typing_extensions import (\n- ParamSpec, # < Py 3.10\n Unpack, # < Py 3.11\n )\n \ndiff --git a/lib/matplotlib/axis.pyi b/lib/matplotlib/axis.pyi\nindex e23ae381c338..8f69fe4039a8 100644\n--- a/lib/matplotlib/axis.pyi\n+++ b/lib/matplotlib/axis.pyi\n@@ -1,6 +1,7 @@\n from collections.abc import Callable, Iterable, Sequence\n import datetime\n from typing import Any, Literal, overload\n+from typing_extensions import Self # < Py 3.11\n \n import numpy as np\n from numpy.typing import ArrayLike\n@@ -93,9 +94,8 @@ class Ticker:\n \n class _LazyTickList:\n def __init__(self, major: bool) -> None: ...\n- # Replace return with Self when py3.9 is dropped\n @overload\n- def __get__(self, instance: None, owner: None) -> _LazyTickList: ...\n+ def __get__(self, instance: None, owner: None) -> Self: ...\n @overload\n def __get__(self, instance: Axis, owner: type[Axis]) -> list[Tick]: ...\n \ndiff --git a/lib/matplotlib/backends/registry.py b/lib/matplotlib/backends/registry.py\nindex e08817bb089b..3c85a9b47d7b 100644\n--- a/lib/matplotlib/backends/registry.py\n+++ b/lib/matplotlib/backends/registry.py\n@@ -132,14 +132,8 @@ def _read_entry_points(self):\n # [project.entry-points.\"matplotlib.backend\"]\n # inline = \"matplotlib_inline.backend_inline\"\n import importlib.metadata as im\n- import sys\n-\n- # entry_points group keyword not available before Python 3.10\n- group = \"matplotlib.backend\"\n- if sys.version_info >= (3, 10):\n- entry_points = im.entry_points(group=group)\n- else:\n- entry_points = im.entry_points().get(group, ())\n+\n+ entry_points = im.entry_points(group=\"matplotlib.backend\")\n entries = [(entry.name, entry.value) for entry in entry_points]\n \n # For backward compatibility, if matplotlib-inline and/or ipympl are installed\ndiff --git a/lib/matplotlib/dviread.pyi b/lib/matplotlib/dviread.pyi\nindex bf5cfcbe317a..270818278f17 100644\n--- a/lib/matplotlib/dviread.pyi\n+++ b/lib/matplotlib/dviread.pyi\n@@ -5,6 +5,7 @@ from enum import Enum\n from collections.abc import Generator\n \n from typing import NamedTuple\n+from typing_extensions import Self # < Py 3.11\n \n class _dvistate(Enum):\n pre: int\n@@ -47,8 +48,7 @@ class Dvi:\n fonts: dict[int, DviFont]\n state: _dvistate\n def __init__(self, filename: str | os.PathLike, dpi: float | None) -> None: ...\n- # Replace return with Self when py3.9 is dropped\n- def __enter__(self) -> Dvi: ...\n+ def __enter__(self) -> Self: ...\n def __exit__(self, etype, evalue, etrace) -> None: ...\n def __iter__(self) -> Generator[Page, None, None]: ...\n def close(self) -> None: ...\n@@ -83,8 +83,7 @@ class PsFont(NamedTuple):\n filename: str\n \n class PsfontsMap:\n- # Replace return with Self when py3.9 is dropped\n- def __new__(cls, filename: str | os.PathLike) -> PsfontsMap: ...\n+ def __new__(cls, filename: str | os.PathLike) -> Self: ...\n def __getitem__(self, texname: bytes) -> PsFont: ...\n \n def find_tex_file(filename: str | os.PathLike) -> str: ...\ndiff --git a/lib/matplotlib/sankey.pyi b/lib/matplotlib/sankey.pyi\nindex 4a40c31e3c6a..33565b998a9c 100644\n--- a/lib/matplotlib/sankey.pyi\n+++ b/lib/matplotlib/sankey.pyi\n@@ -2,6 +2,7 @@ from matplotlib.axes import Axes\n \n from collections.abc import Callable, Iterable\n from typing import Any\n+from typing_extensions import Self # < Py 3.11\n \n import numpy as np\n \n@@ -56,6 +57,5 @@ class Sankey:\n connect: tuple[int, int] = ...,\n rotation: float = ...,\n **kwargs\n- # Replace return with Self when py3.9 is dropped\n- ) -> Sankey: ...\n+ ) -> Self: ...\n def finish(self) -> list[Any]: ...\ndiff --git a/lib/matplotlib/style/core.py b/lib/matplotlib/style/core.py\nindex 7e9008c56165..e36c3c37a882 100644\n--- a/lib/matplotlib/style/core.py\n+++ b/lib/matplotlib/style/core.py\n@@ -12,19 +12,12 @@\n \"\"\"\n \n import contextlib\n+import importlib.resources\n import logging\n import os\n from pathlib import Path\n-import sys\n import warnings\n \n-if sys.version_info >= (3, 10):\n- import importlib.resources as importlib_resources\n-else:\n- # Even though Py3.9 has importlib.resources, it doesn't properly handle\n- # modules added in sys.path.\n- import importlib_resources\n-\n import matplotlib as mpl\n from matplotlib import _api, _docstring, _rc_params_in_file, rcParamsDefault\n \n@@ -121,8 +114,7 @@ def use(style):\n elif \".\" in style:\n pkg, _, name = style.rpartition(\".\")\n try:\n- path = (importlib_resources.files(pkg)\n- / f\"{name}.{STYLE_EXTENSION}\")\n+ path = importlib.resources.files(pkg) / f\"{name}.{STYLE_EXTENSION}\"\n style = _rc_params_in_file(path)\n except (ModuleNotFoundError, OSError, TypeError) as exc:\n # There is an ambiguity whether a dotted name refers to a\ndiff --git a/pyproject.toml b/pyproject.toml\nindex 14ad28d23603..40222fe266da 100644\n--- a/pyproject.toml\n+++ b/pyproject.toml\n@@ -16,7 +16,6 @@ classifiers=[\n \"License :: OSI Approved :: Python Software Foundation License\",\n \"Programming Language :: Python\",\n \"Programming Language :: Python :: 3\",\n- \"Programming Language :: Python :: 3.9\",\n \"Programming Language :: Python :: 3.10\",\n \"Programming Language :: Python :: 3.11\",\n \"Programming Language :: Python :: 3.12\",\n@@ -40,9 +39,8 @@ dependencies = [\n \"pillow >= 8\",\n \"pyparsing >= 2.3.1\",\n \"python-dateutil >= 2.7\",\n- \"importlib-resources >= 3.2.0; python_version < '3.10'\",\n ]\n-requires-python = \">=3.9\"\n+requires-python = \">=3.10\"\n \n [project.optional-dependencies]\n # Should be a copy of the build dependencies below.\n@@ -160,7 +158,7 @@ external = [\n \"E703\",\n ]\n \n-target-version = \"py39\"\n+target-version = \"py310\"\n \n [tool.ruff.pydocstyle]\n convention = \"numpy\"\ndiff --git a/tools/boilerplate.py b/tools/boilerplate.py\nindex a0943df00866..db93b102fce9 100644\n--- a/tools/boilerplate.py\n+++ b/tools/boilerplate.py\n@@ -27,31 +27,9 @@\n import numpy as np\n from matplotlib import _api, mlab\n from matplotlib.axes import Axes\n-from matplotlib.backend_bases import MouseButton\n from matplotlib.figure import Figure\n \n \n-# we need to define a custom str because py310 change\n-# In Python 3.10 the repr and str representation of Enums changed from\n-#\n-# str: 'ClassName.NAME' -> 'NAME'\n-# repr: '' -> 'ClassName.NAME'\n-#\n-# which is more consistent with what str/repr should do, however this breaks\n-# boilerplate which needs to get the ClassName.NAME version in all versions of\n-# Python. Thus, we locally monkey patch our preferred str representation in\n-# here.\n-#\n-# bpo-40066\n-# https://github.com/python/cpython/pull/22392/\n-def enum_str_back_compat_patch(self):\n- return f'{type(self).__name__}.{self.name}'\n-\n-# only monkey patch if we have to.\n-if str(MouseButton.LEFT) != 'MouseButton.Left':\n- MouseButton.__str__ = enum_str_back_compat_patch\n-\n-\n # This is the magic line that must exist in pyplot, after which the boilerplate\n # content will be appended.\n PYPLOT_MAGIC_HEADER = (\n@@ -112,7 +90,7 @@ def __init__(self, value):\n self._repr = \"_api.deprecation._deprecated_parameter\"\n elif isinstance(value, Enum):\n # Enum str is Class.Name whereas their repr is .\n- self._repr = str(value)\n+ self._repr = f'{type(value).__name__}.{value.name}'\n else:\n self._repr = repr(value)\n \ndiff --git a/tox.ini b/tox.ini\nindex cb2fcc979076..00ea746c8923 100644\n--- a/tox.ini\n+++ b/tox.ini\n@@ -4,7 +4,7 @@\n # and then run \"tox\" from this directory.\n \n [tox]\n-envlist = py38, py39, py310, stubtest\n+envlist = py310, py311, py312, stubtest\n \n [testenv]\n changedir = /tmp\n", "test_patch": "diff --git a/.github/workflows/mypy-stubtest.yml b/.github/workflows/mypy-stubtest.yml\nindex 969aacccad74..5b29a93b7533 100644\n--- a/.github/workflows/mypy-stubtest.yml\n+++ b/.github/workflows/mypy-stubtest.yml\n@@ -16,7 +16,7 @@ jobs:\n - name: Set up Python 3\n uses: actions/setup-python@v5\n with:\n- python-version: 3.9\n+ python-version: '3.10'\n \n - name: Set up reviewdog\n uses: reviewdog/action-setup@v1\n@@ -30,7 +30,7 @@ jobs:\n run: |\n set -o pipefail\n tox -e stubtest | \\\n- sed -e \"s!.tox/stubtest/lib/python3.9/site-packages!lib!g\" | \\\n+ sed -e \"s!.tox/stubtest/lib/python3.10/site-packages!lib!g\" | \\\n reviewdog \\\n -efm '%Eerror: %m' \\\n -efm '%CStub: in file %f:%l' \\\ndiff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml\nindex 8875a38cc1bb..230c42c136d5 100644\n--- a/.github/workflows/tests.yml\n+++ b/.github/workflows/tests.yml\n@@ -50,31 +50,28 @@ jobs:\n include:\n - name-suffix: \"(Minimum Versions)\"\n os: ubuntu-20.04\n- python-version: 3.9\n+ python-version: '3.10'\n extra-requirements: '-c requirements/testing/minver.txt'\n- pyqt5-ver: '==5.12.2 sip==5.0.0' # oldest versions with a Py3.9 wheel.\n- pyqt6-ver: '==6.1.0 PyQt6-Qt6==6.1.0'\n- pyside2-ver: '==5.15.1' # oldest version with working Py3.9 wheel.\n- pyside6-ver: '==6.0.0'\n delete-font-cache: true\n+ # Oldest versions with Py3.10 wheels.\n+ pyqt5-ver: '==5.15.5 sip==6.3.0'\n+ pyqt6-ver: '==6.2.0 PyQt6-Qt6==6.2.0'\n+ pyside2-ver: '==5.15.2.1'\n+ pyside6-ver: '==6.2.0'\n - os: ubuntu-20.04\n- python-version: 3.9\n+ python-version: '3.10'\n # One CI run tests ipython/matplotlib-inline before backend mapping moved to mpl\n- extra-requirements: '-r requirements/testing/extra.txt \"ipython==7.19\" \"matplotlib-inline<0.1.7\"'\n+ extra-requirements:\n+ -r requirements/testing/extra.txt\n+ \"ipython==7.29.0\"\n+ \"ipykernel==5.5.6\"\n+ \"matplotlib-inline<0.1.7\"\n CFLAGS: \"-fno-lto\" # Ensure that disabling LTO works.\n # https://github.com/matplotlib/matplotlib/pull/26052#issuecomment-1574595954\n # https://www.riverbankcomputing.com/pipermail/pyqt/2023-November/045606.html\n pyqt6-ver: '!=6.5.1,!=6.6.0'\n # https://bugreports.qt.io/projects/PYSIDE/issues/PYSIDE-2346\n pyside6-ver: '!=6.5.1'\n- - os: ubuntu-20.04\n- python-version: '3.10'\n- extra-requirements: '-r requirements/testing/extra.txt'\n- # https://github.com/matplotlib/matplotlib/pull/26052#issuecomment-1574595954\n- # https://www.riverbankcomputing.com/pipermail/pyqt/2023-November/045606.html\n- pyqt6-ver: '!=6.5.1,!=6.6.0'\n- # https://bugreports.qt.io/projects/PYSIDE/issues/PYSIDE-2346\n- pyside6-ver: '!=6.5.1'\n - os: ubuntu-22.04\n python-version: '3.11'\n # https://www.riverbankcomputing.com/pipermail/pyqt/2023-November/045606.html\n@@ -88,8 +85,8 @@ jobs:\n pyqt6-ver: '!=6.6.0'\n # https://bugreports.qt.io/projects/PYSIDE/issues/PYSIDE-2346\n pyside6-ver: '!=6.5.1'\n- - os: macos-12 # This runnre is on Intel chips.\n- python-version: 3.9\n+ - os: macos-12 # This runner is on Intel chips.\n+ python-version: '3.10'\n # https://bugreports.qt.io/projects/PYSIDE/issues/PYSIDE-2346\n pyside6-ver: '!=6.5.1'\n - os: macos-14 # This runner is on M1 (arm64) chips.\ndiff --git a/doc/devel/testing.rst b/doc/devel/testing.rst\nindex 668d4bd56b83..72f787eca746 100644\n--- a/doc/devel/testing.rst\n+++ b/doc/devel/testing.rst\n@@ -252,7 +252,7 @@ Using tox\n \n `Tox `_ is a tool for running tests\n against multiple Python environments, including multiple versions of Python\n-(e.g., 3.7, 3.8) and even different Python implementations altogether\n+(e.g., 3.10, 3.11) and even different Python implementations altogether\n (e.g., CPython, PyPy, Jython, etc.), as long as all these versions are\n available on your system's $PATH (consider using your system package manager,\n e.g. apt-get, yum, or Homebrew, to install them).\n@@ -269,7 +269,7 @@ You can also run tox on a subset of environments:\n \n .. code-block:: bash\n \n- $ tox -e py38,py39\n+ $ tox -e py310,py311\n \n Tox processes everything serially so it can take a long time to test\n several environments. To speed it up, you might try using a new,\ndiff --git a/lib/matplotlib/tests/test_backend_inline.py b/lib/matplotlib/tests/test_backend_inline.py\nindex 4112eb213e2c..6f0d67d51756 100644\n--- a/lib/matplotlib/tests/test_backend_inline.py\n+++ b/lib/matplotlib/tests/test_backend_inline.py\n@@ -1,7 +1,6 @@\n import os\n from pathlib import Path\n from tempfile import TemporaryDirectory\n-import sys\n \n import pytest\n \n@@ -13,7 +12,6 @@\n pytest.importorskip('matplotlib_inline')\n \n \n-@pytest.mark.skipif(sys.version_info[:2] <= (3, 9), reason=\"Requires Python 3.10+\")\n def test_ipynb():\n nb_path = Path(__file__).parent / 'test_inline_01.ipynb'\n \ndiff --git a/lib/matplotlib/tests/test_sphinxext.py b/lib/matplotlib/tests/test_sphinxext.py\nindex 6624e3b17ba5..24efecbeae9d 100644\n--- a/lib/matplotlib/tests/test_sphinxext.py\n+++ b/lib/matplotlib/tests/test_sphinxext.py\n@@ -10,8 +10,7 @@\n import pytest\n \n \n-pytest.importorskip('sphinx',\n- minversion=None if sys.version_info < (3, 10) else '4.1.3')\n+pytest.importorskip('sphinx', minversion='4.1.3')\n \n \n def build_sphinx_html(source_dir, doctree_dir, html_dir, extra_args=None):\ndiff --git a/requirements/testing/extra.txt b/requirements/testing/extra.txt\nindex b3e9009b561c..a5c1bef5f03a 100644\n--- a/requirements/testing/extra.txt\n+++ b/requirements/testing/extra.txt\n@@ -1,4 +1,4 @@\n-# Extra pip requirements for the Python 3.9+ builds\n+# Extra pip requirements for the Python 3.10+ builds\n \n --prefer-binary\n ipykernel\ndiff --git a/requirements/testing/minver.txt b/requirements/testing/minver.txt\nindex 1a95367eff14..3932e68eb015 100644\n--- a/requirements/testing/minver.txt\n+++ b/requirements/testing/minver.txt\n@@ -4,12 +4,12 @@ contourpy==1.0.1\n cycler==0.10\n fonttools==4.22.0\n importlib-resources==3.2.0\n-kiwisolver==1.3.1\n+kiwisolver==1.3.2\n meson-python==0.13.1\n meson==1.1.0\n numpy==1.23.0\n packaging==20.0\n-pillow==8.0.0\n+pillow==8.3.2\n pyparsing==2.3.1\n pytest==7.0.0\n python-dateutil==2.7\ndiff --git a/requirements/testing/mypy.txt b/requirements/testing/mypy.txt\nindex a5ca15cfbdad..9e3738556a8f 100644\n--- a/requirements/testing/mypy.txt\n+++ b/requirements/testing/mypy.txt\n@@ -25,5 +25,3 @@ pyparsing>=2.3.1\n python-dateutil>=2.7\n setuptools_scm>=7\n setuptools>=64\n-\n-importlib-resources>=3.2.0 ; python_version < \"3.10\"\n", "problem_statement": "[TST] Upcoming dependency test failures\nThe weekly build with nightly wheels from numpy and pandas\nhas failed. Check the logs for any updates that need to be\nmade in matplotlib.\nhttps://github.com/matplotlib/matplotlib/actions/runs/9721989654\n", "hints_text": "It looks like Pandas just recently bumped minimum Python to 3.10, so we aren't getting any 3.9 wheels from the nightly index any more.\nIt is time for us to do the same on main.", "created_at": "2024-07-03T04:15:30Z" }, { "repo": "matplotlib/matplotlib", "pull_number": 28487, "instance_id": "matplotlib__matplotlib-28487", "issue_numbers": [ "28341", "0000" ], "base_commit": "74c7f9a598c4e32b3f551f75ed965f446d786ab1", "patch": "diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py\nindex 328dda4a6a71..5e69bcb57d7f 100644\n--- a/lib/matplotlib/axes/_axes.py\n+++ b/lib/matplotlib/axes/_axes.py\n@@ -1028,7 +1028,7 @@ def axhspan(self, ymin, ymax, xmin=0, xmax=1, **kwargs):\n # For Rectangles and non-separable transforms, add_patch can be buggy\n # and update the x limits even though it shouldn't do so for an\n # yaxis_transformed patch, so undo that update.\n- ix = self.dataLim.intervalx\n+ ix = self.dataLim.intervalx.copy()\n mx = self.dataLim.minposx\n self.add_patch(p)\n self.dataLim.intervalx = ix\n", "test_patch": "diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py\nindex 48121ee04939..13c181b68492 100644\n--- a/lib/matplotlib/tests/test_axes.py\n+++ b/lib/matplotlib/tests/test_axes.py\n@@ -8250,10 +8250,10 @@ def test_relative_ticklabel_sizes(size):\n def test_multiplot_autoscale():\n fig = plt.figure()\n ax1, ax2 = fig.subplots(2, 1, sharex='all')\n- ax1.scatter([1, 2, 3, 4], [2, 3, 2, 3])\n+ ax1.plot([18000, 18250, 18500, 18750], [2, 3, 2, 3])\n ax2.axhspan(-5, 5)\n xlim = ax1.get_xlim()\n- assert np.allclose(xlim, [0.5, 4.5])\n+ assert np.allclose(xlim, [18000, 18800])\n \n \n def test_sharing_does_not_link_positions():\n", "problem_statement": "[Bug]: Incorrect X-axis scaling with date values\n### Bug summary\n\nIn matplotlib 3.9.0, when plotting dates on the X-axis, calling axhspan unexpectedly expands the X-axis range from 1970 to 2024. This behavior did not occur in version 3.8.4, where the X-axis correctly ranges from 2020 to 2024.\n\n### Code for reproduction\n\n```Python\nimport datetime\r\nimport matplotlib.pyplot as plt\r\n\r\nx = [datetime.date(year, 1, 1) for year in range(2020, 2024)]\r\ny = range(4)\r\n\r\nplt.plot(x, y)\r\nplt.axhspan(1, 2)\n```\n\n\n### Actual outcome\n\n![3 9 0](https://github.com/matplotlib/matplotlib/assets/6249977/104927cc-75f7-4a3d-abc7-d66d84b16d93)\r\n\n\n### Expected outcome\n\n![3 8 4](https://github.com/matplotlib/matplotlib/assets/6249977/3b628921-5cc3-4b9b-b2ce-1570d82d0bb4)\r\n\n\n### Additional information\n\n_No response_\n\n### Operating system\n\nFedora\n\n### Matplotlib Version\n\n3.9.0\n\n### Matplotlib Backend\n\ninline or Agg\n\n### Python version\n\n3.12.3\n\n### Jupyter version\n\n4.2.1\n\n### Installation\n\npip\n", "hints_text": "This is a regression in 3.9.\nMost likely related to https://github.com/matplotlib/matplotlib/pull/26788 \nA bisect does indeed point to that commit; I'm a bit confused though as it seems like that change is supposed to try and avoid changing the datalimits, but maybe it's something to do with using date/units.\r\n\r\ncc @anntzer\nWoops. The fix is easy:\r\n```patch\r\ndiff --git i/lib/matplotlib/axes/_axes.py w/lib/matplotlib/axes/_axes.py\r\nindex 9a2b367fb5..7efbd6562f 100644\r\n--- i/lib/matplotlib/axes/_axes.py\r\n+++ w/lib/matplotlib/axes/_axes.py\r\n@@ -1028,7 +1028,7 @@ class Axes(_AxesBase):\r\n # For Rectangles and non-separable transforms, add_patch can be buggy\r\n # and update the x limits even though it shouldn't do so for an\r\n # yaxis_transformed patch, so undo that update.\r\n- ix = self.dataLim.intervalx\r\n+ ix = self.dataLim.intervalx.copy()\r\n mx = self.dataLim.minposx\r\n self.add_patch(p)\r\n self.dataLim.intervalx = ix\r\n```\r\n(intervalx is an array and thus vulnerable to getting mutated in place). Note that axvspan already has the necessary copy() call, only axhspan was missing it.\r\nCan you take over the patch?", "created_at": "2024-06-28T11:05:42Z" }, { "repo": "matplotlib/matplotlib", "pull_number": 28486, "instance_id": "matplotlib__matplotlib-28486", "issue_numbers": [ "28383", "28383" ], "base_commit": "74c7f9a598c4e32b3f551f75ed965f446d786ab1", "patch": "diff --git a/lib/matplotlib/transforms.py b/lib/matplotlib/transforms.py\nindex 5003e2113930..3575bd1fc14d 100644\n--- a/lib/matplotlib/transforms.py\n+++ b/lib/matplotlib/transforms.py\n@@ -1423,7 +1423,7 @@ def contains_branch_seperately(self, other_transform):\n 'transforms with 2 output dimensions')\n # for a non-blended transform each separate dimension is the same, so\n # just return the appropriate shape.\n- return [self.contains_branch(other_transform)] * 2\n+ return (self.contains_branch(other_transform), ) * 2\n \n def __sub__(self, other):\n \"\"\"\n@@ -2404,6 +2404,15 @@ def _iter_break_from_left_to_right(self):\n for left, right in self._b._iter_break_from_left_to_right():\n yield self._a + left, right\n \n+ def contains_branch_seperately(self, other_transform):\n+ # docstring inherited\n+ if self.output_dims != 2:\n+ raise ValueError('contains_branch_seperately only supports '\n+ 'transforms with 2 output dimensions')\n+ if self == other_transform:\n+ return (True, True)\n+ return self._b.contains_branch_seperately(other_transform)\n+\n depth = property(lambda self: self._a.depth + self._b.depth)\n is_affine = property(lambda self: self._a.is_affine and self._b.is_affine)\n is_separable = property(\n", "test_patch": "diff --git a/lib/matplotlib/tests/test_transforms.py b/lib/matplotlib/tests/test_transforms.py\nindex 959814de82db..3d12b90d5210 100644\n--- a/lib/matplotlib/tests/test_transforms.py\n+++ b/lib/matplotlib/tests/test_transforms.py\n@@ -667,6 +667,13 @@ def test_contains_branch(self):\n \n assert not self.stack1.contains_branch(self.tn1 + self.ta2)\n \n+ blend = mtransforms.BlendedGenericTransform(self.tn2, self.stack2)\n+ x, y = blend.contains_branch_seperately(self.stack2_subset)\n+ stack_blend = self.tn3 + blend\n+ sx, sy = stack_blend.contains_branch_seperately(self.stack2_subset)\n+ assert x is sx is False\n+ assert y is sy is True\n+\n def test_affine_simplification(self):\n # tests that a transform stack only calls as much is absolutely\n # necessary \"non-affine\" allowing the best possible optimization with\n", "problem_statement": "[Bug]: axvspan no longer participating in limit calculations\n### Bug summary\n\nSince upgrading to matplotlib 3.9, axvspan plots no longer seem to be included in limit calculations. I suspect this is due to the change from Polygon to Rectangle, but it does seem to have some unintended consequences.\n\n### Code for reproduction\n\n```Python\nIn [2]: fig, ax = plt.subplots()\r\n\r\nIn [3]: ax.axvspan(0.5, 1.0)\r\nOut[3]: \r\n\r\nIn [4]: ax.set(title='Matplotlib 3.9.0')\r\nOut[4]: [Text(0.5, 1.0, 'Matplotlib 3.9.0')]\n```\n\n\n### Actual outcome\n\n![image](https://github.com/matplotlib/matplotlib/assets/1190540/417455cd-c184-4c3a-a446-24994c989284)\r\n\r\nNote that the axis limits are still centered at 0, and the vspan is out of frame.\n\n### Expected outcome\n\n![image](https://github.com/matplotlib/matplotlib/assets/1190540/498e2ff9-43bd-45b0-9757-7da51357fbb2)\r\n\r\nIn 3.8.4, the limits would adapt to the location of the generated artist.\n\n### Additional information\n\nThis is causing us some problems in mir_eval, where we have high-level plot constructors that are built entirely from axvspans for showing time-series segmentation labels.\n\n### Operating system\n\nall\n\n### Matplotlib Version\n\n3.9.0\n\n### Matplotlib Backend\n\ntkagg, but shouldn't matter\n\n### Python version\n\n3.12\n\n### Jupyter version\n\nn/a\n\n### Installation\n\npip\n[Bug]: axvspan no longer participating in limit calculations\n### Bug summary\n\nSince upgrading to matplotlib 3.9, axvspan plots no longer seem to be included in limit calculations. I suspect this is due to the change from Polygon to Rectangle, but it does seem to have some unintended consequences.\n\n### Code for reproduction\n\n```Python\nIn [2]: fig, ax = plt.subplots()\r\n\r\nIn [3]: ax.axvspan(0.5, 1.0)\r\nOut[3]: \r\n\r\nIn [4]: ax.set(title='Matplotlib 3.9.0')\r\nOut[4]: [Text(0.5, 1.0, 'Matplotlib 3.9.0')]\n```\n\n\n### Actual outcome\n\n![image](https://github.com/matplotlib/matplotlib/assets/1190540/417455cd-c184-4c3a-a446-24994c989284)\r\n\r\nNote that the axis limits are still centered at 0, and the vspan is out of frame.\n\n### Expected outcome\n\n![image](https://github.com/matplotlib/matplotlib/assets/1190540/498e2ff9-43bd-45b0-9757-7da51357fbb2)\r\n\r\nIn 3.8.4, the limits would adapt to the location of the generated artist.\n\n### Additional information\n\nThis is causing us some problems in mir_eval, where we have high-level plot constructors that are built entirely from axvspans for showing time-series segmentation labels.\n\n### Operating system\n\nall\n\n### Matplotlib Version\n\n3.9.0\n\n### Matplotlib Backend\n\ntkagg, but shouldn't matter\n\n### Python version\n\n3.12\n\n### Jupyter version\n\nn/a\n\n### Installation\n\npip\n", "hints_text": "This may be duplicate of #28341, which has a patch, but has not been put into a PR yet, I believe.\r\n\r\nThat said, the discussion there was about `axhspan` and indicated that `axvspan` was unaffected, so may not be exactly the same...\n> That said, the discussion there was about `axhspan` and indicated that `axvspan` was unaffected, so may not be exactly the same...\r\n\r\nI took a look at the proposed patch in #28341, and I don't think it addresses the issue.\r\n\r\nIn axhspan, the proposed fix was to .copy() the horizontal interval when updating the datalim:\r\nhttps://github.com/matplotlib/matplotlib/blob/54729dbcb59c6f46c44241fc3193a2e67d5983e8/lib/matplotlib/axes/_axes.py#L1028-L1031\r\nand the post in thread indicates that this is unnecessary for vspan, which already has the .copy():\r\nhttps://github.com/matplotlib/matplotlib/blob/54729dbcb59c6f46c44241fc3193a2e67d5983e8/lib/matplotlib/axes/_axes.py#L1091-L1094\r\nHowever, that's only applied to the y-axis, and it's the x-axis that is causing the issue here.\r\n\r\nSo I think these are two related, but distinct problems.\nSo the problem here is that for patches, we ask for whether it contains the data transform:\r\nhttps://github.com/matplotlib/matplotlib/blob/d347c3227f8de8a99aa327390fee619310452a96/lib/matplotlib/axes/_base.py#L2445-L2448\r\nand for `Rectangle` this returns `False` for both x&y. This is likely because a `Rectangle` is a unit rectangle, with a scaling transform + the data transform.\r\n\r\nSince `Patch.get_transform()` == patch transform + data transform, it's possible that we may want to check data transform, and then use patch transform directly for the datalim update. Something like:\r\n```patch\r\ndiff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py\r\nindex 1cf56c90cc..5f37710ee6 100644\r\n--- a/lib/matplotlib/axes/_base.py\r\n+++ b/lib/matplotlib/axes/_base.py\r\n@@ -2415,17 +2415,17 @@ class _AxesBase(martist.Artist):\r\n if len(vertices):\r\n vertices = np.vstack(vertices)\r\n \r\n- patch_trf = patch.get_transform()\r\n- updatex, updatey = patch_trf.contains_branch_seperately(self.transData)\r\n+ data_trf = patch.get_data_transform()\r\n+ updatex, updatey = data_trf.contains_branch_seperately(self.transData)\r\n if not (updatex or updatey):\r\n return\r\n if self.name != \"rectilinear\":\r\n # As in _update_line_limits, but for axvspan.\r\n- if updatex and patch_trf == self.get_yaxis_transform():\r\n+ if updatex and data_trf == self.get_yaxis_transform():\r\n updatex = False\r\n- if updatey and patch_trf == self.get_xaxis_transform():\r\n+ if updatey and data_trf == self.get_xaxis_transform():\r\n updatey = False\r\n- trf_to_data = patch_trf - self.transData\r\n+ trf_to_data = patch.get_patch_transform()\r\n xys = trf_to_data.transform(vertices)\r\n self.update_datalim(xys, updatex=updatex, updatey=updatey)\r\n ```\nThis may be duplicate of #28341, which has a patch, but has not been put into a PR yet, I believe.\r\n\r\nThat said, the discussion there was about `axhspan` and indicated that `axvspan` was unaffected, so may not be exactly the same...\n> That said, the discussion there was about `axhspan` and indicated that `axvspan` was unaffected, so may not be exactly the same...\r\n\r\nI took a look at the proposed patch in #28341, and I don't think it addresses the issue.\r\n\r\nIn axhspan, the proposed fix was to .copy() the horizontal interval when updating the datalim:\r\nhttps://github.com/matplotlib/matplotlib/blob/54729dbcb59c6f46c44241fc3193a2e67d5983e8/lib/matplotlib/axes/_axes.py#L1028-L1031\r\nand the post in thread indicates that this is unnecessary for vspan, which already has the .copy():\r\nhttps://github.com/matplotlib/matplotlib/blob/54729dbcb59c6f46c44241fc3193a2e67d5983e8/lib/matplotlib/axes/_axes.py#L1091-L1094\r\nHowever, that's only applied to the y-axis, and it's the x-axis that is causing the issue here.\r\n\r\nSo I think these are two related, but distinct problems.\nSo the problem here is that for patches, we ask for whether it contains the data transform:\r\nhttps://github.com/matplotlib/matplotlib/blob/d347c3227f8de8a99aa327390fee619310452a96/lib/matplotlib/axes/_base.py#L2445-L2448\r\nand for `Rectangle` this returns `False` for both x&y. This is likely because a `Rectangle` is a unit rectangle, with a scaling transform + the data transform.\r\n\r\nSince `Patch.get_transform()` == patch transform + data transform, it's possible that we may want to check data transform, and then use patch transform directly for the datalim update. Something like:\r\n```patch\r\ndiff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py\r\nindex 1cf56c90cc..5f37710ee6 100644\r\n--- a/lib/matplotlib/axes/_base.py\r\n+++ b/lib/matplotlib/axes/_base.py\r\n@@ -2415,17 +2415,17 @@ class _AxesBase(martist.Artist):\r\n if len(vertices):\r\n vertices = np.vstack(vertices)\r\n \r\n- patch_trf = patch.get_transform()\r\n- updatex, updatey = patch_trf.contains_branch_seperately(self.transData)\r\n+ data_trf = patch.get_data_transform()\r\n+ updatex, updatey = data_trf.contains_branch_seperately(self.transData)\r\n if not (updatex or updatey):\r\n return\r\n if self.name != \"rectilinear\":\r\n # As in _update_line_limits, but for axvspan.\r\n- if updatex and patch_trf == self.get_yaxis_transform():\r\n+ if updatex and data_trf == self.get_yaxis_transform():\r\n updatex = False\r\n- if updatey and patch_trf == self.get_xaxis_transform():\r\n+ if updatey and data_trf == self.get_xaxis_transform():\r\n updatey = False\r\n- trf_to_data = patch_trf - self.transData\r\n+ trf_to_data = patch.get_patch_transform()\r\n xys = trf_to_data.transform(vertices)\r\n self.update_datalim(xys, updatex=updatex, updatey=updatey)\r\n ```", "created_at": "2024-06-28T10:22:43Z" }, { "repo": "matplotlib/matplotlib", "pull_number": 28473, "instance_id": "matplotlib__matplotlib-28473", "issue_numbers": [ "28432", "0000" ], "base_commit": "664b45729aa6ac2d6ef9459d8c0ad04f50af847c", "patch": "diff --git a/lib/matplotlib/backends/registry.py b/lib/matplotlib/backends/registry.py\nindex 47d5f65e350e..e08817bb089b 100644\n--- a/lib/matplotlib/backends/registry.py\n+++ b/lib/matplotlib/backends/registry.py\n@@ -93,6 +93,9 @@ def __init__(self):\n }\n \n def _backend_module_name(self, backend):\n+ if backend.startswith(\"module://\"):\n+ return backend[9:]\n+\n # Return name of module containing the specified backend.\n # Does not check if the backend is valid, use is_valid_backend for that.\n backend = backend.lower()\n@@ -224,7 +227,8 @@ def is_valid_backend(self, backend):\n bool\n True if backend is valid, False otherwise.\n \"\"\"\n- backend = backend.lower()\n+ if not backend.startswith(\"module://\"):\n+ backend = backend.lower()\n \n # For backward compatibility, convert ipympl and matplotlib-inline long\n # module:// names to their shortened forms.\n@@ -342,7 +346,8 @@ def resolve_backend(self, backend):\n The GUI framework, which will be None for a backend that is non-interactive.\n \"\"\"\n if isinstance(backend, str):\n- backend = backend.lower()\n+ if not backend.startswith(\"module://\"):\n+ backend = backend.lower()\n else: # Might be _auto_backend_sentinel or None\n # Use whatever is already running...\n from matplotlib import get_backend\n@@ -395,7 +400,8 @@ def resolve_gui_or_backend(self, gui_or_backend):\n framework : str or None\n The GUI framework, which will be None for a backend that is non-interactive.\n \"\"\"\n- gui_or_backend = gui_or_backend.lower()\n+ if not gui_or_backend.startswith(\"module://\"):\n+ gui_or_backend = gui_or_backend.lower()\n \n # First check if it is a gui loop name.\n backend = self.backend_for_gui_framework(gui_or_backend)\n", "test_patch": "diff --git a/lib/matplotlib/tests/test_backend_registry.py b/lib/matplotlib/tests/test_backend_registry.py\nindex 141ffd69c266..80c2ce4fc51a 100644\n--- a/lib/matplotlib/tests/test_backend_registry.py\n+++ b/lib/matplotlib/tests/test_backend_registry.py\n@@ -86,6 +86,15 @@ def test_is_valid_backend(backend, is_valid):\n assert backend_registry.is_valid_backend(backend) == is_valid\n \n \n+@pytest.mark.parametrize(\"backend, normalized\", [\n+ (\"agg\", \"matplotlib.backends.backend_agg\"),\n+ (\"QtAgg\", \"matplotlib.backends.backend_qtagg\"),\n+ (\"module://Anything\", \"Anything\"),\n+])\n+def test_backend_normalization(backend, normalized):\n+ assert backend_registry._backend_module_name(backend) == normalized\n+\n+\n def test_deprecated_rcsetup_attributes():\n match = \"was deprecated in Matplotlib 3.9\"\n with pytest.warns(mpl.MatplotlibDeprecationWarning, match=match):\ndiff --git a/lib/matplotlib/tests/test_backend_template.py b/lib/matplotlib/tests/test_backend_template.py\nindex d7e2a5cd1266..964d15c1559a 100644\n--- a/lib/matplotlib/tests/test_backend_template.py\n+++ b/lib/matplotlib/tests/test_backend_template.py\n@@ -49,3 +49,14 @@ def test_show_old_global_api(monkeypatch):\n mpl.use(\"module://mpl_test_backend\")\n plt.show()\n mock_show.assert_called_with()\n+\n+\n+def test_load_case_sensitive(monkeypatch):\n+ mpl_test_backend = SimpleNamespace(**vars(backend_template))\n+ mock_show = MagicMock()\n+ monkeypatch.setattr(\n+ mpl_test_backend.FigureManagerTemplate, \"pyplot_show\", mock_show)\n+ monkeypatch.setitem(sys.modules, \"mpl_Test_Backend\", mpl_test_backend)\n+ mpl.use(\"module://mpl_Test_Backend\")\n+ plt.show()\n+ mock_show.assert_called_with()\n", "problem_statement": "[Bug]: Backend name specified as module gets lowercased since 3.9\n### Bug summary\r\n\r\n`MPLBACKEND=\"module://foo.Bar\"` now tries to import the `foo.bar` module as opposed to `foo.Bar`, as before.\r\n\r\n### Code for reproduction\r\n\r\n```Python\r\nMPLBACKEND=\"module://foo.Bar\"\r\n```\r\n\r\n\r\n### Actual outcome\r\n\r\n`foo.bar` is imported\r\n\r\n### Expected outcome\r\n\r\n`foo.Bar` is imported\r\n\r\n### Additional information\r\n\r\nThis probably regressed in https://github.com/matplotlib/matplotlib/pull/27948.\r\n\r\n### Operating system\r\n\r\nFedora Linux 39\r\n\r\n### Matplotlib Version\r\n\r\n3.9.0\r\n\r\n### Matplotlib Backend\r\n\r\n_No response_\r\n\r\n### Python version\r\n\r\n_No response_\r\n\r\n### Jupyter version\r\n\r\n_No response_\r\n\r\n### Installation\r\n\r\nNone\n", "hints_text": "", "created_at": "2024-06-26T20:54:45Z" }, { "repo": "matplotlib/matplotlib", "pull_number": 28465, "instance_id": "matplotlib__matplotlib-28465", "issue_numbers": [ "28464" ], "base_commit": "57cd5eb22af1590eaf771a9e5f0d60f6bec2354d", "patch": "diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py\nindex 9139b2ed262f..7bb06814f389 100644\n--- a/lib/matplotlib/figure.py\n+++ b/lib/matplotlib/figure.py\n@@ -2227,7 +2227,6 @@ def __init__(self, parent, subplotspec, *,\n self.subplotpars = parent.subplotpars\n self.dpi_scale_trans = parent.dpi_scale_trans\n self._axobservers = parent._axobservers\n- self.canvas = parent.canvas\n self.transFigure = parent.transFigure\n self.bbox_relative = Bbox.null()\n self._redo_transform_rel_fig()\n@@ -2244,6 +2243,10 @@ def __init__(self, parent, subplotspec, *,\n self._set_artist_props(self.patch)\n self.patch.set_antialiased(False)\n \n+ @property\n+ def canvas(self):\n+ return self._parent.canvas\n+\n @property\n def dpi(self):\n return self._parent.dpi\ndiff --git a/lib/matplotlib/figure.pyi b/lib/matplotlib/figure.pyi\nindex 21de9159d56c..b079312695c1 100644\n--- a/lib/matplotlib/figure.pyi\n+++ b/lib/matplotlib/figure.pyi\n@@ -263,7 +263,6 @@ class SubFigure(FigureBase):\n figure: Figure\n subplotpars: SubplotParams\n dpi_scale_trans: Affine2D\n- canvas: FigureCanvasBase\n transFigure: Transform\n bbox_relative: Bbox\n figbbox: BboxBase\n@@ -282,6 +281,8 @@ class SubFigure(FigureBase):\n **kwargs\n ) -> None: ...\n @property\n+ def canvas(self) -> FigureCanvasBase: ...\n+ @property\n def dpi(self) -> float: ...\n @dpi.setter\n def dpi(self, value: float) -> None: ...\n", "test_patch": "diff --git a/lib/matplotlib/tests/test_pickle.py b/lib/matplotlib/tests/test_pickle.py\nindex 7e7ccc14bf8f..0cba4f392035 100644\n--- a/lib/matplotlib/tests/test_pickle.py\n+++ b/lib/matplotlib/tests/test_pickle.py\n@@ -93,6 +93,11 @@ def _generate_complete_test_figure(fig_ref):\n plt.errorbar(x, x * -0.5, xerr=0.2, yerr=0.4, label='$-.5 x$')\n plt.legend(draggable=True)\n \n+ # Ensure subfigure parenting works.\n+ subfigs = fig_ref.subfigures(2)\n+ subfigs[0].subplots(1, 2)\n+ subfigs[1].subplots(1, 2)\n+\n fig_ref.align_ylabels() # Test handling of _align_label_groups Groupers.\n \n \n", "problem_statement": "[Bug]: figure with subfigures cannot be pickled\n### Bug summary\r\n\r\nI tried to pickle figures with subfigures nested.\r\nReturns error with the following code.\r\n\r\n### Code for reproduction\r\n\r\n```Python\r\n#!/usr/bin/env python3\r\nimport pickle\r\nimport matplotlib.pyplot as plt\r\n\r\nfig = plt.figure()\r\nsubfigs = fig.subfigures(2,2)\r\nwith open('fig.p', 'wb') as file:\r\n pickle.dump(fig, file)\r\n```\r\n\r\n\r\n### Actual outcome\r\n```\r\n---------------------------------------------------------------------------\r\nTypeError Traceback (most recent call last)\r\nCell In[1], line 8\r\n 6 subfigs = fig.subfigures(2,2)\r\n 7 with open('fig.p', 'wb') as file:\r\n----> 8 pickle.dump(fig, file)\r\n\r\nTypeError: cannot pickle 'FigureCanvasQTAgg' object\r\n```\r\n\r\n### Expected outcome\r\n\r\nA pickle file should be saved to the working directory.\r\n\r\n### Additional information\r\n\r\nI know the API is still provisional but I couldn't find other reports so I decided to post.\r\nI believe this is caused by the \"subfigures\" API, as skipping that line makes the script work.\r\nIt's my first time to report a issue, so I'm a bit worried if I'm doing things right.\r\n\r\n### Operating system\r\n\r\nWindows 10\r\n\r\n### Matplotlib Version\r\n\r\n3.9.0\r\n\r\n### Matplotlib Backend\r\n\r\nqtagg\r\n\r\n### Python version\r\n\r\n3.12.1\r\n\r\n### Jupyter version\r\n\r\n_No response_\r\n\r\n### Installation\r\n\r\npip\n", "hints_text": "", "created_at": "2024-06-26T07:13:10Z" }, { "repo": "matplotlib/matplotlib", "pull_number": 28458, "instance_id": "matplotlib__matplotlib-28458", "issue_numbers": [ "28448" ], "base_commit": "d7d1bba818ef36b2475b5d73cad6394841710211", "patch": "diff --git a/src/_image_wrapper.cpp b/src/_image_wrapper.cpp\nindex 65c8c8324ebc..856dcf4ea3ce 100644\n--- a/src/_image_wrapper.cpp\n+++ b/src/_image_wrapper.cpp\n@@ -173,20 +173,20 @@ image_resample(py::array input_array,\n \n if (auto resampler =\n (ndim == 2) ? (\n- (dtype.is(py::dtype::of())) ? resample :\n- (dtype.is(py::dtype::of())) ? resample :\n- (dtype.is(py::dtype::of())) ? resample :\n- (dtype.is(py::dtype::of())) ? resample :\n- (dtype.is(py::dtype::of())) ? resample :\n- (dtype.is(py::dtype::of())) ? resample :\n+ (dtype.equal(py::dtype::of())) ? resample :\n+ (dtype.equal(py::dtype::of())) ? resample :\n+ (dtype.equal(py::dtype::of())) ? resample :\n+ (dtype.equal(py::dtype::of())) ? resample :\n+ (dtype.equal(py::dtype::of())) ? resample :\n+ (dtype.equal(py::dtype::of())) ? resample :\n nullptr) : (\n // ndim == 3\n- (dtype.is(py::dtype::of())) ? resample :\n- (dtype.is(py::dtype::of())) ? resample :\n- (dtype.is(py::dtype::of())) ? resample :\n- (dtype.is(py::dtype::of())) ? resample :\n- (dtype.is(py::dtype::of())) ? resample :\n- (dtype.is(py::dtype::of())) ? resample :\n+ (dtype.equal(py::dtype::of())) ? resample :\n+ (dtype.equal(py::dtype::of())) ? resample :\n+ (dtype.equal(py::dtype::of())) ? resample :\n+ (dtype.equal(py::dtype::of())) ? resample :\n+ (dtype.equal(py::dtype::of())) ? resample :\n+ (dtype.equal(py::dtype::of())) ? resample :\n nullptr)) {\n Py_BEGIN_ALLOW_THREADS\n resampler(\n", "test_patch": "diff --git a/lib/matplotlib/tests/test_image.py b/lib/matplotlib/tests/test_image.py\nindex 599265a2d4d8..8d7970078efa 100644\n--- a/lib/matplotlib/tests/test_image.py\n+++ b/lib/matplotlib/tests/test_image.py\n@@ -1576,3 +1576,20 @@ def test_non_transdata_image_does_not_touch_aspect():\n assert ax.get_aspect() == 1\n ax.imshow(im, transform=ax.transAxes, aspect=2)\n assert ax.get_aspect() == 2\n+\n+\n+@pytest.mark.parametrize(\n+ 'dtype',\n+ ('float64', 'float32', 'int16', 'uint16', 'int8', 'uint8'),\n+)\n+@pytest.mark.parametrize('ndim', (2, 3))\n+def test_resample_dtypes(dtype, ndim):\n+ # Issue 28448, incorrect dtype comparisons in C++ image_resample can raise\n+ # ValueError: arrays must be of dtype byte, short, float32 or float64\n+ rng = np.random.default_rng(4181)\n+ shape = (2, 2) if ndim == 2 else (2, 2, 3)\n+ data = rng.uniform(size=shape).astype(np.dtype(dtype, copy=True))\n+ fig, ax = plt.subplots()\n+ axes_image = ax.imshow(data)\n+ # Before fix the following raises ValueError for some dtypes.\n+ axes_image.make_image(None)[0]\n", "problem_statement": "[Bug]: Making an RGB image from pickled data throws error\n### Bug summary\r\n\r\nGetting an error when saving an animated RGB image that was loaded from a pickled figure. I've isolated the error to matplotlib 3.9.0, with this code working in 3.8.3, which makes me think that this is to do with the pybind11 upgrade in https://github.com/matplotlib/matplotlib/pull/26275?\r\n\r\nThings I've tried:\r\n* Grayscale images (eg `data = np.random.rand(100, 100)`) work.\r\n* Numpy v1.26.4 and v2.0.0 show no difference in behavior\r\n* This shows up at least on WSL and Ubuntu \r\n* In the debugger, both `data.dtype` and `out.dtype` are showing `'float64'` prior to the `_image.resample` call.\r\n * However, if I re-cast the arrays with `data = data.astype('float64')`, `out = ...`, then the `_image.resample` call no longer fails! \r\n * If I re-cast only one, then `out.dtype == data.dtype` returns `True`, but on the function call I get the error `ValueError: Input and output arrays have mismatched types`\r\n * ... so something is up with the types, and the C++ code is bombing. But python is saying things line up.\r\n\r\n\r\nSee these parts of the source: \r\n\r\nhttps://github.com/matplotlib/matplotlib/blob/d7d1bba818ef36b2475b5d73cad6394841710211/lib/matplotlib/image.py#L205-L213\r\nhttps://github.com/matplotlib/matplotlib/blob/d7d1bba818ef36b2475b5d73cad6394841710211/src/_image_wrapper.cpp#L174-L199\r\n\r\n\r\n### Code for reproduction\r\n\r\n```Python\r\nimport io\r\nimport pickle\r\nimport numpy as np\r\nimport matplotlib.pyplot as plt\r\nfrom pathlib import Path\r\nfrom matplotlib.animation import FuncAnimation\r\n\r\ndir = Path(__file__).parent.resolve()\r\n\r\n# generate random rgb data\r\nfig, ax = plt.subplots()\r\nnp.random.seed(0)\r\ndata = np.random.rand(100, 100, 3)\r\nax.imshow(data)\r\n\r\n# pick the figure and reload\r\nbuf = io.BytesIO()\r\npickle.dump(fig, buf)\r\nbuf.seek(0)\r\nfig_pickled = pickle.load(buf)\r\n\r\n# Animate\r\ndef update(frame):\r\n return ax,\r\n\r\nani = FuncAnimation(fig_pickled, update, frames=2)\r\n\r\n# Save the animation\r\nfilepath = dir / 'test.gif' \r\nani.save(filepath)\r\n```\r\n\r\n\r\n### Actual outcome\r\n\r\n```\r\nException has occurred: ValueError\r\narrays must be of dtype byte, short, float32 or float64\r\n File \"/mnt/c/Users/Scott/Documents/Documents/Coding/matplotlib/lib/matplotlib/image.py\", line 208, in _resample\r\n _image.resample(data, out, transform,\r\n File \"/mnt/c/Users/Scott/Documents/Documents/Coding/matplotlib/lib/matplotlib/image.py\", line 567, in _make_image\r\n output = _resample( # resample rgb channels\r\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n File \"/mnt/c/Users/Scott/Documents/Documents/Coding/matplotlib/lib/matplotlib/image.py\", line 952, in make_image\r\n return self._make_image(self._A, bbox, transformed_bbox, clip,\r\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n File \"/mnt/c/Users/Scott/Documents/Documents/Coding/matplotlib/lib/matplotlib/image.py\", line 653, in draw\r\n im, l, b, trans = self.make_image(\r\n ^^^^^^^^^^^^^^^^\r\n File \"/mnt/c/Users/Scott/Documents/Documents/Coding/matplotlib/lib/matplotlib/artist.py\", line 72, in draw_wrapper\r\n return draw(artist, renderer)\r\n ^^^^^^^^^^^^^^^^^^^^^^\r\n File \"/mnt/c/Users/Scott/Documents/Documents/Coding/matplotlib/lib/matplotlib/image.py\", line 132, in _draw_list_compositing_images\r\n a.draw(renderer)\r\n File \"/mnt/c/Users/Scott/Documents/Documents/Coding/matplotlib/lib/matplotlib/axes/_base.py\", line 3110, in draw\r\n mimage._draw_list_compositing_images(\r\n File \"/mnt/c/Users/Scott/Documents/Documents/Coding/matplotlib/lib/matplotlib/artist.py\", line 72, in draw_wrapper\r\n return draw(artist, renderer)\r\n ^^^^^^^^^^^^^^^^^^^^^^\r\n File \"/mnt/c/Users/Scott/Documents/Documents/Coding/matplotlib/lib/matplotlib/image.py\", line 132, in _draw_list_compositing_images\r\n a.draw(renderer)\r\n File \"/mnt/c/Users/Scott/Documents/Documents/Coding/matplotlib/lib/matplotlib/figure.py\", line 3157, in draw\r\n mimage._draw_list_compositing_images(\r\n File \"/mnt/c/Users/Scott/Documents/Documents/Coding/matplotlib/lib/matplotlib/artist.py\", line 72, in draw_wrapper\r\n return draw(artist, renderer)\r\n ^^^^^^^^^^^^^^^^^^^^^^\r\n File \"/mnt/c/Users/Scott/Documents/Documents/Coding/matplotlib/lib/matplotlib/artist.py\", line 95, in draw_wrapper\r\n result = draw(artist, renderer, *args, **kwargs)\r\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n File \"/mnt/c/Users/Scott/Documents/Documents/Coding/matplotlib/lib/matplotlib/backends/backend_agg.py\", line 387, in draw\r\n self.figure.draw(self.renderer)\r\n File \"/mnt/c/Users/Scott/Documents/Documents/Coding/matplotlib/lib/matplotlib/backends/backend_agg.py\", line 432, in print_raw\r\n FigureCanvasAgg.draw(self)\r\n File \"/mnt/c/Users/Scott/Documents/Documents/Coding/matplotlib/lib/matplotlib/backend_bases.py\", line 2054, in \r\n print_method = functools.wraps(meth)(lambda *args, **kwargs: meth(\r\n ^^^^^\r\n File \"/mnt/c/Users/Scott/Documents/Documents/Coding/matplotlib/lib/matplotlib/backend_bases.py\", line 2204, in print_figure\r\n result = print_method(\r\n ^^^^^^^^^^^^^\r\n File \"/mnt/c/Users/Scott/Documents/Documents/Coding/matplotlib/lib/matplotlib/backends/backend_qtagg.py\", line 75, in print_figure\r\n super().print_figure(*args, **kwargs)\r\n File \"/mnt/c/Users/Scott/Documents/Documents/Coding/matplotlib/lib/matplotlib/figure.py\", line 3390, in savefig\r\n self.canvas.print_figure(fname, **kwargs)\r\n File \"/mnt/c/Users/Scott/Documents/Documents/Coding/matplotlib/lib/matplotlib/animation.py\", line 371, in grab_frame\r\n self.fig.savefig(self._proc.stdin, format=self.frame_format,\r\n File \"/mnt/c/Users/Scott/Documents/Documents/Coding/matplotlib/lib/matplotlib/animation.py\", line 1109, in save\r\n writer.grab_frame(**savefig_kwargs)\r\n File \"/mnt/c/Users/Scott/Documents/Documents/Coding/matplotlib/_test_pybind11_error.py\", line 35, in \r\n ani.save(filepath)\r\nValueError: arrays must be of dtype byte, short, float32 or float64\r\n```\r\n\r\n### Matplotlib Version\r\n\r\n3.9.0\n", "hints_text": "I am able to work around this issue by manually re-casting the image data prior to the call, so my hunch is that this is an error to do with the pickling:\r\n\r\nUpdated example with workaround:\r\n```python\r\nimport io\r\nimport pickle\r\nimport numpy as np\r\nimport matplotlib as mpl\r\nimport matplotlib.pyplot as plt\r\nfrom pathlib import Path\r\nfrom matplotlib.animation import FuncAnimation\r\n\r\ndir = Path(__file__).parent.resolve()\r\n\r\n# generate random rgb data\r\nfig, ax = plt.subplots()\r\nnp.random.seed(0)\r\ndata = np.random.rand(100, 100, 3)\r\nax.imshow(data)\r\n\r\n# pick the figure and reload\r\nbuf = io.BytesIO()\r\npickle.dump(fig, buf)\r\nbuf.seek(0)\r\nfig_pickled = pickle.load(buf)\r\n\r\n# Workaround\r\nax = fig_pickled.get_axes()[0]\r\nartists = ax.get_children()\r\nfor artist in artists:\r\n if isinstance(artist, mpl.image.AxesImage):\r\n array = artist.get_array()\r\n artist.set_array(array.data.astype('float64'))\r\n\r\n# Animate\r\ndef update(frame):\r\n return ax,\r\n\r\nani = FuncAnimation(fig_pickled, update, frames=2)\r\n\r\n# Save the animation\r\nfilepath = dir / 'test.gif' \r\nani.save(filepath)\r\n```\nI can reproduce this on macOS without animation using:\r\n```python\r\nimport io\r\nimport numpy as np\r\nimport matplotlib.pyplot as plt\r\nimport pickle\r\n\r\nfig, ax = plt.subplots()\r\n\r\nrng = np.random.default_rng(4181)\r\ndata = rng.uniform(size=(2, 2, 3))\r\naxes_image = ax.imshow(data)\r\nprint(axes_image._A.shape, axes_image._A.dtype)\r\nim = axes_image.make_image(None)[0]\r\n\r\nbuf = io.BytesIO()\r\npickle.dump(axes_image, buf)\r\nbuf.seek(0)\r\naxes_image2 = pickle.load(buf)\r\nprint(axes_image2._A.shape, axes_image2._A.dtype)\r\n\r\n#axes_image2._A = axes_image2._A.astype(\"float64\")\r\nprint(\"Same dtype?\", axes_image._A.dtype == axes_image2._A.dtype)\r\n\r\nim = axes_image2.make_image(None)[0]\r\n```\r\nUsing this you get a `ValueError: arrays must be of dtype byte, short, float32 or float64`. If you remove the # to force a dtype change it works fine.\r\n\r\nThe problem occurs on this line\r\nhttps://github.com/matplotlib/matplotlib/blob/d7d1bba818ef36b2475b5d73cad6394841710211/src/_image_wrapper.cpp#L189\r\nAfter pickling and unpickling the numpy array dtype is fine from a Python point of view, but from a C++ pybind11 point of view the dtype has all the right properties but its `PyObject` has a different address so we conclude that it is not really a `double` (i.e. `np.float64`) dtype. I haven't got any further than this yet, but If my analysis is correct it should be possible to write a reproducer that doesn't use Matplotlib at all.\nCan we fallback to eq in the c++ code instead of `is` ? A version of this is reproducible without pickle:\r\n\r\n```python\r\nimport matplotlib.pyplot as plt\r\nimport numpy as np\r\n\r\nfig, ax = plt.subplots()\r\n\r\nrng = np.random.default_rng(4181)\r\ndata = rng.uniform(size=(2, 2, 3)).astype(np.dtype('float64', copy=True))\r\naxes_image = ax.imshow(data)\r\nprint(axes_image._A.shape, axes_image._A.dtype)\r\nim = axes_image.make_image(None)[0]\r\n```\n> Can we fallback to eq in the c++ code instead of `is` ?\r\n\r\nIt looks like `dtype1.equal(dtype2)` is good.", "created_at": "2024-06-25T17:58:44Z" }, { "repo": "matplotlib/matplotlib", "pull_number": 28436, "instance_id": "matplotlib__matplotlib-28436", "issue_numbers": [ "28434", "0000" ], "base_commit": "53431a424fdf624a95de5b14540cfb3b5a5e6eb0", "patch": "diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py\nindex c4e5987fdf92..177557b371a6 100644\n--- a/lib/matplotlib/colors.py\n+++ b/lib/matplotlib/colors.py\n@@ -225,7 +225,7 @@ def is_color_like(c):\n return True\n try:\n to_rgba(c)\n- except ValueError:\n+ except (TypeError, ValueError):\n return False\n else:\n return True\n@@ -296,6 +296,11 @@ def to_rgba(c, alpha=None):\n Tuple of floats ``(r, g, b, a)``, where each channel (red, green, blue,\n alpha) can assume values between 0 and 1.\n \"\"\"\n+ if isinstance(c, tuple) and len(c) == 2:\n+ if alpha is None:\n+ c, alpha = c\n+ else:\n+ c = c[0]\n # Special-case nth color syntax because it should not be cached.\n if _is_nth_color(c):\n prop_cycler = mpl.rcParams['axes.prop_cycle']\n@@ -325,11 +330,6 @@ def _to_rgba_no_colorcycle(c, alpha=None):\n *alpha* is ignored for the color value ``\"none\"`` (case-insensitive),\n which always maps to ``(0, 0, 0, 0)``.\n \"\"\"\n- if isinstance(c, tuple) and len(c) == 2:\n- if alpha is None:\n- c, alpha = c\n- else:\n- c = c[0]\n if alpha is not None and not 0 <= alpha <= 1:\n raise ValueError(\"'alpha' must be between 0 and 1, inclusive\")\n orig_c = c\n", "test_patch": "diff --git a/lib/matplotlib/tests/test_colors.py b/lib/matplotlib/tests/test_colors.py\nindex c8b44b2dea14..d99dd91e9cf5 100644\n--- a/lib/matplotlib/tests/test_colors.py\n+++ b/lib/matplotlib/tests/test_colors.py\n@@ -19,7 +19,7 @@\n import matplotlib.scale as mscale\n from matplotlib.rcsetup import cycler\n from matplotlib.testing.decorators import image_comparison, check_figures_equal\n-from matplotlib.colors import to_rgba_array\n+from matplotlib.colors import is_color_like, to_rgba_array\n \n \n @pytest.mark.parametrize('N, result', [\n@@ -1702,3 +1702,16 @@ def test_to_rgba_array_none_color_with_alpha_param():\n assert_array_equal(\n to_rgba_array(c, alpha), [[0., 0., 1., 1.], [0., 0., 0., 0.]]\n )\n+\n+\n+@pytest.mark.parametrize('input, expected',\n+ [('red', True),\n+ (('red', 0.5), True),\n+ (('red', 2), False),\n+ (['red', 0.5], False),\n+ (('red', 'blue'), False),\n+ (['red', 'blue'], False),\n+ ('C3', True),\n+ (('C3', 0.5), True)])\n+def test_is_color_like(input, expected):\n+ assert is_color_like(input) is expected\n", "problem_statement": "[Bug]: Setting exactly 2 colors with tuple in `plot` method gives confusing error\n### Bug summary\n\nIf one attempts to set the `color` parameter to a tuple of length 2, a nonsense error message is returned.\n\n### Code for reproduction\n\n```Python\nimport matplotlib.pyplot as plt\r\nimport numpy as np\r\n\r\nx = np.linspace(0, 10, 100)\r\ny = np.array([np.sin(x), np.cos(x)]).T\r\n\r\nplt.plot(x, y, label=(\"sin(x)\", \"cos(x)\")) # works\r\nplt.plot(x, y, label=(\"sin(x)\", \"cos(x)\"), color=(\"red\", \"blue\")) # causes strange error\r\nplt.legend()\r\nplt.show()\n```\n\n\n### Actual outcome\n\nTraceback (most recent call last):\r\n File \"/Users/miles/Desktop/thing.py\", line 8, in \r\n plt.plot(x, y, label=(\"sin(x)\", \"cos(x)\"), color=(\"red\", \"blue\")) # causes error\r\n File \"/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/matplotlib/pyplot.py\", line 3708, in plot\r\n return gca().plot(\r\n File \"/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/matplotlib/axes/_axes.py\", line 1779, in plot\r\n lines = [*self._get_lines(self, *args, data=data, **kwargs)]\r\n File \"/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/matplotlib/axes/_base.py\", line 296, in __call__\r\n yield from self._plot_args(\r\n File \"/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/matplotlib/axes/_base.py\", line 534, in _plot_args\r\n return [l[0] for l in result]\r\n File \"/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/matplotlib/axes/_base.py\", line 534, in \r\n return [l[0] for l in result]\r\n File \"/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/matplotlib/axes/_base.py\", line 527, in \r\n result = (make_artist(axes, x[:, j % ncx], y[:, j % ncy], kw,\r\n File \"/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/matplotlib/axes/_base.py\", line 335, in _makeline\r\n seg = mlines.Line2D(x, y, **kw)\r\n File \"/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/matplotlib/lines.py\", line 376, in __init__\r\n self.set_color(color)\r\n File \"/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/matplotlib/lines.py\", line 1066, in set_color\r\n mcolors._check_color_like(color=color)\r\n File \"/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/matplotlib/colors.py\", line 245, in _check_color_like\r\n if not is_color_like(v):\r\n File \"/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/matplotlib/colors.py\", line 227, in is_color_like\r\n to_rgba(c)\r\n File \"/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/matplotlib/colors.py\", line 309, in to_rgba\r\n rgba = _to_rgba_no_colorcycle(c, alpha)\r\n File \"/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/matplotlib/colors.py\", line 334, in _to_rgba_no_colorcycle\r\n if alpha is not None and not 0 <= alpha <= 1:\r\nTypeError: '<=' not supported between instances of 'int' and 'str'\n\n### Expected outcome\n\nEither: (1) works as expected as passing a tuple to `label` does, or, if this won't be supported, (2) a more sensical error message is returned, like if `[\"red\", \"blue\"]` were passed.\n\n### Additional information\n\nThis difficult-to-interpret error seems to be caused by the `if` statement of line 328 in `_to_rgba_no_colorcycle` in `colors.py`, which says `if isinstance(c, tuple) and len(c) == 2`. If I comment out this `if` statement, then the error message gives the normal `ValueError: ('red', 'blue') is not a valid value for color...` message. Of course, it would be great if multiple colors were supported here, but I understand if there is a bigger reason why it is not supported.\n\n### Operating system\n\nmacOS 14.4.1\n\n### Matplotlib Version\n\n3.9.0\n\n### Matplotlib Backend\n\nmacosx\n\n### Python version\n\n3.9.6\n\n### Jupyter version\n\n_No response_\n\n### Installation\n\npip\n", "hints_text": "This is a result of the `(color, alpha)` version of specifying colors that was introduced in [3.8.0](https://matplotlib.org/stable/users/prev_whats_new/whats_new_3.8.0.html#add-a-new-valid-color-format-matplotlib-color-alpha)\r\n\r\nThat is why it is specifically tuples (not lists) and specifically length 2...\r\n\r\nMy gut reaction is that the new color specifier is useful enough and the workaround (casting to list) is easy enough that I'm inclined towards keeping current behavior...\r\n\r\nOn the other hand, it may be reasonable to narrow the condition further to \"is the second entry in a length 2 tuple a number\"... and if it is not, treat it as a standard sequence of 2 colors instead...\r\n\r\nAs long as numbers are not valid color specifiers, this would differentiate the cases. I believe that is the case (though _string_ floating point values _are_ valid color specifiers for shades of gray...). Because if a number were a valid color specifier, then this would be an ambiguous case.\nJust catching the `TypeError` within `is_color_like` makes the error consistent with the list case\r\n\r\n```patch\r\ndiff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py\r\nindex c4e5987fdf..f6e78dc3e4 100644\r\n--- a/lib/matplotlib/colors.py\r\n+++ b/lib/matplotlib/colors.py\r\n@@ -225,7 +225,7 @@ def is_color_like(c):\r\n return True\r\n try:\r\n to_rgba(c)\r\n- except ValueError:\r\n+ except (ValueError, TypeError):\r\n return False\r\n else:\r\n return True\r\n\r\n```\r\n```\r\nValueError: ('red', 'blue') is not a valid value for color: supported inputs are (r, g, b) and (r, g, b, a) 0-1 float tuples; '#rrggbb', '#rrggbbaa', '#rgb', '#rgba' strings; named color strings; string reprs of 0-1 floats for grayscale values; 'C0', 'C1', ... strings for colors of the color cycle; and pairs combining one of the above with an alpha value\r\n\r\n```\nNote there is no issue with functions that do take a color sequence\r\n```python\r\nplt.scatter([1, 2], [4, 5], c=(\"red\", \"blue\"))\r\n```\r\n![image](https://github.com/matplotlib/matplotlib/assets/10599679/f2504f6e-3159-4619-b090-f6ff81d81053)\r\n\n> Note there is no issue with functions that do take a color sequence\n\nDo we want to allow this function to take a color sequence? It would be in line w/ the other PRs that are vectorizing inputs.\nIt does seem odd that by default you get 2 colors but you cannot specify 2 colors.\nThe `_is_color_like` change proposed by @rcomer [above](https://github.com/matplotlib/matplotlib/issues/28434#issuecomment-2183340319) should be applied no matter what.\r\n\r\nI'm a bit hesitant on supporting sequences for color. If we do that, we'd have to expand to all parameters consistently. Note in particular that some parameters use sequences as single-value represenations already, e.g. colors and [linestyles](https://matplotlib.org/stable/gallery/lines_bars_and_markers/linestyles.html#linestyles), so detection of sequence-of-parameters is non-trivial. It's not completely unreasonable to support seqnce-of-parameters, but adds significant complexity to an already very complex function. If somebody implements this, I'd like to see an as-clean-as possible implementation.\r\n\r\nWhen adding label sequences, we've explicitly done that as an exception without the promise to support sequences on all parameters. The argument is that all other parameters can be addressed through the property cycle, i.e. can be configured up-front to a multiple-data plot. That's not possible for labels. See https://github.com/matplotlib/matplotlib/pull/16178#issuecomment-750528043.", "created_at": "2024-06-22T08:25:09Z" }, { "repo": "matplotlib/matplotlib", "pull_number": 28430, "instance_id": "matplotlib__matplotlib-28430", "issue_numbers": [ "22482", "0000" ], "base_commit": "d347c3227f8de8a99aa327390fee619310452a96", "patch": "diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py\nindex ed130e6854f2..a298f3ae3d6a 100644\n--- a/lib/matplotlib/widgets.py\n+++ b/lib/matplotlib/widgets.py\n@@ -90,22 +90,6 @@ def ignore(self, event):\n \"\"\"\n return not self.active\n \n- def _changed_canvas(self):\n- \"\"\"\n- Someone has switched the canvas on us!\n-\n- This happens if `savefig` needs to save to a format the previous\n- backend did not support (e.g. saving a figure using an Agg based\n- backend saved to a vector format).\n-\n- Returns\n- -------\n- bool\n- True if the canvas has been changed.\n-\n- \"\"\"\n- return self.canvas is not self.ax.figure.canvas\n-\n \n class AxesWidget(Widget):\n \"\"\"\n@@ -131,9 +115,10 @@ class AxesWidget(Widget):\n \n def __init__(self, ax):\n self.ax = ax\n- self.canvas = ax.figure.canvas\n self._cids = []\n \n+ canvas = property(lambda self: self.ax.figure.canvas)\n+\n def connect_event(self, event, callback):\n \"\"\"\n Connect a callback function with an event.\n@@ -1100,7 +1085,7 @@ def __init__(self, ax, labels, actives=None, *, useblit=True,\n \n def _clear(self, event):\n \"\"\"Internal event handler to clear the buttons.\"\"\"\n- if self.ignore(event) or self._changed_canvas():\n+ if self.ignore(event) or self.canvas.is_saving():\n return\n self._background = self.canvas.copy_from_bbox(self.ax.bbox)\n self.ax.draw_artist(self._checks)\n@@ -1677,7 +1662,7 @@ def __init__(self, ax, labels, active=0, activecolor=None, *,\n \n def _clear(self, event):\n \"\"\"Internal event handler to clear the buttons.\"\"\"\n- if self.ignore(event) or self._changed_canvas():\n+ if self.ignore(event) or self.canvas.is_saving():\n return\n self._background = self.canvas.copy_from_bbox(self.ax.bbox)\n self.ax.draw_artist(self._buttons)\n@@ -1933,7 +1918,7 @@ def __init__(self, ax, *, horizOn=True, vertOn=True, useblit=False,\n \n def clear(self, event):\n \"\"\"Internal event handler to clear the cursor.\"\"\"\n- if self.ignore(event) or self._changed_canvas():\n+ if self.ignore(event) or self.canvas.is_saving():\n return\n if self.useblit:\n self.background = self.canvas.copy_from_bbox(self.ax.bbox)\n@@ -2573,9 +2558,7 @@ def __init__(self, ax, onselect, direction, *, minspan=0, useblit=False,\n self.drag_from_anywhere = drag_from_anywhere\n self.ignore_event_outside = ignore_event_outside\n \n- # Reset canvas so that `new_axes` connects events.\n- self.canvas = None\n- self.new_axes(ax, _props=props)\n+ self.new_axes(ax, _props=props, _init=True)\n \n # Setup handles\n self._handle_props = {\n@@ -2588,14 +2571,15 @@ def __init__(self, ax, onselect, direction, *, minspan=0, useblit=False,\n \n self._active_handle = None\n \n- def new_axes(self, ax, *, _props=None):\n+ def new_axes(self, ax, *, _props=None, _init=False):\n \"\"\"Set SpanSelector to operate on a new Axes.\"\"\"\n- self.ax = ax\n- if self.canvas is not ax.figure.canvas:\n+ reconnect = False\n+ if _init or self.canvas is not ax.figure.canvas:\n if self.canvas is not None:\n self.disconnect_events()\n-\n- self.canvas = ax.figure.canvas\n+ reconnect = True\n+ self.ax = ax\n+ if reconnect:\n self.connect_default_events()\n \n # Reset\ndiff --git a/lib/matplotlib/widgets.pyi b/lib/matplotlib/widgets.pyi\nindex c85ad2158ee7..58adf85aae60 100644\n--- a/lib/matplotlib/widgets.pyi\n+++ b/lib/matplotlib/widgets.pyi\n@@ -33,8 +33,9 @@ class Widget:\n \n class AxesWidget(Widget):\n ax: Axes\n- canvas: FigureCanvasBase | None\n def __init__(self, ax: Axes) -> None: ...\n+ @property\n+ def canvas(self) -> FigureCanvasBase | None: ...\n def connect_event(self, event: Event, callback: Callable) -> None: ...\n def disconnect_events(self) -> None: ...\n \n@@ -310,7 +311,6 @@ class SpanSelector(_SelectorWidget):\n grab_range: float\n drag_from_anywhere: bool\n ignore_event_outside: bool\n- canvas: FigureCanvasBase | None\n def __init__(\n self,\n ax: Axes,\n@@ -330,7 +330,13 @@ class SpanSelector(_SelectorWidget):\n ignore_event_outside: bool = ...,\n snap_values: ArrayLike | None = ...,\n ) -> None: ...\n- def new_axes(self, ax: Axes, *, _props: dict[str, Any] | None = ...) -> None: ...\n+ def new_axes(\n+ self,\n+ ax: Axes,\n+ *,\n+ _props: dict[str, Any] | None = ...,\n+ _init: bool = ...,\n+ ) -> None: ...\n def connect_default_events(self) -> None: ...\n @property\n def direction(self) -> Literal[\"horizontal\", \"vertical\"]: ...\n", "test_patch": "diff --git a/lib/matplotlib/testing/__init__.py b/lib/matplotlib/testing/__init__.py\nindex 8e60267ed608..19113d399626 100644\n--- a/lib/matplotlib/testing/__init__.py\n+++ b/lib/matplotlib/testing/__init__.py\n@@ -211,3 +211,24 @@ def ipython_in_subprocess(requested_backend_or_gui_framework, all_expected_backe\n )\n \n assert proc.stdout.strip().endswith(f\"'{expected_backend}'\")\n+\n+\n+def is_ci_environment():\n+ # Common CI variables\n+ ci_environment_variables = [\n+ 'CI', # Generic CI environment variable\n+ 'CONTINUOUS_INTEGRATION', # Generic CI environment variable\n+ 'TRAVIS', # Travis CI\n+ 'CIRCLECI', # CircleCI\n+ 'JENKINS', # Jenkins\n+ 'GITLAB_CI', # GitLab CI\n+ 'GITHUB_ACTIONS', # GitHub Actions\n+ 'TEAMCITY_VERSION' # TeamCity\n+ # Add other CI environment variables as needed\n+ ]\n+\n+ for env_var in ci_environment_variables:\n+ if os.getenv(env_var):\n+ return True\n+\n+ return False\ndiff --git a/lib/matplotlib/testing/__init__.pyi b/lib/matplotlib/testing/__init__.pyi\nindex 1f52a8ccb8ee..6917b6a5a380 100644\n--- a/lib/matplotlib/testing/__init__.pyi\n+++ b/lib/matplotlib/testing/__init__.pyi\n@@ -51,3 +51,4 @@ def ipython_in_subprocess(\n requested_backend_or_gui_framework: str,\n all_expected_backends: dict[tuple[int, int], str],\n ) -> None: ...\n+def is_ci_environment() -> bool: ...\ndiff --git a/lib/matplotlib/tests/test_backends_interactive.py b/lib/matplotlib/tests/test_backends_interactive.py\nindex 6830e7d5c845..d624b5db0ac2 100644\n--- a/lib/matplotlib/tests/test_backends_interactive.py\n+++ b/lib/matplotlib/tests/test_backends_interactive.py\n@@ -19,7 +19,7 @@\n import matplotlib as mpl\n from matplotlib import _c_internal_utils\n from matplotlib.backend_tools import ToolToggleBase\n-from matplotlib.testing import subprocess_run_helper as _run_helper\n+from matplotlib.testing import subprocess_run_helper as _run_helper, is_ci_environment\n \n \n class _WaitForStringPopen(subprocess.Popen):\n@@ -110,27 +110,6 @@ def _get_testable_interactive_backends():\n for env, marks in _get_available_interactive_backends()]\n \n \n-def is_ci_environment():\n- # Common CI variables\n- ci_environment_variables = [\n- 'CI', # Generic CI environment variable\n- 'CONTINUOUS_INTEGRATION', # Generic CI environment variable\n- 'TRAVIS', # Travis CI\n- 'CIRCLECI', # CircleCI\n- 'JENKINS', # Jenkins\n- 'GITLAB_CI', # GitLab CI\n- 'GITHUB_ACTIONS', # GitHub Actions\n- 'TEAMCITY_VERSION' # TeamCity\n- # Add other CI environment variables as needed\n- ]\n-\n- for env_var in ci_environment_variables:\n- if os.getenv(env_var):\n- return True\n-\n- return False\n-\n-\n # Reasonable safe values for slower CI/Remote and local architectures.\n _test_timeout = 120 if is_ci_environment() else 20\n \ndiff --git a/lib/matplotlib/tests/test_pickle.py b/lib/matplotlib/tests/test_pickle.py\nindex 0cba4f392035..1474a67d28aa 100644\n--- a/lib/matplotlib/tests/test_pickle.py\n+++ b/lib/matplotlib/tests/test_pickle.py\n@@ -1,5 +1,7 @@\n from io import BytesIO\n import ast\n+import os\n+import sys\n import pickle\n import pickletools\n \n@@ -8,7 +10,7 @@\n \n import matplotlib as mpl\n from matplotlib import cm\n-from matplotlib.testing import subprocess_run_helper\n+from matplotlib.testing import subprocess_run_helper, is_ci_environment\n from matplotlib.testing.decorators import check_figures_equal\n from matplotlib.dates import rrulewrapper\n from matplotlib.lines import VertexSelector\n@@ -307,3 +309,23 @@ def test_cycler():\n ax = pickle.loads(pickle.dumps(ax))\n l, = ax.plot([3, 4])\n assert l.get_color() == \"m\"\n+\n+\n+# Run under an interactive backend to test that we don't try to pickle the\n+# (interactive and non-picklable) canvas.\n+def _test_axeswidget_interactive():\n+ ax = plt.figure().add_subplot()\n+ pickle.dumps(mpl.widgets.Button(ax, \"button\"))\n+\n+\n+@pytest.mark.xfail( # https://github.com/actions/setup-python/issues/649\n+ ('TF_BUILD' in os.environ or 'GITHUB_ACTION' in os.environ) and\n+ sys.platform == 'darwin' and sys.version_info[:2] < (3, 11),\n+ reason='Tk version mismatch on Azure macOS CI'\n+ )\n+def test_axeswidget_interactive():\n+ subprocess_run_helper(\n+ _test_axeswidget_interactive,\n+ timeout=120 if is_ci_environment() else 20,\n+ extra_env={'MPLBACKEND': 'tkagg'}\n+ )\n", "problem_statement": "[ENH]: pickle (or save) matplotlib figure with insteractive slider\n### Problem\n\nI'd like to save/pickle a matplotlib figure containing a `matplotlib.widgets.Widget` object to send it to a colleague without him having to run the code I use to generate it.\r\n\r\nI know since matplotlib version 1.2 `matplotlib.figure.Figure` are pickable. However, when the figure contains a `matplotlib.widgets.Widget`, pickle fails to save it.\r\n\r\nFor example consider this class:\r\n\r\n```\r\nimport matplotlib.pyplot as plt\r\nimport matplotlib.widgets as widgets\r\n\r\nimport numpy as np\r\n\r\nimport pickle as pkl\r\n\r\nclass interactive_plot():\r\n\r\n def __init__(self, add_slider):\r\n\r\n self.fig, self.axs = plt.subplots(2,1, gridspec_kw={'height_ratios':[10,1]})\r\n\r\n self.x_plot = np.linspace(0,10,1000)\r\n self.line = self.axs[0].plot(self.x_plot, self.x_plot*0)[0]\r\n self.axs[0].set_ylim((0,10))\r\n self.axs[0].grid()\r\n self.axs[0].set_title(r'$y = x^\\alpha$')\r\n\r\n if add_slider:\r\n self.slider= widgets.Slider(\r\n self.axs[1],\r\n label = r'$\\alpha$',\r\n valmin = 0,\r\n valmax = 5,\r\n valinit = 0,\r\n valstep = .01\r\n )\r\n self.slider.on_changed(self.update)\r\n\r\n def update(self, val):\r\n self.line.set_ydata(self.x_plot**val)\r\n```\r\n\r\nIf I run\r\n\r\n```\r\nh = interactive_plot(add_slider=False)\r\nwith open('test.pkl','wb') as f: pkl.dump(h, f)\r\n```\r\nall works properly. However\r\n```\r\nh = interactive_plot(add_slider=True)\r\nwith open('test.pkl','wb') as f: pkl.dump(h, f)\r\n```\r\nreturns\r\n```\r\nTraceback (most recent call last):\r\n\r\n File \"C:\\Users\\user\\Desktop\\test.py\", line 47, in \r\n with open('test.pkl','wb') as f: pkl.dump(h, f)\r\n\r\nTypeError: cannot pickle 'FigureCanvasQTAgg' object\r\n```\r\n\r\nIs there a way to workaround this?\r\n\r\nDISCLAIMER: The same question was posted here https://stackoverflow.com/questions/71145817/pickle-or-save-matplotlib-figure-with-insteractive-slider\n\n### Proposed solution\n\n_No response_\n", "hints_text": "```patch\r\ndiff --git i/lib/matplotlib/widgets.py w/lib/matplotlib/widgets.py\r\nindex 16e03f5624..a9f1145bf2 100644\r\n--- i/lib/matplotlib/widgets.py\r\n+++ w/lib/matplotlib/widgets.py\r\n@@ -115,9 +115,10 @@ class AxesWidget(Widget):\r\n \r\n def __init__(self, ax):\r\n self.ax = ax\r\n- self.canvas = ax.figure.canvas\r\n self._cids = []\r\n \r\n+ canvas = property(lambda self: self.ax.figure.canvas)\r\n+\r\n def connect_event(self, event, callback):\r\n \"\"\"\r\n Connect a callback function with an event.\r\n```\r\n\"fixes\" the pickling itself (and seems like a reasonable patch regardless), but even then, the pickling process will drop any connected callbacks as functions can generally not be pickled.\n@anntzer is the dropping of callbacks when picking a figure builtin matplotlib ? or is it a limitation of the standard pickle module ? or is there some kind of criterion that make a function pickle-able or not ?\r\n\r\nI am in the same kind of situation as @LucaAmerio, where I create matplotlib figure (with Qt backend) and callbacks connected to the figure and/or the axes. I'd like to be able to save and re-open those figure with the callbacks still active\nThe ability to pickle functions is limited per https://docs.python.org/3/library/pickle.html#what-can-be-pickled-and-unpickled. We could consider pickling only whatever can actually be pickled and drop the rest, but I believe dropping everything (and asking whoever does the unpickling to reconnect things as needed) is not unreasonable either.", "created_at": "2024-06-20T15:34:29Z" }, { "repo": "matplotlib/matplotlib", "pull_number": 28401, "instance_id": "matplotlib__matplotlib-28401", "issue_numbers": [ "28358" ], "base_commit": "fa16860f58439735ddc4d2225876496545cb8e4e", "patch": "diff --git a/lib/matplotlib/text.py b/lib/matplotlib/text.py\nindex 7fc19c042a1f..af990ec1bf9f 100644\n--- a/lib/matplotlib/text.py\n+++ b/lib/matplotlib/text.py\n@@ -606,9 +606,8 @@ def set_wrap(self, wrap):\n \"\"\"\n Set whether the text can be wrapped.\n \n- Wrapping makes sure the text is completely within the figure box, i.e.\n- it does not extend beyond the drawing area. It does not take into\n- account any other artists.\n+ Wrapping makes sure the text is confined to the (sub)figure box. It\n+ does not take into account any other artists.\n \n Parameters\n ----------\n@@ -657,16 +656,16 @@ def _get_dist_to_box(self, rotation, x0, y0, figure_box):\n \"\"\"\n if rotation > 270:\n quad = rotation - 270\n- h1 = y0 / math.cos(math.radians(quad))\n+ h1 = (y0 - figure_box.y0) / math.cos(math.radians(quad))\n h2 = (figure_box.x1 - x0) / math.cos(math.radians(90 - quad))\n elif rotation > 180:\n quad = rotation - 180\n- h1 = x0 / math.cos(math.radians(quad))\n- h2 = y0 / math.cos(math.radians(90 - quad))\n+ h1 = (x0 - figure_box.x0) / math.cos(math.radians(quad))\n+ h2 = (y0 - figure_box.y0) / math.cos(math.radians(90 - quad))\n elif rotation > 90:\n quad = rotation - 90\n h1 = (figure_box.y1 - y0) / math.cos(math.radians(quad))\n- h2 = x0 / math.cos(math.radians(90 - quad))\n+ h2 = (x0 - figure_box.x0) / math.cos(math.radians(90 - quad))\n else:\n h1 = (figure_box.x1 - x0) / math.cos(math.radians(rotation))\n h2 = (figure_box.y1 - y0) / math.cos(math.radians(90 - rotation))\n", "test_patch": "diff --git a/lib/matplotlib/tests/test_text.py b/lib/matplotlib/tests/test_text.py\nindex f8837d8a5f1b..8904337f68ba 100644\n--- a/lib/matplotlib/tests/test_text.py\n+++ b/lib/matplotlib/tests/test_text.py\n@@ -15,6 +15,7 @@\n from matplotlib.font_manager import FontProperties\n import matplotlib.patches as mpatches\n import matplotlib.pyplot as plt\n+from matplotlib.gridspec import GridSpec\n import matplotlib.transforms as mtransforms\n from matplotlib.testing.decorators import check_figures_equal, image_comparison\n from matplotlib.testing._markers import needs_usetex\n@@ -707,9 +708,13 @@ def test_large_subscript_title():\n (0.3, 0, 'right'),\n (0.3, 185, 'left')])\n def test_wrap(x, rotation, halign):\n- fig = plt.figure(figsize=(6, 6))\n+ fig = plt.figure(figsize=(18, 18))\n+ gs = GridSpec(nrows=3, ncols=3, figure=fig)\n+ subfig = fig.add_subfigure(gs[1, 1])\n+ # we only use the central subfigure, which does not align with any\n+ # figure boundary, to ensure only subfigure boundaries are relevant\n s = 'This is a very long text that should be wrapped multiple times.'\n- text = fig.text(x, 0.7, s, wrap=True, rotation=rotation, ha=halign)\n+ text = subfig.text(x, 0.7, s, wrap=True, rotation=rotation, ha=halign)\n fig.canvas.draw()\n assert text._get_wrapped_text() == ('This is a very long\\n'\n 'text that should be\\n'\n", "problem_statement": "[Bug]: Labels don't get wrapped when set_yticks() is used in subplots\n### Bug summary\r\n\r\nWhen plotting bar charts in subplots with very long labels, the option of wrapping text only works on the first plotted subplot, despite passing `wrap=True` to `set_yticks()` in both cases.\r\n\r\n### Code for reproduction\r\n\r\n```Python\r\nimport matplotlib.pyplot as plt\r\nimport numpy as np\r\n\r\nlong_text_label = 'very long category label i want to wrap'\r\nlabels = [f\"{long_text_label}_{i}\" for i in range(5)]\r\nvalues = np.arange(1, 6)\r\n\r\nfig, axes = plt.subplots(1, 2)\r\n\r\naxes[0].barh(np.arange(len(labels)), values)\r\naxes[0].set_yticks(np.arange(len(labels)), labels=labels, wrap=True)\r\n\r\naxes[1].barh(np.arange(len(labels)), values)\r\naxes[1].set_yticks(np.arange(len(labels)), labels=labels, wrap=True)\r\n```\r\n\r\n\r\n### Actual outcome\r\n\r\n![output](https://github.com/matplotlib/matplotlib/assets/35564508/137c042d-a536-4a27-9991-e4e0a67ea527)\r\n\r\n\r\n### Expected outcome\r\n\r\nThe label text on the y axis should appear wrapped on both subplots\r\n\r\n### Additional information\r\n\r\n_No response_\r\n\r\n### Operating system\r\n\r\nmacOS 14.4 (23E214)\r\n\r\n### Matplotlib Version\r\n\r\n3.8.3\r\n\r\n### Matplotlib Backend\r\n\r\nmodule://matplotlib_inline.backend_inline\r\n\r\n### Python version\r\n\r\nPython 3.11.8\r\n\r\n### Jupyter version\r\n\r\n4.2.0\r\n\r\n### Installation\r\n\r\npip\n", "hints_text": "Thanks for the clear report @soogui. I confirm that I have reproduced this with our `main` development branch.\nI'm not clear what the correct behaviour is here though. Currently it's wrapping on the edge of the figure. What would the inner axes wrap on? The axes to the left presumably, but that isn't conceptually straight forward as an axes knows about its figure but not about its neighbours, and it knows its own spine position but doesn't try to reserve space for itself outside those spines. Note we usually do the opposite and make the axes further apart to accommodate the ytick labels. \nFor the current architecture, this is the expected behavior. We should better document what `warp` can or can't do.\r\n\r\nI see two possible ways to improve:\r\n- hard: Assign each axes a bounding box. This should work well with subplots/subplot_mosaic, but I'm unclear how other axes creation methods would handle this. Up to now, the Axes is defined via the data area, ticks and labels just spill outside as far as they need.\r\n- medium: Expand the `wrap` functionality to allow wrapping after N characters and/or specifying a maximal width.\nI think the conceptually simplest is to not allow auto wrap for tick labels. Folks can manually wrap if they need to. \nWell, the case of one subplot with long tick labels works. IMHO we should not break that.\r\n\r\nJust documenting that wrapping is limited to the figure boundary is good enough to manage expectations on the current behavior.\n@jklymak When #28177 is in, we could switch the wrapping box (in `Text._get_wrap_line_width`) to subfigure instead of figure. IMHO this boundary makes more sense. And it would at least allow to get what the OP wants using subfigures:\r\n\r\n```\r\nimport matplotlib.pyplot as plt\r\nimport numpy as np\r\n\r\nlong_text_label = 'very long category label i want to wrap'\r\nlabels = [f\"{long_text_label}_{i}\" for i in range(5)]\r\nvalues = np.arange(1, 6)\r\n\r\nfig = plt.figure()\r\nsubfigs = fig.subfigures(1, 2)\r\nax0 = subfigs[0].subplots()\r\nax1 = subfigs[1].subplots()\r\n\r\nax0.barh(np.arange(len(labels)), values)\r\nax0.set_yticks(np.arange(len(labels)), labels=labels, wrap=True)\r\n\r\nax1.barh(np.arange(len(labels)), values)\r\nax1.set_yticks(np.arange(len(labels)), labels=labels, wrap=True)\r\n```\r\n\r\n--> Created a separate issue for this #28378.\nThat's likely fine. However it should be noted that even in the single subplot situation the wrap is incompatible with layout management since that adjusts the size of the axes to account for the size of the labels versus wrapping the labels. \n\nAs stated I am mildly opposed to us jumping through hoops to allow wrapping tick labels because I don't think it s a generally useful thing to do. However if the issues with wrapping tick labels also extends to other text boxes in subfigures, that might merit some effort to fix. ", "created_at": "2024-06-15T23:21:40Z" }, { "repo": "matplotlib/matplotlib", "pull_number": 28397, "instance_id": "matplotlib__matplotlib-28397", "issue_numbers": [ "28384", "0000" ], "base_commit": "b19794e126c1077b83eba90fe74ab1f67362b1ba", "patch": "diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py\nindex 9139b2ed262f..9f764cc2332f 100644\n--- a/lib/matplotlib/figure.py\n+++ b/lib/matplotlib/figure.py\n@@ -1636,6 +1636,8 @@ def add_subfigure(self, subplotspec, **kwargs):\n sf = SubFigure(self, subplotspec, **kwargs)\n self.subfigs += [sf]\n sf._remove_method = self.subfigs.remove\n+ sf.stale_callback = _stale_figure_callback\n+ self.stale = True\n return sf\n \n def sca(self, a):\n", "test_patch": "diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py\nindex e8edcf61815d..5a8894b10496 100644\n--- a/lib/matplotlib/tests/test_figure.py\n+++ b/lib/matplotlib/tests/test_figure.py\n@@ -1741,3 +1741,27 @@ def test_subfigure_row_order():\n sf_arr = fig.subfigures(4, 3)\n for a, b in zip(sf_arr.ravel(), fig.subfigs):\n assert a is b\n+\n+\n+def test_subfigure_stale_propagation():\n+ fig = plt.figure()\n+\n+ fig.draw_without_rendering()\n+ assert not fig.stale\n+\n+ sfig1 = fig.subfigures()\n+ assert fig.stale\n+\n+ fig.draw_without_rendering()\n+ assert not fig.stale\n+ assert not sfig1.stale\n+\n+ sfig2 = sfig1.subfigures()\n+ assert fig.stale\n+\n+ fig.draw_without_rendering()\n+ assert not fig.stale\n+ assert not sfig2.stale\n+\n+ sfig2.stale = True\n+ assert fig.stale\n", "problem_statement": "[Bug]: subfigure artists not drawn interactively\n### Bug summary\n\nWhen artists are added to a subfigure in interactive mode, they do not appear until I force a draw by resizing the window.\n\n### Code for reproduction\n\n```Python\n$ ipython --matplotlib=qt\r\nPython 3.12.3 | packaged by conda-forge | (main, Apr 15 2024, 18:38:13) [GCC 12.3.0]\r\nType 'copyright', 'credits' or 'license' for more information\r\nIPython 8.24.0 -- An enhanced Interactive Python. Type '?' for help.\r\n\r\nIn [1]: import matplotlib.pyplot as plt\r\n\r\nIn [2]: fig = plt.figure()\r\n\r\nIn [3]: sfig1, sfig2 = fig.subfigures(ncols=2)\r\n\r\nIn [4]: sfig2.suptitle(\"My Title\")\r\nOut[4]: Text(0.5, 0.98, 'My Title')\r\n\r\nIn [5]: ax = sfig1.subplots()\r\n\r\nIn [6]: ax.plot([1, 3, 2])\r\nOut[6]: []\n```\n\n\n### Actual outcome\n\nApparently empty figure\r\n![image](https://github.com/matplotlib/matplotlib/assets/10599679/c6f531b3-cdb1-4af3-89ad-7f8d24c322b5)\r\n\n\n### Expected outcome\n\nIf I resize the window, the artists appear\r\n![image](https://github.com/matplotlib/matplotlib/assets/10599679/223737ec-3f78-4d58-9e3c-c2bff4833a85)\r\n\n\n### Additional information\n\n_No response_\n\n### Operating system\n\nUbuntu\n\n### Matplotlib Version\n\n`main`\n\n### Matplotlib Backend\n\nQtAgg and TkAgg\n\n### Python version\n\n3.12.3\n\n### Jupyter version\n\n_No response_\n\n### Installation\n\ngit checkout\n", "hints_text": "Very likely the issue is that `obj.stale` is not propogating up from the sub-figure to the parent.\nHuh. I never use interactive mode so didn't test that! Seems a pretty big oversight. Thanks for finding. ", "created_at": "2024-06-14T17:55:24Z" }, { "repo": "matplotlib/matplotlib", "pull_number": 28393, "instance_id": "matplotlib__matplotlib-28393", "issue_numbers": [ "28223", "0000" ], "base_commit": "b19794e126c1077b83eba90fe74ab1f67362b1ba", "patch": "diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py\nindex 1cf56c90cc6c..b810501ec7bb 100644\n--- a/lib/matplotlib/axes/_base.py\n+++ b/lib/matplotlib/axes/_base.py\n@@ -2942,9 +2942,15 @@ def handle_single_axis(\n # Index of largest element < x0 + tol, if any.\n i0 = stickies.searchsorted(x0 + tol) - 1\n x0bound = stickies[i0] if i0 != -1 else None\n+ # Ensure the boundary acts only if the sticky is the extreme value\n+ if x0bound is not None and x0bound > x0:\n+ x0bound = None\n # Index of smallest element > x1 - tol, if any.\n i1 = stickies.searchsorted(x1 - tol)\n x1bound = stickies[i1] if i1 != len(stickies) else None\n+ # Ensure the boundary acts only if the sticky is the extreme value\n+ if x1bound is not None and x1bound < x1:\n+ x1bound = None\n \n # Add the margin in figure space and then transform back, to handle\n # non-linear scales.\n", "test_patch": "diff --git a/lib/matplotlib/tests/baseline_images/test_axes/sticky_tolerance.png b/lib/matplotlib/tests/baseline_images/test_axes/sticky_tolerance.png\nnew file mode 100644\nindex 000000000000..a3fb13d0716a\nBinary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_axes/sticky_tolerance.png differ\ndiff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py\nindex dd37d3d8ee80..48121ee04939 100644\n--- a/lib/matplotlib/tests/test_axes.py\n+++ b/lib/matplotlib/tests/test_axes.py\n@@ -682,6 +682,25 @@ def test_sticky_shared_axes(fig_test, fig_ref):\n ax0.pcolormesh(Z)\n \n \n+@image_comparison(['sticky_tolerance.png'], remove_text=True, style=\"mpl20\")\n+def test_sticky_tolerance():\n+ fig, axs = plt.subplots(2, 2)\n+\n+ width = .1\n+\n+ axs.flat[0].bar(x=0, height=width, bottom=20000.6)\n+ axs.flat[0].bar(x=1, height=width, bottom=20000.1)\n+\n+ axs.flat[1].bar(x=0, height=-width, bottom=20000.6)\n+ axs.flat[1].bar(x=1, height=-width, bottom=20000.1)\n+\n+ axs.flat[2].barh(y=0, width=-width, left=-20000.6)\n+ axs.flat[2].barh(y=1, width=-width, left=-20000.1)\n+\n+ axs.flat[3].barh(y=0, width=width, left=-20000.6)\n+ axs.flat[3].barh(y=1, width=width, left=-20000.1)\n+\n+\n def test_nargs_stem():\n with pytest.raises(TypeError, match='0 were given'):\n # stem() takes 1-3 arguments.\n", "problem_statement": "[Bug]: Inconsistent Visualization of Intervals in ax.barh for Different Duration Widths\n### Bug summary\n\n**Issue Description:**\r\n\r\nWhen using ax.barh to visualize intervals, the function fails to display all intervals under certain conditions, which appears to be linked to the width of the intervals specified as duration. Specifically, intervals with a duration of 3 hours are not displayed properly, while increasing the duration to 5 hours results in correct visualization. This issue suggests a potential problem with how interval widths are handled or rendered in ax.barh.\r\n\r\n**Steps to Reproduce:**\r\nRefer to \"code for reproduction\"\r\n\r\n**Expected Behavior:**\r\n\r\nAll intervals should be visualized consistently, regardless of their duration width.\r\n\r\n**Actual Behavior:**\r\n\r\nIntervals with a shorter duration (3 hours) are not visualized correctly, while longer durations (5 hours) are displayed as expected.\n\n### Code for reproduction\n\n```Python\nfrom datetime import datetime, timedelta\r\nimport matplotlib.pyplot as plt\r\n\r\nfig, ax = plt.subplots()\r\n\r\nduration = timedelta(hours=3)\r\n# duration = timedelta(hours=5)\r\nax.barh(y=0, width=duration, left=datetime(2024, 1, 1, 6))\r\nax.barh(y=1, width=duration, left=datetime(2024, 1, 1, 1))\r\n\r\nplt.xticks(rotation=45)\r\nplt.tight_layout()\r\nplt.savefig('bug.png')\n```\n\n\n### Actual outcome\n\nOnly one of two bars is rendered.\r\n![image](https://github.com/matplotlib/matplotlib/assets/78612439/9ad98184-b0bb-4f8d-a976-043ceab01e70)\r\n\n\n### Expected outcome\n\nI expect both bars to appear:\r\n![image](https://github.com/matplotlib/matplotlib/assets/78612439/e3833e42-0869-4c93-95c7-be81adc576bc)\r\n\n\n### Additional information\n\n_No response_\n\n### Operating system\n\nUbuntu and Manjaro\n\n### Matplotlib Version\n\n3.8.4\n\n### Matplotlib Backend\n\nagg\n\n### Python version\n\n3.11.8\n\n### Jupyter version\n\n_No response_\n\n### Installation\n\npip\n", "hints_text": "This is a bug with the auto-limits as first bar starts immediately on the right limit\r\n\r\nIf you draw the edges on the patches:\r\n\r\n```python\r\nax.barh(y=0, width=duration, left=datetime(2024, 1, 1, 6), lw=10, ec='k')\r\nax.barh(y=1, width=duration, left=datetime(2024, 1, 1, 1), lw=10, ec='k')\r\n```\r\n\r\nyou can see the left edge of the blue bar peaking out\r\n\r\n![bug](https://github.com/matplotlib/matplotlib/assets/199813/90bce6a1-477c-481f-bcf2-e0605a18dcbd)\r\n\r\n\r\nWhy making the orange bar long enough to overlap \"fixes\" it is very surprising to me.\nIf you pan (or manually set the limits) the second bar renders correctly, it is just that it is off screen by default.\nWe discussed this on a call, and the problem isn't with units, but with the relative size of the positions vs the widths of the bars (i.e., when dates are made unitless, they are positioned at ~20000 vs a width of < 1 for hours). This causes some poor interaction with sticky edges. @ksunden will be opening a PR shortly.", "created_at": "2024-06-13T22:54:16Z" }, { "repo": "matplotlib/matplotlib", "pull_number": 28388, "instance_id": "matplotlib__matplotlib-28388", "issue_numbers": [ "28367", "0000" ], "base_commit": "bcaffa137e5724259c4bf96a658fe3a11191b25e", "patch": "diff --git a/lib/matplotlib/backends/registry.py b/lib/matplotlib/backends/registry.py\nindex 19b4cba254ab..47d5f65e350e 100644\n--- a/lib/matplotlib/backends/registry.py\n+++ b/lib/matplotlib/backends/registry.py\n@@ -168,8 +168,11 @@ def backward_compatible_entry_points(\n def _validate_and_store_entry_points(self, entries):\n # Validate and store entry points so that they can be used via matplotlib.use()\n # in the normal manner. Entry point names cannot be of module:// format, cannot\n- # shadow a built-in backend name, and cannot be duplicated.\n- for name, module in entries:\n+ # shadow a built-in backend name, and there cannot be multiple entry points\n+ # with the same name but different modules. Multiple entry points with the same\n+ # name and value are permitted (it can sometimes happen outside of our control,\n+ # see https://github.com/matplotlib/matplotlib/issues/28367).\n+ for name, module in set(entries):\n name = name.lower()\n if name.startswith(\"module://\"):\n raise RuntimeError(\n", "test_patch": "diff --git a/lib/matplotlib/tests/test_backend_registry.py b/lib/matplotlib/tests/test_backend_registry.py\nindex eaf8417e7a5f..141ffd69c266 100644\n--- a/lib/matplotlib/tests/test_backend_registry.py\n+++ b/lib/matplotlib/tests/test_backend_registry.py\n@@ -121,6 +121,17 @@ def test_entry_point_name_duplicate(clear_backend_registry):\n [('some_name', 'module1'), ('some_name', 'module2')])\n \n \n+def test_entry_point_identical(clear_backend_registry):\n+ # Issue https://github.com/matplotlib/matplotlib/issues/28367\n+ # Multiple entry points with the same name and value (value is the module)\n+ # are acceptable.\n+ n = len(backend_registry._name_to_module)\n+ backend_registry._validate_and_store_entry_points(\n+ [('some_name', 'some.module'), ('some_name', 'some.module')])\n+ assert len(backend_registry._name_to_module) == n+1\n+ assert backend_registry._name_to_module['some_name'] == 'module://some.module'\n+\n+\n def test_entry_point_name_is_module(clear_backend_registry):\n with pytest.raises(RuntimeError):\n backend_registry._validate_and_store_entry_points(\n", "problem_statement": "[Bug]: Backend entry points can be erroneously duplicated\n### Summary\n\nUnder certain circumstances outside of our control, an `entry_points`-registering backend such as `matplotlib-inline` can appear as two entry points so that we raise a `RuntimeError(f\"Entry point name '{name}' duplicated\")`. Reported on [Matplotlib Discourse](https://discourse.matplotlib.org/t/latest-versions-via-pip-jupyterlab-import-of-matplotlib-broken/24477/6).\n\n### Proposed fix\n\nThe `BackendRegistry` purposefully does not allow multiple entry points with the same name as it has no way to know which one to prefer. This has now caused a problem in the linked discussion when using system python 3.9 and a virtual environment on Rocky 9.4 Linux. Although there is only a single `entry_points.txt` file for e.g. `matplotlib-inline` the system python `importlib.metadata.entry_points` erroneously returns two identical items. I think this is because the virtualenv has both a `lib` directory and a `lib64` symlink to the `lib` directory, and this particular combination of Linux distribution, python version and use of `venv` does not check that they both refer to the same file. Regardless of where the problem really lies here, Matplotlib needs to be able to deal with it.\r\n\r\nI'll submit a PR later in the week. I think the simplest change is to allow (i.e. discard) entry points that have the same `group` (e.g. `matplotlib.backend`), `name` (e.g. `inline`) and `value` (e.g. `matplotlib_inline.backend_inline`), and then raise if there are multiple entry points of the same `group` and `name` but different `value`.\n", "hints_text": "", "created_at": "2024-06-13T12:00:20Z" }, { "repo": "matplotlib/matplotlib", "pull_number": 28363, "instance_id": "matplotlib__matplotlib-28363", "issue_numbers": [ "28344", "0000" ], "base_commit": "5d6acdf486d0b7ca0065ea5eef9573f2c24b07b3", "patch": "diff --git a/doc/api/next_api_changes/behavior/28363-TS.rst b/doc/api/next_api_changes/behavior/28363-TS.rst\nnew file mode 100644\nindex 000000000000..2242f3929e04\n--- /dev/null\n+++ b/doc/api/next_api_changes/behavior/28363-TS.rst\n@@ -0,0 +1,6 @@\n+Subfigures\n+~~~~~~~~~~\n+\n+`.Figure.subfigures` are now added in row-major order to be consistent with\n+`.Figure.subplots`. The return value of `~.Figure.subfigures` is not changed,\n+but the order of ``fig.subfigs`` is.\ndiff --git a/doc/users/next_whats_new/subfigures_change_order.rst b/doc/users/next_whats_new/subfigures_change_order.rst\nnew file mode 100644\nindex 000000000000..49a018a3fd96\n--- /dev/null\n+++ b/doc/users/next_whats_new/subfigures_change_order.rst\n@@ -0,0 +1,23 @@\n+Subfigures are now added in row-major order\n+-------------------------------------------\n+\n+``Figure.subfigures`` are now added in row-major order for API consistency.\n+\n+\n+.. plot::\n+ :include-source: true\n+ :alt: Example of creating 3 by 3 subfigures.\n+\n+ import matplotlib.pyplot as plt\n+\n+ fig = plt.figure()\n+ subfigs = fig.subfigures(3, 3)\n+ x = np.linspace(0, 10, 100)\n+\n+ for i, sf in enumerate(fig.subfigs):\n+ ax = sf.subplots()\n+ ax.plot(x, np.sin(x + i), label=f'Subfigure {i+1}')\n+ sf.suptitle(f'Subfigure {i+1}')\n+ ax.set_xticks([])\n+ ax.set_yticks([])\n+ plt.show()\ndiff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py\nindex 0a0ff01a2571..0aa90e716b1c 100644\n--- a/lib/matplotlib/figure.py\n+++ b/lib/matplotlib/figure.py\n@@ -1550,6 +1550,9 @@ def subfigures(self, nrows=1, ncols=1, squeeze=True,\n .. note::\n The *subfigure* concept is new in v3.4, and the API is still provisional.\n \n+ .. versionchanged:: 3.10\n+ subfigures are now added in row-major order.\n+\n Parameters\n ----------\n nrows, ncols : int, default: 1\n@@ -1583,9 +1586,9 @@ def subfigures(self, nrows=1, ncols=1, squeeze=True,\n left=0, right=1, bottom=0, top=1)\n \n sfarr = np.empty((nrows, ncols), dtype=object)\n- for i in range(ncols):\n- for j in range(nrows):\n- sfarr[j, i] = self.add_subfigure(gs[j, i], **kwargs)\n+ for i in range(nrows):\n+ for j in range(ncols):\n+ sfarr[i, j] = self.add_subfigure(gs[i, j], **kwargs)\n \n if self.get_layout_engine() is None and (wspace is not None or\n hspace is not None):\n", "test_patch": "diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py\nindex 58aecd3dea8b..e8edcf61815d 100644\n--- a/lib/matplotlib/tests/test_figure.py\n+++ b/lib/matplotlib/tests/test_figure.py\n@@ -1733,3 +1733,11 @@ def test_warn_colorbar_mismatch():\n subfig3_1.colorbar(im3_2) # should not warn\n with pytest.warns(UserWarning, match=\"different Figure\"):\n subfig3_1.colorbar(im4_1)\n+\n+\n+def test_subfigure_row_order():\n+ # Test that subfigures are drawn in row-major order.\n+ fig = plt.figure()\n+ sf_arr = fig.subfigures(4, 3)\n+ for a, b in zip(sf_arr.ravel(), fig.subfigs):\n+ assert a is b\n", "problem_statement": "[Bug]: subfigures are added in column major order\n### Bug summary\n\nsubplots() adds axes in row-major order, but subfigures() add subfigures in column-major order\n\n### Code for reproduction\n\n```Python\nfrom pylab import *\r\n\r\nf = figure()\r\nf.subplots(3, 3)\r\nfor i, ax in enumerate(f.axes): ax.set_title(i)\r\nsavefig(\"/tmp/subplots.png\")\r\n\r\nf = figure()\r\nf.subfigures(3, 3)\r\nfor i, sf in enumerate(f.subfigs): sf.suptitle(i)\r\nsavefig(\"/tmp/subfigures.png\")\n```\n\n\n### Actual outcome\n\nsubplots\r\n![subplots](https://github.com/matplotlib/matplotlib/assets/1322974/0f972af4-492c-4621-941b-440cd9dab208)\r\n\r\nsubfigures\r\n![subfigures](https://github.com/matplotlib/matplotlib/assets/1322974/56b98e97-2b2c-476f-acd7-e9ebe2716ecb)\r\n\n\n### Expected outcome\n\nSubfigures are added in row-major axis, per standard numpy defaults.\n\n### Additional information\n\nIn practice this should rarely matter, as the subfigures don't overlap so drawing order should be irrelevant... perhaps unless some artists are positioned beyond the subfigure edge and not clipped.\r\n\r\nThe behavior directly arises from the iteration in subfigures:\r\n```python\r\n sfarr = np.empty((nrows, ncols), dtype=object)\r\n for i in range(ncols):\r\n for j in range(nrows):\r\n sfarr[j, i] = self.add_subfigure(gs[j, i], **kwargs)\r\n```\n\n### Operating system\n\nmacOS\n\n### Matplotlib Version\n\n3.10.0.dev233+ga833d99e46\n\n### Matplotlib Backend\n\nqtagg\n\n### Python version\n\n3.12\n\n### Jupyter version\n\nno\n\n### Installation\n\ngit checkout\n", "hints_text": "I am in favor of flipping `subfigures` to match `subplots`. I think this is a case where we can not warn, but should just do it (and clearly document it). I hope this won't bite too many people who are relying on the iteration order and get us out of explaining why it is different forever.\nIt should also be noted that subfigures as of 3.9 (I think) now respect z-order so you can move them up and down if you have to.", "created_at": "2024-06-09T07:04:08Z" }, { "repo": "matplotlib/matplotlib", "pull_number": 28306, "instance_id": "matplotlib__matplotlib-28306", "issue_numbers": [ "4568" ], "base_commit": "81756c366400a4c7d2c3934b5ff1a359206f241a", "patch": "diff --git a/lib/matplotlib/projections/polar.py b/lib/matplotlib/projections/polar.py\nindex 8d3e03f64e7c..025155351f88 100644\n--- a/lib/matplotlib/projections/polar.py\n+++ b/lib/matplotlib/projections/polar.py\n@@ -1447,12 +1447,25 @@ def format_sig(value, delta, opt, fmt):\n cbook._g_sig_digits(value, delta))\n return f\"{value:-{opt}.{prec}{fmt}}\"\n \n- return ('\\N{GREEK SMALL LETTER THETA}={}\\N{GREEK SMALL LETTER PI} '\n- '({}\\N{DEGREE SIGN}), r={}').format(\n+ # In case fmt_xdata was not specified, resort to default\n+\n+ if self.fmt_ydata is None:\n+ r_label = format_sig(r, delta_r, \"#\", \"g\")\n+ else:\n+ r_label = self.format_ydata(r)\n+\n+ if self.fmt_xdata is None:\n+ return ('\\N{GREEK SMALL LETTER THETA}={}\\N{GREEK SMALL LETTER PI} '\n+ '({}\\N{DEGREE SIGN}), r={}').format(\n format_sig(theta_halfturns, delta_t_halfturns, \"\", \"f\"),\n format_sig(theta_degrees, delta_t_degrees, \"\", \"f\"),\n- format_sig(r, delta_r, \"#\", \"g\"),\n+ r_label\n )\n+ else:\n+ return '\\N{GREEK SMALL LETTER THETA}={}, r={}'.format(\n+ self.format_xdata(theta),\n+ r_label\n+ )\n \n def get_data_ratio(self):\n \"\"\"\n", "test_patch": "diff --git a/lib/matplotlib/tests/test_polar.py b/lib/matplotlib/tests/test_polar.py\nindex 6b3c08d2eb3f..0bb41a50b2d4 100644\n--- a/lib/matplotlib/tests/test_polar.py\n+++ b/lib/matplotlib/tests/test_polar.py\n@@ -436,6 +436,33 @@ def test_cursor_precision():\n assert ax.format_coord(2, 1) == \"\u03b8=0.637\u03c0 (114.6\u00b0), r=1.000\"\n \n \n+def test_custom_fmt_data():\n+ ax = plt.subplot(projection=\"polar\")\n+ def millions(x):\n+ return '$%1.1fM' % (x*1e-6)\n+\n+ # Test only x formatter\n+ ax.fmt_xdata = None\n+ ax.fmt_ydata = millions\n+ assert ax.format_coord(12, 2e7) == \"\u03b8=3.8197186342\u03c0 (687.54935416\u00b0), r=$20.0M\"\n+ assert ax.format_coord(1234, 2e6) == \"\u03b8=392.794399551\u03c0 (70702.9919191\u00b0), r=$2.0M\"\n+ assert ax.format_coord(3, 100) == \"\u03b8=0.95493\u03c0 (171.887\u00b0), r=$0.0M\"\n+\n+ # Test only y formatter\n+ ax.fmt_xdata = millions\n+ ax.fmt_ydata = None\n+ assert ax.format_coord(2e5, 1) == \"\u03b8=$0.2M, r=1.000\"\n+ assert ax.format_coord(1, .1) == \"\u03b8=$0.0M, r=0.100\"\n+ assert ax.format_coord(1e6, 0.005) == \"\u03b8=$1.0M, r=0.005\"\n+\n+ # Test both x and y formatters\n+ ax.fmt_xdata = millions\n+ ax.fmt_ydata = millions\n+ assert ax.format_coord(2e6, 2e4*3e5) == \"\u03b8=$2.0M, r=$6000.0M\"\n+ assert ax.format_coord(1e18, 12891328123) == \"\u03b8=$1000000000000.0M, r=$12891.3M\"\n+ assert ax.format_coord(63**7, 1081968*1024) == \"\u03b8=$3938980.6M, r=$1107.9M\"\n+\n+\n @image_comparison(['polar_log.png'], style='default')\n def test_polar_log():\n fig = plt.figure()\n", "problem_statement": "Add `fmt_r` and `fmt_theta` methods to polar axes\nThe example here works in Cartesian coordinates:\n\nhttp://matplotlib.org/examples/pylab_examples/coords_report.html\n\nbut if you change \n\n`subplots()`\n\nto\n\n`subplots(subplot_kw={'polar':True})`\n\nThen the `millions()` function is never even called.\n\n( no response on mailing list: http://matplotlib.1069221.n5.nabble.com/fmt-xdata-fmt-ydata-on-polar-plot-td45838.html )\n\n", "hints_text": "Because the polar plot does not use those functions. The message shown in the the gui are generated by calls to `axes.format_coords`, which is the case the the basic Cartesian coordinates the function is: https://github.com/matplotlib/matplotlib/blob/master/lib/matplotlib/axes/_base.py#L3056 which looks at `self.fmt_xdata` and `self.fmt_ydata` (via `format_xdata` and `format_ydata`). In contrast the same function on the `Axes` sub-class for polar axes (https://github.com/matplotlib/matplotlib/blob/master/lib/matplotlib/projections/polar.py#L589) does not. \n\nIf you want to change the message for polar coordinates, directly assign to `ax.format_coords` as shown in http://matplotlib.org/examples/api/image_zcoord.html.\n\nIt would be sensible to have a `fmt_r` and `fmt_theta` functions attached to polar axes.\n\nThanks for the solution!\n\nAs far as naming convention goes (if you do implement the new functions), I don't know if `fmt_r` and `fmt_theta` would make things more or less consistent... there are other cases in polar where we still use functions with 'x' and 'y' in the name... e.g. `set_yticklabels`.\n\nThe problem I have always had with PolarAxes is remembering whether x maps to r or theta. We need to use the x and y notations for subclassing purposes (that way the core matplotlib code can continue to work properly by treating PolarAxes like any other 2D axes object). Aliases to x and y for r and theta would be very useful to end-users in general, like we have now for some things in PolarAxes.\n\nHowever, I don't know how valuable it would be for this particular case. The way we advertise these formatting functions, we expect users to either monkey patch the particular method they want to modify, or to subclass and override the relevant method. If we had aliases, they won't get updated to the new methods, nor would they get used anyway by the internal methods.\n\nNo, I think the correct solution here is to make PolarAxes have a `fmt_xdata` and `fmt_ydata` method like most other classes do. I think the code we see in `format_coords` is intended to be compatibility code to help transition Axes objects that had not yet been updated to provide such methods.\n\nThis issue has been marked \"inactive\" because it has been 365 days since the last comment. If this issue is still present in recent Matplotlib releases, or the feature request is still wanted, please leave a comment and this label will be removed. If there are no updates in another 30 days, this issue will be automatically closed, but you are free to re-open or create a new issue if needed. We value issue reports, and this procedure is meant to help us resurface and prioritize issues that have not been addressed yet, not make them disappear. Thanks for your help!\nhi there, @atpage @tacaswell is this issue still open?\nHi @antara-gandhi , unless the issue is closed (as seen on the GitHub interface) you are free to propose a PR or ask for guidance \ud83d\ude04 In this case, it would be nice to check if the issue is still present in recent versions and then propose a solution. Cheers!\nIs the goal to change the example code to use fmt_r and fmt_theta and be displayed as a polar graph or a way to standardize using fmt_r and fmt_theta to change coordinate plots to polar plots?\r\n\r\nMy second question is more if there is code I can modify, or do I need to come up with a way to standardize this?\r\n\nI changed and added to the example code is this correct?\r\n\r\n`def fmt_r(r):\r\n \r\n return f\"R={r:.2f}\"\r\n\r\ndef fmt_theta(theta, pos=None):\r\n \r\n return f\"Theta={np.degrees(theta):.2f}\u00b0\"\r\n\r\ndef millions(x, pos=None):\r\n return '$%1.1fM' % (x*1e-6)\r\n\r\nx = np.random.rand(20)\r\ny = 1e7 * np.random.rand(20)\r\n\r\nfig, ax = plt.subplots(subplot_kw={'polar': True})\r\n\r\nax.set_yticklabels([fmt_r(label) for label in ax.get_yticks()])\r\n\r\nax.set_xticklabels([fmt_theta(label) for label in ax.get_xticks()])\r\n\r\nax.yaxis.set_major_formatter(plt.FuncFormatter(millions))\r\n\r\nax.plot(x, y, 'o')\r\n\r\nplt.show()`", "created_at": "2024-05-26T19:37:02Z" }, { "repo": "matplotlib/matplotlib", "pull_number": 28261, "instance_id": "matplotlib__matplotlib-28261", "issue_numbers": [ "28256" ], "base_commit": "31a2e1edb756e6b42ecd7ff405e488647d8ea27a", "patch": "diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py\nindex d0f5c8d2b23b..677c2668d4e9 100644\n--- a/lib/mpl_toolkits/mplot3d/axes3d.py\n+++ b/lib/mpl_toolkits/mplot3d/axes3d.py\n@@ -1524,6 +1524,7 @@ def _on_move(self, event):\n dazim = -(dy/h)*180*np.sin(roll) - (dx/w)*180*np.cos(roll)\n elev = self.elev + delev\n azim = self.azim + dazim\n+ roll = self.roll\n vertical_axis = self._axis_names[self._vertical_axis]\n self.view_init(\n elev=elev,\n", "test_patch": "diff --git a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py\nindex ed56e5505d8e..c339e35e903c 100644\n--- a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py\n+++ b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py\n@@ -1766,6 +1766,31 @@ def test_shared_axes_retick():\n assert ax2.get_zlim() == (-0.5, 2.5)\n \n \n+def test_rotate():\n+ \"\"\"Test rotating using the left mouse button.\"\"\"\n+ for roll in [0, 30]:\n+ fig = plt.figure()\n+ ax = fig.add_subplot(1, 1, 1, projection='3d')\n+ ax.view_init(0, 0, roll)\n+ ax.figure.canvas.draw()\n+\n+ # drag mouse horizontally to change azimuth\n+ dx = 0.1\n+ dy = 0.2\n+ ax._button_press(\n+ mock_event(ax, button=MouseButton.LEFT, xdata=0, ydata=0))\n+ ax._on_move(\n+ mock_event(ax, button=MouseButton.LEFT,\n+ xdata=dx*ax._pseudo_w, ydata=dy*ax._pseudo_h))\n+ ax.figure.canvas.draw()\n+ roll_radians = np.deg2rad(ax.roll)\n+ cs = np.cos(roll_radians)\n+ sn = np.sin(roll_radians)\n+ assert ax.elev == (-dy*180*cs + dx*180*sn)\n+ assert ax.azim == (-dy*180*sn - dx*180*cs)\n+ assert ax.roll == roll\n+\n+\n def test_pan():\n \"\"\"Test mouse panning using the middle mouse button.\"\"\"\n \n", "problem_statement": "[Bug]: axes3d.py's _on_move() converts the roll angle to radians, but then passes it to view_init() as if it were still in degrees\n### Bug summary\n\nIn \\lib\\mpl_toolkits\\mplot3d\\axes3d.py, `_on_move()` deals with rotation of 3d axes using the mouse. In line 1522, the roll angle (in degrees) is converted to radians:\r\n` roll = np.deg2rad(self.roll)`\r\nThis roll in radians is used to calculate a new elevation and azimuth, like so:\r\n```\r\n delev = -(dy/h)*180*np.cos(roll) + (dx/w)*180*np.sin(roll)\r\n dazim = -(dy/h)*180*np.sin(roll) - (dx/w)*180*np.cos(roll)\r\n elev = self.elev + delev\r\n azim = self.azim + dazim\r\n```\r\nA moment later, the view is updated:\r\n```\r\n self.view_init(\r\n elev=elev,\r\n azim=azim,\r\n roll=roll,\r\n vertical_axis=vertical_axis,\r\n share=True,\r\n )\r\n```\r\nHowever, `view_init()` expects its parameters to be in degrees, not radians. As a consequence, the roll now diminishes by a factor pi/180 with every mouse movement. Not intended.\r\n\n\n### Code for reproduction\n\n```Python\n# Run the surface3d.py example, adding\r\nax.roll = 45\r\n# It shows the plot, in the intended funny orientation (roll=45)\r\n# Then move the mouse - you will see the orientation jump suddenly (to roll=0)\n```\n\n\n### Actual outcome\n\nThe figure orientation has jumped to roll=0, after trying to rotate it only slightly by dragging the mouse:\r\n![Figure_rotated](https://github.com/matplotlib/matplotlib/assets/122418839/ccbc7f47-dc27-499c-bee4-296b5b5164cb)\r\n\n\n### Expected outcome\n\nThe figure is close to its original orientation (before dragging the mouse), at roll=45:\r\n![Figure_1](https://github.com/matplotlib/matplotlib/assets/122418839/6e572bc9-e1f4-4a87-87f3-4b571ef06288)\r\n\n\n### Additional information\n\nFix: add a line:\r\n`roll = self.roll`\r\nright after updating `elev` and `azim` (i.e., after line 1526).\r\n\n\n### Operating system\n\nAll, presumably; but I noticed it on Windows\n\n### Matplotlib Version\n\n3.10.0.dev191+ge5af947d1b.d20240517\n\n### Matplotlib Backend\n\ntkagg\n\n### Python version\n\nPython 3.12.3\n\n### Jupyter version\n\n_No response_\n\n### Installation\n\npip\n", "hints_text": "", "created_at": "2024-05-19T18:36:49Z" }, { "repo": "matplotlib/matplotlib", "pull_number": 28186, "instance_id": "matplotlib__matplotlib-28186", "issue_numbers": [ "28180", "0000" ], "base_commit": "ce15014066654ec176e3244d33dbd07137b09f8d", "patch": "diff --git a/lib/matplotlib/_mathtext.py b/lib/matplotlib/_mathtext.py\nindex 6e4df209b1f9..e47c58c72f63 100644\n--- a/lib/matplotlib/_mathtext.py\n+++ b/lib/matplotlib/_mathtext.py\n@@ -2278,14 +2278,14 @@ def symbol(self, s: str, loc: int,\n \n if c in self._spaced_symbols:\n # iterate until we find previous character, needed for cases\n- # such as ${ -2}$, $ -2$, or $ -2$.\n+ # such as $=-2$, ${ -2}$, $ -2$, or $ -2$.\n prev_char = next((c for c in s[:loc][::-1] if c != ' '), '')\n # Binary operators at start of string should not be spaced\n # Also, operators in sub- or superscripts should not be spaced\n if (self._in_subscript_or_superscript or (\n c in self._binary_operators and (\n- len(s[:loc].split()) == 0 or prev_char == '{' or\n- prev_char in self._left_delims))):\n+ len(s[:loc].split()) == 0 or prev_char in {\n+ '{', *self._left_delims, *self._relation_symbols}))):\n return [char]\n else:\n return [Hlist([self._make_space(0.2),\n", "test_patch": "diff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_83.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_83.pdf\nnew file mode 100644\nindex 000000000000..31ec241a04fc\nBinary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_83.pdf differ\ndiff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_83.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_83.png\nnew file mode 100644\nindex 000000000000..a4ce71fb244b\nBinary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_83.png differ\ndiff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_83.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_83.svg\nnew file mode 100644\nindex 000000000000..01650aa1cfc5\n--- /dev/null\n+++ b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_cm_83.svg\n@@ -0,0 +1,199 @@\n+\n+\n+\n+ \n+ \n+ \n+ \n+ 2024-05-08T19:52:27.776189\n+ image/svg+xml\n+ \n+ \n+ Matplotlib v3.10.0.dev150+gec4808956b.d20240508, https://matplotlib.org/\n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+\ndiff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_83.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_83.pdf\nnew file mode 100644\nindex 000000000000..e09af853ea1f\nBinary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_83.pdf differ\ndiff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_83.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_83.png\nnew file mode 100644\nindex 000000000000..8a03c6e92bc6\nBinary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_83.png differ\ndiff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_83.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_83.svg\nnew file mode 100644\nindex 000000000000..b0a6fe95cfa3\n--- /dev/null\n+++ b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavusans_83.svg\n@@ -0,0 +1,159 @@\n+\n+\n+\n+ \n+ \n+ \n+ \n+ 2024-05-08T19:52:35.349617\n+ image/svg+xml\n+ \n+ \n+ Matplotlib v3.10.0.dev150+gec4808956b.d20240508, https://matplotlib.org/\n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+\ndiff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_83.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_83.pdf\nnew file mode 100644\nindex 000000000000..db06e90e5490\nBinary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_83.pdf differ\ndiff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_83.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_83.png\nnew file mode 100644\nindex 000000000000..d5c323fa9bd2\nBinary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_83.png differ\ndiff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_83.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_83.svg\nnew file mode 100644\nindex 000000000000..1c3ac31ac5eb\n--- /dev/null\n+++ b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_dejavuserif_83.svg\n@@ -0,0 +1,148 @@\n+\n+\n+\n+ \n+ \n+ \n+ \n+ 2024-05-08T19:52:37.707152\n+ image/svg+xml\n+ \n+ \n+ Matplotlib v3.10.0.dev150+gec4808956b.d20240508, https://matplotlib.org/\n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+\ndiff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_83.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_83.pdf\nnew file mode 100644\nindex 000000000000..6679c1e8af13\nBinary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_83.pdf differ\ndiff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_83.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_83.png\nnew file mode 100644\nindex 000000000000..d6f17be104fa\nBinary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_83.png differ\ndiff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_83.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_83.svg\nnew file mode 100644\nindex 000000000000..3268d5d3d26d\n--- /dev/null\n+++ b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stix_83.svg\n@@ -0,0 +1,159 @@\n+\n+\n+\n+ \n+ \n+ \n+ \n+ 2024-05-08T19:52:30.625389\n+ image/svg+xml\n+ \n+ \n+ Matplotlib v3.10.0.dev150+gec4808956b.d20240508, https://matplotlib.org/\n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+\ndiff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_83.pdf b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_83.pdf\nnew file mode 100644\nindex 000000000000..22e75bb9b0a3\nBinary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_83.pdf differ\ndiff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_83.png b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_83.png\nnew file mode 100644\nindex 000000000000..c23070cdf8b9\nBinary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_83.png differ\ndiff --git a/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_83.svg b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_83.svg\nnew file mode 100644\nindex 000000000000..97c40174b3ef\n--- /dev/null\n+++ b/lib/matplotlib/tests/baseline_images/test_mathtext/mathtext_stixsans_83.svg\n@@ -0,0 +1,136 @@\n+\n+\n+\n+ \n+ \n+ \n+ \n+ 2024-05-08T19:52:33.020611\n+ image/svg+xml\n+ \n+ \n+ Matplotlib v3.10.0.dev150+gec4808956b.d20240508, https://matplotlib.org/\n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+\ndiff --git a/lib/matplotlib/tests/test_mathtext.py b/lib/matplotlib/tests/test_mathtext.py\nindex e3659245d0e7..6ce327f38341 100644\n--- a/lib/matplotlib/tests/test_mathtext.py\n+++ b/lib/matplotlib/tests/test_mathtext.py\n@@ -124,6 +124,7 @@\n r'$,$ $.$ $1{,}234{, }567{ , }890$ and $1,234,567,890$', # github issue 5799\n r'$\\left(X\\right)_{a}^{b}$', # github issue 7615\n r'$\\dfrac{\\$100.00}{y}$', # github issue #1888\n+ r'$a=-b-c$' # github issue #28180\n ]\n # 'svgastext' tests switch svg output to embed text as text (rather than as\n # paths).\n", "problem_statement": "[Bug]: mathtext should distinguish between unary and binary minus\n### Bug summary\n\nTeX inserts a (thin?) space after a binary minus, but not before a unary minus. Typographically, I believe this does look nicer.\r\nmathtext does not distinguish the two cases and always puts in a thin space. It would be nice if it followed tex's behavior.\n\n### Code for reproduction\n\n```Python\nfrom pylab import *\r\nfigtext(.2, .6, \"$a=-b-c$\")\r\nfigtext(.2, .5, \"$a=-b-c$\", usetex=True)\n```\n\n\n### Actual outcome\n\n![Figure_1](https://github.com/matplotlib/matplotlib/assets/1322974/a42a6019-eea7-4bc3-9397-fb2cafe4dc2c)\r\nCompare the spacing before the \"b\" and the \"c\": they are different with usetex, but not with mathtext.\n\n### Expected outcome\n\nRemove the space in mathtext before the unary minus.\n\n### Additional information\n\nProbably involves reading the TeXbook to figure out what is TeX's logic for distinguishing unary and binary minus.\r\nTagging as good first issue *solely* because there should be no API design at all, but medium (-hard) difficulty because that'll require both reading the TeXbook and learning about mathtext's implementation. However I would guess there's no \"strong\" technical blockers either.\n\n### Operating system\n\nany\n\n### Matplotlib Version\n\n3.10.0.dev137+g9387431ab6\n\n### Matplotlib Backend\n\nqtagg\n\n### Python version\n\n3.12\n\n### Jupyter version\n\nno\n\n### Installation\n\ngit checkout\n", "hints_text": "### Good first issue - notes for new contributors\n\nThis issue is suited to new contributors because it does not require understanding of the\nMatplotlib internals. To get started, please see our [contributing\nguide](https://matplotlib.org/stable/devel/index).\n\n**We do not assign issues**. Check the *Development* section in the sidebar for linked pull\nrequests (PRs). If there are none, feel free to start working on it. If there is an open PR, please\ncollaborate on the work by reviewing it rather than duplicating it in a competing PR.\n\nIf something is unclear, please reach out on any of our [communication\nchannels](https://matplotlib.org/stable/devel/contributing.html#get-connected).\nI did some preliminary research and it seems TeX actually does not distinguish between the unary and binary minus symbol. Maybe I was reading an outdated version of the TeXbook, but I cannot find any mention of unary operators. I will keep looking to find out what logic TeX is using to change the spacing for unary operators.\nAnother example where I think the difference is clear:\r\n![Figure_1](https://github.com/matplotlib/matplotlib/assets/1322974/4fafabc6-0475-4cbd-808a-584b2a8c6a69)\r\n\nI have found the issue, the code contains logic to do different spacing for unary minus if the unary minus happens directly after a parenthesis or if the minus is the first character in the equation, but the code does not contain logic to create a unary minus after an equals sign. It is an easy fix, but I am still working on setting up a testing environment to verify my fix.", "created_at": "2024-05-08T20:33:52Z" }, { "repo": "matplotlib/matplotlib", "pull_number": 28177, "instance_id": "matplotlib__matplotlib-28177", "issue_numbers": [ "28170", "0000" ], "base_commit": "0fd212d11cf87a200eb2cfd5b0357d05fc7b3f32", "patch": "diff --git a/doc/api/next_api_changes/behavior/28177-REC.rst b/doc/api/next_api_changes/behavior/28177-REC.rst\nnew file mode 100644\nindex 000000000000..d7ea8ec0e947\n--- /dev/null\n+++ b/doc/api/next_api_changes/behavior/28177-REC.rst\n@@ -0,0 +1,7 @@\n+(Sub)Figure.get_figure\n+~~~~~~~~~~~~~~~~~~~~~~\n+\n+...in future will by default return the direct parent figure, which may be a SubFigure.\n+This will make the default behavior consistent with the\n+`~matplotlib.artist.Artist.get_figure` method of other artists. To control the\n+behavior, use the newly introduced *root* parameter.\ndiff --git a/doc/api/next_api_changes/deprecations/28177-REC.rst b/doc/api/next_api_changes/deprecations/28177-REC.rst\nnew file mode 100644\nindex 000000000000..a3e630630aeb\n--- /dev/null\n+++ b/doc/api/next_api_changes/deprecations/28177-REC.rst\n@@ -0,0 +1,5 @@\n+(Sub)Figure.set_figure\n+~~~~~~~~~~~~~~~~~~~~~~\n+\n+...is deprecated and in future will always raise an exception. The parent and\n+root figures of a (Sub)Figure are set at instantiation and cannot be changed.\ndiff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py\nindex d5b8631e95df..baf3b01ee6e5 100644\n--- a/lib/matplotlib/artist.py\n+++ b/lib/matplotlib/artist.py\n@@ -181,7 +181,7 @@ def __init__(self):\n self._stale = True\n self.stale_callback = None\n self._axes = None\n- self.figure = None\n+ self._parent_figure = None\n \n self._transform = None\n self._transformSet = False\n@@ -251,7 +251,7 @@ def remove(self):\n if self.figure:\n if not _ax_flag:\n self.figure.stale = True\n- self.figure = None\n+ self._parent_figure = None\n \n else:\n raise NotImplementedError('cannot remove artist')\n@@ -720,34 +720,49 @@ def set_path_effects(self, path_effects):\n def get_path_effects(self):\n return self._path_effects\n \n- def get_figure(self):\n- \"\"\"Return the `.Figure` instance the artist belongs to.\"\"\"\n- return self.figure\n+ def get_figure(self, root=False):\n+ \"\"\"\n+ Return the `.Figure` or `.SubFigure` instance the artist belongs to.\n+\n+ Parameters\n+ ----------\n+ root : bool, default=False\n+ If False, return the (Sub)Figure this artist is on. If True,\n+ return the root Figure for a nested tree of SubFigures.\n+ \"\"\"\n+ if root and self._parent_figure is not None:\n+ return self._parent_figure.get_figure(root=True)\n+\n+ return self._parent_figure\n \n def set_figure(self, fig):\n \"\"\"\n- Set the `.Figure` instance the artist belongs to.\n+ Set the `.Figure` or `.SubFigure` instance the artist belongs to.\n \n Parameters\n ----------\n- fig : `~matplotlib.figure.Figure`\n+ fig : `~matplotlib.figure.Figure` or `~matplotlib.figure.SubFigure`\n \"\"\"\n # if this is a no-op just return\n- if self.figure is fig:\n+ if self._parent_figure is fig:\n return\n # if we currently have a figure (the case of both `self.figure`\n # and *fig* being none is taken care of above) we then user is\n # trying to change the figure an artist is associated with which\n # is not allowed for the same reason as adding the same instance\n # to more than one Axes\n- if self.figure is not None:\n+ if self._parent_figure is not None:\n raise RuntimeError(\"Can not put single artist in \"\n \"more than one figure\")\n- self.figure = fig\n- if self.figure and self.figure is not self:\n+ self._parent_figure = fig\n+ if self._parent_figure and self._parent_figure is not self:\n self.pchanged()\n self.stale = True\n \n+ figure = property(get_figure, set_figure,\n+ doc=(\"The (Sub)Figure that the artist is on. For more \"\n+ \"control, use the `get_figure` method.\"))\n+\n def set_clip_box(self, clipbox):\n \"\"\"\n Set the artist's clip `.Bbox`.\ndiff --git a/lib/matplotlib/artist.pyi b/lib/matplotlib/artist.pyi\nindex 50f41b7f70e5..3059600e488c 100644\n--- a/lib/matplotlib/artist.pyi\n+++ b/lib/matplotlib/artist.pyi\n@@ -31,7 +31,8 @@ class _Unset: ...\n class Artist:\n zorder: float\n stale_callback: Callable[[Artist, bool], None] | None\n- figure: Figure | SubFigure | None\n+ @property\n+ def figure(self) -> Figure | SubFigure: ...\n clipbox: BboxBase | None\n def __init__(self) -> None: ...\n def remove(self) -> None: ...\n@@ -87,8 +88,8 @@ class Artist:\n ) -> None: ...\n def set_path_effects(self, path_effects: list[AbstractPathEffect]) -> None: ...\n def get_path_effects(self) -> list[AbstractPathEffect]: ...\n- def get_figure(self) -> Figure | None: ...\n- def set_figure(self, fig: Figure) -> None: ...\n+ def get_figure(self, root: bool = ...) -> Figure | SubFigure | None: ...\n+ def set_figure(self, fig: Figure | SubFigure) -> None: ...\n def set_clip_box(self, clipbox: BboxBase | None) -> None: ...\n def set_clip_path(\n self,\ndiff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py\nindex 18ff80a51e5a..4606e5c01aec 100644\n--- a/lib/matplotlib/axes/_base.py\n+++ b/lib/matplotlib/axes/_base.py\n@@ -1296,7 +1296,7 @@ def __clear(self):\n self._gridOn = mpl.rcParams['axes.grid']\n old_children, self._children = self._children, []\n for chld in old_children:\n- chld.axes = chld.figure = None\n+ chld.axes = chld._parent_figure = None\n self._mouseover_set = _OrderedSet()\n self.child_axes = []\n self._current_image = None # strictly for pyplot via _sci, _gci\ndiff --git a/lib/matplotlib/axes/_base.pyi b/lib/matplotlib/axes/_base.pyi\nindex 751dcd248a5c..1fdc0750f0bc 100644\n--- a/lib/matplotlib/axes/_base.pyi\n+++ b/lib/matplotlib/axes/_base.pyi\n@@ -13,7 +13,7 @@ from matplotlib.cm import ScalarMappable\n from matplotlib.legend import Legend\n from matplotlib.lines import Line2D\n from matplotlib.gridspec import SubplotSpec, GridSpec\n-from matplotlib.figure import Figure\n+from matplotlib.figure import Figure, SubFigure\n from matplotlib.image import AxesImage\n from matplotlib.patches import Patch\n from matplotlib.scale import ScaleBase\n@@ -81,7 +81,7 @@ class _AxesBase(martist.Artist):\n def get_subplotspec(self) -> SubplotSpec | None: ...\n def set_subplotspec(self, subplotspec: SubplotSpec) -> None: ...\n def get_gridspec(self) -> GridSpec | None: ...\n- def set_figure(self, fig: Figure) -> None: ...\n+ def set_figure(self, fig: Figure | SubFigure) -> None: ...\n @property\n def viewLim(self) -> Bbox: ...\n def get_xaxis_transform(\ndiff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py\nindex 1d522f8defa2..51bac3455a28 100644\n--- a/lib/matplotlib/figure.py\n+++ b/lib/matplotlib/figure.py\n@@ -30,6 +30,7 @@\n from contextlib import ExitStack\n import inspect\n import itertools\n+import functools\n import logging\n from numbers import Integral\n import threading\n@@ -227,6 +228,67 @@ def get_children(self):\n *self.legends,\n *self.subfigs]\n \n+ def get_figure(self, root=None):\n+ \"\"\"\n+ Return the `.Figure` or `.SubFigure` instance the (Sub)Figure belongs to.\n+\n+ Parameters\n+ ----------\n+ root : bool, default=True\n+ If False, return the (Sub)Figure this artist is on. If True,\n+ return the root Figure for a nested tree of SubFigures.\n+\n+ .. deprecated:: 3.10\n+\n+ From version 3.12 *root* will default to False.\n+ \"\"\"\n+ if self._root_figure is self:\n+ # Top level Figure\n+ return self\n+\n+ if self._parent is self._root_figure:\n+ # Return early to prevent the deprecation warning when *root* does not\n+ # matter\n+ return self._parent\n+\n+ if root is None:\n+ # When deprecation expires, consider removing the docstring and just\n+ # inheriting the one from Artist.\n+ message = ('From Matplotlib 3.12 SubFigure.get_figure will by default '\n+ 'return the direct parent figure, which may be a SubFigure. '\n+ 'To suppress this warning, pass the root parameter. Pass '\n+ '`True` to maintain the old behavior and `False` to opt-in to '\n+ 'the future behavior.')\n+ _api.warn_deprecated('3.10', message=message)\n+ root = True\n+\n+ if root:\n+ return self._root_figure\n+\n+ return self._parent\n+\n+ def set_figure(self, fig):\n+ \"\"\"\n+ .. deprecated:: 3.10\n+ Currently this method will raise an exception if *fig* is anything other\n+ than the root `.Figure` this (Sub)Figure is on. In future it will always\n+ raise an exception.\n+ \"\"\"\n+ no_switch = (\"The parent and root figures of a (Sub)Figure are set at \"\n+ \"instantiation and cannot be changed.\")\n+ if fig is self._root_figure:\n+ _api.warn_deprecated(\n+ \"3.10\",\n+ message=(f\"{no_switch} From Matplotlib 3.12 this operation will raise \"\n+ \"an exception.\"))\n+ return\n+\n+ raise ValueError(no_switch)\n+\n+ figure = property(functools.partial(get_figure, root=True), set_figure,\n+ doc=(\"The root `Figure`. To get the parent of a `SubFigure`, \"\n+ \"use the `get_figure` method.\"))\n+\n def contains(self, mouseevent):\n \"\"\"\n Test whether the mouse event occurred on the figure.\n@@ -2222,7 +2284,7 @@ def __init__(self, parent, subplotspec, *,\n \n self._subplotspec = subplotspec\n self._parent = parent\n- self.figure = parent.figure\n+ self._root_figure = parent._root_figure\n \n # subfigures use the parent axstack\n self._axstack = parent._axstack\n@@ -2503,7 +2565,7 @@ def __init__(self,\n %(Figure:kwdoc)s\n \"\"\"\n super().__init__(**kwargs)\n- self.figure = self\n+ self._root_figure = self\n self._layout_engine = None\n \n if layout is not None:\ndiff --git a/lib/matplotlib/figure.pyi b/lib/matplotlib/figure.pyi\nindex b079312695c1..711f5b77783e 100644\n--- a/lib/matplotlib/figure.pyi\n+++ b/lib/matplotlib/figure.pyi\n@@ -260,7 +260,8 @@ class FigureBase(Artist):\n ) -> dict[Hashable, Axes]: ...\n \n class SubFigure(FigureBase):\n- figure: Figure\n+ @property\n+ def figure(self) -> Figure: ...\n subplotpars: SubplotParams\n dpi_scale_trans: Affine2D\n transFigure: Transform\n@@ -298,7 +299,8 @@ class SubFigure(FigureBase):\n def get_axes(self) -> list[Axes]: ...\n \n class Figure(FigureBase):\n- figure: Figure\n+ @property\n+ def figure(self) -> Figure: ...\n bbox_inches: Bbox\n dpi_scale_trans: Affine2D\n bbox: BboxBase\ndiff --git a/lib/matplotlib/offsetbox.pyi b/lib/matplotlib/offsetbox.pyi\nindex c222a9b2973e..05e23df4529d 100644\n--- a/lib/matplotlib/offsetbox.pyi\n+++ b/lib/matplotlib/offsetbox.pyi\n@@ -2,7 +2,7 @@ import matplotlib.artist as martist\n from matplotlib.backend_bases import RendererBase, Event, FigureCanvasBase\n from matplotlib.colors import Colormap, Normalize\n import matplotlib.text as mtext\n-from matplotlib.figure import Figure\n+from matplotlib.figure import Figure, SubFigure\n from matplotlib.font_manager import FontProperties\n from matplotlib.image import BboxImage\n from matplotlib.patches import FancyArrowPatch, FancyBboxPatch\n@@ -26,7 +26,7 @@ class OffsetBox(martist.Artist):\n width: float | None\n height: float | None\n def __init__(self, *args, **kwargs) -> None: ...\n- def set_figure(self, fig: Figure) -> None: ...\n+ def set_figure(self, fig: Figure | SubFigure) -> None: ...\n def set_offset(\n self,\n xy: tuple[float, float]\n@@ -271,7 +271,7 @@ class AnnotationBbox(martist.Artist, mtext._AnnotationBase):\n | Callable[[RendererBase], Bbox | Transform],\n ) -> None: ...\n def get_children(self) -> list[martist.Artist]: ...\n- def set_figure(self, fig: Figure) -> None: ...\n+ def set_figure(self, fig: Figure | SubFigure) -> None: ...\n def set_fontsize(self, s: str | float | None = ...) -> None: ...\n def get_fontsize(self) -> float: ...\n def get_tightbbox(self, renderer: RendererBase | None = ...) -> Bbox: ...\ndiff --git a/lib/matplotlib/quiver.pyi b/lib/matplotlib/quiver.pyi\nindex 2a043a92b4b5..164f0ab3a77a 100644\n--- a/lib/matplotlib/quiver.pyi\n+++ b/lib/matplotlib/quiver.pyi\n@@ -1,7 +1,7 @@\n import matplotlib.artist as martist\n import matplotlib.collections as mcollections\n from matplotlib.axes import Axes\n-from matplotlib.figure import Figure\n+from matplotlib.figure import Figure, SubFigure\n from matplotlib.text import Text\n from matplotlib.transforms import Transform, Bbox\n \n@@ -49,7 +49,7 @@ class QuiverKey(martist.Artist):\n ) -> None: ...\n @property\n def labelsep(self) -> float: ...\n- def set_figure(self, fig: Figure) -> None: ...\n+ def set_figure(self, fig: Figure | SubFigure) -> None: ...\n \n class Quiver(mcollections.PolyCollection):\n X: ArrayLike\n", "test_patch": "diff --git a/ci/mypy-stubtest-allowlist.txt b/ci/mypy-stubtest-allowlist.txt\nindex 73dfb1d8ceb0..d6a0f373048d 100644\n--- a/ci/mypy-stubtest-allowlist.txt\n+++ b/ci/mypy-stubtest-allowlist.txt\n@@ -46,3 +46,6 @@ matplotlib.tri.*TriInterpolator.gradient\n matplotlib.backend_bases.FigureCanvasBase._T\n matplotlib.backend_managers.ToolManager._T\n matplotlib.spines.Spine._T\n+\n+# Parameter inconsistency due to 3.10 deprecation\n+matplotlib.figure.FigureBase.get_figure\ndiff --git a/lib/matplotlib/tests/test_artist.py b/lib/matplotlib/tests/test_artist.py\nindex dbb5dd2305e0..edba2c179781 100644\n--- a/lib/matplotlib/tests/test_artist.py\n+++ b/lib/matplotlib/tests/test_artist.py\n@@ -562,3 +562,37 @@ def draw(self, renderer, extra):\n \n assert 'aardvark' == art.draw(renderer, 'aardvark')\n assert 'aardvark' == art.draw(renderer, extra='aardvark')\n+\n+\n+def test_get_figure():\n+ fig = plt.figure()\n+ sfig1 = fig.subfigures()\n+ sfig2 = sfig1.subfigures()\n+ ax = sfig2.subplots()\n+\n+ assert fig.get_figure(root=True) is fig\n+ assert fig.get_figure(root=False) is fig\n+\n+ assert ax.get_figure() is sfig2\n+ assert ax.get_figure(root=False) is sfig2\n+ assert ax.get_figure(root=True) is fig\n+\n+ # SubFigure.get_figure has separate implementation but should give consistent\n+ # results to other artists.\n+ assert sfig2.get_figure(root=False) is sfig1\n+ assert sfig2.get_figure(root=True) is fig\n+ # Currently different results by default.\n+ with pytest.warns(mpl.MatplotlibDeprecationWarning):\n+ assert sfig2.get_figure() is fig\n+ # No deprecation warning if root and parent figure are the same.\n+ assert sfig1.get_figure() is fig\n+\n+ # An artist not yet attached to anything has no figure.\n+ ln = mlines.Line2D([], [])\n+ assert ln.get_figure(root=True) is None\n+ assert ln.get_figure(root=False) is None\n+\n+ # figure attribute is root for (Sub)Figures but parent for other artists.\n+ assert ax.figure is sfig2\n+ assert fig.figure is fig\n+ assert sfig2.figure is fig\ndiff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py\nindex 5a8894b10496..4e73d4091200 100644\n--- a/lib/matplotlib/tests/test_figure.py\n+++ b/lib/matplotlib/tests/test_figure.py\n@@ -1735,6 +1735,22 @@ def test_warn_colorbar_mismatch():\n subfig3_1.colorbar(im4_1)\n \n \n+def test_set_figure():\n+ fig = plt.figure()\n+ sfig1 = fig.subfigures()\n+ sfig2 = sfig1.subfigures()\n+\n+ for f in fig, sfig1, sfig2:\n+ with pytest.warns(mpl.MatplotlibDeprecationWarning):\n+ f.set_figure(fig)\n+\n+ with pytest.raises(ValueError, match=\"cannot be changed\"):\n+ sfig2.set_figure(sfig1)\n+\n+ with pytest.raises(ValueError, match=\"cannot be changed\"):\n+ sfig1.set_figure(plt.figure())\n+\n+\n def test_subfigure_row_order():\n # Test that subfigures are drawn in row-major order.\n fig = plt.figure()\n", "problem_statement": "[Doc]: `get_figure` may return a `SubFigure`\n### Documentation Link\n\nhttps://matplotlib.org/devdocs/api/_as_gen/matplotlib.artist.Artist.get_figure.html#matplotlib.artist.Artist.get_figure\n\n### Problem\n\nI'm not sure whether this is a documentation issue or a bug. The docstring for `get_figure` states that it returns a `Figure`, which I think implies the parent figure. If our artist is on a subfigure, it will return that. Note that `SubFigure` does not inherit from `Figure`.\r\n\r\n```python\r\nIn [1]: import matplotlib.pyplot as plt\r\n\r\nIn [2]: fig = plt.figure()\r\n\r\nIn [3]: sfig = fig.subfigures()\r\n\r\nIn [4]: ax = sfig.subplots()\r\n\r\nIn [5]: ax.get_figure()\r\nOut[5]: \r\n```\n\n### Suggested improvement\n\nEither\r\n* modify the docstring to clarify you might get the `SubFigure` (and point to how you can get the parent `Figure` if you want it)\r\nor\r\n* fix the method so it returns the parent `Figure`\n", "hints_text": "`SubFigure.get_figure` does return a `Figure`, even if the subfigures are nested.\r\n\r\n```python\r\nIn [6]: sfig2 = sfig.subfigures()\r\n\r\nIn [7]: sfig2.get_figure()\r\nOut[7]:
\r\n```\nThis seems inconsistent and likely we haven't thought about it when subfigures were introduced.\r\n\r\nIntuitively, I'd recommend the following behavior: `get_figure()` returns the closest (sub)figure. If we were to go all the way up, it's not possible to get to the next level only.\r\n\r\nThat would imply:\r\n- Change documentation/typing of `Artist.get_figure`\r\n- Change behavior of `Subfigure.get_figure` to return the containing (sub)figure. - Note that `get_figure` only returns `self.figure`, so `self.figure` should be changed for nested subfigures. I consider it a bug that this is not the containing subfigure. @jklymak do you agree?\r\n\r\n\nHmm that seems inconsistent at least so one way or another is a bug\n\nIt seems that a subfigure wants to know its parent rather than its root? \n\nOoops sorry crosspost w above. I'd have to look at the code a bit. \n> This seems inconsistent and likely we haven't thought about it when subfigures were introduced.\r\n> \r\n> Intuitively, I'd recommend the following behavior: `get_figure()` returns the closest (sub)figure. If we were to go all the way up, it's not possible to get to the next level only.\r\n> \r\n> That would imply:\r\n> \r\n> * Change documentation/typing of `Artist.get_figure`\r\n> * Change behavior of `Subfigure.get_figure` to return the containing (sub)figure. - Note that `get_figure` only returns `self.figure`, so `self.figure` should be changed for nested subfigures. I consider it a bug that this is not the containing subfigure. @jklymak do you agree?\r\n\r\nHmmm, I fear it's not so simple. I explicitly set `self.figure = parent.figure` which therefore resolves to the root figure, and I think that is because artists have things like `self.figure.stale` and `self.figure.suppressComposite` which would somehow have to magically thread themselves through.\r\n\r\nSo I wonder if the correct course of action is to override `get_figure` to return the parent figure, but keep `self.figure` as-is. In hindsight, it should have been private, I think?\n> So I wonder if the correct course of action is to override `get_figure` to return the parent figure, but keep `self.figure` as-is. In hindsight, it should have been private, I think?\r\n\r\nIt's bad enough that we have a mixture of attributes and getters, having both with slightly different semantics is even worse. I propse to\r\n\r\n- expand `get_figure()` with a parmeter to select between the direct parent and the top-level figure, choosing the appropriate default. - The default should become the same for artists and subfigures, which means an API change for one of them.\r\n- create whatever internal state is necessary to support the behavior (likely `self._parent_figure`)\r\n- keep `self.figure` with the current semantics, but discourate (deprecate?) it's use in favor of `get_figure()` - if needed turn it into a property.\nThe above seems good to me. \r\nSomething like:\r\n```\r\nget_figure(root=False)\r\n\r\nroot : bool\r\n If False return the (Sub)Figure this artist is on. If True, return the root Figure if there is a nested tree of SubFigures.\r\n```\nAh, this also make sense as to why the `subfigure_mosaic` has problems with getting the nesting right.\r\n\r\nStrongly agree we need a version that gets the parent rather than the root.", "created_at": "2024-05-06T16:36:28Z" }, { "repo": "scikit-learn/scikit-learn", "pull_number": 29612, "instance_id": "scikit-learn__scikit-learn-29612", "issue_numbers": [ "26802" ], "base_commit": "9a6b7d6c9e430afb0a29fbaa3dcbe22788f764a5", "patch": "diff --git a/doc/whats_new/v1.6.rst b/doc/whats_new/v1.6.rst\nindex a4ed45e294062..968da1a6d47e8 100644\n--- a/doc/whats_new/v1.6.rst\n+++ b/doc/whats_new/v1.6.rst\n@@ -165,6 +165,14 @@ Changelog\n and automatic retries in case of HTTP errors. :pr:`29354` by :user:`Olivier\n Grisel `.\n \n+:mod:`sklearn.decomposition`\n+............................\n+\n+- |Fix| Increase rank defficiency threshold in the whitening step of\n+ :class:`decomposition.FastICA` with `whiten_solver=\"eigh\"` to improve the\n+ platform-agnosticity of the estimator. :pr:`29612` by :user:`Olivier Grisel\n+ `.\n+\n :mod:`sklearn.discriminant_analysis`\n ....................................\n \ndiff --git a/sklearn/decomposition/_fastica.py b/sklearn/decomposition/_fastica.py\nindex c923f66416a20..b3725ef079b2b 100644\n--- a/sklearn/decomposition/_fastica.py\n+++ b/sklearn/decomposition/_fastica.py\n@@ -605,7 +605,7 @@ def g(x, fun_args):\n # Faster when num_samples >> n_features\n d, u = linalg.eigh(XT.dot(X))\n sort_indices = np.argsort(d)[::-1]\n- eps = np.finfo(d.dtype).eps\n+ eps = np.finfo(d.dtype).eps * 10\n degenerate_idx = d < eps\n if np.any(degenerate_idx):\n warnings.warn(\n", "test_patch": "diff --git a/sklearn/decomposition/tests/test_fastica.py b/sklearn/decomposition/tests/test_fastica.py\nindex bd7a35bb8a96f..0066d9faf17f2 100644\n--- a/sklearn/decomposition/tests/test_fastica.py\n+++ b/sklearn/decomposition/tests/test_fastica.py\n@@ -13,7 +13,7 @@\n from sklearn.decomposition import PCA, FastICA, fastica\n from sklearn.decomposition._fastica import _gs_decorrelation\n from sklearn.exceptions import ConvergenceWarning\n-from sklearn.utils._testing import assert_allclose\n+from sklearn.utils._testing import assert_allclose, ignore_warnings\n \n \n def center_and_norm(x, axis=-1):\n@@ -448,5 +448,10 @@ def test_fastica_eigh_low_rank_warning(global_random_seed):\n X = A @ A.T\n ica = FastICA(random_state=0, whiten=\"unit-variance\", whiten_solver=\"eigh\")\n msg = \"There are some small singular values\"\n+\n with pytest.warns(UserWarning, match=msg):\n- ica.fit(X)\n+ with ignore_warnings(category=ConvergenceWarning):\n+ # The FastICA solver may not converge for some data with specific\n+ # random seeds but this happens after the whiten step so this is\n+ # not want we want to test here.\n+ ica.fit(X)\n", "problem_statement": "\u26a0\ufe0f CI failed on Linux_Runs.pylatest_conda_forge_mkl (last failure: Jul 10, 2024) \u26a0\ufe0f\n**CI is still failing on [Linux_Runs.pylatest_conda_forge_mkl](https://dev.azure.com/scikit-learn/scikit-learn/_build/results?buildId=68533&view=logs&j=dde5042c-7464-5d47-9507-31bdd2ee0a3a)** (Jul 10, 2024)\n- test_fastica_eigh_low_rank_warning[31]\n", "hints_text": "## CI is no longer failing! \u2705\n\n[Successful run](https://dev.azure.com/scikit-learn/scikit-learn/_build/results?buildId=69196&view=logs&j=dde5042c-7464-5d47-9507-31bdd2ee0a3a) on Aug 02, 2024\nFor future reference, it seems like for some global random seed we get a `ConvergenceWarning` rather than a `UserWarning` about singular values:\r\n\r\n```\r\n____________________ test_fastica_eigh_low_rank_warning[31] ____________________\r\n[gw1] linux -- Python 3.11.4 /usr/share/miniconda/envs/testvenv/bin/python\r\n\r\nglobal_random_seed = 31\r\n\r\n def test_fastica_eigh_low_rank_warning(global_random_seed):\r\n \"\"\"Test FastICA eigh solver raises warning for low-rank data.\"\"\"\r\n rng = np.random.RandomState(global_random_seed)\r\n A = rng.randn(10, 2)\r\n X = A @ A.T\r\n ica = FastICA(random_state=0, whiten=\"unit-variance\", whiten_solver=\"eigh\")\r\n msg = \"There are some small singular values\"\r\n> with pytest.warns(UserWarning, match=msg):\r\nE Failed: DID NOT WARN. No warnings of type (,) matching the regex were emitted.\r\nE Regex: There are some small singular values\r\nE Emitted warnings: [ ConvergenceWarning('FastICA did not converge. Consider increasing tolerance or the maximum number of iterations.')]\r\n\r\nA = array([[-0.41475721, -0.33336867],\r\n [ 0.08109199, -0.79102695],\r\n [-0.21859967, -0.76319684],\r\n [-0.77...-0.91879129],\r\n [ 1.19943449, -0.34137424],\r\n [-1.75860731, 0.05111747],\r\n [-0.57192899, -0.70056604]])\r\nX = array([[ 2.83158216e-01, 2.30070116e-01, 3.45091706e-01,\r\n -2.94201294e-01, 3.21336537e-01, -7.56222642e-02,\r\n... 4.63831401e-01, -7.26324674e-02,\r\n 1.20573385e+00, -4.46836158e-01, 9.69987343e-01,\r\n 8.17895550e-01]])\r\nglobal_random_seed = 31\r\nica = FastICA(random_state=0, whiten_solver='eigh')\r\nmsg = 'There are some small singular values'\r\nrng = RandomState(MT19937) at 0x7F71B6CA6940\r\n\r\n../1/s/sklearn/decomposition/tests/test_fastica.py:450: Failed\r\n```\nThe last error is about `test_minibatch_sensible_reassign[34]`: Edit 3 weeks after creating the comment, the `test_minibatch_sensible_reassign` should now have been fixed by https://github.com/scikit-learn/scikit-learn/pull/29278 :crossed_fingers: \r\n\r\n```\r\n_____________________ test_minibatch_sensible_reassign[34] _____________________\r\n[gw0] linux -- Python 3.11.9 /usr/share/miniconda/envs/testvenv/bin/python\r\n\r\nglobal_random_seed = 34\r\n def test_minibatch_sensible_reassign(global_random_seed):\r\n # check that identical initial clusters are reassigned\r\n # also a regression test for when there are more desired reassignments than\r\n # samples.\r\n zeroed_X, true_labels = make_blobs(\r\n n_samples=100, centers=5, random_state=global_random_seed\r\n )\r\n zeroed_X[::2, :] = 0\r\n \r\n km = MiniBatchKMeans(\r\n n_clusters=20, batch_size=10, random_state=global_random_seed, init=\"random\"\r\n ).fit(zeroed_X)\r\n # there should not be too many exact zero cluster centers\r\n> assert km.cluster_centers_.any(axis=1).sum() > 10\r\nE AssertionError\r\n\r\nglobal_random_seed = 34\r\nkm = MiniBatchKMeans(batch_size=10, init='random', n_clusters=20, random_state=34)\r\ntrue_labels = array([3, 0, 2, 4, 1, 0, 2, 3, 0, 1, 4, 2, 2, 0, 2, 4, 3, 4, 2, 3, 3, 4,\r\n 2, 2, 1, 4, 3, 4, 3, 1, 1, 1, 4, 1, 4,...3,\r\n 4, 1, 0, 3, 3, 4, 3, 2, 2, 2, 0, 2, 2, 4, 4, 0, 4, 2, 3, 0, 2, 1,\r\n 3, 0, 1, 2, 0, 3, 1, 0, 0, 4, 2, 4])\r\nzeroed_X = array([[ 0. , 0. ],\r\n [ -9.93451852, 5.17769196],\r\n [ 0. , 0. ],\r\n ... ],\r\n [ -5.62221677, 0.01380172],\r\n [ 0. , 0. ],\r\n [ -6.95512025, -1.26162786]])\r\n\r\n../1/s/sklearn/cluster/tests/test_k_means.py:440: AssertionError\r\n```\nThe last one seems to be Figshare HTTP 403 issue #28297. Test collection error because early on we download a few datasets in conftest.py and we get an error.\r\n\r\n```\r\nINTERNALERROR> def worker_internal_error(self, node, formatted_error):\r\nINTERNALERROR> \"\"\"\r\nINTERNALERROR> pytest_internalerror() was called on the worker.\r\nINTERNALERROR> \r\nINTERNALERROR> E File \"/usr/share/miniconda/envs/testvenv/lib/python3.11/urllib/request.py\", line 496, in _call_chain\r\nINTERNALERROR> E result = func(*args)\r\nINTERNALERROR> E ^^^^^^^^^^^\r\nINTERNALERROR> E File \"/usr/share/miniconda/envs/testvenv/lib/python3.11/urllib/request.py\", line 643, in http_error_default\r\nINTERNALERROR> E raise HTTPError(req.full_url, code, msg, hdrs, fp)\r\nINTERNALERROR> E urllib.error.HTTPError: HTTP Error 403: Forbidden\r\nINTERNALERROR> E assert False\r\nINTERNALERROR> \r\nINTERNALERROR> /usr/share/miniconda/envs/testvenv/lib/python3.11/site-packages/xdist/dsession.py:200: AssertionError\r\nINTERNALERROR> Traceback (most recent call last):\r\nINTERNALERROR> File \"/usr/share/miniconda/envs/testvenv/lib/python3.11/site-packages/_pytest/main.py\", line 285, in wrap_session\r\nINTERNALERROR> session.exitstatus = doit(config, session) or 0\r\nINTERNALERROR> ^^^^^^^^^^^^^^^^^^^^^\r\nINTERNALERROR> File \"/usr/share/miniconda/envs/testvenv/lib/python3.11/site-packages/_pytest/main.py\", line 339, in _main\r\nINTERNALERROR> config.hook.pytest_runtestloop(session=session)\r\nINTERNALERROR> File \"/usr/share/miniconda/envs/testvenv/lib/python3.11/site-packages/pluggy/_hooks.py\", line 513, in __call__\r\nINTERNALERROR> return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)\r\nINTERNALERROR> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\nINTERNALERROR> File \"/usr/share/miniconda/envs/testvenv/lib/python3.11/site-packages/pluggy/_manager.py\", line 120, in _hookexec\r\nINTERNALERROR> return self._inner_hookexec(hook_name, methods, kwargs, firstresult)\r\nINTERNALERROR> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\nINTERNALERROR> File \"/usr/share/miniconda/envs/testvenv/lib/python3.11/site-packages/pluggy/_callers.py\", line 182, in _multicall\r\nINTERNALERROR> return outcome.get_result()\r\nINTERNALERROR> ^^^^^^^^^^^^^^^^^^^^\r\nINTERNALERROR> File \"/usr/share/miniconda/envs/testvenv/lib/python3.11/site-packages/pluggy/_result.py\", line 100, in get_result\r\nINTERNALERROR> raise exc.with_traceback(exc.__traceback__)\r\nINTERNALERROR> File \"/usr/share/miniconda/envs/testvenv/lib/python3.11/site-packages/pluggy/_callers.py\", line 167, in _multicall\r\nINTERNALERROR> teardown.throw(outcome._exception)\r\nINTERNALERROR> File \"/usr/share/miniconda/envs/testvenv/lib/python3.11/site-packages/_pytest/logging.py\", line 807, in pytest_runtestloop\r\nINTERNALERROR> return (yield) # Run all the tests.\r\nINTERNALERROR> ^^^^^\r\nINTERNALERROR> File \"/usr/share/miniconda/envs/testvenv/lib/python3.11/site-packages/pluggy/_callers.py\", line 103, in _multicall\r\nINTERNALERROR> res = hook_impl.function(*args)\r\nINTERNALERROR> ^^^^^^^^^^^^^^^^^^^^^^^^^\r\nINTERNALERROR> File \"/usr/share/miniconda/envs/testvenv/lib/python3.11/site-packages/xdist/dsession.py\", line 123, in pytest_runtestloop\r\nINTERNALERROR> self.loop_once()\r\nINTERNALERROR> File \"/usr/share/miniconda/envs/testvenv/lib/python3.11/site-packages/xdist/dsession.py\", line 148, in loop_once\r\nINTERNALERROR> call(**kwargs)\r\nINTERNALERROR> File \"/usr/share/miniconda/envs/testvenv/lib/python3.11/site-packages/xdist/dsession.py\", line 188, in worker_workerfinished\r\nINTERNALERROR> self._active_nodes.remove(node)\r\nINTERNALERROR> KeyError: \r\n``` \nFor reference, the `test_fastica_eigh_low_rank_warning[31]` failure happened again last night with the same symptoms as described above (`ConvergenceWarning` instead of `UserWarning`).", "created_at": "2024-08-02T08:36:39Z" }, { "repo": "scikit-learn/scikit-learn", "pull_number": 29598, "instance_id": "scikit-learn__scikit-learn-29598", "issue_numbers": [ "29583" ], "base_commit": "234260dfb7fc615c2adfa90f8c6405200971efc2", "patch": "diff --git a/build_tools/azure/install.sh b/build_tools/azure/install.sh\nindex 73e732e35a05f..398de49ce91c8 100755\n--- a/build_tools/azure/install.sh\n+++ b/build_tools/azure/install.sh\n@@ -85,14 +85,14 @@ python_environment_install_and_activate() {\n # install them from scientific-python-nightly-wheels\n dev_anaconda_url=https://pypi.anaconda.org/scientific-python-nightly-wheels/simple\n dev_packages=\"numpy scipy Cython\"\n- pip install --pre --upgrade --timeout=60 --extra-index $dev_anaconda_url $dev_packages\n+ pip install --pre --upgrade --timeout=60 --extra-index $dev_anaconda_url $dev_packages --only-binary :all:\n fi\n \n if [[ \"$DISTRIB\" == \"conda-pip-scipy-dev\" ]]; then\n echo \"Installing development dependency wheels\"\n dev_anaconda_url=https://pypi.anaconda.org/scientific-python-nightly-wheels/simple\n dev_packages=\"numpy scipy pandas Cython\"\n- pip install --pre --upgrade --timeout=60 --extra-index $dev_anaconda_url $dev_packages\n+ pip install --pre --upgrade --timeout=60 --extra-index $dev_anaconda_url $dev_packages --only-binary :all:\n \n check_packages_dev_version $dev_packages\n \ndiff --git a/build_tools/wheels/build_wheels.sh b/build_tools/wheels/build_wheels.sh\nindex 085ad887c1aec..f2ed8495ec11f 100755\n--- a/build_tools/wheels/build_wheels.sh\n+++ b/build_tools/wheels/build_wheels.sh\n@@ -53,7 +53,7 @@ if [[ \"$CIBW_FREE_THREADED_SUPPORT\" =~ [tT]rue ]]; then\n # Numpy, scipy, Cython only have free-threaded wheels on scientific-python-nightly-wheels\n # TODO: remove this after CPython 3.13 is released (scheduled October 2024)\n # and our dependencies have free-threaded wheels on PyPI\n- export CIBW_BUILD_FRONTEND='pip; args: --pre --extra-index-url \"https://pypi.anaconda.org/scientific-python-nightly-wheels/simple\"'\n+ export CIBW_BUILD_FRONTEND='pip; args: --pre --extra-index-url \"https://pypi.anaconda.org/scientific-python-nightly-wheels/simple\" --only-binary :all:'\n fi\n \n # The version of the built dependencies are specified\n", "test_patch": "diff --git a/build_tools/wheels/cibw_before_test.sh b/build_tools/wheels/cibw_before_test.sh\nindex c2bfc82fbb6a8..b888eac0c942f 100755\n--- a/build_tools/wheels/cibw_before_test.sh\n+++ b/build_tools/wheels/cibw_before_test.sh\n@@ -6,5 +6,5 @@ set -x\n FREE_THREADED_BUILD=\"$(python -c\"import sysconfig; print(bool(sysconfig.get_config_var('Py_GIL_DISABLED')))\")\"\n if [[ $FREE_THREADED_BUILD == \"True\" ]]; then\n # TODO: remove when numpy, scipy and pandas have releases with free-threaded wheels\n- python -m pip install --pre --extra-index https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy scipy pandas\n+ python -m pip install --pre --extra-index https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy scipy pandas --only-binary :all:\n fi\n", "problem_statement": "\u26a0\ufe0f CI failed on Wheel builder (last failure: Aug 01, 2024) \u26a0\ufe0f\n**CI is still failing on [Wheel builder](https://github.com/scikit-learn/scikit-learn/actions/runs/10191626508)** (Aug 01, 2024)\n\n", "hints_text": "Issue when getting pandas development wheel for Python 3.13 free-threaded, which is not available so it is trying to build from source and fails. Note the `tar.gz` (rather than the `.whl` in the [build log](https://github.com/scikit-learn/scikit-learn/actions/runs/10155774547/job/28083048523):\r\n```\r\n Collecting pandas\r\n Downloading https://pypi.anaconda.org/scientific-python-nightly-wheels/simple/pandas/3.0.0.dev0%2B1268.g9c8c685f48/pandas-3.0.0.dev0%2B1268.g9c8c685f48.tar.gz (4.3 MB)\r\n```\r\n\r\nLast time I looked there was not really a way to tell `pip` to only consider wheels not `.tar.gz` so that at least the error would be less cryptic but maybe I have missed it. `--no-binary :none:` is actually not enough.\r\n \r\nDon't know why pandas cp313-313t is not available, looks like there was some issue with Python 3.13 (not only free-threaded but also vanilla 3.13) https://github.com/pandas-dev/pandas/actions/runs/10155668934/job/28082792259 ...\r\n\r\nYou could argue that we could have used the previous nightly wheel but somehow looks like this pandas is cleaning up his anaconda.org bucket and the previous wheel is not there anymore?\nNot sure what the short-term option is, either:\r\n- revert https://github.com/scikit-learn/scikit-learn/pull/29572\r\n- try to install pandas and if that fails still run the tests without pandas installed, this is still better than nothing ... but this may be confusing that the test can run sometimes with pandas sometimes without (only for the Python 3.13 free-threaded build to be explicit)\nMaybe we can wait 48h to see if pandas dev can fix their Python 3.13 nightly builds. If it keeps failing, then we might consider reverting #29572.\nI opened an issue about this https://github.com/pandas-dev/pandas/issues/59372, let's see what they say!", "created_at": "2024-07-31T18:05:32Z" }, { "repo": "scikit-learn/scikit-learn", "pull_number": 29545, "instance_id": "scikit-learn__scikit-learn-29545", "issue_numbers": [ "29000" ], "base_commit": "d20e0b9abc4a4798d1fd839db50b19c01723094e", "patch": "diff --git a/doc/whats_new/v1.5.rst b/doc/whats_new/v1.5.rst\nindex 059875eec12d6..b5542a0d1cf5f 100644\n--- a/doc/whats_new/v1.5.rst\n+++ b/doc/whats_new/v1.5.rst\n@@ -23,6 +23,13 @@ Version 1.5.2\n Changelog\n ---------\n \n+:mod:`sklearn.calibration`\n+..........................\n+\n+- |Fix| Raise error when :class:`~sklearn.model_selection.LeaveOneOut` used in\n+ `cv`, matching what would happen if `KFold(n_splits=n_samples)` was used.\n+ :pr:`29545` by :user:`Lucy Liu `\n+\n :mod:`sklearn.compose`\n ......................\n \ndiff --git a/sklearn/calibration.py b/sklearn/calibration.py\nindex bc5ed634a3be4..609f051ab1626 100644\n--- a/sklearn/calibration.py\n+++ b/sklearn/calibration.py\n@@ -24,7 +24,7 @@\n clone,\n )\n from .isotonic import IsotonicRegression\n-from .model_selection import check_cv, cross_val_predict\n+from .model_selection import LeaveOneOut, check_cv, cross_val_predict\n from .preprocessing import LabelEncoder, label_binarize\n from .svm import LinearSVC\n from .utils import (\n@@ -390,6 +390,13 @@ def fit(self, X, y, sample_weight=None, **fit_params):\n \"cross-validation but provided less than \"\n f\"{n_folds} examples for at least one class.\"\n )\n+ if isinstance(self.cv, LeaveOneOut):\n+ raise ValueError(\n+ \"LeaveOneOut cross-validation does not allow\"\n+ \"all classes to be present in test splits. \"\n+ \"Please use a cross-validation generator that allows \"\n+ \"all classes to appear in every test and train split.\"\n+ )\n cv = check_cv(self.cv, y, classifier=True)\n \n if self.ensemble:\n", "test_patch": "diff --git a/sklearn/tests/test_calibration.py b/sklearn/tests/test_calibration.py\nindex c2cbad4060fde..b80083f3eac0d 100644\n--- a/sklearn/tests/test_calibration.py\n+++ b/sklearn/tests/test_calibration.py\n@@ -146,6 +146,20 @@ def test_calibration_cv_splitter(data, ensemble):\n assert len(calib_clf.calibrated_classifiers_) == expected_n_clf\n \n \n+def test_calibration_cv_nfold(data):\n+ # Check error raised when number of examples per class less than nfold\n+ X, y = data\n+\n+ kfold = KFold(n_splits=101)\n+ calib_clf = CalibratedClassifierCV(cv=kfold, ensemble=True)\n+ with pytest.raises(ValueError, match=\"Requesting 101-fold cross-validation\"):\n+ calib_clf.fit(X, y)\n+\n+ calib_clf = CalibratedClassifierCV(cv=LeaveOneOut(), ensemble=True)\n+ with pytest.raises(ValueError, match=\"LeaveOneOut cross-validation does\"):\n+ calib_clf.fit(X, y)\n+\n+\n @pytest.mark.parametrize(\"method\", [\"sigmoid\", \"isotonic\"])\n @pytest.mark.parametrize(\"ensemble\", [True, False])\n def test_sample_weight(data, method, ensemble):\n@@ -423,45 +437,47 @@ def test_calibration_nan_imputer(ensemble):\n \n @pytest.mark.parametrize(\"ensemble\", [True, False])\n def test_calibration_prob_sum(ensemble):\n- # Test that sum of probabilities is 1. A non-regression test for\n- # issue #7796\n- num_classes = 2\n- X, y = make_classification(n_samples=10, n_features=5, n_classes=num_classes)\n+ # Test that sum of probabilities is (max) 1. A non-regression test for\n+ # issue #7796 - when test has fewer classes than train\n+ X, _ = make_classification(n_samples=10, n_features=5, n_classes=2)\n+ y = [1, 1, 1, 1, 1, 0, 0, 0, 0, 0]\n clf = LinearSVC(C=1.0, random_state=7)\n+ # In the first and last fold, test will have 1 class while train will have 2\n clf_prob = CalibratedClassifierCV(\n- clf, method=\"sigmoid\", cv=LeaveOneOut(), ensemble=ensemble\n+ clf, method=\"sigmoid\", cv=KFold(n_splits=3), ensemble=ensemble\n )\n clf_prob.fit(X, y)\n-\n- probs = clf_prob.predict_proba(X)\n- assert_array_almost_equal(probs.sum(axis=1), np.ones(probs.shape[0]))\n+ assert_allclose(clf_prob.predict_proba(X).sum(axis=1), 1.0)\n \n \n @pytest.mark.parametrize(\"ensemble\", [True, False])\n def test_calibration_less_classes(ensemble):\n # Test to check calibration works fine when train set in a test-train\n # split does not contain all classes\n- # Since this test uses LOO, at each iteration train set will not contain a\n- # class label\n- X = np.random.randn(10, 5)\n- y = np.arange(10)\n- clf = LinearSVC(C=1.0, random_state=7)\n+ # In 1st split, train is missing class 0\n+ # In 3rd split, train is missing class 3\n+ X = np.random.randn(12, 5)\n+ y = [0, 0, 0, 1] + [1, 1, 2, 2] + [2, 3, 3, 3]\n+ clf = DecisionTreeClassifier(random_state=7)\n cal_clf = CalibratedClassifierCV(\n- clf, method=\"sigmoid\", cv=LeaveOneOut(), ensemble=ensemble\n+ clf, method=\"sigmoid\", cv=KFold(3), ensemble=ensemble\n )\n cal_clf.fit(X, y)\n \n- for i, calibrated_classifier in enumerate(cal_clf.calibrated_classifiers_):\n- proba = calibrated_classifier.predict_proba(X)\n- if ensemble:\n+ if ensemble:\n+ classes = np.arange(4)\n+ for calib_i, class_i in zip([0, 2], [0, 3]):\n+ proba = cal_clf.calibrated_classifiers_[calib_i].predict_proba(X)\n # Check that the unobserved class has proba=0\n- assert_array_equal(proba[:, i], np.zeros(len(y)))\n+ assert_array_equal(proba[:, class_i], np.zeros(len(y)))\n # Check for all other classes proba>0\n- assert np.all(proba[:, :i] > 0)\n- assert np.all(proba[:, i + 1 :] > 0)\n- else:\n- # Check `proba` are all 1/n_classes\n- assert np.allclose(proba, 1 / proba.shape[0])\n+ assert np.all(proba[:, classes != class_i] > 0)\n+\n+ # When `ensemble=False`, `cross_val_predict` is used to compute predictions\n+ # to fit only one `calibrated_classifiers_`\n+ else:\n+ proba = cal_clf.calibrated_classifiers_[0].predict_proba(X)\n+ assert_array_almost_equal(proba.sum(axis=1), np.ones(proba.shape[0]))\n \n \n @pytest.mark.parametrize(\n", "problem_statement": "KFold(n_samples=n) not equivalent to LeaveOneOut() cv in CalibratedClassifierCV()\n### Describe the bug\r\n\r\nCalling `CalibratedClassifierCV()` with `cv=KFold(n_samples=n)` (where n is the number of samples) can give different results than using `cv=LeaveOneOut()`, but the docs for `LeaveOneOut()` say these should be equivalent. \r\n\r\nIn particular, the `KFold` class has an `\"n_splits\"` attribute, which means [this branch](https://github.com/scikit-learn/scikit-learn/blob/8721245511de2f225ff5f9aa5f5fadce663cd4a3/sklearn/calibration.py#L387) runs when setting up sigmoid calibration, and then [this error](https://github.com/scikit-learn/scikit-learn/blob/8721245511de2f225ff5f9aa5f5fadce663cd4a3/sklearn/calibration.py#L394) can be thrown. With `LeaveOneOut()`, `n_folds` is set to `None` and that error is never hit.\r\n\r\nI'm not sure whether that error is correct/desirable in every case (see the code to reproduce for my use case where I think(?) the error may be unnecessary) but, either way, the two different `cv` values seem like they should behave equivalently.\r\n\r\n### Steps/Code to Reproduce\r\n\r\n```py\r\nfrom sklearn.pipeline import make_pipeline\r\nfrom sklearn.calibration import CalibratedClassifierCV\r\nfrom sklearn.model_selection import KFold, LeaveOneOut\r\nfrom sklearn.preprocessing import StandardScaler\r\nfrom sklearn.svm import SVC\r\nfrom sklearn.datasets import make_classification\r\n\r\nX, y = make_classification(n_samples=20, random_state=42)\r\n\r\npipeline = make_pipeline(\r\n StandardScaler(),\r\n CalibratedClassifierCV(\r\n SVC(probability=False),\r\n ensemble=False,\r\n cv=LeaveOneOut()\r\n )\r\n)\r\npipeline.fit(X, y)\r\n\r\npipeline2 = make_pipeline(\r\n StandardScaler(),\r\n CalibratedClassifierCV(\r\n SVC(probability=False),\r\n ensemble=False,\r\n cv=KFold(n_splits=20, shuffle=True)\r\n )\r\n)\r\npipeline2.fit(X, y)\r\n```\r\n\r\n### Expected Results\r\n\r\n`pipeline` and `pipeline2` should function identically. Instead, `pipeline.fit()` succeeds and `pipeline2.fit()` throws.\r\n\r\n### Actual Results\r\n\r\n```\r\nTraceback (most recent call last):\r\n File \"\", line 1, in \r\n File \"/python3.11/site-packages/sklearn/base.py\", line 1152, in wrapper\r\n return fit_method(estimator, *args, **kwargs)\r\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n File \"/python3.11/site-packages/sklearn/pipeline.py\", line 427, in fit\r\n self._final_estimator.fit(Xt, y, **fit_params_last_step)\r\n File \"/python3.11/site-packages/sklearn/base.py\", line 1152, in wrapper\r\n return fit_method(estimator, *args, **kwargs)\r\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n File \"/python3.11/site-packages/sklearn/calibration.py\", line 419, in fit\r\n raise ValueError(\r\nValueError: Requesting 20-fold cross-validation but provided less than 20 examples for at least one class.\r\n```\r\n\r\n### Versions\r\n\r\n```shell\r\nSystem:\r\n python: 3.11.9 | packaged by conda-forge | (main, Apr 19 2024, 18:34:54) [Clang 16.0.6 ]\r\n machine: macOS-14.4.1-arm64-arm-64bit\r\n\r\nPython dependencies:\r\n sklearn: 1.3.2\r\n pip: 24.0\r\n setuptools: 69.0.2\r\n numpy: 1.26.2\r\n scipy: 1.11.4\r\n Cython: None\r\n pandas: 2.1.3\r\n matplotlib: 3.8.2\r\n joblib: 1.3.2\r\nthreadpoolctl: 3.2.0\r\n\r\nBuilt with OpenMP: True\r\n\r\nthreadpoolctl info:\r\n user_api: openmp\r\n internal_api: openmp\r\n num_threads: 12\r\n prefix: libomp\r\n version: None\r\n\r\n user_api: blas\r\n internal_api: openblas\r\n num_threads: 12\r\n prefix: libopenblas\r\n version: 0.3.23.dev\r\nthreading_layer: pthreads\r\n architecture: armv8\r\n\r\n user_api: blas\r\n internal_api: openblas\r\n num_threads: 12\r\n prefix: libopenblas\r\n version: 0.3.21.dev\r\nthreading_layer: pthreads\r\n architecture: armv8\r\n```\r\n```\r\n\n", "hints_text": "The error in `KFold` is actually expected. We expect to have at least a sample from each class in each fold. This cannot be achieved with the `LeaveOneOut` cross-validation. So we should not accept this strategy.\r\n\r\nSo we could raise early an error for this strategy. However, I can also see some other strategy leading to having a single class present when fitting the calibrator. I assume that it should be safer to raise an error in this case as well otherwise we get a ill-fitted calibrator anyway.\r\n\r\nping @lucyleeow @ogrisel that might have more insight on this part of the calibrator and to know their opinions.\r\n\r\n\nI think i agree on both accounts but did not check the details in the code yet.\n> The error in `KFold` is actually expected. We expect to have at least a sample from each class in each fold.\r\n\r\nIsn't it the case that `KFold` also doesn't guarantee one sample from each class in each fold (since it doesn't create stratified folds)?\r\n\r\n> However, I can also see some other strategy leading to having a single class present when fitting the calibrator.\r\n\r\nYeah, exactly. There are lots of ways to end up with poorly-fit calibrators, and I'm not sure the code's current check (even when it does apply) really covers that.\nLeaveOneOut does not have different groups like k-folds cv (https://www.cs.cmu.edu/~schneide/tut5/node42.html). More accurately, it sets each sample as a 'fold.' It trains on (n-1) training data at a time (where train data size = n) making it computationally expensive but very reliable. K-folds, on the other hand, divides the training data into k groups and trains the model k times, leaving one group at a time. Perhaps this clarification was not the main issue, but I thought it might be helpful :)\r\n\r\n\n> We expect to have at least a sample from each class in each fold.\r\n\r\nLooking into this, with `ensemble=True` we do need at least one sample from each class in each fold. I think it makes sense to warn when this is not guaranteed (e.g., `KFold`) and error when it is not possible (e.g., `LeaveOneOut`).\r\n\r\nIn the case of `ensemble=False` we use `cross_val_predict` to get unbiased predictions. These predictions are then used to fit a single calibrator. I *think* (?) in this case it is okay that there is not at least a sample from each class in each fold. \r\n\r\nWDYT @glemaitre @ogrisel ?\n> These predictions are then used to fit a single calibrator. I think (?) in this case it is okay that there is not at least a sample from each class in each fold.\r\n\r\nI think this is still kind of weird to fit a calibrator on a single class, isn't it?\nBut it wouldn't be a single class. The 'test' set for each split would be a single sample (and thus one class) but `cross_val_predict(cv=LeaveOneOut)` would iterate through splits for all the data and give predictions for all samples?\nYes you are completely right, I was not awake yet :)\nhi, i'm new to this repository and ML in general, found this discussion interesting and hope you dont mind me chiming in.\r\n\r\nAnother suggestion might be to not throw errors but maybe give warnings in the case when `ensemble=True` and `cv` does not guarantee that each fold has a sample from each class. Perhaps it could be added in the documentation to further caution the user of this issue when using `CalibratedClassifierCV()`. \r\n\r\nThis would result in `KFold(samples=n)` and `LeaveOneOut()` having the same behaviour. Also I can imagine it could be annoying when you are working with 20 classes and want to do cross validation with `KFold(n_samples=30)`, but exactly one of your 20 classes has only 29 samples. Would the result of having 29 over 30 folds be so bad that it should warrant the error? I understand that the negative effects would be much more severe in the case of `LeaveOneOut()` though. \r\n\r\nedit: in the first case, perhaps we require each fold to have a sample of each class because of something like #28178 happening?\nLooking at this further, the only cv splitter where we guarantee that each class is represented in every split is `StratifiedKFold` and `StratifiedShuffleSplit`. Perhaps it is a better idea to highlight in our docs what happens when a class is missing; predicited prob for that split defaults to 0 for the missing class, skewing results when its averaged in `ensemble=True`.\r\n\r\nIn the case of `LeaveOneOut`, which is really should not be used with `CalibratedClassifierCV`, I agree with OP that we should give the same error.\r\n\r\ncc @glemaitre ", "created_at": "2024-07-23T06:49:43Z" }, { "repo": "scikit-learn/scikit-learn", "pull_number": 29540, "instance_id": "scikit-learn__scikit-learn-29540", "issue_numbers": [ "29508" ], "base_commit": "9a6b7d6c9e430afb0a29fbaa3dcbe22788f764a5", "patch": "diff --git a/doc/whats_new/v1.6.rst b/doc/whats_new/v1.6.rst\nindex a4ed45e294062..dafaa599202bb 100644\n--- a/doc/whats_new/v1.6.rst\n+++ b/doc/whats_new/v1.6.rst\n@@ -177,8 +177,9 @@ Changelog\n .......................\n \n - |Efficiency| Small runtime improvement of fitting\n- :class:`ensemble.HistGradientBoostingClassifier` and :class:`ensemble.HistGradientBoostingRegressor`\n- by parallelizing the initial search for bin thresholds\n+ :class:`ensemble.HistGradientBoostingClassifier` and\n+ :class:`ensemble.HistGradientBoostingRegressor` by parallelizing the initial search\n+ for bin thresholds.\n :pr:`28064` by :user:`Christian Lorentzen `.\n \n - |Enhancement| The verbosity of :class:`ensemble.HistGradientBoostingClassifier`\n@@ -193,9 +194,10 @@ Changelog\n :pr:`28622` by :user:`Adam Li ` and\n :user:`S\u00e9rgio Pereira `.\n \n-- |Feature| :class:`ensemble.ExtraTreesClassifier` and :class:`ensemble.ExtraTreesRegressor` now support\n- missing-values in the data matrix `X`. Missing-values are handled by randomly moving all of\n- the samples to the left, or right child node as the tree is traversed.\n+- |Feature| :class:`ensemble.ExtraTreesClassifier` and\n+ :class:`ensemble.ExtraTreesRegressor` now support missing-values in the data matrix\n+ `X`. Missing-values are handled by randomly moving all of the samples to the left, or\n+ right child node as the tree is traversed.\n :pr:`28268` by :user:`Adam Li `.\n \n :mod:`sklearn.impute`\n@@ -249,7 +251,8 @@ Changelog\n estimator without re-fitting it.\n :pr:`29067` by :user:`Guillaume Lemaitre `.\n \n-- |Fix| Improve error message when :func:`model_selection.RepeatedStratifiedKFold.split` is called without a `y` argument\n+- |Fix| Improve error message when :func:`model_selection.RepeatedStratifiedKFold.split`\n+ is called without a `y` argument\n :pr:`29402` by :user:`Anurag Varma `.\n \n :mod:`sklearn.neighbors`\n@@ -285,6 +288,11 @@ Changelog\n :mod:`sklearn.utils`\n ....................\n \n+- |Enhancement| :func:`utils.validation.check_array` now accepts `ensure_non_negative`\n+ to check for negative values in the passed array, until now only available through\n+ calling :func:`utils.validation.check_non_negative`.\n+ :pr:`29540` by :user:`Tamara Atanasoska `.\n+\n - |API| the `assert_all_finite` parameter of functions :func:`utils.check_array`,\n :func:`utils.check_X_y`, :func:`utils.as_float_array` is renamed into\n `ensure_all_finite`. `force_all_finite` will be removed in 1.8.\ndiff --git a/sklearn/decomposition/_nmf.py b/sklearn/decomposition/_nmf.py\nindex 76eff48de44d8..031afd8f4a327 100644\n--- a/sklearn/decomposition/_nmf.py\n+++ b/sklearn/decomposition/_nmf.py\n@@ -1700,8 +1700,6 @@ def _fit_transform(self, X, y=None, W=None, H=None, update_H=True):\n n_iter_ : int\n Actual number of iterations.\n \"\"\"\n- check_non_negative(X, \"NMF (input X)\")\n-\n # check parameters\n self._check_params(X)\n \n@@ -1777,7 +1775,11 @@ def transform(self, X):\n \"\"\"\n check_is_fitted(self)\n X = self._validate_data(\n- X, accept_sparse=(\"csr\", \"csc\"), dtype=[np.float64, np.float32], reset=False\n+ X,\n+ accept_sparse=(\"csr\", \"csc\"),\n+ dtype=[np.float64, np.float32],\n+ reset=False,\n+ ensure_non_negative=True,\n )\n \n with config_context(assume_finite=True):\ndiff --git a/sklearn/ensemble/_weight_boosting.py b/sklearn/ensemble/_weight_boosting.py\nindex e18bafb450d49..e9da6b03e1bdb 100644\n--- a/sklearn/ensemble/_weight_boosting.py\n+++ b/sklearn/ensemble/_weight_boosting.py\n@@ -137,7 +137,7 @@ def fit(self, X, y, sample_weight=None):\n )\n \n sample_weight = _check_sample_weight(\n- sample_weight, X, np.float64, copy=True, only_non_negative=True\n+ sample_weight, X, np.float64, copy=True, ensure_non_negative=True\n )\n sample_weight /= sample_weight.sum()\n \ndiff --git a/sklearn/kernel_approximation.py b/sklearn/kernel_approximation.py\nindex 2c1981295dffa..9def2601b345a 100644\n--- a/sklearn/kernel_approximation.py\n+++ b/sklearn/kernel_approximation.py\n@@ -24,7 +24,6 @@\n from .utils.validation import (\n _check_feature_names_in,\n check_is_fitted,\n- check_non_negative,\n )\n \n \n@@ -674,8 +673,7 @@ def fit(self, X, y=None):\n self : object\n Returns the transformer.\n \"\"\"\n- X = self._validate_data(X, accept_sparse=\"csr\")\n- check_non_negative(X, \"X in AdditiveChi2Sampler.fit\")\n+ X = self._validate_data(X, accept_sparse=\"csr\", ensure_non_negative=True)\n \n if self.sample_interval is None and self.sample_steps not in (1, 2, 3):\n raise ValueError(\n@@ -701,8 +699,9 @@ def transform(self, X):\n Whether the return value is an array or sparse matrix depends on\n the type of the input X.\n \"\"\"\n- X = self._validate_data(X, accept_sparse=\"csr\", reset=False)\n- check_non_negative(X, \"X in AdditiveChi2Sampler.transform\")\n+ X = self._validate_data(\n+ X, accept_sparse=\"csr\", reset=False, ensure_non_negative=True\n+ )\n sparse = sp.issparse(X)\n \n if self.sample_interval is None:\ndiff --git a/sklearn/linear_model/_base.py b/sklearn/linear_model/_base.py\nindex 0ca59d97948bc..1a7006db10a9d 100644\n--- a/sklearn/linear_model/_base.py\n+++ b/sklearn/linear_model/_base.py\n@@ -609,7 +609,7 @@ def fit(self, X, y, sample_weight=None):\n has_sw = sample_weight is not None\n if has_sw:\n sample_weight = _check_sample_weight(\n- sample_weight, X, dtype=X.dtype, only_non_negative=True\n+ sample_weight, X, dtype=X.dtype, ensure_non_negative=True\n )\n \n # Note that neither _rescale_data nor the rest of the fit method of\ndiff --git a/sklearn/neighbors/_base.py b/sklearn/neighbors/_base.py\nindex 3dfd2df16fabd..5ef45bdc7bd38 100644\n--- a/sklearn/neighbors/_base.py\n+++ b/sklearn/neighbors/_base.py\n@@ -30,7 +30,7 @@\n from ..utils.fixes import parse_version, sp_base_version\n from ..utils.multiclass import check_classification_targets\n from ..utils.parallel import Parallel, delayed\n-from ..utils.validation import _to_object_array, check_is_fitted, check_non_negative\n+from ..utils.validation import _to_object_array, check_is_fitted\n from ._ball_tree import BallTree\n from ._kd_tree import KDTree\n \n@@ -167,8 +167,7 @@ def _check_precomputed(X):\n case only non-zero elements may be considered neighbors.\n \"\"\"\n if not issparse(X):\n- X = check_array(X)\n- check_non_negative(X, whom=\"precomputed distance matrix.\")\n+ X = check_array(X, ensure_non_negative=True, input_name=\"X\")\n return X\n else:\n graph = X\n@@ -179,8 +178,12 @@ def _check_precomputed(X):\n \"its handling of explicit zeros\".format(graph.format)\n )\n copied = graph.format != \"csr\"\n- graph = check_array(graph, accept_sparse=\"csr\")\n- check_non_negative(graph, whom=\"precomputed distance matrix.\")\n+ graph = check_array(\n+ graph,\n+ accept_sparse=\"csr\",\n+ ensure_non_negative=True,\n+ input_name=\"precomputed distance matrix\",\n+ )\n graph = sort_graph_by_row_values(graph, copy=not copied, warn_when_not_sorted=True)\n \n return graph\ndiff --git a/sklearn/neighbors/_kde.py b/sklearn/neighbors/_kde.py\nindex 73c50e848ae2b..363801b2a25ff 100644\n--- a/sklearn/neighbors/_kde.py\n+++ b/sklearn/neighbors/_kde.py\n@@ -230,7 +230,7 @@ def fit(self, X, y=None, sample_weight=None):\n \n if sample_weight is not None:\n sample_weight = _check_sample_weight(\n- sample_weight, X, dtype=np.float64, only_non_negative=True\n+ sample_weight, X, dtype=np.float64, ensure_non_negative=True\n )\n \n kwargs = self.metric_params\ndiff --git a/sklearn/utils/validation.py b/sklearn/utils/validation.py\nindex 4bbd922d28e37..8a8c12506216e 100644\n--- a/sklearn/utils/validation.py\n+++ b/sklearn/utils/validation.py\n@@ -741,6 +741,7 @@ def check_array(\n force_writeable=False,\n force_all_finite=\"deprecated\",\n ensure_all_finite=None,\n+ ensure_non_negative=False,\n ensure_2d=True,\n allow_nd=False,\n ensure_min_samples=1,\n@@ -828,6 +829,12 @@ def check_array(\n .. versionadded:: 1.6\n `force_all_finite` was renamed to `ensure_all_finite`.\n \n+ ensure_non_negative : bool, default=False\n+ Make sure the array has only non-negative values. If True, an array that\n+ contains negative values will raise a ValueError.\n+\n+ .. versionadded:: 1.6\n+\n ensure_2d : bool, default=True\n Whether to raise a value error if array is not 2D.\n \n@@ -1132,6 +1139,12 @@ def is_sparse(dtype):\n % (n_features, array.shape, ensure_min_features, context)\n )\n \n+ if ensure_non_negative:\n+ whom = input_name\n+ if estimator_name:\n+ whom += f\" in {estimator_name}\"\n+ check_non_negative(array, whom)\n+\n if force_writeable:\n # By default, array.copy() creates a C-ordered copy. We set order=K to\n # preserve the order of the array.\n@@ -1739,7 +1752,7 @@ def check_non_negative(X, whom):\n X_min = xp.min(X)\n \n if X_min < 0:\n- raise ValueError(\"Negative values in data passed to %s\" % whom)\n+ raise ValueError(f\"Negative values in data passed to {whom}.\")\n \n \n def check_scalar(\n@@ -2044,7 +2057,7 @@ def _check_psd_eigenvalues(lambdas, enable_warnings=False):\n \n \n def _check_sample_weight(\n- sample_weight, X, dtype=None, copy=False, only_non_negative=False\n+ sample_weight, X, dtype=None, copy=False, ensure_non_negative=False\n ):\n \"\"\"Validate sample weights.\n \n@@ -2061,7 +2074,7 @@ def _check_sample_weight(\n X : {ndarray, list, sparse matrix}\n Input data.\n \n- only_non_negative : bool, default=False,\n+ ensure_non_negative : bool, default=False,\n Whether or not the weights are expected to be non-negative.\n \n .. versionadded:: 1.0\n@@ -2112,7 +2125,7 @@ def _check_sample_weight(\n )\n )\n \n- if only_non_negative:\n+ if ensure_non_negative:\n check_non_negative(sample_weight, \"`sample_weight`\")\n \n return sample_weight\n", "test_patch": "diff --git a/sklearn/tests/test_kernel_approximation.py b/sklearn/tests/test_kernel_approximation.py\nindex a25baa45823ae..32a655f3c4b27 100644\n--- a/sklearn/tests/test_kernel_approximation.py\n+++ b/sklearn/tests/test_kernel_approximation.py\n@@ -200,9 +200,9 @@ def test_additive_chi2_sampler_exceptions():\n transformer = AdditiveChi2Sampler()\n X_neg = X.copy()\n X_neg[0, 0] = -1\n- with pytest.raises(ValueError, match=\"X in AdditiveChi2Sampler.fit\"):\n+ with pytest.raises(ValueError, match=\"X in AdditiveChi2Sampler\"):\n transformer.fit(X_neg)\n- with pytest.raises(ValueError, match=\"X in AdditiveChi2Sampler.transform\"):\n+ with pytest.raises(ValueError, match=\"X in AdditiveChi2Sampler\"):\n transformer.fit(X)\n transformer.transform(X_neg)\n \ndiff --git a/sklearn/utils/tests/test_validation.py b/sklearn/utils/tests/test_validation.py\nindex b99dec99498ab..4599f18e7268a 100644\n--- a/sklearn/utils/tests/test_validation.py\n+++ b/sklearn/utils/tests/test_validation.py\n@@ -462,6 +462,17 @@ def test_check_array():\n result = check_array(X_no_array)\n assert isinstance(result, np.ndarray)\n \n+ # check negative values when ensure_non_negative=True\n+ X_neg = check_array([[1, 2], [-3, 4]])\n+ err_msg = \"Negative values in data passed to X in RandomForestRegressor\"\n+ with pytest.raises(ValueError, match=err_msg):\n+ check_array(\n+ X_neg,\n+ ensure_non_negative=True,\n+ input_name=\"X\",\n+ estimator=RandomForestRegressor(),\n+ )\n+\n \n @pytest.mark.parametrize(\n \"X\",\n@@ -1480,13 +1491,13 @@ def test_check_sample_weight():\n sample_weight = _check_sample_weight(None, X, dtype=X.dtype)\n assert sample_weight.dtype == np.float64\n \n- # check negative weight when only_non_negative=True\n+ # check negative weight when ensure_non_negative=True\n X = np.ones((5, 2))\n sample_weight = np.ones(_num_samples(X))\n sample_weight[-1] = -10\n err_msg = \"Negative values in data passed to `sample_weight`\"\n with pytest.raises(ValueError, match=err_msg):\n- _check_sample_weight(sample_weight, X, only_non_negative=True)\n+ _check_sample_weight(sample_weight, X, ensure_non_negative=True)\n \n \n @pytest.mark.parametrize(\"toarray\", [np.array, sp.csr_matrix, sp.csc_matrix])\n", "problem_statement": "Add \"ensure_positive\" to check_array for non-negative value validation \n### Describe the workflow you want to enable\n\nAdding an option to `ensure_positive` to the `sklearn.utils.validation.check_array` function. \r\nCurrently, to ensure that an input array contains only positive values `check_non_negative` is used. Most users then either use the `check_non_negative` right after `check_array`, or create a custom `check_X` function that contains both checks. \r\n\r\nAdditionally, the new [estimator_checks](https://github.com/scikit-learn/scikit-learn/blob/d79cb58c464f0b54bf0f0286c725d2df837574d0/sklearn/utils/estimator_checks.py), in the case of the `\"requires_positive_X\": True` tag, require a `ValueError` to be raised if negative values are found in X. This will also help simplify making a new estimator more compliant easier, just with using `check_array`, as a large number of users already do.\n\n### Describe your proposed solution\n\nAdding an option to `ensure_positive` to the `sklearn.utils.validation.check_array` function, that contains the `check_non_negative` functionality. \n\n### Describe alternatives you've considered, if relevant\n\n_No response_\n\n### Additional context\n\n_No response_\n", "hints_text": "> Most users then either use the check_non_negative right after check_array\r\n\r\nMost `check_non_negative` calls are like that in scikit-learn. I'm in favor to add this option directly in `check_array`.\n> > Most users then either use the check_non_negative right after check_array\r\n> \r\n> Most `check_non_negative` calls are like that in scikit-learn. I'm in favor to add this option directly in `check_array`.\r\n\r\nThanks for the comment @jeremiedbb! I am happy to work on a PR during the next few days.", "created_at": "2024-07-22T11:00:05Z" }, { "repo": "scikit-learn/scikit-learn", "pull_number": 29492, "instance_id": "scikit-learn__scikit-learn-29492", "issue_numbers": [ "29421" ], "base_commit": "d79cb58c464f0b54bf0f0286c725d2df837574d0", "patch": "diff --git a/sklearn/utils/_pprint.py b/sklearn/utils/_pprint.py\nindex 9b33cd617a5fc..5045300357306 100644\n--- a/sklearn/utils/_pprint.py\n+++ b/sklearn/utils/_pprint.py\n@@ -427,7 +427,7 @@ def _safe_repr(object, context, maxlevels, level, changed_only=False):\n if issubclass(typ, BaseEstimator):\n objid = id(object)\n if maxlevels and level >= maxlevels:\n- return \"{...}\", False, objid in context\n+ return f\"{typ.__name__}(...)\", False, objid in context\n if objid in context:\n return pprint._recursion(object), False, True\n context[objid] = 1\n", "test_patch": "diff --git a/sklearn/utils/tests/test_pprint.py b/sklearn/utils/tests/test_pprint.py\nindex ec48c4a012574..bef5836910787 100644\n--- a/sklearn/utils/tests/test_pprint.py\n+++ b/sklearn/utils/tests/test_pprint.py\n@@ -2,6 +2,7 @@\n from pprint import PrettyPrinter\n \n import numpy as np\n+import pytest\n \n from sklearn.utils._pprint import _EstimatorPrettyPrinter\n from sklearn.linear_model import LogisticRegressionCV\n@@ -346,6 +347,24 @@ def test_deeply_nested(print_changed_only_false):\n assert rfe.__repr__() == expected\n \n \n+@pytest.mark.parametrize(\n+ (\"print_changed_only\", \"expected\"),\n+ [\n+ (True, \"RFE(estimator=RFE(...))\"),\n+ (\n+ False,\n+ \"RFE(estimator=RFE(...), n_features_to_select=None, step=1, verbose=0)\",\n+ ),\n+ ],\n+)\n+def test_print_estimator_max_depth(print_changed_only, expected):\n+ with config_context(print_changed_only=print_changed_only):\n+ pp = _EstimatorPrettyPrinter(depth=1)\n+\n+ rfe = RFE(RFE(RFE(RFE(RFE(LogisticRegression())))))\n+ assert pp.pformat(rfe) == expected\n+\n+\n def test_gridsearch(print_changed_only_false):\n # render a gridsearch\n param_grid = [\n", "problem_statement": "Don't print the estimator like a dict (`{...}`) beyond `maxlevels`\n### Describe the bug\n\nEstimator pretty printer doesn't render deeply-nested estimators correctly.\n\n### Steps/Code to Reproduce\n\n```pycon\r\n>>> from sklearn.base import BaseEstimator\r\n>>> from sklearn.utils._pprint import _EstimatorPrettyPrinter\r\n>>>\r\n>>>\r\n>>> # Constructors excerpted to test pprinting\r\n>>> class LogisticRegression(BaseEstimator):\r\n... def __init__(\r\n... self,\r\n... penalty=\"l2\",\r\n... dual=False,\r\n... tol=1e-4,\r\n... C=1.0,\r\n... fit_intercept=True,\r\n... intercept_scaling=1,\r\n... class_weight=None,\r\n... random_state=None,\r\n... solver=\"warn\",\r\n... max_iter=100,\r\n... multi_class=\"warn\",\r\n... verbose=0,\r\n... warm_start=False,\r\n... n_jobs=None,\r\n... l1_ratio=None,\r\n... ):\r\n... self.penalty = penalty\r\n... self.dual = dual\r\n... self.tol = tol\r\n... self.C = C\r\n... self.fit_intercept = fit_intercept\r\n... self.intercept_scaling = intercept_scaling\r\n... self.class_weight = class_weight\r\n... self.random_state = random_state\r\n... self.solver = solver\r\n... self.max_iter = max_iter\r\n... self.multi_class = multi_class\r\n... self.verbose = verbose\r\n... self.warm_start = warm_start\r\n... self.n_jobs = n_jobs\r\n... self.l1_ratio = l1_ratio\r\n... def fit(self, X, y):\r\n... return self\r\n... \r\n>>>\r\n>>> class RFE(BaseEstimator):\r\n... def __init__(self, estimator, n_features_to_select=None, step=1, verbose=0):\r\n... self.estimator = estimator\r\n... self.n_features_to_select = n_features_to_select\r\n... self.step = step\r\n... self.verbose = verbose\r\n... \r\n>>>\r\n>>> pp = _EstimatorPrettyPrinter(depth=1)\r\n>>> rfe = RFE(RFE(RFE(RFE(RFE(LogisticRegression())))))\r\n>>> pp.pformat(rfe)\r\n'RFE(estimator={...})'\r\n```\n\n### Expected Results\n\n```pycon\r\n>>> pp.pformat(rfe)\r\n'RFE(estimator=RFE(...), n_features_to_select=None, step=1, verbose=0)'\r\n```\n\n### Actual Results\n\n```pycon\r\n>>> pp.pformat(rfe)\r\n'RFE(estimator={...})'\r\n```\n\n### Versions\n\n```shell\nSystem:\r\n python: 3.11.8 (main, Feb 26 2024, 15:36:12) [Clang 14.0.6 ]\r\nexecutable: /opt/miniconda3/envs/ibis-ml-all/bin/python\r\n machine: macOS-14.5-arm64-arm-64bit\r\n\r\nPython dependencies:\r\n sklearn: 1.5.0\r\n pip: 24.0\r\n setuptools: 69.5.1\r\n numpy: 1.26.4\r\n scipy: 1.13.1\r\n Cython: None\r\n pandas: 2.2.2\r\n matplotlib: None\r\n joblib: 1.4.2\r\nthreadpoolctl: 3.5.0\r\n\r\nBuilt with OpenMP: True\r\n\r\nthreadpoolctl info:\r\n user_api: blas\r\n internal_api: openblas\r\n num_threads: 8\r\n prefix: libopenblas\r\n filepath: /opt/miniconda3/envs/ibis-ml-all/lib/python3.11/site-packages/numpy/.dylibs/libopenblas64_.0.dylib\r\n version: 0.3.23.dev\r\nthreading_layer: pthreads\r\n architecture: armv8\r\n\r\n user_api: blas\r\n internal_api: openblas\r\n num_threads: 8\r\n prefix: libopenblas\r\n filepath: /opt/miniconda3/envs/ibis-ml-all/lib/python3.11/site-packages/scipy/.dylibs/libopenblas.0.dylib\r\n version: 0.3.27\r\nthreading_layer: pthreads\r\n architecture: neoversen1\r\n\r\n user_api: openmp\r\n internal_api: openmp\r\n num_threads: 8\r\n prefix: libomp\r\n filepath: /opt/miniconda3/envs/ibis-ml-all/lib/python3.11/site-packages/sklearn/.dylibs/libomp.dylib\r\n version: None\n```\n\n", "hints_text": "This seems like an improvement. As stated in the PR, it would be easier if you could only open a PR, fixing solely this bug ;).\nHi! if no one is already working on it, then I can take this and try.\n> Hi! if no one is already working on it, then I can take this and try.\r\n\r\n@tahseenadit I've already got a PR for it, sorry!\r\n\r\n@glemaitre I'll split it out shortly!\nHi again, what is the reason for printing unchanged parameters ? I can see that the logic is to print the changed params if _changed_only is True. You can set it to False like below:\r\n\r\n`pp._changed_only = False`\r\n\r\nAnd then it should print `'RFE(estimator=RFE{...}, n_features_to_select=None, step=1, verbose=0)'`. \r\n\r\nAny reason you want to change this behaviour ?", "created_at": "2024-07-15T12:37:04Z" }, { "repo": "scikit-learn/scikit-learn", "pull_number": 29462, "instance_id": "scikit-learn__scikit-learn-29462", "issue_numbers": [ "29417" ], "base_commit": "a4e2bfbd923302441b7fba54fbf4785f0148f7f2", "patch": "diff --git a/doc/modules/model_evaluation.rst b/doc/modules/model_evaluation.rst\nindex 080ed0c63a58c..7c2314fc3a3a7 100644\n--- a/doc/modules/model_evaluation.rst\n+++ b/doc/modules/model_evaluation.rst\n@@ -92,7 +92,7 @@ Scoring Function\n \n **Regression**\n 'explained_variance' :func:`metrics.explained_variance_score`\n-'max_error' :func:`metrics.max_error`\n+'neg_max_error' :func:`metrics.max_error`\n 'neg_mean_absolute_error' :func:`metrics.mean_absolute_error`\n 'neg_mean_squared_error' :func:`metrics.mean_squared_error`\n 'neg_root_mean_squared_error' :func:`metrics.root_mean_squared_error`\ndiff --git a/doc/whats_new/v1.6.rst b/doc/whats_new/v1.6.rst\nindex 0024d979eeb19..9f1b8514de4a0 100644\n--- a/doc/whats_new/v1.6.rst\n+++ b/doc/whats_new/v1.6.rst\n@@ -219,6 +219,10 @@ Changelog\n :pr:`29210` by :user:`Marc Torrellas Socastro ` and\n :user:`Stefanie Senger `.\n \n+- |API| scoring=\"neg_max_error\" should be used instead of\n+ scoring=\"max_error\" which is now deprecated.\n+ :pr:`29462` by :user:`Farid \"Freddie\" Taba `.\n+\n :mod:`sklearn.model_selection`\n ..............................\n \ndiff --git a/sklearn/metrics/_scorer.py b/sklearn/metrics/_scorer.py\nindex c1a916aa0b5f3..bbc1424c335fb 100644\n--- a/sklearn/metrics/_scorer.py\n+++ b/sklearn/metrics/_scorer.py\n@@ -219,6 +219,8 @@ def __init__(self, score_func, sign, kwargs, response_method=\"predict\"):\n self._sign = sign\n self._kwargs = kwargs\n self._response_method = response_method\n+ # TODO (1.8): remove in 1.8 (scoring=\"max_error\" has been deprecated in 1.6)\n+ self._deprecation_msg = None\n \n def _get_pos_label(self):\n if \"pos_label\" in self._kwargs:\n@@ -270,6 +272,12 @@ def __call__(self, estimator, X, y_true, sample_weight=None, **kwargs):\n score : float\n Score function applied to prediction of estimator on X.\n \"\"\"\n+ # TODO (1.8): remove in 1.8 (scoring=\"max_error\" has been deprecated in 1.6)\n+ if self._deprecation_msg is not None:\n+ warnings.warn(\n+ self._deprecation_msg, category=DeprecationWarning, stacklevel=2\n+ )\n+\n _raise_for_params(kwargs, self, None)\n \n _kwargs = copy.deepcopy(kwargs)\n@@ -420,7 +428,12 @@ def get_scorer(scoring):\n \"\"\"\n if isinstance(scoring, str):\n try:\n- scorer = copy.deepcopy(_SCORERS[scoring])\n+ if scoring == \"max_error\":\n+ # TODO (1.8): scoring=\"max_error\" has been deprecated in 1.6,\n+ # remove in 1.8\n+ scorer = max_error_scorer\n+ else:\n+ scorer = copy.deepcopy(_SCORERS[scoring])\n except KeyError:\n raise ValueError(\n \"%r is not a valid scoring value. \"\n@@ -758,7 +771,15 @@ def make_scorer(\n # Standard regression scores\n explained_variance_scorer = make_scorer(explained_variance_score)\n r2_scorer = make_scorer(r2_score)\n+neg_max_error_scorer = make_scorer(max_error, greater_is_better=False)\n max_error_scorer = make_scorer(max_error, greater_is_better=False)\n+# TODO (1.8): remove in 1.8 (scoring=\"max_error\" has been deprecated in 1.6)\n+deprecation_msg = (\n+ \"Scoring method max_error was renamed to \"\n+ \"neg_max_error in version 1.6 and will \"\n+ \"be removed in 1.8.\"\n+)\n+max_error_scorer._deprecation_msg = deprecation_msg\n neg_mean_squared_error_scorer = make_scorer(mean_squared_error, greater_is_better=False)\n neg_mean_squared_log_error_scorer = make_scorer(\n mean_squared_log_error, greater_is_better=False\n@@ -867,7 +888,7 @@ def negative_likelihood_ratio(y_true, y_pred):\n _SCORERS = dict(\n explained_variance=explained_variance_scorer,\n r2=r2_scorer,\n- max_error=max_error_scorer,\n+ neg_max_error=neg_max_error_scorer,\n matthews_corrcoef=matthews_corrcoef_scorer,\n neg_median_absolute_error=neg_median_absolute_error_scorer,\n neg_mean_absolute_error=neg_mean_absolute_error_scorer,\n", "test_patch": "diff --git a/sklearn/metrics/tests/test_score_objects.py b/sklearn/metrics/tests/test_score_objects.py\nindex 73bb008c47300..ac4bf731ee02c 100644\n--- a/sklearn/metrics/tests/test_score_objects.py\n+++ b/sklearn/metrics/tests/test_score_objects.py\n@@ -78,7 +78,7 @@\n \"mean_absolute_percentage_error\",\n \"mean_squared_error\",\n \"median_absolute_error\",\n- \"max_error\",\n+ \"neg_max_error\",\n \"neg_mean_poisson_deviance\",\n \"neg_mean_gamma_deviance\",\n ]\n@@ -706,6 +706,16 @@ def test_scoring_is_not_metric():\n check_scoring(KMeans(), scoring=cluster_module.rand_score)\n \n \n+def test_deprecated_scorer():\n+ X, y = make_regression(n_samples=10, n_features=1, random_state=0)\n+ X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)\n+ reg = DecisionTreeRegressor()\n+ reg.fit(X_train, y_train)\n+ deprecated_scorer = get_scorer(\"max_error\")\n+ with pytest.warns(DeprecationWarning):\n+ deprecated_scorer(reg, X_test, y_test)\n+\n+\n @pytest.mark.parametrize(\n (\n \"scorers,expected_predict_count,\"\n", "problem_statement": "Documentation section 3.4.1.1 has incorrect description that would be correct if the `max_loss` metric were to be tweaked and renamed\n### Describe the issue linked to the documentation\n\n(Very similar to issue #13887 which was reported and fixed 5 years ago, so I have borrowed much of the text.)\r\n\r\nIn the documentation, section 3.4.1.1. \"Common cases: predefined values\", the remark:\r\n\r\n> All scorer objects follow the convention that higher return values are better than lower return values.\r\n\r\nis not 100% correct, as the `max_error` metric used for regression is _not_ a \"greater is better\" metric, as far as I can tell.\r\n\r\nIf I may, I would love to implement the PR myself, as it would be my first time contributing to a large, well-known library.\n\n### Suggest a potential alternative/fix\n\n1. I suggest implementing a function named `neg_max_score` which simply returns the negative of the value of max_error; this is a direct analogy to what is done in the case of \u2018neg_mean_absolute_error\u2019 and others. A better model has a lower value of mean absolute error, therefore a larger value of the mean absolute error implies a better model. The same is true for maximum error, where it is also the case that a better model is assigned a lower loss.\r\n\r\n2. Remove references to `max_error` from section 3.4.1.1 and replace them with `neg_max_error`.\n", "hints_text": "Thanks for your issue, if you are interested to open a PR, this would be more than welcome :pray:!\r\n\r\nYou can probably draw some inspiration from the PR https://github.com/scikit-learn/scikit-learn/pull/14898 that fixed the similar issue about brier_score you mentioned\ncan i also work on the documentation part of 3.4.1.1? @lesteve @artificialfintelligence \n> can i also work on the documentation part of 3.4.1.1? @lesteve @artificialfintelligence \n\nAbsolutely! Would love to work with you on this. I'm actually not that comfortable with ReStructure Text (.rst) files so you're a lifesaver!\n\nP.S. Apologies for the delayed response. I've been feeling a bit under the weather since I opened this issue. I should have some time tonight to start working on it.\n/take\n> > can i also work on the documentation part of 3.4.1.1? @lesteve @artificialfintelligence\r\n> \r\n> Absolutely! Would love to work with you on this. I'm actually not that comfortable with ReStructure Text (.rst) files so you're a lifesaver!\r\n> \r\n> P.S. Apologies for the delayed response. I've been feeling a bit under the weather since I opened this issue. I should have some time tonight to start working on it.\r\n\r\n@artificialfintelligence Thank you! I'll work on the doc once you update the method. You can then recheck it before merging! If you need any help with the method I'd be happy to help", "created_at": "2024-07-11T02:59:50Z" }, { "repo": "scikit-learn/scikit-learn", "pull_number": 29436, "instance_id": "scikit-learn__scikit-learn-29436", "issue_numbers": [ "29396" ], "base_commit": "0dba98f1eb7edc893e26c36d54519386a265467d", "patch": "diff --git a/build_tools/update_environments_and_lock_files.py b/build_tools/update_environments_and_lock_files.py\nindex 87b4c6478a3f4..12584da3c28b8 100644\n--- a/build_tools/update_environments_and_lock_files.py\n+++ b/build_tools/update_environments_and_lock_files.py\n@@ -225,9 +225,14 @@ def remove_from(alist, to_remove):\n \"pip_dependencies\": (\n remove_from(common_dependencies, [\"python\", \"blas\", \"pip\"])\n + docstring_test_dependencies\n+ # Test with some optional dependencies\n + [\"lightgbm\", \"scikit-image\"]\n+ # Test array API on CPU without PyTorch\n+ + [\"array-api-compat\", \"array-api-strict\"]\n ),\n \"package_constraints\": {\n+ # XXX: we would like to use the latest version of Python but this makes\n+ # the CI much slower. We need to investigate why.\n \"python\": \"3.9\",\n },\n },\ndiff --git a/sklearn/utils/_array_api.py b/sklearn/utils/_array_api.py\nindex 542a8136da661..a00d250ab31d2 100644\n--- a/sklearn/utils/_array_api.py\n+++ b/sklearn/utils/_array_api.py\n@@ -672,16 +672,10 @@ def _average(a, axis=None, weights=None, normalize=True, xp=None):\n f\"weights {tuple(weights.shape)} differ.\"\n )\n \n- if weights.ndim != 1:\n- raise TypeError(\n- f\"1D weights expected when a.shape={tuple(a.shape)} and \"\n- f\"weights.shape={tuple(weights.shape)} differ.\"\n- )\n-\n- if size(weights) != a.shape[axis]:\n+ if tuple(weights.shape) != (a.shape[axis],):\n raise ValueError(\n- f\"Length of weights {size(weights)} not compatible with \"\n- f\" a.shape={tuple(a.shape)} and {axis=}.\"\n+ f\"Shape of weights weights.shape={tuple(weights.shape)} must be \"\n+ f\"consistent with a.shape={tuple(a.shape)} and {axis=}.\"\n )\n \n # If weights are 1D, add singleton dimensions for broadcasting\n@@ -839,9 +833,14 @@ def _estimator_with_converted_arrays(estimator, converter):\n return new_estimator\n \n \n-def _atol_for_type(dtype):\n+def _atol_for_type(dtype_or_dtype_name):\n \"\"\"Return the absolute tolerance for a given numpy dtype.\"\"\"\n- return numpy.finfo(dtype).eps * 100\n+ if dtype_or_dtype_name is None:\n+ # If no dtype is specified when running tests for a given namespace, we\n+ # expect the same floating precision level as NumPy's default floating\n+ # point dtype.\n+ dtype_or_dtype_name = numpy.float64\n+ return numpy.finfo(dtype_or_dtype_name).eps * 100\n \n \n def indexing_dtype(xp):\n", "test_patch": "diff --git a/build_tools/azure/pylatest_pip_openblas_pandas_environment.yml b/build_tools/azure/pylatest_pip_openblas_pandas_environment.yml\nindex adb7add7622e1..c0d6aeaa717c0 100644\n--- a/build_tools/azure/pylatest_pip_openblas_pandas_environment.yml\n+++ b/build_tools/azure/pylatest_pip_openblas_pandas_environment.yml\n@@ -27,3 +27,5 @@ dependencies:\n - numpydoc\n - lightgbm\n - scikit-image\n+ - array-api-compat\n+ - array-api-strict\ndiff --git a/build_tools/azure/pylatest_pip_openblas_pandas_linux-64_conda.lock b/build_tools/azure/pylatest_pip_openblas_pandas_linux-64_conda.lock\nindex e2ffa14d39b43..ac4c92a671ed4 100644\n--- a/build_tools/azure/pylatest_pip_openblas_pandas_linux-64_conda.lock\n+++ b/build_tools/azure/pylatest_pip_openblas_pandas_linux-64_conda.lock\n@@ -1,6 +1,6 @@\n # Generated by conda-lock.\n # platform: linux-64\n-# input_hash: af52e4ce613b7668e1e28daaea07461722275d345395a5eaced4e07a16998179\n+# input_hash: 11d97b96088b6b1eaf3b774050152e7899f0a6ab757350df2efd44b2de3a5f75\n @EXPLICIT\n https://repo.anaconda.com/pkgs/main/linux-64/_libgcc_mutex-0.1-main.conda#c3473ff8bdb3d124ed5ff11ec380d6f9\n https://repo.anaconda.com/pkgs/main/linux-64/ca-certificates-2024.3.11-h06a4308_0.conda#08529eb3504712baabcbda266a19feb7\n@@ -24,6 +24,7 @@ https://repo.anaconda.com/pkgs/main/linux-64/setuptools-69.5.1-py39h06a4308_0.co\n https://repo.anaconda.com/pkgs/main/linux-64/wheel-0.43.0-py39h06a4308_0.conda#40bb60408c7433d767fd8c65b35bc4a0\n https://repo.anaconda.com/pkgs/main/linux-64/pip-24.0-py39h06a4308_0.conda#7f8ce3af15cfecd12e4dda8c5cef5fb7\n # pip alabaster @ https://files.pythonhosted.org/packages/32/34/d4e1c02d3bee589efb5dfa17f88ea08bdb3e3eac12bc475462aec52ed223/alabaster-0.7.16-py3-none-any.whl#sha256=b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92\n+# pip array-api-compat @ https://files.pythonhosted.org/packages/05/ae/2f11031bb9f819f6efaaa66b720b37928fbb0087161fcbae3465ae374a18/array_api_compat-1.7.1-py3-none-any.whl#sha256=6974f51775972f39edbca39e08f1c2e43c51401c093a0fea5ac7159875095d8a\n # pip babel @ https://files.pythonhosted.org/packages/27/45/377f7e32a5c93d94cd56542349b34efab5ca3f9e2fd5a68c5e93169aa32d/Babel-2.15.0-py3-none-any.whl#sha256=08706bdad8d0a3413266ab61bd6c34d0c28d6e1e7badf40a2cebe67644e2e1fb\n # pip certifi @ https://files.pythonhosted.org/packages/1c/d5/c84e1a17bf61d4df64ca866a1c9a913874b4e9bdc131ec689a0ad013fb36/certifi-2024.7.4-py3-none-any.whl#sha256=c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90\n # pip charset-normalizer @ https://files.pythonhosted.org/packages/98/69/5d8751b4b670d623aa7a47bef061d69c279e9f922f6705147983aa76c3ce/charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl#sha256=b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796\n@@ -63,6 +64,7 @@ https://repo.anaconda.com/pkgs/main/linux-64/pip-24.0-py39h06a4308_0.conda#7f8ce\n # pip tzdata @ https://files.pythonhosted.org/packages/65/58/f9c9e6be752e9fcb8b6a0ee9fb87e6e7a1f6bcab2cdc73f02bb7ba91ada0/tzdata-2024.1-py2.py3-none-any.whl#sha256=9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252\n # pip urllib3 @ https://files.pythonhosted.org/packages/ca/1c/89ffc63a9605b583d5df2be791a27bc1a42b7c32bab68d3c8f2f73a98cd4/urllib3-2.2.2-py3-none-any.whl#sha256=a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472\n # pip zipp @ https://files.pythonhosted.org/packages/20/38/f5c473fe9b90c8debdd29ea68d5add0289f1936d6f923b6b9cc0b931194c/zipp-3.19.2-py3-none-any.whl#sha256=f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c\n+# pip array-api-strict @ https://files.pythonhosted.org/packages/08/06/aba69bce257fd1cda0d1db616c12728af0f46878a5cc1923fcbb94201947/array_api_strict-2.0.1-py3-none-any.whl#sha256=f74cbf0d0c182fcb45c5ee7f28f9c7b77e6281610dfbbdd63be60b1a5a7872b3\n # pip contourpy @ https://files.pythonhosted.org/packages/31/a2/2f12e3a6e45935ff694654b710961b03310b0e1ec997ee9f416d3c873f87/contourpy-1.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl#sha256=e1d59258c3c67c865435d8fbeb35f8c59b8bef3d6f46c1f29f6123556af28445\n # pip coverage @ https://files.pythonhosted.org/packages/c4/b4/0cbc18998613f8caaec793ad5878d2450382dfac80e65d352fb7cd9cc1dc/coverage-7.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl#sha256=dbc5958cb471e5a5af41b0ddaea96a37e74ed289535e8deca404811f6cb0bc3d\n # pip imageio @ https://files.pythonhosted.org/packages/3d/84/f1647217231f6cc46883e5d26e870cc3e1520d458ecd52d6df750810d53c/imageio-2.34.2-py3-none-any.whl#sha256=a0bb27ec9d5bab36a9f4835e51b21d2cb099e1f78451441f94687ff3404b79f8\ndiff --git a/sklearn/utils/tests/test_array_api.py b/sklearn/utils/tests/test_array_api.py\nindex beff36499fb92..71f499f7a8dae 100644\n--- a/sklearn/utils/tests/test_array_api.py\n+++ b/sklearn/utils/tests/test_array_api.py\n@@ -34,7 +34,7 @@\n assert_array_equal,\n skip_if_array_api_compat_not_configured,\n )\n-from sklearn.utils.fixes import _IS_32BIT, CSR_CONTAINERS\n+from sklearn.utils.fixes import _IS_32BIT, CSR_CONTAINERS, np_version, parse_version\n \n \n @pytest.mark.parametrize(\"X\", [numpy.asarray([1, 2, 3]), [1, 2, 3]])\n@@ -67,7 +67,12 @@ def test_get_namespace_ndarray_with_dispatch():\n with config_context(array_api_dispatch=True):\n xp_out, is_array_api_compliant = get_namespace(X_np)\n assert is_array_api_compliant\n- assert xp_out is array_api_compat.numpy\n+ if np_version >= parse_version(\"2.0.0\"):\n+ # NumPy 2.0+ is an array API compliant library.\n+ assert xp_out is numpy\n+ else:\n+ # Older NumPy versions require the compatibility layer.\n+ assert xp_out is array_api_compat.numpy\n \n \n @skip_if_array_api_compat_not_configured\n@@ -135,7 +140,7 @@ def test_asarray_with_order_ignored():\n \n \n @pytest.mark.parametrize(\n- \"array_namespace, device, dtype_name\", yield_namespace_device_dtype_combinations()\n+ \"array_namespace, device_, dtype_name\", yield_namespace_device_dtype_combinations()\n )\n @pytest.mark.parametrize(\n \"weights, axis, normalize, expected\",\n@@ -167,19 +172,22 @@ def test_asarray_with_order_ignored():\n ],\n )\n def test_average(\n- array_namespace, device, dtype_name, weights, axis, normalize, expected\n+ array_namespace, device_, dtype_name, weights, axis, normalize, expected\n ):\n- xp = _array_api_for_tests(array_namespace, device)\n+ xp = _array_api_for_tests(array_namespace, device_)\n array_in = numpy.asarray([[1, 2, 3], [4, 5, 6]], dtype=dtype_name)\n- array_in = xp.asarray(array_in, device=device)\n+ array_in = xp.asarray(array_in, device=device_)\n if weights is not None:\n weights = numpy.asarray(weights, dtype=dtype_name)\n- weights = xp.asarray(weights, device=device)\n+ weights = xp.asarray(weights, device=device_)\n \n with config_context(array_api_dispatch=True):\n result = _average(array_in, axis=axis, weights=weights, normalize=normalize)\n \n- assert getattr(array_in, \"device\", None) == getattr(result, \"device\", None)\n+ if np_version < parse_version(\"2.0.0\") or np_version >= parse_version(\"2.1.0\"):\n+ # NumPy 2.0 has a problem with the device attribute of scalar arrays:\n+ # https://github.com/numpy/numpy/issues/26850\n+ assert device(array_in) == device(result)\n \n result = _convert_to_numpy(result, xp)\n assert_allclose(result, expected, atol=_atol_for_type(dtype_name))\n@@ -226,14 +234,15 @@ def test_average_raises_with_wrong_dtype(array_namespace, device, dtype_name):\n (\n 0,\n [[1, 2]],\n- TypeError,\n- \"1D weights expected\",\n+ # NumPy 2 raises ValueError, NumPy 1 raises TypeError\n+ (ValueError, TypeError),\n+ \"weights\", # the message is different for NumPy 1 and 2...\n ),\n (\n 0,\n [1, 2, 3, 4],\n ValueError,\n- \"Length of weights\",\n+ \"weights\",\n ),\n (0, [-1, 1], ZeroDivisionError, \"Weights sum to zero, can't be normalized\"),\n ),\n@@ -580,18 +589,18 @@ def test_get_namespace_and_device():\n \n \n @pytest.mark.parametrize(\n- \"array_namespace, device, dtype_name\", yield_namespace_device_dtype_combinations()\n+ \"array_namespace, device_, dtype_name\", yield_namespace_device_dtype_combinations()\n )\n @pytest.mark.parametrize(\"csr_container\", CSR_CONTAINERS)\n @pytest.mark.parametrize(\"axis\", [0, 1, None, -1, -2])\n @pytest.mark.parametrize(\"sample_weight_type\", [None, \"int\", \"float\"])\n def test_count_nonzero(\n- array_namespace, device, dtype_name, csr_container, axis, sample_weight_type\n+ array_namespace, device_, dtype_name, csr_container, axis, sample_weight_type\n ):\n \n from sklearn.utils.sparsefuncs import count_nonzero as sparse_count_nonzero\n \n- xp = _array_api_for_tests(array_namespace, device)\n+ xp = _array_api_for_tests(array_namespace, device_)\n array = numpy.array([[0, 3, 0], [2, -1, 0], [0, 0, 0], [9, 8, 7], [4, 0, 5]])\n if sample_weight_type == \"int\":\n sample_weight = numpy.asarray([1, 2, 2, 3, 1])\n@@ -602,12 +611,16 @@ def test_count_nonzero(\n expected = sparse_count_nonzero(\n csr_container(array), axis=axis, sample_weight=sample_weight\n )\n- array_xp = xp.asarray(array, device=device)\n+ array_xp = xp.asarray(array, device=device_)\n \n with config_context(array_api_dispatch=True):\n result = _count_nonzero(\n- array_xp, xp=xp, device=device, axis=axis, sample_weight=sample_weight\n+ array_xp, xp=xp, device=device_, axis=axis, sample_weight=sample_weight\n )\n \n assert_allclose(_convert_to_numpy(result, xp=xp), expected)\n- assert getattr(array_xp, \"device\", None) == getattr(result, \"device\", None)\n+\n+ if np_version < parse_version(\"2.0.0\") or np_version >= parse_version(\"2.1.0\"):\n+ # NumPy 2.0 has a problem with the device attribute of scalar arrays:\n+ # https://github.com/numpy/numpy/issues/26850\n+ assert device(array_xp) == device(result)\n", "problem_statement": "Array API tests fail on main\n### Describe the bug\r\n\r\nI ran the Array API tests on main and got 10 failing tests. \r\n(Last week, with an older main and everything else the same, I had 4 failing tests.)\r\n\r\narray_api_compat==1.7.1\r\n\r\nI only ran the cpu tests.\r\n\r\n### Steps/Code to Reproduce\r\n\r\n`pytest sklearn/utils/tests/test_array_api.py`\r\n\r\n### Expected Results\r\n\r\nall tests pass\r\n\r\n### Actual Results\r\n\r\n```\r\nFAILED sklearn/utils/tests/test_array_api.py::test_get_namespace_ndarray_with_dispatch - AssertionError\r\nFAILED sklearn/utils/tests/test_array_api.py::test_average[None-None-False-21-numpy-None-None] - AssertionError\r\nFAILED sklearn/utils/tests/test_array_api.py::test_average_raises_with_invalid_parameters[0-weights1-TypeError-1D weights expected-numpy-None-None] - ValueError: Shape of weights must be consistent with shape of a along specified axis.\r\nFAILED sklearn/utils/tests/test_array_api.py::test_average_raises_with_invalid_parameters[0-weights2-ValueError-Length of weights-numpy-None-None] - AssertionError: Regex pattern did not match.\r\nFAILED sklearn/utils/tests/test_array_api.py::test_count_nonzero[None-None-csr_matrix-numpy-None-None] - AssertionError\r\nFAILED sklearn/utils/tests/test_array_api.py::test_count_nonzero[None-None-csr_array-numpy-None-None] - AssertionError\r\nFAILED sklearn/utils/tests/test_array_api.py::test_count_nonzero[int-None-csr_matrix-numpy-None-None] - AssertionError\r\nFAILED sklearn/utils/tests/test_array_api.py::test_count_nonzero[int-None-csr_array-numpy-None-None] - AssertionError\r\nFAILED sklearn/utils/tests/test_array_api.py::test_count_nonzero[float-None-csr_matrix-numpy-None-None] - AssertionError\r\nFAILED sklearn/utils/tests/test_array_api.py::test_count_nonzero[float-None-csr_array-numpy-None-None] - AssertionError\r\n```\r\n\r\n\r\n### Versions\r\n\r\n```shell\r\nSystem:\r\n python: 3.12.2 (main, Apr 18 2024, 11:14:27) [GCC 13.2.1 20230801]\r\nexecutable: /home/stefanie/.pyenv/versions/3.12.2/envs/scikit-learn_dev/bin/python\r\n machine: Linux-6.9.5-arch1-1-x86_64-with-glibc2.39\r\n\r\nPython dependencies:\r\n sklearn: 1.6.dev0\r\n pip: 24.0\r\n setuptools: 69.5.1\r\n numpy: 2.1.0.dev0\r\n scipy: 1.13.0\r\n Cython: 3.0.10\r\n pandas: 2.2.2\r\n matplotlib: 3.8.4\r\n joblib: 1.4.0\r\nthreadpoolctl: 3.4.0\r\n\r\nBuilt with OpenMP: True\r\n\r\nthreadpoolctl info:\r\n user_api: blas\r\n internal_api: openblas\r\n num_threads: 14\r\n prefix: libopenblas\r\n filepath: /home/stefanie/.pyenv/versions/3.12.2/envs/scikit-learn_dev/lib/python3.12/site-packages/scipy.libs/libopenblasp-r0-24bff013.3.26.dev.so\r\n version: 0.3.26.dev\r\nthreading_layer: pthreads\r\n architecture: Haswell\r\n\r\n user_api: openmp\r\n internal_api: openmp\r\n num_threads: 14\r\n prefix: libgomp\r\n filepath: /usr/lib/libgomp.so.1.0.0\r\n version: None\r\n```\r\n\r\nAlso just tested with numpy==2.0.0, and the same failures.\n", "hints_text": "I do have the same issues locally, it seems like the problem disappear when using `numpy<2` (tried with numpy 1.26.4) for some reason ...\r\n\r\nMaybe @betatim or @OmarManzoor have some suggestions about this and whether this is expected?\n@lesteve Might be because of some updates in the new version of numpy. Will have to check this. \nFor completeness, one of the potential reason this was not noticed before is that lock-file in the CI for the array API tests do use `1.26.4` (I think because we require `pytorch=1.13` that wants `numpy<2`):\r\n```\r\n\u276f rg numpy build_tools/azure/pylatest_conda_forge_mkl_linux-64_conda.lock \r\n209:https://conda.anaconda.org/conda-forge/linux-64/numpy-1.26.4-py311h64a7726_0.conda#a502d7aad449a1206efb366d6a12c52d\r\n```\nThe tests Stefanie ran don't require a GPU, so I thought that we run them in the normal CI as well.\r\n\r\nI'll investigate the failures. If someone else has time/interest to look at whether the tests run as part of the normal CI or not that would be nice. Otherwise I'll also look into that.\r\n\r\nIn general I feel like we need to up our game around the array API tests. My (perceived) impression is that they constantly surprise us with \"Ha, you thought the tests pass ... surprise! Tests don't actually pass!!\" - which is a bit of time suck because you have these unscheduled \"ok, lets fix up the tests once again\" sessions :-/\nxref https://github.com/numpy/numpy/issues/26850 which I found while working on this\r\n\nWaiting for reactions on the above issue before trying to fix things. We could remove the check that the thing returned from `xp.sum` is on the same device as the input. But it isn't clear to me if what we see is a bug in Numpy or us being too strict in our check.\n> In general I feel like we need to up our game around the array API tests. My (perceived) impression is that they constantly surprise us with \"Ha, you thought the tests pass ... surprise! Tests don't actually pass!!\" - which is a bit of time suck because you have these unscheduled \"ok, lets fix up the tests once again\" sessions :-/\r\n\r\nI think part of this feeling is addressed by the CUDA CI. The other would be addressed by configuring a new PR job to run the tests on a GitHub Actions M1 worker but this requires some efforts.\r\n\r\nThe fact that pytorch is holding back on a numpy<1 is temporary. I don't think we should blame our CI config for this particular inability of our CI to tests with the latest numpy. The only alternative would be to have a second array API enabled CI config with only `array-api-strict`, `array-api-compat` as extra dependencies and nothing else. I am not sure if it's worth the extra CI config complexity though.\n> The only alternative would be to have a second array API enabled CI config with only array-api-strict, array-api-compat as extra dependencies and nothing else. I am not sure if it's worth the extra CI config complexity though.\r\n\r\nTim's opinion was that it probably makes sense to have it working for the latest versions IIUC: https://github.com/scikit-learn/scikit-learn/pull/29387#issuecomment-2206174765", "created_at": "2024-07-08T15:53:02Z" }, { "repo": "scikit-learn/scikit-learn", "pull_number": 29402, "instance_id": "scikit-learn__scikit-learn-29402", "issue_numbers": [ "29369" ], "base_commit": "4bdd398d56a5d248c788a09c69febd22c4a0ccff", "patch": "diff --git a/doc/whats_new/v1.6.rst b/doc/whats_new/v1.6.rst\nindex c98314d5ca1de..7e54b15b31641 100644\n--- a/doc/whats_new/v1.6.rst\n+++ b/doc/whats_new/v1.6.rst\n@@ -182,6 +182,9 @@ Changelog\n estimator without re-fitting it.\n :pr:`29067` by :user:`Guillaume Lemaitre `.\n \n+- |Fix| Improve error message when :func:`model_selection.RepeatedStratifiedKFold.split` is called without a `y` argument\n+ :pr:`29402` by :user:`Anurag Varma `.\n+\n :mod:`sklearn.neighbors`\n ........................\n \ndiff --git a/sklearn/model_selection/_split.py b/sklearn/model_selection/_split.py\nindex d0a0252a9697e..f9aa6edecd9e2 100644\n--- a/sklearn/model_selection/_split.py\n+++ b/sklearn/model_selection/_split.py\n@@ -1769,6 +1769,43 @@ def __init__(self, *, n_splits=5, n_repeats=10, random_state=None):\n n_splits=n_splits,\n )\n \n+ def split(self, X, y, groups=None):\n+ \"\"\"Generate indices to split data into training and test set.\n+\n+ Parameters\n+ ----------\n+ X : array-like of shape (n_samples, n_features)\n+ Training data, where `n_samples` is the number of samples\n+ and `n_features` is the number of features.\n+\n+ Note that providing ``y`` is sufficient to generate the splits and\n+ hence ``np.zeros(n_samples)`` may be used as a placeholder for\n+ ``X`` instead of actual training data.\n+\n+ y : array-like of shape (n_samples,)\n+ The target variable for supervised learning problems.\n+ Stratification is done based on the y labels.\n+\n+ groups : object\n+ Always ignored, exists for compatibility.\n+\n+ Yields\n+ ------\n+ train : ndarray\n+ The training set indices for that split.\n+\n+ test : ndarray\n+ The testing set indices for that split.\n+\n+ Notes\n+ -----\n+ Randomized CV splitters may return different results for each call of\n+ split. You can make the results identical by setting `random_state`\n+ to an integer.\n+ \"\"\"\n+ y = check_array(y, input_name=\"y\", ensure_2d=False, dtype=None)\n+ return super().split(X, y, groups=groups)\n+\n \n class BaseShuffleSplit(_MetadataRequester, metaclass=ABCMeta):\n \"\"\"Base class for *ShuffleSplit.\n", "test_patch": "diff --git a/sklearn/model_selection/tests/test_split.py b/sklearn/model_selection/tests/test_split.py\nindex fa425a5e6a18b..4e594499ae59a 100644\n--- a/sklearn/model_selection/tests/test_split.py\n+++ b/sklearn/model_selection/tests/test_split.py\n@@ -86,6 +86,12 @@\n \n ALL_SPLITTERS = NO_GROUP_SPLITTERS + GROUP_SPLITTERS # type: ignore\n \n+SPLITTERS_REQUIRING_TARGET = [\n+ StratifiedKFold(),\n+ StratifiedShuffleSplit(),\n+ RepeatedStratifiedKFold(),\n+]\n+\n X = np.ones(10)\n y = np.arange(10) // 2\n test_groups = (\n@@ -2054,3 +2060,12 @@ def test_no_group_splitters_warns_with_groups(cv):\n \n with pytest.warns(UserWarning, match=msg):\n cv.split(X, y, groups=groups)\n+\n+\n+@pytest.mark.parametrize(\n+ \"cv\", SPLITTERS_REQUIRING_TARGET, ids=[str(cv) for cv in SPLITTERS_REQUIRING_TARGET]\n+)\n+def test_stratified_splitter_without_y(cv):\n+ msg = \"missing 1 required positional argument: 'y'\"\n+ with pytest.raises(TypeError, match=msg):\n+ cv.split(X)\n", "problem_statement": "Erroneous optional status for y parameter in RepeatedStratifiedKFold.split\n### Describe the bug\r\n\r\nFor context, there is a small difference in the `split` function between the variants of the `KFold` class:\r\n\r\nIn class `sklearn.model_selection.KFold`, the [split function](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.KFold.html#sklearn.model_selection.KFold.split) has an optional parameter `y` (same for class [`sklearn.model_selection.RepeatedKFold`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.RepeatedKFold.html#sklearn.model_selection.RepeatedKFold.split)).\r\n\r\nIn class `sklearn.model_selection.StratifiedKFold`, the same [parameter `y` is mandatory](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.StratifiedKFold.html#sklearn.model_selection.StratifiedKFold.split) because [\"Stratification is done based on the y labels\"](https://github.com/scikit-learn/scikit-learn/blob/2621573e6/sklearn/model_selection/_split.py#L813). As expected, omitting `y` when calling `split` causes an explicit error:\r\n\r\n```\r\nTypeError: StratifiedKFold.split() missing 1 required positional argument: 'y'\r\n```\r\n\r\nHowever [`sklearn.model_selection.RepeatedStratifiedKFold`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.RepeatedStratifiedKFold.html) is also a stratified variant which requires parameter `y`, but the parameter is [erroneously left as optional](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.RepeatedStratifiedKFold.html#sklearn.model_selection.RepeatedStratifiedKFold.split). This seems due to the fact this is implemented through a general class [`_UnsupportedGroupCVMixin`](https://github.com/scikit-learn/scikit-learn/blob/2621573e6/sklearn/model_selection/_split.py#L67). As a result, not providing `y` causes an unclear error message inconsistent with the one for `StratifiedKFold` in the same context.\r\n\r\n\r\n\r\n\r\n\r\n### Steps/Code to Reproduce\r\n\r\n```py\r\nfrom sklearn.model_selection import KFold, RepeatedKFold, StratifiedKFold, RepeatedStratifiedKFold\r\n\r\nx = [ 'a '] * 100\r\ny = [ 0 ] * 90 + [ 1 ] * 10\r\n\r\n# y is NOT optional -> error 'missing 1 required positional argument: 'y'' as expected \r\nfor i, (train, test) in enumerate(StratifiedKFold(n_splits=2).split(x)):\r\n print('i =', i, '. train =', train)\r\n print('i =', i, '. test =', test)\r\n\r\n# y is supposed to be optional according to documentation, but not providing it causes an unclear error message \r\nfor i, (train, test) in enumerate(RepeatedStratifiedKFold(n_splits=2, n_repeats=3).split(x)):\r\n print('i =', i, '. train =', train)\r\n print('i =', i, '. test =', test)\r\n```\r\n\r\n\r\n### Expected Results\r\n\r\nSame error message 'missing 1 required positional argument' in both cases.\r\n\r\n### Actual Results\r\n\r\nexpected error in first example, erroneous behavior in second example.\r\n\r\n### Versions\r\n\r\n```shell\r\nSystem:\r\n python: 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0]\r\nexecutable: /usr/bin/python3\r\n machine: Linux-6.1.85+-x86_64-with-glibc2.35\r\n\r\nPython dependencies:\r\n sklearn: 1.2.2\r\n pip: 23.1.2\r\n setuptools: 67.7.2\r\n numpy: 1.25.2\r\n scipy: 1.11.4\r\n Cython: 3.0.10\r\n pandas: 2.0.3\r\n matplotlib: 3.7.1\r\n joblib: 1.4.2\r\nthreadpoolctl: 3.5.0\r\n\r\nBuilt with OpenMP: True\r\n\r\nthreadpoolctl info:\r\n user_api: blas\r\n internal_api: openblas\r\n num_threads: 2\r\n prefix: libopenblas\r\n filepath: /usr/local/lib/python3.10/dist-packages/numpy.libs/libopenblas64_p-r0-5007b62f.3.23.dev.so\r\n version: 0.3.23.dev\r\nthreading_layer: pthreads\r\n architecture: Haswell\r\n\r\n user_api: openmp\r\n internal_api: openmp\r\n num_threads: 2\r\n prefix: libgomp\r\n filepath: /usr/local/lib/python3.10/dist-packages/scikit_learn.libs/libgomp-a34b3233.so.1.0.0\r\n version: None\r\n\r\n user_api: blas\r\n internal_api: openblas\r\n num_threads: 2\r\n prefix: libopenblas\r\n filepath: /usr/local/lib/python3.10/dist-packages/scipy.libs/libopenblasp-r0-23e5df77.3.21.dev.so\r\n version: 0.3.21.dev\r\nthreading_layer: pthreads\r\n architecture: Haswell\r\n```\r\n\n", "hints_text": "Thanks for the issue, indeed it looks like a genuine problem. A PR fixing this and adding a test would be more than welcome. I am going to set the label \"Help wanted\" and \"Easy\".\n/take\n@Anurag-Varma Not sure why but the `/take` thing does not seem to work I assigned manually the issue to you", "created_at": "2024-07-03T14:06:50Z" }, { "repo": "scikit-learn/scikit-learn", "pull_number": 29401, "instance_id": "scikit-learn__scikit-learn-29401", "issue_numbers": [ "29361" ], "base_commit": "ecdc9570953f21d702e3e96394133abdad8e3b6a", "patch": "diff --git a/doc/whats_new/v1.5.rst b/doc/whats_new/v1.5.rst\nindex 20184bbd2a551..059875eec12d6 100644\n--- a/doc/whats_new/v1.5.rst\n+++ b/doc/whats_new/v1.5.rst\n@@ -13,6 +13,23 @@ For a short description of the main highlights of the release, please refer to\n \n .. include:: changelog_legend.inc\n \n+.. _changes_1_5_2:\n+\n+Version 1.5.2\n+=============\n+\n+**release date of 1.5.2**\n+\n+Changelog\n+---------\n+\n+:mod:`sklearn.compose`\n+......................\n+\n+- |Fix| Fixed :class:`compose.TransformedTargetRegressor` not to raise `UserWarning` if\n+ transform output is set to `pandas` or `polars`, since it isn't a transformer.\n+ :pr:`29401` by :user:`Stefanie Senger `.\n+\n .. _changes_1_5_1:\n \n Version 1.5.1\ndiff --git a/sklearn/compose/_target.py b/sklearn/compose/_target.py\nindex ac33957b23ce2..db53eb9be9e65 100644\n--- a/sklearn/compose/_target.py\n+++ b/sklearn/compose/_target.py\n@@ -198,6 +198,10 @@ def _fit_transformer(self, y):\n validate=True,\n check_inverse=self.check_inverse,\n )\n+ # We are transforming the target here and not the features, so we set the\n+ # output of FunctionTransformer() to be a numpy array (default) and to not\n+ # depend on the global configuration:\n+ self.transformer_.set_output(transform=\"default\")\n # XXX: sample_weight is not currently passed to the\n # transformer. However, if transformer starts using sample_weight, the\n # code should be modified accordingly. At the time to consider the\n", "test_patch": "diff --git a/sklearn/compose/tests/test_target.py b/sklearn/compose/tests/test_target.py\nindex a971553b64739..fd885459e76d1 100644\n--- a/sklearn/compose/tests/test_target.py\n+++ b/sklearn/compose/tests/test_target.py\n@@ -1,7 +1,9 @@\n+import warnings\n+\n import numpy as np\n import pytest\n \n-from sklearn import datasets\n+from sklearn import config_context, datasets\n from sklearn.base import BaseEstimator, TransformerMixin, clone\n from sklearn.compose import TransformedTargetRegressor\n from sklearn.dummy import DummyRegressor\n@@ -393,3 +395,18 @@ def test_transform_target_regressor_pass_extra_predict_parameters():\n regr.fit(X, y)\n regr.predict(X, check_input=False)\n assert regr.regressor_.predict_called\n+\n+\n+@pytest.mark.parametrize(\"output_format\", [\"pandas\", \"polars\"])\n+def test_transform_target_regressor_not_warns_with_global_output_set(output_format):\n+ \"\"\"Test that TransformedTargetRegressor will not raise warnings if\n+ set_config(transform_output=\"pandas\"/\"polars\") is set globally; regression test for\n+ issue #29361.\"\"\"\n+ X, y = datasets.make_regression()\n+ y = np.abs(y) + 1\n+ with config_context(transform_output=output_format):\n+ with warnings.catch_warnings():\n+ warnings.simplefilter(\"error\")\n+ TransformedTargetRegressor(\n+ regressor=LinearRegression(), func=np.log, inverse_func=np.exp\n+ ).fit(X, y)\n", "problem_statement": "TransformedTargetRegressor warns about set_output set to pandas\n### Describe the bug\n\nIf `set_output` is set to `\"pandas\"`, `TransformedTargetRegressor` warns unnecessarily.\n\n### Steps/Code to Reproduce\n\n```python\r\nimport numpy as np\r\nimport pandas as pd\r\nfrom sklearn import set_config\r\nfrom sklearn.compose import TransformedTargetRegressor\r\nfrom sklearn.datasets import make_regression\r\nfrom sklearn.linear_model import LinearRegression\r\n\r\nset_config(transform_output=\"pandas\")\r\nX, y = make_regression()\r\ny = np.abs(y) + 1\r\nTransformedTargetRegressor(\r\n regressor=LinearRegression(),\r\n func=np.log,\r\n inverse_func=np.exp,\r\n).fit(X, y)\r\n```\n\n### Expected Results\n\nNo warning.\n\n### Actual Results\n\n3 times the same warning:\r\n```\r\npython3.11/site-packages/sklearn/preprocessing/_function_transformer.py:303: UserWarning: When `set_output` is configured to be 'pandas', `func` should return a pandas DataFrame to follow the `set_output` API or `feature_names_out` should be defined.\r\n warnings.warn(warn_msg.format(\"pandas\"))\r\n```\n\n### Versions\n\n```shell\nSystem:\r\n python: 3.11.7\r\nPython dependencies:\r\n sklearn: 1.5.0\r\n pandas: 2.2.2\n```\n\n", "hints_text": "Thanks for the reproducer, this seems like an issue indeed, I'll try to take a closer look soonish.", "created_at": "2024-07-03T13:00:00Z" }, { "repo": "scikit-learn/scikit-learn", "pull_number": 29314, "instance_id": "scikit-learn__scikit-learn-29314", "issue_numbers": [ "29277" ], "base_commit": "619a1c1028335e9fa7abd4d7fb6477200a4bce67", "patch": "diff --git a/doc/whats_new/v1.5.rst b/doc/whats_new/v1.5.rst\nindex 7a09b5f3a5809..964b59177fd2d 100644\n--- a/doc/whats_new/v1.5.rst\n+++ b/doc/whats_new/v1.5.rst\n@@ -56,6 +56,10 @@ Changelog\n grids that have estimators as parameter values.\n :pr:`29179` by :user:`Marco Gorelli`.\n \n+- |Fix| Fix a regression in :class:`model_selection.GridSearchCV` for parameter\n+ grids that have arrays of different sizes as parameter values.\n+ :pr:`29314` by :user:`Marco Gorelli`.\n+\n :mod:`sklearn.tree`\n ...................\n \ndiff --git a/sklearn/model_selection/_search.py b/sklearn/model_selection/_search.py\nindex 110db2c39a4a2..1fa85808b91d1 100644\n--- a/sklearn/model_selection/_search.py\n+++ b/sklearn/model_selection/_search.py\n@@ -379,6 +379,56 @@ def check(self):\n return check\n \n \n+def _yield_masked_array_for_each_param(candidate_params):\n+ \"\"\"\n+ Yield a masked array for each candidate param.\n+\n+ `candidate_params` is a sequence of params which were used in\n+ a `GridSearchCV`. We use masked arrays for the results, as not\n+ all params are necessarily present in each element of\n+ `candidate_params`. For example, if using `GridSearchCV` with\n+ a `SVC` model, then one might search over params like:\n+\n+ - kernel=[\"rbf\"], gamma=[0.1, 1]\n+ - kernel=[\"poly\"], degree=[1, 2]\n+\n+ and then param `'gamma'` would not be present in entries of\n+ `candidate_params` corresponding to `kernel='poly'`.\n+ \"\"\"\n+ n_candidates = len(candidate_params)\n+ param_results = defaultdict(dict)\n+\n+ for cand_idx, params in enumerate(candidate_params):\n+ for name, value in params.items():\n+ param_results[\"param_%s\" % name][cand_idx] = value\n+\n+ for key, param_result in param_results.items():\n+ param_list = list(param_result.values())\n+ try:\n+ arr = np.array(param_list)\n+ except ValueError:\n+ # This can happen when param_list contains lists of different\n+ # lengths, for example:\n+ # param_list=[[1], [2, 3]]\n+ arr_dtype = np.dtype(object)\n+ else:\n+ # There are two cases when we don't use the automatically inferred\n+ # dtype when creating the array and we use object instead:\n+ # - string dtype\n+ # - when array.ndim > 1, that means that param_list was something\n+ # like a list of same-size sequences, which gets turned into a\n+ # multi-dimensional array but we want a 1d array\n+ arr_dtype = arr.dtype if arr.dtype.kind != \"U\" and arr.ndim == 1 else object\n+\n+ # Use one MaskedArray and mask all the places where the param is not\n+ # applicable for that candidate (which may not contain all the params).\n+ ma = MaskedArray(np.empty(n_candidates), mask=True, dtype=arr_dtype)\n+ for index, value in param_result.items():\n+ # Setting the value at an index unmasks that index\n+ ma[index] = value\n+ yield (key, ma)\n+\n+\n class BaseSearchCV(MetaEstimatorMixin, BaseEstimator, metaclass=ABCMeta):\n \"\"\"Abstract base class for hyper parameter search with cross-validation.\"\"\"\n \n@@ -1079,45 +1129,9 @@ def _store(key_name, array, weights=None, splits=False, rank=False):\n \n _store(\"fit_time\", out[\"fit_time\"])\n _store(\"score_time\", out[\"score_time\"])\n- param_results = defaultdict(dict)\n- for cand_idx, params in enumerate(candidate_params):\n- for name, value in params.items():\n- param_results[\"param_%s\" % name][cand_idx] = value\n- for key, param_result in param_results.items():\n- param_list = list(param_result.values())\n- try:\n- with warnings.catch_warnings():\n- warnings.filterwarnings(\n- \"ignore\",\n- message=\"in the future the `.dtype` attribute\",\n- category=DeprecationWarning,\n- )\n- # Warning raised by NumPy 1.20+\n- arr_dtype = np.result_type(*param_list)\n- except (TypeError, ValueError):\n- arr_dtype = np.dtype(object)\n- else:\n- if any(np.min_scalar_type(x) == object for x in param_list):\n- # `np.result_type` might get thrown off by `.dtype` properties\n- # (which some estimators have).\n- # If finding the result dtype this way would give object,\n- # then we use object.\n- # https://github.com/scikit-learn/scikit-learn/issues/29157\n- arr_dtype = np.dtype(object)\n- if len(param_list) == n_candidates and arr_dtype != object:\n- # Exclude `object` else the numpy constructor might infer a list of\n- # tuples to be a 2d array.\n- results[key] = MaskedArray(param_list, mask=False, dtype=arr_dtype)\n- else:\n- # Use one MaskedArray and mask all the places where the param is not\n- # applicable for that candidate (which may not contain all the params).\n- ma = MaskedArray(np.empty(n_candidates), mask=True, dtype=arr_dtype)\n- for index, value in param_result.items():\n- # Setting the value at an index unmasks that index\n- ma[index] = value\n- results[key] = ma\n-\n # Store a list of param dicts at the key 'params'\n+ for param, ma in _yield_masked_array_for_each_param(candidate_params):\n+ results[param] = ma\n results[\"params\"] = candidate_params\n \n test_scores_dict = _normalize_score_results(out[\"test_scores\"])\n", "test_patch": "diff --git a/sklearn/model_selection/tests/test_search.py b/sklearn/model_selection/tests/test_search.py\nindex cb7fc8992a7cc..77b99747dd4be 100644\n--- a/sklearn/model_selection/tests/test_search.py\n+++ b/sklearn/model_selection/tests/test_search.py\n@@ -61,12 +61,20 @@\n StratifiedShuffleSplit,\n train_test_split,\n )\n-from sklearn.model_selection._search import BaseSearchCV\n+from sklearn.model_selection._search import (\n+ BaseSearchCV,\n+ _yield_masked_array_for_each_param,\n+)\n from sklearn.model_selection.tests.common import OneTimeSplitter\n from sklearn.naive_bayes import ComplementNB\n from sklearn.neighbors import KernelDensity, KNeighborsClassifier, LocalOutlierFactor\n-from sklearn.pipeline import Pipeline\n-from sklearn.preprocessing import OneHotEncoder, OrdinalEncoder, StandardScaler\n+from sklearn.pipeline import Pipeline, make_pipeline\n+from sklearn.preprocessing import (\n+ OneHotEncoder,\n+ OrdinalEncoder,\n+ SplineTransformer,\n+ StandardScaler,\n+)\n from sklearn.svm import SVC, LinearSVC\n from sklearn.tests.metadata_routing_common import (\n ConsumingScorer,\n@@ -2724,6 +2732,37 @@ def test_search_with_estimators_issue_29157():\n assert grid_search.cv_results_[\"param_enc__enc\"].dtype == object\n \n \n+def test_cv_results_multi_size_array():\n+ \"\"\"Check that GridSearchCV works with params that are arrays of different sizes.\n+\n+ Non-regression test for #29277.\n+ \"\"\"\n+ n_features = 10\n+ X, y = make_classification(n_features=10)\n+\n+ spline_reg_pipe = make_pipeline(\n+ SplineTransformer(extrapolation=\"periodic\"),\n+ LogisticRegression(),\n+ )\n+\n+ n_knots_list = [n_features * i for i in [10, 11, 12]]\n+ knots_list = [\n+ np.linspace(0, np.pi * 2, n_knots).reshape((-1, n_features))\n+ for n_knots in n_knots_list\n+ ]\n+ spline_reg_pipe_cv = GridSearchCV(\n+ estimator=spline_reg_pipe,\n+ param_grid={\n+ \"splinetransformer__knots\": knots_list,\n+ },\n+ )\n+\n+ spline_reg_pipe_cv.fit(X, y)\n+ assert (\n+ spline_reg_pipe_cv.cv_results_[\"param_splinetransformer__knots\"].dtype == object\n+ )\n+\n+\n @pytest.mark.parametrize(\n \"array_namespace, device, dtype\", yield_namespace_device_dtype_combinations()\n )\n@@ -2747,3 +2786,77 @@ def test_array_api_search_cv_classifier(SearchCV, array_namespace, device, dtype\n )\n searcher.fit(X_xp, y_xp)\n searcher.score(X_xp, y_xp)\n+\n+\n+# Construct these outside the tests so that the same object is used\n+# for both input and `expected`\n+one_hot_encoder = OneHotEncoder()\n+ordinal_encoder = OrdinalEncoder()\n+\n+# If we construct this directly via `MaskedArray`, the list of tuples\n+# gets auto-converted to a 2D array.\n+ma_with_tuples = np.ma.MaskedArray(np.empty(2), mask=True, dtype=object)\n+ma_with_tuples[0] = (1, 2)\n+ma_with_tuples[1] = (3, 4)\n+\n+\n+@pytest.mark.parametrize(\n+ (\"candidate_params\", \"expected\"),\n+ [\n+ pytest.param(\n+ [{\"foo\": 1}, {\"foo\": 2}],\n+ [\n+ (\"param_foo\", np.ma.MaskedArray(np.array([1, 2]))),\n+ ],\n+ id=\"simple numeric, single param\",\n+ ),\n+ pytest.param(\n+ [{\"foo\": 1, \"bar\": 3}, {\"foo\": 2, \"bar\": 4}, {\"foo\": 3}],\n+ [\n+ (\"param_foo\", np.ma.MaskedArray(np.array([1, 2, 3]))),\n+ (\n+ \"param_bar\",\n+ np.ma.MaskedArray(np.array([3, 4, 0]), mask=[False, False, True]),\n+ ),\n+ ],\n+ id=\"simple numeric, one param is missing in one round\",\n+ ),\n+ pytest.param(\n+ [{\"foo\": [[1], [2], [3]]}, {\"foo\": [[1], [2]]}],\n+ [\n+ (\n+ \"param_foo\",\n+ np.ma.MaskedArray([[[1], [2], [3]], [[1], [2]]], dtype=object),\n+ ),\n+ ],\n+ id=\"lists of different lengths\",\n+ ),\n+ pytest.param(\n+ [{\"foo\": (1, 2)}, {\"foo\": (3, 4)}],\n+ [\n+ (\n+ \"param_foo\",\n+ ma_with_tuples,\n+ ),\n+ ],\n+ id=\"lists tuples\",\n+ ),\n+ pytest.param(\n+ [{\"foo\": ordinal_encoder}, {\"foo\": one_hot_encoder}],\n+ [\n+ (\n+ \"param_foo\",\n+ np.ma.MaskedArray([ordinal_encoder, one_hot_encoder], dtype=object),\n+ ),\n+ ],\n+ id=\"estimators\",\n+ ),\n+ ],\n+)\n+def test_yield_masked_array_for_each_param(candidate_params, expected):\n+ result = list(_yield_masked_array_for_each_param(candidate_params))\n+ for (key, value), (expected_key, expected_value) in zip(result, expected):\n+ assert key == expected_key\n+ assert value.dtype == expected_value.dtype\n+ np.testing.assert_array_equal(value, expected_value)\n+ np.testing.assert_array_equal(value.mask, expected_value.mask)\n", "problem_statement": "GridSearchCV fails when parameters are arrays with different sizes\n### Describe the bug\r\n\r\n[`SplineTransformer`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.SplineTransformer.html) accepts arrays for the `knots` argument to specify the positions of the knots. \r\n\r\nUsing [`GridSearchCV`](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.GridSearchCV.html) to find the best positions fails if the `knots` array has a different size (i.e. if there is a different `n_knots`). This appears to be because the code attempts to coerce the parameters into one array, and therefore fails due to the inhomogeneous shape. \r\n\r\nNote: sklearn versions - this error only occurs in recent versions of sklearn (1.5.0). Earlier versions (1.4.2) did not suffer from this issue. \r\n\r\nNote 2: the issue would be avoided if the `n_knots` parameter were to be searched over (instead of the `knots` parameter). However, it is often important to specify the knots positions directly - for example, with periodic data, as in the provided example, as the periodicity is defined by the first and last knots. In any case there are presumably other places in sklearn where arrays of different shapes can be provided as parameters and where the same issue will occur.\r\n\r\n\r\n### Steps/Code to Reproduce\r\n\r\n```python\r\nimport numpy as np\r\n\r\nimport sklearn.pipeline\r\nimport sklearn.preprocessing\r\nimport sklearn.model_selection\r\nimport sklearn.linear_model\r\n\r\nimport matplotlib.pyplot as plt\r\n\r\nx = np.linspace(-np.pi*2,np.pi*5,1000)\r\ny_true = np.sin(x)\r\ny_train = y_true[(0 26 spline_reg_pipe_cv.fit(X=x_train, y=y_train_noise)\r\n 28 plt.scatter(x_train, y_train_noise, s=1, label='noisy data')\r\n 29 plt.plot(x, y_true, label='truth')\r\n\r\nFile ~/Library/Python/3.12/lib/python/site-packages/sklearn/base.py:1473, in _fit_context..decorator..wrapper(estimator, *args, **kwargs)\r\n 1466 estimator._validate_params()\r\n 1468 with config_context(\r\n 1469 skip_parameter_validation=(\r\n 1470 prefer_skip_nested_validation or global_skip_validation\r\n 1471 )\r\n 1472 ):\r\n-> 1473 return fit_method(estimator, *args, **kwargs)\r\n\r\nFile ~/Library/Python/3.12/lib/python/site-packages/sklearn/model_selection/_search.py:968, in BaseSearchCV.fit(self, X, y, **params)\r\n 962 results = self._format_results(\r\n 963 all_candidate_params, n_splits, all_out, all_more_results\r\n 964 )\r\n 966 return results\r\n--> 968 self._run_search(evaluate_candidates)\r\n 970 # multimetric is determined here because in the case of a callable\r\n 971 # self.scoring the return type is only known after calling\r\n 972 first_test_score = all_out[0][\"test_scores\"]\r\n\r\nFile ~/Library/Python/3.12/lib/python/site-packages/sklearn/model_selection/_search.py:1543, in GridSearchCV._run_search(self, evaluate_candidates)\r\n 1541 def _run_search(self, evaluate_candidates):\r\n 1542 \"\"\"Search all candidates in param_grid\"\"\"\r\n-> 1543 evaluate_candidates(ParameterGrid(self.param_grid))\r\n\r\nFile ~/Library/Python/3.12/lib/python/site-packages/sklearn/model_selection/_search.py:962, in BaseSearchCV.fit..evaluate_candidates(candidate_params, cv, more_results)\r\n 959 all_more_results[key].extend(value)\r\n 961 nonlocal results\r\n--> 962 results = self._format_results(\r\n 963 all_candidate_params, n_splits, all_out, all_more_results\r\n 964 )\r\n 966 return results\r\n\r\nFile ~/Library/Python/3.12/lib/python/site-packages/sklearn/model_selection/_search.py:1098, in BaseSearchCV._format_results(self, candidate_params, n_splits, out, more_results)\r\n 1094 arr_dtype = object\r\n 1095 if len(param_list) == n_candidates and arr_dtype != object:\r\n 1096 # Exclude `object` else the numpy constructor might infer a list of\r\n 1097 # tuples to be a 2d array.\r\n-> 1098 results[key] = MaskedArray(param_list, mask=False, dtype=arr_dtype)\r\n 1099 else:\r\n 1100 # Use one MaskedArray and mask all the places where the param is not\r\n 1101 # applicable for that candidate (which may not contain all the params).\r\n 1102 ma = MaskedArray(np.empty(n_candidates), mask=True, dtype=arr_dtype)\r\n\r\nFile ~/Library/Python/3.12/lib/python/site-packages/numpy/ma/core.py:2820, in MaskedArray.__new__(cls, data, mask, dtype, copy, subok, ndmin, fill_value, keep_mask, hard_mask, shrink, order)\r\n 2811 \"\"\"\r\n 2812 Create a new masked array from scratch.\r\n 2813 \r\n (...)\r\n 2817 \r\n 2818 \"\"\"\r\n 2819 # Process data.\r\n-> 2820 _data = np.array(data, dtype=dtype, copy=copy,\r\n 2821 order=order, subok=True, ndmin=ndmin)\r\n 2822 _baseclass = getattr(data, '_baseclass', type(_data))\r\n 2823 # Check that we're not erasing the mask.\r\n\r\nValueError: setting an array element with a sequence. The requested array has an inhomogeneous shape after 1 dimensions. The detected shape was (9,) + inhomogeneous part.\r\n```\r\n\r\n### Versions\r\n\r\n```shell\r\nSystem:\r\n python: 3.12.3 (v3.12.3:f6650f9ad7, Apr 9 2024, 08:18:47) [Clang 13.0.0 (clang-1300.0.29.30)]\r\nexecutable: /usr/local/bin/python3\r\n machine: macOS-14.5-arm64-arm-64bit\r\n\r\nPython dependencies:\r\n sklearn: 1.5.0\r\n pip: 24.0\r\n setuptools: 70.0.0\r\n numpy: 1.26.4\r\n scipy: 1.13.0\r\n Cython: 3.0.10\r\n pandas: 2.2.2\r\n matplotlib: 3.8.4\r\n joblib: 1.4.2\r\nthreadpoolctl: 3.5.0\r\n\r\nBuilt with OpenMP: True\r\n\r\nthreadpoolctl info:\r\n user_api: blas\r\n internal_api: openblas\r\n num_threads: 11\r\n prefix: libopenblas\r\n filepath: /Users/gabriel.kissin/Library/Python/3.12/lib/python/site-packages/numpy/.dylibs/libopenblas64_.0.dylib\r\n version: 0.3.23.dev\r\nthreading_layer: pthreads\r\n architecture: armv8\r\n\r\n user_api: blas\r\n internal_api: openblas\r\n num_threads: 11\r\n prefix: libopenblas\r\n filepath: /Users/gabriel.kissin/Library/Python/3.12/lib/python/site-packages/scipy/.dylibs/libopenblas.0.dylib\r\n version: 0.3.26.dev\r\nthreading_layer: pthreads\r\n architecture: neoversen1\r\n\r\n user_api: openmp\r\n internal_api: openmp\r\n num_threads: 11\r\n prefix: libomp\r\n filepath: /Users/gabriel.kissin/Library/Python/3.12/lib/python/site-packages/sklearn/.dylibs/libomp.dylib\r\n version: None\r\n\r\n user_api: openmp\r\n internal_api: openmp\r\n num_threads: 11\r\n prefix: libomp\r\n filepath: /Users/gabriel.kissin/Library/Python/3.12/lib/python/site-packages/xgboost/.dylibs/libomp.dylib\r\n version: None\r\n\r\n user_api: openmp\r\n internal_api: openmp\r\n num_threads: 11\r\n prefix: libomp\r\n filepath: /opt/homebrew/Cellar/libomp/18.1.7/lib/libomp.dylib\r\n version: None\r\n```\r\n\n", "hints_text": "**Workaround:** looking at the [source code](https://github.com/scikit-learn/scikit-learn/blob/5080f2527c7af643f036290a9585bffdf296dfdc/sklearn/model_selection/_search.py#L1111) reveals a temporary fix - to turn the arrays into nested lists. Doing this signals to sklearn that these parameters should be treated as objects and not forced into one array. In the code example provided, changing lines 29-30 from \r\n```python\r\n 'splinetransformer__knots' : [np.linspace(0,np.pi*2,n_knots).reshape((-1,1))\r\n for n_knots in range(10,21,5)],\r\n```\r\nto \r\n```python\r\n 'splinetransformer__knots' : [np.linspace(0,np.pi*2,n_knots).reshape((-1,1)).tolist() \r\n for n_knots in range(10,21,5)],\r\n```\r\nsolves the issue. \r\n\r\nPerhaps this treatment should be extended to cover not only when parameters are clearly objects (when they are lists/tuples), but also whenever putting them into an array fails for whatever reason - as that failure can be interpreted as indication that they should be treated as objects.\nThanks for the report @Gabriel-Kissin. It looks like another side effect of https://github.com/scikit-learn/scikit-learn/pull/28352. Ping @MarcoGorelli for confirmation, and maybe propose a quick fix ?\noh no, not another one\r\n\r\ni'll take a look, thanks for the ping\nI just tried this out on `main`, commit 65b2571a3d1cd4b0460f3c3ddd5ad46df26f6e4a , and it doesn't error for me\r\n\r\nLooks like it's already fixed, and all that's needed is another release\nHum that's weird cause I'm able to reproduce the error on main, same commit :/\nthat's probably cause I don't practice building sklearn often enough, just went through the setup instructions again and can reproduce \ud83d\ude33 taking a look now \ud83d\udc40 \nfix incoming", "created_at": "2024-06-20T13:16:24Z" }, { "repo": "scikit-learn/scikit-learn", "pull_number": 29312, "instance_id": "scikit-learn__scikit-learn-29312", "issue_numbers": [ "7308" ], "base_commit": "5600faab452b0adb1f4090219012aa3ae6208c66", "patch": "diff --git a/doc/metadata_routing.rst b/doc/metadata_routing.rst\nindex fec2cf610c02f..9b21b74032562 100644\n--- a/doc/metadata_routing.rst\n+++ b/doc/metadata_routing.rst\n@@ -284,6 +284,8 @@ Meta-estimators and functions supporting metadata routing:\n - :class:`sklearn.ensemble.VotingRegressor`\n - :class:`sklearn.ensemble.BaggingClassifier`\n - :class:`sklearn.ensemble.BaggingRegressor`\n+- :class:`sklearn.feature_selection.RFE`\n+- :class:`sklearn.feature_selection.RFECV`\n - :class:`sklearn.feature_selection.SelectFromModel`\n - :class:`sklearn.feature_selection.SequentialFeatureSelector`\n - :class:`sklearn.impute.IterativeImputer`\n@@ -323,5 +325,3 @@ Meta-estimators and tools not supporting metadata routing yet:\n \n - :class:`sklearn.ensemble.AdaBoostClassifier`\n - :class:`sklearn.ensemble.AdaBoostRegressor`\n-- :class:`sklearn.feature_selection.RFE`\n-- :class:`sklearn.feature_selection.RFECV`\ndiff --git a/doc/whats_new/v1.6.rst b/doc/whats_new/v1.6.rst\nindex 74357c9171f10..6df2d9b2218bb 100644\n--- a/doc/whats_new/v1.6.rst\n+++ b/doc/whats_new/v1.6.rst\n@@ -104,6 +104,10 @@ more details.\n for the `fit` method of its estimator and for its underlying CV splitter and scorer.\n :pr:`29266` by :user:`Adam Li `.\n \n+- |Feature| :class:`feature_selection.RFE` and :class:`feature_selection.RFECV`\n+ now support metadata routing.\n+ :pr:`29312` by :user:`Omar Salman `.\n+\n Dropping support for building with setuptools\n ---------------------------------------------\n \ndiff --git a/sklearn/feature_selection/_rfe.py b/sklearn/feature_selection/_rfe.py\nindex 524c791be6989..8ccbffce9b15e 100644\n--- a/sklearn/feature_selection/_rfe.py\n+++ b/sklearn/feature_selection/_rfe.py\n@@ -10,38 +10,52 @@\n from joblib import effective_n_jobs\n \n from ..base import BaseEstimator, MetaEstimatorMixin, _fit_context, clone, is_classifier\n-from ..metrics import check_scoring\n+from ..metrics import get_scorer\n from ..model_selection import check_cv\n from ..model_selection._validation import _score\n-from ..utils._param_validation import HasMethods, Interval, RealNotInt\n-from ..utils.metadata_routing import (\n- _raise_for_unsupported_routing,\n- _RoutingNotSupportedMixin,\n+from ..utils import Bunch, metadata_routing\n+from ..utils._metadata_requests import (\n+ MetadataRouter,\n+ MethodMapping,\n+ _raise_for_params,\n+ _routing_enabled,\n+ process_routing,\n )\n+from ..utils._param_validation import HasMethods, Interval, RealNotInt\n from ..utils.metaestimators import _safe_split, available_if\n from ..utils.parallel import Parallel, delayed\n-from ..utils.validation import check_is_fitted\n+from ..utils.validation import (\n+ _check_method_params,\n+ _deprecate_positional_args,\n+ check_is_fitted,\n+)\n from ._base import SelectorMixin, _get_feature_importances\n \n \n-def _rfe_single_fit(rfe, estimator, X, y, train, test, scorer):\n+def _rfe_single_fit(rfe, estimator, X, y, train, test, scorer, routed_params):\n \"\"\"\n Return the score and n_features per step for a fit across one fold.\n \"\"\"\n X_train, y_train = _safe_split(estimator, X, y, train)\n X_test, y_test = _safe_split(estimator, X, y, test, train)\n+ fit_params = _check_method_params(\n+ X, params=routed_params.estimator.fit, indices=train\n+ )\n+ score_params = _check_method_params(\n+ X=X, params=routed_params.scorer.score, indices=test\n+ )\n \n rfe._fit(\n X_train,\n y_train,\n lambda estimator, features: _score(\n- # TODO(SLEP6): pass score_params here\n estimator,\n X_test[:, features],\n y_test,\n scorer,\n- score_params=None,\n+ score_params=score_params,\n ),\n+ **fit_params,\n )\n \n return rfe.step_scores_, rfe.step_n_features_\n@@ -66,7 +80,7 @@ def check(self):\n return check\n \n \n-class RFE(_RoutingNotSupportedMixin, SelectorMixin, MetaEstimatorMixin, BaseEstimator):\n+class RFE(SelectorMixin, MetaEstimatorMixin, BaseEstimator):\n \"\"\"Feature ranking with recursive feature elimination.\n \n Given an external estimator that assigns weights to features (e.g., the\n@@ -253,16 +267,31 @@ def fit(self, X, y, **fit_params):\n The target values.\n \n **fit_params : dict\n- Additional parameters passed to the `fit` method of the underlying\n- estimator.\n+ - If `enable_metadata_routing=False` (default):\n+\n+ Parameters directly passed to the ``fit`` method of the\n+ underlying estimator.\n+\n+ - If `enable_metadata_routing=True`:\n+\n+ Parameters safely routed to the ``fit`` method of the\n+ underlying estimator.\n+\n+ .. versionchanged:: 1.6\n+ See :ref:`Metadata Routing User Guide `\n+ for more details.\n \n Returns\n -------\n self : object\n Fitted estimator.\n \"\"\"\n- _raise_for_unsupported_routing(self, \"fit\", **fit_params)\n- return self._fit(X, y, **fit_params)\n+ if _routing_enabled():\n+ routed_params = process_routing(self, \"fit\", **fit_params)\n+ else:\n+ routed_params = Bunch(estimator=Bunch(fit=fit_params))\n+\n+ return self._fit(X, y, **routed_params.estimator.fit)\n \n def _fit(self, X, y, step_score=None, **fit_params):\n # Parameter step_score controls the calculation of self.step_scores_\n@@ -358,7 +387,7 @@ def _fit(self, X, y, step_score=None, **fit_params):\n return self\n \n @available_if(_estimator_has(\"predict\"))\n- def predict(self, X):\n+ def predict(self, X, **predict_params):\n \"\"\"Reduce X to the selected features and predict using the estimator.\n \n Parameters\n@@ -366,16 +395,35 @@ def predict(self, X):\n X : array of shape [n_samples, n_features]\n The input samples.\n \n+ **predict_params : dict\n+ Parameters to route to the ``predict`` method of the\n+ underlying estimator.\n+\n+ .. versionadded:: 1.6\n+ Only available if `enable_metadata_routing=True`,\n+ which can be set by using\n+ ``sklearn.set_config(enable_metadata_routing=True)``.\n+ See :ref:`Metadata Routing User Guide `\n+ for more details.\n+\n Returns\n -------\n y : array of shape [n_samples]\n The predicted target values.\n \"\"\"\n+ _raise_for_params(predict_params, self, \"predict\")\n check_is_fitted(self)\n- return self.estimator_.predict(self.transform(X))\n+ if _routing_enabled():\n+ routed_params = process_routing(self, \"predict\", **predict_params)\n+ else:\n+ routed_params = Bunch(estimator=Bunch(predict={}))\n+\n+ return self.estimator_.predict(\n+ self.transform(X), **routed_params.estimator.predict\n+ )\n \n @available_if(_estimator_has(\"score\"))\n- def score(self, X, y, **fit_params):\n+ def score(self, X, y, **score_params):\n \"\"\"Reduce X to the selected features and return the score of the estimator.\n \n Parameters\n@@ -386,11 +434,22 @@ def score(self, X, y, **fit_params):\n y : array of shape [n_samples]\n The target values.\n \n- **fit_params : dict\n- Parameters to pass to the `score` method of the underlying\n- estimator.\n+ **score_params : dict\n+ - If `enable_metadata_routing=False` (default):\n \n- .. versionadded:: 1.0\n+ Parameters directly passed to the ``score`` method of the\n+ underlying estimator.\n+\n+ .. versionadded:: 1.0\n+\n+ - If `enable_metadata_routing=True`:\n+\n+ Parameters safely routed to the `score` method of the\n+ underlying estimator.\n+\n+ .. versionchanged:: 1.6\n+ See :ref:`Metadata Routing User Guide `\n+ for more details.\n \n Returns\n -------\n@@ -399,7 +458,14 @@ def score(self, X, y, **fit_params):\n features returned by `rfe.transform(X)` and `y`.\n \"\"\"\n check_is_fitted(self)\n- return self.estimator_.score(self.transform(X), y, **fit_params)\n+ if _routing_enabled():\n+ routed_params = process_routing(self, \"score\", **score_params)\n+ else:\n+ routed_params = Bunch(estimator=Bunch(score=score_params))\n+\n+ return self.estimator_.score(\n+ self.transform(X), y, **routed_params.estimator.score\n+ )\n \n def _get_support_mask(self):\n check_is_fitted(self)\n@@ -478,6 +544,29 @@ def _more_tags(self):\n \n return tags\n \n+ def get_metadata_routing(self):\n+ \"\"\"Get metadata routing of this object.\n+\n+ Please check :ref:`User Guide ` on how the routing\n+ mechanism works.\n+\n+ .. versionadded:: 1.6\n+\n+ Returns\n+ -------\n+ routing : MetadataRouter\n+ A :class:`~sklearn.utils.metadata_routing.MetadataRouter` encapsulating\n+ routing information.\n+ \"\"\"\n+ router = MetadataRouter(owner=self.__class__.__name__).add(\n+ estimator=self.estimator,\n+ method_mapping=MethodMapping()\n+ .add(caller=\"fit\", callee=\"fit\")\n+ .add(caller=\"predict\", callee=\"predict\")\n+ .add(caller=\"score\", callee=\"score\"),\n+ )\n+ return router\n+\n \n class RFECV(RFE):\n \"\"\"Recursive feature elimination with cross-validation to select features.\n@@ -668,6 +757,7 @@ class RFECV(RFE):\n \"n_jobs\": [None, Integral],\n }\n _parameter_constraints.pop(\"n_features_to_select\")\n+ __metadata_request__fit = {\"groups\": metadata_routing.UNUSED}\n \n def __init__(\n self,\n@@ -690,11 +780,13 @@ def __init__(\n self.n_jobs = n_jobs\n self.min_features_to_select = min_features_to_select\n \n+ # TODO(1.8): remove `groups` from the signature after deprecation cycle.\n+ @_deprecate_positional_args(version=\"1.8\")\n @_fit_context(\n # RFECV.estimator is not validated yet\n prefer_skip_nested_validation=False\n )\n- def fit(self, X, y, groups=None):\n+ def fit(self, X, y, *, groups=None, **params):\n \"\"\"Fit the RFE model and automatically tune the number of selected features.\n \n Parameters\n@@ -714,12 +806,23 @@ def fit(self, X, y, groups=None):\n \n .. versionadded:: 0.20\n \n+ **params : dict of str -> object\n+ Parameters passed to the ``fit`` method of the estimator,\n+ the scorer, and the CV splitter.\n+\n+ ..versionadded:: 1.6\n+ Only available if `enable_metadata_routing=True`,\n+ which can be set by using\n+ ``sklearn.set_config(enable_metadata_routing=True)``.\n+ See :ref:`Metadata Routing User Guide `\n+ for more details.\n+\n Returns\n -------\n self : object\n Fitted estimator.\n \"\"\"\n- _raise_for_unsupported_routing(self, \"fit\", groups=groups)\n+ _raise_for_params(params, self, \"fit\")\n X, y = self._validate_data(\n X,\n y,\n@@ -729,9 +832,20 @@ def fit(self, X, y, groups=None):\n multi_output=True,\n )\n \n+ if _routing_enabled():\n+ if groups is not None:\n+ params.update({\"groups\": groups})\n+ routed_params = process_routing(self, \"fit\", **params)\n+ else:\n+ routed_params = Bunch(\n+ estimator=Bunch(fit={}),\n+ splitter=Bunch(split={\"groups\": groups}),\n+ scorer=Bunch(score={}),\n+ )\n+\n # Initialization\n cv = check_cv(self.cv, y, classifier=is_classifier(self.estimator))\n- scorer = check_scoring(self.estimator, scoring=self.scoring)\n+ scorer = self._get_scorer()\n \n # Build an RFE object, which will evaluate and score each possible\n # feature count, down to self.min_features_to_select\n@@ -772,8 +886,8 @@ def fit(self, X, y, groups=None):\n func = delayed(_rfe_single_fit)\n \n scores_features = parallel(\n- func(rfe, self.estimator, X, y, train, test, scorer)\n- for train, test in cv.split(X, y, groups)\n+ func(rfe, self.estimator, X, y, train, test, scorer, routed_params)\n+ for train, test in cv.split(X, y, **routed_params.splitter.split)\n )\n scores, step_n_features = zip(*scores_features)\n \n@@ -793,14 +907,14 @@ def fit(self, X, y, groups=None):\n verbose=self.verbose,\n )\n \n- rfe.fit(X, y)\n+ rfe.fit(X, y, **routed_params.estimator.fit)\n \n # Set final attributes\n self.support_ = rfe.support_\n self.n_features_ = rfe.n_features_\n self.ranking_ = rfe.ranking_\n self.estimator_ = clone(self.estimator)\n- self.estimator_.fit(self._transform(X), y)\n+ self.estimator_.fit(self._transform(X), y, **routed_params.estimator.fit)\n \n # reverse to stay consistent with before\n scores_rev = scores[:, ::-1]\n@@ -811,3 +925,81 @@ def fit(self, X, y, groups=None):\n \"n_features\": step_n_features_rev,\n }\n return self\n+\n+ def score(self, X, y, **score_params):\n+ \"\"\"Score using the `scoring` option on the given test data and labels.\n+\n+ Parameters\n+ ----------\n+ X : array-like of shape (n_samples, n_features)\n+ Test samples.\n+\n+ y : array-like of shape (n_samples,)\n+ True labels for X.\n+\n+ **score_params : dict\n+ Parameters to pass to the `score` method of the underlying scorer.\n+\n+ ..versionadded:: 1.6\n+ Only available if `enable_metadata_routing=True`,\n+ which can be set by using\n+ ``sklearn.set_config(enable_metadata_routing=True)``.\n+ See :ref:`Metadata Routing User Guide `\n+ for more details.\n+\n+ Returns\n+ -------\n+ score : float\n+ Score of self.predict(X) w.r.t. y defined by `scoring`.\n+ \"\"\"\n+ _raise_for_params(score_params, self, \"score\")\n+ scoring = self._get_scorer()\n+ if _routing_enabled():\n+ routed_params = process_routing(self, \"score\", **score_params)\n+ else:\n+ routed_params = Bunch()\n+ routed_params.scorer = Bunch(score={})\n+\n+ return scoring(self, X, y, **routed_params.scorer.score)\n+\n+ def get_metadata_routing(self):\n+ \"\"\"Get metadata routing of this object.\n+\n+ Please check :ref:`User Guide ` on how the routing\n+ mechanism works.\n+\n+ .. versionadded:: 1.6\n+\n+ Returns\n+ -------\n+ routing : MetadataRouter\n+ A :class:`~sklearn.utils.metadata_routing.MetadataRouter` encapsulating\n+ routing information.\n+ \"\"\"\n+ router = MetadataRouter(owner=self.__class__.__name__)\n+ router.add(\n+ estimator=self.estimator,\n+ method_mapping=MethodMapping().add(caller=\"fit\", callee=\"fit\"),\n+ )\n+ router.add(\n+ splitter=check_cv(self.cv),\n+ method_mapping=MethodMapping().add(\n+ caller=\"fit\",\n+ callee=\"split\",\n+ ),\n+ )\n+ router.add(\n+ scorer=self._get_scorer(),\n+ method_mapping=MethodMapping()\n+ .add(caller=\"fit\", callee=\"score\")\n+ .add(caller=\"score\", callee=\"score\"),\n+ )\n+\n+ return router\n+\n+ def _get_scorer(self):\n+ if self.scoring is None:\n+ scoring = \"accuracy\" if is_classifier(self.estimator) else \"r2\"\n+ else:\n+ scoring = self.scoring\n+ return get_scorer(scoring)\ndiff --git a/sklearn/metrics/_scorer.py b/sklearn/metrics/_scorer.py\nindex 76ad55514b8c2..f09b4e6d77442 100644\n--- a/sklearn/metrics/_scorer.py\n+++ b/sklearn/metrics/_scorer.py\n@@ -378,7 +378,10 @@ def _score(self, method_caller, estimator, X, y_true, **kwargs):\n pos_label = None if is_regressor(estimator) else self._get_pos_label()\n response_method = _check_response_method(estimator, self._response_method)\n y_pred = method_caller(\n- estimator, response_method.__name__, X, pos_label=pos_label\n+ estimator,\n+ _get_response_method_name(response_method),\n+ X,\n+ pos_label=pos_label,\n )\n \n scoring_kwargs = {**self._kwargs, **kwargs}\n@@ -651,6 +654,13 @@ def _get_response_method(response_method, needs_threshold, needs_proba):\n return response_method\n \n \n+def _get_response_method_name(response_method):\n+ try:\n+ return response_method.__name__\n+ except AttributeError:\n+ return _get_response_method_name(response_method.func)\n+\n+\n @validate_params(\n {\n \"score_func\": [callable],\n", "test_patch": "diff --git a/sklearn/feature_selection/tests/test_rfe.py b/sklearn/feature_selection/tests/test_rfe.py\nindex a0610e990054f..6e9acd7acc0ee 100644\n--- a/sklearn/feature_selection/tests/test_rfe.py\n+++ b/sklearn/feature_selection/tests/test_rfe.py\n@@ -26,7 +26,7 @@\n from sklearn.utils.fixes import CSR_CONTAINERS\n \n \n-class MockClassifier:\n+class MockClassifier(ClassifierMixin):\n \"\"\"\n Dummy classifier to test recursive feature elimination\n \"\"\"\n@@ -37,10 +37,11 @@ def __init__(self, foo_param=0):\n def fit(self, X, y):\n assert len(X) == len(y)\n self.coef_ = np.ones(X.shape[1], dtype=np.float64)\n+ self.classes_ = sorted(set(y))\n return self\n \n def predict(self, T):\n- return T.shape[0]\n+ return np.ones(T.shape[0])\n \n predict_proba = predict\n decision_function = predict\n@@ -666,3 +667,36 @@ def test_rfe_n_features_to_select_warning(ClsRFE, param):\n # larger than the number of features present in the X variable\n clsrfe = ClsRFE(estimator=LogisticRegression(), **{param: 21})\n clsrfe.fit(X, y)\n+\n+\n+def test_rfe_with_sample_weight():\n+ \"\"\"Test that `RFE` works correctly with sample weights.\"\"\"\n+ X, y = make_classification(random_state=0)\n+ n_samples = X.shape[0]\n+\n+ # Assign the first half of the samples with twice the weight\n+ sample_weight = np.ones_like(y)\n+ sample_weight[: n_samples // 2] = 2\n+\n+ # Duplicate the first half of the data samples to replicate the effect\n+ # of sample weights for comparison\n+ X2 = np.concatenate([X, X[: n_samples // 2]], axis=0)\n+ y2 = np.concatenate([y, y[: n_samples // 2]])\n+\n+ estimator = SVC(kernel=\"linear\")\n+\n+ rfe_sw = RFE(estimator=estimator, step=0.1)\n+ rfe_sw.fit(X, y, sample_weight=sample_weight)\n+\n+ rfe = RFE(estimator=estimator, step=0.1)\n+ rfe.fit(X2, y2)\n+\n+ assert_array_equal(rfe_sw.ranking_, rfe.ranking_)\n+\n+ # Also verify that when sample weights are not doubled the results\n+ # are different from the duplicated data\n+ rfe_sw_2 = RFE(estimator=estimator, step=0.1)\n+ sample_weight_2 = np.ones_like(y)\n+ rfe_sw_2.fit(X, y, sample_weight=sample_weight_2)\n+\n+ assert not np.array_equal(rfe_sw_2.ranking_, rfe.ranking_)\ndiff --git a/sklearn/tests/metadata_routing_common.py b/sklearn/tests/metadata_routing_common.py\nindex 5fffec8fccecf..174164daada8c 100644\n--- a/sklearn/tests/metadata_routing_common.py\n+++ b/sklearn/tests/metadata_routing_common.py\n@@ -201,6 +201,7 @@ def __init__(self, alpha=0.0):\n \n def fit(self, X, y):\n self.classes_ = np.unique(y)\n+ self.coef_ = np.ones_like(X)\n return self\n \n def partial_fit(self, X, y, classes=None):\n@@ -281,6 +282,7 @@ def fit(self, X, y, sample_weight=\"default\", metadata=\"default\"):\n )\n \n self.classes_ = np.unique(y)\n+ self.coef_ = np.ones_like(X)\n return self\n \n def predict(self, X, sample_weight=\"default\", metadata=\"default\"):\ndiff --git a/sklearn/tests/test_metaestimators.py b/sklearn/tests/test_metaestimators.py\nindex e06d2f59a6c10..9c12afd60c206 100644\n--- a/sklearn/tests/test_metaestimators.py\n+++ b/sklearn/tests/test_metaestimators.py\n@@ -40,6 +40,10 @@ def __init__(\n self.skip_methods = skip_methods\n \n \n+# For the following meta estimators we check for the existence of relevant\n+# methods only if the sub estimator also contains them. Any methods that\n+# are implemented in the meta estimator themselves and are not dependent\n+# on the sub estimator are specified in the `skip_methods` parameter.\n DELEGATING_METAESTIMATORS = [\n DelegatorData(\"Pipeline\", lambda est: Pipeline([(\"est\", est)])),\n DelegatorData(\n@@ -55,7 +59,9 @@ def __init__(\n skip_methods=[\"score\"],\n ),\n DelegatorData(\"RFE\", RFE, skip_methods=[\"transform\", \"inverse_transform\"]),\n- DelegatorData(\"RFECV\", RFECV, skip_methods=[\"transform\", \"inverse_transform\"]),\n+ DelegatorData(\n+ \"RFECV\", RFECV, skip_methods=[\"transform\", \"inverse_transform\", \"score\"]\n+ ),\n DelegatorData(\n \"BaggingClassifier\",\n BaggingClassifier,\ndiff --git a/sklearn/tests/test_metaestimators_metadata_routing.py b/sklearn/tests/test_metaestimators_metadata_routing.py\nindex 5c31361163689..614c8669592b4 100644\n--- a/sklearn/tests/test_metaestimators_metadata_routing.py\n+++ b/sklearn/tests/test_metaestimators_metadata_routing.py\n@@ -419,6 +419,26 @@ def enable_slep006():\n \"cv_name\": \"cv\",\n \"cv_routing_methods\": [\"fit\"],\n },\n+ {\n+ \"metaestimator\": RFE,\n+ \"estimator\": \"classifier\",\n+ \"estimator_name\": \"estimator\",\n+ \"X\": X,\n+ \"y\": y,\n+ \"estimator_routing_methods\": [\"fit\", \"predict\", \"score\"],\n+ },\n+ {\n+ \"metaestimator\": RFECV,\n+ \"estimator\": \"classifier\",\n+ \"estimator_name\": \"estimator\",\n+ \"estimator_routing_methods\": [\"fit\"],\n+ \"cv_name\": \"cv\",\n+ \"cv_routing_methods\": [\"fit\"],\n+ \"scorer_name\": \"scoring\",\n+ \"scorer_routing_methods\": [\"fit\", \"score\"],\n+ \"X\": X,\n+ \"y\": y,\n+ },\n ]\n \"\"\"List containing all metaestimators to be tested and their settings\n \n@@ -460,8 +480,6 @@ def enable_slep006():\n UNSUPPORTED_ESTIMATORS = [\n AdaBoostClassifier(),\n AdaBoostRegressor(),\n- RFE(ConsumingClassifier()),\n- RFECV(ConsumingClassifier()),\n ]\n \n \n", "problem_statement": "RFE/RFECV doesn't work with sample weights\nAs far as I can tell, `sklearn.feature_selection.RFE` has no way to pass sample weights to the estimator alongside the data.\r\n\r\nI have fixed this in my code with:\r\n\r\n``` diff\r\nindex bbe0cda..f5072b2 100644\r\n--- a/sklearn/feature_selection/rfe.py\r\n+++ b/sklearn/feature_selection/rfe.py\r\n@@ -120,7 +120,7 @@ class RFE(BaseEstimator, MetaEstimatorMixin, SelectorMixin):\r\n def _estimator_type(self):\r\n return self.estimator._estimator_type\r\n\r\n- def fit(self, X, y):\r\n+ def fit(self, X, y, **fit_params):\r\n \"\"\"Fit the RFE model and then the underlying estimator on the selected\r\n features.\r\n\r\n@@ -132,9 +132,9 @@ class RFE(BaseEstimator, MetaEstimatorMixin, SelectorMixin):\r\n y : array-like, shape = [n_samples]\r\n The target values.\r\n \"\"\"\r\n- return self._fit(X, y)\r\n+ return self._fit(X, y, **fit_params)\r\n\r\n- def _fit(self, X, y, step_score=None):\r\n+ def _fit(self, X, y, step_score=None, **fit_params):\r\n X, y = check_X_y(X, y, \"csc\")\r\n # Initialization\r\n n_features = X.shape[1]\r\n@@ -166,7 +166,7 @@ class RFE(BaseEstimator, MetaEstimatorMixin, SelectorMixin):\r\n if self.verbose > 0:\r\n print(\"Fitting estimator with %d features.\" % np.sum(support_))\r\n\r\n- estimator.fit(X[:, features], y)\r\n+ estimator.fit(X[:, features], y, **fit_params)\r\n\r\n # Get coefs\r\n if hasattr(estimator, 'coef_'):\r\n```\r\n\r\nWould this be a worthwhile contribution to scikit-learn?\r\n#### Versions\r\n\r\n\r\n\r\n``` python\r\n\r\nIn [1]: import platform; print(platform.platform())\r\nLinux-3.13.0-63-generic-x86_64-with-Ubuntu-14.04-trusty\r\n\r\nIn [2]: import sys; print(\"Python\", sys.version)\r\n('Python', '2.7.6 (default, Jun 22 2015, 17:58:13) \\n[GCC 4.8.2]')\r\n\r\nIn [3]: import numpy; print(\"NumPy\", numpy.__version__)\r\n('NumPy', '1.11.0')\r\n\r\nIn [4]: import scipy; print(\"SciPy\", scipy.__version__)\r\n('SciPy', '0.17.1')\r\n\r\nIn [5]: import sklearn; print(\"Scikit-Learn\", sklearn.__version__)\r\n('Scikit-Learn', '0.18.dev0')\r\n```\r\n\r\n\r\n\r\nTODO:\r\n\r\n- [x] Add support for `sample_weight` in `RFE`\r\n- [ ] Add support for `sample_weight` in `RFECV`\r\n\n", "hints_text": "yeah I think you can open a PR for that. You need to add tests, though.\n\nNote that testing for sample weights usually involves checking that weights\ncorrespond to repeating samples.\n\nOn 1 September 2016 at 03:33, Andreas Mueller notifications@github.com\nwrote:\n\n> yeah I think you can open a PR for that. You need to add tests, though.\n> \n> \u2014\n> You are receiving this because you are subscribed to this thread.\n> Reply to this email directly, view it on GitHub\n> https://github.com/scikit-learn/scikit-learn/issues/7308#issuecomment-243839981,\n> or mute the thread\n> https://github.com/notifications/unsubscribe-auth/AAEz64VJO8sJbmnWKX_vPF61jQEWBmwwks5qlbrtgaJpZM4Jx1Lf\n> .\n\nGreat, want to assign this to me @amueller? I'll sort out a PR with tests.\n\nWe don't use the \"assignee\" feature much, and for some reason it's not letting me assign. Go ahead and open a PR, reference this issue, then ping one of us after we've released 0.18: it's not likely to be looked at before then.\n\nyou can only \"assign\" contributors\n\nHey all, just an FIY, @ijpulidos and I worked on this over #20380 \n@fbidu thank you so much for this! for what it's worth I think it would be fairly simple to add this also to RFECV (which just calls RFE), but I understand if that's outside of the scope of what you're working on (since you're already building/trying to merge)\n@nathanwalker-sp you're welcome! Well, I agree with you and sure enough should be a simple change, but I'm not familiar with the practices adopted by this project to track these.\r\n\r\n@glemaitre as the reviewer for my PR, what do you think? May I just go ahead and implement this there or is it better if we first merge #20380 and then create a new issue/PR pair to track RFECV?\n@nathanwalker-sp yeah, @glemaitre already merged it. Can you please create a new issue referring back to this in order to track RFECV?\nI am reopening this issue and will rename the title. Feel free to open a new PR.\nHello, \r\nIs this still open for RFECV? \n@max-franceschi metadata routing need to be implemented for the CV. Check https://github.com/scikit-learn/scikit-learn/issues/22893", "created_at": "2024-06-20T11:52:44Z" }, { "repo": "scikit-learn/scikit-learn", "pull_number": 29204, "instance_id": "scikit-learn__scikit-learn-29204", "issue_numbers": [ "29166" ], "base_commit": "b285de0be5420f4b78dcabd4c320beff6f279387", "patch": "diff --git a/build_tools/azure/debian_atlas_32bit_lock.txt b/build_tools/azure/debian_atlas_32bit_lock.txt\nindex 92b4b864fb9f5..ba957463e60bc 100644\n--- a/build_tools/azure/debian_atlas_32bit_lock.txt\n+++ b/build_tools/azure/debian_atlas_32bit_lock.txt\n@@ -6,7 +6,7 @@\n #\n attrs==23.2.0\n # via pytest\n-coverage==7.5.2\n+coverage==7.5.3\n # via pytest-cov\n cython==3.0.10\n # via -r build_tools/azure/debian_atlas_32bit_requirements.txt\n@@ -14,7 +14,7 @@ iniconfig==2.0.0\n # via pytest\n joblib==1.2.0\n # via -r build_tools/azure/debian_atlas_32bit_requirements.txt\n-meson==1.4.0\n+meson==1.4.1\n # via meson-python\n meson-python==0.16.0\n # via -r build_tools/azure/debian_atlas_32bit_requirements.txt\ndiff --git a/build_tools/azure/pymin_conda_defaults_openblas_linux-64_conda.lock b/build_tools/azure/pymin_conda_defaults_openblas_linux-64_conda.lock\nindex deb9f11010bd9..56d3dbdb62a79 100644\n--- a/build_tools/azure/pymin_conda_defaults_openblas_linux-64_conda.lock\n+++ b/build_tools/azure/pymin_conda_defaults_openblas_linux-64_conda.lock\n@@ -56,7 +56,7 @@ https://repo.anaconda.com/pkgs/main/linux-64/libclang-14.0.6-default_hc6dbbc7_1.\n https://repo.anaconda.com/pkgs/main/linux-64/libpq-12.17-hdbd6064_0.conda#6bed363e25859faff66bf546a11c10e8\n https://repo.anaconda.com/pkgs/main/linux-64/openjpeg-2.4.0-h3ad879b_0.conda#86baecb47ecaa7f7ff2657a1f03b90c9\n https://repo.anaconda.com/pkgs/main/linux-64/python-3.9.19-h955ad1f_1.conda#4b453281859c293c9d577271f3b18a0d\n-https://repo.anaconda.com/pkgs/main/linux-64/certifi-2024.2.2-py39h06a4308_0.conda#2bc1db9166ecbb968f61252e6f08c2ce\n+https://repo.anaconda.com/pkgs/main/linux-64/certifi-2024.6.2-py39h06a4308_0.conda#738daf43271605d7291ecae0e8cac41c\n https://repo.anaconda.com/pkgs/main/noarch/cycler-0.11.0-pyhd3eb1b0_0.conda#f5e365d2cdb66d547eb8c3ab93843aab\n https://repo.anaconda.com/pkgs/main/linux-64/cython-3.0.10-py39h5eee18b_0.conda#1419a658ed2b4d5c3ac1964f33143b64\n https://repo.anaconda.com/pkgs/main/linux-64/exceptiongroup-1.2.0-py39h06a4308_0.conda#960e2cb83ac5134df8e593a130aa11af\ndiff --git a/build_tools/azure/pymin_conda_forge_mkl_win-64_conda.lock b/build_tools/azure/pymin_conda_forge_mkl_win-64_conda.lock\nindex f4c2f51b1ea88..e36c8ed6ff697 100644\n--- a/build_tools/azure/pymin_conda_forge_mkl_win-64_conda.lock\n+++ b/build_tools/azure/pymin_conda_forge_mkl_win-64_conda.lock\n@@ -2,7 +2,7 @@\n # platform: win-64\n # input_hash: ea607aaeb7b1d1f8a1f821a9f505b3601083a218ec4763e2d72d3d3d800e718c\n @EXPLICIT\n-https://conda.anaconda.org/conda-forge/win-64/ca-certificates-2024.2.2-h56e8100_0.conda#63da060240ab8087b60d1357051ea7d6\n+https://conda.anaconda.org/conda-forge/win-64/ca-certificates-2024.6.2-h56e8100_0.conda#12a3a2b3a00a21bbb390d4de5ad8dd0f\n https://conda.anaconda.org/conda-forge/win-64/intel-openmp-2024.1.0-h57928b3_966.conda#35d7ea07ad6c878bd7240d2d6c1b8657\n https://conda.anaconda.org/conda-forge/win-64/mkl-include-2024.1.0-h66d3029_692.conda#60233966dc7c0261c9a443120b43c477\n https://conda.anaconda.org/conda-forge/win-64/msys2-conda-epoch-20160418-1.tar.bz2#b0309b72560df66f71a9d5e34a5efdfa\n@@ -26,10 +26,10 @@ https://conda.anaconda.org/conda-forge/win-64/libjpeg-turbo-3.0.0-hcfcfb64_1.con\n https://conda.anaconda.org/conda-forge/win-64/libogg-1.3.4-h8ffe710_1.tar.bz2#04286d905a0dcb7f7d4a12bdfe02516d\n https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.45.3-hcfcfb64_0.conda#73f5dc8e2d55d9a1e14b11f49c3b4a28\n https://conda.anaconda.org/conda-forge/win-64/libwebp-base-1.4.0-hcfcfb64_0.conda#abd61d0ab127ec5cd68f62c2969e6f34\n-https://conda.anaconda.org/conda-forge/win-64/libzlib-1.2.13-hcfcfb64_5.conda#5fdb9c6a113b6b6cb5e517fd972d5f41\n+https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.1-h2466b09_1.conda#d4483ca8afc57ddf1f6dded53b36c17f\n https://conda.anaconda.org/conda-forge/win-64/m2w64-gcc-libgfortran-5.3.0-6.tar.bz2#066552ac6b907ec6d72c0ddab29050dc\n https://conda.anaconda.org/conda-forge/win-64/ninja-1.12.1-hc790b64_0.conda#a557dde55343e03c68cd7e29e7f87279\n-https://conda.anaconda.org/conda-forge/win-64/openssl-3.3.0-h2466b09_3.conda#d7fec5d3bb8fc0c8e266bf1ad350cec5\n+https://conda.anaconda.org/conda-forge/win-64/openssl-3.3.1-h2466b09_0.conda#27fe798366ef3a81715b13eedf699e2f\n https://conda.anaconda.org/conda-forge/win-64/pthreads-win32-2.9.1-hfa6e2cd_3.tar.bz2#e2da8758d7d51ff6aa78a14dfb9dbed4\n https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h5226925_1.conda#fc048363eb8f03cd1737600a5d08aafe\n https://conda.anaconda.org/conda-forge/win-64/xz-5.2.6-h8d14728_0.tar.bz2#515d77642eaa3639413c6b1bc3f94219\n@@ -39,7 +39,7 @@ https://conda.anaconda.org/conda-forge/win-64/libbrotlienc-1.1.0-hcfcfb64_1.cond\n https://conda.anaconda.org/conda-forge/win-64/libintl-0.22.5-h5728263_2.conda#aa622c938af057adc119f8b8eecada01\n https://conda.anaconda.org/conda-forge/win-64/libpng-1.6.43-h19919ed_0.conda#77e398acc32617a0384553aea29e866b\n https://conda.anaconda.org/conda-forge/win-64/libvorbis-1.3.7-h0e60522_0.tar.bz2#e1a22282de0169c93e4ffe6ce6acc212\n-https://conda.anaconda.org/conda-forge/win-64/libxml2-2.12.7-h283a6d9_0.conda#1451be68a5549561979125c1827b79ed\n+https://conda.anaconda.org/conda-forge/win-64/libxml2-2.12.7-h283a6d9_1.conda#7ab2653cc21c44a1370ef3b409261b3d\n https://conda.anaconda.org/conda-forge/win-64/m2w64-gcc-libs-5.3.0-7.tar.bz2#fe759119b8b3bfa720b8762c6fdc35de\n https://conda.anaconda.org/conda-forge/win-64/pcre2-10.43-h17e33f8_0.conda#d0485b8aa2cedb141a7bd27b4efa4c9c\n https://conda.anaconda.org/conda-forge/win-64/python-3.9.19-h4de0772_0_cpython.conda#b6999bc275e0e6beae7b1c8ea0be1e85\n@@ -77,7 +77,7 @@ https://conda.anaconda.org/conda-forge/win-64/xorg-libxau-1.0.11-hcd874cb_0.cond\n https://conda.anaconda.org/conda-forge/win-64/xorg-libxdmcp-1.1.3-hcd874cb_0.tar.bz2#46878ebb6b9cbd8afcf8088d7ef00ece\n https://conda.anaconda.org/conda-forge/noarch/zipp-3.17.0-pyhd8ed1ab_0.conda#2e4d6bc0b14e10f895fc6791a7d9b26a\n https://conda.anaconda.org/conda-forge/win-64/brotli-1.1.0-hcfcfb64_1.conda#f47f6db2528e38321fb00ae31674c133\n-https://conda.anaconda.org/conda-forge/win-64/coverage-7.5.2-py39ha55e580_0.conda#efb1e63bf5157f005afcc40778efaff5\n+https://conda.anaconda.org/conda-forge/win-64/coverage-7.5.3-py39ha55e580_0.conda#28d426e365cb4ed87d22d1a89c0bd006\n https://conda.anaconda.org/conda-forge/win-64/glib-tools-2.80.2-h2f9d560_0.conda#42fc785d9db7ab051a206fbf882ecf2e\n https://conda.anaconda.org/conda-forge/noarch/importlib_resources-6.4.0-pyhd8ed1ab_0.conda#c5d3907ad8bd7bf557521a1833cf7e6d\n https://conda.anaconda.org/conda-forge/noarch/joblib-1.4.2-pyhd8ed1ab_0.conda#25df261d4523d9f9783bcdb7208d872f\n@@ -87,11 +87,11 @@ https://conda.anaconda.org/conda-forge/noarch/meson-1.4.0-pyhd8ed1ab_0.conda#52a\n https://conda.anaconda.org/conda-forge/win-64/openjpeg-2.5.2-h3d672ee_0.conda#7e7099ad94ac3b599808950cec30ad4e\n https://conda.anaconda.org/conda-forge/noarch/pip-24.0-pyhd8ed1ab_0.conda#f586ac1e56c8638b64f9c8122a7b8a67\n https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.8.0-pyhd8ed1ab_0.conda#573fe09d7bd0cd4bcc210d8369b5ca47\n-https://conda.anaconda.org/conda-forge/noarch/pytest-8.2.1-pyhd8ed1ab_0.conda#e4418e8bdbaa8eea28e047531e6763c8\n+https://conda.anaconda.org/conda-forge/noarch/pytest-8.2.2-pyhd8ed1ab_0.conda#0f3f49c22c7ef3a1195fa61dad3c43be\n https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0-pyhd8ed1ab_0.conda#2cf4264fffb9e6eff6031c5b6884d61c\n https://conda.anaconda.org/conda-forge/win-64/sip-6.7.12-py39h99910a6_0.conda#0cc5774390ada632ed7975203057c91c\n https://conda.anaconda.org/conda-forge/win-64/tbb-2021.12.0-hc790b64_1.conda#e98333643abc739ebea1bac97a479828\n-https://conda.anaconda.org/conda-forge/win-64/fonttools-4.52.1-py39ha55e580_0.conda#781c66ea2eeed910c0f1abc5ccc4a079\n+https://conda.anaconda.org/conda-forge/win-64/fonttools-4.53.0-py39ha55e580_0.conda#7c4625b8a1013dd22e924f1fa9fbc605\n https://conda.anaconda.org/conda-forge/win-64/glib-2.80.2-h0df6a38_0.conda#a728ca6f04c33ecb0f39eeda5fbd0e23\n https://conda.anaconda.org/conda-forge/noarch/importlib-resources-6.4.0-pyhd8ed1ab_0.conda#dcbadab7a68738a028e195ab68ab2d2e\n https://conda.anaconda.org/conda-forge/noarch/meson-python-0.16.0-pyh0c530f3_0.conda#e16f0dbf502da873be9f9adb0dc52547\n@@ -100,10 +100,10 @@ https://conda.anaconda.org/conda-forge/win-64/pillow-10.3.0-py39h9ee4981_0.conda\n https://conda.anaconda.org/conda-forge/win-64/pyqt5-sip-12.12.2-py39h99910a6_5.conda#dffbcea794c524c471772a5f697c2aea\n https://conda.anaconda.org/conda-forge/noarch/pytest-cov-5.0.0-pyhd8ed1ab_0.conda#c54c0107057d67ddf077751339ec2c63\n https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.5.0-pyhd8ed1ab_0.conda#d5f595da2daead898ca958ac62f0307b\n-https://conda.anaconda.org/conda-forge/win-64/gstreamer-1.24.3-h5006eae_0.conda#8c8959a520ef4911271fbf2cb2dfc3fe\n+https://conda.anaconda.org/conda-forge/win-64/gstreamer-1.24.4-h5006eae_0.conda#3d7ebad364d5f63a1ae54eecb35aee31\n https://conda.anaconda.org/conda-forge/win-64/libblas-3.9.0-22_win64_mkl.conda#65c56ecdeceffd6c32d3d54db7e02c6e\n https://conda.anaconda.org/conda-forge/win-64/mkl-devel-2024.1.0-h57928b3_692.conda#9b3d1d4916a56fd32460f6fe784dcb51\n-https://conda.anaconda.org/conda-forge/win-64/gst-plugins-base-1.24.3-hba88be7_0.conda#1fa879c7b4868c58830762b6fac0075d\n+https://conda.anaconda.org/conda-forge/win-64/gst-plugins-base-1.24.4-hba88be7_0.conda#0b1d683d462029446924fa87a50dda12\n https://conda.anaconda.org/conda-forge/win-64/libcblas-3.9.0-22_win64_mkl.conda#336c93ab102846c6131cf68e722a68f1\n https://conda.anaconda.org/conda-forge/win-64/liblapack-3.9.0-22_win64_mkl.conda#c752cc2af9f3d8d7b2fdebb915a33ef7\n https://conda.anaconda.org/conda-forge/win-64/liblapacke-3.9.0-22_win64_mkl.conda#db33ffa4bae1d2f6d5602afaa048bf6b\ndiff --git a/build_tools/azure/pymin_conda_forge_openblas_ubuntu_2204_linux-64_conda.lock b/build_tools/azure/pymin_conda_forge_openblas_ubuntu_2204_linux-64_conda.lock\nindex 66cd5eced566b..c0ac0fddd1ca6 100644\n--- a/build_tools/azure/pymin_conda_forge_openblas_ubuntu_2204_linux-64_conda.lock\n+++ b/build_tools/azure/pymin_conda_forge_openblas_ubuntu_2204_linux-64_conda.lock\n@@ -3,12 +3,12 @@\n # input_hash: 3974f9847d888a2fd37ba5fcfb76cb09bba4c9b84b6200932500fc94e3b0c4ae\n @EXPLICIT\n https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81\n-https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.2.2-hbcca054_0.conda#2f4327a1cbe7f022401b236e915a5fef\n+https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.6.2-hbcca054_0.conda#847c3c2905cc467cea52c24f9cfa8080\n https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2#0c96522c6bdaed4b1566d11387caaf45\n https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6\n https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb\n https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_2.conda#cbbe59391138ea5ad3658c76912e147f\n-https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.40-hf3520f5_1.conda#33b7851c39c25da14f6a233a8ccbeeca\n+https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.40-hf3520f5_2.conda#61b0bd5219ce7192b4e3633521a78975\n https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-13.2.0-hc0a3c3a_7.conda#53ebd4c833fa01cb2c6353e99f905406\n https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.9-4_cp39.conda#bfe4b3259a8ac6cdf0037752904da6a7\n https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h0c530f3_0.conda#161081fc7cec0bfda0d86d7cb595f8d8\n@@ -40,13 +40,13 @@ https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2\n https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda#40b61aab5c7ba9ff276c41cfffe6b80b\n https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.4.0-hd590300_0.conda#b26e8aa824079e1be0294e7152ca4559\n https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda#5aa797f8787fe7a17d1b0821485b5adc\n-https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.13-hd590300_5.conda#f36c115f1ee199da648e0597ec2047ad\n+https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-h4ab18f5_1.conda#57d7dc60e9325e3de37ff8dffd18e814\n https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.4-hcb278e6_0.conda#318b08df404f9c9be5712aaa5a6f0bb0\n https://conda.anaconda.org/conda-forge/linux-64/mpg123-1.32.6-h59595ed_0.conda#9160cdeb523a1b20cf8d2a0bf821f45d\n https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h59595ed_0.conda#fcea371545eda051b6deafb24889fc69\n https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-h297d8ca_0.conda#3aa1c7e292afeff25a0091ddd7c69b72\n https://conda.anaconda.org/conda-forge/linux-64/nspr-4.35-h27087fc_0.conda#da0ec11a6454ae19bff5b02ed881a2b1\n-https://conda.anaconda.org/conda-forge/linux-64/openssl-3.3.0-h4ab18f5_3.conda#12ea6d0d4ed54530eaed18e4835c1f7c\n+https://conda.anaconda.org/conda-forge/linux-64/openssl-3.3.1-h4ab18f5_0.conda#a41fa0e391cc9e0d6b78ac69ca047a6c\n https://conda.anaconda.org/conda-forge/linux-64/pixman-0.43.2-h59595ed_0.conda#71004cbf7924e19c02746ccde9fd7123\n https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2#22dad4df6e8630e8dff2428f6f6a7036\n https://conda.anaconda.org/conda-forge/linux-64/xorg-kbproto-1.0.7-h7f98852_1002.tar.bz2#4b230e8381279d76131116660f5a241a\n@@ -71,13 +71,13 @@ https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.43-h2797004_0.conda#0\n https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.45.3-h2797004_0.conda#b3316cbe90249da4f8e84cd66e1cc55b\n https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0\n https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.15-h0b41bf4_0.conda#33277193f5b92bad9fdd230eb700929c\n-https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.12.7-hc051c1a_0.conda#5d801a4906adc712d480afc362623b59\n+https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.12.7-hc051c1a_1.conda#340278ded8b0dc3a73f3660bbb0adbc6\n https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.3.0-hf1915f5_4.conda#784a4df6676c581ca624fbe460703a6d\n https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.43-hcad00b1_0.conda#8292dea9e022d9610a11fce5e0896ed8\n https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda#47d31b792659ce70f470b5c82fdfb7a4\n https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda#d453b98d9c83e71da0741bb0ff4d76bc\n https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.4-h7391055_0.conda#93ee23f12bc2e684548181256edd2cf6\n-https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.13-hd590300_5.conda#68c34ec6149623be41a1933ab996a209\n+https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.1-h4ab18f5_1.conda#9653f1bf3766164d0e65fa723cabbc54\n https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.6-ha6fb4c9_0.conda#4d056880988120e29d75bfff282e0f45\n https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.1.0-hd590300_1.conda#39f910d205726805a958da408ca194ba\n https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-h267a509_2.conda#9ae35c3d96db2c94ce0cef86efdfa2cb\n@@ -155,7 +155,7 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.11-hd590300_\n https://conda.anaconda.org/conda-forge/noarch/zipp-3.17.0-pyhd8ed1ab_0.conda#2e4d6bc0b14e10f895fc6791a7d9b26a\n https://conda.anaconda.org/conda-forge/noarch/babel-2.14.0-pyhd8ed1ab_0.conda#9669586875baeced8fc30c0826c3270e\n https://conda.anaconda.org/conda-forge/linux-64/cairo-1.18.0-h3faef2a_0.conda#f907bb958910dc404647326ca80c263e\n-https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.52.1-py39hd3abc70_0.conda#66b6088c2446c25e714829a289070499\n+https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.53.0-py39hd3abc70_0.conda#9dae301603c88aef61dba733e8931cdd\n https://conda.anaconda.org/conda-forge/linux-64/glib-2.80.2-hf974151_0.conda#d427988dc3dbd0a4c136f52db356cc6a\n https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-7.1.0-pyha770c72_0.conda#0896606848b2dc5cebdf111b6543aa04\n https://conda.anaconda.org/conda-forge/noarch/importlib_resources-6.4.0-pyhd8ed1ab_0.conda#c5d3907ad8bd7bf557521a1833cf7e6d\n@@ -170,11 +170,11 @@ https://conda.anaconda.org/conda-forge/noarch/meson-1.4.0-pyhd8ed1ab_0.conda#52a\n https://conda.anaconda.org/conda-forge/linux-64/pillow-10.3.0-py39h90c7501_0.conda#1e3b6af9592be71ce19f0a6aae05d97b\n https://conda.anaconda.org/conda-forge/noarch/pip-24.0-pyhd8ed1ab_0.conda#f586ac1e56c8638b64f9c8122a7b8a67\n https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.8.0-pyhd8ed1ab_0.conda#573fe09d7bd0cd4bcc210d8369b5ca47\n-https://conda.anaconda.org/conda-forge/noarch/pytest-8.2.1-pyhd8ed1ab_0.conda#e4418e8bdbaa8eea28e047531e6763c8\n+https://conda.anaconda.org/conda-forge/noarch/pytest-8.2.2-pyhd8ed1ab_0.conda#0f3f49c22c7ef3a1195fa61dad3c43be\n https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0-pyhd8ed1ab_0.conda#2cf4264fffb9e6eff6031c5b6884d61c\n https://conda.anaconda.org/conda-forge/linux-64/sip-6.7.12-py39h3d6467e_0.conda#e667a3ab0df62c54e60e1843d2e6defb\n https://conda.anaconda.org/conda-forge/noarch/urllib3-2.2.1-pyhd8ed1ab_0.conda#08807a87fa7af10754d46f63b368e016\n-https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.24.3-haf2f30d_0.conda#f3df87cc9ef0b5113bff55aefcbcafd5\n+https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.24.4-haf2f30d_0.conda#926c2c7ee7a0b48d6d70783a33f7bc80\n https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-8.5.0-hfac3d4d_0.conda#f5126317dd0ce0ba26945e411ecc6960\n https://conda.anaconda.org/conda-forge/noarch/importlib-resources-6.4.0-pyhd8ed1ab_0.conda#dcbadab7a68738a028e195ab68ab2d2e\n https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-22_linux64_openblas.conda#1fd156abd41a4992835952f6f4d951d0\n@@ -183,10 +183,10 @@ https://conda.anaconda.org/conda-forge/noarch/meson-python-0.16.0-pyh0c530f3_0.c\n https://conda.anaconda.org/conda-forge/linux-64/numpy-1.26.4-py39h474f0d3_0.conda#aa265f5697237aa13cc10f53fa8acc4f\n https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.12.2-py39h3d6467e_5.conda#93aff412f3e49fdb43361c0215cbd72d\n https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.5.0-pyhd8ed1ab_0.conda#d5f595da2daead898ca958ac62f0307b\n-https://conda.anaconda.org/conda-forge/noarch/requests-2.32.2-pyhd8ed1ab_0.conda#e1643b34b19df8c028a4f00bf5df58a6\n+https://conda.anaconda.org/conda-forge/noarch/requests-2.32.3-pyhd8ed1ab_0.conda#5ede4753180c7a550a443c430dc8ab52\n https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-22_linux64_openblas.conda#63ddb593595c9cf5eb08d3de54d66df8\n https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.2.1-py39h7633fee_0.conda#bdc188e59857d6efab332714e0d01d93\n-https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.24.3-h9ad1361_0.conda#8fb0e954c616bb0f9389efac4b4ed44b\n+https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.24.4-h9ad1361_0.conda#147cce520ec59367549fd0d96d404213\n https://conda.anaconda.org/conda-forge/linux-64/pandas-2.2.2-py39hfc16268_1.conda#8b23d2b425035a7468d17e6fe1d54124\n https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-client-17.0-hb77b528_0.conda#07f45f1be1c25345faddb8db0de8039b\n https://conda.anaconda.org/conda-forge/linux-64/scipy-1.13.1-py39haf93ffa_0.conda#492a2cd65862d16a4aaf535ae9ccb761\n@@ -196,7 +196,7 @@ https://conda.anaconda.org/conda-forge/linux-64/pyamg-5.1.0-py39h85c637f_1.conda\n https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.8-hc9dc06e_21.conda#b325046180590c868ce0dbf267b82eb8\n https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.9-py39h52134e7_5.conda#e1f148e57d071b09187719df86f513c1\n https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.8.4-py39hf3d152e_2.conda#bd956c7563b6a6b27521b83623c74e22\n-https://conda.anaconda.org/conda-forge/noarch/numpydoc-1.7.0-pyhd8ed1ab_0.conda#1ad3afced398492586ca1bef70328be4\n+https://conda.anaconda.org/conda-forge/noarch/numpydoc-1.7.0-pyhd8ed1ab_1.conda#66798cbfdcb003d9fbccd92cd08eb3ac\n https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-applehelp-1.0.8-pyhd8ed1ab_0.conda#611a35a27914fac3aa37611a6fe40bb5\n https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-devhelp-1.0.6-pyhd8ed1ab_0.conda#d7e4954df0d3aea2eacc7835ad12671d\n https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-htmlhelp-2.0.5-pyhd8ed1ab_0.conda#7e1e7437273682ada2ed5e9e9714b140\ndiff --git a/build_tools/azure/ubuntu_atlas_lock.txt b/build_tools/azure/ubuntu_atlas_lock.txt\nindex e66f5ca8943fd..95dda28aa4f8c 100644\n--- a/build_tools/azure/ubuntu_atlas_lock.txt\n+++ b/build_tools/azure/ubuntu_atlas_lock.txt\n@@ -14,7 +14,7 @@ iniconfig==2.0.0\n # via pytest\n joblib==1.2.0\n # via -r build_tools/azure/ubuntu_atlas_requirements.txt\n-meson==1.4.0\n+meson==1.4.1\n # via meson-python\n meson-python==0.16.0\n # via -r build_tools/azure/ubuntu_atlas_requirements.txt\n@@ -29,7 +29,7 @@ pluggy==1.5.0\n # via pytest\n pyproject-metadata==0.8.0\n # via meson-python\n-pytest==8.2.1\n+pytest==8.2.2\n # via\n # -r build_tools/azure/ubuntu_atlas_requirements.txt\n # pytest-xdist\ndiff --git a/build_tools/circle/doc_linux-64_conda.lock b/build_tools/circle/doc_linux-64_conda.lock\nindex 0d925375b643b..88926a5e29866 100644\n--- a/build_tools/circle/doc_linux-64_conda.lock\n+++ b/build_tools/circle/doc_linux-64_conda.lock\n@@ -3,13 +3,13 @@\n # input_hash: f6f3862aafcafa139a322e498517c3db58e1b8db95f1b1ca8c18f5b70d446dc9\n @EXPLICIT\n https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81\n-https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.2.2-hbcca054_0.conda#2f4327a1cbe7f022401b236e915a5fef\n+https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.6.2-hbcca054_0.conda#847c3c2905cc467cea52c24f9cfa8080\n https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2#0c96522c6bdaed4b1566d11387caaf45\n https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6\n https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb\n https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_2.conda#cbbe59391138ea5ad3658c76912e147f\n https://conda.anaconda.org/conda-forge/noarch/kernel-headers_linux-64-2.6.32-he073ed8_17.conda#d731b543793afc0433c4fd593e693fce\n-https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.40-hf3520f5_1.conda#33b7851c39c25da14f6a233a8ccbeeca\n+https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.40-hf3520f5_2.conda#61b0bd5219ce7192b4e3633521a78975\n https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-12.3.0-h0223996_107.conda#851e9651c9e4cd5dc19f80398eba9a1c\n https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-12.3.0-h0223996_107.conda#167a1f5d77d8f3c2a638f7eb418429f1\n https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-13.2.0-hc0a3c3a_7.conda#53ebd4c833fa01cb2c6353e99f905406\n@@ -18,9 +18,9 @@ https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h0c530f3_0.conda#1610\n https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2#f766549260d6815b0c52253f1fb1bb29\n https://conda.anaconda.org/conda-forge/linux-64/libgomp-13.2.0-h77fa898_7.conda#abf3fec87c2563697defa759dec3d639\n https://conda.anaconda.org/conda-forge/noarch/sysroot_linux-64-2.12-he073ed8_17.conda#595db67e32b276298ff3d94d07d47fbf\n-https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.40-ha1999f0_1.conda#e901545940ebdc5c40017fab53642b3c\n+https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.40-ha1999f0_2.conda#861a9d0b9ad43dcebe5a76f38a7d2527\n https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2#fee5683a3f04bd15cbd8318b096a27ab\n-https://conda.anaconda.org/conda-forge/linux-64/binutils-2.40-h4852527_1.conda#dfaea5684bbbbf0b64a4c31f984d0661\n+https://conda.anaconda.org/conda-forge/linux-64/binutils-2.40-h4852527_2.conda#0ea11d9433ec00000e96e82d6381671d\n https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.40-hdade7a5_3.conda#2d9a60578bc28469d9aeef9aea5520c3\n https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_kmp_llvm.tar.bz2#562b26ba2e19059551a811e72ab7f793\n https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-13.2.0-h77fa898_7.conda#72ec1b1b04c4d15d4204ece1ecea5978\n@@ -56,14 +56,14 @@ https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-12.3.0-hb8811af_7.c\n https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda#40b61aab5c7ba9ff276c41cfffe6b80b\n https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.4.0-hd590300_0.conda#b26e8aa824079e1be0294e7152ca4559\n https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda#5aa797f8787fe7a17d1b0821485b5adc\n-https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.13-h4ab18f5_6.conda#27329162c0dc732bcf67a4e0cd488125\n+https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-h4ab18f5_1.conda#57d7dc60e9325e3de37ff8dffd18e814\n https://conda.anaconda.org/conda-forge/linux-64/libzopfli-1.0.3-h9c3ff4c_0.tar.bz2#c66fe2d123249af7651ebde8984c51c2\n https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.4-hcb278e6_0.conda#318b08df404f9c9be5712aaa5a6f0bb0\n https://conda.anaconda.org/conda-forge/linux-64/mpg123-1.32.6-h59595ed_0.conda#9160cdeb523a1b20cf8d2a0bf821f45d\n https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h59595ed_0.conda#fcea371545eda051b6deafb24889fc69\n https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-h297d8ca_0.conda#3aa1c7e292afeff25a0091ddd7c69b72\n https://conda.anaconda.org/conda-forge/linux-64/nspr-4.35-h27087fc_0.conda#da0ec11a6454ae19bff5b02ed881a2b1\n-https://conda.anaconda.org/conda-forge/linux-64/openssl-3.3.0-h4ab18f5_3.conda#12ea6d0d4ed54530eaed18e4835c1f7c\n+https://conda.anaconda.org/conda-forge/linux-64/openssl-3.3.1-h4ab18f5_0.conda#a41fa0e391cc9e0d6b78ac69ca047a6c\n https://conda.anaconda.org/conda-forge/linux-64/pixman-0.43.2-h59595ed_0.conda#71004cbf7924e19c02746ccde9fd7123\n https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2#22dad4df6e8630e8dff2428f6f6a7036\n https://conda.anaconda.org/conda-forge/linux-64/rav1e-0.6.6-he8a937b_2.conda#77d9955b4abddb811cb8ab1aa7d743e4\n@@ -95,13 +95,13 @@ https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.43-h2797004_0.conda#0\n https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.45.3-h2797004_0.conda#b3316cbe90249da4f8e84cd66e1cc55b\n https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0\n https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.15-h0b41bf4_0.conda#33277193f5b92bad9fdd230eb700929c\n-https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.12.7-hc051c1a_0.conda#5d801a4906adc712d480afc362623b59\n+https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.12.7-hc051c1a_1.conda#340278ded8b0dc3a73f3660bbb0adbc6\n https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.3.0-hf1915f5_4.conda#784a4df6676c581ca624fbe460703a6d\n https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.43-hcad00b1_0.conda#8292dea9e022d9610a11fce5e0896ed8\n https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda#47d31b792659ce70f470b5c82fdfb7a4\n https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda#d453b98d9c83e71da0741bb0ff4d76bc\n https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.4-h7391055_0.conda#93ee23f12bc2e684548181256edd2cf6\n-https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.13-h4ab18f5_6.conda#559d338a4234c2ad6e676f460a093e67\n+https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.1-h4ab18f5_1.conda#9653f1bf3766164d0e65fa723cabbc54\n https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.6-ha6fb4c9_0.conda#4d056880988120e29d75bfff282e0f45\n https://conda.anaconda.org/conda-forge/linux-64/blosc-1.21.5-hc2324a3_1.conda#11d76bee958b1989bd1ac6ee7372ea6d\n https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.1.0-hd590300_1.conda#39f910d205726805a958da408ca194ba\n@@ -185,7 +185,7 @@ https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.5.0-pyhc1e730c_0.c\n https://conda.anaconda.org/conda-forge/noarch/toml-0.10.2-pyhd8ed1ab_0.tar.bz2#f832c45a477c78bebd107098db465095\n https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5844808ffab9ebdb694585b50ba02a96\n https://conda.anaconda.org/conda-forge/linux-64/tornado-6.4-py39hd1e30aa_0.conda#1e865e9188204cdfb1fd2531780add88\n-https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.11.0-pyha770c72_0.conda#6ef2fc37559256cf682d8b3375e89b80\n+https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.12.1-pyha770c72_0.conda#26d7ee34132362115093717c706c384c\n https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-15.1.0-py39hd1e30aa_0.conda#1da984bbb6e765743e13388ba7b7b2c8\n https://conda.anaconda.org/conda-forge/noarch/wheel-0.43.0-pyhd8ed1ab_1.conda#0b5293a157c2b5cd513dd1b03d8d3aae\n https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-h8ee46fc_1.conda#9d7bcddf49cbf727730af10e71022c73\n@@ -199,7 +199,7 @@ https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.12.3-pyha770c72_0\n https://conda.anaconda.org/conda-forge/linux-64/brunsli-0.1-h9c3ff4c_0.tar.bz2#c1ac6229d0bfd14f8354ff9ad2a26cad\n https://conda.anaconda.org/conda-forge/linux-64/cairo-1.18.0-h3faef2a_0.conda#f907bb958910dc404647326ca80c263e\n https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.7.0-h00ab1b0_1.conda#28de2e073db9ca9b72858bee9fb6f571\n-https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.52.4-py39hd3abc70_0.conda#2e309d4c5736d32dfb1a1afccb4fea66\n+https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.53.0-py39hd3abc70_0.conda#9dae301603c88aef61dba733e8931cdd\n https://conda.anaconda.org/conda-forge/linux-64/fortran-compiler-1.7.0-heb67821_1.conda#cf4b0e7c4c78bb0662aed9b27c414a3c\n https://conda.anaconda.org/conda-forge/linux-64/glib-2.80.2-hf974151_0.conda#d427988dc3dbd0a4c136f52db356cc6a\n https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-7.1.0-pyha770c72_0.conda#0896606848b2dc5cebdf111b6543aa04\n@@ -217,7 +217,7 @@ https://conda.anaconda.org/conda-forge/linux-64/pillow-10.3.0-py39h90c7501_0.con\n https://conda.anaconda.org/conda-forge/noarch/pip-24.0-pyhd8ed1ab_0.conda#f586ac1e56c8638b64f9c8122a7b8a67\n https://conda.anaconda.org/conda-forge/noarch/plotly-5.22.0-pyhd8ed1ab_0.conda#5b409a5f738e7d76c2b426eddb7e9956\n https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.8.0-pyhd8ed1ab_0.conda#573fe09d7bd0cd4bcc210d8369b5ca47\n-https://conda.anaconda.org/conda-forge/noarch/pytest-8.2.1-pyhd8ed1ab_0.conda#e4418e8bdbaa8eea28e047531e6763c8\n+https://conda.anaconda.org/conda-forge/noarch/pytest-8.2.2-pyhd8ed1ab_0.conda#0f3f49c22c7ef3a1195fa61dad3c43be\n https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0-pyhd8ed1ab_0.conda#2cf4264fffb9e6eff6031c5b6884d61c\n https://conda.anaconda.org/conda-forge/linux-64/sip-6.7.12-py39h3d6467e_0.conda#e667a3ab0df62c54e60e1843d2e6defb\n https://conda.anaconda.org/conda-forge/noarch/urllib3-2.2.1-pyhd8ed1ab_0.conda#08807a87fa7af10754d46f63b368e016\n@@ -232,15 +232,15 @@ https://conda.anaconda.org/conda-forge/noarch/meson-python-0.16.0-pyh0c530f3_0.c\n https://conda.anaconda.org/conda-forge/linux-64/numpy-1.26.4-py39h474f0d3_0.conda#aa265f5697237aa13cc10f53fa8acc4f\n https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.12.2-py39h3d6467e_5.conda#93aff412f3e49fdb43361c0215cbd72d\n https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.5.0-pyhd8ed1ab_0.conda#d5f595da2daead898ca958ac62f0307b\n-https://conda.anaconda.org/conda-forge/noarch/requests-2.32.2-pyhd8ed1ab_0.conda#e1643b34b19df8c028a4f00bf5df58a6\n+https://conda.anaconda.org/conda-forge/noarch/requests-2.32.3-pyhd8ed1ab_0.conda#5ede4753180c7a550a443c430dc8ab52\n https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-22_linux64_openblas.conda#63ddb593595c9cf5eb08d3de54d66df8\n https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.2.1-py39h7633fee_0.conda#bdc188e59857d6efab332714e0d01d93\n https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.24.4-h9ad1361_0.conda#147cce520ec59367549fd0d96d404213\n-https://conda.anaconda.org/conda-forge/linux-64/imagecodecs-2024.1.1-py39hbbab4d9_7.conda#ade05a8093fc3a23c5637f433706141e\n+https://conda.anaconda.org/conda-forge/linux-64/imagecodecs-2024.6.1-py39hbbab4d9_0.conda#bc3c956def472cc1562a325198db91c0\n https://conda.anaconda.org/conda-forge/noarch/imageio-2.34.1-pyh4b66e23_0.conda#bcf6a6f4c6889ca083e8d33afbafb8d5\n https://conda.anaconda.org/conda-forge/linux-64/pandas-2.2.2-py39hfc16268_1.conda#8b23d2b425035a7468d17e6fe1d54124\n https://conda.anaconda.org/conda-forge/noarch/patsy-0.5.6-pyhd8ed1ab_0.conda#a5b55d1cb110cdcedc748b5c3e16e687\n-https://conda.anaconda.org/conda-forge/linux-64/polars-0.20.30-py39ha963410_0.conda#322084e8890afc27fcca6df7a528df25\n+https://conda.anaconda.org/conda-forge/linux-64/polars-0.20.31-py39ha963410_0.conda#ef7ffefe34eae8f69a2ed0cdf2a27678\n https://conda.anaconda.org/conda-forge/noarch/pooch-1.8.1-pyhd8ed1ab_0.conda#d15917f33140f8d2ac9ca44db7ec8a25\n https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-client-17.0-hb77b528_0.conda#07f45f1be1c25345faddb8db0de8039b\n https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.4.1-py39h44dd56e_1.conda#d037c20e3da2e85f03ebd20ad480c359\n@@ -256,7 +256,7 @@ https://conda.anaconda.org/conda-forge/linux-64/scikit-image-0.22.0-py39hddac248\n https://conda.anaconda.org/conda-forge/noarch/seaborn-base-0.13.2-pyhd8ed1ab_2.conda#b713b116feaf98acdba93ad4d7f90ca1\n https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.8.4-py39hf3d152e_2.conda#bd956c7563b6a6b27521b83623c74e22\n https://conda.anaconda.org/conda-forge/noarch/seaborn-0.13.2-hd8ed1ab_2.conda#a79d8797f62715255308d92d3a91ef2e\n-https://conda.anaconda.org/conda-forge/noarch/numpydoc-1.7.0-pyhd8ed1ab_0.conda#1ad3afced398492586ca1bef70328be4\n+https://conda.anaconda.org/conda-forge/noarch/numpydoc-1.7.0-pyhd8ed1ab_1.conda#66798cbfdcb003d9fbccd92cd08eb3ac\n https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.15.3-pyhd8ed1ab_0.conda#55e445f4fcb07f2471fb0e1102d36488\n https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.2-pyhd8ed1ab_0.conda#ac832cc43adc79118cf6e23f1f9b8995\n https://conda.anaconda.org/conda-forge/noarch/sphinx-design-0.5.0-pyhd8ed1ab_0.conda#264b3c697fa9cdade87eb0abe4440d54\n@@ -282,7 +282,7 @@ https://conda.anaconda.org/conda-forge/noarch/sphinxext-opengraph-0.9.1-pyhd8ed1\n # pip mistune @ https://files.pythonhosted.org/packages/f0/74/c95adcdf032956d9ef6c89a9b8a5152bf73915f8c633f3e3d88d06bd699c/mistune-3.0.2-py3-none-any.whl#sha256=71481854c30fdbc938963d3605b72501f5c10a9320ecd412c121c163a1c7d205\n # pip overrides @ https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl#sha256=c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49\n # pip pandocfilters @ https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl#sha256=93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc\n-# pip pkginfo @ https://files.pythonhosted.org/packages/56/09/054aea9b7534a15ad38a363a2bd974c20646ab1582a387a95b8df1bfea1c/pkginfo-1.10.0-py3-none-any.whl#sha256=889a6da2ed7ffc58ab5b900d888ddce90bce912f2d2de1dc1c26f4cb9fe65097\n+# pip pkginfo @ https://files.pythonhosted.org/packages/66/46/f6bce532c9181b0d99b4612ebc2e633e5e0dae8c8540f2a664fe71e12953/pkginfo-1.11.0-py3-none-any.whl#sha256=6d4998d1cd42c297af72cc0eab5f5bab1d356fb8a55b828fa914173f8bc1ba05\n # pip prometheus-client @ https://files.pythonhosted.org/packages/c7/98/745b810d822103adca2df8decd4c0bbe839ba7ad3511af3f0d09692fc0f0/prometheus_client-0.20.0-py3-none-any.whl#sha256=cde524a85bce83ca359cc837f28b8c0db5cac7aa653a588fd7e84ba061c329e7\n # pip ptyprocess @ https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl#sha256=4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35\n # pip pycparser @ https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl#sha256=c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc\n@@ -318,11 +318,11 @@ https://conda.anaconda.org/conda-forge/noarch/sphinxext-opengraph-0.9.1-pyhd8ed1\n # pip argon2-cffi @ https://files.pythonhosted.org/packages/a4/6a/e8a041599e78b6b3752da48000b14c8d1e8a04ded09c88c714ba047f34f5/argon2_cffi-23.1.0-py3-none-any.whl#sha256=c670642b78ba29641818ab2e68bd4e6a78ba53b7eff7b4c3815ae16abf91c7ea\n # pip jsonschema @ https://files.pythonhosted.org/packages/c8/2f/324fab4be6fe37fb7b521546e8a557e6cf08c1c1b3d0b4839a00f589d9ef/jsonschema-4.22.0-py3-none-any.whl#sha256=ff4cfd6b1367a40e7bc6411caec72effadd3db0bbe5017de188f2d6108335802\n # pip jupyter-client @ https://files.pythonhosted.org/packages/cf/d3/c4bb02580bc0db807edb9a29b2d0c56031be1ef0d804336deb2699a470f6/jupyter_client-8.6.2-py3-none-any.whl#sha256=50cbc5c66fd1b8f65ecb66bc490ab73217993632809b6e505687de18e9dea39f\n-# pip jupyterlite-pyodide-kernel @ https://files.pythonhosted.org/packages/83/bf/749279904094015d5cb7e030dd7a111f8b013b9f1809d954d04ebe0c1197/jupyterlite_pyodide_kernel-0.3.1-py3-none-any.whl#sha256=ac9d9dd95adcced57d465a7b298f220d8785845c017ad3abf2a3677ff02631c6\n+# pip jupyterlite-pyodide-kernel @ https://files.pythonhosted.org/packages/42/ce/87fadd7eaa01caaa564d3345025b983f72b4200abc82245068bd2664fb56/jupyterlite_pyodide_kernel-0.3.2-py3-none-any.whl#sha256=ae600571fa755b6fd7a2633a171de3fe490f2b1264bef32cdd7e8c34c95cd5ff\n # pip jupyter-events @ https://files.pythonhosted.org/packages/a5/94/059180ea70a9a326e1815176b2370da56376da347a796f8c4f0b830208ef/jupyter_events-0.10.0-py3-none-any.whl#sha256=4b72130875e59d57716d327ea70d3ebc3af1944d3717e5a498b8a06c6c159960\n # pip nbformat @ https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl#sha256=3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b\n # pip nbclient @ https://files.pythonhosted.org/packages/66/e8/00517a23d3eeaed0513e718fbc94aab26eaa1758f5690fc8578839791c79/nbclient-0.10.0-py3-none-any.whl#sha256=f13e3529332a1f1f81d82a53210322476a168bb7090a0289c795fe9cc11c9d3f\n # pip nbconvert @ https://files.pythonhosted.org/packages/b8/bb/bb5b6a515d1584aa2fd89965b11db6632e4bdc69495a52374bcc36e56cfa/nbconvert-7.16.4-py3-none-any.whl#sha256=05873c620fe520b6322bf8a5ad562692343fe3452abda5765c7a34b7d1aa3eb3\n-# pip jupyter-server @ https://files.pythonhosted.org/packages/07/46/6bb926b3bf878bf687b952fb6a4c09d014b4575a25960f2cd1a61793763f/jupyter_server-2.14.0-py3-none-any.whl#sha256=fb6be52c713e80e004fac34b35a0990d6d36ba06fd0a2b2ed82b899143a64210\n+# pip jupyter-server @ https://files.pythonhosted.org/packages/26/f5/be75c159deda5b54e15cf54029915ad28337fcfef402d671566c45f9e61f/jupyter_server-2.14.1-py3-none-any.whl#sha256=16f7177c3a4ea8fe37784e2d31271981a812f0b2874af17339031dc3510cc2a5\n # pip jupyterlab-server @ https://files.pythonhosted.org/packages/cb/46/d5ffd7c0f63db4e9f0982c3d58efeea10fc5f47e79fb328431df78843772/jupyterlab_server-2.27.2-py3-none-any.whl#sha256=54aa2d64fd86383b5438d9f0c032f043c4d8c0264b8af9f60bd061157466ea43\n # pip jupyterlite-sphinx @ https://files.pythonhosted.org/packages/71/2c/bd797dc46a7281d43444c79ff312d4f8d27d41a0de05f48cad81c7939966/jupyterlite_sphinx-0.15.0-py3-none-any.whl#sha256=344d1f9ee5a20b141a4a4139874eae30a68216f0c995d03ea2e3b3e9d29c4cd5\ndiff --git a/build_tools/circle/doc_min_dependencies_linux-64_conda.lock b/build_tools/circle/doc_min_dependencies_linux-64_conda.lock\nindex 3708a86ff6a7a..680607c97c7fc 100644\n--- a/build_tools/circle/doc_min_dependencies_linux-64_conda.lock\n+++ b/build_tools/circle/doc_min_dependencies_linux-64_conda.lock\n@@ -3,13 +3,13 @@\n # input_hash: aa64e81a701c97b7c4cf149f108c3ca59fc65572bfda79dbaeb2d093afc8a665\n @EXPLICIT\n https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81\n-https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.2.2-hbcca054_0.conda#2f4327a1cbe7f022401b236e915a5fef\n+https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.6.2-hbcca054_0.conda#847c3c2905cc467cea52c24f9cfa8080\n https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2#0c96522c6bdaed4b1566d11387caaf45\n https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6\n https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb\n https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_2.conda#cbbe59391138ea5ad3658c76912e147f\n https://conda.anaconda.org/conda-forge/noarch/kernel-headers_linux-64-2.6.32-he073ed8_17.conda#d731b543793afc0433c4fd593e693fce\n-https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.40-hf3520f5_1.conda#33b7851c39c25da14f6a233a8ccbeeca\n+https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.40-hf3520f5_2.conda#61b0bd5219ce7192b4e3633521a78975\n https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-12.3.0-h0223996_107.conda#851e9651c9e4cd5dc19f80398eba9a1c\n https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-12.3.0-h0223996_107.conda#167a1f5d77d8f3c2a638f7eb418429f1\n https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-13.2.0-hc0a3c3a_7.conda#53ebd4c833fa01cb2c6353e99f905406\n@@ -19,9 +19,9 @@ https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h0c530f3_0.conda#1610\n https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2#f766549260d6815b0c52253f1fb1bb29\n https://conda.anaconda.org/conda-forge/linux-64/libgomp-13.2.0-h77fa898_7.conda#abf3fec87c2563697defa759dec3d639\n https://conda.anaconda.org/conda-forge/noarch/sysroot_linux-64-2.12-he073ed8_17.conda#595db67e32b276298ff3d94d07d47fbf\n-https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.40-ha1999f0_1.conda#e901545940ebdc5c40017fab53642b3c\n+https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.40-ha1999f0_2.conda#861a9d0b9ad43dcebe5a76f38a7d2527\n https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2#fee5683a3f04bd15cbd8318b096a27ab\n-https://conda.anaconda.org/conda-forge/linux-64/binutils-2.40-h4852527_1.conda#dfaea5684bbbbf0b64a4c31f984d0661\n+https://conda.anaconda.org/conda-forge/linux-64/binutils-2.40-h4852527_2.conda#0ea11d9433ec00000e96e82d6381671d\n https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.40-hdade7a5_3.conda#2d9a60578bc28469d9aeef9aea5520c3\n https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_kmp_llvm.tar.bz2#562b26ba2e19059551a811e72ab7f793\n https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-13.2.0-h77fa898_7.conda#72ec1b1b04c4d15d4204ece1ecea5978\n@@ -49,13 +49,13 @@ https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-12.3.0-hb8811af_7.c\n https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda#40b61aab5c7ba9ff276c41cfffe6b80b\n https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.4.0-hd590300_0.conda#b26e8aa824079e1be0294e7152ca4559\n https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda#5aa797f8787fe7a17d1b0821485b5adc\n-https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.13-h4ab18f5_6.conda#27329162c0dc732bcf67a4e0cd488125\n+https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-h4ab18f5_1.conda#57d7dc60e9325e3de37ff8dffd18e814\n https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.4-hcb278e6_0.conda#318b08df404f9c9be5712aaa5a6f0bb0\n https://conda.anaconda.org/conda-forge/linux-64/mpg123-1.32.6-h59595ed_0.conda#9160cdeb523a1b20cf8d2a0bf821f45d\n https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h59595ed_0.conda#fcea371545eda051b6deafb24889fc69\n https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-h297d8ca_0.conda#3aa1c7e292afeff25a0091ddd7c69b72\n https://conda.anaconda.org/conda-forge/linux-64/nspr-4.35-h27087fc_0.conda#da0ec11a6454ae19bff5b02ed881a2b1\n-https://conda.anaconda.org/conda-forge/linux-64/openssl-3.3.0-h4ab18f5_3.conda#12ea6d0d4ed54530eaed18e4835c1f7c\n+https://conda.anaconda.org/conda-forge/linux-64/openssl-3.3.1-h4ab18f5_0.conda#a41fa0e391cc9e0d6b78ac69ca047a6c\n https://conda.anaconda.org/conda-forge/linux-64/pixman-0.43.2-h59595ed_0.conda#71004cbf7924e19c02746ccde9fd7123\n https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2#22dad4df6e8630e8dff2428f6f6a7036\n https://conda.anaconda.org/conda-forge/linux-64/xorg-kbproto-1.0.7-h7f98852_1002.tar.bz2#4b230e8381279d76131116660f5a241a\n@@ -80,13 +80,13 @@ https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.43-h2797004_0.conda#0\n https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.45.3-h2797004_0.conda#b3316cbe90249da4f8e84cd66e1cc55b\n https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0\n https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.15-h0b41bf4_0.conda#33277193f5b92bad9fdd230eb700929c\n-https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.12.7-hc051c1a_0.conda#5d801a4906adc712d480afc362623b59\n+https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.12.7-hc051c1a_1.conda#340278ded8b0dc3a73f3660bbb0adbc6\n https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.3.0-hf1915f5_4.conda#784a4df6676c581ca624fbe460703a6d\n https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.43-hcad00b1_0.conda#8292dea9e022d9610a11fce5e0896ed8\n https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda#47d31b792659ce70f470b5c82fdfb7a4\n https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda#d453b98d9c83e71da0741bb0ff4d76bc\n https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.4-h7391055_0.conda#93ee23f12bc2e684548181256edd2cf6\n-https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.13-h4ab18f5_6.conda#559d338a4234c2ad6e676f460a093e67\n+https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.1-h4ab18f5_1.conda#9653f1bf3766164d0e65fa723cabbc54\n https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.6-ha6fb4c9_0.conda#4d056880988120e29d75bfff282e0f45\n https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-h267a509_2.conda#9ae35c3d96db2c94ce0cef86efdfa2cb\n https://conda.anaconda.org/conda-forge/linux-64/gcc-12.3.0-h915e2ae_7.conda#84b1c5cebd0a0443f3d7f90a4be93fc6\n@@ -125,7 +125,7 @@ https://conda.anaconda.org/conda-forge/noarch/docutils-0.21.2-pyhd8ed1ab_0.conda\n https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.0-pyhd8ed1ab_2.conda#8d652ea2ee8eaee02ed8dc820bc794aa\n https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_0.conda#15dda3cdbf330abfe9f555d22f66db46\n https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.2-h14ed4e7_0.conda#0f69b688f52ff6da70bccb7ff7001d1d\n-https://conda.anaconda.org/conda-forge/noarch/fsspec-2024.5.0-pyhff2d567_0.conda#d73e9932511ef7670b2cc0ebd9dfbd30\n+https://conda.anaconda.org/conda-forge/noarch/fsspec-2024.6.0-pyhff2d567_0.conda#ad6af3f92e71b1579ac2362b6cf29105\n https://conda.anaconda.org/conda-forge/linux-64/gfortran-12.3.0-h915e2ae_7.conda#8efa768f7f74085629f3e1090e7f0569\n https://conda.anaconda.org/conda-forge/linux-64/gfortran_linux-64-12.3.0-h617cb40_3.conda#3a9e5b8a6f651ff14e74d896d8f04ab6\n https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.80.2-hb6ce0ca_0.conda#a965aeaf060289528a3fbe09326edae2\n@@ -167,7 +167,7 @@ https://conda.anaconda.org/conda-forge/noarch/toml-0.10.2-pyhd8ed1ab_0.tar.bz2#f\n https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5844808ffab9ebdb694585b50ba02a96\n https://conda.anaconda.org/conda-forge/noarch/toolz-0.12.1-pyhd8ed1ab_0.conda#2fcb582444635e2c402e8569bb94e039\n https://conda.anaconda.org/conda-forge/linux-64/tornado-6.4-py39hd1e30aa_0.conda#1e865e9188204cdfb1fd2531780add88\n-https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.11.0-pyha770c72_0.conda#6ef2fc37559256cf682d8b3375e89b80\n+https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.12.1-pyha770c72_0.conda#26d7ee34132362115093717c706c384c\n https://conda.anaconda.org/conda-forge/noarch/wheel-0.43.0-pyhd8ed1ab_1.conda#0b5293a157c2b5cd513dd1b03d8d3aae\n https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-h8ee46fc_1.conda#9d7bcddf49cbf727730af10e71022c73\n https://conda.anaconda.org/conda-forge/linux-64/xkeyboard-config-2.41-hd590300_0.conda#81f740407b45e3f9047b3174fa94eb9e\n@@ -196,7 +196,7 @@ https://conda.anaconda.org/conda-forge/linux-64/pillow-10.3.0-py39h90c7501_0.con\n https://conda.anaconda.org/conda-forge/noarch/pip-24.0-pyhd8ed1ab_0.conda#f586ac1e56c8638b64f9c8122a7b8a67\n https://conda.anaconda.org/conda-forge/noarch/plotly-5.14.0-pyhd8ed1ab_0.conda#6a7bcc42ef58dd6cf3da9333ea102433\n https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.8.0-pyhd8ed1ab_0.conda#573fe09d7bd0cd4bcc210d8369b5ca47\n-https://conda.anaconda.org/conda-forge/noarch/pytest-8.2.1-pyhd8ed1ab_0.conda#e4418e8bdbaa8eea28e047531e6763c8\n+https://conda.anaconda.org/conda-forge/noarch/pytest-8.2.2-pyhd8ed1ab_0.conda#0f3f49c22c7ef3a1195fa61dad3c43be\n https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0-pyhd8ed1ab_0.conda#2cf4264fffb9e6eff6031c5b6884d61c\n https://conda.anaconda.org/conda-forge/linux-64/sip-6.7.12-py39h3d6467e_0.conda#e667a3ab0df62c54e60e1843d2e6defb\n https://conda.anaconda.org/conda-forge/noarch/urllib3-2.2.1-pyhd8ed1ab_0.conda#08807a87fa7af10754d46f63b368e016\n@@ -210,8 +210,8 @@ https://conda.anaconda.org/conda-forge/noarch/meson-python-0.16.0-pyh0c530f3_0.c\n https://conda.anaconda.org/conda-forge/linux-64/mkl-devel-2024.1.0-ha770c72_693.conda#7f422e2cf549a3fb920c95288393870d\n https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.12.2-py39h3d6467e_5.conda#93aff412f3e49fdb43361c0215cbd72d\n https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.5.0-pyhd8ed1ab_0.conda#d5f595da2daead898ca958ac62f0307b\n-https://conda.anaconda.org/conda-forge/noarch/requests-2.32.2-pyhd8ed1ab_0.conda#e1643b34b19df8c028a4f00bf5df58a6\n-https://conda.anaconda.org/conda-forge/noarch/dask-core-2024.5.1-pyhd8ed1ab_0.conda#d4f60ccc5421472d2583efd9ce39d8b1\n+https://conda.anaconda.org/conda-forge/noarch/requests-2.32.3-pyhd8ed1ab_0.conda#5ede4753180c7a550a443c430dc8ab52\n+https://conda.anaconda.org/conda-forge/noarch/dask-core-2024.5.2-pyhd8ed1ab_0.conda#1a57a819915e1c169b74933720b138f2\n https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.24.4-h9ad1361_0.conda#147cce520ec59367549fd0d96d404213\n https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-22_linux64_mkl.conda#d6f942423116553f068b2f2d93ffea2e\n https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-22_linux64_mkl.conda#4edf2e7ce63920e4f539d12e32fb478e\n", "test_patch": "diff --git a/build_tools/azure/pylatest_conda_forge_mkl_linux-64_conda.lock b/build_tools/azure/pylatest_conda_forge_mkl_linux-64_conda.lock\nindex bf4b0087c662b..fd242f3dcadf0 100644\n--- a/build_tools/azure/pylatest_conda_forge_mkl_linux-64_conda.lock\n+++ b/build_tools/azure/pylatest_conda_forge_mkl_linux-64_conda.lock\n@@ -3,12 +3,12 @@\n # input_hash: 50fed47bc507d9ee3dbf5ff7a2247cb88944928bd5797e534ebdf8ece2d858ec\n @EXPLICIT\n https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81\n-https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.2.2-hbcca054_0.conda#2f4327a1cbe7f022401b236e915a5fef\n+https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.6.2-hbcca054_0.conda#847c3c2905cc467cea52c24f9cfa8080\n https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2#0c96522c6bdaed4b1566d11387caaf45\n https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6\n https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb\n https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_2.conda#cbbe59391138ea5ad3658c76912e147f\n-https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.40-hf3520f5_1.conda#33b7851c39c25da14f6a233a8ccbeeca\n+https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.40-hf3520f5_2.conda#61b0bd5219ce7192b4e3633521a78975\n https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-13.2.0-hc0a3c3a_7.conda#53ebd4c833fa01cb2c6353e99f905406\n https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.11-4_cp311.conda#d786502c97404c94d7d58d258a445a65\n https://conda.anaconda.org/conda-forge/noarch/tzdata-2024a-h0c530f3_0.conda#161081fc7cec0bfda0d86d7cb595f8d8\n@@ -48,13 +48,13 @@ https://conda.anaconda.org/conda-forge/linux-64/libutf8proc-2.8.0-h166bdaf_0.tar\n https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda#40b61aab5c7ba9ff276c41cfffe6b80b\n https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.4.0-hd590300_0.conda#b26e8aa824079e1be0294e7152ca4559\n https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda#5aa797f8787fe7a17d1b0821485b5adc\n-https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.13-hd590300_5.conda#f36c115f1ee199da648e0597ec2047ad\n+https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-h4ab18f5_1.conda#57d7dc60e9325e3de37ff8dffd18e814\n https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.4-hcb278e6_0.conda#318b08df404f9c9be5712aaa5a6f0bb0\n https://conda.anaconda.org/conda-forge/linux-64/mpg123-1.32.6-h59595ed_0.conda#9160cdeb523a1b20cf8d2a0bf821f45d\n https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h59595ed_0.conda#fcea371545eda051b6deafb24889fc69\n https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-h297d8ca_0.conda#3aa1c7e292afeff25a0091ddd7c69b72\n https://conda.anaconda.org/conda-forge/linux-64/nspr-4.35-h27087fc_0.conda#da0ec11a6454ae19bff5b02ed881a2b1\n-https://conda.anaconda.org/conda-forge/linux-64/openssl-3.3.0-h4ab18f5_3.conda#12ea6d0d4ed54530eaed18e4835c1f7c\n+https://conda.anaconda.org/conda-forge/linux-64/openssl-3.3.1-h4ab18f5_0.conda#a41fa0e391cc9e0d6b78ac69ca047a6c\n https://conda.anaconda.org/conda-forge/linux-64/pixman-0.43.2-h59595ed_0.conda#71004cbf7924e19c02746ccde9fd7123\n https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2#22dad4df6e8630e8dff2428f6f6a7036\n https://conda.anaconda.org/conda-forge/linux-64/rdma-core-28.9-h59595ed_1.conda#aeffb7c06b5f65e55e6c637408dc4100\n@@ -91,7 +91,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.45.3-h2797004_0.cond\n https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.11.0-h0841786_0.conda#1f5a58e686b13bcfde88b93f547d23fe\n https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0\n https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.15-h0b41bf4_0.conda#33277193f5b92bad9fdd230eb700929c\n-https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.12.7-hc051c1a_0.conda#5d801a4906adc712d480afc362623b59\n+https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.12.7-hc051c1a_1.conda#340278ded8b0dc3a73f3660bbb0adbc6\n https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.3.0-hf1915f5_4.conda#784a4df6676c581ca624fbe460703a6d\n https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.43-hcad00b1_0.conda#8292dea9e022d9610a11fce5e0896ed8\n https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda#47d31b792659ce70f470b5c82fdfb7a4\n@@ -99,7 +99,7 @@ https://conda.anaconda.org/conda-forge/linux-64/s2n-1.3.49-h06160fa_0.conda#1d78\n https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda#d453b98d9c83e71da0741bb0ff4d76bc\n https://conda.anaconda.org/conda-forge/linux-64/ucx-1.14.1-h64cca9d_5.conda#39aa3b356d10d7e5add0c540945a0944\n https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.4-h7391055_0.conda#93ee23f12bc2e684548181256edd2cf6\n-https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.13-hd590300_5.conda#68c34ec6149623be41a1933ab996a209\n+https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.1-h4ab18f5_1.conda#9653f1bf3766164d0e65fa723cabbc54\n https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.6-ha6fb4c9_0.conda#4d056880988120e29d75bfff282e0f45\n https://conda.anaconda.org/conda-forge/linux-64/aws-c-io-0.13.32-he9a53bd_1.conda#8a24e5820f4a0ffd2ed9c4722cd5d7ca\n https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_9.conda#d47dee1856d9cb955b8076eeff304a5b\n@@ -124,7 +124,7 @@ https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h8ee46fc_\n https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-hd590300_1.conda#e995b155d938b6779da6ace6c6b13816\n https://conda.anaconda.org/conda-forge/linux-64/xcb-util-wm-0.4.1-h8ee46fc_1.conda#90108a432fb5c6150ccfee3f03388656\n https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.8.9-h8ee46fc_0.conda#077b6e8ad6a3ddb741fce2496dd01bec\n-https://conda.anaconda.org/conda-forge/noarch/array-api-compat-1.7-pyhd8ed1ab_0.conda#c359e5b3182a7147e29f9e29ebad7cdd\n+https://conda.anaconda.org/conda-forge/noarch/array-api-compat-1.7.1-pyhd8ed1ab_0.conda#8791d81c38f676a7c08c76546800bf70\n https://conda.anaconda.org/conda-forge/linux-64/aws-c-event-stream-0.3.1-h2e3709c_4.conda#2cf21b1cbc1c096a28ffa2892257a2c1\n https://conda.anaconda.org/conda-forge/linux-64/aws-c-http-0.7.11-h00aa349_4.conda#cb932dff7328ff620ce8059c9968b095\n https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_9.conda#4601544b4982ba1861fa9b9c607b2c06\n@@ -163,7 +163,7 @@ https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.5.0-pyhc1e730c_0.c\n https://conda.anaconda.org/conda-forge/noarch/toml-0.10.2-pyhd8ed1ab_0.tar.bz2#f832c45a477c78bebd107098db465095\n https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5844808ffab9ebdb694585b50ba02a96\n https://conda.anaconda.org/conda-forge/linux-64/tornado-6.4-py311h459d7ec_0.conda#cc7727006191b8f3630936b339a76cd0\n-https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.11.0-pyha770c72_0.conda#6ef2fc37559256cf682d8b3375e89b80\n+https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.12.1-pyha770c72_0.conda#26d7ee34132362115093717c706c384c\n https://conda.anaconda.org/conda-forge/noarch/wheel-0.43.0-pyhd8ed1ab_1.conda#0b5293a157c2b5cd513dd1b03d8d3aae\n https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-h8ee46fc_1.conda#9d7bcddf49cbf727730af10e71022c73\n https://conda.anaconda.org/conda-forge/linux-64/xkeyboard-config-2.41-hd590300_0.conda#81f740407b45e3f9047b3174fa94eb9e\n@@ -172,8 +172,8 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.11-hd590300_\n https://conda.anaconda.org/conda-forge/linux-64/aws-c-auth-0.7.3-h28f7589_1.conda#97503d3e565004697f1651753aa95b9e\n https://conda.anaconda.org/conda-forge/linux-64/aws-c-mqtt-0.9.3-hb447be9_1.conda#c520669eb0be9269a5f0d8ef62531882\n https://conda.anaconda.org/conda-forge/linux-64/cairo-1.18.0-h3faef2a_0.conda#f907bb958910dc404647326ca80c263e\n-https://conda.anaconda.org/conda-forge/linux-64/coverage-7.5.2-py311h331c9d8_0.conda#6afe87fd0c278ed708393cc1cf085146\n-https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.52.1-py311h331c9d8_0.conda#bb70cda5777de08084a402a2cdc13935\n+https://conda.anaconda.org/conda-forge/linux-64/coverage-7.5.3-py311h331c9d8_0.conda#543dd05fd661e4e9c9deb3b37093d6a2\n+https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.53.0-py311h331c9d8_0.conda#2daef6c4ce74840c8d7a431498be83e9\n https://conda.anaconda.org/conda-forge/linux-64/glib-2.80.2-hf974151_0.conda#d427988dc3dbd0a4c136f52db356cc6a\n https://conda.anaconda.org/conda-forge/noarch/joblib-1.4.2-pyhd8ed1ab_0.conda#25df261d4523d9f9783bcdb7208d872f\n https://conda.anaconda.org/conda-forge/linux-64/libgcrypt-1.10.3-hd590300_0.conda#32d16ad533c59bb0a3c5ffaf16110829\n@@ -185,12 +185,12 @@ https://conda.anaconda.org/conda-forge/linux-64/mkl-2022.2.1-h84fe81f_16997.cond\n https://conda.anaconda.org/conda-forge/linux-64/pillow-10.3.0-py311h18e6fac_0.conda#6c520a9d36c9d7270988c7a6c360d6d4\n https://conda.anaconda.org/conda-forge/noarch/pip-24.0-pyhd8ed1ab_0.conda#f586ac1e56c8638b64f9c8122a7b8a67\n https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.8.0-pyhd8ed1ab_0.conda#573fe09d7bd0cd4bcc210d8369b5ca47\n-https://conda.anaconda.org/conda-forge/noarch/pytest-8.2.1-pyhd8ed1ab_0.conda#e4418e8bdbaa8eea28e047531e6763c8\n+https://conda.anaconda.org/conda-forge/noarch/pytest-8.2.2-pyhd8ed1ab_0.conda#0f3f49c22c7ef3a1195fa61dad3c43be\n https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0-pyhd8ed1ab_0.conda#2cf4264fffb9e6eff6031c5b6884d61c\n https://conda.anaconda.org/conda-forge/linux-64/sip-6.7.12-py311hb755f60_0.conda#02336abab4cb5dd794010ef53c54bd09\n https://conda.anaconda.org/conda-forge/linux-64/aws-c-s3-0.3.14-hf3aad02_1.conda#a968ffa7e9fe0c257628033d393e512f\n https://conda.anaconda.org/conda-forge/linux-64/blas-1.0-mkl.tar.bz2#349aef876b1d8c9dccae01de20d5b385\n-https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.24.3-haf2f30d_0.conda#f3df87cc9ef0b5113bff55aefcbcafd5\n+https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.24.4-haf2f30d_0.conda#926c2c7ee7a0b48d6d70783a33f7bc80\n https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-8.5.0-hfac3d4d_0.conda#f5126317dd0ce0ba26945e411ecc6960\n https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-16_linux64_mkl.tar.bz2#85f61af03fd291dae33150ffe89dc09a\n https://conda.anaconda.org/conda-forge/linux-64/libsystemd0-255-h3516f8a_1.conda#3366af27f0b593544a6cd453c7932ac5\n@@ -199,7 +199,7 @@ https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.12.2-py311hb755f60_\n https://conda.anaconda.org/conda-forge/noarch/pytest-cov-5.0.0-pyhd8ed1ab_0.conda#c54c0107057d67ddf077751339ec2c63\n https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.5.0-pyhd8ed1ab_0.conda#d5f595da2daead898ca958ac62f0307b\n https://conda.anaconda.org/conda-forge/linux-64/aws-crt-cpp-0.21.0-hb942446_5.conda#07d92ed5403ad7b5c66ffd7d5b8f7e57\n-https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.24.3-h9ad1361_0.conda#8fb0e954c616bb0f9389efac4b4ed44b\n+https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.24.4-h9ad1361_0.conda#147cce520ec59367549fd0d96d404213\n https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_mkl.tar.bz2#361bf757b95488de76c4f123805742d3\n https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_mkl.tar.bz2#a2f166748917d6d6e4707841ca1f519e\n https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-client-17.0-hb77b528_0.conda#07f45f1be1c25345faddb8db0de8039b\n@@ -210,7 +210,7 @@ https://conda.anaconda.org/conda-forge/noarch/array-api-strict-1.1.1-pyhd8ed1ab_\n https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.2.1-py311h9547e67_0.conda#74ad0ae64f1ef565e27eda87fa749e84\n https://conda.anaconda.org/conda-forge/linux-64/libarrow-12.0.1-hb87d912_8_cpu.conda#3f3b11398fe79b578e3c44dd00a44e4a\n https://conda.anaconda.org/conda-forge/linux-64/pandas-2.2.2-py311h14de704_1.conda#84e2dd379d4edec4dd6382861486104d\n-https://conda.anaconda.org/conda-forge/linux-64/polars-0.20.29-py311h00856b1_0.conda#f231b6f51b2154ac92fe26b874dafca2\n+https://conda.anaconda.org/conda-forge/linux-64/polars-0.20.31-py311h00856b1_0.conda#4f1cc2c95c25fe838acabfa8dc0d48ff\n https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.9-py311hf0fb5b6_5.conda#ec7e45bc76d9d0b69a74a2075932b8e8\n https://conda.anaconda.org/conda-forge/linux-64/pytorch-1.13.1-cpu_py311h410fd25_1.conda#ddd2fadddf89e3dc3d541a2537fce010\n https://conda.anaconda.org/conda-forge/linux-64/scipy-1.13.1-py311h517d4fd_0.conda#764b0e055f59dbd7d114d32b8c6e55e6\ndiff --git a/build_tools/azure/pylatest_conda_forge_mkl_osx-64_conda.lock b/build_tools/azure/pylatest_conda_forge_mkl_osx-64_conda.lock\nindex 833ce559e02b9..3dd8b0e898c3b 100644\n--- a/build_tools/azure/pylatest_conda_forge_mkl_osx-64_conda.lock\n+++ b/build_tools/azure/pylatest_conda_forge_mkl_osx-64_conda.lock\n@@ -3,7 +3,7 @@\n # input_hash: e7c2bc2b07721ef735f30d3b1cf0b2a780b5bf5c138d9d18ad174611bfbd32bf\n @EXPLICIT\n https://conda.anaconda.org/conda-forge/osx-64/bzip2-1.0.8-h10d778d_5.conda#6097a6ca9ada32699b5fc4312dd6ef18\n-https://conda.anaconda.org/conda-forge/osx-64/ca-certificates-2024.2.2-h8857fd0_0.conda#f2eacee8c33c43692f1ccfd33d0f50b1\n+https://conda.anaconda.org/conda-forge/osx-64/ca-certificates-2024.6.2-h8857fd0_0.conda#3c23a8cab15ae51ebc9efdc229fccecf\n https://conda.anaconda.org/conda-forge/osx-64/icu-73.2-hf5e326d_0.conda#5cc301d759ec03f28328428e28f65591\n https://conda.anaconda.org/conda-forge/osx-64/libbrotlicommon-1.1.0-h0dc2134_1.conda#9e6c31441c9aa24e41ace40d6151aab6\n https://conda.anaconda.org/conda-forge/osx-64/libdeflate-1.20-h49d49c5_0.conda#d46104f6a896a0bc6a1d37b88b2edf5c\n@@ -13,7 +13,6 @@ https://conda.anaconda.org/conda-forge/noarch/libgfortran-devel_osx-64-12.3.0-h0\n https://conda.anaconda.org/conda-forge/osx-64/libiconv-1.17-hd75f5a5_2.conda#6c3628d047e151efba7cf08c5e54d1ca\n https://conda.anaconda.org/conda-forge/osx-64/libjpeg-turbo-3.0.0-h0dc2134_1.conda#72507f8e3961bc968af17435060b6dd6\n https://conda.anaconda.org/conda-forge/osx-64/libwebp-base-1.4.0-h10d778d_0.conda#b2c0047ea73819d992484faacbbe1c24\n-https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.2.13-h8a1eda9_5.conda#4a3ad23f6e16f99c04e166767193d700\n https://conda.anaconda.org/conda-forge/osx-64/mkl-include-2023.2.0-h6bab518_50500.conda#835abb8ded5e26f23ea6996259c7972e\n https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-h5846eda_0.conda#02a888433d165c99bf09784a7b14d900\n https://conda.anaconda.org/conda-forge/osx-64/pthread-stubs-0.4-hc929b4f_1001.tar.bz2#addd19059de62181cd11ae8f4ef26084\n@@ -25,29 +24,33 @@ https://conda.anaconda.org/conda-forge/osx-64/xz-5.2.6-h775f41a_0.tar.bz2#a72f9d\n https://conda.anaconda.org/conda-forge/osx-64/libbrotlidec-1.1.0-h0dc2134_1.conda#9ee0bab91b2ca579e10353738be36063\n https://conda.anaconda.org/conda-forge/osx-64/libbrotlienc-1.1.0-h0dc2134_1.conda#8a421fe09c6187f0eb5e2338a8a8be6d\n https://conda.anaconda.org/conda-forge/osx-64/libcxx-17.0.6-h88467a6_0.conda#0fe355aecb8d24b8bc07c763209adbd9\n-https://conda.anaconda.org/conda-forge/osx-64/libpng-1.6.43-h92b6c6a_0.conda#65dcddb15965c9de2c0365cb14910532\n-https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.45.3-h92b6c6a_0.conda#68e462226209f35182ef66eda0f794ff\n https://conda.anaconda.org/conda-forge/osx-64/libxcb-1.15-hb7f2c08_0.conda#5513f57e0238c87c12dffedbcc9c1a4a\n-https://conda.anaconda.org/conda-forge/osx-64/libxml2-2.12.7-h3e169fe_0.conda#4c04ba47fdd2ebecc1d3b6a77534d9ef\n+https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.1-h87427d6_1.conda#b7575b5aa92108dcc9aaab0f05f2dbce\n https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-18.1.6-h15ab845_0.conda#065f974bc7afcef3f94df56394e16154\n-https://conda.anaconda.org/conda-forge/osx-64/openssl-3.3.0-h87427d6_3.conda#ec504fefb403644d893adffb6e7a2dbe\n+https://conda.anaconda.org/conda-forge/osx-64/openssl-3.3.1-h87427d6_0.conda#1bdad93ae01353340f194c5d879745db\n https://conda.anaconda.org/conda-forge/osx-64/readline-8.2-h9e318b2_1.conda#f17f77f2acf4d344734bda76829ce14e\n-https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-h1abcd95_1.conda#bf830ba5afc507c6232d4ef0fb1a882d\n-https://conda.anaconda.org/conda-forge/osx-64/zlib-1.2.13-h8a1eda9_5.conda#75a8a98b1c4671c5d2897975731da42d\n-https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.6-h915ae27_0.conda#4cb2cd56f039b129bb0e491c1164167e\n https://conda.anaconda.org/conda-forge/osx-64/brotli-bin-1.1.0-h0dc2134_1.conda#ece565c215adcc47fc1db4e651ee094b\n-https://conda.anaconda.org/conda-forge/osx-64/freetype-2.12.1-h60636b9_2.conda#25152fce119320c980e5470e64834b50\n https://conda.anaconda.org/conda-forge/osx-64/gmp-6.3.0-h73e2aa4_1.conda#92f8d748d95d97f92fc26cfac9bb5b6e\n https://conda.anaconda.org/conda-forge/osx-64/isl-0.26-imath32_h2e86a7b_101.conda#d06222822a9144918333346f145b68c6\n https://conda.anaconda.org/conda-forge/osx-64/lerc-4.0.0-hb486fe8_0.tar.bz2#f9d6a4c82889d5ecedec1d90eb673c55\n https://conda.anaconda.org/conda-forge/osx-64/libgfortran5-13.2.0-h2873a65_3.conda#e4fb4d23ec2870ff3c40d10afe305aec\n-https://conda.anaconda.org/conda-forge/osx-64/libhwloc-2.10.0-default_h456cccd_1001.conda#d2dc768b14cdf226a30a8eab15641305\n-https://conda.anaconda.org/conda-forge/osx-64/libllvm16-16.0.6-hbedff68_3.conda#8fd56c0adc07a37f93bd44aa61a97c90\n+https://conda.anaconda.org/conda-forge/osx-64/libpng-1.6.43-h92b6c6a_0.conda#65dcddb15965c9de2c0365cb14910532\n+https://conda.anaconda.org/conda-forge/osx-64/libsqlite-3.45.3-h92b6c6a_0.conda#68e462226209f35182ef66eda0f794ff\n+https://conda.anaconda.org/conda-forge/osx-64/libxml2-2.12.7-h3e169fe_1.conda#ddb63049aa7bd9f08f2cdc5a1c144d1a\n https://conda.anaconda.org/conda-forge/osx-64/ninja-1.12.1-h3c5361c_0.conda#a0ebabd021c8191aeb82793fe43cfdcb\n-https://conda.anaconda.org/conda-forge/osx-64/python-3.12.3-h1411813_0_cpython.conda#df1448ec6cbf8eceb03d29003cf72ae6\n https://conda.anaconda.org/conda-forge/osx-64/sigtool-0.1.3-h88f4db0_0.tar.bz2#fbfb84b9de9a6939cb165c02c69b1865\n https://conda.anaconda.org/conda-forge/osx-64/tapi-1100.0.11-h9ce4665_0.tar.bz2#f9ff42ccf809a21ba6f8607f8de36108\n+https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-h1abcd95_1.conda#bf830ba5afc507c6232d4ef0fb1a882d\n+https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.1-h87427d6_1.conda#3ac9ef8975965f9698dbedd2a4cc5894\n+https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.6-h915ae27_0.conda#4cb2cd56f039b129bb0e491c1164167e\n https://conda.anaconda.org/conda-forge/osx-64/brotli-1.1.0-h0dc2134_1.conda#9272dd3b19c4e8212f8542cefd5c3d67\n+https://conda.anaconda.org/conda-forge/osx-64/freetype-2.12.1-h60636b9_2.conda#25152fce119320c980e5470e64834b50\n+https://conda.anaconda.org/conda-forge/osx-64/libgfortran-5.0.0-13_2_0_h97931a8_3.conda#0b6e23a012ee7a9a5f6b244f5a92c1d5\n+https://conda.anaconda.org/conda-forge/osx-64/libhwloc-2.10.0-default_h456cccd_1001.conda#d2dc768b14cdf226a30a8eab15641305\n+https://conda.anaconda.org/conda-forge/osx-64/libllvm16-16.0.6-hbedff68_3.conda#8fd56c0adc07a37f93bd44aa61a97c90\n+https://conda.anaconda.org/conda-forge/osx-64/libtiff-4.6.0-h129831d_3.conda#568593071d2e6cea7b5fc1f75bfa10ca\n+https://conda.anaconda.org/conda-forge/osx-64/mpfr-4.2.1-h4f6b447_1.conda#b90df08f0deb2f58631447c1462c92a7\n+https://conda.anaconda.org/conda-forge/osx-64/python-3.12.3-h1411813_0_cpython.conda#df1448ec6cbf8eceb03d29003cf72ae6\n https://conda.anaconda.org/conda-forge/noarch/certifi-2024.2.2-pyhd8ed1ab_0.conda#0876280e409658fc6f9e75d035960333\n https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_0.tar.bz2#3faab06a954c2a04039983f2c4a50d99\n https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhd8ed1ab_0.conda#5cd86562580f274031ede6aa6aa24441\n@@ -56,13 +59,14 @@ https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.0-pyhd8ed1ab_2.\n https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_0.conda#15dda3cdbf330abfe9f555d22f66db46\n https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_0.conda#f800d2da156d08e289b14e87e43c1ae5\n https://conda.anaconda.org/conda-forge/osx-64/kiwisolver-1.4.5-py312h49ebfd2_1.conda#21f174a5cfb5964069c374171a979157\n+https://conda.anaconda.org/conda-forge/osx-64/lcms2-2.16-ha2f27b4_0.conda#1442db8f03517834843666c422238c9b\n https://conda.anaconda.org/conda-forge/osx-64/ld64_osx-64-711-ha20a434_0.conda#a8b41eb97c8a9d618243a79ba78fdc3c\n-https://conda.anaconda.org/conda-forge/osx-64/libclang-cpp16-16.0.6-default_h7151d67_6.conda#7eaad118ab797d1427f8745c861d1925\n-https://conda.anaconda.org/conda-forge/osx-64/libgfortran-5.0.0-13_2_0_h97931a8_3.conda#0b6e23a012ee7a9a5f6b244f5a92c1d5\n-https://conda.anaconda.org/conda-forge/osx-64/libtiff-4.6.0-h129831d_3.conda#568593071d2e6cea7b5fc1f75bfa10ca\n+https://conda.anaconda.org/conda-forge/osx-64/libclang-cpp16-16.0.6-default_h4c8afb6_7.conda#784816790fe438443354d13050fcd67d\n+https://conda.anaconda.org/conda-forge/osx-64/libhiredis-1.0.2-h2beb688_0.tar.bz2#524282b2c46c9dedf051b3bc2ae05494\n https://conda.anaconda.org/conda-forge/osx-64/llvm-tools-16.0.6-hbedff68_3.conda#e9356b0807462e8f84c1384a8da539a5\n-https://conda.anaconda.org/conda-forge/osx-64/mpfr-4.2.1-h4f6b447_1.conda#b90df08f0deb2f58631447c1462c92a7\n+https://conda.anaconda.org/conda-forge/osx-64/mpc-1.3.1-h81bd1dd_0.conda#c752c0eb6c250919559172c011e5f65b\n https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19\n+https://conda.anaconda.org/conda-forge/osx-64/openjpeg-2.5.2-h7310d3a_0.conda#05a14cc9d725dd74995927968d6547e3\n https://conda.anaconda.org/conda-forge/noarch/packaging-24.0-pyhd8ed1ab_0.conda#248f521b64ce055e7feae3105e7abeb8\n https://conda.anaconda.org/conda-forge/noarch/pluggy-1.5.0-pyhd8ed1ab_0.conda#d3483c8fc2dc2cc3f5cf43e26d60cabf\n https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.1.2-pyhd8ed1ab_0.conda#b9a4dacf97241704529131a0dfc0494f\n@@ -76,33 +80,29 @@ https://conda.anaconda.org/conda-forge/noarch/toml-0.10.2-pyhd8ed1ab_0.tar.bz2#f\n https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5844808ffab9ebdb694585b50ba02a96\n https://conda.anaconda.org/conda-forge/osx-64/tornado-6.4-py312h41838bb_0.conda#2d2d1fde5800d45cb56218583156d23d\n https://conda.anaconda.org/conda-forge/noarch/wheel-0.43.0-pyhd8ed1ab_1.conda#0b5293a157c2b5cd513dd1b03d8d3aae\n+https://conda.anaconda.org/conda-forge/osx-64/ccache-4.9.1-h41adc32_0.conda#45aaf96b67840bd98a928de8679098fa\n https://conda.anaconda.org/conda-forge/osx-64/cctools_osx-64-986-ha1c5b94_0.conda#a8951de2506df5649f5a3295fdfd9f2c\n-https://conda.anaconda.org/conda-forge/osx-64/clang-16-16.0.6-default_h7151d67_6.conda#1c298568c30efe7d9369c7c15b748461\n-https://conda.anaconda.org/conda-forge/osx-64/coverage-7.5.2-py312hbd25219_0.conda#7af2cf0238c403ef3f92abdcda6151bf\n-https://conda.anaconda.org/conda-forge/osx-64/fonttools-4.52.1-py312hbd25219_0.conda#e830ecb59e43bcaabc98fa945e1985a3\n+https://conda.anaconda.org/conda-forge/osx-64/clang-16-16.0.6-default_h4c8afb6_7.conda#c9da6a62b571cac3707db69610ed7bd3\n+https://conda.anaconda.org/conda-forge/osx-64/coverage-7.5.3-py312hbd25219_0.conda#135eeb22a4da903e2d06c4323b459003\n+https://conda.anaconda.org/conda-forge/osx-64/fonttools-4.53.0-py312hbd25219_0.conda#ce2e9b0279cbbae03017ec7be748b255\n+https://conda.anaconda.org/conda-forge/osx-64/gfortran_impl_osx-64-12.3.0-hc328e78_3.conda#b3d751dc7073bbfdfa9d863e39b9685d\n https://conda.anaconda.org/conda-forge/noarch/joblib-1.4.2-pyhd8ed1ab_0.conda#25df261d4523d9f9783bcdb7208d872f\n-https://conda.anaconda.org/conda-forge/osx-64/lcms2-2.16-ha2f27b4_0.conda#1442db8f03517834843666c422238c9b\n https://conda.anaconda.org/conda-forge/osx-64/ld64-711-ha02d983_0.conda#3ae4930ec076735cce481e906f5192e0\n-https://conda.anaconda.org/conda-forge/osx-64/libhiredis-1.0.2-h2beb688_0.tar.bz2#524282b2c46c9dedf051b3bc2ae05494\n https://conda.anaconda.org/conda-forge/noarch/meson-1.4.0-pyhd8ed1ab_0.conda#52a0660cfa40b45bf254ecc3374cb2e0\n https://conda.anaconda.org/conda-forge/osx-64/mkl-2023.2.0-h54c2260_50500.conda#0a342ccdc79e4fcd359245ac51941e7b\n-https://conda.anaconda.org/conda-forge/osx-64/mpc-1.3.1-h81bd1dd_0.conda#c752c0eb6c250919559172c011e5f65b\n-https://conda.anaconda.org/conda-forge/osx-64/openjpeg-2.5.2-h7310d3a_0.conda#05a14cc9d725dd74995927968d6547e3\n+https://conda.anaconda.org/conda-forge/osx-64/pillow-10.3.0-py312h0c923fa_0.conda#6f0591ae972e9b815739da3392fbb3c3\n https://conda.anaconda.org/conda-forge/noarch/pip-24.0-pyhd8ed1ab_0.conda#f586ac1e56c8638b64f9c8122a7b8a67\n https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.8.0-pyhd8ed1ab_0.conda#573fe09d7bd0cd4bcc210d8369b5ca47\n-https://conda.anaconda.org/conda-forge/noarch/pytest-8.2.1-pyhd8ed1ab_0.conda#e4418e8bdbaa8eea28e047531e6763c8\n+https://conda.anaconda.org/conda-forge/noarch/pytest-8.2.2-pyhd8ed1ab_0.conda#0f3f49c22c7ef3a1195fa61dad3c43be\n https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0-pyhd8ed1ab_0.conda#2cf4264fffb9e6eff6031c5b6884d61c\n-https://conda.anaconda.org/conda-forge/osx-64/ccache-4.9.1-h41adc32_0.conda#45aaf96b67840bd98a928de8679098fa\n https://conda.anaconda.org/conda-forge/osx-64/cctools-986-h40f6528_0.conda#b7a2ca0062a6ee8bc4e83ec887bef942\n-https://conda.anaconda.org/conda-forge/osx-64/clang-16.0.6-hdae98eb_6.conda#884e7b24306e4f21b7ee08dabadb2ecc\n-https://conda.anaconda.org/conda-forge/osx-64/gfortran_impl_osx-64-12.3.0-hc328e78_3.conda#b3d751dc7073bbfdfa9d863e39b9685d\n+https://conda.anaconda.org/conda-forge/osx-64/clang-16.0.6-hd4457cd_7.conda#0f91e4c1d9d85887db66ddbc185d65d4\n https://conda.anaconda.org/conda-forge/osx-64/libblas-3.9.0-20_osx64_mkl.conda#160fdc97a51d66d51dc782fb67d35205\n https://conda.anaconda.org/conda-forge/noarch/meson-python-0.16.0-pyh0c530f3_0.conda#e16f0dbf502da873be9f9adb0dc52547\n https://conda.anaconda.org/conda-forge/osx-64/mkl-devel-2023.2.0-h694c41f_50500.conda#1b4d0235ef253a1e19459351badf4f9f\n-https://conda.anaconda.org/conda-forge/osx-64/pillow-10.3.0-py312h0c923fa_0.conda#6f0591ae972e9b815739da3392fbb3c3\n https://conda.anaconda.org/conda-forge/noarch/pytest-cov-5.0.0-pyhd8ed1ab_0.conda#c54c0107057d67ddf077751339ec2c63\n https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.5.0-pyhd8ed1ab_0.conda#d5f595da2daead898ca958ac62f0307b\n-https://conda.anaconda.org/conda-forge/osx-64/clangxx-16.0.6-default_h7151d67_6.conda#cc8c007a529a7cfaa5d29d8599df3fe6\n+https://conda.anaconda.org/conda-forge/osx-64/clangxx-16.0.6-default_ha3b9224_7.conda#00c8a212cbbd427dcbcc4231b23ddc5e\n https://conda.anaconda.org/conda-forge/osx-64/libcblas-3.9.0-20_osx64_mkl.conda#51089a4865eb4aec2bc5c7468bd07f9f\n https://conda.anaconda.org/conda-forge/osx-64/liblapack-3.9.0-20_osx64_mkl.conda#58f08e12ad487fac4a08f90ff0b87aec\n https://conda.anaconda.org/conda-forge/noarch/compiler-rt_osx-64-16.0.6-ha38d28d_2.conda#7a46507edc35c6c8818db0adaf8d787f\ndiff --git a/build_tools/azure/pylatest_conda_mkl_no_openmp_osx-64_conda.lock b/build_tools/azure/pylatest_conda_mkl_no_openmp_osx-64_conda.lock\nindex 956622fe0008e..0f63997fd8d9d 100644\n--- a/build_tools/azure/pylatest_conda_mkl_no_openmp_osx-64_conda.lock\n+++ b/build_tools/azure/pylatest_conda_mkl_no_openmp_osx-64_conda.lock\n@@ -80,7 +80,7 @@ https://repo.anaconda.com/pkgs/main/osx-64/scipy-1.11.4-py312h81688c2_0.conda#7d\n https://repo.anaconda.com/pkgs/main/osx-64/pandas-2.2.1-py312he282a81_0.conda#021b70a1e40efb75b89eb8ebdb347132\n https://repo.anaconda.com/pkgs/main/osx-64/pyamg-4.2.3-py312h44cbcf4_0.conda#3bdc7be74087b3a5a83c520a74e1e8eb\n # pip cython @ https://files.pythonhosted.org/packages/d5/6d/06c08d75adb98cdf72af18801e193d22580cc86ca553610f430f18ea26b3/Cython-3.0.10-cp312-cp312-macosx_10_9_x86_64.whl#sha256=8f2864ab5fcd27a346f0b50f901ebeb8f60b25a60a575ccfd982e7f3e9674914\n-# pip meson @ https://files.pythonhosted.org/packages/33/75/b1a37fa7b2dbca8c0dbb04d5cdd7e2720c8ef6febe41b4a74866350e041c/meson-1.4.0-py3-none-any.whl#sha256=476a458d51fcfa322a6bdc64da5138997c542d08e6b2e49b9fa68c46fd7c4475\n+# pip meson @ https://files.pythonhosted.org/packages/44/b2/d4433391a7c5e94a39b50ca7295a8ceba736e7c72c455752a60122f52453/meson-1.4.1-py3-none-any.whl#sha256=d5acc3abae2dad3c70ddcbd10acac92b78b144d34d43f40f5b8ac31dfd8a826a\n # pip threadpoolctl @ https://files.pythonhosted.org/packages/4b/2c/ffbf7a134b9ab11a67b0cf0726453cedd9c5043a4fe7a35d1cefa9a1bcfb/threadpoolctl-3.5.0-py3-none-any.whl#sha256=56c1e26c150397e58c4926da8eeee87533b1e32bef131bd4bf6a2f45f3185467\n # pip pyproject-metadata @ https://files.pythonhosted.org/packages/aa/5f/bb5970d3d04173b46c9037109f7f05fc8904ff5be073ee49bb6ff00301bc/pyproject_metadata-0.8.0-py3-none-any.whl#sha256=ad858d448e1d3a1fb408ac5bac9ea7743e7a8bbb472f2693aaa334d2db42f526\n # pip meson-python @ https://files.pythonhosted.org/packages/91/c0/104cb6244c83fe6bc3886f144cc433db0c0c78efac5dc00e409a5a08c87d/meson_python-0.16.0-py3-none-any.whl#sha256=842dc9f5dc29e55fc769ff1b6fe328412fe6c870220fc321060a1d2d395e69e8\ndiff --git a/build_tools/azure/pylatest_pip_openblas_pandas_linux-64_conda.lock b/build_tools/azure/pylatest_pip_openblas_pandas_linux-64_conda.lock\nindex fb409d975c5a4..3084083a7edf5 100644\n--- a/build_tools/azure/pylatest_pip_openblas_pandas_linux-64_conda.lock\n+++ b/build_tools/azure/pylatest_pip_openblas_pandas_linux-64_conda.lock\n@@ -25,21 +25,21 @@ https://repo.anaconda.com/pkgs/main/linux-64/wheel-0.43.0-py39h06a4308_0.conda#4\n https://repo.anaconda.com/pkgs/main/linux-64/pip-24.0-py39h06a4308_0.conda#7f8ce3af15cfecd12e4dda8c5cef5fb7\n # pip alabaster @ https://files.pythonhosted.org/packages/32/34/d4e1c02d3bee589efb5dfa17f88ea08bdb3e3eac12bc475462aec52ed223/alabaster-0.7.16-py3-none-any.whl#sha256=b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92\n # pip babel @ https://files.pythonhosted.org/packages/27/45/377f7e32a5c93d94cd56542349b34efab5ca3f9e2fd5a68c5e93169aa32d/Babel-2.15.0-py3-none-any.whl#sha256=08706bdad8d0a3413266ab61bd6c34d0c28d6e1e7badf40a2cebe67644e2e1fb\n-# pip certifi @ https://files.pythonhosted.org/packages/ba/06/a07f096c664aeb9f01624f858c3add0a4e913d6c96257acb4fce61e7de14/certifi-2024.2.2-py3-none-any.whl#sha256=dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1\n+# pip certifi @ https://files.pythonhosted.org/packages/5b/11/1e78951465b4a225519b8c3ad29769c49e0d8d157a070f681d5b6d64737f/certifi-2024.6.2-py3-none-any.whl#sha256=ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56\n # pip charset-normalizer @ https://files.pythonhosted.org/packages/98/69/5d8751b4b670d623aa7a47bef061d69c279e9f922f6705147983aa76c3ce/charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl#sha256=b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796\n # pip cycler @ https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl#sha256=85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30\n # pip cython @ https://files.pythonhosted.org/packages/a7/f5/3dde4d96076888ceaa981827b098274c2b45ddd4b20d75a8cfaa92b91eec/Cython-3.0.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl#sha256=651a15a8534ebfb9b58cb0b87c269c70984b6f9c88bfe65e4f635f0e3f07dfcd\n # pip docutils @ https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl#sha256=dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2\n # pip exceptiongroup @ https://files.pythonhosted.org/packages/01/90/79fe92dd413a9cab314ef5c591b5aa9b9ba787ae4cadab75055b0ae00b33/exceptiongroup-1.2.1-py3-none-any.whl#sha256=5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad\n # pip execnet @ https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-py3-none-any.whl#sha256=26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc\n-# pip fonttools @ https://files.pythonhosted.org/packages/22/56/8b2ffe792b0a11d481ed797e0c246ee914b86370e317476c84d4fc537d95/fonttools-4.52.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl#sha256=ee2a8c1101d06cc8fca7851dceb67afd53dd6fc0288bacaa632e647bc5afff58\n+# pip fonttools @ https://files.pythonhosted.org/packages/c1/cb/b1877d606dfa1daca70324bf37afec2b0a386138c467580027b9b51188a8/fonttools-4.53.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl#sha256=ba9f09ff17f947392a855e3455a846f9855f6cf6bec33e9a427d3c1d254c712f\n # pip idna @ https://files.pythonhosted.org/packages/e5/3e/741d8c82801c347547f8a2a06aa57dbb1992be9e948df2ea0eda2c8b79e8/idna-3.7-py3-none-any.whl#sha256=82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0\n # pip imagesize @ https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl#sha256=0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b\n # pip iniconfig @ https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl#sha256=b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374\n # pip joblib @ https://files.pythonhosted.org/packages/91/29/df4b9b42f2be0b623cbd5e2140cafcaa2bef0759a00b7b70104dcfe2fb51/joblib-1.4.2-py3-none-any.whl#sha256=06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6\n # pip kiwisolver @ https://files.pythonhosted.org/packages/c0/a8/841594f11d0b88d8aeb26991bc4dac38baa909dc58d0c4262a4f7893bcbf/kiwisolver-1.4.5-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl#sha256=6c3bd3cde54cafb87d74d8db50b909705c62b17c2099b8f2e25b461882e544ff\n # pip markupsafe @ https://files.pythonhosted.org/packages/5f/5a/360da85076688755ea0cceb92472923086993e86b5613bbae9fbc14136b0/MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl#sha256=17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3\n-# pip meson @ https://files.pythonhosted.org/packages/33/75/b1a37fa7b2dbca8c0dbb04d5cdd7e2720c8ef6febe41b4a74866350e041c/meson-1.4.0-py3-none-any.whl#sha256=476a458d51fcfa322a6bdc64da5138997c542d08e6b2e49b9fa68c46fd7c4475\n+# pip meson @ https://files.pythonhosted.org/packages/44/b2/d4433391a7c5e94a39b50ca7295a8ceba736e7c72c455752a60122f52453/meson-1.4.1-py3-none-any.whl#sha256=d5acc3abae2dad3c70ddcbd10acac92b78b144d34d43f40f5b8ac31dfd8a826a\n # pip networkx @ https://files.pythonhosted.org/packages/d5/f0/8fbc882ca80cf077f1b246c0e3c3465f7f415439bdea6b899f6b19f61f70/networkx-3.2.1-py3-none-any.whl#sha256=f18c69adc97877c42332c170849c96cefa91881c99a7cb3e95b7c659ebdc1ec2\n # pip ninja @ https://files.pythonhosted.org/packages/6d/92/8d7aebd4430ab5ff65df2bfee6d5745f95c004284db2d8ca76dcbfd9de47/ninja-1.11.1.1-py2.py3-none-manylinux1_x86_64.manylinux_2_5_x86_64.whl#sha256=84502ec98f02a037a169c4b0d5d86075eaf6afc55e1879003d6cab51ced2ea4b\n # pip numpy @ https://files.pythonhosted.org/packages/54/30/c2a907b9443cf42b90c17ad10c1e8fa801975f01cb9764f3f8eb8aea638b/numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl#sha256=f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3\n@@ -62,18 +62,18 @@ https://repo.anaconda.com/pkgs/main/linux-64/pip-24.0-py39h06a4308_0.conda#7f8ce\n # pip tomli @ https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl#sha256=939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc\n # pip tzdata @ https://files.pythonhosted.org/packages/65/58/f9c9e6be752e9fcb8b6a0ee9fb87e6e7a1f6bcab2cdc73f02bb7ba91ada0/tzdata-2024.1-py2.py3-none-any.whl#sha256=9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252\n # pip urllib3 @ https://files.pythonhosted.org/packages/a2/73/a68704750a7679d0b6d3ad7aa8d4da8e14e151ae82e6fee774e6e0d05ec8/urllib3-2.2.1-py3-none-any.whl#sha256=450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d\n-# pip zipp @ https://files.pythonhosted.org/packages/7f/2d/670176f39d6613af2908b5bc31c9974588208de1fcc4117dfae08623d188/zipp-3.19.0-py3-none-any.whl#sha256=96dc6ad62f1441bcaccef23b274ec471518daf4fbbc580341204936a5a3dddec\n+# pip zipp @ https://files.pythonhosted.org/packages/20/38/f5c473fe9b90c8debdd29ea68d5add0289f1936d6f923b6b9cc0b931194c/zipp-3.19.2-py3-none-any.whl#sha256=f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c\n # pip contourpy @ https://files.pythonhosted.org/packages/31/a2/2f12e3a6e45935ff694654b710961b03310b0e1ec997ee9f416d3c873f87/contourpy-1.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl#sha256=e1d59258c3c67c865435d8fbeb35f8c59b8bef3d6f46c1f29f6123556af28445\n-# pip coverage @ https://files.pythonhosted.org/packages/b2/31/aa090d5e5a4e1a0e2d517f73a737a6d4b4975ca1f2b9cea9cb985b3ef307/coverage-7.5.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl#sha256=24bb4c7859a3f757a116521d4d3a8a82befad56ea1bdacd17d6aafd113b0071e\n+# pip coverage @ https://files.pythonhosted.org/packages/07/e0/0e30ca5c6c5bcae86df9583c30807ff26e0b991e76f266b81224410663e4/coverage-7.5.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl#sha256=2e079c9ec772fedbade9d7ebc36202a1d9ef7291bc9b3a024ca395c4d52853d7\n # pip imageio @ https://files.pythonhosted.org/packages/a3/b6/39c7dad203d9984225f47e0aa39ac3ba3a47c77a02d0ef2a7be691855a06/imageio-2.34.1-py3-none-any.whl#sha256=408c1d4d62f72c9e8347e7d1ca9bc11d8673328af3913868db3b828e28b40a4c\n # pip importlib-metadata @ https://files.pythonhosted.org/packages/2d/0a/679461c511447ffaf176567d5c496d1de27cbe34a87df6677d7171b2fbd4/importlib_metadata-7.1.0-py3-none-any.whl#sha256=30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570\n # pip importlib-resources @ https://files.pythonhosted.org/packages/75/06/4df55e1b7b112d183f65db9503bff189e97179b256e1ea450a3c365241e0/importlib_resources-6.4.0-py3-none-any.whl#sha256=50d10f043df931902d4194ea07ec57960f66a80449ff867bfe782b4c486ba78c\n # pip jinja2 @ https://files.pythonhosted.org/packages/31/80/3a54838c3fb461f6fec263ebf3a3a41771bd05190238de3486aae8540c36/jinja2-3.1.4-py3-none-any.whl#sha256=bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d\n # pip lazy-loader @ https://files.pythonhosted.org/packages/83/60/d497a310bde3f01cb805196ac61b7ad6dc5dcf8dce66634dc34364b20b4f/lazy_loader-0.4-py3-none-any.whl#sha256=342aa8e14d543a154047afb4ba8ef17f5563baad3fc610d7b15b213b0f119efc\n # pip pyproject-metadata @ https://files.pythonhosted.org/packages/aa/5f/bb5970d3d04173b46c9037109f7f05fc8904ff5be073ee49bb6ff00301bc/pyproject_metadata-0.8.0-py3-none-any.whl#sha256=ad858d448e1d3a1fb408ac5bac9ea7743e7a8bbb472f2693aaa334d2db42f526\n-# pip pytest @ https://files.pythonhosted.org/packages/b4/c1/27a1274b73712232328cb5115030057b7dec377f36a518c83f2e01d4f305/pytest-8.2.1-py3-none-any.whl#sha256=faccc5d332b8c3719f40283d0d44aa5cf101cec36f88cde9ed8f2bc0538612b1\n+# pip pytest @ https://files.pythonhosted.org/packages/4e/e7/81ebdd666d3bff6670d27349b5053605d83d55548e6bd5711f3b0ae7dd23/pytest-8.2.2-py3-none-any.whl#sha256=c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343\n # pip python-dateutil @ https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl#sha256=a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427\n-# pip requests @ https://files.pythonhosted.org/packages/c3/20/748e38b466e0819491f0ce6e90ebe4184966ee304fe483e2c414b0f4ef07/requests-2.32.2-py3-none-any.whl#sha256=fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c\n+# pip requests @ https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl#sha256=70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6\n # pip scipy @ https://files.pythonhosted.org/packages/35/f5/d0ad1a96f80962ba65e2ce1de6a1e59edecd1f0a7b55990ed208848012e0/scipy-1.13.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl#sha256=637e98dcf185ba7f8e663e122ebf908c4702420477ae52a04f9908707456ba4d\n # pip tifffile @ https://files.pythonhosted.org/packages/d9/6c/740c07588434e86028c24b0653c1eb6b46904d9ce585a20f07590620ec41/tifffile-2024.5.22-py3-none-any.whl#sha256=e281781c15d7d197d7e12749849c965651413aa905f97a48b0f84bd90a3b4c6f\n # pip lightgbm @ https://files.pythonhosted.org/packages/ba/11/cb8b67f3cbdca05b59a032bb57963d4fe8c8d18c3870f30bed005b7f174d/lightgbm-4.3.0-py3-none-manylinux_2_28_x86_64.whl#sha256=104496a3404cb2452d3412cbddcfbfadbef9c372ea91e3a9b8794bcc5183bf07\ndiff --git a/sklearn/compose/tests/test_column_transformer.py b/sklearn/compose/tests/test_column_transformer.py\nindex d0f2274272230..fb64e5c191d48 100644\n--- a/sklearn/compose/tests/test_column_transformer.py\n+++ b/sklearn/compose/tests/test_column_transformer.py\n@@ -33,6 +33,7 @@\n _Registry,\n check_recorded_metadata,\n )\n+from sklearn.utils._indexing import _safe_indexing\n from sklearn.utils._testing import (\n _convert_container,\n assert_allclose_dense_sparse,\n@@ -2521,8 +2522,12 @@ def test_column_transformer_column_renaming(dataframe_lib):\n (\"A\", \"passthrough\", [\"x1\", \"x2\", \"x3\"]),\n (\"B\", FunctionTransformer(), [\"x1\", \"x2\"]),\n (\"C\", StandardScaler(), [\"x1\", \"x3\"]),\n- # special case of empty transformer\n- (\"D\", FunctionTransformer(lambda x: x[[]]), [\"x1\", \"x2\", \"x3\"]),\n+ # special case of a transformer returning 0-columns, e.g feature selector\n+ (\n+ \"D\",\n+ FunctionTransformer(lambda x: _safe_indexing(x, [], axis=1)),\n+ [\"x1\", \"x2\", \"x3\"],\n+ ),\n ],\n verbose_feature_names_out=True,\n ).set_output(transform=dataframe_lib)\n@@ -2551,8 +2556,12 @@ def test_column_transformer_error_with_duplicated_columns(dataframe_lib):\n (\"A\", \"passthrough\", [\"x1\", \"x2\", \"x3\"]),\n (\"B\", FunctionTransformer(), [\"x1\", \"x2\"]),\n (\"C\", StandardScaler(), [\"x1\", \"x3\"]),\n- # special case of empty transformer\n- (\"D\", FunctionTransformer(lambda x: x[[]]), [\"x1\", \"x2\", \"x3\"]),\n+ # special case of a transformer returning 0-columns, e.g feature selector\n+ (\n+ \"D\",\n+ FunctionTransformer(lambda x: _safe_indexing(x, [], axis=1)),\n+ [\"x1\", \"x2\", \"x3\"],\n+ ),\n ],\n verbose_feature_names_out=False,\n ).set_output(transform=dataframe_lib)\n", "problem_statement": ":lock: :robot: CI Update lock files for main CI build(s) :lock: :robot:\nUpdate lock files.\n\n### Note\nIf the CI tasks fail, create a new branch based on this PR and add the required fixes to that branch.\n", "hints_text": "", "created_at": "2024-06-06T11:59:36Z" }, { "repo": "scikit-learn/scikit-learn", "pull_number": 29191, "instance_id": "scikit-learn__scikit-learn-29191", "issue_numbers": [ "29189" ], "base_commit": "1009ca42f5051aa1bdf5d5c63a6c44133ae82605", "patch": "diff --git a/azure-pipelines.yml b/azure-pipelines.yml\nindex 1ce5244124d4d..b65d17faafc66 100644\n--- a/azure-pipelines.yml\n+++ b/azure-pipelines.yml\n@@ -62,40 +62,26 @@ jobs:\n SKLEARN_WARNINGS_AS_ERRORS: '1'\n CHECK_PYTEST_SOFT_DEPENDENCY: 'true'\n \n-- template: build_tools/azure/posix-docker.yml\n- # Experimental CPython branch without the Global Interpreter Lock:\n- # https://github.com/colesbury/nogil/\n- #\n- # The nogil build relies on a dedicated PyPI-style index to install patched\n- # versions of NumPy, SciPy and Cython maintained by @colesbury and that\n- # include specific fixes to make them run correctly without relying on the GIL.\n- #\n- # The goal of this CI entry is to make sure that we do not introduce any\n- # dependency on the GIL in scikit-learn itself. An auxiliary goal is to early\n- # detect any regression in the patched build dependencies to report them\n- # upstream. The long-term goal is to be able to stop having to maintain\n- # multiprocessing based workaround / hacks in joblib / loky to make multi-CPU\n- # computing in scikit-learn efficient by default using regular threads.\n- #\n- # If this experimental entry becomes too unstable, feel free to disable it.\n+- template: build_tools/azure/posix.yml\n+ # CPython 3.13 free-threaded build\n parameters:\n- name: Linux_nogil\n- vmImage: ubuntu-20.04\n+ name: Linux_free_threaded\n+ vmImage: ubuntu-22.04\n dependsOn: [git_commit, linting]\n condition: |\n and(\n succeeded(),\n not(contains(dependencies['git_commit']['outputs']['commit.message'], '[ci skip]')),\n or(eq(variables['Build.Reason'], 'Schedule'),\n- contains(dependencies['git_commit']['outputs']['commit.message'], '[nogil]'\n+ contains(dependencies['git_commit']['outputs']['commit.message'], '[free-threaded]'\n )\n )\n )\n matrix:\n- pylatest_pip_nogil:\n- DOCKER_CONTAINER: 'nogil/python'\n- DISTRIB: 'pip-nogil'\n- LOCK_FILE: './build_tools/azure/python_nogil_lock.txt'\n+ pylatest_pip_free_threaded:\n+ PYTHON_GIL: '0'\n+ DISTRIB: 'pip-free-threaded'\n+ LOCK_FILE: './build_tools/azure/cpython_free_threaded_lock.txt'\n COVERAGE: 'false'\n \n - job: Linux_Nightly_Pyodide\ndiff --git a/build_tools/azure/cpython_free_threaded_lock.txt b/build_tools/azure/cpython_free_threaded_lock.txt\nnew file mode 100644\nindex 0000000000000..f1c58b65a59d6\n--- /dev/null\n+++ b/build_tools/azure/cpython_free_threaded_lock.txt\n@@ -0,0 +1,39 @@\n+#\n+# This file is autogenerated by pip-compile with Python 3.13\n+# by the following command:\n+#\n+# pip-compile --allow-unsafe --output-file=/scikit-learn/build_tools/azure/cpython_free_threaded_lock.txt /scikit-learn/build_tools/azure/cpython_free_threaded_requirements.txt\n+#\n+execnet==2.1.1\n+ # via pytest-xdist\n+iniconfig==2.0.0\n+ # via pytest\n+joblib==1.4.2\n+ # via -r /scikit-learn/build_tools/azure/cpython_free_threaded_requirements.txt\n+meson==1.4.1\n+ # via meson-python\n+meson-python==0.16.0\n+ # via -r /scikit-learn/build_tools/azure/cpython_free_threaded_requirements.txt\n+ninja==1.11.1.1\n+ # via -r /scikit-learn/build_tools/azure/cpython_free_threaded_requirements.txt\n+packaging==24.0\n+ # via\n+ # meson-python\n+ # pyproject-metadata\n+ # pytest\n+pluggy==1.5.0\n+ # via pytest\n+pyproject-metadata==0.8.0\n+ # via meson-python\n+pytest==8.2.2\n+ # via\n+ # -r /scikit-learn/build_tools/azure/cpython_free_threaded_requirements.txt\n+ # pytest-xdist\n+pytest-xdist==3.6.1\n+ # via -r /scikit-learn/build_tools/azure/cpython_free_threaded_requirements.txt\n+threadpoolctl==3.5.0\n+ # via -r /scikit-learn/build_tools/azure/cpython_free_threaded_requirements.txt\n+\n+# The following packages are considered to be unsafe in a requirements file:\n+setuptools==70.0.0\n+ # via -r /scikit-learn/build_tools/azure/cpython_free_threaded_requirements.txt\ndiff --git a/build_tools/azure/cpython_free_threaded_requirements.txt b/build_tools/azure/cpython_free_threaded_requirements.txt\nnew file mode 100644\nindex 0000000000000..b48f69abbc283\n--- /dev/null\n+++ b/build_tools/azure/cpython_free_threaded_requirements.txt\n@@ -0,0 +1,17 @@\n+# To generate cpython_free_threaded_lock.txt, use the following command:\n+# docker run -v $PWD:/scikit-learn -it ubuntu bash -c 'export DEBIAN_FRONTEND=noninteractive; apt-get -yq update; apt-get install software-properties-common ccache -y; add-apt-repository --yes ppa:deadsnakes/nightly; apt-get update -y; apt-get install -y --no-install-recommends python3.13-dev python3.13-venv python3.13-nogil; python3.13t -m venv /venvs/myenv; source /venvs/myenv/bin/activate; pip install pip-tools; pip-compile --allow-unsafe /scikit-learn/build_tools/azure/cpython_free_threaded_requirements.txt -o /scikit-learn/build_tools/azure/cpython_free_threaded_lock.txt'\n+\n+# The reason behind it is that you need python-3.13t to generate the pip lock\n+# file. For pure Python wheel this does not really matter. But when there are\n+# cython, numpy and scipy releases that have a CPython 3.13 free-threaded\n+# wheel, we can add them here and this is important that the Python 3.13\n+# free-threaded wheel is picked up in the lock-file\n+joblib\n+threadpoolctl\n+pytest\n+pytest-xdist\n+ninja\n+meson-python\n+# For some reason some of our tests require setuptools.\n+# TODO: update those tests to remove the dependency.\n+setuptools\ndiff --git a/build_tools/azure/install.sh b/build_tools/azure/install.sh\nindex cdaaa0c8cc965..c67446dfc2d24 100755\n--- a/build_tools/azure/install.sh\n+++ b/build_tools/azure/install.sh\n@@ -38,6 +38,17 @@ pre_python_environment_install() {\n apt-get install -y python3-dev python3-numpy python3-scipy \\\n python3-matplotlib libatlas3-base libatlas-base-dev \\\n python3-virtualenv python3-pandas ccache git\n+\n+ # TODO for now we use CPython 3.13 from Ubuntu deadsnakes PPA. When CPython\n+ # 3.13 is released (scheduled October 2024) we can use something more\n+ # similar to other conda+pip based builds\n+ elif [[ \"$DISTRIB\" == \"pip-free-threaded\" ]]; then\n+ sudo apt-get -yq update\n+ sudo apt-get install -yq ccache\n+ sudo apt-get install -yq software-properties-common\n+ sudo add-apt-repository --yes ppa:deadsnakes/nightly\n+ sudo apt-get update -yq\n+ sudo apt-get install -yq --no-install-recommends python3.13-dev python3.13-venv python3.13-nogil\n fi\n }\n \n@@ -66,10 +77,25 @@ python_environment_install_and_activate() {\n source $VIRTUALENV/bin/activate\n pip install -r \"${LOCK_FILE}\"\n \n- elif [[ \"$DISTRIB\" == \"pip-nogil\" ]]; then\n- python -m venv $VIRTUALENV\n+ elif [[ \"$DISTRIB\" == \"pip-free-threaded\" ]]; then\n+ python3.13t -m venv $VIRTUALENV\n source $VIRTUALENV/bin/activate\n pip install -r \"${LOCK_FILE}\"\n+ # TODO for now need pip 24.1b1 to find free-threaded wheels\n+ pip install -U --pre pip\n+ # TODO When there are CPython 3.13 free-threaded wheels for numpy and\n+ # scipy move this to\n+ # build_tools/azure/cpython_free_threaded_requirements.txt. For now we\n+ # install them from scientific-python-nightly-wheels\n+ dev_anaconda_url=https://pypi.anaconda.org/scientific-python-nightly-wheels/simple\n+ dev_packages=\"numpy scipy\"\n+ pip install --pre --upgrade --timeout=60 --extra-index $dev_anaconda_url $dev_packages\n+ # TODO Move cython to\n+ # build_tools/azure/cpython_free_threaded_requirements.txt when there\n+ # is a CPython 3.13 free-threaded wheel\n+ # For now, we need the development version of Cython which has CPython\n+ # 3.13 free-threaded fixes so we install it from source\n+ pip install git+https://github.com/cython/cython\n fi\n \n if [[ \"$DISTRIB\" == \"conda-pip-scipy-dev\" ]]; then\n@@ -86,11 +112,6 @@ python_environment_install_and_activate() {\n pip install https://github.com/joblib/joblib/archive/master.zip\n echo \"Installing pillow from latest sources\"\n pip install https://github.com/python-pillow/Pillow/archive/main.zip\n-\n- elif [[ \"$DISTRIB\" == \"pip-nogil\" ]]; then\n- apt-get -yq update\n- apt-get install -yq ccache\n-\n fi\n }\n \ndiff --git a/build_tools/azure/python_nogil_lock.txt b/build_tools/azure/python_nogil_lock.txt\ndeleted file mode 100644\nindex 7f67a48842dea..0000000000000\n--- a/build_tools/azure/python_nogil_lock.txt\n+++ /dev/null\n@@ -1,73 +0,0 @@\n-#\n-# This file is autogenerated by pip-compile with Python 3.9\n-# by the following command:\n-#\n-# pip-compile --output-file=/scikit-learn/build_tools/azure/python_nogil_lock.txt /scikit-learn/build_tools/azure/python_nogil_requirements.txt\n-#\n---index-url https://d1yxz45j0ypngg.cloudfront.net/\n---extra-index-url https://pypi.org/simple\n-\n-contourpy==1.1.1\n- # via matplotlib\n-cycler==0.12.1\n- # via matplotlib\n-cython==3.0.10\n- # via -r /scikit-learn/build_tools/azure/python_nogil_requirements.txt\n-exceptiongroup==1.2.1\n- # via pytest\n-execnet==2.1.1\n- # via pytest-xdist\n-fonttools==4.51.0\n- # via matplotlib\n-iniconfig==2.0.0\n- # via pytest\n-joblib==1.4.2\n- # via -r /scikit-learn/build_tools/azure/python_nogil_requirements.txt\n-kiwisolver==1.4.4\n- # via matplotlib\n-matplotlib==3.6.2\n- # via -r /scikit-learn/build_tools/azure/python_nogil_requirements.txt\n-meson==1.4.0\n- # via meson-python\n-meson-python==0.16.0\n- # via -r /scikit-learn/build_tools/azure/python_nogil_requirements.txt\n-ninja==1.11.1.1\n- # via -r /scikit-learn/build_tools/azure/python_nogil_requirements.txt\n-numpy==1.24.0\n- # via\n- # -r /scikit-learn/build_tools/azure/python_nogil_requirements.txt\n- # contourpy\n- # matplotlib\n- # scipy\n-packaging==24.0\n- # via\n- # matplotlib\n- # meson-python\n- # pyproject-metadata\n- # pytest\n-pillow==9.5.0\n- # via matplotlib\n-pluggy==1.5.0\n- # via pytest\n-pyparsing==3.1.2\n- # via matplotlib\n-pyproject-metadata==0.8.0\n- # via meson-python\n-pytest==7.4.4\n- # via\n- # -r /scikit-learn/build_tools/azure/python_nogil_requirements.txt\n- # pytest-xdist\n-pytest-xdist==3.6.1\n- # via -r /scikit-learn/build_tools/azure/python_nogil_requirements.txt\n-python-dateutil==2.9.0.post0\n- # via matplotlib\n-scipy==1.9.3\n- # via -r /scikit-learn/build_tools/azure/python_nogil_requirements.txt\n-six==1.16.0\n- # via python-dateutil\n-threadpoolctl==3.5.0\n- # via -r /scikit-learn/build_tools/azure/python_nogil_requirements.txt\n-tomli==2.0.1\n- # via\n- # meson-python\n- # pytest\ndiff --git a/build_tools/azure/python_nogil_requirements.txt b/build_tools/azure/python_nogil_requirements.txt\ndeleted file mode 100644\nindex 2cebad9a03b25..0000000000000\n--- a/build_tools/azure/python_nogil_requirements.txt\n+++ /dev/null\n@@ -1,20 +0,0 @@\n-# To generate python_nogil_lock.txt, use the following command:\n-# docker run -v $PWD:/scikit-learn -it nogil/python bash -c 'pip install pip-tools; pip-compile --upgrade /scikit-learn/build_tools/azure/python_nogil_requirements.txt -o /scikit-learn/build_tools/azure/python_nogil_lock.txt'\n-#\n-# The reason behind it is that you need python-nogil to generate the pip lock\n-# file. Using pip-compile --index and --extra-index will not work, for example\n-# the latest cython will be picked up from PyPI, rather than the one from the\n-# python-nogil index\n-matplotlib\n-numpy\n-scipy\n-cython\n-joblib\n-threadpoolctl\n-# TODO: somehow pytest 8 does not seem to work with meson editable\n-# install. Exit code is 5, i.e. no test collected\n-# This would be fixed by https://github.com/mesonbuild/meson-python/pull/569\n-pytest<8\n-pytest-xdist\n-meson-python\n-ninja\ndiff --git a/build_tools/shared.sh b/build_tools/shared.sh\nindex 4866c149d506f..958c6ca1408e7 100644\n--- a/build_tools/shared.sh\n+++ b/build_tools/shared.sh\n@@ -29,7 +29,7 @@ show_installed_libraries(){\n activate_environment() {\n if [[ \"$DISTRIB\" =~ ^conda.* ]]; then\n source activate $VIRTUALENV\n- elif [[ \"$DISTRIB\" == \"ubuntu\" || \"$DISTRIB\" == \"debian-32\" || \"$DISTRIB\" == \"pip-nogil\" ]]; then\n+ elif [[ \"$DISTRIB\" == \"ubuntu\" || \"$DISTRIB\" == \"debian-32\" || \"$DISTRIB\" == \"pip-free-threaded\" ]]; then\n source $VIRTUALENV/bin/activate\n fi\n }\ndiff --git a/doc/developers/contributing.rst b/doc/developers/contributing.rst\nindex 8c8d478e6d6f5..535983712cc8c 100644\n--- a/doc/developers/contributing.rst\n+++ b/doc/developers/contributing.rst\n@@ -518,7 +518,7 @@ Commit Message Marker Action Taken by CI\n [cd build cirrus] CD is run only for Cirrus CI\n [lint skip] Azure pipeline skips linting\n [scipy-dev] Build & test with our dependencies (numpy, scipy, etc.) development builds\n-[nogil] Build & test with the nogil experimental branches of CPython, Cython, NumPy, SciPy, ...\n+[free-threaded] Build & test with CPython 3.13 free-threaded\n [pyodide] Build & test with Pyodide\n [azure parallel] Run Azure CI jobs in parallel\n [cirrus arm] Run Cirrus CI ARM test\n", "test_patch": "diff --git a/build_tools/azure/test_docs.sh b/build_tools/azure/test_docs.sh\nindex 61e855425786b..1258ecf69f080 100755\n--- a/build_tools/azure/test_docs.sh\n+++ b/build_tools/azure/test_docs.sh\n@@ -4,7 +4,7 @@ set -e\n \n if [[ \"$DISTRIB\" =~ ^conda.* ]]; then\n source activate $VIRTUALENV\n-elif [[ \"$DISTRIB\" == \"ubuntu\" || \"$DISTRIB\" == \"pip-nogil\" ]]; then\n+elif [[ \"$DISTRIB\" == \"ubuntu\" || \"$DISTRIB\" == \"pip-free-threaded\" ]]; then\n source $VIRTUALENV/bin/activate\n fi\n \n", "problem_statement": "\u26a0\ufe0f CI failed on Linux_nogil.pylatest_pip_nogil (last failure: Jun 10, 2024) \u26a0\ufe0f\n**CI is still failing on [Linux_nogil.pylatest_pip_nogil](https://dev.azure.com/scikit-learn/scikit-learn/_build/results?buildId=67402&view=logs&j=67fbb25f-e417-50be-be55-3b1e9637fce5)** (Jun 10, 2024)\nUnable to find junit file. Please see link for details.\n", "hints_text": "", "created_at": "2024-06-05T05:20:57Z" }, { "repo": "scikit-learn/scikit-learn", "pull_number": 29179, "instance_id": "scikit-learn__scikit-learn-29179", "issue_numbers": [ "29157" ], "base_commit": "bffa4609d06a2eb7b223b21530ad6fcdc4a18c46", "patch": "diff --git a/doc/whats_new/v1.5.rst b/doc/whats_new/v1.5.rst\nindex 60b8dadc97373..5c0d3d76419e3 100644\n--- a/doc/whats_new/v1.5.rst\n+++ b/doc/whats_new/v1.5.rst\n@@ -38,6 +38,10 @@ Changelog\n grids that have heterogeneous parameter values.\n :pr:`29078` by :user:`Lo\u00efc Est\u00e8ve `.\n \n+- |Fix| Fix a regression in :class:`model_selection.GridSearchCV` for parameter\n+ grids that have estimators as parameter values.\n+ :pr:`29179` by :user:`Marco Gorelli`.\n+\n \n .. _changes_1_5:\n \ndiff --git a/sklearn/model_selection/_search.py b/sklearn/model_selection/_search.py\nindex edf492b84877a..fdc6abf195a67 100644\n--- a/sklearn/model_selection/_search.py\n+++ b/sklearn/model_selection/_search.py\n@@ -1089,9 +1089,24 @@ def _store(key_name, array, weights=None, splits=False, rank=False):\n for key, param_result in param_results.items():\n param_list = list(param_result.values())\n try:\n- arr_dtype = np.result_type(*param_list)\n+ with warnings.catch_warnings():\n+ warnings.filterwarnings(\n+ \"ignore\",\n+ message=\"in the future the `.dtype` attribute\",\n+ category=DeprecationWarning,\n+ )\n+ # Warning raised by NumPy 1.20+\n+ arr_dtype = np.result_type(*param_list)\n except (TypeError, ValueError):\n- arr_dtype = object\n+ arr_dtype = np.dtype(object)\n+ else:\n+ if any(np.min_scalar_type(x) == object for x in param_list):\n+ # `np.result_type` might get thrown off by `.dtype` properties\n+ # (which some estimators have).\n+ # If finding the result dtype this way would give object,\n+ # then we use object.\n+ # https://github.com/scikit-learn/scikit-learn/issues/29157\n+ arr_dtype = np.dtype(object)\n if len(param_list) == n_candidates and arr_dtype != object:\n # Exclude `object` else the numpy constructor might infer a list of\n # tuples to be a 2d array.\n", "test_patch": "diff --git a/sklearn/model_selection/tests/test_search.py b/sklearn/model_selection/tests/test_search.py\nindex cb4af646aee39..7beb0d73bd993 100644\n--- a/sklearn/model_selection/tests/test_search.py\n+++ b/sklearn/model_selection/tests/test_search.py\n@@ -17,6 +17,7 @@\n from sklearn import config_context\n from sklearn.base import BaseEstimator, ClassifierMixin, is_classifier\n from sklearn.cluster import KMeans\n+from sklearn.compose import ColumnTransformer\n from sklearn.datasets import (\n make_blobs,\n make_classification,\n@@ -64,7 +65,7 @@\n from sklearn.naive_bayes import ComplementNB\n from sklearn.neighbors import KernelDensity, KNeighborsClassifier, LocalOutlierFactor\n from sklearn.pipeline import Pipeline\n-from sklearn.preprocessing import StandardScaler\n+from sklearn.preprocessing import OneHotEncoder, OrdinalEncoder, StandardScaler\n from sklearn.svm import SVC, LinearSVC\n from sklearn.tests.metadata_routing_common import (\n ConsumingScorer,\n@@ -1403,9 +1404,7 @@ def test_search_cv_results_none_param():\n est_parameters,\n cv=cv,\n ).fit(X, y)\n- assert_array_equal(\n- grid_search.cv_results_[\"param_random_state\"], [0, float(\"nan\")]\n- )\n+ assert_array_equal(grid_search.cv_results_[\"param_random_state\"], [0, None])\n \n \n @ignore_warnings()\n@@ -2686,3 +2685,36 @@ def score(self, X, y):\n grid_search.fit(X, y)\n for param in param_grid:\n assert grid_search.cv_results_[f\"param_{param}\"].dtype == object\n+\n+\n+def test_search_with_estimators_issue_29157():\n+ \"\"\"Check cv_results_ for estimators with a `dtype` parameter, e.g. OneHotEncoder.\"\"\"\n+ pd = pytest.importorskip(\"pandas\")\n+ df = pd.DataFrame(\n+ {\n+ \"numeric_1\": [1, 2, 3, 4, 5],\n+ \"object_1\": [\"a\", \"a\", \"a\", \"a\", \"a\"],\n+ \"target\": [1.0, 4.1, 2.0, 3.0, 1.0],\n+ }\n+ )\n+ X = df.drop(\"target\", axis=1)\n+ y = df[\"target\"]\n+ enc = ColumnTransformer(\n+ [(\"enc\", OneHotEncoder(sparse_output=False), [\"object_1\"])],\n+ remainder=\"passthrough\",\n+ )\n+ pipe = Pipeline(\n+ [\n+ (\"enc\", enc),\n+ (\"regressor\", LinearRegression()),\n+ ]\n+ )\n+ grid_params = {\n+ \"enc__enc\": [\n+ OneHotEncoder(sparse_output=False),\n+ OrdinalEncoder(),\n+ ]\n+ }\n+ grid_search = GridSearchCV(pipe, grid_params, cv=2)\n+ grid_search.fit(X, y)\n+ assert grid_search.cv_results_[\"param_enc__enc\"].dtype == object\n", "problem_statement": "TypeError when fitting GridSearchCV or RandomizedSearchCV with OrdinalEncoder and OneHotEncoder in parameters grid\n### Describe the bug\r\n\r\nHaving both `OrdinalEncoder` and `OneHotEncoder` inside the parameters grid to be used by the `GridSearchCV` or `RandomizedSearchCV` results in the following error: `TypeError: float() argument must be a string or a real number, not 'OneHotEncoder'`.\r\n\r\n### Steps/Code to Reproduce\r\n\r\n```python\r\nimport numpy as np\r\nimport pandas as pd\r\nfrom sklearn import set_config\r\nfrom sklearn.compose import ColumnTransformer\r\nfrom sklearn.ensemble import HistGradientBoostingRegressor\r\nfrom sklearn.model_selection import GridSearchCV, RandomizedSearchCV\r\nfrom sklearn.pipeline import Pipeline\r\nfrom sklearn.preprocessing import OneHotEncoder, OrdinalEncoder\r\n\r\nset_config(transform_output=\"pandas\")\r\n\r\n# Setting seed for reproducibility\r\nnp.random.seed(42)\r\n\r\n# Create a DataFrame with 1000 rows and 5 columns\r\nnum_rows = 1000\r\ndata = {\r\n \"numeric_1\": np.random.randn(num_rows), # Normally distributed random numbers\r\n \"numeric_3\": np.random.randint(\r\n 1, 100, size=num_rows\r\n ), # Random integers between 1 and 100\r\n \"object_1\": np.random.choice(\r\n [\"A\", \"B\", \"C\", \"D\"], size=num_rows\r\n ), # Random choice among 'A', 'B', 'C', 'D'\r\n \"object_2\": np.random.choice(\r\n [\"X\", \"Y\", \"Z\"], size=num_rows\r\n ), # Random choice among 'X', 'Y', 'Z'\r\n \"target\": np.random.rand(num_rows)\r\n * 100, # Uniformly distributed random numbers [0, 100)\r\n}\r\n\r\ndf = pd.DataFrame(data)\r\n\r\nX = df.drop(\"target\", axis=1)\r\ny = df[\"target\"]\r\n\r\nenc = ColumnTransformer(\r\n [(\"enc\", OneHotEncoder(sparse_output=False), [\"object_1\", \"object_2\"])],\r\n remainder=\"passthrough\",\r\n verbose_feature_names_out=False,\r\n)\r\n\r\npipe = Pipeline(\r\n [\r\n (\"enc\", enc),\r\n (\"regressor\", HistGradientBoostingRegressor()),\r\n ]\r\n)\r\n\r\ngrid_params = {\r\n \"enc__enc\": [\r\n OneHotEncoder(sparse_output=False),\r\n OrdinalEncoder(),\r\n ]\r\n}\r\n\r\ngrid_search = GridSearchCV(pipe, grid_params, cv=5)\r\ngrid_search.fit(X, y)\r\n# RandomizedSearchCV produces the same error\r\n# rand_search = RandomizedSearchCV(pipe, grid_params, cv=5)\r\n# rand_search.fit(X, y)\r\n```\r\n\r\n### Expected Results\r\nI would have expected the pipeline to run without errors, like that:\r\n![image](https://github.com/scikit-learn/scikit-learn/assets/48819468/2085613d-f2d0-4350-b54c-9ec8efc7efd9)\r\n\r\n\r\n### Actual Results\r\n```python\r\n{\r\n\t\"name\": \"TypeError\",\r\n\t\"message\": \"float() argument must be a string or a real number, not 'OneHotEncoder'\",\r\n\t\"stack\": \"---------------------------------------------------------------------------\r\nTypeError Traceback (most recent call last)\r\nCell In[108], line 1\r\n----> 1 grid_search.fit(X, y)\r\n\r\nFile ~/Documents/Coding/ML_exercises/.venv/lib/python3.12/site-packages/sklearn/base.py:1473, in _fit_context..decorator..wrapper(estimator, *args, **kwargs)\r\n 1466 estimator._validate_params()\r\n 1468 with config_context(\r\n 1469 skip_parameter_validation=(\r\n 1470 prefer_skip_nested_validation or global_skip_validation\r\n 1471 )\r\n 1472 ):\r\n-> 1473 return fit_method(estimator, *args, **kwargs)\r\n\r\nFile ~/Documents/Coding/ML_exercises/.venv/lib/python3.12/site-packages/sklearn/model_selection/_search.py:968, in BaseSearchCV.fit(self, X, y, **params)\r\n 962 results = self._format_results(\r\n 963 all_candidate_params, n_splits, all_out, all_more_results\r\n 964 )\r\n 966 return results\r\n--> 968 self._run_search(evaluate_candidates)\r\n 970 # multimetric is determined here because in the case of a callable\r\n 971 # self.scoring the return type is only known after calling\r\n 972 first_test_score = all_out[0][\\\"test_scores\\\"]\r\n\r\nFile ~/Documents/Coding/ML_exercises/.venv/lib/python3.12/site-packages/sklearn/model_selection/_search.py:1543, in GridSearchCV._run_search(self, evaluate_candidates)\r\n 1541 def _run_search(self, evaluate_candidates):\r\n 1542 \\\"\\\"\\\"Search all candidates in param_grid\\\"\\\"\\\"\r\n-> 1543 evaluate_candidates(ParameterGrid(self.param_grid))\r\n\r\nFile ~/Documents/Coding/ML_exercises/.venv/lib/python3.12/site-packages/sklearn/model_selection/_search.py:962, in BaseSearchCV.fit..evaluate_candidates(candidate_params, cv, more_results)\r\n 959 all_more_results[key].extend(value)\r\n 961 nonlocal results\r\n--> 962 results = self._format_results(\r\n 963 all_candidate_params, n_splits, all_out, all_more_results\r\n 964 )\r\n 966 return results\r\n\r\nFile ~/Documents/Coding/ML_exercises/.venv/lib/python3.12/site-packages/sklearn/model_selection/_search.py:1098, in BaseSearchCV._format_results(self, candidate_params, n_splits, out, more_results)\r\n 1094 arr_dtype = object\r\n 1095 if len(param_list) == n_candidates and arr_dtype != object:\r\n 1096 # Exclude `object` else the numpy constructor might infer a list of\r\n 1097 # tuples to be a 2d array.\r\n-> 1098 results[key] = MaskedArray(param_list, mask=False, dtype=arr_dtype)\r\n 1099 else:\r\n 1100 # Use one MaskedArray and mask all the places where the param is not\r\n 1101 # applicable for that candidate (which may not contain all the params).\r\n 1102 ma = MaskedArray(np.empty(n_candidates), mask=True, dtype=arr_dtype)\r\n\r\nFile ~/Documents/Coding/ML_exercises/.venv/lib/python3.12/site-packages/numpy/ma/core.py:2820, in MaskedArray.__new__(cls, data, mask, dtype, copy, subok, ndmin, fill_value, keep_mask, hard_mask, shrink, order)\r\n 2811 \\\"\\\"\\\"\r\n 2812 Create a new masked array from scratch.\r\n 2813 \r\n (...)\r\n 2817 \r\n 2818 \\\"\\\"\\\"\r\n 2819 # Process data.\r\n-> 2820 _data = np.array(data, dtype=dtype, copy=copy,\r\n 2821 order=order, subok=True, ndmin=ndmin)\r\n 2822 _baseclass = getattr(data, '_baseclass', type(_data))\r\n 2823 # Check that we're not erasing the mask.\r\n\r\nTypeError: float() argument must be a string or a real number, not 'OneHotEncoder'\"\r\n}\r\n```\r\n\r\n### Versions\r\n\r\n```shell\r\nSystem:\r\n python: 3.12.3 (main, Apr 9 2024, 08:09:14) [Clang 15.0.0 (clang-1500.3.9.4)]\r\nexecutable: /Users/brice/Documents/Coding/ML_exercises/.venv/bin/python\r\n machine: macOS-14.5-arm64-arm-64bit\r\n\r\nPython dependencies:\r\n sklearn: 1.5.0\r\n pip: 24.0\r\n setuptools: 70.0.0\r\n numpy: 1.26.4\r\n scipy: 1.13.1\r\n Cython: None\r\n pandas: 2.2.2\r\n matplotlib: 3.9.0\r\n joblib: 1.4.2\r\nthreadpoolctl: 3.5.0\r\n\r\nBuilt with OpenMP: True\r\n\r\nthreadpoolctl info:\r\n user_api: blas\r\n internal_api: openblas\r\n num_threads: 8\r\n prefix: libopenblas\r\n filepath: /Users/brice/Documents/Coding/ML_exercises/.venv/lib/python3.12/site-packages/numpy/.dylibs/libopenblas64_.0.dylib\r\n version: 0.3.23.dev\r\nthreading_layer: pthreads\r\n architecture: armv8\r\n\r\n user_api: blas\r\n internal_api: openblas\r\n num_threads: 8\r\n prefix: libopenblas\r\n filepath: /Users/brice/Documents/Coding/ML_exercises/.venv/lib/python3.12/site-packages/scipy/.dylibs/libopenblas.0.dylib\r\n version: 0.3.27\r\nthreading_layer: pthreads\r\n architecture: neoversen1\r\n\r\n user_api: openmp\r\n internal_api: openmp\r\n num_threads: 8\r\n prefix: libomp\r\n filepath: /Users/brice/Documents/Coding/ML_exercises/.venv/lib/python3.12/site-packages/sklearn/.dylibs/libomp.dylib\r\n version: None\r\n```\r\n\n", "hints_text": "Could you please provide a minimal reproducer?\r\n\r\n- remove the extra bits from the code which do not contribute to the error\r\n- use a dataset from `sklearn.datasets`\r\n- the code should run without requiring extra datasets by simply copy pasting the code.\n> Could you please provide a minimal reproducer?\r\n> \r\n> * remove the extra bits from the code which do not contribute to the error\r\n> * use a dataset from `sklearn.datasets`\r\n> * the code should run without requiring extra datasets by simply copy pasting the code.\r\n\r\nThanks for your comment. I modified the issue's description accordingly.\nThis seems to be another one related to dtypes of the result in grid search. @lesteve @MarcoGorelli WDYT?\nI can confirm this still happens in `main`. I have modified the snippet to not use ` force_int_remainder_cols` (new `ColumnTransformer` parameter in 1.5) and the snippet runs on 1.4 so this seems like a regression indeed.\r\n\r\nThis is possible that this is the dtype tweak in grid-search `.cv_results_` https://github.com/scikit-learn/scikit-learn/pull/28352. I did the previous bug fix so I am happy to let @MarcoGorelli take this one :wink:.\nthanks for the ping - this seems to be the issue:\r\n```python\r\n(Pdb) p param_list\r\n[OneHotEncoder(sparse_output=False), OrdinalEncoder()]\r\n(Pdb) p np.result_type(*param_list)\r\ndtype('float64')\r\n(Pdb) p np.array(param_list).dtype\r\ndtype('O')\r\n```\r\n\r\nI find it a bit surprising that `np.result_type` gives `'float64'` here\nwait wut\r\n```\r\nIn [6]: OrdinalEncoder().dtype\r\nOut[6]: numpy.float64\r\n```\nOh dear, `OrdinalEncoder` has a `dtype` parameter and hence a `.dtype` attribute. `np.result_type` probably relies on the `.dtype` attribute? Edit: same thing for `OneHotEncoder`.\nIn a sense, it does make sense that `result_type` is `float64`, since `result_type` implies result of an operation on those values. But we just want to create an array here, so maybe we should get the dtype of a created array instead?\nI think that creates other issues https://github.com/scikit-learn/scikit-learn/pull/28352#discussion_r1509648638 which @thomasjpfan wanted to avoid\r\n\r\nIt might be simplest to just check if any object in `param_list` is an instance of `BaseEstimator`, and if so, set `arr_dtype` to `object`?\r\n\r\nGot a call coming up but I can submit a pr later\nNot everything is a `BaseEstimator`. A third party estimator might not be inheriting from `BaseEstimator` and that breaks this then.\r\n\r\nWe could check if anything is not a scaler of a simple object maybe? Not sure.\nAh thanks\r\n\r\nA third-party estimator should still implement fit and predict/transform though? Maybe just check for those attributes?\r\n\r\n---\r\n\r\nAs an aside, I expect that the `dtype` property might create other problems going forwards? It looks like it's not documented https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.OneHotEncoder.html#onehotencoder , so that may make the case for renaming it?", "created_at": "2024-06-04T14:29:46Z" }, { "repo": "scikit-learn/scikit-learn", "pull_number": 29158, "instance_id": "scikit-learn__scikit-learn-29158", "issue_numbers": [ "29032" ], "base_commit": "63e158462fdca475215f181bdfc5f732bcb8ae46", "patch": "diff --git a/doc/whats_new/v1.6.rst b/doc/whats_new/v1.6.rst\nindex 9f1b8514de4a0..ba9c722ae35f9 100644\n--- a/doc/whats_new/v1.6.rst\n+++ b/doc/whats_new/v1.6.rst\n@@ -241,6 +241,13 @@ Changelog\n when duplicate values in the training data lead to inaccurate outlier detection.\n :pr:`28773` by :user:`Henrique Caro\u00e7o `.\n \n+:mod:`sklearn.preprocessing`\n+............................\n+\n+- |Enhancement| The HTML representation of :class:`preprocessing.FunctionTransformer`\n+ will show the function name in the label.\n+ :pr:`29158` by :user:`Yao Xiao `.\n+\n :mod:`sklearn.semi_supervised`\n ..............................\n \n@@ -257,6 +264,8 @@ Changelog\n traversed.\n :pr:`27966` by :user:`Adam Li `.\n \n+.. rubric:: Code and documentation contributors\n+\n Thanks to everyone who has contributed to the maintenance and improvement of\n the project since version 1.5, including:\n \ndiff --git a/sklearn/preprocessing/_function_transformer.py b/sklearn/preprocessing/_function_transformer.py\nindex c49684d0ebfbc..72884a3366c5d 100644\n--- a/sklearn/preprocessing/_function_transformer.py\n+++ b/sklearn/preprocessing/_function_transformer.py\n@@ -1,8 +1,10 @@\n import warnings\n+from functools import partial\n \n import numpy as np\n \n from ..base import BaseEstimator, TransformerMixin, _fit_context\n+from ..utils._estimator_html_repr import _VisualBlock\n from ..utils._param_validation import StrOptions\n from ..utils._set_output import (\n _get_adapter_from_container,\n@@ -414,3 +416,21 @@ def set_output(self, *, transform=None):\n \n self._sklearn_output_config[\"transform\"] = transform\n return self\n+\n+ def _get_function_name(self):\n+ \"\"\"Get the name display of the `func` used in HTML representation.\"\"\"\n+ if hasattr(self.func, \"__name__\"):\n+ return self.func.__name__\n+ if isinstance(self.func, partial):\n+ return self.func.func.__name__\n+ return f\"{self.func.__class__.__name__}(...)\"\n+\n+ def _sk_visual_block_(self):\n+ return _VisualBlock(\n+ \"single\",\n+ self,\n+ names=self._get_function_name(),\n+ name_details=str(self),\n+ name_caption=\"FunctionTransformer\",\n+ doc_link_label=\"FunctionTransformer\",\n+ )\ndiff --git a/sklearn/utils/_estimator_html_repr.css b/sklearn/utils/_estimator_html_repr.css\nindex 3f29c70eddefc..0a8c277845cb1 100644\n--- a/sklearn/utils/_estimator_html_repr.css\n+++ b/sklearn/utils/_estimator_html_repr.css\n@@ -1,6 +1,7 @@\n #$id {\n /* Definition of color scheme common for light and dark mode */\n- --sklearn-color-text: black;\n+ --sklearn-color-text: #000;\n+ --sklearn-color-text-muted: #666;\n --sklearn-color-line: gray;\n /* Definition of color scheme for unfitted estimators */\n --sklearn-color-unfitted-level-0: #fff5e6;\n@@ -145,12 +146,21 @@ clickable and can be expanded/collapsed.\n /* Toggleable label */\n #$id label.sk-toggleable__label {\n cursor: pointer;\n- display: block;\n+ display: flex;\n width: 100%;\n margin-bottom: 0;\n padding: 0.5em;\n box-sizing: border-box;\n text-align: center;\n+ align-items: start;\n+ justify-content: space-between;\n+ gap: 0.5em;\n+}\n+\n+#$id label.sk-toggleable__label .caption {\n+ font-size: 0.6rem;\n+ font-weight: lighter;\n+ color: var(--sklearn-color-text-muted);\n }\n \n #$id label.sk-toggleable__label-arrow:before {\n@@ -303,7 +313,8 @@ a:visited.sk-estimator-doc-link {\n height: 1em;\n width: 1em;\n text-decoration: none !important;\n- margin-left: 1ex;\n+ margin-left: 0.5em;\n+ text-align: center;\n /* unfitted */\n border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n color: var(--sklearn-color-unfitted-level-1);\ndiff --git a/sklearn/utils/_estimator_html_repr.py b/sklearn/utils/_estimator_html_repr.py\nindex 5e465234f516b..4205ea7c619de 100644\n--- a/sklearn/utils/_estimator_html_repr.py\n+++ b/sklearn/utils/_estimator_html_repr.py\n@@ -54,17 +54,36 @@ class _VisualBlock:\n If kind == 'single', then `name_details` is a single string\n corresponding to the single estimator.\n \n+ name_caption : str, default=None\n+ The caption below the name. `None` stands for no caption.\n+ Only active when kind == 'single'.\n+\n+ doc_link_label : str, default=None\n+ The label for the documentation link. If provided, the label would be\n+ \"Documentation for {doc_link_label}\". Otherwise it will look for `names`.\n+ Only active when kind == 'single'.\n+\n dash_wrapped : bool, default=True\n If true, wrapped HTML element will be wrapped with a dashed border.\n Only active when kind != 'single'.\n \"\"\"\n \n def __init__(\n- self, kind, estimators, *, names=None, name_details=None, dash_wrapped=True\n+ self,\n+ kind,\n+ estimators,\n+ *,\n+ names=None,\n+ name_details=None,\n+ name_caption=None,\n+ doc_link_label=None,\n+ dash_wrapped=True,\n ):\n self.kind = kind\n self.estimators = estimators\n self.dash_wrapped = dash_wrapped\n+ self.name_caption = name_caption\n+ self.doc_link_label = doc_link_label\n \n if self.kind in (\"parallel\", \"serial\"):\n if names is None:\n@@ -83,6 +102,8 @@ def _write_label_html(\n out,\n name,\n name_details,\n+ name_caption=None,\n+ doc_link_label=None,\n outer_class=\"sk-label-container\",\n inner_class=\"sk-label\",\n checked=False,\n@@ -104,6 +125,11 @@ def _write_label_html(\n The details to show as content in the dropdown part of the toggleable label. It\n can contain information such as non-default parameters or column information for\n `ColumnTransformer`.\n+ name_caption : str, default=None\n+ The caption below the name. If `None`, no caption will be created.\n+ doc_link_label : str, default=None\n+ The label for the documentation link. If provided, the label would be\n+ \"Documentation for {doc_link_label}\". Otherwise it will look for `name`.\n outer_class : {\"sk-label-container\", \"sk-item\"}, default=\"sk-label-container\"\n The CSS class for the outer container.\n inner_class : {\"sk-label\", \"sk-estimator\"}, default=\"sk-label\"\n@@ -123,9 +149,6 @@ def _write_label_html(\n The HTML representation to show the fitted information in the diagram. An empty\n string means that no information is shown.\n \"\"\"\n- # we need to add some padding to the left of the label to be sure it is centered\n- padding_label = \" \" if is_fitted_icon else \"\" # add padding for the \"i\" char\n-\n out.write(\n f'
'\n@@ -134,31 +157,42 @@ def _write_label_html(\n \n if name_details is not None:\n name_details = html.escape(str(name_details))\n- label_class = (\n- f\"sk-toggleable__label {is_fitted_css_class} sk-toggleable__label-arrow\"\n- )\n-\n checked_str = \"checked\" if checked else \"\"\n est_id = _ESTIMATOR_ID_COUNTER.get_id()\n \n if doc_link:\n doc_label = \"Online documentation\"\n- if name is not None:\n+ if doc_link_label is not None:\n+ doc_label = f\"Documentation for {doc_link_label}\"\n+ elif name is not None:\n doc_label = f\"Documentation for {name}\"\n doc_link = (\n f'?{doc_label}'\n )\n- padding_label += \" \" # add additional padding for the \"?\" char\n+\n+ name_caption_div = (\n+ \"\"\n+ if name_caption is None\n+ else f'
{html.escape(name_caption)}
'\n+ )\n+ name_caption_div = f\"
{name}
{name_caption_div}
\"\n+ links_div = (\n+ f\"
{doc_link}{is_fitted_icon}
\"\n+ if doc_link or is_fitted_icon\n+ else \"\"\n+ )\n+\n+ label_html = (\n+ f''\n+ )\n \n fmt_str = (\n- '
'\n- f\"
{name_details}
\"\n+ f'{label_html}
{name_details}'\n+            \"
\"\n )\n out.write(fmt_str)\n else:\n@@ -306,6 +340,8 @@ def _write_estimator_html(\n out,\n est_block.names,\n est_block.name_details,\n+ est_block.name_caption,\n+ est_block.doc_link_label,\n outer_class=\"sk-item\",\n inner_class=\"sk-estimator\",\n checked=first_call,\n", "test_patch": "diff --git a/sklearn/utils/tests/test_estimator_html_repr.py b/sklearn/utils/tests/test_estimator_html_repr.py\nindex d59658998432d..20bc87dad8f92 100644\n--- a/sklearn/utils/tests/test_estimator_html_repr.py\n+++ b/sklearn/utils/tests/test_estimator_html_repr.py\n@@ -2,9 +2,11 @@\n import locale\n import re\n from contextlib import closing\n+from functools import partial\n from io import StringIO\n from unittest.mock import patch\n \n+import numpy as np\n import pytest\n \n from sklearn import config_context\n@@ -23,7 +25,7 @@\n from sklearn.multiclass import OneVsOneClassifier\n from sklearn.neural_network import MLPClassifier\n from sklearn.pipeline import FeatureUnion, Pipeline, make_pipeline\n-from sklearn.preprocessing import OneHotEncoder, StandardScaler\n+from sklearn.preprocessing import FunctionTransformer, OneHotEncoder, StandardScaler\n from sklearn.svm import LinearSVC, LinearSVR\n from sklearn.tree import DecisionTreeClassifier\n from sklearn.utils._estimator_html_repr import (\n@@ -36,6 +38,10 @@\n from sklearn.utils.fixes import parse_version\n \n \n+def dummy_function(x, y):\n+ return x + y # pragma: nocover\n+\n+\n @pytest.mark.parametrize(\"checked\", [True, False])\n def test_write_label_html(checked):\n # Test checking logic and labeling\n@@ -48,8 +54,8 @@ def test_write_label_html(checked):\n \n p = (\n r'\" in html_output\n+ assert \"
passthrough
\" in html_output\n assert html.escape(str(num_trans[\"imputer\"])) in html_output\n \n for _, _, cols in preprocess.transformers:\n@@ -246,8 +252,8 @@ def test_stacking_regressor(final_estimator):\n assert html.escape(str(reg.estimators[0][0])) in html_output\n p = (\n r'