From c018a4f2244ba39ce7053f07df05d28e075ef582 Mon Sep 17 00:00:00 2001 From: ISAS-Admin <57131193+ISAS-Admin@users.noreply.github.com> Date: Sat, 11 Jul 2026 22:19:03 +0000 Subject: [PATCH] [MegaLinter] Apply linters automatic fixes --- benchmarks/low_rank_hypertoroidal_fourier.py | 1 - .../basic/dp_extended_target_subclustering.py | 1 - .../basic/nonparametric_cardinality_priors.py | 30 +- .../multisensor_ddp_association.py | 11 +- scripts/check_public_api_registry.py | 2 +- src/pyrecest/__init__.py | 8 +- src/pyrecest/_backend/_common.py | 12 +- .../_shared_numpy/_rng_pkg/__init__.py | 2 +- src/pyrecest/_backend/capabilities.py | 3 +- .../_backend/capabilities/__init__.py | 7 +- src/pyrecest/_backend/jax/__init__.py | 1 + src/pyrecest/_backend/jax/fft.py | 5 +- src/pyrecest/_backend/jax/random/__init__.py | 4 +- src/pyrecest/_backend/pytorch/_common.py | 5 +- src/pyrecest/_backend/pytorch/_dtype.py | 15 +- .../_backend/pytorch/random/__init__.py | 8 +- src/pyrecest/_backend_runtime_patches.py | 38 +-- src/pyrecest/_backend_submodules.py | 4 +- src/pyrecest/_pytorch_dot_contract.py | 2 +- src/pyrecest/backend_support/__init__.py | 28 +- .../_jax_array_from_sparse_contract.py | 2 +- .../_jax_assignment_numpy_index_contract.py | 14 +- .../_jax_random_empty_contract.py | 32 ++- .../_pytorch_allclose_device_contract.py | 4 +- .../_pytorch_creation_shape_contract.py | 5 +- .../_pytorch_matmul_device_contract.py | 6 +- .../_pytorch_minmax_device_contract.py | 12 +- .../_pytorch_one_hot_scalar_contract.py | 1 - .../_pytorch_quantile_empty_batch_contract.py | 2 +- .../_pytorch_reduction_axis_contract.py | 21 +- .../_pytorch_searchsorted_sorter_contract.py | 6 +- .../_pytorch_sort_numpy_contract.py | 1 - .../_pytorch_split_index_contract.py | 3 +- .../_pytorch_stack_helpers_device_contract.py | 10 +- .../_pytorch_take_index_contract.py | 2 +- .../_pytorch_trapezoid_numpy_contract.py | 12 +- .../_random_uniform_empty_contract.py | 66 +++-- .../_torch_dtype_promotion_contract.py | 32 ++- .../__init__.py | 66 +++-- src/pyrecest/backend_tools.py | 4 +- src/pyrecest/calibration/time_offset.py | 268 +++++++++++++++--- src/pyrecest/cli.py | 9 +- src/pyrecest/distributions/__init__.py | 6 +- .../abstract_dirac_distribution.py | 4 +- .../distributions/abstract_mixture.py | 4 +- .../circle/circular_grid_distribution.py | 3 +- .../circle/piecewise_constant_distribution.py | 1 - .../circle/sine_skewed_distributions.py | 5 +- .../circle/wrapped_cauchy_distribution.py | 4 +- .../circle/wrapped_laplace_distribution.py | 3 +- .../hyperspherical_uniform_distribution.py | 4 +- .../von_mises_fisher_distribution.py | 9 +- .../distributions/hypertorus/_tensor_train.py | 36 ++- .../distributions/hypertorus/fejer.py | 82 ++++-- ...ejer_hypertoroidal_fourier_distribution.py | 85 ++++-- .../hypertoroidal_dirac_distribution.py | 4 +- .../hypertoroidal_uniform_distribution.py | 2 +- ...pertoroidal_wrapped_normal_distribution.py | 1 - ...rank_hypertoroidal_fourier_distribution.py | 55 +++- .../toroidal_vm_rivest_distribution.py | 23 +- .../nonperiodic/gaussian_mixture.py | 4 +- .../linear_box_particle_distribution.py | 4 +- .../nonperiodic/linear_dirac_distribution.py | 4 +- .../so3_product_dirac_distribution.py | 1 - ...3_product_tangent_gaussian_distribution.py | 4 +- src/pyrecest/evaluation/get_axis_label.py | 4 +- .../evaluation/get_distance_function.py | 8 +- src/pyrecest/evaluation/get_extract_mean.py | 4 +- src/pyrecest/evaluation/pareto.py | 3 +- src/pyrecest/evidence.py | 34 ++- .../dp_measurement_subclusters.py | 29 +- .../multisensor_ddp_association.py | 131 +++++++-- .../filters/adaptive_process_noise.py | 8 +- .../filters/association_hypotheses.py | 3 +- .../dirichlet_process_birth_tracker.py | 19 +- src/pyrecest/filters/fejer_identity_filter.py | 168 +++++++---- .../filters/gaussian_hypothesis_mixture.py | 4 +- .../filters/linear_update_planning.py | 4 +- .../low_rank_hypertoroidal_fourier_filter.py | 8 +- .../filters/measurement_reliability.py | 2 +- .../filters/mode_rbpf_manifold_ukf_tracker.py | 6 +- .../filters/multisensor_hdp_association.py | 8 +- .../filters/nonparametric_cardinality.py | 112 ++++++-- src/pyrecest/filters/survival_association.py | 142 ++++++++-- .../filters/survival_aware_association.py | 53 +++- src/pyrecest/filters/survival_aware_crp.py | 29 +- src/pyrecest/filters/update_diagnostics.py | 4 +- src/pyrecest/filters/wrapped_normal_filter.py | 4 +- .../_sampleable_transition_validation.py | 4 +- src/pyrecest/models/additive_noise.py | 4 +- src/pyrecest/models/validation.py | 22 +- src/pyrecest/smoothers/__init__.py | 10 +- .../hypertoroidal_fourier_smoother.py | 74 +++-- .../smoothers/hypertoroidal_grid_smoother.py | 104 +++++-- src/pyrecest/stability.py | 1 + src/pyrecest/tracking/audit_guards.py | 12 +- .../tracking/nonparametric_cardinality.py | 34 ++- .../residual_hypothesis_diagnostics.py | 8 +- src/pyrecest/utils/__init__.py | 12 +- .../utils/_point_set_registration_common.py | 17 +- src/pyrecest/utils/candidate_pruning.py | 4 +- src/pyrecest/utils/cost_matrix_adjustments.py | 12 +- src/pyrecest/utils/history_recorder.py | 1 - .../test_numpy_random_size_validation.py | 2 - .../test_numpy_random_uniform_validation.py | 1 - .../test_numpy_uniform_ragged_bounds.py | 1 - ...test_pytorch_comparison_device_contract.py | 7 +- .../test_pytorch_flip_axis_contract.py | 3 +- .../test_pytorch_probability_underflow.py | 4 +- tests/backend_support/conftest.py | 1 - .../test_common_dot_contract.py | 1 - .../test_diagonal_bool_axes.py | 1 - .../test_gaussian_scalar_logpdf_contract.py | 1 - .../test_jax_array_from_sparse_contract.py | 1 - .../test_jax_fft_array_length_contract.py | 4 +- .../test_jax_fftn_sequence_contract.py | 8 +- .../test_jax_one_hot_contract.py | 1 - .../test_jax_take_out_contract.py | 1 - ...test_pytorch_apply_along_axis_arguments.py | 1 - .../test_pytorch_arctan_contract.py | 1 - .../test_pytorch_argsort_axis_contract.py | 1 - ...st_pytorch_argsort_kind_stable_contract.py | 1 - ..._pytorch_array_equal_equal_nan_contract.py | 1 - ...test_pytorch_array_from_sparse_contract.py | 1 - ...pytorch_assignment_uint8_index_contract.py | 4 +- .../test_pytorch_binary_arraylike_contract.py | 1 - ...ytorch_broadcast_arrays_device_contract.py | 1 - .../test_pytorch_broadcast_to_contract.py | 8 +- .../test_pytorch_copy_contract.py | 1 - .../test_pytorch_copy_export_contract.py | 1 - ...test_pytorch_creation_temporal_contract.py | 20 +- .../test_pytorch_cross_device_contract.py | 3 +- ...test_pytorch_diagonal_numpy_scalar_args.py | 1 - .../test_pytorch_diff_contract.py | 1 - .../test_pytorch_dot_outer_device_contract.py | 1 - .../test_pytorch_dtype_alias_contract.py | 1 - .../test_pytorch_equality_device_contract.py | 1 - ...pytorch_fft_alias_numpy_arrays_contract.py | 4 +- .../test_pytorch_fft_scalar_axis_contract.py | 4 +- ...est_pytorch_gaussian_mixture_statistics.py | 1 - ...test_pytorch_isclose_equal_nan_contract.py | 8 +- ..._pytorch_linalg_logm_arraylike_contract.py | 1 - .../test_pytorch_linalg_logm_contract.py | 3 +- ...test_pytorch_linear_dirac_plot_contract.py | 1 - .../test_pytorch_log1p_contract.py | 1 - ...est_pytorch_logical_and_device_contract.py | 3 +- .../test_pytorch_matmul_device_contract.py | 1 - .../test_pytorch_matvec_device_contract.py | 1 - .../test_pytorch_minmax_device_contract.py | 1 - .../test_pytorch_non_native_dtype_contract.py | 1 - .../test_pytorch_one_hot_scalar_contract.py | 1 - .../test_pytorch_pad_contract.py | 1 - .../test_pytorch_pad_edge_contract.py | 1 - ...est_pytorch_randint_empty_size_contract.py | 1 - ...st_pytorch_reduction_axis_bool_contract.py | 14 +- ..._pytorch_reduction_axis_scalar_contract.py | 14 +- .../test_pytorch_roll_contract.py | 7 +- .../test_pytorch_rotation_stub_contract.py | 1 - .../test_pytorch_rotation_stub_methods.py | 1 - .../test_pytorch_scatter_add_contract.py | 3 +- ...st_pytorch_searchsorted_sorter_contract.py | 1 - .../test_pytorch_solve_sylvester_device.py | 9 +- .../test_pytorch_sort_axis_none_contract.py | 2 - .../test_pytorch_sort_axis_validation.py | 1 - .../test_pytorch_sort_facade_contract.py | 1 - .../test_pytorch_special_contract.py | 10 +- .../test_pytorch_take_index_contract.py | 1 - .../test_pytorch_tile_contract.py | 8 +- .../test_pytorch_where_device_contract.py | 1 - .../test_raw_pytorch_cumulative_contract.py | 3 +- .../test_uniform_empty_bounds_contract.py | 1 - .../test_temporal_object_validation.py | 4 +- .../test_time_offset_none_offset.py | 1 - ...est_time_offset_real_numeric_validation.py | 1 - .../test_time_offset_temporal_validation.py | 1 - ...test_abstract_mixture_sample_validation.py | 9 +- .../test_dirac_entropy_zero_weights.py | 1 - .../test_dirac_tensor_copy_contract.py | 4 +- tests/distributions/test_fejer_helpers.py | 25 +- tests/distributions/test_fejer_reduction.py | 10 +- .../test_fejer_shape_validation.py | 1 - .../test_gauss_von_mises_distribution.py | 6 +- ...gaussian_distribution_sample_validation.py | 5 +- ...ussian_sample_count_temporal_validation.py | 1 - ...ed_sine_skewed_wrapped_cauchy_stability.py | 1 - ...rtoroidal_dirac_moment_order_validation.py | 3 +- ...hypertoroidal_dirac_set_mean_validation.py | 5 +- .../test_hypertoroidal_grid_multiturn_wrap.py | 1 - .../test_hypertoroidal_tensor_train.py | 5 +- ...rtoroidal_tensor_train_entry_validation.py | 5 +- ...ypersphere_cart_prod_dirac_distribution.py | 1 - ...linear_box_particle_set_mean_validation.py | 4 +- .../test_linear_dirac_distribution.py | 4 +- tests/distributions/test_linear_mixture.py | 4 +- .../test_low_rank_fourier_complex_center.py | 5 +- ...rank_hypertoroidal_fourier_distribution.py | 57 +++- ...rank_hypertoroidal_fourier_scalar_shape.py | 9 +- .../test_mixture_weight_shape_validation.py | 5 +- ...est_piecewise_constant_input_validation.py | 3 +- ...cewise_constant_sample_count_validation.py | 1 - .../test_sine_skewed_scalar_validation.py | 1 - ...product_dirac_marginal_index_validation.py | 1 - .../test_toroidal_vm_rivest_large_kappa.py | 10 +- .../test_uniform_order_validation.py | 5 +- .../test_von_mises_fisher_large_kappa.py | 2 +- .../test_wrapped_cauchy_mean_validation.py | 1 - ...est_wrapped_exponential_rate_validation.py | 1 - ...test_wrapped_normal_cdf_wrap_validation.py | 1 - ...wrapped_normal_distribution_pytorch_cdf.py | 4 +- .../test_final_estimate_consistency.py | 5 +- .../test_flat_empty_measurements.py | 1 - .../test_get_extract_mean_mtt_flag.py | 1 - ..._iterate_configs_vector_parameter_shape.py | 18 +- .../test_pareto_boolean_constraints.py | 1 - .../test_pareto_front_numeric_eligibility.py | 5 +- .../test_scenario_generation_rng_state.py | 5 +- .../test_selection_score_validation.py | 1 - .../test_selection_temporal_validation.py | 1 - tests/evaluation/test_zero_measurements.py | 1 - .../test_dp_measurement_subclusters.py | 13 +- ...vs_event_likelihood_temporal_validation.py | 1 - .../test_multisensor_ddp_association.py | 44 ++- tests/filters/test_adaptive_process_noise.py | 4 +- tests/filters/test_association_hypotheses.py | 4 +- .../test_dirichlet_process_birth_tracker.py | 13 +- .../test_discrete_state_masked_emissions.py | 1 - tests/filters/test_fejer_identity_filter.py | 4 +- .../test_gaussian_hypothesis_mixture.py | 4 +- ...rical_ukf_arbitrary_noise_normalization.py | 1 - ...rtoroidal_particle_filter_numpy_scalars.py | 1 - ...ear_update_planning_temporal_validation.py | 1 - .../filters/test_linear_update_thresholds.py | 7 +- ...t_low_rank_hypertoroidal_fourier_filter.py | 33 ++- .../test_multisensor_hdp_association.py | 1 - .../filters/test_nonparametric_cardinality.py | 9 +- tests/filters/test_replay_grid_ess_gating.py | 5 +- .../test_se2_ukf_zero_norm_sigma_points.py | 1 - .../test_sequence_association_array_frames.py | 7 +- tests/filters/test_survival_association.py | 18 +- .../test_survival_aware_association.py | 4 +- tests/filters/test_survival_aware_crp.py | 11 +- ...survival_aware_crp_stable_normalization.py | 1 - .../test_track_manager_structural_cost.py | 1 - .../test_update_diagnostics_metadata.py | 1 - .../test_update_diagnostics_skipped_reason.py | 1 - ...apped_normal_filter_constant_likelihood.py | 1 - ...est_identity_gaussian_scalar_covariance.py | 1 - .../test_linear_gaussian_complex_inputs.py | 8 +- tests/models/test_linear_gaussian_models.py | 14 +- .../test_state_dim_inference_fallback.py | 4 +- ...est_transition_sample_count_unsupported.py | 4 +- tests/models/test_validation.py | 33 ++- ...test_support_points_covariance_symmetry.py | 1 - tests/smoothers/test_delayed_output.py | 1 - ...est_hypertoroidal_information_smoothers.py | 25 +- ...test_so3_chordal_mean_nonfinite_weights.py | 1 - tests/test_autograd_vmap_contract.py | 1 - tests/test_axis_labels.py | 4 +- tests/test_backend_set_diag_contract.py | 3 +- tests/test_backend_squeeze_axis_contract.py | 1 - tests/test_benchmark_regression_limits.py | 23 +- ..._candidate_pruning_configured_nan_costs.py | 1 - ...test_candidate_pruning_temporal_scalars.py | 1 - tests/test_cli_final_estimate_validation.py | 5 +- tests/test_common_reduction_axis_scalar.py | 1 - .../test_common_reduction_axis_validation.py | 1 - tests/test_common_size_axis_validation.py | 1 - ...test_cost_matrix_adjustment_diagnostics.py | 1 - ...t_cost_matrix_adjustment_nan_validation.py | 1 - ...t_matrix_adjustment_temporal_validation.py | 6 +- tests/test_cost_matrix_adjustments.py | 5 +- tests/test_eot_shape_sample_counts.py | 5 +- ...st_evaluate_for_file_measurement_counts.py | 4 +- tests/test_evaluation_reload.py | 1 - tests/test_evidence_bool_flag_conversion.py | 1 - tests/test_evidence_metadata_validation.py | 1 - tests/test_evidence_mode_validation.py | 1 - tests/test_evidence_support_invalid_values.py | 1 - tests/test_healpix_hopf_density_validation.py | 3 +- tests/test_history_recorder_unsigned_dtype.py | 5 +- tests/test_jax_fft_scalar_array_axes.py | 7 +- tests/test_jax_linalg_matrix_power.py | 1 - tests/test_metrics_order_validation.py | 1 - .../test_model_comparison_infinite_margin.py | 1 - ...est_multisession_scalar_cost_validation.py | 8 +- tests/test_numerics.py | 4 +- .../test_numerics_temporal_scalar_controls.py | 1 - ...int_set_registration_numeric_validation.py | 1 - tests/test_public_api_registry_script.py | 1 - tests/test_pytorch_close_missing_values.py | 4 +- .../test_pytorch_conj_array_like_contract.py | 1 - tests/test_pytorch_cross_contract.py | 3 +- tests/test_pytorch_isclose_device_contract.py | 1 - tests/test_pytorch_prod_aliases.py | 3 +- tests/test_scenario_input_validation.py | 1 - tests/test_shared_numpy_array_dtype_none.py | 1 - tests/test_sigma_points_complex_inputs.py | 1 - ...test_sigma_points_covariance_validation.py | 1 - tests/test_sylvester_shortcut_residual.py | 1 - ...ck_completion_text_candidate_validation.py | 4 +- ...st_track_latency_datetime_session_times.py | 1 - tests/tracking/test_audit_guards.py | 15 +- tests/tracking/test_hypothesis_replay.py | 4 +- ...ovation_diagnostics_temporal_validation.py | 8 +- .../test_nonparametric_cardinality.py | 5 +- .../test_assignment_murty_forced_prefix.py | 3 +- ..._logistic_association_scalar_prediction.py | 1 - 307 files changed, 2065 insertions(+), 1044 deletions(-) diff --git a/benchmarks/low_rank_hypertoroidal_fourier.py b/benchmarks/low_rank_hypertoroidal_fourier.py index a9c447db0..6f05b3f5d 100644 --- a/benchmarks/low_rank_hypertoroidal_fourier.py +++ b/benchmarks/low_rank_hypertoroidal_fourier.py @@ -10,7 +10,6 @@ from time import perf_counter import numpy as np - from pyrecest.distributions.hypertorus.hypertoroidal_fourier_distribution import ( HypertoroidalFourierDistribution, ) diff --git a/examples/basic/dp_extended_target_subclustering.py b/examples/basic/dp_extended_target_subclustering.py index c2c614ea3..5274081a4 100644 --- a/examples/basic/dp_extended_target_subclustering.py +++ b/examples/basic/dp_extended_target_subclustering.py @@ -7,7 +7,6 @@ """ import numpy as np - from pyrecest.experimental.dp_measurement_subclusters import ( DPMeasurementSubclusterConfig, fit_dp_measurement_subclusters, diff --git a/examples/basic/nonparametric_cardinality_priors.py b/examples/basic/nonparametric_cardinality_priors.py index f9f2a3c88..e9ba96f08 100644 --- a/examples/basic/nonparametric_cardinality_priors.py +++ b/examples/basic/nonparametric_cardinality_priors.py @@ -19,18 +19,36 @@ def main(): pitman_yor_prior = PitmanYorProcessCardinalityPrior(discount=0.5, strength=2.0) print("cluster sizes:", cluster_sizes) - print("DP assignment probabilities: ", dirichlet_prior.predictive_assignment_probabilities(cluster_sizes)) - print("PYP assignment probabilities:", pitman_yor_prior.predictive_assignment_probabilities(cluster_sizes)) - print("DP expected clusters after 50 observations: ", round(dirichlet_prior.expected_number_of_clusters(50), 3)) - print("PYP expected clusters after 50 observations:", round(pitman_yor_prior.expected_number_of_clusters(50), 3)) + print( + "DP assignment probabilities: ", + dirichlet_prior.predictive_assignment_probabilities(cluster_sizes), + ) + print( + "PYP assignment probabilities:", + pitman_yor_prior.predictive_assignment_probabilities(cluster_sizes), + ) + print( + "DP expected clusters after 50 observations: ", + round(dirichlet_prior.expected_number_of_clusters(50), 3), + ) + print( + "PYP expected clusters after 50 observations:", + round(pitman_yor_prior.expected_number_of_clusters(50), 3), + ) birth_probability = PitmanYorBirthProbability( discount=0.5, strength=1.0, base_birth_existence_probability=0.8, ) - print("first unassigned-measurement birth probability:", birth_probability(num_existing_components=0, num_new_births=0)) - print("second burst birth probability: ", birth_probability(num_existing_components=0, num_new_births=1)) + print( + "first unassigned-measurement birth probability:", + birth_probability(num_existing_components=0, num_new_births=0), + ) + print( + "second burst birth probability: ", + birth_probability(num_existing_components=0, num_new_births=1), + ) if __name__ == "__main__": diff --git a/examples/experimental/multisensor_ddp_association.py b/examples/experimental/multisensor_ddp_association.py index 6abb32762..05c0386cf 100644 --- a/examples/experimental/multisensor_ddp_association.py +++ b/examples/experimental/multisensor_ddp_association.py @@ -3,8 +3,10 @@ from __future__ import annotations import numpy as np - -from pyrecest.experimental import SensorAssociationBlock, multisensor_ddp_association_update +from pyrecest.experimental import ( + SensorAssociationBlock, + multisensor_ddp_association_update, +) def main() -> None: @@ -37,7 +39,10 @@ def main() -> None: for sensor_id, posterior in result.sensor_posteriors.items(): print(sensor_id, posterior.hard_assignments) - print("updated target weights:", dict(zip(target_labels, result.updated_target_weights, strict=True))) + print( + "updated target weights:", + dict(zip(target_labels, result.updated_target_weights, strict=True)), + ) print("updated birth weight:", result.updated_birth_weight) print("expected clutter count:", result.expected_clutter_count) diff --git a/scripts/check_public_api_registry.py b/scripts/check_public_api_registry.py index b11e75c6f..8b219c9bf 100644 --- a/scripts/check_public_api_registry.py +++ b/scripts/check_public_api_registry.py @@ -164,4 +164,4 @@ def main(argv: list[str] | None = None) -> int: if __name__ == "__main__": - raise SystemExit(main()) \ No newline at end of file + raise SystemExit(main()) diff --git a/src/pyrecest/__init__.py b/src/pyrecest/__init__.py index 72c8056df..24060ca70 100644 --- a/src/pyrecest/__init__.py +++ b/src/pyrecest/__init__.py @@ -465,7 +465,9 @@ def _patch_raw_pytorch_cumulative_facade() -> None: try: import pyrecest._backend.pytorch as pytorch_backend # pylint: disable=import-outside-toplevel - from pyrecest._backend_submodules import _copy_result_to_out # pylint: disable=import-outside-toplevel + from pyrecest._backend_submodules import ( # pylint: disable=import-outside-toplevel + _copy_result_to_out, + ) except ModuleNotFoundError: # pragma: no cover - only relevant without PyTorch return @@ -486,7 +488,9 @@ def wrapped_cumulative(x, axis=None, dtype=None, out=None): import pyrecest.backend as backend # pylint: disable=import-outside-toplevel - selected_backend_is_pytorch = getattr(backend, "__backend_name__", None) == "pytorch" + selected_backend_is_pytorch = ( + getattr(backend, "__backend_name__", None) == "pytorch" + ) for helper_name in ("cumsum", "cumprod"): cumulative = getattr(pytorch_backend, helper_name, None) if cumulative is None: diff --git a/src/pyrecest/_backend/_common.py b/src/pyrecest/_backend/_common.py index addcd3de3..b7b0a7a5d 100644 --- a/src/pyrecest/_backend/_common.py +++ b/src/pyrecest/_backend/_common.py @@ -245,7 +245,9 @@ def _torch_cross(a, b, torch, *, axisa=-1, axisb=-1, axisc=-1, axis=None): a_dim = a.shape[-1] b_dim = b.shape[-1] if a_dim not in (2, 3) or b_dim not in (2, 3): - raise ValueError("incompatible dimensions for cross product (dimension must be 2 or 3)") + raise ValueError( + "incompatible dimensions for cross product (dimension must be 2 or 3)" + ) a0 = a[..., 0] a1 = a[..., 1] @@ -345,9 +347,13 @@ def cross(a, b, axisa=-1, axisb=-1, axisc=-1, axis=None): if torch_pair is not None: a, b = torch_pair torch = _torch_module_for_values(a, b) - return _torch_cross(a, b, torch, axisa=axisa, axisb=axisb, axisc=axisc, axis=axis) + return _torch_cross( + a, b, torch, axisa=axisa, axisb=axisb, axisc=axisc, axis=axis + ) - return _np.cross(_np.asarray(a), _np.asarray(b), axisa=axisa, axisb=axisb, axisc=axisc, axis=axis) + return _np.cross( + _np.asarray(a), _np.asarray(b), axisa=axisa, axisb=axisb, axisc=axisc, axis=axis + ) def matvec(matrix, vector): diff --git a/src/pyrecest/_backend/_shared_numpy/_rng_pkg/__init__.py b/src/pyrecest/_backend/_shared_numpy/_rng_pkg/__init__.py index bdffc0a86..31095fd8a 100644 --- a/src/pyrecest/_backend/_shared_numpy/_rng_pkg/__init__.py +++ b/src/pyrecest/_backend/_shared_numpy/_rng_pkg/__init__.py @@ -1,8 +1,8 @@ """Compatibility package for shared NumPy RNG helpers.""" +import sys as _sys from importlib import util as _importlib_util from pathlib import Path as _Path -import sys as _sys import numpy as _np diff --git a/src/pyrecest/_backend/capabilities.py b/src/pyrecest/_backend/capabilities.py index 2982ab7e1..319f4db86 100644 --- a/src/pyrecest/_backend/capabilities.py +++ b/src/pyrecest/_backend/capabilities.py @@ -389,7 +389,8 @@ def get_api_backend_support(api_name: str) -> dict[str, str]: def iter_api_backend_capabilities() -> tuple[tuple[str, dict[str, str]], ...]: """Return public API backend support rows in a stable order.""" return tuple( - (api_name, dict(row)) for api_name, row in sorted(API_BACKEND_CAPABILITIES.items()) + (api_name, dict(row)) + for api_name, row in sorted(API_BACKEND_CAPABILITIES.items()) ) diff --git a/src/pyrecest/_backend/capabilities/__init__.py b/src/pyrecest/_backend/capabilities/__init__.py index 9fa819159..2945fe08d 100644 --- a/src/pyrecest/_backend/capabilities/__init__.py +++ b/src/pyrecest/_backend/capabilities/__init__.py @@ -74,7 +74,9 @@ def _patch_pytorch_dot_outer_device_contract() -> None: helper_names = ("dot", "outer") if all( - getattr(getattr(raw_pytorch, helper_name, None), "_pyrecest_device_contract", False) + getattr( + getattr(raw_pytorch, helper_name, None), "_pyrecest_device_contract", False + ) for helper_name in helper_names ): if getattr(backend, "__backend_name__", None) == "pytorch": @@ -157,7 +159,8 @@ def get_api_backend_support(api_name: str) -> dict[str, str]: def iter_api_backend_capabilities() -> tuple[tuple[str, dict[str, str]], ...]: """Return public API backend support rows in a stable order.""" return tuple( - (api_name, dict(row)) for api_name, row in sorted(API_BACKEND_CAPABILITIES.items()) + (api_name, dict(row)) + for api_name, row in sorted(API_BACKEND_CAPABILITIES.items()) ) diff --git a/src/pyrecest/_backend/jax/__init__.py b/src/pyrecest/_backend/jax/__init__.py index 6a1b3547f..214c7a990 100644 --- a/src/pyrecest/_backend/jax/__init__.py +++ b/src/pyrecest/_backend/jax/__init__.py @@ -238,6 +238,7 @@ def cov( def diagonal(a, offset=0, axis1=0, axis2=1): return _jnp.diagonal(_jnp.asarray(a), offset=offset, axis1=axis1, axis2=axis2) + def squeeze(a, axis=None): return _jnp.squeeze(_jnp.asarray(a), axis=axis) diff --git a/src/pyrecest/_backend/jax/fft.py b/src/pyrecest/_backend/jax/fft.py index e0e00bcec..f1b6b87cc 100644 --- a/src/pyrecest/_backend/jax/fft.py +++ b/src/pyrecest/_backend/jax/fft.py @@ -6,7 +6,6 @@ import numpy as _np from jax.numpy import fft as _fft - _BOOLEAN_FFT_AXIS_ERROR = "axis must be an integer, not boolean" _BOOLEAN_FFT_LENGTH_ERROR = "n must be an integer length, not boolean" _FFT_SHAPE_SEQUENCE_ERROR = "s must be None or a sequence of integer lengths" @@ -82,7 +81,9 @@ def _normalize_fft_sequence_item(value, boolean_error, integer_error): raise TypeError(integer_error) from exc -def _normalize_fft_integer_sequence(value, sequence_error, boolean_error, integer_error): +def _normalize_fft_integer_sequence( + value, sequence_error, boolean_error, integer_error +): """Normalize NumPy/JAX scalar-array entries inside FFT integer sequences.""" if value is None: return None diff --git a/src/pyrecest/_backend/jax/random/__init__.py b/src/pyrecest/_backend/jax/random/__init__.py index c10e269ff..afd2de629 100644 --- a/src/pyrecest/_backend/jax/random/__init__.py +++ b/src/pyrecest/_backend/jax/random/__init__.py @@ -10,7 +10,9 @@ _LEGACY_MODULE_NAME = __name__ + "._legacy" _LEGACY_PATH = _Path(__file__).resolve().parent.parent / "random.py" -_LEGACY_SPEC = _importlib_util.spec_from_file_location(_LEGACY_MODULE_NAME, _LEGACY_PATH) +_LEGACY_SPEC = _importlib_util.spec_from_file_location( + _LEGACY_MODULE_NAME, _LEGACY_PATH +) if _LEGACY_SPEC is None or _LEGACY_SPEC.loader is None: # pragma: no cover raise ImportError(f"Cannot load legacy JAX random backend from {_LEGACY_PATH}") _LEGACY = _importlib_util.module_from_spec(_LEGACY_SPEC) diff --git a/src/pyrecest/_backend/pytorch/_common.py b/src/pyrecest/_backend/pytorch/_common.py index e83f41bef..2ae876f8e 100644 --- a/src/pyrecest/_backend/pytorch/_common.py +++ b/src/pyrecest/_backend/pytorch/_common.py @@ -99,7 +99,10 @@ def _preferred_tensor_device(value): def _move_tensors_to_device(tensors, device): if device is None: return tensors - return [tensor.to(device=device) if tensor.device != device else tensor for tensor in tensors] + return [ + tensor.to(device=device) if tensor.device != device else tensor + for tensor in tensors + ] def array(val, dtype=None): diff --git a/src/pyrecest/_backend/pytorch/_dtype.py b/src/pyrecest/_backend/pytorch/_dtype.py index 438aabf55..d4b30102e 100644 --- a/src/pyrecest/_backend/pytorch/_dtype.py +++ b/src/pyrecest/_backend/pytorch/_dtype.py @@ -15,8 +15,8 @@ from pyrecest._backend._dtype_utils import ( get_default_dtype as _shared_get_default_dtype, ) +from torch import bool as torch_bool from torch import ( - bool as torch_bool, complex64, complex128, float16, @@ -171,9 +171,8 @@ def _is_random_array_parameter(value): def _is_binary_array_operand(value): - return ( - isinstance(value, (list, tuple)) - or (not isinstance(value, (str, bytes)) and hasattr(value, "__array__")) + return isinstance(value, (list, tuple)) or ( + not isinstance(value, (str, bytes)) and hasattr(value, "__array__") ) @@ -301,7 +300,9 @@ def _wrapped(x1, x2, *args, **kwargs): x1 = _torch.tensor(x1) if box_x2 and not _torch.is_tensor(x2): x2 = _binary_tensor_operand(x2, x1) - elif not box_x2 and not _torch.is_tensor(x2) and _is_binary_array_operand(x2): + elif ( + not box_x2 and not _torch.is_tensor(x2) and _is_binary_array_operand(x2) + ): x2 = _binary_tensor_operand(x2, x1) return func(x1, x2, *args, **kwargs) @@ -318,7 +319,9 @@ def _patch_parent_log1p_arraylike_contract() -> None: """Make PyTorch backend ``log1p`` accept NumPy-style array-like inputs.""" try: import pyrecest._backend.pytorch as pytorch_backend # pylint: disable=import-outside-toplevel - except ModuleNotFoundError: # pragma: no cover - parent backend import failed earlier + except ( + ModuleNotFoundError + ): # pragma: no cover - parent backend import failed earlier return original_log1p = getattr(pytorch_backend, "log1p", None) diff --git a/src/pyrecest/_backend/pytorch/random/__init__.py b/src/pyrecest/_backend/pytorch/random/__init__.py index 1deb57763..f99b127ea 100644 --- a/src/pyrecest/_backend/pytorch/random/__init__.py +++ b/src/pyrecest/_backend/pytorch/random/__init__.py @@ -10,7 +10,9 @@ _LEGACY_MODULE_NAME = __name__.rsplit(".", 1)[0] + "._random_legacy" _LEGACY_PATH = _Path(__file__).resolve().parent.parent / "random.py" -_LEGACY_SPEC = _importlib_util.spec_from_file_location(_LEGACY_MODULE_NAME, _LEGACY_PATH) +_LEGACY_SPEC = _importlib_util.spec_from_file_location( + _LEGACY_MODULE_NAME, _LEGACY_PATH +) if _LEGACY_SPEC is None or _LEGACY_SPEC.loader is None: # pragma: no cover raise ImportError(f"Cannot load legacy PyTorch random backend from {_LEGACY_PATH}") _LEGACY = _importlib_util.module_from_spec(_LEGACY_SPEC) @@ -30,7 +32,9 @@ def _probability_accumulation_dtype(probabilities): def _normalize_nonnegative_probabilities(probabilities): torch = _LEGACY._torch - probabilities = probabilities.to(dtype=_probability_accumulation_dtype(probabilities)) + probabilities = probabilities.to( + dtype=_probability_accumulation_dtype(probabilities) + ) if bool(torch.any(probabilities < 0)): raise ValueError(_PROBABILITY_SUM_ERROR) scale = probabilities.max() diff --git a/src/pyrecest/_backend_runtime_patches.py b/src/pyrecest/_backend_runtime_patches.py index 57510ca4c..21055ac50 100644 --- a/src/pyrecest/_backend_runtime_patches.py +++ b/src/pyrecest/_backend_runtime_patches.py @@ -9,8 +9,8 @@ def patch_pytorch_close_equal_nan_device_contract() -> None: """Preserve ``equal_nan`` while keeping PyTorch close operands on one device.""" try: - import pyrecest.backend as backend # pylint: disable=import-outside-toplevel import pyrecest._backend.pytorch as raw_pytorch # pylint: disable=import-outside-toplevel + import pyrecest.backend as backend # pylint: disable=import-outside-toplevel import torch # pylint: disable=import-outside-toplevel except ModuleNotFoundError: # pragma: no cover - PyTorch backend may be unavailable return @@ -123,8 +123,8 @@ def patch_pytorch_searchsorted_contract() -> None: """Restore PyTorch ``searchsorted`` with NumPy-style array-like inputs.""" try: - import pyrecest.backend as backend # pylint: disable=import-outside-toplevel import pyrecest._backend.pytorch as raw_pytorch # pylint: disable=import-outside-toplevel + import pyrecest.backend as backend # pylint: disable=import-outside-toplevel import torch # pylint: disable=import-outside-toplevel except ModuleNotFoundError: # pragma: no cover - PyTorch backend may be unavailable return @@ -200,8 +200,8 @@ def patch_pytorch_repeat_numpy_contract() -> None: try: import numpy as np # pylint: disable=import-outside-toplevel - import pyrecest.backend as backend # pylint: disable=import-outside-toplevel import pyrecest._backend.pytorch as raw_pytorch # pylint: disable=import-outside-toplevel + import pyrecest.backend as backend # pylint: disable=import-outside-toplevel import torch # pylint: disable=import-outside-toplevel from pyrecest._backend_submodules import ( # pylint: disable=import-outside-toplevel _pytorch_repeat_with_arraylike_inputs, @@ -237,8 +237,8 @@ def patch_pytorch_edge_pad_contract() -> None: try: import numpy as np # pylint: disable=import-outside-toplevel - import pyrecest.backend as backend # pylint: disable=import-outside-toplevel import pyrecest._backend.pytorch as raw_pytorch # pylint: disable=import-outside-toplevel + import pyrecest.backend as backend # pylint: disable=import-outside-toplevel import torch # pylint: disable=import-outside-toplevel except ModuleNotFoundError: # pragma: no cover - PyTorch backend may be unavailable return @@ -259,7 +259,9 @@ def _normalize_pad_pairs(pad_width, ndim): try: pad_pairs = np.broadcast_to(pad_width_array, (ndim, 2)) except ValueError as exc: - raise ValueError(f"pad_width must be broadcastable to shape ({ndim}, 2)") from exc + raise ValueError( + f"pad_width must be broadcastable to shape ({ndim}, 2)" + ) from exc if np.any(pad_pairs < 0): raise ValueError("index can't contain negative values") return tuple((int(before), int(after)) for before, after in pad_pairs.tolist()) @@ -363,8 +365,8 @@ def patch_pytorch_array_from_sparse_assignment_contract() -> None: try: import numpy as np # pylint: disable=import-outside-toplevel - import pyrecest.backend as backend # pylint: disable=import-outside-toplevel import pyrecest._backend.pytorch as raw_pytorch # pylint: disable=import-outside-toplevel + import pyrecest.backend as backend # pylint: disable=import-outside-toplevel import torch # pylint: disable=import-outside-toplevel except ModuleNotFoundError: # pragma: no cover - PyTorch backend may be unavailable return @@ -417,8 +419,8 @@ def patch_raw_pytorch_reduction_alias_contract() -> None: """Expose PyTorch ``dim``/``keepdim`` aliases on raw reductions always.""" try: - import pyrecest.backend as backend # pylint: disable=import-outside-toplevel import pyrecest._backend.pytorch as raw_pytorch # pylint: disable=import-outside-toplevel + import pyrecest.backend as backend # pylint: disable=import-outside-toplevel from pyrecest._backend_submodules import ( # pylint: disable=import-outside-toplevel _wrap_pytorch_axis_keepdim_reduction, _wrap_pytorch_count_nonzero_reduction, @@ -468,8 +470,8 @@ def patch_pytorch_transpose_boolean_axes_contract() -> None: """Make PyTorch ``transpose`` reject boolean axes sequences like NumPy.""" try: - import pyrecest.backend as backend # pylint: disable=import-outside-toplevel import pyrecest._backend.pytorch as raw_pytorch # pylint: disable=import-outside-toplevel + import pyrecest.backend as backend # pylint: disable=import-outside-toplevel import torch # pylint: disable=import-outside-toplevel except ModuleNotFoundError: # pragma: no cover - PyTorch backend may be unavailable return @@ -523,8 +525,8 @@ def patch_jax_take_arraylike_contract() -> None: try: import jax.numpy as jnp # pylint: disable=import-outside-toplevel - import pyrecest.backend as backend # pylint: disable=import-outside-toplevel import pyrecest._backend.jax as raw_jax # pylint: disable=import-outside-toplevel + import pyrecest.backend as backend # pylint: disable=import-outside-toplevel except ModuleNotFoundError: # pragma: no cover - JAX backend may be unavailable return @@ -573,21 +575,20 @@ def patch_pytorch_assignment_uint8_index_contract() -> None: """Treat uint8 PyTorch assignment indices as integer indices, not masks.""" try: - import pyrecest.backend as backend # pylint: disable=import-outside-toplevel import pyrecest._backend.pytorch as raw_pytorch # pylint: disable=import-outside-toplevel + import pyrecest.backend as backend # pylint: disable=import-outside-toplevel import torch # pylint: disable=import-outside-toplevel except ModuleNotFoundError: # pragma: no cover - PyTorch backend may be unavailable return current_is_boolean = getattr(raw_pytorch, "_is_boolean", None) current_as_assignment_index = getattr(raw_pytorch, "_as_assignment_index", None) - if ( - getattr(current_is_boolean, "_pyrecest_uint8_assignment_index_contract", False) - and getattr( - current_as_assignment_index, - "_pyrecest_uint8_assignment_index_contract", - False, - ) + if getattr( + current_is_boolean, "_pyrecest_uint8_assignment_index_contract", False + ) and getattr( + current_as_assignment_index, + "_pyrecest_uint8_assignment_index_contract", + False, ): return @@ -617,12 +618,13 @@ def _as_assignment_index(index, *, device): backend.assignment = raw_pytorch.assignment backend.assignment_by_sum = raw_pytorch.assignment_by_sum + def patch_pytorch_take_axis_contract() -> None: """Make PyTorch ``take`` reject non-integer axes like NumPy.""" try: - import pyrecest.backend as backend # pylint: disable=import-outside-toplevel import pyrecest._backend.pytorch as raw_pytorch # pylint: disable=import-outside-toplevel + import pyrecest.backend as backend # pylint: disable=import-outside-toplevel import torch # pylint: disable=import-outside-toplevel except ModuleNotFoundError: # pragma: no cover - PyTorch backend may be unavailable return diff --git a/src/pyrecest/_backend_submodules.py b/src/pyrecest/_backend_submodules.py index d8fc4466a..4772c9be5 100644 --- a/src/pyrecest/_backend_submodules.py +++ b/src/pyrecest/_backend_submodules.py @@ -647,7 +647,9 @@ def cross(a, b, axisa=-1, axisb=-1, axisc=-1, axis=None): "(dimension must be 2 or 3)" ) - leading_shape = numpy_module.broadcast_shapes(tuple(a.shape[:-1]), tuple(b.shape[:-1])) + leading_shape = numpy_module.broadcast_shapes( + tuple(a.shape[:-1]), tuple(b.shape[:-1]) + ) if tuple(a.shape[:-1]) != leading_shape: a = torch_module.broadcast_to(a, leading_shape + (a_dim,)) if tuple(b.shape[:-1]) != leading_shape: diff --git a/src/pyrecest/_pytorch_dot_contract.py b/src/pyrecest/_pytorch_dot_contract.py index bdebd130c..3ea6337de 100644 --- a/src/pyrecest/_pytorch_dot_contract.py +++ b/src/pyrecest/_pytorch_dot_contract.py @@ -7,8 +7,8 @@ def patch_pytorch_dot_numpy_contract() -> None: """Make raw and public PyTorch ``dot`` follow NumPy's axis contract.""" try: - import pyrecest.backend as backend # pylint: disable=import-outside-toplevel import pyrecest._backend.pytorch as raw_pytorch # pylint: disable=import-outside-toplevel + import pyrecest.backend as backend # pylint: disable=import-outside-toplevel import torch # pylint: disable=import-outside-toplevel except ModuleNotFoundError: # pragma: no cover - PyTorch may be unavailable return diff --git a/src/pyrecest/backend_support/__init__.py b/src/pyrecest/backend_support/__init__.py index 1e1c87c2e..1c6215799 100644 --- a/src/pyrecest/backend_support/__init__.py +++ b/src/pyrecest/backend_support/__init__.py @@ -48,8 +48,8 @@ def _patch_raw_pytorch_assignment_scalar_tensor_indices() -> None: """Make raw PyTorch assignment helpers accept scalar integer tensor indices.""" try: - import pyrecest.backend as backend # pylint: disable=import-outside-toplevel import pyrecest._backend.pytorch as raw_pytorch # pylint: disable=import-outside-toplevel + import pyrecest.backend as backend # pylint: disable=import-outside-toplevel import torch as _torch # pylint: disable=import-outside-toplevel except ModuleNotFoundError: # pragma: no cover - PyTorch backend may be unavailable return @@ -71,8 +71,8 @@ def _patch_pytorch_asarray_numpy_contract() -> None: """Make PyTorch asarray accept NumPy dtype aliases and strided views.""" try: import numpy as np # pylint: disable=import-outside-toplevel - import pyrecest.backend as backend # pylint: disable=import-outside-toplevel import pyrecest._backend.pytorch as raw_pytorch # pylint: disable=import-outside-toplevel + import pyrecest.backend as backend # pylint: disable=import-outside-toplevel import torch # pylint: disable=import-outside-toplevel from pyrecest._backend.pytorch._common import ( # pylint: disable=import-outside-toplevel _normalize_dtype, @@ -356,7 +356,9 @@ def _patch_pytorch_copy_numpy_contract() -> None: try: import pyrecest._backend.pytorch as raw_pytorch # pylint: disable=import-outside-toplevel - except ModuleNotFoundError: # pragma: no cover - PyTorch backend import failed earlier + except ( + ModuleNotFoundError + ): # pragma: no cover - PyTorch backend import failed earlier return original_copy = raw_pytorch.copy if getattr(original_copy, "_pyrecest_numpy_contract", False): @@ -387,7 +389,9 @@ def _patch_pytorch_broadcast_arrays_numpy_contract() -> None: try: import pyrecest._backend.pytorch as raw_pytorch # pylint: disable=import-outside-toplevel import torch # pylint: disable=import-outside-toplevel - except ModuleNotFoundError: # pragma: no cover - PyTorch backend import failed earlier + except ( + ModuleNotFoundError + ): # pragma: no cover - PyTorch backend import failed earlier return original_broadcast_arrays = raw_pytorch.broadcast_arrays @@ -421,7 +425,9 @@ def _patch_pytorch_clip_numpy_contract() -> None: try: import pyrecest._backend.pytorch as raw_pytorch # pylint: disable=import-outside-toplevel import torch # pylint: disable=import-outside-toplevel - except ModuleNotFoundError: # pragma: no cover - PyTorch backend import failed earlier + except ( + ModuleNotFoundError + ): # pragma: no cover - PyTorch backend import failed earlier return original_clip = raw_pytorch.clip if getattr(original_clip, "_pyrecest_numpy_contract", False): @@ -501,12 +507,16 @@ def _patch_pytorch_isclose_device_contract() -> None: try: import pyrecest._backend.pytorch as raw_pytorch # pylint: disable=import-outside-toplevel import torch # pylint: disable=import-outside-toplevel - except ModuleNotFoundError: # pragma: no cover - PyTorch backend import failed earlier + except ( + ModuleNotFoundError + ): # pragma: no cover - PyTorch backend import failed earlier return helper_names = ("isclose", "allclose") if all( - getattr(getattr(raw_pytorch, helper_name, None), "_pyrecest_device_contract", False) + getattr( + getattr(raw_pytorch, helper_name, None), "_pyrecest_device_contract", False + ) for helper_name in helper_names ): if active_pytorch_backend: @@ -586,7 +596,9 @@ def _patch_pytorch_broadcast_to_numpy_contract() -> None: import numpy as np # pylint: disable=import-outside-toplevel import pyrecest._backend.pytorch as raw_pytorch # pylint: disable=import-outside-toplevel import torch # pylint: disable=import-outside-toplevel - except ModuleNotFoundError: # pragma: no cover - PyTorch backend import failed earlier + except ( + ModuleNotFoundError + ): # pragma: no cover - PyTorch backend import failed earlier return original_broadcast_to = raw_pytorch.broadcast_to diff --git a/src/pyrecest/backend_support/_jax_array_from_sparse_contract.py b/src/pyrecest/backend_support/_jax_array_from_sparse_contract.py index 8b0c55f08..ecfc67334 100644 --- a/src/pyrecest/backend_support/_jax_array_from_sparse_contract.py +++ b/src/pyrecest/backend_support/_jax_array_from_sparse_contract.py @@ -18,8 +18,8 @@ def patch_jax_array_from_sparse_flat_index_contract() -> None: try: import jax.numpy as jnp # pylint: disable=import-outside-toplevel - import pyrecest.backend as backend # pylint: disable=import-outside-toplevel import pyrecest._backend.jax as raw_jax # pylint: disable=import-outside-toplevel + import pyrecest.backend as backend # pylint: disable=import-outside-toplevel except ModuleNotFoundError: # pragma: no cover - JAX backend may be unavailable return diff --git a/src/pyrecest/backend_support/_jax_assignment_numpy_index_contract.py b/src/pyrecest/backend_support/_jax_assignment_numpy_index_contract.py index 85cc49e9f..6fffca4e2 100644 --- a/src/pyrecest/backend_support/_jax_assignment_numpy_index_contract.py +++ b/src/pyrecest/backend_support/_jax_assignment_numpy_index_contract.py @@ -26,8 +26,10 @@ def _is_array_like_index(index, np, jnp): def _is_per_axis_tuple_index(indices, np, jnp): """Return whether ``indices`` is a NumPy-style tuple of per-axis arrays.""" - return isinstance(indices, tuple) and bool(indices) and _is_array_like_index( - indices[0], np, jnp + return ( + isinstance(indices, tuple) + and bool(indices) + and _is_array_like_index(indices[0], np, jnp) ) @@ -71,7 +73,9 @@ def _rectangular_triangular_to_vec(helper_name, original_helper, index_helper, j def triangular_to_vec(x, k=0): x = jnp.asarray(x) if x.ndim < 2: - raise ValueError("triangular vector helpers require at least two matrix dimensions") + raise ValueError( + "triangular vector helpers require at least two matrix dimensions" + ) rows, cols = index_helper(x.shape[-2], k=k, m=x.shape[-1]) return x[..., rows, cols] @@ -82,7 +86,9 @@ def triangular_to_vec(x, k=0): return triangular_to_vec -def _patch_jax_triangular_vector_helpers_rectangular_contract(jax_backend, backend, jnp) -> None: +def _patch_jax_triangular_vector_helpers_rectangular_contract( + jax_backend, backend, jnp +) -> None: """Make JAX triangular-vector helpers work for rectangular matrices.""" active_jax_backend = getattr(backend, "__backend_name__", None) == "jax" diff --git a/src/pyrecest/backend_support/_jax_random_empty_contract.py b/src/pyrecest/backend_support/_jax_random_empty_contract.py index b584a490f..00f6d88de 100644 --- a/src/pyrecest/backend_support/_jax_random_empty_contract.py +++ b/src/pyrecest/backend_support/_jax_random_empty_contract.py @@ -34,11 +34,17 @@ def _parse_randint_arguments(low, high, size, kwargs): return None return legacy_minval, legacy_maxval, size, kwargs if ( - raw_jax_random._looks_like_shape_sequence(low) # pylint: disable=protected-access + raw_jax_random._looks_like_shape_sequence( + low + ) # pylint: disable=protected-access and high is not None and size is not None - and raw_jax_random._looks_like_scalar_randint_bound(high) # pylint: disable=protected-access - and raw_jax_random._looks_like_scalar_randint_bound(size) # pylint: disable=protected-access + and raw_jax_random._looks_like_scalar_randint_bound( + high + ) # pylint: disable=protected-access + and raw_jax_random._looks_like_scalar_randint_bound( + size + ) # pylint: disable=protected-access ): return high, size, low, kwargs if high is None: @@ -55,17 +61,27 @@ def _empty_invalid_bound_result(low, high, size, args, kwargs): return None low, high, size, kwargs = parsed - low = raw_jax_random._validate_randint_bound(low, "low") # pylint: disable=protected-access - high = raw_jax_random._validate_randint_bound(high, "high") # pylint: disable=protected-access + low = raw_jax_random._validate_randint_bound( + low, "low" + ) # pylint: disable=protected-access + high = raw_jax_random._validate_randint_bound( + high, "high" + ) # pylint: disable=protected-access try: low, high = jnp.broadcast_arrays(low, high) except ValueError as exc: raise ValueError("low and high could not be broadcast together") from exc - shape = raw_jax_random._bounded_sampler_shape(size, low, high) # pylint: disable=protected-access - if not raw_jax_random._shape_has_no_samples(shape): # pylint: disable=protected-access + shape = raw_jax_random._bounded_sampler_shape( + size, low, high + ) # pylint: disable=protected-access + if not raw_jax_random._shape_has_no_samples( + shape + ): # pylint: disable=protected-access return None - state, has_state, remaining_kwargs = raw_jax_random._get_state(**kwargs) # pylint: disable=protected-access + state, has_state, remaining_kwargs = raw_jax_random._get_state( + **kwargs + ) # pylint: disable=protected-access state, result = raw_jax_random._randint( # pylint: disable=protected-access state, shape, diff --git a/src/pyrecest/backend_support/_pytorch_allclose_device_contract.py b/src/pyrecest/backend_support/_pytorch_allclose_device_contract.py index bfd0a1319..437cdcedd 100644 --- a/src/pyrecest/backend_support/_pytorch_allclose_device_contract.py +++ b/src/pyrecest/backend_support/_pytorch_allclose_device_contract.py @@ -63,7 +63,9 @@ def _patch_pytorch_linalg_logm_arraylike_contract() -> None: return def logm(x): - return original_logm(pytorch_linalg._as_linalg_tensor(x)) # pylint: disable=protected-access + return original_logm( + pytorch_linalg._as_linalg_tensor(x) + ) # pylint: disable=protected-access logm.__name__ = getattr(original_logm, "__name__", "logm") logm.__doc__ = getattr(original_logm, "__doc__", None) diff --git a/src/pyrecest/backend_support/_pytorch_creation_shape_contract.py b/src/pyrecest/backend_support/_pytorch_creation_shape_contract.py index c602baaf8..e531bcc14 100644 --- a/src/pyrecest/backend_support/_pytorch_creation_shape_contract.py +++ b/src/pyrecest/backend_support/_pytorch_creation_shape_contract.py @@ -4,7 +4,6 @@ from operator import index as _operator_index - _ARANGE_SENTINEL = object() @@ -215,7 +214,9 @@ def like_creation_helper(a, dtype=None, *args, **kwargs): **kwargs, ) - like_creation_helper.__name__ = getattr(original_helper, "__name__", helper_name) + like_creation_helper.__name__ = getattr( + original_helper, "__name__", helper_name + ) like_creation_helper.__doc__ = getattr(original_helper, "__doc__", None) like_creation_helper._pyrecest_numpy_contract = True like_creation_helper._pyrecest_arraylike_contract = True diff --git a/src/pyrecest/backend_support/_pytorch_matmul_device_contract.py b/src/pyrecest/backend_support/_pytorch_matmul_device_contract.py index fd4f8442f..f9583fd19 100644 --- a/src/pyrecest/backend_support/_pytorch_matmul_device_contract.py +++ b/src/pyrecest/backend_support/_pytorch_matmul_device_contract.py @@ -41,7 +41,11 @@ def patch_pytorch_matmul_device_contract() -> None: helper_names = ("matmul", "matvec") if all( - getattr(getattr(raw_pytorch, helper_name, None), "_pyrecest_matmul_device_contract", False) + getattr( + getattr(raw_pytorch, helper_name, None), + "_pyrecest_matmul_device_contract", + False, + ) for helper_name in helper_names ): if getattr(backend, "__backend_name__", None) == "pytorch": diff --git a/src/pyrecest/backend_support/_pytorch_minmax_device_contract.py b/src/pyrecest/backend_support/_pytorch_minmax_device_contract.py index c159a7d54..174a93ccd 100644 --- a/src/pyrecest/backend_support/_pytorch_minmax_device_contract.py +++ b/src/pyrecest/backend_support/_pytorch_minmax_device_contract.py @@ -102,11 +102,15 @@ def _normalize_pytorch_norm_axis(axis, torch_module): return int(axis) if isinstance(axis, (list, tuple)): - return tuple(_pytorch_norm_axis_entry(one_axis, torch_module) for one_axis in axis) + return tuple( + _pytorch_norm_axis_entry(one_axis, torch_module) for one_axis in axis + ) return _pytorch_norm_axis_entry(axis, torch_module) -def _patch_pytorch_linalg_norm_axis_contract(raw_pytorch, backend, torch_module) -> None: +def _patch_pytorch_linalg_norm_axis_contract( + raw_pytorch, backend, torch_module +) -> None: """Patch PyTorch linalg.norm axis normalization for boolean sequences.""" raw_linalg = getattr(raw_pytorch, "linalg", None) if raw_linalg is None: @@ -118,7 +122,9 @@ def _patch_pytorch_linalg_norm_axis_contract(raw_pytorch, backend, torch_module) if getattr(backend, "__backend_name__", None) == "pytorch": try: backend_linalg = importlib.import_module("pyrecest.backend.linalg") - except ModuleNotFoundError: # pragma: no cover - backend import failed first + except ( + ModuleNotFoundError + ): # pragma: no cover - backend import failed first return backend_linalg.norm = original_norm return diff --git a/src/pyrecest/backend_support/_pytorch_one_hot_scalar_contract.py b/src/pyrecest/backend_support/_pytorch_one_hot_scalar_contract.py index f222e2ae6..9ea708312 100644 --- a/src/pyrecest/backend_support/_pytorch_one_hot_scalar_contract.py +++ b/src/pyrecest/backend_support/_pytorch_one_hot_scalar_contract.py @@ -4,7 +4,6 @@ from operator import index as _operator_index - _INTEGER_MESSAGE = "num_classes must be an integer" diff --git a/src/pyrecest/backend_support/_pytorch_quantile_empty_batch_contract.py b/src/pyrecest/backend_support/_pytorch_quantile_empty_batch_contract.py index d3e7a3287..4ec490903 100644 --- a/src/pyrecest/backend_support/_pytorch_quantile_empty_batch_contract.py +++ b/src/pyrecest/backend_support/_pytorch_quantile_empty_batch_contract.py @@ -8,8 +8,8 @@ def patch_pytorch_quantile_empty_batch_contract() -> None: try: import numpy as np # pylint: disable=import-outside-toplevel - import pyrecest.backend as backend # pylint: disable=import-outside-toplevel import pyrecest._backend.pytorch as raw_pytorch # pylint: disable=import-outside-toplevel + import pyrecest.backend as backend # pylint: disable=import-outside-toplevel import torch # pylint: disable=import-outside-toplevel except ModuleNotFoundError: # pragma: no cover - PyTorch may be unavailable return diff --git a/src/pyrecest/backend_support/_pytorch_reduction_axis_contract.py b/src/pyrecest/backend_support/_pytorch_reduction_axis_contract.py index 3f47857f8..486b7ad14 100644 --- a/src/pyrecest/backend_support/_pytorch_reduction_axis_contract.py +++ b/src/pyrecest/backend_support/_pytorch_reduction_axis_contract.py @@ -5,7 +5,6 @@ from operator import index as _operator_index import numpy as np - from pyrecest.backend_support._pytorch_searchsorted_sorter_contract import ( patch_pytorch_searchsorted_sorter_contract as _patch_pytorch_searchsorted_sorter_contract, ) @@ -39,7 +38,9 @@ def _axis_contains_boolean_value(axis, torch_module) -> bool: axes = tuple(axis) except TypeError: return False - return any(_axis_contains_boolean_value(one_axis, torch_module) for one_axis in axes) + return any( + _axis_contains_boolean_value(one_axis, torch_module) for one_axis in axes + ) return False @@ -126,9 +127,9 @@ def reduction(*args, **kwargs): if "axis" not in kwargs and len(args) >= 2: axis = args[1] dim = kwargs.get("dim") - if _axis_contains_boolean_value(axis, torch_module) or _axis_contains_boolean_value( - dim, torch_module - ): + if _axis_contains_boolean_value( + axis, torch_module + ) or _axis_contains_boolean_value(dim, torch_module): raise TypeError(_AXIS_TYPE_MESSAGE) args = list(args) @@ -155,8 +156,8 @@ def patch_pytorch_reduction_axis_contract() -> None: try: import pyrecest._backend as backend_loader # pylint: disable=import-outside-toplevel - import pyrecest.backend as backend # pylint: disable=import-outside-toplevel import pyrecest._backend.pytorch as raw_pytorch # pylint: disable=import-outside-toplevel + import pyrecest.backend as backend # pylint: disable=import-outside-toplevel import torch # pylint: disable=import-outside-toplevel except ModuleNotFoundError: # pragma: no cover - PyTorch backend may be unavailable return @@ -187,7 +188,9 @@ def _normalize_backend_reduction_axes(axis, ndim_value): original_normalizer = getattr(raw_pytorch, "_normalize_reduction_axes", None) if original_normalizer is None: return - if not getattr(original_normalizer, "_pyrecest_reduction_axis_bool_contract", False): + if not getattr( + original_normalizer, "_pyrecest_reduction_axis_bool_contract", False + ): def _normalize_reduction_axes(axis, ndim_value): return normalize_reduction_axes(axis, ndim_value, torch) @@ -197,7 +200,9 @@ def _normalize_reduction_axes(axis, ndim_value): "__name__", "_normalize_reduction_axes", ) - _normalize_reduction_axes.__doc__ = getattr(original_normalizer, "__doc__", None) + _normalize_reduction_axes.__doc__ = getattr( + original_normalizer, "__doc__", None + ) _normalize_reduction_axes._pyrecest_reduction_axis_bool_contract = True raw_pytorch._normalize_reduction_axes = _normalize_reduction_axes diff --git a/src/pyrecest/backend_support/_pytorch_searchsorted_sorter_contract.py b/src/pyrecest/backend_support/_pytorch_searchsorted_sorter_contract.py index f157cb025..e74006ab4 100644 --- a/src/pyrecest/backend_support/_pytorch_searchsorted_sorter_contract.py +++ b/src/pyrecest/backend_support/_pytorch_searchsorted_sorter_contract.py @@ -35,7 +35,9 @@ def _validate_sorter(sorter, numpy_module, torch_module) -> None: if sorter_array.ndim != 1: raise TypeError(_SORTER_SHAPE_MESSAGE) - if numpy_module.issubdtype(sorter_array.dtype, numpy_module.bool_) or not numpy_module.can_cast( + if numpy_module.issubdtype( + sorter_array.dtype, numpy_module.bool_ + ) or not numpy_module.can_cast( sorter_array.dtype, numpy_module.dtype(numpy_module.intp), casting="safe", @@ -50,8 +52,8 @@ def patch_pytorch_searchsorted_sorter_contract() -> None: try: import numpy as np # pylint: disable=import-outside-toplevel - import pyrecest.backend as backend # pylint: disable=import-outside-toplevel import pyrecest._backend.pytorch as raw_pytorch # pylint: disable=import-outside-toplevel + import pyrecest.backend as backend # pylint: disable=import-outside-toplevel import torch # pylint: disable=import-outside-toplevel except ModuleNotFoundError: # pragma: no cover - PyTorch may be unavailable return diff --git a/src/pyrecest/backend_support/_pytorch_sort_numpy_contract.py b/src/pyrecest/backend_support/_pytorch_sort_numpy_contract.py index dfe30ccf8..b38273124 100644 --- a/src/pyrecest/backend_support/_pytorch_sort_numpy_contract.py +++ b/src/pyrecest/backend_support/_pytorch_sort_numpy_contract.py @@ -6,7 +6,6 @@ import numpy as _np - _SORT_KIND_MESSAGE = ( "sort kind must be one of 'quicksort', 'heapsort', 'stable', or 'mergesort'" ) diff --git a/src/pyrecest/backend_support/_pytorch_split_index_contract.py b/src/pyrecest/backend_support/_pytorch_split_index_contract.py index a08cc1227..447be2964 100644 --- a/src/pyrecest/backend_support/_pytorch_split_index_contract.py +++ b/src/pyrecest/backend_support/_pytorch_split_index_contract.py @@ -5,7 +5,6 @@ from operator import index as _operator_index import numpy as np - from pyrecest.backend_support._pytorch_take_index_contract import ( patch_pytorch_take_index_contract as _patch_pytorch_take_index_contract, ) @@ -56,8 +55,8 @@ def patch_pytorch_split_index_contract() -> None: _patch_pytorch_take_index_contract() try: - import pyrecest.backend as backend # pylint: disable=import-outside-toplevel import pyrecest._backend.pytorch as raw_pytorch # pylint: disable=import-outside-toplevel + import pyrecest.backend as backend # pylint: disable=import-outside-toplevel import torch # pylint: disable=import-outside-toplevel except ModuleNotFoundError: # pragma: no cover - PyTorch may be unavailable return diff --git a/src/pyrecest/backend_support/_pytorch_stack_helpers_device_contract.py b/src/pyrecest/backend_support/_pytorch_stack_helpers_device_contract.py index 7c7d64ac5..3b88995f0 100644 --- a/src/pyrecest/backend_support/_pytorch_stack_helpers_device_contract.py +++ b/src/pyrecest/backend_support/_pytorch_stack_helpers_device_contract.py @@ -4,8 +4,14 @@ import importlib - -_STACK_HELPER_NAMES = ("concatenate", "stack", "hstack", "vstack", "column_stack", "dstack") +_STACK_HELPER_NAMES = ( + "concatenate", + "stack", + "hstack", + "vstack", + "column_stack", + "dstack", +) _EMPTY_CONCATENATE_MESSAGE = "need at least one array to concatenate" _EMPTY_STACK_MESSAGE = "need at least one array to stack" diff --git a/src/pyrecest/backend_support/_pytorch_take_index_contract.py b/src/pyrecest/backend_support/_pytorch_take_index_contract.py index 513e36945..a5a00b3b0 100644 --- a/src/pyrecest/backend_support/_pytorch_take_index_contract.py +++ b/src/pyrecest/backend_support/_pytorch_take_index_contract.py @@ -30,8 +30,8 @@ def patch_pytorch_take_index_contract() -> None: """Make public and raw PyTorch ``take`` reject invalid index arrays.""" try: - import pyrecest.backend as backend # pylint: disable=import-outside-toplevel import pyrecest._backend.pytorch as raw_pytorch # pylint: disable=import-outside-toplevel + import pyrecest.backend as backend # pylint: disable=import-outside-toplevel import torch # pylint: disable=import-outside-toplevel except ModuleNotFoundError: # pragma: no cover - PyTorch may be unavailable return diff --git a/src/pyrecest/backend_support/_pytorch_trapezoid_numpy_contract.py b/src/pyrecest/backend_support/_pytorch_trapezoid_numpy_contract.py index 4425bc3be..485ae1584 100644 --- a/src/pyrecest/backend_support/_pytorch_trapezoid_numpy_contract.py +++ b/src/pyrecest/backend_support/_pytorch_trapezoid_numpy_contract.py @@ -101,7 +101,9 @@ def _make_triangular_to_vec(helper_name, torch_index_helper, original_helper): def triangular_to_vec(x, k=0): x = raw_pytorch.array(x) if x.ndim < 2: - raise ValueError("triangular vector helpers require at least two dimensions") + raise ValueError( + "triangular vector helpers require at least two dimensions" + ) rows, cols = torch_index_helper( row=x.shape[-2], col=x.shape[-1], @@ -123,7 +125,9 @@ def triangular_to_vec(x, k=0): original_helper = getattr(raw_pytorch, helper_name, None) if original_helper is None: continue - helper = _make_triangular_to_vec(helper_name, torch_index_helper, original_helper) + helper = _make_triangular_to_vec( + helper_name, torch_index_helper, original_helper + ) setattr(raw_pytorch, helper_name, helper) if active_pytorch_backend: setattr(backend, helper_name, helper) @@ -144,7 +148,9 @@ def _make_triangular_to_vec(helper_name, jax_index_helper, original_helper): def triangular_to_vec(x, k=0): x = jnp.asarray(x) if x.ndim < 2: - raise ValueError("triangular vector helpers require at least two dimensions") + raise ValueError( + "triangular vector helpers require at least two dimensions" + ) rows, cols = jax_index_helper( n=x.shape[-2], k=_operator_index(k), diff --git a/src/pyrecest/backend_support/_random_uniform_empty_contract.py b/src/pyrecest/backend_support/_random_uniform_empty_contract.py index fe181c082..fa467ee72 100644 --- a/src/pyrecest/backend_support/_random_uniform_empty_contract.py +++ b/src/pyrecest/backend_support/_random_uniform_empty_contract.py @@ -70,13 +70,17 @@ def _empty_result(low, high, size, args, kwargs): return None dtype = kwargs.get("dtype", None) numpy_module = raw_random._np # pylint: disable=protected-access - low_array = raw_random._validate_uniform_bound( # pylint: disable=protected-access - low, - "low", + low_array = ( + raw_random._validate_uniform_bound( # pylint: disable=protected-access + low, + "low", + ) ) - high_array = raw_random._validate_uniform_bound( # pylint: disable=protected-access - high, - "high", + high_array = ( + raw_random._validate_uniform_bound( # pylint: disable=protected-access + high, + "high", + ) ) sample_shape = _sample_shape_from_numpy_size( raw_random._normalize_size(size), # pylint: disable=protected-access @@ -133,23 +137,29 @@ def _patch_pytorch_uniform_empty_bounds_contract() -> None: return def _empty_result(low, high, size, dtype): - dtype = raw_random._normalize_random_dtype(dtype, default=None) # pylint: disable=protected-access + dtype = raw_random._normalize_random_dtype( + dtype, default=None + ) # pylint: disable=protected-access device = None if torch.is_tensor(low): device = low.device elif torch.is_tensor(high): device = high.device - low_tensor = raw_random._validate_uniform_bound( # pylint: disable=protected-access - low, - "low", - dtype=dtype, - device=device, + low_tensor = ( + raw_random._validate_uniform_bound( # pylint: disable=protected-access + low, + "low", + dtype=dtype, + device=device, + ) ) - high_tensor = raw_random._validate_uniform_bound( # pylint: disable=protected-access - high, - "high", - dtype=dtype, - device=device, + high_tensor = ( + raw_random._validate_uniform_bound( # pylint: disable=protected-access + high, + "high", + dtype=dtype, + device=device, + ) ) sample_shape = raw_random._uniform_size( # pylint: disable=protected-access size, @@ -203,20 +213,24 @@ def _patch_jax_uniform_empty_bounds_contract() -> None: def _empty_result(low, high, size, args, kwargs): if args: return None - low_value, high_value = raw_random._validate_uniform_bounds( # pylint: disable=protected-access - low, - high, + low_value, high_value = ( + raw_random._validate_uniform_bounds( # pylint: disable=protected-access + low, + high, + ) ) - sample_shape = raw_random._bounded_sampler_shape( # pylint: disable=protected-access - size, - low_value, - high_value, + sample_shape = ( + raw_random._bounded_sampler_shape( # pylint: disable=protected-access + size, + low_value, + high_value, + ) ) if not _shape_has_no_samples(sample_shape): return None - state, has_state, remaining_kwargs = raw_random._get_state( # pylint: disable=protected-access + state, has_state, remaining_kwargs = raw_random._get_state( **kwargs - ) + ) # pylint: disable=protected-access state, _ = raw_random.jax.random.split(state) dtype = remaining_kwargs.get("dtype", None) result = jnp.empty(sample_shape, dtype=dtype) diff --git a/src/pyrecest/backend_support/_torch_dtype_promotion_contract.py b/src/pyrecest/backend_support/_torch_dtype_promotion_contract.py index d37757ded..0412e11d6 100644 --- a/src/pyrecest/backend_support/_torch_dtype_promotion_contract.py +++ b/src/pyrecest/backend_support/_torch_dtype_promotion_contract.py @@ -16,7 +16,9 @@ def patch_pytorch_dtype_promotion_contract() -> None: from pyrecest._backend.pytorch._common import ( # pylint: disable=import-outside-toplevel _normalize_dtype, ) - except ModuleNotFoundError: # pragma: no cover - PyTorch backend import failed earlier + except ( + ModuleNotFoundError + ): # pragma: no cover - PyTorch backend import failed earlier return _patch_pytorch_repeat_numpy_contract(raw_pytorch, torch) @@ -161,7 +163,9 @@ def _normalize_axis(axis, ndim): if axis < 0: axis += ndim if axis < 0 or axis >= ndim: - raise IndexError(f"axis {axis} is out of bounds for array of dimension {ndim}") + raise IndexError( + f"axis {axis} is out of bounds for array of dimension {ndim}" + ) return axis def _boundary(value, reference, axis): @@ -295,7 +299,9 @@ def roll(a, shift=None, axis=None, *, shifts=None, dims=None): tuple(values.shape) ) - roll_shifts, roll_axes = _pytorch_roll_pairs(shift, axis, values.ndim, np, torch) + roll_shifts, roll_axes = _pytorch_roll_pairs( + shift, axis, values.ndim, np, torch + ) if not roll_shifts: return values.clone() return torch.roll(values, roll_shifts, roll_axes) @@ -325,7 +331,10 @@ def _patch_pytorch_transpose_numpy_axes_contract(raw_pytorch, np) -> None: original_transpose = raw_pytorch.transpose if getattr(original_transpose, "_pyrecest_numpy_axes_contract", False): - if backend is not None and getattr(backend, "__backend_name__", None) == "pytorch": + if ( + backend is not None + and getattr(backend, "__backend_name__", None) == "pytorch" + ): backend.transpose = original_transpose return @@ -352,8 +361,7 @@ def _normalize_creation_shape(shape, torch, np): normalized_shape = (_operator_index(shape_array.item()),) else: normalized_shape = tuple( - _operator_index(one_dimension) - for one_dimension in shape_array.tolist() + _operator_index(one_dimension) for one_dimension in shape_array.tolist() ) if any(one_dimension < 0 for one_dimension in normalized_shape): raise ValueError("negative dimensions are not allowed") @@ -530,7 +538,9 @@ def _patch_pytorch_randint_empty_size_contract(raw_pytorch_random, torch) -> Non active_pytorch_backend = ( backend is not None and getattr(backend, "__backend_name__", None) == "pytorch" ) - backend_random = getattr(backend, "random", None) if active_pytorch_backend else None + backend_random = ( + getattr(backend, "random", None) if active_pytorch_backend else None + ) original_randint = raw_pytorch_random.randint if getattr(original_randint, "_pyrecest_empty_size_contract", False): if active_pytorch_backend and backend_random is not None: @@ -608,7 +618,9 @@ def _normalize_pad_pairs(pad_width, ndim, np): try: pad_pairs = np.broadcast_to(pad_width_array, (ndim, 2)) except ValueError as exc: - raise ValueError(f"pad_width must be broadcastable to shape ({ndim}, 2)") from exc + raise ValueError( + f"pad_width must be broadcastable to shape ({ndim}, 2)" + ) from exc if np.any(pad_pairs < 0): raise ValueError("index can't contain negative values") return tuple( @@ -630,7 +642,9 @@ def _normalize_constant_value_pairs(constant_values, ndim, np): def _filled_pad_block(shape, value, reference, torch): """Return a constant-filled block compatible with ``reference``.""" - scalar_value = torch.as_tensor(value, dtype=reference.dtype, device=reference.device) + scalar_value = torch.as_tensor( + value, dtype=reference.dtype, device=reference.device + ) if scalar_value.ndim != 0: raise ValueError("constant_values entries must be scalar") block = torch.empty(tuple(shape), dtype=reference.dtype, device=reference.device) diff --git a/src/pyrecest/backend_support/_torch_dtype_promotion_contract/__init__.py b/src/pyrecest/backend_support/_torch_dtype_promotion_contract/__init__.py index 7ecf75970..285ea8fce 100644 --- a/src/pyrecest/backend_support/_torch_dtype_promotion_contract/__init__.py +++ b/src/pyrecest/backend_support/_torch_dtype_promotion_contract/__init__.py @@ -8,13 +8,17 @@ def _load_base_contract_module(): - module_path = Path(__file__).resolve().parent.parent / "_torch_dtype_promotion_contract.py" + module_path = ( + Path(__file__).resolve().parent.parent / "_torch_dtype_promotion_contract.py" + ) spec = importlib.util.spec_from_file_location( "_pyrecest_torch_dtype_promotion_contract_base", module_path, ) if spec is None or spec.loader is None: - raise ImportError(f"Cannot load PyTorch dtype contract module from {module_path}") + raise ImportError( + f"Cannot load PyTorch dtype contract module from {module_path}" + ) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module @@ -31,7 +35,9 @@ def patch_pytorch_dtype_promotion_contract() -> None: import pyrecest._backend.pytorch as raw_pytorch # pylint: disable=import-outside-toplevel import pyrecest.backend as backend # pylint: disable=import-outside-toplevel import torch # pylint: disable=import-outside-toplevel - except ModuleNotFoundError: # pragma: no cover - PyTorch backend import failed earlier + except ( + ModuleNotFoundError + ): # pragma: no cover - PyTorch backend import failed earlier return _patch_pytorch_assignment_numpy_index_contract(raw_pytorch, backend, torch, np) @@ -143,11 +149,17 @@ def assignment(x, values, indices, axis=0): return assignment -def _patch_pytorch_assignment_numpy_index_contract(raw_pytorch, backend, torch, np) -> None: +def _patch_pytorch_assignment_numpy_index_contract( + raw_pytorch, backend, torch, np +) -> None: """Make PyTorch assignment helpers accept NumPy integer and boolean indices.""" helper_names = ("assignment", "assignment_by_sum") if all( - getattr(getattr(raw_pytorch, helper_name, None), "_pyrecest_numpy_index_contract", False) + getattr( + getattr(raw_pytorch, helper_name, None), + "_pyrecest_numpy_index_contract", + False, + ) for helper_name in helper_names ): if getattr(backend, "__backend_name__", None) == "pytorch": @@ -295,7 +307,9 @@ def _patch_pytorch_comparison_device_contract(raw_pytorch, backend, torch) -> No """Make PyTorch comparison helpers accept NumPy-style array-like inputs.""" helper_names = ("greater", "less", "logical_or") if all( - getattr(getattr(raw_pytorch, helper_name, None), "_pyrecest_device_contract", False) + getattr( + getattr(raw_pytorch, helper_name, None), "_pyrecest_device_contract", False + ) for helper_name in helper_names ): if getattr(backend, "__backend_name__", None) == "pytorch": @@ -320,7 +334,9 @@ def _patch_pytorch_binary_device_contract(raw_pytorch, backend, torch) -> None: "power": False, } if all( - getattr(getattr(raw_pytorch, helper_name, None), "_pyrecest_device_contract", False) + getattr( + getattr(raw_pytorch, helper_name, None), "_pyrecest_device_contract", False + ) for helper_name in helpers ): if getattr(backend, "__backend_name__", None) == "pytorch": @@ -343,7 +359,9 @@ def _patch_pytorch_equality_device_contract(raw_pytorch, backend, torch) -> None """Keep equality-style helpers on an existing non-CPU tensor device.""" helper_names = ("equal", "less_equal", "array" + "_equal") if all( - getattr(getattr(raw_pytorch, helper_name, None), "_pyrecest_device_contract", False) + getattr( + getattr(raw_pytorch, helper_name, None), "_pyrecest_device_contract", False + ) for helper_name in helper_names ): if getattr(backend, "__backend_name__", None) == "pytorch": @@ -386,6 +404,7 @@ def _patch_pytorch_linspace_integer_dtype_contract(raw_pytorch, backend, torch) if getattr(backend, "__backend_name__", None) == "pytorch": backend.linspace = original_linspace return + def linspace(start, stop, num=50, endpoint=True, dtype=None): integer_dtype = _integer_torch_dtype(dtype, raw_pytorch, torch) if integer_dtype is None: @@ -433,8 +452,7 @@ def _pytorch_creation_shape(shape, torch, np) -> tuple[int, ...]: if shape_array.size and np.issubdtype(shape_array.dtype, np.bool_): raise TypeError("shape dimensions must be integers") return tuple( - _pytorch_creation_dimension(dimension, np) - for dimension in shape_array.tolist() + _pytorch_creation_dimension(dimension, np) for dimension in shape_array.tolist() ) @@ -444,7 +462,9 @@ def _wrap_creation_shape_helper(original_helper, torch, np): return original_helper def creation_helper(shape, *args, **kwargs): - return original_helper(_pytorch_creation_shape(shape, torch, np), *args, **kwargs) + return original_helper( + _pytorch_creation_shape(shape, torch, np), *args, **kwargs + ) creation_helper.__name__ = getattr(original_helper, "__name__", "creation_helper") creation_helper.__doc__ = getattr(original_helper, "__doc__", None) @@ -452,11 +472,17 @@ def creation_helper(shape, *args, **kwargs): return creation_helper -def _patch_pytorch_creation_bool_shape_contract(raw_pytorch, backend, torch, np) -> None: +def _patch_pytorch_creation_bool_shape_contract( + raw_pytorch, backend, torch, np +) -> None: """Reject boolean creation shapes before PyTorch interprets them as integers.""" helper_names = ("empty", "zeros", "ones", "full") if all( - getattr(getattr(raw_pytorch, helper_name, None), "_pyrecest_bool_shape_contract", False) + getattr( + getattr(raw_pytorch, helper_name, None), + "_pyrecest_bool_shape_contract", + False, + ) for helper_name in helper_names ): if getattr(backend, "__backend_name__", None) == "pytorch": @@ -505,7 +531,9 @@ def _wrap_argsort_arraylike_helper(original_argsort, raw_pytorch, torch): if getattr(original_argsort, "_pyrecest_arraylike_contract", False): return original_argsort - def argsort(input, axis=-1, descending=False, stable=False, *, dim=None): # pylint: disable=redefined-builtin + def argsort( + input, axis=-1, descending=False, stable=False, *, dim=None + ): # pylint: disable=redefined-builtin if dim is not None: if axis != -1 and axis != dim: raise TypeError("argsort() got both 'axis' and 'dim'") @@ -537,7 +565,11 @@ def _patch_pytorch_arraylike_helper_contract(raw_pytorch, backend, torch) -> Non ) all_helper_names = (*helper_names, "argsort") if all( - getattr(getattr(raw_pytorch, helper_name, None), "_pyrecest_arraylike_contract", False) + getattr( + getattr(raw_pytorch, helper_name, None), + "_pyrecest_arraylike_contract", + False, + ) for helper_name in all_helper_names ): if getattr(backend, "__backend_name__", None) == "pytorch": @@ -555,7 +587,9 @@ def _patch_pytorch_arraylike_helper_contract(raw_pytorch, backend, torch) -> Non if getattr(backend, "__backend_name__", None) == "pytorch": setattr(backend, helper_name, wrapped_helper) - wrapped_argsort = _wrap_argsort_arraylike_helper(raw_pytorch.argsort, raw_pytorch, torch) + wrapped_argsort = _wrap_argsort_arraylike_helper( + raw_pytorch.argsort, raw_pytorch, torch + ) raw_pytorch.argsort = wrapped_argsort if getattr(backend, "__backend_name__", None) == "pytorch": backend.argsort = wrapped_argsort diff --git a/src/pyrecest/backend_tools.py b/src/pyrecest/backend_tools.py index 521142b34..fb6973b1b 100644 --- a/src/pyrecest/backend_tools.py +++ b/src/pyrecest/backend_tools.py @@ -313,7 +313,9 @@ def _patch_pytorch_stack_helpers_numpy_contract() -> None: helper_names = ("stack", "hstack", "vstack", "column_stack", "dstack") if all( - getattr(getattr(pytorch_backend, helper_name), "_pyrecest_numpy_contract", False) + getattr( + getattr(pytorch_backend, helper_name), "_pyrecest_numpy_contract", False + ) for helper_name in helper_names ): if active_pytorch_backend: diff --git a/src/pyrecest/calibration/time_offset.py b/src/pyrecest/calibration/time_offset.py index 875978096..d29c0593e 100644 --- a/src/pyrecest/calibration/time_offset.py +++ b/src/pyrecest/calibration/time_offset.py @@ -34,8 +34,14 @@ class TimeOffsetFitResult: metadata: Mapping[str, Any] = field(default_factory=dict) def summary(self) -> dict[str, Any]: - out: dict[str, Any] = {"metric": self.metric, "best_offset_s": self.best_offset_s, "evaluated_offsets": int(len(self.offsets_s))} - best_index = _best_metric_index(self.offsets_s, self.metric_values, self.counts, self.best_offset_s) + out: dict[str, Any] = { + "metric": self.metric, + "best_offset_s": self.best_offset_s, + "evaluated_offsets": int(len(self.offsets_s)), + } + best_index = _best_metric_index( + self.offsets_s, self.metric_values, self.counts, self.best_offset_s + ) if best_index is not None: out["best_metric_value"] = float(self.metric_values[best_index]) out["best_count"] = int(self.counts[best_index]) @@ -43,7 +49,12 @@ def summary(self) -> dict[str, Any]: return out -def _best_metric_index(offsets_s: np.ndarray, metric_values: np.ndarray, counts: np.ndarray, best_offset_s: float | None) -> int | None: +def _best_metric_index( + offsets_s: np.ndarray, + metric_values: np.ndarray, + counts: np.ndarray, + best_offset_s: float | None, +) -> int | None: if best_offset_s is None: return None offsets = np.asarray(offsets_s, dtype=float).reshape(-1) @@ -143,7 +154,11 @@ def _as_nonnegative_time_delta(value: Any, name: str) -> float: def _as_real_numeric_array(value: Any, name: str) -> np.ndarray: arr = np.asarray(value) - if arr.dtype == np.bool_ or arr.dtype.kind in "OUSbc" or _contains_temporal_values(arr): + if ( + arr.dtype == np.bool_ + or arr.dtype.kind in "OUSbc" + or _contains_temporal_values(arr) + ): raise ValueError(f"{name} must contain real numeric values") try: return np.asarray(value, dtype=float) @@ -206,11 +221,19 @@ def apply_time_offset(times_s: np.ndarray, offset_s: float | None) -> np.ndarray def _validate_max_time_delta(max_time_delta_s: float | None) -> float | None: - return None if max_time_delta_s is None else _as_nonnegative_time_delta(max_time_delta_s, "max_time_delta_s") - - -def _finite_reference_rows(reference_times_s: np.ndarray, reference_values: np.ndarray | None = None) -> np.ndarray: - reference_times = _as_real_numeric_array(reference_times_s, "reference_times_s").reshape(-1) + return ( + None + if max_time_delta_s is None + else _as_nonnegative_time_delta(max_time_delta_s, "max_time_delta_s") + ) + + +def _finite_reference_rows( + reference_times_s: np.ndarray, reference_values: np.ndarray | None = None +) -> np.ndarray: + reference_times = _as_real_numeric_array( + reference_times_s, "reference_times_s" + ).reshape(-1) finite = np.isfinite(reference_times) if reference_values is not None: values = _as_real_numeric_array(reference_values, "reference_values") @@ -220,8 +243,12 @@ def _finite_reference_rows(reference_times_s: np.ndarray, reference_values: np.n return finite -def nearest_time_indices(reference_times_s: np.ndarray, query_times_s: np.ndarray) -> np.ndarray: - reference = _as_real_numeric_array(reference_times_s, "reference_times_s").reshape(-1) +def nearest_time_indices( + reference_times_s: np.ndarray, query_times_s: np.ndarray +) -> np.ndarray: + reference = _as_real_numeric_array(reference_times_s, "reference_times_s").reshape( + -1 + ) query = _as_real_numeric_array(query_times_s, "query_times_s").reshape(-1) finite_reference = _finite_reference_rows(reference) if not finite_reference.any(): @@ -238,14 +265,24 @@ def nearest_time_indices(reference_times_s: np.ndarray, query_times_s: np.ndarra insertion = np.searchsorted(sorted_reference, finite_query_values) right = np.clip(insertion, 0, sorted_reference.size - 1) left = np.clip(insertion - 1, 0, sorted_reference.size - 1) - use_right = np.abs(sorted_reference[right] - finite_query_values) < np.abs(sorted_reference[left] - finite_query_values) + use_right = np.abs(sorted_reference[right] - finite_query_values) < np.abs( + sorted_reference[left] - finite_query_values + ) nearest[finite_query] = original_indices[order[np.where(use_right, right, left)]] return nearest -def interpolate_reference_values(reference_times_s: np.ndarray, reference_values: np.ndarray, query_times_s: np.ndarray, *, max_time_delta_s: float | None = None) -> tuple[np.ndarray, np.ndarray]: +def interpolate_reference_values( + reference_times_s: np.ndarray, + reference_values: np.ndarray, + query_times_s: np.ndarray, + *, + max_time_delta_s: float | None = None, +) -> tuple[np.ndarray, np.ndarray]: max_time_delta = _validate_max_time_delta(max_time_delta_s) - reference_times = _as_real_numeric_array(reference_times_s, "reference_times_s").reshape(-1) + reference_times = _as_real_numeric_array( + reference_times_s, "reference_times_s" + ).reshape(-1) reference_values = _as_real_numeric_array(reference_values, "reference_values") query_times = _as_real_numeric_array(query_times_s, "query_times_s").reshape(-1) if reference_values.ndim not in (1, 2): @@ -256,14 +293,25 @@ def interpolate_reference_values(reference_times_s: np.ndarray, reference_values raise ValueError("reference_times_s length must match reference_values rows") finite_reference = _finite_reference_rows(reference_times, reference_values) if np.count_nonzero(finite_reference) < 2: - raise ValueError("at least two finite reference rows are required for interpolation") + raise ValueError( + "at least two finite reference rows are required for interpolation" + ) reference_times = reference_times[finite_reference] reference_values = reference_values[finite_reference] order = np.argsort(reference_times) reference_times = reference_times[order] reference_values = reference_values[order] - interpolated = np.column_stack([np.interp(query_times, reference_times, reference_values[:, dim]) for dim in range(reference_values.shape[1])]) - valid = np.isfinite(query_times) & (query_times >= reference_times[0]) & (query_times <= reference_times[-1]) + interpolated = np.column_stack( + [ + np.interp(query_times, reference_times, reference_values[:, dim]) + for dim in range(reference_values.shape[1]) + ] + ) + valid = ( + np.isfinite(query_times) + & (query_times >= reference_times[0]) + & (query_times <= reference_times[-1]) + ) if max_time_delta is not None: nearest = nearest_time_indices(reference_times, query_times) valid &= np.abs(reference_times[nearest] - query_times) <= max_time_delta @@ -271,40 +319,110 @@ def interpolate_reference_values(reference_times_s: np.ndarray, reference_values return interpolated, valid -def time_offset_error_summary(measurement_times_s: np.ndarray, measurement_values: np.ndarray, reference_times_s: np.ndarray, reference_values: np.ndarray, offset_s: float | None, *, max_time_delta_s: float | None = None) -> dict[str, float]: +def time_offset_error_summary( + measurement_times_s: np.ndarray, + measurement_values: np.ndarray, + reference_times_s: np.ndarray, + reference_values: np.ndarray, + offset_s: float | None, + *, + max_time_delta_s: float | None = None, +) -> dict[str, float]: offset = _validate_time_offset(offset_s) - measurement_values = _as_real_numeric_array(measurement_values, "measurement_values") + measurement_values = _as_real_numeric_array( + measurement_values, "measurement_values" + ) if measurement_values.ndim == 1: measurement_values = measurement_values.reshape(-1, 1) elif measurement_values.ndim != 2: raise ValueError("measurement_values must be one- or two-dimensional") query_times = apply_time_offset(measurement_times_s, offset) if query_times.size != measurement_values.shape[0]: - raise ValueError("measurement_times_s length must match measurement_values rows") - reference_at_query, valid = interpolate_reference_values(reference_times_s, reference_values, query_times, max_time_delta_s=max_time_delta_s) + raise ValueError( + "measurement_times_s length must match measurement_values rows" + ) + reference_at_query, valid = interpolate_reference_values( + reference_times_s, + reference_values, + query_times, + max_time_delta_s=max_time_delta_s, + ) if measurement_values.shape[1] != reference_at_query.shape[1]: - raise ValueError("measurement_values and reference_values must have the same value dimension") + raise ValueError( + "measurement_values and reference_values must have the same value dimension" + ) valid &= np.isfinite(measurement_values).all(axis=1) - errors = np.linalg.norm(measurement_values[valid] - reference_at_query[valid], axis=1) + errors = np.linalg.norm( + measurement_values[valid] - reference_at_query[valid], axis=1 + ) return _error_stats(offset, errors, total_count=len(measurement_values)) -def time_offset_sweep(measurement_times_s: np.ndarray, measurement_values: np.ndarray, reference_times_s: np.ndarray, reference_values: np.ndarray, offsets_s: Iterable[float | None], *, max_time_delta_s: float | None = None) -> list[dict[str, float]]: - return [time_offset_error_summary(measurement_times_s, measurement_values, reference_times_s, reference_values, offset, max_time_delta_s=max_time_delta_s) for offset in offsets_s] - - -def fit_time_offset(measurement_times_s: np.ndarray, measurement_values: np.ndarray, reference_times_s: np.ndarray, reference_values: np.ndarray, offsets_s: Iterable[float | None], *, metric: str = "rmse", max_time_delta_s: float | None = None, metadata: Mapping[str, Any] | None = None) -> TimeOffsetFitResult: +def time_offset_sweep( + measurement_times_s: np.ndarray, + measurement_values: np.ndarray, + reference_times_s: np.ndarray, + reference_values: np.ndarray, + offsets_s: Iterable[float | None], + *, + max_time_delta_s: float | None = None, +) -> list[dict[str, float]]: + return [ + time_offset_error_summary( + measurement_times_s, + measurement_values, + reference_times_s, + reference_values, + offset, + max_time_delta_s=max_time_delta_s, + ) + for offset in offsets_s + ] + + +def fit_time_offset( + measurement_times_s: np.ndarray, + measurement_values: np.ndarray, + reference_times_s: np.ndarray, + reference_values: np.ndarray, + offsets_s: Iterable[float | None], + *, + metric: str = "rmse", + max_time_delta_s: float | None = None, + metadata: Mapping[str, Any] | None = None, +) -> TimeOffsetFitResult: metric = _validate_error_metric(metric) - summaries = time_offset_sweep(measurement_times_s, measurement_values, reference_times_s, reference_values, offsets_s, max_time_delta_s=max_time_delta_s) + summaries = time_offset_sweep( + measurement_times_s, + measurement_values, + reference_times_s, + reference_values, + offsets_s, + max_time_delta_s=max_time_delta_s, + ) offsets = np.array([row["time_offset_s"] for row in summaries], dtype=float) values = np.array([row[metric] for row in summaries], dtype=float) counts = np.array([row.get("count", 0.0) for row in summaries], dtype=float) finite = np.isfinite(values) & (counts > 0) - best = None if not finite.any() else float(offsets[np.where(finite)[0][int(np.nanargmin(values[finite]))]]) - return TimeOffsetFitResult(best_offset_s=best, metric=metric, offsets_s=offsets, metric_values=values, counts=counts.astype(int), summaries=summaries, metadata={} if metadata is None else dict(metadata)) - - -def aggregate_time_offset_sweeps(sweeps: Iterable[Iterable[Mapping[str, float]]], *, metric: str = "rmse") -> list[dict[str, float]]: + best = ( + None + if not finite.any() + else float(offsets[np.where(finite)[0][int(np.nanargmin(values[finite]))]]) + ) + return TimeOffsetFitResult( + best_offset_s=best, + metric=metric, + offsets_s=offsets, + metric_values=values, + counts=counts.astype(int), + summaries=summaries, + metadata={} if metadata is None else dict(metadata), + ) + + +def aggregate_time_offset_sweeps( + sweeps: Iterable[Iterable[Mapping[str, float]]], *, metric: str = "rmse" +) -> list[dict[str, float]]: metric = _validate_error_metric(metric) by_offset: dict[float, list[Mapping[str, float]]] = {} for sweep in sweeps: @@ -313,12 +431,32 @@ def aggregate_time_offset_sweeps(sweeps: Iterable[Iterable[Mapping[str, float]]] by_offset.setdefault(offset, []).append(row) rows: list[dict[str, float]] = [] for offset, parts in sorted(by_offset.items()): - counts = np.array([_as_nonnegative_summary_count(part.get("count", 0.0), "count") for part in parts], dtype=float) + counts = np.array( + [ + _as_nonnegative_summary_count(part.get("count", 0.0), "count") + for part in parts + ], + dtype=float, + ) row = {"time_offset_s": float(offset), "count": float(np.sum(counts))} for key in dict.fromkeys(("mean", "std", "rmse", "p95", "max", metric)): - values = np.array([_as_summary_scalar(part.get(key, np.nan), str(key), allow_nan=True) for part in parts], dtype=float) + values = np.array( + [ + _as_summary_scalar(part.get(key, np.nan), str(key), allow_nan=True) + for part in parts + ], + dtype=float, + ) if key == "std": - means = np.array([_as_summary_scalar(part.get("mean", np.nan), "mean", allow_nan=True) for part in parts], dtype=float) + means = np.array( + [ + _as_summary_scalar( + part.get("mean", np.nan), "mean", allow_nan=True + ) + for part in parts + ], + dtype=float, + ) row[key] = _aggregate_std_metric(values, means, counts) else: row[key] = _aggregate_summary_metric(key, values, counts) @@ -326,7 +464,9 @@ def aggregate_time_offset_sweeps(sweeps: Iterable[Iterable[Mapping[str, float]]] return rows -def _aggregate_summary_metric(key: str, values: np.ndarray, counts: np.ndarray) -> float: +def _aggregate_summary_metric( + key: str, values: np.ndarray, counts: np.ndarray +) -> float: valid = np.isfinite(values) & (counts > 0.0) if not valid.any(): return float("nan") @@ -337,22 +477,58 @@ def _aggregate_summary_metric(key: str, values: np.ndarray, counts: np.ndarray) return float(np.average(values[valid], weights=counts[valid])) -def _aggregate_std_metric(stds: np.ndarray, means: np.ndarray, counts: np.ndarray) -> float: +def _aggregate_std_metric( + stds: np.ndarray, means: np.ndarray, counts: np.ndarray +) -> float: valid = np.isfinite(stds) & np.isfinite(means) & (counts > 0.0) if not valid.any(): return float("nan") weights = counts[valid] pooled_mean = float(np.average(means[valid], weights=weights)) - second_moment = float(np.average(stds[valid] ** 2 + means[valid] ** 2, weights=weights)) + second_moment = float( + np.average(stds[valid] ** 2 + means[valid] ** 2, weights=weights) + ) return float(np.sqrt(max(0.0, second_moment - pooled_mean**2))) -def _error_stats(offset_s: float, errors: np.ndarray, *, total_count: int) -> dict[str, float]: +def _error_stats( + offset_s: float, errors: np.ndarray, *, total_count: int +) -> dict[str, float]: errors = np.asarray(errors, dtype=float).reshape(-1) errors = errors[np.isfinite(errors)] if errors.size == 0: - return {"time_offset_s": float(offset_s), "count": 0.0, "coverage": 0.0 if total_count else float("nan"), "mean": float("nan"), "std": float("nan"), "rmse": float("nan"), "p95": float("nan"), "max": float("nan")} - return {"time_offset_s": float(offset_s), "count": float(errors.size), "coverage": float(errors.size / total_count) if total_count > 0 else float("nan"), "mean": float(np.mean(errors)), "std": float(np.std(errors)), "rmse": float(np.sqrt(np.mean(errors**2))), "p95": float(np.percentile(errors, 95)), "max": float(np.max(errors))} - - -__all__ = ["TimeOffsetFitResult", "aggregate_time_offset_sweeps", "apply_time_offset", "fit_time_offset", "interpolate_reference_values", "make_offset_grid", "nearest_time_indices", "time_offset_error_summary", "time_offset_sweep"] + return { + "time_offset_s": float(offset_s), + "count": 0.0, + "coverage": 0.0 if total_count else float("nan"), + "mean": float("nan"), + "std": float("nan"), + "rmse": float("nan"), + "p95": float("nan"), + "max": float("nan"), + } + return { + "time_offset_s": float(offset_s), + "count": float(errors.size), + "coverage": ( + float(errors.size / total_count) if total_count > 0 else float("nan") + ), + "mean": float(np.mean(errors)), + "std": float(np.std(errors)), + "rmse": float(np.sqrt(np.mean(errors**2))), + "p95": float(np.percentile(errors, 95)), + "max": float(np.max(errors)), + } + + +__all__ = [ + "TimeOffsetFitResult", + "aggregate_time_offset_sweeps", + "apply_time_offset", + "fit_time_offset", + "interpolate_reference_values", + "make_offset_grid", + "nearest_time_indices", + "time_offset_error_summary", + "time_offset_sweep", +] diff --git a/src/pyrecest/cli.py b/src/pyrecest/cli.py index 897f12e2f..255cb03e5 100644 --- a/src/pyrecest/cli.py +++ b/src/pyrecest/cli.py @@ -44,7 +44,10 @@ def _validate_tolerance(value: Any) -> float: except (TypeError, ValueError, RuntimeError) as exc: raise ValueError(_INVALID_TOLERANCE_MESSAGE) from exc - if value_array.shape != () or value_array.dtype.kind in _INVALID_NUMERIC_DTYPE_KINDS: + if ( + value_array.shape != () + or value_array.dtype.kind in _INVALID_NUMERIC_DTYPE_KINDS + ): raise ValueError(_INVALID_TOLERANCE_MESSAGE) scalar = value_array.item() @@ -229,7 +232,9 @@ def _cmd_run_scenario(args: argparse.Namespace) -> int: f"expected {len(expected_estimate_values)}, got {len(actual_estimate_values)}" ) else: - max_error = _max_abs_error(actual_estimate_values, expected_estimate_values) + max_error = _max_abs_error( + actual_estimate_values, expected_estimate_values + ) if max_error > tolerance: failures.append( f"final_estimate mismatch: max_abs_error={max_error:.6g} > tolerance={tolerance:.6g}" diff --git a/src/pyrecest/distributions/__init__.py b/src/pyrecest/distributions/__init__.py index 000eaecff..7b7c04eaa 100644 --- a/src/pyrecest/distributions/__init__.py +++ b/src/pyrecest/distributions/__init__.py @@ -222,13 +222,13 @@ CustomHypertoroidalDistribution, ) from .hypertorus.custom_toroidal_distribution import CustomToroidalDistribution +from .hypertorus.fejer_hypertoroidal_fourier_distribution import ( + FejerHypertoroidalFourierDistribution, +) from .hypertorus.hypertoroidal_dirac_distribution import HypertoroidalDiracDistribution from .hypertorus.hypertoroidal_fourier_distribution import ( HypertoroidalFourierDistribution, ) -from .hypertorus.fejer_hypertoroidal_fourier_distribution import ( - FejerHypertoroidalFourierDistribution, -) from .hypertorus.hypertoroidal_grid_distribution import HypertoroidalGridDistribution from .hypertorus.hypertoroidal_mixture import HypertoroidalMixture from .hypertorus.hypertoroidal_uniform_distribution import ( diff --git a/src/pyrecest/distributions/abstract_dirac_distribution.py b/src/pyrecest/distributions/abstract_dirac_distribution.py index 833a51295..e19a4b081 100644 --- a/src/pyrecest/distributions/abstract_dirac_distribution.py +++ b/src/pyrecest/distributions/abstract_dirac_distribution.py @@ -13,7 +13,9 @@ arange, argmax, asarray, - copy as backend_copy, +) +from pyrecest.backend import copy as backend_copy +from pyrecest.backend import ( int32, int64, isclose, diff --git a/src/pyrecest/distributions/abstract_mixture.py b/src/pyrecest/distributions/abstract_mixture.py index 7e0f794a4..248b7612b 100644 --- a/src/pyrecest/distributions/abstract_mixture.py +++ b/src/pyrecest/distributions/abstract_mixture.py @@ -144,7 +144,9 @@ def __init__( raise ValueError("At least one mixture weight must be nonzero") weight_sum = sum(weights) - if not bool(pyrecest.backend.isfinite(weight_sum)) or not bool(weight_sum > 0.0): + if not bool(pyrecest.backend.isfinite(weight_sum)) or not bool( + weight_sum > 0.0 + ): raise ValueError("Mixture weights must have positive finite total mass") if len(non_zero_indices) < len(weights): diff --git a/src/pyrecest/distributions/circle/circular_grid_distribution.py b/src/pyrecest/distributions/circle/circular_grid_distribution.py index adedfb6ad..673a90288 100644 --- a/src/pyrecest/distributions/circle/circular_grid_distribution.py +++ b/src/pyrecest/distributions/circle/circular_grid_distribution.py @@ -53,8 +53,7 @@ def __init__(self, grid_values, enforce_pdf_nonnegative=True): ) if enforce_pdf_nonnegative and any(grid_values < 0): raise ValueError( - "grid_values must be nonnegative when " - "enforce_pdf_nonnegative=True." + "grid_values must be nonnegative when " "enforce_pdf_nonnegative=True." ) n = grid_values.shape[0] grid = linspace(0.0, 2.0 * pi, n, endpoint=False) diff --git a/src/pyrecest/distributions/circle/piecewise_constant_distribution.py b/src/pyrecest/distributions/circle/piecewise_constant_distribution.py index 4712348d3..617b4b446 100644 --- a/src/pyrecest/distributions/circle/piecewise_constant_distribution.py +++ b/src/pyrecest/distributions/circle/piecewise_constant_distribution.py @@ -20,7 +20,6 @@ from .abstract_circular_distribution import AbstractCircularDistribution - _INVALID_SAMPLE_COUNT_TYPES = ( bool, np.bool_, diff --git a/src/pyrecest/distributions/circle/sine_skewed_distributions.py b/src/pyrecest/distributions/circle/sine_skewed_distributions.py index c7659e5cc..125110ca1 100644 --- a/src/pyrecest/distributions/circle/sine_skewed_distributions.py +++ b/src/pyrecest/distributions/circle/sine_skewed_distributions.py @@ -255,10 +255,7 @@ def pdf(self, xs): rho = exp(-self.gamma) one_minus_rho = 1.0 - rho numerator = one_minus_rho * (1.0 + rho) - denominator = ( - one_minus_rho**2 - + 4.0 * rho * sin((xs - self.mu) / 2.0) ** 2 - ) + denominator = one_minus_rho**2 + 4.0 * rho * sin((xs - self.mu) / 2.0) ** 2 wc_pdf_vals = numerator / (2.0 * pi * denominator) skew_factor = (1 + self.lambda_ * sin(self.k * (xs - self.mu))) ** self.m diff --git a/src/pyrecest/distributions/circle/wrapped_cauchy_distribution.py b/src/pyrecest/distributions/circle/wrapped_cauchy_distribution.py index 77db56097..41232fc49 100644 --- a/src/pyrecest/distributions/circle/wrapped_cauchy_distribution.py +++ b/src/pyrecest/distributions/circle/wrapped_cauchy_distribution.py @@ -59,9 +59,7 @@ def pdf(self, xs): rho = exp(-self.gamma) one_minus_rho = 1.0 - rho numerator = one_minus_rho * (1.0 + rho) - denominator = ( - one_minus_rho**2 + 4.0 * rho * sin(xs_centered / 2.0) ** 2 - ) + denominator = one_minus_rho**2 + 4.0 * rho * sin(xs_centered / 2.0) ** 2 return numerator / (2.0 * pi * denominator) def cdf(self, xs, starting_point=0.0): diff --git a/src/pyrecest/distributions/circle/wrapped_laplace_distribution.py b/src/pyrecest/distributions/circle/wrapped_laplace_distribution.py index b883e4494..55692d7fc 100644 --- a/src/pyrecest/distributions/circle/wrapped_laplace_distribution.py +++ b/src/pyrecest/distributions/circle/wrapped_laplace_distribution.py @@ -51,8 +51,7 @@ def pdf(self, xs): * self.kappa / (1 + self.kappa**2) * ( - exp(-positive_rate * xs) - / (1 - exp(-2.0 * pi * positive_rate)) + exp(-positive_rate * xs) / (1 - exp(-2.0 * pi * positive_rate)) + exp(-negative_rate * (2.0 * pi - xs)) / (1 - exp(-2.0 * pi * negative_rate)) ) diff --git a/src/pyrecest/distributions/hypersphere_subset/hyperspherical_uniform_distribution.py b/src/pyrecest/distributions/hypersphere_subset/hyperspherical_uniform_distribution.py index 13eebb508..e7aa7dc6a 100644 --- a/src/pyrecest/distributions/hypersphere_subset/hyperspherical_uniform_distribution.py +++ b/src/pyrecest/distributions/hypersphere_subset/hyperspherical_uniform_distribution.py @@ -11,7 +11,9 @@ linalg, ones, pi, - random as backend_random, +) +from pyrecest.backend import random as backend_random +from pyrecest.backend import ( sin, sqrt, stack, diff --git a/src/pyrecest/distributions/hypersphere_subset/von_mises_fisher_distribution.py b/src/pyrecest/distributions/hypersphere_subset/von_mises_fisher_distribution.py index e48129ba9..671a30eee 100644 --- a/src/pyrecest/distributions/hypersphere_subset/von_mises_fisher_distribution.py +++ b/src/pyrecest/distributions/hypersphere_subset/von_mises_fisher_distribution.py @@ -126,9 +126,7 @@ def __init__(self, mu, kappa): self._log_scaled_normalization = _scaled_log_normalization( self.input_dim, kappa_scalar ) - self.C = array( - math.exp(self._log_scaled_normalization - kappa_scalar) - ) + self.C = array(math.exp(self._log_scaled_normalization - kappa_scalar)) def pdf(self, xs): """Evaluate the density at unit vectors. @@ -145,10 +143,7 @@ def pdf(self, xs): f"xs must have trailing dimension {self.input_dim}, got {xs.shape}." ) - return exp( - self._log_scaled_normalization - + self.kappa * (xs @ self.mu - 1.0) - ) + return exp(self._log_scaled_normalization + self.kappa * (xs @ self.mu - 1.0)) def mean_direction(self): """Return the unit mean direction with shape ``(d,)``.""" diff --git a/src/pyrecest/distributions/hypertorus/_tensor_train.py b/src/pyrecest/distributions/hypertorus/_tensor_train.py index 7ae98a103..f74a684fc 100644 --- a/src/pyrecest/distributions/hypertorus/_tensor_train.py +++ b/src/pyrecest/distributions/hypertorus/_tensor_train.py @@ -61,7 +61,9 @@ def _choose_rank(singular_values, max_rank, local_tolerance): squared_tail = np.cumsum(singular_values[::-1] ** 2)[::-1] rank = full_rank for candidate in range(1, full_rank + 1): - tail = 0.0 if candidate == full_rank else sqrt(float(squared_tail[candidate])) + tail = ( + 0.0 if candidate == full_rank else sqrt(float(squared_tail[candidate])) + ) if tail <= local_tolerance: rank = candidate break @@ -95,7 +97,9 @@ def __init__(self, cores): raise ValueError("At least one TT core is required.") for core in checked: if core.ndim != 3: - raise ValueError("TT cores must have shape (left_rank, mode_size, right_rank).") + raise ValueError( + "TT cores must have shape (left_rank, mode_size, right_rank)." + ) if checked[0].shape[0] != 1 or checked[-1].shape[2] != 1: raise ValueError("Boundary TT ranks must be one.") for left, right in zip(checked, checked[1:]): @@ -138,7 +142,9 @@ def from_dense(cls, tensor, *, max_rank=None, rtol=0.0, atol=0.0): norm = float(np.linalg.norm(array.ravel())) global_tolerance = max(atol, rtol * norm) - local_tolerance = global_tolerance / sqrt(array.ndim - 1) if global_tolerance > 0 else 0.0 + local_tolerance = ( + global_tolerance / sqrt(array.ndim - 1) if global_tolerance > 0 else 0.0 + ) cores = [] unfolding = array @@ -175,7 +181,9 @@ def entry(self, multi_index): def norm_squared(self): environment = np.ones((1, 1), dtype=np.complex128) for core in self.cores: - next_environment = np.zeros((core.shape[2], core.shape[2]), dtype=np.complex128) + next_environment = np.zeros( + (core.shape[2], core.shape[2]), dtype=np.complex128 + ) for mode_index in range(core.shape[1]): core_slice = core[:, mode_index, :] next_environment += core_slice.conj().T @ environment @ core_slice @@ -197,7 +205,9 @@ def multiply_axis_factors(self, factors): for core, factor in zip(self.cores, factors): vector = np.asarray(factor, dtype=np.complex128) if vector.shape != (core.shape[1],): - raise ValueError("Each factor vector must match the corresponding mode size.") + raise ValueError( + "Each factor vector must match the corresponding mode size." + ) cores.append(core * vector[None, :, None]) return TensorTrain(cores) @@ -218,14 +228,18 @@ def hadamard_product(self, other): def coefficient_convolution(self, other, *, target_shape=None): if self.ndim != other.ndim: - raise ValueError("Convolution operands must have the same number of dimensions.") + raise ValueError( + "Convolution operands must have the same number of dimensions." + ) if target_shape is None: target_shape = self.shape if len(target_shape) != self.ndim: raise ValueError("target_shape must contain one mode size per dimension.") cores = [ _convolve_cores_centered(left_core, right_core, int(mode_size)) - for left_core, right_core, mode_size in zip(self.cores, other.cores, target_shape) + for left_core, right_core, mode_size in zip( + self.cores, other.cores, target_shape + ) ] return TensorTrain(cores) @@ -278,7 +292,9 @@ def round(self, *, max_rank=None, rtol=0.0, atol=0.0, max_dense_entries=None): norm = self.norm() global_tolerance = max(atol, rtol * norm) - local_tolerance = global_tolerance / sqrt(self.ndim - 1) if global_tolerance > 0 else 0.0 + local_tolerance = ( + global_tolerance / sqrt(self.ndim - 1) if global_tolerance > 0 else 0.0 + ) cores = [core.copy() for core in self.cores] @@ -293,9 +309,7 @@ def round(self, *, max_rank=None, rtol=0.0, atol=0.0, max_dense_entries=None): new_left_rank = q_trans.shape[1] cores[axis] = q_trans.T.reshape(new_left_rank, mode_size, right_rank) transfer = r_trans.T - cores[axis - 1] = np.tensordot( - cores[axis - 1], transfer, axes=([2], [0]) - ) + cores[axis - 1] = np.tensordot(cores[axis - 1], transfer, axes=([2], [0])) # Sweep left to right, truncate every bond, and absorb singular values # into the following core. diff --git a/src/pyrecest/distributions/hypertorus/fejer.py b/src/pyrecest/distributions/hypertorus/fejer.py index 09b112f87..4bcef5a70 100644 --- a/src/pyrecest/distributions/hypertorus/fejer.py +++ b/src/pyrecest/distributions/hypertorus/fejer.py @@ -7,12 +7,16 @@ import numpy as np - CoefficientShape = int | Iterable[int] ReductionKernel = Literal["sharp", "none", "fejer", "korovkin", "fejer-korovkin", "fk"] -def normalize_coefficient_shape(shape_like: CoefficientShape, *, dim: int | None = None, name: str = "n_coefficients") -> tuple[int, ...]: +def normalize_coefficient_shape( + shape_like: CoefficientShape, + *, + dim: int | None = None, + name: str = "n_coefficients", +) -> tuple[int, ...]: """Validate and normalize a centered Fourier coefficient shape. PyRecEst's hypertoroidal Fourier coefficients use odd side lengths so that @@ -65,7 +69,9 @@ def normalize_kernel_name(kernel: str) -> str: return "fejer" if normalized in ("korovkin", "fejer-korovkin", "fk"): return "korovkin" - raise ValueError(f"Unsupported reduction kernel {kernel!r}. Use 'sharp', 'fejer', or 'korovkin'.") + raise ValueError( + f"Unsupported reduction kernel {kernel!r}. Use 'sharp', 'fejer', or 'korovkin'." + ) def centered_coefficients(coefficients, target_shape: CoefficientShape): @@ -80,8 +86,12 @@ def centered_coefficients(coefficients, target_shape: CoefficientShape): """ coeff_arr = np.asarray(coefficients) - target_shape = normalize_coefficient_shape(target_shape, dim=coeff_arr.ndim, name="target_shape") - current_shape = normalize_coefficient_shape(coeff_arr.shape, dim=coeff_arr.ndim, name="coefficients.shape") + target_shape = normalize_coefficient_shape( + target_shape, dim=coeff_arr.ndim, name="target_shape" + ) + current_shape = normalize_coefficient_shape( + coeff_arr.shape, dim=coeff_arr.ndim, name="coefficients.shape" + ) if current_shape == target_shape: return coeff_arr.copy() @@ -100,7 +110,9 @@ def centered_coefficients(coefficients, target_shape: CoefficientShape): return result -def _product_weights(shape: tuple[int, ...], one_dimensional_weight_factory, *, dtype=float): +def _product_weights( + shape: tuple[int, ...], one_dimensional_weight_factory, *, dtype=float +): weights = np.ones(shape, dtype=dtype) for axis, side_length in enumerate(shape): one_dim = one_dimensional_weight_factory(side_length, dtype=dtype) @@ -137,7 +149,10 @@ def _korovkin_weights_1d(side_length: int, *, dtype=float): abs_ks = np.abs(np.arange(-order, order + 1, dtype=dtype)) angle = np.pi / (order + 2.0) - weights = ((order + 1.0 - abs_ks) * np.cos(abs_ks * angle) + np.sin((abs_ks + 1.0) * angle) / np.sin(angle)) / (order + 2.0) + weights = ( + (order + 1.0 - abs_ks) * np.cos(abs_ks * angle) + + np.sin((abs_ks + 1.0) * angle) / np.sin(angle) + ) / (order + 2.0) # Remove harmless floating-point drift at the zero-frequency coefficient. weights[order] = 1.0 @@ -172,7 +187,9 @@ def korovkin_weights(shape: CoefficientShape, *, dtype=float): return _product_weights(shape, _korovkin_weights_1d, dtype=dtype) -def positive_kernel_weights(shape: CoefficientShape, *, kernel: str = "fejer", dtype=float): +def positive_kernel_weights( + shape: CoefficientShape, *, kernel: str = "fejer", dtype=float +): """Return tensor-product weights for a supported coefficient-reduction kernel.""" kernel = normalize_kernel_name(kernel) @@ -198,7 +215,9 @@ def apply_kernel_weights(coefficients, *, kernel: str = "fejer", exponent: float if exponent < 0.0: raise ValueError("exponent must be nonnegative.") coeff_arr = np.asarray(coefficients) - normalize_coefficient_shape(coeff_arr.shape, dim=coeff_arr.ndim, name="coefficients.shape") + normalize_coefficient_shape( + coeff_arr.shape, dim=coeff_arr.ndim, name="coefficients.shape" + ) kernel = normalize_kernel_name(kernel) if kernel == "sharp" or exponent == 0.0: return coeff_arr.copy() @@ -214,7 +233,13 @@ def apply_fejer_weights(coefficients): return apply_kernel_weights(coefficients, kernel="fejer") -def reduce_coefficients(coefficients, target_shape: CoefficientShape | None = None, *, kernel: str = "fejer", exponent: float = 1.0): +def reduce_coefficients( + coefficients, + target_shape: CoefficientShape | None = None, + *, + kernel: str = "fejer", + exponent: float = 1.0, +): """Reduce centered coefficients by center alignment followed by kernel weights.""" coeff_arr = np.asarray(coefficients) @@ -224,13 +249,17 @@ def reduce_coefficients(coefficients, target_shape: CoefficientShape | None = No return apply_kernel_weights(reduced, kernel=kernel, exponent=exponent) -def fejer_reduce_coefficients(coefficients, target_shape: CoefficientShape | None = None): +def fejer_reduce_coefficients( + coefficients, target_shape: CoefficientShape | None = None +): """Reduce centered coefficients by center alignment followed by Fejer weights.""" return reduce_coefficients(coefficients, target_shape, kernel="fejer") -def coefficient_grid_shape(shape: CoefficientShape, oversampling_factor: int = 1) -> tuple[int, ...]: +def coefficient_grid_shape( + shape: CoefficientShape, oversampling_factor: int = 1 +) -> tuple[int, ...]: """Return an odd FFT-grid shape obtained by centered zero-padding.""" if isinstance(oversampling_factor, bool) or int(oversampling_factor) < 1: @@ -244,15 +273,21 @@ def values_on_fft_grid(coefficients, grid_shape: CoefficientShape | None = None) """Evaluate centered Fourier coefficients on their equidistant FFT grid.""" coeff_arr = np.asarray(coefficients) - normalize_coefficient_shape(coeff_arr.shape, dim=coeff_arr.ndim, name="coefficients.shape") + normalize_coefficient_shape( + coeff_arr.shape, dim=coeff_arr.ndim, name="coefficients.shape" + ) if grid_shape is not None: - grid_shape = normalize_coefficient_shape(grid_shape, dim=coeff_arr.ndim, name="grid_shape") + grid_shape = normalize_coefficient_shape( + grid_shape, dim=coeff_arr.ndim, name="grid_shape" + ) coeff_arr = centered_coefficients(coeff_arr, grid_shape) values = np.fft.ifftn(np.fft.ifftshift(coeff_arr)) * np.prod(coeff_arr.shape) return np.real_if_close(values, tol=1000).real -def minimum_on_fft_grid(coefficients, grid_shape: CoefficientShape | None = None) -> float: +def minimum_on_fft_grid( + coefficients, grid_shape: CoefficientShape | None = None +) -> float: """Return the minimum real value on an equidistant FFT diagnostic grid.""" return float(np.min(values_on_fft_grid(coefficients, grid_shape=grid_shape))) @@ -290,15 +325,22 @@ def adaptive_kernel_reduce_coefficients( coeff_arr = np.asarray(coefficients) if target_shape is None: target_shape = coeff_arr.shape - target_shape = normalize_coefficient_shape(target_shape, dim=coeff_arr.ndim, name="target_shape") - diagnostic_shape = coefficient_grid_shape(target_shape, oversampling_factor=oversampling_factor) + target_shape = normalize_coefficient_shape( + target_shape, dim=coeff_arr.ndim, name="target_shape" + ) + diagnostic_shape = coefficient_grid_shape( + target_shape, oversampling_factor=oversampling_factor + ) sharp = centered_coefficients(coeff_arr, target_shape) if minimum_on_fft_grid(sharp, diagnostic_shape) >= -min_value_tolerance: return (sharp, 0.0) if return_exponent else sharp full = reduce_coefficients(coeff_arr, target_shape, kernel=kernel, exponent=1.0) - if minimum_on_fft_grid(full, diagnostic_shape) < -min_value_tolerance or exponent_search_steps == 0: + if ( + minimum_on_fft_grid(full, diagnostic_shape) < -min_value_tolerance + or exponent_search_steps == 0 + ): return (full, 1.0) if return_exponent else full low = 0.0 @@ -306,7 +348,9 @@ def adaptive_kernel_reduce_coefficients( best = full for _ in range(int(exponent_search_steps)): mid = 0.5 * (low + high) - candidate = reduce_coefficients(coeff_arr, target_shape, kernel=kernel, exponent=mid) + candidate = reduce_coefficients( + coeff_arr, target_shape, kernel=kernel, exponent=mid + ) if minimum_on_fft_grid(candidate, diagnostic_shape) >= -min_value_tolerance: best = candidate high = mid diff --git a/src/pyrecest/distributions/hypertorus/fejer_hypertoroidal_fourier_distribution.py b/src/pyrecest/distributions/hypertorus/fejer_hypertoroidal_fourier_distribution.py index ef035bac7..bb194e4b4 100644 --- a/src/pyrecest/distributions/hypertorus/fejer_hypertoroidal_fourier_distribution.py +++ b/src/pyrecest/distributions/hypertorus/fejer_hypertoroidal_fourier_distribution.py @@ -8,8 +8,12 @@ import numpy as np from pyrecest.backend import signal -from pyrecest.distributions.hypertorus.abstract_hypertoroidal_distribution import AbstractHypertoroidalDistribution -from pyrecest.distributions.hypertorus.hypertoroidal_fourier_distribution import HypertoroidalFourierDistribution +from pyrecest.distributions.hypertorus.abstract_hypertoroidal_distribution import ( + AbstractHypertoroidalDistribution, +) +from pyrecest.distributions.hypertorus.hypertoroidal_fourier_distribution import ( + HypertoroidalFourierDistribution, +) from .fejer import ( adaptive_kernel_reduce_coefficients, @@ -62,10 +66,14 @@ def reduction_options(self) -> dict[str, Any]: "exponent_search_steps": self.exponent_search_steps, } - def _new_with_same_reduction(self, coeff_mat, *, reduction_exponent: float | None = None) -> "FejerHypertoroidalFourierDistribution": + def _new_with_same_reduction( + self, coeff_mat, *, reduction_exponent: float | None = None + ) -> "FejerHypertoroidalFourierDistribution": result = type(self)(coeff_mat, **self.reduction_options) result.last_reduction_exponent = reduction_exponent - result.last_used_prior_smoothing_fallback = self.last_used_prior_smoothing_fallback + result.last_used_prior_smoothing_fallback = ( + self.last_used_prior_smoothing_fallback + ) return result @classmethod @@ -86,11 +94,15 @@ def from_fourier_distribution( if not isinstance(distribution, HypertoroidalFourierDistribution): raise TypeError("distribution must be a HypertoroidalFourierDistribution.") if distribution.transformation != "identity": - raise ValueError("FejerHypertoroidalFourierDistribution requires identity coefficients.") + raise ValueError( + "FejerHypertoroidalFourierDistribution requires identity coefficients." + ) if n_coefficients is None: n_coefficients = distribution.coeff_mat.shape - n_coefficients = normalize_coefficient_shape(n_coefficients, dim=distribution.dim) + n_coefficients = normalize_coefficient_shape( + n_coefficients, dim=distribution.dim + ) result = cls( centered_coefficients(distribution.coeff_mat, n_coefficients), @@ -131,9 +143,13 @@ def from_distribution( exponent_search_steps=exponent_search_steps, ) if not isinstance(distribution, AbstractHypertoroidalDistribution): - raise TypeError("distribution must be an AbstractHypertoroidalDistribution.") + raise TypeError( + "distribution must be an AbstractHypertoroidalDistribution." + ) - base = HypertoroidalFourierDistribution.from_distribution(distribution, n_coefficients, "identity") + base = HypertoroidalFourierDistribution.from_distribution( + distribution, n_coefficients, "identity" + ) return cls.from_fourier_distribution( base, n_coefficients, @@ -160,7 +176,9 @@ def from_function( ) -> "FejerHypertoroidalFourierDistribution": """Construct a positive-kernel identity HFD by sampling a vectorized function.""" - base = HypertoroidalFourierDistribution.from_function(fun, n_coefficients, "identity") + base = HypertoroidalFourierDistribution.from_function( + fun, n_coefficients, "identity" + ) return cls.from_fourier_distribution( base, n_coefficients, @@ -194,7 +212,9 @@ def from_function_values( desired_transformation="identity", already_transformed=already_transformed, ) - target_shape = base.coeff_mat.shape if n_coefficients is None else n_coefficients + target_shape = ( + base.coeff_mat.shape if n_coefficients is None else n_coefficients + ) return cls.from_fourier_distribution( base, target_shape, @@ -206,16 +226,22 @@ def from_function_values( exponent_search_steps=exponent_search_steps, ) - def fejer_reduce(self, n_coefficients: int | tuple[int, ...] | None = None) -> "FejerHypertoroidalFourierDistribution": + def fejer_reduce( + self, n_coefficients: int | tuple[int, ...] | None = None + ) -> "FejerHypertoroidalFourierDistribution": """Center-crop/pad and reduce coefficients with the configured kernel.""" if n_coefficients is None: n_coefficients = self.coeff_mat.shape n_coefficients = normalize_coefficient_shape(n_coefficients, dim=self.dim) coeff = self._reduce_coefficients(self.coeff_mat, n_coefficients) - return self._new_with_same_reduction(coeff, reduction_exponent=self.last_reduction_exponent) + return self._new_with_same_reduction( + coeff, reduction_exponent=self.last_reduction_exponent + ) - def truncate(self, n_coefficients: int | tuple[int, ...], force_normalization: bool = False): + def truncate( + self, n_coefficients: int | tuple[int, ...], force_normalization: bool = False + ): """Return a distribution with the requested centered coefficient shape. If the shape changes, reduction is performed with the configured kernel @@ -253,7 +279,9 @@ def multiply(self, f2: HypertoroidalFourierDistribution, n_coefficients=None): conv = retry_conv self.last_used_prior_smoothing_fallback = True coeff = self._reduce_coefficients(conv, n_coefficients) - return self._new_with_same_reduction(coeff, reduction_exponent=self.last_reduction_exponent) + return self._new_with_same_reduction( + coeff, reduction_exponent=self.last_reduction_exponent + ) def convolve(self, f2: HypertoroidalFourierDistribution, n_coefficients=None): """Topology-aware convolution for additive noise in identity form. @@ -268,11 +296,17 @@ def convolve(self, f2: HypertoroidalFourierDistribution, n_coefficients=None): n_coefficients = self.coeff_mat.shape n_coefficients = normalize_coefficient_shape(n_coefficients, dim=self.dim) - f1_aligned = self if tuple(self.coeff_mat.shape) == n_coefficients else self.fejer_reduce(n_coefficients) + f1_aligned = ( + self + if tuple(self.coeff_mat.shape) == n_coefficients + else self.fejer_reduce(n_coefficients) + ) if tuple(f2.coeff_mat.shape) == n_coefficients: f2_aligned = f2 else: - f2_aligned = type(self).from_fourier_distribution(f2, n_coefficients, apply_fejer=True, **self.reduction_options) + f2_aligned = type(self).from_fourier_distribution( + f2, n_coefficients, apply_fejer=True, **self.reduction_options + ) c_conv = (2.0 * np.pi) ** self.dim * f1_aligned.coeff_mat * f2_aligned.coeff_mat with warnings.catch_warnings(): @@ -283,7 +317,12 @@ def shift(self, shift_by): """Shift the distribution on the hypertorus and keep reduction settings.""" shifted = super().shift(shift_by) - return type(self).from_fourier_distribution(shifted, shifted.coeff_mat.shape, apply_fejer=False, **self.reduction_options) + return type(self).from_fourier_distribution( + shifted, + shifted.coeff_mat.shape, + apply_fejer=False, + **self.reduction_options, + ) def _reduce_coefficients(self, coefficients, n_coefficients: tuple[int, ...]): if self.adaptive_reduction: @@ -300,7 +339,9 @@ def _reduce_coefficients(self, coefficients, n_coefficients: tuple[int, ...]): return coeff self.last_reduction_exponent = 1.0 if self.reduction_kernel != "sharp" else 0.0 - return reduce_coefficients(coefficients, n_coefficients, kernel=self.reduction_kernel) + return reduce_coefficients( + coefficients, n_coefficients, kernel=self.reduction_kernel + ) def _has_positive_update_evidence(self, coefficients) -> bool: coeff_arr = np.asarray(coefficients) @@ -313,8 +354,12 @@ def _has_positive_update_evidence(self, coefficients) -> bool: def _validate_compatible_identity(self, other, operation: str) -> None: if not isinstance(other, HypertoroidalFourierDistribution): - raise TypeError(f"{operation}: other must be a HypertoroidalFourierDistribution.") + raise TypeError( + f"{operation}: other must be a HypertoroidalFourierDistribution." + ) if self.dim != other.dim: raise ValueError(f"{operation}: dimensions must match.") if self.transformation != "identity" or other.transformation != "identity": - raise ValueError(f"{operation}: both distributions must use the identity transformation.") + raise ValueError( + f"{operation}: both distributions must use the identity transformation." + ) diff --git a/src/pyrecest/distributions/hypertorus/hypertoroidal_dirac_distribution.py b/src/pyrecest/distributions/hypertorus/hypertoroidal_dirac_distribution.py index fd5f02e20..44c5a6fe7 100644 --- a/src/pyrecest/distributions/hypertorus/hypertoroidal_dirac_distribution.py +++ b/src/pyrecest/distributions/hypertorus/hypertoroidal_dirac_distribution.py @@ -8,7 +8,9 @@ # pylint: disable=redefined-builtin,no-name-in-module,no-member # pylint: disable=no-name-in-module,no-member -from pyrecest.backend import abs +from pyrecest.backend import ( + abs, +) from pyrecest.backend import any as backend_any from pyrecest.backend import ( arctan2, diff --git a/src/pyrecest/distributions/hypertorus/hypertoroidal_uniform_distribution.py b/src/pyrecest/distributions/hypertorus/hypertoroidal_uniform_distribution.py index b6d1e8aff..ee9554a77 100644 --- a/src/pyrecest/distributions/hypertorus/hypertoroidal_uniform_distribution.py +++ b/src/pyrecest/distributions/hypertorus/hypertoroidal_uniform_distribution.py @@ -2,8 +2,8 @@ # pylint: disable=no-name-in-module,no-member import numpy as np +from pyrecest.backend import all as backend_all from pyrecest.backend import ( - all as backend_all, asarray, int32, int64, diff --git a/src/pyrecest/distributions/hypertorus/hypertoroidal_wrapped_normal_distribution.py b/src/pyrecest/distributions/hypertorus/hypertoroidal_wrapped_normal_distribution.py index d76d0e4e1..e521082c1 100644 --- a/src/pyrecest/distributions/hypertorus/hypertoroidal_wrapped_normal_distribution.py +++ b/src/pyrecest/distributions/hypertorus/hypertoroidal_wrapped_normal_distribution.py @@ -26,7 +26,6 @@ from ._input_validation import as_hypertoroidal_points, as_shift_vector from .abstract_hypertoroidal_distribution import AbstractHypertoroidalDistribution - _FINITE_REAL_MESSAGE = "{name} must contain only finite real values" diff --git a/src/pyrecest/distributions/hypertorus/low_rank_hypertoroidal_fourier_distribution.py b/src/pyrecest/distributions/hypertorus/low_rank_hypertoroidal_fourier_distribution.py index 2664cb496..23e7c356e 100644 --- a/src/pyrecest/distributions/hypertorus/low_rank_hypertoroidal_fourier_distribution.py +++ b/src/pyrecest/distributions/hypertorus/low_rank_hypertoroidal_fourier_distribution.py @@ -12,7 +12,6 @@ from .abstract_hypertoroidal_distribution import AbstractHypertoroidalDistribution from .hypertoroidal_fourier_distribution import HypertoroidalFourierDistribution - _SHAPE_ERROR = "Fourier coefficient side lengths must be positive odd integers." @@ -35,7 +34,9 @@ def _as_shape(shape, *, dim=None): try: normalized = tuple(_as_positive_odd_coefficient_count(n) for n in shape) except TypeError as exc: - raise TypeError("shape must be an integer or a sequence of integers.") from exc + raise TypeError( + "shape must be an integer or a sequence of integers." + ) from exc if not normalized: raise ValueError("shape must contain at least one dimension.") if dim is not None and len(normalized) != dim: @@ -179,7 +180,9 @@ def value(self, xs): frequencies = [_freqs(axis_size) for axis_size in self.coeff_shape] for point_index, point in enumerate(points): accumulated = np.ones((1, 1), dtype=np.complex128) - for axis, (core, ks) in enumerate(zip(self.coefficients.cores, frequencies)): + for axis, (core, ks) in enumerate( + zip(self.coefficients.cores, frequencies) + ): weights = np.exp(1j * ks * point[axis]) matrix = np.tensordot(weights, core, axes=([0], [1])) accumulated = accumulated @ matrix @@ -214,15 +217,21 @@ def centered_hermitian_deviation(self, *, max_entries=1_000_000): """Return max ``|C[k] - conj(C[-k])|`` for identity coefficients.""" if self.transformation != "identity": - raise NotImplementedError("Hermitian coefficient checks apply to identity coefficients only.") + raise NotImplementedError( + "Hermitian coefficient checks apply to identity coefficients only." + ) return self.coefficients.centered_hermitian_deviation(max_entries=max_entries) def is_centered_hermitian(self, *, atol=1e-10, max_entries=1_000_000): """Return whether identity coefficients define a real-valued Fourier series.""" if self.transformation != "identity": - raise NotImplementedError("Hermitian coefficient checks apply to identity coefficients only.") - return self.coefficients.is_centered_hermitian(atol=atol, max_entries=max_entries) + raise NotImplementedError( + "Hermitian coefficient checks apply to identity coefficients only." + ) + return self.coefficients.is_centered_hermitian( + atol=atol, max_entries=max_entries + ) def centered_hermitianized( self, @@ -235,7 +244,9 @@ def centered_hermitianized( """Return a centered-Hermitian identity-coefficient distribution.""" if self.transformation != "identity": - raise NotImplementedError("Hermitian coefficient repair applies to identity coefficients only.") + raise NotImplementedError( + "Hermitian coefficient repair applies to identity coefficients only." + ) coefficients = self.coefficients.centered_hermitianized( max_rank=max_rank, rtol=rtol, @@ -248,7 +259,9 @@ def centered_hermitianized( normalize=True, ) - def multiply(self, other, n_coefficients=None, *, max_rank=None, rtol=0.0, atol=0.0): + def multiply( + self, other, n_coefficients=None, *, max_rank=None, rtol=0.0, atol=0.0 + ): other = self._ensure_low_rank(other) self._check_compatible(other) target_shape = ( @@ -262,11 +275,15 @@ def multiply(self, other, n_coefficients=None, *, max_rank=None, rtol=0.0, atol= coeffs = coeffs.round(max_rank=max_rank, rtol=rtol, atol=atol) return LowRankHypertoroidalFourierDistribution(coeffs, self.transformation) - def convolve(self, other, n_coefficients=None, *, max_rank=None, rtol=0.0, atol=0.0): + def convolve( + self, other, n_coefficients=None, *, max_rank=None, rtol=0.0, atol=0.0 + ): other = self._ensure_low_rank(other) self._check_compatible(other) if self.transformation != "identity": - raise NotImplementedError("Low-rank SqFF prediction is not implemented yet.") + raise NotImplementedError( + "Low-rank SqFF prediction is not implemented yet." + ) if n_coefficients is not None: target_shape = _as_shape(n_coefficients, dim=self.dim) if target_shape != self.coeff_shape: @@ -280,7 +297,9 @@ def convolve(self, other, n_coefficients=None, *, max_rank=None, rtol=0.0, atol= def mean_direction(self): if self.transformation != "identity": - raise NotImplementedError("mean_direction is implemented for identity coefficients only.") + raise NotImplementedError( + "mean_direction is implemented for identity coefficients only." + ) scale = (2.0 * np.pi) ** self.dim means = np.zeros(self.dim) for axis in range(self.dim): @@ -292,7 +311,11 @@ def mean_direction(self): def integrate(self): if self.transformation == "identity": - return float(np.real_if_close(((2.0 * np.pi) ** self.dim) * self.coefficient_at_zero()).real) + return float( + np.real_if_close( + ((2.0 * np.pi) ** self.dim) * self.coefficient_at_zero() + ).real + ) return float(((2.0 * np.pi) ** self.dim) * self.coefficients.norm_squared()) def compression_ratio(self): @@ -303,10 +326,14 @@ def _ensure_low_rank(self, other): return other if isinstance(other, HypertoroidalFourierDistribution): return LowRankHypertoroidalFourierDistribution.from_dense(other) - raise TypeError("Expected a dense or low-rank hypertoroidal Fourier distribution.") + raise TypeError( + "Expected a dense or low-rank hypertoroidal Fourier distribution." + ) def _check_compatible(self, other): if self.coeff_shape != other.coeff_shape: - raise ValueError("Fourier distributions must have identical coefficient shapes.") + raise ValueError( + "Fourier distributions must have identical coefficient shapes." + ) if self.transformation != other.transformation: raise ValueError("Fourier distributions must use the same transformation.") diff --git a/src/pyrecest/distributions/hypertorus/toroidal_vm_rivest_distribution.py b/src/pyrecest/distributions/hypertorus/toroidal_vm_rivest_distribution.py index c22189a68..4e284ab79 100644 --- a/src/pyrecest/distributions/hypertorus/toroidal_vm_rivest_distribution.py +++ b/src/pyrecest/distributions/hypertorus/toroidal_vm_rivest_distribution.py @@ -35,9 +35,7 @@ def __init__(self, mu, kappa, alpha, beta): self._bessel_exponential_scale = self._series_exponential_scale() self._scaled_norm_series_sum = self._compute_scaled_norm_series_sum() - self._scaled_norm_const = ( - 4.0 * math.pi**2 * self._scaled_norm_series_sum - ) + self._scaled_norm_const = 4.0 * math.pi**2 * self._scaled_norm_series_sum if not math.isfinite(self._scaled_norm_const) or self._scaled_norm_const <= 0.0: raise FloatingPointError( "Rivest normalization series must be positive and finite" @@ -94,12 +92,8 @@ def pdf(self, xs): log_unnormalized = ( self.kappa[0] * cos(xs[..., 0] - self.mu[0]) + self.kappa[1] * cos(xs[..., 1] - self.mu[1]) - + self.alpha - * cos(xs[..., 0] - self.mu[0]) - * cos(xs[..., 1] - self.mu[1]) - + self.beta - * sin(xs[..., 0] - self.mu[0]) - * sin(xs[..., 1] - self.mu[1]) + + self.alpha * cos(xs[..., 0] - self.mu[0]) * cos(xs[..., 1] - self.mu[1]) + + self.beta * sin(xs[..., 0] - self.mu[0]) * sin(xs[..., 1] - self.mu[1]) ) return exp(log_unnormalized - self._bessel_exponential_scale) / ( self._scaled_norm_const @@ -114,14 +108,11 @@ def trigonometric_moment(self, n): for j in range(-m, m + 1): for ell in range(-m, m + 1): if (j + ell) % 2 == 0: - bessel_jl = float( - ive((j + ell) // 2, alpha_plus) - ) * float(ive((j - ell) // 2, alpha_minus)) + bessel_jl = float(ive((j + ell) // 2, alpha_plus)) * float( + ive((j - ell) // 2, alpha_minus) + ) terms1.append( - ( - float(ive(j + 1, kappa0)) - + float(ive(j - 1, kappa0)) - ) + (float(ive(j + 1, kappa0)) + float(ive(j - 1, kappa0))) * float(ive(ell, kappa1)) * bessel_jl ) diff --git a/src/pyrecest/distributions/nonperiodic/gaussian_mixture.py b/src/pyrecest/distributions/nonperiodic/gaussian_mixture.py index 166d5559e..9770b3222 100644 --- a/src/pyrecest/distributions/nonperiodic/gaussian_mixture.py +++ b/src/pyrecest/distributions/nonperiodic/gaussian_mixture.py @@ -27,9 +27,7 @@ def set_mean(self, new_mean): new_mean = array(new_mean) if new_mean.ndim == 0: if self.dim != 1: - raise ValueError( - f"new_mean must have shape ({self.dim},), got scalar." - ) + raise ValueError(f"new_mean must have shape ({self.dim},), got scalar.") new_mean = reshape(new_mean, (1,)) elif new_mean.shape != (self.dim,): raise ValueError( diff --git a/src/pyrecest/distributions/nonperiodic/linear_box_particle_distribution.py b/src/pyrecest/distributions/nonperiodic/linear_box_particle_distribution.py index a2f620720..f0b9516d4 100644 --- a/src/pyrecest/distributions/nonperiodic/linear_box_particle_distribution.py +++ b/src/pyrecest/distributions/nonperiodic/linear_box_particle_distribution.py @@ -161,9 +161,7 @@ def set_mean(self, new_mean): new_mean = array(new_mean) if new_mean.ndim == 0: if self.dim != 1: - raise ValueError( - f"new_mean must have shape ({self.dim},), got scalar." - ) + raise ValueError(f"new_mean must have shape ({self.dim},), got scalar.") new_mean = reshape(new_mean, (1,)) elif new_mean.shape != (self.dim,): raise ValueError( diff --git a/src/pyrecest/distributions/nonperiodic/linear_dirac_distribution.py b/src/pyrecest/distributions/nonperiodic/linear_dirac_distribution.py index 6322726d3..7ddae38c3 100644 --- a/src/pyrecest/distributions/nonperiodic/linear_dirac_distribution.py +++ b/src/pyrecest/distributions/nonperiodic/linear_dirac_distribution.py @@ -63,7 +63,7 @@ def plot(self, *args, **kwargs): sample_locs[:, 1], sample_weights / max(sample_weights) * 100, *args, - **kwargs + **kwargs, ) elif self.dim == 3: fig = plt.figure() @@ -75,7 +75,7 @@ def plot(self, *args, **kwargs): sample_locs[:, 2], s=(sample_weights / max(sample_weights) * 100), *args, - **kwargs + **kwargs, ) else: raise ValueError("Plotting not supported for this dimension") diff --git a/src/pyrecest/distributions/so3_product_dirac_distribution.py b/src/pyrecest/distributions/so3_product_dirac_distribution.py index 9fb900efe..0f12370e7 100644 --- a/src/pyrecest/distributions/so3_product_dirac_distribution.py +++ b/src/pyrecest/distributions/so3_product_dirac_distribution.py @@ -5,7 +5,6 @@ from numbers import Integral import numpy as np - from pyrecest.backend import ( abs, all, diff --git a/src/pyrecest/distributions/so3_product_tangent_gaussian_distribution.py b/src/pyrecest/distributions/so3_product_tangent_gaussian_distribution.py index 0d524846b..a7a5f8752 100644 --- a/src/pyrecest/distributions/so3_product_tangent_gaussian_distribution.py +++ b/src/pyrecest/distributions/so3_product_tangent_gaussian_distribution.py @@ -101,9 +101,7 @@ def _normalize_rotation_indices(rotation_indices, num_rotations: int) -> list[in raise ValueError(message) parsed_index = int(index) if parsed_index < 0 or parsed_index >= num_rotations: - raise ValueError( - f"rotation_indices must be in [0, {num_rotations - 1}]." - ) + raise ValueError(f"rotation_indices must be in [0, {num_rotations - 1}].") normalized_indices.append(parsed_index) if len(set(normalized_indices)) != len(normalized_indices): diff --git a/src/pyrecest/evaluation/get_axis_label.py b/src/pyrecest/evaluation/get_axis_label.py index d71f053b2..5cf55ea95 100644 --- a/src/pyrecest/evaluation/get_axis_label.py +++ b/src/pyrecest/evaluation/get_axis_label.py @@ -1,7 +1,9 @@ def _normalize_manifold_name(manifold_name): if not isinstance(manifold_name, str) or not manifold_name.strip(): raise ValueError("manifold_name must be a non-empty string") - compact_name = "".join(char for char in manifold_name.strip().lower() if char.isalnum()) + compact_name = "".join( + char for char in manifold_name.strip().lower() if char.isalnum() + ) if not compact_name: raise ValueError("manifold_name must be a non-empty string") return compact_name diff --git a/src/pyrecest/evaluation/get_distance_function.py b/src/pyrecest/evaluation/get_distance_function.py index 000494af0..e07af868c 100644 --- a/src/pyrecest/evaluation/get_distance_function.py +++ b/src/pyrecest/evaluation/get_distance_function.py @@ -221,13 +221,9 @@ def _euclidean_mtt_distance(x1, x2, *, cutoff_distance: float) -> float: first = _as_target_matrix(x1, "x1") second = _as_target_matrix(x2, "x2") if first.shape[0] == 0 and first.shape[1] != 0: - second = _as_target_matrix( - x2, "x2", expected_target_dim=first.shape[1] - ) + second = _as_target_matrix(x2, "x2", expected_target_dim=first.shape[1]) elif second.shape[0] == 0 and second.shape[1] != 0: - first = _as_target_matrix( - x1, "x1", expected_target_dim=second.shape[1] - ) + first = _as_target_matrix(x1, "x1", expected_target_dim=second.shape[1]) if first.shape[0] == 0 or second.shape[0] == 0: if ( first.shape[1] != 0 diff --git a/src/pyrecest/evaluation/get_extract_mean.py b/src/pyrecest/evaluation/get_extract_mean.py index 7e87c0e3c..47929fc0f 100644 --- a/src/pyrecest/evaluation/get_extract_mean.py +++ b/src/pyrecest/evaluation/get_extract_mean.py @@ -98,7 +98,9 @@ def _extract_mtt_mean(filter_state): def get_extract_mean(manifold_name, mtt_scenario=False): normalized_name = _normalize_registry_name(manifold_name) - is_mtt_scenario = _coerce_mtt_scenario_flag(mtt_scenario) or "mtt" in normalized_name + is_mtt_scenario = ( + _coerce_mtt_scenario_flag(mtt_scenario) or "mtt" in normalized_name + ) registered_factory = _EXTRACT_MEAN_FACTORIES.get(normalized_name) if registered_factory is not None: return registered_factory(manifold_name, is_mtt_scenario) diff --git a/src/pyrecest/evaluation/pareto.py b/src/pyrecest/evaluation/pareto.py index 558c12e77..a7b8efcdc 100644 --- a/src/pyrecest/evaluation/pareto.py +++ b/src/pyrecest/evaluation/pareto.py @@ -309,8 +309,7 @@ def _has_front_eligible_objectives( for objective in objectives ) return all( - not _is_missing(_lookup_numeric(record, objective)) - for objective in objectives + not _is_missing(_lookup_numeric(record, objective)) for objective in objectives ) diff --git a/src/pyrecest/evidence.py b/src/pyrecest/evidence.py index 08dac1e2f..6e30ccb99 100644 --- a/src/pyrecest/evidence.py +++ b/src/pyrecest/evidence.py @@ -211,26 +211,38 @@ def resolve_evidence_computation_mode( try: - from pyrecest._backend_runtime_patches import ( # pylint: disable=import-outside-toplevel - patch_pytorch_close_equal_nan_device_contract as _patch_pytorch_close_equal_nan_device_contract, + from pyrecest._backend_runtime_patches import ( + patch_pytorch_close_equal_nan_device_contract as _patch_pytorch_close_equal_nan_device_contract, # pylint: disable=import-outside-toplevel + ) + from pyrecest._backend_runtime_patches import ( patch_pytorch_edge_pad_contract as _patch_pytorch_edge_pad_contract, + ) + from pyrecest._backend_runtime_patches import ( patch_pytorch_repeat_numpy_contract as _patch_pytorch_repeat_numpy_contract, + ) + from pyrecest._backend_runtime_patches import ( patch_pytorch_searchsorted_contract as _patch_pytorch_searchsorted_contract, - patch_raw_pytorch_reduction_alias_contract as _patch_raw_pytorch_reduction_alias_contract, + ) + from pyrecest._backend_runtime_patches import ( patch_pytorch_take_axis_contract as _patch_pytorch_take_axis_contract, + ) + from pyrecest._backend_runtime_patches import ( patch_pytorch_transpose_boolean_axes_contract as _patch_pytorch_transpose_boolean_axes_contract, ) - from pyrecest.backend_support._jax_array_from_sparse_contract import ( # pylint: disable=import-outside-toplevel - patch_jax_array_from_sparse_flat_index_contract as _patch_jax_array_from_sparse_flat_index_contract, + from pyrecest._backend_runtime_patches import ( + patch_raw_pytorch_reduction_alias_contract as _patch_raw_pytorch_reduction_alias_contract, + ) + from pyrecest.backend_support._jax_array_from_sparse_contract import ( + patch_jax_array_from_sparse_flat_index_contract as _patch_jax_array_from_sparse_flat_index_contract, # pylint: disable=import-outside-toplevel ) - from pyrecest.backend_support._pytorch_one_hot_scalar_contract import ( # pylint: disable=import-outside-toplevel - patch_pytorch_one_hot_scalar_contract as _patch_pytorch_one_hot_scalar_contract, + from pyrecest.backend_support._pytorch_one_hot_scalar_contract import ( + patch_pytorch_one_hot_scalar_contract as _patch_pytorch_one_hot_scalar_contract, # pylint: disable=import-outside-toplevel ) - from pyrecest.backend_support._pytorch_quantile_empty_batch_contract import ( # pylint: disable=import-outside-toplevel - patch_pytorch_quantile_empty_batch_contract as _patch_pytorch_quantile_empty_batch_contract, + from pyrecest.backend_support._pytorch_quantile_empty_batch_contract import ( + patch_pytorch_quantile_empty_batch_contract as _patch_pytorch_quantile_empty_batch_contract, # pylint: disable=import-outside-toplevel ) - from pyrecest.backend_support._pytorch_reduction_axis_contract import ( # pylint: disable=import-outside-toplevel - patch_pytorch_reduction_axis_contract as _patch_pytorch_reduction_axis_contract, + from pyrecest.backend_support._pytorch_reduction_axis_contract import ( + patch_pytorch_reduction_axis_contract as _patch_pytorch_reduction_axis_contract, # pylint: disable=import-outside-toplevel ) except ModuleNotFoundError: # pragma: no cover - source tree corruption only pass diff --git a/src/pyrecest/experimental/dp_measurement_subclusters.py b/src/pyrecest/experimental/dp_measurement_subclusters.py index 4c4c52672..34c3545ed 100644 --- a/src/pyrecest/experimental/dp_measurement_subclusters.py +++ b/src/pyrecest/experimental/dp_measurement_subclusters.py @@ -72,7 +72,9 @@ def _integer_at_least(value: int | None, name: str, lower_bound: int) -> int | N def _as_measurement_matrix(measurements: np.ndarray) -> np.ndarray: values = np.asarray(measurements, dtype=float) if values.ndim != 2: - raise ValueError("measurements must have shape (num_measurements, measurement_dim)") + raise ValueError( + "measurements must have shape (num_measurements, measurement_dim)" + ) if np.any(~np.isfinite(values)): raise ValueError("measurements must contain only finite values") return values @@ -98,10 +100,14 @@ def _logsumexp(log_values: np.ndarray) -> float: return maximum + log(float(np.sum(np.exp(log_values - maximum)))) -def _isotropic_gaussian_logpdf(value: np.ndarray, mean: np.ndarray, variance: float) -> float: +def _isotropic_gaussian_logpdf( + value: np.ndarray, mean: np.ndarray, variance: float +) -> float: residual = value - mean dim = value.shape[0] - return -0.5 * (dim * log(2.0 * pi * variance) + float(residual @ residual) / variance) + return -0.5 * ( + dim * log(2.0 * pi * variance) + float(residual @ residual) / variance + ) def _posterior_mean_and_variance( @@ -145,9 +151,7 @@ def __post_init__(self) -> None: self.measurement_variance, "measurement_variance" ) prior_variance = _positive_finite_scalar(self.prior_variance, "prior_variance") - max_subclusters = _integer_at_least( - self.max_subclusters, "max_subclusters", 1 - ) + max_subclusters = _integer_at_least(self.max_subclusters, "max_subclusters", 1) if self.prior_mean is not None: prior_mean = np.asarray(self.prior_mean, dtype=float) if prior_mean.ndim != 1: @@ -337,7 +341,9 @@ def fit_dp_measurement_subclusters( ) ) - can_create_new_cluster = config.max_subclusters is None or len(clusters) < config.max_subclusters + can_create_new_cluster = ( + config.max_subclusters is None or len(clusters) < config.max_subclusters + ) if can_create_new_cluster: log_scores.append( log(config.concentration) @@ -446,7 +452,10 @@ def score_dp_measurement_partition( ) ) else: - if config.max_subclusters is not None and len(clusters) >= config.max_subclusters: + if ( + config.max_subclusters is not None + and len(clusters) >= config.max_subclusters + ): return float("-inf") cluster = _ClusterAccumulator(values.shape[1]) clusters[label] = cluster @@ -474,7 +483,9 @@ def dp_measurement_subset_log_likelihood( posterior-predictive likelihood while the returned partition from ``fit_dp_measurement_subclusters`` can be used as a proposal or diagnostic. """ - return fit_dp_measurement_subclusters(measurements, config).log_predictive_likelihood + return fit_dp_measurement_subclusters( + measurements, config + ).log_predictive_likelihood __all__ = [ diff --git a/src/pyrecest/experimental/multisensor_ddp_association.py b/src/pyrecest/experimental/multisensor_ddp_association.py index 72c32605b..4c74180fc 100644 --- a/src/pyrecest/experimental/multisensor_ddp_association.py +++ b/src/pyrecest/experimental/multisensor_ddp_association.py @@ -118,7 +118,9 @@ def predict_ddp_base_weights( :func:`multisensor_ddp_association_update`. """ weights = _coerce_nonnegative_vector(target_weights, None, "target_weights") - survival = _coerce_probability_vector(survival_probabilities, len(weights), "survival_probabilities") + survival = _coerce_probability_vector( + survival_probabilities, len(weights), "survival_probabilities" + ) birth = _as_nonnegative_float(birth_weight, "birth_weight") survived = weights * survival return _normalize_base_weights(survived, birth) @@ -152,12 +154,16 @@ def multisensor_ddp_association_update( weights = _coerce_nonnegative_vector(target_weights, None, "target_weights") num_targets = len(weights) - labels = tuple(range(num_targets)) if target_labels is None else tuple(target_labels) + labels = ( + tuple(range(num_targets)) if target_labels is None else tuple(target_labels) + ) if len(labels) != num_targets: raise ValueError("target_labels must have the same length as target_weights") prior_strength = _as_nonnegative_float(prior_strength, "prior_strength") - target_base, normalized_birth_weight = _normalize_base_weights(weights, birth_weight) + target_base, normalized_birth_weight = _normalize_base_weights( + weights, birth_weight + ) assignment_labels = (*labels, BIRTH_LABEL, CLUTTER_LABEL) sensor_posteriors: dict[str, SensorAssociationPosterior] = {} @@ -171,12 +177,28 @@ def multisensor_ddp_association_update( raise ValueError(f"duplicate sensor_id {block.sensor_id!r}") seen_sensor_ids.add(block.sensor_id) - log_likelihoods = _coerce_log_likelihoods(block.log_likelihoods, num_targets, block.sensor_id) + log_likelihoods = _coerce_log_likelihoods( + block.log_likelihoods, num_targets, block.sensor_id + ) num_measurements = int(log_likelihoods.shape[0]) - detection_probabilities = _coerce_probability_vector(block.detection_probabilities, num_targets, f"{block.sensor_id}.detection_probabilities") - birth_log_weights = _coerce_log_weight_vector(block.birth_log_weights, num_measurements, f"{block.sensor_id}.birth_log_weights") - clutter_log_weights = _coerce_log_weight_vector(block.clutter_log_weights, num_measurements, f"{block.sensor_id}.clutter_log_weights") - concentration = _as_positive_float(block.concentration, f"{block.sensor_id}.concentration") + detection_probabilities = _coerce_probability_vector( + block.detection_probabilities, + num_targets, + f"{block.sensor_id}.detection_probabilities", + ) + birth_log_weights = _coerce_log_weight_vector( + block.birth_log_weights, + num_measurements, + f"{block.sensor_id}.birth_log_weights", + ) + clutter_log_weights = _coerce_log_weight_vector( + block.clutter_log_weights, + num_measurements, + f"{block.sensor_id}.clutter_log_weights", + ) + concentration = _as_positive_float( + block.concentration, f"{block.sensor_id}.concentration" + ) log_scores = _association_log_scores( log_likelihoods, @@ -189,15 +211,24 @@ def multisensor_ddp_association_update( ) if point_target: responsibilities = _greedy_point_target_projection(log_scores, num_targets) - log_normalizers = np.max(log_scores, axis=1) if num_measurements else np.empty(0, dtype=float) + log_normalizers = ( + np.max(log_scores, axis=1) + if num_measurements + else np.empty(0, dtype=float) + ) else: - responsibilities, log_normalizers = _rowwise_softmax_from_log_scores(log_scores) + responsibilities, log_normalizers = _rowwise_softmax_from_log_scores( + log_scores + ) expected_counts = responsibilities.sum(axis=0) expected_target_counts += expected_counts[:num_targets] expected_birth_count += float(expected_counts[-2]) expected_clutter_count += float(expected_counts[-1]) - hard_assignments = tuple(assignment_labels[int(index)] for index in np.argmax(responsibilities, axis=1)) + hard_assignments = tuple( + assignment_labels[int(index)] + for index in np.argmax(responsibilities, axis=1) + ) sensor_posteriors[block.sensor_id] = SensorAssociationPosterior( sensor_id=block.sensor_id, @@ -208,11 +239,19 @@ def multisensor_ddp_association_update( hard_assignments=hard_assignments, ) - posterior_target_pseudocounts = prior_strength * target_base + expected_target_counts - posterior_birth_pseudocount = prior_strength * normalized_birth_weight + expected_birth_count - posterior_total = float(np.sum(posterior_target_pseudocounts) + posterior_birth_pseudocount) + posterior_target_pseudocounts = ( + prior_strength * target_base + expected_target_counts + ) + posterior_birth_pseudocount = ( + prior_strength * normalized_birth_weight + expected_birth_count + ) + posterior_total = float( + np.sum(posterior_target_pseudocounts) + posterior_birth_pseudocount + ) if posterior_total <= 0.0: - raise ValueError("posterior target and birth mass is zero; increase prior_strength or birth_weight") + raise ValueError( + "posterior target and birth mass is zero; increase prior_strength or birth_weight" + ) updated_target_weights = posterior_target_pseudocounts / posterior_total updated_birth_weight = float(posterior_birth_pseudocount / posterior_total) @@ -241,27 +280,37 @@ def _association_log_scores( num_measurements, num_targets = log_likelihoods.shape target_prior = np.full(num_targets, -np.inf, dtype=float) positive_target_mask = target_base > 0.0 - target_prior[positive_target_mask] = log(concentration) + np.log(target_base[positive_target_mask]) + target_prior[positive_target_mask] = log(concentration) + np.log( + target_base[positive_target_mask] + ) detection_log = np.full(num_targets, -np.inf, dtype=float) positive_detection_mask = detection_probabilities > 0.0 - detection_log[positive_detection_mask] = np.log(detection_probabilities[positive_detection_mask]) + detection_log[positive_detection_mask] = np.log( + detection_probabilities[positive_detection_mask] + ) - existing_scores = log_likelihoods + target_prior.reshape(1, -1) + detection_log.reshape(1, -1) + existing_scores = ( + log_likelihoods + target_prior.reshape(1, -1) + detection_log.reshape(1, -1) + ) birth_prior = -np.inf if birth_base <= 0.0 else log(concentration) + log(birth_base) birth_scores = (birth_log_weights + birth_prior).reshape(num_measurements, 1) clutter_scores = clutter_log_weights.reshape(num_measurements, 1) return np.concatenate((existing_scores, birth_scores, clutter_scores), axis=1) -def _rowwise_softmax_from_log_scores(log_scores: np.ndarray) -> tuple[np.ndarray, np.ndarray]: +def _rowwise_softmax_from_log_scores( + log_scores: np.ndarray, +) -> tuple[np.ndarray, np.ndarray]: if log_scores.ndim != 2: raise ValueError("log_scores must be two-dimensional") if log_scores.shape[0] == 0: return np.empty_like(log_scores, dtype=float), np.empty(0, dtype=float) row_max = np.max(log_scores, axis=1) if np.any(~np.isfinite(row_max)): - raise ValueError("each measurement must have at least one finite association score") + raise ValueError( + "each measurement must have at least one finite association score" + ) shifted = log_scores - row_max.reshape(-1, 1) unnormalized = np.exp(shifted) normalizers = unnormalized.sum(axis=1) @@ -270,18 +319,26 @@ def _rowwise_softmax_from_log_scores(log_scores: np.ndarray) -> tuple[np.ndarray return responsibilities, log_normalizers -def _greedy_point_target_projection(log_scores: np.ndarray, num_targets: int) -> np.ndarray: +def _greedy_point_target_projection( + log_scores: np.ndarray, num_targets: int +) -> np.ndarray: if log_scores.shape[0] == 0: return np.empty_like(log_scores, dtype=float) if np.any(~np.isfinite(np.max(log_scores, axis=1))): - raise ValueError("each measurement must have at least one finite association score") + raise ValueError( + "each measurement must have at least one finite association score" + ) responsibilities = np.zeros_like(log_scores, dtype=float) used_targets: set[int] = set() priorities: list[tuple[float, int]] = [] for measurement_index, row in enumerate(log_scores): finite_scores = np.sort(row[np.isfinite(row)]) - margin = float("inf") if finite_scores.size == 1 else float(finite_scores[-1] - finite_scores[-2]) + margin = ( + float("inf") + if finite_scores.size == 1 + else float(finite_scores[-1] - finite_scores[-2]) + ) priorities.append((margin, measurement_index)) for _, measurement_index in sorted(priorities, reverse=True): @@ -305,11 +362,15 @@ def _greedy_point_target_projection(log_scores: np.ndarray, num_targets: int) -> return responsibilities -def _normalize_base_weights(target_weights: np.ndarray, birth_weight: float) -> tuple[np.ndarray, float]: +def _normalize_base_weights( + target_weights: np.ndarray, birth_weight: float +) -> tuple[np.ndarray, float]: birth = _as_nonnegative_float(birth_weight, "birth_weight") total = float(np.sum(target_weights) + birth) if total <= 0.0: - raise ValueError("target_weights and birth_weight must contain positive total mass") + raise ValueError( + "target_weights and birth_weight must contain positive total mass" + ) return target_weights.astype(float, copy=False) / total, float(birth / total) @@ -329,7 +390,9 @@ def _contains_temporal_value(value: Any, *, _depth: int = 0) -> bool: return True if array.dtype != object or _depth >= 4: return False - return any(_contains_temporal_value(item, _depth=_depth + 1) for item in array.ravel()) + return any( + _contains_temporal_value(item, _depth=_depth + 1) for item in array.ravel() + ) def _is_temporal_dtype(dtype: np.dtype) -> bool: @@ -341,7 +404,9 @@ def _coerce_log_likelihoods(value: Any, num_targets: int, sensor_id: str) -> np. array = np.asarray(value, dtype=float) if array.ndim == 1: if num_targets != 1: - raise ValueError(f"{sensor_id}.log_likelihoods must have shape (num_measurements, {num_targets})") + raise ValueError( + f"{sensor_id}.log_likelihoods must have shape (num_measurements, {num_targets})" + ) array = array.reshape(-1, 1) if array.ndim != 2: raise ValueError(f"{sensor_id}.log_likelihoods must be two-dimensional") @@ -352,7 +417,9 @@ def _coerce_log_likelihoods(value: Any, num_targets: int, sensor_id: str) -> np. return array.astype(float, copy=False) -def _coerce_log_weight_vector(value: float | Sequence[float], length: int, name: str) -> np.ndarray: +def _coerce_log_weight_vector( + value: float | Sequence[float], length: int, name: str +) -> np.ndarray: _reject_temporal_values(value, name) array = np.asarray(value, dtype=float) if array.shape == (): @@ -367,7 +434,9 @@ def _coerce_log_weight_vector(value: float | Sequence[float], length: int, name: return array.astype(float, copy=False) -def _coerce_nonnegative_vector(value: Sequence[float], expected_length: int | None, name: str) -> np.ndarray: +def _coerce_nonnegative_vector( + value: Sequence[float], expected_length: int | None, name: str +) -> np.ndarray: _reject_temporal_values(value, name) array = np.asarray(value, dtype=float) if array.ndim != 1: @@ -379,7 +448,9 @@ def _coerce_nonnegative_vector(value: Sequence[float], expected_length: int | No return array.astype(float, copy=False) -def _coerce_probability_vector(value: float | Sequence[float], length: int, name: str) -> np.ndarray: +def _coerce_probability_vector( + value: float | Sequence[float], length: int, name: str +) -> np.ndarray: _reject_temporal_values(value, name) array = np.asarray(value, dtype=float) if array.shape == (): diff --git a/src/pyrecest/filters/adaptive_process_noise.py b/src/pyrecest/filters/adaptive_process_noise.py index fd74b3e98..80c753504 100644 --- a/src/pyrecest/filters/adaptive_process_noise.py +++ b/src/pyrecest/filters/adaptive_process_noise.py @@ -36,7 +36,9 @@ def _has_rejected_numeric_dtype(value: object) -> bool: def _is_rejected_scalar_payload(value: object) -> bool: - return isinstance(value, _REJECTED_SCALAR_TYPES) or _has_rejected_numeric_dtype(value) + return isinstance(value, _REJECTED_SCALAR_TYPES) or _has_rejected_numeric_dtype( + value + ) @dataclass(frozen=True) @@ -178,7 +180,9 @@ def observe( accepted = _normalize_bool_flag(accepted, "accepted") if not accepted: return self.ratios_by_source.get(source, 1.0) - measurement_dim = _normalize_positive_integer(measurement_dim, "measurement_dim") + measurement_dim = _normalize_positive_integer( + measurement_dim, "measurement_dim" + ) nis = _normalize_nonnegative_finite_scalar(nis, "nis") ratio = nis / float(measurement_dim) previous = self.ratios_by_source.get(source, 1.0) diff --git a/src/pyrecest/filters/association_hypotheses.py b/src/pyrecest/filters/association_hypotheses.py index 9fdf22cdc..8c13ad16d 100644 --- a/src/pyrecest/filters/association_hypotheses.py +++ b/src/pyrecest/filters/association_hypotheses.py @@ -352,8 +352,7 @@ def filter( ), ) accepted_indices.update( - hypothesis_index - for hypothesis_index, _ in sorted_group[: self.k] + hypothesis_index for hypothesis_index, _ in sorted_group[: self.k] ) result = [] diff --git a/src/pyrecest/filters/dirichlet_process_birth_tracker.py b/src/pyrecest/filters/dirichlet_process_birth_tracker.py index 0281ddab7..1de099fe1 100644 --- a/src/pyrecest/filters/dirichlet_process_birth_tracker.py +++ b/src/pyrecest/filters/dirichlet_process_birth_tracker.py @@ -7,7 +7,6 @@ from typing import Any import numpy as np - from pyrecest.distributions import GaussianDistribution from .multi_bernoulli_tracker import BernoulliComponent, MultiBernoulliTracker @@ -87,11 +86,15 @@ def update_from_measurement( measurement_matrix @ self.covariance @ measurement_matrix.T + measurement_covariance ) - gain = self.covariance @ measurement_matrix.T @ np.linalg.inv( - innovation_covariance + gain = ( + self.covariance + @ measurement_matrix.T + @ np.linalg.inv(innovation_covariance) ) self.mean = self.mean + gain @ innovation - updated_covariance = self.covariance - gain @ measurement_matrix @ self.covariance + updated_covariance = ( + self.covariance - gain @ measurement_matrix @ self.covariance + ) self.covariance = 0.5 * (updated_covariance + updated_covariance.T) self.count += 1.0 @@ -148,7 +151,9 @@ def _normalize_birth_atoms(birth_atoms): elif isinstance(atom, tuple) and len(atom) == 2: normalized_atoms.append(DirichletProcessBirthAtom(atom[0], atom[1])) elif isinstance(atom, tuple) and len(atom) == 3: - normalized_atoms.append(DirichletProcessBirthAtom(atom[0], atom[1], atom[2])) + normalized_atoms.append( + DirichletProcessBirthAtom(atom[0], atom[1], atom[2]) + ) else: raise ValueError( "birth_atoms must contain atoms or (mean, covariance[, count]) tuples" @@ -359,7 +364,9 @@ def _prune_and_cap_birth_atoms(self): return maximum_number_of_birth_atoms = int(maximum_number_of_birth_atoms) if maximum_number_of_birth_atoms < 0: - raise ValueError("maximum_number_of_birth_atoms must be non-negative or None") + raise ValueError( + "maximum_number_of_birth_atoms must be non-negative or None" + ) self.birth_atoms = sorted( self.birth_atoms, key=lambda atom: atom.count, diff --git a/src/pyrecest/filters/fejer_identity_filter.py b/src/pyrecest/filters/fejer_identity_filter.py index 30edc9868..c2b4f28c5 100644 --- a/src/pyrecest/filters/fejer_identity_filter.py +++ b/src/pyrecest/filters/fejer_identity_filter.py @@ -4,12 +4,11 @@ import warnings -from scipy import signal - import pyrecest.backend from pyrecest.backend import array, column_stack, pi, reshape, stack, tile, zeros -from pyrecest.distributions.hypertorus.abstract_hypertoroidal_distribution import AbstractHypertoroidalDistribution -from pyrecest.distributions.hypertorus.hypertoroidal_fourier_distribution import HypertoroidalFourierDistribution +from pyrecest.distributions.hypertorus.abstract_hypertoroidal_distribution import ( + AbstractHypertoroidalDistribution, +) from pyrecest.distributions.hypertorus.fejer import ( normalize_coefficient_shape, normalize_kernel_name, @@ -17,8 +16,12 @@ from pyrecest.distributions.hypertorus.fejer_hypertoroidal_fourier_distribution import ( FejerHypertoroidalFourierDistribution, ) +from pyrecest.distributions.hypertorus.hypertoroidal_fourier_distribution import ( + HypertoroidalFourierDistribution, +) from pyrecest.filters.abstract_filter import AbstractFilter from pyrecest.filters.manifold_mixins import HypertoroidalFilterMixin +from scipy import signal class FejerIdentityFilter(AbstractFilter, HypertoroidalFilterMixin): @@ -45,8 +48,13 @@ def __init__( oversampling_factor: int = 1, exponent_search_steps: int = 24, ): - if pyrecest.backend.__backend_name__ in ("jax", "pytorch"): # pylint: disable=no-member - raise NotImplementedError(f"FejerIdentityFilter is not supported on the {pyrecest.backend.__backend_name__} backend.") + if pyrecest.backend.__backend_name__ in ( + "jax", + "pytorch", + ): # pylint: disable=no-member + raise NotImplementedError( + f"FejerIdentityFilter is not supported on the {pyrecest.backend.__backend_name__} backend." + ) if isinstance(n_coefficients, int): n_coefficients = (n_coefficients,) @@ -64,7 +72,10 @@ def __init__( coeff_mat[center] = 1.0 / (2.0 * pi) ** dim HypertoroidalFilterMixin.__init__(self) - AbstractFilter.__init__(self, FejerHypertoroidalFourierDistribution(coeff_mat, **self.reduction_options)) + AbstractFilter.__init__( + self, + FejerHypertoroidalFourierDistribution(coeff_mat, **self.reduction_options), + ) @property def reduction_options(self): @@ -88,34 +99,42 @@ def filter_state(self, new_state): "setState:noOfCoeffsDiffer: New density has a different number of coefficients; applying configured positive-kernel reduction to the filter shape.", RuntimeWarning, ) - new_state = FejerHypertoroidalFourierDistribution.from_fourier_distribution( - new_state, - self._filter_state.coeff_mat.shape, - apply_fejer=True, - **self.reduction_options, + new_state = ( + FejerHypertoroidalFourierDistribution.from_fourier_distribution( + new_state, + self._filter_state.coeff_mat.shape, + apply_fejer=True, + **self.reduction_options, + ) ) elif new_state.reduction_options != self.reduction_options: - new_state = FejerHypertoroidalFourierDistribution.from_fourier_distribution( - new_state, - self._filter_state.coeff_mat.shape, - apply_fejer=False, - **self.reduction_options, + new_state = ( + FejerHypertoroidalFourierDistribution.from_fourier_distribution( + new_state, + self._filter_state.coeff_mat.shape, + apply_fejer=False, + **self.reduction_options, + ) ) self._filter_state = new_state return if isinstance(new_state, HypertoroidalFourierDistribution): if new_state.transformation != "identity": - raise ValueError("FejerIdentityFilter only accepts identity Fourier distributions.") + raise ValueError( + "FejerIdentityFilter only accepts identity Fourier distributions." + ) warnings.warn( "setState:nonFejerFourier: converting identity Fourier distribution to positive-kernel identity form.", RuntimeWarning, ) - self._filter_state = FejerHypertoroidalFourierDistribution.from_fourier_distribution( - new_state, - self._filter_state.coeff_mat.shape, - apply_fejer=True, - **self.reduction_options, + self._filter_state = ( + FejerHypertoroidalFourierDistribution.from_fourier_distribution( + new_state, + self._filter_state.coeff_mat.shape, + apply_fejer=True, + **self.reduction_options, + ) ) return @@ -124,11 +143,13 @@ def filter_state(self, new_state): "setState:nonFourier: new_state is not a Fourier distribution. Transforming with the same number of coefficients as the filter.", RuntimeWarning, ) - self._filter_state = FejerHypertoroidalFourierDistribution.from_distribution( - new_state, - self._filter_state.coeff_mat.shape, - apply_fejer=True, - **self.reduction_options, + self._filter_state = ( + FejerHypertoroidalFourierDistribution.from_distribution( + new_state, + self._filter_state.coeff_mat.shape, + apply_fejer=True, + **self.reduction_options, + ) ) return @@ -141,13 +162,17 @@ def predict_identity(self, d_sys): d_sys, "predict_identity:automaticConversion: d_sys is not a positive-kernel identity Fourier distribution. Transforming with the same number of coefficients as the filter.", ) - self._filter_state = self._filter_state.convolve(d_sys, self._filter_state.coeff_mat.shape) + self._filter_state = self._filter_state.convolve( + d_sys, self._filter_state.coeff_mat.shape + ) def get_f_trans_as_hfd(self, f, noise_distribution): """Build a positive-kernel identity transition density for nonlinear prediction.""" if not isinstance(noise_distribution, AbstractHypertoroidalDistribution): - raise TypeError("noise_distribution must be an AbstractHypertoroidalDistribution.") + raise TypeError( + "noise_distribution must be an AbstractHypertoroidalDistribution." + ) if not callable(f): raise TypeError("f must be callable.") @@ -163,19 +188,25 @@ def _f_trans(*args): if dim == 1: ws_flat = args[0].ravel() - f_out[0].ravel() else: - ws_flat = column_stack([args[i].ravel() - f_out[i].ravel() for i in range(dim)]) + ws_flat = column_stack( + [args[i].ravel() - f_out[i].ravel() for i in range(dim)] + ) pdf_vals = noise_distribution.pdf(ws_flat) return reshape(pdf_vals, grid_shape) / (2.0 * pi) ** dim with warnings.catch_warnings(): warnings.filterwarnings("ignore", "Normalization:notNormalized") - return FejerHypertoroidalFourierDistribution.from_function(_f_trans, n_coefficients_2d, apply_fejer=True, **self.reduction_options) + return FejerHypertoroidalFourierDistribution.from_function( + _f_trans, n_coefficients_2d, apply_fejer=True, **self.reduction_options + ) def predict_nonlinear(self, f, noise_distribution): """Predict with ``x(k+1) = f(x(k)) + w(k) mod 2*pi``.""" - self.predict_nonlinear_via_transition_density(self.get_f_trans_as_hfd(f, noise_distribution)) + self.predict_nonlinear_via_transition_density( + self.get_f_trans_as_hfd(f, noise_distribution) + ) def predict_nonlinear_via_transition_density(self, f_trans): """Predict using a 2*dim-dimensional transition density.""" @@ -183,7 +214,9 @@ def predict_nonlinear_via_transition_density(self, f_trans): dim = self._filter_state.dim n_coefficients = self._filter_state.coeff_mat.shape - if callable(f_trans) and not isinstance(f_trans, HypertoroidalFourierDistribution): + if callable(f_trans) and not isinstance( + f_trans, HypertoroidalFourierDistribution + ): f_trans = FejerHypertoroidalFourierDistribution.from_function( f_trans, n_coefficients * 2, @@ -192,25 +225,38 @@ def predict_nonlinear_via_transition_density(self, f_trans): ) else: if not isinstance(f_trans, HypertoroidalFourierDistribution): - raise TypeError("f_trans must be a HypertoroidalFourierDistribution or a callable.") + raise TypeError( + "f_trans must be a HypertoroidalFourierDistribution or a callable." + ) if f_trans.transformation != "identity": raise ValueError("f_trans must use the identity transformation.") if f_trans.dim != 2 * dim: - raise ValueError("f_trans must be a 2*dim-dimensional HFD (first dim dims for x_{k+1}, last dim dims for x_k).") + raise ValueError( + "f_trans must be a 2*dim-dimensional HFD (first dim dims for x_{k+1}, last dim dims for x_k)." + ) if not isinstance(f_trans, FejerHypertoroidalFourierDistribution): - f_trans = FejerHypertoroidalFourierDistribution.from_fourier_distribution( - f_trans, - f_trans.coeff_mat.shape, - apply_fejer=True, - **self.reduction_options, + f_trans = ( + FejerHypertoroidalFourierDistribution.from_fourier_distribution( + f_trans, + f_trans.coeff_mat.shape, + apply_fejer=True, + **self.reduction_options, + ) ) - hfd_reshaped = reshape(self._filter_state.coeff_mat, (1,) * dim + self._filter_state.coeff_mat.shape) + hfd_reshaped = reshape( + self._filter_state.coeff_mat, + (1,) * dim + self._filter_state.coeff_mat.shape, + ) with warnings.catch_warnings(): warnings.filterwarnings("ignore", "Normalization:notNormalized") - c_predicted = (2.0 * pi) ** (2 * dim) * signal.fftconvolve(f_trans.coeff_mat, hfd_reshaped, mode="valid") + c_predicted = (2.0 * pi) ** (2 * dim) * signal.fftconvolve( + f_trans.coeff_mat, hfd_reshaped, mode="valid" + ) c_predicted = reshape(c_predicted, n_coefficients) - self._filter_state = FejerHypertoroidalFourierDistribution(c_predicted, **self.reduction_options) + self._filter_state = FejerHypertoroidalFourierDistribution( + c_predicted, **self.reduction_options + ) def update_identity(self, d_meas, z): """Update with ``z(k) = x(k) + v(k) mod 2*pi``.""" @@ -221,9 +267,13 @@ def update_identity(self, d_meas, z): ) z = array(z) if z.shape != (self._filter_state.dim,): - raise ValueError(f"z must have shape ({self._filter_state.dim},), got {z.shape}") + raise ValueError( + f"z must have shape ({self._filter_state.dim},), got {z.shape}" + ) d_meas_shifted = d_meas.shift(z) - self._filter_state = self._filter_state.multiply(d_meas_shifted, self._filter_state.coeff_mat.shape) + self._filter_state = self._filter_state.multiply( + d_meas_shifted, self._filter_state.coeff_mat.shape + ) def update_nonlinear(self, likelihood, z=None): """Update with a Fourier likelihood or a callable likelihood.""" @@ -231,14 +281,21 @@ def update_nonlinear(self, likelihood, z=None): n_coefficients = self._filter_state.coeff_mat.shape if z is None: if not isinstance(likelihood, HypertoroidalFourierDistribution): - raise TypeError("When z is not given, likelihood must be a HypertoroidalFourierDistribution.") - likelihood = self._as_fejer_identity_distribution(likelihood, "update_nonlinear:nonFejerFourier: converting likelihood to positive-kernel identity form.") + raise TypeError( + "When z is not given, likelihood must be a HypertoroidalFourierDistribution." + ) + likelihood = self._as_fejer_identity_distribution( + likelihood, + "update_nonlinear:nonFejerFourier: converting likelihood to positive-kernel identity form.", + ) else: if not callable(likelihood): raise TypeError("likelihood must be callable when z is provided.") z = array(z) if z.shape != (self._filter_state.dim,): - raise ValueError(f"z must have shape ({self._filter_state.dim},), got {z.shape}") + raise ValueError( + f"z must have shape ({self._filter_state.dim},), got {z.shape}" + ) z_col = reshape(z, (-1, 1)) def _likelihood_fn(*grid_args): @@ -258,8 +315,13 @@ def _likelihood_fn(*grid_args): self._filter_state = self._filter_state.multiply(likelihood, n_coefficients) - def _as_fejer_identity_distribution(self, distribution, warning_message: str) -> FejerHypertoroidalFourierDistribution: - if isinstance(distribution, FejerHypertoroidalFourierDistribution) and distribution.coeff_mat.shape == self._filter_state.coeff_mat.shape: + def _as_fejer_identity_distribution( + self, distribution, warning_message: str + ) -> FejerHypertoroidalFourierDistribution: + if ( + isinstance(distribution, FejerHypertoroidalFourierDistribution) + and distribution.coeff_mat.shape == self._filter_state.coeff_mat.shape + ): return distribution if isinstance(distribution, HypertoroidalFourierDistribution): @@ -274,7 +336,9 @@ def _as_fejer_identity_distribution(self, distribution, warning_message: str) -> ) if not isinstance(distribution, AbstractHypertoroidalDistribution): - raise TypeError("distribution must be an AbstractHypertoroidalDistribution.") + raise TypeError( + "distribution must be an AbstractHypertoroidalDistribution." + ) warnings.warn(warning_message, RuntimeWarning) return FejerHypertoroidalFourierDistribution.from_distribution( distribution, diff --git a/src/pyrecest/filters/gaussian_hypothesis_mixture.py b/src/pyrecest/filters/gaussian_hypothesis_mixture.py index 66a4b938b..9f7554a22 100644 --- a/src/pyrecest/filters/gaussian_hypothesis_mixture.py +++ b/src/pyrecest/filters/gaussian_hypothesis_mixture.py @@ -110,7 +110,9 @@ def _as_float_array(value: Any, name: str) -> np.ndarray: raise ValueError(f"{name} must contain real numeric values") from exc if array.dtype.kind in _INVALID_FLOAT_ARRAY_KINDS or ( array.dtype.kind == "O" - and any(isinstance(item, _INVALID_FLOAT_ARRAY_SCALAR_TYPES) for item in array.flat) + and any( + isinstance(item, _INVALID_FLOAT_ARRAY_SCALAR_TYPES) for item in array.flat + ) ): raise ValueError(f"{name} must contain real numeric values") try: diff --git a/src/pyrecest/filters/linear_update_planning.py b/src/pyrecest/filters/linear_update_planning.py index 396e8b2e7..618488f50 100644 --- a/src/pyrecest/filters/linear_update_planning.py +++ b/src/pyrecest/filters/linear_update_planning.py @@ -343,7 +343,9 @@ def gate_threshold_for_measurement( source = str(measurement.source) if gate_thresholds_by_source and source in gate_thresholds_by_source: value = gate_thresholds_by_source[source] - return None if value is None else _as_nonnegative_scalar(value, "gate_threshold") + return ( + None if value is None else _as_nonnegative_scalar(value, "gate_threshold") + ) if gate_probabilities_by_source and source in gate_probabilities_by_source: probability = gate_probabilities_by_source[source] vector = _as_finite_array(measurement.vector, "measurement.vector").reshape(-1) diff --git a/src/pyrecest/filters/low_rank_hypertoroidal_fourier_filter.py b/src/pyrecest/filters/low_rank_hypertoroidal_fourier_filter.py index 7a8ca5b6d..f3b2e3863 100644 --- a/src/pyrecest/filters/low_rank_hypertoroidal_fourier_filter.py +++ b/src/pyrecest/filters/low_rank_hypertoroidal_fourier_filter.py @@ -38,7 +38,9 @@ def __init__( atol=0.0, ): if pyrecest.backend.__backend_name__ != "numpy": # pylint: disable=no-member - raise NotImplementedError("LowRankHypertoroidalFourierFilter is NumPy-only.") + raise NotImplementedError( + "LowRankHypertoroidalFourierFilter is NumPy-only." + ) if transformation != "identity": raise NotImplementedError( "The first low-rank prototype supports only transformation='identity'." @@ -73,7 +75,9 @@ def filter_state(self, new_state): new_state, max_rank=self.max_rank, rtol=self.rtol, atol=self.atol ) if new_state.transformation != "identity": - raise NotImplementedError("Only identity-transformed low-rank states are supported.") + raise NotImplementedError( + "Only identity-transformed low-rank states are supported." + ) self._filter_state = new_state def _convert_noise(self, distribution): diff --git a/src/pyrecest/filters/measurement_reliability.py b/src/pyrecest/filters/measurement_reliability.py index 1f1a72929..b9cbbf9ff 100644 --- a/src/pyrecest/filters/measurement_reliability.py +++ b/src/pyrecest/filters/measurement_reliability.py @@ -318,4 +318,4 @@ def normalize_measurement_noise_covariances( shared_noise = as_covariance_matrix(noise, measurement_dim, name) if n_measurements == 0: return zeros(empty_shape) - return stack([shared_noise for _ in range(n_measurements)]) \ No newline at end of file + return stack([shared_noise for _ in range(n_measurements)]) diff --git a/src/pyrecest/filters/mode_rbpf_manifold_ukf_tracker.py b/src/pyrecest/filters/mode_rbpf_manifold_ukf_tracker.py index b77a1eec2..0c2fe5610 100644 --- a/src/pyrecest/filters/mode_rbpf_manifold_ukf_tracker.py +++ b/src/pyrecest/filters/mode_rbpf_manifold_ukf_tracker.py @@ -673,9 +673,9 @@ def _canonicalize_particles(self): swapped_log_axes = self.mu[swap, 5:7][:, ::-1].copy() state_permutation = np.array([0, 1, 2, 3, 4, 6, 5]) self.mu[swap, 5:7] = swapped_log_axes - self.covariances[swap] = self.covariances[swap][ - :, state_permutation - ][:, :, state_permutation] + self.covariances[swap] = self.covariances[swap][:, state_permutation][ + :, :, state_permutation + ] self.mu[swap, 4] = self._wrap_ellipse_angle(self.mu[swap, 4] + np.pi / 2.0) def get_point_estimate(self): diff --git a/src/pyrecest/filters/multisensor_hdp_association.py b/src/pyrecest/filters/multisensor_hdp_association.py index 75089033f..0060f1c25 100644 --- a/src/pyrecest/filters/multisensor_hdp_association.py +++ b/src/pyrecest/filters/multisensor_hdp_association.py @@ -160,7 +160,9 @@ def best_assignments(self) -> tuple[HDPAssociationDecision, ...]: HDPAssociationDecision( measurement_index=measurement_index, label=self.labels[label_index], - probability=float(self.probabilities[measurement_index, label_index]), + probability=float( + self.probabilities[measurement_index, label_index] + ), log_weight=float(self.log_weights[measurement_index, label_index]), ) ) @@ -259,7 +261,9 @@ def multisensor_hdp_association( ) global_mass = float(target_weights.sum() + global_birth_weight) if global_mass <= 0.0: - raise ValueError("global target and birth weights must contain positive total mass") + raise ValueError( + "global target and birth weights must contain positive total mass" + ) base_target_weights = target_weights / global_mass base_birth_weight = global_birth_weight / global_mass diff --git a/src/pyrecest/filters/nonparametric_cardinality.py b/src/pyrecest/filters/nonparametric_cardinality.py index 6c1b6b078..053b81fe8 100644 --- a/src/pyrecest/filters/nonparametric_cardinality.py +++ b/src/pyrecest/filters/nonparametric_cardinality.py @@ -115,7 +115,9 @@ def _coerce_cluster_sizes(cluster_sizes): try: raw_sizes = tuple(cluster_sizes) except TypeError as exc: - raise TypeError("cluster_sizes must be an iterable of positive integers.") from exc + raise TypeError( + "cluster_sizes must be an iterable of positive integers." + ) from exc sizes = tuple( _validate_positive_integer(size, "cluster_sizes") for size in raw_sizes @@ -124,11 +126,15 @@ def _coerce_cluster_sizes(cluster_sizes): @staticmethod def _validate_count_state(num_observations, num_clusters): - num_observations = _validate_nonnegative_integer(num_observations, "num_observations") + num_observations = _validate_nonnegative_integer( + num_observations, "num_observations" + ) num_clusters = _validate_nonnegative_integer(num_clusters, "num_clusters") if num_observations == 0: if num_clusters != 0: - raise ValueError("num_clusters must be zero when num_observations is zero.") + raise ValueError( + "num_clusters must be zero when num_observations is zero." + ) elif not 1 <= num_clusters <= num_observations: raise ValueError("num_clusters must be between one and num_observations.") return num_observations, num_clusters @@ -158,9 +164,13 @@ def predictive_existing_cluster_probabilities(self, cluster_sizes): def predictive_new_cluster_probability(self, cluster_sizes): """Return the predictive probability that the next observation starts a new cluster.""" sizes = self._coerce_cluster_sizes(cluster_sizes) - return self.predictive_new_cluster_probability_from_counts(sum(sizes), len(sizes)) + return self.predictive_new_cluster_probability_from_counts( + sum(sizes), len(sizes) + ) - def predictive_new_cluster_probability_from_counts(self, num_observations, num_clusters): + def predictive_new_cluster_probability_from_counts( + self, num_observations, num_clusters + ): """Return the next-observation new-cluster probability from count summaries. Parameters @@ -170,29 +180,45 @@ def predictive_new_cluster_probability_from_counts(self, num_observations, num_c num_clusters : int Number of occupied clusters represented by those observations. """ - num_observations, num_clusters = self._validate_count_state(num_observations, num_clusters) + num_observations, num_clusters = self._validate_count_state( + num_observations, num_clusters + ) if num_observations == 0: return 1.0 - return (self.strength + self.discount * num_clusters) / (self.strength + num_observations) + return (self.strength + self.discount * num_clusters) / ( + self.strength + num_observations + ) def predictive_assignment_probabilities(self, cluster_sizes): """Return existing-cluster probabilities followed by the new-cluster probability.""" - existing_probabilities = self.predictive_existing_cluster_probabilities(cluster_sizes) - return existing_probabilities + (self.predictive_new_cluster_probability(cluster_sizes),) + existing_probabilities = self.predictive_existing_cluster_probabilities( + cluster_sizes + ) + return existing_probabilities + ( + self.predictive_new_cluster_probability(cluster_sizes), + ) def expected_number_of_clusters(self, num_observations): """Return ``E[K_n]`` after ``num_observations`` exchangeable observations.""" - num_observations = _validate_nonnegative_integer(num_observations, "num_observations") + num_observations = _validate_nonnegative_integer( + num_observations, "num_observations" + ) return self.expected_additional_clusters(num_observations) - def expected_additional_clusters(self, additional_observations, initial_observations=0, initial_clusters=0): + def expected_additional_clusters( + self, additional_observations, initial_observations=0, initial_clusters=0 + ): """Return the expected number of new clusters in a future batch. The recurrence is exact for the Pitman--Yor predictive rule because the new-cluster probability is linear in the current number of clusters. """ - additional_observations = _validate_nonnegative_integer(additional_observations, "additional_observations") - initial_observations, initial_clusters = self._validate_count_state(initial_observations, initial_clusters) + additional_observations = _validate_nonnegative_integer( + additional_observations, "additional_observations" + ) + initial_observations, initial_clusters = self._validate_count_state( + initial_observations, initial_clusters + ) observations = initial_observations expected_total_clusters = float(initial_clusters) @@ -200,7 +226,9 @@ def expected_additional_clusters(self, additional_observations, initial_observat if observations == 0: new_cluster_probability = 1.0 else: - new_cluster_probability = (self.strength + self.discount * expected_total_clusters) / (self.strength + observations) + new_cluster_probability = ( + self.strength + self.discount * expected_total_clusters + ) / (self.strength + observations) expected_total_clusters += new_cluster_probability observations += 1 return expected_total_clusters - initial_clusters @@ -220,7 +248,9 @@ def log_eppf(self, cluster_sizes): log_probability = 0.0 for cluster_index in range(1, num_clusters): log_probability += log(self.strength + cluster_index * self.discount) - log_probability -= _log_rising_factorial(self.strength + 1.0, num_observations - 1) + log_probability -= _log_rising_factorial( + self.strength + 1.0, num_observations - 1 + ) for size in sizes: log_probability += _log_rising_factorial(1.0 - self.discount, size - 1) return float(log_probability) @@ -286,16 +316,30 @@ class PitmanYorBirthProbability: cardinality_prior: PitmanYorProcessCardinalityPrior = field(init=False) def __post_init__(self): - cardinality_prior = PitmanYorProcessCardinalityPrior(self.discount, self.strength) - base_birth_existence_probability = _validate_probability(self.base_birth_existence_probability, "base_birth_existence_probability") - prior_observation_count, prior_cluster_count = cardinality_prior._validate_count_state(self.prior_observation_count, self.prior_cluster_count) - minimum_probability = _validate_probability(self.minimum_probability, "minimum_probability") - maximum_probability = _validate_probability(self.maximum_probability, "maximum_probability") + cardinality_prior = PitmanYorProcessCardinalityPrior( + self.discount, self.strength + ) + base_birth_existence_probability = _validate_probability( + self.base_birth_existence_probability, "base_birth_existence_probability" + ) + prior_observation_count, prior_cluster_count = ( + cardinality_prior._validate_count_state( + self.prior_observation_count, self.prior_cluster_count + ) + ) + minimum_probability = _validate_probability( + self.minimum_probability, "minimum_probability" + ) + maximum_probability = _validate_probability( + self.maximum_probability, "maximum_probability" + ) if minimum_probability > maximum_probability: raise ValueError("minimum_probability must not exceed maximum_probability.") object.__setattr__(self, "cardinality_prior", cardinality_prior) - object.__setattr__(self, "base_birth_existence_probability", base_birth_existence_probability) + object.__setattr__( + self, "base_birth_existence_probability", base_birth_existence_probability + ) object.__setattr__(self, "prior_observation_count", prior_observation_count) object.__setattr__(self, "prior_cluster_count", prior_cluster_count) object.__setattr__(self, "minimum_probability", minimum_probability) @@ -319,10 +363,24 @@ def __call__( """ del measurement, measurement_index, num_measurements - num_existing_components = _validate_nonnegative_integer(num_existing_components, "num_existing_components") + num_existing_components = _validate_nonnegative_integer( + num_existing_components, "num_existing_components" + ) num_new_births = _validate_nonnegative_integer(num_new_births, "num_new_births") - num_observations = self.prior_observation_count + num_existing_components + num_new_births - num_clusters = self.prior_cluster_count + num_existing_components + num_new_births - new_cluster_probability = self.cardinality_prior.predictive_new_cluster_probability_from_counts(num_observations, num_clusters) - birth_probability = self.base_birth_existence_probability * new_cluster_probability - return _clip_probability(birth_probability, self.minimum_probability, self.maximum_probability) + num_observations = ( + self.prior_observation_count + num_existing_components + num_new_births + ) + num_clusters = ( + self.prior_cluster_count + num_existing_components + num_new_births + ) + new_cluster_probability = ( + self.cardinality_prior.predictive_new_cluster_probability_from_counts( + num_observations, num_clusters + ) + ) + birth_probability = ( + self.base_birth_existence_probability * new_cluster_probability + ) + return _clip_probability( + birth_probability, self.minimum_probability, self.maximum_probability + ) diff --git a/src/pyrecest/filters/survival_association.py b/src/pyrecest/filters/survival_association.py index 2b18e76cc..9a88b622e 100644 --- a/src/pyrecest/filters/survival_association.py +++ b/src/pyrecest/filters/survival_association.py @@ -116,7 +116,10 @@ def track_survival_prior_components( ) -> list[TrackSurvivalPriorComponents]: """Resolve survival-aware prior factors for every track.""" cfg = _as_config(config) - return [_components_for_track(tracks, i, current_step=current_step, config=cfg) for i in range(len(tracks))] + return [ + _components_for_track(tracks, i, current_step=current_step, config=cfg) + for i in range(len(tracks)) + ] def survival_aware_missed_detection_costs( @@ -127,7 +130,12 @@ def survival_aware_missed_detection_costs( ) -> np.ndarray: """Return per-track costs for leaving tracks unmatched.""" return np.asarray( - [component.missed_detection_cost for component in track_survival_prior_components(tracks, current_step=current_step, config=config)] + [ + component.missed_detection_cost + for component in track_survival_prior_components( + tracks, current_step=current_step, config=config + ) + ] ) @@ -176,16 +184,28 @@ def apply_survival_association_prior( ) -> list[AssociationHypothesis]: """Fold survival-aware prior factors into pairwise hypothesis scores.""" _validate_cost_mode(cost_mode) - components = track_survival_prior_components(tracks, current_step=current_step, config=config) + components = track_survival_prior_components( + tracks, current_step=current_step, config=config + ) adjusted = [] for hypothesis in hypotheses: if hypothesis.is_missed_detection: adjusted.append(hypothesis) continue - component = components[_valid_track_index(hypothesis.track_index, len(components))] + component = components[ + _valid_track_index(hypothesis.track_index, len(components)) + ] log_weight = component.log_assignment_weight - adjusted_log_likelihood = None if hypothesis.log_likelihood is None else float(hypothesis.log_likelihood) + log_weight - adjusted_probability = None if hypothesis.probability is None else float(hypothesis.probability) * component.assignment_weight + adjusted_log_likelihood = ( + None + if hypothesis.log_likelihood is None + else float(hypothesis.log_likelihood) + log_weight + ) + adjusted_probability = ( + None + if hypothesis.probability is None + else float(hypothesis.probability) * component.assignment_weight + ) metadata = dict(hypothesis.metadata or {}) metadata["survival_prior"] = { "track_mass": component.track_mass, @@ -200,7 +220,9 @@ def apply_survival_association_prior( adjusted.append( replace( hypothesis, - cost=_adjusted_cost(hypothesis, log_weight, adjusted_log_likelihood, cost_mode), + cost=_adjusted_cost( + hypothesis, log_weight, adjusted_log_likelihood, cost_mode + ), log_likelihood=adjusted_log_likelihood, probability=adjusted_probability, metadata=metadata, @@ -249,11 +271,17 @@ def associator(tracks, measurements, **kwargs): config=cfg, cost_mode=eff_cost_mode, ) - measurement_vectors = _coerce_measurements(measurements, measurement_axis=eff_axis, measurement_dim=measurement_dim) + measurement_vectors = _coerce_measurements( + measurements, measurement_axis=eff_axis, measurement_dim=measurement_dim + ) track_cost = kwargs.get("unassigned_track_cost", unassigned_track_cost) if track_cost is None: - track_cost = survival_aware_missed_detection_costs(tracks, current_step=eff_step, config=cfg) - measurement_cost = kwargs.get("unassigned_measurement_cost", unassigned_measurement_cost) + track_cost = survival_aware_missed_detection_costs( + tracks, current_step=eff_step, config=cfg + ) + measurement_cost = kwargs.get( + "unassigned_measurement_cost", unassigned_measurement_cost + ) if measurement_cost is None: measurement_cost = survival_aware_birth_costs( measurement_vectors, @@ -286,8 +314,18 @@ def _components_for_track(tracks, track_index, *, current_step, config): track = tracks[track_index] miss_count = _misses(track) steps_since_seen = _steps_since_seen(track, current_step=current_step) - base_mass = _resolve_track_nonnegative(config.track_mass, tracks, track_index, "track_mass", current_step=current_step, default_value=max(float(_hits(track)), 1.0)) - track_mass = max(base_mass * float(config.track_mass_decay) ** miss_count, float(config.minimum_track_mass)) + base_mass = _resolve_track_nonnegative( + config.track_mass, + tracks, + track_index, + "track_mass", + current_step=current_step, + default_value=max(float(_hits(track)), 1.0), + ) + track_mass = max( + base_mass * float(config.track_mass_decay) ** miss_count, + float(config.minimum_track_mass), + ) survival = ( _resolve_track_probability( config.survival_probability, @@ -339,27 +377,61 @@ def _components_for_track(tracks, track_index, *, current_step, config): def _adjusted_cost(hypothesis, log_weight, adjusted_log_likelihood, cost_mode): - if cost_mode == "log_likelihood" or (cost_mode == "auto" and adjusted_log_likelihood is not None): + if cost_mode == "log_likelihood" or ( + cost_mode == "auto" and adjusted_log_likelihood is not None + ): if adjusted_log_likelihood is not None: return -float(adjusted_log_likelihood) return hypothesis_cost(hypothesis) - log_weight -def _resolve_track_probability(value, tracks, index, name, *, current_step, attr_name=None, metadata_key=None, default_value): +def _resolve_track_probability( + value, + tracks, + index, + name, + *, + current_step, + attr_name=None, + metadata_key=None, + default_value, +): return _as_probability( - _resolve_track_value(value, tracks, index, name, current_step=current_step, attr_name=attr_name, metadata_key=metadata_key, default_value=default_value), + _resolve_track_value( + value, + tracks, + index, + name, + current_step=current_step, + attr_name=attr_name, + metadata_key=metadata_key, + default_value=default_value, + ), name, ) -def _resolve_track_nonnegative(value, tracks, index, name, *, current_step, default_value): +def _resolve_track_nonnegative( + value, tracks, index, name, *, current_step, default_value +): return _as_nonnegative_scalar( - _resolve_track_value(value, tracks, index, name, current_step=current_step, attr_name=None, metadata_key=None, default_value=default_value), + _resolve_track_value( + value, + tracks, + index, + name, + current_step=current_step, + attr_name=None, + metadata_key=None, + default_value=default_value, + ), name, ) -def _resolve_track_value(value, tracks, index, name, *, current_step, attr_name, metadata_key, default_value): +def _resolve_track_value( + value, tracks, index, name, *, current_step, attr_name, metadata_key, default_value +): track = tracks[index] if value is None: metadata = _metadata(track) @@ -374,22 +446,30 @@ def _resolve_track_value(value, tracks, index, name, *, current_step, attr_name, if values.shape == (): return _scalar_array_item(values, name) if values.size != len(tracks): - raise ValueError(f"{name} must be scalar, callable, or have length {len(tracks)}") + raise ValueError( + f"{name} must be scalar, callable, or have length {len(tracks)}" + ) return values.reshape(-1)[index] -def _resolve_measurement_probability(value, measurements, index, name, *, current_step, default_value): +def _resolve_measurement_probability( + value, measurements, index, name, *, current_step, default_value +): if value is None: resolved = default_value elif callable(value): - resolved = value(measurements[index], measurement_index=index, current_step=current_step) + resolved = value( + measurements[index], measurement_index=index, current_step=current_step + ) else: values = np.asarray(value) if values.shape == (): resolved = _scalar_array_item(values, name) else: if values.size != len(measurements): - raise ValueError(f"{name} must be scalar, callable, or have length {len(measurements)}") + raise ValueError( + f"{name} must be scalar, callable, or have length {len(measurements)}" + ) resolved = values.reshape(-1)[index] return _as_probability(resolved, name) @@ -428,14 +508,22 @@ def _steps_since_seen(track, *, current_step): def _valid_track_index(value, num_tracks): values = np.asarray(value) dtype_kind = getattr(values.dtype, "kind", None) - if values.shape != () or values.dtype == np.bool_ or dtype_kind in _INVALID_NUMERIC_DTYPE_KINDS: + if ( + values.shape != () + or values.dtype == np.bool_ + or dtype_kind in _INVALID_NUMERIC_DTYPE_KINDS + ): raise ValueError("hypothesis.track_index must be a nonnegative integer") scalar = values.item() if isinstance(scalar, (bool, np.bool_)): raise ValueError("hypothesis.track_index must be a nonnegative integer") if isinstance(scalar, (int, np.integer)): index = int(scalar) - elif isinstance(scalar, (float, np.floating)) and np.isfinite(scalar) and float(scalar).is_integer(): + elif ( + isinstance(scalar, (float, np.floating)) + and np.isfinite(scalar) + and float(scalar).is_integer() + ): index = int(scalar) else: raise ValueError("hypothesis.track_index must be a nonnegative integer") @@ -452,7 +540,11 @@ def _validate_cost_mode(cost_mode): def _as_scalar_float(value, name): values = np.asarray(value) dtype_kind = getattr(values.dtype, "kind", None) - if values.shape != () or values.dtype == np.bool_ or dtype_kind in _INVALID_NUMERIC_DTYPE_KINDS: + if ( + values.shape != () + or values.dtype == np.bool_ + or dtype_kind in _INVALID_NUMERIC_DTYPE_KINDS + ): raise ValueError(f"{name} must be a scalar number") scalar = values.item() if isinstance(scalar, _INVALID_SCALAR_TYPES): diff --git a/src/pyrecest/filters/survival_aware_association.py b/src/pyrecest/filters/survival_aware_association.py index b627cbe9b..7a6638a1e 100644 --- a/src/pyrecest/filters/survival_aware_association.py +++ b/src/pyrecest/filters/survival_aware_association.py @@ -21,7 +21,10 @@ association_result_from_hypotheses, linear_gaussian_association_hypotheses, ) -from .survival_aware_crp import SurvivalAwareCRPAssociationPrior, SurvivalAwareTrackEvidence +from .survival_aware_crp import ( + SurvivalAwareCRPAssociationPrior, + SurvivalAwareTrackEvidence, +) from .track_manager import AssociationResult FactorSpec = float | Callable[..., float] @@ -59,12 +62,16 @@ def __post_init__(self) -> None: raise TypeError("crp_prior must be a SurvivalAwareCRPAssociationPrior") _validate_probability_spec(self.survival_probability, "survival_probability") _validate_probability_spec(self.detection_probability, "detection_probability") - _validate_probability_spec(self.visibility_probability, "visibility_probability") + _validate_probability_spec( + self.visibility_probability, "visibility_probability" + ) _validate_nonnegative_likelihood_spec( self.appearance_likelihood, "appearance_likelihood" ) if self.existence_probability is not None: - _validate_probability_spec(self.existence_probability, "existence_probability") + _validate_probability_spec( + self.existence_probability, "existence_probability" + ) _validate_probability(self.mass_decay, "mass_decay", allow_zero=True) _validate_nonnegative_finite(self.mass_power, "mass_power") _validate_nonnegative_finite(self.birth_weight, "birth_weight") @@ -148,9 +155,11 @@ def survival_aware_missed_detection_costs( measurement_index=None, step=step, ) - predicted_existence = SurvivalAwareCRPAssociationPrior.predict_existence_probability( - existence, - survival, + predicted_existence = ( + SurvivalAwareCRPAssociationPrior.predict_existence_probability( + existence, + survival, + ) ) missed_probability = 1.0 - predicted_existence * detection * visibility missed_probability = max( @@ -272,7 +281,9 @@ def build_survival_aware_linear_gaussian_hypothesis_associator( def associator(tracks, measurements, **kwargs) -> AssociationResult: effective_config = kwargs.get("config", config) - effective_measurement_matrix = kwargs.get("measurement_matrix", measurement_matrix) + effective_measurement_matrix = kwargs.get( + "measurement_matrix", measurement_matrix + ) effective_measurement_axis = kwargs.get("measurement_axis", measurement_axis) hypotheses = survival_aware_linear_gaussian_association_hypotheses( tracks, @@ -291,8 +302,10 @@ def associator(tracks, measurements, **kwargs) -> AssociationResult: measurements, effective_measurement_matrix, effective_measurement_axis ) if unassigned_measurement_cost is None: - default_unassigned_measurement_cost = survival_aware_unassigned_measurement_cost( - effective_config, num_existing_tracks=len(tracks) + default_unassigned_measurement_cost = ( + survival_aware_unassigned_measurement_cost( + effective_config, num_existing_tracks=len(tracks) + ) ) else: default_unassigned_measurement_cost = unassigned_measurement_cost @@ -315,7 +328,9 @@ def associator(tracks, measurements, **kwargs) -> AssociationResult: return associator -def _crp_prior(config: SurvivalAwareAssociationConfig) -> SurvivalAwareCRPAssociationPrior: +def _crp_prior( + config: SurvivalAwareAssociationConfig, +) -> SurvivalAwareCRPAssociationPrior: if config.crp_prior is not None: return config.crp_prior return SurvivalAwareCRPAssociationPrior( @@ -337,7 +352,11 @@ def _survival_aware_track_evidence( return SurvivalAwareTrackEvidence( mass=_effective_track_mass(track, config), existence_probability=_resolve_existence_probability( - track, measurement, measurement_index=measurement_index, step=step, config=config + track, + measurement, + measurement_index=measurement_index, + step=step, + config=config, ), survival_probability=_resolve_probability( config.survival_probability, @@ -566,17 +585,23 @@ def _coerce_measurements_for_prior( try: array = _as_numpy_array(measurements) except (TypeError, ValueError, RuntimeError): - return [_as_numpy_array(measurement).reshape(-1) for measurement in measurements] + return [ + _as_numpy_array(measurement).reshape(-1) for measurement in measurements + ] if array.ndim == 1: return [array.reshape(-1)] if array.ndim != 2: - return [_as_numpy_array(measurement).reshape(-1) for measurement in measurements] + return [ + _as_numpy_array(measurement).reshape(-1) for measurement in measurements + ] if measurement_axis == "columns": return [array[:, index].reshape(-1) for index in range(array.shape[1])] if measurement_axis in ("rows", "sequence"): return [array[index, :].reshape(-1) for index in range(array.shape[0])] if measurement_axis != "auto": - raise ValueError("measurement_axis must be 'auto', 'columns', 'rows', or 'sequence'") + raise ValueError( + "measurement_axis must be 'auto', 'columns', 'rows', or 'sequence'" + ) columns_match = array.shape[0] == measurement_dim rows_match = array.shape[1] == measurement_dim if columns_match and not rows_match: diff --git a/src/pyrecest/filters/survival_aware_crp.py b/src/pyrecest/filters/survival_aware_crp.py index 5182a83b5..d377fcbde 100644 --- a/src/pyrecest/filters/survival_aware_crp.py +++ b/src/pyrecest/filters/survival_aware_crp.py @@ -56,21 +56,31 @@ def _validate_nonnegative_integer(value, name): def _normalize_assignment_weights(weights, minimum_total_weight): """Normalize finite nonnegative association weights without sum overflow.""" - weights = tuple(_validate_nonnegative(weight, "association weight") for weight in weights) + weights = tuple( + _validate_nonnegative(weight, "association weight") for weight in weights + ) if not weights: - raise ValueError("At least one association alternative must have positive weight.") + raise ValueError( + "At least one association alternative must have positive weight." + ) scale = max(weights) if scale <= 0.0: - raise ValueError("At least one association alternative must have positive weight.") + raise ValueError( + "At least one association alternative must have positive weight." + ) scaled_weights = tuple(weight / scale for weight in weights) scaled_total = sum(scaled_weights) if not isfinite(scaled_total) or scaled_total <= 0.0: - raise ValueError("At least one association alternative must have positive weight.") + raise ValueError( + "At least one association alternative must have positive weight." + ) if scale <= minimum_total_weight / scaled_total: - raise ValueError("At least one association alternative must have positive weight.") + raise ValueError( + "At least one association alternative must have positive weight." + ) return tuple(weight / scaled_total for weight in scaled_weights) @@ -269,7 +279,7 @@ def effective_track_mass(self, track_evidence): track_evidence = self._coerce_track_evidence(track_evidence) return track_evidence.mass * ( - self.temporal_decay ** track_evidence.last_seen_steps + self.temporal_decay**track_evidence.last_seen_steps ) def existing_track_weight(self, track_evidence): @@ -407,15 +417,12 @@ def missed_detection_existence_probability( "visibility_probability", ) - effective_detection_probability = ( - detection_probability * visibility_probability - ) + effective_detection_probability = detection_probability * visibility_probability numerator = predicted_existence_probability * ( 1.0 - effective_detection_probability ) denominator = ( - 1.0 - - predicted_existence_probability * effective_detection_probability + 1.0 - predicted_existence_probability * effective_detection_probability ) if denominator <= 0.0: return 0.0 diff --git a/src/pyrecest/filters/update_diagnostics.py b/src/pyrecest/filters/update_diagnostics.py index 7d88a66a8..ce6299447 100644 --- a/src/pyrecest/filters/update_diagnostics.py +++ b/src/pyrecest/filters/update_diagnostics.py @@ -53,7 +53,9 @@ def __post_init__(self): "active_measurement_indices must be smaller than measurement_count" ) object.__setattr__(self, "measurement_count", measurement_count) - object.__setattr__(self, "skipped_reason", _normalize_skipped_reason(self.skipped_reason)) + object.__setattr__( + self, "skipped_reason", _normalize_skipped_reason(self.skipped_reason) + ) object.__setattr__(self, "metadata", _normalize_metadata(self.metadata)) @property diff --git a/src/pyrecest/filters/wrapped_normal_filter.py b/src/pyrecest/filters/wrapped_normal_filter.py index e2de0e247..df25d8d77 100644 --- a/src/pyrecest/filters/wrapped_normal_filter.py +++ b/src/pyrecest/filters/wrapped_normal_filter.py @@ -140,7 +140,9 @@ def update_nonlinear_progressive( ) if current_lambda <= 0: - raise ValueError("Progressive update with given threshold impossible") + raise ValueError( + "Progressive update with given threshold impossible" + ) current_lambda = maximum(current_lambda, MINIMUM_LAMBDA) current_lambda = minimum(current_lambda, lambda_) diff --git a/src/pyrecest/models/_sampleable_transition_validation.py b/src/pyrecest/models/_sampleable_transition_validation.py index 330386401..d6f8f9fe8 100644 --- a/src/pyrecest/models/_sampleable_transition_validation.py +++ b/src/pyrecest/models/_sampleable_transition_validation.py @@ -37,7 +37,9 @@ def _patch_sampler_count_check(model_cls) -> None: def checked_sample_next(self, state, n=1): has_sampler = getattr(self, "_sample_next", None) is not None - has_count_argument = getattr(self, "_sample_next_count_call_mode", None) is not None + has_count_argument = ( + getattr(self, "_sample_next_count_call_mode", None) is not None + ) if has_sampler and not has_count_argument and _requested_sample_count(n) != 1: raise TypeError("sample count is not supported by this sampler.") return original(self, state, n=n) diff --git a/src/pyrecest/models/additive_noise.py b/src/pyrecest/models/additive_noise.py index e76f1d6df..0f375c6df 100644 --- a/src/pyrecest/models/additive_noise.py +++ b/src/pyrecest/models/additive_noise.py @@ -420,7 +420,9 @@ def has_jacobian(self): def measurement_residual(self, measurement, state, **kwargs): """Return ``measurement - h(state)``.""" - return _array_difference(measurement, self.measurement_function(state, **kwargs)) + return _array_difference( + measurement, self.measurement_function(state, **kwargs) + ) def sample_measurement(self, state, n: int = 1, **kwargs): """Draw ``n`` samples from ``p(measurement | state)``.""" diff --git a/src/pyrecest/models/validation.py b/src/pyrecest/models/validation.py index 71b60e005..3679be3a4 100644 --- a/src/pyrecest/models/validation.py +++ b/src/pyrecest/models/validation.py @@ -27,8 +27,7 @@ def _as_backend_array(value: Any, name: str): except (TypeError, ValueError, RuntimeError, OverflowError): value_array = None if value_array is not None and ( - _array_has_temporal_dtype(value_array) - or _contains_temporal_scalar(value_array) + _array_has_temporal_dtype(value_array) or _contains_temporal_scalar(value_array) ): raise ValueError(f"{name} must contain numeric non-boolean values.") try: @@ -98,7 +97,16 @@ def _validate_nonnegative_finite_scalar(value: Any, name: str) -> float: scalar = value_array.item() if isinstance( scalar, - (bool, np.bool_, str, bytes, bytearray, np.str_, np.bytes_, *_TEMPORAL_SCALAR_TYPES), + ( + bool, + np.bool_, + str, + bytes, + bytearray, + np.str_, + np.bytes_, + *_TEMPORAL_SCALAR_TYPES, + ), ): raise ValueError(f"{name} must be a finite nonnegative scalar.") try: @@ -136,7 +144,9 @@ def _validate_expected_dim_scalar(value: Any, dim_name: str) -> int: ): raise TypeError(f"{dim_name} must be an integer or None.") scalar = value_array.item() - if isinstance(scalar, (bool, np.bool_, *_TEMPORAL_SCALAR_TYPES)) or not isinstance(scalar, Integral): + if isinstance(scalar, (bool, np.bool_, *_TEMPORAL_SCALAR_TYPES)) or not isinstance( + scalar, Integral + ): raise TypeError(f"{dim_name} must be an integer or None.") return int(scalar) @@ -470,7 +480,9 @@ def infer_state_dim_from_distribution( if hasattr(distribution, "d"): try: - support = _maybe_call(getattr(distribution, "d"), allow_methods=allow_methods) + support = _maybe_call( + getattr(distribution, "d"), allow_methods=allow_methods + ) support = validate_matrix(support, name="distribution.d") return _shape_tuple(support)[1] except (TypeError, ValueError): diff --git a/src/pyrecest/smoothers/__init__.py b/src/pyrecest/smoothers/__init__.py index 067dc5f4e..28701f103 100644 --- a/src/pyrecest/smoothers/__init__.py +++ b/src/pyrecest/smoothers/__init__.py @@ -1,5 +1,10 @@ from .abstract_smoother import AbstractSmoother from .delayed_output import DelayedStateOutput, DelayedStateOutputMixin +from .fbfb_mem_qkf_smoother import ( + FBFBMEMQKFSmoother, + ForwardBackwardForwardBackwardMEMQKFSmoother, + ForwardBackwardMEMQKFSmoother, +) from .fixed_lag_mem_qkf_smoother import ( FixedIntervalMEMQKFSmoother, FixedIntervalMemQkfSmoother, @@ -11,11 +16,6 @@ MEMQKFSmootherGain, MEMQKFTrackerState, ) -from .fbfb_mem_qkf_smoother import ( - FBFBMEMQKFSmoother, - ForwardBackwardForwardBackwardMEMQKFSmoother, - ForwardBackwardMEMQKFSmoother, -) from .fixed_lag_random_matrix_smoother import ( FactorizedGIWRandomMatrixTrackerState, FixedLagFactorizedGIWRandomMatrixSmoother, diff --git a/src/pyrecest/smoothers/hypertoroidal_fourier_smoother.py b/src/pyrecest/smoothers/hypertoroidal_fourier_smoother.py index d2cd4e7a7..e6094be91 100644 --- a/src/pyrecest/smoothers/hypertoroidal_fourier_smoother.py +++ b/src/pyrecest/smoothers/hypertoroidal_fourier_smoother.py @@ -72,18 +72,24 @@ def smooth_identity(self, filtered_states, likelihoods, system_noises): likelihoods = self._as_distribution_list(likelihoods, "likelihoods") if len(likelihoods) != len(filtered_states): - raise ValueError("likelihoods must have the same length as filtered_states.") + raise ValueError( + "likelihoods must have the same length as filtered_states." + ) n_steps = len(filtered_states) reference = filtered_states[0] n_coefficients = reference.coeff_mat.shape filtered_states = [ - self._ensure_compatible(dist, reference, n_coefficients, f"filtered_states[{idx}]") + self._ensure_compatible( + dist, reference, n_coefficients, f"filtered_states[{idx}]" + ) for idx, dist in enumerate(filtered_states) ] likelihoods = [ - self._ensure_compatible(dist, reference, n_coefficients, f"likelihoods[{idx}]") + self._ensure_compatible( + dist, reference, n_coefficients, f"likelihoods[{idx}]" + ) for idx, dist in enumerate(likelihoods) ] system_noises = self._normalize_noise_sequence( @@ -93,11 +99,17 @@ def smooth_identity(self, filtered_states, likelihoods, system_noises): n_coefficients, ) - backward_messages: list[HypertoroidalFourierDistribution | None] = [None] * n_steps - smoothed_states: list[HypertoroidalFourierDistribution | None] = [None] * n_steps + backward_messages: list[HypertoroidalFourierDistribution | None] = [ + None + ] * n_steps + smoothed_states: list[HypertoroidalFourierDistribution | None] = [ + None + ] * n_steps backward_messages[-1] = self.constant_message_like(filtered_states[-1]) - smoothed_states[-1] = filtered_states[-1].multiply(backward_messages[-1], n_coefficients) + smoothed_states[-1] = filtered_states[-1].multiply( + backward_messages[-1], n_coefficients + ) for t in range(n_steps - 2, -1, -1): next_backward = backward_messages[t + 1] @@ -105,8 +117,12 @@ def smooth_identity(self, filtered_states, likelihoods, system_noises): future_message = likelihoods[t + 1].multiply(next_backward, n_coefficients) reversed_noise = self.reverse_frequencies(system_noises[t]) - backward_messages[t] = future_message.convolve(reversed_noise, n_coefficients) - smoothed_states[t] = filtered_states[t].multiply(backward_messages[t], n_coefficients) + backward_messages[t] = future_message.convolve( + reversed_noise, n_coefficients + ) + smoothed_states[t] = filtered_states[t].multiply( + backward_messages[t], n_coefficients + ) return ( [state for state in smoothed_states if state is not None], @@ -114,7 +130,9 @@ def smooth_identity(self, filtered_states, likelihoods, system_noises): ) @staticmethod - def reverse_frequencies(distribution: HypertoroidalFourierDistribution) -> HypertoroidalFourierDistribution: + def reverse_frequencies( + distribution: HypertoroidalFourierDistribution, + ) -> HypertoroidalFourierDistribution: """Return a distribution with coefficients indexed by negated frequencies. For identity coefficients this represents ``p(-x)``. For square-root @@ -126,11 +144,15 @@ def reverse_frequencies(distribution: HypertoroidalFourierDistribution) -> Hyper raise TypeError("distribution must be a HypertoroidalFourierDistribution.") result = copy.deepcopy(distribution) - result.coeff_mat = distribution.coeff_mat[(slice(None, None, -1),) * distribution.dim] + result.coeff_mat = distribution.coeff_mat[ + (slice(None, None, -1),) * distribution.dim + ] return result @staticmethod - def constant_message_like(reference: HypertoroidalFourierDistribution) -> HypertoroidalFourierDistribution: + def constant_message_like( + reference: HypertoroidalFourierDistribution, + ) -> HypertoroidalFourierDistribution: """Return a constant backward message compatible with ``reference``.""" if not isinstance(reference, HypertoroidalFourierDistribution): @@ -146,7 +168,9 @@ def constant_message_like(reference: HypertoroidalFourierDistribution) -> Hypert elif reference.transformation == "sqrt": coeffs[center] = 1.0 / sqrt((2.0 * pi) ** dim) else: - raise ValueError(f"Unsupported transformation: {reference.transformation!r}") + raise ValueError( + f"Unsupported transformation: {reference.transformation!r}" + ) return HypertoroidalFourierDistribution(coeffs, reference.transformation) @@ -158,19 +182,25 @@ def _check_backend() -> None: ) @staticmethod - def _as_distribution_list(distributions, name: str) -> list[HypertoroidalFourierDistribution]: + def _as_distribution_list( + distributions, name: str + ) -> list[HypertoroidalFourierDistribution]: if isinstance(distributions, HypertoroidalFourierDistribution): raise ValueError(f"{name} must be a sequence, not a single distribution.") try: distribution_list = list(distributions) except TypeError as exc: - raise TypeError(f"{name} must be a sequence of HypertoroidalFourierDistribution instances.") from exc + raise TypeError( + f"{name} must be a sequence of HypertoroidalFourierDistribution instances." + ) from exc if len(distribution_list) == 0: raise ValueError(f"{name} must contain at least one distribution.") for idx, distribution in enumerate(distribution_list): if not isinstance(distribution, HypertoroidalFourierDistribution): - raise TypeError(f"{name}[{idx}] must be a HypertoroidalFourierDistribution.") + raise TypeError( + f"{name}[{idx}] must be a HypertoroidalFourierDistribution." + ) return distribution_list @staticmethod @@ -181,7 +211,9 @@ def _ensure_compatible( name: str, ) -> HypertoroidalFourierDistribution: if distribution.dim != reference.dim: - raise ValueError(f"{name} has dimension {distribution.dim}, expected {reference.dim}.") + raise ValueError( + f"{name} has dimension {distribution.dim}, expected {reference.dim}." + ) if distribution.transformation != reference.transformation: raise ValueError( f"{name} uses transformation {distribution.transformation!r}, expected {reference.transformation!r}." @@ -224,8 +256,14 @@ def _normalize_noise_sequence( normalized = [] for idx, noise in enumerate(noise_list): if not isinstance(noise, HypertoroidalFourierDistribution): - raise TypeError(f"system_noises[{idx}] must be a HypertoroidalFourierDistribution.") - normalized.append(cls._ensure_compatible(noise, reference, n_coefficients, f"system_noises[{idx}]")) + raise TypeError( + f"system_noises[{idx}] must be a HypertoroidalFourierDistribution." + ) + normalized.append( + cls._ensure_compatible( + noise, reference, n_coefficients, f"system_noises[{idx}]" + ) + ) return normalized diff --git a/src/pyrecest/smoothers/hypertoroidal_grid_smoother.py b/src/pyrecest/smoothers/hypertoroidal_grid_smoother.py index fbf6c5c4c..61abd40ae 100644 --- a/src/pyrecest/smoothers/hypertoroidal_grid_smoother.py +++ b/src/pyrecest/smoothers/hypertoroidal_grid_smoother.py @@ -45,17 +45,31 @@ def smooth(self, filtered_states, likelihoods, transitions): Backward information messages. Their scale is arbitrary. """ - filtered_states = self._as_grid_distribution_list(filtered_states, "filtered_states") + filtered_states = self._as_grid_distribution_list( + filtered_states, "filtered_states" + ) likelihoods = self._as_grid_distribution_list(likelihoods, "likelihoods") if len(likelihoods) != len(filtered_states): - raise ValueError("likelihoods must have the same length as filtered_states.") + raise ValueError( + "likelihoods must have the same length as filtered_states." + ) n_steps = len(filtered_states) reference = filtered_states[0] - filtered_states = [self._ensure_compatible_distribution(dist, reference, f"filtered_states[{idx}]") for idx, dist in enumerate(filtered_states)] - likelihoods = [self._ensure_compatible_distribution(dist, reference, f"likelihoods[{idx}]") for idx, dist in enumerate(likelihoods)] - transitions = self._normalize_transition_sequence(transitions, max(n_steps - 1, 0), reference) + filtered_states = [ + self._ensure_compatible_distribution( + dist, reference, f"filtered_states[{idx}]" + ) + for idx, dist in enumerate(filtered_states) + ] + likelihoods = [ + self._ensure_compatible_distribution(dist, reference, f"likelihoods[{idx}]") + for idx, dist in enumerate(likelihoods) + ] + transitions = self._normalize_transition_sequence( + transitions, max(n_steps - 1, 0), reference + ) backward_messages: list[HypertoroidalGridDistribution | None] = [None] * n_steps smoothed_states: list[HypertoroidalGridDistribution | None] = [None] * n_steps @@ -68,10 +82,14 @@ def smooth(self, filtered_states, likelihoods, transitions): next_backward = backward_messages[t + 1] assert next_backward is not None - future_values = self._flat_grid_values(likelihoods[t + 1]) * self._flat_grid_values(next_backward) + future_values = self._flat_grid_values( + likelihoods[t + 1] + ) * self._flat_grid_values(next_backward) transition_values = asarray(transitions[t].grid_values) beta_values = transition_values.T @ (cell_volume * future_values) - backward_messages[t] = self._make_grid_distribution_like(beta_values, reference) + backward_messages[t] = self._make_grid_distribution_like( + beta_values, reference + ) smoothed_states[t] = filtered_states[t].multiply(backward_messages[t]) return ( @@ -80,12 +98,16 @@ def smooth(self, filtered_states, likelihoods, transitions): ) @staticmethod - def constant_message_like(reference: HypertoroidalGridDistribution) -> HypertoroidalGridDistribution: + def constant_message_like( + reference: HypertoroidalGridDistribution, + ) -> HypertoroidalGridDistribution: """Return a constant backward message compatible with ``reference``.""" if not isinstance(reference, HypertoroidalGridDistribution): raise TypeError("reference must be a HypertoroidalGridDistribution.") - return HypertoroidalGridSmoother._make_grid_distribution_like(ones(reference.grid_values.shape), reference) + return HypertoroidalGridSmoother._make_grid_distribution_like( + ones(reference.grid_values.shape), reference + ) @staticmethod def _flat_grid_values(distribution: HypertoroidalGridDistribution): @@ -93,10 +115,14 @@ def _flat_grid_values(distribution: HypertoroidalGridDistribution): @staticmethod def _cell_volume(reference: HypertoroidalGridDistribution) -> float: - return float((2.0 * pi) ** reference.dim / math.prod(reference.grid_values.shape)) + return float( + (2.0 * pi) ** reference.dim / math.prod(reference.grid_values.shape) + ) @staticmethod - def _make_grid_distribution_like(values, reference: HypertoroidalGridDistribution) -> HypertoroidalGridDistribution: + def _make_grid_distribution_like( + values, reference: HypertoroidalGridDistribution + ) -> HypertoroidalGridDistribution: return HypertoroidalGridDistribution( grid_values=reshape(values, reference.grid_values.shape), grid_type=reference.grid_type, @@ -106,19 +132,25 @@ def _make_grid_distribution_like(values, reference: HypertoroidalGridDistributio ) @staticmethod - def _as_grid_distribution_list(distributions, name: str) -> list[HypertoroidalGridDistribution]: + def _as_grid_distribution_list( + distributions, name: str + ) -> list[HypertoroidalGridDistribution]: if isinstance(distributions, HypertoroidalGridDistribution): raise ValueError(f"{name} must be a sequence, not a single distribution.") try: distribution_list = list(distributions) except TypeError as exc: - raise TypeError(f"{name} must be a sequence of HypertoroidalGridDistribution instances.") from exc + raise TypeError( + f"{name} must be a sequence of HypertoroidalGridDistribution instances." + ) from exc if len(distribution_list) == 0: raise ValueError(f"{name} must contain at least one distribution.") for idx, distribution in enumerate(distribution_list): if not isinstance(distribution, HypertoroidalGridDistribution): - raise TypeError(f"{name}[{idx}] must be a HypertoroidalGridDistribution.") + raise TypeError( + f"{name}[{idx}] must be a HypertoroidalGridDistribution." + ) return distribution_list @staticmethod @@ -128,19 +160,31 @@ def _ensure_compatible_distribution( name: str, ) -> HypertoroidalGridDistribution: if distribution.dim != reference.dim: - raise ValueError(f"{name} has dimension {distribution.dim}, expected {reference.dim}.") + raise ValueError( + f"{name} has dimension {distribution.dim}, expected {reference.dim}." + ) if distribution.grid_type != reference.grid_type: - raise ValueError(f"{name} has grid type {distribution.grid_type!r}, expected {reference.grid_type!r}.") + raise ValueError( + f"{name} has grid type {distribution.grid_type!r}, expected {reference.grid_type!r}." + ) if distribution.grid_values.shape != reference.grid_values.shape: raise ValueError( f"{name} has grid value shape {distribution.grid_values.shape}, expected {reference.grid_values.shape}." ) if distribution.enforce_pdf_nonnegative != reference.enforce_pdf_nonnegative: - raise ValueError(f"{name} must agree with the reference distribution on enforce_pdf_nonnegative.") + raise ValueError( + f"{name} must agree with the reference distribution on enforce_pdf_nonnegative." + ) if (distribution.grid is None) != (reference.grid is None): - raise ValueError(f"{name} and the reference distribution must either both store grids or both omit them.") - if distribution.grid is not None and not allclose(distribution.grid, reference.grid): - raise ValueError(f"{name} has grid coordinates that differ from the reference distribution.") + raise ValueError( + f"{name} and the reference distribution must either both store grids or both omit them." + ) + if distribution.grid is not None and not allclose( + distribution.grid, reference.grid + ): + raise ValueError( + f"{name} has grid coordinates that differ from the reference distribution." + ) return distribution @classmethod @@ -159,13 +203,19 @@ def _normalize_transition_sequence( try: transition_list = list(transitions) except TypeError as exc: - raise TypeError("transitions must be a TdCondTdGridDistribution or a sequence of them.") from exc + raise TypeError( + "transitions must be a TdCondTdGridDistribution or a sequence of them." + ) from exc if len(transition_list) != expected_length: - raise ValueError("transitions must be a single distribution or contain one distribution per transition.") + raise ValueError( + "transitions must be a single distribution or contain one distribution per transition." + ) for idx, transition in enumerate(transition_list): - cls._ensure_compatible_transition(transition, reference, f"transitions[{idx}]") + cls._ensure_compatible_transition( + transition, reference, f"transitions[{idx}]" + ) return transition_list @staticmethod @@ -183,10 +233,14 @@ def _ensure_compatible_transition( f"{name}.grid_values must have shape {(n_points, n_points)}, got {transition.grid_values.shape}." ) if transition.grid.shape != (n_points, reference.dim): - raise ValueError(f"{name}.grid must have shape {(n_points, reference.dim)}, got {transition.grid.shape}.") + raise ValueError( + f"{name}.grid must have shape {(n_points, reference.dim)}, got {transition.grid.shape}." + ) reference_grid = reference.get_grid() if not allclose(transition.grid, reference_grid): - raise ValueError(f"{name}.grid must match the grid of the state distributions.") + raise ValueError( + f"{name}.grid must match the grid of the state distributions." + ) HypertoroidalGridBackwardInformationSmoother = HypertoroidalGridSmoother diff --git a/src/pyrecest/stability.py b/src/pyrecest/stability.py index d350ce7ce..eee7ed1a3 100644 --- a/src/pyrecest/stability.py +++ b/src/pyrecest/stability.py @@ -574,6 +574,7 @@ def triu_to_vec(x, k=0): if getattr(backend, "__backend_name__", None) == "jax": setattr(backend, helper_name, helper) + _patch_jax_assignment_numpy_index_contract() _patch_pytorch_allclose_device_contract() _patch_pytorch_diag_numpy_contract() diff --git a/src/pyrecest/tracking/audit_guards.py b/src/pyrecest/tracking/audit_guards.py index 567ace73c..0fda72f01 100644 --- a/src/pyrecest/tracking/audit_guards.py +++ b/src/pyrecest/tracking/audit_guards.py @@ -56,7 +56,9 @@ def guarded_mapping( ) -> GuardedMapping: """Wrap one mapping so forbidden audit keys fail on access.""" - return GuardedMapping(mapping=mapping, forbidden_keys=frozenset(map(str, forbidden_keys))) + return GuardedMapping( + mapping=mapping, forbidden_keys=frozenset(map(str, forbidden_keys)) + ) def guarded_mappings( @@ -76,7 +78,9 @@ def strip_forbidden_keys( """Return a copy of ``mapping`` without forbidden audit-only keys.""" forbidden = frozenset(map(str, forbidden_keys)) - return {str(key): value for key, value in mapping.items() if str(key) not in forbidden} + return { + str(key): value for key, value in mapping.items() if str(key) not in forbidden + } def strip_forbidden_keys_from_mappings( @@ -139,7 +143,9 @@ def assert_selector_invariant_under_forbidden_key_changes( forbidden = frozenset(map(str, forbidden_keys)) normalize_result = normalize or (lambda result: result) baseline = normalize_result(selector(rows)) - stripped = normalize_result(selector(strip_forbidden_keys_from_mappings(rows, forbidden))) + stripped = normalize_result( + selector(strip_forbidden_keys_from_mappings(rows, forbidden)) + ) poisoned = normalize_result( selector( poison_forbidden_keys_in_mappings( diff --git a/src/pyrecest/tracking/nonparametric_cardinality.py b/src/pyrecest/tracking/nonparametric_cardinality.py index 8ef663f1f..8963def1b 100644 --- a/src/pyrecest/tracking/nonparametric_cardinality.py +++ b/src/pyrecest/tracking/nonparametric_cardinality.py @@ -82,7 +82,9 @@ def predictive_weights(self, cluster_sizes: Sequence[int]) -> tuple[float, ...]: new_weight = self.strength + self.discount * len(sizes) return existing_weights + (float(new_weight),) - def predictive_probabilities(self, cluster_sizes: Sequence[int]) -> tuple[float, ...]: + def predictive_probabilities( + self, cluster_sizes: Sequence[int] + ) -> tuple[float, ...]: """Return CRP predictive probabilities for existing clusters and a new cluster.""" sizes = _validate_cluster_sizes(cluster_sizes) @@ -91,12 +93,19 @@ def predictive_probabilities(self, cluster_sizes: Sequence[int]) -> tuple[float, denominator = self.strength + float(sum(sizes)) return tuple(weight / denominator for weight in self.predictive_weights(sizes)) - def predictive_log_probabilities(self, cluster_sizes: Sequence[int]) -> tuple[float, ...]: + def predictive_log_probabilities( + self, cluster_sizes: Sequence[int] + ) -> tuple[float, ...]: """Return log predictive probabilities for existing clusters and a new cluster.""" - return tuple(log(probability) for probability in self.predictive_probabilities(cluster_sizes)) + return tuple( + log(probability) + for probability in self.predictive_probabilities(cluster_sizes) + ) - def log_exchangeable_partition_probability(self, cluster_sizes: Sequence[int]) -> float: + def log_exchangeable_partition_probability( + self, cluster_sizes: Sequence[int] + ) -> float: """Return the EPPF log probability of one partition with given block sizes. The value is the probability of one exchangeable partition whose block @@ -146,7 +155,9 @@ def cluster_count_pmf(self, num_observations: int) -> tuple[float, ...]: for num_clusters, probability in enumerate(pmf): if probability == 0.0 or num_clusters == 0: continue - stay_weight = float(occupied_observations) - self.discount * float(num_clusters) + stay_weight = float(occupied_observations) - self.discount * float( + num_clusters + ) new_weight = self.strength + self.discount * float(num_clusters) next_pmf[num_clusters] += probability * stay_weight / denominator next_pmf[num_clusters + 1] += probability * new_weight / denominator @@ -157,9 +168,16 @@ def expected_number_of_clusters(self, num_observations: int) -> float: """Return the prior expected number of occupied clusters after ``n`` observations.""" pmf = self.cluster_count_pmf(num_observations) - return float(sum(num_clusters * probability for num_clusters, probability in enumerate(pmf))) - - def cluster_count_tail_probability(self, num_observations: int, min_clusters: int) -> float: + return float( + sum( + num_clusters * probability + for num_clusters, probability in enumerate(pmf) + ) + ) + + def cluster_count_tail_probability( + self, num_observations: int, min_clusters: int + ) -> float: """Return ``P(K_n >= min_clusters)`` under the prior predictive PMF.""" min_clusters = _validate_nonnegative_int(min_clusters, "min_clusters") diff --git a/src/pyrecest/tracking/residual_hypothesis_diagnostics.py b/src/pyrecest/tracking/residual_hypothesis_diagnostics.py index 0fc7bdd57..455b02cdf 100644 --- a/src/pyrecest/tracking/residual_hypothesis_diagnostics.py +++ b/src/pyrecest/tracking/residual_hypothesis_diagnostics.py @@ -107,7 +107,9 @@ def hypotheses_to_diagnostic_dicts( hypothesis_diagnostic_to_dict( hypothesis, rank=index, - selected=(selected_ids is not None and hypothesis.candidate_ids == selected_ids), + selected=( + selected_ids is not None and hypothesis.candidate_ids == selected_ids + ), ) for index, hypothesis in enumerate(hypotheses, start=1) ) @@ -126,7 +128,9 @@ def selection_ledger_to_dicts( selected_ids: tuple[str, ...] = () if selected_hypothesis is not None: - selected_ids = tuple(str(candidate_id) for candidate_id in selected_hypothesis.candidate_ids) + selected_ids = tuple( + str(candidate_id) for candidate_id in selected_hypothesis.candidate_ids + ) return { "candidates": candidates_to_dicts( candidates, diff --git a/src/pyrecest/utils/__init__.py b/src/pyrecest/utils/__init__.py index 0d33bcc7b..0e5458f8e 100644 --- a/src/pyrecest/utils/__init__.py +++ b/src/pyrecest/utils/__init__.py @@ -171,7 +171,9 @@ def _fit_standardization(self, features): "__name__", "_fit_standardization", ) - _fit_standardization.__doc__ = getattr(original_fit_standardization, "__doc__", None) + _fit_standardization.__doc__ = getattr( + original_fit_standardization, "__doc__", None + ) _fit_standardization._pyrecest_backend_std_contract = True LogisticPairwiseAssociationModel._fit_standardization = _fit_standardization @@ -201,7 +203,9 @@ def _prepare_prediction_features(features, expected_feature_dimension): if not _backend.all(_backend.isfinite(flattened)): raise ValueError("features must be finite") return flattened, () - return original_prepare_prediction_features(features, expected_feature_dimension) + return original_prepare_prediction_features( + features, expected_feature_dimension + ) _prepare_prediction_features.__name__ = getattr( original_prepare_prediction_features, @@ -331,7 +335,9 @@ def _prepare_prediction_features(features, expected_feature_dimension): ) _multisession_assignment_module.tracks_to_session_labels = tracks_to_session_labels -_multisession_assignment_module._validate_scalar_cost = _validate_multisession_scalar_cost +_multisession_assignment_module._validate_scalar_cost = ( + _validate_multisession_scalar_cost +) __all__ = [ "MultiSessionAssignmentResult", diff --git a/src/pyrecest/utils/_point_set_registration_common.py b/src/pyrecest/utils/_point_set_registration_common.py index 82bb9d07b..c8ade77be 100644 --- a/src/pyrecest/utils/_point_set_registration_common.py +++ b/src/pyrecest/utils/_point_set_registration_common.py @@ -174,8 +174,7 @@ def _is_temporal_dtype(value) -> bool: dtype_kind = _dtype_kind(value) dtype_name = _dtype_name(value) return dtype_kind in {"M", "m"} or any( - temporal_name in dtype_name - for temporal_name in ("datetime64", "timedelta64") + temporal_name in dtype_name for temporal_name in ("datetime64", "timedelta64") ) @@ -245,10 +244,9 @@ def _validate_max_cost(max_cost) -> float: max_cost_scalar = max_cost_array.item() except (TypeError, ValueError, AttributeError, RuntimeError) as exc: raise ValueError("max_cost must be a scalar numeric value.") from exc - if ( - isinstance(max_cost_scalar, (bool, str, bytes, bytearray)) - or _is_numpy_temporal_scalar(max_cost_scalar) - ): + if isinstance( + max_cost_scalar, (bool, str, bytes, bytearray) + ) or _is_numpy_temporal_scalar(max_cost_scalar): raise ValueError("max_cost must be a scalar numeric value.") try: @@ -274,10 +272,9 @@ def _validate_tolerance(tolerance) -> float: raise ValueError("tolerance must be a finite non-negative scalar.") tolerance_scalar = tolerance_array.item() - if ( - isinstance(tolerance_scalar, (bool, str, bytes, bytearray)) - or _is_numpy_temporal_scalar(tolerance_scalar) - ): + if isinstance( + tolerance_scalar, (bool, str, bytes, bytearray) + ) or _is_numpy_temporal_scalar(tolerance_scalar): raise ValueError("tolerance must be a finite non-negative scalar.") try: diff --git a/src/pyrecest/utils/candidate_pruning.py b/src/pyrecest/utils/candidate_pruning.py index d026e66fc..fd2fc7880 100644 --- a/src/pyrecest/utils/candidate_pruning.py +++ b/src/pyrecest/utils/candidate_pruning.py @@ -401,7 +401,9 @@ def _as_probability_matrix( if probabilities.shape != shape: raise ValueError("probability_matrix must match cost_matrix shape") if np.any(np.isinf(probabilities)): - raise ValueError("probability_matrix may only contain finite probabilities or NaN") + raise ValueError( + "probability_matrix may only contain finite probabilities or NaN" + ) finite = np.isfinite(probabilities) if np.any(finite & ((probabilities < 0.0) | (probabilities > 1.0))): raise ValueError("finite probability_matrix entries must lie in [0, 1]") diff --git a/src/pyrecest/utils/cost_matrix_adjustments.py b/src/pyrecest/utils/cost_matrix_adjustments.py index f2a56cbae..be70940a9 100644 --- a/src/pyrecest/utils/cost_matrix_adjustments.py +++ b/src/pyrecest/utils/cost_matrix_adjustments.py @@ -106,9 +106,11 @@ def apply( def apply_cost_matrix_adjustment( cost_matrix: Any, - adjustment: CostMatrixAdjustment - | Callable[[np.ndarray], Any] - | CallableCostMatrixAdjustment, + adjustment: ( + CostMatrixAdjustment + | Callable[[np.ndarray], Any] + | CallableCostMatrixAdjustment + ), *, metadata: Mapping[str, Any] | None = None, ) -> CostMatrixAdjustmentResult: @@ -132,7 +134,9 @@ def apply_cost_matrix_adjustment( def compose_cost_matrix_adjustments( cost_matrix: Any, adjustments: Sequence[ - CostMatrixAdjustment | Callable[[np.ndarray], Any] | CallableCostMatrixAdjustment + CostMatrixAdjustment + | Callable[[np.ndarray], Any] + | CallableCostMatrixAdjustment ], *, metadata: Mapping[str, Any] | None = None, diff --git a/src/pyrecest/utils/history_recorder.py b/src/pyrecest/utils/history_recorder.py index fed66f722..82953100d 100644 --- a/src/pyrecest/utils/history_recorder.py +++ b/src/pyrecest/utils/history_recorder.py @@ -8,7 +8,6 @@ from typing import Any import numpy as np - from pyrecest import backend # pylint: disable=no-name-in-module,no-member diff --git a/tests/backend/test_numpy_random_size_validation.py b/tests/backend/test_numpy_random_size_validation.py index 1c47b496c..35ad26129 100644 --- a/tests/backend/test_numpy_random_size_validation.py +++ b/tests/backend/test_numpy_random_size_validation.py @@ -1,9 +1,7 @@ import numpy as np import pytest - from pyrecest._backend.numpy import random - _INVALID_SIZE_ARGUMENTS = ( True, np.bool_(True), diff --git a/tests/backend/test_numpy_random_uniform_validation.py b/tests/backend/test_numpy_random_uniform_validation.py index d9a1f88a8..a83df27ea 100644 --- a/tests/backend/test_numpy_random_uniform_validation.py +++ b/tests/backend/test_numpy_random_uniform_validation.py @@ -1,5 +1,4 @@ import pytest - from pyrecest._backend.numpy import random diff --git a/tests/backend/test_numpy_uniform_ragged_bounds.py b/tests/backend/test_numpy_uniform_ragged_bounds.py index 2fe03a215..ed70556cd 100644 --- a/tests/backend/test_numpy_uniform_ragged_bounds.py +++ b/tests/backend/test_numpy_uniform_ragged_bounds.py @@ -1,5 +1,4 @@ import pytest - from pyrecest._backend import numpy as backend diff --git a/tests/backend/test_pytorch_comparison_device_contract.py b/tests/backend/test_pytorch_comparison_device_contract.py index 906371969..205912146 100644 --- a/tests/backend/test_pytorch_comparison_device_contract.py +++ b/tests/backend/test_pytorch_comparison_device_contract.py @@ -1,10 +1,9 @@ import pytest - torch = pytest.importorskip("torch") -import pyrecest.backend_tools # noqa: E402,F401 import pyrecest._backend.pytorch as pytorch_backend # noqa: E402 +import pyrecest.backend_tools # noqa: E402,F401 def _non_cpu_device(): @@ -72,7 +71,9 @@ def test_raw_pytorch_isclose_prefers_existing_non_cpu_device_for_right_operand() def test_raw_pytorch_allclose_accepts_arraylike_against_cuda_operand(): if not torch.cuda.is_available(): - pytest.skip("allclose returns a host bool and cannot be exercised on meta tensors") + pytest.skip( + "allclose returns a host bool and cannot be exercised on meta tensors" + ) right = torch.tensor([1.0, float("nan")], device="cuda") diff --git a/tests/backend/test_pytorch_flip_axis_contract.py b/tests/backend/test_pytorch_flip_axis_contract.py index 6124552d8..fca7bd435 100644 --- a/tests/backend/test_pytorch_flip_axis_contract.py +++ b/tests/backend/test_pytorch_flip_axis_contract.py @@ -1,11 +1,10 @@ import numpy as np import pytest - torch = pytest.importorskip("torch") -import pyrecest.backend_tools # noqa: E402,F401 import pyrecest._backend.pytorch as pytorch_backend # noqa: E402 +import pyrecest.backend_tools # noqa: E402,F401 from tests.support.backend_runner import run_backend_code # noqa: E402 diff --git a/tests/backend/test_pytorch_probability_underflow.py b/tests/backend/test_pytorch_probability_underflow.py index 4b39895eb..a7a3724fa 100644 --- a/tests/backend/test_pytorch_probability_underflow.py +++ b/tests/backend/test_pytorch_probability_underflow.py @@ -8,7 +8,9 @@ def _pytorch_random_backend(): if importlib.util.find_spec("torch") is None: pytest.skip("torch is not installed") import torch # pylint: disable=import-outside-toplevel - from pyrecest._backend.pytorch import random # pylint: disable=import-outside-toplevel + from pyrecest._backend.pytorch import ( # pylint: disable=import-outside-toplevel + random, + ) return torch, random diff --git a/tests/backend_support/conftest.py b/tests/backend_support/conftest.py index 76b59c91e..a3c77d2e7 100644 --- a/tests/backend_support/conftest.py +++ b/tests/backend_support/conftest.py @@ -1,6 +1,5 @@ from pathlib import Path - _placeholder_regression = Path(__file__).with_name( "test_pytorch_dot_outer_device_contract.py" ) diff --git a/tests/backend_support/test_common_dot_contract.py b/tests/backend_support/test_common_dot_contract.py index 72d6d5c4d..89e52db47 100644 --- a/tests/backend_support/test_common_dot_contract.py +++ b/tests/backend_support/test_common_dot_contract.py @@ -1,7 +1,6 @@ import numpy as np import numpy.testing as npt import pytest - from pyrecest._backend import _common as common diff --git a/tests/backend_support/test_diagonal_bool_axes.py b/tests/backend_support/test_diagonal_bool_axes.py index cab8081de..0dd0cb350 100644 --- a/tests/backend_support/test_diagonal_bool_axes.py +++ b/tests/backend_support/test_diagonal_bool_axes.py @@ -7,7 +7,6 @@ import pytest from tests.support.backend_runner import run_backend_code - _CHECK = """ import pyrecest.backend as backend diff --git a/tests/backend_support/test_gaussian_scalar_logpdf_contract.py b/tests/backend_support/test_gaussian_scalar_logpdf_contract.py index cc4d9fc24..925416de2 100644 --- a/tests/backend_support/test_gaussian_scalar_logpdf_contract.py +++ b/tests/backend_support/test_gaussian_scalar_logpdf_contract.py @@ -1,7 +1,6 @@ import importlib.util import pytest - from tests.support.backend_runner import run_backend_code pytestmark = pytest.mark.backend_portable diff --git a/tests/backend_support/test_jax_array_from_sparse_contract.py b/tests/backend_support/test_jax_array_from_sparse_contract.py index fb54557ec..68ef34781 100644 --- a/tests/backend_support/test_jax_array_from_sparse_contract.py +++ b/tests/backend_support/test_jax_array_from_sparse_contract.py @@ -5,7 +5,6 @@ import importlib.util import pytest - from tests.support.backend_runner import run_backend_code pytestmark = pytest.mark.backend_portable diff --git a/tests/backend_support/test_jax_fft_array_length_contract.py b/tests/backend_support/test_jax_fft_array_length_contract.py index 01778a3ba..8e8c4d3a3 100644 --- a/tests/backend_support/test_jax_fft_array_length_contract.py +++ b/tests/backend_support/test_jax_fft_array_length_contract.py @@ -23,9 +23,7 @@ def test_jax_real_fft_accepts_singleton_array_lengths(): ) for length in lengths: - actual_spectrum = _as_numpy( - backend.fft.rfft(backend_samples, n=length, axis=1) - ) + actual_spectrum = _as_numpy(backend.fft.rfft(backend_samples, n=length, axis=1)) assert np.allclose(actual_spectrum, expected_spectrum) actual_roundtrip = _as_numpy( diff --git a/tests/backend_support/test_jax_fftn_sequence_contract.py b/tests/backend_support/test_jax_fftn_sequence_contract.py index d059f32f1..3f10c733e 100644 --- a/tests/backend_support/test_jax_fftn_sequence_contract.py +++ b/tests/backend_support/test_jax_fftn_sequence_contract.py @@ -13,13 +13,17 @@ class TestJaxFftnSequenceContract(unittest.TestCase): def test_fftn_accepts_scalar_array_entries_in_shape_and_axes(self): values = np.arange(4.0) - actual = np.asarray(jax_backend.fft.fftn(values, s=(np.array(4),), axes=(np.array(0),))) + actual = np.asarray( + jax_backend.fft.fftn(values, s=(np.array(4),), axes=(np.array(0),)) + ) expected = np.fft.fftn(values, s=(4,), axes=(0,)) self.assertTrue(np.allclose(actual, expected)) def test_ifftn_accepts_scalar_array_entries_in_shape_and_axes(self): spectrum = np.fft.fftn(np.arange(4.0)) - actual = np.asarray(jax_backend.fft.ifftn(spectrum, s=[np.array(4)], axes=[np.array(0)])) + actual = np.asarray( + jax_backend.fft.ifftn(spectrum, s=[np.array(4)], axes=[np.array(0)]) + ) expected = np.fft.ifftn(spectrum, s=(4,), axes=(0,)) self.assertTrue(np.allclose(actual, expected)) diff --git a/tests/backend_support/test_jax_one_hot_contract.py b/tests/backend_support/test_jax_one_hot_contract.py index 44b47bec1..b96d4cba1 100644 --- a/tests/backend_support/test_jax_one_hot_contract.py +++ b/tests/backend_support/test_jax_one_hot_contract.py @@ -1,7 +1,6 @@ import importlib.util import pytest - from tests.support.backend_runner import run_backend_code pytestmark = pytest.mark.backend_portable diff --git a/tests/backend_support/test_jax_take_out_contract.py b/tests/backend_support/test_jax_take_out_contract.py index 36fc8ef8a..9022629b4 100644 --- a/tests/backend_support/test_jax_take_out_contract.py +++ b/tests/backend_support/test_jax_take_out_contract.py @@ -1,7 +1,6 @@ import importlib.util import pytest - from tests.support.backend_runner import run_backend_code diff --git a/tests/backend_support/test_pytorch_apply_along_axis_arguments.py b/tests/backend_support/test_pytorch_apply_along_axis_arguments.py index d748cb8fa..5c889b501 100644 --- a/tests/backend_support/test_pytorch_apply_along_axis_arguments.py +++ b/tests/backend_support/test_pytorch_apply_along_axis_arguments.py @@ -1,7 +1,6 @@ """Regression tests for PyTorch apply_along_axis callback arguments.""" import pytest - from tests.support.backend_runner import run_backend_code diff --git a/tests/backend_support/test_pytorch_arctan_contract.py b/tests/backend_support/test_pytorch_arctan_contract.py index 07b017c4d..177b308c1 100644 --- a/tests/backend_support/test_pytorch_arctan_contract.py +++ b/tests/backend_support/test_pytorch_arctan_contract.py @@ -5,7 +5,6 @@ import importlib.util import pytest - from tests.support.backend_runner import run_backend_code diff --git a/tests/backend_support/test_pytorch_argsort_axis_contract.py b/tests/backend_support/test_pytorch_argsort_axis_contract.py index fd9248eb7..6d502c76b 100644 --- a/tests/backend_support/test_pytorch_argsort_axis_contract.py +++ b/tests/backend_support/test_pytorch_argsort_axis_contract.py @@ -1,7 +1,6 @@ import importlib.util import pytest - from tests.support.backend_runner import run_backend_code pytestmark = pytest.mark.backend_portable diff --git a/tests/backend_support/test_pytorch_argsort_kind_stable_contract.py b/tests/backend_support/test_pytorch_argsort_kind_stable_contract.py index 46962659d..8e0f560a3 100644 --- a/tests/backend_support/test_pytorch_argsort_kind_stable_contract.py +++ b/tests/backend_support/test_pytorch_argsort_kind_stable_contract.py @@ -1,7 +1,6 @@ import importlib.util import pytest - from tests.support.backend_runner import run_backend_code pytestmark = pytest.mark.backend_portable diff --git a/tests/backend_support/test_pytorch_array_equal_equal_nan_contract.py b/tests/backend_support/test_pytorch_array_equal_equal_nan_contract.py index 72b6e7c0a..0b5ac7710 100644 --- a/tests/backend_support/test_pytorch_array_equal_equal_nan_contract.py +++ b/tests/backend_support/test_pytorch_array_equal_equal_nan_contract.py @@ -3,7 +3,6 @@ import pytest from tests.support.backend_runner import run_backend_code - _ARRAY_EQUAL_EQUAL_NAN_SCRIPT = """ import importlib diff --git a/tests/backend_support/test_pytorch_array_from_sparse_contract.py b/tests/backend_support/test_pytorch_array_from_sparse_contract.py index 28447dc4f..b0fa64276 100644 --- a/tests/backend_support/test_pytorch_array_from_sparse_contract.py +++ b/tests/backend_support/test_pytorch_array_from_sparse_contract.py @@ -5,7 +5,6 @@ import importlib.util import pytest - from tests.support.backend_runner import run_backend_code pytestmark = pytest.mark.backend_portable diff --git a/tests/backend_support/test_pytorch_assignment_uint8_index_contract.py b/tests/backend_support/test_pytorch_assignment_uint8_index_contract.py index d7beba638..3fa6e5c39 100644 --- a/tests/backend_support/test_pytorch_assignment_uint8_index_contract.py +++ b/tests/backend_support/test_pytorch_assignment_uint8_index_contract.py @@ -10,7 +10,9 @@ @pytest.mark.backend_portable @pytest.mark.parametrize("backend_name", ["numpy", "pytorch"]) -def test_raw_pytorch_assignment_treats_uint8_tensor_indices_as_integer_indices(backend_name): +def test_raw_pytorch_assignment_treats_uint8_tensor_indices_as_integer_indices( + backend_name, +): if importlib.util.find_spec("torch") is None: pytest.skip("PyTorch is not installed") diff --git a/tests/backend_support/test_pytorch_binary_arraylike_contract.py b/tests/backend_support/test_pytorch_binary_arraylike_contract.py index cbb33c061..ab41b3248 100644 --- a/tests/backend_support/test_pytorch_binary_arraylike_contract.py +++ b/tests/backend_support/test_pytorch_binary_arraylike_contract.py @@ -1,7 +1,6 @@ import importlib.util import pytest - from tests.support.backend_runner import run_backend_code pytestmark = pytest.mark.backend_portable diff --git a/tests/backend_support/test_pytorch_broadcast_arrays_device_contract.py b/tests/backend_support/test_pytorch_broadcast_arrays_device_contract.py index 6a34551ee..cb26d4e37 100644 --- a/tests/backend_support/test_pytorch_broadcast_arrays_device_contract.py +++ b/tests/backend_support/test_pytorch_broadcast_arrays_device_contract.py @@ -1,7 +1,6 @@ import importlib.util import pytest - from tests.support.backend_runner import run_backend_code pytestmark = pytest.mark.backend_portable diff --git a/tests/backend_support/test_pytorch_broadcast_to_contract.py b/tests/backend_support/test_pytorch_broadcast_to_contract.py index 924564f0b..43aed0f45 100644 --- a/tests/backend_support/test_pytorch_broadcast_to_contract.py +++ b/tests/backend_support/test_pytorch_broadcast_to_contract.py @@ -61,7 +61,9 @@ def test_pytorch_broadcast_to_shape_inputs_match_numpy_contract(): else: raise AssertionError(f"broadcast_to accepted boolean shape {invalid_shape!r}") """ - subprocess.run([sys.executable, "-c", code], check=True, env=_backend_test_env("pytorch")) + subprocess.run( + [sys.executable, "-c", code], check=True, env=_backend_test_env("pytorch") + ) @pytest.mark.backend_portable @@ -101,4 +103,6 @@ def test_raw_pytorch_broadcast_to_shape_inputs_with_numpy_public_backend(): else: raise AssertionError(f"raw broadcast_to accepted boolean shape {invalid_shape!r}") """ - subprocess.run([sys.executable, "-c", code], check=True, env=_backend_test_env("numpy")) + subprocess.run( + [sys.executable, "-c", code], check=True, env=_backend_test_env("numpy") + ) diff --git a/tests/backend_support/test_pytorch_copy_contract.py b/tests/backend_support/test_pytorch_copy_contract.py index 12018dbf2..616f6c169 100644 --- a/tests/backend_support/test_pytorch_copy_contract.py +++ b/tests/backend_support/test_pytorch_copy_contract.py @@ -1,6 +1,5 @@ import numpy as np import pytest - from tests.support.backend_runner import run_backend_code diff --git a/tests/backend_support/test_pytorch_copy_export_contract.py b/tests/backend_support/test_pytorch_copy_export_contract.py index 65e3cf170..4829b5198 100644 --- a/tests/backend_support/test_pytorch_copy_export_contract.py +++ b/tests/backend_support/test_pytorch_copy_export_contract.py @@ -1,7 +1,6 @@ import importlib.util import pytest - from tests.support.backend_runner import run_backend_code pytestmark = pytest.mark.backend_portable diff --git a/tests/backend_support/test_pytorch_creation_temporal_contract.py b/tests/backend_support/test_pytorch_creation_temporal_contract.py index a2581e36f..e6a523305 100644 --- a/tests/backend_support/test_pytorch_creation_temporal_contract.py +++ b/tests/backend_support/test_pytorch_creation_temporal_contract.py @@ -1,6 +1,5 @@ import numpy as np import pytest - from pyrecest.backend_support._pytorch_creation_shape_contract import ( _pytorch_creation_scalar, _pytorch_creation_shape, @@ -41,7 +40,9 @@ def test_pytorch_creation_shape_rejects_native_temporal_dtypes(shape): ) def test_pytorch_creation_scalar_rejects_native_temporal_dtypes(value): with pytest.raises(TypeError, match="arange start must be numeric"): - _pytorch_creation_scalar(value, np, _NoTensorTorch, argument_name="arange start") + _pytorch_creation_scalar( + value, np, _NoTensorTorch, argument_name="arange start" + ) def test_pytorch_creation_shape_still_accepts_numpy_integer_scalars(): @@ -49,9 +50,12 @@ def test_pytorch_creation_shape_still_accepts_numpy_integer_scalars(): def test_pytorch_creation_scalar_still_accepts_numpy_numeric_scalars(): - assert _pytorch_creation_scalar( - np.asarray(3.5), - np, - _NoTensorTorch, - argument_name="arange start", - ) == 3.5 + assert ( + _pytorch_creation_scalar( + np.asarray(3.5), + np, + _NoTensorTorch, + argument_name="arange start", + ) + == 3.5 + ) diff --git a/tests/backend_support/test_pytorch_cross_device_contract.py b/tests/backend_support/test_pytorch_cross_device_contract.py index 620528a34..88787a68e 100644 --- a/tests/backend_support/test_pytorch_cross_device_contract.py +++ b/tests/backend_support/test_pytorch_cross_device_contract.py @@ -2,10 +2,9 @@ from __future__ import annotations -import pytest - import pyrecest.backend as backend import pyrecest.stability # noqa: F401 ensure compatibility patches are installed +import pytest pytorch_backend = pytest.importorskip("pyrecest._backend.pytorch") torch = pytest.importorskip("torch") diff --git a/tests/backend_support/test_pytorch_diagonal_numpy_scalar_args.py b/tests/backend_support/test_pytorch_diagonal_numpy_scalar_args.py index a1e117efd..35cfea6e1 100644 --- a/tests/backend_support/test_pytorch_diagonal_numpy_scalar_args.py +++ b/tests/backend_support/test_pytorch_diagonal_numpy_scalar_args.py @@ -1,6 +1,5 @@ import numpy as np import pytest - from pyrecest._backend import _common as common diff --git a/tests/backend_support/test_pytorch_diff_contract.py b/tests/backend_support/test_pytorch_diff_contract.py index 3413f6a82..4f37605cb 100644 --- a/tests/backend_support/test_pytorch_diff_contract.py +++ b/tests/backend_support/test_pytorch_diff_contract.py @@ -5,7 +5,6 @@ import pytest - SCRIPT = """ import pyrecest # noqa: F401 # triggers raw-backend compatibility patches import torch diff --git a/tests/backend_support/test_pytorch_dot_outer_device_contract.py b/tests/backend_support/test_pytorch_dot_outer_device_contract.py index 4224d25aa..0d374f7ca 100644 --- a/tests/backend_support/test_pytorch_dot_outer_device_contract.py +++ b/tests/backend_support/test_pytorch_dot_outer_device_contract.py @@ -1,7 +1,6 @@ import importlib.util import pytest - from tests.support.backend_runner import run_backend_code pytestmark = pytest.mark.backend_portable diff --git a/tests/backend_support/test_pytorch_dtype_alias_contract.py b/tests/backend_support/test_pytorch_dtype_alias_contract.py index 333f4b1f5..ba3b52c0d 100644 --- a/tests/backend_support/test_pytorch_dtype_alias_contract.py +++ b/tests/backend_support/test_pytorch_dtype_alias_contract.py @@ -1,7 +1,6 @@ import importlib.util import pytest - from tests.support.backend_runner import run_backend_code pytestmark = pytest.mark.backend_portable diff --git a/tests/backend_support/test_pytorch_equality_device_contract.py b/tests/backend_support/test_pytorch_equality_device_contract.py index 225f6a4b0..c6cab6fdc 100644 --- a/tests/backend_support/test_pytorch_equality_device_contract.py +++ b/tests/backend_support/test_pytorch_equality_device_contract.py @@ -1,7 +1,6 @@ import importlib.util import pytest - from tests.support.backend_runner import run_backend_code pytestmark = pytest.mark.backend_portable diff --git a/tests/backend_support/test_pytorch_fft_alias_numpy_arrays_contract.py b/tests/backend_support/test_pytorch_fft_alias_numpy_arrays_contract.py index b5a48a456..6efae09c0 100644 --- a/tests/backend_support/test_pytorch_fft_alias_numpy_arrays_contract.py +++ b/tests/backend_support/test_pytorch_fft_alias_numpy_arrays_contract.py @@ -57,4 +57,6 @@ def test_pytorch_fft_aliases_accept_matching_numpy_array_axes(): else: raise AssertionError("conflicting FFT axis aliases were accepted") """ - subprocess.run([sys.executable, "-c", code], check=True, env=_backend_test_env("pytorch")) + subprocess.run( + [sys.executable, "-c", code], check=True, env=_backend_test_env("pytorch") + ) diff --git a/tests/backend_support/test_pytorch_fft_scalar_axis_contract.py b/tests/backend_support/test_pytorch_fft_scalar_axis_contract.py index 96a4a374a..fe42f1bbb 100644 --- a/tests/backend_support/test_pytorch_fft_scalar_axis_contract.py +++ b/tests/backend_support/test_pytorch_fft_scalar_axis_contract.py @@ -17,7 +17,9 @@ def test_raw_pytorch_real_fft_accepts_numpy_scalar_array_axis_alias(): npt.assert_allclose(spectrum.numpy(), np.fft.rfft(vector, axis=axis)) reconstructed = pytorch_fft.irfft(spectrum, n=vector.size, axis=axis) - expected = np.fft.irfft(np.fft.rfft(vector, axis=axis), n=vector.size, axis=axis) + expected = np.fft.irfft( + np.fft.rfft(vector, axis=axis), n=vector.size, axis=axis + ) npt.assert_allclose(reconstructed.numpy(), expected) diff --git a/tests/backend_support/test_pytorch_gaussian_mixture_statistics.py b/tests/backend_support/test_pytorch_gaussian_mixture_statistics.py index d1079bbd0..f3bff6831 100644 --- a/tests/backend_support/test_pytorch_gaussian_mixture_statistics.py +++ b/tests/backend_support/test_pytorch_gaussian_mixture_statistics.py @@ -3,7 +3,6 @@ import importlib.util import pytest - from tests.support.backend_runner import run_backend_code diff --git a/tests/backend_support/test_pytorch_isclose_equal_nan_contract.py b/tests/backend_support/test_pytorch_isclose_equal_nan_contract.py index e22a3e487..0e19a83f2 100644 --- a/tests/backend_support/test_pytorch_isclose_equal_nan_contract.py +++ b/tests/backend_support/test_pytorch_isclose_equal_nan_contract.py @@ -43,7 +43,9 @@ def test_public_pytorch_isclose_accepts_equal_nan_keyword_when_selected(): right = [np.nan, 1.0 + 1e-9, 2.0] result = backend.isclose(left, right, equal_nan=True) - expected = np.isclose(left, right, rtol=backend.rtol, atol=backend.atol, equal_nan=True) + expected = np.isclose( + left, right, rtol=backend.rtol, atol=backend.atol, equal_nan=True + ) npt.assert_array_equal(backend.to_numpy(result), expected) result_without_nan_match = backend.isclose(left, right, equal_nan=False) @@ -54,4 +56,6 @@ def test_public_pytorch_isclose_accepts_equal_nan_keyword_when_selected(): atol=backend.atol, equal_nan=False, ) - npt.assert_array_equal(backend.to_numpy(result_without_nan_match), expected_without_nan_match) + npt.assert_array_equal( + backend.to_numpy(result_without_nan_match), expected_without_nan_match + ) diff --git a/tests/backend_support/test_pytorch_linalg_logm_arraylike_contract.py b/tests/backend_support/test_pytorch_linalg_logm_arraylike_contract.py index 68243b5f1..40a8296a9 100644 --- a/tests/backend_support/test_pytorch_linalg_logm_arraylike_contract.py +++ b/tests/backend_support/test_pytorch_linalg_logm_arraylike_contract.py @@ -1,7 +1,6 @@ """Regression tests for PyTorch linalg matrix logarithm input coercion.""" import pytest - from tests.support.backend_runner import run_backend_code diff --git a/tests/backend_support/test_pytorch_linalg_logm_contract.py b/tests/backend_support/test_pytorch_linalg_logm_contract.py index 6fbb0aa48..fa35007ca 100644 --- a/tests/backend_support/test_pytorch_linalg_logm_contract.py +++ b/tests/backend_support/test_pytorch_linalg_logm_contract.py @@ -1,8 +1,7 @@ """Regression tests for PyTorch linalg.logm input normalization.""" -import pytest - import pyrecest.backend as backend +import pytest pytorch_backend = pytest.importorskip("pyrecest._backend.pytorch") pytorch_linalg = pytest.importorskip("pyrecest._backend.pytorch.linalg") diff --git a/tests/backend_support/test_pytorch_linear_dirac_plot_contract.py b/tests/backend_support/test_pytorch_linear_dirac_plot_contract.py index 1395fff4d..a55c82246 100644 --- a/tests/backend_support/test_pytorch_linear_dirac_plot_contract.py +++ b/tests/backend_support/test_pytorch_linear_dirac_plot_contract.py @@ -3,7 +3,6 @@ import importlib.util import pytest - from tests.support.backend_runner import run_backend_code diff --git a/tests/backend_support/test_pytorch_log1p_contract.py b/tests/backend_support/test_pytorch_log1p_contract.py index f157b0873..f2e58f536 100644 --- a/tests/backend_support/test_pytorch_log1p_contract.py +++ b/tests/backend_support/test_pytorch_log1p_contract.py @@ -5,7 +5,6 @@ import importlib.util import pytest - from tests.support.backend_runner import run_backend_code diff --git a/tests/backend_support/test_pytorch_logical_and_device_contract.py b/tests/backend_support/test_pytorch_logical_and_device_contract.py index 053a6d41f..83b073f24 100644 --- a/tests/backend_support/test_pytorch_logical_and_device_contract.py +++ b/tests/backend_support/test_pytorch_logical_and_device_contract.py @@ -2,10 +2,9 @@ from __future__ import annotations -import pytest - import pyrecest.backend as backend import pyrecest.backend_support # noqa: F401 ensure compatibility patches are installed +import pytest pytorch_backend = pytest.importorskip("pyrecest._backend.pytorch") torch = pytest.importorskip("torch") diff --git a/tests/backend_support/test_pytorch_matmul_device_contract.py b/tests/backend_support/test_pytorch_matmul_device_contract.py index 0232750c6..40a0160cb 100644 --- a/tests/backend_support/test_pytorch_matmul_device_contract.py +++ b/tests/backend_support/test_pytorch_matmul_device_contract.py @@ -1,7 +1,6 @@ import importlib.util import pytest - from tests.support.backend_runner import run_backend_code pytestmark = pytest.mark.backend_portable diff --git a/tests/backend_support/test_pytorch_matvec_device_contract.py b/tests/backend_support/test_pytorch_matvec_device_contract.py index ddf9ba2c8..4e9986b4f 100644 --- a/tests/backend_support/test_pytorch_matvec_device_contract.py +++ b/tests/backend_support/test_pytorch_matvec_device_contract.py @@ -1,7 +1,6 @@ import importlib.util import pytest - from tests.support.backend_runner import run_backend_code pytestmark = pytest.mark.backend_portable diff --git a/tests/backend_support/test_pytorch_minmax_device_contract.py b/tests/backend_support/test_pytorch_minmax_device_contract.py index b1dc3097a..491b278b4 100644 --- a/tests/backend_support/test_pytorch_minmax_device_contract.py +++ b/tests/backend_support/test_pytorch_minmax_device_contract.py @@ -5,7 +5,6 @@ import importlib.util import pytest - from tests.support.backend_runner import run_backend_code pytestmark = pytest.mark.backend_portable diff --git a/tests/backend_support/test_pytorch_non_native_dtype_contract.py b/tests/backend_support/test_pytorch_non_native_dtype_contract.py index a53d1d04c..393c137c7 100644 --- a/tests/backend_support/test_pytorch_non_native_dtype_contract.py +++ b/tests/backend_support/test_pytorch_non_native_dtype_contract.py @@ -1,5 +1,4 @@ import pytest - from tests.support.backend_runner import run_backend_code diff --git a/tests/backend_support/test_pytorch_one_hot_scalar_contract.py b/tests/backend_support/test_pytorch_one_hot_scalar_contract.py index c8e821642..efb39d80d 100644 --- a/tests/backend_support/test_pytorch_one_hot_scalar_contract.py +++ b/tests/backend_support/test_pytorch_one_hot_scalar_contract.py @@ -1,7 +1,6 @@ import importlib.util import pytest - from tests.support.backend_runner import run_backend_code pytestmark = pytest.mark.backend_portable diff --git a/tests/backend_support/test_pytorch_pad_contract.py b/tests/backend_support/test_pytorch_pad_contract.py index b834d351b..bf133d236 100644 --- a/tests/backend_support/test_pytorch_pad_contract.py +++ b/tests/backend_support/test_pytorch_pad_contract.py @@ -1,7 +1,6 @@ import importlib.util import pytest - from tests.support.backend_runner import run_backend_code pytestmark = pytest.mark.backend_portable diff --git a/tests/backend_support/test_pytorch_pad_edge_contract.py b/tests/backend_support/test_pytorch_pad_edge_contract.py index 11e7ab5cd..496f8b790 100644 --- a/tests/backend_support/test_pytorch_pad_edge_contract.py +++ b/tests/backend_support/test_pytorch_pad_edge_contract.py @@ -1,7 +1,6 @@ import importlib.util import pytest - from tests.support.backend_runner import run_backend_code pytestmark = pytest.mark.backend_portable diff --git a/tests/backend_support/test_pytorch_randint_empty_size_contract.py b/tests/backend_support/test_pytorch_randint_empty_size_contract.py index 3662d09fb..f9727bb81 100644 --- a/tests/backend_support/test_pytorch_randint_empty_size_contract.py +++ b/tests/backend_support/test_pytorch_randint_empty_size_contract.py @@ -1,7 +1,6 @@ from __future__ import annotations import pytest - from tests.support.backend_runner import run_backend_code diff --git a/tests/backend_support/test_pytorch_reduction_axis_bool_contract.py b/tests/backend_support/test_pytorch_reduction_axis_bool_contract.py index 96a178960..4626d690a 100644 --- a/tests/backend_support/test_pytorch_reduction_axis_bool_contract.py +++ b/tests/backend_support/test_pytorch_reduction_axis_bool_contract.py @@ -18,7 +18,7 @@ def _backend_subprocess_env(backend_name): return env -_REDUCTION_BOOL_AXIS_CODE = r''' +_REDUCTION_BOOL_AXIS_CODE = r""" import numpy as np import torch @@ -60,7 +60,7 @@ def assert_bool_axes_rejected(module): assert module.to_numpy(module.max(values, axis=0)).tolist() == [2, 3] assert module.to_numpy(module.mean(values, axis=0)).tolist() == [1.0, 2.0] assert module.to_numpy(module.sum(values, axis=1)).tolist() == [1, 5] -''' +""" @pytest.mark.backend_portable @@ -68,15 +68,12 @@ def test_public_pytorch_reduction_helpers_reject_boolean_axes(): if importlib.util.find_spec("torch") is None: pytest.skip("torch is not installed") - code = ( - _REDUCTION_BOOL_AXIS_CODE - + """ + code = _REDUCTION_BOOL_AXIS_CODE + """ import pyrecest.backend as backend assert backend.__backend_name__ == "pytorch" assert_bool_axes_rejected(backend) """ - ) subprocess.run( [sys.executable, "-c", code], check=True, @@ -89,9 +86,7 @@ def test_raw_pytorch_reduction_helpers_are_patched_under_numpy_backend(): if importlib.util.find_spec("torch") is None: pytest.skip("torch is not installed") - code = ( - _REDUCTION_BOOL_AXIS_CODE - + """ + code = _REDUCTION_BOOL_AXIS_CODE + """ import pyrecest # noqa: F401 import pyrecest.backend as public_backend import pyrecest._backend.pytorch as raw_pytorch @@ -99,7 +94,6 @@ def test_raw_pytorch_reduction_helpers_are_patched_under_numpy_backend(): assert public_backend.__backend_name__ == "numpy" assert_bool_axes_rejected(raw_pytorch) """ - ) subprocess.run( [sys.executable, "-c", code], check=True, diff --git a/tests/backend_support/test_pytorch_reduction_axis_scalar_contract.py b/tests/backend_support/test_pytorch_reduction_axis_scalar_contract.py index 0eac48021..3965f76f2 100644 --- a/tests/backend_support/test_pytorch_reduction_axis_scalar_contract.py +++ b/tests/backend_support/test_pytorch_reduction_axis_scalar_contract.py @@ -18,7 +18,7 @@ def _backend_subprocess_env(backend_name): return env -_REDUCTION_SCALAR_AXIS_CODE = r''' +_REDUCTION_SCALAR_AXIS_CODE = r""" import numpy as np import torch @@ -49,7 +49,7 @@ def assert_integer_scalar_axes_supported(module): for helper_name in ("sum", "mean", "std"): result = getattr(module, helper_name)(values, dim=dim) assert module.to_numpy(result).tolist() == expected[helper_name] -''' +""" @pytest.mark.backend_portable @@ -57,15 +57,12 @@ def test_public_pytorch_reductions_accept_integer_scalar_axes(): if importlib.util.find_spec("torch") is None: pytest.skip("torch is not installed") - code = ( - _REDUCTION_SCALAR_AXIS_CODE - + """ + code = _REDUCTION_SCALAR_AXIS_CODE + """ import pyrecest.backend as backend assert backend.__backend_name__ == "pytorch" assert_integer_scalar_axes_supported(backend) """ - ) subprocess.run( [sys.executable, "-c", code], check=True, @@ -78,9 +75,7 @@ def test_raw_pytorch_reductions_are_patched_under_numpy_backend(): if importlib.util.find_spec("torch") is None: pytest.skip("torch is not installed") - code = ( - _REDUCTION_SCALAR_AXIS_CODE - + """ + code = _REDUCTION_SCALAR_AXIS_CODE + """ import pyrecest # noqa: F401 import pyrecest.backend as public_backend import pyrecest._backend.pytorch as raw_pytorch @@ -88,7 +83,6 @@ def test_raw_pytorch_reductions_are_patched_under_numpy_backend(): assert public_backend.__backend_name__ == "numpy" assert_integer_scalar_axes_supported(raw_pytorch) """ - ) subprocess.run( [sys.executable, "-c", code], check=True, diff --git a/tests/backend_support/test_pytorch_roll_contract.py b/tests/backend_support/test_pytorch_roll_contract.py index c0f1b2b12..2fa104e30 100644 --- a/tests/backend_support/test_pytorch_roll_contract.py +++ b/tests/backend_support/test_pytorch_roll_contract.py @@ -1,7 +1,6 @@ -import pytest - import pyrecest.backend as backend import pyrecest.backend_support # noqa: F401 ensure compatibility patches are installed +import pytest pytorch_backend = pytest.importorskip("pyrecest._backend.pytorch") @@ -34,7 +33,9 @@ def test_public_pytorch_roll_accepts_array_like_inputs_when_active(): values = [[0, 1, 2], [3, 4, 5]] assert backend.to_numpy(backend.roll(values, 1)).tolist() == [[5, 0, 1], [2, 3, 4]] - assert backend.to_numpy(backend.roll(values, shift=(1, 2), axis=(0, 1))).tolist() == [ + assert backend.to_numpy( + backend.roll(values, shift=(1, 2), axis=(0, 1)) + ).tolist() == [ [4, 5, 3], [1, 2, 0], ] diff --git a/tests/backend_support/test_pytorch_rotation_stub_contract.py b/tests/backend_support/test_pytorch_rotation_stub_contract.py index 4223cb30a..e101d5169 100644 --- a/tests/backend_support/test_pytorch_rotation_stub_contract.py +++ b/tests/backend_support/test_pytorch_rotation_stub_contract.py @@ -5,7 +5,6 @@ import importlib.util import pytest - from pyrecest.exceptions import BackendNotSupportedError diff --git a/tests/backend_support/test_pytorch_rotation_stub_methods.py b/tests/backend_support/test_pytorch_rotation_stub_methods.py index 3901c92aa..fa3fb8af4 100644 --- a/tests/backend_support/test_pytorch_rotation_stub_methods.py +++ b/tests/backend_support/test_pytorch_rotation_stub_methods.py @@ -6,7 +6,6 @@ from pathlib import Path import pytest - from pyrecest.exceptions import BackendNotSupportedError diff --git a/tests/backend_support/test_pytorch_scatter_add_contract.py b/tests/backend_support/test_pytorch_scatter_add_contract.py index f77f99d9b..53b3e686e 100644 --- a/tests/backend_support/test_pytorch_scatter_add_contract.py +++ b/tests/backend_support/test_pytorch_scatter_add_contract.py @@ -1,8 +1,7 @@ import importlib.util -import pytest - import pyrecest.backend as backend +import pytest from tests.support.backend_runner import run_backend_code diff --git a/tests/backend_support/test_pytorch_searchsorted_sorter_contract.py b/tests/backend_support/test_pytorch_searchsorted_sorter_contract.py index e687871c9..bfe854518 100644 --- a/tests/backend_support/test_pytorch_searchsorted_sorter_contract.py +++ b/tests/backend_support/test_pytorch_searchsorted_sorter_contract.py @@ -1,7 +1,6 @@ import importlib.util import pytest - from tests.support.backend_runner import run_backend_code pytestmark = pytest.mark.backend_portable diff --git a/tests/backend_support/test_pytorch_solve_sylvester_device.py b/tests/backend_support/test_pytorch_solve_sylvester_device.py index 5361e190e..504749749 100644 --- a/tests/backend_support/test_pytorch_solve_sylvester_device.py +++ b/tests/backend_support/test_pytorch_solve_sylvester_device.py @@ -5,7 +5,6 @@ torch: Any try: import torch - from pyrecest._backend import pytorch as pytorch_backend except ModuleNotFoundError: torch = None @@ -31,11 +30,11 @@ def test_solve_sylvester_keeps_common_dtype_for_mixed_array_like_inputs(self): self.assertEqual(result.dtype, pytorch_backend.float64) self.assertTrue(pytorch_backend.allclose(result, expected)) - @unittest.skipIf(torch is None or not torch.cuda.is_available(), "CUDA is not available") + @unittest.skipIf( + torch is None or not torch.cuda.is_available(), "CUDA is not available" + ) def test_solve_sylvester_aligns_mixed_tensor_devices(self): - a = torch.tensor( - [[2.0, 0.0], [0.0, 3.0]], dtype=torch.float32, device="cuda" - ) + a = torch.tensor([[2.0, 0.0], [0.0, 3.0]], dtype=torch.float32, device="cuda") b = torch.tensor([[2.0, 0.0], [0.0, 3.0]], dtype=torch.float32) q = torch.tensor([[8.0, 10.0], [10.0, 12.0]], dtype=torch.float64) diff --git a/tests/backend_support/test_pytorch_sort_axis_none_contract.py b/tests/backend_support/test_pytorch_sort_axis_none_contract.py index dd1a9384e..bc281abd7 100644 --- a/tests/backend_support/test_pytorch_sort_axis_none_contract.py +++ b/tests/backend_support/test_pytorch_sort_axis_none_contract.py @@ -5,8 +5,6 @@ import numpy as np import pytest -from pyrecest.backend_support._pytorch_sort_numpy_contract import resolve_sort_stability - from pyrecest.backend_support._pytorch_sort_numpy_contract import ( resolve_sort_stability, ) diff --git a/tests/backend_support/test_pytorch_sort_axis_validation.py b/tests/backend_support/test_pytorch_sort_axis_validation.py index 98fdf1434..fd8ad0dc6 100644 --- a/tests/backend_support/test_pytorch_sort_axis_validation.py +++ b/tests/backend_support/test_pytorch_sort_axis_validation.py @@ -1,5 +1,4 @@ import pytest - from pyrecest.backend_support._pytorch_sort_numpy_contract import normalize_sort_axis diff --git a/tests/backend_support/test_pytorch_sort_facade_contract.py b/tests/backend_support/test_pytorch_sort_facade_contract.py index 7951f7836..8177baf86 100644 --- a/tests/backend_support/test_pytorch_sort_facade_contract.py +++ b/tests/backend_support/test_pytorch_sort_facade_contract.py @@ -5,7 +5,6 @@ import pytest from tests.support.backend_runner import run_backend_code - _CHECK = """ import pyrecest.backend as backend diff --git a/tests/backend_support/test_pytorch_special_contract.py b/tests/backend_support/test_pytorch_special_contract.py index 9e422f729..420a563e4 100644 --- a/tests/backend_support/test_pytorch_special_contract.py +++ b/tests/backend_support/test_pytorch_special_contract.py @@ -29,8 +29,7 @@ def test_raw_pytorch_special_helpers_are_patched_under_default_backend(): if importlib.util.find_spec("torch") is None: pytest.skip("torch is not installed") - _run_python( - r''' + _run_python(r""" import math import pyrecest # noqa: F401 @@ -70,8 +69,7 @@ def assert_close(values, expected): assert math.isnan(pole_values[0]) assert math.isinf(pole_values[1]) and pole_values[1] > 0 assert math.isinf(pole_values[2]) and pole_values[2] < 0 -''' - ) +""") @pytest.mark.backend_portable @@ -80,7 +78,7 @@ def test_public_pytorch_special_helpers_accept_arraylike_and_out(): pytest.skip("torch is not installed") _run_python( - r''' + r""" import math import pyrecest.backend as backend @@ -116,6 +114,6 @@ def assert_close(module, values, expected): special_backend.polygamma(1, [1.0, 2.0]), [math.pi**2 / 6.0, math.pi**2 / 6.0 - 1.0], ) -''', +""", backend_name="pytorch", ) diff --git a/tests/backend_support/test_pytorch_take_index_contract.py b/tests/backend_support/test_pytorch_take_index_contract.py index 6100d640b..35c8f2173 100644 --- a/tests/backend_support/test_pytorch_take_index_contract.py +++ b/tests/backend_support/test_pytorch_take_index_contract.py @@ -1,7 +1,6 @@ import importlib.util import pytest - from tests.support.backend_runner import run_backend_code pytestmark = pytest.mark.backend_portable diff --git a/tests/backend_support/test_pytorch_tile_contract.py b/tests/backend_support/test_pytorch_tile_contract.py index 23fb45e66..ba3bab2c1 100644 --- a/tests/backend_support/test_pytorch_tile_contract.py +++ b/tests/backend_support/test_pytorch_tile_contract.py @@ -54,7 +54,9 @@ def test_pytorch_tile_scalar_and_array_repetitions_match_numpy_contract(): else: raise AssertionError(f"tile accepted non-integer repetitions {bad_reps!r}") """ - subprocess.run([sys.executable, "-c", code], check=True, env=_backend_test_env("pytorch")) + subprocess.run( + [sys.executable, "-c", code], check=True, env=_backend_test_env("pytorch") + ) @pytest.mark.backend_portable @@ -90,4 +92,6 @@ def test_raw_pytorch_tile_matches_numpy_contract_with_numpy_public_backend(): else: raise AssertionError(f"raw tile accepted non-integer repetitions {bad_reps!r}") """ - subprocess.run([sys.executable, "-c", code], check=True, env=_backend_test_env("numpy")) + subprocess.run( + [sys.executable, "-c", code], check=True, env=_backend_test_env("numpy") + ) diff --git a/tests/backend_support/test_pytorch_where_device_contract.py b/tests/backend_support/test_pytorch_where_device_contract.py index 08601e768..3767ca6a3 100644 --- a/tests/backend_support/test_pytorch_where_device_contract.py +++ b/tests/backend_support/test_pytorch_where_device_contract.py @@ -5,7 +5,6 @@ import importlib.util import pytest - from tests.support.backend_runner import run_backend_code pytestmark = pytest.mark.backend_portable diff --git a/tests/backend_support/test_raw_pytorch_cumulative_contract.py b/tests/backend_support/test_raw_pytorch_cumulative_contract.py index 5a46d22c3..54270f5d8 100644 --- a/tests/backend_support/test_raw_pytorch_cumulative_contract.py +++ b/tests/backend_support/test_raw_pytorch_cumulative_contract.py @@ -1,8 +1,7 @@ import importlib.util -import pytest - import pyrecest.backend as backend +import pytest from pyrecest.backend_tools import get_backend_name diff --git a/tests/backend_support/test_uniform_empty_bounds_contract.py b/tests/backend_support/test_uniform_empty_bounds_contract.py index 7fa48319d..fc95d40bc 100644 --- a/tests/backend_support/test_uniform_empty_bounds_contract.py +++ b/tests/backend_support/test_uniform_empty_bounds_contract.py @@ -3,7 +3,6 @@ from __future__ import annotations import pytest - from tests.support.backend_runner import run_backend_code pytestmark = pytest.mark.backend_portable diff --git a/tests/calibration/test_temporal_object_validation.py b/tests/calibration/test_temporal_object_validation.py index d856da59b..40ebaea96 100644 --- a/tests/calibration/test_temporal_object_validation.py +++ b/tests/calibration/test_temporal_object_validation.py @@ -1,10 +1,8 @@ from __future__ import annotations import numpy as np -import pytest - import pyrecest.calibration as calibration - +import pytest _OBJECT_TEMPORAL_SCALARS = ( np.array(np.timedelta64(1, "ns"), dtype=object), diff --git a/tests/calibration/test_time_offset_none_offset.py b/tests/calibration/test_time_offset_none_offset.py index 80e02e3e9..440aaf61c 100644 --- a/tests/calibration/test_time_offset_none_offset.py +++ b/tests/calibration/test_time_offset_none_offset.py @@ -1,5 +1,4 @@ import numpy as np - from pyrecest.calibration.time_offset import time_offset_error_summary diff --git a/tests/calibration/test_time_offset_real_numeric_validation.py b/tests/calibration/test_time_offset_real_numeric_validation.py index 5eebd8208..5b8a73b05 100644 --- a/tests/calibration/test_time_offset_real_numeric_validation.py +++ b/tests/calibration/test_time_offset_real_numeric_validation.py @@ -1,7 +1,6 @@ import unittest import numpy as np - from pyrecest.calibration import apply_time_offset, make_offset_grid diff --git a/tests/calibration/test_time_offset_temporal_validation.py b/tests/calibration/test_time_offset_temporal_validation.py index 4eaa63721..2e5799991 100644 --- a/tests/calibration/test_time_offset_temporal_validation.py +++ b/tests/calibration/test_time_offset_temporal_validation.py @@ -1,7 +1,6 @@ import unittest import numpy as np - from pyrecest.calibration.time_offset import ( aggregate_time_offset_sweeps, apply_time_offset, diff --git a/tests/distributions/test_abstract_mixture_sample_validation.py b/tests/distributions/test_abstract_mixture_sample_validation.py index 889ec04e2..fac1ab9f6 100644 --- a/tests/distributions/test_abstract_mixture_sample_validation.py +++ b/tests/distributions/test_abstract_mixture_sample_validation.py @@ -1,15 +1,18 @@ import numpy as np import pytest - from pyrecest.backend import array, eye from pyrecest.distributions.hypertorus.hypertoroidal_mixture import HypertoroidalMixture -from pyrecest.distributions.hypertorus.toroidal_wrapped_normal_distribution import ToroidalWrappedNormalDistribution +from pyrecest.distributions.hypertorus.toroidal_wrapped_normal_distribution import ( + ToroidalWrappedNormalDistribution, +) @pytest.mark.parametrize("count", ["3", np.str_("3")]) def test_mixture_sample_rejects_text_count(count): vmf = ToroidalWrappedNormalDistribution(array([1.0, 0.0]), eye(2)) - mixture = HypertoroidalMixture([vmf, vmf.shift(array([1.0, 1.0]))], array([0.5, 0.5])) + mixture = HypertoroidalMixture( + [vmf, vmf.shift(array([1.0, 1.0]))], array([0.5, 0.5]) + ) with pytest.raises(ValueError, match="n must be a positive integer"): mixture.sample(count) diff --git a/tests/distributions/test_dirac_entropy_zero_weights.py b/tests/distributions/test_dirac_entropy_zero_weights.py index 4d1a1a484..9f8654059 100644 --- a/tests/distributions/test_dirac_entropy_zero_weights.py +++ b/tests/distributions/test_dirac_entropy_zero_weights.py @@ -4,7 +4,6 @@ import numpy as np import numpy.testing as npt - from pyrecest.backend import array from pyrecest.distributions import LinearDiracDistribution diff --git a/tests/distributions/test_dirac_tensor_copy_contract.py b/tests/distributions/test_dirac_tensor_copy_contract.py index ae746ae58..4b8668c62 100644 --- a/tests/distributions/test_dirac_tensor_copy_contract.py +++ b/tests/distributions/test_dirac_tensor_copy_contract.py @@ -39,4 +39,6 @@ def test_pytorch_dirac_distribution_clones_input_tensor_storage(): assert dist.d.data_ptr() != samples.data_ptr() assert dist.w.data_ptr() != weights.data_ptr() """ - subprocess.run([sys.executable, "-c", code], check=True, env=_backend_test_env("pytorch")) + subprocess.run( + [sys.executable, "-c", code], check=True, env=_backend_test_env("pytorch") + ) diff --git a/tests/distributions/test_fejer_helpers.py b/tests/distributions/test_fejer_helpers.py index a5b966323..57d988164 100644 --- a/tests/distributions/test_fejer_helpers.py +++ b/tests/distributions/test_fejer_helpers.py @@ -1,7 +1,6 @@ import numpy as np import numpy.testing as npt import pytest - from pyrecest.distributions.hypertorus.fejer import ( adaptive_kernel_reduce_coefficients, apply_fejer_weights, @@ -14,12 +13,16 @@ def test_fejer_weights_1d(): - npt.assert_allclose(fejer_weights((5,)), np.array([1 / 3, 2 / 3, 1.0, 2 / 3, 1 / 3])) + npt.assert_allclose( + fejer_weights((5,)), np.array([1 / 3, 2 / 3, 1.0, 2 / 3, 1 / 3]) + ) def test_fejer_weights_product(): w = fejer_weights((3, 5)) - expected = np.outer(np.array([0.5, 1.0, 0.5]), np.array([1 / 3, 2 / 3, 1.0, 2 / 3, 1 / 3])) + expected = np.outer( + np.array([0.5, 1.0, 0.5]), np.array([1 / 3, 2 / 3, 1.0, 2 / 3, 1 / 3]) + ) npt.assert_allclose(w, expected) @@ -49,17 +52,23 @@ def test_apply_fejer_weights_preserves_center(): def test_centered_coefficients_crop_and_pad(): c = np.arange(7) npt.assert_array_equal(centered_coefficients(c, (3,)), np.array([2, 3, 4])) - npt.assert_array_equal(centered_coefficients(c, (9,)), np.array([0, 0, 1, 2, 3, 4, 5, 6, 0])) + npt.assert_array_equal( + centered_coefficients(c, (9,)), np.array([0, 0, 1, 2, 3, 4, 5, 6, 0]) + ) def test_reduce_coefficients_sharp_matches_centered_coefficients(): c = np.arange(7) - npt.assert_array_equal(reduce_coefficients(c, (3,), kernel="sharp"), centered_coefficients(c, (3,))) + npt.assert_array_equal( + reduce_coefficients(c, (3,), kernel="sharp"), centered_coefficients(c, (3,)) + ) def test_adaptive_reduction_keeps_nonnegative_sharp_result_unchanged(): coeff = np.array([0.05, 1.0 / (2.0 * np.pi), 0.05]) - reduced, exponent = adaptive_kernel_reduce_coefficients(coeff, (3,), return_exponent=True) + reduced, exponent = adaptive_kernel_reduce_coefficients( + coeff, (3,), return_exponent=True + ) npt.assert_allclose(reduced, coeff) assert exponent == 0.0 @@ -68,7 +77,9 @@ def test_adaptive_reduction_keeps_nonnegative_sharp_result_unchanged(): def test_adaptive_reduction_damps_negative_sharp_result(): coeff = np.array([0.0, -0.1, 1.0 / (2.0 * np.pi), -0.1, 0.0]) sharp = centered_coefficients(coeff, (3,)) - reduced, exponent = adaptive_kernel_reduce_coefficients(coeff, (3,), kernel="korovkin", return_exponent=True) + reduced, exponent = adaptive_kernel_reduce_coefficients( + coeff, (3,), kernel="korovkin", return_exponent=True + ) assert minimum_on_fft_grid(sharp) < 0.0 assert exponent > 0.0 diff --git a/tests/distributions/test_fejer_reduction.py b/tests/distributions/test_fejer_reduction.py index 69ba51a0a..1214a1ea6 100644 --- a/tests/distributions/test_fejer_reduction.py +++ b/tests/distributions/test_fejer_reduction.py @@ -1,14 +1,18 @@ import numpy as np import numpy.testing as npt - -from pyrecest.distributions.hypertorus.fejer import fejer_reduce_coefficients, fejer_weights +from pyrecest.distributions.hypertorus.fejer import ( + fejer_reduce_coefficients, + fejer_weights, +) def _evaluate_centered_1d(coefficients, xs): coefficients = np.asarray(coefficients) order = (coefficients.size - 1) // 2 ks = np.arange(-order, order + 1) - return np.sum(coefficients[:, None] * np.exp(1j * ks[:, None] * xs[None, :]), axis=0).real + return np.sum( + coefficients[:, None] * np.exp(1j * ks[:, None] * xs[None, :]), axis=0 + ).real def test_fejer_reduction_preserves_zero_frequency_coefficient(): diff --git a/tests/distributions/test_fejer_shape_validation.py b/tests/distributions/test_fejer_shape_validation.py index b156d7b0c..e7bd44a2f 100644 --- a/tests/distributions/test_fejer_shape_validation.py +++ b/tests/distributions/test_fejer_shape_validation.py @@ -1,7 +1,6 @@ import numpy as np import numpy.testing as npt import pytest - from pyrecest.distributions.hypertorus.fejer import fejer_weights diff --git a/tests/distributions/test_gauss_von_mises_distribution.py b/tests/distributions/test_gauss_von_mises_distribution.py index e49c106e2..b672f0fa0 100644 --- a/tests/distributions/test_gauss_von_mises_distribution.py +++ b/tests/distributions/test_gauss_von_mises_distribution.py @@ -95,9 +95,9 @@ def test_constructor_accepts_zero_kappa_uniform_limit(self): dist = GaussVonMisesDistribution(2, 1.3, 3, 0, 0.001, 0.0) points = array([[0.1, 1.2, 4.0], [1.5, 2.0, 3.0]]) - expected_pdf = GaussianDistribution( - dist.mu, dist.P, check_validity=False - ).pdf(points[1:, :].T) / (2.0 * float(pi)) + expected_pdf = GaussianDistribution(dist.mu, dist.P, check_validity=False).pdf( + points[1:, :].T + ) / (2.0 * float(pi)) self.assertTrue(allclose(dist.pdf(points), expected_pdf, atol=1e-12)) self.assertTrue(allclose(dist.hybrid_moment(), array([0.0, 0.0, 2.0]))) diff --git a/tests/distributions/test_gaussian_distribution_sample_validation.py b/tests/distributions/test_gaussian_distribution_sample_validation.py index 8a8f8864d..0d91f54fc 100644 --- a/tests/distributions/test_gaussian_distribution_sample_validation.py +++ b/tests/distributions/test_gaussian_distribution_sample_validation.py @@ -1,8 +1,9 @@ import numpy as np import pytest - from pyrecest.backend import array, eye -from pyrecest.distributions.nonperiodic.gaussian_distribution import GaussianDistribution +from pyrecest.distributions.nonperiodic.gaussian_distribution import ( + GaussianDistribution, +) @pytest.mark.parametrize("count", ["3", b"3", np.str_("3"), np.bytes_(b"3")]) diff --git a/tests/distributions/test_gaussian_sample_count_temporal_validation.py b/tests/distributions/test_gaussian_sample_count_temporal_validation.py index 9991a844d..e796ab8b2 100644 --- a/tests/distributions/test_gaussian_sample_count_temporal_validation.py +++ b/tests/distributions/test_gaussian_sample_count_temporal_validation.py @@ -1,6 +1,5 @@ import numpy as np import pytest - from pyrecest.backend import array, diag from pyrecest.distributions import GaussianDistribution diff --git a/tests/distributions/test_generalized_sine_skewed_wrapped_cauchy_stability.py b/tests/distributions/test_generalized_sine_skewed_wrapped_cauchy_stability.py index b54b45d4e..ac5ecab53 100644 --- a/tests/distributions/test_generalized_sine_skewed_wrapped_cauchy_stability.py +++ b/tests/distributions/test_generalized_sine_skewed_wrapped_cauchy_stability.py @@ -1,6 +1,5 @@ import numpy as np import numpy.testing as npt - from pyrecest.backend import array, pi, sin from pyrecest.distributions.circle.sine_skewed_distributions import ( GeneralizedKSineSkewedWrappedCauchyDistribution, diff --git a/tests/distributions/test_hypertoroidal_dirac_moment_order_validation.py b/tests/distributions/test_hypertoroidal_dirac_moment_order_validation.py index 684863319..6789aecc5 100644 --- a/tests/distributions/test_hypertoroidal_dirac_moment_order_validation.py +++ b/tests/distributions/test_hypertoroidal_dirac_moment_order_validation.py @@ -1,8 +1,7 @@ import numpy as np import numpy.testing as npt -import pytest - import pyrecest.backend +import pytest from pyrecest.backend import array from pyrecest.distributions import HypertoroidalDiracDistribution diff --git a/tests/distributions/test_hypertoroidal_dirac_set_mean_validation.py b/tests/distributions/test_hypertoroidal_dirac_set_mean_validation.py index a6c185cfb..8e26e72d6 100644 --- a/tests/distributions/test_hypertoroidal_dirac_set_mean_validation.py +++ b/tests/distributions/test_hypertoroidal_dirac_set_mean_validation.py @@ -3,7 +3,10 @@ # pylint: disable=no-name-in-module,no-member from pyrecest.backend import array, zeros_like -from pyrecest.distributions import AbstractHypertoroidalDistribution, HypertoroidalDiracDistribution +from pyrecest.distributions import ( + AbstractHypertoroidalDistribution, + HypertoroidalDiracDistribution, +) def _three_dimensional_distribution(): diff --git a/tests/distributions/test_hypertoroidal_grid_multiturn_wrap.py b/tests/distributions/test_hypertoroidal_grid_multiturn_wrap.py index 08e5342dd..6d74a36f8 100644 --- a/tests/distributions/test_hypertoroidal_grid_multiturn_wrap.py +++ b/tests/distributions/test_hypertoroidal_grid_multiturn_wrap.py @@ -1,5 +1,4 @@ import numpy.testing as npt - from pyrecest.backend import array, pi from pyrecest.distributions.hypertorus.hypertoroidal_grid_distribution import ( HypertoroidalGridDistribution, diff --git a/tests/distributions/test_hypertoroidal_tensor_train.py b/tests/distributions/test_hypertoroidal_tensor_train.py index 3c42c2450..0a82bbe6e 100644 --- a/tests/distributions/test_hypertoroidal_tensor_train.py +++ b/tests/distributions/test_hypertoroidal_tensor_train.py @@ -2,7 +2,6 @@ import numpy as np import numpy.testing as npt - from pyrecest.distributions.hypertorus._tensor_train import TensorTrain @@ -22,7 +21,9 @@ def test_frobenius_norm(self): def test_hadamard_product_matches_dense(self): left = np.arange(9, dtype=float).reshape(3, 3) right = np.arange(9, dtype=float).reshape(3, 3) + 1.0 - product = TensorTrain.from_dense(left).hadamard_product(TensorTrain.from_dense(right)) + product = TensorTrain.from_dense(left).hadamard_product( + TensorTrain.from_dense(right) + ) npt.assert_allclose(product.to_dense(), left * right, atol=1e-12) def test_centered_coefficient_convolution_matches_dense_1d(self): diff --git a/tests/distributions/test_hypertoroidal_tensor_train_entry_validation.py b/tests/distributions/test_hypertoroidal_tensor_train_entry_validation.py index eb2465441..9f66b3b65 100644 --- a/tests/distributions/test_hypertoroidal_tensor_train_entry_validation.py +++ b/tests/distributions/test_hypertoroidal_tensor_train_entry_validation.py @@ -2,7 +2,6 @@ import numpy as np import numpy.testing as npt - from pyrecest.distributions.hypertorus._tensor_train import TensorTrain @@ -12,7 +11,9 @@ def setUp(self): self.tt = TensorTrain.from_dense(self.tensor) def test_entry_accepts_numpy_integer_and_negative_indices(self): - npt.assert_allclose(self.tt.entry((np.int64(1), -1)), self.tensor[1, -1], atol=1e-12) + npt.assert_allclose( + self.tt.entry((np.int64(1), -1)), self.tensor[1, -1], atol=1e-12 + ) def test_entry_rejects_non_integer_indices_without_coercion(self): invalid_indices = [ diff --git a/tests/distributions/test_lin_hypersphere_cart_prod_dirac_distribution.py b/tests/distributions/test_lin_hypersphere_cart_prod_dirac_distribution.py index 5c6520b6c..176f9e59d 100644 --- a/tests/distributions/test_lin_hypersphere_cart_prod_dirac_distribution.py +++ b/tests/distributions/test_lin_hypersphere_cart_prod_dirac_distribution.py @@ -1,5 +1,4 @@ import numpy as np - from pyrecest.distributions.cart_prod.lin_hypersphere_cart_prod_dirac_distribution import ( LinHypersphereCartProdDiracDistribution, ) diff --git a/tests/distributions/test_linear_box_particle_set_mean_validation.py b/tests/distributions/test_linear_box_particle_set_mean_validation.py index 439261b1b..428379eab 100644 --- a/tests/distributions/test_linear_box_particle_set_mean_validation.py +++ b/tests/distributions/test_linear_box_particle_set_mean_validation.py @@ -11,9 +11,7 @@ class LinearBoxParticleSetMeanValidationTest(unittest.TestCase): def test_set_mean_rejects_broadcastable_wrong_shapes(self): - dist = LinearBoxParticleDistribution( - array([[0.0, 0.0]]), array([[2.0, 4.0]]) - ) + dist = LinearBoxParticleDistribution(array([[0.0, 0.0]]), array([[2.0, 4.0]])) for new_mean in (array(10.0), array([10.0]), array([[10.0, 11.0]])): with self.subTest(shape=new_mean.shape): diff --git a/tests/distributions/test_linear_dirac_distribution.py b/tests/distributions/test_linear_dirac_distribution.py index 7bdcb09b7..21b218992 100644 --- a/tests/distributions/test_linear_dirac_distribution.py +++ b/tests/distributions/test_linear_dirac_distribution.py @@ -56,7 +56,9 @@ def test_set_mean_rejects_invalid_multidimensional_shapes(self): ("column-vector", array([[1.0], [2.0]])), ): with self.subTest(name=name): - with self.assertRaisesRegex(ValueError, r"new_mean must have shape \(2,\)"): + with self.assertRaisesRegex( + ValueError, r"new_mean must have shape \(2,\)" + ): dist.set_mean(new_mean) npt.assert_allclose(dist.d, expected_samples) diff --git a/tests/distributions/test_linear_mixture.py b/tests/distributions/test_linear_mixture.py index 1ca5df27b..98e6c5c36 100644 --- a/tests/distributions/test_linear_mixture.py +++ b/tests/distributions/test_linear_mixture.py @@ -213,9 +213,7 @@ def test_sample_rejects_invalid_count(self): np.array(np.timedelta64(4, "ns")), np.array(np.datetime64("1970-01-01T00:00:00.000000004")), np.array(np.timedelta64(4, "ns"), dtype=object), - np.array( - np.datetime64("1970-01-01T00:00:00.000000004"), dtype=object - ), + np.array(np.datetime64("1970-01-01T00:00:00.000000004"), dtype=object), ) for n in invalid_counts: with self.subTest(n=n): diff --git a/tests/distributions/test_low_rank_fourier_complex_center.py b/tests/distributions/test_low_rank_fourier_complex_center.py index 7d956937a..0bbcbd7b0 100644 --- a/tests/distributions/test_low_rank_fourier_complex_center.py +++ b/tests/distributions/test_low_rank_fourier_complex_center.py @@ -3,7 +3,6 @@ import unittest import numpy as np - import pyrecest.backend from pyrecest.distributions.hypertorus.low_rank_hypertoroidal_fourier_distribution import ( LowRankHypertoroidalFourierDistribution, @@ -19,7 +18,9 @@ def test_identity_normalization_rejects_complex_center_coefficient(self): coefficients = np.zeros(3, dtype=np.complex128) coefficients[1] = 1.0 + 1.0j - with self.assertRaisesRegex(ValueError, "Center coefficient must be real-valued"): + with self.assertRaisesRegex( + ValueError, "Center coefficient must be real-valued" + ): LowRankHypertoroidalFourierDistribution.from_dense(coefficients) diff --git a/tests/distributions/test_low_rank_hypertoroidal_fourier_distribution.py b/tests/distributions/test_low_rank_hypertoroidal_fourier_distribution.py index 5f9247d6b..5f813a6f3 100644 --- a/tests/distributions/test_low_rank_hypertoroidal_fourier_distribution.py +++ b/tests/distributions/test_low_rank_hypertoroidal_fourier_distribution.py @@ -2,7 +2,6 @@ import numpy as np import numpy.testing as npt - import pyrecest.backend from pyrecest.distributions.hypertorus._tensor_train import TensorTrain from pyrecest.distributions.hypertorus.hypertoroidal_fourier_distribution import ( @@ -66,7 +65,9 @@ def test_rejects_non_integral_shape_entries(self): LowRankHypertoroidalFourierDistribution.uniform(shape) def test_value_and_pdf_match_dense_1d(self): - dense = HypertoroidalFourierDistribution(_identity_coefficients_1d(), "identity") + dense = HypertoroidalFourierDistribution( + _identity_coefficients_1d(), "identity" + ) low_rank = LowRankHypertoroidalFourierDistribution.from_dense(dense) xs = np.linspace(0.0, 2.0 * np.pi, 17, endpoint=False) npt.assert_allclose(low_rank.value(xs), dense.value(xs), atol=1e-10) @@ -88,7 +89,9 @@ def test_value_rejects_invalid_point_rank(self): low_rank_2d.value(points) def test_shift_matches_dense_2d(self): - dense = HypertoroidalFourierDistribution(_identity_coefficients_2d(), "identity") + dense = HypertoroidalFourierDistribution( + _identity_coefficients_2d(), "identity" + ) low_rank = LowRankHypertoroidalFourierDistribution.from_dense(dense) shift = np.array([0.2, -0.5]) npt.assert_allclose( @@ -96,16 +99,24 @@ def test_shift_matches_dense_2d(self): ) def test_predict_additive_noise_matches_dense_2d(self): - prior_dense = HypertoroidalFourierDistribution(_identity_coefficients_2d(), "identity") - noise_dense = HypertoroidalFourierDistribution(_identity_coefficients_2d(), "identity") + prior_dense = HypertoroidalFourierDistribution( + _identity_coefficients_2d(), "identity" + ) + noise_dense = HypertoroidalFourierDistribution( + _identity_coefficients_2d(), "identity" + ) predicted_dense = prior_dense.convolve(noise_dense) predicted_low_rank = LowRankHypertoroidalFourierDistribution.from_dense( prior_dense ).convolve(LowRankHypertoroidalFourierDistribution.from_dense(noise_dense)) - npt.assert_allclose(predicted_low_rank.to_dense(), predicted_dense.coeff_mat, atol=1e-10) + npt.assert_allclose( + predicted_low_rank.to_dense(), predicted_dense.coeff_mat, atol=1e-10 + ) def test_update_multiply_matches_dense_1d(self): - prior_dense = HypertoroidalFourierDistribution(_identity_coefficients_1d(), "identity") + prior_dense = HypertoroidalFourierDistribution( + _identity_coefficients_1d(), "identity" + ) likelihood_dense = HypertoroidalFourierDistribution( _identity_coefficients_1d(), "identity" ).shift(np.array([1.5])) @@ -113,7 +124,9 @@ def test_update_multiply_matches_dense_1d(self): updated_low_rank = LowRankHypertoroidalFourierDistribution.from_dense( prior_dense ).multiply(LowRankHypertoroidalFourierDistribution.from_dense(likelihood_dense)) - npt.assert_allclose(updated_low_rank.to_dense(), updated_dense.coeff_mat, atol=1e-10) + npt.assert_allclose( + updated_low_rank.to_dense(), updated_dense.coeff_mat, atol=1e-10 + ) def test_high_dimensional_uniform_smoke(self): dist = LowRankHypertoroidalFourierDistribution.uniform((3,) * 8) @@ -143,7 +156,9 @@ def test_centered_hermitianized_repairs_and_normalizes_distribution(self): npt.assert_allclose(repaired.integrate(), 1.0, atol=1e-12) def test_hermitian_helpers_reject_sqrt_transformation(self): - dist = LowRankHypertoroidalFourierDistribution.uniform((3,), transformation="sqrt") + dist = LowRankHypertoroidalFourierDistribution.uniform( + (3,), transformation="sqrt" + ) with self.assertRaises(NotImplementedError): dist.centered_hermitian_deviation() with self.assertRaises(NotImplementedError): @@ -151,7 +166,9 @@ def test_hermitian_helpers_reject_sqrt_transformation(self): with self.assertRaises(NotImplementedError): dist.centered_hermitianized() - def test_tensor_train_from_dense_validates_max_rank_before_one_dimensional_return(self): + def test_tensor_train_from_dense_validates_max_rank_before_one_dimensional_return( + self, + ): for max_rank in (0, -1, np.array(0)): with self.subTest(max_rank=repr(max_rank)): with self.assertRaises(ValueError): @@ -180,7 +197,9 @@ def test_tensor_train_accepts_integer_scalar_array_max_rank(self): self.assertEqual(tensor_train.ranks, (1, 1, 1)) def test_update_multiply_accepts_scalar_1d_coefficient_count(self): - prior_dense = HypertoroidalFourierDistribution(_identity_coefficients_1d(), "identity") + prior_dense = HypertoroidalFourierDistribution( + _identity_coefficients_1d(), "identity" + ) likelihood_dense = HypertoroidalFourierDistribution( _identity_coefficients_1d(), "identity" ).shift(np.array([1.5])) @@ -191,11 +210,17 @@ def test_update_multiply_accepts_scalar_1d_coefficient_count(self): LowRankHypertoroidalFourierDistribution.from_dense(likelihood_dense), n_coefficients=5, ) - npt.assert_allclose(updated_low_rank.to_dense(), updated_dense.coeff_mat, atol=1e-10) + npt.assert_allclose( + updated_low_rank.to_dense(), updated_dense.coeff_mat, atol=1e-10 + ) def test_predict_convolve_accepts_scalar_1d_coefficient_count(self): - prior_dense = HypertoroidalFourierDistribution(_identity_coefficients_1d(), "identity") - noise_dense = HypertoroidalFourierDistribution(_identity_coefficients_1d(), "identity") + prior_dense = HypertoroidalFourierDistribution( + _identity_coefficients_1d(), "identity" + ) + noise_dense = HypertoroidalFourierDistribution( + _identity_coefficients_1d(), "identity" + ) predicted_dense = prior_dense.convolve(noise_dense) predicted_low_rank = LowRankHypertoroidalFourierDistribution.from_dense( prior_dense @@ -203,7 +228,9 @@ def test_predict_convolve_accepts_scalar_1d_coefficient_count(self): LowRankHypertoroidalFourierDistribution.from_dense(noise_dense), n_coefficients=5, ) - npt.assert_allclose(predicted_low_rank.to_dense(), predicted_dense.coeff_mat, atol=1e-10) + npt.assert_allclose( + predicted_low_rank.to_dense(), predicted_dense.coeff_mat, atol=1e-10 + ) if __name__ == "__main__": diff --git a/tests/distributions/test_low_rank_hypertoroidal_fourier_scalar_shape.py b/tests/distributions/test_low_rank_hypertoroidal_fourier_scalar_shape.py index 411d09104..01c8b681b 100644 --- a/tests/distributions/test_low_rank_hypertoroidal_fourier_scalar_shape.py +++ b/tests/distributions/test_low_rank_hypertoroidal_fourier_scalar_shape.py @@ -2,7 +2,6 @@ import numpy as np import numpy.testing as npt - import pyrecest.backend from pyrecest.distributions.hypertorus.hypertoroidal_fourier_distribution import ( HypertoroidalFourierDistribution, @@ -36,8 +35,12 @@ def test_scalar_target_shape_matches_dense_multiply_and_convolve(self): updated_dense = prior_dense.multiply(other_dense, n_coefficients=3) updated_low_rank = prior_low_rank.multiply(other_low_rank, n_coefficients=3) self.assertEqual(updated_low_rank.coeff_shape, (3,)) - npt.assert_allclose(updated_low_rank.to_dense(), updated_dense.coeff_mat, atol=1e-10) + npt.assert_allclose( + updated_low_rank.to_dense(), updated_dense.coeff_mat, atol=1e-10 + ) predicted_dense = prior_dense.convolve(other_dense, n_coefficients=5) predicted_low_rank = prior_low_rank.convolve(other_low_rank, n_coefficients=5) - npt.assert_allclose(predicted_low_rank.to_dense(), predicted_dense.coeff_mat, atol=1e-10) + npt.assert_allclose( + predicted_low_rank.to_dense(), predicted_dense.coeff_mat, atol=1e-10 + ) diff --git a/tests/distributions/test_mixture_weight_shape_validation.py b/tests/distributions/test_mixture_weight_shape_validation.py index 0bc17051e..0460b1f79 100644 --- a/tests/distributions/test_mixture_weight_shape_validation.py +++ b/tests/distributions/test_mixture_weight_shape_validation.py @@ -1,6 +1,7 @@ import pytest - -from pyrecest.distributions.nonperiodic.gaussian_distribution import GaussianDistribution +from pyrecest.distributions.nonperiodic.gaussian_distribution import ( + GaussianDistribution, +) from pyrecest.distributions.nonperiodic.gaussian_mixture import GaussianMixture diff --git a/tests/distributions/test_piecewise_constant_input_validation.py b/tests/distributions/test_piecewise_constant_input_validation.py index 3660f0bcf..75bda5580 100644 --- a/tests/distributions/test_piecewise_constant_input_validation.py +++ b/tests/distributions/test_piecewise_constant_input_validation.py @@ -1,9 +1,8 @@ """Regression tests for piecewise-constant input validation.""" import numpy as np -import pytest import pyrecest.backend - +import pytest from pyrecest.distributions.circle.piecewise_constant_distribution import ( PiecewiseConstantDistribution, ) diff --git a/tests/distributions/test_piecewise_constant_sample_count_validation.py b/tests/distributions/test_piecewise_constant_sample_count_validation.py index 241af264d..268ff327a 100644 --- a/tests/distributions/test_piecewise_constant_sample_count_validation.py +++ b/tests/distributions/test_piecewise_constant_sample_count_validation.py @@ -2,7 +2,6 @@ import numpy as np import pytest - from pyrecest.distributions.circle.piecewise_constant_distribution import ( _validate_positive_sample_count, ) diff --git a/tests/distributions/test_sine_skewed_scalar_validation.py b/tests/distributions/test_sine_skewed_scalar_validation.py index e6583bd6b..edc2cefb4 100644 --- a/tests/distributions/test_sine_skewed_scalar_validation.py +++ b/tests/distributions/test_sine_skewed_scalar_validation.py @@ -1,5 +1,4 @@ import pytest - from pyrecest.distributions.circle.sine_skewed_distributions import ( GeneralizedKSineSkewedVonMisesDistribution, GeneralizedKSineSkewedWrappedCauchyDistribution, diff --git a/tests/distributions/test_so3_product_dirac_marginal_index_validation.py b/tests/distributions/test_so3_product_dirac_marginal_index_validation.py index e58082f49..a81834412 100644 --- a/tests/distributions/test_so3_product_dirac_marginal_index_validation.py +++ b/tests/distributions/test_so3_product_dirac_marginal_index_validation.py @@ -3,7 +3,6 @@ import numpy as np import numpy.testing as npt - from pyrecest.backend import array from pyrecest.distributions import SO3ProductDiracDistribution diff --git a/tests/distributions/test_toroidal_vm_rivest_large_kappa.py b/tests/distributions/test_toroidal_vm_rivest_large_kappa.py index 1b7d2bd2a..e06bff46d 100644 --- a/tests/distributions/test_toroidal_vm_rivest_large_kappa.py +++ b/tests/distributions/test_toroidal_vm_rivest_large_kappa.py @@ -2,15 +2,13 @@ import numpy as np import numpy.testing as npt -import pytest -from scipy.special import ive - import pyrecest.backend +import pytest from pyrecest.backend import array from pyrecest.distributions.hypertorus.toroidal_vm_rivest_distribution import ( ToroidalVMRivestDistribution, ) - +from scipy.special import ive pytestmark = pytest.mark.skipif( pyrecest.backend.__backend_name__ != "numpy", @@ -26,9 +24,7 @@ def test_large_independent_concentrations_remain_finite(): ) density_at_mode = float(dist.pdf(mu)) - expected_density = 1.0 / ( - 4.0 * math.pi**2 * float(ive(0, concentration)) ** 2 - ) + expected_density = 1.0 / (4.0 * math.pi**2 * float(ive(0, concentration)) ** 2) assert math.isfinite(density_at_mode) npt.assert_allclose(density_at_mode, expected_density, rtol=1e-12) diff --git a/tests/distributions/test_uniform_order_validation.py b/tests/distributions/test_uniform_order_validation.py index 8c3d08623..2964121d8 100644 --- a/tests/distributions/test_uniform_order_validation.py +++ b/tests/distributions/test_uniform_order_validation.py @@ -1,7 +1,8 @@ import pytest - from pyrecest.backend import array -from pyrecest.distributions.hypertorus.hypertoroidal_uniform_distribution import HypertoroidalUniformDistribution +from pyrecest.distributions.hypertorus.hypertoroidal_uniform_distribution import ( + HypertoroidalUniformDistribution, +) def test_uniform_moment_rejects_invalid_order(): diff --git a/tests/distributions/test_von_mises_fisher_large_kappa.py b/tests/distributions/test_von_mises_fisher_large_kappa.py index 54a743487..33914f167 100644 --- a/tests/distributions/test_von_mises_fisher_large_kappa.py +++ b/tests/distributions/test_von_mises_fisher_large_kappa.py @@ -3,11 +3,11 @@ import numpy as np import numpy.testing as npt -from scipy.special import ive # pylint: disable=no-name-in-module,no-member from pyrecest.backend import array, to_numpy from pyrecest.distributions import VonMisesFisherDistribution +from scipy.special import ive class TestVonMisesFisherLargeKappa(unittest.TestCase): diff --git a/tests/distributions/test_wrapped_cauchy_mean_validation.py b/tests/distributions/test_wrapped_cauchy_mean_validation.py index 88e1e58a5..fe83039cd 100644 --- a/tests/distributions/test_wrapped_cauchy_mean_validation.py +++ b/tests/distributions/test_wrapped_cauchy_mean_validation.py @@ -1,5 +1,4 @@ import pytest - from pyrecest.backend import array from pyrecest.distributions.circle.wrapped_cauchy_distribution import ( WrappedCauchyDistribution, diff --git a/tests/distributions/test_wrapped_exponential_rate_validation.py b/tests/distributions/test_wrapped_exponential_rate_validation.py index b6543d630..dca7ed0b8 100644 --- a/tests/distributions/test_wrapped_exponential_rate_validation.py +++ b/tests/distributions/test_wrapped_exponential_rate_validation.py @@ -1,7 +1,6 @@ import unittest import numpy as np - from pyrecest.distributions.circle.wrapped_exponential_distribution import ( WrappedExponentialDistribution, ) diff --git a/tests/distributions/test_wrapped_normal_cdf_wrap_validation.py b/tests/distributions/test_wrapped_normal_cdf_wrap_validation.py index 22dffc1b4..c700eb8e9 100644 --- a/tests/distributions/test_wrapped_normal_cdf_wrap_validation.py +++ b/tests/distributions/test_wrapped_normal_cdf_wrap_validation.py @@ -2,7 +2,6 @@ import numpy as np import pytest - from pyrecest.distributions.circle.wrapped_normal_distribution import ( WrappedNormalDistribution, ) diff --git a/tests/distributions/test_wrapped_normal_distribution_pytorch_cdf.py b/tests/distributions/test_wrapped_normal_distribution_pytorch_cdf.py index b90f1839b..7470359f0 100644 --- a/tests/distributions/test_wrapped_normal_distribution_pytorch_cdf.py +++ b/tests/distributions/test_wrapped_normal_distribution_pytorch_cdf.py @@ -26,7 +26,7 @@ def test_wrapped_normal_cdf_uses_backend_erf_under_pytorch(): pytest.skip("torch is not installed") _run_python_with_backend( - r''' + r""" import math import pyrecest.backend as backend @@ -50,6 +50,6 @@ def test_wrapped_normal_cdf_uses_backend_erf_under_pytorch(): scalar_value = dist.cdf(backend.asarray(0.4), starting_point=0.0, n_wraps=4) assert scalar_value.ndim == 0 assert math.isfinite(float(backend.to_numpy(scalar_value))) -''', +""", backend_name="pytorch", ) diff --git a/tests/evaluation/test_final_estimate_consistency.py b/tests/evaluation/test_final_estimate_consistency.py index d9ad74ee7..3f5a4dbcf 100644 --- a/tests/evaluation/test_final_estimate_consistency.py +++ b/tests/evaluation/test_final_estimate_consistency.py @@ -1,6 +1,5 @@ import numpy as np import numpy.testing as npt - from pyrecest.backend import array from pyrecest.evaluation import perform_predict_update_cycles from pyrecest.evaluation.configure_for_filter import register_filter_factory @@ -63,9 +62,7 @@ def test_extracting_history_does_not_change_returned_final_estimate(): npt.assert_allclose(estimate_without_history, array([1.0])) npt.assert_allclose(estimate_with_history, estimate_without_history) - npt.assert_allclose( - estimate_with_history, state_with_history.get_point_estimate() - ) + npt.assert_allclose(estimate_with_history, state_with_history.get_point_estimate()) npt.assert_allclose( estimate_without_history, state_without_history.get_point_estimate() ) diff --git a/tests/evaluation/test_flat_empty_measurements.py b/tests/evaluation/test_flat_empty_measurements.py index 530b5cc48..c6372be4e 100644 --- a/tests/evaluation/test_flat_empty_measurements.py +++ b/tests/evaluation/test_flat_empty_measurements.py @@ -1,6 +1,5 @@ import numpy as np import numpy.testing as npt - from pyrecest.backend import array from pyrecest.evaluation import perform_predict_update_cycles from pyrecest.evaluation.configure_for_filter import register_filter_factory diff --git a/tests/evaluation/test_get_extract_mean_mtt_flag.py b/tests/evaluation/test_get_extract_mean_mtt_flag.py index e796e019d..b0b4e389f 100644 --- a/tests/evaluation/test_get_extract_mean_mtt_flag.py +++ b/tests/evaluation/test_get_extract_mean_mtt_flag.py @@ -1,5 +1,4 @@ import pytest - from pyrecest.evaluation.get_extract_mean import get_extract_mean diff --git a/tests/evaluation/test_iterate_configs_vector_parameter_shape.py b/tests/evaluation/test_iterate_configs_vector_parameter_shape.py index 9814dfff9..e8b0b0768 100644 --- a/tests/evaluation/test_iterate_configs_vector_parameter_shape.py +++ b/tests/evaluation/test_iterate_configs_vector_parameter_shape.py @@ -9,7 +9,9 @@ def test_iterate_configs_and_runs_uses_config_count_for_vector_parameters(monkey if backend.__backend_name__ not in ("numpy", "autograd"): pytest.skip("iterate_configs_and_runs stores object-valued filter states") - iterate_module = importlib.import_module("pyrecest.evaluation.iterate_configs_and_runs") + iterate_module = importlib.import_module( + "pyrecest.evaluation.iterate_configs_and_runs" + ) vector_parameter = np.array([1.0, 2.0]) calls = [] @@ -46,12 +48,14 @@ def fake_predict_update_cycles( "auto_warning_on_off": False, } - last_filter_states, runtimes, run_failed, *_ = iterate_module.iterate_configs_and_runs( - groundtruths, - measurements, - {"name": "dummy"}, - [{"name": "dummy_filter", "parameter": vector_parameter}], - evaluation_config, + last_filter_states, runtimes, run_failed, *_ = ( + iterate_module.iterate_configs_and_runs( + groundtruths, + measurements, + {"name": "dummy"}, + [{"name": "dummy_filter", "parameter": vector_parameter}], + evaluation_config, + ) ) assert np.shape(last_filter_states) == (1, 2) diff --git a/tests/evaluation/test_pareto_boolean_constraints.py b/tests/evaluation/test_pareto_boolean_constraints.py index e861be1be..9f3f97b64 100644 --- a/tests/evaluation/test_pareto_boolean_constraints.py +++ b/tests/evaluation/test_pareto_boolean_constraints.py @@ -2,7 +2,6 @@ import pandas as pd import pytest - from pyrecest.evaluation import constraint_mask, select_under_constraints diff --git a/tests/evaluation/test_pareto_front_numeric_eligibility.py b/tests/evaluation/test_pareto_front_numeric_eligibility.py index 3c4e2646e..02254e471 100644 --- a/tests/evaluation/test_pareto_front_numeric_eligibility.py +++ b/tests/evaluation/test_pareto_front_numeric_eligibility.py @@ -1,11 +1,12 @@ from __future__ import annotations import pandas as pd - from pyrecest.evaluation import is_pareto_front, pareto_front_indices -def test_pareto_front_excludes_rows_without_numeric_objectives_when_missing_allowed() -> None: +def test_pareto_front_excludes_rows_without_numeric_objectives_when_missing_allowed() -> ( + None +): table = pd.DataFrame( [ {"name": "invalid", "error": "unknown", "runtime": [1.0, 2.0]}, diff --git a/tests/evaluation/test_scenario_generation_rng_state.py b/tests/evaluation/test_scenario_generation_rng_state.py index 186fd0890..0034f0466 100644 --- a/tests/evaluation/test_scenario_generation_rng_state.py +++ b/tests/evaluation/test_scenario_generation_rng_state.py @@ -9,7 +9,6 @@ import numpy as np import numpy.testing as npt - import pyrecest.backend from pyrecest.backend import random @@ -59,9 +58,7 @@ def test_generation_preserves_caller_rng_state(self): return random.seed(314159) - expected_backend = pyrecest.backend.to_numpy( - random.rand(size=(4,)) - ).copy() + expected_backend = pyrecest.backend.to_numpy(random.rand(size=(4,))).copy() random.seed(314159) np.random.seed(271828) diff --git a/tests/evaluation/test_selection_score_validation.py b/tests/evaluation/test_selection_score_validation.py index 945421b8b..d7a3fed0e 100644 --- a/tests/evaluation/test_selection_score_validation.py +++ b/tests/evaluation/test_selection_score_validation.py @@ -1,6 +1,5 @@ import numpy as np import pytest - from pyrecest.evaluation.selection import sanitized_score_vector, top_count_mask diff --git a/tests/evaluation/test_selection_temporal_validation.py b/tests/evaluation/test_selection_temporal_validation.py index adcd1c6fe..35aa6a4b8 100644 --- a/tests/evaluation/test_selection_temporal_validation.py +++ b/tests/evaluation/test_selection_temporal_validation.py @@ -2,7 +2,6 @@ import numpy as np import pytest - from pyrecest.evaluation.selection import ( retained_count_from_fraction, tail_rescue_quota_count, diff --git a/tests/evaluation/test_zero_measurements.py b/tests/evaluation/test_zero_measurements.py index 147ca1a39..8e2072953 100644 --- a/tests/evaluation/test_zero_measurements.py +++ b/tests/evaluation/test_zero_measurements.py @@ -1,5 +1,4 @@ import numpy as np - from pyrecest.distributions import GaussianDistribution from pyrecest.evaluation.generate_measurements import generate_measurements diff --git a/tests/experimental/test_dp_measurement_subclusters.py b/tests/experimental/test_dp_measurement_subclusters.py index 335ce4800..e65680023 100644 --- a/tests/experimental/test_dp_measurement_subclusters.py +++ b/tests/experimental/test_dp_measurement_subclusters.py @@ -1,6 +1,5 @@ import numpy as np import pytest - from pyrecest.experimental.dp_measurement_subclusters import ( DPMeasurementSubclusterConfig, active_subcluster_responsibilities, @@ -66,8 +65,12 @@ def test_partition_score_prefers_separate_far_blobs(): prior_variance=100.0, ) - single_score = score_dp_measurement_partition(measurements, np.array([0, 0, 0, 0]), config) - split_score = score_dp_measurement_partition(measurements, np.array([0, 0, 1, 1]), config) + single_score = score_dp_measurement_partition( + measurements, np.array([0, 0, 0, 0]), config + ) + split_score = score_dp_measurement_partition( + measurements, np.array([0, 0, 1, 1]), config + ) assert split_score > single_score @@ -106,7 +109,9 @@ def test_active_responsibilities_can_be_recomputed(): responsibilities = active_subcluster_responsibilities(measurements, result.atoms) assert responsibilities.shape == result.responsibilities.shape - np.testing.assert_allclose(responsibilities.sum(axis=1), np.ones(measurements.shape[0])) + np.testing.assert_allclose( + responsibilities.sum(axis=1), np.ones(measurements.shape[0]) + ) @pytest.mark.parametrize( diff --git a/tests/experimental/test_dvs_event_likelihood_temporal_validation.py b/tests/experimental/test_dvs_event_likelihood_temporal_validation.py index 43598c198..f2a2cb097 100644 --- a/tests/experimental/test_dvs_event_likelihood_temporal_validation.py +++ b/tests/experimental/test_dvs_event_likelihood_temporal_validation.py @@ -1,7 +1,6 @@ import unittest import numpy as np - from pyrecest.experimental.dvs.event_likelihood import ( ContourSample, EventLikelihoodConfig, diff --git a/tests/experimental/test_multisensor_ddp_association.py b/tests/experimental/test_multisensor_ddp_association.py index 4316e79a7..1cee1be2b 100644 --- a/tests/experimental/test_multisensor_ddp_association.py +++ b/tests/experimental/test_multisensor_ddp_association.py @@ -1,6 +1,5 @@ import numpy as np import pytest - from pyrecest.experimental.multisensor_ddp_association import ( BIRTH_LABEL, CLUTTER_LABEL, @@ -9,7 +8,6 @@ predict_ddp_base_weights, ) - _TEMPORAL_PAYLOADS = ( np.timedelta64(1, "ns"), np.datetime64("1970-01-01T00:00:00.000000001", "ns"), @@ -41,12 +39,25 @@ def test_multisensor_update_shares_target_atoms_across_sensors(): prior_strength=0.5, ) - assert result.assignment_labels == ("target-a", "target-b", BIRTH_LABEL, CLUTTER_LABEL) - assert result.posterior_for_sensor("radar").hard_assignments == ("target-a", "target-b") - assert result.posterior_for_sensor("camera").hard_assignments == ("target-a", "target-b") + assert result.assignment_labels == ( + "target-a", + "target-b", + BIRTH_LABEL, + CLUTTER_LABEL, + ) + assert result.posterior_for_sensor("radar").hard_assignments == ( + "target-a", + "target-b", + ) + assert result.posterior_for_sensor("camera").hard_assignments == ( + "target-a", + "target-b", + ) assert result.posterior_for_sensor("radar").responsibilities[0, 0] > 0.90 assert result.posterior_for_sensor("camera").responsibilities[1, 1] > 0.85 - assert np.isclose(result.updated_target_weights.sum() + result.updated_birth_weight, 1.0) + assert np.isclose( + result.updated_target_weights.sum() + result.updated_birth_weight, 1.0 + ) assert result.expected_clutter_count < 0.2 @@ -59,7 +70,9 @@ def test_clutter_evidence_competes_with_existing_target_and_birth_atoms(): concentration=1.0, ) - result = multisensor_ddp_association_update([0.45, 0.45], [block], birth_weight=0.10) + result = multisensor_ddp_association_update( + [0.45, 0.45], [block], birth_weight=0.10 + ) posterior = result.posterior_for_sensor("lidar") assert posterior.hard_assignments == (CLUTTER_LABEL,) @@ -76,8 +89,12 @@ def test_point_target_projection_allows_each_existing_target_once_per_sensor(): concentration=5.0, ) - result = multisensor_ddp_association_update([0.5, 0.5], [block], birth_weight=0.05, point_target=True) - target_responsibilities = result.posterior_for_sensor("radar").target_responsibilities + result = multisensor_ddp_association_update( + [0.5, 0.5], [block], birth_weight=0.05, point_target=True + ) + target_responsibilities = result.posterior_for_sensor( + "radar" + ).target_responsibilities assert np.all(target_responsibilities.sum(axis=0) <= 1.0) assert result.posterior_for_sensor("radar").hard_assignments.count(0) == 1 @@ -130,7 +147,10 @@ def test_input_validation_rejects_shape_and_probability_errors(): with pytest.raises(ValueError, match="duplicate sensor_id"): multisensor_ddp_association_update( [1.0], - [SensorAssociationBlock("camera", [[0.0]]), SensorAssociationBlock("camera", [[0.0]])], + [ + SensorAssociationBlock("camera", [[0.0]]), + SensorAssociationBlock("camera", [[0.0]]), + ], ) @@ -157,7 +177,9 @@ def test_multisensor_update_rejects_temporal_numeric_payloads(bad_value): multisensor_ddp_association_update([1.0], [valid_block], birth_weight=bad_value) with pytest.raises(ValueError, match="datetime64|timedelta64"): - multisensor_ddp_association_update([1.0], [valid_block], prior_strength=bad_value) + multisensor_ddp_association_update( + [1.0], [valid_block], prior_strength=bad_value + ) invalid_sensor_blocks = [ SensorAssociationBlock("radar", [[bad_value]]), diff --git a/tests/filters/test_adaptive_process_noise.py b/tests/filters/test_adaptive_process_noise.py index ed85e5741..090e73658 100644 --- a/tests/filters/test_adaptive_process_noise.py +++ b/tests/filters/test_adaptive_process_noise.py @@ -132,7 +132,9 @@ def test_observe_rejects_temporal_scalar_controls(): np.timedelta64(2, "ns"), np.datetime64("1970-01-01T00:00:00.000000002"), ): - with pytest.raises(ValueError, match="measurement_dim must be a positive integer"): + with pytest.raises( + ValueError, match="measurement_dim must be a positive integer" + ): adapter.observe(measurement_dim=value, nis=1.0) for value in ( diff --git a/tests/filters/test_association_hypotheses.py b/tests/filters/test_association_hypotheses.py index 636763e59..2dec6d6a6 100644 --- a/tests/filters/test_association_hypotheses.py +++ b/tests/filters/test_association_hypotheses.py @@ -135,9 +135,7 @@ def test_top_k_gate_keeps_only_best_duplicate_pair_hypothesis(self): ) gated = [ - hypothesis - for hypothesis in gated_with_rejections - if hypothesis.accepted + hypothesis for hypothesis in gated_with_rejections if hypothesis.accepted ] cost_matrix = hypotheses_to_cost_matrix( gated, diff --git a/tests/filters/test_dirichlet_process_birth_tracker.py b/tests/filters/test_dirichlet_process_birth_tracker.py index 580921fa2..b9e6f9a15 100644 --- a/tests/filters/test_dirichlet_process_birth_tracker.py +++ b/tests/filters/test_dirichlet_process_birth_tracker.py @@ -1,10 +1,9 @@ import numpy as np -import pytest - import pyrecest.backend +import pytest from pyrecest.filters.dirichlet_process_birth_tracker import ( - DPBirthMultiBernoulliTracker, DirichletProcessBirthMultiBernoulliTracker, + DPBirthMultiBernoulliTracker, ) pytestmark = pytest.mark.skipif( @@ -47,7 +46,9 @@ def _tracker(**overrides): def test_unassigned_measurement_creates_birth_atom_and_component(): tracker, measurement_matrix, measurement_covariance = _tracker() - tracker.update_linear(np.array([[2.0], [3.0]]), measurement_matrix, measurement_covariance) + tracker.update_linear( + np.array([[2.0], [3.0]]), measurement_matrix, measurement_covariance + ) assert len(tracker.birth_atoms) == 1 assert tracker.get_number_of_components() == 1 @@ -61,7 +62,9 @@ def test_high_clutter_suppresses_birth_creation(): dp_birth_threshold=10.0, ) - tracker.update_linear(np.array([[2.0], [3.0]]), measurement_matrix, measurement_covariance) + tracker.update_linear( + np.array([[2.0], [3.0]]), measurement_matrix, measurement_covariance + ) assert len(tracker.birth_atoms) == 0 assert tracker.get_number_of_components() == 0 diff --git a/tests/filters/test_discrete_state_masked_emissions.py b/tests/filters/test_discrete_state_masked_emissions.py index 1e4cd5541..77eccdc9a 100644 --- a/tests/filters/test_discrete_state_masked_emissions.py +++ b/tests/filters/test_discrete_state_masked_emissions.py @@ -1,7 +1,6 @@ """Regression tests for masked discrete-state emission scaling.""" import numpy as np - from pyrecest.filters.discrete_state import ( discrete_forward_backward, discrete_forward_backward_time_varying, diff --git a/tests/filters/test_fejer_identity_filter.py b/tests/filters/test_fejer_identity_filter.py index cc3900ca3..110e0cfe3 100644 --- a/tests/filters/test_fejer_identity_filter.py +++ b/tests/filters/test_fejer_identity_filter.py @@ -42,7 +42,9 @@ def test_predict_and_update_identity_1d_are_normalized(): filt = FejerIdentityFilter((15,)) filt.filter_state = wrapped_normal(backend.array(1.0), backend.array(0.4)) filt.predict_identity(wrapped_normal(backend.array(0.0), backend.array(0.2))) - filt.update_identity(wrapped_normal(backend.array(0.0), backend.array(0.7)), backend.array([1.3])) + filt.update_identity( + wrapped_normal(backend.array(0.0), backend.array(0.7)), backend.array([1.3]) + ) xs = backend.linspace(0.0, 2.0 * backend.pi, 256, endpoint=False) integral = float(filt.filter_state.pdf(xs).sum()) * float(2.0 * backend.pi / 256) diff --git a/tests/filters/test_gaussian_hypothesis_mixture.py b/tests/filters/test_gaussian_hypothesis_mixture.py index 63dadfd63..e5379c004 100644 --- a/tests/filters/test_gaussian_hypothesis_mixture.py +++ b/tests/filters/test_gaussian_hypothesis_mixture.py @@ -57,9 +57,7 @@ def test_complex_numeric_inputs_are_rejected_without_dropping_imaginary_parts(se WeightedGaussianHypothesis(np.array([1.0 + 2.0j]), np.array([[1.0]])) with self.assertRaisesRegex(ValueError, "covariance"): - WeightedGaussianHypothesis( - np.array([0.0]), np.array([[1.0 + 2.0j]]) - ) + WeightedGaussianHypothesis(np.array([0.0]), np.array([[1.0 + 2.0j]])) with self.assertRaisesRegex(ValueError, "log_weight"): WeightedGaussianHypothesis( diff --git a/tests/filters/test_hyperspherical_ukf_arbitrary_noise_normalization.py b/tests/filters/test_hyperspherical_ukf_arbitrary_noise_normalization.py index d65a6b4e7..49793fa20 100644 --- a/tests/filters/test_hyperspherical_ukf_arbitrary_noise_normalization.py +++ b/tests/filters/test_hyperspherical_ukf_arbitrary_noise_normalization.py @@ -2,7 +2,6 @@ import numpy as np import numpy.testing as npt - import pyrecest.backend from pyrecest.backend import array from pyrecest.filters.hyperspherical_ukf import HypersphericalUKF diff --git a/tests/filters/test_hypertoroidal_particle_filter_numpy_scalars.py b/tests/filters/test_hypertoroidal_particle_filter_numpy_scalars.py index 4a85c07ac..746ed25db 100644 --- a/tests/filters/test_hypertoroidal_particle_filter_numpy_scalars.py +++ b/tests/filters/test_hypertoroidal_particle_filter_numpy_scalars.py @@ -1,6 +1,5 @@ import numpy as np import pytest - from pyrecest.filters import HypertoroidalParticleFilter diff --git a/tests/filters/test_linear_update_planning_temporal_validation.py b/tests/filters/test_linear_update_planning_temporal_validation.py index 46d58ed4d..8b21f8223 100644 --- a/tests/filters/test_linear_update_planning_temporal_validation.py +++ b/tests/filters/test_linear_update_planning_temporal_validation.py @@ -2,7 +2,6 @@ import numpy as np import pytest - from pyrecest.filters.linear_update_planning import ( gate_threshold_for_measurement, huber_covariance_scale, diff --git a/tests/filters/test_linear_update_thresholds.py b/tests/filters/test_linear_update_thresholds.py index ba241e372..76238479c 100644 --- a/tests/filters/test_linear_update_thresholds.py +++ b/tests/filters/test_linear_update_thresholds.py @@ -1,9 +1,10 @@ from dataclasses import dataclass import numpy as np - -from pyrecest.filters.linear_update_planning import gate_threshold_for_measurement -from pyrecest.filters.linear_update_planning import plan_linear_measurement_update +from pyrecest.filters.linear_update_planning import ( + gate_threshold_for_measurement, + plan_linear_measurement_update, +) @dataclass diff --git a/tests/filters/test_low_rank_hypertoroidal_fourier_filter.py b/tests/filters/test_low_rank_hypertoroidal_fourier_filter.py index ff14b6329..368c4c248 100644 --- a/tests/filters/test_low_rank_hypertoroidal_fourier_filter.py +++ b/tests/filters/test_low_rank_hypertoroidal_fourier_filter.py @@ -2,7 +2,6 @@ import numpy as np import numpy.testing as npt - import pyrecest.backend from pyrecest.distributions.hypertorus.hypertoroidal_fourier_distribution import ( HypertoroidalFourierDistribution, @@ -67,34 +66,50 @@ def test_stores_truncation_controls(self): def test_predict_identity_matches_dense_1d(self): dense_filter = HypertoroidalFourierFilter((5,), "identity") low_rank_filter = LowRankHypertoroidalFourierFilter((5,), "identity") - prior = HypertoroidalFourierDistribution(_identity_coefficients_1d(), "identity") - noise = HypertoroidalFourierDistribution(_identity_coefficients_1d(0.5), "identity") + prior = HypertoroidalFourierDistribution( + _identity_coefficients_1d(), "identity" + ) + noise = HypertoroidalFourierDistribution( + _identity_coefficients_1d(0.5), "identity" + ) dense_filter.filter_state = prior low_rank_filter.filter_state = prior dense_filter.predict_identity(noise) low_rank_filter.predict_identity(noise) npt.assert_allclose( - low_rank_filter.filter_state.to_dense(), dense_filter.filter_state.coeff_mat, atol=1e-10 + low_rank_filter.filter_state.to_dense(), + dense_filter.filter_state.coeff_mat, + atol=1e-10, ) def test_update_identity_matches_dense_1d(self): dense_filter = HypertoroidalFourierFilter((5,), "identity") low_rank_filter = LowRankHypertoroidalFourierFilter((5,), "identity") - prior = HypertoroidalFourierDistribution(_identity_coefficients_1d(), "identity") - noise = HypertoroidalFourierDistribution(_identity_coefficients_1d(0.5), "identity") + prior = HypertoroidalFourierDistribution( + _identity_coefficients_1d(), "identity" + ) + noise = HypertoroidalFourierDistribution( + _identity_coefficients_1d(0.5), "identity" + ) dense_filter.filter_state = prior low_rank_filter.filter_state = prior dense_filter.update_identity(noise, np.array([1.5])) low_rank_filter.update_identity(noise, np.array([1.5])) npt.assert_allclose( - low_rank_filter.filter_state.to_dense(), dense_filter.filter_state.coeff_mat, atol=1e-10 + low_rank_filter.filter_state.to_dense(), + dense_filter.filter_state.coeff_mat, + atol=1e-10, ) def test_update_identity_accepts_scalar_1d_measurement(self): vector_filter = LowRankHypertoroidalFourierFilter((5,), "identity") scalar_filter = LowRankHypertoroidalFourierFilter((5,), "identity") - prior = HypertoroidalFourierDistribution(_identity_coefficients_1d(), "identity") - noise = HypertoroidalFourierDistribution(_identity_coefficients_1d(0.5), "identity") + prior = HypertoroidalFourierDistribution( + _identity_coefficients_1d(), "identity" + ) + noise = HypertoroidalFourierDistribution( + _identity_coefficients_1d(0.5), "identity" + ) vector_filter.filter_state = prior scalar_filter.filter_state = prior diff --git a/tests/filters/test_multisensor_hdp_association.py b/tests/filters/test_multisensor_hdp_association.py index 2bcd3b02d..1e6e96b3d 100644 --- a/tests/filters/test_multisensor_hdp_association.py +++ b/tests/filters/test_multisensor_hdp_association.py @@ -1,7 +1,6 @@ import unittest import numpy as np - from pyrecest.filters.multisensor_hdp_association import ( HDPAssociationLabel, multisensor_hdp_association, diff --git a/tests/filters/test_nonparametric_cardinality.py b/tests/filters/test_nonparametric_cardinality.py index 7729404ff..63d650c85 100644 --- a/tests/filters/test_nonparametric_cardinality.py +++ b/tests/filters/test_nonparametric_cardinality.py @@ -2,7 +2,6 @@ import unittest import numpy.testing as npt - from pyrecest.filters.nonparametric_cardinality import ( DirichletProcessCardinalityPrior, PitmanYorBirthProbability, @@ -15,8 +14,12 @@ def test_pitman_yor_predictive_probabilities_are_heavier_tailed_than_dp(self): pitman_yor_prior = PitmanYorProcessCardinalityPrior(discount=0.5, strength=2.0) dirichlet_prior = DirichletProcessCardinalityPrior(concentration=2.0) - pitman_yor_probabilities = pitman_yor_prior.predictive_assignment_probabilities([3, 2]) - dirichlet_probabilities = dirichlet_prior.predictive_assignment_probabilities([3, 2]) + pitman_yor_probabilities = pitman_yor_prior.predictive_assignment_probabilities( + [3, 2] + ) + dirichlet_probabilities = dirichlet_prior.predictive_assignment_probabilities( + [3, 2] + ) npt.assert_allclose( pitman_yor_probabilities, diff --git a/tests/filters/test_replay_grid_ess_gating.py b/tests/filters/test_replay_grid_ess_gating.py index 19944b5fe..5d2835828 100644 --- a/tests/filters/test_replay_grid_ess_gating.py +++ b/tests/filters/test_replay_grid_ess_gating.py @@ -2,7 +2,6 @@ import unittest import numpy as np - from pyrecest.filters import adaptive_position_proposal_probability @@ -18,9 +17,7 @@ def test_probability_one_still_respects_ess_threshold(self): self.assertAlmostEqual(ess_fraction, 1.0) low_ess_filter = types.SimpleNamespace( - filter_state=types.SimpleNamespace( - w=np.array([1.0, 0.0, 0.0, 0.0]) - ) + filter_state=types.SimpleNamespace(w=np.array([1.0, 0.0, 0.0, 0.0])) ) probability, ess_fraction = adaptive_position_proposal_probability( low_ess_filter, 1.0, 0.5 diff --git a/tests/filters/test_se2_ukf_zero_norm_sigma_points.py b/tests/filters/test_se2_ukf_zero_norm_sigma_points.py index 312b2e3eb..552b1756c 100644 --- a/tests/filters/test_se2_ukf_zero_norm_sigma_points.py +++ b/tests/filters/test_se2_ukf_zero_norm_sigma_points.py @@ -11,7 +11,6 @@ from pyrecest.distributions import GaussianDistribution from pyrecest.filters.se2_ukf import SE2UKF - _IDENTITY = array([1.0, 0.0, 0.0, 0.0]) diff --git a/tests/filters/test_sequence_association_array_frames.py b/tests/filters/test_sequence_association_array_frames.py index 4e9537b42..e871fde4c 100644 --- a/tests/filters/test_sequence_association_array_frames.py +++ b/tests/filters/test_sequence_association_array_frames.py @@ -1,5 +1,4 @@ import numpy as np - from pyrecest.filters import ( SequenceAssociationNode, solve_viterbi_sequence_association, @@ -19,9 +18,9 @@ def test_viterbi_accepts_numpy_object_array_of_frames(): path = solve_viterbi_sequence_association( frames, - lambda previous, current, _context: 0.0 - if previous.payload == current.payload - else 10.0, + lambda previous, current, _context: ( + 0.0 if previous.payload == current.payload else 10.0 + ), ) assert path.candidate_indices == (0, 0) diff --git a/tests/filters/test_survival_association.py b/tests/filters/test_survival_association.py index eb867cc07..618c55a0e 100644 --- a/tests/filters/test_survival_association.py +++ b/tests/filters/test_survival_association.py @@ -1,6 +1,6 @@ -from dataclasses import dataclass, field import math import unittest +from dataclasses import dataclass, field import numpy as np from pyrecest.filters.association_hypotheses import AssociationHypothesis @@ -22,7 +22,9 @@ class DummyTrack: class SurvivalAssociationTest(unittest.TestCase): def test_resolves_survival_weight_from_track_lifecycle(self): - tracks = [DummyTrack(hits=4, misses=2, metadata={"visibility_probability": 0.25})] + tracks = [ + DummyTrack(hits=4, misses=2, metadata={"visibility_probability": 0.25}) + ] config = SurvivalAwareAssociationConfig( existence_probability=0.8, survival_probability=0.5, @@ -45,8 +47,12 @@ def test_resolves_survival_weight_from_track_lifecycle(self): def test_prior_can_prefer_recent_track_over_popular_stale_track(self): tracks = [DummyTrack(hits=1, misses=0), DummyTrack(hits=10, misses=4)] hypotheses = [ - AssociationHypothesis(track_index=0, measurement_index=0, cost=0.0, log_likelihood=0.0), - AssociationHypothesis(track_index=1, measurement_index=0, cost=0.0, log_likelihood=0.0), + AssociationHypothesis( + track_index=0, measurement_index=0, cost=0.0, log_likelihood=0.0 + ), + AssociationHypothesis( + track_index=1, measurement_index=0, cost=0.0, log_likelihood=0.0 + ), ] config = SurvivalAwareAssociationConfig( existence_probability=1.0, @@ -105,7 +111,9 @@ def test_probability_scalars_reject_text_bool_and_temporal_payloads(self): track_survival_prior_components([DummyTrack()], config=config) def test_numeric_object_probability_scalar_is_preserved(self): - config = SurvivalAwareAssociationConfig(survival_probability=np.array(0.5, dtype=object)) + config = SurvivalAwareAssociationConfig( + survival_probability=np.array(0.5, dtype=object) + ) components = track_survival_prior_components([DummyTrack()], config=config) diff --git a/tests/filters/test_survival_aware_association.py b/tests/filters/test_survival_aware_association.py index ab589ce6e..40c89d4c7 100644 --- a/tests/filters/test_survival_aware_association.py +++ b/tests/filters/test_survival_aware_association.py @@ -142,7 +142,9 @@ def test_linear_gaussian_hypotheses_expose_crp_prior_metadata(self): self.assertIn("survival_aware_crp_weight", metadata) self.assertIn("survival_aware_measurement_log_score", metadata) self.assertIsNone(hypotheses[0].probability) - self.assertAlmostEqual(hypotheses[0].cost, -metadata["survival_aware_log_score"]) + self.assertAlmostEqual( + hypotheses[0].cost, -metadata["survival_aware_log_score"] + ) def test_track_manager_associator_keeps_far_measurement_unassigned(self): tracks = [_track(0.0, hits=5, misses=0, existence=0.95)] diff --git a/tests/filters/test_survival_aware_crp.py b/tests/filters/test_survival_aware_crp.py index 0b10474a1..17339915d 100644 --- a/tests/filters/test_survival_aware_crp.py +++ b/tests/filters/test_survival_aware_crp.py @@ -1,7 +1,6 @@ import unittest import numpy.testing as npt - from pyrecest.filters.survival_aware_crp import ( SurvivalAwareCRPAssociationPrior, SurvivalAwareTrackEvidence, @@ -32,15 +31,7 @@ def test_predictive_probabilities_match_hand_computed_weights(self): clutter_weight=0.25, ) - existing_weight = ( - (4.0 * 0.5 - 0.25) - * 0.8 - * 0.9 - * 0.7 - * 0.5 - * 0.25 - * 0.5 - ) + existing_weight = (4.0 * 0.5 - 0.25) * 0.8 * 0.9 * 0.7 * 0.5 * 0.25 * 0.5 birth_weight = 0.3 * (2.0 + 0.25 * 1.0) clutter_weight = 0.25 total_weight = existing_weight + birth_weight + clutter_weight diff --git a/tests/filters/test_survival_aware_crp_stable_normalization.py b/tests/filters/test_survival_aware_crp_stable_normalization.py index 181f6b015..46807da1c 100644 --- a/tests/filters/test_survival_aware_crp_stable_normalization.py +++ b/tests/filters/test_survival_aware_crp_stable_normalization.py @@ -1,7 +1,6 @@ import unittest import numpy.testing as npt - from pyrecest.filters.survival_aware_crp import ( SurvivalAwareCRPAssociationPrior, SurvivalAwareTrackEvidence, diff --git a/tests/filters/test_track_manager_structural_cost.py b/tests/filters/test_track_manager_structural_cost.py index e6b5a7b11..b4cae73d5 100644 --- a/tests/filters/test_track_manager_structural_cost.py +++ b/tests/filters/test_track_manager_structural_cost.py @@ -1,5 +1,4 @@ import numpy as np - from pyrecest.filters.track_manager import solve_global_nearest_neighbor diff --git a/tests/filters/test_update_diagnostics_metadata.py b/tests/filters/test_update_diagnostics_metadata.py index 05ed107e1..4b2bc0e37 100644 --- a/tests/filters/test_update_diagnostics_metadata.py +++ b/tests/filters/test_update_diagnostics_metadata.py @@ -1,5 +1,4 @@ import pytest - from pyrecest.filters.update_diagnostics import MeasurementUpdateDiagnostics diff --git a/tests/filters/test_update_diagnostics_skipped_reason.py b/tests/filters/test_update_diagnostics_skipped_reason.py index 48b8eee87..e25cf94b8 100644 --- a/tests/filters/test_update_diagnostics_skipped_reason.py +++ b/tests/filters/test_update_diagnostics_skipped_reason.py @@ -1,5 +1,4 @@ import pytest - from pyrecest.filters.update_diagnostics import MeasurementUpdateDiagnostics diff --git a/tests/filters/test_wrapped_normal_filter_constant_likelihood.py b/tests/filters/test_wrapped_normal_filter_constant_likelihood.py index dac5dbc0e..080a2ebc0 100644 --- a/tests/filters/test_wrapped_normal_filter_constant_likelihood.py +++ b/tests/filters/test_wrapped_normal_filter_constant_likelihood.py @@ -1,5 +1,4 @@ import numpy.testing as npt - from pyrecest.backend import array from pyrecest.distributions import WrappedNormalDistribution from pyrecest.filters.wrapped_normal_filter import WrappedNormalFilter diff --git a/tests/models/test_identity_gaussian_scalar_covariance.py b/tests/models/test_identity_gaussian_scalar_covariance.py index 579a36714..208bea37a 100644 --- a/tests/models/test_identity_gaussian_scalar_covariance.py +++ b/tests/models/test_identity_gaussian_scalar_covariance.py @@ -1,5 +1,4 @@ import numpy.testing as npt - from pyrecest.backend import eye, to_numpy from pyrecest.models import ( IdentityGaussianMeasurementModel, diff --git a/tests/models/test_linear_gaussian_complex_inputs.py b/tests/models/test_linear_gaussian_complex_inputs.py index cf707da75..497f6e087 100644 --- a/tests/models/test_linear_gaussian_complex_inputs.py +++ b/tests/models/test_linear_gaussian_complex_inputs.py @@ -31,9 +31,7 @@ def test_prediction_rejects_complex_state_inputs(self): with self.assertRaisesRegex(ValueError, "state_mean must be real-valued"): model.predict_mean(np.asarray([1.0 + 1.0j])) - with self.assertRaisesRegex( - ValueError, "state_covariance must be real-valued" - ): + with self.assertRaisesRegex(ValueError, "state_covariance must be real-valued"): model.predict_covariance(np.asarray([[1.0 + 1.0j]])) def test_measurement_model_rejects_complex_inputs(self): @@ -45,9 +43,7 @@ def test_measurement_model_rejects_complex_inputs(self): model = LinearGaussianMeasurementModel(real_matrix, real_matrix) with self.assertRaisesRegex(ValueError, "state_mean must be real-valued"): model.predict_mean(np.asarray([1.0 + 1.0j])) - with self.assertRaisesRegex( - ValueError, "state_covariance must be real-valued" - ): + with self.assertRaisesRegex(ValueError, "state_covariance must be real-valued"): model.innovation_covariance(np.asarray([[1.0 + 1.0j]])) diff --git a/tests/models/test_linear_gaussian_models.py b/tests/models/test_linear_gaussian_models.py index bd31a7089..85c628b6d 100644 --- a/tests/models/test_linear_gaussian_models.py +++ b/tests/models/test_linear_gaussian_models.py @@ -43,7 +43,9 @@ def test_transition_accepts_noise_covariance_keyword_alias(self): noise_cov = diag(array([0.1, 0.2])) model = LinearGaussianTransitionModel(eye(2), noise_covariance=noise_cov) - npt.assert_allclose(to_numpy(model.system_noise_cov), to_numpy(noise_cov), atol=1e-12) + npt.assert_allclose( + to_numpy(model.system_noise_cov), to_numpy(noise_cov), atol=1e-12 + ) def test_measurement_predict_distribution(self): model = LinearGaussianMeasurementModel(array([[1.0, 0.0]]), array([[0.25]])) @@ -71,18 +73,22 @@ def test_measurement_accepts_noise_covariance_keyword_alias(self): array([[1.0, 0.0]]), noise_covariance=noise_cov ) - npt.assert_allclose(to_numpy(model.measurement_noise_cov), to_numpy(noise_cov), atol=1e-12) + npt.assert_allclose( + to_numpy(model.measurement_noise_cov), to_numpy(noise_cov), atol=1e-12 + ) def test_noise_covariance_keyword_rejects_ambiguous_inputs(self): with self.assertRaisesRegex( - TypeError, "LinearGaussianTransitionModel got both noise_cov and noise_covariance" + TypeError, + "LinearGaussianTransitionModel got both noise_cov and noise_covariance", ): LinearGaussianTransitionModel( eye(1), array([[1.0]]), noise_covariance=array([[1.0]]) ) with self.assertRaisesRegex( - TypeError, "LinearGaussianMeasurementModel got both noise_cov and noise_covariance" + TypeError, + "LinearGaussianMeasurementModel got both noise_cov and noise_covariance", ): LinearGaussianMeasurementModel( eye(1), array([[1.0]]), noise_covariance=array([[1.0]]) diff --git a/tests/models/test_state_dim_inference_fallback.py b/tests/models/test_state_dim_inference_fallback.py index d64db13e2..ed52942f6 100644 --- a/tests/models/test_state_dim_inference_fallback.py +++ b/tests/models/test_state_dim_inference_fallback.py @@ -14,7 +14,9 @@ class DistributionWithFallbackMean: dim = UncoercibleDimension() mu = array([0.0, 1.0, 2.0]) - self.assertEqual(infer_state_dim_from_distribution(DistributionWithFallbackMean()), 3) + self.assertEqual( + infer_state_dim_from_distribution(DistributionWithFallbackMean()), 3 + ) if __name__ == "__main__": diff --git a/tests/models/test_transition_sample_count_unsupported.py b/tests/models/test_transition_sample_count_unsupported.py index f54ec7bbd..8c71ea6a1 100644 --- a/tests/models/test_transition_sample_count_unsupported.py +++ b/tests/models/test_transition_sample_count_unsupported.py @@ -12,7 +12,9 @@ class UnsupportedTransitionSampleCountTest(unittest.TestCase): - def test_sampleable_transition_rejects_count_when_sampler_has_no_count_argument(self): + def test_sampleable_transition_rejects_count_when_sampler_has_no_count_argument( + self, + ): calls = [] def sample_next(state): diff --git a/tests/models/test_validation.py b/tests/models/test_validation.py index 328d7722f..cff3260bf 100644 --- a/tests/models/test_validation.py +++ b/tests/models/test_validation.py @@ -1,7 +1,6 @@ import unittest import numpy as np - from pyrecest.backend import array, eye, zeros from pyrecest.models.validation import ( infer_state_dim_from_distribution, @@ -62,14 +61,26 @@ def test_validate_expected_dimensions_reject_temporal_values(self): for value in temporal_values: invalid_calls = [ - lambda value=value: validate_state_vector(array([1.0, 2.0]), state_dim=value), - lambda value=value: validate_measurement_vector(array([1.0, 2.0]), meas_dim=value), + lambda value=value: validate_state_vector( + array([1.0, 2.0]), state_dim=value + ), + lambda value=value: validate_measurement_vector( + array([1.0, 2.0]), meas_dim=value + ), lambda value=value: validate_covariance_matrix(eye(2), dim=value), lambda value=value: validate_noise_covariance(eye(2), dim=value), - lambda value=value: validate_transition_matrix(zeros((2, 2)), state_dim=value), - lambda value=value: validate_transition_matrix(zeros((2, 2)), pred_dim=value), - lambda value=value: validate_measurement_matrix(zeros((2, 2)), state_dim=value), - lambda value=value: validate_measurement_matrix(zeros((2, 2)), meas_dim=value), + lambda value=value: validate_transition_matrix( + zeros((2, 2)), state_dim=value + ), + lambda value=value: validate_transition_matrix( + zeros((2, 2)), pred_dim=value + ), + lambda value=value: validate_measurement_matrix( + zeros((2, 2)), state_dim=value + ), + lambda value=value: validate_measurement_matrix( + zeros((2, 2)), meas_dim=value + ), ] for call in invalid_calls: with self.subTest(value=value, call=call): @@ -109,8 +120,12 @@ def test_validate_vector_and_matrix_reject_temporal_arrays(self): lambda: validate_measurement_vector(timedelta_vector, meas_dim=2), lambda: validate_covariance_matrix(timedelta_matrix, dim=2), lambda: validate_noise_covariance(object_temporal_matrix, dim=1), - lambda: validate_transition_matrix(datetime_matrix, state_dim=2, pred_dim=1), - lambda: validate_measurement_matrix(timedelta_matrix, state_dim=2, meas_dim=2), + lambda: validate_transition_matrix( + datetime_matrix, state_dim=2, pred_dim=1 + ), + lambda: validate_measurement_matrix( + timedelta_matrix, state_dim=2, meas_dim=2 + ), ] for call in invalid_calls: diff --git a/tests/sampling/test_support_points_covariance_symmetry.py b/tests/sampling/test_support_points_covariance_symmetry.py index 72c042072..7f6a76fd3 100644 --- a/tests/sampling/test_support_points_covariance_symmetry.py +++ b/tests/sampling/test_support_points_covariance_symmetry.py @@ -2,7 +2,6 @@ import numpy as np import pytest - from pyrecest.sampling import ( ellipsoid_axis_offsets, ellipsoid_sigma_points, diff --git a/tests/smoothers/test_delayed_output.py b/tests/smoothers/test_delayed_output.py index 0c48555ce..c8d8bfac7 100644 --- a/tests/smoothers/test_delayed_output.py +++ b/tests/smoothers/test_delayed_output.py @@ -2,7 +2,6 @@ import numpy as np import pytest - from pyrecest.smoothers import DelayedStateOutputMixin diff --git a/tests/smoothers/test_hypertoroidal_information_smoothers.py b/tests/smoothers/test_hypertoroidal_information_smoothers.py index 6873fe72e..67f01a7b0 100644 --- a/tests/smoothers/test_hypertoroidal_information_smoothers.py +++ b/tests/smoothers/test_hypertoroidal_information_smoothers.py @@ -19,7 +19,6 @@ ) from pyrecest.smoothers.hypertoroidal_grid_smoother import HypertoroidalGridSmoother - N_COEFFICIENTS = 15 @@ -91,7 +90,9 @@ def test_identity_transition_collapses_to_product_of_all_likelihoods(self): self.assertEqual(len(smoothed), len(likelihoods)) self.assertEqual(len(backward_messages), len(likelihoods)) for smoothed_state in smoothed: - npt.assert_allclose(smoothed_state.coeff_mat, expected.coeff_mat, rtol=1e-10, atol=1e-12) + npt.assert_allclose( + smoothed_state.coeff_mat, expected.coeff_mat, rtol=1e-10, atol=1e-12 + ) @unittest.skipIf( pyrecest.backend.__backend_name__ in ("jax", "pytorch"), @@ -125,7 +126,9 @@ def test_single_step_sqrt_smoothing_returns_filtered_state(self): self.assertEqual(len(smoothed), 1) self.assertEqual(len(backward_messages), 1) - npt.assert_allclose(smoothed[0].pdf(xs), filtered_state.pdf(xs), rtol=1e-10, atol=1e-12) + npt.assert_allclose( + smoothed[0].pdf(xs), filtered_state.pdf(xs), rtol=1e-10, atol=1e-12 + ) @unittest.skipIf( pyrecest.backend.__backend_name__ in ("jax", "pytorch"), @@ -135,7 +138,9 @@ def test_likelihood_length_is_checked(self): likelihoods = [_cosine_identity_hfd(0.2, 0.1), _cosine_identity_hfd(0.3, 0.2)] filtered = _filtered_from_likelihoods(likelihoods) with self.assertRaises(ValueError): - HypertoroidalFourierSmoother().smooth_identity(filtered, likelihoods[:1], _delta_identity_hfd()) + HypertoroidalFourierSmoother().smooth_identity( + filtered, likelihoods[:1], _delta_identity_hfd() + ) def _grid_likelihood(amplitude, phase, n_grid_points=21): @@ -166,7 +171,9 @@ def _filtered_grid_from_likelihoods(likelihoods): def _identity_grid_transition(reference): n_points = reference.grid_values.shape[0] cell_volume = (2.0 * np.pi) / n_points - return TdCondTdGridDistribution(reference.get_grid(), np.eye(n_points) / cell_volume) + return TdCondTdGridDistribution( + reference.get_grid(), np.eye(n_points) / cell_volume + ) class TestHypertoroidalGridSmoother(unittest.TestCase): @@ -190,11 +197,15 @@ def test_identity_transition_collapses_to_product_of_all_likelihoods(self): self.assertEqual(len(smoothed), len(likelihoods)) self.assertEqual(len(backward_messages), len(likelihoods)) for smoothed_state in smoothed: - npt.assert_allclose(smoothed_state.grid_values, expected.grid_values, rtol=1e-12, atol=1e-12) + npt.assert_allclose( + smoothed_state.grid_values, expected.grid_values, rtol=1e-12, atol=1e-12 + ) def test_transition_length_is_checked(self): likelihoods = [_grid_likelihood(0.2, 0.1), _grid_likelihood(0.3, 0.2)] filtered = _filtered_grid_from_likelihoods(likelihoods) transition = _identity_grid_transition(filtered[0]) with self.assertRaises(ValueError): - HypertoroidalGridSmoother().smooth(filtered, likelihoods, [transition, transition]) + HypertoroidalGridSmoother().smooth( + filtered, likelihoods, [transition, transition] + ) diff --git a/tests/smoothers/test_so3_chordal_mean_nonfinite_weights.py b/tests/smoothers/test_so3_chordal_mean_nonfinite_weights.py index 504d20084..5493427b1 100644 --- a/tests/smoothers/test_so3_chordal_mean_nonfinite_weights.py +++ b/tests/smoothers/test_so3_chordal_mean_nonfinite_weights.py @@ -1,7 +1,6 @@ """Regression coverage for non-finite SO(3) chordal smoother weights.""" import pytest - from pyrecest.backend import eye from pyrecest.smoothers import SO3ChordalMeanSmoother diff --git a/tests/test_autograd_vmap_contract.py b/tests/test_autograd_vmap_contract.py index df2500941..82eeb68ad 100644 --- a/tests/test_autograd_vmap_contract.py +++ b/tests/test_autograd_vmap_contract.py @@ -1,5 +1,4 @@ import pytest - from tests.support.backend_runner import run_backend_code diff --git a/tests/test_axis_labels.py b/tests/test_axis_labels.py index a8eb0d644..d4c5ebbeb 100644 --- a/tests/test_axis_labels.py +++ b/tests/test_axis_labels.py @@ -37,9 +37,7 @@ def test_generic_manifold_axis_labels_are_preserved(manifold_name, expected_labe ("hypersphere_symmetric", "Angular error in radian"), ], ) -def test_axis_label_accepts_common_mathematical_notation( - manifold_name, expected_label -): +def test_axis_label_accepts_common_mathematical_notation(manifold_name, expected_label): assert get_axis_label(manifold_name) == expected_label diff --git a/tests/test_backend_set_diag_contract.py b/tests/test_backend_set_diag_contract.py index 1d5379af4..890d7e59a 100644 --- a/tests/test_backend_set_diag_contract.py +++ b/tests/test_backend_set_diag_contract.py @@ -1,6 +1,5 @@ -import pytest - import pyrecest.backend as backend +import pytest def _to_python(value): diff --git a/tests/test_backend_squeeze_axis_contract.py b/tests/test_backend_squeeze_axis_contract.py index f969bfeae..7808184bc 100644 --- a/tests/test_backend_squeeze_axis_contract.py +++ b/tests/test_backend_squeeze_axis_contract.py @@ -1,7 +1,6 @@ """Regression tests for JAX squeeze axis validation.""" import pytest - from tests.support.backend_runner import run_backend_code diff --git a/tests/test_benchmark_regression_limits.py b/tests/test_benchmark_regression_limits.py index ffe77c1bd..eae1f07e0 100644 --- a/tests/test_benchmark_regression_limits.py +++ b/tests/test_benchmark_regression_limits.py @@ -6,8 +6,14 @@ def _load_checker_module(): - module_path = Path(__file__).resolve().parents[1] / "scripts" / "check_benchmark_regression.py" - spec = importlib.util.spec_from_file_location("check_benchmark_regression", module_path) + module_path = ( + Path(__file__).resolve().parents[1] + / "scripts" + / "check_benchmark_regression.py" + ) + spec = importlib.util.spec_from_file_location( + "check_benchmark_regression", module_path + ) assert spec is not None assert spec.loader is not None module = importlib.util.module_from_spec(spec) @@ -28,8 +34,11 @@ def test_rejects_unbounded_cli_limits(): def test_accepts_default_cli_limits(): checker = _load_checker_module() - assert checker._validate_cli_limits( # pylint: disable=protected-access - max_slowdown=1.5, - abs_tol=1e-8, - rel_tol=1e-8, - ) == [] + assert ( + checker._validate_cli_limits( # pylint: disable=protected-access + max_slowdown=1.5, + abs_tol=1e-8, + rel_tol=1e-8, + ) + == [] + ) diff --git a/tests/test_candidate_pruning_configured_nan_costs.py b/tests/test_candidate_pruning_configured_nan_costs.py index 85b14534f..61e55d07c 100644 --- a/tests/test_candidate_pruning_configured_nan_costs.py +++ b/tests/test_candidate_pruning_configured_nan_costs.py @@ -1,6 +1,5 @@ import numpy as np import pytest - from pyrecest.utils import ( CandidatePruningConfig, candidate_mask_from_costs, diff --git a/tests/test_candidate_pruning_temporal_scalars.py b/tests/test_candidate_pruning_temporal_scalars.py index 0c70cb2f4..1af102e57 100644 --- a/tests/test_candidate_pruning_temporal_scalars.py +++ b/tests/test_candidate_pruning_temporal_scalars.py @@ -2,7 +2,6 @@ import unittest import numpy as np - from pyrecest.utils import CandidatePruningConfig, prune_pairwise_cost_matrix diff --git a/tests/test_cli_final_estimate_validation.py b/tests/test_cli_final_estimate_validation.py index 78ca6a526..3128a0816 100644 --- a/tests/test_cli_final_estimate_validation.py +++ b/tests/test_cli_final_estimate_validation.py @@ -4,7 +4,6 @@ from types import SimpleNamespace import pytest - from pyrecest.cli import _cmd_run_scenario @@ -46,7 +45,9 @@ def test_cmd_run_scenario_reports_malformed_expected_final_estimate( ) result = _cmd_run_scenario( - SimpleNamespace(config=tmp_path / "config.toml", expected=expected_path, tolerance=None) + SimpleNamespace( + config=tmp_path / "config.toml", expected=expected_path, tolerance=None + ) ) captured = capsys.readouterr() diff --git a/tests/test_common_reduction_axis_scalar.py b/tests/test_common_reduction_axis_scalar.py index 5be0adc2a..2ad9d4fd1 100644 --- a/tests/test_common_reduction_axis_scalar.py +++ b/tests/test_common_reduction_axis_scalar.py @@ -1,5 +1,4 @@ import numpy as np - from pyrecest._backend import _common diff --git a/tests/test_common_reduction_axis_validation.py b/tests/test_common_reduction_axis_validation.py index a4b3b9d78..b76512df7 100644 --- a/tests/test_common_reduction_axis_validation.py +++ b/tests/test_common_reduction_axis_validation.py @@ -1,7 +1,6 @@ import importlib.util import pytest - from pyrecest._backend import _common diff --git a/tests/test_common_size_axis_validation.py b/tests/test_common_size_axis_validation.py index 30b3dc624..861e9e5e9 100644 --- a/tests/test_common_size_axis_validation.py +++ b/tests/test_common_size_axis_validation.py @@ -1,6 +1,5 @@ import numpy as np import pytest - from pyrecest._backend import _common diff --git a/tests/test_cost_matrix_adjustment_diagnostics.py b/tests/test_cost_matrix_adjustment_diagnostics.py index 880aa8032..f9e853d2b 100644 --- a/tests/test_cost_matrix_adjustment_diagnostics.py +++ b/tests/test_cost_matrix_adjustment_diagnostics.py @@ -1,7 +1,6 @@ import numpy as np import numpy.testing as npt import pytest - from pyrecest.utils.cost_matrix_adjustments import ( CallableCostMatrixAdjustment, CostMatrixAdjustmentResult, diff --git a/tests/test_cost_matrix_adjustment_nan_validation.py b/tests/test_cost_matrix_adjustment_nan_validation.py index c7306681f..25699d340 100644 --- a/tests/test_cost_matrix_adjustment_nan_validation.py +++ b/tests/test_cost_matrix_adjustment_nan_validation.py @@ -1,7 +1,6 @@ import unittest import numpy as np - from pyrecest.utils.cost_matrix_adjustments import apply_cost_matrix_adjustment diff --git a/tests/test_cost_matrix_adjustment_temporal_validation.py b/tests/test_cost_matrix_adjustment_temporal_validation.py index 3d23a3593..d665fb9f6 100644 --- a/tests/test_cost_matrix_adjustment_temporal_validation.py +++ b/tests/test_cost_matrix_adjustment_temporal_validation.py @@ -2,13 +2,11 @@ import numpy as np import pytest - from pyrecest.utils.cost_matrix_adjustments import ( additive_cost_matrix_adjustment, apply_cost_matrix_adjustment, ) - TEMPORAL_MATRICES = ( np.array([[np.timedelta64(2, "ns")]]), np.array([[np.datetime64("1970-01-01T00:00:00.000000002")]]), @@ -26,7 +24,9 @@ def test_cost_matrix_adjustment_rejects_temporal_input_matrices(matrix) -> None: @pytest.mark.parametrize("matrix", TEMPORAL_MATRICES) -def test_additive_cost_matrix_adjustment_rejects_temporal_penalty_matrices(matrix) -> None: +def test_additive_cost_matrix_adjustment_rejects_temporal_penalty_matrices( + matrix, +) -> None: with pytest.raises(ValueError, match="real-valued numeric"): additive_cost_matrix_adjustment(matrix) diff --git a/tests/test_cost_matrix_adjustments.py b/tests/test_cost_matrix_adjustments.py index 86deed14d..e0687af80 100644 --- a/tests/test_cost_matrix_adjustments.py +++ b/tests/test_cost_matrix_adjustments.py @@ -2,7 +2,6 @@ import numpy as np import numpy.testing as npt - from pyrecest.utils.cost_matrix_adjustments import ( CallableCostMatrixAdjustment, CostMatrixAdjustmentResult, @@ -152,7 +151,9 @@ def test_numeric_validation(self): def test_named_adjustment_validation(self): with self.assertRaises(ValueError): - CallableCostMatrixAdjustment(name="", function=lambda matrix, metadata: matrix) + CallableCostMatrixAdjustment( + name="", function=lambda matrix, metadata: matrix + ) with self.assertRaises(ValueError): CallableCostMatrixAdjustment(name="bad", function=None) # type: ignore[arg-type] diff --git a/tests/test_eot_shape_sample_counts.py b/tests/test_eot_shape_sample_counts.py index 4ea829d9f..a72bf8391 100644 --- a/tests/test_eot_shape_sample_counts.py +++ b/tests/test_eot_shape_sample_counts.py @@ -1,12 +1,9 @@ import pytest - from pyrecest.evaluation.eot_shape_database import StarShapedPolygon def test_eot_shape_sampling_rejects_negative_counts(): - polygon = StarShapedPolygon( - [(-0.5, -0.5), (-0.5, 0.5), (0.5, 0.5), (0.5, -0.5)] - ) + polygon = StarShapedPolygon([(-0.5, -0.5), (-0.5, 0.5), (0.5, 0.5), (0.5, -0.5)]) with pytest.raises(ValueError, match="nonnegative integer"): polygon.sample_on_boundary(-1) diff --git a/tests/test_evaluate_for_file_measurement_counts.py b/tests/test_evaluate_for_file_measurement_counts.py index 23017478c..edc27de3b 100644 --- a/tests/test_evaluate_for_file_measurement_counts.py +++ b/tests/test_evaluate_for_file_measurement_counts.py @@ -51,7 +51,9 @@ def test_evaluate_for_file_counts_empty_1d_measurements_as_zero(tmp_path, monkey ) -def test_evaluate_for_file_counts_flat_object_measurement_timesteps(tmp_path, monkeypatch): +def test_evaluate_for_file_counts_flat_object_measurement_timesteps( + tmp_path, monkeypatch +): module = importlib.import_module("pyrecest.evaluation.evaluate_for_file") input_file = tmp_path / "flat_scenario.npy" measurements = np.empty(3, dtype=object) diff --git a/tests/test_evaluation_reload.py b/tests/test_evaluation_reload.py index 300e4187a..49e83194d 100644 --- a/tests/test_evaluation_reload.py +++ b/tests/test_evaluation_reload.py @@ -1,7 +1,6 @@ import importlib import pandas as pd - import pyrecest.evaluation as evaluation diff --git a/tests/test_evidence_bool_flag_conversion.py b/tests/test_evidence_bool_flag_conversion.py index 0eda79ebb..655950ba8 100644 --- a/tests/test_evidence_bool_flag_conversion.py +++ b/tests/test_evidence_bool_flag_conversion.py @@ -1,5 +1,4 @@ import pytest - from pyrecest.evidence import EvidenceComputationMode, resolve_evidence_computation_mode diff --git a/tests/test_evidence_metadata_validation.py b/tests/test_evidence_metadata_validation.py index eeea66646..8629e0a73 100644 --- a/tests/test_evidence_metadata_validation.py +++ b/tests/test_evidence_metadata_validation.py @@ -1,5 +1,4 @@ import pytest - from pyrecest.evidence import EvidenceComputationMode diff --git a/tests/test_evidence_mode_validation.py b/tests/test_evidence_mode_validation.py index a9697335b..17527b1a2 100644 --- a/tests/test_evidence_mode_validation.py +++ b/tests/test_evidence_mode_validation.py @@ -1,5 +1,4 @@ import pytest - from pyrecest.evidence import resolve_evidence_computation_mode diff --git a/tests/test_evidence_support_invalid_values.py b/tests/test_evidence_support_invalid_values.py index ff375473a..b4a11b5c6 100644 --- a/tests/test_evidence_support_invalid_values.py +++ b/tests/test_evidence_support_invalid_values.py @@ -1,5 +1,4 @@ import pytest - from pyrecest.diagnostics import EvidenceSupport diff --git a/tests/test_healpix_hopf_density_validation.py b/tests/test_healpix_hopf_density_validation.py index 9b3fe877f..94a9270a1 100644 --- a/tests/test_healpix_hopf_density_validation.py +++ b/tests/test_healpix_hopf_density_validation.py @@ -4,9 +4,8 @@ from types import ModuleType import numpy as np -import pytest - import pyrecest.backend +import pytest from pyrecest.sampling.hyperspherical_sampler import ( HealpixHopfSampler, SphericalFibonacciSampler, diff --git a/tests/test_history_recorder_unsigned_dtype.py b/tests/test_history_recorder_unsigned_dtype.py index 3e20dc9f4..7345dbbdc 100644 --- a/tests/test_history_recorder_unsigned_dtype.py +++ b/tests/test_history_recorder_unsigned_dtype.py @@ -1,6 +1,5 @@ import numpy as np import pytest - from pyrecest import backend from pyrecest.utils.history_recorder import HistoryRecorder @@ -31,4 +30,6 @@ def test_padded_history_still_rejects_unicode_arrays(): recorder = HistoryRecorder() with pytest.raises(TypeError, match="padded history values must be real numeric"): - recorder.register("bad", np.array(["1"], dtype=np.dtype("U1")), pad_with_nan=True) + recorder.register( + "bad", np.array(["1"], dtype=np.dtype("U1")), pad_with_nan=True + ) diff --git a/tests/test_jax_fft_scalar_array_axes.py b/tests/test_jax_fft_scalar_array_axes.py index 001113007..8a3a6f741 100644 --- a/tests/test_jax_fft_scalar_array_axes.py +++ b/tests/test_jax_fft_scalar_array_axes.py @@ -34,6 +34,11 @@ def test_jax_fft_rejects_non_singleton_length_arrays(): values = jnp.arange(4.0) - for n in (np.array([3, 4]), np.array([[3, 4]]), jnp.array([3, 4]), jnp.array([[3, 4]])): + for n in ( + np.array([3, 4]), + np.array([[3, 4]]), + jnp.array([3, 4]), + jnp.array([[3, 4]]), + ): with pytest.raises(TypeError): fft.rfft(values, n=n) diff --git a/tests/test_jax_linalg_matrix_power.py b/tests/test_jax_linalg_matrix_power.py index 66dfc82f9..8731dffb4 100644 --- a/tests/test_jax_linalg_matrix_power.py +++ b/tests/test_jax_linalg_matrix_power.py @@ -1,7 +1,6 @@ """Regression tests for JAX linalg static-argument normalization.""" import pytest - from tests.support.backend_runner import run_backend_code diff --git a/tests/test_metrics_order_validation.py b/tests/test_metrics_order_validation.py index 3c77c2526..d5049be0e 100644 --- a/tests/test_metrics_order_validation.py +++ b/tests/test_metrics_order_validation.py @@ -1,7 +1,6 @@ import unittest import numpy as np - from pyrecest.utils.metrics import gospa_distance, ospa_distance diff --git a/tests/test_model_comparison_infinite_margin.py b/tests/test_model_comparison_infinite_margin.py index 79efd4c7f..b568d0fac 100644 --- a/tests/test_model_comparison_infinite_margin.py +++ b/tests/test_model_comparison_infinite_margin.py @@ -1,6 +1,5 @@ import numpy as np import pandas as pd - from pyrecest.evaluation.model_comparison import ( classify_evidence_margin, evidence_margin_table, diff --git a/tests/test_multisession_scalar_cost_validation.py b/tests/test_multisession_scalar_cost_validation.py index 5e6f02558..0b8893570 100644 --- a/tests/test_multisession_scalar_cost_validation.py +++ b/tests/test_multisession_scalar_cost_validation.py @@ -23,13 +23,17 @@ def test_boolean_scalar_costs_are_rejected(self): for name, value in invalid_costs: with self.subTest(name=name): - with self.assertRaisesRegex(ValueError, f"{name} must be a finite scalar"): + with self.assertRaisesRegex( + ValueError, f"{name} must be a finite scalar" + ): solve_multisession_assignment( {}, session_sizes=[1], **{name: value}, ) - with self.assertRaisesRegex(ValueError, f"{name} must be a finite scalar"): + with self.assertRaisesRegex( + ValueError, f"{name} must be a finite scalar" + ): multisession_assignment_module.solve_multisession_assignment( {}, session_sizes=[1], diff --git a/tests/test_numerics.py b/tests/test_numerics.py index 5421256d8..cd8660cc5 100644 --- a/tests/test_numerics.py +++ b/tests/test_numerics.py @@ -187,7 +187,9 @@ def test_assert_covariance_matrix_validates_dimension_argument(): np.asarray(assert_covariance_matrix(matrix, dim=2)), matrix ) - with pytest.raises(DimensionMismatchError, match="covariance has dimension 2") as exc_info: + with pytest.raises( + DimensionMismatchError, match="covariance has dimension 2" + ) as exc_info: assert_covariance_matrix(matrix, dim=3) error = exc_info.value assert error.left_name == "covariance" diff --git a/tests/test_numerics_temporal_scalar_controls.py b/tests/test_numerics_temporal_scalar_controls.py index a4c3c046e..e3fdfc72c 100644 --- a/tests/test_numerics_temporal_scalar_controls.py +++ b/tests/test_numerics_temporal_scalar_controls.py @@ -1,6 +1,5 @@ import numpy as np import pytest - from pyrecest.numerics import ( assert_covariance_matrix, is_positive_semidefinite, diff --git a/tests/test_point_set_registration_numeric_validation.py b/tests/test_point_set_registration_numeric_validation.py index 025d2dbf3..e12c8579a 100644 --- a/tests/test_point_set_registration_numeric_validation.py +++ b/tests/test_point_set_registration_numeric_validation.py @@ -1,7 +1,6 @@ import unittest import numpy as np - import pyrecest.backend from pyrecest.backend import array from pyrecest.utils.point_set_registration import ( diff --git a/tests/test_public_api_registry_script.py b/tests/test_public_api_registry_script.py index f356bf904..e6f7a3cab 100644 --- a/tests/test_public_api_registry_script.py +++ b/tests/test_public_api_registry_script.py @@ -1,5 +1,4 @@ import pytest - from scripts import check_public_api_registry as registry_script diff --git a/tests/test_pytorch_close_missing_values.py b/tests/test_pytorch_close_missing_values.py index 07b9652f7..ff9a7af07 100644 --- a/tests/test_pytorch_close_missing_values.py +++ b/tests/test_pytorch_close_missing_values.py @@ -21,7 +21,9 @@ def test_isclose_accepts_equal_nan_for_raw_backend(self): left = pytorch_backend.array([1.0, float("nan"), 3.0]) right = pytorch_backend.array([1.0, float("nan"), 4.0]) - self.assertEqual(pytorch_backend.isclose(left, right).tolist(), [True, False, False]) + self.assertEqual( + pytorch_backend.isclose(left, right).tolist(), [True, False, False] + ) self.assertEqual( pytorch_backend.isclose(left, right, equal_nan=True).tolist(), [True, True, False], diff --git a/tests/test_pytorch_conj_array_like_contract.py b/tests/test_pytorch_conj_array_like_contract.py index c376f962b..87be13555 100644 --- a/tests/test_pytorch_conj_array_like_contract.py +++ b/tests/test_pytorch_conj_array_like_contract.py @@ -1,5 +1,4 @@ import pytest - from tests.support.backend_runner import run_backend_code diff --git a/tests/test_pytorch_cross_contract.py b/tests/test_pytorch_cross_contract.py index 9cc2ace8a..176f577eb 100644 --- a/tests/test_pytorch_cross_contract.py +++ b/tests/test_pytorch_cross_contract.py @@ -1,8 +1,7 @@ import numpy as np import numpy.testing as npt -import pytest - import pyrecest.backend as backend +import pytest @pytest.mark.skipif( diff --git a/tests/test_pytorch_isclose_device_contract.py b/tests/test_pytorch_isclose_device_contract.py index 0a75bf63d..d540856c7 100644 --- a/tests/test_pytorch_isclose_device_contract.py +++ b/tests/test_pytorch_isclose_device_contract.py @@ -1,5 +1,4 @@ import pytest - from tests.support.backend_runner import run_backend_code diff --git a/tests/test_pytorch_prod_aliases.py b/tests/test_pytorch_prod_aliases.py index c8dec5a75..92a710d5b 100644 --- a/tests/test_pytorch_prod_aliases.py +++ b/tests/test_pytorch_prod_aliases.py @@ -1,6 +1,5 @@ -import pytest - import pyrecest.backend as backend +import pytest from pyrecest.backend import array diff --git a/tests/test_scenario_input_validation.py b/tests/test_scenario_input_validation.py index 386160220..9ba8072da 100644 --- a/tests/test_scenario_input_validation.py +++ b/tests/test_scenario_input_validation.py @@ -1,5 +1,4 @@ import pytest - from pyrecest.scenarios import _to_float_list diff --git a/tests/test_shared_numpy_array_dtype_none.py b/tests/test_shared_numpy_array_dtype_none.py index a1089787d..334d841e3 100644 --- a/tests/test_shared_numpy_array_dtype_none.py +++ b/tests/test_shared_numpy_array_dtype_none.py @@ -1,7 +1,6 @@ import pytest from tests.support.backend_runner import run_backend_code - _ARRAY_DTYPE_NONE_CODE = """ import pyrecest.backend as backend diff --git a/tests/test_sigma_points_complex_inputs.py b/tests/test_sigma_points_complex_inputs.py index c6468b61b..c20213541 100644 --- a/tests/test_sigma_points_complex_inputs.py +++ b/tests/test_sigma_points_complex_inputs.py @@ -1,6 +1,5 @@ import numpy as np import pytest - from pyrecest.sampling import JulierSigmaPoints, MerweScaledSigmaPoints diff --git a/tests/test_sigma_points_covariance_validation.py b/tests/test_sigma_points_covariance_validation.py index cfeae3e1d..8c39a6780 100644 --- a/tests/test_sigma_points_covariance_validation.py +++ b/tests/test_sigma_points_covariance_validation.py @@ -1,6 +1,5 @@ import numpy as np import pytest - from pyrecest.sampling import JulierSigmaPoints, MerweScaledSigmaPoints diff --git a/tests/test_sylvester_shortcut_residual.py b/tests/test_sylvester_shortcut_residual.py index a4eda03ea..0ac1290c8 100644 --- a/tests/test_sylvester_shortcut_residual.py +++ b/tests/test_sylvester_shortcut_residual.py @@ -1,6 +1,5 @@ import numpy as np import numpy.testing as npt - from pyrecest._backend import numpy as numpy_backend diff --git a/tests/test_track_completion_text_candidate_validation.py b/tests/test_track_completion_text_candidate_validation.py index e774bb92c..d0c646fcb 100644 --- a/tests/test_track_completion_text_candidate_validation.py +++ b/tests/test_track_completion_text_candidate_validation.py @@ -18,7 +18,9 @@ def provider(*args): def _assert_candidate_rejected(candidate): try: enumerate_fragment_completion_paths( - [[0, None]], direction="suffix", candidate_provider=_provider_with(candidate) + [[0, None]], + direction="suffix", + candidate_provider=_provider_with(candidate), ) except ValueError as exc: assert "candidate observations must be non-negative integers" in str(exc) diff --git a/tests/test_track_latency_datetime_session_times.py b/tests/test_track_latency_datetime_session_times.py index 009b5f10a..356974afb 100644 --- a/tests/test_track_latency_datetime_session_times.py +++ b/tests/test_track_latency_datetime_session_times.py @@ -3,7 +3,6 @@ import unittest import numpy as np - from pyrecest.utils.track_metrics import score_track_latency, track_latencies diff --git a/tests/tracking/test_audit_guards.py b/tests/tracking/test_audit_guards.py index 93da58119..d40070ee0 100644 --- a/tests/tracking/test_audit_guards.py +++ b/tests/tracking/test_audit_guards.py @@ -1,7 +1,6 @@ from __future__ import annotations import pytest - from pyrecest.tracking import ( ForbiddenKeyAccessError, assert_selector_invariant_under_forbidden_key_changes, @@ -13,7 +12,6 @@ strip_forbidden_keys_from_mappings, ) - FORBIDDEN = {"audit_label", "audit_delta"} @@ -34,7 +32,10 @@ def test_strip_and_poison_forbidden_keys() -> None: row = {"candidate_id": "a", "score": 1.0, "audit_delta": 99.0} assert strip_forbidden_keys(row, FORBIDDEN) == {"candidate_id": "a", "score": 1.0} - assert poison_forbidden_keys(row, FORBIDDEN)["audit_delta"] == "__PYRECEST_FORBIDDEN_AUDIT_VALUE__" + assert ( + poison_forbidden_keys(row, FORBIDDEN)["audit_delta"] + == "__PYRECEST_FORBIDDEN_AUDIT_VALUE__" + ) def test_sequence_helpers_strip_poison_and_guard_rows() -> None: @@ -44,7 +45,9 @@ def test_sequence_helpers_strip_poison_and_guard_rows() -> None: ] stripped = strip_forbidden_keys_from_mappings(rows, FORBIDDEN) - poisoned = poison_forbidden_keys_in_mappings(rows, FORBIDDEN, poison_value="SENTINEL") + poisoned = poison_forbidden_keys_in_mappings( + rows, FORBIDDEN, poison_value="SENTINEL" + ) guarded = guarded_mappings(rows, FORBIDDEN) assert all("audit_delta" not in row for row in stripped) @@ -72,7 +75,9 @@ def test_selector_invariance_helper_catches_forbidden_feature_selector() -> None ] def selector(candidate_rows): - return max(candidate_rows, key=lambda row: row.get("audit_delta", 0.0))["candidate_id"] + return max(candidate_rows, key=lambda row: row.get("audit_delta", 0.0))[ + "candidate_id" + ] with pytest.raises((AssertionError, ForbiddenKeyAccessError)): assert_selector_invariant_under_forbidden_key_changes(selector, rows, FORBIDDEN) diff --git a/tests/tracking/test_hypothesis_replay.py b/tests/tracking/test_hypothesis_replay.py index db20bc8c2..500130454 100644 --- a/tests/tracking/test_hypothesis_replay.py +++ b/tests/tracking/test_hypothesis_replay.py @@ -108,7 +108,9 @@ def test_hypothesis_replay_rejects_text_count_fields() -> None: for field_name, value, message in invalid_cases: with pytest.raises(ValueError, match=message): - HypothesisReplay(hypothesis_id="bad-count", records=[], **{field_name: value}) + HypothesisReplay( + hypothesis_id="bad-count", records=[], **{field_name: value} + ) def test_rank_replayed_hypotheses_calls_replay_function() -> None: diff --git a/tests/tracking/test_innovation_diagnostics_temporal_validation.py b/tests/tracking/test_innovation_diagnostics_temporal_validation.py index 47d1a1ee1..22322cb7f 100644 --- a/tests/tracking/test_innovation_diagnostics_temporal_validation.py +++ b/tests/tracking/test_innovation_diagnostics_temporal_validation.py @@ -44,13 +44,9 @@ def test_diagnostic_from_record_treats_temporal_float_fields_as_missing() -> Non diagnostic = diagnostic_from_record( { "nis": np.timedelta64(2, "ns"), - "residual_norm_m": np.datetime64( - "1970-01-01T00:00:00.000000003" - ), + "residual_norm_m": np.datetime64("1970-01-01T00:00:00.000000003"), "gate_threshold": np.array(np.timedelta64(4, "ns"), dtype=object), - "time_s": np.asarray( - np.datetime64("1970-01-01T00:00:00.000000005") - ), + "time_s": np.asarray(np.datetime64("1970-01-01T00:00:00.000000005")), } ) diff --git a/tests/tracking/test_nonparametric_cardinality.py b/tests/tracking/test_nonparametric_cardinality.py index 397c90e9f..965752a3c 100644 --- a/tests/tracking/test_nonparametric_cardinality.py +++ b/tests/tracking/test_nonparametric_cardinality.py @@ -1,7 +1,6 @@ import math import pytest - from pyrecest.tracking.nonparametric_cardinality import ( DirichletProcessCardinalityPrior, PitmanYorCardinalityPrior, @@ -46,7 +45,9 @@ def test_zero_discount_pitman_yor_matches_dirichlet_process_cluster_count_pmf(): pitman_yor = PitmanYorCardinalityPrior(strength=1.7, discount=0.0) dirichlet = DirichletProcessCardinalityPrior(strength=1.7) - assert pitman_yor.cluster_count_pmf(6) == pytest.approx(dirichlet.cluster_count_pmf(6)) + assert pitman_yor.cluster_count_pmf(6) == pytest.approx( + dirichlet.cluster_count_pmf(6) + ) def test_pitman_yor_has_heavier_cluster_count_tail_than_matching_dirichlet_process(): diff --git a/tests/utils/test_assignment_murty_forced_prefix.py b/tests/utils/test_assignment_murty_forced_prefix.py index fa33edfb9..e705597ac 100644 --- a/tests/utils/test_assignment_murty_forced_prefix.py +++ b/tests/utils/test_assignment_murty_forced_prefix.py @@ -1,7 +1,6 @@ import numpy as np -import pytest - import pyrecest.backend as backend +import pytest from pyrecest.utils.assignment import murty_k_best_assignments diff --git a/tests/utils/test_logistic_association_scalar_prediction.py b/tests/utils/test_logistic_association_scalar_prediction.py index 0c1aaed38..6499cc41a 100644 --- a/tests/utils/test_logistic_association_scalar_prediction.py +++ b/tests/utils/test_logistic_association_scalar_prediction.py @@ -1,6 +1,5 @@ import numpy as np import pytest - from pyrecest.utils import LogisticPairwiseAssociationModel