|
10 | 10 | gcv, |
11 | 11 | RobustWeights, |
12 | 12 | warning, |
13 | | - dctND |
| 13 | + dctND, |
| 14 | + InitialGuess, |
| 15 | + peaks |
14 | 16 | ) |
15 | 17 |
|
16 | 18 | def test_smoothn_basic(): |
@@ -442,3 +444,168 @@ def test_warning_function(): |
442 | 444 | output = captured_output.getvalue() |
443 | 445 | assert "Warning type" in output |
444 | 446 | assert "Warning message" in output |
| 447 | + |
| 448 | +def test_smoothn_with_negative_weights(): |
| 449 | + """Test smoothn with negative weights (should raise a warning)""" |
| 450 | + # Create a noisy 1D signal |
| 451 | + x = np.linspace(0, 10, 100) |
| 452 | + y_true = np.sin(x) |
| 453 | + noise = np.random.normal(0, 0.1, x.size) |
| 454 | + y_noisy = y_true + noise |
| 455 | + |
| 456 | + # Create weights with some negative values |
| 457 | + W = np.ones_like(y_noisy) |
| 458 | + W[40:60] = -1.0 # Negative weights in the middle |
| 459 | + |
| 460 | + # The function should raise a ValueError with negative weights |
| 461 | + try: |
| 462 | + y_smooth, _, _, Wtot = smoothn(y_noisy, W=W) |
| 463 | + # If we get here, the test should fail |
| 464 | + assert False, "smoothn should raise ValueError with negative weights" |
| 465 | + except ValueError as e: |
| 466 | + # Check that the error message is correct |
| 467 | + assert "Weights must all be >=0" in str(e) |
| 468 | + |
| 469 | + # Now try with zero weights instead |
| 470 | + W[40:60] = 0.0 |
| 471 | + y_smooth, _, _, Wtot = smoothn(y_noisy, W=W) |
| 472 | + |
| 473 | + # Check that zero weights were preserved |
| 474 | + assert np.all(Wtot[40:60] == 0) |
| 475 | + |
| 476 | + # Check that the smoothed signal is still reasonable |
| 477 | + assert np.mean((y_smooth - y_true)**2) < np.mean((y_noisy - y_true)**2) |
| 478 | + |
| 479 | +def test_initial_guess_function(): |
| 480 | + """Test the InitialGuess function""" |
| 481 | + # Create a simple test case |
| 482 | + y = np.array([1.0, 2.0, 3.0, 4.0, 5.0]) |
| 483 | + |
| 484 | + # Test with default z0=None |
| 485 | + z = InitialGuess(y, None) |
| 486 | + assert np.array_equal(z, y) |
| 487 | + |
| 488 | + # Test with provided z0 |
| 489 | + z0 = np.array([0.5, 1.5, 2.5, 3.5, 4.5]) |
| 490 | + z = InitialGuess(y, z0) |
| 491 | + assert np.array_equal(z, z0) |
| 492 | + |
| 493 | + # Test with z0 of wrong size |
| 494 | + z0_wrong_size = np.array([0.5, 1.5, 2.5]) |
| 495 | + z = InitialGuess(y, z0_wrong_size) |
| 496 | + assert np.array_equal(z, y) |
| 497 | + |
| 498 | + # Test with masked array |
| 499 | + mask = np.zeros_like(y, dtype=bool) |
| 500 | + mask[2] = True |
| 501 | + y_masked = ma.array(y, mask=mask) |
| 502 | + z = InitialGuess(y_masked, None) |
| 503 | + assert isinstance(z, ma.MaskedArray) |
| 504 | + assert np.array_equal(z.mask, mask) |
| 505 | + |
| 506 | +def test_peaks_function(): |
| 507 | + """Test the peaks function""" |
| 508 | + # Create a signal with peaks |
| 509 | + x = np.linspace(0, 10, 100) |
| 510 | + y = np.sin(x) + 0.5 * np.sin(2*x) |
| 511 | + |
| 512 | + # Find peaks |
| 513 | + idx = peaks(y) |
| 514 | + |
| 515 | + # Check that peaks were found |
| 516 | + assert len(idx) > 0 |
| 517 | + |
| 518 | + # Check that the identified points are actually peaks |
| 519 | + for i in idx: |
| 520 | + if i > 0 and i < len(y) - 1: |
| 521 | + assert y[i] > y[i-1] and y[i] > y[i+1] |
| 522 | + |
| 523 | +def test_smoothn_3d(): |
| 524 | + """Test smoothn with 3D data""" |
| 525 | + # Create a noisy 3D signal |
| 526 | + x, y, z = np.meshgrid( |
| 527 | + np.linspace(0, 1, 10), |
| 528 | + np.linspace(0, 1, 10), |
| 529 | + np.linspace(0, 1, 10) |
| 530 | + ) |
| 531 | + data_true = np.sin(2*np.pi*x) * np.cos(2*np.pi*y) * np.sin(2*np.pi*z) |
| 532 | + noise = np.random.normal(0, 0.1, data_true.shape) |
| 533 | + data_noisy = data_true + noise |
| 534 | + |
| 535 | + # Apply smoothn |
| 536 | + data_smooth, s, exitflag, _ = smoothn(data_noisy) |
| 537 | + |
| 538 | + # Check that the smoothed signal is closer to the true signal than the noisy one |
| 539 | + assert np.mean((data_smooth - data_true)**2) < np.mean((data_noisy - data_true)**2) |
| 540 | + |
| 541 | + # Check that s is positive (smoothing parameter) |
| 542 | + assert s > 0 |
| 543 | + |
| 544 | + # Check that exitflag is 1 (convergence) |
| 545 | + assert exitflag == 1 |
| 546 | + |
| 547 | +def test_smoothn_with_verbose(): |
| 548 | + """Test smoothn with verbose output""" |
| 549 | + # Create a noisy 1D signal |
| 550 | + x = np.linspace(0, 10, 100) |
| 551 | + y_true = np.sin(x) |
| 552 | + noise = np.random.normal(0, 0.1, x.size) |
| 553 | + y_noisy = y_true + noise |
| 554 | + |
| 555 | + # Capture stdout to check for verbose output |
| 556 | + import io |
| 557 | + import sys |
| 558 | + captured_output = io.StringIO() |
| 559 | + sys.stdout = captured_output |
| 560 | + |
| 561 | + # Apply smoothn with verbose=True |
| 562 | + y_smooth, _, _, _ = smoothn(y_noisy, verbose=True) |
| 563 | + |
| 564 | + # Restore stdout |
| 565 | + sys.stdout = sys.__stdout__ |
| 566 | + |
| 567 | + # Check that verbose output was produced |
| 568 | + output = captured_output.getvalue() |
| 569 | + assert "tol" in output.lower() or "nit" in output.lower() |
| 570 | + |
| 571 | + # Check that the smoothed signal is reasonable |
| 572 | + assert np.mean((y_smooth - y_true)**2) < np.mean((y_noisy - y_true)**2) |
| 573 | + |
| 574 | +def test_smoothn_with_max_iter(): |
| 575 | + """Test smoothn with maximum iterations""" |
| 576 | + # Create a noisy 1D signal |
| 577 | + x = np.linspace(0, 10, 100) |
| 578 | + y_true = np.sin(x) |
| 579 | + noise = np.random.normal(0, 0.1, x.size) |
| 580 | + y_noisy = y_true + noise |
| 581 | + |
| 582 | + # Apply smoothn with very low MaxIter |
| 583 | + y_smooth, _, exitflag, _ = smoothn(y_noisy, isrobust=True, MaxIter=1) |
| 584 | + |
| 585 | + # Check that exitflag is 0 (max iterations reached) |
| 586 | + assert exitflag == 0 |
| 587 | + |
| 588 | + # Check that the smoothed signal is still reasonable |
| 589 | + assert np.mean((y_smooth - y_true)**2) < np.mean((y_noisy - y_true)**2) |
| 590 | + |
| 591 | +def test_smoothn_with_tolerance(): |
| 592 | + """Test smoothn with different tolerance values""" |
| 593 | + # Create a noisy 1D signal |
| 594 | + x = np.linspace(0, 10, 100) |
| 595 | + y_true = np.sin(x) |
| 596 | + noise = np.random.normal(0, 0.1, x.size) |
| 597 | + y_noisy = y_true + noise |
| 598 | + |
| 599 | + # Apply smoothn with high tolerance (should converge quickly) |
| 600 | + y_smooth_high_tol, _, exitflag1, _ = smoothn(y_noisy, isrobust=True, TolZ=0.5) |
| 601 | + |
| 602 | + # Apply smoothn with low tolerance (should take more iterations) |
| 603 | + y_smooth_low_tol, _, exitflag2, _ = smoothn(y_noisy, isrobust=True, TolZ=1e-6) |
| 604 | + |
| 605 | + # Both should converge |
| 606 | + assert exitflag1 == 1 |
| 607 | + assert exitflag2 == 1 |
| 608 | + |
| 609 | + # Check that both smoothed signals are reasonable |
| 610 | + assert np.mean((y_smooth_high_tol - y_true)**2) < np.mean((y_noisy - y_true)**2) |
| 611 | + assert np.mean((y_smooth_low_tol - y_true)**2) < np.mean((y_noisy - y_true)**2) |
0 commit comments