Skip to content

Conversation

@rfrenchseti
Copy link
Collaborator

@rfrenchseti rfrenchseti commented Dec 5, 2025

Summary by CodeRabbit

  • Refactor

    • Consolidated many extension operations into mixin-based classes and switched Qube to compose those mixins, improving internal modularity while preserving public APIs.
    • Iterator internals were made private with public wrappers preserved; iteration behavior remains unchanged.
  • Chore

    • Updated lint configuration to suppress long-line warnings for extension files without altering runtime behavior.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 5, 2025

Walkthrough

Refactors polymath extensions from module-level functions to mixin classes and updates Qube to inherit these mixins. Removes the centralized extension wiring in polymath/extensions/init.py and localizes imports; adds a flake8 per-file ignore for extension files.

Changes

Cohort / File(s) Change Summary
Flake8 Configuration
'.flake8'
Adds per-file-ignores to suppress E501 for polymath/extensions/*.py.
Package Init
polymath/__init__.py
Removes explicit import of polymath.extensions to avoid import-time side effects.
Extension wiring
polymath/extensions/__init__.py
Removes prior runtime wiring that injected methods into Qube; mixin import/wiring moved to qube.py.
Mixins — broadcaster, indexer, item ops
polymath/extensions/broadcaster.py, polymath/extensions/indexer.py, polymath/extensions/item_ops.py
Introduces QubeBroadcaster, QubeIndexer, QubeItemOps classes; moves former module-level broadcast, indexing, and item-manipulation functions into instance/static methods on those mixins.
Iterators
polymath/extensions/iterator.py
Renames public iterator classes to private _QubeIterator/_QubeNDIterator; public wrappers now delegate to private iterators.
Masking & TVL
polymath/extensions/mask_ops.py, polymath/extensions/tvl.py
Adds QubeMaskOps and QubeTVL mixins; moves mask and three-valued-logic functions into class methods and helpers.
Pickling
polymath/extensions/pickler.py
Adds QubePickler class; moves pickling/encoding/decoding helpers into static/instance methods and adds state hooks.
Shaping & shrinking
polymath/extensions/shaper.py, polymath/extensions/shrinker.py
Adds QubeShaper and QubeShrinker mixins; relocates reshape/axis operations and shrink/unshrink logic into mixin methods.
Vector & math ops
polymath/extensions/vector_ops.py, polymath/extensions/math_ops.py
Adds QubeVectorOps (vector ops moved into static/instance methods) and continues QubeMathOps usage; exposes rms and other ops as mixin methods.
Qube class composition
polymath/qube.py
Imports mixins and changes Qube to multiple inheritance: class Qube(QubeBroadcaster, QubeIndexer, QubeItemOps, QubeIterator, QubeMaskOps, QubeMathOps, QubePickler, QubeShaper, QubeShrinker, QubeTVL, QubeVectorOps):.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • Areas needing extra attention:
    • polymath/qube.py — MRO and potential method shadowing across many mixins.
    • polymath/extensions/__init__.py — Ensure removed wiring has been fully relocated and nothing relied on import-time side effects.
    • polymath/extensions/indexer.py and mask_ops.py — Indexing/masking edge cases and derivative propagation.
    • polymath/extensions/pickler.py and shrinker.py — State/serialization and caching semantics after refactor.

Pre-merge checks

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Refactor into mixins' directly and clearly describes the primary architectural change throughout the pull request.
Docstring Coverage ✅ Passed Docstring coverage is 95.70% which is sufficient. The required threshold is 80.00%.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link

codecov bot commented Dec 5, 2025

Codecov Report

❌ Patch coverage is 79.00614% with 376 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.54%. Comparing base (7e4a15f) to head (7799d94).

Files with missing lines Patch % Lines
polymath/extensions/pickler.py 79.62% 60 Missing and 27 partials ⚠️
polymath/extensions/vector_ops.py 77.65% 34 Missing and 27 partials ⚠️
polymath/extensions/mask_ops.py 68.09% 41 Missing and 11 partials ⚠️
polymath/extensions/tvl.py 55.55% 34 Missing and 14 partials ⚠️
polymath/extensions/indexer.py 87.98% 19 Missing and 18 partials ⚠️
polymath/extensions/item_ops.py 78.62% 28 Missing and 3 partials ⚠️
polymath/extensions/shaper.py 86.48% 12 Missing and 13 partials ⚠️
polymath/extensions/shrinker.py 70.73% 14 Missing and 10 partials ⚠️
polymath/extensions/broadcaster.py 86.41% 10 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #14      +/-   ##
==========================================
- Coverage   72.83%   72.54%   -0.29%     
==========================================
  Files          24       23       -1     
  Lines        7123     7074      -49     
  Branches     1599     1599              
==========================================
- Hits         5188     5132      -56     
- Misses       1497     1504       +7     
  Partials      438      438              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 26

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
polymath/extensions/pickler.py (1)

2-2: Fix file header comment to match filename.

The comment says pickle.py but the file is pickler.py.

-# polymath/extensions/pickle.py
+# polymath/extensions/pickler.py
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7e4a15f and b098249.

📒 Files selected for processing (14)
  • .flake8 (1 hunks)
  • polymath/__init__.py (0 hunks)
  • polymath/extensions/__init__.py (1 hunks)
  • polymath/extensions/broadcaster.py (1 hunks)
  • polymath/extensions/indexer.py (1 hunks)
  • polymath/extensions/item_ops.py (1 hunks)
  • polymath/extensions/iterator.py (3 hunks)
  • polymath/extensions/mask_ops.py (2 hunks)
  • polymath/extensions/pickler.py (3 hunks)
  • polymath/extensions/shaper.py (1 hunks)
  • polymath/extensions/shrinker.py (1 hunks)
  • polymath/extensions/tvl.py (1 hunks)
  • polymath/extensions/vector_ops.py (1 hunks)
  • polymath/qube.py (1 hunks)
💤 Files with no reviewable changes (1)
  • polymath/init.py
🧰 Additional context used
🧬 Code graph analysis (5)
polymath/extensions/tvl.py (2)
polymath/qube.py (9)
  • Qube (23-3158)
  • is_one_false (2239-2245)
  • antimask (1304-1317)
  • and_ (930-967)
  • prefer_builtins (337-352)
  • as_builtin (354-388)
  • mask (1298-1301)
  • _set_mask (1208-1252)
  • or_ (890-927)
polymath/extensions/math_ops.py (3)
  • logical_not (1615-1625)
  • any (1631-1675)
  • all (1677-1718)
polymath/extensions/broadcaster.py (2)
polymath/qube.py (6)
  • shape (1344-1347)
  • Qube (23-3158)
  • derivs (1338-1341)
  • insert_deriv (1520-1586)
  • item (1380-1383)
  • _is_one_value (2248-2251)
polymath/extensions/shaper.py (1)
  • reshape (11-50)
polymath/extensions/mask_ops.py (1)
polymath/qube.py (9)
  • mask (1298-1301)
  • remask (2723-2753)
  • Qube (23-3158)
  • _suitable_mask (529-586)
  • copy (2161-2208)
  • remask_or (2755-2786)
  • or_ (890-927)
  • derivs (1338-1341)
  • shape (1344-1347)
polymath/extensions/shrinker.py (3)
polymath/qube.py (5)
  • antimask (1304-1317)
  • Qube (23-3158)
  • shape (1344-1347)
  • mask (1298-1301)
  • item (1380-1383)
polymath/extensions/mask_ops.py (1)
  • mask_where (11-84)
polymath/extensions/broadcaster.py (1)
  • broadcast_to (34-120)
polymath/extensions/vector_ops.py (3)
polymath/qube.py (11)
  • Qube (23-3158)
  • antimask (1304-1317)
  • shape (1344-1347)
  • denom (1392-1395)
  • ndim (1356-1359)
  • or_ (890-927)
  • nrank (1368-1371)
  • drank (1374-1377)
  • wod (1725-1750)
  • _disallow_denom (3010-3018)
  • dtype (2257-2260)
polymath/extensions/math_ops.py (4)
  • mean (1894-1926)
  • sum (1860-1892)
  • any (1631-1675)
  • all (1677-1718)
polymath/vector.py (1)
  • unit (443-456)
🪛 Ruff (0.14.7)
polymath/extensions/tvl.py

363-363: Missing return type annotation for private function _tvl_op

(ANN202)

polymath/extensions/broadcaster.py

123-123: Missing return type annotation for staticmethod broadcasted_shape

(ANN205)


123-123: Missing type annotation for *objects

(ANN002)


182-182: Avoid specifying long messages outside the exception class

(TRY003)


187-187: Missing return type annotation for staticmethod broadcast

(ANN205)


187-187: Missing type annotation for *objects

(ANN002)

polymath/extensions/indexer.py

12-12: Missing return type annotation for special method __getitem__

(ANN204)


45-45: Avoid specifying long messages outside the exception class

(TRY003)


96-96: Missing return type annotation for special method __setitem__

Add return type annotation: None

(ANN204)


104-104: Unpacked variable shape_before is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


244-244: Missing return type annotation for private function _prep_index

(ANN202)


314-314: Abstract raise to an inner function

(TRY301)


314-314: Avoid specifying long messages outside the exception class

(TRY003)


329-329: Abstract raise to an inner function

(TRY301)


329-329: Avoid specifying long messages outside the exception class

(TRY003)


356-356: Abstract raise to an inner function

(TRY301)


356-356: Avoid specifying long messages outside the exception class

(TRY003)


375-379: Abstract raise to an inner function

(TRY301)


375-379: Avoid specifying long messages outside the exception class

(TRY003)


494-494: Abstract raise to an inner function

(TRY301)


519-519: Do not catch blind exception: Exception

(BLE001)


520-520: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


522-522: Missing return type annotation for private function _prep_scalar_index

(ANN202)


560-560: Avoid specifying long messages outside the exception class

(TRY003)


562-562: Avoid specifying long messages outside the exception class

(TRY003)


572-572: Avoid specifying long messages outside the exception class

(TRY003)


580-580: Avoid specifying long messages outside the exception class

(TRY003)


588-588: Avoid specifying long messages outside the exception class

(TRY003)

polymath/extensions/iterator.py

117-117: Missing return type annotation for special method __iter__

(ANN204)

polymath/extensions/mask_ops.py

50-51: Avoid specifying long messages outside the exception class

(TRY003)


61-61: Boolean positional value in function call

(FBT003)


423-423: Missing return type annotation for private function _limit_from_qube

(ANN202)


483-483: Missing return type annotation for staticmethod is_below

(ANN205)


483-483: Boolean default positional argument in function definition

(FBT002)


502-502: Missing return type annotation for staticmethod is_above

(ANN205)


502-502: Boolean default positional argument in function definition

(FBT002)


521-521: Missing return type annotation for staticmethod is_outside

(ANN205)


521-521: Boolean default positional argument in function definition

(FBT002)


541-541: Missing return type annotation for staticmethod is_inside

(ANN205)


541-541: Boolean default positional argument in function definition

(FBT002)

polymath/extensions/shaper.py

138-138: Avoid specifying long messages outside the exception class

(TRY003)


196-196: Avoid specifying long messages outside the exception class

(TRY003)


237-237: Missing return type annotation for staticmethod stack

(ANN205)


237-237: Missing type annotation for *args

(ANN002)


287-288: Avoid specifying long messages outside the exception class

(TRY003)


340-340: Consider (len(args), *args[subclass_indx].shape) instead of concatenation

Replace with (len(args), *args[subclass_indx].shape)

(RUF005)


357-357: Consider iterable unpacking instead of concatenation

Replace with iterable unpacking

(RUF005)

polymath/extensions/pickler.py

98-98: Missing return type annotation for staticmethod _pickle_debug

Add return type annotation: None

(ANN205)


161-161: Missing return type annotation for staticmethod set_default_pickle_digits

Add return type annotation: None

(ANN205)


237-237: Missing return type annotation for private function _check_pickle_digits

Add return type annotation: None

(ANN202)


255-255: Loop control variable key not used within loop body

Rename unused key to _key

(B007)


262-262: Missing return type annotation for staticmethod _validate_pickle_digits

(ANN205)


283-283: Abstract raise to an inner function

(TRY301)


288-288: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


293-293: Missing return type annotation for staticmethod _validate_pickle_reference

(ANN205)


314-314: Abstract raise to an inner function

(TRY301)


314-314: Avoid specifying long messages outside the exception class

(TRY003)


317-317: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


317-317: Avoid specifying long messages outside the exception class

(TRY003)


326-326: Missing return type annotation for staticmethod fpzip_compress

(ANN205)


358-358: Consider (-1, *shape[-3:]) instead of concatenation

Replace with (-1, *shape[-3:])

(RUF005)


394-394: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


400-400: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


401-401: Consider (-1, *shape[2:]) instead of concatenation

(RUF005)


412-412: No explicit stacklevel keyword argument found

Set stacklevel=2

(B028)


415-415: No explicit stacklevel keyword argument found

Set stacklevel=2

(B028)


421-421: Missing return type annotation for staticmethod fpzip_decompress

(ANN205)


476-476: Missing return type annotation for staticmethod _encode_one_float_array

(ANN205)


527-527: Use format specifiers instead of percent format

Replace with format specifiers

(UP031)


576-576: Missing return type annotation for staticmethod _encode_floats

(ANN205)


632-632: Consider (-1, item_size) instead of concatenation

Replace with (-1, item_size)

(RUF005)


643-643: Missing return type annotation for staticmethod _decode_scaled_uints

(ANN205)


668-668: Missing return type annotation for staticmethod _decode_floats

(ANN205)


691-691: Use format specifiers instead of percent format

Replace with format specifiers

(UP031)


699-699: Consider (len(items), *shape[:-item_rank]) instead of concatenation

Replace with (len(items), *shape[:-item_rank])

(RUF005)


707-707: Missing return type annotation for staticmethod _encode_ints

(ANN205)


716-716: Missing return type annotation for staticmethod _decode_ints

(ANN205)


723-723: Missing return type annotation for staticmethod _encode_bools

(ANN205)


732-732: Missing return type annotation for staticmethod _decode_bools

(ANN205)


745-745: Missing return type annotation for special method __getstate__

(ANN204)


886-886: Missing return type annotation for special method __setstate__

(ANN204)


966-966: Avoid specifying long messages outside the exception class

(TRY003)

polymath/extensions/item_ops.py

88-88: Consider (type(self), *classes) instead of concatenation

Replace with (type(self), *classes)

(RUF005)


111-111: Avoid specifying long messages outside the exception class

(TRY003)


217-217: Boolean default positional argument in function definition

(FBT002)


241-241: Avoid specifying long messages outside the exception class

(TRY003)


255-255: Boolean positional value in function call

(FBT003)


337-338: Avoid specifying long messages outside the exception class

(TRY003)


430-430: Loop control variable r not used within loop body

Rename unused r to _r

(B007)


459-459: Missing return type annotation for special method __matmul__

(ANN204)

polymath/extensions/vector_ops.py

13-13: Missing return type annotation for private function _mean_or_sum

(ANN202)


54-54: Boolean positional value in function call

(FBT003)


58-58: Boolean positional value in function call

(FBT003)


63-63: Boolean positional value in function call

(FBT003)


108-108: Missing return type annotation for private function _check_axis

Add return type annotation: None

(ANN202)


138-139: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


138-139: Avoid specifying long messages outside the exception class

(TRY003)


142-142: Avoid specifying long messages outside the exception class

(TRY003)


146-146: Missing return type annotation for private function _zero_sized_result

(ANN202)


170-170: Missing return type annotation for staticmethod dot

(ANN205)


207-208: Avoid specifying long messages outside the exception class

(TRY003)


217-218: Avoid specifying long messages outside the exception class

(TRY003)


223-224: Avoid specifying long messages outside the exception class

(TRY003)


283-283: Missing return type annotation for staticmethod norm

(ANN205)


314-315: Avoid specifying long messages outside the exception class

(TRY003)


339-339: Missing return type annotation for staticmethod norm_sq

(ANN205)


369-370: Avoid specifying long messages outside the exception class

(TRY003)


394-394: Missing return type annotation for staticmethod cross

(ANN205)


430-431: Avoid specifying long messages outside the exception class

(TRY003)


440-441: Avoid specifying long messages outside the exception class

(TRY003)


446-447: Avoid specifying long messages outside the exception class

(TRY003)


510-510: Missing return type annotation for staticmethod cross_3x3

(ANN205)


530-530: Avoid specifying long messages outside the exception class

(TRY003)


540-540: Missing return type annotation for staticmethod cross_2x2

(ANN205)


560-560: Avoid specifying long messages outside the exception class

(TRY003)


566-566: Missing return type annotation for staticmethod outer

(ANN205)


566-566: Boolean default positional argument in function definition

(FBT002)


642-642: Missing return type annotation for staticmethod as_diagonal

(ANN205)


642-642: Boolean default positional argument in function definition

(FBT002)


668-669: Avoid specifying long messages outside the exception class

(TRY003)


693-693: Boolean positional value in function call

(FBT003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Test polymath (windows-latest, 3.12)
  • GitHub Check: Test polymath (windows-latest, 3.8)
  • GitHub Check: Test polymath (windows-latest, 3.10)
  • GitHub Check: Test polymath (macos-latest, 3.8)
🔇 Additional comments (29)
polymath/extensions/shrinker.py (1)

106-188: LGTM!

The unshrink() method correctly handles cached values, shape reconstruction, value/mask expansion, and derivative propagation.

polymath/extensions/item_ops.py (1)

438-473: LGTM!

The chain() and __matmul__() methods are implemented correctly with proper delegation.

polymath/extensions/pickler.py (2)

745-884: LGTM!

The __getstate__ method comprehensively handles mask/value encoding, derivative serialization, and various data types with appropriate compression strategies.


886-1016: LGTM!

The __setstate__ method correctly reverses the serialization process, handling backward compatibility for renamed keys, proper mask/value decoding, and derivative restoration with antimask expansion.

polymath/extensions/iterator.py (3)

9-62: LGTM!

The private _QubeIterator class correctly implements first-axis iteration with proper __iter__ and __next__ methods.


64-113: LGTM!

The private _QubeNDIterator class correctly implements multi-dimensional iteration using itertools.product.


116-137: LGTM!

The public QubeIterator mixin cleanly delegates to private iterator implementations, providing a clean public API.

polymath/extensions/indexer.py (1)

8-10: LGTM! Mixin class structure is appropriate.

The QubeIndexer mixin encapsulates indexing operations cleanly and follows the pattern used by other mixins in this PR.

polymath/extensions/broadcaster.py (1)

8-9: LGTM! Mixin structure is appropriate.

The QubeBroadcaster mixin cleanly encapsulates broadcast operations with appropriate docstrings for the deprecated broadcast_into_shape method.

polymath/qube.py (1)

10-25: LGTM! Mixin composition is well-structured.

The multi-inheritance approach cleanly separates concerns into dedicated mixins. The import organization and class declaration follow good practices for Python mixin patterns.

polymath/extensions/shaper.py (1)

328-336: LGTM! Mask determination logic is correct.

The handling of None placeholders and mask type determination is properly implemented.

polymath/extensions/tvl.py (2)

36-73: LGTM! Three-valued logic implementation is correct.

The tvl_and implementation correctly follows the documented truth table for three-valued logic AND operation. The handling of masked values as indeterminate is properly implemented.


397-400: The mutation is safe and not a concern.

Comparison operators (==, <, etc.) always return freshly instantiated Boolean objects via the Qube._BOOLEAN_CLASS(...) constructor, not cached or reused objects. Since _set_mask() is called on a newly created object with no other references, the in-place mutation has no side effects. This pattern is consistent throughout the codebase.

polymath/extensions/mask_ops.py (7)

8-9: LGTM - Clean mixin class definition.

The mixin pattern is appropriate for extending Qube functionality.


11-84: LGTM - Well-structured mask_where implementation.

The method correctly handles all cases: scalar, no replacement, and with replacement. The early return optimization at line 36-37 is good.


86-137: LGTM - Correct implementation of equality-based masking.

The use of np.all across item axes correctly handles multi-dimensional items.


139-246: LGTM - Consistent comparison-based masking methods.

The use of ±infinity for masked limit values correctly ensures masked positions don't trigger masking.


248-354: LGTM - Range-based masking methods implemented correctly.

The operator selection logic based on mask_endpoints is correct, and the masked limit values are appropriately chosen to make masked limits effectively ignored.


356-421: LGTM - Comprehensive clip implementation.

The method correctly handles both the optimized scalar case and the general case. The derivative handling when remask=False properly zeroes out derivatives for clipped values.


482-558: LGTM - Clean utility methods for range checking.

These static methods provide clear, reusable range-checking logic. The static analysis hints about return type annotations are valid but optional improvements.

polymath/extensions/vector_ops.py (9)

10-11: LGTM - Appropriate mixin class for vector operations.

The class consolidates vector operations that will be mixed into the Qube class.


13-106: LGTM - Comprehensive mean/sum implementation.

The method correctly handles all masking scenarios and properly propagates derivatives. The axis normalization logic is correct.


169-279: LGTM - Correct dot product implementation.

The axis handling, shape manipulation, and derivative propagation logic is correct.


283-390: LGTM - Correct norm and norm_sq implementations.

The derivative calculations correctly apply the chain rule.


394-507: LGTM - Correct cross product implementation.

The method properly handles both 2D and 3D cross products with appropriate result shapes.


509-562: LGTM - Correct cross product helper implementations.

The standard cross product formulas are correctly implemented.


566-638: LGTM - Correct outer product implementation.

The shape manipulation for outer product and derivative propagation is correct.


642-695: LGTM - Correct as_diagonal implementation.

The method correctly expands a single axis into a diagonal matrix representation.


697-711: LGTM - Simple and correct RMS implementation.

The method correctly computes root-mean-square across item dimensions.

@rfrenchseti
Copy link
Collaborator Author

This work is postponed due to unforeseen problems with Sphinx. See #15.

@rfrenchseti rfrenchseti closed this Dec 5, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

♻️ Duplicate comments (10)
polymath/extensions/shrinker.py (1)

47-50: Inconsistent read-only handling for shapeless objects remains unaddressed.

The docstring at line 23 states "Shrunken objects are always converted to read-only," but when self._is_scalar, the method returns self without calling as_readonly(). Other code paths (lines 42, 86, 97) consistently call as_readonly().

         # If this is a shapeless object, return it as is
         if self._is_scalar:
-            self._cache['unshrunk'] = self
-            return self
+            obj = self.as_readonly()
+            obj._cache['unshrunk'] = self
+            return obj
polymath/extensions/indexer.py (1)

103-104: Prefix unused variable with underscore.

shape_before is unpacked but never used. Use _shape_before to signal intent and silence linter warnings.

         try:
             (masked, size_zero,
-             shape_before, shape_after) = self._prep_scalar_index(indx)
+             _shape_before, shape_after) = self._prep_scalar_index(indx)
polymath/extensions/shaper.py (2)

149-152: Self-reassignment pattern is intentional but could use clarification.

The reassignment of self creates a reshaped view for subsequent operations. As noted in a previous review, a brief comment would clarify this intentional pattern.

         # Add missing axes if necessary
         if self._ndims < rank:
+            # Reassign self to a reshaped view with leading singleton axes
             self = self.reshape((rank - self._ndims) * (1,) + self._shape,
                                 recursive=recursive)

212-215: Same self-reassignment pattern as roll_axis.

Consider adding a similar clarifying comment here for consistency.

         # Add missing axes if necessary
         if self._ndims < rank:
+            # Reassign self to a reshaped view with leading singleton axes
             self = self.reshape((rank - self._ndims) * (1,) + self._shape,
                                 recursive=recursive)
polymath/extensions/item_ops.py (3)

8-9: Class docstring could be more descriptive.

The docstring is generic. Consider specifying that this mixin provides numerator/denominator item restructuring operations.


56-91: Use tuple unpacking for cleaner syntax.

Static analysis suggests using tuple unpacking instead of concatenation.

-        obj = obj.cast((type(self),) + classes)
+        obj = obj.cast((type(self), *classes))

411-436: Rename unused loop variable to _r.

The loop variable r is not used within the loop body.

-        for r in range(self._nrank):
+        for _r in range(self._nrank):
             new_values = np.rollaxis(new_values, -self._drank-1, len_shape)
polymath/extensions/vector_ops.py (1)

160-169: Bug: Misplaced else clause causes incorrect behavior.

The else on line 164 is a for-else construct (executes after loop completion), not an if-else. This means line 165 indx[i] = 0 runs after the loop with i set to the last iterated value, which is redundant. Additionally, when axis is a single integer (not a list/tuple), the code falls through to line 167 correctly, but the for-else is confusing and incorrect.

         # Construct an index to obtain the correct shape
         indx = self._ndims * [slice(None)]
         if isinstance(axis, (list, tuple)):
             for i in axis:
                 indx[i] = 0
-            else:
-                indx[i] = 0
+        else:
+            indx[axis] = 0
 
         return self[tuple(indx)]
polymath/extensions/pickler.py (2)

287-288: Use exception chaining for clearer tracebacks.

When re-raising exceptions inside an except block, use raise ... from None to suppress the original exception context when it's not relevant.


316-317: Use exception chaining for clearer tracebacks.

When re-raising exceptions inside an except block, use raise ... from None.

         except (ValueError, IndexError, TypeError):
-            raise ValueError(f'invalid pickle reference {original_references!r}')
+            raise ValueError(f'invalid pickle reference {original_references!r}') from None
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b098249 and 7799d94.

📒 Files selected for processing (9)
  • polymath/extensions/__init__.py (1 hunks)
  • polymath/extensions/indexer.py (1 hunks)
  • polymath/extensions/item_ops.py (1 hunks)
  • polymath/extensions/mask_ops.py (2 hunks)
  • polymath/extensions/pickler.py (3 hunks)
  • polymath/extensions/shaper.py (1 hunks)
  • polymath/extensions/shrinker.py (1 hunks)
  • polymath/extensions/tvl.py (1 hunks)
  • polymath/extensions/vector_ops.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (6)
polymath/extensions/shaper.py (2)
polymath/qube.py (19)
  • shape (1344-1347)
  • Qube (23-3158)
  • insert_deriv (1520-1586)
  • rank (1362-1365)
  • denom (1392-1395)
  • is_float (2291-2296)
  • is_int (2344-2351)
  • is_bool (2395-2400)
  • confirm_unit (1949-1966)
  • drank (1374-1377)
  • as_this_type (2439-2516)
  • is_one_true (2230-2236)
  • is_one_false (2239-2245)
  • mask (1298-1301)
  • zeros (1030-1051)
  • dtype (2257-2260)
  • values (1259-1262)
  • derivs (1338-1341)
  • insert_derivs (1588-1620)
polymath/extensions/broadcaster.py (1)
  • broadcast (187-229)
polymath/extensions/indexer.py (3)
polymath/qube.py (12)
  • as_size_zero (2585-2624)
  • shape (1344-1347)
  • copy (2161-2208)
  • dtype (2257-2260)
  • mask (1298-1301)
  • Qube (23-3158)
  • insert_deriv (1520-1586)
  • antimask (1304-1317)
  • denom (1392-1395)
  • item (1380-1383)
  • _disallow_denom (3010-3018)
  • is_float (2291-2296)
polymath/scalar.py (1)
  • as_index_and_mask (142-199)
polymath/vector.py (1)
  • as_index_and_mask (200-254)
polymath/extensions/mask_ops.py (1)
polymath/qube.py (8)
  • mask (1298-1301)
  • remask (2723-2753)
  • Qube (23-3158)
  • _suitable_mask (529-586)
  • copy (2161-2208)
  • derivs (1338-1341)
  • _opstr (2990-3008)
  • shape (1344-1347)
polymath/extensions/item_ops.py (1)
polymath/qube.py (11)
  • Qube (23-3158)
  • _require_axis_in_range (3030-3045)
  • nrank (1368-1371)
  • cast (2518-2554)
  • insert_deriv (1520-1586)
  • drank (1374-1377)
  • shape (1344-1347)
  • nsize (1410-1413)
  • _opstr (2990-3008)
  • dsize (1416-1419)
  • wod (1725-1750)
polymath/extensions/pickler.py (1)
polymath/qube.py (5)
  • values (1259-1262)
  • dtype (2257-2260)
  • shape (1344-1347)
  • Qube (23-3158)
  • _dtype (673-676)
polymath/extensions/vector_ops.py (2)
polymath/qube.py (6)
  • Qube (23-3158)
  • denom (1392-1395)
  • or_ (890-927)
  • drank (1374-1377)
  • _disallow_denom (3010-3018)
  • dtype (2257-2260)
polymath/extensions/math_ops.py (4)
  • mean (1894-1926)
  • sum (1860-1892)
  • any (1631-1675)
  • all (1677-1718)
🪛 Ruff (0.14.7)
polymath/extensions/shaper.py

138-138: Avoid specifying long messages outside the exception class

(TRY003)


196-196: Avoid specifying long messages outside the exception class

(TRY003)


236-236: Missing return type annotation for staticmethod stack

(ANN205)


236-236: Missing type annotation for *args

(ANN002)


286-287: Avoid specifying long messages outside the exception class

(TRY003)


339-339: Consider (len(args), *args[subclass_indx].shape) instead of concatenation

Replace with (len(args), *args[subclass_indx].shape)

(RUF005)


356-356: Consider iterable unpacking instead of concatenation

Replace with iterable unpacking

(RUF005)

polymath/extensions/indexer.py

12-12: Missing return type annotation for special method __getitem__

(ANN204)


45-45: Avoid specifying long messages outside the exception class

(TRY003)


96-96: Missing return type annotation for special method __setitem__

Add return type annotation: None

(ANN204)


104-104: Unpacked variable shape_before is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


244-244: Missing return type annotation for private function _prep_index

(ANN202)


314-314: Abstract raise to an inner function

(TRY301)


314-314: Avoid specifying long messages outside the exception class

(TRY003)


329-329: Abstract raise to an inner function

(TRY301)


329-329: Avoid specifying long messages outside the exception class

(TRY003)


356-356: Abstract raise to an inner function

(TRY301)


356-356: Avoid specifying long messages outside the exception class

(TRY003)


375-379: Abstract raise to an inner function

(TRY301)


375-379: Avoid specifying long messages outside the exception class

(TRY003)


494-494: Abstract raise to an inner function

(TRY301)


522-522: Missing return type annotation for private function _prep_scalar_index

(ANN202)


560-560: Avoid specifying long messages outside the exception class

(TRY003)


562-562: Avoid specifying long messages outside the exception class

(TRY003)


572-572: Avoid specifying long messages outside the exception class

(TRY003)


580-580: Avoid specifying long messages outside the exception class

(TRY003)


588-588: Avoid specifying long messages outside the exception class

(TRY003)

polymath/extensions/mask_ops.py

50-51: Avoid specifying long messages outside the exception class

(TRY003)


61-61: Boolean positional value in function call

(FBT003)


423-423: Missing return type annotation for private function _limit_from_qube

(ANN202)


483-483: Missing return type annotation for staticmethod is_below

(ANN205)


483-483: Boolean default positional argument in function definition

(FBT002)


502-502: Missing return type annotation for staticmethod is_above

(ANN205)


502-502: Boolean default positional argument in function definition

(FBT002)


521-521: Missing return type annotation for staticmethod is_outside

(ANN205)


521-521: Boolean default positional argument in function definition

(FBT002)


541-541: Missing return type annotation for staticmethod is_inside

(ANN205)


541-541: Boolean default positional argument in function definition

(FBT002)

polymath/extensions/tvl.py

362-362: Missing return type annotation for private function _tvl_op

(ANN202)

polymath/extensions/item_ops.py

88-88: Consider (type(self), *classes) instead of concatenation

Replace with (type(self), *classes)

(RUF005)


111-111: Avoid specifying long messages outside the exception class

(TRY003)


217-217: Boolean default positional argument in function definition

(FBT002)


241-241: Avoid specifying long messages outside the exception class

(TRY003)


255-255: Boolean positional value in function call

(FBT003)


337-338: Avoid specifying long messages outside the exception class

(TRY003)


430-430: Loop control variable r not used within loop body

Rename unused r to _r

(B007)


459-459: Missing return type annotation for special method __matmul__

(ANN204)

polymath/extensions/pickler.py

98-98: Missing return type annotation for staticmethod _pickle_debug

Add return type annotation: None

(ANN205)


161-161: Missing return type annotation for staticmethod set_default_pickle_digits

Add return type annotation: None

(ANN205)


237-237: Missing return type annotation for private function _check_pickle_digits

Add return type annotation: None

(ANN202)


255-255: Loop control variable key not used within loop body

Rename unused key to _key

(B007)


262-262: Missing return type annotation for staticmethod _validate_pickle_digits

(ANN205)


283-283: Abstract raise to an inner function

(TRY301)


293-293: Missing return type annotation for staticmethod _validate_pickle_reference

(ANN205)


296-296: Unused blanket noqa directive

Remove unused noqa directive

(RUF100)


314-314: Abstract raise to an inner function

(TRY301)


314-314: Avoid specifying long messages outside the exception class

(TRY003)


317-317: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


317-317: Avoid specifying long messages outside the exception class

(TRY003)


326-326: Missing return type annotation for staticmethod fpzip_compress

(ANN205)


358-358: Consider (-1, *shape[-3:]) instead of concatenation

Replace with (-1, *shape[-3:])

(RUF005)


394-394: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


400-400: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


401-401: Consider (-1, *shape[2:]) instead of concatenation

(RUF005)


423-423: Missing return type annotation for staticmethod fpzip_decompress

(ANN205)


478-478: Missing return type annotation for staticmethod _encode_one_float_array

(ANN205)


529-529: Use format specifiers instead of percent format

Replace with format specifiers

(UP031)


578-578: Missing return type annotation for staticmethod _encode_floats

(ANN205)


634-634: Consider (-1, item_size) instead of concatenation

Replace with (-1, item_size)

(RUF005)


645-645: Missing return type annotation for staticmethod _decode_scaled_uints

(ANN205)


670-670: Missing return type annotation for staticmethod _decode_floats

(ANN205)


693-693: Use format specifiers instead of percent format

Replace with format specifiers

(UP031)


701-701: Consider (len(items), *shape[:-item_rank]) instead of concatenation

Replace with (len(items), *shape[:-item_rank])

(RUF005)


709-709: Missing return type annotation for staticmethod _encode_ints

(ANN205)


718-718: Missing return type annotation for staticmethod _decode_ints

(ANN205)


725-725: Missing return type annotation for staticmethod _encode_bools

(ANN205)


734-734: Missing return type annotation for staticmethod _decode_bools

(ANN205)


747-747: Missing return type annotation for special method __getstate__

(ANN204)


888-888: Missing return type annotation for special method __setstate__

(ANN204)


968-968: Avoid specifying long messages outside the exception class

(TRY003)

polymath/extensions/vector_ops.py

13-13: Missing return type annotation for private function _mean_or_sum

(ANN202)


54-54: Boolean positional value in function call

(FBT003)


58-58: Boolean positional value in function call

(FBT003)


63-63: Boolean positional value in function call

(FBT003)


108-108: Missing return type annotation for private function _check_axis

Add return type annotation: None

(ANN202)


138-139: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


138-139: Avoid specifying long messages outside the exception class

(TRY003)


142-142: Avoid specifying long messages outside the exception class

(TRY003)


146-146: Missing return type annotation for private function _zero_sized_result

(ANN202)


172-172: Missing return type annotation for staticmethod dot

(ANN205)


209-210: Avoid specifying long messages outside the exception class

(TRY003)


219-220: Avoid specifying long messages outside the exception class

(TRY003)


225-226: Avoid specifying long messages outside the exception class

(TRY003)


284-284: Missing return type annotation for staticmethod norm

(ANN205)


315-316: Avoid specifying long messages outside the exception class

(TRY003)


339-339: Missing return type annotation for staticmethod norm_sq

(ANN205)


369-370: Avoid specifying long messages outside the exception class

(TRY003)


393-393: Missing return type annotation for staticmethod cross

(ANN205)


429-430: Avoid specifying long messages outside the exception class

(TRY003)


439-440: Avoid specifying long messages outside the exception class

(TRY003)


445-446: Avoid specifying long messages outside the exception class

(TRY003)


509-509: Missing return type annotation for staticmethod cross_3x3

(ANN205)


529-529: Avoid specifying long messages outside the exception class

(TRY003)


539-539: Missing return type annotation for staticmethod cross_2x2

(ANN205)


559-559: Avoid specifying long messages outside the exception class

(TRY003)


564-564: Missing return type annotation for staticmethod outer

(ANN205)


564-564: Boolean default positional argument in function definition

(FBT002)


639-639: Missing return type annotation for staticmethod as_diagonal

(ANN205)


639-639: Boolean default positional argument in function definition

(FBT002)


665-666: Avoid specifying long messages outside the exception class

(TRY003)


690-690: Boolean positional value in function call

(FBT003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Test polymath (macos-latest, 3.10)
  • GitHub Check: Test polymath (windows-latest, 3.8)
🔇 Additional comments (47)
polymath/extensions/__init__.py (1)

4-4: LGTM!

The comment correctly indicates that mixins are now imported directly by qube.py. The typo from the previous review ("directory" → "directly") has been addressed.

polymath/extensions/shrinker.py (3)

8-9: Docstring improvement applied.

The docstring now correctly describes "shrink and unshrink operations" as suggested in previous review.


94-98: Verify as_readonly() return value is intentionally discarded.

At line 97, obj.as_readonly() is called but the return value is not captured. This works correctly only if as_readonly() modifies obj in-place and returns self. Other code paths (lines 42, 86) assign the result. For consistency and safety, consider capturing the return value.

         # Construct the new object
         obj = Qube.__new__(type(self))
         obj.__init__(self._values[extras * (slice(None),) + (antimask, Ellipsis)], mask,
                      example=self)
-        obj.as_readonly()
+        obj = obj.as_readonly()

106-187: LGTM!

The unshrink method is well-implemented with proper read-only handling (line 181), correct derivative propagation, and appropriate local imports to avoid circular dependencies.

polymath/extensions/indexer.py (5)

9-10: LGTM!

The QubeIndexer mixin class is well-structured with clear organization of indexing operations.


12-94: LGTM!

The __getitem__ implementation correctly handles both shapeless and shaped indexing, with proper derivative propagation and readonly status preservation.


519-520: Exception chaining implemented correctly.

The exception handling now properly chains the original exception with from e, preserving the traceback context as suggested in the previous review.


244-521: LGTM!

The _prep_index method is comprehensive, handling various index types (slices, ellipsis, boolean arrays, scalar arrays) with proper validation and error handling.


522-593: LGTM!

The _prep_scalar_index method correctly handles all valid scalar index types (True, False, Ellipsis, None, empty slice) with clear error messages for invalid cases.

polymath/extensions/shaper.py (2)

235-236: Duplicate @staticmethod decorator removed.

The duplicate decorator issue from the previous review has been addressed. Only one @staticmethod decorator is now present.


236-390: LGTM!

The stack method is comprehensive and handles type coercion, broadcasting, mask construction, and derivative propagation correctly. The implementation aligns with the pattern in polymath/qube.py.

polymath/extensions/tvl.py (3)

90-92: Stray docstring fragment removed.

The duplicate/leftover docstring fragment noted in the previous review has been addressed. The docstring now reads cleanly.


362-399: LGTM!

The _tvl_op helper correctly handles mask propagation for comparison operations. The in-place mask modification via _set_mask is safe since comparison operators (==, !=, etc.) return newly constructed objects.


11-250: LGTM!

The three-valued logic methods (tvl_and, tvl_or, tvl_any, tvl_all) correctly implement the truth tables documented in their docstrings, with proper handling of masked values as indeterminate.

polymath/extensions/mask_ops.py (5)

8-9: LGTM!

The QubeMaskOps mixin class correctly encapsulates all masking-related operations with a clear docstring.


11-84: LGTM!

The mask_where method is well-implemented with proper handling of replacement values, shapeless objects, and derivative propagation through the indexing operation.


472-476: Inconsistent property access fixed.

The code now consistently uses limit._mask throughout the method, addressing the previous review comment.


482-558: LGTM!

The static helper methods (is_below, is_above, is_outside, is_inside) are clean implementations of range-checking utilities that support the clip method.


356-421: LGTM!

The clip method correctly handles both scalar and Qube-based limits with proper derivative zeroing for clipped values when remask=False.

polymath/extensions/item_ops.py (9)

11-54: LGTM!

The extract_numer method correctly handles axis positioning, array slicing via rollaxis, object construction with proper nrank adjustment, and recursive derivative handling.


93-120: LGTM!

The extract_denoms method correctly handles edge cases (drank == 0 returns self, drank != 1 raises error) and properly constructs objects using Qube.__new__ with correct initialization.


122-167: LGTM!

The slice_numer method correctly handles slicing with proper axis positioning, rolls the axis back after slicing, and propagates derivatives recursively.


173-215: LGTM!

The transpose_numer method correctly validates both axes, handles negative indices, swaps axes using np.swapaxes, and properly propagates derivatives.


259-274: LGTM!

The flatten_numer method is a clean delegation to reshape_numer.


280-316: LGTM!

The transpose_denom method correctly validates axes, handles negative indices, and properly constructs the result object. No recursive derivative handling is needed for denominator operations.


318-355: LGTM!

The reshape_denom and flatten_denom methods correctly handle denominator reshaping with proper validation and object construction.


361-409: LGTM!

The join_items and split_items methods correctly handle the transfer of axes between numerator and denominator, stripping derivatives as documented.


438-473: LGTM!

The chain and __matmul__ methods correctly implement chain multiplication semantics by flattening, joining items, and delegating to Qube.dot. The __matmul__ properly delegates to chain.

polymath/extensions/vector_ops.py (9)

13-106: LGTM!

The _mean_or_sum method correctly handles all edge cases: zero-size arrays, fully masked/unmasked arrays, partial masks with proper counting, and recursive derivative handling. The axis normalization and NumPy function selection are correct.


171-281: LGTM!

The dot static method correctly handles:

  • Dual denominator validation
  • Axis positioning and validation for both operands
  • Shape broadcasting for combined numerator/denominator structures
  • Proper derivative propagation from both operands

283-336: LGTM!

The norm static method correctly computes the Euclidean norm along the specified axis, validates the axis range, disallows denominators, and properly propagates derivatives using the chain rule (d/dx ||v|| = v/||v|| · dv/dx).


338-390: LGTM!

The norm_sq static method correctly computes the squared norm, handles units properly via Unit.mul_units, and propagates derivatives using the chain rule (d/dx ||v||² = 2v · dv/dx).


392-506: LGTM!

The cross static method correctly:

  • Validates axis lengths (must be 2 or 3)
  • Handles both 2D and 3D cross products
  • Properly manages numerator ranks for 2D (reduces by 2) vs 3D (reduces by 1)
  • Propagates derivatives from both operands

508-561: LGTM!

The cross_3x3 and cross_2x2 static methods correctly implement the cross product formulas with proper broadcasting and shape validation.


563-636: LGTM!

The outer static method correctly computes the outer product by reshaping arrays to broadcast properly, validates dual denominators, and propagates derivatives from both operands.


638-692: LGTM!

The as_diagonal static method correctly converts an axis to a diagonal by creating a zero-filled array and populating the diagonal elements, with proper axis roll operations and recursive derivative handling.


694-709: LGTM!

The rms method correctly computes the root-mean-square by summing squares across all item axes and dividing by isize.

polymath/extensions/pickler.py (10)

102-159: LGTM!

The set_pickle_digits method correctly validates inputs using the static validation methods, sets instance attributes, and propagates settings to derivatives.


160-206: LGTM!

The set_default_pickle_digits static method correctly updates global defaults with proper validation.


207-259: LGTM!

The pickle_digits, pickle_reference, and _check_pickle_digits methods correctly handle lazy initialization with global defaults and propagate settings to derivatives.


325-420: LGTM!

The fpzip_compress method correctly handles:

  • Precision calculation based on digits and dtype
  • Dimension limiting to 4 axes (fpzip constraint)
  • Retry logic for precision and buffer overflow errors
  • Warning emission with proper stacklevel

422-471: LGTM!

The fpzip_decompress method correctly decompresses and applies bias correction using the alternating bit pattern to compensate for systematic truncation towards zero.


477-575: LGTM!

The _encode_one_float_array method correctly handles multiple encoding strategies (fpzip, constant, scaled integers) with proper precision calculation and byte-efficient storage selection.


577-706: LGTM!

The _encode_floats and _decode_floats methods correctly handle all encoding formats (literal, fpzip, constant, scaled, items) with proper shape handling and item-wise encoding for heterogeneous ranges.


708-741: LGTM!

The integer and boolean encoding/decoding methods correctly use BZ2 compression with proper handling of contiguous arrays and packbits for booleans.


747-886: LGTM!

The __getstate__ method correctly:

  • Creates a shallow clone without recursive derivatives
  • Handles scalar values, fully masked objects, and arrays
  • Encodes masks with corner optimization
  • Encodes values based on dtype (float/int/bool)
  • Processes derivatives with antimask application

888-1017: LGTM!

The __setstate__ method correctly:

  • Handles legacy key renaming
  • Decodes masks in reverse order (CORNERS, BOOL)
  • Decodes values in reverse order (ALL_MASKED, ANTIMASKED, FLOAT, BOOL, INT)
  • Restores readonly status and writability
  • Expands derivatives with antimask restoration

Comment on lines +217 to +257
def reshape_numer(self, shape, classes=(), recursive=True):
"""This object with a new shape for numerator items.
Raises:
ValueError: If the item size would be changed by the reshape operation.
"""
Parameters:
shape (tuple): The new shape for numerator items.
classes (class, list, or tuple, optional): The class of the object returned. If
a list is provided, the object will be an instance of the first suitable class
in the list. Otherwise, a generic Qube object will be returned.
recursive (bool, optional): True to reshape the derivatives in the same way;
otherwise, the returned object will not contain derivatives.
# Validate the shape
shape = tuple(shape)
if self.nsize != int(np.prod(shape)):
opstr = self._opstr('reshape_numer()')
raise ValueError(f'{opstr} item size must be unchanged: {self._numer}, {shape}')
Returns:
Qube: The reshaped object.
# Reshape
full_shape = self._shape + shape + self._denom
new_values = np.asarray(self._values).reshape(full_shape)
Raises:
ValueError: If the item size would be changed by the reshape operation.
"""

# Construct and cast
obj = Qube(new_values, self._mask, nrank=len(shape), example=self)
obj = obj.cast(classes)
obj._readonly = self._readonly
from polymath.qube import Qube

# Reshape the derivatives if necessary
if recursive:
for key, deriv in self._derivs.items():
obj.insert_deriv(key, deriv.reshape_numer(shape, classes, False))
# Validate the shape
shape = tuple(shape)
if self.nsize != int(np.prod(shape)):
opstr = self._opstr('reshape_numer()')
raise ValueError(f'{opstr} item size must be unchanged: {self._numer}, {shape}')

return obj
# Reshape
full_shape = self._shape + shape + self._denom
new_values = np.asarray(self._values).reshape(full_shape)

# Construct and cast
obj = Qube(new_values, self._mask, nrank=len(shape), example=self)
obj = obj.cast(classes)
obj._readonly = self._readonly

def flatten_numer(self, classes=(), *, recursive=True):
"""This object with a new numerator shape such that nrank == 1.
# Reshape the derivatives if necessary
if recursive:
for key, deriv in self._derivs.items():
obj.insert_deriv(key, deriv.reshape_numer(shape, classes, False))

Parameters:
classes (class, list, or tuple, optional): The class of the object returned. If
a list is provided, the object will be an instance of the first suitable class
in the list. Otherwise, a generic Qube object will be returned.
recursive (bool, optional): True to include matching slices of the derivatives in
the returned object; otherwise, the returned object will not contain
derivatives.
return obj
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Boolean positional argument should be keyword-only.

The recursive parameter is a boolean positional argument which can lead to confusing calls like reshape_numer(shape, classes, True). Consider making it keyword-only for consistency with other methods.

-    def reshape_numer(self, shape, classes=(), recursive=True):
+    def reshape_numer(self, shape, classes=(), *, recursive=True):

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Ruff (0.14.7)

217-217: Boolean default positional argument in function definition

(FBT002)


241-241: Avoid specifying long messages outside the exception class

(TRY003)


255-255: Boolean positional value in function call

(FBT003)

🤖 Prompt for AI Agents
In polymath/extensions/item_ops.py around lines 217 to 257, the boolean
positional parameter "recursive" should be made keyword-only to avoid confusing
calls; change the function signature to make recursive keyword-only (e.g., add a
bare * before recursive) and update the docstring if needed to keep parameter
docs aligned, then run a project-wide search for calls to reshape_numer and
update any call sites that currently pass recursive positionally (e.g.,
reshape_numer(shape, classes, True) -> reshape_numer(shape, classes,
recursive=True)).

Comment on lines +94 to +95
class QubePickler:
"""Mix-in class providing operations for Qube objects."""
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Class docstring is too generic.

The docstring "Mix-in class providing operations for Qube objects" doesn't describe the pickling functionality. Consider "Mix-in class providing pickle serialization and compression operations for Qube objects."

🤖 Prompt for AI Agents
In polymath/extensions/pickler.py around lines 94 to 95, the class docstring for
QubePickler is too generic; update it to clearly state that this mix-in provides
pickle serialization and optional compression/decompression operations for Qube
objects (e.g., "Mix-in class providing pickle serialization and compression
operations for Qube objects."), ensuring it mentions serialization and
compression so future readers understand the class responsibilities.

Comment on lines +255 to +259
for key, deriv in self._derivs.items():
if not hasattr(deriv, '_pickle_digits'):
deriv._pickle_digits = 2 * self._pickle_digits[1:]
if not hasattr(deriv, '_pickle_reference'):
deriv._pickle_reference = 2 * self._pickle_reference[1:]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Rename unused loop variable to _key.

The loop variable key is not used within the loop body.

-        for key, deriv in self._derivs.items():
+        for _key, deriv in self._derivs.items():
             if not hasattr(deriv, '_pickle_digits'):
                 deriv._pickle_digits = 2 * self._pickle_digits[1:]
             if not hasattr(deriv, '_pickle_reference'):
                 deriv._pickle_reference = 2 * self._pickle_reference[1:]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for key, deriv in self._derivs.items():
if not hasattr(deriv, '_pickle_digits'):
deriv._pickle_digits = 2 * self._pickle_digits[1:]
if not hasattr(deriv, '_pickle_reference'):
deriv._pickle_reference = 2 * self._pickle_reference[1:]
for _key, deriv in self._derivs.items():
if not hasattr(deriv, '_pickle_digits'):
deriv._pickle_digits = 2 * self._pickle_digits[1:]
if not hasattr(deriv, '_pickle_reference'):
deriv._pickle_reference = 2 * self._pickle_reference[1:]
🧰 Tools
🪛 Ruff (0.14.7)

255-255: Loop control variable key not used within loop body

Rename unused key to _key

(B007)

🤖 Prompt for AI Agents
In polymath/extensions/pickler.py around lines 255 to 259, the loop variable
"key" is unused and should be renamed to "_key" to satisfy linters and make
intent clear; change the loop header from "for key, deriv in
self._derivs.items():" to "for _key, deriv in self._derivs.items():" (leave the
rest of the loop body unchanged).

Comment on lines +8 to +9
class QubeShaper:
"""Mix-in class providing operations for Qube objects."""
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider a more specific docstring.

The docstring "Mix-in class providing operations for Qube objects" is generic. Consider describing the reshaping/axis manipulation functionality specifically, similar to how QubeShrinker was updated.

 class QubeShaper:
-    """Mix-in class providing operations for Qube objects."""
+    """Mix-in class providing reshape and axis manipulation operations for Qube objects."""
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
class QubeShaper:
"""Mix-in class providing operations for Qube objects."""
class QubeShaper:
"""Mix-in class providing reshape and axis manipulation operations for Qube objects."""
🤖 Prompt for AI Agents
In polymath/extensions/shaper.py around lines 8 to 9, the class docstring is too
generic; replace it with a specific description of the reshaping and
axis-manipulation responsibilities of QubeShaper (e.g., explain it provides
methods to reshape, transpose, insert/remove/reorder axes, and preserve
coordinate metadata), matching the level of detail used in QubeShrinker so
readers and docs understand its exact purpose and behavior.

Comment on lines +8 to +9
class QubeTVL:
"""Mix-in class providing operations for Qube objects."""
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider a more specific docstring.

The docstring is generic. Consider describing the three-valued logic functionality specifically.

 class QubeTVL:
-    """Mix-in class providing operations for Qube objects."""
+    """Mix-in class providing three-valued logic operations for Qube objects."""
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
class QubeTVL:
"""Mix-in class providing operations for Qube objects."""
class QubeTVL:
"""Mix-in class providing three-valued logic operations for Qube objects."""
🤖 Prompt for AI Agents
In polymath/extensions/tvl.py around lines 8 to 9, the class docstring is
generic; update it to explicitly describe the three-valued logic functionality
this mix-in provides (e.g., the three truth values it models, semantics for
unknown/partial values, and how Qube objects should use the mix-in). Mention the
main public methods/behaviour (inputs, return values, and side effects), any
invariants or expectations on Qube attributes, and a short usage example or note
about interoperability with boolean operations.

Comment on lines +10 to +11
class QubeVectorOps:
"""Mix-in class providing operations for Qube objects."""
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Class docstring is too generic.

The docstring "Mix-in class providing operations for Qube objects" doesn't describe what operations. Consider something like "Mix-in class providing vector and aggregation operations (dot, cross, norm, mean, sum, etc.) for Qube objects."

🤖 Prompt for AI Agents
In polymath/extensions/vector_ops.py around lines 10 to 11, the class docstring
is too generic; replace it with a specific summary of what operations the mix-in
provides (e.g., vector and aggregation operations such as dot, cross, norm,
normalize, magnitude, mean, sum, min, max, and any broadcasting/shape
behaviors), mention the target type (Qube objects), and briefly note any
important behavior or exceptions (e.g., axis handling or in-place vs.
functional). Keep it concise, use imperative/descriptive tone suitable for API
docs, and follow existing project docstring style (one-line summary plus a short
elaboration if needed).

Comment on lines +134 to +144
for i in axis:
try:
_ = selections[i]
except IndexError:
raise IndexError(f'axis is out of range ({-self._ndims},{self._ndims}) in '
f'{type(self)}.{op}: {i}')

if selections[i]:
raise IndexError(f'duplicated axis in {type(self)}.{op}: {axis_for_show}')

def _check_axis(arg, axis, op):
"""Validate the axis as None, an int, or a tuple of ints.
selections[i] = True
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Use exception chaining for clearer tracebacks.

When re-raising exceptions inside an except block, use raise ... from err or raise ... from None to distinguish between errors in exception handling and the original error.

         try:
             _ = selections[i]
         except IndexError:
-            raise IndexError(f'axis is out of range ({-self._ndims},{self._ndims}) in '
-                             f'{type(self)}.{op}: {i}')
+            raise IndexError(f'axis is out of range ({-self._ndims},{self._ndims}) in '
+                             f'{type(self)}.{op}: {i}') from None
🧰 Tools
🪛 Ruff (0.14.7)

138-139: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


138-139: Avoid specifying long messages outside the exception class

(TRY003)


142-142: Avoid specifying long messages outside the exception class

(TRY003)

🤖 Prompt for AI Agents
In polymath/extensions/vector_ops.py around lines 134 to 144, the code catches
IndexError but re-raises a new IndexError without chaining; change the except
block to "except IndexError as err:" and re-raise the new IndexError using
"raise IndexError(... ) from err" so the original IndexError is preserved in the
traceback (leave the duplicated-axis check as-is).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants