|
1 | 1 | import numpy as np |
2 | | -from pytest import raises |
| 2 | +from pytest import raises, warns |
3 | 3 | from scipy.integrate import solve_ivp |
4 | 4 |
|
5 | 5 | from pydmd.bopdmd import BOPDMD |
6 | 6 |
|
7 | | - |
8 | 7 | def simulate_z(t_eval): |
9 | 8 | """ |
10 | 9 | Given a time vector t_eval = t1, t2, ..., evaluates and returns |
@@ -331,3 +330,258 @@ def make_imag(x): |
331 | 330 | bopdmd1 = BOPDMD(svd_rank=2, eig_constraints={"imag"}).fit(Z, t) |
332 | 331 | bopdmd2 = BOPDMD(svd_rank=2, eig_constraints=make_imag).fit(Z, t) |
333 | 332 | np.testing.assert_array_equal(bopdmd1.eigs, bopdmd2.eigs) |
| 333 | + |
| 334 | + |
| 335 | +def test_plot_mode_uq(): |
| 336 | + """ |
| 337 | + Test that a basic call to plot_mode_uq is successful. |
| 338 | + """ |
| 339 | + bopdmd = BOPDMD(svd_rank=2, num_trials=10, trial_size=0.8) |
| 340 | + bopdmd.fit(Z, t) |
| 341 | + bopdmd.plot_mode_uq() |
| 342 | + |
| 343 | + |
| 344 | +def test_plot_eig_uq(): |
| 345 | + """ |
| 346 | + Test that a basic call to plot_eig_uq is successful. |
| 347 | + """ |
| 348 | + bopdmd = BOPDMD(svd_rank=2, num_trials=10, trial_size=0.8) |
| 349 | + bopdmd.fit(Z, t) |
| 350 | + bopdmd.plot_eig_uq() |
| 351 | + |
| 352 | + |
| 353 | +def test_plot_error(): |
| 354 | + """ |
| 355 | + Test that UQ plotters fail if bagging wasn't used. |
| 356 | + """ |
| 357 | + bopdmd = BOPDMD(svd_rank=2, num_trials=0) |
| 358 | + bopdmd.fit(Z, t) |
| 359 | + |
| 360 | + with raises(ValueError): |
| 361 | + bopdmd.plot_mode_uq() |
| 362 | + |
| 363 | + with raises(ValueError): |
| 364 | + bopdmd.plot_eig_uq() |
| 365 | + |
| 366 | + |
| 367 | +def test_varpro_opts_warn(): |
| 368 | + """ |
| 369 | + Test that errors or warnings are correctly thrown if invalid |
| 370 | + or poorly-chosen variable projection parameters are given. |
| 371 | + The `tol` parameter is specifically tested. |
| 372 | + """ |
| 373 | + with raises(TypeError): |
| 374 | + bopdmd = BOPDMD(varpro_opts_dict={"tol": None}) |
| 375 | + bopdmd.fit(Z, t) |
| 376 | + |
| 377 | + with warns(): |
| 378 | + bopdmd = BOPDMD(varpro_opts_dict={"tol": -1.0}) |
| 379 | + bopdmd.fit(Z, t) |
| 380 | + |
| 381 | + with warns(): |
| 382 | + bopdmd = BOPDMD(varpro_opts_dict={"tol": np.inf}) |
| 383 | + bopdmd.fit(Z, t) |
| 384 | + |
| 385 | + |
| 386 | +def test_varpro_opts_print(): |
| 387 | + """ |
| 388 | + Test that variable projection parameters can be printed after fitting. |
| 389 | + """ |
| 390 | + bopdmd = BOPDMD(svd_rank=2) |
| 391 | + |
| 392 | + with raises(ValueError): |
| 393 | + bopdmd.print_varpro_opts() |
| 394 | + |
| 395 | + bopdmd.fit(Z, t) |
| 396 | + bopdmd.print_varpro_opts() |
| 397 | + |
| 398 | + |
| 399 | +def test_verbose_outputs_1(): |
| 400 | + """ |
| 401 | + Test variable projection verbosity for optimized DMD. |
| 402 | + """ |
| 403 | + bopdmd = BOPDMD( |
| 404 | + svd_rank=2, |
| 405 | + num_trials=0, |
| 406 | + varpro_opts_dict={"verbose": True}, |
| 407 | + ) |
| 408 | + bopdmd.fit(Z, t) |
| 409 | + |
| 410 | + |
| 411 | +def test_verbose_outputs_2(): |
| 412 | + """ |
| 413 | + Test variable projection verbosity for BOP-DMD. |
| 414 | + """ |
| 415 | + bopdmd = BOPDMD( |
| 416 | + svd_rank=2, |
| 417 | + num_trials=10, |
| 418 | + trial_size=0.8, |
| 419 | + varpro_opts_dict={"verbose": True}, |
| 420 | + ) |
| 421 | + bopdmd.fit(Z, t) |
| 422 | + |
| 423 | + |
| 424 | +def test_verbose_outputs_3(): |
| 425 | + """ |
| 426 | + Test variable projection verbosity for BOP-DMD without bad bags. |
| 427 | + """ |
| 428 | + bopdmd = BOPDMD( |
| 429 | + svd_rank=2, |
| 430 | + num_trials=10, |
| 431 | + trial_size=0.8, |
| 432 | + varpro_opts_dict={"verbose": True, "tol": 1.0}, |
| 433 | + remove_bad_bags=True, |
| 434 | + ) |
| 435 | + bopdmd.fit(Z, t) |
| 436 | + |
| 437 | + |
| 438 | +def test_bag_int(): |
| 439 | + """ |
| 440 | + Test that trial_size can be a valid integer value. |
| 441 | + """ |
| 442 | + bopdmd = BOPDMD(svd_rank=2, num_trials=10, trial_size=3200) |
| 443 | + bopdmd.fit(Z, t) |
| 444 | + |
| 445 | + |
| 446 | +def test_bag_error(): |
| 447 | + """ |
| 448 | + Test that errors are thrown if invalid bagging parameters are given. |
| 449 | + """ |
| 450 | + # Error should raise if trial_size isn't a positive integer... |
| 451 | + with raises(ValueError): |
| 452 | + bopdmd = BOPDMD(svd_rank=2, num_trials=10, trial_size=-1) |
| 453 | + bopdmd.fit(Z, t) |
| 454 | + |
| 455 | + # ...or if it isn't a float in the range (0.0, 1.0). |
| 456 | + with raises(ValueError): |
| 457 | + bopdmd = BOPDMD(svd_rank=2, num_trials=10, trial_size=2.0) |
| 458 | + bopdmd.fit(Z, t) |
| 459 | + |
| 460 | + # Error should raise if the requested trial size is too big... |
| 461 | + with raises(ValueError): |
| 462 | + bopdmd = BOPDMD(svd_rank=2, num_trials=10, trial_size=4001) |
| 463 | + bopdmd.fit(Z, t) |
| 464 | + |
| 465 | + # ...or if the requested trial size is too small. |
| 466 | + with raises(ValueError): |
| 467 | + bopdmd = BOPDMD(svd_rank=2, num_trials=10, trial_size=1e-6) |
| 468 | + bopdmd.fit(Z, t) |
| 469 | + |
| 470 | + |
| 471 | +def test_mode_prox(): |
| 472 | + """ |
| 473 | + Test that the mode_prox function is applied as expected. |
| 474 | + """ |
| 475 | + def dummy_prox(X): |
| 476 | + return X + 1.0 |
| 477 | + |
| 478 | + # Test that the function is applied in the use_proj=False case. |
| 479 | + bopdmd = BOPDMD(svd_rank=2, use_proj=False, mode_prox=dummy_prox) |
| 480 | + bopdmd.fit(Z, t) |
| 481 | + |
| 482 | + # Test that the function is applied in the use_proj=True case. |
| 483 | + bopdmd = BOPDMD(svd_rank=2, use_proj=True, mode_prox=dummy_prox) |
| 484 | + bopdmd.fit(Z, t) |
| 485 | + |
| 486 | + # Compare use_proj=True results to the no prox case. |
| 487 | + bopdmd_noprox = BOPDMD(svd_rank=2, use_proj=True) |
| 488 | + bopdmd_noprox.fit(Z, t) |
| 489 | + np.testing.assert_allclose(bopdmd.modes, dummy_prox(bopdmd_noprox.modes)) |
| 490 | + |
| 491 | + |
| 492 | +def test_init_alpha_initializer(): |
| 493 | + """ |
| 494 | + Test that the eigenvalues are accurately initialized by default. |
| 495 | + """ |
| 496 | + bopdmd = BOPDMD(svd_rank=2) |
| 497 | + |
| 498 | + # Initial eigs shouldn't be defined yet. |
| 499 | + with raises(RuntimeError): |
| 500 | + _ = bopdmd.init_alpha |
| 501 | + |
| 502 | + # After fitting, the initial eigs used should be fairly accurate. |
| 503 | + bopdmd.fit(Z, t) |
| 504 | + np.testing.assert_allclose( |
| 505 | + sort_imag(bopdmd.init_alpha), |
| 506 | + expected_eigs, |
| 507 | + rtol=0.01, |
| 508 | + ) |
| 509 | + |
| 510 | + |
| 511 | +def test_proj_basis_initializer(): |
| 512 | + """ |
| 513 | + Test that the projection basis is accurately initialized by default. |
| 514 | + """ |
| 515 | + bopdmd = BOPDMD(svd_rank=2) |
| 516 | + |
| 517 | + # Projection basis shouldn't be defined yet. |
| 518 | + with raises(RuntimeError): |
| 519 | + _ = bopdmd.proj_basis |
| 520 | + |
| 521 | + # After fitting, the projection basis used should be accurate. |
| 522 | + bopdmd.fit(Z, t) |
| 523 | + np.testing.assert_array_equal(bopdmd.proj_basis, np.linalg.svd(Z)[0]) |
| 524 | + |
| 525 | + |
| 526 | +def test_svd_rank(): |
| 527 | + """ |
| 528 | + Test svd_rank getter and setter. |
| 529 | + """ |
| 530 | + bopdmd = BOPDMD(svd_rank=2) |
| 531 | + assert bopdmd.svd_rank == 2 |
| 532 | + |
| 533 | + bopdmd.svd_rank = 0 |
| 534 | + assert bopdmd.svd_rank == 0 |
| 535 | + |
| 536 | + |
| 537 | +def test_init_alpha(): |
| 538 | + """ |
| 539 | + Test init_alpha getter and setter. |
| 540 | + """ |
| 541 | + dummy_eigs = 2.0 * expected_eigs |
| 542 | + bopdmd = BOPDMD(init_alpha=dummy_eigs) |
| 543 | + np.testing.assert_array_equal(bopdmd.init_alpha, dummy_eigs) |
| 544 | + |
| 545 | + bopdmd.init_alpha = 2.0 * dummy_eigs |
| 546 | + np.testing.assert_array_equal(bopdmd.init_alpha, 2.0 * dummy_eigs) |
| 547 | + |
| 548 | + |
| 549 | +def test_proj_basis(): |
| 550 | + """ |
| 551 | + Test proj_basis getter and setter. |
| 552 | + """ |
| 553 | + dummy_basis = 2.0 * np.linalg.svd(Z)[0] |
| 554 | + bopdmd = BOPDMD(proj_basis=dummy_basis) |
| 555 | + np.testing.assert_array_equal(bopdmd.proj_basis, dummy_basis) |
| 556 | + |
| 557 | + bopdmd.proj_basis = 2.0 * dummy_basis |
| 558 | + np.testing.assert_array_equal(bopdmd.proj_basis, 2.0 * dummy_basis) |
| 559 | + |
| 560 | + |
| 561 | +def test_default_initializers(): |
| 562 | + """ |
| 563 | + Test that default initialization does not impact custom inputs. |
| 564 | + """ |
| 565 | + # Define the dummy init_alpha and proj_basis to NOT be the defaults. |
| 566 | + dummy_eigs = 2.0 * expected_eigs |
| 567 | + dummy_basis = 2.0 * np.linalg.svd(Z)[0] |
| 568 | + bopdmd = BOPDMD( |
| 569 | + svd_rank=2, |
| 570 | + init_alpha=dummy_eigs, |
| 571 | + proj_basis=dummy_basis, |
| 572 | + ) |
| 573 | + bopdmd.fit(Z, t) |
| 574 | + np.testing.assert_array_equal(bopdmd.init_alpha, dummy_eigs) |
| 575 | + np.testing.assert_array_equal(bopdmd.proj_basis, dummy_basis) |
| 576 | + |
| 577 | + |
| 578 | +def test_std_shape(): |
| 579 | + """ |
| 580 | + Test the shapes of the standard deviation attributes. |
| 581 | + """ |
| 582 | + bopdmd = BOPDMD(svd_rank=2, num_trials=10, trial_size=0.8) |
| 583 | + bopdmd.fit(Z, t) |
| 584 | + |
| 585 | + assert bopdmd.eigenvalues_std.shape == bopdmd.eigs.shape |
| 586 | + assert bopdmd.modes_std.shape == bopdmd.modes.shape |
| 587 | + assert bopdmd.amplitudes_std.shape == bopdmd.amplitudes.shape |
0 commit comments