stumpy-dev/stumpy

`ValueError: operands could not be broadcast together` when using `custom_iac` in `stumpy.fluss` (v1.13.0)

Closed this issue · 1 comments

snvv commented

Hello and thank you for your excellent package.

When providing a pre-computed Inverse Average Complexity (IAC) profile via the custom_iac parameter to stumpy.fluss, a ValueError related to shape incompatibility is raised within the internal _cac function. This occurs even when the custom_iac array is confirmed to have the correct expected shape (len(I) - L + 1,).

According to the documentation and intended use, stumpy.fluss should accept custom_iac of shape (len(I) - L + 1,) and use it directly in the CAC calculation.

Steps to Reproduce

  1. Import necessary libraries.
  2. Create a synthetic time series I.
  3. Define window size L and the number of regimes n_regimes.
  4. Calculate the expected profile length profile_len = len(I) - L + 1.
  5. Create a simple custom_iac array of shape (profile_len,).
  6. Call stumpy.fluss passing the custom_iac.
import numpy as np
import stumpy
import matplotlib.pyplot as plt # Included in case plotting code is run afterwards

# 1. Create a synthetic time series (I) with different regimes
ts_len1 = 200
ts_len2 = 200
ts_len3 = 200
I = np.concatenate([
    5 * np.sin(np.linspace(0, 10 * np.pi, ts_len1)),
    10 + 0.5 * np.random.randn(ts_len2),
    5 * np.random.randn(ts_len3)
])
N = len(I) # N = 600

# 2. Define window size (L) and number of regimes to find
L = 50
n_regimes = 3

# 3. Calculate the required length of the profile (N - L + 1)
profile_len = N - L + 1 # profile_len = 600 - 50 + 1 = 551

# 4. Create a SIMPLE custom_iac profile of the correct shape
# Using a simple constant IAC to eliminate complexity in IAC creation logic as the cause
custom_iac = np.ones(profile_len) * 0.5 # Should be shape (551,)

# Add print statements to verify shapes immediately before the call
print(f"Shape of original time series I: {I.shape}")
print(f"Calculated expected profile length (N - L + 1): {profile_len}")
print(f"Shape of the created custom_iac: {custom_iac.shape}") # Expected: (551,)

# 5. Run stumpy.fluss with the custom_iac
try:
    # Note: stumpy.fluss returns (regimes, iac, cac) in v1.13.0
    regimes, iac_output, cac = stumpy.fluss(I, L, n_regimes, custom_iac=custom_iac)

    print("stumpy.fluss completed successfully (unexpected based on error seen before).")
    print(f"Identified regime change points (indices in I): {regimes}")

    # Optional: Add plotting code here if it runs without the error

except ValueError as e:
    print(f"\nA ValueError occurred: {e}")
    print("This error occurred inside stumpy.floss._cac during the CAC calculation.")
    # Re-print actual shape just in case something changed (highly unlikely)
    print(f"Actual custom_iac shape *after* error caught: {custom_iac.shape}")

Observed Behavior

When running the code above, the following output and traceback are observed:

STUMPY version: 1.13.0
Shape of original time series I: (600,)
Calculated expected profile length (N - L + 1): 551
Shape of the created custom_iac: (551,)

A ValueError occurred: operands could not be broadcast together with shapes (600,) (551,)
This error occurred inside stumpy.floss._cac during the CAC calculation.
Actual custom_iac shape *after* error caught: (551,)

Followed by a traceback similar to this (line numbers might vary slightly but the core error location is within stumpy.floss._cac):

ValueError                                Traceback (most recent call last)
File "<ipython-input-...>", line XX, in <module>
    regimes, iac_output, cac = stumpy.fluss(I, L, n_regimes, custom_iac=custom_iac)

File "~/miniconda3/envs/stumpy_env/lib/python3.12/site-packages/stumpy/floss.py", line 300, in fluss
    cac = _cac(I, L, bidirectional=True, excl_factor=excl_factor, custom_iac=custom_iac)

File "~/miniconda3/envs/stumpy_env/lib/python3.12/site-packages/stumpy/floss.py", line 174, in _cac
    CAC[:] = AC / IAC

ValueError: operands could not be broadcast together with shapes (600,) (551,)

The error occurs at CAC[:] = AC / IAC within _cac. The error message indicates shapes (600,) and (551,). Since CAC and AC are internally computed based on N and L, they are expected to have shape (551,). The presence of (600,) suggests that the variable representing the IAC profile within _cac is incorrectly inheriting the shape of the original time series I (600) instead of using the provided custom_iac which has the correct shape (551). This contradicts the print statements verifying the custom_iac shape before the function call.

Expected Behavior

The stumpy.fluss function should successfully run when provided with a custom_iac array that has the shape (len(I) - L + 1,), without raising a ValueError. The custom_iac should be used directly in the calculation CAC = AC / IAC where AC, IAC, and CAC all have the shape (len(I) - L + 1,).


Regards
snvv

@snvv Thank you for your question and welcome to the STUMPY community.

Create a synthetic time series I.

So, throughout the STUMPY documentation, we generally reserve T to represent our time series and, instead, I would represent the the resultant matrix profile indices. Note that the length of T is often denoted by n = len(T) and the window size is represented as m. Thus, the length of I (the matrix profile indices) would be n - m + 1.

Thus, this should work:

import numpy as np
import stumpy
import matplotlib.pyplot as plt # Included in case plotting code is run afterwards

ts_len1 = 200
ts_len2 = 200
ts_len3 = 200
T = np.concatenate([
    5 * np.sin(np.linspace(0, 10 * np.pi, ts_len1)),
    10 + 0.5 * np.random.randn(ts_len2),
    5 * np.random.randn(ts_len3)
])
n = len(T) # N = 600

L = 50
n_regimes = 3

mp = stumpy.stump(T, L)
I = mp.I_  # This is the same as `I = mp[:, 1]`
profile_len = len(I)  # This is `n - L + 1`
custom_iac = np.ones(profile_len) * 0.5 # Should be shape (551,)

cac, regimes = stumpy.fluss(I, L, n_regimes, custom_iac=custom_iac)

Note: stumpy.fluss returns (regimes, iac, cac) in v1.13.0

I don't think this statement is true according to the fluss documentation for v1.13.0

Please feel free to ask any follow up questions or let me know if there's anything else that I could clarify.