flapjax 1.2.0.dev0__tar.gz → 1.2.2__tar.gz
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.
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/PKG-INFO +3 -3
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/packaging/full/README.md +0 -8
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/packaging/full/pyproject.toml +3 -3
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/pyproject.toml +3 -3
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/aero/linear/linear_uvlm.py +469 -27
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/aero/utils.py +43 -7
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/aero/uvlm.py +60 -32
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/coupled/__init__.py +1 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/coupled/coupled.py +5 -2
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/coupled/data_structures.py +5 -3
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/coupled/gradients/coupled.py +106 -69
- flapjax-1.2.2/src/flapjax/coupled/gradients/utils.py +78 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/coupled/linear/linear_coupled.py +84 -4
- flapjax-1.2.2/src/flapjax/coupled/linear_aero_coupled.py +364 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/models/simple_hale/simple_hale.py +14 -7
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/structure/beam.py +116 -26
- flapjax-1.2.2/src/flapjax/structure/constraints.py +191 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/structure/gradients/beam.py +1 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/tests/aero/test_infinite_wing_polar.py +39 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/tests/conftest.py +6 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/tests/coupled/dynamic/test_dynamic_gust_adjoint.py +2 -4
- flapjax-1.2.2/src/flapjax/tests/coupled/dynamic/test_linear_aero_equilibrium.py +85 -0
- flapjax-1.2.2/src/flapjax/tests/structure/dynamic_primal/test_prescribed_motion.py +135 -0
- flapjax-1.2.2/src/flapjax/tests/structure/static_primal/test_free_free_modes.py +95 -0
- flapjax-1.2.2/src/flapjax/tests/structure/static_primal/test_nodal_constraints.py +166 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/utils/linear.py +6 -6
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/.gitignore +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/LICENSE +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/README.md +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/docs/figures/beam_transform.png +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/docs/figures/primal_solve.png +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/docs/figures/uvlm_grid.png +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/docs/index.md +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/docs/javascripts/mathjax.js +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/docs/reference/aero.md +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/docs/reference/algebra.md +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/docs/reference/coupled.md +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/docs/reference/index.md +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/docs/reference/structure.md +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/docs/reference/utils.md +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/docs/stylesheets/extra.css +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/docs/theory/adjoint.md +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/docs/theory/beam.md +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/docs/theory/coupling.md +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/docs/theory/index.md +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/docs/theory/linear.md +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/docs/theory/uvlm.md +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/docs/tutorials/flying_spaghetti.ipynb +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/docs/tutorials/flying_spaghetti_optimise.ipynb +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/docs/tutorials/geradin_beam_adjoint.ipynb +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/docs/tutorials/geradin_beam_static.ipynb +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/docs/tutorials/index.md +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/docs/tutorials/patil_wing_control.ipynb +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/docs/tutorials/simple_hale_gust.ipynb +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/docs/tutorials/straight_pazy_deformed_modes.ipynb +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/docs/tutorials/straight_pazy_flutter.ipynb +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/docs/tutorials/straight_pazy_lco.ipynb +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/docs/tutorials/straight_pazy_static_deflection.ipynb +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/docs/tutorials/swept_pazy_deformed_modes.ipynb +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/docs/tutorials/swept_pazy_flutter.ipynb +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/docs/tutorials/swept_pazy_lco.ipynb +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/docs/tutorials/swept_pazy_static_deflection.ipynb +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/mkdocs.yml +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/packaging/full/LICENSE +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/__init__.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/aero/__init__.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/aero/aic.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/aero/data_structures.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/aero/flowfields.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/aero/gradients/__init__.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/aero/gradients/data_structures.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/aero/linear/__init__.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/aero/linear/data_structures.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/algebra/__init__.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/algebra/array_utils.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/algebra/base.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/algebra/integration.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/algebra/se3.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/algebra/so3.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/algebra/test_routines.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/coupled/gradients/__init__.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/coupled/gradients/data_structures.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/coupled/linear/__init__.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/coupled/linear/data_structures.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/models/__init__.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/models/cantilever_wing.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/models/flying_spaghetti/__init__.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/models/flying_spaghetti/flying_spaghetti.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/models/flying_spaghetti/free_dynamic.ipynb +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/models/flying_spaghetti/mass_optimisation.ipynb +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/models/geradin_beam/__init__.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/models/geradin_beam/adjoint_gradients.ipynb +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/models/geradin_beam/geradin_beam.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/models/geradin_beam/static_deformation.ipynb +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/models/ifasd_aviation_2026/__init__.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/models/ifasd_aviation_2026/cantilever_wing_adjoint.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/models/ifasd_aviation_2026/optimal_roll.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/models/panel_scaling_profile.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/models/patil_wing/__init__.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/models/patil_wing/open_loop_control.ipynb +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/models/patil_wing/patil_wing.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/models/pazy/__init__.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/models/pazy/base.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/models/pazy/straight/__init__.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/models/pazy/straight/data/__init__.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/models/pazy/straight/data/prepazy_properties.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/models/pazy/straight/data/technion_pazy_properties.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/models/pazy/straight/deformed_modes.ipynb +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/models/pazy/straight/flutter.ipynb +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/models/pazy/straight/lco.ipynb +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/models/pazy/straight/pazy_wing.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/models/pazy/straight/static_deflection.ipynb +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/models/pazy/swept/__init__.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/models/pazy/swept/data/__init__.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/models/pazy/swept/data/properties.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/models/pazy/swept/deformed_modes.ipynb +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/models/pazy/swept/flutter.ipynb +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/models/pazy/swept/lco.ipynb +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/models/pazy/swept/static_deflection.ipynb +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/models/pazy/swept/swept_pazy_wing.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/models/scitech_abstract_2027/__init__.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/models/scitech_abstract_2027/linear_uvlm_case.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/models/simple_hale/__init__.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/models/simple_hale/parallel_gust.ipynb +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/plotting/__init__.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/plotting/aerogrid.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/plotting/beam.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/plotting/modal.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/plotting/pvd.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/structure/__init__.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/structure/data_structures.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/structure/gradients/__init__.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/structure/gradients/data_structures.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/structure/linear/__init__.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/structure/linear/data_structures.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/structure/linear/linear_beam.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/structure/time_integration.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/structure/utils.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/tests/aero/test_linear_planar_wing.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/tests/aero/test_mirrored_wing.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/tests/aero/test_static_no_wake.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/tests/aero/test_variable_wake_disc.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/tests/algebra/test_beam_algebra.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/tests/algebra/test_kernels.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/tests/coupled/dynamic/test_dynamic_equilibrium.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/tests/coupled/dynamic/test_dynamic_equilibrium_adjoint.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/tests/coupled/dynamic/test_parallel_cantilever.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/tests/coupled/static/test_pazy.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/tests/coupled/static/test_static_adjoint.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/tests/plotting/test_beam_plot.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/tests/structure/dynamic_linear/test_oscillating_cantilever.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/tests/structure/dynamic_primal/test_applied_force_point.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/tests/structure/dynamic_primal/test_const_initial_linear_velocity.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/tests/structure/dynamic_primal/test_const_initial_rotational_velocity.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/tests/structure/dynamic_primal/test_const_initial_velocity_lumped_mass.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/tests/structure/dynamic_primal/test_gravity_beam_drop.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/tests/structure/dynamic_primal/test_gravity_point_drop.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/tests/structure/dynamic_primal/test_parallel_spaghetti.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/tests/structure/dynamic_primal/test_two_lumped_mass_const_rot_velocity.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/tests/structure/dynamic_tangent/test_beam_dynamic_adjoint.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/tests/structure/dynamic_tangent/test_lumped_mass_dynamic_adjoint.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/tests/structure/static_primal/test_geradin_beam.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/tests/structure/static_primal/test_mass_gravity.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/tests/structure/static_primal/test_matrices.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/tests/structure/static_primal/test_modal.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/tests/structure/static_primal/test_multi_element_beam.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/tests/structure/static_primal/test_pazy_modes.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/tests/structure/static_primal/test_two_node_beam.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/tests/structure/static_tangent/test_geradin_gradients.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/utils/__init__.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/utils/constants.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/utils/data_structures.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/utils/print_utils.py +0 -0
- {flapjax-1.2.0.dev0 → flapjax-1.2.2}/src/flapjax/utils/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: flapjax
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.2
|
|
4
4
|
Summary: Solver for computing aeroelastic responses and corresponding design derivatives.
|
|
5
5
|
Project-URL: Homepage, https://ben-l-p.github.io/flapjax/
|
|
6
6
|
Project-URL: Documentation, https://ben-l-p.github.io/flapjax/
|
|
@@ -19,8 +19,8 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
19
19
|
Classifier: Topic :: Scientific/Engineering
|
|
20
20
|
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
21
21
|
Requires-Python: >=3.12
|
|
22
|
-
Requires-Dist: jax>=0.
|
|
23
|
-
Requires-Dist: matplotlib>=3.8
|
|
22
|
+
Requires-Dist: jax>=0.8.1
|
|
23
|
+
Requires-Dist: matplotlib>=3.8.4
|
|
24
24
|
Requires-Dist: numpy>=2.0
|
|
25
25
|
Requires-Dist: scipy>=1.13
|
|
26
26
|
Requires-Dist: vtk>=9.4
|
|
@@ -27,11 +27,3 @@ pip install flapjax-full # includes tests and tutorial notebooks
|
|
|
27
27
|
```bash
|
|
28
28
|
pytest --pyargs flapjax
|
|
29
29
|
```
|
|
30
|
-
|
|
31
|
-
## Locating the notebooks
|
|
32
|
-
|
|
33
|
-
The tutorial notebooks ship inside the installed package. Find them with:
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
python -c "import flapjax, pathlib; print(pathlib.Path(flapjax.__file__).parent / 'models')"
|
|
37
|
-
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "flapjax-full"
|
|
3
|
-
version = "1.2.
|
|
3
|
+
version = "1.2.2"
|
|
4
4
|
description = "FLAPJAX with tests and tutorial notebooks bundled. Install this OR flapjax, not both."
|
|
5
5
|
authors = [{ name = "Ben Preston", email = "b.preston23@imperial.ac.uk" }]
|
|
6
6
|
keywords = ["aeroelasticity", "unsteady vortex lattice method", "nonlinear beam", "adjoint methods"]
|
|
@@ -19,11 +19,11 @@ readme = "README.md"
|
|
|
19
19
|
license = "MIT"
|
|
20
20
|
license-files = ["LICENSE"]
|
|
21
21
|
dependencies = [
|
|
22
|
-
"jax>=0.
|
|
22
|
+
"jax>=0.8.1",
|
|
23
23
|
"numpy>=2.0",
|
|
24
24
|
"vtk>=9.4",
|
|
25
25
|
"scipy>=1.13",
|
|
26
|
-
"matplotlib>=3.8",
|
|
26
|
+
"matplotlib>=3.8.4",
|
|
27
27
|
"pytest>=9.0",
|
|
28
28
|
]
|
|
29
29
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "flapjax"
|
|
3
|
-
version = "1.2.
|
|
3
|
+
version = "1.2.2"
|
|
4
4
|
description = "Solver for computing aeroelastic responses and corresponding design derivatives."
|
|
5
5
|
authors = [{ name = "Ben Preston", email = "b.preston23@imperial.ac.uk" }]
|
|
6
6
|
keywords = ["aeroelasticity", "unsteady vortex lattice method", "nonlinear beam", "adjoint methods"]
|
|
@@ -19,11 +19,11 @@ readme = "README.md"
|
|
|
19
19
|
license = "MIT"
|
|
20
20
|
license-files = ["LICENSE"]
|
|
21
21
|
dependencies = [
|
|
22
|
-
"jax>=0.
|
|
22
|
+
"jax>=0.8.1",
|
|
23
23
|
"numpy>=2.0",
|
|
24
24
|
"vtk>=9.4",
|
|
25
25
|
"scipy>=1.13",
|
|
26
|
-
"matplotlib>=3.8",
|
|
26
|
+
"matplotlib>=3.8.4",
|
|
27
27
|
]
|
|
28
28
|
|
|
29
29
|
[project.urls]
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
-
from collections.abc import Sequence
|
|
4
|
+
from collections.abc import Callable, Sequence
|
|
5
|
+
from dataclasses import dataclass
|
|
5
6
|
from pathlib import Path
|
|
6
|
-
from typing import TYPE_CHECKING, Literal
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
7
8
|
|
|
8
|
-
import jax
|
|
9
9
|
import jax.numpy as jnp
|
|
10
10
|
from jax import Array, vmap
|
|
11
11
|
|
|
@@ -25,7 +25,12 @@ from flapjax.aero.utils import (
|
|
|
25
25
|
calculate_steady_forcing,
|
|
26
26
|
compute_nc,
|
|
27
27
|
)
|
|
28
|
-
from flapjax.algebra.array_utils import
|
|
28
|
+
from flapjax.algebra.array_utils import (
|
|
29
|
+
ArrayList,
|
|
30
|
+
construct_named_block_jacobian,
|
|
31
|
+
split_to_vertex,
|
|
32
|
+
)
|
|
33
|
+
from flapjax.algebra.base import ADMode, jacrev_custom
|
|
29
34
|
from flapjax.utils.linear import (
|
|
30
35
|
LinearComponent,
|
|
31
36
|
LinearModel,
|
|
@@ -40,6 +45,37 @@ if TYPE_CHECKING:
|
|
|
40
45
|
type LinearWakeType = Literal["frozen", "prescribed", "free"]
|
|
41
46
|
|
|
42
47
|
|
|
48
|
+
@dataclass
|
|
49
|
+
class LinearInputProjection:
|
|
50
|
+
r"""
|
|
51
|
+
Projection of grid coordinates and velocities from a lower-dimensional input space (e.g. beam DOFs) to
|
|
52
|
+
``(zeta_b_np1_vec, zeta_b_dot_np1_vec)``.
|
|
53
|
+
:param arg_names: Names of the projected input dofs (used as column labels).
|
|
54
|
+
:param arg_sizes: Sizes of each projected input dof block.
|
|
55
|
+
:param arg_refs: Reference values for the projected inputs.
|
|
56
|
+
:param to_zeta: Callable ``(**{arg_name: vec}) -> (zeta_b_vec, zeta_b_dot_vec)`` returning their total values.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
arg_names: tuple[str, ...]
|
|
60
|
+
arg_sizes: tuple[int, ...]
|
|
61
|
+
arg_refs: tuple[Array, ...]
|
|
62
|
+
to_zeta: Callable[..., tuple[Array, Array]]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass
|
|
66
|
+
class LinearOutputProjection:
|
|
67
|
+
r"""
|
|
68
|
+
Projection of forces from the full aerodynamic grid to a single output space.
|
|
69
|
+
:param name: Name of the projected output block (used as row label).
|
|
70
|
+
:param size: Size of the projected output.
|
|
71
|
+
:param to_output: Callable ``(f_steady_vec, f_unsteady_vec | None) -> projected_vec``.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
name: str
|
|
75
|
+
size: int
|
|
76
|
+
to_output: Callable[[Array, Array | None], Array]
|
|
77
|
+
|
|
78
|
+
|
|
43
79
|
class LinearUVLM(
|
|
44
80
|
LinearModel[
|
|
45
81
|
AeroCase,
|
|
@@ -74,7 +110,6 @@ class LinearUVLM(
|
|
|
74
110
|
:param wake_upwash: If true, include wake surface upwash velocities as inputs.
|
|
75
111
|
:param unsteady_force: If true, include unsteady force.
|
|
76
112
|
"""
|
|
77
|
-
|
|
78
113
|
# options
|
|
79
114
|
self.prescribed_wake, self.free_wake = {
|
|
80
115
|
"frozen": (False, False),
|
|
@@ -272,7 +307,7 @@ class LinearUVLM(
|
|
|
272
307
|
"""
|
|
273
308
|
|
|
274
309
|
u_np1 = self._unpack_input_vector(u_vec)
|
|
275
|
-
x_n = self.
|
|
310
|
+
x_n = self.unpack_state_vector(x_vec)
|
|
276
311
|
|
|
277
312
|
assert isinstance(u_np1, AeroInputUnflattened) and isinstance(
|
|
278
313
|
x_n, AeroStateUnflattened
|
|
@@ -282,7 +317,7 @@ class LinearUVLM(
|
|
|
282
317
|
|
|
283
318
|
x_np1, y_n = self.step(u_np1=u_np1, x_n=x_n)
|
|
284
319
|
|
|
285
|
-
return self.
|
|
320
|
+
return self.pack_state_vector(x_np1), self.pack_output_vector(y_n)
|
|
286
321
|
|
|
287
322
|
def step(
|
|
288
323
|
self,
|
|
@@ -394,33 +429,440 @@ class LinearUVLM(
|
|
|
394
429
|
|
|
395
430
|
return x_np1, y_n
|
|
396
431
|
|
|
397
|
-
def
|
|
432
|
+
def _step_from_vecs(
|
|
433
|
+
self,
|
|
434
|
+
gamma_b_n_vec: Array | None = None,
|
|
435
|
+
gamma_w_n_vec: Array | None = None,
|
|
436
|
+
gamma_b_nm1_vec: Array | None = None,
|
|
437
|
+
zeta_w_n_vec: Array | None = None,
|
|
438
|
+
zeta_b_n_vec: Array | None = None,
|
|
439
|
+
zeta_b_np1_vec: Array | None = None,
|
|
440
|
+
zeta_b_dot_np1_vec: Array | None = None,
|
|
441
|
+
nu_b_np1_vec: Array | None = None,
|
|
442
|
+
nu_w_np1_vec: Array | None = None,
|
|
443
|
+
) -> tuple[AeroStateUnflattened, AeroOutputUnflattened]:
|
|
398
444
|
r"""
|
|
399
|
-
|
|
445
|
+
Unravel vectors into their shaped forms and step the system.
|
|
400
446
|
"""
|
|
447
|
+
ref = self.reference
|
|
401
448
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
449
|
+
def _unravel(vec: Array | None, template: ArrayList) -> ArrayList:
|
|
450
|
+
return (
|
|
451
|
+
ArrayList.from_vector(vect=vec, shapes=template.shape)
|
|
452
|
+
if vec is not None
|
|
453
|
+
else template
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
gamma_b = _unravel(gamma_b_n_vec, ref.gamma_b)
|
|
457
|
+
gamma_w = _unravel(gamma_w_n_vec, ref.gamma_w)
|
|
458
|
+
gamma_b_nm1 = (
|
|
459
|
+
_unravel(gamma_b_nm1_vec, ref.gamma_b) if self.unsteady_force else None
|
|
411
460
|
)
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
461
|
+
zeta_b_np1 = _unravel(zeta_b_np1_vec, ref.zeta_b)
|
|
462
|
+
zeta_b_dot_np1 = _unravel(zeta_b_dot_np1_vec, ref.zeta_b_dot)
|
|
463
|
+
zeta_w_n = _unravel(zeta_w_n_vec, ref.zeta_w) if self.prescribed_wake else None
|
|
464
|
+
zeta_b_n = _unravel(zeta_b_n_vec, ref.zeta_b) if self.prescribed_wake else None
|
|
465
|
+
nu_b = (
|
|
466
|
+
_unravel(nu_b_np1_vec, ref.zeta_b)
|
|
467
|
+
if self.bound_upwash and nu_b_np1_vec is not None
|
|
468
|
+
else (ArrayList.zeros_like(ref.zeta_b) if self.bound_upwash else None)
|
|
469
|
+
)
|
|
470
|
+
nu_w = (
|
|
471
|
+
_unravel(nu_w_np1_vec, ref.zeta_w)
|
|
472
|
+
if self.wake_upwash and nu_w_np1_vec is not None
|
|
473
|
+
else (ArrayList.zeros_like(ref.zeta_w) if self.wake_upwash else None)
|
|
418
474
|
)
|
|
419
475
|
|
|
420
|
-
|
|
421
|
-
|
|
476
|
+
u_np1 = AeroInputUnflattened(
|
|
477
|
+
zeta_b=zeta_b_np1,
|
|
478
|
+
zeta_b_dot=zeta_b_dot_np1,
|
|
479
|
+
nu_b=nu_b,
|
|
480
|
+
nu_w=nu_w,
|
|
481
|
+
)
|
|
482
|
+
x_n = AeroStateUnflattened(
|
|
483
|
+
gamma_b=gamma_b,
|
|
484
|
+
gamma_w=gamma_w,
|
|
485
|
+
gamma_b_nm1=gamma_b_nm1,
|
|
486
|
+
zeta_w=zeta_w_n,
|
|
487
|
+
zeta_b=zeta_b_n,
|
|
488
|
+
)
|
|
489
|
+
return self.step(u_np1=u_np1, x_n=x_n)
|
|
490
|
+
|
|
491
|
+
def gamma_b_step(self, **kwargs: Any) -> Array:
|
|
492
|
+
x_np1, _ = self._step_from_vecs(**kwargs)
|
|
493
|
+
return x_np1.gamma_b.ravel()
|
|
494
|
+
|
|
495
|
+
def wake_prop_step(self, **kwargs: Any) -> tuple[Array | None, Array]:
|
|
496
|
+
x_np1, _ = self._step_from_vecs(**kwargs)
|
|
497
|
+
return (
|
|
498
|
+
x_np1.zeta_w.ravel() if x_np1.zeta_w is not None else None,
|
|
499
|
+
x_np1.gamma_w.ravel(),
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
def f_steady_step(self, **kwargs: Any) -> Array:
|
|
503
|
+
_, y_n = self._step_from_vecs(**kwargs)
|
|
504
|
+
return y_n.f_steady.ravel()
|
|
505
|
+
|
|
506
|
+
def f_unsteady_step(self, **kwargs: Any) -> Array:
|
|
507
|
+
_, y_n = self._step_from_vecs(**kwargs)
|
|
508
|
+
assert y_n.f_unsteady is not None, "unsteady_force must be enabled"
|
|
509
|
+
return y_n.f_unsteady.ravel()
|
|
510
|
+
|
|
511
|
+
def compute_jacobians(
|
|
512
|
+
self,
|
|
513
|
+
input_projection: LinearInputProjection | None = None,
|
|
514
|
+
output_projection: LinearOutputProjection | None = None,
|
|
515
|
+
) -> dict[str, tuple[Callable[..., Any], dict[str, Any], Sequence[str]]]:
|
|
516
|
+
r"""
|
|
517
|
+
Build the Jacobians for the linear system.
|
|
518
|
+
:param input_projection: If set, projects the inputs onto a different space.
|
|
519
|
+
:param output_projection: If set, projects the forcing outputs onto a different space.
|
|
520
|
+
:return: Mapping of residual name to Jacobian(s).
|
|
521
|
+
"""
|
|
522
|
+
ref = self.reference
|
|
523
|
+
|
|
524
|
+
# bound circulation
|
|
525
|
+
gamma_b_args: dict[str, Any] = {
|
|
526
|
+
"gamma_b_n_vec": ref.gamma_b.ravel(),
|
|
527
|
+
"gamma_w_n_vec": ref.gamma_w.ravel(),
|
|
528
|
+
"zeta_b_np1_vec": ref.zeta_b.ravel(),
|
|
529
|
+
"zeta_b_dot_np1_vec": ref.zeta_b_dot.ravel(),
|
|
530
|
+
}
|
|
531
|
+
gamma_b_diff = [
|
|
532
|
+
"gamma_b_n_vec",
|
|
533
|
+
"gamma_w_n_vec",
|
|
534
|
+
"zeta_b_np1_vec",
|
|
535
|
+
"zeta_b_dot_np1_vec",
|
|
536
|
+
]
|
|
537
|
+
if self.prescribed_wake:
|
|
538
|
+
gamma_b_args["zeta_w_n_vec"] = ref.zeta_w.ravel()
|
|
539
|
+
gamma_b_args["zeta_b_n_vec"] = ref.zeta_b.ravel()
|
|
540
|
+
gamma_b_diff.extend(["zeta_w_n_vec", "zeta_b_n_vec"])
|
|
541
|
+
if self.bound_upwash:
|
|
542
|
+
gamma_b_args["nu_b_np1_vec"] = jnp.zeros(ref.zeta_b.size)
|
|
543
|
+
gamma_b_diff.append("nu_b_np1_vec")
|
|
544
|
+
if self.wake_upwash:
|
|
545
|
+
gamma_b_args["nu_w_np1_vec"] = jnp.zeros(ref.zeta_w.size)
|
|
546
|
+
gamma_b_diff.append("nu_w_np1_vec")
|
|
422
547
|
|
|
423
|
-
|
|
548
|
+
# wake propagation (gamma_w and optionally zeta_w)
|
|
549
|
+
wake_args: dict[str, Any] = {
|
|
550
|
+
"gamma_b_n_vec": ref.gamma_b.ravel(),
|
|
551
|
+
"gamma_w_n_vec": ref.gamma_w.ravel(),
|
|
552
|
+
}
|
|
553
|
+
gamma_w_diff = ["gamma_b_n_vec", "gamma_w_n_vec"]
|
|
554
|
+
zeta_w_diff: list[str] = []
|
|
555
|
+
if self.prescribed_wake:
|
|
556
|
+
wake_args["zeta_w_n_vec"] = ref.zeta_w.ravel()
|
|
557
|
+
wake_args["zeta_b_np1_vec"] = ref.zeta_b.ravel()
|
|
558
|
+
zeta_w_diff.extend(["zeta_w_n_vec", "zeta_b_np1_vec"])
|
|
559
|
+
if self.wake_upwash:
|
|
560
|
+
wake_args["nu_w_np1_vec"] = jnp.zeros(ref.zeta_w.size)
|
|
561
|
+
zeta_w_diff.append("nu_w_np1_vec")
|
|
562
|
+
if self.free_wake:
|
|
563
|
+
zeta_w_diff.extend(["gamma_b_n_vec", "gamma_w_n_vec"])
|
|
564
|
+
|
|
565
|
+
# steady forcing
|
|
566
|
+
f_steady_args: dict[str, Any] = {
|
|
567
|
+
"gamma_b_n_vec": ref.gamma_b.ravel(),
|
|
568
|
+
"gamma_w_n_vec": ref.gamma_w.ravel(),
|
|
569
|
+
"zeta_b_np1_vec": ref.zeta_b.ravel(),
|
|
570
|
+
"zeta_b_dot_np1_vec": ref.zeta_b_dot.ravel(),
|
|
571
|
+
}
|
|
572
|
+
f_steady_diff = [
|
|
573
|
+
"gamma_b_n_vec",
|
|
574
|
+
"gamma_w_n_vec",
|
|
575
|
+
"zeta_b_np1_vec",
|
|
576
|
+
"zeta_b_dot_np1_vec",
|
|
577
|
+
]
|
|
578
|
+
if self.prescribed_wake:
|
|
579
|
+
f_steady_args["zeta_w_n_vec"] = ref.zeta_w.ravel()
|
|
580
|
+
f_steady_diff.append("zeta_w_n_vec")
|
|
581
|
+
if self.bound_upwash:
|
|
582
|
+
f_steady_args["nu_b_np1_vec"] = jnp.zeros(ref.zeta_b.size)
|
|
583
|
+
f_steady_diff.append("nu_b_np1_vec")
|
|
584
|
+
|
|
585
|
+
residuals: dict[
|
|
586
|
+
str, tuple[Callable[..., Any], dict[str, Any], Sequence[str]]
|
|
587
|
+
] = {
|
|
588
|
+
"gamma_b": (self.gamma_b_step, gamma_b_args, gamma_b_diff),
|
|
589
|
+
"gamma_w": (
|
|
590
|
+
lambda **kw: self.wake_prop_step(**kw)[1],
|
|
591
|
+
wake_args,
|
|
592
|
+
gamma_w_diff,
|
|
593
|
+
),
|
|
594
|
+
}
|
|
595
|
+
if self.prescribed_wake:
|
|
596
|
+
residuals["zeta_w"] = (
|
|
597
|
+
lambda **kw: self.wake_prop_step(**kw)[0],
|
|
598
|
+
wake_args,
|
|
599
|
+
zeta_w_diff,
|
|
600
|
+
)
|
|
601
|
+
if self.unsteady_force:
|
|
602
|
+
residuals["gamma_b_nm1"] = (
|
|
603
|
+
lambda **kw: kw["gamma_b_n_vec"],
|
|
604
|
+
{"gamma_b_n_vec": ref.gamma_b.ravel()},
|
|
605
|
+
["gamma_b_n_vec"],
|
|
606
|
+
)
|
|
607
|
+
if self.prescribed_wake:
|
|
608
|
+
# zeta_b state at n+1 == zeta_b input at n+1
|
|
609
|
+
residuals["zeta_b"] = (
|
|
610
|
+
lambda **kw: kw["zeta_b_np1_vec"],
|
|
611
|
+
{"zeta_b_np1_vec": ref.zeta_b.ravel()},
|
|
612
|
+
["zeta_b_np1_vec"],
|
|
613
|
+
)
|
|
614
|
+
residuals["f_steady"] = (self.f_steady_step, f_steady_args, f_steady_diff)
|
|
615
|
+
if self.unsteady_force:
|
|
616
|
+
f_unsteady_args: dict[str, Any] = {
|
|
617
|
+
"gamma_b_n_vec": ref.gamma_b.ravel(),
|
|
618
|
+
"gamma_b_nm1_vec": ref.gamma_b.ravel(),
|
|
619
|
+
"zeta_b_np1_vec": ref.zeta_b.ravel(),
|
|
620
|
+
}
|
|
621
|
+
residuals["f_unsteady"] = (
|
|
622
|
+
self.f_unsteady_step,
|
|
623
|
+
f_unsteady_args,
|
|
624
|
+
["gamma_b_n_vec", "gamma_b_nm1_vec", "zeta_b_np1_vec"],
|
|
625
|
+
)
|
|
626
|
+
|
|
627
|
+
# apply I/O projections if provided
|
|
628
|
+
if input_projection is not None:
|
|
629
|
+
residuals = self._apply_input_projection(residuals, input_projection)
|
|
630
|
+
if output_projection is not None:
|
|
631
|
+
residuals = self._apply_output_projection(residuals, output_projection)
|
|
632
|
+
|
|
633
|
+
return residuals
|
|
634
|
+
|
|
635
|
+
@staticmethod
|
|
636
|
+
def _apply_input_projection(
|
|
637
|
+
residuals: dict[str, tuple[Callable[..., Any], dict[str, Any], Sequence[str]]],
|
|
638
|
+
proj: LinearInputProjection,
|
|
639
|
+
) -> dict[str, tuple[Callable[..., Any], dict[str, Any], Sequence[str]]]:
|
|
640
|
+
r"""
|
|
641
|
+
Project residual functions onto a given input space.
|
|
642
|
+
"""
|
|
643
|
+
zeta_args_replaced = {"zeta_b_np1_vec", "zeta_b_dot_np1_vec", "zeta_b_n_vec"}
|
|
644
|
+
proj_names = tuple(proj.arg_names)
|
|
645
|
+
|
|
646
|
+
new_residuals: dict[
|
|
647
|
+
str, tuple[Callable[..., Any], dict[str, Any], Sequence[str]]
|
|
648
|
+
] = {}
|
|
649
|
+
for name, (func, args, diff_arg_names) in residuals.items():
|
|
650
|
+
if not any(k in args for k in zeta_args_replaced):
|
|
651
|
+
new_residuals[name] = (func, args, diff_arg_names)
|
|
652
|
+
continue
|
|
653
|
+
|
|
654
|
+
new_args = {k: v for k, v in args.items() if k not in zeta_args_replaced}
|
|
655
|
+
for aname, aref in zip(proj_names, proj.arg_refs, strict=True):
|
|
656
|
+
new_args[aname] = aref
|
|
657
|
+
|
|
658
|
+
new_diff = [k for k in diff_arg_names if k not in zeta_args_replaced]
|
|
659
|
+
for aname in proj_names:
|
|
660
|
+
if aname not in new_diff:
|
|
661
|
+
new_diff.append(aname)
|
|
662
|
+
|
|
663
|
+
def _wrap(
|
|
664
|
+
inner: Callable[..., Any],
|
|
665
|
+
needs_zeta_b_np1: bool = "zeta_b_np1_vec" in args,
|
|
666
|
+
needs_zeta_b_dot_np1: bool = "zeta_b_dot_np1_vec" in args,
|
|
667
|
+
needs_zeta_b_n: bool = "zeta_b_n_vec" in args,
|
|
668
|
+
) -> Callable[..., Any]:
|
|
669
|
+
def wrapped(**kwargs: Any) -> Any:
|
|
670
|
+
proj_kwargs = {n: kwargs.pop(n) for n in proj_names if n in kwargs}
|
|
671
|
+
zb, zbd = proj.to_zeta(**proj_kwargs)
|
|
672
|
+
if needs_zeta_b_np1:
|
|
673
|
+
kwargs["zeta_b_np1_vec"] = zb
|
|
674
|
+
if needs_zeta_b_dot_np1:
|
|
675
|
+
kwargs["zeta_b_dot_np1_vec"] = zbd
|
|
676
|
+
if needs_zeta_b_n:
|
|
677
|
+
# previous-step bound geometry uses the same projection output
|
|
678
|
+
kwargs["zeta_b_n_vec"] = zb
|
|
679
|
+
return inner(**kwargs)
|
|
680
|
+
|
|
681
|
+
return wrapped
|
|
682
|
+
|
|
683
|
+
new_residuals[name] = (_wrap(func), new_args, new_diff)
|
|
684
|
+
|
|
685
|
+
return new_residuals
|
|
686
|
+
|
|
687
|
+
@staticmethod
|
|
688
|
+
def _apply_output_projection(
|
|
689
|
+
residuals: dict[str, tuple[Callable[..., Any], dict[str, Any], Sequence[str]]],
|
|
690
|
+
proj: LinearOutputProjection,
|
|
691
|
+
) -> dict[str, tuple[Callable[..., Any], dict[str, Any], Sequence[str]]]:
|
|
692
|
+
r"""
|
|
693
|
+
Project forcing outputs into a given output space.
|
|
694
|
+
"""
|
|
695
|
+
f_steady_entry = residuals.get("f_steady")
|
|
696
|
+
f_unsteady_entry = residuals.get("f_unsteady")
|
|
697
|
+
assert f_steady_entry is not None
|
|
698
|
+
|
|
699
|
+
f_steady_func, f_steady_args, f_steady_diff = f_steady_entry
|
|
700
|
+
f_unsteady_func: Callable[..., Any] | None
|
|
701
|
+
if f_unsteady_entry is not None:
|
|
702
|
+
f_unsteady_func, f_unsteady_args, f_unsteady_diff = f_unsteady_entry
|
|
703
|
+
else:
|
|
704
|
+
f_unsteady_func = None
|
|
705
|
+
f_unsteady_args = {}
|
|
706
|
+
f_unsteady_diff = []
|
|
707
|
+
|
|
708
|
+
combined_args = {**f_steady_args, **f_unsteady_args}
|
|
709
|
+
combined_diff_ordered: list[str] = []
|
|
710
|
+
for name in list(f_steady_diff) + list(f_unsteady_diff):
|
|
711
|
+
if name not in combined_diff_ordered:
|
|
712
|
+
combined_diff_ordered.append(name)
|
|
713
|
+
|
|
714
|
+
f_steady_kwargs_names = set(f_steady_args.keys())
|
|
715
|
+
f_unsteady_kwargs_names = set(f_unsteady_args.keys())
|
|
716
|
+
|
|
717
|
+
def combined(
|
|
718
|
+
f_unsteady_func_: Callable[..., Any] | None = f_unsteady_func,
|
|
719
|
+
**kwargs: Any,
|
|
720
|
+
) -> Array:
|
|
721
|
+
f_steady_kwargs = {
|
|
722
|
+
k: v for k, v in kwargs.items() if k in f_steady_kwargs_names
|
|
723
|
+
}
|
|
724
|
+
f_steady = f_steady_func(**f_steady_kwargs)
|
|
725
|
+
if f_unsteady_func_ is not None:
|
|
726
|
+
f_unsteady_kwargs = {
|
|
727
|
+
k: v for k, v in kwargs.items() if k in f_unsteady_kwargs_names
|
|
728
|
+
}
|
|
729
|
+
f_unsteady = f_unsteady_func_(**f_unsteady_kwargs)
|
|
730
|
+
else:
|
|
731
|
+
f_unsteady = None
|
|
732
|
+
return proj.to_output(f_steady, f_unsteady)
|
|
733
|
+
|
|
734
|
+
new_residuals = {
|
|
735
|
+
k: v for k, v in residuals.items() if k not in ("f_steady", "f_unsteady")
|
|
736
|
+
}
|
|
737
|
+
new_residuals[proj.name] = (combined, combined_args, combined_diff_ordered)
|
|
738
|
+
|
|
739
|
+
return new_residuals
|
|
740
|
+
|
|
741
|
+
def create_jacobians(
|
|
742
|
+
self,
|
|
743
|
+
mode: ADMode | dict[str, ADMode] = "reverse",
|
|
744
|
+
batch_size: int | None = None,
|
|
745
|
+
input_projection: LinearInputProjection | None = None,
|
|
746
|
+
output_projection: LinearOutputProjection | None = None,
|
|
747
|
+
) -> dict[str, dict[str, Array]]:
|
|
748
|
+
r"""
|
|
749
|
+
Compute the per-residual Jacobians using :func:`jacrev_custom`.
|
|
750
|
+
"""
|
|
751
|
+
residuals = self.compute_jacobians(
|
|
752
|
+
input_projection=input_projection,
|
|
753
|
+
output_projection=output_projection,
|
|
754
|
+
)
|
|
755
|
+
|
|
756
|
+
jacobians: dict[str, dict[str, Array]] = {}
|
|
757
|
+
for res_name, (res_func, args, diff_arg_names) in residuals.items():
|
|
758
|
+
res_jac_options: dict[str, Callable[..., Array] | None] = {
|
|
759
|
+
arg: None for arg in diff_arg_names
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
res_mode: ADMode = (
|
|
763
|
+
mode.get(res_name, "reverse") if isinstance(mode, dict) else mode
|
|
764
|
+
)
|
|
765
|
+
|
|
766
|
+
jacs, _, _ = jacrev_custom(
|
|
767
|
+
func=res_func,
|
|
768
|
+
jac_options=res_jac_options,
|
|
769
|
+
n_profile_loops=None,
|
|
770
|
+
func_name=res_name,
|
|
771
|
+
map_batch_size=batch_size,
|
|
772
|
+
mode=res_mode,
|
|
773
|
+
)(**args)
|
|
774
|
+
jacobians[res_name] = jacs
|
|
775
|
+
|
|
776
|
+
return jacobians
|
|
777
|
+
|
|
778
|
+
def linearise(
|
|
779
|
+
self,
|
|
780
|
+
batch_size: int | None = None,
|
|
781
|
+
*,
|
|
782
|
+
input_projection: LinearInputProjection | None = None,
|
|
783
|
+
output_projection: LinearOutputProjection | None = None,
|
|
784
|
+
) -> LinearSystem:
|
|
785
|
+
r"""
|
|
786
|
+
Build the linear state-space system.
|
|
787
|
+
:param batch_size: If not None, batch the Jacobian passes to reduce memory.
|
|
788
|
+
:param input_projection: Optional projection from lower-dim inputs (e.g. beam DOFs) to
|
|
789
|
+
``(zeta_b, zeta_b_dot)``.
|
|
790
|
+
:param output_projection: Optional projection from ``(f_steady, f_unsteady)`` to a
|
|
791
|
+
smaller output (e.g. beam force); when set, C/D rows correspond to the projected output.
|
|
792
|
+
:return: LinearSystem object.
|
|
793
|
+
"""
|
|
794
|
+
jacobians = self.create_jacobians(
|
|
795
|
+
mode="reverse",
|
|
796
|
+
batch_size=batch_size,
|
|
797
|
+
input_projection=input_projection,
|
|
798
|
+
output_projection=output_projection,
|
|
799
|
+
)
|
|
800
|
+
|
|
801
|
+
# (row, column, size)
|
|
802
|
+
ref = self.reference
|
|
803
|
+
state_specs: list[tuple[str, str, int]] = [
|
|
804
|
+
("gamma_b", "gamma_b_n_vec", ref.gamma_b.size),
|
|
805
|
+
("gamma_w", "gamma_w_n_vec", ref.gamma_w.size),
|
|
806
|
+
]
|
|
807
|
+
if self.unsteady_force:
|
|
808
|
+
state_specs.append(("gamma_b_nm1", "gamma_b_nm1_vec", ref.gamma_b.size))
|
|
809
|
+
if self.prescribed_wake:
|
|
810
|
+
state_specs.append(("zeta_w", "zeta_w_n_vec", ref.zeta_w.size))
|
|
811
|
+
state_specs.append(("zeta_b", "zeta_b_n_vec", ref.zeta_b.size))
|
|
812
|
+
state_names = [s[0] for s in state_specs]
|
|
813
|
+
state_arg_names = [s[1] for s in state_specs]
|
|
814
|
+
state_sizes = [s[2] for s in state_specs]
|
|
815
|
+
|
|
816
|
+
if input_projection is None:
|
|
817
|
+
input_specs: list[tuple[str, int]] = [
|
|
818
|
+
("zeta_b_np1_vec", ref.zeta_b.size),
|
|
819
|
+
("zeta_b_dot_np1_vec", ref.zeta_b.size),
|
|
820
|
+
]
|
|
821
|
+
else:
|
|
822
|
+
input_specs = list(
|
|
823
|
+
zip(input_projection.arg_names, input_projection.arg_sizes)
|
|
824
|
+
)
|
|
825
|
+
if self.bound_upwash:
|
|
826
|
+
input_specs.append(("nu_b_np1_vec", ref.zeta_b.size))
|
|
827
|
+
if self.wake_upwash:
|
|
828
|
+
input_specs.append(("nu_w_np1_vec", ref.zeta_w.size))
|
|
829
|
+
input_arg_names = [s[0] for s in input_specs]
|
|
830
|
+
input_sizes = [s[1] for s in input_specs]
|
|
831
|
+
|
|
832
|
+
if output_projection is None:
|
|
833
|
+
n_fs = sum(3 * (m + 1) * (n + 1) for (m, n) in ref.gamma_b.shape)
|
|
834
|
+
output_specs: list[tuple[str, int]] = [("f_steady", n_fs)]
|
|
835
|
+
if self.unsteady_force:
|
|
836
|
+
output_specs.append(("f_unsteady", n_fs))
|
|
837
|
+
else:
|
|
838
|
+
output_specs = [(output_projection.name, output_projection.size)]
|
|
839
|
+
output_names = [s[0] for s in output_specs]
|
|
840
|
+
output_sizes = [s[1] for s in output_specs]
|
|
841
|
+
|
|
842
|
+
a = construct_named_block_jacobian(
|
|
843
|
+
entries=tuple(jacobians[k] for k in state_names),
|
|
844
|
+
keys=state_arg_names,
|
|
845
|
+
widths=state_sizes,
|
|
846
|
+
heights=state_sizes,
|
|
847
|
+
)
|
|
848
|
+
b = construct_named_block_jacobian(
|
|
849
|
+
entries=tuple(jacobians[k] for k in state_names),
|
|
850
|
+
keys=input_arg_names,
|
|
851
|
+
widths=input_sizes,
|
|
852
|
+
heights=state_sizes,
|
|
853
|
+
)
|
|
854
|
+
c = construct_named_block_jacobian(
|
|
855
|
+
entries=tuple(jacobians[k] for k in output_names),
|
|
856
|
+
keys=state_arg_names,
|
|
857
|
+
widths=state_sizes,
|
|
858
|
+
heights=output_sizes,
|
|
859
|
+
)
|
|
860
|
+
d = construct_named_block_jacobian(
|
|
861
|
+
entries=tuple(jacobians[k] for k in output_names),
|
|
862
|
+
keys=input_arg_names,
|
|
863
|
+
widths=input_sizes,
|
|
864
|
+
heights=output_sizes,
|
|
865
|
+
)
|
|
424
866
|
|
|
425
867
|
return LinearSystem(a=a, b=b, c=c, d=d, dt=self.dt)
|
|
426
868
|
|