gragra 0.2.0rc1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gragra/__init__.py +46 -0
- gragra/_analytic_evaluation.py +413 -0
- gragra/_arrays.py +194 -0
- gragra/_continuous_contract.py +427 -0
- gragra/_continuous_fourier.py +890 -0
- gragra/_continuous_rigid_motion.py +275 -0
- gragra/_evaluation_setup.py +680 -0
- gragra/_finite_window_fourier.py +627 -0
- gragra/_interactions.py +26 -0
- gragra/_periodic_fourier.py +522 -0
- gragra/_phasor_matrix.py +74 -0
- gragra/_plotting/__init__.py +1 -0
- gragra/_plotting/_appearance.py +38 -0
- gragra/_plotting/_contribution.py +363 -0
- gragra/_plotting/_diagnostics.py +662 -0
- gragra/_plotting/_dynamic_contribution.py +1281 -0
- gragra/_plotting/_field.py +704 -0
- gragra/_plotting/_formula_latex.py +59 -0
- gragra/_plotting/_hist.py +775 -0
- gragra/_plotting/_map.py +316 -0
- gragra/_plotting/_radial_histogram.py +1029 -0
- gragra/_plotting/_rayleigh_setup.py +567 -0
- gragra/_plotting/_result_field.py +670 -0
- gragra/_plotting/_setup.py +447 -0
- gragra/_plotting/_setup_shapes.py +308 -0
- gragra/_plotting/_source_contracts.py +287 -0
- gragra/_plotting/_source_data.py +478 -0
- gragra/_plotting/_source_field.py +1243 -0
- gragra/_plotting/_source_quantities.py +541 -0
- gragra/_rayleigh_contract.py +859 -0
- gragra/_rayleigh_evaluation_setup.py +214 -0
- gragra/_rayleigh_response_setup.py +151 -0
- gragra/_spectral_from_timeseries.py +136 -0
- gragra/_time_matrix.py +146 -0
- gragra/adapters/__init__.py +136 -0
- gragra/adapters/_gwexpy_series.py +210 -0
- gragra/adapters/field_series_hdf5.py +906 -0
- gragra/adapters/finite_body_mesh.py +153 -0
- gragra/adapters/gwexpy_adapter.py +243 -0
- gragra/adapters/gwexpy_density_series.py +69 -0
- gragra/adapters/gwexpy_displacement_series.py +35 -0
- gragra/adapters/gwexpy_observable.py +545 -0
- gragra/adapters/gwexpy_source.py +336 -0
- gragra/adapters/gwexpy_surface_density_series.py +42 -0
- gragra/adapters/hdf5_field.py +814 -0
- gragra/adapters/hex8_series.py +174 -0
- gragra/adapters/lebedev.py +59 -0
- gragra/adapters/mesh_quality.py +252 -0
- gragra/adapters/meshio_adapter.py +658 -0
- gragra/adapters/openfoam_density.py +531 -0
- gragra/adapters/specfem/__init__.py +55 -0
- gragra/adapters/specfem/__main__.py +8 -0
- gragra/adapters/specfem/_validation.py +501 -0
- gragra/adapters/specfem/cli.py +137 -0
- gragra/adapters/specfem/export.py +844 -0
- gragra/adapters/specfem/gll.py +569 -0
- gragra/adapters/specfem/provenance.py +939 -0
- gragra/adapters/specfem/reader.py +152 -0
- gragra/adapters/specfem/schema.py +1248 -0
- gragra/adapters/specfem/specfem_bin.py +970 -0
- gragra/adapters/specfem/streaming.py +412 -0
- gragra/adapters/specfem/writer.py +332 -0
- gragra/analytic/__init__.py +130 -0
- gragra/analytic/_bessel.py +376 -0
- gragra/analytic/_fullspace_bodywave.py +126 -0
- gragra/analytic/_u10_shell_series.py +240 -0
- gragra/analytic/_u9_spatial_derivatives.py +295 -0
- gragra/analytic/_validation.py +140 -0
- gragra/analytic/atmospheric/__init__.py +59 -0
- gragra/analytic/atmospheric/_acoustic.py +372 -0
- gragra/analytic/atmospheric/_cavity/__init__.py +1 -0
- gragra/analytic/atmospheric/_cavity/_cuboid.py +1674 -0
- gragra/analytic/atmospheric/_cavity/_cylinder.py +3236 -0
- gragra/analytic/atmospheric/_cavity/_model.py +760 -0
- gragra/analytic/atmospheric/_cavity/_numerics.py +1168 -0
- gragra/analytic/atmospheric/_cavity/_sphere_harmonics.py +177 -0
- gragra/analytic/atmospheric/_temperature.py +591 -0
- gragra/analytic/bodywave_literature.py +562 -0
- gragra/analytic/cavity_coupling.py +193 -0
- gragra/analytic/cavity_cuboid.py +488 -0
- gragra/analytic/cavity_cylinder.py +1345 -0
- gragra/analytic/cavity_sphere.py +848 -0
- gragra/analytic/cylinder_potential.py +215 -0
- gragra/analytic/cylinder_sca.py +47 -0
- gragra/analytic/halfspace.py +155 -0
- gragra/analytic/halfspace_psv.py +202 -0
- gragra/analytic/objects/__init__.py +44 -0
- gragra/analytic/objects/_cuboid_model.py +370 -0
- gragra/analytic/objects/_cylinder_model.py +287 -0
- gragra/analytic/objects/_gen1_motion.py +789 -0
- gragra/analytic/objects/_uniform_sphere.py +59 -0
- gragra/analytic/oriented.py +104 -0
- gragra/analytic/rayleigh_literature.py +367 -0
- gragra/analytic/sca_tensor.py +308 -0
- gragra/analytic/seismic/__init__.py +36 -0
- gragra/analytic/seismic/_gen1_point_force.py +422 -0
- gragra/analytic/seismic/_gen1_sca_wall.py +404 -0
- gragra/analytic/seismic/_halfspace_psv_model.py +629 -0
- gragra/analytic/seismic/_phase_speed.py +139 -0
- gragra/analytic/seismic/_rayleigh.py +73 -0
- gragra/analytic/seismic/_rayleigh_response.py +104 -0
- gragra/analytic/seismic/_spherical_cavity_model.py +572 -0
- gragra/analytic/seismic/_spherical_shell_model.py +563 -0
- gragra/analytic/spherical_cavity_series.py +343 -0
- gragra/analytic/stochastic_acoustic.py +375 -0
- gragra/analytic/stochastic_bodywave.py +201 -0
- gragra/analytic/stochastic_rayleigh.py +170 -0
- gragra/analytic/stochastic_temperature.py +221 -0
- gragra/analytic/u10_spherical_shell_general.py +308 -0
- gragra/analytic/u11_rayleigh_small_cavity.py +246 -0
- gragra/analytic/u9_sca_wall_observables.py +213 -0
- gragra/analytic/uniform_sphere.py +245 -0
- gragra/analytic/values.py +279 -0
- gragra/analytic/water.py +893 -0
- gragra/analytic/wave456_reference.py +1192 -0
- gragra/array_types.py +35 -0
- gragra/coherent/__init__.py +20 -0
- gragra/coherent/average.py +165 -0
- gragra/coherent/bulk_weights.py +156 -0
- gragra/coherent/cross.py +103 -0
- gragra/coherent/ensemble.py +110 -0
- gragra/coherent/weights.py +177 -0
- gragra/constants.py +5 -0
- gragra/evaluation.py +611 -0
- gragra/exceptions.py +184 -0
- gragra/fields/__init__.py +135 -0
- gragra/fields/_series.py +157 -0
- gragra/fields/_temperature.py +313 -0
- gragra/fields/background.py +52 -0
- gragra/fields/batch.py +285 -0
- gragra/fields/density_series.py +100 -0
- gragra/fields/dipole_diagnostics.py +288 -0
- gragra/fields/displacement_series.py +69 -0
- gragra/fields/evaluation.py +300 -0
- gragra/fields/finite_body.py +441 -0
- gragra/fields/finite_body_p1.py +595 -0
- gragra/fields/generic_integration.py +597 -0
- gragra/fields/hex8_geometry.py +198 -0
- gragra/fields/material_hex8.py +249 -0
- gragra/fields/regular_grid.py +364 -0
- gragra/fields/surface.py +247 -0
- gragra/fields/surface_density_series.py +83 -0
- gragra/fields/surface_orientation.py +354 -0
- gragra/fields/thermodynamic.py +115 -0
- gragra/fields/unstructured.py +396 -0
- gragra/geometry/__init__.py +62 -0
- gragra/geometry/_appearance.py +34 -0
- gragra/geometry/_cuboid.py +58 -0
- gragra/geometry/_cylinder.py +64 -0
- gragra/geometry/_frame.py +242 -0
- gragra/geometry/_grid.py +202 -0
- gragra/geometry/_halfspace.py +20 -0
- gragra/geometry/_sphere.py +59 -0
- gragra/geometry/_spherical_shell.py +64 -0
- gragra/geometry/primitives.py +981 -0
- gragra/geometry/surface_mesh.py +467 -0
- gragra/geometry/tetra_quadrature.py +155 -0
- gragra/gravity_sources/__init__.py +74 -0
- gragra/gravity_sources/_discretize.py +77 -0
- gragra/gravity_sources/_force_history.py +411 -0
- gragra/gravity_sources/_models.py +165 -0
- gragra/gravity_sources/_rigid_motion_model.py +505 -0
- gragra/gravity_sources/surface.py +158 -0
- gragra/gravity_sources/trajectory.py +313 -0
- gragra/gravity_sources/volume.py +107 -0
- gragra/gravity_sources/weighted_points.py +127 -0
- gragra/integrators/__init__.py +22 -0
- gragra/integrators/generic.py +1343 -0
- gragra/integrators/point_direct.py +393 -0
- gragra/integrators/rigid_motion.py +981 -0
- gragra/integrators/streaming.py +684 -0
- gragra/integrators/trajectory_direct.py +191 -0
- gragra/interactions/__init__.py +5 -0
- gragra/kernels/__init__.py +23 -0
- gragra/kernels/_collision.py +20 -0
- gragra/kernels/_cpp_inverse_square.py +184 -0
- gragra/kernels/_cpp_parametric.py +140 -0
- gragra/kernels/_cpp_planewave.py +55 -0
- gragra/kernels/_cpp_required.py +38 -0
- gragra/kernels/_cupy_inverse_square.py +263 -0
- gragra/kernels/_cupy_parametric.py +101 -0
- gragra/kernels/_cupy_planewave.py +127 -0
- gragra/kernels/_numba_inverse_square.py +796 -0
- gragra/kernels/_numba_parametric.py +366 -0
- gragra/kernels/_numba_planewave.py +57 -0
- gragra/kernels/_source_terms.py +137 -0
- gragra/kernels/generic_backend.py +1021 -0
- gragra/kernels/inverse_square.py +679 -0
- gragra/kernels/parametric.py +358 -0
- gragra/observables/__init__.py +171 -0
- gragra/observables/_source_terms.py +64 -0
- gragra/observables/acceleration.py +131 -0
- gragra/observables/analytic.py +818 -0
- gragra/observables/bodywave_literature.py +97 -0
- gragra/observables/coherent.py +274 -0
- gragra/observables/dipole.py +145 -0
- gragra/observables/gradient.py +60 -0
- gragra/observables/gravitoelastic.py +196 -0
- gragra/observables/halfspace_psv.py +39 -0
- gragra/observables/potential.py +57 -0
- gragra/observables/projection.py +102 -0
- gragra/observables/rayleigh_literature.py +50 -0
- gragra/observables/rigid_motion.py +253 -0
- gragra/observables/series.py +195 -0
- gragra/observables/stochastic_acoustic.py +91 -0
- gragra/observables/stochastic_bodywave.py +71 -0
- gragra/observables/stochastic_rayleigh.py +52 -0
- gragra/observables/stochastic_temperature.py +50 -0
- gragra/observables/streaming.py +115 -0
- gragra/observables/trajectory.py +190 -0
- gragra/observables/u10_spherical_shell_general.py +28 -0
- gragra/observables/u11_rayleigh_small_cavity.py +21 -0
- gragra/observables/u9_sca_wall_observables.py +29 -0
- gragra/observables/water.py +458 -0
- gragra/observables/wave456_reference.py +86 -0
- gragra/plotting/__init__.py +119 -0
- gragra/py.typed +1 -0
- gragra/reference/__init__.py +23 -0
- gragra/reference/_cavern.py +130 -0
- gragra/reference/_context.py +69 -0
- gragra/reference/_types.py +49 -0
- gragra/research/__init__.py +1 -0
- gragra/research/rayleigh_timefit.py +55 -0
- gragra/results/__init__.py +150 -0
- gragra/results/_binding.py +70 -0
- gragra/results/_comparison.py +655 -0
- gragra/results/_context.py +301 -0
- gragra/results/_coords.py +92 -0
- gragra/results/_evaluation.py +574 -0
- gragra/results/_field_definition.py +213 -0
- gragra/results/_gen1_definitions.py +142 -0
- gragra/results/_protocols.py +45 -0
- gragra/results/_record.py +65 -0
- gragra/results/_representation.py +18 -0
- gragra/results/_response.py +589 -0
- gragra/results/_spectral.py +163 -0
- gragra/results/_time.py +64 -0
- gragra/results/_time_grid.py +149 -0
- gragra/targets/__init__.py +19 -0
- gragra/targets/_high_points.py +71 -0
- gragra/targets/points.py +45 -0
- gragra/waves/__init__.py +61 -0
- gragra/waves/directions.py +115 -0
- gragra/waves/displacement.py +112 -0
- gragra/waves/halfspace.py +53 -0
- gragra/waves/phase.py +154 -0
- gragra/waves/plane.py +331 -0
- gragra/waves/polarization.py +198 -0
- gragra/waves/rayleigh.py +352 -0
- gragra/waves/stochastic.py +345 -0
- gragra-0.2.0rc1.dist-info/METADATA +98 -0
- gragra-0.2.0rc1.dist-info/RECORD +254 -0
- gragra-0.2.0rc1.dist-info/WHEEL +5 -0
- gragra-0.2.0rc1.dist-info/licenses/LICENSE +21 -0
gragra/__init__.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""gragra: A Python package for gravitational perturbation calculation."""
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from gragra.constants import G_SI
|
|
6
|
+
from gragra.gravity_sources import PointMass, PointMassCloud, WeightedPointSource
|
|
7
|
+
from gragra.observables import (
|
|
8
|
+
coherent_acceleration_csd,
|
|
9
|
+
coherent_gravitoelastic_tensor,
|
|
10
|
+
coherent_point_acceleration,
|
|
11
|
+
coherent_point_gravity_gradient,
|
|
12
|
+
displacement_field_acceleration,
|
|
13
|
+
displacement_transfer_acceleration,
|
|
14
|
+
point_acceleration,
|
|
15
|
+
point_gravity_gradient,
|
|
16
|
+
point_potential,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"G_SI",
|
|
21
|
+
"WeightedPointSource",
|
|
22
|
+
"PointMass",
|
|
23
|
+
"PointMassCloud",
|
|
24
|
+
"TargetPoints",
|
|
25
|
+
"point_potential",
|
|
26
|
+
"point_acceleration",
|
|
27
|
+
"point_gravity_gradient",
|
|
28
|
+
"coherent_point_acceleration",
|
|
29
|
+
"coherent_point_gravity_gradient",
|
|
30
|
+
"coherent_gravitoelastic_tensor",
|
|
31
|
+
"coherent_acceleration_csd",
|
|
32
|
+
"displacement_transfer_acceleration",
|
|
33
|
+
"displacement_field_acceleration",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
if TYPE_CHECKING:
|
|
37
|
+
from gragra.targets._high_points import TargetPoints
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def __getattr__(name: str):
|
|
41
|
+
"""Load high-level target types only when callers request them."""
|
|
42
|
+
if name == "TargetPoints":
|
|
43
|
+
from gragra.targets._high_points import TargetPoints
|
|
44
|
+
|
|
45
|
+
return TargetPoints
|
|
46
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
"""Shared evaluation bridge for analytic model families."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable, Mapping
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from importlib.metadata import version
|
|
8
|
+
from platform import python_version
|
|
9
|
+
from types import MappingProxyType
|
|
10
|
+
from typing import TypeAlias
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
import pandas as pd
|
|
14
|
+
from astropy import units as u
|
|
15
|
+
from astropy.table import QTable
|
|
16
|
+
from gwexpy.frequencyseries import FrequencySeriesMatrix
|
|
17
|
+
|
|
18
|
+
from gragra._interactions import Newtonian
|
|
19
|
+
from gragra._time_matrix import make_time_matrix
|
|
20
|
+
from gragra.constants import G_SI
|
|
21
|
+
from gragra.exceptions import ConvergenceError, NumericalDiagnostic
|
|
22
|
+
from gragra.geometry._frame import Frame
|
|
23
|
+
from gragra.results import GravityResult
|
|
24
|
+
from gragra.results._context import EvaluationContext
|
|
25
|
+
from gragra.results._evaluation import EvaluationResult
|
|
26
|
+
from gragra.results._record import OBSERVABLES, EvaluationRecord
|
|
27
|
+
from gragra.results._representation import (
|
|
28
|
+
StaticRepresentation,
|
|
29
|
+
TimeSeriesRepresentation,
|
|
30
|
+
)
|
|
31
|
+
from gragra.results._spectral import PhasorRepresentation
|
|
32
|
+
|
|
33
|
+
NativeValues: TypeAlias = Mapping[str, object]
|
|
34
|
+
|
|
35
|
+
_UNITS = {
|
|
36
|
+
"potential": u.m**2 / u.s**2,
|
|
37
|
+
"acceleration": u.m / u.s**2,
|
|
38
|
+
"gradient": u.s**-2,
|
|
39
|
+
}
|
|
40
|
+
_COMPONENTS = {
|
|
41
|
+
"potential": ("potential",),
|
|
42
|
+
"acceleration": ("x", "y", "z"),
|
|
43
|
+
"gradient": ("xx", "xy", "xz", "yx", "yy", "yz", "zx", "zy", "zz"),
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass(frozen=True, slots=True, init=False)
|
|
48
|
+
class NativeEvaluation:
|
|
49
|
+
"""G-free family output before common frame transforms and SI packaging."""
|
|
50
|
+
|
|
51
|
+
values: NativeValues
|
|
52
|
+
diagnostics: tuple[NumericalDiagnostic, ...]
|
|
53
|
+
|
|
54
|
+
def __init__(
|
|
55
|
+
self,
|
|
56
|
+
values: Mapping[str, object],
|
|
57
|
+
diagnostics: tuple[NumericalDiagnostic, ...] = (),
|
|
58
|
+
) -> None:
|
|
59
|
+
if not isinstance(values, Mapping):
|
|
60
|
+
raise TypeError("native values must be an observable mapping")
|
|
61
|
+
snapshot = dict(values)
|
|
62
|
+
if any(
|
|
63
|
+
not isinstance(name, str) or name not in OBSERVABLES for name in snapshot
|
|
64
|
+
):
|
|
65
|
+
raise ValueError("native values contain an unknown observable")
|
|
66
|
+
if isinstance(diagnostics, (str, bytes)):
|
|
67
|
+
raise TypeError("diagnostics must be an iterable of NumericalDiagnostic")
|
|
68
|
+
try:
|
|
69
|
+
diagnostic_snapshot = tuple(diagnostics)
|
|
70
|
+
except TypeError as exc:
|
|
71
|
+
raise TypeError(
|
|
72
|
+
"diagnostics must be an iterable of NumericalDiagnostic"
|
|
73
|
+
) from exc
|
|
74
|
+
if any(
|
|
75
|
+
not isinstance(item, NumericalDiagnostic) for item in diagnostic_snapshot
|
|
76
|
+
):
|
|
77
|
+
raise TypeError("diagnostics must contain NumericalDiagnostic values")
|
|
78
|
+
object.__setattr__(self, "values", MappingProxyType(snapshot))
|
|
79
|
+
object.__setattr__(self, "diagnostics", diagnostic_snapshot)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
NativeCallback: TypeAlias = Callable[[np.ndarray, tuple[str, ...]], NativeEvaluation]
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclass(frozen=True, eq=False, slots=True, init=False)
|
|
86
|
+
class AnalyticEvaluation:
|
|
87
|
+
"""Shared static, phasor, and time-series evaluation adapter.
|
|
88
|
+
|
|
89
|
+
Family callbacks return G-free arrays. This adapter owns validation of the
|
|
90
|
+
shared context, component transforms, one application of ``G_SI``, and
|
|
91
|
+
native result construction. Family-specific physics and target rejection
|
|
92
|
+
stay in the model callback and optional validator.
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
context: EvaluationContext
|
|
96
|
+
computation_frame: Frame
|
|
97
|
+
_target_positions_bytes: bytes
|
|
98
|
+
_target_count: int
|
|
99
|
+
evaluate_native: NativeCallback
|
|
100
|
+
validate_targets: Callable[[np.ndarray], object] | None
|
|
101
|
+
|
|
102
|
+
def __init__(
|
|
103
|
+
self,
|
|
104
|
+
*,
|
|
105
|
+
context: EvaluationContext,
|
|
106
|
+
computation_frame: Frame,
|
|
107
|
+
target_positions_m: object,
|
|
108
|
+
evaluate_native: NativeCallback,
|
|
109
|
+
validate_targets: Callable[[np.ndarray], object] | None = None,
|
|
110
|
+
) -> None:
|
|
111
|
+
if not isinstance(context, EvaluationContext):
|
|
112
|
+
raise TypeError("context must be an EvaluationContext")
|
|
113
|
+
if not isinstance(getattr(context.model_spec, "interaction", None), Newtonian):
|
|
114
|
+
raise TypeError("analytic evaluation requires a Newtonian interaction")
|
|
115
|
+
if not isinstance(computation_frame, Frame):
|
|
116
|
+
raise TypeError("computation_frame must be a Frame")
|
|
117
|
+
if not callable(evaluate_native):
|
|
118
|
+
raise TypeError("evaluate_native must be callable")
|
|
119
|
+
if validate_targets is not None and not callable(validate_targets):
|
|
120
|
+
raise TypeError("validate_targets must be callable or None")
|
|
121
|
+
context.targets.frame._path_to(computation_frame)
|
|
122
|
+
computation_frame._path_to(context.frame)
|
|
123
|
+
|
|
124
|
+
raw = np.asarray(target_positions_m)
|
|
125
|
+
if np.iscomplexobj(raw):
|
|
126
|
+
raise ValueError("target_positions_m must contain only real coordinates")
|
|
127
|
+
if np.issubdtype(raw.dtype, np.bool_):
|
|
128
|
+
raise TypeError("target_positions_m must not contain bool values")
|
|
129
|
+
try:
|
|
130
|
+
positions = np.asarray(raw, dtype=np.float64)
|
|
131
|
+
except (TypeError, ValueError, OverflowError) as exc:
|
|
132
|
+
raise TypeError("target_positions_m must contain real coordinates") from exc
|
|
133
|
+
expected_shape = (len(context.targets), 3)
|
|
134
|
+
if positions.shape != expected_shape:
|
|
135
|
+
raise ValueError(
|
|
136
|
+
"target_positions_m must have shape "
|
|
137
|
+
f"{expected_shape}, got {positions.shape}"
|
|
138
|
+
)
|
|
139
|
+
if not np.isfinite(positions).all():
|
|
140
|
+
raise ValueError("target_positions_m must contain only finite coordinates")
|
|
141
|
+
|
|
142
|
+
object.__setattr__(self, "context", context)
|
|
143
|
+
object.__setattr__(self, "computation_frame", computation_frame)
|
|
144
|
+
object.__setattr__(self, "_target_positions_bytes", positions.copy().tobytes())
|
|
145
|
+
object.__setattr__(self, "_target_count", len(positions))
|
|
146
|
+
object.__setattr__(self, "evaluate_native", evaluate_native)
|
|
147
|
+
object.__setattr__(self, "validate_targets", validate_targets)
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def target_positions_m(self) -> np.ndarray:
|
|
151
|
+
"""Read-only positions in the numerical kernel's computation frame."""
|
|
152
|
+
return np.frombuffer(self._target_positions_bytes, dtype=np.float64).reshape(
|
|
153
|
+
self._target_count, 3
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def model_spec(self):
|
|
158
|
+
return self.context.model_spec
|
|
159
|
+
|
|
160
|
+
@property
|
|
161
|
+
def parameters(self) -> QTable:
|
|
162
|
+
value = getattr(self.model_spec, "parameters", None)
|
|
163
|
+
return QTable() if value is None else value
|
|
164
|
+
|
|
165
|
+
@property
|
|
166
|
+
def formulas(self) -> pd.DataFrame:
|
|
167
|
+
value = getattr(self.model_spec, "formulas", None)
|
|
168
|
+
return pd.DataFrame() if value is None else value
|
|
169
|
+
|
|
170
|
+
@property
|
|
171
|
+
def references(self) -> pd.DataFrame:
|
|
172
|
+
value = getattr(self.model_spec, "references", None)
|
|
173
|
+
return pd.DataFrame() if value is None else value
|
|
174
|
+
|
|
175
|
+
def validate(self) -> None:
|
|
176
|
+
"""Recheck shared interpretation and then run family target checks."""
|
|
177
|
+
self.context.validate()
|
|
178
|
+
if not isinstance(
|
|
179
|
+
getattr(self.context.model_spec, "interaction", None), Newtonian
|
|
180
|
+
):
|
|
181
|
+
raise TypeError("analytic evaluation requires a Newtonian interaction")
|
|
182
|
+
positions = self.target_positions_m
|
|
183
|
+
if (
|
|
184
|
+
positions.shape != (len(self.context.targets), 3)
|
|
185
|
+
or not np.isfinite(positions).all()
|
|
186
|
+
):
|
|
187
|
+
raise ValueError("analytic evaluation target coordinates are invalid")
|
|
188
|
+
self.context.targets.frame._path_to(self.computation_frame)
|
|
189
|
+
self.computation_frame._path_to(self.context.frame)
|
|
190
|
+
if self.validate_targets is not None:
|
|
191
|
+
self.validate_targets(positions)
|
|
192
|
+
|
|
193
|
+
def run(self) -> EvaluationResult:
|
|
194
|
+
"""Evaluate once and return all requested observables or raise."""
|
|
195
|
+
self.validate()
|
|
196
|
+
try:
|
|
197
|
+
native = self.evaluate_native(
|
|
198
|
+
self.target_positions_m, self.context.observables
|
|
199
|
+
)
|
|
200
|
+
except Exception as cause:
|
|
201
|
+
raise self._evaluation_error(cause) from cause
|
|
202
|
+
if not isinstance(native, NativeEvaluation):
|
|
203
|
+
raise TypeError("evaluate_native must return NativeEvaluation")
|
|
204
|
+
self._validate_diagnostics(native.diagnostics)
|
|
205
|
+
if any(
|
|
206
|
+
diagnostic.termination in {"resource_limit", "uncertainty_unresolved"}
|
|
207
|
+
for diagnostic in native.diagnostics
|
|
208
|
+
):
|
|
209
|
+
raise ValueError(
|
|
210
|
+
"successful evaluations cannot contain failed numerical diagnostics"
|
|
211
|
+
)
|
|
212
|
+
arrays = self._validate_native_values(native.values)
|
|
213
|
+
arrays = self._rotate_and_apply_g(arrays)
|
|
214
|
+
values = self._package_values(arrays)
|
|
215
|
+
return EvaluationResult(
|
|
216
|
+
values=GravityResult(**values),
|
|
217
|
+
evaluation=self,
|
|
218
|
+
record=self._record(native.diagnostics),
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
def _evaluation_error(self, cause: Exception):
|
|
222
|
+
from gragra.evaluation import EvaluationError
|
|
223
|
+
|
|
224
|
+
target_index = 0 if self._target_count else None
|
|
225
|
+
observable = self.context.observables[0]
|
|
226
|
+
if isinstance(cause, ConvergenceError):
|
|
227
|
+
try:
|
|
228
|
+
self._validate_diagnostics(cause.diagnostics)
|
|
229
|
+
except (TypeError, ValueError):
|
|
230
|
+
pass
|
|
231
|
+
else:
|
|
232
|
+
failure = next(
|
|
233
|
+
diagnostic
|
|
234
|
+
for diagnostic in cause.diagnostics
|
|
235
|
+
if diagnostic.termination
|
|
236
|
+
in {"resource_limit", "uncertainty_unresolved"}
|
|
237
|
+
)
|
|
238
|
+
target_index = failure.target_index
|
|
239
|
+
observable = failure.observable
|
|
240
|
+
target_id = (
|
|
241
|
+
None if target_index is None else self.context.targets.ids[target_index]
|
|
242
|
+
)
|
|
243
|
+
return EvaluationError(
|
|
244
|
+
target_id=target_id,
|
|
245
|
+
source_index=None,
|
|
246
|
+
observables=self.context.observables,
|
|
247
|
+
observable=observable,
|
|
248
|
+
cause=cause,
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
def _validate_diagnostics(
|
|
252
|
+
self, diagnostics: tuple[NumericalDiagnostic, ...]
|
|
253
|
+
) -> None:
|
|
254
|
+
for diagnostic in diagnostics:
|
|
255
|
+
index = diagnostic.target_index
|
|
256
|
+
if index >= len(self.context.targets):
|
|
257
|
+
raise ValueError("diagnostic target_index is outside context targets")
|
|
258
|
+
expected_id = self.context.targets.ids[index]
|
|
259
|
+
if diagnostic.target_id != expected_id:
|
|
260
|
+
raise ValueError(
|
|
261
|
+
"diagnostic target_id must match context target ID at target_index"
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
def _validate_native_values(
|
|
265
|
+
self, values: Mapping[str, object]
|
|
266
|
+
) -> dict[str, np.ndarray]:
|
|
267
|
+
names = self.context.observables
|
|
268
|
+
if set(values) != set(names):
|
|
269
|
+
raise ValueError("native values must match requested observables exactly")
|
|
270
|
+
representation = self.context.representation
|
|
271
|
+
target_count = len(self.context.targets)
|
|
272
|
+
if isinstance(representation, StaticRepresentation):
|
|
273
|
+
axis_count = None
|
|
274
|
+
elif isinstance(representation, PhasorRepresentation):
|
|
275
|
+
axis_count = len(self.context.frequencies_hz or ())
|
|
276
|
+
elif isinstance(representation, TimeSeriesRepresentation):
|
|
277
|
+
sampling = self.context.time_sampling
|
|
278
|
+
if sampling is None:
|
|
279
|
+
raise ValueError("time_series context requires TimeSampling")
|
|
280
|
+
axis_count = sampling.count
|
|
281
|
+
else:
|
|
282
|
+
raise TypeError("analytic representation is unsupported")
|
|
283
|
+
|
|
284
|
+
arrays: dict[str, np.ndarray] = {}
|
|
285
|
+
for name in names:
|
|
286
|
+
raw = np.asarray(values[name])
|
|
287
|
+
if not np.issubdtype(raw.dtype, np.number):
|
|
288
|
+
raise TypeError(f"native {name} values must be numeric")
|
|
289
|
+
if isinstance(representation, StaticRepresentation):
|
|
290
|
+
expected_shape = {
|
|
291
|
+
"potential": (target_count,),
|
|
292
|
+
"acceleration": (target_count, 3),
|
|
293
|
+
"gradient": (target_count, 3, 3),
|
|
294
|
+
}[name]
|
|
295
|
+
complex_required = False
|
|
296
|
+
else:
|
|
297
|
+
expected_shape = (
|
|
298
|
+
target_count,
|
|
299
|
+
len(_COMPONENTS[name]),
|
|
300
|
+
axis_count,
|
|
301
|
+
)
|
|
302
|
+
complex_required = isinstance(representation, PhasorRepresentation)
|
|
303
|
+
if raw.shape != expected_shape:
|
|
304
|
+
raise ValueError(
|
|
305
|
+
f"native {name} values must have shape {expected_shape}, "
|
|
306
|
+
f"got {raw.shape}"
|
|
307
|
+
)
|
|
308
|
+
if complex_required and not np.iscomplexobj(raw):
|
|
309
|
+
raise ValueError(f"native phasor {name} values must be complex")
|
|
310
|
+
if not complex_required and np.iscomplexobj(raw):
|
|
311
|
+
raise ValueError(f"native {name} values must be real")
|
|
312
|
+
dtype = np.complex128 if complex_required else np.float64
|
|
313
|
+
try:
|
|
314
|
+
array = np.asarray(raw, dtype=dtype)
|
|
315
|
+
except (TypeError, ValueError, OverflowError) as exc:
|
|
316
|
+
raise ValueError(f"native {name} values cannot be represented") from exc
|
|
317
|
+
if not np.isfinite(array).all():
|
|
318
|
+
raise ValueError(f"native {name} values must be finite")
|
|
319
|
+
arrays[name] = array
|
|
320
|
+
return arrays
|
|
321
|
+
|
|
322
|
+
def _rotate_and_apply_g(
|
|
323
|
+
self, arrays: Mapping[str, np.ndarray]
|
|
324
|
+
) -> dict[str, np.ndarray]:
|
|
325
|
+
rotation = self.computation_frame.rotation_to(self.context.frame)
|
|
326
|
+
representation = self.context.representation
|
|
327
|
+
result: dict[str, np.ndarray] = {}
|
|
328
|
+
for name, values in arrays.items():
|
|
329
|
+
with np.errstate(over="ignore", invalid="ignore"):
|
|
330
|
+
if name == "potential":
|
|
331
|
+
output = G_SI * values
|
|
332
|
+
elif isinstance(representation, StaticRepresentation):
|
|
333
|
+
if name == "acceleration":
|
|
334
|
+
output = G_SI * np.einsum("ij,nj->ni", rotation, values)
|
|
335
|
+
else:
|
|
336
|
+
output = G_SI * np.einsum(
|
|
337
|
+
"ia,nab,jb->nij", rotation, values, rotation
|
|
338
|
+
)
|
|
339
|
+
elif name == "acceleration":
|
|
340
|
+
output = G_SI * np.einsum("ij,njt->nit", rotation, values)
|
|
341
|
+
else:
|
|
342
|
+
count, _, axis_count = values.shape
|
|
343
|
+
tensors = values.reshape(count, 3, 3, axis_count)
|
|
344
|
+
output = G_SI * np.einsum(
|
|
345
|
+
"ia,nabt,jb->nijt", rotation, tensors, rotation
|
|
346
|
+
).reshape(count, 9, axis_count)
|
|
347
|
+
if not np.isfinite(output).all():
|
|
348
|
+
raise ValueError(f"SI {name} values became non-finite")
|
|
349
|
+
result[name] = output
|
|
350
|
+
return result
|
|
351
|
+
|
|
352
|
+
def _package_values(self, arrays: Mapping[str, np.ndarray]) -> dict[str, object]:
|
|
353
|
+
representation = self.context.representation
|
|
354
|
+
if isinstance(representation, StaticRepresentation):
|
|
355
|
+
return {
|
|
356
|
+
name: arrays[name] * _UNITS[name] for name in self.context.observables
|
|
357
|
+
}
|
|
358
|
+
if isinstance(representation, PhasorRepresentation):
|
|
359
|
+
matrices = {}
|
|
360
|
+
frequencies = (
|
|
361
|
+
np.asarray(self.context.frequencies_hz, dtype=np.float64) * u.Hz
|
|
362
|
+
)
|
|
363
|
+
target_ids = self.context.targets.ids
|
|
364
|
+
for name in self.context.observables:
|
|
365
|
+
raw = arrays[name]
|
|
366
|
+
components = _COMPONENTS[name]
|
|
367
|
+
matrices[name] = FrequencySeriesMatrix(
|
|
368
|
+
raw.copy(),
|
|
369
|
+
frequencies=frequencies,
|
|
370
|
+
units=np.full(
|
|
371
|
+
(len(target_ids), len(components)), _UNITS[name], dtype=object
|
|
372
|
+
),
|
|
373
|
+
rows=list(target_ids),
|
|
374
|
+
cols=list(components),
|
|
375
|
+
name=f"Gen1 {name}",
|
|
376
|
+
)
|
|
377
|
+
return matrices
|
|
378
|
+
if isinstance(representation, TimeSeriesRepresentation):
|
|
379
|
+
sampling = self.context.time_sampling
|
|
380
|
+
if sampling is None:
|
|
381
|
+
raise ValueError("time_series context requires TimeSampling")
|
|
382
|
+
return {
|
|
383
|
+
name: make_time_matrix(
|
|
384
|
+
arrays[name],
|
|
385
|
+
sampling=sampling,
|
|
386
|
+
unit=_UNITS[name],
|
|
387
|
+
rows=self.context.targets.ids,
|
|
388
|
+
cols=_COMPONENTS[name],
|
|
389
|
+
name=f"Gen1 {name}",
|
|
390
|
+
)
|
|
391
|
+
for name in self.context.observables
|
|
392
|
+
}
|
|
393
|
+
raise TypeError("analytic representation is unsupported")
|
|
394
|
+
|
|
395
|
+
@staticmethod
|
|
396
|
+
def _record(diagnostics: tuple[NumericalDiagnostic, ...]) -> EvaluationRecord:
|
|
397
|
+
return EvaluationRecord(
|
|
398
|
+
backend="gen1_analytic_native",
|
|
399
|
+
versions={
|
|
400
|
+
"gragra": version("gragra"),
|
|
401
|
+
"python": python_version(),
|
|
402
|
+
"numpy": np.__version__,
|
|
403
|
+
"scipy": version("scipy"),
|
|
404
|
+
"astropy": version("astropy"),
|
|
405
|
+
"gwexpy": version("gwexpy"),
|
|
406
|
+
},
|
|
407
|
+
gravitational_constant_si=G_SI,
|
|
408
|
+
g_applications=1,
|
|
409
|
+
numerical_diagnostics=diagnostics,
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
__all__ = ["AnalyticEvaluation", "NativeEvaluation"]
|
gragra/_arrays.py
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"""Internal array normalization and validation helpers."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from gragra.array_types import ComplexArray, FloatArray
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def as_xyz_vector(name: str, value: list | tuple | np.ndarray) -> FloatArray:
|
|
9
|
+
"""Normalize and validate a single 3D vector.
|
|
10
|
+
|
|
11
|
+
Expects a value convertable to a float64 array of shape (3,).
|
|
12
|
+
Sets the writeable flag of the returned array to False (read-only).
|
|
13
|
+
|
|
14
|
+
Parameters
|
|
15
|
+
----------
|
|
16
|
+
name : str
|
|
17
|
+
Parameter name for error messages.
|
|
18
|
+
value : array-like
|
|
19
|
+
The input coordinates, e.g., [x, y, z].
|
|
20
|
+
|
|
21
|
+
Returns
|
|
22
|
+
-------
|
|
23
|
+
FloatArray
|
|
24
|
+
A read-only float64 array of shape (3,).
|
|
25
|
+
"""
|
|
26
|
+
try:
|
|
27
|
+
arr = np.asarray(value, dtype=np.float64)
|
|
28
|
+
except (ValueError, TypeError) as e:
|
|
29
|
+
raise TypeError(f"{name} must be numeric convertible to float64: {e}") from e
|
|
30
|
+
|
|
31
|
+
if arr.shape != (3,):
|
|
32
|
+
raise ValueError(f"{name} must have shape (3,), got {arr.shape}")
|
|
33
|
+
|
|
34
|
+
if not np.isfinite(arr).all():
|
|
35
|
+
raise ValueError(f"{name} must contain only finite numbers")
|
|
36
|
+
|
|
37
|
+
# Defensive copy and set to read-only
|
|
38
|
+
arr = arr.copy()
|
|
39
|
+
arr.setflags(write=False)
|
|
40
|
+
return arr
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def as_xyz_table(name: str, value: list | tuple | np.ndarray) -> FloatArray:
|
|
44
|
+
"""Normalize and validate a table of 3D vectors.
|
|
45
|
+
|
|
46
|
+
Expects a value convertable to a float64 array of shape (N, 3).
|
|
47
|
+
Strictly rejects shape (3,).
|
|
48
|
+
Sets the writeable flag of the returned array to False (read-only).
|
|
49
|
+
|
|
50
|
+
Parameters
|
|
51
|
+
----------
|
|
52
|
+
name : str
|
|
53
|
+
Parameter name for error messages.
|
|
54
|
+
value : array-like
|
|
55
|
+
The input coordinates table, e.g., [[x1, y1, z1], [x2, y2, z2], ...].
|
|
56
|
+
|
|
57
|
+
Returns
|
|
58
|
+
-------
|
|
59
|
+
FloatArray
|
|
60
|
+
A read-only float64 array of shape (N, 3).
|
|
61
|
+
"""
|
|
62
|
+
is_1d = False
|
|
63
|
+
try:
|
|
64
|
+
val_arr = np.asarray(value)
|
|
65
|
+
if val_arr.ndim == 1:
|
|
66
|
+
is_1d = True
|
|
67
|
+
except (ValueError, TypeError):
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
if is_1d:
|
|
71
|
+
raise ValueError(f"{name} must be a 2D table of shape (N, 3), not a 1D vector")
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
arr = np.asarray(value, dtype=np.float64)
|
|
75
|
+
except (ValueError, TypeError) as e:
|
|
76
|
+
raise TypeError(f"{name} must be numeric convertible to float64: {e}") from e
|
|
77
|
+
|
|
78
|
+
if arr.ndim != 2 or arr.shape[1] != 3:
|
|
79
|
+
raise ValueError(f"{name} must have shape (N, 3), got {arr.shape}")
|
|
80
|
+
|
|
81
|
+
if not np.isfinite(arr).all():
|
|
82
|
+
raise ValueError(f"{name} must contain only finite numbers")
|
|
83
|
+
|
|
84
|
+
arr = arr.copy()
|
|
85
|
+
arr.setflags(write=False)
|
|
86
|
+
return arr
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def as_weights(
|
|
90
|
+
name: str, value: list | tuple | np.ndarray
|
|
91
|
+
) -> FloatArray | ComplexArray:
|
|
92
|
+
"""Normalize and validate weight vectors (real or complex).
|
|
93
|
+
|
|
94
|
+
Expects a value convertable to float64 or complex128 array of shape (N,).
|
|
95
|
+
Keeps real inputs as float64 and complex inputs as complex128.
|
|
96
|
+
Sets the writeable flag of the returned array to False (read-only).
|
|
97
|
+
|
|
98
|
+
Parameters
|
|
99
|
+
----------
|
|
100
|
+
name : str
|
|
101
|
+
Parameter name for error messages.
|
|
102
|
+
value : array-like
|
|
103
|
+
The weights array, e.g. [w1, w2, ...].
|
|
104
|
+
|
|
105
|
+
Returns
|
|
106
|
+
-------
|
|
107
|
+
FloatArray or ComplexArray
|
|
108
|
+
A read-only array of shape (N,).
|
|
109
|
+
"""
|
|
110
|
+
raw_arr = np.asarray(value)
|
|
111
|
+
|
|
112
|
+
# Determine appropriate dtype (preserve complex if input is complex)
|
|
113
|
+
dtype = np.complex128 if np.iscomplexobj(raw_arr) else np.float64
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
arr = np.asarray(value, dtype=dtype)
|
|
117
|
+
except (ValueError, TypeError) as e:
|
|
118
|
+
raise TypeError(
|
|
119
|
+
f"{name} must be convertible to float64 or complex128: {e}"
|
|
120
|
+
) from e
|
|
121
|
+
|
|
122
|
+
if arr.ndim != 1:
|
|
123
|
+
raise ValueError(
|
|
124
|
+
f"{name} must be a 1D array of shape (N,), got shape {arr.shape}"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
if not np.isfinite(arr).all():
|
|
128
|
+
raise ValueError(f"{name} must contain only finite numbers")
|
|
129
|
+
|
|
130
|
+
arr = arr.copy()
|
|
131
|
+
arr.setflags(write=False)
|
|
132
|
+
return arr
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def as_index_table(
|
|
136
|
+
name: str, value: list | tuple | np.ndarray, n_max: int
|
|
137
|
+
) -> np.ndarray:
|
|
138
|
+
"""Normalize and validate an integer connectivity table of shape (Nt, 3).
|
|
139
|
+
|
|
140
|
+
Expects integer vertex indices in the half-open range ``[0, n_max)``.
|
|
141
|
+
Returns a read-only ``np.intp`` array of shape (Nt, 3), independent of the
|
|
142
|
+
input integer dtype, so callers obtain a canonical connectivity table.
|
|
143
|
+
|
|
144
|
+
This consolidates the integer-connectivity validation that is otherwise
|
|
145
|
+
duplicated across the field/adapter helpers; it rejects bool, non-integer
|
|
146
|
+
dtype, wrong shape, and both lower (``< 0``) and upper (``>= n_max``)
|
|
147
|
+
out-of-range indices.
|
|
148
|
+
|
|
149
|
+
Parameters
|
|
150
|
+
----------
|
|
151
|
+
name : str
|
|
152
|
+
Parameter name for error messages.
|
|
153
|
+
value : array-like
|
|
154
|
+
The connectivity table, e.g. [[i0, i1, i2], ...].
|
|
155
|
+
n_max : int
|
|
156
|
+
Exclusive upper bound for indices (typically the number of vertices).
|
|
157
|
+
|
|
158
|
+
Returns
|
|
159
|
+
-------
|
|
160
|
+
np.ndarray
|
|
161
|
+
A read-only ``np.intp`` array of shape (Nt, 3).
|
|
162
|
+
"""
|
|
163
|
+
_check_no_bool(value, name)
|
|
164
|
+
|
|
165
|
+
raw = np.asarray(value)
|
|
166
|
+
if raw.dtype.kind not in "iu":
|
|
167
|
+
raise TypeError(f"{name} must be of integer type, got {raw.dtype}")
|
|
168
|
+
|
|
169
|
+
if raw.ndim != 2 or raw.shape[1] != 3:
|
|
170
|
+
raise ValueError(f"{name} must have shape (Nt, 3), got {raw.shape}")
|
|
171
|
+
|
|
172
|
+
arr = raw.astype(np.intp, copy=True)
|
|
173
|
+
if arr.size > 0 and ((arr < 0).any() or (arr >= n_max).any()):
|
|
174
|
+
raise ValueError(f"{name} indices must be within [0, {n_max})")
|
|
175
|
+
|
|
176
|
+
arr.setflags(write=False)
|
|
177
|
+
return arr
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _check_no_bool(val, name):
|
|
181
|
+
"""Helper to raise TypeError if any boolean value is found in inputs."""
|
|
182
|
+
if isinstance(val, (bool, np.bool_)):
|
|
183
|
+
raise TypeError(f"{name} must not contain bool")
|
|
184
|
+
if hasattr(val, "dtype"):
|
|
185
|
+
if val.dtype.kind == "b":
|
|
186
|
+
raise TypeError(f"{name} must not contain bool")
|
|
187
|
+
if val.dtype.kind in "fiuc":
|
|
188
|
+
return
|
|
189
|
+
if isinstance(val, np.ndarray):
|
|
190
|
+
for x in val.flat:
|
|
191
|
+
_check_no_bool(x, name)
|
|
192
|
+
elif isinstance(val, (list, tuple)):
|
|
193
|
+
for x in val:
|
|
194
|
+
_check_no_bool(x, name)
|